docs(orders): order assistant runbook final pass
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 <noreply@anthropic.com>
This commit is contained in:
parent
d3a52d97f2
commit
34d3422586
1 changed files with 105 additions and 13 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue