Inginerie de conformitate: Solvency II și IFRS 17 pentru Platform Builder
Puține domenii ale tehnologiei sunt la fel de complexe și de risc ridicat precum conformitatea regulamentele de asigurare. Solvency II și IFRS 17 sunt cele două cadre de reglementare care guvernează respectiv solvabilitatea (cât capital să dețină) și raportarea financiară (cum contabilizarea contractelor de asigurare) pentru companiile europene. Implementarea greșită tehnica acestor cadre înseamnă sancțiuni, restricții operaționale și pierderea reputației.
Provocarea pentru dezvoltatori este că aceste cadre nu au fost proiectate cu tehnologie rețineți: sunt reglementări actuariale și contabile care trebuie transpuse în sisteme software. Majoritatea companiilor s-au luptat cu implementările bazate pe foi de Excel și pe loturi nopți care durează ore. Vestea bună este că arhitectura modernă — depozit de date coloană, conductă ELT, calcul distribuit — vă permite în cele din urmă să construiți sisteme de conformare scalabil e auditabil.
Potrivit PwC, conformitatea cu Solvency II și IFRS 17 prezintă o oportunitate unică: ambele reglementările partajează o mulțime de date de intrare. Integrați conductele de raportare în loc de menținerea lor separată poate reduce 40-60% costul anual de exploatare de conformare.
Ce vei învăța
- Prezentare tehnică Solvency II: Pilonul 1 (SCR), Pilonul 2 (ORSA), Pilonul 3 (raportare)
- Model de date IFRS 17: grupuri de contracte, modele de măsurare (GMM, PAA, VFA)
- Arhitectură de depozit de date pentru conformitatea asigurărilor
- Conducta de calcul SCR cu Python și dbt
- Construirea rapoartelor Solvency II (QRT) în format XBRL
- Modelul de date IFRS 17 și calculul măsurilor de răspundere
- Integrarea Solvency II + IFRS 17: date partajate și reducerea dublării
Solvency II: Prezentare tehnică pentru dezvoltatori
Solvabilitate II și cadrul european de solvabilitate în asigurări (Directiva 2009/138/CE), structurat pe trei piloni:
- Pilonul 1 - Cerințe cantitative: calculul cerinței de capital de solvabilitate (SCR), al cerinței de capital minim (MCR) și al rezervelor tehnice (Cea mai bună estimare + marjă de risc)
- Pilonul 2 - Guvernanța și managementul riscurilor: ORSA (Own Risk and Solvency Assessment), sistem de control intern, funcții cheie
- Pilonul 3 - Raportare și transparență: QRT (Șabloane de raportare cantitativă) pentru EIOPA, SFCR public (Raport de solvabilitate și stare financiară), RSR pentru supraveghetor
Pentru un constructor de platforme, principalele puncte de lucru sunt: conducta de calcul rezerve tehnice și SCR (Pilonul 1), infrastructura de date pentru raportarea QRT (Pilonul 3) și sisteme de urmărire de audit pentru pilonul 2.
Componentele datelor Solvency II
| Componentă | Descriere | Calculul frecvenței | Ieșire tehnică |
|---|---|---|---|
| Răspunderea cea mai bună estimare (BEL) | Valoarea prezentă așteptată a fluxurilor de numerar viitoare | Trimestrial/anual | Tabele cu fluxul de numerar pe an/linie |
| Marja de risc (RM) | Costul capitalului pentru riscurile care nu pot fi acoperite | Trimestrial/anual | Valoare scalară pe linie de activitate |
| SCR (Formulă standard) | Capital necesar pentru șocuri pe 16 module de risc | Anual (YE), semestrial | Matrice de corelare + agregare |
| QRT (Șabloane de raportare cantitativă) | Model EIOPA pentru raportarea reglementară | Trimestrial + anual | XBRL, șablon Excel EIOPA |
| Raport ORSA | Evaluare proprie de risc și solvabilitate | Anual | Document PDF + date justificative |
Arhitectură de depozit de date pentru conformitate
Conformitatea asigurărilor necesită un depozit de date cu caracteristici specifice: istoricizarea complet (pistă de audit), trasabilitatea fiecărei transformări, reconciliere între diferite sisteme, și capacitatea de a reprocesa perioadele trecute când datele sunt corectate (modificări târzii).
-- ============================================================
-- 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)
);
Cel mai bun canal de calcul estimativ cu Python
Calculul celei mai bune estimări (BEL) este miezul actuarial al Solvency II. În termeni tehnic, necesită proiectarea fluxurilor de numerar viitoare așteptate (cereri, cheltuieli, prime) și actualizarea acestora cu curba corespunzătoare a randamentului fără risc EIOPA. Următorul cod implementează o versiune calcul BEL simplificat pentru ramura generală.
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: Model de date și implementare
IFRS 17 (în vigoare de la 1 ianuarie 2023 pentru companiile din UE) revoluționează contabilitatea contracte de asigurare. Principiul cheie este că contractele de asigurare nu mai există contabilizate atunci când primele sunt colectate, dar acea profitabilitate este recunoscută așteptări viitoare (Contractual Service Margin - CSM) și că creanțele sunt măsurate la cost curent (Valoarea curentă, nu costul istoric).
Cele trei modele principale de măsurare ale IFRS 17 sunt:
- Model general de măsurare (GMM): modelul principal; calculați BEL (reducere) + Ajustarea riscului + CSM
- Abordarea de alocare a primelor (PAA): simplificare pentru contractele pe termen scurt (maximum 1 an), similar cu IFRS 4 anterior
- Abordarea taxei variabile (VFA): pe viaţă contracte cu împărţirea profitului
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),
)
Generație QRT XBRL pentru Solvency II
Șabloanele de raportare cantitativă (QRT) sunt șabloanele EIOPA standardizate care companiile trebuie să transmită în mod frecvent autorității de supraveghere (în Italia: IVASS). trimestrial și anual. Din 2016, formatul de transmisie obligatoriu este XBRL (eXtensible Limbajul de raportare a afacerilor). Generarea automată de QRT-uri dintr-un depozit de date e unul dintre cele mai impactante cazuri de utilizare pentru ingineria conformității.
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)
Integrarea Solvency II + IFRS 17: Date partajate
Marea oportunitate de a reduce costul conformității este aceea pe care o utilizează Solvency II și IFRS 17 multe date în comun: ambele necesită cea mai bună estimare a pasivelor (chiar dacă sunt calculate cu metodologii diferite), ambele necesită o curbă de randament pentru actualizare (chiar dacă diferite: EIOPA fără risc pentru Solvency II, rata actuală de actualizare pentru IFRS 17), ambele se segmentează după linie de activitate.
Solvabilitate II / IFRS 17 Date partajate
| Dat | Solvabilitate II | IFRS 17 | Diferența cheie |
|---|---|---|---|
| Răspunderea cea mai bună estimare | BEL (reducere fără riscuri) | FCF - componenta BEL | Curbă de randament diferită, definiție diferită a fluxului de numerar |
| Curba randamentului | EIOPA fără riscuri + VA/MA | Curent + rata de blocare | Două curbe separate în IFRS 17 |
| Segmentarea LoBs | 17 linii EIOPA | Mai multe linii agregate | Este necesară cartografierea |
| Triunghiuri de dezvoltare | Pentru scara cu lanț | Pentru FCF | Aceeași sursă de date |
| Date privind politica/contractul | Pentru calculul rezervei | Pe grupuri de contracte | Aceeași sursă de date, agregare diferită |
Cele mai bune practici și anti-modele
Cele mai bune practici pentru ingineria conformității
- Sursa unică a adevărului pentru datele de intrare: daunele, polițele și primele trebuie să provină dintr-un singur depozit de date certificat; Calculele Solvency II și IFRS 17 trebuie să plece de la aceleași date brute
- Pista de audit imuabilă pentru fiecare rulare: fiecare execuție de calcul trebuie să producă un jurnal complet cu: versiunea codului, datele de intrare, parametrii, ieșirile - necesare pentru revizuirile actuariale și de reglementare
- Separarea datelor vs calcul: depozitul de date trebuie să conțină numai date istorice și organizate; logica de calcul trebuie să fie în stratul de aplicație (Python/R), nu în procedurile stocate SQL
- Testare actuarială obligatorie: fiecare modificare a modelelor de calcul trebuie să fie însoțită de validarea de către echipa actuarială cu compararea cu perioada anterioară și analiza abaterilor
- Automatizare ciclu de inchidere: procesul de calcul, validare și transmitere a QRT-urilor trebuie să fie complet automatizat cu declanșatoare de timp și alerte pentru SLA
Anti-modele de evitat
- Excel ca sistem de calcul al producției: Foile Excel nu sunt auditabile, nu sunt scalabile și nu acceptă controlul versiunilor; fiecare calcul de reglementare trebuie să fie în cod versionat
- Date separate pentru Solvabilitate II și IFRS 17: menținerea a două conducte separate dublează costurile și riscurile; dublare și provoacă reconcilieri costisitoare între cele două cadre
- Codificarea curbei randamentului: curba fără risc EIOPA se modifică în fiecare lună; trebuie să fie încărcat automat de pe site-ul web EIOPA, nu introdus manual
- Generare manuală QRT: compilarea manuală a EIOPA și a șabloanelor Excel predispuse la erori și nescalabile; automatizează generarea XBRL din depozitul de date
Concluzii: Sfârșitul seriei InsurTech Engineering
Ingineria de conformitate pentru Solvency II și IFRS 17 este unul dintre cele mai complexe domenii și strategii IT de asigurare. Provocarea nu este doar tehnică (calcule actuariale, format XBRL, calendarul calculelor) dar și organizatoric: coordonare actuarială, contabilă, IT și conformarea unui proces critic și reglementat.
Vestea bună este că tehnologiile moderne — depozite de date coloane, conducte ELT declarative (dbt), calcularea distribuită (Spark), automatizarea fluxului de lucru - în cele din urmă o fac posibilă construiți sisteme de conformitate scalabile, auditabile și rapid actualizate atunci când reglementările evoluează.
Acest articol încheie seria InsurTech Engineering. Avem a acoperit întreaga stivă tehnologică a industriei moderne de asigurări: din domeniu și date model, la managementul politicilor native în cloud, de la telematică UBI, la subscriere AI, la automatizarea reclamațiilor, detectarea fraudei, standardele ACORD, până la conformitate reglementare cu Solvabilitate II și IFRS 17.
Seria InsurTech Engineering - Articole complete
- 01 - Domeniul de asigurare pentru Dezvoltatori: Produse, Actori și Modele de Date
- 02 - Cloud-Native Policy Management: API-First Architecture
- 03 - Telematics Pipeline: UBI Data Processing la scară
- 04 - Subscriere AI: Ingineria caracteristicilor și scorul de risc
- 05 - Automatizarea revendicărilor: Computer Vision și NLP
- 06 - Detectarea fraudelor: analiză grafică și semnal comportamental
- 07 - Integrare ACORD Standard și Insurance API
- 08 - Compliance Engineering: Solvency II și IFRS 17 (acest articol)







