Underwriting — the module guide

Underwriting is the governed underwriting workflow, built as the engineering-standard successor to the first-generation UW Deepfield workstation (the old UWDeepfield module, retired 2026-09-04 — the UW Deepfield name now means this module). Same domain, different construction: a normalized data model over the Reinsurance suite, the rulebook as executable business rules, small single-purpose layout areas linked by navigation, stored (never render-derived) lifecycle state, and an executed test suite on every type.

Learn the model first: the Underwriting data model walks one shipped deal from submission to decision, and pricing scopes and formulas works every SubmissionMetrics and DeskEconomics member through with its real numbers.

The four types

Type Holds Areas
Submission The offer: tower, stated economics, reported claims bordereau, stored status Structure · Compliance · Economics · Formulae · ClaimsReview · Tests
Decision Proposed lines, stored lifecycle, recorded sign-offs Governance · Tests
Guideline The rulebook's provenance (the rules are code) Compliance · Reference · Tests
Workbench Nothing — the register derives live Register · Tests

Layout areas render on instances of a type, so the module ships two singleton pages: Underwriting Desk (the Workbench instance — the live register) and Group Underwriting Guidelines (the Guideline instance — compliance sample, reference tables, rulebook tests). Submissions and decisions host their areas themselves — see the sample book.

The rulebook's reference DATA ships as four further node types, one node per row: CapacityCap (§6), AuthorityRung (§4.2), AppetiteMetric (§3) and RegionalLimit (§7) — browsable under Rulebook/Reference. The §6/§4.2 nodes are what the compliance scopes evaluate; the §3/§7 nodes are reference data the page renders (not yet wired into verdicts).

The standards this module demonstrates

  1. One model, no duplicates. Cedent, broker, line of business, currency and underwriter are the Reinsurance plugin's types, referenced by node path (dependsOn: ["Reinsurance"], one-way, no C# reference). The cedent's rating is read live off the referenced node at evaluation time — a copied rating could drift; a referenced one cannot.
  2. Rules are scopes, not prose or constants. Every evaluable rule of GUW-GL-2026-001 v4.2 is an IScope member: SubmissionMetrics (the premium math), Guideline (checks → escalations → verdict → share action, plus the §4.2 authority ladder), Governance (required vs recorded sign-offs → ready-to-bind). The compliance and reference pages render from the rules' own tables, so documentation cannot drift from behaviour.
  3. Null-honest economics. A missing combined ratio is a data-quality referral to the Head of LoB — it is never read as CR 0 ("the most aggressive stance on the worst data"), and an empty tower yields null rates, not fake zeros.
  4. Governance is stored facts + evaluated rules. The decision node stores the lifecycle status and each sign-off with who and when. The scope decides what counts: no identity or timestamp → not a record; the decider signing their own four-eyes → self-approval, never counts; a decline commits no capacity → its escalations are suppressed; the stored lifecycle gates the bind.
  5. Small areas, smart navigation. Every area is one concern, one screen, with a breadcrumb and deep links (register → submission → compliance → decision governance and back). No 13-tab monoliths, no hand-rolled HTML tables — Controls.Markdown and Controls.MeshSearch do the work.
  6. Reactive end to end, bounded reads. No async/await anywhere. Referenced-node reads are one-shot with a 10s timeout and a graceful fallback — the "silent hang on a never-emitting stream" class is structurally excluded.
  7. Tests are executed, not just compiled. Each type ships throwing Test/ methods run by its Tests layout area — the same evaluators the pages use, so the tables shown are produced by the very rules the tests pin.

Mapping from the first-generation UW Deepfield

First generation (the old UWDeepfield module) Here
TreatySubmission (~50 props, 13 tabs, 7.7k-line areas) Submission + the SubmissionMetrics scope + two small areas
TreatyDecisionEngine (616 lines of constants & switches) The Guideline scope + GuidelineTables (data, not code paths)
TreatyDecision + _Approval satellites (derived status) Decision with stored status + recorded sign-offs + the Governance scope
UwDecisionLog / portfolio walks (duplicated 6×) The Workbench register (one two-hop read pattern, derives everything)
ReinsuranceClient vs Cedent (two cedent models) Reinsurance/Cedent, referenced — no second model
Prose guideline (UnderwritingGuideline.md) Executable scopes + a Reference page rendered from the rules' tables

The claims reconciliation

A renewal submission carries the cedent's reported claims bordereau (ReportedClaims on the record). The ClaimsReview area joins it against our own Claims records of the same cedent (the ReconciliationRules scope): ✅ amounts agree, ⚠ amounts differ, ➕ reported but not on our books, ❓ on our books but missing from the bordereau.

