Task 8 of the AI order assistant plan.
- new AiQuoteMatchPanel: "Linked by AI" / "AI suggests Q…", the
reason, the doubts and the line-pair table (order line -> quote line
with same_item)
- the quote match card gains a "Match with AI" button in every state,
posts to /api/v1/orders/{id}/assistant/run with the PB token, toasts
the 409 message verbatim, then reloads the order
- new card state: the AI suggested a quote -> the AI panel + Accept
(links the AI's quote) / Match with AI / Dismiss
- the AI suggestion marks the order as quote_matching_attempted so the
deterministic sweep cannot overwrite it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Task 6 of the AI order assistant plan.
- matchOrder(orderId): load order + lines, isOrderEligible, shortlist
from computeQuoteSuggestion (best + alternatives, first 5), empty
shortlist writes ai_no_match with no AI call, otherwise ask the AI in
batches of 30 lines against the same shortlist
- auto_link only through setOrderQuote inside
runWithAuditActor({ name: "AI order assistant" }); a 409 falls back to
a suggestion, never an error
- suggest writes potential_quote + potential_quote_confidence
(0.9 / 0.7 / 0.5) + quote_match_method "ai_suggest"
- always writes orders.ai_quote_match (lines_hash, model, run_at); an
AI failure is recorded with status "error" and never thrown out
- runOrderAssistantTick: same due/lease/stamp logic as the quote
assistant, oldest eligible order first, max 10 per tick
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>