feat: add support for Claude LLM integration across multiple modules

- Introduced `useClaude` option in `AnalysisPipelineOptions` to toggle Claude LLM usage.
- Updated `processProductChunk` and `analyzeProducts` functions to accept and handle `useClaude` parameter.
- Modified argument parsing in various scripts (`bestsellers-by-category`, `mid-range-sellers-by-category`, `top-monthly-sold-by-category`, etc.) to include `--claude` flag.
- Enhanced `analyzeProductsInternal` to differentiate between LLM providers and handle requests to Claude API.
- Added error handling for Claude API responses and ensured proper configuration for using Claude.
- Updated documentation and usage messages to reflect the new `--claude` flag.
This commit is contained in:
Victor Noguera
2026-05-21 19:57:46 -04:00
parent 0f256be2be
commit 95cebaa27c
12 changed files with 423 additions and 144 deletions

View File

@@ -103,6 +103,7 @@ const DEFAULT_PAGE_SIZE = 25;
const MAX_PAGE_SIZE = 200;
const ASIN_PATTERN = /^[A-Z0-9]{10}$/;
const MAX_UPCS_PER_REQUEST = 1000;
const USE_CLAUDE = process.argv.includes("--claude");
initDb(DB_PATH);
const db = getDb(DB_PATH);
@@ -128,7 +129,8 @@ function xlsx(buffer: ArrayBuffer, filename: string): Response {
return new Response(buffer, {
status: 200,
headers: {
"content-type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"content-type":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"content-disposition": `attachment; filename="${filename}"`,
},
});
@@ -758,7 +760,10 @@ function parseStalkerSort(sortParam: string | null): string {
return parsed
.replaceAll("runId", "runId")
.replaceAll("rating_count", "rating_count")
.replaceAll("persisted_inventory_asin_count", "persisted_inventory_asin_count")
.replaceAll(
"persisted_inventory_asin_count",
"persisted_inventory_asin_count",
)
.replaceAll("storefront_asin_total", "storefront_asin_total");
}
@@ -955,7 +960,11 @@ function parseStalkerProductSort(sortParam: string | null): string {
"confidence",
"last_seen_at",
]);
return parseSort(sortParam, allowedSort, "monthly_sold DESC, last_seen_at DESC, asin ASC");
return parseSort(
sortParam,
allowedSort,
"monthly_sold DESC, last_seen_at DESC, asin ASC",
);
}
function getStalkerProducts(filters: URLSearchParams) {
@@ -1036,7 +1045,9 @@ function getStalkerProducts(filters: URLSearchParams) {
};
}
function getStalkerProductsForExport(filters: URLSearchParams): StalkerProductRecord[] {
function getStalkerProductsForExport(
filters: URLSearchParams,
): StalkerProductRecord[] {
const { where, params } = parseStalkerProductFilters(filters);
const orderBy = parseStalkerProductSort(filters.get("sort"));
@@ -1100,7 +1111,12 @@ function exportStalkerProductsXlsx(filters: URLSearchParams): Response {
Category: parseCategoryTreeForExport(row.category_tree),
"Monthly Sold": row.monthly_sold ?? null,
Sellers: row.seller_count ?? null,
"Amazon Seller": row.amazon_is_seller == null ? "" : row.amazon_is_seller === 1 ? "Yes" : "No",
"Amazon Seller":
row.amazon_is_seller == null
? ""
: row.amazon_is_seller === 1
? "Yes"
: "No",
"Sales Rank": row.sales_rank ?? null,
"Current Price": row.current_price ?? null,
"Avg 90d": row.avg_price_90d ?? null,
@@ -1155,11 +1171,31 @@ function exportStalkerProductsXlsx(filters: URLSearchParams): Response {
function purgeStalkerData() {
const counts = {
inventory: (db.query("SELECT COUNT(*) AS count FROM stalker_seller_inventory").get() as { count: number }).count,
asinSellers: (db.query("SELECT COUNT(*) AS count FROM stalker_asin_sellers").get() as { count: number }).count,
sellers: (db.query("SELECT COUNT(*) AS count FROM stalker_sellers").get() as { count: number }).count,
scans: (db.query("SELECT COUNT(*) AS count FROM stalker_asin_scans").get() as { count: number }).count,
runs: (db.query("SELECT COUNT(*) AS count FROM stalker_runs").get() as { count: number }).count,
inventory: (
db
.query("SELECT COUNT(*) AS count FROM stalker_seller_inventory")
.get() as { count: number }
).count,
asinSellers: (
db.query("SELECT COUNT(*) AS count FROM stalker_asin_sellers").get() as {
count: number;
}
).count,
sellers: (
db.query("SELECT COUNT(*) AS count FROM stalker_sellers").get() as {
count: number;
}
).count,
scans: (
db.query("SELECT COUNT(*) AS count FROM stalker_asin_scans").get() as {
count: number;
}
).count,
runs: (
db.query("SELECT COUNT(*) AS count FROM stalker_runs").get() as {
count: number;
}
).count,
};
db.transaction(() => {
@@ -1683,7 +1719,9 @@ async function reanalyzeSingleAsin(
fetchedAt: new Date().toISOString(),
};
const verdicts = await analyzeProducts([enriched]);
const verdicts = await analyzeProducts([enriched], {
useClaude: USE_CLAUDE,
});
const verdict = verdicts[0] ?? {
asin,
verdict: "SKIP" as const,