Which of our claims take part (ClaimMatching): a claim of the submission's cedent is matched by claim reference first — the bordereau names each claim by the cedent's reference — then by policy: a claim whose Acceptance (the Reinsurance/Acceptance it falls under, shown as a link on the claim's Development page) is one the submission lists in acceptances — and by programme last (our claims booked under the submission's programme, reported or not). A renewal is usually filed under a programme key of its own (the submission reference) while its reported claims were booked on the EXPIRING treaty's programme, so matching on the programme alone showed a claim we hold as "➕ New to us". The claim discovery is therefore narrowed by the cedent only, never by the programme, over the anchored QueryReach legs (own partition, then the sample book); the cache id is uw-our-claims-by-cedent:{partitions}:{cedent}:{programme} (a NEW name: the cache ignores changed queries on an id hit). A claim of another cedent never matches, even on an equal reference.

Alternative options are never added up

Quoted layers that overlap (ordered by attachment, one attaches below the exhaustion of the one beneath it) are mutually exclusive options — "deductible 150,000" OR "deductible 225,000" on the same limit — not a tower; at most one of them binds. SubmissionOptions is the one rule (AreAlternatives, the same test StructureArea.AreAlternatives applies) and the split (SubmissionOptions.Of: one bindable programme per option, or the whole tower). Every total is per bindable programme:

Tower layers of one programme (true XL layers that stack) still add up everywhere — both cases are pinned in the Tests area (Economics_AlternativeOptionsAreNeverSummed, Offer_AlternativeOptionsPerOption, TowerLayers_StillAddUp). Where the pack reports more outstanding than we hold, the page offers the two follow-ups: take over the reported outstanding (appended to the claim as a Source = Submission development — history is appended, never rewritten) or enquire with the client (a Draft Claims/Enquiry under the claim asking for a formal advice). The worked flow ships in ReinsuranceDemo.

How each number is calculated — the Formulae page

