Industry Rating Model (Prism Score) Assign comparable financial-strength scores to insurance firms using public filings (CapScore subproject — replaces NRSRO ratings).
Public-data-driven analogue to AM Best / S&P / Moody's NRSRO ratings. Input: statutory + GAAP filings via FinView and StatSight. Output: Prism Score (firm-level financial strength index). Per Decision 044 the rating is implied purely from fundamentals — the engine ingests no agency rating (disclosed ratings are validation targets, not inputs). Active at Tier-3 (RAT-200-v1.0.1); factor weights/thresholds remain approximated from public agency methodology, not calibrated against a labeled rating dataset (COND-003 open — gates external publication or promotion above Tier-3; D044 implied-vs-disclosed cross-check INV-026 is the open build).
Inputs, processing, outputs
Methodology
Identity note (INV-031, reconciled — RAT-200 COND-001). The backing engine is keyed by its dotted engine_id
insmodel.L3.rating_model, not by a model_id. Its legacy Scheme-B governance id M-916 is RETIRED per Decision 046: therating_model.pysource header now reads "Engine ID: insmodel.L3.rating_model | Model membership: M-200 (Tier-3)", the engine_registry no longer carries an activemodel_id, and the legacy_metadata archive marks M-916 RETIRED. The Scheme-A linkage ismodel_membership: [M-200], and this card + the model registry use M-200 (intended-use model scheme). The historical M-916 string survives only in the SR 11-7 archive lineage; it is not an active identity.
M-200 (engine insmodel.L3.rating_model, class RatingModel v1.0.0) is an
implied financial-strength / credit rating model. It takes a single firm's
disclosed fundamental metrics and produces a model-implied AM Best and S&P
rating, a 0–100 composite ("Prism") score, ten factor sub-scores, per-factor
weighted contributions, a peer percentile, and a distance-to-downgrade buffer.
The score is built in six mechanical steps (RatingModel.calculate,
firmmodel/engines/rating_model.py):
- KPI extraction. The 15 optional fundamental KPIs declared in
ENGINE_CONTRACT["inputs"]["optional"]are read per firm from the input DataFrame:rbc_ratio_est,asset_leverage,debt_equity,investment_yield,loss_ratio,expense_ratio,roe,operating_leverage,reserve_coverage,reinsurance_leverage,interest_coverage,duration_gap_proxy,credit_quality_score,liquidity_coverage_ratio,total_assets_B. The only required input is a firm identifier (firm_name|firm_id); every KPI is optional and missing KPIs are tolerated — see step 2. (Note: not all 15 declared KPIs map to a scoring factor; the 10 factors in step 2 consume a subset, withig_allocation_est/equity_allocation_pctused as factor alternates.) - Per-factor scoring. Each of 10 factors maps one (or two) KPIs to a
0–100 sub-score by piecewise-linear interpolation against calibrated
threshold breakpoints (
_interpolate_score). For example capitalization maps RBC 400% → 100, 300% → 80, 250% → 60, 200% → 40, 150% → 20. Factors carrying a primary and an alternate KPI (e.g. operating_performance = ROE + investment yield) blend them 70/30 when both are present. A factor whose KPI is absent receives a neutral score of 50.0 (NEUTRAL_SCORE) rather than being dropped. - Composite. The composite Prism score is the weighted sum of the ten
sub-scores, with weights summing to 1.0 (
RATING_FACTORS,firmmodel/config/rating_factors.py). Capitalization (0.25) and operating performance (0.20) dominate; liquidity (0.10) is next. - Rating mapping. The composite is mapped to an implied AM Best / S&P
band via a top-down threshold table (
RATING_THRESHOLDS,_map_composite_to_ratings): ≥90 → A++/AAA, ≥80 → A+/AA, ≥70 → A/A, ≥60 → A-/BBB+, ≥50 → B++/BBB, ≥40 → B+/BB, else B/B. - Peer percentile. When more than one firm is scored together, each firm's composite is ranked against the others in the input population.
- Distance to downgrade. The number of composite points the firm sits
above the next lower rating threshold (
_compute_distance_to_downgrade), a thin "buffer" diagnostic — not a probability.
The D044 stance: recover, don't ingest
Per Decision 044, the platform's posture is recover, don't ingest.
Agency ratings (AM Best, S&P, Moody's) are treated strictly as outputs that
the model is validated against — never as model inputs. M-200 implies a
rating purely from disclosed fundamentals; it never reads a disclosed agency
letter grade into the scoring path. Inspect the input contract
(ENGINE_CONTRACT["inputs"]) and you will find only fundamental KPIs — no
agency_rating field exists. The disagreement between the model-implied
rating and a firm's actually-disclosed agency rating is the product signal:
where the fundamentals imply A++ but the agency says A-, that gap is the
analyst's lead, not an error to be calibrated away. Agency methodology
documents (AM Best BCAR, S&P, Moody's) inform the factor thresholds, but
the agencies' published outputs enter only as a downstream cross-check.
Key Assumptions and Their Justification
The model's behavior is governed almost entirely by the ten factor weights and their threshold tables. Weights are approximated from public agency methodology descriptions and sum to 1.0.
| Factor | Weight | Primary KPI (alt) | Direction | Justification |
|---|---|---|---|---|
| capitalization | 0.25 | rbc_ratio_est | higher better | Capital adequacy is the single largest driver in AM Best BCAR and S&P capital models; RBC ratio is the most load-bearing solvency signal. |
| operating_performance | 0.20 | roe (+ investment_yield) | higher better | Earnings power and investment yield are the primary forward-looking strength indicators; blended 70/30 to balance accounting ROE against asset productivity. |
| liquidity | 0.10 | liquidity_coverage_ratio | higher better | Liquidity stress is the proximate cause of insurer failure even when solvent on a book basis; weighted above most individual risk factors. |
| fixed_income_risk | 0.08 | credit_quality_score (+ ig_allocation_est) | higher better | Asset credit quality dominates the general-account risk profile of life insurers. |
| credit_risk | 0.08 | reinsurance_leverage | lower better | Heavy reliance on ceded reserves introduces counterparty exposure; high reinsurance leverage is penalized. |
| reserve_risk | 0.08 | reserve_coverage | higher better | Reserve adequacy (held / required) directly bounds balance-sheet integrity. |
| ir_duration_risk | 0.06 | duration_gap_proxy | lower better | Asset-liability duration mismatch drives rate-shock vulnerability; small absolute gap is rewarded. |
| premium_risk | 0.06 | operating_leverage | lower better | Net-premium-to-surplus leverage; high writing leverage thins the capital cushion. |
| business_risk | 0.05 | expense_ratio (+ net_income_margin) | lower better | Operational efficiency proxy; high expense ratios erode margin. |
| equity_risk | 0.04 | equity_allocation_pct | lower better | Equity allocation in the general account adds capital volatility; smallest weight reflects modest typical exposure. |
Prose justification. The weighting deliberately concentrates ~45% of the score in capitalization + operating performance, mirroring the public emphasis of AM Best and S&P that solvency and earnings dominate insurer financial strength. The remaining 55% is spread across asset, reserve, reinsurance, duration, premium-leverage, efficiency, liquidity, and equity-risk factors so that no single secondary factor can swing the composite by more than ~10 points. Two operational assumptions matter materially: (a) missing KPIs are scored 50, not excluded — so a firm disclosing only its name lands exactly at composite 50 (B++/BBB), the deliberate "no information → mid-band" prior; and (b) interpolation is linear between breakpoints, whereas real agency mappings are often non-linear — a known approximation.
Output Snapshot
Deterministic three-firm run of RatingModel v1.0.0 — reproducible, requires
no live firm data (python scripts/model_snapshots.py M-200 in InsModel;
the same canonical strong-firm fundamentals are asserted by
tests/test_rating_model.py::TestRatingModelCore). The StrongCo input is
the test's _strong_firm_row(); MidCo and WeakCo are included only to
illustrate peer ranking and the rating spread.
Input (StrongCo, synthetic): RBC 400% · ROE 12% · investment yield 5% · reserve coverage 1.20 · expense ratio 18% · operating leverage 1.5 · reinsurance leverage 0.15 · credit-quality 0.93 · liquidity coverage 1.8 · duration gap 0.8 · equity allocation 3%.
| output | value | meaning |
|---|---|---|
| composite_score (StrongCo) | 92.17 | weighted Prism score (0–100) |
| implied_am_best | A++ | implied AM Best band (composite ≥ 90) |
| implied_sp | AAA | implied S&P band |
| peer_percentile | 100.0 | top of the 3-firm input population |
| distance_to_downgrade | 2.17 | composite points above the A++ (90) floor |
Factor contributions (score × weight → contribution to 92.17):
| factor | sub-score | weight | contribution |
|---|---|---|---|
| capitalization | 100.00 | 0.25 | 25.00 |
| operating_performance | 83.00 | 0.20 | 16.60 |
| liquidity | 92.00 | 0.10 | 9.20 |
| reserve_risk | 100.00 | 0.08 | 8.00 |
| fixed_income_risk | 92.00 | 0.08 | 7.36 |
| credit_risk | 90.00 | 0.08 | 7.20 |
| premium_risk | 90.00 | 0.06 | 5.40 |
| ir_duration_risk | 88.00 | 0.06 | 5.28 |
| business_risk | 88.00 | 0.05 | 4.40 |
| equity_risk | 93.33 | 0.04 | 3.73 |
Peer rating spread (implied AM Best / S&P / composite): StrongCo A++/AAA/92.17 · MidCo A+/AA/82.37 · WeakCo B/B/23.78.
The contributions sum exactly to the composite (25.00 + 16.60 + 9.20 + 8.00 + 7.36 + 7.20 + 5.40 + 5.28 + 4.40 + 3.73 = 92.17), confirming the weighted-sum construction. Capitalization alone contributes 25 of the 92 points; together with operating performance it accounts for 42 of the 92. The distance-to- downgrade of 2.17 shows StrongCo sits only ~2 composite points above the A++ floor — a thin buffer despite the strong inputs, because the A++ band starts at 90. No disclosed agency rating entered this computation (D044): if this firm's actual disclosed rating were, say, A+ rather than A++, that one-notch gap would be the reported signal.
Captured 2026-06-04 · deterministic, no live data.
Limitations and Known Gaps
- Indicative-only — not production-validated. M-200 is
activeat Tier-3 for internal strategy use (RAT-200-v1.0.1, 2026-06-07) but is NOT production-validated: the factor weights and thresholds are approximated from public agency methodology descriptions, not calibrated against a labeled rating dataset (COND-003, open — gates external publication or promotion above Tier-3). Implied ratings are indicative only and must not be relied on as financial-strength opinions. - Weights and thresholds are approximations of proprietary methodologies. Actual AM Best / S&P / Moody's weights and mappings are proprietary and non-linear; M-200 uses linear interpolation between public breakpoints. Calibration error against the true agency surfaces is unquantified.
- No qualitative overlay. Management quality, enterprise risk management, strategic positioning, group support, and sovereign/sector ceilings — all material to real agency ratings — are entirely absent. The model is purely quantitative.
- Missing-KPI neutral-score bias. Absent KPIs are scored 50, not excluded. A sparsely-disclosing firm is pulled toward the B++/BBB mid-band regardless of its true posture; a firm disclosing only a name scores exactly
- This can mask both strength and weakness.
- Peer percentile is population-relative, not universe-relative. The percentile ranks only firms in the input DataFrame (here, 3), not the full rated universe; the 100.0 shown is "best of these three," not "100th percentile of all insurers."
- Single-period snapshot; no outlook or trajectory. The model captures one point in time. It produces no rating outlook, no transition probability, and no through-the-cycle adjustment; thresholds are static across economic regimes.
- Distance-to-downgrade is a static buffer, not a probability. It assumes all other factors hold constant (ceteris paribus) and measures only points above the next threshold; it is not a calibrated downgrade likelihood.
- No holding-company notching. Subordinated-debt or holdco-structure notching that agencies apply is not modeled.
- Subject to BV-032. Snapshot figures here are deterministic on synthetic fundamentals; any run on live firm data is exposed to the BV-032 firm-data divergence and is out of scope for this card.
Tracked for ratification. Status after the RAT-200 remediation pass: - Engine identity + tier (COND-001) — RESOLVED. The engine_registry tier inconsistency is fixed:
insmodel.L3.rating_modelis nowtier: tier-3/owner: engine-owner-intelligence(wastier-1/engine-owner-actuarial), matching the Tier-3 M-200 it solely feeds, per R-12 tier inheritance; the legacy Scheme-Bmodel_id: M-916is retired across source header, engine_registry, and legacy_metadata (see the Identity note under Methodology). - Registry documentation_pack (COND-002) — RESOLVED.model_cardflag set topresentin model_registry.yaml. - Scaffold maturity / D044 cross-check (COND-003) — OPEN (non-blocking at Tier-3). Output-changing / modeling-code items left for a future build: the D044 implied-vs-disclosed agency-rating cross-check (INV-026 — the model-vs-agency disagreement is the product signal; recorded in the Validation Packet as the intended next step, not yet implemented), and a calibrated validation-evidence pack (the ten factor weights/thresholds are approximated from public agency methodology, not calibrated against a labeled dataset). These MUST close before external publication or promotion above Tier-3.
Validation Packet
| Check | Where | What it asserts |
|---|---|---|
| Weights sum to 1.0 | test_rating_model.py::test_factor_weights_sum_to_one |
The 10 RATING_FACTORS weights sum to 1.0 within 1e-9. |
| Composite in [0,100] | ::test_composite_score_range |
Every firm's composite is bounded to [0, 100]. |
| Strong firm rates well | ::test_high_quality_firm_rates_well |
RBC 400% / ROE 12% / reserve-cov 1.20 → A+ or A++ (the canonical input above yields A++). |
| Weak firm rates poorly | ::test_weak_firm_rates_poorly |
RBC 120% / ROE 2% / reserve-cov 0.90 → B+ or B (WeakCo → B). |
| Threshold boundaries | ::test_rbc_scoring_boundaries |
RBC 400/300/250/150% interpolate to ~100/80/60/20. |
| Missing KPI → neutral 50 | ::test_missing_kpi_gets_neutral_score |
Name-only firm scores composite ≈ 50.0. |
| Directionality | ::test_higher_is_better_direction, ::test_lower_is_better_direction |
higher-is-better and lower-is-better factors move the score in the correct direction. |
| Rating mapping | ::test_score_90_maps_to_a_plus_plus, ::test_score_50_maps_to_b_plus_plus |
Composite 92 → A++/AAA; 55 → B++/BBB. |
| Monotonic mapping | ::test_score_ordering |
Across composite 0→100 in steps of 5, the implied rating never gets worse as the score rises. |
| Peer ranking | ::test_multi_firm_peer_ranking |
Three firms get distinct percentiles in [0,100]; strongest > weakest. |
| Distance-to-downgrade | ::test_distance_to_downgrade_computed, ::test_strong_firm_has_positive_buffer |
Buffer column present, non-NaN, and positive for a strong firm. |
Suite status: 15 passed (pytest tests/test_rating_model.py,
re-run 2026-06-12). Ratified active at Tier-3 by RAT-200-v1.0.1
(2026-06-07); the standing evidence is the abbreviated Tier-3 pack at
modelling/validation_evidence/M-200/v1.0.0/ (the unit suite + the
deterministic snapshot — no calibrated validation exists).
Recover-don't-ingest validation (D044) is the intended next step —
cross-checking implied ratings against disclosed agency ratings as targets,
which is not yet implemented (INV-026, open). The curated disclosed-rating
validation-target tables located in CapScore/sources/*_scraper.py
(provenance: public agency lookups/press releases, 14 tickers) are the
identified target data for that build; the open gap is the per-firm
15-KPI fundamentals vectors (FinView/StatSight assembly).
References
Internal:
- Decision 044 (recover-don't-ingest) — agency ratings are validation
targets/outputs, never model inputs; the model-vs-agency disagreement is
the signal.
- Engine source: ecosystem/InsModel/Models/firmmodel/engines/rating_model.py
(class RatingModel, insmodel.L3.rating_model).
- Factor config: ecosystem/InsModel/Models/firmmodel/config/rating_factors.py
(RATING_FACTORS, RATING_THRESHOLDS).
- Tests: ecosystem/InsModel/Models/tests/test_rating_model.py.
- Snapshot: ecosystem/InsModel/Models/scripts/model_snapshots.py (snap_M_200).
- Legacy metadata: firmmodel/governance/legacy_metadata/RatingModel.yaml.
Agency methodology (comparison / threshold provenance — outputs validated against, not ingested): - AM Best — Best's Capital Adequacy Ratio (BCAR) methodology (public). - S&P Global — Insurance Rating Criteria (insurer financial strength framework). - Moody's — Financial Metrics and Scorecard for Insurance Companies. - NAIC — Insurance Regulatory Information System (IRIS) ratio benchmarks.
Change Log
Card change history. Code-side change history lives in git log of the component files.
- 2026-05-08 — stub created from registry data per Decision 023 Phase 5 / B-07.
- 2026-06-06 — code-grounded documentation-accuracy pass against
firmmodel/engines/rating_model.pyENGINE_CONTRACT+ engine_registry M-200 entry. Removed the stale, self-contradictory "NRSRO ratings hardcoded pending live extraction" phrasing from the Description and replaced it with the code-verified D044 stance (engine ingests no agency rating; rating is implied from fundamentals) plus an honest scaffold-maturity statement. Added Standards Coverage (ASOP_56 sole conformance regime + AM Best / S&P / Moody's / NAIC IRIS threshold provenance, validated-against not ingested per D044) and Dependencies (upstream models: none, grounded in registry — empty-by-design vs omitted) sections, mirroring the M-001 structure. Completed Methodology step-1 KPI enumeration to the full 15ENGINE_CONTRACToptional inputs. Added a dual-ID note (engine governance id M-916 vs card/registry M-200 — the known INV-031 overload). Noted gated/ratification items (INV-026 D044 cross-check, ratification + validation evidence, engine_registry tier-1 inconsistency under R-12) under Limitations. No model outputs, back-test numbers, or validation results were fabricated or changed. - 2026-06-06 — RAT-200 remediation pass (1L, Decision 053). Closed
COND-001 (engine identity + tier collision): retired the legacy Scheme-B
model_id: M-916across therating_model.pysource header, the engine_registry, and the legacy_metadata archive per Decision 046, and re-tagged the enginetier: tier-3/owner: engine-owner-intelligenceto match the Tier-3 M-200 it solely feeds (R-12 tier inheritance). Rewrote the Methodology Identity note and the Limitations ratification block to reflect the resolution. Closed COND-002: setdocumentation_pack.model_card: presentin model_registry.yaml. Assembled the Tier-3 validation-evidence pack atmodelling/validation_evidence/M-200/v1.0.0/from the existing 15/15 unit suite + the deterministic snapshot reconciliation (no new numbers generated). COND-003 (scaffold maturity / D044 INV-026 cross-check / uncalibrated weights) remains honestly open — a genuine engine-build that is not fabricated. No model outputs, back-test numbers, or validation results were fabricated or changed. - 2026-06-12 — RAT-200-v1.0.1 post-ratification reconciliation pass (1L,
Decision 053 remediation flow for INV-026 + COND-003). Doc reconciliation:
card status
under_development→active(RAT-200-v1.0.1 promoted M-200 on 2026-06-07; the registry was already reconciled), Limitation 1 and the validation-suite paragraph updated to the post-ratification reality, the model_registry.yaml description's stale "scaffold maturity / NRSRO ratings hardcoded pending live extraction" phrasing replaced with the code-verified D044 stance, and themodel_snapshots.pyM-200 header re-labeled ACTIVE/Tier-3 (figures unchanged — re-run 2026-06-12 reproduces composite 92.17 / A++ / AAA exactly; unit suite 15/15). F-001 closed: legacy_metadataindex.yamlRatingModel entry annotated M-916 RETIRED (mirrors the LiquidityEngine/RAT-053 precedent). INV-026 and COND-003 remain HONESTLY OPEN — genuine engine/data builds (no cross-check or calibration result was fabricated). Scoping addendum added atmodelling/validation_evidence/M-200/v1.0.0/addendum-2026-06-12-inv026-cond003-scoping.md: the disclosed-rating validation-target data EXISTS (curated public-lookup tables inCapScore/sources/*_scraper.py, 14 tickers); the remaining build is the per-firm 15-KPI fundamentals assembly + the cross-check harness, then weight/threshold calibration. No model outputs, back-test numbers, or validation results were fabricated or changed.
Open findings (2)
Independent 2nd-line review (INV-2026-06) — implemented capability vs registered scope. Each carries a recommended fix and is tracked in insightalm-mrm until closed.
Validation evidence + change logs missing across most of the inventory
Only M-001/M-020/M-050 carried full documentation packs before this pass. Most models record validation_evidence: missing and change_log: missing with peer_review: pending. Gold tests freeze behaviour but many assert only structural invariants (e.g. reserve>0), not correctness against external truth. The flagship T0-vs-10-K match is circular (BV-032).
Recommendation: For each Tier-1 model: produce a validation-evidence pack (back-test vs disclosed results once BV-032 re-calibration lands, sensitivity suite, challenger comparison), a change log, and a 2L ratification. Sequence behind BV-032 (firm-data) for anything needing 10-K reconciliation.
Prism rating model under_development; D044 cross-check not implemented
RatingModel is functional (fundamentals -> implied score, no agency-rating input, so the D044 'recover-don't-ingest' stance is structurally enforced) but status under_development with no governance ratification and only a unit-test suite. The D044 next step — cross- checking implied vs disclosed agency ratings as validation targets — is not built.
Recommendation: Implement the implied-vs-disclosed cross-check (the disagreement is the signal, per D044), add validation evidence, and ratify before promoting out of under_development.
Ratified — RAT-200-v1.0.1
Latest ratification on file: RAT-200-v1.0.1. Authored by 2L (mrm-peer-reviewer) per Decision 028 charter §5 Pattern A.