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>
83 lines
3 KiB
TypeScript
83 lines
3 KiB
TypeScript
/**
|
||
* 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);
|
||
});
|