Ingénierie de conformité : Solvabilité II et IFRS 17 pour Platform Builder
Certains domaines technologiques sont également complexes et présentent un risque élevé de conformité réglementation des assurances. Solvabilité II et IFRS 17 sont les deux organismes de réglementation respectivement la solvabilité (combinaison du capital détenu) et l'information financière (commentaire couverture des contrats d'assurance) pour les entreprises européennes. Se tromper de mise en œuvre La technique de ces cadres implique des sanctions, des restrictions opérationnelles et un risque de réputation.
Le défi pour les développeurs est que ces frameworks n'ont pas été développés avec la technologie à l'esprit : il s'agit de réglementations actuarielles et comptables qui doivent être traduites en systèmes logiques. Les tâches les plus difficiles avec les implémentations basées sur les tableaux et lots Excel. des nuits qui durant des heures. La bonne nouvelle est que l’architecture moderne – entrepôt de femmes en colonnes, pipeline ELT, technologie de l'information distribuée - vous pouvez toujours créer les systèmes de conformité évolutif e vérifiable.
Selon PwC, la conformité à Solvabilité II et à IFRS 17 présente une opportunité unique : les deux les réglementations partagent de nombreuses données d’entrée. Intégrez des pipelines de reporting au lieu de les garder séparés peut réduire le 40-60% le coût annuel de fonctionnement de conformité.
Ce que Vous Apprendrez
- Panoramica tecnica di Solvency II: Pillar 1 (SCR), Pillar 2 (ORSA), Pillar 3 (reporting)
- Modèle de données IFRS 17 : groupes de contrats, modèles de mesure (GMM, PAA, VFA)
- Architecture d'entrepôt de données pour la conformité des assurances
- Pipeline de calcul SCR avec Python et dbt
- Construction de rapports Solvabilité II (QRT) au format XBRL
- Modèle de données IFRS 17 et calcul des mesures du passif
- Integrazione Solvency II + IFRS 17: dati condivisi e riduzione della duplicazione
Solvabilité II : aperçu technique pour les développeurs
Solvabilité II et le cadre européen de solvabilité des assurances (Directive 2009/138/CE), structuré sur trois piliers :
- Pillar 1 - Requisiti Quantitativi: calcolo del Solvency Capital Requirement (SCR), del Minimum Capital Requirement (MCR) e delle riserve tecniche (Best Estimate + Risk Margin)
- Pilier 2 – Gouvernance et gestion des risques : ORSA (Own Risk and Solvency Assessment), système de contrôle interne, fonctions clés
- Pilier 3 – Reporting et transparence : QRT (Quantitative Reporting Templates) pour l'EIOPA, SFCR (Solvency and Financial Condition Report) public, RSR pour le superviseur
Pour un constructeur de plateforme, les principaux points de travail sont : le pipeline de calcul les réserves techniques et le SCR (pilier 1), l'infrastructure des femmes pour le reporting QRT (pilier 3) et les systèmes de piste d'audit pour le pilote 2.
Composants de données Solvabilité II
| Componente | Description | Frequenza Calcolo | Output Tecnico |
|---|---|---|---|
| Best Estimate Liability (BEL) | Valeur actuelle attendue des flux de trésorerie futurs | Trimestrale / annuale | Tableaux avec flux de trésorerie par année/ligne |
| Risk Margin (RM) | Coût du capital pour les risques non couvrables | Trimestrale / annuale | Valeur scalaire par métier |
| SCR (Standard Formula) | Capital requis pour les chocs sur 16 modules de risque | Annuale (YE), semestrale | Matrice de corrélation + agrégation |
| QRT (Quantitative Reporting Templates) | Modèle EIOPA pour les rapports réglementaires | Trimestrale + annuale | XBRL, Excel template EIOPA |
| ORSA Report | Proprie évaluation des risques et de la solvabilité | Annuale | Document PDF + données à l'appui |
Architecture d'entrepôt de données pour la conformité
La conformité des assurances nécessite une introduction de la part des femmes à des caractéristiques spécifiques : historicisation complète (pistes d'audit), traçabilité de chaque transformation, rapprochement entre les différents systèmes, et la possibilité de rétracter les règles passées avant que les femmes n'aient été corrigées (modifications tardives).
-- ============================================================
-- Schema dbt per Solvency II + IFRS 17 Data Warehouse
-- ============================================================
-- Layer 1: Raw / Staging (dati grezzi dai sistemi operativi)
-- Tabella base contratti assicurativi
CREATE TABLE staging.insurance_contracts (
contract_id VARCHAR(50) NOT NULL,
policy_number VARCHAR(30) NOT NULL,
product_code VARCHAR(20) NOT NULL,
line_of_business VARCHAR(30) NOT NULL, -- auto, property, liability, life
inception_date DATE NOT NULL,
expiry_date DATE,
issue_date DATE NOT NULL,
policyholder_id VARCHAR(50),
sum_insured DECIMAL(18, 2),
annual_premium DECIMAL(18, 2),
currency CHAR(3) NOT NULL,
status VARCHAR(20), -- active, lapsed, expired, cancelled
-- Audit columns
source_system VARCHAR(30),
load_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
valid_from DATE NOT NULL,
valid_to DATE, -- NULL = record corrente
is_current BOOLEAN DEFAULT TRUE,
PRIMARY KEY (contract_id, valid_from)
);
-- Tabella sinistri per BEL cashflow
CREATE TABLE staging.claims (
claim_id VARCHAR(50) NOT NULL PRIMARY KEY,
contract_id VARCHAR(50) NOT NULL,
fnol_date DATE NOT NULL,
incident_date DATE NOT NULL,
reported_amount DECIMAL(18, 2),
paid_amount DECIMAL(18, 2),
reserve_amount DECIMAL(18, 2), -- riserva corrente
case_reserve DECIMAL(18, 2), -- riserva per sinistro specifico
ibnr_reserve DECIMAL(18, 2), -- riserva IBNR
settlement_date DATE,
status VARCHAR(20),
load_timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Tabella yield curve per sconto BEL (EIOPA Risk-Free Rate)
CREATE TABLE staging.eiopa_yield_curve (
curve_date DATE NOT NULL,
currency CHAR(3) NOT NULL,
maturity_years INTEGER NOT NULL, -- 1, 2, ..., 150
spot_rate DECIMAL(10, 6), -- tasso spot risk-free
forward_rate DECIMAL(10, 6),
source VARCHAR(30), -- EIOPA pubblicazione ufficiale
PRIMARY KEY (curve_date, currency, maturity_years)
);
-- Layer 2: Intermediate / Mart (trasformazioni dbt)
-- Calcolo cashflow proiettati per linea di business
-- Modello semplificato: in produzione usare modelli attuariali complessi
CREATE TABLE mart.solvency2_bel_cashflows (
valuation_date DATE NOT NULL,
line_of_business VARCHAR(30) NOT NULL,
projection_year INTEGER NOT NULL, -- anni futuri: 1, 2, ..., N
currency CHAR(3) NOT NULL,
-- Cashflow per categoria
expected_claims_paid DECIMAL(18, 2), -- sinistri attesi da pagare
expected_expenses DECIMAL(18, 2), -- spese di gestione attese
expected_premiums DECIMAL(18, 2), -- premi futuri attesi (rami vita)
net_cashflow DECIMAL(18, 2), -- cashflow netto (claims + exp - premi)
-- Fattore di sconto dalla yield curve EIOPA
discount_factor DECIMAL(10, 6),
present_value_net_cf DECIMAL(18, 2), -- PV del cashflow netto
-- Metadata
calc_run_id VARCHAR(36), -- UUID del run di calcolo
calc_timestamp TIMESTAMP,
actuary_model_version VARCHAR(20),
PRIMARY KEY (valuation_date, line_of_business, projection_year, currency)
);
-- Best Estimate Liability aggregata
CREATE TABLE mart.solvency2_bel_summary (
valuation_date DATE NOT NULL,
line_of_business VARCHAR(30) NOT NULL,
currency CHAR(3) NOT NULL,
best_estimate DECIMAL(18, 2), -- somma PV cashflow futuri
risk_margin DECIMAL(18, 2), -- costo del capitale rischi non hedgiabili
technical_provision DECIMAL(18, 2), -- BEL + Risk Margin
-- Componenti BEL
bel_claims DECIMAL(18, 2),
bel_expenses DECIMAL(18, 2),
bel_premiums DECIMAL(18, 2),
-- Confronto con periodo precedente
prior_quarter_bel DECIMAL(18, 2),
bel_movement DECIMAL(18, 2),
-- Metadata
calc_run_id VARCHAR(36),
approved_by VARCHAR(100),
approval_date DATE,
PRIMARY KEY (valuation_date, line_of_business, currency)
);
Pipeline de calcul de la meilleure estimation avec Python
Le calcul de la meilleure estimation du passif (BEL) est le véritable cœur de Solvability II. En termes technique, nécessité de projeter les flux de trésorerie futurs attendus (gauches, dépenses, primes) et les actualisateurs auprès du tribunal compétent de l’EIOPA sans risque. Le code suivant implémente une version calcul simplifié du BEL pour la branche non vie.
import numpy as np
import pandas as pd
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from datetime import date
import uuid
@dataclass
class BELInputData:
"""Dati di input per il calcolo del BEL."""
valuation_date: date
line_of_business: str
currency: str
# Triangolo di sviluppo sinistri (per Chain-Ladder o BF)
claims_triangle: pd.DataFrame # rows=accident_year, cols=development_year
# Premi di competenza e spese storici
earned_premiums: pd.Series # indexed by year
expense_ratio: float # % premi
# Yield curve EIOPA
yield_curve: pd.Series # indexed by maturity (1, 2, ..., 150)
# Parametri del modello
tail_factor: float = 1.05 # fattore di coda per il triangolo
projection_years: int = 25 # anni di proiezione
@dataclass
class BELResult:
"""Risultato del calcolo BEL."""
valuation_date: date
line_of_business: str
currency: str
best_estimate: float
bel_claims: float
bel_expenses: float
cashflow_by_year: pd.DataFrame
calc_run_id: str
calc_timestamp: str
class BestEstimateLiabilityCalculator:
"""
Calcolo del Best Estimate Liability per ramo danni (Solvency II).
Implementa il metodo Chain-Ladder per la proiezione dei sinistri
e il discounting con la curva risk-free EIOPA.
NOTA: Questo e un modello semplificato a scopo didattico.
In produzione, il calcolo attuariale richiede modelli certificati
e validati dal team attuariale.
"""
def calculate(self, data: BELInputData) -> BELResult:
"""Esegue il calcolo completo del BEL."""
calc_run_id = str(uuid.uuid4())
# Step 1: Proiezione dei sinistri con Chain-Ladder
projected_claims = self._chain_ladder_projection(data)
# Step 2: Proiezione cashflow annuali
cashflow_df = self._build_cashflow_projections(data, projected_claims)
# Step 3: Sconto con yield curve EIOPA
cashflow_df = self._apply_discounting(cashflow_df, data.yield_curve)
# Step 4: Aggregazione
bel_claims = float(cashflow_df["pv_claims"].sum())
bel_expenses = float(cashflow_df["pv_expenses"].sum())
best_estimate = bel_claims + bel_expenses
return BELResult(
valuation_date=data.valuation_date,
line_of_business=data.line_of_business,
currency=data.currency,
best_estimate=round(best_estimate, 2),
bel_claims=round(bel_claims, 2),
bel_expenses=round(bel_expenses, 2),
cashflow_by_year=cashflow_df,
calc_run_id=calc_run_id,
calc_timestamp=pd.Timestamp.now().isoformat(),
)
def _chain_ladder_projection(self, data: BELInputData) -> pd.DataFrame:
"""
Proiezione dei sinistri con il metodo Chain-Ladder.
Il triangolo di sviluppo ha:
- Righe: anni di accadimento (accident year)
- Colonne: anni di sviluppo (development year 1, 2, ..., N)
"""
triangle = data.claims_triangle.copy()
# Calcola i fattori di sviluppo (link ratios) dalla diagonale
n_dev_years = len(triangle.columns)
development_factors = []
for j in range(n_dev_years - 1):
col_curr = triangle.columns[j]
col_next = triangle.columns[j + 1]
# Solo le righe con dati in entrambe le colonne
mask = triangle[col_curr].notna() & triangle[col_next].notna()
if mask.sum() == 0:
development_factors.append(1.0)
continue
factor = (
triangle.loc[mask, col_next].sum() /
triangle.loc[mask, col_curr].sum()
)
development_factors.append(factor)
# Aggiungi il tail factor per l'ultimo anno di sviluppo
development_factors.append(data.tail_factor)
# Completa il triangolo proiettando i valori mancanti
for i, accident_year in enumerate(triangle.index):
for j, dev_year in enumerate(triangle.columns):
if pd.isna(triangle.loc[accident_year, dev_year]):
# Proietta dalla cella precedente
prev_col = triangle.columns[j - 1]
if not pd.isna(triangle.loc[accident_year, prev_col]):
triangle.loc[accident_year, dev_year] = (
triangle.loc[accident_year, prev_col] * development_factors[j - 1]
)
# Calcola i sinistri da sviluppare per anno
# (ultima colonna del triangolo completato - ultima diagonale)
ultimate_claims = triangle.iloc[:, -1]
last_known = triangle.apply(lambda row: row.dropna().iloc[-1] if row.notna().any() else 0, axis=1)
ibnr_by_year = ultimate_claims - last_known
return pd.DataFrame({
"accident_year": triangle.index,
"ultimate": ultimate_claims.values,
"last_known": last_known.values,
"ibnr": ibnr_by_year.values,
}).set_index("accident_year")
def _build_cashflow_projections(
self, data: BELInputData, projected_claims: pd.DataFrame
) -> pd.DataFrame:
"""
Costruisce il profilo temporale dei cashflow futuri.
Distribuisce i sinistri proiettati negli anni futuri.
"""
rows = []
total_ibnr = projected_claims["ibnr"].sum()
avg_premium = data.earned_premiums.mean() if not data.earned_premiums.empty else 0
for year in range(1, data.projection_years + 1):
# Pattern di pagamento semplificato: esponenziale decrescente
payment_weight = np.exp(-0.3 * year)
normalizer = sum(np.exp(-0.3 * y) for y in range(1, data.projection_years + 1))
year_claims = total_ibnr * (payment_weight / normalizer)
year_expenses = year_claims * data.expense_ratio
rows.append({
"projection_year": year,
"claims_cashflow": round(year_claims, 2),
"expense_cashflow": round(year_expenses, 2),
"net_cashflow": round(year_claims + year_expenses, 2),
})
return pd.DataFrame(rows).set_index("projection_year")
def _apply_discounting(
self, cashflows: pd.DataFrame, yield_curve: pd.Series
) -> pd.DataFrame:
"""Applica il discounting con la curva risk-free EIOPA."""
result = cashflows.copy()
pv_claims = []
pv_expenses = []
for year in cashflows.index:
# Tasso spot per la maturity corrispondente
rate = float(yield_curve.get(year, yield_curve.iloc[-1]))
discount_factor = 1.0 / (1.0 + rate) ** year
pv_claims.append(cashflows.loc[year, "claims_cashflow"] * discount_factor)
pv_expenses.append(cashflows.loc[year, "expense_cashflow"] * discount_factor)
result["pv_claims"] = pv_claims
result["pv_expenses"] = pv_expenses
result["pv_net"] = result["pv_claims"] + result["pv_expenses"]
return result
IFRS 17: Data Model e Implementazione
La norme IFRS 17 (en vigueur depuis le 1er janvier 2023 pour les sociétés de l'UE) révolutionne la compatibilité des contrats d'assurance. L'essentiel est que les contrats d'assurance ne soient pas plus constatée lors de l'encaissement des premiers, mais cette rentabilité est reconnue des futurs attentifs (Marge de Service Contractuel - CSM) et que les réclamations soient évaluées au coût actuelle (valeur actuelle, pas coût historique).
Les trois principaux modèles d’évaluation d’IFRS 17 sont :
- General Measurement Model (GMM): modello principale; calcola BEL (attualizzato) + Risk Adjustment + CSM
- Premium Allocation Approach (PAA): semplificazione per contratti breve termine (massimo 1 anno), simile al precedente IFRS 4
- Approche à frais variables (VFA) : contrats à vie avec participation aux bénéfices
import pandas as pd
import numpy as np
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from datetime import date
from enum import Enum
class IFRS17MeasurementModel(str, Enum):
GMM = "GMM" # General Measurement Model
PAA = "PAA" # Premium Allocation Approach
VFA = "VFA" # Variable Fee Approach
class ContractGroup(str, Enum):
"""IFRS 17 richiede la separazione in 3 gruppi di redditivita."""
ONEROUS = "onerous" # contratti in perdita
NO_SIGNIFICANT_RISK = "no_significant_risk" # nessun rischio di diventare onerosi
REMAINING = "remaining" # tutti gli altri
@dataclass
class IFRS17ContractGroup:
"""
Gruppo di contratti IFRS 17.
IFRS 17 richiede di raggruppare i contratti per:
- Anno di emissione (cohort annuale)
- Linea di business
- Gruppo di redditivita (onerous/remaining/no-significant-risk)
I gruppi NON possono essere mescolati tra anni diversi.
"""
group_id: str
line_of_business: str
issue_cohort_year: int
profitability_group: ContractGroup
measurement_model: IFRS17MeasurementModel
currency: str
# Contratti nel gruppo
contract_ids: List[str] = field(default_factory=list)
@dataclass
class IFRS17LiabilityMeasures:
"""
Misure delle passivita IFRS 17 per un gruppo di contratti.
GMM: LRC = FCF (BEL + RA) + CSM
"""
group_id: str
valuation_date: date
measurement_model: IFRS17MeasurementModel
# Fulfilment Cash Flows (FCF)
best_estimate_liability: float # BEL scontato (come Solvency II)
risk_adjustment: float # aggiustamento per rischio non-finanziario
fulfilment_cash_flows: float # = BEL + RA
# Contractual Service Margin (CSM)
# Profitto futuro atteso ancora da riconoscere
csm_opening: float # CSM inizio periodo
csm_accretion: float # interessi maturati
csm_experience_adjustments: float # rettifiche per esperienza
csm_release: float # CSM rilasciato a P&L nel periodo
csm_closing: float # CSM fine periodo
# Liability for Remaining Coverage (LRC)
lrc: float # = FCF + CSM (se > 0) oppure FCF (se CSM < 0, onerous)
# Liability for Incurred Claims (LIC)
lic: float # per sinistri già occorsi ma non ancora liquidati
# Total Insurance Contract Liabilities
total_liability: float # = LRC + LIC
class IFRS17Calculator:
"""
Calcolatore delle misure IFRS 17.
Implementa il GMM (General Measurement Model) e il PAA
(Premium Allocation Approach) per contratti breve termine.
"""
RISK_ADJUSTMENT_CONFIDENCE = 0.75 # confidenza target per RA (tipicamente 70-80%)
def calculate_gmm(
self,
group: IFRS17ContractGroup,
bel: float,
bel_prior: float,
risk_adjustment: float,
csm_opening: float,
discount_rate: float,
coverage_units_current: float,
coverage_units_remaining: float,
experience_variance: float = 0.0,
) -> IFRS17LiabilityMeasures:
"""
Calcola le misure IFRS 17 con il GMM.
Args:
bel: Best Estimate Liability corrente (attualizzato)
bel_prior: BEL al periodo precedente
risk_adjustment: RA al periodo corrente
csm_opening: CSM di apertura del periodo
discount_rate: tasso di interesse locked-in (tasso alla data di emissione)
coverage_units_current: unita di copertura del periodo corrente
coverage_units_remaining: unita di copertura residue
experience_variance: varianza di esperienza sul BEL
"""
# Fulfilment Cash Flows
fcf = bel + risk_adjustment
# CSM movement
csm_accretion = csm_opening * discount_rate
# Aggiustamento CSM per variazioni delle stime future (non experience)
# Le experience variances vanno a P&L, non al CSM
csm_after_accretion = csm_opening + csm_accretion
# Rilascio CSM: proporzionale alle coverage units del periodo vs totali
if (coverage_units_current + coverage_units_remaining) > 0:
release_ratio = coverage_units_current / (
coverage_units_current + coverage_units_remaining
)
else:
release_ratio = 0.0
csm_release = csm_after_accretion * release_ratio
csm_closing = max(0.0, csm_after_accretion - csm_release)
# Se il gruppo diventa oneroso (CSM negativo), impatta subito P&L
if csm_after_accretion < 0:
# Loss component: ammontare per cui il gruppo e oneroso
csm_closing = 0.0
# LRC = FCF + CSM (contratti non onerosi)
lrc = fcf + csm_closing
# LIC approssimato (in produzione: calcolo separato per sinistri incorsi)
lic = abs(bel * 0.15) # stima semplificata: 15% BEL e LIC
return IFRS17LiabilityMeasures(
group_id=group.group_id,
valuation_date=date.today(),
measurement_model=IFRS17MeasurementModel.GMM,
best_estimate_liability=round(bel, 2),
risk_adjustment=round(risk_adjustment, 2),
fulfilment_cash_flows=round(fcf, 2),
csm_opening=round(csm_opening, 2),
csm_accretion=round(csm_accretion, 2),
csm_experience_adjustments=round(experience_variance, 2),
csm_release=round(csm_release, 2),
csm_closing=round(csm_closing, 2),
lrc=round(lrc, 2),
lic=round(lic, 2),
total_liability=round(lrc + lic, 2),
)
def calculate_paa(
self,
group: IFRS17ContractGroup,
unearned_premium_reserve: float,
acquisition_costs_deferred: float,
claims_liability: float,
risk_adjustment_incurred: float,
) -> IFRS17LiabilityMeasures:
"""
Calcola le misure IFRS 17 con il PAA (contratti <= 1 anno).
Nel PAA la LRC e approssimata dalla riserva premi non guadagnati
meno i costi di acquisizione differiti.
"""
lrc = unearned_premium_reserve - acquisition_costs_deferred
lic = claims_liability + risk_adjustment_incurred
return IFRS17LiabilityMeasures(
group_id=group.group_id,
valuation_date=date.today(),
measurement_model=IFRS17MeasurementModel.PAA,
best_estimate_liability=claims_liability,
risk_adjustment=risk_adjustment_incurred,
fulfilment_cash_flows=claims_liability + risk_adjustment_incurred,
csm_opening=0.0, # PAA non ha CSM esplicito
csm_accretion=0.0,
csm_experience_adjustments=0.0,
csm_release=0.0,
csm_closing=0.0,
lrc=round(lrc, 2),
lic=round(lic, 2),
total_liability=round(lrc + lic, 2),
)
Génération QRT XBRL pour Solvabilité II
Les modèles de reporting quantitatif (QRT) sont ici les modèles standards de l'EIOPA Les entreprises doivent être envoyées fréquemment à l'autorité de contrôle (en Italie : IVASS) trimestriel et annuel. Depuis 2016, le format de transmission obligatoire est XBRL (eXtensible Langage de reporting commercial). Génération QRT automatique à partir d'une femme entrepôt et l'une des façons d'utiliser les marquages les plus importants dans les matériaux d'ingénierie de conformité.
import pandas as pd
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from datetime import date
import xml.etree.ElementTree as ET
from xml.dom import minidom
# Namespace XBRL standard per Solvency II (EIOPA)
XBRL_NAMESPACES = {
"xbrli": "http://www.xbrl.org/2003/instance",
"link": "http://www.xbrl.org/2003/linkbase",
"xlink": "http://www.w3.org/1999/xlink",
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
"s2md_met": "http://eiopa.europa.eu/xbrl/s2md/dict/met",
"s2c_dim": "http://eiopa.europa.eu/xbrl/s2c/dict/dim",
"s2c_CA": "http://eiopa.europa.eu/xbrl/s2c/dict/dom/CA",
"iso4217": "http://www.xbrl.org/2003/iso4217",
}
# Template S.01.01 - Contenuto della presentazione (indice dei QRT)
# Template S.02.01 - Stato patrimoniale
# Template S.17.01 - Riserve tecniche ramo non vita
# Template S.25.01 - Solvency Capital Requirement (Standard Formula)
@dataclass
class QRTContext:
"""Metadati per la generazione del QRT XBRL."""
entity_id: str # codice identificativo IVASS/LEI
entity_name: str
reporting_period_end: date
reporting_currency: str # EUR per la maggior parte
solo_or_group: str # "solo" o "group"
report_type: str # "annual" o "quarterly"
class SolvencyIIQRTGenerator:
"""
Generatore di QRT XBRL per Solvency II.
Implementa un sottoinsieme dei template EIOPA:
- S.01.01 (indice)
- S.02.01 (stato patrimoniale Solvency II)
- S.17.01 (riserve tecniche non vita)
NOTA: In produzione, usare librerie XBRL specializzate come
Arelle o soluzioni certified EIOPA per la compliance completa.
"""
def generate_s01_01(self, ctx: QRTContext) -> str:
"""
Genera il template S.01.01 - Contenuto della presentazione.
Elenca i QRT inclusi nella submission.
"""
root = ET.Element("xbrl", attrib={
"xmlns": "http://www.xbrl.org/2003/instance",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
})
# Context
context = ET.SubElement(root, "context", id="ctx_S0101")
entity_el = ET.SubElement(context, "entity")
identifier = ET.SubElement(entity_el, "identifier", scheme="http://www.lei.org")
identifier.text = ctx.entity_id
period = ET.SubElement(context, "period")
instant = ET.SubElement(period, "instant")
instant.text = ctx.reporting_period_end.isoformat()
# Unit (EUR)
unit = ET.SubElement(root, "unit", id="EUR")
measure = ET.SubElement(unit, "measure")
measure.text = "iso4217:EUR"
# Template S.01.01 data points
# R0010: Template S.01.01 presente
self._add_fact(root, "s2md_met:ei_S0101R0010C0010", "ctx_S0101", "EUR", "true")
# R0020: Template S.02.01 presente (stato patrimoniale)
self._add_fact(root, "s2md_met:ei_S0101R0020C0010", "ctx_S0101", "EUR", "true")
# R0080: Template S.17.01 presente (riserve non vita)
self._add_fact(root, "s2md_met:ei_S0101R0080C0010", "ctx_S0101", "EUR", "true")
# R0190: Template S.25.01 presente (SCR formula standard)
self._add_fact(root, "s2md_met:ei_S0101R0190C0010", "ctx_S0101", "EUR", "true")
return self._pretty_print(root)
def generate_s17_01(
self,
ctx: QRTContext,
bel_data: Dict[str, float],
risk_margin_data: Dict[str, float],
) -> str:
"""
Genera S.17.01 - Riserve tecniche ramo non vita.
Args:
bel_data: BEL per linea EIOPA (es. {"motor_vehicle_liability": 1500000.0})
risk_margin_data: Risk Margin per linea EIOPA
"""
root = ET.Element("xbrl", attrib={
"xmlns": "http://www.xbrl.org/2003/instance",
})
# Mapping linee di business interne -> codici EIOPA QRT
lob_codes = {
"motor_vehicle_liability": "s2c_CA:x1",
"other_motor": "s2c_CA:x2",
"marine": "s2c_CA:x5",
"fire_property": "s2c_CA:x7",
"general_liability": "s2c_CA:x9",
"credit_suretyship": "s2c_CA:x10",
}
for lob_internal, bel_value in bel_data.items():
lob_code = lob_codes.get(lob_internal, "s2c_CA:x99")
ctx_id = f"ctx_S1701_{lob_internal}"
# Context con dimensione linea di business
context = ET.SubElement(root, "context", id=ctx_id)
entity_el = ET.SubElement(context, "entity")
identifier = ET.SubElement(entity_el, "identifier", scheme="http://www.lei.org")
identifier.text = ctx.entity_id
period = ET.SubElement(context, "period")
instant = ET.SubElement(period, "instant")
instant.text = ctx.reporting_period_end.isoformat()
scenario = ET.SubElement(context, "scenario")
explicit_member = ET.SubElement(
scenario, "explicitMember",
dimension="s2c_dim:LB"
)
explicit_member.text = lob_code
# Unit
unit = ET.SubElement(root, "unit", id=f"EUR_{lob_internal}")
measure = ET.SubElement(unit, "measure")
measure.text = "iso4217:EUR"
# Dati QRT S.17.01
# R0010: Riserve premi - BEL
self._add_fact(
root, "s2md_met:tp_S1701R0010C0010",
ctx_id, f"EUR_{lob_internal}",
str(round(bel_value * 0.3, 2)) # stima: 30% del BEL e premi
)
# R0020: Riserve sinistri - BEL
self._add_fact(
root, "s2md_met:tp_S1701R0020C0010",
ctx_id, f"EUR_{lob_internal}",
str(round(bel_value * 0.7, 2)) # 70% del BEL e sinistri
)
# R0060: Best Estimate (totale)
self._add_fact(
root, "s2md_met:tp_S1701R0060C0010",
ctx_id, f"EUR_{lob_internal}",
str(round(bel_value, 2))
)
# R0070: Risk Margin
rm = risk_margin_data.get(lob_internal, 0.0)
self._add_fact(
root, "s2md_met:tp_S1701R0070C0010",
ctx_id, f"EUR_{lob_internal}",
str(round(rm, 2))
)
# R0100: Technical Provisions totale (BEL + RM)
self._add_fact(
root, "s2md_met:tp_S1701R0100C0010",
ctx_id, f"EUR_{lob_internal}",
str(round(bel_value + rm, 2))
)
return self._pretty_print(root)
def _add_fact(
self, parent: ET.Element,
concept: str, context_ref: str,
unit_ref: str, value: str
) -> ET.Element:
"""Aggiunge un data point XBRL."""
fact = ET.SubElement(parent, concept, attrib={
"contextRef": context_ref,
"unitRef": unit_ref,
"decimals": "2",
})
fact.text = value
return fact
def _pretty_print(self, root: ET.Element) -> str:
xml_str = ET.tostring(root, encoding="unicode")
dom = minidom.parseString(xml_str)
return dom.toprettyxml(indent=" ", encoding=None)
Integrazione Solvency II + IFRS 17: Dati Condivisi
La grande opportunité de réduire le coût de la conformité est que Solvabilité II et IFRS 17 sont utiles de nombreuses femmes en commun : les deux ont besoin de la meilleure estimation du passif (même s'il est calculé avec des méthodes différentes), les deux ont besoin d'une méthode de rendu pour l'actualisation (même si différents : EIOPA sans risque pour Solvabilité II, taux d'actualisation actuel pour IFRS 17), les deux Le secteur segmenté par activité.
Dati Condivisi Solvency II / IFRS 17
| Dato | Solvency II | IFRS 17 | Differenza Chiave |
|---|---|---|---|
| Best Estimate Liability | BEL (risk-free discounted) | FCF - BEL component | Yield curve diversa, definizione cashflow diversa |
| Yield Curve | EIOPA risk-free + VA/MA | Current + locked-in rate | Due curve separate in IFRS 17 |
| Segmentazione LoBs | 17 linee EIOPA | Lignes plus agrégées | Mappatura necessaria |
| Triangles de développement | Pour échelle à chaîne | Pour FCF | Stesso dato sorgente |
| Dati polizze/contratti | Pour le calcul de la réserve | Par groupes de contrats | Stesso dato sorgente, diversa aggregazione |
Best Practices e Anti-pattern
Meilleures Pratiques par Compliance Engineering
- Source unique de vérité pour les données d’entrée : les sinistres, les polices et les primes doivent provenir d’un seul entrepôt de données certifié ; Les calculs Solvabilité II et IFRS 17 doivent partir des mêmes données brutes
- Piste d'audit immuable pour chaque exécution : chaque exécution de calcul doit produire un journal complet avec : la version du code, les données d'entrée, les paramètres, les résultats - nécessaires aux examens actuariels et réglementaires
- Séparation des données vs calcul : l'entrepôt de données doit contenir uniquement des données historiques et conservées ; la logique de calcul doit être dans la couche application (Python/R), pas dans les procédures stockées SQL
- Tests actuariels obligatoires : chaque modification des modèles de calcul doit être accompagnée d'une validation par l'équipe actuarielle avec comparaison avec la période précédente et analyse des écarts
- Automatisation du cycle de fermeture : le processus de calcul, de validation et de transmission des QRT doit être entièrement automatisé avec des déclencheurs temporels et des alertes pour les SLA
Anti-modèles à éviter
- Excel comme système de calcul de production : Les feuilles Excel ne sont pas vérifiables, ne sont pas évolutives et ne prennent pas en charge le contrôle de version ; chaque calcul réglementaire doit être en code versionné
- Données distinctes pour Solvabilité II et IFRS 17 : le maintien de deux pipelines distincts fait double emploi avec les coûts et les risques ; duplication et entraîne des rapprochements coûteux entre les deux cadres
- Coder en dur la courbe des taux : la courbe sans risque EIOPA évolue chaque mois ; le doit être téléchargé automatiquement depuis le site de l'EIOPA et ne peut pas être appris manuellement
- Génération QRT manuelle : compilation manuelle de modèles EIOPA et Excel sujets aux erreurs et non évolutifs ; automatise la génération XBRL à partir de l'entrepôt de données
Conclusions : Fin de la série InsurTech Engineering
L'ingénierie de conformité pour Solvabilité II et IFRS 17 est l'un des domaines les plus complexes et stratégies informatiques d’assurance. L'enjeu n'est pas seulement technique (calculs actuariels, format XBRL, timing des calculs) mais aussi organisationnel : coordination actuarielle, comptable, informatique et conformité sur un processus critique et réglementé.
La bonne nouvelle est que les technologies modernes – les entrepôts des femmes en colonnes, ont déclaré les pipelines ELT (dbt), information distribuée (Spark), automatisation du flux de travail — ce qui rend cela tout à fait possible Créer chaque jour des systèmes de conformité progressifs, vérifiables et rapides la réglementation évolue.
Cet article conclut la série Ingénierie AssurTech. Nous avons découvrez l'ensemble de la pile technologique du secteur de l'assurance moderne : du domaine aux données modèle, pour la gestion des politiques d'assurance cloud natives, de la télématique UBI pour la souscription par IA, pour l'automatisation des réclamations, pour la détection des fraudes, selon la réglementation ACORD, juste pour la conformité réglementation avec Solvabilité II et IFRS 17.
Serie InsurTech Engineering - Articoli Completi
- 01 - Dominio Assicurativo per Developer: Prodotti, Attori e Data Model
- 02 - Policy Management Cloud-Native: Architettura API-First
- 03 - Pipeline télématique : traitement des données UBI à grande échelle
- 04 - AI Underwriting: Feature Engineering e Risk Scoring
- 05 - Automazione Sinistri: Computer Vision e NLP
- 06 - Fraud Detection: Graph Analytics e Behavioral Signal
- 07 - Standard ACORD e API Integration Assicurative
- 08 - Compliance Engineering: Solvency II e IFRS 17 (questo articolo)







