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
- 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. - Rules are scopes, not prose or constants. Every evaluable rule of GUW-GL-2026-001 v4.2 is an
IScopemember: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. - 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
nullrates, not fake zeros. - 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.
- 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.MarkdownandControls.MeshSearchdo the work. - Reactive end to end, bounded reads. No
async/awaitanywhere. 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. - Tests are executed, not just compiled. Each type ships throwing
Test/methods run by itsTestslayout 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:
- Economics — one KPI strip, one waterfall and one small tower per option, each with its own premium at 100%, line and our premium; the rate-on-line chart compares the options without a blended line; the commentary speaks per option.
- Offer — one grid row per option carrying its own capacity and premium, no total across them, and the guideline verdict run per option (each at its own line and rate).
- Pricing models — Offer and Economics link each quoted option (layer i, 1-based) to its
LossModelling/StopLossModelat{submission}/Option{i}/Pricing(itsResultarea) when one exists; no model, nothing shown. ONE anchored shell read of the submission's subtree (PricingModels, cache iduw-pricing-models:{submission}). - Compliance inputs —
RulesEngine.BuildStategrades the deal atSubmissionOptions.CommittableGrossLine: the tower's line, or the LARGEST single option's — never the sum of the alternatives. A proposed line, when given, still wins.
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).
- One card per declaration:
SubmissionMetrics(the business-rules scope —#scope-submission-metrics),DealEconomics(the waterfall record:DeskEconomicsCalc.Assesssets its inputs, its expression-bodied members compute the rest —#scope-deal-economics) andDeskEconomicsCalc(the desk's declared constants and the functions that assess and score a deal —#scope-desk-economics-calc).DeskEconomics.csholds a record and a static class, not a scope, so both are shown the same way a scope is. - Each card has a member grid (framework
DataGrid): member, kind (rule / input / constant / function), its///summary, the expression exactly as written, the value for this deal (per option for alternatives) and what it depends on; a dependency diagram (UmlClassDiagram, one box per member with its value, an edge to every member its expression names, the scope'sGetStorage()and another card's constants as subdued boxes); and the whole declaration in a read-only code editor. One editor per card, not per row: a Monaco instance per one-line body would be ~30 editors on a page, and the block-bodied functions (Assess,Score) need the editor anyway. - The expressions cannot drift. They are read from the Source Code nodes the mesh compiles
(
Underwriting/Source/SubmissionMetrics,Underwriting/Source/DeskEconomics) through one exact-path synced query each (uw-scope-source:{path},contentselected) and parsed byScopeSource— a small C# reader that masks comments and literals and splits the declaration into members. The platform has no scope metadata that keeps a member's expression (a scope compiles to IL and is evaluated by a non-virtual call), so the source node is the only place the text exists. The parsed members are checked against the compiled type by reflection; a disagreement (an un-recompiled type, a source edited since) is shown on the card, never hidden. - The values come from the evaluation the Economics page runs (
SubmissionCalc.Metrics,DeskEconomicsCalc.Assess/Score, per bindable programme) with its formatters, so a row reads exactly what the KPI tile shows. - The data model is drawn from the schemas the types publish (
@{type}/schema/, the Data Model Explorer's read):Submission◆SubmissionLayer/SubmissionPricing, → the option'sReinsurance/Acceptance◆ theLossModelling/StopLossModelunder it (itsPricingchild node) ◆StopLossPricingResult, which the submission's stored pricing summarises. The Explorer's ownUmlarea is linked, not embedded: it draws node references only, so the value records and the child pricing node would be missing, and it lives in a package an Underwriting install does not require. A schema that cannot be read (its module not installed) is named under the diagram, never drawn as if complete. - Each formula section's "In the code" reference links to its declaration's card.
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's expiring policy, and where a submission's links lead
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)
- Accumulation / zonal limits (§7–§8) and the cedent-concentration control (§3) — they need book-level exposure aggregation the model does not yet carry; adding them is a scope over a book state, not a rewrite.
- FX conversion — guideline caps are EUR; the state builder takes EUR figures (samples are EUR). A currency-aware build belongs beside a proper FX node, not as a hard-coded grid.
- Broker communications, ingestion, tasks/attention, scenarios — separate concerns; the clean submission→decision spine is the foundation they attach to.
- Write-actions on the pages (propose / sign-off / bind buttons) — the v1 pages evaluate and navigate; mutations follow once the module's read spine is proven on a mesh.