From 34d3422586155c377142746ada59bf766fdc68ba Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 24 Oct 2026 15:34:00 +0200 Subject: [PATCH] docs(orders): order assistant runbook final pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runbook now matches the code as built. - New section: filling the lines from the quote — which lines may be filled, what is written, the 5% price doubt, the purple review and how it ends, and the board badge. - The "Fill lines from the quote" switch is no longer described as reserved for a later phase, and the tick's bounds now say what the code does: 10 orders matched, 10 orders filled, and an order with nothing to do does not use up that budget. - Reading order_items.ai_fill is documented alongside orders.ai_quote_match. - Live steps confirmed against the code: the migration is pb:migrate:order-assistant, ai_quote_match and ai_fill are both in IGNORED_FIELDS, the n8n tick is POST /api/v1/orders/assistant/tick, and the settings labels are "Order assistant enabled", "Run every (minutes)", "Auto-link when sure" and "Fill lines from the quote". Co-Authored-By: Claude Opus 5.5 --- docs/order-assistant.md | 118 +++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 13 deletions(-) diff --git a/docs/order-assistant.md b/docs/order-assistant.md index 6307ea3..dc7d1b8 100644 --- a/docs/order-assistant.md +++ b/docs/order-assistant.md @@ -16,9 +16,13 @@ the candidate quotes the order came from, and then either: - **links** the order to the quote itself (when it is sure), or - **suggests** the quote, and a person accepts it. -The AI never touches prices, never changes a line's status, never creates a -purchase order and never sends anything. It only decides which quote an -order belongs to. +Once the order points at a quote, the assistant can also **fill the +order's lines** from that quote — the cost, the supplier, the SKU and the +part number — leaving them in **purple** until a person checks them. + +The AI never changes a line's status, never creates a purchase order and +never sends anything. It decides which quote an order belongs to, and it +may pre-fill a line's prices from that quote. ## The auto-link rule, in plain words @@ -43,6 +47,86 @@ when a line on the order has already moved past `pending`, or when another order already points at that quote. A refusal is **not** an error: the assistant falls back to a suggestion. +## Filling the lines from the quote + +With **"Fill lines from the quote"** on, the assistant prices the order's +lines from the quote they are paired with. This is a copy, not a new +price: no AI call is involved, the pairing is already on the lines. + +A line may be filled only when **all** of these are true: + +- It is still `pending`. +- It carries **no cost** of its own (`cost_price` is 0). +- Its `pricing_source` is not `manual` and not `quote` — a price a person + set is never overwritten. +- It has no **reviewed** AI fill on it. +- It is paired to a quote line (`quote_item` is set) that actually has a + cost. + +What the fill writes: + +| Field | Rule | +|---|---| +| `cost_price` | The quote line's cost | +| `selling_price` | Only when the order line has none of its own | +| `supplier`, `sku`, `matched_price_list_id` | Only when the order line has none of its own | +| `markup` + `markup_percent` | Written together, from cost and selling | +| `pricing_source` | `ai_quote` | +| `ai_fill` | The audit record of the fill (see below) | + +Every line is **re-read immediately before** it is written, so a cost a +person typed in the meantime wins over the AI's. + +If the customer's price and the quoted price both exist and differ by more +than **5%**, the fill records a doubt, e.g. `customer price R16.64, quoted +R18.20`. Doubts show on the line's marker. + +### The purple review + +A filled line is **pending review** while all three are true: + +- `ai_fill.status` is `filled`, +- `ai_fill.reviewed_at` is empty, and +- `pricing_source` is still `ai_quote`. + +While a line is pending it shows up **purple** on the order Process page, +with the bot marker next to it. Hover or focus the marker to see the quote +it was filled from, the cost, the selling price, the markup, the reason and +any doubts. + +The purple ends the moment somebody touches the line: + +- **Saving the line** records the review on `ai_fill` (`reviewed_at`, + `reviewed_by`, and whether the prices were accepted unchanged or + changed). The pricing source stays `ai_quote`. +- The one-click **"use this cost"** copy writes `pricing_source: "quote"` + — the operator chose the quote's cost themselves — so the AI's + provenance ends too. +- A line whose price the operator types by hand becomes `pricing_source: + "manual"`. + +### The board badge + +The Orders board shows an **"AI x/y"** badge on an order when the AI +filled some of its lines: `x` is how many lines still need a review, `y` +is how many the AI filled. The badge is hidden when nothing is pending. + +### `order_items.ai_fill` + +| Field | Meaning | +|---|---| +| `status` | `filled`, `skipped` or `error` | +| `source` | `quote` | +| `quote_id`, `quote_number`, `quote_item_id` | Where the numbers came from | +| `cost_price`, `selling_price`, `markup` | What was written | +| `supplier_id`, `sku` | What was written | +| `reason` | Why the line was paired with that quote line | +| `doubts` | Anything a person should check (e.g. the 5% price difference) | +| `model`, `attempted_at` | Which model, and when | +| `reviewed_at`, `reviewed_by` | Set when a person checked the line | +| `accepted_unchanged` | True when the person kept both prices | +| `supplier_changed` | True when the person changed the supplier | + ## Going live (manual steps, in this order) 1. **Migrate PocketBase.** Run against the live PB: @@ -71,7 +155,8 @@ the assistant falls back to a suggestion. 5. **Activate the n8n clock** (below). 6. **Turn it on** in Settings, AI: the global "AI enabled" switch must be on, then switch on **"Order assistant enabled"** in the Order assistant - section and click Save. Defaults: every 10 minutes, auto-link on. + section and click Save. Defaults: every 10 minutes, auto-link on, fill + lines on. The tick does nothing until both switches are on, so steps 5 and 6 are safe in either order. @@ -118,9 +203,15 @@ Click-by-click: A 401 means the credential is wrong (it must send the app's `AGENT_API_KEY` as a Bearer token). A tick that takes long is normal when -there is work. Each run is bounded: at most **10 orders**, oldest first, -and it stops starting new orders once 90 seconds have passed, so a run -normally answers inside the 120 s timeout; leftovers carry to the next run. +there is work. Each run is bounded: it matches **at most 10 orders** and +fills **at most 10 orders**, and it stops starting new orders once 90 +seconds have passed, so a run normally answers inside the 120 s timeout; +leftovers carry to the next run. + +An order the tick has nothing to do with does **not** use up that budget: +the run looks through up to 200 unlinked orders to find 10 it can work +on, and an order whose lines the fill could not write is skipped rather +than counted. Runs never overlap. A run stamps its start time before it does any work and writes its summary when it ends. While a start is newer than the last @@ -131,7 +222,8 @@ summary, the next tick after those 10 minutes takes over. ## Running it by hand - **Order page** — the **"Match with AI"** button on the quote match card - runs the assistant for that one order and reloads the order. + runs the assistant for that one order and reloads the order. It also + fills the lines when the order ends up linked. - **MCP tool** — `orders.assistant_match` with `{"orderId": "..."}`. Both force a run past the "already looked at these lines" rule. @@ -161,7 +253,7 @@ about an order. Read it in the PB admin. | Order assistant enabled | off | The tick does nothing while this is off | | Run every (minutes) | 10 | How often the tick may run | | Auto-link when sure | on | May the assistant link the order itself? | -| Fill lines from the quote | on | Reserved for the next phase — the assistant does not fill lines yet | +| Fill lines from the quote | on | May the assistant pre-fill the order's lines from the linked quote? | | Prompt | empty | Overrides the built-in prompt when set | `orders.ai_quote_match` and `order_items.ai_fill` are in `IGNORED_FIELDS` @@ -170,9 +262,9 @@ log. ## Not in this phase -- The assistant does **not** fill order lines from the quote yet — that is - the next phase. The `order_items.ai_fill` field and the "Fill lines from - the quote" switch are already in place for it. -- The assistant does not touch prices, suppliers or SKUs. +- The assistant does not price a line from scratch — it only copies the + cost, supplier, SKU and part number from the quote line the order line + is already paired with. - It never creates a purchase order, never sends an email and never changes a line's status. +- There is no POS fallback: the assistant fills only from a quote.