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.
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
numpyfor the vectorised present-value and quantile arithmetic andpandasfor 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 to its -quantile,
while the cost-of-capital variant charges a rate on projected non-financial-risk capital discounted on the locked-in curve,
The coverage-unit release fraction applied to the margin in period is the current period’s units over all units still to be provided,
where is the quantity of benefit, the expected in-force weight, and 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
- 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.
- 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.
- Back out the disclosed confidence level. Invert the empirical distribution so the paragraph 119 disclosure is available regardless of which technique produced the number.
- 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.
- 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.
- 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.
- 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.
Related
- Contractual Service Margin Automation — the roll-forward that maintains the margin balance the release fraction draws down.
- Calculating IFRS 17 Coverage Units in Python — the focused per-period coverage-unit and release-fraction walkthrough.
- IFRS 17 Discount Rate Unlocking — the locked-in curve used to discount coverage units and cost-of-capital charges.
- Dynamic Threshold Tuning for Assumption Drift — monitoring the diversification and capital assumptions that move the risk adjustment.
Up a level: IFRS 17 & LDTI Filing Automation
Regulatory references: IFRS 17 Insurance Contracts, paragraphs 37, 119, and B86–B92 and B119 (IFRS Foundation).