The brief. A treaty specified only in prose — the way they actually arrive. Your first deliverable is not code: it is a battery of assertions, one pinned net per sentence of the spec (and one per collision between sentences). Only when the battery is written — and failing against the stub — do you implement Ceded. The sign rule is easy; the two boundary rules interact, and the interactions are where a real cession quietly leaks money.

This is the hard version of Lesson 1's claim that ceded is just a negative amount. Yes — but how much is negative depends on rules that read innocently until they meet each other. Pin them before you code, and you will never ship a cession that cedes more than the claim, or one that quietly cedes the attritional bulk.

The spec — all of it, in prose (the only prose you get)

Alpina places a per-risk facultative treaty over the 2025 book. Ceded(claim, kind, gross) returns the ceded amount (stored negative); net is always gross + Ceded(...).

  1. Flat cession. Every individual claim cedes a flat 100 — so its net is gross − 100.
  2. No over-cession. A certificate can never cede more than the claim is worth. The ceded amount is capped at the claim's gross, so net never goes below 0.
  3. The attritional bulk is untouchable. The attritional amount has no individual claim sizes, so a per-claim treaty cannot cede from it — its ceded is 0, its net stays the gross.

The rules of the game

Your workbench

using MeshWeaver.Layout;
using MeshWeaver.Layout.Composition;
using MeshWeaver.Layout.DataGrid;
using MeshWeaver.Utils;

// YOUR TREATY — nothing yet. Implement the three sentences of the spec, in order.
// Ceded is stored NEGATIVE; net = gross + Ceded(claim, kind, gross).
static double Ceded(string claim, string kind, double gross)
    => 0.0;   // TODO: flat 100 per INDIVIDUAL claim, capped so net never < 0, and 0 from the attritional bulk.

record Case(string Spec, string Claim, string Kind, double Gross, double ExpectedNet);
record Row(string Spec, string ExpectedNet, string ActualNet, string Result);

// TWO rows are seeded (both plain claims). Grow this battery straight from the prose —
// the floor (C1 = 80, C6 = 90) and the untouchable attritional (ATT) are where the marks
// are — BEFORE you trust your implementation.
var cases = new[]
{
    new Case("plain claim: C4 760 cedes 100 -> net 660", "C4", "Individual", 760, 660),
    new Case("plain claim: C2 240 cedes 100 -> net 140", "C2", "Individual", 240, 140),
};

var rows = cases.Select(c =>
{
    var net = c.Gross + Ceded(c.Claim, c.Kind, c.Gross);
    var ok = Math.Abs(net - c.ExpectedNet) < 1e-9;
    return new Row(c.Spec, c.ExpectedNet.ToString("N0"), net.ToString("N0"), ok ? "✓ green" : "✗ red");
}).ToArray();

var green = rows.Count(r => r.Result.StartsWith("✓"));

Controls.Stack
    .WithView(Controls.H4($"Treaty battery — {green}/{rows.Length} green (grow it, then implement)"))
    .WithView(Controls.DataGrid(rows)
        .WithColumn(new PropertyColumnControl<string> { Property = nameof(Row.Spec).ToCamelCase() }.WithTitle("Spec sentence"))
        .WithColumn(new PropertyColumnControl<string> { Property = nameof(Row.ExpectedNet).ToCamelCase() }.WithTitle("Expected net").WithAlign("end"))
        .WithColumn(new PropertyColumnControl<string> { Property = nameof(Row.ActualNet).ToCamelCase() }.WithTitle("Actual net").WithAlign("end"))
        .WithColumn(new PropertyColumnControl<string> { Property = nameof(Row.Result).ToCamelCase() }.WithTitle("Result"))
        .Resizable(false))

A hint that is not a spoiler: three of the seven claims do not simply lose 100. Two are too small to give it, and one is not a claim at all. Your battery must know which before your code does.


Battery written, treaty green — or arguing with a sentence? Reveal the solution — the full seven-claim battery with both interactions pinned, and the two-line treaty that satisfies it.

← Lesson 1 exercises · Lesson 1 — From gross to net

Reconnecting…
The connection to the server was interrupted. Trying to restore it…
Trying again…
The connection could not be restored. Reloading the page…
The server was updated. Reloading the page to pick up the latest version.