Skip to content

Part 13: ENBP compliance verification

Part 13: ENBP compliance verification

After solving, always verify ENBP compliance per-policy rather than relying solely on the constraint having been active during the solve. This is a belt-and-braces check that catches implementation errors.

# Compute adjusted premium and NB equivalent for each renewal policy
# Use Polars for the computation, then extract numpy arrays for the check

renewal_flag_np = df["renewal_flag"].to_numpy()

# factor_adj: the solved multipliers from Part 9
factor_adj  = result.factor_adjustments

adj_premium = df["current_premium"].to_numpy().copy()
nb_equiv    = df["current_premium"].to_numpy().copy()

for fname in FACTOR_NAMES:
    m = factor_adj.get(fname, 1.0)
    adj_premium = adj_premium * m
    if fname not in fs.renewal_factor_names:
        nb_equiv = nb_equiv * m

# Check: for every renewal policy, adjusted_renewal <= NB_equivalent
# Allow 1p tolerance for floating-point rounding
violations = (adj_premium[renewal_flag_np] > nb_equiv[renewal_flag_np] + 0.01)
n_renewals = renewal_flag_np.sum()

print("ENBP compliance verification:")
print(f"  Renewal policies checked: {n_renewals:,}")
print(f"  ENBP violations:          {violations.sum()}")

if violations.sum() == 0:
    print("  RESULT: All renewal premiums are at or below the NB equivalent.")
    print("  ENBP constraint satisfied per-policy.")
else:
    print("  RESULT: ENBP violations detected.")
    print("  Do not proceed to sign-off. Investigate the factor classification.")
    # Show the worst violations
    excess = adj_premium[renewal_flag_np] - nb_equiv[renewal_flag_np]
    top5 = sorted(excess[violations], reverse=True)[:5]
    print(f"  Top 5 violation amounts (£): {[f'{x:.2f}' for x in top5]}")

What you should see:

ENBP compliance verification:
  Renewal policies checked: 3,250
  ENBP violations:          0
  RESULT: All renewal premiums are at or below the NB equivalent.
  ENBP constraint satisfied per-policy.

If you see violations, the most likely cause is that the tenure discount factor was accidentally included in the shared factors (not in renewal_factor_names), or that the optimiser found a way to increase the tenure discount that the ENBP constraint did not catch. Do not proceed to sign-off until this check passes.

The per-policy ENBP check is the compliance evidence for the FCA. Keep this output in the notebook and export it to a Unity Catalog table alongside the factor adjustments.