Refactor supplier analysis and product handling

- Updated `SupplierAnalysisResult` to include a `product` field and modified related tests.
- Refactored `addRowsSheet` to accommodate changes in the product structure.
- Enhanced UPC file analysis to utilize a new `toSupplierInputRecord` function for cleaner record creation.
- Introduced new types for supplier input records and product observations.
- Updated frontend components to handle new product details and analysis history.
- Improved database writing functions to streamline run completion and error handling.
- Added new API endpoints for product details and adjusted routing in the frontend.
This commit is contained in:
Victor Noguera
2026-05-25 12:27:41 -04:00
parent c006d87c54
commit 923ebbaec5
33 changed files with 2536 additions and 4872 deletions

View File

@@ -1,5 +1,6 @@
import { config } from "../config.ts";
import type { KeepaData, KeepaUpcLookupDetail } from "../types.ts";
import { config } from "../config.ts";
import { normalizeAsin } from "../asin.ts";
import type { KeepaData, KeepaUpcLookupDetail } from "../types.ts";
const KEEPA_BASE = "https://api.keepa.com";
const MAX_ASINS_PER_REQUEST = 100;
@@ -224,14 +225,21 @@ function buildFailureDetail(
};
}
export async function fetchKeepaDataBatch(
asins: string[],
): Promise<Map<string, KeepaData>> {
const results = new Map<string, KeepaData>();
// Split into chunks of MAX_ASINS_PER_REQUEST
for (let i = 0; i < asins.length; i += MAX_ASINS_PER_REQUEST) {
const chunk = asins.slice(i, i + MAX_ASINS_PER_REQUEST);
export async function fetchKeepaDataBatch(
asins: string[],
): Promise<Map<string, KeepaData>> {
const results = new Map<string, KeepaData>();
const canonicalAsins = Array.from(
new Set(
asins
.map((asin) => normalizeAsin(asin))
.filter((asin): asin is string => asin !== null),
),
);
// Split into chunks of MAX_ASINS_PER_REQUEST
for (let i = 0; i < canonicalAsins.length; i += MAX_ASINS_PER_REQUEST) {
const chunk = canonicalAsins.slice(i, i + MAX_ASINS_PER_REQUEST);
const url = buildProductUrl("asin", chunk, {
includeStats: true,
includeBuybox: true,
@@ -248,11 +256,11 @@ export async function fetchKeepaDataBatch(
`Keepa: ${data.products?.length ?? 0} products returned, ${tokensLeft} tokens remaining (refill: ${refillRate}/min)`,
);
if (data.products) {
for (const product of data.products) {
const asin = product.asin;
if (!asin) continue;
results.set(asin, parseKeepaProduct(product));
if (data.products) {
for (const product of data.products) {
const asin = normalizeAsin(product.asin);
if (!asin) continue;
results.set(asin, parseKeepaProduct(product));
}
}
}
@@ -307,10 +315,10 @@ export async function lookupKeepaUpcs(
`Keepa: ${data.products?.length ?? 0} products returned for UPC query, ${tokensLeft} tokens remaining (refill: ${refillRate}/min)`,
);
const byUpc = new Map<string, Map<string, KeepaData>>();
for (const product of data.products ?? []) {
const asin = String(product.asin ?? "").trim();
if (!asin) continue;
const byUpc = new Map<string, Map<string, KeepaData>>();
for (const product of data.products ?? []) {
const asin = normalizeAsin(product.asin);
if (!asin) continue;
const keepaData = parseKeepaProduct(product);
const productUpcs = extractUpcsFromProduct(product);