SKILL: MedBridge Sales Ops — B2B Quote + Engagement-Reconciliation Tasks
Description & when this applies
Use this skill for "MedBridge Sales Ops" CRM/B2B task-group tasks. You get a free-text
prompt (an email-style request), a read-only JSON HTTP API, and an answer_template.json.
Your job: read real facts from the API and return ONLY a JSON object that fills the template
exactly. No markdown, no prose, no extra keys, no missing keys — just the JSON.
Two task families occur in this domain:
- Family A — Quote / Freight decision package. Prompt names a quote id (e.g.
Q-...) or
an RFQ id (e.g. RFQ-...), a product/module, a confirmed quantity, and a quote date. You
return catalog-tier pricing, an EXW total, freight options with totals, route risk, a
recommended transport mode, validity/reconfirmation flags, and payment terms.
- Family B — Engagement / milestone reconciliation. Prompt names an opportunity id
(
OPP-...) and customer id (CUST-...), a contact, often an event/voucher, and an "as-of"
business date. You reconcile won amount vs milestone phases, invoice/payment/outstanding
state, revenue-recognition coverage, and route the right accounting/collection/event tasks.
Match the family by the template shape: pricing/freight keys ⇒ A; milestones/revenue/
opportunity keys ⇒ B.
The data API
Base URL is provided by the runner (env var like API_BASE_URL / BASE_URL, or
http://127.0.0.1:<PORT>). All endpoints are GET and return JSON.
Collections: customers, products, rfqs, quotes, freight-quotes, policies, opportunities, invoices, payments, revenue-journals, events, vouchers.
Useful patterns:
- Single record:
GET /api/<collection>/<id> (e.g. /api/quotes/Q-..., /api/opportunities/OPP-...,
/api/products/<CODE>, /api/customers/<CUST-...>).
- List + filter:
GET /api/<collection>?field=value (server flattens nested keys, matches
case-insensitively, and numbers match their 2-decimal form). Returns
{collection, count, records:[...]}.
- Full-text:
GET /api/search?q=<text> searches every collection — great for resolving a
customer by name (e.g. ?q=NovaAid) or pulling everything tied to an account (?q=HELIOS
returns the customer, opportunity, invoices, payments, journals, event, and voucher in one call).
Resolving IDs from the prompt
- The prompt usually gives the key id directly (quote/rfq/opportunity/customer/event/voucher).
- If only a company NAME is given, use
/api/search?q=<name> to get the CUST-... id.
/api/products/<CODE> returns price_tiers, article_number, shelf_life_months, unit,
cold_chain_required, components.
API gotchas (hit during iteration)
revenue-journals records have NO customer_id field — filtering ?customer_id=... returns 0.
Filter by opportunity_id or fetch the whole collection and match on opportunity_id/phase_id.
- Freight lists contain DISTRACTORS: records with id
FR-DIS-* and/or destination
"Distractor route" — never include these in the answer.
- Trust the API record over the prompt for names: prompts paraphrase (e.g. prompt said "Helios
Health Alliance" but the customer record name is "Helios Health Initiative"; use the record).
quotes may carry prior_quote_quantity/prior_unit_price_usd — these are OLD values;
always re-price from the product price_tiers using the CONFIRMED quantity.
Output conventions (confirmed)
- Return ONLY the JSON object matching the template. No markdown fences, no commentary.
- Money: numbers with 2 decimals (e.g.
42480.00). Totals must reconcile exactly.
- Dates: ISO
YYYY-MM-DD.
- Enums: use the EXACT controlled values printed in the template (UPPERCASE). Map raw API
strings to the template enum (e.g. API
closed_won ⇒ template WON; API scheduled ⇒
SCHEDULED; API active ⇒ ACTIVE).
- Fill every template key. Use
null only where the template says "or null" and the value
genuinely does not apply (see paid-milestone due_date rule below).
- Echo ids/dates exactly as the API spells them.
Catalog tier & EXW math (Family A)
GET /api/products/<CODE>. Find the tier whose [min_qty, max_qty] contains the CONFIRMED
quantity (max_qty: null means open-ended upper bound). Take that tier's unit_price_usd,
lead_time_days, and the product's shelf_life_months.
exw_total_usd = confirmed_quantity * tier.unit_price_usd (2 decimals). Per line:
line_total = quantity * unit_price; grand_total = sum(line_totals).
quote_basis is EXW (single-product freight task) or EXW_ONLY (indicative no-destination task).
- For module RFQs: quote at MODULE line level only — one line per requested module, using the
module's own tier/article/shelf. IGNORE any
component_composition_distractors / component
breakdowns; do NOT expand modules into component SKUs.
Freight options (Family A)
GET /api/freight-quotes?quote_id=<Q-...>. The real options are the per-mode records named
FR-<product>-AIR, FR-<product>-SEA, FR-<product>-ROAD. EXCLUDE every FR-DIS-* /
"Distractor route" / wrong-shipment-size record.
- For each option:
freight_cost_usd = cost_usd; transit_days = the transit_days_text
(e.g. "4-6 days"); valid_until from the record; grand_total_usd = exw_total + freight_cost.
- Risk mapping:
route_risk low ⇒ risk_level LOW; medium ⇒ MEDIUM; high ⇒ HIGH.
risk_flag: NONE for low risk; for a medium ROAD border lane use MEDIUM_BORDER_RISK
(driven by the record's risk_notes/border language). High customs ROAD ⇒ high customs flag.
- In the richer template:
customs_border_risk mirrors the road/route risk (LOW/MEDIUM/HIGH);
source_is_stale = true ONLY when the freight record status == "stale";
validity_status = VALID when valid_until >= quote_date and not stale, else EXPIRED/STALE.
- Freight may be SHOWN even if stale/expired: when the template/prompt asks to flag a stale road
quote, include all three AIR/SEA/ROAD options and mark the road one stale/expired/high-risk and
set
road_quote_invalid_or_stale = true. (Distractor FR-DIS-* records are always dropped.)
recommended_mode — CONFIRMED RULE
recommended_mode = the cheapest VALID option by grand_total_usd, where "valid" means
NOT stale and NOT expired on the quote date. Exclude any stale/expired option from the choice.
It is COST-driven, not lowest-risk: if SEA is valid and cheaper than AIR, recommend SEA even
when SEA is MEDIUM risk and AIR is LOW.
Validity / reconfirmation / payment (Family A)
freight_reconfirmation_required = true (policy POL-FREIGHT-RECONFIRM: rates reconfirmed at
order; valid only through each freight valid_until).
all_freight_options_valid_on_quote_date = true only if EVERY shown real option has
valid_until >= quote_date.
- Catalog pricing validity = 30 days (
POL-QUOTE-VALIDITY) ⇒ offer_validity_days = 30.
- For indicative/no-destination RFQs:
quote_basis = EXW_ONLY, freight_excluded = true
(policy POL-INDICATIVE-EXW). Do not attach freight.
- WHO documentation: for IEHK / emergency-health-kit module quotes,
who_documentation_required = true.
Payment terms by account type (Family A & B)
- New NGO / no approved credit (
is_recurring=false, segment new_ngo,
payment_profile NEW_CLIENT_REVIEW) ⇒ PREPAY_100 (POL-NEW-CLIENT-PAYMENT).
- Recurring NGO / recurring commercial with PO terms (payment_profile
NET_30_AFTER_PO)
⇒ NET_30_AFTER_PO (POL-RECURRING-NGO-PAYMENT), unless restricted grant terms override.
- Prefer the customer's own
payment_profile value when present; it matches the policy code.
- Where a
customer_policy field is requested, the policy id (e.g. POL-RECURRING-NGO-PAYMENT)
is the clean choice.
Engagement / milestone reconciliation (Family B)
Gather: GET /api/opportunities/<OPP-...>, GET /api/customers/<CUST-...>,
GET /api/invoices?opportunity_id=<OPP-...>, GET /api/payments?opportunity_id=<OPP-...>,
revenue-journals (fetch all, match on opportunity_id/phase_id), and the named event/voucher.
Treat the prompt's "as-of"/current business date as as_of_date.
Header / totals
stage: closed_won ⇒ WON (else OPEN/LOST).
won_amount from opportunity won_amount_usd (2 decimals).
phase_total_amount = sum of phase amount_usd. opportunity_matches_* = (won == phase total).
total_paid_amount = sum of paid_amount_usd across invoices (or posted payments).
outstanding_balance = sum of invoice outstanding_amount_usd (= opportunity
outstanding_amount_usd).
primary_contact = the opportunity contact / matching customer contact named in the prompt.
customer_name from the customer record.
Milestones (one per phase, ascending)
milestone_id: if the template enum is MS1 | MS2 | MS3, use POSITIONAL ids in phase order
(MS1, MS2, MS3) — NOT the raw MER-P1 phase ids. If the template instead says "stable phase or
invoice id", use the phase id (HEL-P1) consistently.
amount / invoice_total = phase/invoice amount_usd.
invoice_state: paid invoice ⇒ PAID; unpaid invoice ⇒ OPEN (void ⇒ VOID).
payment_state: PAID if fully paid, UNPAID if nothing paid, PARTIAL if some paid.
paid_amount / amount_paid = invoice paid_amount_usd; amount_unpaid = outstanding_amount_usd.
- due_date — CONFIRMED HARD RULE: a PAID milestone's
due_date MUST be null. Only an
UNPAID/OPEN milestone carries its invoice due_date. (Setting a paid milestone's due_date to
the invoice date is incorrect.)
recognition_status / revenue_recognition_status:
RECOGNIZED — milestone paid AND a posted revenue-journal exists for it.
MISSING_REVENUE_JOURNAL (a.k.a. REQUIRED_MISSING) — milestone PAID but NO journal exists.
NOT_REQUIRED_UNPAID — milestone unpaid (recognition not yet required).
Revenue-recognition summary
- Recognize revenue ONLY for PAID milestones. A paid-but-missing-journal milestone is the trigger
for a RECORD_REVENUE action.
recognition_status (summary): COMPLETE_FOR_PAID_MILESTONES if every paid milestone has a
journal; MISSING_FOR_PAID_MILESTONES if any paid milestone lacks one; NOT_REQUIRED if none paid.
recognized_milestones = paid milestones WITH a journal; recognized_amount = sum of their amounts.
missing_required_milestones = paid milestones WITHOUT a journal.
Accounting action routing — CONFIRMED
- If a paid milestone is missing its journal (e.g. MS2):
primary_accounting_action /
accounting_action.action = RECORD_REVENUE_MS2, milestone_id = that milestone,
amount = its amount, debit_account = DEFERRED_REVENUE,
credit_account = IMPLEMENTATION_SERVICES_REVENUE, owner_queue = ACCOUNTING.
(debit/credit mirror the revenue-journal record's accounts.)
- If all paid milestones already recognized ⇒
VERIFY_REVENUE_ONLY; if nothing to do ⇒
NO_ACCOUNTING_ACTION with milestone NONE and accounts NONE.
Collection action routing — CONFIRMED
- Pick the unpaid milestone. Compare its due_date to
as_of_date:
- due_date in the FUTURE (not yet due) ⇒
collection_action = MONITOR_UNPAID_NOT_DUE,
owner_queue = ACCOUNT_MANAGEMENT (NOT COLLECTIONS). Using COLLECTIONS here is incorrect
while the milestone is not yet due.
- due_date PAST (overdue) ⇒
SEND_COLLECTION_NOTICE, owner_queue = COLLECTIONS.
- no unpaid milestone ⇒
NO_COLLECTION_ACTION, milestone NONE.
collection_task: milestone_id, amount = outstanding, due_date = the unpaid invoice due_date
(NOT null — it is unpaid), contact_name from the account contact.
- Accounting vs collection are SEPARATE routings: revenue gaps go to ACCOUNTING; unpaid balances
go to ACCOUNT_MANAGEMENT/COLLECTIONS. Do not conflate them.
Event / voucher actions
event_status from event status mapped to the enum (scheduled⇒SCHEDULED, etc.).
- voucher:
voucher_status from voucher status (active⇒ACTIVE); discount_amount =
voucher discount_percent; max_uses = voucher max_redemptions.
- For a scheduled briefing/celebration:
invite_action = SEND_BRIEFING_INVITE (or
SEND_EVENT_INVITATION per template). invite_task.owner_queue = ACCOUNT_MANAGEMENT
(matches event follow_up_owner "Account Management"); contact_name & customer_id from records.
- For simpler templates, follow_up_tasks is a list: one COLLECTION task for the unpaid milestone
(next_action
COLLECT_UNPAID_MILESTONE, with milestone_id/amount_due set, event/voucher null)
and one EVENT_INVITATION task (next_action SEND_EVENT_INVITATION, with event_id/voucher_code
set, milestone_id/amount_due null). The COLLECTION task due_date = the unpaid invoice due_date;
the EVENT task due_date = the event_date.
Common misjudgments / exclusion rules
- Do NOT include
FR-DIS-* / "Distractor route" / wrong-shipment-size freight records.
- Do NOT price from
prior_unit_price_usd; always re-tier from the product on the confirmed qty.
- Do NOT expand modules into components; quote module-level only and ignore component distractors.
- Do NOT give a PAID milestone a due_date — it must be
null.
- Do NOT recommend the lowest-RISK mode; recommend the cheapest VALID (non-stale) mode.
- Do NOT route a not-yet-due unpaid balance to COLLECTIONS — that's ACCOUNT_MANAGEMENT/MONITOR.
- Do NOT recognize revenue for unpaid milestones; only paid ones (and flag paid-but-missing-journal).
- Do NOT filter revenue-journals by customer_id (field absent) — use opportunity_id.
- Do NOT invent enum values — copy the exact tokens from the template; map raw API strings to them.
Final reminder
Return ONLY the JSON object that matches the provided answer_template.json — correct keys,
controlled enum values, 2-decimal money, ISO dates, and nulls only where required. No markdown.
1---2name: reflect-3-attempt-03-233description: SKILL: MedBridge Sales Ops — B2B Quote + Engagement-Reconciliation Tasks4---5# SKILL: MedBridge Sales Ops — B2B Quote + Engagement-Reconciliation Tasks67## Description & when this applies8Use this skill for "MedBridge Sales Ops" CRM/B2B task-group tasks. You get a free-text9prompt (an email-style request), a read-only JSON HTTP API, and an `answer_template.json`.10Your job: read real facts from the API and return ONLY a JSON object that fills the template11exactly. No markdown, no prose, no extra keys, no missing keys — just the JSON.1213Two task families occur in this domain:1415- **Family A — Quote / Freight decision package.** Prompt names a quote id (e.g. `Q-...`) or16 an RFQ id (e.g. `RFQ-...`), a product/module, a confirmed quantity, and a quote date. You17 return catalog-tier pricing, an EXW total, freight options with totals, route risk, a18 recommended transport mode, validity/reconfirmation flags, and payment terms.19- **Family B — Engagement / milestone reconciliation.** Prompt names an opportunity id20 (`OPP-...`) and customer id (`CUST-...`), a contact, often an event/voucher, and an "as-of"21 business date. You reconcile won amount vs milestone phases, invoice/payment/outstanding22 state, revenue-recognition coverage, and route the right accounting/collection/event tasks.2324Match the family by the template shape: pricing/freight keys ⇒ A; milestones/revenue/25opportunity keys ⇒ B.2627---2829## The data API3031Base URL is provided by the runner (env var like `API_BASE_URL` / `BASE_URL`, or32`http://127.0.0.1:<PORT>`). All endpoints are GET and return JSON.3334Collections: `customers, products, rfqs, quotes, freight-quotes, policies, opportunities,35invoices, payments, revenue-journals, events, vouchers`.3637Useful patterns:38- Single record: `GET /api/<collection>/<id>` (e.g. `/api/quotes/Q-...`, `/api/opportunities/OPP-...`,39 `/api/products/<CODE>`, `/api/customers/<CUST-...>`).40- List + filter: `GET /api/<collection>?field=value` (server flattens nested keys, matches41 case-insensitively, and numbers match their 2-decimal form). Returns42 `{collection, count, records:[...]}`.43- Full-text: `GET /api/search?q=<text>` searches every collection — great for resolving a44 customer by name (e.g. `?q=NovaAid`) or pulling everything tied to an account (`?q=HELIOS`45 returns the customer, opportunity, invoices, payments, journals, event, and voucher in one call).4647### Resolving IDs from the prompt48- The prompt usually gives the key id directly (quote/rfq/opportunity/customer/event/voucher).49- If only a company NAME is given, use `/api/search?q=<name>` to get the `CUST-...` id.50- `/api/products/<CODE>` returns `price_tiers`, `article_number`, `shelf_life_months`, `unit`,51 `cold_chain_required`, `components`.5253### API gotchas (hit during iteration)54- `revenue-journals` records have NO `customer_id` field — filtering `?customer_id=...` returns 0.55 Filter by `opportunity_id` or fetch the whole collection and match on `opportunity_id`/`phase_id`.56- Freight lists contain DISTRACTORS: records with id `FR-DIS-*` and/or `destination`57 "Distractor route" — never include these in the answer.58- Trust the API record over the prompt for names: prompts paraphrase (e.g. prompt said "Helios59 Health Alliance" but the customer record name is "Helios Health Initiative"; use the record).60- `quotes` may carry `prior_quote_quantity`/`prior_unit_price_usd` — these are OLD values;61 always re-price from the product `price_tiers` using the CONFIRMED quantity.6263---6465## Output conventions (confirmed)66- Return ONLY the JSON object matching the template. No markdown fences, no commentary.67- Money: numbers with 2 decimals (e.g. `42480.00`). Totals must reconcile exactly.68- Dates: ISO `YYYY-MM-DD`.69- Enums: use the EXACT controlled values printed in the template (UPPERCASE). Map raw API70 strings to the template enum (e.g. API `closed_won` ⇒ template `WON`; API `scheduled` ⇒71 `SCHEDULED`; API `active` ⇒ `ACTIVE`).72- Fill every template key. Use `null` only where the template says "or null" and the value73 genuinely does not apply (see paid-milestone due_date rule below).74- Echo ids/dates exactly as the API spells them.7576---7778## Catalog tier & EXW math (Family A)791. `GET /api/products/<CODE>`. Find the tier whose `[min_qty, max_qty]` contains the CONFIRMED80 quantity (`max_qty: null` means open-ended upper bound). Take that tier's `unit_price_usd`,81 `lead_time_days`, and the product's `shelf_life_months`.822. `exw_total_usd = confirmed_quantity * tier.unit_price_usd` (2 decimals). Per line:83 `line_total = quantity * unit_price`; `grand_total = sum(line_totals)`.843. `quote_basis` is `EXW` (single-product freight task) or `EXW_ONLY` (indicative no-destination task).854. For module RFQs: quote at MODULE line level only — one line per requested module, using the86 module's own tier/article/shelf. IGNORE any `component_composition_distractors` / component87 breakdowns; do NOT expand modules into component SKUs.8889## Freight options (Family A)901. `GET /api/freight-quotes?quote_id=<Q-...>`. The real options are the per-mode records named91 `FR-<product>-AIR`, `FR-<product>-SEA`, `FR-<product>-ROAD`. EXCLUDE every `FR-DIS-*` /92 "Distractor route" / wrong-shipment-size record.932. For each option: `freight_cost_usd = cost_usd`; `transit_days` = the `transit_days_text`94 (e.g. `"4-6 days"`); `valid_until` from the record; `grand_total_usd = exw_total + freight_cost`.953. Risk mapping:96 - `route_risk` low ⇒ `risk_level` LOW; medium ⇒ MEDIUM; high ⇒ HIGH.97 - `risk_flag`: NONE for low risk; for a medium ROAD border lane use `MEDIUM_BORDER_RISK`98 (driven by the record's risk_notes/border language). High customs ROAD ⇒ high customs flag.99 - In the richer template: `customs_border_risk` mirrors the road/route risk (LOW/MEDIUM/HIGH);100 `source_is_stale = true` ONLY when the freight record `status == "stale"`;101 `validity_status` = VALID when `valid_until >= quote_date` and not stale, else EXPIRED/STALE.1024. Freight may be SHOWN even if stale/expired: when the template/prompt asks to flag a stale road103 quote, include all three AIR/SEA/ROAD options and mark the road one stale/expired/high-risk and104 set `road_quote_invalid_or_stale = true`. (Distractor `FR-DIS-*` records are always dropped.)105106### recommended_mode — CONFIRMED RULE107`recommended_mode` = the **cheapest VALID option by `grand_total_usd`**, where "valid" means108NOT stale and NOT expired on the quote date. Exclude any stale/expired option from the choice.109It is COST-driven, not lowest-risk: if SEA is valid and cheaper than AIR, recommend SEA even110when SEA is MEDIUM risk and AIR is LOW.111112### Validity / reconfirmation / payment (Family A)113- `freight_reconfirmation_required` = true (policy `POL-FREIGHT-RECONFIRM`: rates reconfirmed at114 order; valid only through each freight `valid_until`).115- `all_freight_options_valid_on_quote_date` = true only if EVERY shown real option has116 `valid_until >= quote_date`.117- Catalog pricing validity = 30 days (`POL-QUOTE-VALIDITY`) ⇒ `offer_validity_days = 30`.118- For indicative/no-destination RFQs: `quote_basis = EXW_ONLY`, `freight_excluded = true`119 (policy `POL-INDICATIVE-EXW`). Do not attach freight.120- WHO documentation: for IEHK / emergency-health-kit module quotes, `who_documentation_required = true`.121122### Payment terms by account type (Family A & B)123- New NGO / no approved credit (`is_recurring=false`, segment `new_ngo`,124 payment_profile `NEW_CLIENT_REVIEW`) ⇒ `PREPAY_100` (`POL-NEW-CLIENT-PAYMENT`).125- Recurring NGO / recurring commercial with PO terms (payment_profile `NET_30_AFTER_PO`)126 ⇒ `NET_30_AFTER_PO` (`POL-RECURRING-NGO-PAYMENT`), unless restricted grant terms override.127- Prefer the customer's own `payment_profile` value when present; it matches the policy code.128- Where a `customer_policy` field is requested, the policy id (e.g. `POL-RECURRING-NGO-PAYMENT`)129 is the clean choice.130131---132133## Engagement / milestone reconciliation (Family B)134135Gather: `GET /api/opportunities/<OPP-...>`, `GET /api/customers/<CUST-...>`,136`GET /api/invoices?opportunity_id=<OPP-...>`, `GET /api/payments?opportunity_id=<OPP-...>`,137revenue-journals (fetch all, match on `opportunity_id`/`phase_id`), and the named event/voucher.138Treat the prompt's "as-of"/current business date as `as_of_date`.139140### Header / totals141- `stage`: `closed_won` ⇒ `WON` (else OPEN/LOST).142- `won_amount` from opportunity `won_amount_usd` (2 decimals).143- `phase_total_amount` = sum of phase `amount_usd`. `opportunity_matches_*` = (won == phase total).144- `total_paid_amount` = sum of `paid_amount_usd` across invoices (or posted payments).145- `outstanding_balance` = sum of invoice `outstanding_amount_usd` (= opportunity146 `outstanding_amount_usd`).147- `primary_contact` = the opportunity `contact` / matching customer contact named in the prompt.148- `customer_name` from the customer record.149150### Milestones (one per phase, ascending)151- `milestone_id`: if the template enum is `MS1 | MS2 | MS3`, use POSITIONAL ids in phase order152 (MS1, MS2, MS3) — NOT the raw `MER-P1` phase ids. If the template instead says "stable phase or153 invoice id", use the phase id (`HEL-P1`) consistently.154- `amount` / `invoice_total` = phase/invoice `amount_usd`.155- `invoice_state`: paid invoice ⇒ `PAID`; unpaid invoice ⇒ `OPEN` (void ⇒ VOID).156- `payment_state`: `PAID` if fully paid, `UNPAID` if nothing paid, `PARTIAL` if some paid.157- `paid_amount` / `amount_paid` = invoice `paid_amount_usd`; `amount_unpaid` = `outstanding_amount_usd`.158- **due_date — CONFIRMED HARD RULE:** a PAID milestone's `due_date` MUST be `null`. Only an159 UNPAID/OPEN milestone carries its invoice `due_date`. (Setting a paid milestone's due_date to160 the invoice date is incorrect.)161- `recognition_status` / `revenue_recognition_status`:162 - `RECOGNIZED` — milestone paid AND a posted revenue-journal exists for it.163 - `MISSING_REVENUE_JOURNAL` (a.k.a. `REQUIRED_MISSING`) — milestone PAID but NO journal exists.164 - `NOT_REQUIRED_UNPAID` — milestone unpaid (recognition not yet required).165166### Revenue-recognition summary167- Recognize revenue ONLY for PAID milestones. A paid-but-missing-journal milestone is the trigger168 for a RECORD_REVENUE action.169- `recognition_status` (summary): `COMPLETE_FOR_PAID_MILESTONES` if every paid milestone has a170 journal; `MISSING_FOR_PAID_MILESTONES` if any paid milestone lacks one; `NOT_REQUIRED` if none paid.171- `recognized_milestones` = paid milestones WITH a journal; `recognized_amount` = sum of their amounts.172- `missing_required_milestones` = paid milestones WITHOUT a journal.173174### Accounting action routing — CONFIRMED175- If a paid milestone is missing its journal (e.g. MS2): `primary_accounting_action` /176 `accounting_action.action` = `RECORD_REVENUE_MS2`, `milestone_id` = that milestone,177 `amount` = its amount, `debit_account` = `DEFERRED_REVENUE`,178 `credit_account` = `IMPLEMENTATION_SERVICES_REVENUE`, `owner_queue` = `ACCOUNTING`.179 (debit/credit mirror the revenue-journal record's accounts.)180- If all paid milestones already recognized ⇒ `VERIFY_REVENUE_ONLY`; if nothing to do ⇒181 `NO_ACCOUNTING_ACTION` with milestone `NONE` and accounts `NONE`.182183### Collection action routing — CONFIRMED184- Pick the unpaid milestone. Compare its due_date to `as_of_date`:185 - due_date in the FUTURE (not yet due) ⇒ `collection_action` = `MONITOR_UNPAID_NOT_DUE`,186 `owner_queue` = `ACCOUNT_MANAGEMENT` (NOT `COLLECTIONS`). Using COLLECTIONS here is incorrect187 while the milestone is not yet due.188 - due_date PAST (overdue) ⇒ `SEND_COLLECTION_NOTICE`, `owner_queue` = `COLLECTIONS`.189 - no unpaid milestone ⇒ `NO_COLLECTION_ACTION`, milestone `NONE`.190- `collection_task`: milestone_id, amount = outstanding, due_date = the unpaid invoice due_date191 (NOT null — it is unpaid), contact_name from the account contact.192- Accounting vs collection are SEPARATE routings: revenue gaps go to ACCOUNTING; unpaid balances193 go to ACCOUNT_MANAGEMENT/COLLECTIONS. Do not conflate them.194195### Event / voucher actions196- `event_status` from event `status` mapped to the enum (`scheduled`⇒SCHEDULED, etc.).197- voucher: `voucher_status` from voucher `status` (`active`⇒ACTIVE); `discount_amount` =198 voucher `discount_percent`; `max_uses` = voucher `max_redemptions`.199- For a scheduled briefing/celebration: `invite_action` = `SEND_BRIEFING_INVITE` (or200 `SEND_EVENT_INVITATION` per template). `invite_task.owner_queue` = `ACCOUNT_MANAGEMENT`201 (matches event `follow_up_owner` "Account Management"); contact_name & customer_id from records.202- For simpler templates, follow_up_tasks is a list: one COLLECTION task for the unpaid milestone203 (next_action `COLLECT_UNPAID_MILESTONE`, with milestone_id/amount_due set, event/voucher null)204 and one EVENT_INVITATION task (next_action `SEND_EVENT_INVITATION`, with event_id/voucher_code205 set, milestone_id/amount_due null). The COLLECTION task due_date = the unpaid invoice due_date;206 the EVENT task due_date = the event_date.207208---209210## Common misjudgments / exclusion rules211- Do NOT include `FR-DIS-*` / "Distractor route" / wrong-shipment-size freight records.212- Do NOT price from `prior_unit_price_usd`; always re-tier from the product on the confirmed qty.213- Do NOT expand modules into components; quote module-level only and ignore component distractors.214- Do NOT give a PAID milestone a due_date — it must be `null`.215- Do NOT recommend the lowest-RISK mode; recommend the cheapest VALID (non-stale) mode.216- Do NOT route a not-yet-due unpaid balance to COLLECTIONS — that's ACCOUNT_MANAGEMENT/MONITOR.217- Do NOT recognize revenue for unpaid milestones; only paid ones (and flag paid-but-missing-journal).218- Do NOT filter revenue-journals by customer_id (field absent) — use opportunity_id.219- Do NOT invent enum values — copy the exact tokens from the template; map raw API strings to them.220221## Final reminder222Return ONLY the JSON object that matches the provided `answer_template.json` — correct keys,223controlled enum values, 2-decimal money, ISO dates, and nulls only where required. No markdown.