Standardele ACORD și integrarea API de asigurări
Ecosistemul de asigurări este fragmentat prin natura sa: companii de asigurări, reasiguratori, brokeri, agentii, banci (bancassurance), comparatori online, furnizori de servicii (ateliere, spitale, avocați) — toți acești actori trebuie să facă schimb de date eficient, precis și auditabil. Fără standarde comune, fiecare integrare devine un proiect personalizat cu costuri si riscuri mari.
ACORD (Asociația pentru Cercetare și Dezvoltare Operațională Cooperativă) și organizația globală care stabilește standarde de date pentru industria asigurărilor din 1970. Standardele sale — mesaje XML și JSON pentru gestionarea politicilor, reclamațiilor, reasigurare şi reglementare contabilă — sunt adoptate de peste 36.000 de organizații în 100 de țări. În 2025, ACORD a lansat i Standarde de date GRLC Generation 2.0, o revizuire cuprinzătoare a standardelor de reasigurare și a riscurilor comerciale mari cu suport JSON nativ și orientare pe primul loc API.
Acest ghid traduce standardele ACORD în cod concret: cum să structurați mesajele XML/JSON compatibil cu standardele, cum să construiți un strat de mapare între sistemele proprietare și ACORD și cum să expuneți API-urile RESTful care respectă cele mai bune practici din industrie.
Ce vei învăța
- Prezentare generală a standardelor ACORD: XML și JSON pentru diferite linii de afaceri
- Structura mesajelor ACORD pentru politici și reclamații
- Maparea între modelele de date proprietare și ACORD
- Implementarea unui gateway API compatibil ACORD în Python/FastAPI
- Validarea mesajelor cu schema XML (XSD) și schema JSON
- Modele de integrare: punct la punct, ESB, bazate pe evenimente
- Certificare ACORD: cerințe și proces de testare
Prezentare generală a standardelor ACORD
ACORD publică standarde diferite pentru fiecare domeniu al industriei asigurărilor. Cele principale de știut pentru un dezvoltator care lucrează în sector sunt:
Standarde ACORD pe zonă
| Standard | Zonă | Format | Note |
|---|---|---|---|
| ACORD XML (P&C) | Daune: mașină, proprietate, răspundere | XML | Cel mai răspândit standard în America de Nord |
| ACORD GRLC | Reasigurare și riscuri comerciale mari | XML + JSON (Gen. 2.0) | Reînnoit în 2025 cu abordarea API-first |
| ACORD EBOT | Reglementare contabilă (contabilitate și decontare) | XML | Actualizare Gen 2.0 planificată |
| ACORD ECOT | Mișcări de revendicare | XML | Actualizare Gen 2.0 planificată |
| ACORD CRP | Contract, risc și precontabilitate | XML + JSON | Noul standard GRLC Gen 2.0 |
| ACORD Life XML | Ramura viata si sanatate | XML | ACORD Standard 103 și aferente |
Structura unui mesaj ACORD XML
Un mesaj ACORD XML are o structură ierarhică bine definită. Înțelegerea blocurilor fundamentale și primul pas pentru orice integrare. Iată un exemplu de mesaj pentru solicitarea unei cotații pentru o poliță de asigurare auto (ACORD 165 Personal Auto Quote Request).
<?xml version="1.0" encoding="UTF-8"?>
<ACORD>
<!-- Header obbligatorio: identifica mittente, destinatario, versione -->
<SignonRq>
<SignonTransport>
<SignonRoleCd>Customer</SignonRoleCd>
<CustId>
<SPName>it.federicocalo.insurtech</SPName>
<CustPermId>broker-001</CustPermId>
</CustId>
</SignonTransport>
<ClientDt>2025-03-10T14:30:00</ClientDt>
<CustLangPref>it-IT</CustLangPref>
<ClientApp>
<Org>ACME InsurTech</Org>
<Name>PolicyPortal</Name>
<Version>2.1.0</Version>
</ClientApp>
</SignonRq>
<!-- Corpo del messaggio: richiesta di quotazione auto -->
<InsuranceSvcRq>
<RqUID>a1b2c3d4-e5f6-7890-abcd-ef1234567890</RqUID>
<SPName>it.federicocalo.insurtech</SPName>
<RqDt>2025-03-10</RqDt>
<PersAutoPolicyQuoteInqRq>
<RqUID>quote-2025-001234</RqUID>
<CurCd>EUR</CurCd>
<QuoteInfo>
<EffectiveDt>2025-04-01</EffectiveDt>
<ExpirationDt>2026-03-31</ExpirationDt>
</QuoteInfo>
<!-- Informazioni polizza -->
<PersPolicy>
<LOBCd>AUTO</LOBCd>
<ContractTerm>
<DurationPeriod>
<NumUnits>12</NumUnits>
<UnitMeasurementCd>MON</UnitMeasurementCd>
</DurationPeriod>
</ContractTerm>
</PersPolicy>
<!-- Assicurato principale -->
<InsuredOrPrincipal>
<GeneralPartyInfo>
<NameInfo>
<PersonName>
<GivenName>Mario</GivenName>
<Surname>Rossi</Surname>
</PersonName>
</NameInfo>
<Addr>
<Addr1>Via Roma 1</Addr1>
<City>Milano</City>
<StateProvCd>MI</StateProvCd>
<PostalCode>20121</PostalCode>
<CountryCd>IT</CountryCd>
</Addr>
</GeneralPartyInfo>
<InsuredOrPrincipalInfo>
<InsuredOrPrincipalRoleCd>Insured</InsuredOrPrincipalRoleCd>
<PersonInfo>
<BirthDt>1985-06-15</BirthDt>
<GenderCd>M</GenderCd>
<LicensedDt>2003-09-20</LicensedDt>
</PersonInfo>
</InsuredOrPrincipalInfo>
</InsuredOrPrincipal>
<!-- Veicolo -->
<PersAutoLineBusiness>
<PersVeh>
<VehNumber>1</VehNumber>
<ModelYr>2022</ModelYr>
<Manufacturer>FIAT</Manufacturer>
<Model>Tipo</Model>
<VehIdentificationNumber>ZFA35600006G12345</VehIdentificationNumber>
<EstimatedAnnualDistance>
<NumUnits>15000</NumUnits>
<UnitMeasurementCd>KMT</UnitMeasurementCd>
</EstimatedAnnualDistance>
<Coverage>
<CoverageCd>RCA</CoverageCd>
<CoverageDesc>Responsabilità Civile Auto</CoverageDesc>
</Coverage>
<Coverage>
<CoverageCd>KASKO</CoverageCd>
<CoverageDesc>Kasko Completo</CoverageDesc>
<Deductible>
<FormatInteger>500</FormatInteger>
<CurCd>EUR</CurCd>
</Deductible>
</Coverage>
</PersVeh>
</PersAutoLineBusiness>
</PersAutoPolicyQuoteInqRq>
</InsuranceSvcRq>
</ACORD>
Mesaj ACORD în format JSON (GRLC Gen 2.0)
Noul standard GRLC Generation 2.0 (2025) introduce reprezentarea JSON nativă pentru mesaje de reasigurare și riscuri comerciale mari. Formatul JSON este mai ușor, mai ușor de procesat în arhitecturile moderne (API REST, streaming de evenimente) și da se integrează nativ cu instrumente precum OpenAPI și JSON Schema.
{
"acordVersion": "GRLC-2.0",
"messageId": "msg-2025-03-10-001234",
"messageType": "RiskSubmission",
"timestamp": "2025-03-10T14:30:00Z",
"sender": {
"organizationId": "broker-ACME-001",
"name": "ACME Broker SpA",
"role": "Broker"
},
"receiver": {
"organizationId": "insurer-XYZ-IT",
"name": "XYZ Assicurazioni SpA",
"role": "Insurer"
},
"risk": {
"riskId": "RISK-2025-IT-00456",
"lineOfBusiness": "Marine",
"inceptionDate": "2025-04-01",
"expiryDate": "2026-03-31",
"currency": "EUR",
"insuredObject": {
"type": "Vessel",
"name": "MV Adriatico",
"imoNumber": "9876543",
"vesselType": "BulkCarrier",
"grossTonnage": 45000,
"yearBuilt": 2018,
"flag": "IT",
"value": 25000000.00
},
"coverages": [
{
"coverageType": "HullAndMachinery",
"insuredValue": 25000000.00,
"deductible": 100000.00,
"conditions": "Institute Cargo Clauses (A)"
},
{
"coverageType": "ProtectionAndIndemnity",
"limit": 100000000.00,
"conditions": "Standard P&I Terms"
}
],
"locations": [
{
"locationType": "PrimaryPort",
"portName": "Genova",
"country": "IT",
"coordinates": {
"latitude": 44.4071,
"longitude": 8.9342
}
}
]
},
"submission": {
"requestedCapacity": 10000000.00,
"requestedShare": 0.4,
"expiringPremium": 185000.00,
"desiredPremium": 190000.00,
"submissionNotes": "Rinnovo con storico sinistri pulito. Certificato classe 1A Lloyd's Register."
}
}
Stratul de cartografiere: de la modelul proprietar la ACORD
Rareori sistemele interne ale companiei folosesc direct nomenclatura ACORD. Stratul de mapare se traduce între modelul de date proprietar și standardul ACORD, bidirecţional. Acest strat trebuie menținut actualizat cu versiunile standardelor.
from dataclasses import dataclass
from datetime import date
from typing import Dict, List, Optional, Any
from enum import Enum
import xml.etree.ElementTree as ET
from xml.dom import minidom
import json
# ============ Modello dati INTERNO (proprietario) ============
class CoverageType(str, Enum):
LIABILITY = "RCA"
COMPREHENSIVE = "KASKO"
FIRE_THEFT = "INCENDIO_FURTO"
WINDSHIELD = "CRISTALLI"
DRIVER_ACCIDENT = "INFORTUNI_CONDUCENTE"
@dataclass
class InternalVehicle:
"""Modello veicolo del sistema interno."""
plate: str
vin: str
brand: str
model: str
year: int
engine_cc: int
fuel_type: str # benzina, diesel, elettrico, ibrido
annual_km: int
@dataclass
class InternalPolicyholder:
"""Modello assicurato del sistema interno."""
tax_code: str # codice fiscale
first_name: str
last_name: str
birth_date: date
gender: str # M/F
address: str
city: str
province: str
postal_code: str
license_date: date
claims_3yr: int
@dataclass
class InternalQuoteRequest:
"""Richiesta di quotazione nel formato del sistema interno."""
request_id: str
policyholder: InternalPolicyholder
vehicle: InternalVehicle
coverages: List[CoverageType]
effective_date: date
annual_mileage: int
garage_type: str # "privato", "condominiale", "pubblico"
# ============ Mapper ACORD ============
class ACORDMapper:
"""
Mappa i modelli dati interni ai messaggi ACORD XML/JSON.
Supporta:
- ACORD 165: Personal Auto Quote Request (XML)
- ACORD GRLC 2.0: Risk Submission (JSON)
"""
# Mapping codici interni -> codici ACORD standard
GENDER_MAP: Dict[str, str] = {
"M": "M", "F": "F", "ALTRO": "O", "NON_SPECIFICATO": "U"
}
FUEL_MAP: Dict[str, str] = {
"benzina": "GAS", "diesel": "DIE", "elettrico": "ELE",
"ibrido": "HYB", "gpl": "LPG", "metano": "CNG"
}
GARAGE_MAP: Dict[str, str] = {
"privato": "PRIV", "condominiale": "COND", "pubblico": "PUB",
"nessuno": "NONE"
}
COVERAGE_MAP: Dict[CoverageType, Dict[str, str]] = {
CoverageType.LIABILITY: {"code": "BI", "desc": "Bodily Injury Liability"},
CoverageType.COMPREHENSIVE: {"code": "COMP", "desc": "Comprehensive"},
CoverageType.FIRE_THEFT: {"code": "FT", "desc": "Fire and Theft"},
CoverageType.WINDSHIELD: {"code": "GLAS", "desc": "Glass Coverage"},
CoverageType.DRIVER_ACCIDENT: {"code": "PACC", "desc": "Personal Accident"},
}
def to_acord_xml(self, request: InternalQuoteRequest) -> str:
"""
Converte una richiesta interna nel formato ACORD 165 XML.
Returns: stringa XML formattata e pretty-printed.
"""
root = ET.Element("ACORD")
# Header
signon_rq = ET.SubElement(root, "SignonRq")
transport = ET.SubElement(signon_rq, "SignonTransport")
role = ET.SubElement(transport, "SignonRoleCd")
role.text = "Customer"
client_dt = ET.SubElement(signon_rq, "ClientDt")
client_dt.text = date.today().isoformat()
# Body
svc_rq = ET.SubElement(root, "InsuranceSvcRq")
rq_uid = ET.SubElement(svc_rq, "RqUID")
rq_uid.text = request.request_id
quote_rq = ET.SubElement(svc_rq, "PersAutoPolicyQuoteInqRq")
# Valuta
cur = ET.SubElement(quote_rq, "CurCd")
cur.text = "EUR"
# Info quotazione
quote_info = ET.SubElement(quote_rq, "QuoteInfo")
eff_dt = ET.SubElement(quote_info, "EffectiveDt")
eff_dt.text = request.effective_date.isoformat()
# Assicurato
self._add_insured_xml(quote_rq, request.policyholder)
# Linea Auto
auto_line = ET.SubElement(quote_rq, "PersAutoLineBusiness")
self._add_vehicle_xml(auto_line, request.vehicle, request.coverages)
# Pretty print
xml_string = ET.tostring(root, encoding="unicode")
dom = minidom.parseString(xml_string)
return dom.toprettyxml(indent=" ", encoding=None)
def _add_insured_xml(self, parent: ET.Element, ph: InternalPolicyholder) -> None:
"""Aggiunge il blocco InsuredOrPrincipal al messaggio XML."""
insured = ET.SubElement(parent, "InsuredOrPrincipal")
party_info = ET.SubElement(insured, "GeneralPartyInfo")
name_info = ET.SubElement(party_info, "NameInfo")
person_name = ET.SubElement(name_info, "PersonName")
given = ET.SubElement(person_name, "GivenName")
given.text = ph.first_name
surname = ET.SubElement(person_name, "Surname")
surname.text = ph.last_name
addr = ET.SubElement(party_info, "Addr")
addr1 = ET.SubElement(addr, "Addr1")
addr1.text = ph.address
city_el = ET.SubElement(addr, "City")
city_el.text = ph.city
state = ET.SubElement(addr, "StateProvCd")
state.text = ph.province
postal = ET.SubElement(addr, "PostalCode")
postal.text = ph.postal_code
country = ET.SubElement(addr, "CountryCd")
country.text = "IT"
insured_info = ET.SubElement(insured, "InsuredOrPrincipalInfo")
role = ET.SubElement(insured_info, "InsuredOrPrincipalRoleCd")
role.text = "Insured"
person_info = ET.SubElement(insured_info, "PersonInfo")
birth = ET.SubElement(person_info, "BirthDt")
birth.text = ph.birth_date.isoformat()
gender = ET.SubElement(person_info, "GenderCd")
gender.text = self.GENDER_MAP.get(ph.gender, "U")
licensed = ET.SubElement(person_info, "LicensedDt")
licensed.text = ph.license_date.isoformat()
def _add_vehicle_xml(
self, parent: ET.Element, vehicle: InternalVehicle, coverages: List[CoverageType]
) -> None:
"""Aggiunge il blocco PersVeh al messaggio XML."""
veh = ET.SubElement(parent, "PersVeh")
num = ET.SubElement(veh, "VehNumber")
num.text = "1"
yr = ET.SubElement(veh, "ModelYr")
yr.text = str(vehicle.year)
manuf = ET.SubElement(veh, "Manufacturer")
manuf.text = vehicle.brand
model_el = ET.SubElement(veh, "Model")
model_el.text = vehicle.model
vin = ET.SubElement(veh, "VehIdentificationNumber")
vin.text = vehicle.vin
ann_dist = ET.SubElement(veh, "EstimatedAnnualDistance")
num_units = ET.SubElement(ann_dist, "NumUnits")
num_units.text = str(vehicle.annual_km)
unit_meas = ET.SubElement(ann_dist, "UnitMeasurementCd")
unit_meas.text = "KMT"
for cov_type in coverages:
cov_data = self.COVERAGE_MAP.get(cov_type, {"code": "UNKN", "desc": "Unknown"})
cov_el = ET.SubElement(veh, "Coverage")
cov_cd = ET.SubElement(cov_el, "CoverageCd")
cov_cd.text = cov_data["code"]
cov_desc = ET.SubElement(cov_el, "CoverageDesc")
cov_desc.text = cov_data["desc"]
def to_acord_json(self, request: InternalQuoteRequest) -> Dict[str, Any]:
"""
Converte la richiesta interna nel formato ACORD JSON (semplificato).
Per GRLC 2.0: usa il formato esteso con risk/coverage/submission structure.
"""
ph = request.policyholder
veh = request.vehicle
return {
"acordVersion": "PersonalLines-1.0",
"messageId": request.request_id,
"messageType": "AutoQuoteRequest",
"timestamp": date.today().isoformat(),
"policyholder": {
"taxCode": ph.tax_code,
"firstName": ph.first_name,
"lastName": ph.last_name,
"birthDate": ph.birth_date.isoformat(),
"gender": self.GENDER_MAP.get(ph.gender, "U"),
"licenseDate": ph.license_date.isoformat(),
"claimsLast3Years": ph.claims_3yr,
"address": {
"street": ph.address,
"city": ph.city,
"province": ph.province,
"postalCode": ph.postal_code,
"countryCode": "IT",
},
},
"vehicle": {
"registrationPlate": veh.plate,
"vin": veh.vin,
"manufacturer": veh.brand,
"model": veh.model,
"year": veh.year,
"engineCC": veh.engine_cc,
"fuelType": self.FUEL_MAP.get(veh.fuel_type, "UNKN"),
"annualMileageKm": veh.annual_km,
},
"requestedCoverages": [
self.COVERAGE_MAP.get(c, {"code": "UNKN", "desc": "Unknown"})["code"]
for c in request.coverages
],
"effectiveDate": request.effective_date.isoformat(),
"currency": "EUR",
}
ACORD API Gateway cu FastAPI
Expunerea capabilităților de integrare ACORD prin API-ul REST permite brokerilor, agențiilor și sistemelor terțelor părți să se conecteze la sistemul de asigurări fără a fi nevoie să gestioneze complexitatea standardelor XML la nivel intern. Gateway-ul traduce apelurile REST în mesaje ACORD și invers.
from fastapi import FastAPI, HTTPException, Depends, Header
from fastapi.responses import Response
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import date
import uuid
# ============ Pydantic Models per l'API REST ============
class VehicleRequest(BaseModel):
plate: str = Field(..., description="Targa del veicolo (formato italiano)")
vin: str = Field(..., min_length=17, max_length=17, description="VIN (17 caratteri)")
brand: str
model: str
year: int = Field(..., ge=1990, le=2030)
engine_cc: int = Field(..., ge=50, le=10000)
fuel_type: str = Field(..., description="benzina|diesel|elettrico|ibrido|gpl|metano")
annual_km: int = Field(..., ge=1000, le=150000)
class PolicyholderRequest(BaseModel):
tax_code: str = Field(..., min_length=16, max_length=16)
first_name: str
last_name: str
birth_date: date
gender: str = Field(..., pattern="^[MFO]$")
address: str
city: str
province: str = Field(..., min_length=2, max_length=2)
postal_code: str = Field(..., pattern=r"^\d{5}$")
license_date: date
claims_3yr: int = Field(default=0, ge=0, le=10)
class QuoteRequestBody(BaseModel):
policyholder: PolicyholderRequest
vehicle: VehicleRequest
coverages: List[str] = Field(..., min_items=1)
effective_date: date
response_format: str = Field(default="json", description="json|xml")
class QuoteResponse(BaseModel):
quote_id: str
request_id: str
status: str
estimated_annual_premium: Optional[float] = None
currency: str = "EUR"
valid_until: Optional[date] = None
breakdown: Optional[dict] = None
acord_message_id: Optional[str] = None
# ============ FastAPI Application ============
app = FastAPI(
title="Insurance ACORD API Gateway",
description="REST API layer per integrazione ACORD con sistemi assicurativi",
version="2.1.0",
docs_url="/api/v1/docs",
)
def verify_api_key(x_api_key: str = Header(...)) -> str:
"""Dependency per autenticazione API key."""
valid_keys = {"broker-001": "ACME Broker", "broker-002": "XYZ Agency"}
if x_api_key not in valid_keys:
raise HTTPException(status_code=401, detail="API key non valida")
return valid_keys[x_api_key]
@app.post(
"/api/v1/quotes/personal-auto",
response_model=QuoteResponse,
summary="Richiesta quotazione polizza auto personale",
description="Genera una quotazione per polizza RC auto e garanzie accessorie",
)
async def create_auto_quote(
request: QuoteRequestBody,
client_name: str = Depends(verify_api_key),
) -> QuoteResponse:
"""
Endpoint per richiesta di quotazione polizza auto.
Converte la richiesta REST nel formato ACORD appropriato,
invia al motore di pricing interno e restituisce la quotazione.
"""
request_id = str(uuid.uuid4())
# Converti il body REST nel modello interno
internal_request = _build_internal_request(request, request_id)
# Genera il messaggio ACORD
mapper = ACORDMapper()
if request.response_format == "xml":
acord_message = mapper.to_acord_xml(internal_request)
else:
acord_message = mapper.to_acord_json(internal_request)
# Invia al motore di pricing (simulato)
pricing_result = await _call_pricing_engine(internal_request)
return QuoteResponse(
quote_id=str(uuid.uuid4()),
request_id=request_id,
status="QUOTED",
estimated_annual_premium=pricing_result.get("annual_premium"),
currency="EUR",
valid_until=date.today().replace(month=date.today().month % 12 + 1) if date.today().month < 12 else date.today().replace(year=date.today().year + 1, month=1),
breakdown=pricing_result.get("breakdown"),
acord_message_id=f"ACORD-{request_id[:8]}",
)
@app.post(
"/api/v1/quotes/personal-auto/xml",
response_class=Response,
summary="Richiesta quotazione in formato ACORD XML",
)
async def create_auto_quote_xml(
request: QuoteRequestBody,
client_name: str = Depends(verify_api_key),
) -> Response:
"""Restituisce direttamente il messaggio ACORD XML della quotazione."""
request_id = str(uuid.uuid4())
internal_request = _build_internal_request(request, request_id)
mapper = ACORDMapper()
xml_content = mapper.to_acord_xml(internal_request)
return Response(content=xml_content, media_type="application/xml")
@app.get(
"/api/v1/health",
summary="Health check del gateway",
)
async def health_check() -> dict:
return {
"status": "healthy",
"version": "2.1.0",
"acord_version": "ACORD-XML-165 / GRLC-2.0",
}
def _build_internal_request(request: QuoteRequestBody, request_id: str) -> InternalQuoteRequest:
"""Converte il body REST nel modello interno."""
from datetime import timedelta
ph = request.policyholder
veh = request.vehicle
return InternalQuoteRequest(
request_id=request_id,
policyholder=InternalPolicyholder(
tax_code=ph.tax_code,
first_name=ph.first_name,
last_name=ph.last_name,
birth_date=ph.birth_date,
gender=ph.gender,
address=ph.address,
city=ph.city,
province=ph.province,
postal_code=ph.postal_code,
license_date=ph.license_date,
claims_3yr=ph.claims_3yr,
),
vehicle=InternalVehicle(
plate=veh.plate,
vin=veh.vin,
brand=veh.brand,
model=veh.model,
year=veh.year,
engine_cc=veh.engine_cc,
fuel_type=veh.fuel_type,
annual_km=veh.annual_km,
),
coverages=[CoverageType(c) for c in request.coverages if c in CoverageType.__members__.values()],
effective_date=request.effective_date,
annual_mileage=veh.annual_km,
garage_type="privato",
)
async def _call_pricing_engine(request: InternalQuoteRequest) -> dict:
"""Simulazione chiamata al motore di pricing interno."""
# In produzione: chiamata HTTP al microservizio di pricing
base_premium = 450.0
if request.policyholder.claims_3yr > 0:
base_premium *= (1 + request.policyholder.claims_3yr * 0.3)
if request.vehicle.year < 2015:
base_premium *= 1.1
return {
"annual_premium": round(base_premium, 2),
"breakdown": {
"RCA": round(base_premium * 0.65, 2),
"KASKO": round(base_premium * 0.25, 2),
"accessories": round(base_premium * 0.10, 2),
},
}
Validarea mesajelor ACORD cu schema JSON
Validarea mesajelor este crucială înainte de a trimite sau procesa orice mesaj ACORD. Un mesaj malformat poate provoca respingeri silențioase din partea sistemului de recepție sau, mai rău, erori în datele privind politica sau reclamația. Schema JSON este instrumentul standard pentru validare de mesaje JSON ACORD.
import jsonschema
from jsonschema import validate, ValidationError
import json
from typing import Dict, List, Any, Tuple
# Schema JSON per una Quote Request ACORD semplificata
ACORD_AUTO_QUOTE_SCHEMA: Dict[str, Any] = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://acord.org/schemas/personal-auto-quote-request-v1",
"title": "ACORD Personal Auto Quote Request",
"type": "object",
"required": [
"acordVersion", "messageId", "messageType", "timestamp",
"policyholder", "vehicle", "requestedCoverages", "effectiveDate", "currency"
],
"properties": {
"acordVersion": {
"type": "string",
"pattern": "^[A-Za-z0-9.-]+$"
},
"messageId": {"type": "string", "minLength": 1},
"messageType": {
"type": "string",
"enum": ["AutoQuoteRequest", "AutoPolicyIssue", "ClaimNotification"]
},
"timestamp": {"type": "string", "format": "date-time"},
"policyholder": {
"type": "object",
"required": ["firstName", "lastName", "birthDate", "gender", "licenseDate"],
"properties": {
"firstName": {"type": "string", "minLength": 1},
"lastName": {"type": "string", "minLength": 1},
"birthDate": {"type": "string", "format": "date"},
"gender": {"type": "string", "enum": ["M", "F", "O", "U"]},
"licenseDate": {"type": "string", "format": "date"},
"claimsLast3Years": {"type": "integer", "minimum": 0, "maximum": 10},
"address": {
"type": "object",
"required": ["city", "countryCode"],
"properties": {
"street": {"type": "string"},
"city": {"type": "string"},
"postalCode": {"type": "string"},
"countryCode": {"type": "string", "pattern": "^[A-Z]{2}$"},
}
}
}
},
"vehicle": {
"type": "object",
"required": ["manufacturer", "model", "year", "fuelType", "annualMileageKm"],
"properties": {
"vin": {"type": "string", "pattern": "^[A-HJ-NPR-Z0-9]{17}$"},
"manufacturer": {"type": "string"},
"model": {"type": "string"},
"year": {"type": "integer", "minimum": 1980, "maximum": 2030},
"fuelType": {
"type": "string",
"enum": ["GAS", "DIE", "ELE", "HYB", "LPG", "CNG", "UNKN"]
},
"annualMileageKm": {"type": "integer", "minimum": 100, "maximum": 200000}
}
},
"requestedCoverages": {
"type": "array",
"items": {"type": "string"},
"minItems": 1
},
"effectiveDate": {"type": "string", "format": "date"},
"currency": {"type": "string", "pattern": "^[A-Z]{3}$"}
}
}
class ACORDMessageValidator:
"""Validatore di messaggi ACORD con JSON Schema."""
def __init__(self) -> None:
self.validator = jsonschema.Draft202012Validator(ACORD_AUTO_QUOTE_SCHEMA)
def validate(self, message: Dict) -> Tuple[bool, List[str]]:
"""
Valida un messaggio ACORD.
Returns:
(is_valid, error_messages): tuple con risultato e lista di errori
"""
errors: List[str] = []
try:
self.validator.validate(message)
return True, []
except ValidationError as e:
# Raccogli tutti gli errori, non solo il primo
for error in self.validator.iter_errors(message):
path = " -> ".join(str(p) for p in error.absolute_path)
errors.append(f"Campo '{path}': {error.message}")
return False, errors
def validate_strict(self, message_json: str) -> Tuple[bool, List[str]]:
"""Valida da stringa JSON con parsing incluso."""
try:
message = json.loads(message_json)
except json.JSONDecodeError as e:
return False, [f"JSON malformato: {e}"]
return self.validate(message)
# Esempio di utilizzo
if __name__ == "__main__":
validator = ACORDMessageValidator()
valid_message = {
"acordVersion": "PersonalLines-1.0",
"messageId": "msg-001",
"messageType": "AutoQuoteRequest",
"timestamp": "2025-03-10T14:30:00Z",
"policyholder": {
"firstName": "Mario",
"lastName": "Rossi",
"birthDate": "1985-06-15",
"gender": "M",
"licenseDate": "2003-09-20",
"address": {"city": "Milano", "countryCode": "IT"},
},
"vehicle": {
"manufacturer": "Fiat",
"model": "Tipo",
"year": 2022,
"fuelType": "GAS",
"annualMileageKm": 15000,
},
"requestedCoverages": ["BI", "COMP"],
"effectiveDate": "2025-04-01",
"currency": "EUR",
}
is_valid, errors = validator.validate(valid_message)
print(f"Valido: {is_valid}, Errori: {errors}")
# Output: Valido: True, Errori: []
Model de integrare a asigurărilor
Alegerea modelului de integrare depinde de volumul mesajelor și de latența necesară și complexitatea ecosistemului. Iată principalele modele din industria asigurărilor:
Compararea modelelor de integrare
| Modele | Când să-l folosești | Avantaje | Dezavantaje |
|---|---|---|---|
| API punct la punct | Puține integrări, volum redus | Simplitate, latență scăzută | Cuplaj strâns, fără detartrare |
| ESB (Autobuz de servicii pentru întreprinderi) | Multe sisteme eterogene, companii mari | Orchestrație centralizată, transformări | Punct unic de eșec, complexitate |
| Bazat pe evenimente (Kafka) | Microservicii de volum mare, în timp real | Scalabilitate, decuplare, pistă de audit | Complexitate operațională, eventual consistență |
| API Gateway + Adaptor | InsurTech modern, nativ în cloud | Standardizare, versiune, securitate | Latență suplimentară, gateway ca blocaj |
Cele mai bune practici și anti-modele
Cele mai bune practici pentru integrarea ACORD
- Mesaje de versiune: includeți întotdeauna versiunea standard ACORD în mesaj (de exemplu, „acordVersion”: „GRLC-2.0”); Compatibilitatea inversă este critică
- Idempotenta obligatorie: utilizează RqUID (ID unic al cererii) pentru a se asigura că mesajele duplicate nu generează tranzacții duplicate
- Valabil înainte de a trimite: validați întotdeauna mesajul cu schema XSD/JSON din partea expeditorului înainte de a-l trimite - nu depindeți de eroarea destinatarului
- Jurnal complet de mesaje: păstrați o copie a tuturor mesajelor ACORD trimise și primite timp de cel puțin 10 ani (obligatoriu pentru reglementările de asigurări)
- Timeout și reîncercați cu backoff exponențial: integrările de asigurări trebuie să facă față eșecurilor temporare; implementați reîncercarea cu jitter pentru a evita turma de tunet
Anti-modele de evitat
- Mapări hardcoded: nu codificați codurile ACORD în codul aplicației; păstrați-le în fișierele de configurare versionate și actualizabile fără implementare
- Ignorați codurile de răspuns: ACORD oferă mesaje de răspuns standardizate; Tratați întotdeauna codurile de eroare (MsgStatusCd) nu doar codurile HTTP
- Transformări în ESB: păstrați transformările datelor în stratul de aplicație, nu în ESB; ESB trebuie să fie un router, nu un procesor de date
- Utilizați ACORD XML pentru sisteme noi: pentru noi integrări preferați ACORD JSON (GRLC 2.0) sau un API REST pe care compania destinatară îl expune pe deasupra ACORD
Concluzii și pașii următori
Standardele ACORD sunt lingua franca a interoperabilității asigurărilor. Înțelege-i iar a ști cum să le implementezi corect este o abilitate fundamentală pentru orice dezvoltator care lucrează în domeniul InsurTech, atât pe partea companiei, cât și pe partea brokerului sau agregatorului.
GRLC Generația 2.0 din 2025 marchează un schimbător de joc: suport nativ JSON și Abordarea API-first face în sfârșit standardele ACORD accesibile dezvoltatorilor utilizatorii moderni obișnuiți cu REST și JSON, scăzând semnificativ bariera de intrare.
Următorul și ultimul articol din serie analizează Inginerie de conformitate: Solvabilitate II și IFRS 17 — cum să construiți infrastructura de date și conducte de raportare necesare pentru îndeplinirea celor mai exigente obligații de reglementare ale sectorului asigurărilor.
Seria InsurTech Engineering
- 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 (acest articol)
- 08 - Inginerie de conformitate: Solvabilitate II și IFRS 17







