MedBridge Sales Ops — Account-Ready JSON Packages
This skill turns MedBridge Sales Ops API records into the exact JSON an account
manager needs. The API is the single source of truth — never invent or
recall numbers; every value must trace back to a fetched record. Output is
JSON only, matching the task's answer_template.json exactly (no markdown,
no prose, no extra keys).
Golden rules (apply to every task)
- Read the template first. Open
input/payloads/answer_template.json. It declares the exact keys, value types, controlled enums, and string conventions the grader expects. Mirror its structure 1:1 — same keys, same order is safe, no additions, no omissions. When the template embeds an enum list in a value (e.g."enum: WON | OPEN | LOST"), you MUST output one of those literal tokens. - The API source is authoritative; the prompt narrative is intent, not data. Confirmed quantities, quote dates, and IDs are stated in the prompt but always reconcile them against the quote/RFQ record. If the prompt and a record disagree, prefer the record the prompt points you to, and let the record's own fields (tier, status, dates) drive computed values.
- Money at cent precision, dates ISO
YYYY-MM-DD, IDs verbatim. Use the stable record IDs exactly as returned (e.g.FR-WC-SEA,INV-HELIOS-P1). - Beware the loose filter. List endpoints (
?quote_id=...,?opportunity_id=...) match a value anywhere in the record and return extra unrelated rows. Never trust a filtered list blindly. Fetch the full collection and filter precisely yourself on the field you care about, and drop any record whosestatusmarks it as not-real (see exclusion rules). - Compute, don't copy hopeful-looking fields. Derive totals and statuses from primitive fields (quantity × tier price, valid_until vs quote_date, invoice/payment/journal cross-checks). Do not assume a convenient pre-aggregated field is correct.
API quick reference
Base URL comes from the runner (env API_BASE_URL / BASE_URL, or
http://127.0.0.1:<PORT>). Reach it with curl -s.
| Need | Endpoint |
|---|---|
| Customer (payment profile, segment, contacts, recurring flag) | /api/customers/<id> |
| Product (price_tiers, shelf_life_months, article_number, cold_chain) | /api/products/<code> |
| RFQ (requested modules/quantities, incoterm, destination) | /api/rfqs/<id> |
| Quote (line_items, confirmed_quantity, quote_date, incoterm) | /api/quotes/<id> |
| Freight quotes (cost, mode, route_risk, valid_until, status) | /api/freight-quotes (fetch all, filter by quote_id) |
| Policies (payment terms, validity, freight reconfirm, revrec) | /api/policies |
| Opportunity (stage, won_amount, phases, contact) | /api/opportunities/<id> |
| Invoices / Payments / Revenue journals | /api/invoices, /api/payments, /api/revenue-journals |
| Events / Vouchers | /api/events, /api/vouchers |
| Last resort lookup | /api/search?q=<text> |
Detail endpoints (/api/<collection>/<id>) return one record. Prefer them when
you know the ID. For freight/invoice/payment/journal work, pull the whole
collection and match precisely — the safest path.
Choosing the task family
Read the prompt and template, then route to one SOP:
- Quote + Freight decision package — prompt gives a quote ID with a confirmed
quantity and asks for revised EXW pricing, freight/transport options, route
risk, recommended mode, validity/reconfirmation, payment terms. Template has
freight_options/transport_decisions. → SOP A. - Indicative / module-level EXW quote — prompt references an RFQ, asks to
quote at module level only with freight excluded (often no destination, new
client). Template has
quote_header+line_items+quote_controls. → SOP B. - Engagement / receivables reconciliation — prompt names an opportunity and
customer, asks to reconcile milestones, invoices, payments, revenue
recognition, and route follow-up tasks (collection + event invite). Template
has
milestones,revenue_recognition/invoice_actions,event/event_actions. → SOP C.
SOP A — Quote + Freight decision package
Fetch: the quote, its customer, the primary product, all freight-quotes, and policies.
A1. Catalog tier & EXW pricing
- Confirmed quantity = the quote's
confirmed_quantity(and/or line item). The catalog tier override replaces any prior/old unit price in the line — use the current catalog tier, neverprior_unit_price_usd. - Select the tier from
product.price_tierswheremin_qty ≤ confirmed_quantity ≤ max_qty. Amax_qtyofnullmeans unbounded (open-ended top tier). Pick exactly one tier. unit_price_usdandlead_time_dayscome from that tier.shelf_life_monthscomes from the product root (not the tier).exw_total_usd = confirmed_quantity × tier.unit_price_usd(cents).quote_basis:EXWwhen the template uses a bare basis field; useEXW_PLUS_FREIGHT_OPTIONSwhere the template's policy/basis field expects the freight-inclusive form (the quote's incoterm is "EXW plus freight options").
A2. Selecting the right freight options
- From the full freight collection, keep only rows whose
quote_idexactly equals this quote's ID. - Exclude distractors and non-real rows. Drop any record whose
statusisstale,mismatch, or otherwise notactivefor the purpose of being a recommendable, real option — but you usually still must report all three canonical modes (air/sea/road) the template lists, including a stale road row, with its true flags. Always drop IDs that are explicitly distractors (FR-DIS-*, "Distractor route" destination, risk_notes like "benchmark", "Wrong shipment size", "Archived", "Unrelated"). The genuine options share the quote's real destination and shipment weight/CBM; distractors do not. - Order the options as the template orders them (typically AIR, SEA, ROAD).
A3. Per-option fields
mode: uppercase (AIR/SEA/ROAD).freight_cost_usd: the row'scost_usd.transit_days: use the row'stransit_days_text("4-6 days") when the template shows a units suffix; use the bare range ("3-5") when the template's example omits "days". Match the template's example format.valid_until: the row'svalid_until.grand_total_usd = exw_total_usd + freight_cost_usd.- Risk mapping from
route_risk:low→risk_level: LOW,risk_flag: NONE,customs_border_risk: LOWmedium→risk_level: MEDIUM,risk_flag: MEDIUM_BORDER_RISK,customs_border_risk: MEDIUMhigh→risk_level: HIGH,risk_flag: HIGH_BORDER_RISK(or template's high form),customs_border_risk: HIGH
- Validity / staleness (compute against the quote_date):
validity_status:VALIDifstatus == activeANDvalid_until ≥ quote_date; elseSTALE(expired or status stale).source_is_stale: true ifstatus == staleORvalid_until < quote_date.road_quote_invalid_or_stale/ similar warning booleans: true when the road option is expired or stale.
A4. Recommendation & control flags — feasibility vs. risk are separate
recommended_mode: choose the cheapest grand_total among ELIGIBLE options only. An option is eligible only if it is VALID (not stale/expired) AND not HIGH border risk. This is the load-bearing judgment: a stale or high-risk road lane can have the lowest grand total and must STILL be rejected. In practice SEA (cheap, valid, low risk) usually wins over a cheaper but stale/high-risk road option. Do not recommend a mode you flagged as stale/invalid.freight_reconfirmation_required:true— policyPOL-FREIGHT-RECONFIRM("RECONFIRM_AT_ORDER") makes freight always reconfirm at final order.all_freight_options_valid_on_quote_date: true only if every reported option is valid on the quote date; false if any is expired/stale.freight_warning: a one-sentence client note when something is wrong — state that freight needs reconfirmation at final order, name the stale/expired freight ID, its expiry date, and its high customs/border risk, and that it must not be used without a fresh quote.
A5. Payment terms & customer policy
payment_terms= the customer'spayment_profile, validated against policy:- Recurring NGO / recurring account →
NET_30_AFTER_PO(policyPOL-RECURRING-NGO-PAYMENT), unless restricted grant terms say otherwise. - New NGO / new client without approved credit →
PREPAY_100(POL-NEW-CLIENT-PAYMENT).
- Recurring NGO / recurring account →
customer_policy(when the template asks for it): the segment label in the controlled form, e.g.RECURRING_NGO. Derive from the customersegment(recurring_ngo→RECURRING_NGO).
SOP B — Indicative / module-level EXW quote
Fetch: the RFQ, its customer, each requested product, and policies.
quote_header:rfq_id,customer_id,quote_date(from prompt/RFQ),currency: "USD",quote_basis: "EXW_ONLY".- Quote at module level only. RFQs may include a "component composition" or
composition table — that is for medical review only (policy
POL-MODULE-GRANULARITY, "MODULE_LINES"). Do NOT split modules into component SKUs. Oneline_itemper requested module, in the RFQ's order. - Per line:
product_code,article_number(product root),quantity(from the RFQ's requested module),unit_price(the matching price tier — most modules have a single open tier; otherwise select by quantity bracket),lead_time_days(tier),shelf_life_months(product root),line_total = quantity × unit_price. quote_controls:grand_total = Σ line_total(cents).freight_excluded: true— no destination ⇒ EXW only, freight excluded (POL-INDICATIVE-EXW).payment_terms: from customer/policy. New NGO ⇒PREPAY_100.offer_validity_days:30(catalog quote validity,POL-QUOTE-VALIDITY, "QUOTE_VALID_30_DAYS") unless overridden.who_documentation_required:truefor WHO/IEHK-style emergency health kit modules.
SOP C — Engagement / receivables reconciliation
Fetch: the opportunity, customer, and the full invoices, payments,
revenue-journals, events, and vouchers collections. Filter each precisely on
opportunity_id (and cross-check customer_id) — never trust loose filters.
Treat the prompt's "current business date" (often 2026-06-01) as as_of_date.
C1. Account / opportunity status
stage: map opportunitystageto the template enum (closed_won→WON).won_amount= opportunitywon_amount_usd.phase_total_amount= Σ of the opportunity's phaseamount_usd.opportunity_matches_milestones/opportunity_matches_phase_total:won_amount == phase_total(within cents).outstanding_balance= Σ unpaid invoiceoutstanding_amount_usdfor this opportunity (equivalently won_amount − total paid). Cross-check against the opportunity's ownoutstanding_amount_usd.total_paid_amount= Σ posted payments for this opportunity.contact/primary_contact: the account contact named in the prompt (also on the opportunity/event). Tie every follow-up task to this contact and tolinked_customer_id/linked_opportunity_id.
C2. Per-milestone reconciliation (one phase = one milestone)
Order milestones ascending (MS1, MS2, MS3 / phase 1,2,3). Map each opportunity
phase → its invoice (by phase_id/invoice_id) → its payment(s) → its revenue
journal.
milestone_id: stable label (MS1/MS2/... or the phase id). Match the template's enum form.phase_numberis the ordinal.invoice_total/amount= invoiceamount_usd(= phase amount).invoice_state(when asked):paid→PAID;unpaid/overdue/draft→OPEN(use template's enum, e.g.OPEN); follow the template's allowed tokens.payment_status/payment_state:PAIDif paid_amount ≥ amount;PARTIALif 0 < paid < amount;UNPAIDif no posting.amount_paid/paid_amount= invoicepaid_amount_usd(sum of posted payments).amount_unpaid= invoiceoutstanding_amount_usd.due_date— paid-milestone nulling: if the milestone is fully PAID, outputdue_date: null(a paid milestone has no live due date / no collection clock). Only unpaid milestones carry their invoicedue_date. This is a deliberate convention; do not leak the original due date on a paid line.revenue_recognition_status/recognition_status:- RECOGNIZED — milestone is complete AND paid AND a posted revenue journal exists for it (debit Deferred Revenue → credit Implementation Services Revenue).
- MISSING (REQUIRED_MISSING / MISSING_REVENUE_JOURNAL) — milestone is complete AND paid but no revenue journal exists. This requires action.
- NOT_REQUIRED_UNPAID — milestone is unpaid; recognition is not yet due,
it just stays outstanding and drives a collection task.
Rule of thumb (policy
POL-REVREC, "RECOGNIZE_PAID_COMPLETE_MILESTONES"): recognize paid + complete milestones; unpaid future milestones stay outstanding.
C3. Rolled-up revenue recognition block (if template has one)
recognition_status:COMPLETE_FOR_PAID_MILESTONESif every paid milestone already has a journal;MISSING_FOR_PAID_MILESTONESif any paid milestone lacks one;NOT_REQUIREDif nothing is paid yet.recognized_milestones: paid milestones with a journal.missing_required_milestones: paid milestones lacking a journal.recognized_amount: Σ amounts of journaled (recognized) milestones.
C4. Accounting & collection actions (state-driven enums)
Pick the action that matches the account's actual state and the template's enum vocabulary.
- Accounting action:
- If a paid milestone is missing its journal → record it. e.g.
primary_accounting_action: RECORD_REVENUE_MS2,accounting_action.action: RECORD_REVENUE_MS2,milestone_id= that milestone,amount= its value,debit_account: DEFERRED_REVENUE,credit_account: IMPLEMENTATION_SERVICES_REVENUE,owner_queue: ACCOUNTING. - If all paid milestones are already recognized →
VERIFY_REVENUE_ONLY(or the template's "no missing" form),NONEaccounts, no owner action. - If nothing paid →
NO_ACCOUNTING_ACTION.
- If a paid milestone is missing its journal → record it. e.g.
- Collection action / task (one per relevant unpaid milestone):
- Unpaid but not yet due (due_date ≥ as_of_date) →
MONITOR_UNPAID_NOT_DUE/COLLECTIONwithnext_action: COLLECT_UNPAID_MILESTONE, ownerACCOUNT_MANAGEMENT. - Unpaid and overdue (due_date < as_of_date) →
SEND_COLLECTION_NOTICE, ownerCOLLECTIONS. - Nothing unpaid →
NO_COLLECTION_ACTION. - Include
milestone_id,amount/amount_due, the unpaid invoicedue_date, and the accountcontact_name.
- Unpaid but not yet due (due_date ≥ as_of_date) →
C5. Event / voucher block & invite routing
- Match the event and voucher to this opportunity/customer (exact link, not loose filter).
event_id,event_date;event_statusmapped to the template enum (scheduled→SCHEDULED,confirmed/live→ACTIVEper the template's tokens,completed→COMPLETED).- Voucher facts:
voucher_code;voucher_discount/discount_amount= the voucher'sdiscount_percentvalue (output as the number the template shows, cent precision if it asks for USD-style two decimals);voucher_max_uses/max_uses=max_redemptions;voucher_status=active→ACTIVE. - Invite action: if the event is upcoming/scheduled and the invite hasn't gone →
SEND_BRIEFING_INVITE/SEND_EVENT_INVITATION, ownerACCOUNT_MANAGEMENT, withcontact_name,customer_id,event_id,voucher_code. - For an event-invitation follow-up task, the
due_dateis the invite-send date the template implies (typically ahead of the event_date — match the template; leave milestone/amount fields null on an invite task and event/ voucher fields null on a collection task).
Common misjudgments — do NOT do these
- Do not use prior/old unit prices. Quote line items carry
prior_unit_price_usd/prior_quote_quantity; these are history. The current catalog tier overrides them. - Do not include distractor or stale freight as real options.
FR-DIS-*, "Distractor route", and benchmark/archived/mismatch rows are traps. Match on the exactquote_idplus real destination/shipment size, and honorstatus. - Do not recommend the cheapest option blindly. Eligibility (valid + acceptable risk) gates the cheapest-grand-total choice. Feasibility/validity and risk are separate from price and override it.
- Do not split RFQ modules into components. Module RFQs are quoted at module line level unless the customer explicitly asks for component pricing.
- Do not carry a due_date on a fully paid milestone. Paid →
due_date: null. - Do not mark a paid milestone RECOGNIZED unless a posted revenue journal actually exists for it. Missing journal on a paid milestone = the action item (RECORD_REVENUE…), not a silent pass.
- Do not add freight to an indicative no-destination quote. EXW only, freight excluded.
- Do not add explanatory prose, markdown fences, or extra keys. Return only the JSON object the template defines.
- Do not invent IDs, dates, or amounts. If a value isn't in a fetched record, find the record — don't guess.
Final checklist before returning
- Output parses as JSON and matches the template's keys/structure exactly.
- Every controlled-enum field uses a literal token from the template.
- Money is cent-precise; dates are ISO; IDs are verbatim from records.
- Totals recomputed from primitives (qty×price; EXW+freight; Σ phases; Σ paid).
- Recommended mode is eligible (valid + acceptable risk), not just cheapest.
- Paid milestones have null due_date; recognition status reflects real journals.
- Follow-up tasks are tied to the correct contact, customer, and opportunity.
- No prose outside the JSON.