Skip to content

PRD — Phoenix Firm-Order Memo (DocGen solution)

status: implemented
date: 2026-06-21
solution: phoenix
consumes: docgen framework
sources:
  - docs/explanation/phoenix-uc2-analysis.md            # primary — requirements + gap map
  - docs/reference/Phoenix_Firm_Order_Memo_Spec.docx  # Odyssey spec (§6 = the five-paragraph contract)
relates_to: epic [E-release-3] Odyssey UAT ; epic [E-release-2] framework features

What this is. A solution-level PRD for the Odyssey Re firm-order memo built as a configuration of the generic DocGen framework. The goal is to prove the framework — Sections × render modes × Fields × computed fields × the gather/synthesize/render pipeline — can produce this document with no Phoenix-specific engine code, only solution-supplied config, prompts, computed-field functions, and two MCP data sources (Phoenix + EFS). Where the use-case does not map onto a framework primitive it is flagged here and specified as a framework gap in tdd.md (G1/G2/G3/G4).

Shipped. Two declarative specs (an enriched template and a spec-faithful template), a two-MCP data path (Phoenix fetch_deal + EFS fetch_referral, run as real loopback HTTP servers), a Phoenix computed-field hook (approval resolution + metrics + readiness verdict), and a headless eval over four real Odyssey deals plus 14 committed synthetic stand-ins. Status box + as-built detail live in tdd.md.


[PH-feat-1] The use-case

Who An Odyssey Re (Stamford) reinsurance underwriter — Matthew Nicolini is the named UAT driver.
What A five-paragraph reinsurance firm-order memo for one deal, identified by a Phoenix Program ID ("PID").
Deal types CAT XOL (catastrophe excess-of-loss) and Per Risk. Same five-paragraph contract; P3 metrics differ by type.
Workflow ingest (gather pulls the phoenix deal/pricing block + the efs approval/narrative block for a PID, over two MCP data sources) → compute / gate (Phoenix compute resolves the approval, derives the metrics, and emits the readiness verdict; the gate blocks before render on an unresolved approval) → assemble (deterministic P1–P3 from template + data + computed values) → generate (temp-0 LLM for P4–P5, faithful to source, no fabrication) → review (underwriter edits in the web UI, copies/exports).
Core principle The AI summarizes and reformats source data; it does not invent content. Temperature 0; nothing generated de novo except pre-approved generic fallback phrasing. The AI may derive standard underwriting figures (loss ratios, RARC, YoY %, composites) by arithmetic from source figures — analysis, not fabrication.

The five-paragraph structure is mandatory and ordered — no paragraph may be omitted, the breaks and order are fixed. Wording within a paragraph follows the rules but obeys the hard constraints below.

Two templates — enriched vs spec-faithful

Phoenix ships as two declarative specs against the same two-MCP data path, registered together on the Odyssey org (fixtures/orgs.py):

