Risk Adjustment & Coverage Units

The risk adjustment for non-financial risk and the coverage units that release the contractual service margin are the two IFRS 17 quantities that decide how much profit an insurer reports each period and how much compensation it holds for uncertainty — and both must be computed reproducibly, disclosed at a stated confidence level, and reconciled against the same cash-flow model. This guide, part of the IFRS 17 & LDTI Filing Automation reference architecture, shows how to derive the risk adjustment under the confidence-level and cost-of-capital techniques of IFRS 17 paragraphs 37 and B86–B92, how to define coverage units under paragraph B119, and how the two feed insurance revenue alongside the Contractual Service Margin Automation engine.

Risk adjustment release and coverage-unit-driven CSM release converging into IFRS 17 insurance revenue Coverage units, equal to quantity of benefit times expected in-force, drive the release fraction applied to the contractual service margin balance to give the CSM release. Separately the risk adjustment for non-financial risk gives a risk adjustment release for expired risk. Both the CSM release and the risk adjustment release flow into insurance revenue for the period. Coverage units benefit × in-force CSM balance unearned profit Risk adjustment non-financial risk CSM release × release fraction RA release expired risk Insurance revenue for the period
Coverage units set the fraction of contractual service margin released; the risk adjustment releases as non-financial risk expires; both land in insurance revenue.

The Problem

Under the IFRS 17 general measurement model, the carrying amount of a group of insurance contracts is the sum of the fulfilment cash flows and the contractual service margin. The fulfilment cash flows themselves decompose into the present value of future cash flows plus an explicit risk adjustment for non-financial risk, which paragraph 37 defines as the compensation the entity requires for bearing the uncertainty in the amount and timing of cash flows that arises from insurance risk, lapse risk, and expense risk — pointedly excluding financial risk, which the discount rate already captures. The risk adjustment is not a prudential margin picked by regulation; it is an entity-specific measurement of the compensation demanded, and paragraph 119 requires the entity to disclose the confidence level to which that compensation corresponds even when it did not use a confidence-level technique to derive it.

The second quantity is quieter but just as consequential. The contractual service margin represents unearned profit, and paragraph B119 requires that it be recognised in profit or loss over the coverage period by allocating it to coverage units. The number of coverage units in a group is the quantity of the benefits provided under the contracts multiplied by their expected coverage duration — a single scalar per period that determines what share of the unearned profit becomes insurance revenue now versus later. Get the risk adjustment wrong and the liability and the disclosed confidence level are misstated; get the coverage units wrong and profit emerges on the wrong pattern across every reporting period until run-off. Both are recomputed every cycle, both feed the same insurance-revenue line, and both must reconcile bit-for-bit against the cash-flow model that a national supervisor or an auditor will re-run. That reproducibility requirement is the reason the two calculations belong in one deterministic, version-pinned engine rather than two spreadsheets.

Architecture

The engine has three stages that share one input: the projected, in-force-weighted cash-flow set for a group of contracts. The first stage computes the risk adjustment from that projection under whichever technique the entity has adopted as policy. The second stage computes the coverage-unit vector — one figure per future period — from the quantity of benefit and the expected in-force pattern. The third stage combines them: the coverage-unit vector defines the release fraction that draws down the contractual service margin, the change in risk adjustment for expired risk defines the risk-adjustment release, and the two releases are summed into insurance revenue for the period, as the diagram above traces.

Keeping the stages separate matters because they answer to different policy choices and different disclosures. The risk-adjustment technique — confidence level versus cost of capital — is an accounting policy that must be applied consistently and reconciled back to a disclosed confidence level. The coverage-unit definition — the choice of benefit quantity, and whether the units are discounted for the time value of money — is a separate judgement under B119 that the IFRS 17 Discount Rate Unlocking discipline directly touches, because a discounted coverage-unit basis uses the same locked-in curve the margin accretes at. The margin balance the release fraction is applied to is maintained by the Contractual Service Margin Automation roll-forward, and the assumption drift that would silently move either quantity between valuations is watched by Dynamic Threshold Tuning for Assumption Drift. This guide is the layer that turns a cash-flow projection into the two IFRS 17 measurements those neighbours consume and monitor.

Prerequisites

