• contact@verticalserve.com
Home / Engineering / Post 85
Engineering Blog · Post #85

From Ticker to Premium in One Quote Run: How InsightUW Auto-Populates a Public D&O Rater from EDGAR, S&P Capital IQ, and LinkedIn

A broker submits a public-company D&O renewal with only the ticker symbol. On quote-run, InsightUW fetches SEC EDGAR company facts, S&P Capital IQ financials, auto-generates LinkedIn officer search links for every named executive, populates 11 rating features — and hands a complete input set to the D&O rater. No typing. No copy-paste. No "we'll get back to you on the exposure data."


The Problem

A public D&O submission's rating inputs are 90% public record:

  • Market cap (EDGAR, Capital IQ)
  • Total assets (EDGAR 10-K balance sheet)
  • Annual revenue (EDGAR 10-K income statement)
  • Officer count + named executives (EDGAR DEF 14A proxy)
  • Securities class action history (EDGAR litigation + specialized databases)
  • S&P rating + analyst target prices (Capital IQ)
  • CEO name + tenure (Capital IQ)
  • NAICS sector + industry classification (D&B, EDGAR 10-K Item 1)

But in most workstations, the UW types it all in. Or asks the broker to fill a 30-field intake form. Or copy-pastes from Bloomberg. Each account takes 20-40 minutes of data entry before rating can even begin. A portfolio UW working $25M-$500M market-cap D&O is on a 45-submission-per-week pace and spends three hours a day typing exposure data that's already in the public domain.

The InsightUW approach: declare the data sources once, let the engine fetch on every quote run.

The Architecture

Four cap-level concepts combine to make this work end-to-end:

graph TB subgraph Profile["Risk Assessment Profile (cap RA #1)"] P["Risk Assessment Profile<br/>us public dno pack<br/>source codes: [edgar public company,<br/>sec filings list, sp capital iq profile,<br/>linkedin officer search]"] end subgraph Sources["Exposure Sources (cap R/IR #5)"] S1["edgar public company<br/>platform call via sec edgar<br/>output mapping → 7 features"] S2["sec filings list<br/>platform call via sec edgar<br/>asset output mapping → filing URLs"] S3["sp capital iq profile<br/>platform call via sp capital iq<br/>output mapping → 4 features"] S4["linkedin officer search<br/>link only<br/>Jinja renders search URL"] end subgraph Features["Feature Store (cap R/IR #2)"] F["Rating Feature Value<br/>11 values written<br/>with shared lineage"] end subgraph Rater["Pricing Model (cap R/IR #1)"] R["us public dno v1 2026<br/>input schema.source = 'feature:<code>'<br/>auto-populate before validate"] end P --> S1 P --> S2 P --> S3 P --> S4 S1 --> F S2 --> F S3 --> F F --> R

The UW types one thing: the insured's ticker (or the submission intake bot extracts it from the broker's email). The rest is a single run quote call away.

The Account

  • Insured: ACME Semiconductor Corp
  • Ticker: ACME
  • Broker-provided data: ticker + requested $25M limit + $50k retention + 7/1/2026 effective
  • LOB: public_company_dno

That's all that comes in the submission. The broker doesn't attach a 10-K. They don't list the officers. They don't note the litigation history. That's fine — the system finds all of it.

Step 1 — Submission Intake