Spec file Role Why it exists
odyssey-phoenix-firm-order.json Enriched — P3 renders worksheet ROLs (quoted / broker-flat / as-if) and per-layer our-share $; P4/P5 are fed a source-figure digest (losses, zone PMLs, expiring-vs-current towers) plus the full cat-model extract. verify_cited_sources: true. Chases the gold memos' figures — maximizes how much of a real underwriter's memo DocGen reproduces (the eval's recall metric).
odyssey-phoenix-firm-order-spec.json Spec-faithful — follows source-spec §6 literally: P1 reconfirmation, P2 layer structure, P3 authorized amounts, P4 rationale from referral commentary (generic fallback if absent), P5 underlying book. No digest / gold-recall machinery. verify_cited_sources: false. The literal client contract — the document §6 actually specifies, with P4/P5 grounded only on the EFS narrative.

Both reuse the same phoenix + efs MCP sources, the same compute_phoenix_deal hook, the same odyssey-underwriter voice, and the same readiness gate. They differ only in what the sections render/prompt. The genericity argument holds for both: same engine, different config.

Self-service prompt layer (deferred from this solution's first cut)

The Odyssey spec's headline ask is a human-editable "prompt read-me" so underwriters can tweak wording without an engineering cycle. In DocGen terms this is editing a Section's prompt / output_template through the existing UI — the framework already exposes section-prompt editing and (R2) version history, so this is a UI-surfacing concern, not a new primitive. Treated as a fast-follow once the five-paragraph contract is locked; not a gate on the genericity proof.

[PH-feat-2] The five paragraphs — deterministic vs LLM

Primary source: phoenix-uc2-analysis.md §1. P1–P3 are deterministic (template + data + computed values, no LLM); P4–P5 are LLM (temp 0, faithful to EFS, conservative fallback). This is the split the genericity proof rests on — three sections never touch a model, two do.

Content Produced by
P1 — Reconfirmation "Please reconfirm as agreed via {source} on {date}." Source + date resolved from the matched EFS approval artifact. email → "via email on {date}"; signed PDF → "per our office meeting on {date}" (the word office deliberately signals a physical signed artifact). deterministic — Field substitution over the resolved approval (approver initials + source kind + extracted artifact date).
P2 — Outcome totals Signed limit / premium totalled across the subscribed layers (the structure, not the per-layer authorized amounts — P2 and P3 must not duplicate). deterministic — sum over layers (computed field).
P3 — Per-layer structure + metrics One block per layer, then a total. CAT XOL: ROL, MLR, MSD, RARC. Per Risk: rate, modeled/expiring composite. deterministic, iterates over N layers (G1 + G2).
P4 — Rationale / pricing position Narrative rationale for the firm order. LLM (temp 0) from EFS referralCommentary; conservative generic language if commentary not visible — never invents negotiations or implied approvals.
P5 — Underlying-book narrative Underlying book + exposure/loss commentary. LLM (temp 0) — enriched template: from the source-figure digest (zone PMLs, prior losses) + cat-model extract; spec-faithful template: from EFS underlyingBook (Phoenix program summary / cap modeling / pricing as supporting context).

Note on P-numbering. The two templates split P2/P3 differently. The spec-faithful template follows §6 literally — P2 "Layer Summary" (structure) and P3 "Authorized Amounts". The enriched template treats P2 as outcome totals and P3 as the per-layer structure-and-metrics block. The genericity argument is unchanged either way: three deterministic sections, two LLM sections, the per-layer section iterates over N layers.

memoMode — recommendation vs finalized firm order. A deal carries a memoMode (recommendation | firm_order, default firm_order). The compute layer and both templates branch on it: a recommendation memo frames the shares as the team's recommended position not yet authorized (P1 "We have received and reviewed the firm order terms"; "We are recommending …"; P4 present-tense recommending voice); a firm_order memo frames them as already authorized (P1 "Please reconfirm as agreed via {source} on {date}"; "Per our discussion, we are authorized at …"; P4 past-tense). The four real Odyssey deals are recommendation; the synthetic stand-ins default to firm_order. This is a generic field on the deal, read in phoenix_compute._enrich, not a per-deal carve-out.

[PH-feat-3] Inputs

Per PID, gather pulls two blocks from two MCP data sources (the split mirrors the real two-system world — a pricing system and a filing system). Faked only at the MCP boundary: each source is a real local JSON-RPC server returning sample data; everything downstream is the production path, and a live deployment swaps in MCP_PHOENIX_URL / MCP_EFS_URL with no engine change (see tdd.md [PH-T-2]).

Source (MCP tool) Contents Feeds
phoenix (fetch_deal) — deal / pricing program summary, per-layer detail (limit, retention, reinstatement, share, rate, SubjBasePML, OccEV), cat modeling (Occ EV, MSD), pricing (RARC), authorization (per-layer authShare + totals); for the real deals also the inuring/FHCF, first-event attaches, expiring towers, losses, zone PMLs, and the chunked cat-model/PDF sourceDocuments. P1 (auth), P2 (totals), P3 (per-layer + computed metrics), P4/P5 (digest + extract).
efs (fetch_referral) — filing approval artifact / approvalHistory (email or signed PDF, with internal artifactDate + commentField), referralCommentary, underlyingBook narrative. P1 (approval source + date), P4 (referralCommentary), P5 (underlyingBook).

Sample-data split (client-data handling). The two MCP servers read a split store: mcp_servers/data/fake/ holds 14 synthetic stand-in deals (committed), and mcp_servers/data/client/ holds the 4 real client deals + their cat-model source_extracts/ (GITIGNORED) — real reinsurance figures for named carriers must never be committed. The loaders merge both and degrade to fake-only when data/client/ is absent (a clean checkout). For the four real deals the EFS narrative is intentionally empty, so the enriched P4/P5 ground on the committed cat-model/PDF extract — never on the gold memo.

[PH-feat-4] Formulas (computed during assembly — Phoenix does NOT export these)

DevPremium = SubjBasePML × Rate / 100
ROL        = DevPremium / Limit × 100
MLR        = OccEV / DevPremium × 100      ← computed; NOT a Phoenix field
OdyPremium = DevPremium × SignShare / 100
OdyLimit   = Limit × SignShare / 100

These are derived from gathered data, not stored — DocGen has no derived-value concept, so this is framework gap G2 (computed fields). Solution owns the formulas as pure functions; framework owns merging their outputs into the render scope.

[PH-feat-5] Insufficient-data behaviour (readiness gate)

Generation is blocked before render with a structured gap ({generation_should_proceed: false, ui_state, gaps:[…]}) when an approval with a resolved date cannot be established — the engine does not generate a memo with a fabricated or missing P1. The document surfaces as action-required with a manual-entry prompt. Status taxonomy: ready (gate passes) / attention (gate blocks, awaiting underwriter input). This is framework gap G3 (pre-generation readiness gate — distinct from the post-hoc verify phase) plus G4 (a gap may name a satisfiable_by_field so the underwriter types the missing value and re-runs).

The compute layer (phoenix_compute.resolve_for_generation) emits the verdict; the four edge cases the gate handles:

Deal Situation Required behaviour
619055 No approval comment at all block → attention
620998 Comments exist, none match the strict "{initials} approval" pattern block (do not best-guess) → attention
621557 PDF approval, handwritten date, OCR fails block, request the date via satisfiable_by_field: approval_date (no substituting the EFS upload date); the underwriter types it and re-runs (G4) → attention
618015 Multiple matching approvals not a block — selection rule: pick the most recent by artifactDate

[PH-feat-6] Sample data & the eval

Sample deals (two stores; see [PH-feat-3]). data/fake/ — 14 committed synthetic stand-ins, of which 614563 (Bamboo CAT XOL) + 618702 (Heritage Per Risk) are the GROUNDED happy paths and the rest are EDGE-CASE/SYNTHETIC. data/client/ (gitignored) — the 4 real Odyssey deals (American Integrity 616311, ACEIG 619188, Liberty Mutual 606476, American Family 606637), reconstructed from each deal's Program-Compare PDF + TSRe cat-model .xlsm.

Targets. docs/reference/phoenix-data/expected-memos.json is the byte-exact regression target for the 14 synthetic stand-ins (P1–P3) + their block verdicts. For the four real deals the answer key is eval_reports/gold-real-memos.json — the underwriter's actual referral memo, never committed (it lives in the gitignored eval dir; absent → those cases are skipped). The gold is the diff target only — never an input to generation.

The eval (scripts/demo_phoenix_eval.py). Seeds the org, runs the full gather → readiness-gate → synth → render pipeline on every doc, and scores honestly by what is checkable:

  • Synthetic stand-ins — P1–P3 must be byte-exact vs the gold (the PASS gate); blocked deals must block with the expected verdict.
  • Real deals — scored by gold-precision recall (a number is "reproduced" only when it matches at the gold's displayed precision and is genuinely the same figure) + figures-grounded (every figure DocGen states is present in the gold or our own source — proves nothing is fabricated). Surfacing more real data than the underwriter never lowers recall.
  • A source-figure digest feeds the gold's loss/PML/expiring-tower figures into the enriched P4/P5 to lift recall (rather than haystack-mining the 200k-char extract).
  • A "held in source but not surfaced" report splits the gold figures DocGen didn't surface into those present in our source (a prose gap) vs in no source we hold (the honest floor — needs a referral feed).

Supporting extraction (scripts/). build_source_extracts.py dumps every meaningful cat-model .xlsm tab + the Program-Compare .pdf into the committed data/client/source_extracts/<pid>.{md,sources.json} (the latter chunked as {url, snippet, page_text} evidence rows for the cited/verify path). extract_phoenix_comps.py decodes per-layer worksheet ROLs (quoted / broker-flat / as-if) — header-labeled grids self-identify; unlabeled grids use an audited per-deal column map — plus the loss/zone-PML block, with a _validate_extraction sanity check and a --strict flag that exits non-zero on any warning.

[PH-feat-7] Success criteria

  1. Five-paragraph contract. Every generated memo contains all five paragraphs in the fixed order — none omitted.
  2. Deterministic P1–P3. P1–P3 match expected-memos.json byte-exactly for the 14 synthetic stand-ins; identical inputs → identical P1–P3 with no model call involved.
  3. Reproducible, faithful P4–P5. Temperature-0 P4–P5; faithful to source; zero fabricated approvals, dates, amounts, or negotiations (hard requirement, not a percentage) — figures-grounded ≈ 100%.
  4. Real-deal recall. For the four real Odyssey deals, P1–P3 reproduce the gold memo's numbers, and gold coverage (recall) is reported with every gap accounted for (in-source vs not-in-any-source).
  5. Correct gating on the edge cases. 619055 / 620998 / 621557 → blocked with the right verdict (621557 via the G4 manual-date re-arm); 618015 → most-recent approval selected, not blocked.
  6. Genericity. The whole memo is produced with framework primitives + solution config + the two MCP data sources only — no Phoenix-specific changes to the DocGen engine. (The proof is in tdd.md; G1/G2/G3/G4 are framework features, not Phoenix engine code.)

Cross-references

  • TDD (the DocGen configuration + genericity proof): ./tdd.md
  • Use-case analysis + gap map: ../../explanation/phoenix-uc2-analysis.md
  • Odyssey spec (§6 = the five-paragraph contract): ../../reference/Phoenix_Firm_Order_Memo_Spec.docx
  • As-designed 2-MCP + 4-case eval reference: ../../design/specs/2026-06-19-odyssey-4case-eval-and-2mcp-design.md
  • Framework R1: ../../releases/r1-framework-v0-core/r1-tdd.md · R2: ../../releases/r2-framework-production-features/r2-tdd.md