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,4 +1,5 @@
import path from "node:path";
import { requireAsin } from "../asin.ts";
import { fetchKeepaDataBatch, lookupKeepaUpcs } from "../integrations/keepa.ts";
import {
fetchSellabilityBatch,
@@ -11,6 +12,8 @@ import {
} from "./upc-file-reader.ts";
import {
appendSupplierResultsToRun,
completeRunInDb,
failRunInDb,
refreshRunCountsInDb,
startRunInDb,
type RunCounts,
@@ -239,8 +242,8 @@ async function lookupUpcsWithChunking(
chunkDetails.set(
upc,
fallbackDetail && fallbackDetail.status !== "request_failed"
? fallbackDetail
: spDetail!,
? { ...fallbackDetail, provider: "keepa" }
: { ...spDetail!, provider: "sp_api" },
);
}
@@ -266,7 +269,7 @@ function toProductRecord(
const keepaCategory = detail.keepaData?.categoryTree?.[0];
return {
asin: detail.asin ?? row.upc,
asin: requireAsin(detail.asin),
name: row.name ?? detail.asin ?? row.upc,
unitCost: row.unitCost ?? 0,
brand: row.brand,
@@ -274,6 +277,15 @@ function toProductRecord(
};
}
function toSupplierInputRecord(row: UpcInputRow) {
return {
name: row.name ?? row.upc,
unitCost: row.unitCost ?? 0,
brand: row.brand,
category: row.category,
};
}
async function fetchFeesForProducts(
products: ProductRecord[],
keepaResults: Map<string, NonNullable<SupplierAnalysisResult["keepa"]>>,
@@ -359,7 +371,7 @@ export async function runUpcFileAnalysis(
let processedRows = 0;
let matchedRows = 0;
const runId = await startRunInDb(options.inputFile, outputFile);
const runId = await startRunInDb(options.inputFile, outputFile, undefined, "supplier_upc");
try {
const readerSummary = await processUpcFileInBatches(
@@ -382,12 +394,19 @@ export async function runUpcFileAnalysis(
product: ProductRecord;
}> = [];
for (const row of rows) {
const detail = detailMap.get(row.upc);
if (!detail) {
unresolvedByStatus.request_failed += 1;
continue;
}
const detail =
detailMap.get(row.upc) ??
({
requestedUpc: row.upc,
normalizedUpc: row.upc,
status: "request_failed",
asin: null,
candidateAsins: [],
keepaData: null,
provider: "sp_api",
reason: "UPC lookup returned no result",
} satisfies UpcLookupDetail);
if (!detailMap.has(row.upc)) detailMap.set(row.upc, detail);
unresolvedByStatus[detail.status] += 1;
if (detail.status === "found" && detail.asin) {
@@ -407,30 +426,15 @@ export async function runUpcFileAnalysis(
const batchResults: SupplierAnalysisResult[] = [];
for (const row of rows) {
const detail = detailMap.get(row.upc);
if (!detail || detail.status === "found") continue;
const detail = detailMap.get(row.upc)!;
if (detail.status === "found") continue;
batchResults.push({
upc: row.upc,
rowNumber: row.rowNumber,
record: {
asin: detail?.asin ?? row.upc,
name: row.name ?? row.upc,
unitCost: row.unitCost ?? 0,
brand: row.brand,
category: row.category,
},
lookup:
detail ??
({
requestedUpc: row.upc,
normalizedUpc: row.upc,
status: "request_failed",
asin: null,
candidateAsins: [],
keepaData: null,
reason: "UPC lookup returned no result",
} satisfies UpcLookupDetail),
record: toSupplierInputRecord(row),
product: null,
lookup: detail,
keepa: null,
spApi: null,
score: skippedScore(detail?.reason ?? "UPC unresolved"),
@@ -465,7 +469,8 @@ export async function runUpcFileAnalysis(
batchResults.push({
upc: entry.detail.normalizedUpc,
rowNumber: entry.row.rowNumber,
record: entry.product,
record: toSupplierInputRecord(entry.row),
product: entry.product,
lookup: entry.detail,
keepa,
spApi,
@@ -488,6 +493,7 @@ export async function runUpcFileAnalysis(
const exportSummary = summarizeSupplierResults(allResults, unresolvedByStatus);
await writeSupplierWorkbook(outputFile, allResults, exportSummary);
await completeRunInDb(runId);
if (allResults.length > 0) {
const ranked = allResults
@@ -530,6 +536,9 @@ export async function runUpcFileAnalysis(
skippedInvalidUpc: readerSummary.skippedInvalidUpc,
},
};
} catch (error) {
await failRunInDb(runId, error);
throw error;
} finally {
if (manageResources) {
await disconnectCache();