Submission lands. Submission Queue row created. The Insured row has ticker = 'ACME' populated (either by the broker's structured intake or by the cap #5 broker submission facts enrichment which scans the submission PDF).

At this point the rater has nothing. If we ran us public dno v1 2026 now, every feature:* field would come up null and the quote would fail validation.

Step 2 — The Risk Assessment Profile Fires

The UW opens the submission and clicks Risk Assessment in the toolbar. The workbench route /uw/risk-assessment/<submissionGuid> loads. The applicable profile for lob=dno, product=public_company is us_public_dno_pack — auto-selected.

She clicks Compute snapshot. The service iterates four exposure sources:

Source 1: edgar public company

Callback lands with company facts:

The output_mapping walks JSON pointers → feature values:

source_path feature_code value
/facts/cik edgar cik "0001234567"
/facts/market_cap edgar market cap 18,500,000,000
/facts/total_assets edgar total assets 24,100,000,000
/facts/revenue edgar revenue 9,800,000,000
/facts/officer_count edgar officer count 12
/facts/securities_class_actions_5y edgar securities class actions 5y 1
/facts/latest_10k_filed_at edgar latest 10k filed at "2026-02-14"

Seven Rating Feature Value rows written, all citing the same Exposure Fetch lineage. Audit viewer later shows the raw EDGAR payload behind every value.

Source 2: sec filings list

Same platform (sec_edgar), different contract (edgar filings list). Returns a list of recent filings:

The asset_output_mapping (not feature mapping — these aren't scalar values) produces three document-link assets:

On the workbench, these render as clickable PDF buttons. The UW can click into the 10-K directly from the submission without leaving the workstation.

Source 3: sp capital iq profile

Identifier: {{ insured.ticker | default(insured.cik) }} = "ACME". Platform sp capital iq, contract sp capital iq profile. Returns:

Four more feature values written:

source_path feature_code value
/profile/market_cap ciq market cap 18,500,000,000
/profile/sp_rating ciq sp rating "BBB+"
/profile/analyst_target ciq analyst target 142.50
/profile/ceo_name ciq ceo name "Jane Chen"

Note: edgar market cap and ciq market cap can disagree (EDGAR lags in real-time price; Capital IQ is intraday-fresh). Both are stored separately — UW sees both on the features panel.

Source 4: linkedin officer search

This one never dispatches. source_type = link_only. The link template jinja renders:

A fetch row is written with fetch_status = success and raw_response_json = {"link_url": "...", "provider": "LinkedIn"}. No external call.

The workbench renders it as a clickable LinkedIn-branded button. Click opens in a new tab. The UW can vet the officers' backgrounds without leaving the submission.

Step 3 — The Snapshot

Risk Assessment Snapshot row is written. by source json:

Status: completed. Features produced: 11. Assets produced: 3 filing docs + 1 LinkedIn link.

Step 4 — Run the Quote

Sarah opens the rater workbench at /uw/rater/<submissionGuid>. Picks us public dno v1 2026. Clicks Run quote.

Pricing Model Service.run_quote does three things before dispatching to the builtin engine:

1. Refresh on-quote exposure sources. Any source with refresh_policy = on_quote fires again if stale. edgar public company has stale_after_days = 30 — still fresh, skipped. sp capital iq profile has stale_after_days = 30 — skipped. No extra latency.

2. Auto-populate feature-sourced fields. The pricing model's input_schema.fields list declares which inputs read from the feature store:

The service reads each source: "feature:<code>" field and populates it from Rating Feature Value (insured-scoped, current). requested limit and retention came from the broker's structured submission. Now the inputs dict is complete:

3. Validate + dispatch. Schema validation runs (all fields present, types correct), then the builtin engine renders compute formula jinja:

Output:

Factor stack: 1.185 × 1.15 × 0.97 = 1.322. Subtotal: $18,000 × 1.322 = $23,796. Taxes: $0 for US D&O (no surplus lines here because admitted). Fees: broker commission 15% × $23,796 = $3,569; policy fee $2,500 → $6,069. Final: $29,865.

Step 5 — The UW Sees the Factor Lineage

Every factor in the quote's factors json carries a source key. On the quote summary page, the premium buildup shows:

Step Value Source
Base premium $18,000
× size (market cap band) 1.185 compute_formula
× litigation (5y) 1.15 compute_formula
× rating (S&P) 0.97 compute_formula
Subtotal $23,796
+ taxes $0
+ broker commission (15%) $3,569
+ policy fee $2,500
Final premium $29,865

The UW knows why each factor has the value it does — click on the litigation factor and the lineage popover shows "input: sec_class_actions_5y = 1 (source: feature)". Click the feature, and the feature audit viewer shows "populated from edgar public company exposure fetch on 2026-04-24 09:15, raw EDGAR payload attached."

Zero black box. Zero guessing.

Step 6 — What Happens on a Stale Fetch?

Suppose Sarah opens the same quote 45 days later (new market cap, new analyst targets). She clicks Re-run quote.

refresh_policy = on_quote sources check staleness. sp capital iq profile has stale_after_days = 30 and was fetched 45 days ago → stale. The engine re-fires the exposure source:

  • New dispatch to Capital IQ
  • Callback lands with updated market_cap = 17,200,000,000 (market is off)
  • apply output mapping writes a new Rating Feature Value row with is_current = true; prior row flips to is_current = false

When the quote runs, feature:ciq_market_cap now reads 17.2B. Size factor re-computes to 1.172. Subtotal drops. The summary page's comparison-vs-original shows the delta if this is a re-quote of an overridden version, or simply the new premium if a fresh run.

UWs never have to remember to refresh. Staleness is a declarative property of the source.

Step 7 — What About D&B + LinkedIn on Private D&O?

The us_private_dno_pack profile is the same architecture with different sources:

Private D&O has no EDGAR (private company), no Capital IQ (mostly). What it does have is D&B's firmographics + social data via LinkedIn. Same pattern: D&B dispatch returns employee count + revenue, populates features → rater consumes. LinkedIn is link-only, renders deep-links the UW clicks through.

Point is: same framework, different source pack per LOB. New sector (e.g. financial_institutions_dno)? Ops adds a new profile and assembles the source codes — zero code change.

Step 8 — The Cyber Variant

us_cyber_pack uses:

CyberCube populates 7 cyber-specific features (cyber security rating, cyber known breaches 5y, cyber dark web exposure score, etc.). The cyber rater's input schema reads all of them via source: "feature:cyber_*". Same engine, different provider pack.

The Lineage Panel

For any quote, Sarah can open the risk-assessment workbench and see the full provenance:

Each section expands to show the raw response JSON. Each feature value shows the producing fetch. The entire rating input set has a traceable origin.

Config — Defining a New Source

To add a new external provider (e.g. Moody's ESG score), ops navigates to /uw/admin/exposure-sources:

Adds four Rating Feature Definition rows for the output features. Optionally adds the source to us public dno pack profile. Optionally adds the features to the D&O rater's input schema as new source: "feature:moodys_esg_*" fields.

Zero deploy. Next quote run picks it up.

What This Replaces

Before InsightUW's exposure-source framework, carriers handle public company data one of three ways:

  • Broker fills a questionnaire. 30 fields, manual, error-prone, 2-day turnaround, brokers hate it.
  • UW looks it up manually. Bloomberg terminal, SEC website, S&P portal, three tabs, 20 minutes per account, $200k per UW per year in lost capacity.
  • Automated integration with one provider. Usually Capital IQ or D&B. Hardcoded. Breaks when the provider changes their API. Adding LinkedIn takes 6 months of engineering.

The InsightUW approach: declarative sources, one framework, three-field admin UI to add a new provider. Current state: 11 seeded sources across Edgar, CyberCube, D&B, Capital IQ, FEMA Flood, Google Street View, Munich Re CATNet, LinkedIn, and internal (broker SOV, prior-year pull-forward, broker submission facts). A new carrier LOB = new profile + reuse of existing sources.

Data Model Summary

  • Exposure Source — declarative source spec: source_type, identifier_jinja, platform_code + contract_code (external) OR internal_fetcher_key (internal), output_mapping (JSON-pointer → feature_code), asset_output_mapping (image/map/doc URLs), link_template_jinja (link_only), refresh_policy, entity_scope.
  • Exposure Fetch — per (source, entity) cached fetch with raw_response_json and supersede chain. Links back to a cap #20 Platform Dispatch when external.
  • Rating Feature Definition — feature catalog with output_type, target_range (for RAG zones), refresh_policy.
  • Rating Feature Value — cached value per (entity, feature) with supersede chain + lineage_json citing the producing fetch.
  • Risk Assessment Profile — per-LOB pack: source_codes list.
  • Risk Assessment Snapshot — per (submission, profile) materialized output for the workbench.

All of these ship as seeded catalogs with idempotent startup seeding. Ops extends via admin UIs. UWs experience it as: "I typed the ticker. Premium came back. Everything's filled in. Everything has a source."

Quick Reference

Trigger a fetch manually: POST /api/uw_exposure_source/sources/{guid}/fetch

Refresh all sources for an entity: POST /api/uw_exposure_source/refresh-entity

Compute a risk assessment snapshot: POST /api/uw_risk_assessment/submissions/{guid}/snapshot/{profile_code}/compute

Pull prior-year data for a renewal: POST /api/uw_exposure_source/pull-forward

Workflow effect that fires an exposure source: fetch exposure source with source code in effect config json.

Jinja helpers (workflow rules):


One ticker symbol → 11 rating features + 3 document links + 1 deep-link, all traceable back to a named external source — in the time it takes to click "Run quote." That's the standard the framework sets.

See InsightUW run on your data

A 45-minute working session with a real broker email and your LOBs.

Request a demo