Imported from Prism-Shadow/GDPevo (
experiments/claude_code_glm_5_2_max/reports/skills/task_group_002/reflect-3/reflect-3_attempt_01/SKILL.md). Install upstream withnpx skills add Prism-Shadow/GDPevo --skill reflect-3_attempt_01. Copyright stays with the author.
MedBridge Sales Ops — CRM Reconciliation & Quote Decision Skill
Transferable workflow rules for solving MedBridge Sales Ops CRM tasks against the remote business API. Covers the two task families: QUOTE (reconcile a customer RFQ/quote into an account-ready quote decision package) and RECONCILIATION (reconcile a won opportunity into a finance-ready engagement reconciliation).
These are reusable SOPs and pitfalls — not answers. Always derive every value from the live API records for the task's specific quote/RFQ/opportunity.
0. Environment & API usage
- Business records live ONLY behind the remote HTTP JSON API (
API_BASE_URL). Do not invent records; do not read any localenv/source. Reconcile the task narrative against the API and trust the API over narrative wording when they conflict (e.g., a customer name in the prompt that differs fromcustomers[].name— use the API value; use the APIcustomer_idlinked to the quote/RFQ/opportunity even if the prompt names the customer differently). - Endpoints (all GET unless noted):
GET /api— service metadata (collections list).GET /api/<collection>— list all records ({collection, count, records}).GET /api/<collection>?<key>=<value>— filter (case-insensitive, matches nested keys; multiple filters AND). Use this for collections that have no detail-by-id.GET /api/<collection>/<id>— single record by id. Detail-by-id is only available for:customers,products,rfqs,quotes,freight-quotes,opportunities. Forinvoices,payments,revenue-journals,events,vouchers,policiesuse the listing/filter endpoints.GET /api/search?q=<text>— substring search across all collections.
- Collections:
customers,products,rfqs,quotes,freight-quotes,policies,opportunities,invoices,payments,revenue-journals,events,vouchers. - Revenue-journals have NO
customer_idand will not filter by customer_id — always filter revenue-journals byopportunity_id. (Same safe pattern for invoices/payments/events/vouchers: filter byopportunity_id.) - Money = USD with 2 decimals. Dates = ISO
YYYY-MM-DD. Use stable record IDs exactly as they appear in the API. - Output: return ONLY valid JSON matching the task's
answer_template.jsonexactly — same field names, same nesting, the controlled enum values declared in the template, no markdown fences, no prose.
1. QUOTE tasks — quote decision package
Trigger: prompt references a quote id (Q-...) or RFQ id (RFQ-...) and asks
for revised EXW pricing, freight options/totals, route risk, recommended mode,
freight-validity/reconfirmation, and payment terms.
1.1 Record gathering
- Fetch the quote (
/api/quotes/<id>) or RFQ (/api/rfqs/<id>) — notecustomer_id,quote_date,primary_product_code/requested_modules,confirmed_quantity,incoterm,quote_type. - Fetch the customer (
/api/customers/<customer_id>) — notesegment,customer_type,is_recurring,payment_profile,grant_terms. - Fetch the product (
/api/products/<code>) — noteprice_tiers,shelf_life_months,article_number,cold_chain_required. - Fetch freight quotes:
/api/freight-quotes?quote_id=<quote_id>. - Fetch policies:
/api/policies(payment_terms, quote_scope, freight, quote_validity, incoterms).
1.2 Catalog tier by quantity (EXW pricing)
- Pick the
price_tiersentry wheremin_qty <= confirmed_quantityAND (max_qtyis null ORconfirmed_quantity <= max_qty). This single tier givesunit_price_usd,lead_time_days,shelf_life_months. exw_total_usd(orline_total/grand_totalfor EXW-only) =confirmed_quantity × tier.unit_price_usd.- Do NOT use a "prior_unit_price_usd" from the quote line item — the catalog tier overrides it (revision quotes raise quantity into a cheaper tier).
1.3 Freight options
- Distractor exclusion: drop any freight quote whose
idstarts withFR-DIS-OR whosedestinationcontains "Distractor". These are never part of the answer. - Include every REAL freight quote for the quote_id, one per mode (typically AIR, SEA, ROAD). Include a stale/expired real option with its stale flag set — do NOT silently drop it (the stale flag and a client warning are how staleness is reported).
- Per-option fields:
freight_id= the freight quoteid.mode= UPPER(RI mode) (AIR/SEA/ROAD).freight_cost_usd=cost_usd.transit_days= the freight quote'stransit_days_textstring verbatim (e.g."4-6 days").valid_until= freight quotevalid_until.grand_total_usd=exw_total_usd + freight_cost_usd(compute even for stale options).- Risk enums (derive from
route_risk):- QUOTE-template
risk_level:low→LOW,medium→MEDIUM,high→HIGH. - QUOTE-template
risk_flag:low→NONE,medium→MEDIUM_BORDER_RISK(high-risk analog:HIGH_BORDER_RISK). - DECISION-template
customs_border_risk:LOW/MEDIUM/HIGH.
- QUOTE-template
- DECISION-template staleness fields:
source_is_stale= (freight quote status == "stale").validity_status=VALIDwhenvalid_until >= quote_dateand status active;EXPIREDwhenvalid_until < quote_date(stale/expired).
1.4 Recommended transport mode (KEY RULE)
- Recommended mode = the CHEAPEST VALID freight option by
grand_total_usd, considering only VALID (active, non-stale, non-distractor) options. - Risk level does NOT determine the recommendation — cost among valid options does. A low-risk air option does NOT beat a cheaper valid sea option. Examples: cheapest valid = SEA when SEA is active and cheapest; AIR only when AIR is the sole valid (or cheapest valid) option.
- Stale and distractor options are never recommended.
1.5 Policy / control flags
freight_reconfirmation_required=truealways (policy: freight rates need reconfirmation at order; valid only throughvalid_until).all_freight_options_valid_on_quote_date(QUOTE template) =trueonly if every included real option hasvalid_until >= quote_date(i.e., none expired before the quote date). A stale/expired real option makes thisfalse.quote_basis:- Freight-options quote (
incoterm"EXW plus freight options") →"EXW". - Indicative EXW-only quote (RFQ with no destination) →
"EXW_ONLY". Use the template's controlled placeholder value, NOT the policyterms_code(EXW_ONLY_EXCLUDE_FREIGHT).
- Freight-options quote (
payment_terms:- New NGO client (
payment_profileNEW_CLIENT_REVIEW,is_recurringfalse,segmentnew_ngo) →PREPAY_100. - Recurring NGO / commercial (
payment_profileNET_30_AFTER_PO) →NET_30_AFTER_PO. - Map from the customer's
payment_profile/ the matching payment policyterms_code.
- New NGO client (
customer_policy(where present) = customersegment(e.g.recurring_ngo).offer_validity_days=30(policy: catalog quote pricing valid 30 calendar days from quote date).freight_excluded=truefor indicative quotes with no destination (EXW only);false/omit for freight-options quotes.who_documentation_required=truefor IEHK-family / WHO-standardized emergency-health-kit modules (codes prefixedIEHK-);falsefor non-WHO products. (Note: products are "IEHK-style" but still trigger the WHO doc flag.)
1.6 Module-level quote discipline (RFQ module quotes)
- Output exactly ONE line item per entry in the RFQ
requested_modules, at module granularity:product_code,article_number,quantity,unit_price(single-tier module price),lead_time_days,shelf_life_months,line_total = quantity × unit_price. - Do NOT expand
componentsinto line items, and ignorecomponent_composition_distractors— these are medical-review noise, not quoteable SKUs. The customer must explicitly request component-level pricing to split a module. - EXW-only module quote: no freight options block;
quote_basisEXW_ONLY,freight_excludedtrue.
2. RECONCILIATION tasks — engagement/finance reconciliation
Trigger: prompt references an opportunity id (OPP-...) and customer id
(CUST-...) and asks to reconcile won-amount, milestone invoices, payments,
revenue-recognition coverage, outstanding balance, and CRM/accounting/event
follow-ups. Use the prompt's stated current business date as as_of_date and for
due-date comparisons (default 2026-06-01).
2.1 Record gathering
- Opportunity:
/api/opportunities/<opp_id>—stage,won_amount_usd,outstanding_amount_usd,contact,phases[](phase_id,amount_usd,completion_date,invoice_id). - Customer:
/api/customers/<customer_id>—name,segment,contacts. - Invoices:
/api/invoices?opportunity_id=<opp_id>—amount_usd,status,paid_amount_usd,outstanding_amount_usd,due_date,phase_id. - Payments:
/api/payments?opportunity_id=<opp_id>—amount_usd,invoice_id,status. - Revenue journals:
/api/revenue-journals?opportunity_id=<opp_id>(NOT by customer_id) —invoice_id,phase_id,amount_usd,status. - Event:
/api/events?opportunity_id=<opp_id>—event_date,status,voucher_code,primary_contact,follow_up_owner. - Voucher:
/api/vouchers?opportunity_id=<opp_id>—discount_percent,max_redemptions,status,valid_until.
2.2 Milestone identification & ordering
- Each opportunity phase = one milestone, ordered ascending by phase order.
- Where the template declares a milestone_id enum (
MS1 | MS2 | MS3), use those enum values IN PHASE ORDER — NOT the APIphase_id(MER-P1, etc.) and not the invoice id. Map phase 1→MS1, phase 2→MS2, phase 3→MS3. - Where the template allows a freeform "stable phase or invoice milestone id",
use the API
phase_id(e.g.HEL-P1). - Money per milestone:
amount/invoice_total= invoiceamount_usd;paid_amount/amount_paid= invoicepaid_amount_usd;amount_unpaid= invoiceoutstanding_amount_usd.
2.3 Invoice / payment state enums
invoice_state: invoicestatuspaid→PAID,unpaid/open→OPEN,void→VOID, unknown→UNKNOWN.payment_state: fully paid→PAID, partially paid→PARTIAL, unpaid→UNPAID, unknown→UNKNOWN. A milestone isPAIDonly ifpaid_amount_usdequalsamount_usd(and a posted payment exists).
2.4 due_date rule (KEY)
- Paid milestone →
due_date: null(the obligation is settled; the invoice due date is no longer relevant). - Unpaid milestone →
due_date= the invoice'sdue_date(ISO string).
2.5 Revenue-recognition state mapping (KEY — missing-journal detection)
For each milestone, set revenue_recognition_status / recognition_status:
- Paid AND a matching revenue-journal exists (journal for that
invoice_id/phase_id, status
posted) →RECOGNIZED. - Paid AND NO matching revenue-journal →
MISSING_REVENUE_JOURNAL(decision template) /REQUIRED_MISSING(status template). This is the critical defect to detect: a milestone that is complete and paid but whose revenue was never recognized (deferred revenue not moved to income). - Unpaid →
NOT_REQUIRED_UNPAID(no revenue to recognize until paid). - Unknown/inconsistent →
UNKNOWN(decision template only).
Top-level recognition_status (status template):
- All paid milestones have journals →
COMPLETE_FOR_PAID_MILESTONES. - Any paid milestone lacks a journal →
MISSING_FOR_PAID_MILESTONES. recognized_milestones= list of milestone ids with journals;missing_required_milestones= paid milestones lacking journals;recognized_amount= sum of recognized journal amounts.
2.6 Balance & match booleans
total_paid_amount= sum of invoicepaid_amount_usd.outstanding_balance=won_amount − total_paid_amount(= sum of invoiceoutstanding_amount_usd). Must agree with opportunityoutstanding_amount_usd.opportunity_matches_milestones/opportunity_matches_phase_total=(sum of phase amounts == won_amount_usd).
2.7 Follow-up routing (collection vs accounting vs event-invitation)
Accounting action — based on revenue-recognition need:
- A paid milestone MISSING its revenue journal →
RECORD_REVENUE_MSx(milestone_id= that MS,amount= its invoice amount,debit_account=DEFERRED_REVENUE,credit_account=IMPLEMENTATION_SERVICES_REVENUE,owner_queue=ACCOUNTING). This is theprimary_accounting_actionwhen present. - Paid milestone already recognized →
VERIFY_REVENUE_ONLY(no new posting; ownerACCOUNTING). - Unpaid milestones →
NO_ACCOUNTING_ACTION(owner_queueNONE).
Collection action — based on the unpaid milestone vs current business date:
- Unpaid milestone with
due_date<= current business date (due/overdue) →SEND_COLLECTION_NOTICE(owner_queue=COLLECTIONS,due_date= invoice due date,amount= outstanding). - Unpaid milestone with
due_date> current business date (not yet due) →MONITOR_UNPAID_NOT_DUE(owner_queue=ACCOUNT_MANAGEMENT,due_date= invoice due date,amount= outstanding). - No unpaid milestone →
NO_COLLECTION_ACTION(owner_queueNONE).
Event-invitation action — based on the linked event:
- Event
statusscheduled/confirmedand upcoming (event_date >= current date) with invitation not yet sent →SEND_BRIEFING_INVITE(owner_queue= the event'sfollow_up_owner, typicallyACCOUNT_MANAGEMENT;event_id,voucher_code,contact_name,customer_idpopulated). - Invitation already sent / event active →
VERIFY_INVITE_SENT. - No event →
NO_INVITE_ACTION. - Event status enum:
scheduled→SCHEDULED,confirmed→ACTIVE/CONFIRMEDper template,completed→COMPLETED,cancelled→CANCELLED. - Voucher:
voucher_statusfrom voucherstatus(active→ACTIVE,draft→DRAFT,expired→EXPIRED,disabled→DISABLED);discount_amount= voucherdiscount_percentvalue;max_uses= vouchermax_redemptions.
2.8 Contact linkage
primary_contact/contact= the opportunity'scontactname; link to the customer viacustomer_id(andopportunity_idwhere the template field exists). Tie every follow-up task to this contact name and the customer/opportunity ids.
3. Cross-cutting pitfalls (from reflect feedback)
- Recommended mode is cheapest-VALID, not lowest-risk. Picking the low-risk air option over a cheaper valid sea option is wrong. (Cost among active/non-stale/non-distractor options decides.)
- Stale real freight options stay in the array, flagged — do not drop them.
Only distractors (
FR-DIS-*/ "Distractor" destination) are removed. - Paid milestones get
due_date: null. Leaving the invoice due date on a paid milestone is wrong. - Detect missing revenue journals explicitly. A paid milestone with no
matching revenue-journal is
MISSING_REVENUE_JOURNALand drives aRECORD_REVENUEaccounting action — this is the single most important reconciliation finding. - Collection routing depends on due date vs current business date. A
not-yet-due unpaid milestone is
MONITOR_UNPAID_NOT_DUE(account management), not a collection notice. - Filter revenue-journals (and other finance records) by
opportunity_id, notcustomer_id. Revenue journals carry no customer_id. quote_basisuses the template's controlled value, not the policyterms_code(e.g.EXW_ONLY, notEXW_ONLY_EXCLUDE_FREIGHT).who_documentation_requiredis true for IEHK-family modules even though the catalog calls them "IEHK-style".- Module RFQs stay at module line level — never expand
componentsor honorcomponent_composition_distractors. - Catalog tier overrides prior unit price on revision quotes; recompute EXW total from the tier that matches the confirmed quantity.
- Trust API values over prompt narrative for customer name, ids, amounts, and statuses; reconcile which entity the prompt asks about, then read facts from that API record.
- Use detail-by-id only for the six id-keyed collections; otherwise list
- filter. Use stable API ids verbatim.
- Freeform string fields (warning messages, task titles) are not reliably matchable — prioritize getting every structured id, amount, date, and enum exactly right; for freeform warning text, state the concrete finding concisely (which freight id is stale/expired, its valid_until vs quote date, the recommendation, the reconfirmation requirement).
- Output ordering: milestones ascending by milestone_id (MS1, MS2, MS3); freight options in AIR, SEA, ROAD order unless the template implies otherwise.