Before implementing the engine, the following should be in place:

  • Python 3.11+ with numpy for the vectorised present-value and quantile arithmetic and pandas for the per-period ledger the disclosures are built from.
  • A projected cash-flow set for the group of contracts: best-estimate net outflows by future period, plus, for the confidence-level technique, a distribution of the present value of those outflows (typically the scenario reserves from a stochastic run).
  • A locked-in discount curve for the group’s initial recognition, since coverage units may be discounted at that curve and the risk adjustment’s cost-of-capital variant discounts projected capital along it. Curve construction is covered by the parent IFRS 17 & LDTI Filing Automation reference and its discount-rate guidance.
  • A recorded risk-adjustment accounting policy: the chosen technique, the target confidence level or cost-of-capital rate, and the diversification benefit assumed across risk types.
  • A recorded coverage-unit policy: the benefit-quantity definition (for example the death benefit in force, or a maximum-amount-payable proxy) and whether units are discounted, both applied consistently across the group per B119.
  • A calibrated CSM balance at the start of the period from the Contractual Service Margin Automation roll-forward, since the release fraction is meaningless without the balance it draws down.

Core Implementation

The engine below computes the risk adjustment under both permitted techniques, derives the coverage-unit vector, and returns the coverage-unit-based release fraction that the margin roll-forward applies. The two risk-adjustment methods are exposed side by side so the disclosed confidence level can be reconciled against whichever one is the accounting policy.

from __future__ import annotations

from dataclasses import dataclass

import numpy as np


@dataclass(frozen=True)
class GroupInputs:
    """Projected inputs for one IFRS 17 group of contracts."""
    benefit_quantity: np.ndarray     # quantity of benefit per future period
    expected_in_force: np.ndarray    # expected proportion still in force per period
    discount_factors: np.ndarray     # locked-in curve discount factor per period
    required_capital: np.ndarray     # projected non-financial-risk capital per period
    pv_outflow_scenarios: np.ndarray # PV of net outflows, one per stochastic scenario


def risk_adjustment_confidence_level(
    pv_outflow_scenarios: np.ndarray,
    confidence_level: float = 0.75,
) -> float:
    """RA as the excess of the target quantile over the mean PV of outflows.

    IFRS 17 paragraph B87: the RA reflects the compensation for bearing
    non-financial-risk uncertainty. The confidence-level technique reads it
    as the distance from the expected value to the chosen quantile.
    """
    mean_pv = float(pv_outflow_scenarios.mean())
    quantile_pv = float(np.quantile(pv_outflow_scenarios, confidence_level))
    return max(quantile_pv - mean_pv, 0.0)


def risk_adjustment_cost_of_capital(
    required_capital: np.ndarray,
    discount_factors: np.ndarray,
    cost_of_capital_rate: float = 0.06,
) -> float:
    """RA as the discounted cost of holding non-financial-risk capital.

    IFRS 17 paragraphs B91-B92 permit a technique other than confidence
    level; the cost-of-capital form charges a rate on projected capital and
    discounts it back on the locked-in curve.
    """
    return float(cost_of_capital_rate * np.sum(required_capital * discount_factors))


def implied_confidence_level(
    risk_adjustment: float,
    pv_outflow_scenarios: np.ndarray,
) -> float:
    """Back out the confidence level a given RA corresponds to.

    Paragraph 119 requires disclosing the confidence level even when the RA
    was set by cost of capital, so we invert the empirical distribution.
    """
    mean_pv = float(pv_outflow_scenarios.mean())
    target = mean_pv + risk_adjustment
    return float((pv_outflow_scenarios <= target).mean())


def coverage_units(
    benefit_quantity: np.ndarray,
    expected_in_force: np.ndarray,
    discount_factors: np.ndarray | None = None,
) -> np.ndarray:
    """Coverage units per period under IFRS 17 B119.

    Quantity of benefit x expected coverage duration, where duration enters
    through the expected in-force weight. Discounting is an accounting policy
    choice; pass discount_factors only if the group discounts its units.
    """
    units = benefit_quantity * expected_in_force
    if discount_factors is not None:
        units = units * discount_factors
    return units


def csm_release_fraction(coverage_unit_vector: np.ndarray) -> float:
    """Fraction of the CSM released this period per B119.

    Current-period units over current-plus-remaining units. The denominator
    is the total coverage still to be provided, so the fraction rises as the
    group runs off and reaches 1.0 in the final period.
    """
    total = float(coverage_unit_vector.sum())
    if total <= 0.0:
        return 0.0
    return float(coverage_unit_vector[0]) / total


def measure_group(inputs: GroupInputs, policy: str, csm_balance: float) -> dict:
    """Produce the RA, coverage units, and CSM release for one period."""
    if policy == "cost_of_capital":
        ra = risk_adjustment_cost_of_capital(
            inputs.required_capital, inputs.discount_factors
        )
    else:
        ra = risk_adjustment_confidence_level(inputs.pv_outflow_scenarios)

    units = coverage_units(
        inputs.benefit_quantity,
        inputs.expected_in_force,
        inputs.discount_factors,
    )
    fraction = csm_release_fraction(units)
    return {
        "risk_adjustment": ra,
        "disclosed_confidence_level": implied_confidence_level(
            ra, inputs.pv_outflow_scenarios
        ),
        "coverage_units_current": float(units[0]),
        "csm_release_fraction": fraction,
        "csm_released": csm_balance * fraction,
    }

