SKILL: MedBridge Sales Ops — Quote & Reconciliation Decision Packages
Executable field-craft for producing account/finance-ready JSON from the MedBridge Sales Ops remote API. Two task families share one environment; the output schema is dictated per-task by input/payloads/answer_template.json. Always read that template first and mirror its exact field names, nesting, and enum values — quote tasks come in at least three template variants and reconciliation tasks in at least two. Do NOT assume a single canonical schema.
0. Environment & API discipline
- Source of truth = the remote HTTP JSON API. Base URL is given to the runner as
API_BASE_URL(env file:<remote-env-url>). Never start a local server; never read anyenv/directory. - Endpoints:
GET /health,GET /api(metadata),GET /api/<collection>,GET /api/<collection>?<key>=<value>(case-insensitive, AND-combined, 2dp numeric tolerance, matches nested keys),GET /api/<collection>/<id>detail-by-id,GET /api/search?q=<text>(cross-collection substring, ≤100 hits). - Detail-by-id is available ONLY for:
customers,products,rfqs,quotes,freight-quotes,opportunities. Forinvoices,payments,revenue-journals,events,vouchers,policiesuse the listing endpoint with a filter, or/api/search. - Canonical filter keys:
freight-quotes?quote_id=…;invoices?opportunity_id=…;payments?opportunity_id=…;revenue-journals?opportunity_id=…;events?customer_id=…(or?opportunity_id=…);vouchers?customer_id=…;quotes?rfq_id=…. - Money = USD, 2 decimals. Dates = ISO
YYYY-MM-DD. Use stable record IDs verbatim (e.g.Q-TR-WC-1187,FR-WC-AIR,OPP-TR-HELIOS,CUST-MERIDIAN). - The shared business/"as-of" date across tasks is 2026-06-01 (the API
generated_atand the quote_date used throughout). Use it asas_of_datewhen a reconciliation template requires one and the prompt does not state another. - Reconcile the prompt's named entity (customer/quote/opportunity ID) against the API; trust the API for IDs, amounts, dates, statuses, and tier facts. One exception for the display name only — see §6.
- Output: only valid JSON matching the task's template. No markdown fences, no prose. Money as numbers (cent-level precision, e.g.
42480.0). Nulls where the template allows (null), not omitted.
1. Task routing
Read the prompt and the template together:
- QUOTE family — prompt references a
Q-TR-…quote id or anRFQ-TR-…rfq id; template top-level keys arequote_summary/quote_header/pricing+ freight/lines + policy/controls. → §2. - RECONCILIATION family — prompt references an
OPP-TR-…opportunity id +CUST-…; template top-level keys areaccount_status/engagement_reconciliation+ milestones + actions/tasks. → §3.
2. QUOTE tasks
2.1 Fetch the source records
- If a
Q-TR-…id is given:GET /api/quotes/<id>(detail). Useconfirmed_quantity,primary_product_code/line_items[].product_code,destination,incoterm,quote_date,customer_id. ThenGET /api/products/<product_code>for tiers/shelf-life/article_number,GET /api/customers/<customer_id>,GET /api/freight-quotes?quote_id=<id>, andGET /api/policies. - If an
RFQ-TR-…id is given (indicative/module quote):GET /api/rfqs/<id>. There may be no quote record at all (quotes?rfq_id=…can return 0) — build the answer from the RFQ'srequested_modules[]+ the product catalog + customer + policies. Userequested_modules[].quantityas the line quantity andrequested_modules[].product_codeto fetch each product.
2.2 Catalog tier pricing (the core repricing rule)
- Each product has
price_tiers[]keyed by quantity bands (min_qty,max_qtywherenull= unbounded). Select the single tier wheremin_qty <= confirmed_quantity <= max_qty(null max = open-ended). unit_price= that tier'sunit_price_usd.lead_time_days= that tier'slead_time_days.shelf_life_months= the product-levelshelf_life_months(constant across tiers).- Always reprice to the current catalog tier for the confirmed quantity. Ignore any
prior_quote_quantity/prior_unit_price_usdon the quote line — those are distractors. (A revision that moved into a higher-quantity tier lowers the unit price; the catalog tier overrides the prior price.) - EXW total / line total =
quantity × unit_price. For multi-line module quotes,grand_total= Σ line totals. - Quantity source: quote
line_items[0].confirmed_quantity(single-product revisions) orrequested_modules[].quantity(RFQ).
2.3 Freight option selection & validity
- Pull
freight-quotes?quote_id=<id>. The set typically contains 3 real mode quotes (AIR, SEA, ROAD) plus distractors. Exclude distractors: any record whosedestinationis literally"Distractor route"or whoseidstarts withFR-DIS-. The surviving 3 (one per mode) are the freight options. Do not invent a mode that isn't present. - For each real freight quote compute:
freight_cost_usd=cost_usd.grand_total_usd=exw_total_usd + freight_cost_usd(for single-product EXW-plus-freight).valid_until= freight record'svalid_until.transit_days= the freight record'stransit_days_text(canonical string, e.g."4-6 days"). If the task template's examples show bare ranges, use"{transit_days_min}-{transit_days_max}".- Validity: a freight quote is VALID on the quote date iff
status == "active"ANDvalid_until >= quote_date. It is STALE/expired ifstatus == "stale"ORvalid_until < quote_date.
freight_reconfirmation_required= true always (policy: all freight rates need reconfirmation at final order and are valid only throughvalid_until).
2.4 Risk fields (template-dependent)
The risk representation differs by template — read it:
- risk_level + risk_flag style (e.g.
quote_summary/policy_flagstemplate):risk_level=route_risk.toUpperCase()→LOW|MEDIUM|HIGH.risk_flag=NONEwhenroute_riskislow;"{LEVEL}_BORDER_RISK"(e.g.MEDIUM_BORDER_RISK,HIGH_BORDER_RISK) whenroute_riskismedium/highand therisk_notesmention border/customs exposure. (A road lane with "Border risk medium" →MEDIUM_BORDER_RISK.) - customs_border_risk style (e.g.
pricing+transport_decisionstemplate): this measures customs/border exposure specifically, not general transit risk.HIGHwhenroute_risk == "high"orrisk_notesexplicitly flag customs/border risk as high;LOWotherwise — amediumroute_riskthat only describes transit/shelf-life (e.g. a long sea reefer lane) is reportedLOWfor customs/border. Paired fields:validity_status(VALID|STALE),source_is_stale(bool =validity_status == "STALE").
2.5 Recommended transport mode
recommended_mode= the freight option with the lowestgrand_total_usdamong the options that are VALID on the quote date (active + not expired). Stale/expired options are never recommended. (In practice SEA usually wins on cost among valid options; ROAD is often disqualified by staleness or HIGH border risk.)- Defensive tie-break: if the cheapest valid option carries HIGH customs/border risk, prefer the cheapest LOW-risk valid option and call out the risk in any warning field.
2.6 Payment terms & policy flags
- Look up
policiesand the customer record.- New NGO client (
customer.is_recurring == false/segment == "new_ngo"/payment_profile == "NEW_CLIENT_REVIEW"/ noclient_since) →payment_terms = "PREPAY_100"(New client prepayment policy). - Recurring NGO (
segment == "recurring_ngo") →NET_30_AFTER_POunless restricted grant terms override. - Recurring commercial / others → use
customer.payment_profile(typicallyNET_30_AFTER_PO).
- New NGO client (
customer_policy(where the template has it) =customer.segment.toUpperCase()(e.g.recurring_ngo→RECURRING_NGO).offer_validity_days=30(Standard quote validity policy: catalog pricing valid 30 calendar days from quote date; freight may expire sooner).
2.7 Exclusions / scope flags (critical misjudgment traps)
- EXW-only excludes freight. If the RFQ/quote has no real destination (
destinationcontains "pending"/"Destination pending" or is absent) ORincoterm_requested == "EXW"with no destination →quote_basis = "EXW_ONLY",freight_excluded = true, and do not emit any freight_options block. (Indicative EXW policy + EXW commercial scope policy: EXW excludes freight, insurance, duty, customs clearance, last-mile unless explicitly added.) Conversely, when a destination exists and the quoteincotermis "EXW plus freight options", show the freight triple withquote_basis = "EXW"or"EXW_PLUS_FREIGHT_OPTIONS"per the template. - Module-level granularity. For module/IEHK RFQs, emit one line per requested module only. Do NOT expand to component SKUs even though products carry a
components[]list and the RFQ may includecomponent_composition_distractors(those are explicitly "for medical review only"). (Module RFQ granularity policy.) Quantities come fromrequested_modules, not from component counts. - WHO documentation flag. Set
who_documentation_required = truefor IEHK-style / WHO-standard kit module quotes (the Interagency Emergency Health Kit is a WHO specification). There is no explicit flag field on the product/RFQ/policy — infer it from the IEHK/WHO kit context. Omit/false otherwise. (Cold-chain products may warrant a separate cold-chain documentation note — e.g. a lab-diagnostics quote whose customer_note mentions "Cold-chain documentation required" — but that is distinct from the WHO flag.) - Quote-revision distractors.
prior_quote_quantity/prior_unit_price_usdand anycomponent_composition_distractorsare noise; reprice from the live catalog tier.
2.8 Quote variant field maps (follow the task's template exactly)
- Variant A (
quote_summary+freight_options+policy_flags): single-product revision with freight.quote_summarycarriesquote_id, customer_id, quote_date, product_code, confirmed_quantity, unit_price_usd, lead_time_days, shelf_life_months, quote_basis="EXW", exw_total_usd.freight_options[](AIR/SEA/ROAD):freight_id, mode, freight_cost_usd, transit_days, valid_until, risk_level, risk_flag, grand_total_usd.policy_flags:recommended_mode, freight_reconfirmation_required, all_freight_options_valid_on_quote_date(true only if all 3 real options are active &valid_until>=quote_date),customer_policy, payment_terms. - Variant B (
quote_header+line_items+quote_controls): indicative EXW module quote.quote_header:rfq_id, customer_id, quote_date, currency="USD", quote_basis="EXW_ONLY".line_items[]:product_code, article_number, quantity, unit_price, lead_time_days, shelf_life_months, line_total.quote_controls:grand_total(=Σline_total),freight_excluded=true, payment_terms, offer_validity_days=30, who_documentation_required. - Variant C (
pricing+transport_decisions+client_warnings): single-product revision with stale-freight flagging.pricing:quote_id, customer_id, quote_date, product_code, confirmed_quantity, catalog_tier{min_quantity,max_quantity,unit_price_usd,lead_time_days,shelf_life_months}, exw_total_usd, payment_terms.transport_decisions.freight_options[]:freight_id, mode, freight_cost_usd, transit_days, valid_until, validity_status, source_is_stale, customs_border_risk, grand_total_usd; plusrecommended_mode, freight_reconfirmation_required.client_warnings:road_quote_invalid_or_stale(true if the ROAD option is stale/expired or HIGH risk),freight_warning(human sentence naming the stale/expired option and its risk),policy_terms{quote_basis, payment_terms, freight_reconfirmation_required}.
3. RECONCILIATION tasks
3.1 Fetch the engagement records
GET /api/opportunities/<opp_id> (detail) → GET /api/invoices?opportunity_id=<opp>, GET /api/payments?opportunity_id=<opp>, GET /api/revenue-journals?opportunity_id=<opp>, GET /api/events?customer_id=<cust> (and/or ?opportunity_id=), GET /api/vouchers?customer_id=<cust>, GET /api/customers/<cust>, GET /api/policies.
3.2 Milestone / phase mapping
- The opportunity's
phases[](sorted by presentation order) map 1:1 to milestones MS1, MS2, MS3, … in ascending order.phase_number(Variant A) = 1-based index. - Each phase has
invoice_id→ look up that invoice for amount/status/dates/paid.amount/invoice_total=invoice.amount_usd.paid_amount/amount_paid=invoice.paid_amount_usd.amount_unpaid=invoice.outstanding_amount_usd. - Match payments via
payment.invoice_idand revenue-journals viarevenue_journal.invoice_id(orphase_id).
3.3 State & status mapping
- invoice_state (Variant B):
invoice.status"paid"→PAID;"unpaid"→OPEN;"void"→VOID; elseUNKNOWN. - payment_state / payment_status:
PAIDwhen invoice fully paid (outstanding_amount_usd == 0andpaid_amount_usd == amount);PARTIALwhen0 < paid_amount < amount;UNPAIDwhenpaid_amount == 0and not paid;UNKNOWNwhen indeterminate. - due_date:
nullfor PAID milestones (paid milestones have no remaining due date). For unpaid/open milestones,due_date=invoice.due_date. (One official train answer reported a collection date that did not equal the invoicedue_date; when in doubt use the invoicedue_datefrom the API — it is the authoritative, reproducible value.) - recognition_status (per milestone):
RECOGNIZEDif the milestone is PAID and a postedrevenue-journalexists for its invoice/phase;MISSING_REVENUE_JOURNALif the milestone is PAID but no posted revenue-journal exists;NOT_REQUIRED_UNPAIDif the milestone is unpaid (revenue not yet earned);UNKNOWNotherwise. (Policy: when a milestone is complete and paid, create/verify revenue recognition from deferred revenue to income; unpaid future milestones stay outstanding.)
3.4 Reconciliation totals
won_amount=opportunity.won_amount_usd.phase_total_amount(Variant B) / milestone sum (Variant A) = Σphases[].amount_usd.opportunity_matches_phase_total/opportunity_matches_milestones= (Σ phases == won_amount).total_paid_amount= Σ postedpayments[].amount_usdfor the opportunity (= Σinvoice.paid_amount_usd).outstanding_balance=opportunity.outstanding_amount_usd=won_amount − total_paid_amount= Σ unpaidinvoice.outstanding_amount_usd. All three must agree.- Revenue-recognition summary (Variant A):
recognized_milestones= PAID milestones that have a posted journal;missing_required_milestones= PAID milestones with no posted journal (unpaid milestones are NOT "missing" — they're not required);recognized_amount= Σ journal amounts for recognized milestones; overallrecognition_status=COMPLETE_FOR_PAID_MILESTONESwhen every PAID milestone is recognized, else incomplete (flag the missing ones). opportunity_stage/stage=opportunity.stageuppercased:closed_won→WON,open→OPEN,closed_lost/lost→LOST.
3.5 Contact linkage
- Primary contact name =
opportunity.contact(alsoevent.primary_contactand the prompt-named person — they agree). Link it tocustomer_id(andopportunity_idin Variant A'scontactobject). Every follow-up task carries thiscontact_name.
3.6 Action routing (Variant B — invoice_actions + event_actions)
Accounting action (revenue recognition):
- If any PAID milestone lacks a posted revenue journal →
action = "RECORD_REVENUE_MS<N>",milestone_id = "MS<N>",amount= that milestone's amount,debit_account = "DEFERRED_REVENUE",credit_account = "IMPLEMENTATION_SERVICES_REVENUE",owner_queue = "ACCOUNTING". (These accounts mirror the posted revenue-journal records: debit "Deferred Revenue", credit "Implementation Services Revenue".)primary_accounting_actionmirrors thisaction. - If all PAID milestones are already recognized →
VERIFY_REVENUE_ONLY,milestone_id = "NONE",amount = 0, accountsNONE, ownerACCOUNTING. - If there are no paid milestones / nothing to recognize →
NO_ACCOUNTING_ACTION,NONE/NONE/NONE.
Collection action (unpaid milestones):
- For each unpaid milestone: if
invoice.due_date > as_of_date(not yet due) →MONITOR_UNPAID_NOT_DUE,owner_queue = "ACCOUNT_MANAGEMENT". Ifdue_date <= as_of_date(overdue) →SEND_COLLECTION_NOTICE,owner_queue = "COLLECTIONS". collection_task:action,milestone_id,amount(unpaid amount),due_date(the milestone's due date),owner_queue,contact_name. Top-levelcollection_actionmirrorscollection_task.action.- If everything is paid →
NO_COLLECTION_ACTION,milestone_id = "NONE",owner_queue = "NONE".
Event/invite action:
- Fetch the linked event (
events?customer_id=/?opportunity_id=) and its voucher (vouchers?customer_id=, matched byvoucher_code). event_status=event.status.toUpperCase()(scheduled→SCHEDULED,confirmed→CONFIRMED,active→ACTIVE,completed→COMPLETED,cancelled→CANCELLED).voucher:voucher_code,voucher_status=voucher.status.toUpperCase()(active→ACTIVE,draft→DRAFT,expired→EXPIRED,disabled→DISABLED);discount_amount=voucher.discount_percent(report the percent value as a 2-decimal number — the API storesdiscount_percent, not a dollar amount);max_uses=voucher.max_redemptions.invite_action:SEND_BRIEFING_INVITEwhen the event is scheduled/active/confirmed and the invite has not yet gone out (redemptions_used == 0);VERIFY_INVITE_SENTif already sent;NO_INVITE_ACTIONif cancelled/completed.invite_task:action,event_id,voucher_code,owner_queue = "ACCOUNT_MANAGEMENT"(matchesevent.follow_up_owner "Account Management"),contact_name,customer_id.
3.7 Follow-up tasks (Variant A — follow_up_tasks[])
Variant A uses a flat task list instead of the action objects. Emit tasks in this order:
- COLLECTION (one per unpaid milestone):
task_type = "COLLECTION",next_action = "COLLECT_UNPAID_MILESTONE",milestone_id = "MS<N>",amount_due= unpaid amount,due_date= milestone due date,event_id = null,voucher_code = null,linked_customer_id,linked_opportunity_id,contact_name,task_title(e.g."Milestone <N> collection - <customer name>"). - EVENT_INVITATION (one per linked event needing an invite):
task_type = "EVENT_INVITATION",next_action = "SEND_EVENT_INVITATION",event_id,voucher_code,milestone_id = null,amount_due = null,due_date(=as_of_date + 30 days, the standard follow-up window —- do not schedule after the event date),linked_customer_id,linked_opportunity_id,contact_name,task_title(e.g."Send celebration invite - <customer name>").
- Use
null(not omitted) for inapplicable fields (amount_due,milestone_id,event_id,voucher_code) so the array shape is uniform.
3.8 Reconciliation variant field maps
- Variant A (
account_status+milestones+revenue_recognition+event+follow_up_tasks): top-levelaccount_status{customer_id, customer_name, opportunity_id, opportunity_stage, won_amount, opportunity_matches_milestones, outstanding_balance, contact{name, linked_customer_id, linked_opportunity_id}};milestones[]{milestone_id, phase_number, invoice_total, payment_status, amount_paid, amount_unpaid, due_date|null, revenue_recognition_status};revenue_recognition{recognition_status, recognized_milestones[], missing_required_milestones[], recognized_amount};event{event_id, event_date, voucher_code, voucher_discount, voucher_max_uses};follow_up_tasks[]per §3.7. - Variant B (
engagement_reconciliation+invoice_actions+event_actions):engagement_reconciliation{as_of_date, opportunity_id, customer_id, customer_name, stage, won_amount, phase_total_amount, opportunity_matches_phase_total, total_paid_amount, outstanding_balance, primary_contact{contact_name, customer_id}, milestones[]{milestone_id, amount, invoice_state, payment_state, paid_amount, due_date|null, recognition_status}};invoice_actions{primary_accounting_action, collection_action, accounting_action{…}, collection_task{…}}per §3.6;event_actions{event_id, event_status, voucher{…}, invite_action, invite_task{…}}per §3.6.
4. Controlled enum values (exact strings)
quote_basis:EXW|EXW_ONLY|EXW_PLUS_FREIGHT_OPTIONS(per template).payment_terms:PREPAY_100|NET_30_AFTER_PO(other profiles pass through verbatim fromcustomer.payment_profile).mode:AIR|SEA|ROAD(uppercase).risk_level:LOW|MEDIUM|HIGH.risk_flag:NONE|MEDIUM_BORDER_RISK|HIGH_BORDER_RISK.customs_border_risk:LOW|HIGH(mid transit risk that is not customs-related reportsLOW).validity_status:VALID|STALE.opportunity_stage/stage:WON|OPEN|LOST.invoice_state:PAID|OPEN|VOID|UNKNOWN.payment_state/payment_status:PAID|PARTIAL|UNPAID|UNKNOWN.recognition_status/revenue_recognition_status:RECOGNIZED|MISSING_REVENUE_JOURNAL|NOT_REQUIRED_UNPAID|UNKNOWN.primary_accounting_action/accounting_action.action:RECORD_REVENUE_MS<N>|VERIFY_REVENUE_ONLY|NO_ACCOUNTING_ACTION.debit_account:DEFERRED_REVENUE|ACCOUNTS_RECEIVABLE|CASH|NONE.credit_account:IMPLEMENTATION_SERVICES_REVENUE|DEFERRED_REVENUE|ACCOUNTS_RECEIVABLE|NONE.owner_queue(accounting):ACCOUNTING|ACCOUNT_MANAGEMENT|NONE.collection_action:MONITOR_UNPAID_NOT_DUE|SEND_COLLECTION_NOTICE|NO_COLLECTION_ACTION.collection_task.owner_queue:ACCOUNT_MANAGEMENT|COLLECTIONS|NONE.event_status:SCHEDULED|ACTIVE|COMPLETED|CANCELLED|UNKNOWN.voucher_status:ACTIVE|DRAFT|EXPIRED|DISABLED|UNKNOWN.invite_action:SEND_BRIEFING_INVITE|VERIFY_INVITE_SENT|NO_INVITE_ACTION.invite_task.owner_queue:ACCOUNT_MANAGEMENT|EVENTS|NONE.- Variant A
task_type:COLLECTION|EVENT_INVITATION.next_action:COLLECT_UNPAID_MILESTONE|SEND_EVENT_INVITATION. milestone_id:MS1|MS2|MS3| … (NONEwhere the action isn't milestone-specific). Sort milestones ascending.
5. Owner-queue routing summary
- Revenue recognition (record/verify) →
ACCOUNTING. - Unpaid, not yet due (monitor) →
ACCOUNT_MANAGEMENT. - Unpaid, overdue (collection notice) →
COLLECTIONS. - Event invites / briefing →
ACCOUNT_MANAGEMENT(mirrorsevent.follow_up_owner).
6. Trust-API caveat — customer display name
The API is authoritative for IDs, amounts, dates, statuses, tiers, and freight facts. The one exception is the human-readable customer_name/customer_name field in reconciliation outputs: official answers use the customer name as written in the task prompt narrative, even when the API customer.name differs slightly (e.g. the API record may read "… Initiative" while the prompt/answer uses "… Alliance"). Resolve the customer by ID against the API, but report the prompt's display name. If the prompt does not name the customer, fall back to customer.name.
7. Known gold-answer anomalies (do not over-fit)
These are quirks observed in the official train gold. Follow the API-grounded rule for unseen tasks; be aware the gold may differ on these specific points:
- A reconciliation gold reported an unpaid-milestone
due_datethat did not equal the invoice'sdue_date. Reproduce frominvoice.due_date(the API value) — it is the only derivable source. - A module-quote gold
grand_totalexceeded the sum of itsline_totals by a small round amount with no documented surcharge in the API. Setgrand_total = Σ line_totalunless an explicit fee appears in the data. - A reconciliation gold used the prompt's customer name rather than the API
customer.name(see §6).
8. Output discipline checklist
- Match the task's
answer_template.jsonfield names, nesting, and order exactly; milestones ascendingMS1... - All money as JSON numbers, 2 decimals where the template shows decimals (e.g.
42480.0). Dates ISOYYYY-MM-DD.null(not omitted) where the template marks a field nullable. - Stable IDs verbatim. Enums in exact UPPERCASE above.
- No markdown fences, no commentary — only the JSON object.
- Before emitting: re-check (a) tier selected by confirmed quantity, (b) prior prices ignored, (c) distractor freight excluded, (d) freight validity vs quote_date, (e) payment terms by client type, (f) paid milestones →
due_date: nulland revenue-journal coverage, (g) outstanding balance == won − total_paid == Σ unpaid outstanding, (h) module quotes not exploded to components, (i) EXW-only when no destination.