MedBridge Sales Ops — Account-Manager CRM Skill
Transferable operating procedure for two families of account-manager work against the
MedBridge Sales Ops remote API: QUOTE decision packages (reconcile a customer
RFQ/quote against catalog + freight + policies) and RECONCILIATION packages
(reconcile a won opportunity against invoices/payments/revenue-journals/events/vouchers).
The rules below are derived from the 5 train tasks and the live API records behind them.
Apply them to unseen tasks; do not memorize train answers.
0. Output discipline (applies to every task)
- Return only valid JSON matching
input/payloads/answer_template.json exactly:
same field names, same nesting, same key order, controlled enum values only.
- No markdown fences, no prose, no trailing commas, no comments.
- Money = USD dollars with 2 decimals (e.g.
50000.00, 42480.0), NOT integer
cents — despite any "cent-level" wording in a prompt, the template declares
"USD currency with 2 decimals" and the gold uses dollar amounts.
- Dates = ISO
YYYY-MM-DD. Nulls = JSON null (not "", not omitted).
- IDs = stable record IDs exactly as they appear in the API (
Q-TR-WC-1187,
CUST-HHA, OPP-TR-HELIOS, EVT-MERIDIAN-BRIEFING, etc.).
- Reconcile the prompt's narrative against the API; trust the API over narrative
wording when they conflict, but read the prompt carefully for which entity
(quote id, opportunity id, customer id, product code, quantities, quote date) to
reconcile. The prompt's confirmed quantity / quote date override stale quote-record
priors.
- For
customer_name in a reconciliation, use the name as given in the task prompt
(the account-facing label); cross-check customer_id and all financial records
against the API. (The API legal name can differ from the prompt's display name.)
1. Remote API usage
Base URL is given by the runner (e.g. <remote-env-url>). Call with curl,
pipe to python3 -m json.tool. All responses are JSON.
Endpoints:
GET /health, GET /api (lists collections + endpoints).
GET /api/<collection> — list all records ({collection, count, records}).
GET /api/<collection>?<key>=<value> — filter (case-insensitive, nested keys, AND
together, numeric tolerance 2dp).
GET /api/<collection>/<id> — detail-by-id ONLY works for customers,
products, rfqs, quotes, freight-quotes, opportunities. For
invoices, payments, revenue-journals, events, vouchers, policies use
the list + filter endpoints (filter by opportunity_id, customer_id,
invoice_id, phase_id, code, etc.).
GET /api/search?q=<text> — substring search across ALL collections (≤100 hits).
Collections: customers, products, rfqs, quotes, freight-quotes, policies,
opportunities, invoices, payments, revenue-journals, events, vouchers.
Filter gotchas (important — these waste calls if missed)
- Products' identifying field is
code (the record id equals code). Filtering
by product_code= returns nothing; filter by code= or use detail-by-id
/api/products/<code>.
- Filter
invoices, payments, revenue-journals by opportunity_id=OPP-... to
scope a reconciliation. Also invoice_id= on payments/revenue-journals ties a
payment/journal to a specific invoice.
events and vouchers filter by opportunity_id or customer_id; a voucher's
code is its id.
ID conventions / distractor records (critical exclusion rule)
- IDs prefixed
TR (e.g. Q-TR-WC-1187, RFQ-TR-IEHK-204, OPP-TR-HELIOS) are the
reconcilable train/test entities. IDs prefixed DIS are distractors — never
select them.
- In
freight-quotes, distractor freight records have destination = "Distractor route" and id starting with FR-DIS-. Always exclude them. The real freight
options for a quote have a real city destination and id FR-<family>-<MODE>
(e.g. FR-WC-AIR, FR-LD-ROAD). Also exclude any record with status: "mismatch".
- In
rfqs, modules may carry component_composition_distractors — these are
explicitly "for medical review only; do not split into component SKUs."
- Quote records carry
prior_quote_quantity / prior_unit_price_usd and a
source_notes hint (e.g. "catalog tier overrides prior unit price") — the current
catalog tier wins, never the prior price.
2. Policies (always fetch /api/policies and apply)
Eight policy records govern decisions. Map them by policy_area / terms_code:
| Policy |
Rule (transferable) |
POL-NEW-CLIENT-PAYMENT (PREPAY_100) |
New NGO clients with no credit history → PREPAY_100 before production release. Trigger: customer payment_profile = NEW_CLIENT_REVIEW or segment = new_ngo. |
POL-RECURRING-NGO-PAYMENT (NET_30_AFTER_PO) |
Recurring NGO → NET_30_AFTER_PO unless grant terms restrict. |
POL-INDICATIVE-EXW (EXW_ONLY_EXCLUDE_FREIGHT) |
Indicative quote with no confirmed destination → EXW only, freight excluded. |
POL-FREIGHT-RECONFIRM (RECONFIRM_AT_ORDER) |
Freight rates need reconfirmation at final order; valid only through valid_until. → freight_reconfirmation_required = true whenever freight is quoted. |
POL-MODULE-GRANULARITY (MODULE_LINES) |
Module RFQs quoted at module line level — do NOT expand into component SKUs. |
POL-REVREC (RECOGNIZE_PAID_COMPLETE_MILESTONES) |
When a milestone is complete and paid, recognize revenue (deferred → income). Unpaid future milestones stay outstanding and drive collection tasks when due/overdue. Paid milestone with no revenue journal ⇒ REQUIRED_MISSING / MISSING_REVENUE_JOURNAL. |
POL-QUOTE-VALIDITY (QUOTE_VALID_30_DAYS) |
Catalog quote pricing valid 30 days from quote date; freight may expire sooner. → offer_validity_days = 30. |
POL-EXW-SCOPE (EXW_EXCLUSIONS) |
EXW excludes freight, insurance, import duty, customs clearance, last-mile — unless explicitly added as separate options. |
3. QUOTE tasks — two sub-families
3A. Quote revision WITH freight options (template has freight_options + grand totals)
Fetch: GET /api/quotes/<quote_id> → customer_id, primary_product_code,
confirmed_quantity, quote_date, incoterm. Then GET /api/products/<product_code>
for price tiers, GET /api/customers/<customer_id> for payment profile, and
GET /api/freight-quotes?quote_id=<quote_id> (then strip distractors, §1).
Catalog tier selection. Products expose price_tiers: [{min_qty, max_qty, unit_price_usd, lead_time_days, lead_time_weeks}] (max_qty null = unbounded). Pick the
single tier where min_qty <= confirmed_quantity <= max_qty (null max = infinity).
Use that tier's unit_price_usd and lead_time_days; shelf_life_months is
product-level (same across tiers). Ignore prior_unit_price_usd. Round quantities to
the tier the confirmed quantity falls into.
exw_total_usd = unit_price_usd × confirmed_quantity (2dp).
grand_total_usd (per freight option) = exw_total_usd + freight_cost_usd.
Freight option selection. From the filtered freight records for the quote, keep the
3 canonical modes AIR / SEA / ROAD (real destination, id FR-<family>-{AIR,SEA,ROAD}).
Exclude FR-DIS-* / destination="Distractor route" / status="mismatch". Include a
stale/expired option (status stale or valid_until < quote_date) but flag it —
do not drop it. Order: AIR, SEA, ROAD.
Freight validity & staleness. Compare each option's valid_until to the quote date
and its status:
VALID (or all_freight_options_valid_on_quote_date=true) when status="active"
AND valid_until >= quote_date.
STALE / source_is_stale=true / road_quote_invalid_or_stale=true when
status="stale" OR valid_until < quote_date.
freight_reconfirmation_required = true whenever freight is quoted (per
POL-FREIGHT-RECONFIRM), independent of validity.
Risk mapping — NOTE the two schemas differ:
- risk_level / risk_flag schema (one freight template):
risk_level =
route_risk uppercased (LOW/MEDIUM/HIGH). risk_flag: NONE when low;
MEDIUM_BORDER_RISK when notes mention border risk medium; HIGH_* when high
border/customs risk.
- customs_border_risk schema (another template):
customs_border_risk reflects
customs/border-specific exposure in risk_notes, NOT the generic route_risk.
LOW by default; HIGH when notes say "customs risk high"/"border risk high";
MEDIUM when notes say border risk medium. A medium route_risk driven by
transit/shelf-life (not customs) still maps to customs_border_risk = LOW.
Recommended mode decision rule:
- Exclude candidates that are STALE or HIGH customs/border risk.
- Among remaining VALID options, pick the lowest
grand_total_usd.
- SEA is typically the cheapest valid low/medium-risk option → recommended
SEA.
If SEA is stale/high-risk, fall back to the next cheapest valid low-risk mode
(usually AIR). Both observed revisions recommend SEA.
Payment terms. Map from customer payment_profile + policies:
NEW_CLIENT_REVIEW (new NGO, is_recurring=false, status=prospect) → PREPAY_100.
NET_30_AFTER_PO (recurring) → NET_30_AFTER_PO.
MILESTONE_BILLING is for services/reconciliation, not catalog quotes.
customer_policy (when the template has it) = uppercased customer segment
(e.g. recurring_ngo → RECURRING_NGO) / matching policy label.
quote_basis controlled values: EXW (EXW pricing with freight options shown
separately, one template), EXW_PLUS_FREIGHT_OPTIONS (the policy_terms block in another
template), EXW_ONLY (freight excluded). Match the token the template demonstrates.
Field map (revision-with-freight), per template:
- Quote/pricing header:
quote_id, customer_id, quote_date, product_code,
confirmed_quantity, unit_price_usd (or catalog_tier sub-object with
min_quantity/max_quantity/unit_price_usd/lead_time_days/shelf_life_months),
lead_time_days, shelf_life_months, quote_basis, exw_total_usd, payment_terms.
- Freight options:
freight_id, mode (AIR/SEA/ROAD uppercased — API stores
lowercase), freight_cost_usd, transit_days (the record's transit_days_text;
some templates expect the bare min-max range — match the template's shown format),
valid_until, risk fields (per schema above), grand_total_usd.
- Policy/controls:
recommended_mode, freight_reconfirmation_required,
all_freight_options_valid_on_quote_date (or road_quote_invalid_or_stale +
freight_warning text + policy_terms.{quote_basis,payment_terms, freight_reconfirmation_required}).
3B. Indicative EXW-only module quote (RFQ, no destination, freight excluded)
Fetch: GET /api/rfqs/<rfq_id> → customer_id, quote_date, requested_modules[]
(each product_code + quantity), incoterm_requested, destination. Then
GET /api/customers/<customer_id>, and GET /api/products/<code> per requested module.
Granularity rule (POL-MODULE-GRANULARITY): emit one line per requested module,
using the module's code, article_number, the RFQ-requested quantity, and the
matching price tier unit_price. Never expand components[] into component lines,
even though the product record lists them — the RFQ says composition is for medical
review only. Select the tier where min_qty <= quantity <= max_qty (single-tier modules
have one tier with min_qty=1, max_qty=null).
line_total = unit_price × quantity (2dp). grand_total = sum of line_totals.
quote_basis = EXW_ONLY. freight_excluded = true (RFQ has no confirmed
destination → POL-INDICATIVE-EXW).
payment_terms: new NGO → PREPAY_100 (POL-NEW-CLIENT-PAYMENT).
offer_validity_days = 30 (POL-QUOTE-VALIDITY).
who_documentation_required = true for WHO-standard emergency health kit modules
(e.g. IEHK family / family = emergency_health_kit); these carry WHO documentation.
currency = USD.
Exclusion watch: an RFQ may include a component_composition_distractors list and
the catalog may contain a different RFQ for the same customer (e.g. a budgetary-check
RFQ with different quantities/status draft) — reconcile only the RFQ id named in the
prompt, at the module level.
4. RECONCILIATION tasks — won opportunity + milestones + revenue + events
Fetch: GET /api/opportunities/<opp_id> → customer_id, contact, stage,
won_amount_usd, phases[] (each phase_id, amount_usd, completion_date,
invoice_id). Then:
GET /api/invoices?opportunity_id=<opp> (one invoice per milestone phase, keyed by
phase_id / invoice_id).
GET /api/payments?opportunity_id=<opp> (ties to invoices via invoice_id;
status="posted").
GET /api/revenue-journals?opportunity_id=<opp> (ties to invoices via invoice_id /
phase_id; status="posted"; debit_account="Deferred Revenue",
credit_account="Implementation Services Revenue").
GET /api/events?opportunity_id=<opp> and GET /api/vouchers?opportunity_id=<opp>
(or filter vouchers by event_id).
GET /api/customers/<customer_id> for contact + name.
4.1 Milestone construction & ordering
Emit milestones as MS1, MS2, MS3, … in ascending phase order (one per
opportunity phase / invoice). phase_number (when the template has it) = the 1-based
phase index. Each milestone maps to exactly one invoice (by phase_id/invoice_id).
4.2 Payment & invoice state enums
Determine from invoice paid_amount_usd vs amount_usd and outstanding_amount_usd:
- Fully paid (
paid_amount >= amount, outstanding = 0, status="paid"):
payment_status/payment_state = PAID, amount_paid = amount, amount_unpaid = 0,
due_date = null (paid milestones have no due date).
- Partial (
0 < paid_amount < amount): PARTIAL, amount_paid/amount_unpaid
split, due_date retained.
- Unpaid (
paid_amount = 0, status="unpaid"): UNPAID, amount_paid = 0,
amount_unpaid = amount, due_date retained.
invoice_state enum (where the template separates it): map invoice status:
"paid" → PAID; "unpaid" → OPEN; "void" → VOID; unknown → UNKNOWN.
4.3 due_date rule
- Paid in full →
due_date = null.
- Unpaid/Partial →
due_date = invoice.due_date (the invoice record's contractual
due date). The collection follow-up task's due_date mirrors this milestone due date.
- If an unpaid invoice is already due/overdue as of the as-of date, escalate the
collection action (§4.5) and set the collection task due date to the invoice due date
(or a near-term forward target if past due).
4.4 Revenue recognition status (per milestone)
For each milestone, classify by paid-state AND presence of a matching revenue journal
(a revenue-journals record with the same invoice_id/phase_id, status="posted"):
- Paid and has a revenue journal →
RECOGNIZED.
- Paid and NO revenue journal →
REQUIRED_MISSING (one template) /
MISSING_REVENUE_JOURNAL (the other template). This triggers an accounting action
(§4.6).
- Unpaid/partial →
NOT_REQUIRED_UNPAID.
- Unknown/incomplete data →
UNKNOWN (only where the enum allows).
Overall recognition block (when the template has one):
recognition_status:
COMPLETE_FOR_PAID_MILESTONES — every paid milestone has a revenue journal.
MISSING_FOR_PAID_MILESTONES — at least one paid milestone lacks a journal.
NOT_REQUIRED — no paid milestones to recognize.
recognized_milestones = list of MS ids that are RECOGNIZED.
missing_required_milestones = list of paid MS ids missing a journal.
recognized_amount = sum of amounts of recognized milestones (2dp).
4.5 Collection action routing
Use the as-of date (the prompt's stated current date, e.g. 2026-06-01; if unstated,
infer from the prompt context). For each unpaid/partial milestone:
- Not yet due (
invoice.due_date > as_of): MONITOR_UNPAID_NOT_DUE,
owner_queue = ACCOUNT_MANAGEMENT. (No dunning yet.)
- Due or overdue (
invoice.due_date <= as_of): SEND_COLLECTION_NOTICE,
owner_queue = COLLECTIONS.
- If no unpaid milestone exists:
NO_COLLECTION_ACTION / NONE.
- Templates with a single collection
next_action (COLLECT_UNPAID_MILESTONE) route
one collection task per unpaid milestone (owner is implicit/the account contact).
collection_task carries: action, milestone_id (the unpaid MS), amount =
unpaid amount, due_date = milestone due date, owner_queue, contact_name =
opportunity contact.
4.6 Accounting action routing (revenue recognition)
- If a paid milestone is missing its revenue journal → record revenue for it:
primary_accounting_action / accounting_action.action =
RECORD_REVENUE_MS<N> (e.g. RECORD_REVENUE_MS2).
milestone_id = MS<N>, amount = milestone amount.
debit_account = DEFERRED_REVENUE, credit_account = IMPLEMENTATION_SERVICES_REVENUE (mirrors the posted journal pattern).
owner_queue = ACCOUNTING.
- If all paid milestones are already recognized →
VERIFY_REVENUE_ONLY (verify
journals exist) or NO_ACCOUNTING_ACTION, milestone_id = NONE, accounts NONE,
owner ACCOUNTING/NONE.
NO_ACCOUNTING_ACTION when nothing requires recording/verifying.
4.7 Event & voucher linkage, invite action
From the event record (GET /api/events?opportunity_id=...) and its voucher
(vouchers?event_id=... or match voucher_code on the event):
event_id, event_date, event_status: map event status →
"scheduled" → SCHEDULED; "confirmed"/"live" → ACTIVE; "completed" →
COMPLETED; "cancelled" → CANCELLED; "tentative"/unknown → UNKNOWN.
- voucher fields:
voucher_code, voucher_status (voucher status uppercased:
ACTIVE/DRAFT/EXPIRED/DISABLED/UNKNOWN), discount_amount /
voucher_discount = voucher discount_percent (as a number, e.g. 50.00, 100.00),
max_uses / voucher_max_uses = voucher max_redemptions.
invite_action decision:
SEND_BRIEFING_INVITE (or SEND_EVENT_INVITATION) when the event is
scheduled/active, in the future relative to as-of, and the voucher is active → emit
an invite task.
VERIFY_INVITE_SENT when an invite should already have gone out (e.g. event imminent
/ already started) — verify rather than re-send.
NO_INVITE_ACTION when the event is cancelled/completed or the voucher expired/
disabled.
invite task: event_id, voucher_code, owner_queue = ACCOUNT_MANAGEMENT
(matches the event's follow_up_owner = "Account Management"), contact_name =
opportunity contact, customer_id. For templates with an invite due_date, set it to
event_date − 21 days (send ~3 weeks ahead).
4.8 follow_up_tasks (combined collection + event invitation)
When the template uses a flat follow_up_tasks[] array, emit:
- One
COLLECTION task per unpaid milestone: task_type = COLLECTION,
task_title = "Milestone <N> collection - <customer_name>", next_action = COLLECT_UNPAID_MILESTONE, milestone_id, amount_due, due_date = milestone due
date, event_id = null, voucher_code = null.
- One
EVENT_INVITATION task: task_type = EVENT_INVITATION, task_title = "Send <event> invite - <customer_name>", next_action = SEND_EVENT_INVITATION,
milestone_id = null, amount_due = null, due_date = event_date − 21,
event_id, voucher_code.
Every task links linked_customer_id, linked_opportunity_id, and contact_name
(named in the prompt, e.g. "Mara Okafor", "Daniel Rees") — the contact is tied to all
follow-up work.
4.9 Totals & match checks
won_amount / won_amount_usd = opportunity won_amount_usd.
phase_total_amount / milestone sum = sum of all phase amount_usd.
opportunity_matches_milestones / opportunity_matches_phase_total = true iff
won_amount_usd == sum(phase amounts).
total_paid_amount = sum of paid_amount across milestones.
outstanding_balance = sum of amount_unpaid across milestones (should equal
opportunity outstanding_amount_usd).
as_of_date = the prompt's stated current date.
4.10 Enum reference (reconciliation)
opportunity_stage / stage: WON (from closed_won), OPEN, LOST.
payment_status / payment_state: PAID | PARTIAL | UNPAID (| UNKNOWN).
invoice_state: PAID | OPEN | VOID | UNKNOWN.
- per-milestone
recognition_status: RECOGNIZED | REQUIRED_MISSING |
NOT_REQUIRED_UNPAID (template A) or RECOGNIZED | MISSING_REVENUE_JOURNAL |
NOT_REQUIRED_UNPAID | UNKNOWN (template B) — use the exact enum the template
declares.
- overall
recognition_status: COMPLETE_FOR_PAID_MILESTONES |
MISSING_FOR_PAID_MILESTONES | NOT_REQUIRED.
primary_accounting_action / accounting action: RECORD_REVENUE_MS<N> |
VERIFY_REVENUE_ONLY | NO_ACCOUNTING_ACTION.
collection_action: MONITOR_UNPAID_NOT_DUE | SEND_COLLECTION_NOTICE |
NO_COLLECTION_ACTION.
invite_action: SEND_BRIEFING_INVITE | VERIFY_INVITE_SENT | NO_INVITE_ACTION
(template B) / SEND_EVENT_INVITATION (template A next_action).
task_type: COLLECTION | EVENT_INVITATION.
debit_account: DEFERRED_REVENUE | ACCOUNTS_RECEIVABLE | CASH | NONE.
credit_account: IMPLEMENTATION_SERVICES_REVENUE | DEFERRED_REVENUE |
ACCOUNTS_RECEIVABLE | NONE.
owner_queue: ACCOUNTING | ACCOUNT_MANAGEMENT | COLLECTIONS | EVENTS |
NONE.
event_status: SCHEDULED | ACTIVE | COMPLETED | CANCELLED | UNKNOWN.
voucher_status: ACTIVE | DRAFT | EXPIRED | DISABLED | UNKNOWN.
5. Common misjudgments & exclusion rules (do NOT do these)
- Expanding module RFQs into component lines. IEHK/WHO modules list
components[]
(and component_composition_distractors); keep one line per module. Component
detail is medical-review-only.
- Including freight on an EXW-only indicative quote. No confirmed destination ⇒
EXW_ONLY, freight_excluded = true, no freight_options block at all.
- Using the prior unit price. Quote revisions carry
prior_unit_price_usd; the
current catalog tier for the confirmed quantity overrides it.
- Selecting distractor freight.
FR-DIS-* / destination="Distractor route" /
status="mismatch" are never quoted — even if status looks "active".
- Dropping a stale/expired freight option. Include it but flag
STALE /
source_is_stale=true / road_quote_invalid_or_stale=true + a freight_warning.
- Treating
customs_border_risk as route_risk. A medium route risk driven by
shelf-life/transit is still customs_border_risk = LOW; only explicit customs/border
notes raise it.
- Keeping a due_date on a paid milestone. Fully paid ⇒
due_date = null.
- Marking a paid milestone RECOGNIZED without a revenue journal. Paid + no
revenue-journals record ⇒ REQUIRED_MISSING / MISSING_REVENUE_JOURNAL and a
RECORD_REVENUE_MS<N> accounting action (DEFERRED_REVENUE →
IMPLEMENTATION_SERVICES_REVENUE, owner ACCOUNTING).
- Sending a collection notice on a not-yet-due invoice.
due_date > as_of ⇒
MONITOR_UNPAID_NOT_DUE (owner ACCOUNT_MANAGEMENT), not SEND_COLLECTION_NOTICE.
- Recommending a stale/high-risk mode. Exclude stale and HIGH-customs options
before picking the lowest
grand_total; default cost-effective choice is SEA.
- Setting
freight_reconfirmation_required = false. It is true whenever
freight options are quoted (policy POL-FREIGHT-RECONFIRM).
- Wrong money type. Use 2-decimal USD dollar amounts, not integer cents.
- Pulling the wrong RFQ/quote for a customer. Several customers have multiple
RFQs (incl. draft/budgetary distractors). Reconcile only the id named in the prompt.
- Forgetting WHO documentation. IEHK /
emergency_health_kit modules ⇒
who_documentation_required = true.
- Mismatching enum tokens across templates. Two reconciliation templates use
different recognition tokens (
REQUIRED_MISSING vs MISSING_REVENUE_JOURNAL) and
different collection/invite tokens (COLLECT_UNPAID_MILESTONE/
SEND_EVENT_INVITATION vs MONITOR_UNPAID_NOT_DUE/SEND_COLLECTION_NOTICE/
SEND_BRIEFING_INVITE). Always copy the enum values declared in the specific
answer_template.json for the task at hand.
6. Worked decision flow (apply per task)
- Identify the family (quote vs reconciliation) and the exact entity id(s) from the
prompt.
- Fetch the named record(s) by id; fetch related collections via filter; fetch
/api/policies.
- Reconcile quantities/date against the API; select catalog tier / milestones /
freight options; classify states using the rules above.
- Map every field to the exact template field names + enum tokens.
- Compute money (2dp), dates (ISO), totals, and outstanding/recognized amounts.
- Emit one JSON object only — no prose, no fences — matching
input/payloads/answer_template.json.
1---2name: fewshot-attempt-01-33description: MedBridge Sales Ops — Account-Manager CRM Skill4---5# MedBridge Sales Ops — Account-Manager CRM Skill67Transferable operating procedure for two families of account-manager work against the8MedBridge Sales Ops remote API: **QUOTE decision packages** (reconcile a customer9RFQ/quote against catalog + freight + policies) and **RECONCILIATION packages**10(reconcile a won opportunity against invoices/payments/revenue-journals/events/vouchers).1112The rules below are derived from the 5 train tasks and the live API records behind them.13Apply them to unseen tasks; do not memorize train answers.1415---1617## 0. Output discipline (applies to every task)1819- Return **only valid JSON** matching `input/payloads/answer_template.json` exactly:20 same field names, same nesting, same key order, **controlled enum values only**.21- No markdown fences, no prose, no trailing commas, no comments.22- **Money** = USD dollars with **2 decimals** (e.g. `50000.00`, `42480.0`), NOT integer23 cents — despite any "cent-level" wording in a prompt, the template declares24 "USD currency with 2 decimals" and the gold uses dollar amounts.25- **Dates** = ISO `YYYY-MM-DD`. **Nulls** = JSON `null` (not `""`, not omitted).26- **IDs** = stable record IDs exactly as they appear in the API (`Q-TR-WC-1187`,27 `CUST-HHA`, `OPP-TR-HELIOS`, `EVT-MERIDIAN-BRIEFING`, etc.).28- Reconcile the prompt's narrative against the API; **trust the API over narrative29 wording** when they conflict, but read the prompt carefully for *which* entity30 (quote id, opportunity id, customer id, product code, quantities, quote date) to31 reconcile. The prompt's confirmed quantity / quote date override stale quote-record32 priors.33- For `customer_name` in a reconciliation, use the **name as given in the task prompt**34 (the account-facing label); cross-check `customer_id` and all financial records35 against the API. (The API legal name can differ from the prompt's display name.)3637---3839## 1. Remote API usage4041Base URL is given by the runner (e.g. `<remote-env-url>`). Call with `curl`,42pipe to `python3 -m json.tool`. All responses are JSON.4344Endpoints:45- `GET /health`, `GET /api` (lists collections + endpoints).46- `GET /api/<collection>` — list all records (`{collection, count, records}`).47- `GET /api/<collection>?<key>=<value>` — filter (case-insensitive, nested keys, AND48 together, numeric tolerance 2dp).49- `GET /api/<collection>/<id>` — **detail-by-id ONLY works for** `customers`,50 `products`, `rfqs`, `quotes`, `freight-quotes`, `opportunities`. For51 `invoices`, `payments`, `revenue-journals`, `events`, `vouchers`, `policies` use52 the **list + filter** endpoints (filter by `opportunity_id`, `customer_id`,53 `invoice_id`, `phase_id`, `code`, etc.).54- `GET /api/search?q=<text>` — substring search across ALL collections (≤100 hits).5556Collections: `customers`, `products`, `rfqs`, `quotes`, `freight-quotes`, `policies`,57`opportunities`, `invoices`, `payments`, `revenue-journals`, `events`, `vouchers`.5859### Filter gotchas (important — these waste calls if missed)60- Products' identifying field is **`code`** (the record `id` equals `code`). Filtering61 by `product_code=` returns nothing; filter by `code=` or use detail-by-id62 `/api/products/<code>`.63- Filter `invoices`, `payments`, `revenue-journals` by `opportunity_id=OPP-...` to64 scope a reconciliation. Also `invoice_id=` on payments/revenue-journals ties a65 payment/journal to a specific invoice.66- `events` and `vouchers` filter by `opportunity_id` or `customer_id`; a voucher's67 `code` is its id.6869### ID conventions / distractor records (critical exclusion rule)70- IDs prefixed `TR` (e.g. `Q-TR-WC-1187`, `RFQ-TR-IEHK-204`, `OPP-TR-HELIOS`) are the71 reconcilable train/test entities. IDs prefixed `DIS` are **distractors** — never72 select them.73- In `freight-quotes`, **distractor freight records** have `destination` = `"Distractor74 route"` and `id` starting with `FR-DIS-`. Always **exclude** them. The real freight75 options for a quote have a real city destination and id `FR-<family>-<MODE>`76 (e.g. `FR-WC-AIR`, `FR-LD-ROAD`). Also exclude any record with `status: "mismatch"`.77- In `rfqs`, modules may carry `component_composition_distractors` — these are78 explicitly "for medical review only; do not split into component SKUs."79- Quote records carry `prior_quote_quantity` / `prior_unit_price_usd` and a80 `source_notes` hint (e.g. "catalog tier overrides prior unit price") — the **current81 catalog tier wins**, never the prior price.8283---8485## 2. Policies (always fetch `/api/policies` and apply)8687Eight policy records govern decisions. Map them by `policy_area` / `terms_code`:8889| Policy | Rule (transferable) |90|---|---|91| `POL-NEW-CLIENT-PAYMENT` (`PREPAY_100`) | New NGO clients with no credit history → **PREPAY_100** before production release. Trigger: customer `payment_profile = NEW_CLIENT_REVIEW` or `segment = new_ngo`. |92| `POL-RECURRING-NGO-PAYMENT` (`NET_30_AFTER_PO`) | Recurring NGO → **NET_30_AFTER_PO** unless grant terms restrict. |93| `POL-INDICATIVE-EXW` (`EXW_ONLY_EXCLUDE_FREIGHT`) | Indicative quote with **no confirmed destination** → **EXW only, freight excluded**. |94| `POL-FREIGHT-RECONFIRM` (`RECONFIRM_AT_ORDER`) | Freight rates need **reconfirmation at final order**; valid only through `valid_until`. → `freight_reconfirmation_required = true` whenever freight is quoted. |95| `POL-MODULE-GRANULARITY` (`MODULE_LINES`) | Module RFQs quoted at **module line level** — do NOT expand into component SKUs. |96| `POL-REVREC` (`RECOGNIZE_PAID_COMPLETE_MILESTONES`) | When a milestone is complete **and paid**, recognize revenue (deferred → income). Unpaid future milestones stay outstanding and drive collection tasks when due/overdue. Paid milestone with **no revenue journal ⇒ REQUIRED_MISSING / MISSING_REVENUE_JOURNAL**. |97| `POL-QUOTE-VALIDITY` (`QUOTE_VALID_30_DAYS`) | Catalog quote pricing valid **30 days** from quote date; freight may expire sooner. → `offer_validity_days = 30`. |98| `POL-EXW-SCOPE` (`EXW_EXCLUSIONS`) | EXW **excludes** freight, insurance, import duty, customs clearance, last-mile — unless explicitly added as separate options. |99100---101102## 3. QUOTE tasks — two sub-families103104### 3A. Quote revision WITH freight options (template has freight_options + grand totals)105106Fetch: `GET /api/quotes/<quote_id>` → `customer_id`, `primary_product_code`,107`confirmed_quantity`, `quote_date`, `incoterm`. Then `GET /api/products/<product_code>`108for price tiers, `GET /api/customers/<customer_id>` for payment profile, and109`GET /api/freight-quotes?quote_id=<quote_id>` (then strip distractors, §1).110111**Catalog tier selection.** Products expose `price_tiers: [{min_qty, max_qty,112unit_price_usd, lead_time_days, lead_time_weeks}]` (max_qty `null` = unbounded). Pick the113**single tier where `min_qty <= confirmed_quantity <= max_qty`** (null max = infinity).114Use that tier's `unit_price_usd` and `lead_time_days`; `shelf_life_months` is115product-level (same across tiers). Ignore `prior_unit_price_usd`. Round quantities to116the tier the confirmed quantity falls into.117118- `exw_total_usd` = `unit_price_usd × confirmed_quantity` (2dp).119- `grand_total_usd` (per freight option) = `exw_total_usd + freight_cost_usd`.120121**Freight option selection.** From the filtered freight records for the quote, keep the1223 canonical modes AIR / SEA / ROAD (real destination, id `FR-<family>-{AIR,SEA,ROAD}`).123Exclude `FR-DIS-*` / `destination="Distractor route"` / `status="mismatch"`. **Include a124stale/expired option** (status `stale` or `valid_until < quote_date`) but **flag it** —125do not drop it. Order: AIR, SEA, ROAD.126127**Freight validity & staleness.** Compare each option's `valid_until` to the quote date128and its `status`:129- `VALID` (or `all_freight_options_valid_on_quote_date=true`) when `status="active"`130 AND `valid_until >= quote_date`.131- `STALE` / `source_is_stale=true` / `road_quote_invalid_or_stale=true` when132 `status="stale"` OR `valid_until < quote_date`.133- `freight_reconfirmation_required` = **true** whenever freight is quoted (per134 `POL-FREIGHT-RECONFIRM`), independent of validity.135136**Risk mapping — NOTE the two schemas differ:**137- *risk_level / risk_flag schema (one freight template):* `risk_level` =138 `route_risk` uppercased (`LOW`/`MEDIUM`/`HIGH`). `risk_flag`: `NONE` when low;139 `MEDIUM_BORDER_RISK` when notes mention border risk medium; `HIGH_*` when high140 border/customs risk.141- *customs_border_risk schema (another template):* `customs_border_risk` reflects142 **customs/border-specific** exposure in `risk_notes`, NOT the generic `route_risk`.143 `LOW` by default; `HIGH` when notes say "customs risk high"/"border risk high";144 `MEDIUM` when notes say border risk medium. A medium `route_risk` driven by145 transit/shelf-life (not customs) still maps to `customs_border_risk = LOW`.146147**Recommended mode decision rule:**1481. Exclude candidates that are STALE or HIGH customs/border risk.1492. Among remaining VALID options, pick the **lowest `grand_total_usd`**.1503. SEA is typically the cheapest valid low/medium-risk option → recommended `SEA`.151 If SEA is stale/high-risk, fall back to the next cheapest valid low-risk mode152 (usually AIR). Both observed revisions recommend `SEA`.153154**Payment terms.** Map from customer `payment_profile` + policies:155- `NEW_CLIENT_REVIEW` (new NGO, `is_recurring=false`, `status=prospect`) → `PREPAY_100`.156- `NET_30_AFTER_PO` (recurring) → `NET_30_AFTER_PO`.157- `MILESTONE_BILLING` is for services/reconciliation, not catalog quotes.158`customer_policy` (when the template has it) = uppercased customer segment159(e.g. `recurring_ngo` → `RECURRING_NGO`) / matching policy label.160161**quote_basis** controlled values: `EXW` (EXW pricing with freight options shown162separately, one template), `EXW_PLUS_FREIGHT_OPTIONS` (the policy_terms block in another163template), `EXW_ONLY` (freight excluded). Match the token the template demonstrates.164165**Field map (revision-with-freight), per template:**166- Quote/pricing header: `quote_id`, `customer_id`, `quote_date`, `product_code`,167 `confirmed_quantity`, `unit_price_usd` (or `catalog_tier` sub-object with168 `min_quantity`/`max_quantity`/`unit_price_usd`/`lead_time_days`/`shelf_life_months`),169 `lead_time_days`, `shelf_life_months`, `quote_basis`, `exw_total_usd`, `payment_terms`.170- Freight options: `freight_id`, `mode` (`AIR`/`SEA`/`ROAD` uppercased — API stores171 lowercase), `freight_cost_usd`, `transit_days` (the record's `transit_days_text`;172 some templates expect the bare min-max range — match the template's shown format),173 `valid_until`, risk fields (per schema above), `grand_total_usd`.174- Policy/controls: `recommended_mode`, `freight_reconfirmation_required`,175 `all_freight_options_valid_on_quote_date` (or `road_quote_invalid_or_stale` +176 `freight_warning` text + `policy_terms.{quote_basis,payment_terms,177 freight_reconfirmation_required}`).178179### 3B. Indicative EXW-only module quote (RFQ, no destination, freight excluded)180181Fetch: `GET /api/rfqs/<rfq_id>` → `customer_id`, `quote_date`, `requested_modules[]`182(each `product_code` + `quantity`), `incoterm_requested`, `destination`. Then183`GET /api/customers/<customer_id>`, and `GET /api/products/<code>` per requested module.184185**Granularity rule (`POL-MODULE-GRANULARITY`):** emit **one line per requested module**,186using the module's `code`, `article_number`, the RFQ-requested `quantity`, and the187matching price tier `unit_price`. **Never expand `components[]` into component lines**,188even though the product record lists them — the RFQ says composition is for medical189review only. Select the tier where `min_qty <= quantity <= max_qty` (single-tier modules190have one tier with `min_qty=1, max_qty=null`).191192- `line_total` = `unit_price × quantity` (2dp). `grand_total` = sum of `line_total`s.193- `quote_basis = EXW_ONLY`. `freight_excluded = true` (RFQ has no confirmed194 destination → `POL-INDICATIVE-EXW`).195- `payment_terms`: new NGO → `PREPAY_100` (`POL-NEW-CLIENT-PAYMENT`).196- `offer_validity_days = 30` (`POL-QUOTE-VALIDITY`).197- `who_documentation_required = true` for WHO-standard emergency health kit modules198 (e.g. IEHK family / `family = emergency_health_kit`); these carry WHO documentation.199- `currency = USD`.200201**Exclusion watch:** an RFQ may include a `component_composition_distractors` list and202the catalog may contain a *different* RFQ for the same customer (e.g. a budgetary-check203RFQ with different quantities/status `draft`) — reconcile only the RFQ id named in the204prompt, at the module level.205206---207208## 4. RECONCILIATION tasks — won opportunity + milestones + revenue + events209210Fetch: `GET /api/opportunities/<opp_id>` → `customer_id`, `contact`, `stage`,211`won_amount_usd`, `phases[]` (each `phase_id`, `amount_usd`, `completion_date`,212`invoice_id`). Then:213- `GET /api/invoices?opportunity_id=<opp>` (one invoice per milestone phase, keyed by214 `phase_id` / `invoice_id`).215- `GET /api/payments?opportunity_id=<opp>` (ties to invoices via `invoice_id`;216 `status="posted"`).217- `GET /api/revenue-journals?opportunity_id=<opp>` (ties to invoices via `invoice_id` /218 `phase_id`; `status="posted"`; `debit_account="Deferred Revenue"`,219 `credit_account="Implementation Services Revenue"`).220- `GET /api/events?opportunity_id=<opp>` and `GET /api/vouchers?opportunity_id=<opp>`221 (or filter vouchers by `event_id`).222- `GET /api/customers/<customer_id>` for contact + name.223224### 4.1 Milestone construction & ordering225Emit milestones as **`MS1`, `MS2`, `MS3`, …** in ascending phase order (one per226opportunity phase / invoice). `phase_number` (when the template has it) = the 1-based227phase index. Each milestone maps to exactly one invoice (by `phase_id`/`invoice_id`).228229### 4.2 Payment & invoice state enums230Determine from invoice `paid_amount_usd` vs `amount_usd` and `outstanding_amount_usd`:231- **Fully paid** (`paid_amount >= amount`, `outstanding = 0`, `status="paid"`):232 `payment_status/payment_state = PAID`, `amount_paid = amount`, `amount_unpaid = 0`,233 `due_date = null` (paid milestones have **no due date**).234- **Partial** (`0 < paid_amount < amount`): `PARTIAL`, `amount_paid`/`amount_unpaid`235 split, `due_date` retained.236- **Unpaid** (`paid_amount = 0`, `status="unpaid"`): `UNPAID`, `amount_paid = 0`,237 `amount_unpaid = amount`, `due_date` retained.238239`invoice_state` enum (where the template separates it): map invoice `status`:240`"paid"` → `PAID`; `"unpaid"` → `OPEN`; `"void"` → `VOID`; unknown → `UNKNOWN`.241242### 4.3 due_date rule243- **Paid in full** → `due_date = null`.244- **Unpaid/Partial** → `due_date = invoice.due_date` (the invoice record's contractual245 due date). The collection follow-up task's `due_date` mirrors this milestone due date.246- If an unpaid invoice is **already due/overdue as of the as-of date**, escalate the247 collection action (§4.5) and set the collection task due date to the invoice due date248 (or a near-term forward target if past due).249250### 4.4 Revenue recognition status (per milestone)251For each milestone, classify by paid-state AND presence of a matching revenue journal252(a `revenue-journals` record with the same `invoice_id`/`phase_id`, `status="posted"`):253- Paid **and** has a revenue journal → `RECOGNIZED`.254- Paid **and NO** revenue journal → `REQUIRED_MISSING` (one template) /255 `MISSING_REVENUE_JOURNAL` (the other template). This triggers an accounting action256 (§4.6).257- Unpaid/partial → `NOT_REQUIRED_UNPAID`.258- Unknown/incomplete data → `UNKNOWN` (only where the enum allows).259260**Overall recognition block (when the template has one):**261- `recognition_status`:262 - `COMPLETE_FOR_PAID_MILESTONES` — every paid milestone has a revenue journal.263 - `MISSING_FOR_PAID_MILESTONES` — at least one paid milestone lacks a journal.264 - `NOT_REQUIRED` — no paid milestones to recognize.265- `recognized_milestones` = list of MS ids that are RECOGNIZED.266- `missing_required_milestones` = list of paid MS ids missing a journal.267- `recognized_amount` = sum of amounts of recognized milestones (2dp).268269### 4.5 Collection action routing270Use the as-of date (the prompt's stated current date, e.g. `2026-06-01`; if unstated,271infer from the prompt context). For each **unpaid/partial** milestone:272- **Not yet due** (`invoice.due_date > as_of`): `MONITOR_UNPAID_NOT_DUE`,273 `owner_queue = ACCOUNT_MANAGEMENT`. (No dunning yet.)274- **Due or overdue** (`invoice.due_date <= as_of`): `SEND_COLLECTION_NOTICE`,275 `owner_queue = COLLECTIONS`.276- If no unpaid milestone exists: `NO_COLLECTION_ACTION` / `NONE`.277- Templates with a single collection `next_action` (`COLLECT_UNPAID_MILESTONE`) route278 one collection task per unpaid milestone (owner is implicit/the account contact).279280`collection_task` carries: `action`, `milestone_id` (the unpaid MS), `amount` =281unpaid amount, `due_date` = milestone due date, `owner_queue`, `contact_name` =282opportunity contact.283284### 4.6 Accounting action routing (revenue recognition)285- If a **paid** milestone is missing its revenue journal → record revenue for it:286 - `primary_accounting_action` / `accounting_action.action` =287 `RECORD_REVENUE_MS<N>` (e.g. `RECORD_REVENUE_MS2`).288 - `milestone_id = MS<N>`, `amount` = milestone amount.289 - `debit_account = DEFERRED_REVENUE`, `credit_account =290 IMPLEMENTATION_SERVICES_REVENUE` (mirrors the posted journal pattern).291 - `owner_queue = ACCOUNTING`.292- If all paid milestones are already recognized → `VERIFY_REVENUE_ONLY` (verify293 journals exist) or `NO_ACCOUNTING_ACTION`, `milestone_id = NONE`, accounts `NONE`,294 owner `ACCOUNTING`/`NONE`.295- `NO_ACCOUNTING_ACTION` when nothing requires recording/verifying.296297### 4.7 Event & voucher linkage, invite action298From the event record (`GET /api/events?opportunity_id=...`) and its voucher299(`vouchers?event_id=...` or match `voucher_code` on the event):300- `event_id`, `event_date`, `event_status`: map event `status` →301 `"scheduled"` → `SCHEDULED`; `"confirmed"`/`"live"` → `ACTIVE`; `"completed"` →302 `COMPLETED`; `"cancelled"` → `CANCELLED`; `"tentative"`/unknown → `UNKNOWN`.303- voucher fields: `voucher_code`, `voucher_status` (voucher `status` uppercased:304 `ACTIVE`/`DRAFT`/`EXPIRED`/`DISABLED`/`UNKNOWN`), `discount_amount` /305 `voucher_discount` = voucher `discount_percent` (as a number, e.g. `50.00`, `100.00`),306 `max_uses` / `voucher_max_uses` = voucher `max_redemptions`.307308**invite_action** decision:309- `SEND_BRIEFING_INVITE` (or `SEND_EVENT_INVITATION`) when the event is310 scheduled/active, in the future relative to as-of, and the voucher is active → emit311 an invite task.312- `VERIFY_INVITE_SENT` when an invite should already have gone out (e.g. event imminent313 / already started) — verify rather than re-send.314- `NO_INVITE_ACTION` when the event is cancelled/completed or the voucher expired/315 disabled.316317**invite task:** `event_id`, `voucher_code`, `owner_queue = ACCOUNT_MANAGEMENT`318(matches the event's `follow_up_owner = "Account Management"`), `contact_name` =319opportunity contact, `customer_id`. For templates with an invite `due_date`, set it to320**`event_date − 21 days`** (send ~3 weeks ahead).321322### 4.8 follow_up_tasks (combined collection + event invitation)323When the template uses a flat `follow_up_tasks[]` array, emit:324- One `COLLECTION` task per unpaid milestone: `task_type = COLLECTION`,325 `task_title = "Milestone <N> collection - <customer_name>"`, `next_action =326 COLLECT_UNPAID_MILESTONE`, `milestone_id`, `amount_due`, `due_date` = milestone due327 date, `event_id = null`, `voucher_code = null`.328- One `EVENT_INVITATION` task: `task_type = EVENT_INVITATION`, `task_title =329 "Send <event> invite - <customer_name>"`, `next_action = SEND_EVENT_INVITATION`,330 `milestone_id = null`, `amount_due = null`, `due_date = event_date − 21`,331 `event_id`, `voucher_code`.332333Every task links `linked_customer_id`, `linked_opportunity_id`, and `contact_name`334(named in the prompt, e.g. "Mara Okafor", "Daniel Rees") — the contact is tied to all335follow-up work.336337### 4.9 Totals & match checks338- `won_amount` / `won_amount_usd` = opportunity `won_amount_usd`.339- `phase_total_amount` / milestone sum = sum of all phase `amount_usd`.340- `opportunity_matches_milestones` / `opportunity_matches_phase_total` = `true` iff341 `won_amount_usd == sum(phase amounts)`.342- `total_paid_amount` = sum of `paid_amount` across milestones.343- `outstanding_balance` = sum of `amount_unpaid` across milestones (should equal344 opportunity `outstanding_amount_usd`).345- `as_of_date` = the prompt's stated current date.346347### 4.10 Enum reference (reconciliation)348- `opportunity_stage` / `stage`: `WON` (from `closed_won`), `OPEN`, `LOST`.349- `payment_status` / `payment_state`: `PAID` | `PARTIAL` | `UNPAID` (| `UNKNOWN`).350- `invoice_state`: `PAID` | `OPEN` | `VOID` | `UNKNOWN`.351- per-milestone `recognition_status`: `RECOGNIZED` | `REQUIRED_MISSING` |352 `NOT_REQUIRED_UNPAID` (template A) **or** `RECOGNIZED` | `MISSING_REVENUE_JOURNAL` |353 `NOT_REQUIRED_UNPAID` | `UNKNOWN` (template B) — **use the exact enum the template354 declares**.355- overall `recognition_status`: `COMPLETE_FOR_PAID_MILESTONES` |356 `MISSING_FOR_PAID_MILESTONES` | `NOT_REQUIRED`.357- `primary_accounting_action` / accounting `action`: `RECORD_REVENUE_MS<N>` |358 `VERIFY_REVENUE_ONLY` | `NO_ACCOUNTING_ACTION`.359- `collection_action`: `MONITOR_UNPAID_NOT_DUE` | `SEND_COLLECTION_NOTICE` |360 `NO_COLLECTION_ACTION`.361- `invite_action`: `SEND_BRIEFING_INVITE` | `VERIFY_INVITE_SENT` | `NO_INVITE_ACTION`362 (template B) / `SEND_EVENT_INVITATION` (template A `next_action`).363- `task_type`: `COLLECTION` | `EVENT_INVITATION`.364- `debit_account`: `DEFERRED_REVENUE` | `ACCOUNTS_RECEIVABLE` | `CASH` | `NONE`.365- `credit_account`: `IMPLEMENTATION_SERVICES_REVENUE` | `DEFERRED_REVENUE` |366 `ACCOUNTS_RECEIVABLE` | `NONE`.367- `owner_queue`: `ACCOUNTING` | `ACCOUNT_MANAGEMENT` | `COLLECTIONS` | `EVENTS` |368 `NONE`.369- `event_status`: `SCHEDULED` | `ACTIVE` | `COMPLETED` | `CANCELLED` | `UNKNOWN`.370- `voucher_status`: `ACTIVE` | `DRAFT` | `EXPIRED` | `DISABLED` | `UNKNOWN`.371372---373374## 5. Common misjudgments & exclusion rules (do NOT do these)3753761. **Expanding module RFQs into component lines.** IEHK/WHO modules list `components[]`377 (and `component_composition_distractors`); keep one line per module. Component378 detail is medical-review-only.3792. **Including freight on an EXW-only indicative quote.** No confirmed destination ⇒380 `EXW_ONLY`, `freight_excluded = true`, no freight_options block at all.3813. **Using the prior unit price.** Quote revisions carry `prior_unit_price_usd`; the382 **current catalog tier** for the confirmed quantity overrides it.3834. **Selecting distractor freight.** `FR-DIS-*` / `destination="Distractor route"` /384 `status="mismatch"` are never quoted — even if `status` looks "active".3855. **Dropping a stale/expired freight option.** Include it but flag `STALE` /386 `source_is_stale=true` / `road_quote_invalid_or_stale=true` + a `freight_warning`.3876. **Treating `customs_border_risk` as `route_risk`.** A medium route risk driven by388 shelf-life/transit is still `customs_border_risk = LOW`; only explicit customs/border389 notes raise it.3907. **Keeping a due_date on a paid milestone.** Fully paid ⇒ `due_date = null`.3918. **Marking a paid milestone RECOGNIZED without a revenue journal.** Paid + no392 `revenue-journals` record ⇒ `REQUIRED_MISSING` / `MISSING_REVENUE_JOURNAL` and a393 `RECORD_REVENUE_MS<N>` accounting action (`DEFERRED_REVENUE` →394 `IMPLEMENTATION_SERVICES_REVENUE`, owner `ACCOUNTING`).3959. **Sending a collection notice on a not-yet-due invoice.** `due_date > as_of` ⇒396 `MONITOR_UNPAID_NOT_DUE` (owner `ACCOUNT_MANAGEMENT`), not `SEND_COLLECTION_NOTICE`.39710. **Recommending a stale/high-risk mode.** Exclude stale and HIGH-customs options398 before picking the lowest `grand_total`; default cost-effective choice is `SEA`.39911. **Setting `freight_reconfirmation_required = false`.** It is `true` whenever400 freight options are quoted (policy `POL-FREIGHT-RECONFIRM`).40112. **Wrong money type.** Use 2-decimal USD dollar amounts, not integer cents.40213. **Pulling the wrong RFQ/quote for a customer.** Several customers have multiple403 RFQs (incl. draft/budgetary distractors). Reconcile only the id named in the prompt.40414. **Forgetting WHO documentation.** IEHK / `emergency_health_kit` modules ⇒405 `who_documentation_required = true`.40615. **Mismatching enum tokens across templates.** Two reconciliation templates use407 different recognition tokens (`REQUIRED_MISSING` vs `MISSING_REVENUE_JOURNAL`) and408 different collection/invite tokens (`COLLECT_UNPAID_MILESTONE`/409 `SEND_EVENT_INVITATION` vs `MONITOR_UNPAID_NOT_DUE`/`SEND_COLLECTION_NOTICE`/410 `SEND_BRIEFING_INVITE`). Always copy the enum values **declared in the specific411 answer_template.json** for the task at hand.412413---414415## 6. Worked decision flow (apply per task)4164171. Identify the family (quote vs reconciliation) and the exact entity id(s) from the418 prompt.4192. Fetch the named record(s) by id; fetch related collections via filter; fetch420 `/api/policies`.4213. Reconcile quantities/date against the API; select catalog tier / milestones /422 freight options; classify states using the rules above.4234. Map every field to the **exact** template field names + enum tokens.4245. Compute money (2dp), dates (ISO), totals, and outstanding/recognized amounts.4256. Emit **one JSON object** only — no prose, no fences — matching426 `input/payloads/answer_template.json`.