コンプライアンス エンジニアリング: プラットフォーム ビルダー向けのソルベンシー II および IFRS 17
コンプライアンスほど複雑でリスクの高いテクノロジー分野はほとんどありません。 保険規制。ソルベンシー II と IFRS 第 17 号は 2 つの規制枠組みです それぞれソルベンシー(どれだけの資本を保有するか)と財務報告(どのように保有するか) 欧州企業向けの保険契約の会計処理)。実装を間違える これらのフレームワークの手法は、制裁、運用制限、評判の低下を意味します。
開発者にとっての課題は、これらのフレームワークがテクノロジーを考慮して設計されていないことです。 念頭に置いてください。これらはソフトウェア システムに変換する必要がある保険数理および会計の規制です。 ほとんどの企業は、Excel シートベースおよびバッチベースの実装に苦労しています。 何時間も続く夜。良いニュースは、最新のアーキテクチャであるデータ ウェアハウスです。 カラム型、ELT パイプライン、分散コンピューティング — ついにシステムを構築できるようになりました コンプライアンスの スケーラブルな e 監査可能.
PwC によると、ソルベンシー II と IFRS 第 17 号への準拠は、両方ともユニークな機会をもたらします。 規制では多くの入力データが共有されます。代わりにレポート パイプラインを統合します。 それらを別々にしておくと、 40-60% 年間運営コスト コンプライアンスの遵守。
何を学ぶか
- ソルベンシー II の技術概要: 第 1 柱 (SCR)、第 2 柱 (ORSA)、第 3 柱 (レポート)
- IFRS第17号データモデル:契約グループ、測定モデル(GMM、PAA、VFA)
- 保険コンプライアンスのためのデータ ウェアハウス アーキテクチャ
- Python と dbt を使用した SCR 計算パイプライン
- XBRL 形式での Solvency II レポート (QRT) の構築
- IFRS第17号のデータモデルと責任尺度の計算
- ソルベンシー II + IFRS 第 17 号の統合: データの共有と重複の削減
Solvency II: 開発者向けの技術概要
ソルベンシー II および欧州の保険ソルベンシー フレームワーク (指令 2009/138/EC)、 3つの柱で構成されています。
- 第 1 の柱 – 定量的要件: ソルベンシー資本要件(SCR)、最低資本要件(MCR)および技術準備金(最良推定値 + リスクマージン)の計算
- 第 2 の柱 - ガバナンスとリスク管理: ORSA(Own Risk and Solvency Assessment)、内部統制システム、主要機能
- 柱 3 - 報告と透明性: EIOPA用のQRT(定量的報告テンプレート)、公的SFCR(ソルベンシーおよび財務状況報告書)、監督者用のRSR
プラットフォーム ビルダーの主な作業ポイントは次のとおりです。 計算パイプライン 技術的準備金と SCR (第 1 の柱)、QRT レポート用のデータ インフラストラクチャ (第 3 の柱) そして第 2 の柱の監査証跡システム。
ソルベンシー II データコンポーネント
| 成分 | 説明 | 周波数の計算 | 技術成果 |
|---|---|---|---|
| 最良推定責任 (BEL) | 将来キャッシュフローの期待現在価値 | 四半期/年次 | 年/ライン別のキャッシュフローの表 |
| リスクマージン(RM) | ヘッジ不可能なリスクの資本コスト | 四半期/年次 | 事業分野別のスカラー値 |
| SCR(スタンダードフォーミュラ) | 16 のリスクモジュールに対するショックに必要な資本 | 年次 (YE)、半年ごと | 相関 + 集計行列 |
| QRT (定量的レポートテンプレート) | 規制報告用の EIOPA テンプレート | 四半期+年次 | XBRL、Excel テンプレート EIOPA |
| ORSAレポート | 自身のリスクと支払能力の評価 | 年間 | PDFドキュメント+サポートデータ |
コンプライアンスのためのデータ ウェアハウス アーキテクチャ
保険コンプライアンスには、履歴化という特定の特性を備えたデータ ウェアハウスが必要です 完全 (監査証跡)、各変換のトレーサビリティ、異なるシステム間の調整、 データが修正された場合(後期修正)、過去の期間を再処理する機能。
-- ============================================================
-- 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)
);
Python を使用した最適な見積り計算パイプライン
The calculation of the Best Estimate Liability (BEL) is the actuarial heart of Solvency II.用語的には technical, requires projecting expected future cash flows (claims, expenses, premiums) and discounting them with the appropriate EIOPA risk-free yield curve.次のコードはバージョンを実装します。 simplified BEL calculation for the non-life branch.
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号:データモデルと実装
IFRS 第 17 号 (EU 企業に対して 2023 年 1 月 1 日から施行) は会計処理に革命をもたらします。 保険契約。重要な原則は、保険契約はもはや存在しないということです。 保険料徴収時に会計処理されるが、収益性は認識される 将来の期待 (契約サービスマージン - CSM) と請求額は原価で測定されること 現在 (過去のコストではなく、現在の価値)。
IFRS第17号の3つの主要な測定モデルは次のとおりです。
- 一般測定モデル (GMM): メインモデル。 BEL (割引後) + リスク調整 + CSM を計算します
- プレミアム配分アプローチ (PAA): 以前のIFRS第4号と同様、短期契約(最長1年)の簡素化
- 変動料金アプローチ (VFA): 利益分配のある終身契約の場合
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),
)
Solvency II の QRT XBRL 生成
定量的レポート テンプレート (QRT) は、標準化された EIOPA テンプレートです。 企業は監督当局 (イタリアでは IVASS) に頻繁に情報を送信する必要があります。 四半期ごとと毎年。 2016 年以降、必須の送信形式は XBRL (eXtensible ビジネスレポート言語)。データ ウェアハウスからの QRT の自動生成 これは、コンプライアンス エンジニアリングにとって最も影響力のあるユース ケースの 1 つです。
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)
ソルベンシー II + IFRS 第 17 号の統合: データの共有
コンプライアンスのコストを削減する絶好の機会は、ソルベンシー II と IFRS 第 17 号が 多くの共通データ: どちらも負債の最良推定値が必要です (たとえ計算されたとしても) 方法論が異なります)、どちらも割引のためのイールドカーブが必要です(たとえ 異なる: ソルベンシー II の EIOPA リスクフリー、IFRS 第 17 号の現在の割引率)、両方 事業分野ごとにセグメント化されています。
ソルベンシー II / IFRS 第 17 号の共有データ
| 与えられた | ソルベンシー II | IFRS第17号 | 主な違い |
|---|---|---|---|
| 最良の見積り責任 | BEL (リスクフリー割引) | FCF - BEL コンポーネント | 異なるイールドカーブ、異なるキャッシュフロー定義 |
| イールドカーブ | EIOPA リスクフリー + VA/MA | 現在 + ロックイン率 | IFRS第17号の2つの別々の曲線 |
| LoB セグメンテーション | 17 EIOPA ライン | より集約された行 | マッピングが必要です |
| 開発トライアングル | チェーンラダー用 | FCF用 | 同じソースデータ |
| 保険契約・契約データ | 積立金計算用 | 契約のグループごと | 同じソースデータ、異なる集計 |
ベストプラクティスとアンチパターン
コンプライアンスエンジニアリングのベストプラクティス
- 入力データに対する単一の信頼できる情報源: 保険金請求、保険契約、および保険料は、単一の認定されたデータ ウェアハウスから取得される必要があります。ソルベンシー II と IFRS 第 17 号の計算は同じ生データから開始する必要があります
- 実行ごとに不変の監査証跡: 各計算実行では、コード バージョン、入力データ、パラメータ、出力を含む完全なログを生成する必要があります。これは保険数理および規制上のレビューに必要です。
- データ分離と計算: データ ウェアハウスには、履歴データと厳選されたデータのみが含まれている必要があります。計算ロジックは SQL ストアド プロシージャではなく、アプリケーション層 (Python/R) に存在する必要があります。
- 必須の保険数理テスト: 計算モデルへの各変更には、前期間との比較および偏差の分析による保険数理チームによる検証を伴う必要があります。
- クロージングサイクルの自動化: QRT の計算、検証、送信のプロセスは、時間トリガーと SLA のアラートを使用して完全に自動化する必要があります
避けるべきアンチパターン
- 生産計算システムとしての Excel: Excel シートは監査可能ではなく、スケーラブルではなく、バージョン管理をサポートしていません。すべての規制上の計算はバージョン管理されたコードに含める必要があります
- ソルベンシー II と IFRS 第 17 号の個別データ: 2 つの別々のパイプラインを維持すると、コストとリスクが重複します。重複が発生し、2 つのフレームワーク間でコストのかかる調整が発生します。
- イールドカーブをハードコーディングします。 EIOPA のリスクフリー曲線は毎月変化します。手動で入力するのではなく、EIOPA Web サイトから自動的にアップロードする必要があります
- 手動 QRT 生成: EIOPA およびエラーが発生しやすくスケーラブルでない Excel テンプレートの手動コンパイル。データ ウェアハウスからの XBRL 生成を自動化します
結論: InsurTech Engineering シリーズの終了
ソルベンシー II および IFRS 第 17 号のコンプライアンス エンジニアリングは、最も複雑な領域の 1 つであり、 保険IT戦略。課題は技術的なもの (保険数理計算、XBRL 形式、 計算のタイミング)だけでなく、組織面でも:保険数理、会計、IT、および 重要な規制されたプロセスのコンプライアンス。
良いニュースは、最新のテクノロジーであるカラム型データ ウェアハウス、宣言型 ELT パイプラインです。 (dbt)、分散コンピューティング (Spark)、ワークフローの自動化 - これらによりついに可能になります。 スケーラブルで監査可能、迅速に更新可能なコンプライアンス システムを構築する 規制は進化します。
この記事でシリーズは終了です インシュアテックエンジニアリング。私たちは持っています ドメインとデータから、現代の保険業界のテクノロジースタック全体をカバー モデルからクラウドネイティブなポリシー管理、UBI テレマティクス、AI 引受業務まで、 請求の自動化、不正検出、ACORD 標準、コンプライアンスまで ソルベンシー II および IFRS 第 17 号による規制。
InsurTech エンジニアリング シリーズ - 完全な記事
- 01 - 開発者向けの保険ドメイン: 製品、アクター、データ モデル
- 02 - クラウドネイティブのポリシー管理: API ファーストのアーキテクチャ
- 03 - テレマティクス パイプライン: 大規模な UBI データ処理
- 04 - AI 引受業務: 特徴エンジニアリングとリスク スコアリング
- 05 - 請求の自動化: コンピューター ビジョンと NLP
- 06 - 不正行為の検出: グラフ分析と行動シグナル
- 07 - ACORD Standard と保険 API の統合
- 08 - コンプライアンス エンジニアリング: ソルベンシー II および IFRS 第 17 号 (この記事)







