Skill: MedBridge Sales Ops API Data Reconciliation
Environment Overview
The remote environment is a MedBridge Sales Ops service exposing a REST API. It stores CRM, quote, logistics, and milestone engagement data across multiple linked collections.
Base URL
http://<host>:8002
- Health check:
GET /health
- API info:
GET /api (lists all collections and endpoints)
Key Collections
| Collection |
Endpoint |
Purpose |
opportunities |
GET /api/opportunities |
Sales opportunities with phases, amounts, stage, contact |
invoices |
GET /api/invoices |
Invoice records per phase with amount_usd, paid_amount_usd, outstanding_amount_usd, status |
payments |
GET /api/payments |
Payment records linked to invoices |
revenue-journals |
GET /api/revenue-journals |
Revenue recognition journal entries per phase |
events |
GET /api/events |
Customer events linked to opportunities |
vouchers |
GET /api/vouchers |
Event voucher codes with discount_percent, max_redemptions, status |
customers |
GET /api/customers |
Customer records with nested contacts array |
quotes, rfqs, freight-quotes, products, policies |
Their respective endpoints |
Quote and logistics data |
All collections support GET /<collection>/<id> for single-record lookup. There is also GET /api/search?q=<text> for cross-collection search.
Data Model Relationships
- Opportunity → has many phases (each with
phase_id, amount_usd, invoice_id)
- Phase → linked to one invoice (via
invoice_id)
- Invoice → linked to payments (via
invoice_id) and revenue-journals (via invoice_id)
- Opportunity → linked to events (via
opportunity_id)
- Event → linked to voucher (via
voucher_code)
- Customer → has nested
contacts array with name, email, phone, role
Field Mapping Rules
When populating answer templates, map API fields to template fields carefully:
| Template Field |
API Source |
Notes |
won_amount |
opportunity.won_amount_usd |
Use exact value; validate it equals sum of phase amounts |
outstanding_amount / outstanding_balance |
opportunity.outstanding_amount_usd or computed from invoices |
Use two decimals |
total_paid_amount |
Sum of invoice.paid_amount_usd or sum of payment.amount_usd for the opportunity |
Cross-check both sources |
phase_total_amount |
Sum of phase.amount_usd across all phases |
Should match won_amount |
currency |
opportunity.currency |
Usually "USD" |
contact |
opportunity.contact |
Primary contact name string |
customer_name |
customer.name |
Look up via customer_id |
Status Translations
API statuses often differ from template enum values. Map them explicitly:
| API Status |
Template Enum |
Context |
closed_won |
WON |
stage field |
proposal, negotiation |
OPEN |
stage field |
paid |
PAID |
invoice_state |
unpaid |
OPEN |
invoice_state |
overdue |
OPEN |
invoice_state (still unpaid) |
draft |
VOID or UNKNOWN |
invoice_state (use judgment) |
live |
ACTIVE |
event_status |
confirmed |
SCHEDULED |
event_status |
scheduled |
SCHEDULED |
event_status |
completed |
COMPLETED |
event_status |
tentative |
UNKNOWN |
event_status |
Financial Computations
Opportunity Reconciliation
- Phase total: Sum
phase.amount_usd for all phases on the opportunity.
- Opportunity match:
won_amount should equal phase total (within rounding). Set opportunity_matches_phase_total accordingly.
- Total paid: Sum all
paid_amount_usd from invoices for this opportunity, OR sum all amount_usd from payments for this opportunity. Both should agree.
- Outstanding balance:
won_amount − total_paid_amount, OR use opportunity.outstanding_amount_usd. Cross-check.
Milestone State Derivation
For each phase/milestone:
- Find the linked invoice by
invoice_id.
- Find all payments for that
invoice_id.
- Find the revenue-journal for that
invoice_id.
- Invoice state: Map
invoice.status to template enum.
- Payment state: If
invoice.paid_amount_usd ≥ invoice.amount_usd → PAID. If 0 → UNPAID. If partial → PARTIAL.
- Recognition status:
- If milestone is paid AND complete AND a revenue-journal exists →
RECOGNIZED
- If milestone is paid AND complete BUT no revenue-journal exists →
MISSING_REVENUE_JOURNAL
- If milestone is unpaid →
NOT_REQUIRED_UNPAID
- Otherwise →
UNKNOWN
Action Determination
Based on the reconciliation, derive required actions:
Accounting Action (from invoice_actions.accounting_action):
- If any paid milestone is missing a revenue journal →
RECORD_REVENUE_MS2 (or appropriate MS#), debit DEFERRED_REVENUE, credit IMPLEMENTATION_SERVICES_REVENUE
- If all paid milestones have journals →
VERIFY_REVENUE_ONLY
- Otherwise →
NO_ACCOUNTING_ACTION
Collection Action (from invoice_actions.collection_task):
- If any invoice is
unpaid and past due → SEND_COLLECTION_NOTICE, owner COLLECTIONS
- If unpaid but not yet due →
MONITOR_UNPAID_NOT_DUE, owner ACCOUNT_MANAGEMENT
- Otherwise →
NO_COLLECTION_ACTION
Event Invite Action (from event_actions.invite_task):
- If event is
ACTIVE or SCHEDULED and invite has not been verified → SEND_BRIEFING_INVITE, owner EVENTS
- If event is
COMPLETED → VERIFY_INVITE_SENT or NO_INVITE_ACTION
- For
UNKNOWN/CANCELLED → NO_INVITE_ACTION
Voucher Handling
Vouchers in the API have discount_percent and max_redemptions.
The answer template expects discount_amount (USD, two decimals).
- If
discount_percent is 100 and there is no explicit ticket price, discount_amount may be equal to discount_percent (treated as a percentage value) or computed from a known event ticket price if available.
max_uses maps directly from max_redemptions.
voucher_status maps from API status (active → ACTIVE, etc.).
Query Strategy for Test Solvers
- Read the prompt to identify the target record(s) and required output fields.
- Query
/api to discover available collections and endpoints.
- Fetch the primary collection (e.g.,
/api/opportunities or /api/opportunities/<id>).
- Identify the correct record by the criteria in the prompt (ID, amount, stage, etc.). If the prompt references an ID that does not exist verbatim, look for records with matching data patterns (e.g., similar index in list, matching won_amount, or matching customer context).
- Fetch all related collections needed for the answer template:
- Invoices, payments, revenue-journals for financial reconciliation
- Events and vouchers for event actions
- Customers for contact names and customer names
- Map and translate all API fields to the template fields using the rules above.
- Compute derived values (sums, comparisons, states, actions) precisely.
- Validate enum values against the answer template descriptions before returning.
Common Pitfalls
- Using raw API statuses without translating to template enums.
- Forgetting to cross-check
won_amount against phase totals.
- Using
invoice.status directly as invoice_state without mapping unpaid/overdue to OPEN.
- Missing the revenue-journal lookup when determining
recognition_status.
- Returning
discount_percent instead of discount_amount for vouchers.
- Omitting two-decimal precision on all currency fields.
- Using
closed_won instead of WON for the stage enum.
1---2name: reflect-3-attempt-03-313description: Skill: MedBridge Sales Ops API Data Reconciliation4---5# Skill: MedBridge Sales Ops API Data Reconciliation67## Environment Overview89The remote environment is a **MedBridge Sales Ops** service exposing a REST API. It stores CRM, quote, logistics, and milestone engagement data across multiple linked collections.1011### Base URL12- `http://<host>:8002`13- Health check: `GET /health`14- API info: `GET /api` (lists all collections and endpoints)1516### Key Collections1718| Collection | Endpoint | Purpose |19|---|---|---|20| `opportunities` | `GET /api/opportunities` | Sales opportunities with phases, amounts, stage, contact |21| `invoices` | `GET /api/invoices` | Invoice records per phase with `amount_usd`, `paid_amount_usd`, `outstanding_amount_usd`, `status` |22| `payments` | `GET /api/payments` | Payment records linked to invoices |23| `revenue-journals` | `GET /api/revenue-journals` | Revenue recognition journal entries per phase |24| `events` | `GET /api/events` | Customer events linked to opportunities |25| `vouchers` | `GET /api/vouchers` | Event voucher codes with `discount_percent`, `max_redemptions`, `status` |26| `customers` | `GET /api/customers` | Customer records with nested `contacts` array |27| `quotes`, `rfqs`, `freight-quotes`, `products`, `policies` | Their respective endpoints | Quote and logistics data |2829All collections support `GET /<collection>/<id>` for single-record lookup. There is also `GET /api/search?q=<text>` for cross-collection search.3031## Data Model Relationships3233- **Opportunity** → has many **phases** (each with `phase_id`, `amount_usd`, `invoice_id`)34- **Phase** → linked to one **invoice** (via `invoice_id`)35- **Invoice** → linked to **payments** (via `invoice_id`) and **revenue-journals** (via `invoice_id`)36- **Opportunity** → linked to **events** (via `opportunity_id`)37- **Event** → linked to **voucher** (via `voucher_code`)38- **Customer** → has nested `contacts` array with `name`, `email`, `phone`, `role`3940## Field Mapping Rules4142When populating answer templates, map API fields to template fields carefully:4344| Template Field | API Source | Notes |45|---|---|---|46| `won_amount` | `opportunity.won_amount_usd` | Use exact value; validate it equals sum of phase amounts |47| `outstanding_amount` / `outstanding_balance` | `opportunity.outstanding_amount_usd` or computed from invoices | Use two decimals |48| `total_paid_amount` | Sum of `invoice.paid_amount_usd` or sum of `payment.amount_usd` for the opportunity | Cross-check both sources |49| `phase_total_amount` | Sum of `phase.amount_usd` across all phases | Should match `won_amount` |50| `currency` | `opportunity.currency` | Usually `"USD"` |51| `contact` | `opportunity.contact` | Primary contact name string |52| `customer_name` | `customer.name` | Look up via `customer_id` |5354### Status Translations5556API statuses often differ from template enum values. Map them explicitly:5758| API Status | Template Enum | Context |59|---|---|---|60| `closed_won` | `WON` | `stage` field |61| `proposal`, `negotiation` | `OPEN` | `stage` field |62| `paid` | `PAID` | `invoice_state` |63| `unpaid` | `OPEN` | `invoice_state` |64| `overdue` | `OPEN` | `invoice_state` (still unpaid) |65| `draft` | `VOID` or `UNKNOWN` | `invoice_state` (use judgment) |66| `live` | `ACTIVE` | `event_status` |67| `confirmed` | `SCHEDULED` | `event_status` |68| `scheduled` | `SCHEDULED` | `event_status` |69| `completed` | `COMPLETED` | `event_status` |70| `tentative` | `UNKNOWN` | `event_status` |7172## Financial Computations7374### Opportunity Reconciliation751. **Phase total**: Sum `phase.amount_usd` for all phases on the opportunity.762. **Opportunity match**: `won_amount` should equal phase total (within rounding). Set `opportunity_matches_phase_total` accordingly.773. **Total paid**: Sum all `paid_amount_usd` from invoices for this opportunity, OR sum all `amount_usd` from payments for this opportunity. Both should agree.784. **Outstanding balance**: `won_amount` − `total_paid_amount`, OR use `opportunity.outstanding_amount_usd`. Cross-check.7980### Milestone State Derivation81For each phase/milestone:821. Find the linked **invoice** by `invoice_id`.832. Find all **payments** for that `invoice_id`.843. Find the **revenue-journal** for that `invoice_id`.854. **Invoice state**: Map `invoice.status` to template enum.865. **Payment state**: If `invoice.paid_amount_usd` ≥ `invoice.amount_usd` → `PAID`. If 0 → `UNPAID`. If partial → `PARTIAL`.876. **Recognition status**:88 - If milestone is paid AND complete AND a revenue-journal exists → `RECOGNIZED`89 - If milestone is paid AND complete BUT no revenue-journal exists → `MISSING_REVENUE_JOURNAL`90 - If milestone is unpaid → `NOT_REQUIRED_UNPAID`91 - Otherwise → `UNKNOWN`9293### Action Determination94Based on the reconciliation, derive required actions:9596**Accounting Action** (from `invoice_actions.accounting_action`):97- If any paid milestone is missing a revenue journal → `RECORD_REVENUE_MS2` (or appropriate MS#), debit `DEFERRED_REVENUE`, credit `IMPLEMENTATION_SERVICES_REVENUE`98- If all paid milestones have journals → `VERIFY_REVENUE_ONLY`99- Otherwise → `NO_ACCOUNTING_ACTION`100101**Collection Action** (from `invoice_actions.collection_task`):102- If any invoice is `unpaid` and past due → `SEND_COLLECTION_NOTICE`, owner `COLLECTIONS`103- If unpaid but not yet due → `MONITOR_UNPAID_NOT_DUE`, owner `ACCOUNT_MANAGEMENT`104- Otherwise → `NO_COLLECTION_ACTION`105106**Event Invite Action** (from `event_actions.invite_task`):107- If event is `ACTIVE` or `SCHEDULED` and invite has not been verified → `SEND_BRIEFING_INVITE`, owner `EVENTS`108- If event is `COMPLETED` → `VERIFY_INVITE_SENT` or `NO_INVITE_ACTION`109- For `UNKNOWN`/`CANCELLED` → `NO_INVITE_ACTION`110111## Voucher Handling112113Vouchers in the API have `discount_percent` and `max_redemptions`.114The answer template expects `discount_amount` (USD, two decimals).115116- If `discount_percent` is 100 and there is no explicit ticket price, `discount_amount` may be equal to `discount_percent` (treated as a percentage value) or computed from a known event ticket price if available.117- `max_uses` maps directly from `max_redemptions`.118- `voucher_status` maps from API `status` (`active` → `ACTIVE`, etc.).119120## Query Strategy for Test Solvers1211221. **Read the prompt** to identify the target record(s) and required output fields.1232. **Query `/api`** to discover available collections and endpoints.1243. **Fetch the primary collection** (e.g., `/api/opportunities` or `/api/opportunities/<id>`).1254. **Identify the correct record** by the criteria in the prompt (ID, amount, stage, etc.). If the prompt references an ID that does not exist verbatim, look for records with matching data patterns (e.g., similar index in list, matching won_amount, or matching customer context).1265. **Fetch all related collections** needed for the answer template:127 - Invoices, payments, revenue-journals for financial reconciliation128 - Events and vouchers for event actions129 - Customers for contact names and customer names1306. **Map and translate** all API fields to the template fields using the rules above.1317. **Compute derived values** (sums, comparisons, states, actions) precisely.1328. **Validate enum values** against the answer template descriptions before returning.133134## Common Pitfalls135136- Using raw API statuses without translating to template enums.137- Forgetting to cross-check `won_amount` against phase totals.138- Using `invoice.status` directly as `invoice_state` without mapping `unpaid`/`overdue` to `OPEN`.139- Missing the revenue-journal lookup when determining `recognition_status`.140- Returning `discount_percent` instead of `discount_amount` for vouchers.141- Omitting two-decimal precision on all currency fields.142- Using `closed_won` instead of `WON` for the `stage` enum.