Refactor mid-range seller processing to enforce sellability gates and enhance command-line arguments

- Updated test case to reflect changes in processing mid-range matches based on sellability.
- Modified `processCategory` function to implement strict and soft sellability gates.
- Introduced new command-line arguments for category selection and sellability gate configuration.
- Enhanced error handling and validation for new arguments.
- Improved logging for category processing and budget usage.
This commit is contained in:
Victor Noguera
2026-05-12 14:14:20 -04:00
parent f2c8a9728d
commit 41ef57a7bc
5 changed files with 681 additions and 404 deletions

View File

@@ -14,11 +14,14 @@ import type {
export const DEFAULT_LLM_BATCH_SIZE = 5;
export const DEFAULT_PRICING_CONCURRENCY = 5;
export type SellabilityFilter = "available" | "all";
export type AnalysisPipelineOptions = {
llmBatchSize?: number;
pricingConcurrency?: number;
llmBatchDelayMs?: number;
llmRetryDelayMs?: number;
sellability?: SellabilityFilter;
};
export function chunkArray<T>(items: T[], chunkSize: number): T[][] {
@@ -56,6 +59,7 @@ export async function processProductChunk(
);
const llmBatchDelayMs = Math.max(0, options.llmBatchDelayMs ?? 5_000);
const llmRetryDelayMs = Math.max(0, options.llmRetryDelayMs ?? 10_000);
const sellabilityFilter = options.sellability ?? "available";
console.log(`\nChecking cache for ${products.length} products...`);
const cached = new Map<string, EnrichedProduct>();
@@ -65,7 +69,10 @@ export async function processProductChunk(
for (const p of products) {
const hit = await getCache(p.asin);
if (hit) {
if (hit.spApi.sellabilityStatus === "available") {
if (
sellabilityFilter === "all" ||
hit.spApi.sellabilityStatus === "available"
) {
console.log(` [cache hit] ${p.asin}`);
cached.set(p.asin, hit);
} else {
@@ -103,7 +110,10 @@ export async function processProductChunk(
};
sellabilityMap.set(p.asin, info);
if (info.sellabilityStatus === "available") {
if (
sellabilityFilter === "all" ||
info.sellabilityStatus === "available"
) {
availableProducts.push(p);
console.log(
` [available] ${p.asin} - status=${info.sellabilityStatus}`,
@@ -116,9 +126,15 @@ export async function processProductChunk(
}
}
console.log(
`\nSellability gate: ${availableProducts.length} available, ${unavailableProducts.length} excluded`,
);
if (sellabilityFilter === "all") {
console.log(
`\nSellability gate disabled: including all ${availableProducts.length} products`,
);
} else {
console.log(
`\nSellability gate: ${availableProducts.length} available, ${unavailableProducts.length} excluded`,
);
}
}
let keepaResults = new Map<string, KeepaData>();
@@ -224,13 +240,17 @@ export async function processProductChunk(
let verdicts;
try {
verdicts = await analyzeProducts(batch);
verdicts = await analyzeProducts(batch, {
ignoreSellability: sellabilityFilter === "all",
});
} catch {
if (llmRetryDelayMs > 0) {
await wait(llmRetryDelayMs);
}
try {
verdicts = await analyzeProducts(batch);
verdicts = await analyzeProducts(batch, {
ignoreSellability: sellabilityFilter === "all",
});
} catch {
verdicts = null;
}