Skip to content

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:

SectionCategories
1 — Journey CostsBase Compensation, Monthly Allowance, Maternity Clothing, Multiples, Health Insurance, Life Insurance, C-Section
2 — IncidentalsSupport Group, Counseling, Housekeeping, Bed Rest, Breast Milk, Miscellaneous 1–5
3 — Lost WagesGC Lost Wages, Companion Lost Wages
4 — Funding MilestonesUpon 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. Total is computed client-side as Per 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:

CategoryMatches SPC codes where…
Base Compensationcode contains "base" or equals "post_delivery_lump_sum"
Monthly Allowancecode equals "monthly_expense_allowance"
Maternity Clothingcode is "maternity_clothing_1" or "maternity_clothing_2"
Multiplescode contains "multi_fetal"
Embryo Transfercode equals "embryo_transfer_scheduled_fee"
Health Insurancecode equals "insurance_premium"
Life Insurancecode 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:

FieldFormula
Total EstimatedSum of all effective estimated_total values (SPC-derived for locked rows, manual for others) and Agency OOP
Amount in EscrowCurrent case ledger balance
Min. Balance RequiredCase’s minimum balance requirement
Total Est. to FundTotal 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.

MethodPathPurpose
POST/<case_id>/funding-milestonesCreate a new analysis (one per case)
PUT/<case_id>/funding-milestonesUpdate the existing analysis
GET/<case_id>/funding-milestones/downloadDownload 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:

  • 400agency_oop_medical_estimate is negative
  • 409 (POST only) — an analysis already exists for this case
  • 404 — 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.