From 506e2344b711eb3af5ec61e5469dd67a97861c4a Mon Sep 17 00:00:00 2001 From: Victor Noguera Date: Mon, 25 May 2026 15:57:24 -0400 Subject: [PATCH] feat: implement reanalyze and distributor discovery endpoints for Stalker products by ASIN --- .abacusai/config.json | 2 ++ src/server.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ src/web/frontend.tsx | 33 ++++++++++++++------------ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/.abacusai/config.json b/.abacusai/config.json index e784e4a..8c27c51 100644 --- a/.abacusai/config.json +++ b/.abacusai/config.json @@ -25,6 +25,8 @@ "Bash(git --no-pager diff -- src/web/frontend.tsx src/web/styles.css 2>&1 || true)", "Bash(bun run build:web 2>&1 || true)", "Bash(bun run build:web 2>&1 || true)", + "Bash(bun run build:web 2>&1 || true)", + "Bash(bun run build:web 2>&1 || true)", "Bash(bun run build:web 2>&1 || true)" ] }, diff --git a/src/server.ts b/src/server.ts index 601cd8d..aa11eb6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -531,6 +531,36 @@ async function getProduct(asin: string) { return { product, observations, analyses, distributorResearch }; } +async function findLatestStalkerRunItemIdByAsin(asin: string): Promise { + const row = await pgGet<{ id: number }>( + `SELECT ri.id + FROM run_items ri + JOIN runs run ON run.id = ri.run_id + WHERE ri.product_asin = ? + AND run.type = 'stalker' + ORDER BY ri.id DESC + LIMIT 1`, + [asin], + ); + return row?.id == null ? null : Number(row.id); +} + +async function reanalyzeStalkerProductByAsin(asin: string, useClaude = USE_CLAUDE) { + const runItemId = await findLatestStalkerRunItemIdByAsin(asin); + if (runItemId == null) { + throw new Error("Stalker product item not found"); + } + return reanalyzeRunItem(runItemId, useClaude); +} + +async function findDistributorsForStalkerProductByAsin(asin: string) { + const runItemId = await findLatestStalkerRunItemIdByAsin(asin); + if (runItemId == null) { + throw new Error("Stalker product item not found"); + } + return findDistributorsForStalkerProduct(runItemId); +} + async function reanalyzeRunItem(itemId: number, useClaude = USE_CLAUDE) { const row = await pgGet>( `SELECT ri.id, ri.run_id, ri.product_asin AS asin, r.type, @@ -1159,6 +1189,30 @@ const server = Bun.serve({ return json({ error: message }, message === "Stalker product item not found" ? 404 : 500); } }, + "/api/stalker/products/by-asin/:asin/reanalyze": async (req) => { + if (req.method !== "POST") return json({ error: "Method not allowed" }, 405); + const asin = normalizeAsin(req.params.asin); + if (!asin) return json({ error: "Invalid ASIN" }, 400); + const provider = new URL(req.url).searchParams.get("provider")?.trim().toLowerCase(); + const useClaude = provider === "claude"; + try { + return json(await reanalyzeStalkerProductByAsin(asin, useClaude || USE_CLAUDE)); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return json({ error: message }, message === "Stalker product item not found" ? 404 : 500); + } + }, + "/api/stalker/products/by-asin/:asin/distributors": async (req) => { + if (req.method !== "POST") return json({ error: "Method not allowed" }, 405); + const asin = normalizeAsin(req.params.asin); + if (!asin) return json({ error: "Invalid ASIN" }, 400); + try { + return json(await findDistributorsForStalkerProductByAsin(asin)); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return json({ error: message }, message === "Stalker product item not found" ? 404 : 500); + } + }, "/api/stalker/purge": async (req) => req.method === "DELETE" || req.method === "POST" ? json(await purgeStalkerData()) diff --git a/src/web/frontend.tsx b/src/web/frontend.tsx index bf125e5..d2004e0 100644 --- a/src/web/frontend.tsx +++ b/src/web/frontend.tsx @@ -1514,10 +1514,13 @@ function ProductDetails({ }, [asin]); async function reanalyze() { - if (effectiveRunItemId == null || reanalyzing) return; + if (reanalyzing) return; setReanalyzing(true); try { - const res = await fetch(`/api/stalker/products/${effectiveRunItemId}/reanalyze?provider=claude`, { method: "POST" }); + const endpoint = effectiveRunItemId == null + ? `/api/stalker/products/by-asin/${encodeURIComponent(asin)}/reanalyze?provider=claude` + : `/api/stalker/products/${effectiveRunItemId}/reanalyze?provider=claude`; + const res = await fetch(endpoint, { method: "POST" }); if (!res.ok) { const body = (await res.json().catch(() => null)) as { error?: string } | null; window.alert(body?.error ?? "Failed to re-run analysis"); @@ -1530,16 +1533,18 @@ function ProductDetails({ } async function discoverDistributors() { - if (effectiveRunItemId == null || findingDistributors) return; + if (findingDistributors) return; setFindingDistributors(true); try { - const res = await fetch(`/api/stalker/products/${effectiveRunItemId}/distributors`, { method: "POST" }); + const endpoint = effectiveRunItemId == null + ? `/api/stalker/products/by-asin/${encodeURIComponent(asin)}/distributors` + : `/api/stalker/products/${effectiveRunItemId}/distributors`; + const res = await fetch(endpoint, { method: "POST" }); if (!res.ok) { const body = (await res.json().catch(() => null)) as { error?: string } | null; window.alert(body?.error ?? "Failed to find distributors"); } } catch { - // network error or timeout — job may have completed on the server anyway } finally { load(); setFindingDistributors(false); @@ -1552,16 +1557,14 @@ function ProductDetails({

{data?.product.name ?? asin}

- {effectiveRunItemId != null && ( -
- - -
- )} +
+ + +
ASIN: {asin}