WFDASH-WEBAPP/scripts/smoke/populate-quote-item-insights.ts
SJCoetz 63048e51c9 feat: add AI quote processing pipeline and admin settings UI
Add complete end-to-end AI-powered quote extraction workflow:
- Disable PB SDK auto-cancellation to support parallel batch requests
- Add required runtime dependencies for AI processing and document parsing
- Implement AI settings backend, operator UI, and REST API routes
- Add quote ingestion, insight population, and manual re-run AI endpoints
- Build service layers for quote items, AI settings, and structured item insights
- Fix attachment upload handling and quote item history blob parsing
- Add supporting migrations, smoke tests, and shared utility functions
- Export previously private helper functions for cross-service reuse
2026-08-09 16:17:29 +02:00

98 lines
3.4 KiB
TypeScript

/**
* Smoke test for the quote-item insights backfill.
*
* What it does
* ────────────
* 1. Captures the existing `ai_item_insights` blob (so we can restore).
* 2. Calls `populateQuoteItemInsights` on the target id, write=true.
* 3. Re-reads the row and prints the new blob.
* 4. Restores the original blob (best-effort) so the test is non-destructive.
*
* Run with:
* npx tsx scripts/smoke/populate-quote-item-insights.ts
*
* Requires .env.local with NEXT_PUBLIC_POCKETBASE_URL, PB_ADMIN_EMAIL,
* PB_ADMIN_PASSWORD.
*/
import "./_env"; // MUST be first
import { populateQuoteItemInsights } from "../../src/lib/server/quote-items-insights-service";
import { getAdminPocketBase } from "../../src/lib/server/pocketbase-admin";
// Edit this id to point at a different item.
const TARGET_ITEM_ID = process.env.TARGET_ITEM_ID || "6zvr769y6210qe0";
async function main() {
console.log(`=== Populate insights for quote_item ${TARGET_ITEM_ID} ===\n`);
const pb = await getAdminPocketBase();
// 1. Capture original blob for restore.
const original = await pb.collection("quote_items").getOne<{
id: string;
sku: string;
description: string;
ai_item_insights: unknown;
}>(TARGET_ITEM_ID, { fields: "id,sku,description,ai_item_insights" });
console.log(`Target: ${original.description}`);
console.log(`SKU: "${original.sku}"`);
console.log(
`Existing ai_item_insights (truncated):`,
JSON.stringify(original.ai_item_insights).slice(0, 200),
);
console.log();
// 2. Run the populate.
const t0 = Date.now();
const result = await populateQuoteItemInsights(TARGET_ITEM_ID, { write: true });
const elapsed = Date.now() - t0;
console.log(`\nDone in ${elapsed}ms.`);
console.log(`match_kind: ${result.match_kind}`);
console.log(`found_data: ${result.found_data}`);
console.log();
console.log(`summary: ${result.insights.summary}`);
console.log();
// 3. Re-read to confirm persistence.
const reread = await pb.collection("quote_items").getOne<{
ai_item_insights: { summary?: string; generated_at?: string };
updated: string;
}>(TARGET_ITEM_ID, { fields: "ai_item_insights,updated" });
console.log(
`Persisted? ${JSON.stringify(reread.ai_item_insights?.summary) === JSON.stringify(result.insights.summary) ? "YES" : "NO"}`,
);
console.log(`Updated at: ${reread.updated}`);
console.log();
// 4. Pretty-print the blob so the operator can sanity-check it.
console.log("=== Persisted blob ===");
console.log(JSON.stringify(reread.ai_item_insights, null, 2));
// 5. Restore the original blob UNLESS the operator asked us to keep
// the populated version. Set KEEP_POPULATED=1 to leave the new
// blob in place — that's the default for the user's request to
// "try and fill one in". Unset to run safely as a smoke.
if (!process.env.KEEP_POPULATED) {
try {
await pb.collection("quote_items").update(TARGET_ITEM_ID, {
ai_item_insights: original.ai_item_insights ?? null,
});
console.log("\n[cleanup] Original blob restored (KEEP_POPULATED not set).");
} catch (err) {
console.warn(
"\n[cleanup] Failed to restore original blob:",
err instanceof Error ? err.message : String(err),
);
}
} else {
console.log("\n[kept] KEEP_POPULATED=1 — populated blob stays in PB.");
}
}
main().catch((err) => {
console.error("\nFAILED:", err);
process.exit(1);
});