Funding Milestones
A Funding Milestones analysis is a cost estimate for a surrogacy or egg donation journey. It breaks down anticipated compensation, allowances, incidentals, and lost wages across 23 line items, then summarises how much still needs to be funded to cover those costs plus the case’s minimum balance requirement.
Each case can have at most one funding analysis.
The 23 Categories
Categories are fixed — the list never changes in the UI or database. They are split into four sections:
| Section | Categories |
|---|---|
| 1 — Journey Costs | Base Compensation, Monthly Allowance, Maternity Clothing, Multiples, Health Insurance, Life Insurance, C-Section |
| 2 — Incidentals | Support Group, Counseling, Housekeeping, Bed Rest, Breast Milk, Miscellaneous 1–5 |
| 3 — Lost Wages | GC Lost Wages, Companion Lost Wages |
| 4 — Funding Milestones | Upon Matching, Contract Signing, Embryo Transfer, Delivery |
The five Miscellaneous rows allow a custom display label. All other labels are fixed.
How Estimated Totals Work
Each category row has three fields: Per Occurrence, Est. Occurrences, and Est. Total.
SPC-backed categories (read-only)
Seven categories have a direct mapping to SPC compensation codes (see table below). When a case has scheduled (unpaid) SPC records matching a category, that category becomes SPC-locked:
- Per Occurrence, Est. Occurrences, and Est. Total are derived from the SPC records and displayed as read-only — they cannot be edited.
- The values are recomputed on every read; they are never stored in the funding analysis tables.
- If all payments for a category are later paid (no remaining unpaid SPC records), the lock releases and the admin can enter manual estimates.
Manual categories
Categories without SPC mappings (C-Section, all Incidentals, Lost Wages, Funding Milestones section) are always editable:
Est. Totalis computed client-side asPer Occurrence × Est. Occurrences.- Every field is optional. Rows with no values contribute $0 to the totals.
SPC Code Mappings
Paid-to-Date and SPC-locked estimates are computed at read time from Compensation records matched by code:
| Category | Matches SPC codes where… |
|---|---|
| Base Compensation | code contains "base" or equals "post_delivery_lump_sum" |
| Monthly Allowance | code equals "monthly_expense_allowance" |
| Maternity Clothing | code is "maternity_clothing_1" or "maternity_clothing_2" |
| Multiples | code contains "multi_fetal" |
| Embryo Transfer | code equals "embryo_transfer_scheduled_fee" |
| Health Insurance | code equals "insurance_premium" |
| Life Insurance | code equals "life_insurance_premium" |
- Paid-to-Date — sum of matching records where
paid = 1 AND removed = 0 - SPC-locked Est. Total — sum of matching records where
paid = 0 AND removed = 0(pending, not yet disbursed)
All other categories show $0 for Paid-to-Date and are never SPC-locked.
A row shows a Paid badge when paid_to_date ≥ estimated_total and estimated_total > 0.
A category row is visible in the read-only view whenever it has a non-zero estimated_total or any Paid-to-Date SPC activity.
Summary Fields
The summary block is computed from the 23 category rows plus two case-level values:
| Field | Formula |
|---|---|
| Total Estimated | Sum of all effective estimated_total values (SPC-derived for locked rows, manual for others) and Agency OOP |
| Amount in Escrow | Current case ledger balance |
| Min. Balance Required | Case’s minimum balance requirement |
| Total Est. to Fund | Total Estimated + Agency OOP + Min. Balance − Amount in Escrow |
Remaining per category row:
- SPC-locked row: equals
estimated_total(all scheduled payments are still owed) - Manual row:
max(estimated_total − paid_to_date, 0)
Agency OOP Medical Estimate is a separate field (not one of the 23 rows) that accounts for out-of-pocket medical costs expected from the agency. It is optional.
API
All endpoints live under /api/admin/case/<case_id>/funding-milestones and require an authenticated admin session.
| Method | Path | Purpose |
|---|---|---|
POST | /<case_id>/funding-milestones | Create a new analysis (one per case) |
PUT | /<case_id>/funding-milestones | Update the existing analysis |
GET | /<case_id>/funding-milestones/download | Download a PDF copy |
POST/PUT request body:
{ "agency_oop_medical_estimate": "1500.00", "categories": [ { "key": "base_compensation", "per_occurrence": "5000.00", "estimated_occurrences": "1", "estimated_total": "5000.00", "estimated_total_overridden": false, "notes": "Per contract", "custom_label": null } ]}Categories omitted from the list are written with null values. The server always writes all 23 rows regardless of how many are supplied.
For SPC-locked categories, any values submitted are stored but will be overridden by live SPC data on the next read.
Error responses:
400—agency_oop_medical_estimateis negative409(POST only) — an analysis already exists for this case404— case not found, or no analysis exists (PUT/download)
Every successful save creates a CaseAction audit entry.
Where to Find It
The feature lives on the Funding tab of the case ledger page (/admin/case/<id>/ledger). Admins click Generate Milestones to create a first draft, and Edit to revise it. Generate Invoice downloads the PDF.
Gotchas for Developers
All 23 rows always exist after the first save. upsert_categories() ensures this — it creates any missing rows with null values. Code reading the analysis can assume a complete set of 23 rows is present.
SPC-locked values are never stored — they are always recomputed on read. build_analysis_response() queries the compensation table on every call and overrides the DB-stored per_occurrence, estimated_occurrences, and estimated_total for any category with pending SPC records. Two SQL queries run: one for paid = 1 (Paid-to-Date), one for paid = 0 (scheduled/locked estimate).
total_remainder in the response uses effective values. For SPC-locked categories it reflects live SPC totals, not whatever is stored in funding_analysis_category. This means Total Est. to Fund is always in sync with the current SPC state without needing a re-save.
The database schema has two tables. funding_analysis holds one row per case (metadata + agency OOP). funding_analysis_category holds 23 rows per analysis. Deleting a funding_analysis row cascades to all its categories.
estimated_total_overridden applies only to manual rows. For SPC-locked categories this flag has no effect; the server ignores stored values and always derives from SPC data.