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
98 lines
3.4 KiB
TypeScript
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);
|
|
});
|