Formally, the confidence-level risk adjustment is the distance from the expected present value of net outflows LL to its α\alpha-quantile,

RACL  =  Qα(L)    E[L],RA_{CL} \;=\; Q_{\alpha}(L) \;-\; \mathbb{E}[L],

while the cost-of-capital variant charges a rate cc on projected non-financial-risk capital CtC_t discounted on the locked-in curve,

RACoC  =  ct=0nCt(1+rt)t.RA_{CoC} \;=\; c \sum_{t=0}^{n} \frac{C_t}{(1 + r_{t})^{t}} .

The coverage-unit release fraction applied to the margin in period tt is the current period’s units over all units still to be provided,

ft  =  uts=tnusv(t,s),us=qsws,f_t \;=\; \frac{u_t}{\displaystyle\sum_{s=t}^{n} u_s\, v(t,s)}, \qquad u_s = q_s \, w_s,

where qsq_s is the quantity of benefit, wsw_s the expected in-force weight, and v(t,s)v(t,s) the discount factor applied only when the group’s policy discounts its coverage units. The focused mechanics of that per-period vector and its release fraction are worked line by line in Calculating IFRS 17 Coverage Units in Python.

Configuration and Tuning

Three configuration decisions change the reported numbers materially, and each must be a recorded, version-pinned value rather than a default buried in code. The first is the risk-adjustment technique and its parameter — the target confidence level for the quantile method, or the cost-of-capital rate for the capital method. The second is the diversification assumption: risk adjustments computed per risk type must be aggregated with an explicit correlation, and a full-diversification assumption understates the liability while a no-diversification assumption overstates it. The third is whether coverage units are discounted.

from dataclasses import dataclass


@dataclass(frozen=True)
class MeasurementPolicy:
    """Recorded, version-pinned IFRS 17 measurement choices for a group."""
    ra_technique: str = "cost_of_capital"   # or "confidence_level"
    confidence_level: float = 0.75          # target alpha if quantile method
    cost_of_capital_rate: float = 0.06      # charge on capital if CoC method
    discount_coverage_units: bool = True    # B119 policy choice
    diversification_corr: float = 0.30      # cross-risk correlation for aggregation
    policy_version: str = "2026H1.2"        # pinned for reconciliation


def aggregate_risk_adjustment(
    ra_by_risk: dict[str, float],
    correlation: float,
) -> float:
    """Aggregate standalone RAs with a single off-diagonal correlation.

    A correlation below 1.0 recognises that non-financial risks do not all
    bite at once; the diversified total is the square root of the quadratic
    form, always at or below the simple sum.
    """
    values = list(ra_by_risk.values())
    variance = sum(a * b * (1.0 if i == j else correlation)
                   for i, a in enumerate(values)
                   for j, b in enumerate(values))
    return float(variance ** 0.5)

Two tuning notes carry the most reconciliation risk. First, the discounting policy for coverage units must match the disclosure: if the units are discounted, the disclosed release pattern and the IFRS 17 Discount Rate Unlocking curve are linked, and switching the flag between the dress-rehearsal close and the filed close changes profit emergence even though the ultimate profit is invariant. Second, the diversification correlation is the single most examined assumption in the risk adjustment; hold it stable, document its basis, and never let it drift silently — which is exactly the class of change Dynamic Threshold Tuning for Assumption Drift is built to flag.

Step-by-Step

  1. Assemble the group’s projection. Gather the best-estimate net outflows, the in-force weights, the benefit quantities, and — for the confidence-level technique — the distribution of the present value of outflows across scenarios.
  2. Compute the risk adjustment under the chosen technique. Run the confidence-level quantile-minus-mean or the cost-of-capital capital charge, then aggregate across risk types with the recorded diversification correlation.
  3. Back out the disclosed confidence level. Invert the empirical distribution so the paragraph 119 disclosure is available regardless of which technique produced the number.
  4. Build the coverage-unit vector. Multiply benefit quantity by expected in-force per period, applying the locked-in discount factors only if the group’s policy discounts its units.
  5. Derive the release fraction. Divide the current period’s units by the total remaining units to get the share of the contractual service margin recognised this period.
  6. Release the margin and the risk adjustment. Apply the fraction to the opening CSM balance and recognise the change in risk adjustment for expired risk, summing both into insurance revenue.
  7. Seal the run. Record the policy version, the technique parameters, the coverage-unit basis, and a checksum of the inputs so the whole measurement replays deterministically.

Validation and Testing

Correctness here means three invariants hold: the release fraction reaches exactly 1.0 in the final period so the margin fully unwinds, the disclosed confidence level round-trips against the risk adjustment that produced it, and a diversified aggregate never exceeds the undiversified sum.

