ACORD標準と保険APIの統合
保険エコシステムはその性質上、保険会社、 再保険会社、ブローカー、代理店、銀行(銀行窓販)、オンライン比較業者、サプライヤー サービス (ワークショップ、病院、弁護士) のすべて - これらすべての関係者がデータを交換する必要がある 効率的、正確、そして監査可能に。共通の標準がなければ、あらゆる統合が それは高コストとリスクを伴うカスタム プロジェクトになります。
アコード (共同事業研究開発協会) 保険業界のデータ標準を設定する世界的な組織 1970 年以来。その標準 — ポリシー、クレーム、 再保険と会計規制 — 世界中から採用されています 36,000の組織 100か国で。 2025 年に ACORD は、 GRLC 第 2.0 世代データ標準、 再保険基準と大規模な商業リスクの包括的な見直し ネイティブ JSON サポートと API ファースト指向。
このガイドは、ACORD 標準を具体的なコードに変換します: メッセージの構造化方法 標準に準拠した XML/JSON、独自システム間のマッピング層を構築する方法 と ACORD、そして業界のベスト プラクティスに準拠した RESTful API を公開する方法。
何を学ぶか
- ACORD 標準の概要: さまざまな事業分野向けの XML と JSON
- ポリシーとクレームの ACORD メッセージの構造
- 独自のデータモデルとACORD間のマッピング
- Python/FastAPI での ACORD 準拠の API ゲートウェイの実装
- XML スキーマ (XSD) および JSON スキーマを使用したメッセージ検証
- 統合パターン: ポイントツーポイント、ESB、イベント駆動型
- ACORD 認定: 要件とテストプロセス
ACORD規格の概要
ACORD は、保険業界の各分野に対して異なる基準を発行しています。主なもの この分野で働く開発者が知っておくべきことは次のとおりです。
地域別ACORD規格
| 標準 | エリア | 形式 | 注意事項 |
|---|---|---|---|
| ACORD XML (P&C) | 損害: 車、物、賠償責任 | XML | 北米で最も普及している規格 |
| アコードGRLC | 再保険と大きな商業リスク | XML + JSON (Gen 2.0) | 2025年にAPIファーストでリニューアル |
| アコードエボット | 会計規程(会計・決算) | XML | Gen 2.0アップデートが予定されています |
| アコードエコット | 請求の動き | XML | Gen 2.0アップデートが予定されています |
| アコード CRP | 契約、リスク、事前会計 | XML + JSON | 新しい GRLC Gen 2.0 標準 |
| ACORD ライフ XML | 生命と健康の部門 | XML | ACORD Standard 103 および関連 |
ACORD XML メッセージの構造
ACORD XML メッセージには、明確に定義された階層構造があります。ブロックを理解する 基本であり、統合の最初のステップです。メッセージの例は次のとおりです 自動車保険契約の見積もりをリクエストする場合 (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>
JSON 形式の ACORD メッセージ (GRLC Gen 2.0)
新しい GRLC Generation 2.0 (2025) 標準では、ネイティブ JSON 表現が導入されています。 再保険メッセージと大きな商業リスク。 JSON形式の方が軽いので、 最新のアーキテクチャ (REST API、イベント ストリーミング) での処理が容易になり、はい OpenAPI や JSON スキーマなどのツールとネイティブに統合します。
{
"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."
}
}
マッピング層: 独自モデルから ACORD へ
企業の内部システムが ACORD 命名法を直接使用することはほとんどありません。 マッピング レイヤーは、独自のデータ モデルと ACORD 標準の間で変換を行います。 双方向で。このレイヤーは、標準のバージョンで常に最新の状態に保つ必要があります。
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",
}
FastAPI を使用した ACORD API ゲートウェイ
REST API 経由で ACORD 統合機能を公開すると、ブローカーが可能になります。 代理店やサードパーティのシステムを管理することなく保険システムに接続できるようになります。 XML 標準の内部の複雑さ。ゲートウェイは REST 呼び出しをメッセージに変換します ACORD やその逆も同様です。
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),
},
}
JSON スキーマを使用した ACORD メッセージの検証
ACORD メッセージを送信または処理する前に、メッセージの検証が非常に重要です。 不正な形式のメッセージは、受信側システムからサイレント拒否を引き起こしたり、さらに悪いことに、 保険契約または保険金請求データのエラー。 JSON スキーマは検証用の標準ツールです 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: []
保険統合パターン
統合パターンの選択は、メッセージの量と必要な待機時間によって異なります。 そして生態系の複雑さ。保険業界の主なパターンは次のとおりです。
統合パターンの比較
| パターン | いつ使用するか | 利点 | 短所 |
|---|---|---|---|
| ポイントツーポイント API | 統合はほとんどなく、量は少ない | シンプルさ、低遅延 | ノンスケーリング、密結合 |
| ESB (エンタープライズ サービス バス) | 多くの異種システム、大企業 | 一元化されたオーケストレーション、変換 | 単一障害点、複雑さ |
| イベント駆動型 (Kafka) | 大容量、リアルタイム、マイクロサービス | スケーラビリティ、分離、監査証跡 | 運用の複雑さ、最終的な整合性 |
| APIゲートウェイ+アダプター | 最新のクラウドネイティブなインシュアテック | 標準化、バージョン管理、セキュリティ | 追加の遅延、ボトルネックとなるゲートウェイ |
ベストプラクティスとアンチパターン
ACORD 統合のベスト プラクティス
- バージョンメッセージ: メッセージには常に ACORD 標準バージョンを含めてください (例: "acordVersion": "GRLC-2.0")。下位互換性は重要です
- 義務的な冪等性: RqUID (リクエストの一意の ID) を使用して、重複メッセージによって重複トランザクションが生成されないようにする
- 送信前に有効: メッセージを送信する前に、常に送信者側で XSD/JSON スキーマを使用してメッセージを検証します。受信者のエラーに依存しません。
- 完全なメッセージ ログ: 送受信されたすべての ACORD メッセージのコピーを少なくとも 10 年間保存する (保険規制により義務付けられています)
- タイムアウトと指数バックオフによる再試行: 保険統合では一時的な障害に対処する必要があります。雷のような群れを避けるためにジッターを伴う再試行を実装する
避けるべきアンチパターン
- ハードコードされたマッピング: ACORD コード マップをアプリケーション コードにハードコーディングしないでください。バージョン管理された構成ファイルに保存し、展開しなくても更新できるようにします
- 応答コードを無視します。 ACORD は標準化された応答メッセージを提供します。 HTTP コードだけでなく、常にエラー コード (MsgStatusCd) を扱います。
- BSE における変化: データ変換は ESB ではなくアプリケーション層に保持します。 ESB はデータ プロセッサではなくルーターでなければなりません
- 新しいシステムには ACORD XML を使用します。 新しい統合の場合は、ACORD JSON (GRLC 2.0) または受信側の企業が ACORD 上で公開する REST API を推奨します。
結論と次のステップ
ACORD 標準は保険の相互運用性の共通語です。それらを理解する そして、それらを正しく実装する方法を知ることは、開発者にとって基本的なスキルです。 企業側とブローカーまたはアグリゲーター側の両方でインシュアテック分野で働く人たちです。
2025 年の GRLC Generation 2.0 は、ネイティブ JSON サポートと API ファーストのアプローチにより、開発者はついに ACORD 標準にアクセスできるようになりました 現代のユーザーは REST と JSON に慣れており、参入障壁が大幅に低くなります。
シリーズの次の最後の記事では、 コンプライアンスエンジニアリング: ソルベンシー II および IFRS 第 17 号 — データインフラストラクチャとレポートパイプラインを構築する方法 保険業界の最も厳しい規制義務を満たすために必要です。
インシュアテックエンジニアリングシリーズ
- 01 - 開発者向けの保険ドメイン: 製品、アクター、データ モデル
- 02 - クラウドネイティブのポリシー管理: API ファーストのアーキテクチャ
- 03 - テレマティクス パイプライン: 大規模な UBI データ処理
- 04 - AI 引受業務: 特徴エンジニアリングとリスク スコアリング
- 05 - 請求の自動化: コンピューター ビジョンと NLP
- 06 - 不正行為の検出: グラフ分析と行動シグナル
- 07 - ACORD Standard と Insurance API の統合 (この記事)
- 08 - コンプライアンス エンジニアリング: ソルベンシー II および IFRS 17







