Subscrição de IA: engenharia de recursos e pontuação de risco em seguros modernos
A subscrição é o coração de qualquer companhia de seguros: e o processo pelo qual ela decide se aceita um risco, a que preço e em que condições. Durante décadas, esse processo dependia da experiência de subscritores humanos analisando documentos em papel e aplicando regras atuariais codificadas em tabelas. O resultado? Decisões em 3-5 dias úteis, custos altos custos operacionais e variabilidade subjetiva entre os subscritores.
A inteligência artificial está reescrevendo radicalmente essas regras. De acordo com McKinsey, o os investimentos globais em soluções de IA para seguros ultrapassarão o 6 bilhões de dólares em 2025, com o BCG estimando que o 36% do valor total do seguro AI está concentrada precisamente na função de subscrição. Os números operacionais são igualmente impressionantes: o tempo médio de decisão de subscrição caiu de 3-5 dias para 12,4 minutos para eles políticas padrão, mantendo uma taxa de precisão na avaliação de risco de 99,3%.
Mas como funciona realmente um sistema de subscrição de IA? Este guia desconstrói tudo pilha técnica: desde a coleta e engenharia de recursos, até modelos de pontuação de risco, até à interpretabilidade e gerenciamento de preconceitos — com exemplos de códigos reais prontos para produção.
O que Voce Aprendera
- Arquitetura de um sistema de subscrição de IA ponta a ponta
- Engenharia de recursos específica para o domínio de seguros
- Modelos de ML para pontuação de risco: XGBoost, dois estágios de frequência/gravidade
- Interpretabilidade com SHAP para decisões auditáveis e prontas para conformidade
- Detecção de preconceitos e mitigação de justiça no contexto regulatório da UE
- MLOps para modelos de subscrição em produção com MLflow
- Monitoramento de desvio de dados com Índice de Estabilidade Populacional (PSI)
O processo de assinatura: vinculado à IA nativa
Antes de projetar um sistema de IA, é essencial compreender o fluxo de trabalho tradicional com o qual estamos lidando automatizando. O processo de subscrição é dividido em quatro fases fundamentais:
- Coleta de informações: O requerente fornece dados sobre si e sobre o risco (questionário, documentos, possível inspeção física do ativo)
- Análise de risco: O subscritor avalia a probabilidade e a gravidade de quaisquer sinistros futuros
- Pricing: Determinazione del premio in base al rischio valutato e agli obiettivi di combined ratio del portfolio
- Decisão: Aceitação, rejeição ou aceitação com condições (exclusões, franquia, prêmio)
Um sistema nativo de IA não elimina essas fases, mas as transforma profundamente: coleta de dados a análise de risco torna-se automática a partir de fontes heterogêneas (dados abertos, telemática, agências de crédito). e realizada por modelos de ML em milissegundos, a precificação é dinâmica e personalizada para cada requerente, e a decisão é automatizada para casos padrão com supervisão humana para i casos complexos ou limítrofes.
Quadro Normativo: AI Act EU e Underwriting
A Lei Europeia de IA (totalmente em vigor desde agosto de 2027) classifica os sistemas pontuação crédito e seguro como IA de alto risco (Anexo III). Isto implica obrigações específicas: transparência de decisões automatizadas, direito de revisão humana, documentação técnica detalhada e avaliação de conformidade pré-comercialização. O projeto de Os sistemas de subscrição de IA devem incorporar esses requisitos desde a arquitetura, e não como retrofit posterior.
Engenharia de recursos para subscrição de seguros
A qualidade da engenharia de recursos é o fator que mais diferencia um modelo de subscrição excelente de medíocre. Ao contrário de domínios como visão computacional, onde os recursos são extraído automaticamente de camadas convolucionais, os dados tabulares de seguros requerem engenharia manual profunda baseada em conhecimento do domínio atuarial.
As funcionalidades para o ramo automotivo estão agrupadas em cinco categorias principais:
- Características demográficas: idade, estado civil, tipo de residência
- Recursos de condução: anos de carteira de habilitação, idade na obtenção da primeira carteira, histórico de acidentes e infrações
- Características do veículo: marca, modelo, ano, valor, potência, ano de registro
- Características geográficas: densidade urbana, índice de criminalidade da área, risco climático
- Características econômicas: pontuação de crédito, tipo de contrato de seguro necessário
import pandas as pd
import numpy as np
from typing import Dict, Optional
from dataclasses import dataclass
from datetime import date
@dataclass
class PolicyApplicant:
"""Rappresenta i dati grezzi di un richiedente polizza auto."""
applicant_id: str
birth_date: date
license_date: date
zip_code: str
vehicle_make: str
vehicle_year: int
vehicle_value: float
annual_mileage: int
claims_3yr: int
violations_3yr: int
credit_score: Optional[int] = None
marital_status: str = "single"
housing_type: str = "tenant"
class AutoInsuranceFeatureEngineer:
"""
Feature engineering per underwriting auto.
Produce 40+ feature da dati grezzi del richiedente,
includendo feature derivate, interazioni e encoding
domain-specific.
"""
VEHICLE_MAKE_RISK: Dict[str, int] = {
"Ferrari": 5, "Lamborghini": 5, "Porsche": 4,
"BMW": 3, "Mercedes": 3, "Audi": 3,
"Toyota": 1, "Honda": 1, "Volkswagen": 2,
"Ford": 2, "Fiat": 2, "Renault": 2,
}
def __init__(self, reference_date: Optional[date] = None):
self.reference_date = reference_date or date.today()
def engineer_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
features: Dict[str, float] = {}
features.update(self._demographic_features(applicant))
features.update(self._driving_experience_features(applicant))
features.update(self._vehicle_features(applicant))
features.update(self._claims_features(applicant))
features.update(self._geographic_features(applicant))
if applicant.credit_score is not None:
features.update(self._credit_features(applicant))
features.update(self._interaction_features(features))
return features
def _demographic_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
age = (self.reference_date - applicant.birth_date).days / 365.25
return {
"age": age,
"age_squared": age ** 2,
"age_under_25": float(age < 25),
"age_over_70": float(age > 70),
"age_risk_young": max(0.0, (25 - age) / 25) if age < 25 else 0.0,
"age_risk_senior": max(0.0, (age - 70) / 20) if age > 70 else 0.0,
"is_married": float(applicant.marital_status == "married"),
"is_homeowner": float(applicant.housing_type == "owner"),
}
def _driving_experience_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
years_licensed = (self.reference_date - applicant.license_date).days / 365.25
age = (self.reference_date - applicant.birth_date).days / 365.25
age_at_license = age - years_licensed
return {
"years_licensed": years_licensed,
"years_licensed_squared": years_licensed ** 2,
"age_at_first_license": age_at_license,
"late_license_ratio": max(0.0, (age_at_license - 18) / 10),
"is_new_driver": float(years_licensed < 2),
"is_experienced_driver": float(years_licensed > 10),
}
def _vehicle_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
vehicle_age = self.reference_date.year - applicant.vehicle_year
make_risk = self.VEHICLE_MAKE_RISK.get(applicant.vehicle_make, 2)
return {
"vehicle_age": float(vehicle_age),
"vehicle_value": applicant.vehicle_value,
"vehicle_value_log": np.log1p(applicant.vehicle_value),
"vehicle_make_risk_score": float(make_risk),
"is_high_performance": float(make_risk >= 4),
"is_new_vehicle": float(vehicle_age <= 2),
"is_old_vehicle": float(vehicle_age > 10),
"annual_mileage": float(applicant.annual_mileage),
"annual_mileage_log": np.log1p(applicant.annual_mileage),
"high_mileage": float(applicant.annual_mileage > 20000),
}
def _claims_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
claims = applicant.claims_3yr
violations = applicant.violations_3yr
return {
"claims_3yr": float(claims),
"violations_3yr": float(violations),
"has_any_claim": float(claims > 0),
"has_multiple_claims": float(claims > 1),
"has_violations": float(violations > 0),
# Score combinato ponderato: sinistri pesano 3x rispetto a infrazioni
"incident_score": claims * 3.0 + violations * 1.5,
"claims_x_violations": float(claims * violations),
}
def _geographic_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
# In produzione: lookup su DB geografici (ISTAT, OpenStreetMap, criminalita)
zip_hash = hash(applicant.zip_code) % 100
urban_score = (zip_hash % 5) / 4.0
crime_index = (zip_hash % 3) / 2.0
weather_risk = (zip_hash % 4) / 3.0
return {
"urban_density_score": urban_score,
"area_crime_index": crime_index,
"area_weather_risk": weather_risk,
"composite_geo_risk": (urban_score + crime_index + weather_risk) / 3,
}
def _credit_features(self, applicant: PolicyApplicant) -> Dict[str, float]:
score = applicant.credit_score or 0
return {
"credit_score": float(score),
"credit_score_normalized": (score - 300) / (850 - 300),
"poor_credit": float(score < 580),
"fair_credit": float(580 <= score < 670),
"good_credit": float(670 <= score < 740),
"excellent_credit": float(score >= 740),
}
def _interaction_features(self, features: Dict[str, float]) -> Dict[str, float]:
return {
# Giovane + auto sportiva = rischio molto alto
"young_high_perf": (
features.get("age_risk_young", 0) *
features.get("is_high_performance", 0)
),
# Sinistri + area ad alto crimine amplificano il rischio
"claims_urban": (
features.get("claims_3yr", 0) *
features.get("urban_density_score", 0)
),
# Mileage alto + veicolo vecchio = rischio meccanico aumentato
"mileage_old_vehicle": (
features.get("annual_mileage_log", 0) *
features.get("is_old_vehicle", 0)
),
}
Modelos de pontuação de risco: abordagens e compensações
A escolha do modelo de aprendizado de máquina para pontuação de risco deve equilibrar a precisão preditivo, interpretabilidade (fundamental para conformidade), velocidade de inferência e facilidade de manutenção. Aqui estão as principais abordagens do setor de seguros:
Compare modelos para pontuação de risco de seguro
| Modello | Accuratezza | Interpretabilita | Use Case Ideale |
|---|---|---|---|
| GLM (Poisson/Gamma) | Media | Muito alto | Baseline attuariale, regulatory acceptance |
| Random Forest | Alta | Media | Feature importance, robustezza agli outlier |
| XGBoost / LightGBM | Muito alto | Media | Produção padrão, SOTA em dados tabulares |
| Neural Network Tabulare | Alta | Bassa | Recursos complexos com incorporações categóricas |
A abordagem mais estabelecida na indústria é a modelo de dois estágios: um modelo de frequência (probabilidade de ocorrer pelo menos um acidente) e um de gravidade (custo esperado do acidente dado que isso ocorre). O prêmio puro esperado é: Frequência x Gravidade.
import xgboost as xgb
from sklearn.metrics import mean_absolute_error, mean_squared_error
import numpy as np
import pandas as pd
from typing import Dict, Optional
class TwoStageRiskScorer:
"""
Modello a due stadi per pricing assicurativo auto.
Stage 1: Frequency model (Poisson regression con XGBoost)
Target = numero sinistri per polizza
Stage 2: Severity model (Tweedie/Gamma con XGBoost)
Target = importo sinistro, addestrato solo su polizze con sinistri
Pure Premium = E[Frequency] * E[Severity | has_claim]
"""
FREQUENCY_PARAMS: Dict = {
"objective": "count:poisson",
"eval_metric": "poisson-nloglik",
"max_depth": 6,
"learning_rate": 0.05,
"n_estimators": 500,
"min_child_weight": 50, # stabilita attuariale: min sinistri per leaf
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_alpha": 0.1,
"reg_lambda": 1.0,
"tree_method": "hist",
"early_stopping_rounds": 50,
}
SEVERITY_PARAMS: Dict = {
"objective": "reg:tweedie",
"tweedie_variance_power": 1.5, # 1=Poisson, 2=Gamma
"eval_metric": "tweedie-nloglik@1.5",
"max_depth": 5,
"learning_rate": 0.05,
"n_estimators": 300,
"min_child_weight": 30,
"subsample": 0.8,
"colsample_bytree": 0.7,
"reg_alpha": 0.1,
"reg_lambda": 1.0,
"tree_method": "hist",
"early_stopping_rounds": 30,
}
def __init__(self) -> None:
self.frequency_model = xgb.XGBRegressor(**self.FREQUENCY_PARAMS)
self.severity_model = xgb.XGBRegressor(**self.SEVERITY_PARAMS)
self.feature_names: list = []
def fit(
self,
X: pd.DataFrame,
y_claims: pd.Series,
y_amounts: pd.Series,
exposure: pd.Series,
eval_fraction: float = 0.2,
) -> "TwoStageRiskScorer":
"""
Addestra entrambi i modelli.
IMPORTANTE: usa split temporale, non random shuffle.
I dati assicurativi sono autocorrelati nel tempo.
"""
self.feature_names = X.columns.tolist()
split_idx = int(len(X) * (1 - eval_fraction))
X_train, X_val = X.iloc[:split_idx], X.iloc[split_idx:]
freq_train = y_claims.iloc[:split_idx]
freq_val = y_claims.iloc[split_idx:]
# Stage 1: Frequency
self.frequency_model.fit(
X_train, freq_train,
sample_weight=exposure.iloc[:split_idx],
eval_set=[(X_val, freq_val)],
verbose=50,
)
# Stage 2: Severity - solo su polizze con sinistri
has_claim = y_amounts > 0
X_sev = X[has_claim]
y_sev = y_amounts[has_claim]
sev_split = int(len(X_sev) * (1 - eval_fraction))
self.severity_model.fit(
X_sev.iloc[:sev_split], y_sev.iloc[:sev_split],
eval_set=[(X_sev.iloc[sev_split:], y_sev.iloc[sev_split:])],
verbose=30,
)
return self
def predict_pure_premium(
self, X: pd.DataFrame, exposure: float = 1.0
) -> np.ndarray:
"""Calcola il pure premium: E[Freq] * E[Severity]."""
freq = self.frequency_model.predict(X) * exposure
sev = self.severity_model.predict(X)
return freq * sev
def evaluate(self, X: pd.DataFrame, y_claims: pd.Series) -> Dict[str, float]:
pred = self.frequency_model.predict(X)
mae = mean_absolute_error(y_claims, pred)
rmse = float(np.sqrt(mean_squared_error(y_claims, pred)))
gini = self._gini_coefficient(y_claims.values, pred)
lift = self._lift_at_decile(y_claims.values, pred, 0.1)
return {
"mae": round(mae, 6),
"rmse": round(rmse, 6),
"gini_coefficient": round(gini, 4),
"lift_top_decile": round(lift, 4),
}
def _gini_coefficient(self, actual: np.ndarray, predicted: np.ndarray) -> float:
"""Gini coefficient: metrica attuariale standard per modelli di frequenza."""
idx = np.argsort(predicted)
cum = np.cumsum(actual[idx])
cum_norm = cum / cum[-1]
n = len(actual)
lorenz_area = float(np.sum(cum_norm)) / n
return 2 * (lorenz_area - 0.5)
def _lift_at_decile(
self, actual: np.ndarray, predicted: np.ndarray, decile: float
) -> float:
k = max(1, int(len(actual) * decile))
top_idx = np.argsort(predicted)[-k:]
base_rate = actual.mean()
if base_rate == 0:
return 0.0
return float(actual[top_idx].mean() / base_rate)
Interpretabilidade com SHAP: Decisões Auditáveis
Num contexto regulamentado como o dos seguros, um modelo de caixa preta não é suficiente. A legislação exige que as decisões de subscrição sejam explicáveis: para o cliente (certo à explicação do GDPR), para subscritores (revisão de casos limítrofes) e para reguladores (Solvência II Pilar 3, ORSA). SHAP (SHapley Additive exPlanations) é a ferramenta de referência indústria para interpretabilidade post-hoc de modelos de conjunto.
import shap
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
class UnderwritingExplainer:
"""
Spiegazioni SHAP per decisioni underwriting.
Genera output a tre livelli: cliente, underwriter, compliance.
"""
FEATURE_LABELS: Dict[str, str] = {
"age": "eta del guidatore",
"years_licensed": "anni di patente",
"claims_3yr": "sinistri negli ultimi 3 anni",
"violations_3yr": "infrazioni negli ultimi 3 anni",
"vehicle_make_risk_score": "categoria rischio veicolo",
"vehicle_age": "anzianita del veicolo",
"vehicle_value": "valore del veicolo",
"annual_mileage": "chilometraggio annuo dichiarato",
"composite_geo_risk": "rischio della zona geografica",
"credit_score": "score creditizio",
"young_high_perf": "combinazione giovane + veicolo sportivo",
}
def __init__(self, model, feature_names: List[str]) -> None:
self.feature_names = feature_names
self.explainer = shap.TreeExplainer(model)
def explain(
self, X_row: pd.DataFrame, risk_score: float
) -> Dict:
"""Spiegazione completa per una singola valutazione."""
shap_values = self.explainer.shap_values(X_row)
impacts: List[Tuple[str, float]] = sorted(
zip(self.feature_names, shap_values[0]),
key=lambda x: abs(x[1]),
reverse=True
)
return {
"risk_score": round(risk_score, 2),
"decision": self._score_to_decision(risk_score),
"customer_message": self._customer_message(impacts, risk_score),
"top_risk_factors": [
{
"name": name,
"label": self.FEATURE_LABELS.get(name, name),
"direction": "aumenta rischio" if shap > 0 else "riduce rischio",
"magnitude": round(abs(shap), 4),
}
for name, shap in impacts[:5]
],
"audit_trail": {
"base_expected_value": float(self.explainer.expected_value),
"all_shap_values": {
n: round(float(s), 6)
for n, s in zip(self.feature_names, shap_values[0])
},
"input_features": X_row.to_dict(orient="records")[0],
},
}
def _customer_message(
self, impacts: List[Tuple[str, float]], score: float
) -> str:
high = [(n, v) for n, v in impacts if abs(v) > 0.1]
if not high:
return "Il tuo profilo rientra nella fascia di rischio standard."
positivi = [self.FEATURE_LABELS.get(n, n) for n, v in high[:3] if v < 0]
negativi = [self.FEATURE_LABELS.get(n, n) for n, v in high[:3] if v > 0]
parts = []
if negativi:
parts.append(f"Fattori che aumentano il profilo di rischio: {', '.join(negativi)}.")
if positivi:
parts.append(f"Fattori a tuo favore: {', '.join(positivi)}.")
return " ".join(parts)
def _score_to_decision(self, score: float) -> str:
if score < 30:
return "ACCEPT_PREFERRED"
elif score < 60:
return "ACCEPT_STANDARD"
elif score < 80:
return "ACCEPT_SUBSTANDARD"
return "DECLINE_OR_MANUAL_REVIEW"
Detecção de justiça e preconceitos no contexto da UE
O uso de variáveis proxy (código postal, pontuação de crédito) pode introduzir discriminação indireta proibido por lei. Na Europa, a directiva sobre a igualdade de género (confirmada pela Decisão Test-Achats do Tribunal de Justiça da UE de 2011) proíbe a utilização do género na fixação de preços seguro. A Lei AI acrescenta restrições para sistemas de alto risco classificados no Anexo III, exigindo avaliações de conformidade obrigatórias antes da implantação.
import pandas as pd
import numpy as np
from sklearn.metrics import confusion_matrix
from typing import Dict, List
class FairnessAuditor:
"""
Auditor di fairness per modelli underwriting (EU-compliant).
Metriche implementate:
- Disparate Impact (regola 80%)
- Demographic Parity Gap
- Equal Opportunity (TPR parity)
- Calibration by group
"""
DISPARATE_IMPACT_THRESHOLD = 0.8 # EEOC 80% rule
MAX_DP_GAP = 0.1 # linee guida EIOPA
def __init__(
self,
predictions: np.ndarray,
true_labels: np.ndarray,
sensitive_df: pd.DataFrame,
) -> None:
self.predictions = predictions
self.true_labels = true_labels
self.sensitive_df = sensitive_df
def full_audit(self) -> Dict:
results: Dict = {}
for attr in self.sensitive_df.columns:
groups = self.sensitive_df[attr].unique()
attr_results: Dict = {}
for group in groups:
mask = self.sensitive_df[attr] == group
g_pred = self.predictions[mask]
g_true = self.true_labels[mask]
attr_results[str(group)] = {
"count": int(mask.sum()),
"acceptance_rate": float((g_pred < 0.6).mean()),
"avg_score": round(float(g_pred.mean()), 4),
"tpr": self._tpr(g_true, g_pred),
}
di = self._disparate_impact(attr_results)
dp = self._dp_gap(attr_results)
attr_results["_metrics"] = {
"disparate_impact": round(di, 4),
"demographic_parity_gap": round(dp, 4),
"passes_di_rule": di >= self.DISPARATE_IMPACT_THRESHOLD,
"passes_dp_rule": dp <= self.MAX_DP_GAP,
"overall_fair": di >= self.DISPARATE_IMPACT_THRESHOLD and dp <= self.MAX_DP_GAP,
}
results[attr] = attr_results
return results
def _tpr(self, labels: np.ndarray, preds: np.ndarray, thr: float = 0.5) -> float:
if len(labels) < 10:
return float("nan")
binary = (preds >= thr).astype(int)
try:
tn, fp, fn, tp = confusion_matrix(labels, binary, labels=[0, 1]).ravel()
return round(tp / (tp + fn), 4) if (tp + fn) > 0 else 0.0
except ValueError:
return float("nan")
def _disparate_impact(self, groups: Dict) -> float:
rates = [v["acceptance_rate"] for k, v in groups.items()
if not k.startswith("_") and isinstance(v, dict)]
if not rates or max(rates) == 0:
return 1.0
return min(rates) / max(rates)
def _dp_gap(self, groups: Dict) -> float:
rates = [v["acceptance_rate"] for k, v in groups.items()
if not k.startswith("_") and isinstance(v, dict)]
return (max(rates) - min(rates)) if rates else 0.0
MLOps e Monitoring in Produzione
Os modelos de subscrição estão sujeitos a desvio de conceito frequente: o perfil das alterações dos candidatos (novos modelos de veículos eléctricos, alterações demográficas), custos dos reparos sofrem inflação, eventos climáticos extremos alteram os padrões de perdas. Um sistema de monitoramento contínuo com Índice de Estabilidade Populacional (ISA) e essencial para identificar quando o modelo precisa ser rebocado.
from scipy import stats
import numpy as np
import pandas as pd
from typing import Dict, List
from datetime import datetime
class DriftMonitor:
"""
Monitora data drift per modelli underwriting.
Usa PSI (Population Stability Index) come metrica primaria.
PSI interpretation:
- PSI < 0.1: Nessun cambiamento significativo
- PSI 0.1-0.25: Cambiamento moderato, monitorare
- PSI > 0.25: Cambiamento significativo, retraining consigliato
"""
def __init__(self, reference_df: pd.DataFrame, features: List[str]) -> None:
self.reference_df = reference_df
self.features = features
def check_drift(self, current_df: pd.DataFrame) -> Dict:
feature_results: Dict = {}
critical_features = []
for feat in self.features:
if feat not in current_df.columns:
continue
psi = self._psi(self.reference_df[feat], current_df[feat])
ks_stat, ks_p = stats.ks_2samp(
self.reference_df[feat].dropna(),
current_df[feat].dropna()
)
status = "ok" if psi < 0.1 else ("warning" if psi < 0.25 else "critical")
feature_results[feat] = {
"psi": round(psi, 4),
"ks_statistic": round(ks_stat, 4),
"ks_pvalue": round(ks_p, 4),
"status": status,
}
if status == "critical":
critical_features.append(feat)
avg_psi = float(np.mean([v["psi"] for v in feature_results.values()]))
return {
"checked_at": datetime.now().isoformat(),
"overall_psi": round(avg_psi, 4),
"retraining_recommended": avg_psi > 0.1,
"critical_features": critical_features,
"feature_details": feature_results,
}
def _psi(self, ref: pd.Series, cur: pd.Series, bins: int = 10) -> float:
ref_clean = ref.dropna().values
cur_clean = cur.dropna().values
edges = np.percentile(ref_clean, np.linspace(0, 100, bins + 1))
edges = np.unique(edges)
ref_counts, _ = np.histogram(ref_clean, bins=edges)
cur_counts, _ = np.histogram(cur_clean, bins=edges)
ref_pct = (ref_counts + 1e-10) / len(ref_clean)
cur_pct = (cur_counts + 1e-10) / len(cur_clean)
return float(np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct)))
Best Practices e Anti-pattern
Melhores Práticas para Subscrição de IA
- Arquitetura de dois estágios (frequência/severidade): e o padrão atuarial e produz preços mais precisos do que um modelo único sobre o valor do sinistro
- Divisão de tempo obrigatória: os dados de seguros são autocorrelacionados ao longo do tempo; nunca use embaralhamento aleatório para divisão de treinamento/teste
- Exposição como deslocamento: sempre use a duração da apólice (exposição em anos) como uma compensação no modelo de Poisson para normalizar a contagem de sinistros
- Mantenha um GLM básico: modelos lineares generalizados são mais facilmente validados pelos reguladores e fornecem referências para avaliar o valor acrescentado do BC
- Modo sombra antes da ativação: Execute o modelo em paralelo com a subscrição humana por 30 a 90 dias, comparando as decisões antes de automatizar
- Monitore o PSI semanalmente: A deriva no setor automobilístico é frequente devido a novos modelos de veículos, inflação dos custos de reparo e mudanças regulatórias
Antipadrões a serem evitados
- Vazamento de recursos: nunca use variáveis disponíveis apenas após o sinistro (valor do sinistro, reserva) como recursos de treinamento do modelo de frequência
- Otimizar apenas AUC: no setor de seguros as métricas relevantes são o coeficiente de Gini, o Índice Combinado e o Lift no decil superior do risco
- Modelos com mais de 500 recursos: impossível validar atuarialmente e justificar ao regulador; prefira uma seleção rigorosa de recursos (máximo de 40-60 recursos)
- Ignorando a concentração do portfólio: um modelo que aceita apenas perfis de risco muito baixo cria anti-seleção e uma carteira desequilibrada
- Discriminação por procuração: variáveis como CEP podem ser proxies de etnia; sempre verifique o impacto diferente antes da implantação
Conclusão e Próximos Passos
A subscrição de IA não substitui o subscritor humano, mas o amplifica: decisões para As políticas padrão (80-90% do volume) podem ser totalmente automatizadas com precisão superior à média humana, liberando especialistas para casos complexos onde a experiência de domínio e insubstituível.
As chaves para um sistema de sucesso são: engenharia profunda de recursos baseada em conhecimento atuarial, arquitetura de frequência/gravidade de dois estágios, interpretabilidade SHAP para conformidade, auditoria de imparcialidade obrigatória e monitoramento contínuo com PSI para gestão de desvios.
O próximo artigo da série explora oautomação de sinistros com visão computacional e PNL: do FNOL digital à avaliação automática de danos fotográficos, até liquidação ponta a ponta acelerada.
Serie InsurTech Engineering
- 01 - Dominio Assicurativo per Developer: Prodotti, Attori e Data Model
- 02 - Policy Management Cloud-Native: Architettura API-First
- 03 - Pipeline Telemática: Processamento de Dados UBI em Escala
- 04 - AI Underwriting: Feature Engineering e Risk Scoring (questo articolo)
- 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







