WFDASH-WEBAPP/scripts/2026-10-24-clear-rule-quote-suggestions.ts

84 lines
3 KiB
TypeScript
Raw Normal View History

/**
* One-off clean-up when quote matching became AI-only (2026-10-24).
*
* The retired rule-based matcher left soft suggestions (`potential_quote`)
* on unlinked orders — many wrong: one quote was suggested to 16 unrelated
* orders because same company + customer scored 0.5 with no matching
* lines. This clears every suggestion that did NOT come from the AI order
* assistant (`quote_match_method` not starting with `ai_`), so the order
* page shows "Match with AI" instead of a stale guess.
*
* Hard links (`orders.quote`) are never touched. AI suggestions stay.
* Dry run unless `--apply`.
*
* npx tsx scripts/2026-10-24-clear-rule-quote-suggestions.ts [--apply]
*/
import "./_env";
import { getAdminPocketBase } from "../src/lib/server/pocketbase-admin";
import { runWithAuditActor } from "../src/lib/server/audit-context";
const APPLY = process.argv.includes("--apply");
const actor = { id: "claude-quote-suggestion-clear", name: "Claude (rule-based suggestion clean-up)" };
const FILTER = 'potential_quote != "" && quote = "" && quote_match_method !~ "ai_"';
interface Row {
id: string;
order_number?: string;
potential_quote?: string;
potential_quote_confidence?: number;
quote_match_method?: string;
}
async function main() {
const pb = await getAdminPocketBase();
pb.autoCancellation(false);
const rows = await pb.collection("orders").getFullList<Row>({
filter: FILTER,
fields: "id,order_number,potential_quote,potential_quote_confidence,quote_match_method",
sort: "-created",
batch: 500,
});
console.log(`${APPLY ? "APPLY" : "DRY "} ${rows.length} rule-based suggestion(s) on unlinked orders`);
const perQuote = new Map<string, number>();
const perMethod = new Map<string, number>();
for (const r of rows) {
perQuote.set(r.potential_quote || "", (perQuote.get(r.potential_quote || "") ?? 0) + 1);
perMethod.set(r.quote_match_method || "(none)", (perMethod.get(r.quote_match_method || "(none)") ?? 0) + 1);
}
console.log("\n── by method ──");
for (const [m, n] of [...perMethod].sort((a, b) => b[1] - a[1])) console.log(` ${n} × ${m}`);
console.log("\n── most-suggested quotes ──");
for (const [q, n] of [...perQuote].sort((a, b) => b[1] - a[1]).slice(0, 10)) console.log(` ${n} × ${q}`);
if (!APPLY) {
console.log("\nDry run — nothing was written. Re-run with --apply to clear them.");
return;
}
let cleared = 0;
let failed = 0;
for (const r of rows) {
try {
await pb.collection("orders").update(r.id, {
potential_quote: "",
potential_quote_confidence: 0,
quote_match_method: "",
quote_matching_attempted: false,
});
cleared++;
} catch (err) {
failed++;
console.error(`ERROR ${r.order_number || r.id}: ${err instanceof Error ? err.message : err}`);
}
}
console.log(`\ncleared ${cleared}, failed ${failed}`);
}
runWithAuditActor({ ...actor, source: "server", ignoreRequestUser: true }, main).catch((err) => {
console.error(err);
process.exit(1);
});