A submission's Formulae area (/{submission}/Formulae) states every figure the Economics page shows, one section each: the definition in words, the formula, and the formula worked with this deal's figures — premium at 100%, blended rate on line, our line, our premium, the combined ratio on each basis, rate on line per layer and its minimum, the premium split and margin, every waterfall step (loss ratio and its basis, expected loss, external and internal cost, technical profit, capital, cost of capital, expected profit, RoE), our priced loss ratio and the desk score (Desk ranking). The worked values are computed by the very functions the Economics page calls (SubmissionCalc.Metrics, DeskEconomicsCalc.Assess / Score, EconomicsReport's formatters) — never a second implementation — and alternatives are worked per option, as the page shows them.

Every section has a stable anchor (FormulaAnchors: premium-100, blended-rol, our-line, our-premium, treaty-combined-ratio, expected-profit, desk-score, …), rendered into its heading as {#anchor} and also the section's view name. Economics links in: "How each number is calculated →" under the title, a row of ƒ links under the KPI strip and under the waterfall, a small ƒ formula link on every section heading, and the footer row; each formula section links back. FormulaeTests (in the Tests area) pins that every KPI tile and every waterfall step has its section and that the worked value equals the value the tile shows.

The scopes and the data model, on the same page

The page opens with the computations as the code writes them — the Scopes view (#scopes) — and the Data model they run over (#data-model); the formula-by-formula sections follow (#formulae).

ScopeSourceTests pins the reader (bodies, summaries, comments and strings), the dependency edges (BlendedRateOnLine → Premium100, TotalLimit), that every member value is the KPI tile's / waterfall's figure, the anchors, and the data-model composition; its two live-source cases run in the Tests area only, reading the source nodes off the mesh as the page does, and pin the parsed members against the compiled interface by reflection.

The submission pages are templates

Structure (and the Overview it backs), Tower, Compliance, Offer and ClaimsCompare return their whole control tree at once and BIND their values (the platform rule: Doc/GUI/DataBinding → Templates first, data later). Each has a view record and a pure Project(...) — the Structure page opens with the title and reference, then the same structured header the catalog card shows, and the quoted and the placed layer grids are both in its tree, a bound style showing the one the submission has; its towers are an item template over TowerCards — one tower, or one per option when the quoted layers overlap and are therefore alternatives (StructureArea.AreAlternatives); the comparison's claim cards are an item template over the reconciliation rows; every tower is a TowerControl at the bounded TowerDrawing.Height, its bands bound. SubmissionTemplateTests (in the Tests area) pins every template and projection. Still built from data, and why: Economics (its charts are built from the figures; a bound chart model is a platform question) and ClaimsReview (its grid cells and the enquiry composer act on the clicked row — a template button cannot know its row).

The submission header — breadcrumb, lifecycle chips, field strips

A submission shows its structured fields the same way wherever it appears — the Submission Book's catalog card (SubmissionCardArea.Card) and the top of its own Overview — because both views derive from one record, SubmissionStripView, filled by SubmissionStrip.Fill and drawn by SubmissionStrip.Template() (both in Submission/Source/SubmissionLifecycle.cs):

LONE STAR MUTUAL › MDSL 2027 · Maple Retail Co.                ← breadcrumb (cedent › programme · insured), linked
📬 Received 26 Sep → 💬 Quoted 3 Oct → 📤 Offered — → ✓ Bound —  ← lifecycle chips, each opens its screen, exact date in the tooltip
Under assessment · Julia Morales · follow-up 1 Oct ⚠ 12d overdue ← status strip
Premium USD 768,768 · 2 options · 1 reported claim              ← field strip

The lifecycle is read off stored fields, never invented (SubmissionLifecycle.Steps, pinned by SubmissionLifecycleTests): Received is always reached (dated ReceivedOn) and opens ClaimsCompare; Quoted is reached once FirstQuotedOn/LastQuotedOn is recorded or the status is past assessment, and opens Compliance; Offered opens the Offer page and is reached at the statuses Quoted, Bound and Not taken up — nothing on the record stores an "offer sent" date, and the status Quoted is defined as "our quote has been communicated to the broker", so it IS that event (dated StatusChangedAt while Quoted, else the last quote sent); the outcome is Bound (green), Declined / Not taken up (red, the closed reason in the tooltip), dated StatusChangedAt. A step not reached is a muted chip reading "—"; the last reached step of an open deal is drawn bold. A follow-up past due on an OPEN submission is marked ⚠ in the warning tone. The Overview's Deal and Follow-up facts below the header are field strips too (broker, line of business, programme, inception, term; assistant, priority, probability of close, awaiting, status reason, last activity); a value that is not set hides its slot.

The controls come from Underwriting/Source/ChipBar.cs — Chip(text, href, tooltip, tone), Breadcrumb(segments), FieldStrip(fields), the CRM's chip-bar shape (a chip is a navigating badge, never a button; every colour a hue mixed into a theme token). It is an interim partition-level helper: the same three are moving into the platform (MeshWeaver.Layout: Controls.ChipBar / Controls.Breadcrumb / Controls.FieldStrip); when they ship, swap the call sites and delete the file. The facts used to be markdown tables built in a StringBuilder (SubmissionHeader.Card) — structured data as hand-built markup, which AGENTS.md forbids.

A renewal often references the policy it renews (Submission.Acceptances naming last year's Reinsurance/Acceptance). That is not the deal's placement: an acceptance whose underwriting year precedes the submission's inception year is the expiring policy (StructureArea.IsExpiring). The Structure page then keeps the quoted options as the offer (one tower per alternative, the quoted grid) and draws the expiring policy BESIDE them — its own tower card "Expiring policy · UWY 2025" and its own labelled grid, "for comparison, not part of the offer". An acceptance of the submission's own year (the demo's MTPL2027), one with no year, and one that could not be read all still count as placed, as before.

A submission's "back to the register" links (the Compliance, Offer, Economics, Claims compare and Claims review crumbs and hand-overs) lead to its OWN partition's books: the partition's Underwriting/SubmissionBook and its Claims/Register (the {Cedent}/ClaimsBook convention), read live from the partition's root children (SubmissionRegisters). The global desk (/Underwriting/Desk/Register) and claims book (/Claims/Book/Register) are the fallback only when the partition has none. A claim's page does the same for its claims register (ClaimAreas.RegisterHref).

The submission book

Underwriting/SubmissionBook is a book of submissions over named cedent partitions. It is configuration only: partitions, brokers, folder (default Submissions) and an optional intro. Leave partitions empty and it reads its own top-level partition.

Page What
Submissions (default) a CatalogApp: one tab per folder (Pipeline · Waiting on client · My follow-ups · Overdue · Offers out · Bound · Declined & lost · Renewals · New business · All), one shared search bar, a 3-column card grid of 24 per page. Each hit renders through Underwriting/Submission's Card area.
Options the Reinsurance/Acceptance quote options, a tab per partition, grouped by submission so a deal's options sit side by side; each hit renders through the acceptance's Card.
Book the book overview, derived live by cedent and treaty: premium, paid losses, open reserve, loss ratio, the pipeline by status, the brokers.

Every folder is ONE hidden query anchored on the partitions (namespace:(A/Submissions OR B/Submissions)), so the store filters and pages. Overdue is content.followUpDue:<today, My follow-ups is content.responsibleUser:<viewer>. Nothing is filtered in memory, and nothing sweeps the mesh.

Deliberately out of scope (v1)