WFDASH-WEBAPP/scripts/2026-10-24-clear-rule-quote-suggestions.ts
Dave 37933ccdc0 feat(orders): quote matching is AI-only
The rule-based matcher no longer writes or shows suggestions: on
2026-10-24 it had left 2,888 on unlinked orders, one quote suggested
to 309 of them.

- createOrder runs the AI order assistant (matchOrder) straight after
  intake when it is switched on, instead of the rule-based suggestion
- order page: Re-match / Try again and the rule-based suggestion card
  are gone; Match with AI is the action, and "Match by reference" saves
  the reference then runs the AI (the run route takes { reference })
- the unauthenticated /orders/[id]/quote-match route is removed
- the sweep route and MCP orders.quote_match_sweep run the AI batch
  (matchUnlinkedOrders, shared with the tick)
- scripts/2026-10-24-clear-rule-quote-suggestions.ts clears the old
  suggestions (dry run unless --apply); replaces the rematch script,
  which would have written rule-based ones again
- also fixes the setState-in-effect lint error on the order page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:49:26 +02:00

83 lines
3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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);
});