import numpy as np
import pytest


def test_final_period_releases_everything():
    """The last coverage-unit period must release the whole CSM."""
    units = np.array([120.0])  # only one period remains
    assert csm_release_fraction(units) == pytest.approx(1.0)


def test_confidence_level_round_trips():
    """Disclosing the CL of a CL-derived RA must recover the input alpha."""
    rng = np.random.default_rng(20260701)
    scenarios = rng.lognormal(mean=13.0, sigma=0.25, size=100_000)
    ra = risk_adjustment_confidence_level(scenarios, confidence_level=0.75)
    disclosed = implied_confidence_level(ra, scenarios)
    assert disclosed == pytest.approx(0.75, abs=1e-2)


def test_diversification_never_increases_ra():
    """A correlation below 1.0 cannot raise the aggregate risk adjustment."""
    ra_by_risk = {"mortality": 40.0, "lapse": 30.0, "expense": 20.0}
    diversified = aggregate_risk_adjustment(ra_by_risk, correlation=0.30)
    undiversified = sum(ra_by_risk.values())
    assert diversified <= undiversified

Beyond unit tests, reconcile the sum of every period’s coverage-unit-based release against the opening margin: the released fractions across the run-off must sum to the whole balance, net of interest accretion, with no residual stranded in the final period. Track the disclosed confidence level and the diversified risk adjustment across successive closes and route their movement into the drift monitoring that Dynamic Threshold Tuning for Assumption Drift governs, so an unexplained jump surfaces before the disclosure is filed.

Failure Modes and Gotchas

  • Discounting the coverage units inconsistently. Whether units are discounted is a B119 policy choice, but it must be applied to every group the same way and match the disclosed release pattern. Discounting one group and not another, or flipping the flag mid-year, distorts profit emergence and breaks the reconciliation to the prior close.
  • Confusing quantity of benefit with premium or account value. Coverage units measure the service of insurance coverage, not the money collected. Using premium or account value as the benefit quantity releases the margin on a savings pattern rather than a coverage pattern and misstates insurance revenue.
  • Assuming full diversification by accident. Aggregating standalone risk adjustments by simple addition assumes zero diversification; using a single low correlation across unlike risks can assume too much. Either extreme is a finding — pin the correlation, document its basis, and test that the diversified total never exceeds the sum.
  • A stranded final-period balance. If the release fractions across the run-off do not sum to the whole margin, a residual is left unrecognised at run-off. This is almost always a coverage-unit vector that omits the last period or an in-force weight that never reaches zero; assert the fractions telescope to 1.0.
  • Disclosing a confidence level the technique never targeted. Under the cost-of-capital method the confidence level is an output, not an input, and must be backed out of the empirical distribution. Reporting the cost-of-capital rate as if it were a confidence level fails paragraph 119.
  • Silent risk-adjustment drift. The diversification correlation and the capital projection feed the risk adjustment through several multiplications; a small unreviewed change to either moves the liability. Route both into assumption-drift monitoring so the movement is explained rather than discovered.

Frequently Asked Questions

What is the difference between the confidence-level and cost-of-capital techniques for the risk adjustment?

The confidence-level technique reads the risk adjustment as the distance from the expected present value of net outflows to a chosen quantile of their distribution, so the confidence level is an input. The cost-of-capital technique charges a rate on projected non-financial-risk capital and discounts it back, so the confidence level becomes an output that must be inferred from the distribution to satisfy the disclosure.

Why must IFRS 17 preparers disclose a confidence level even when they do not use one?

Paragraph 119 requires the confidence level corresponding to the risk adjustment to be disclosed regardless of the technique used to determine it, so users can compare the compensation different insurers hold for non-financial risk. When a cost-of-capital method sets the amount, the equivalent confidence level is backed out by finding the probability that the present value of outflows falls below the mean plus the risk adjustment.

How do coverage units release the contractual service margin?

Under paragraph B119 the contractual service margin is recognised in insurance revenue by allocating it across coverage units, where the units in a period equal the quantity of benefit provided times the expected coverage duration. The share released in a period is that period’s units over all units still to be provided, so the fraction rises through run-off and recognises the whole margin by the final period.

Should coverage units be discounted?

Discounting coverage units for the time value of money is an accounting policy choice that must be applied consistently across groups. Discounting weights earlier coverage more heavily and accelerates margin recognition; not discounting treats a unit of coverage as equally valuable whenever it is provided. The choice must match the disclosed release pattern and the locked-in discount curve the margin accretes at.

Up a level: IFRS 17 & LDTI Filing Automation

Regulatory references: IFRS 17 Insurance Contracts, paragraphs 37, 119, and B86–B92 and B119 (IFRS Foundation).