Portfolio roll-up — European-listed economic-statecraft exposure (v1 prototype)
What this is
The first concrete shape of the FI proof artefact named in the 2026-06-01 pivot (§3 Buyer A, §6 Phase 1). A worked basket-level economic-statecraft exposure roll-up over a STOXX-Europe-shaped 28-name proxy, sourced live from the existing dossier corpus + lib/company-risk.ts engine. Issuer rows pulled by running allCompanyRisks() and filtering to a hand-curated set of EU-listed dossier slugs (see methodology, below).
What this artefact is NOT: a productionised dashboard, a real index, an ECB-template compliant report, or a forecast. It is a static shape demo proving the FI roll-up works end-to-end with real numbers from the existing scoring engine, and surfacing what an FI portfolio manager would see if they were to ask for an "economic-statecraft exposure" page in their risk pack.
Methodology (verify-or-don't-file)
- Universe: dossiers in
docs/intelligence/dossiers/*.mdwhoseticker:
resolves to a European exchange (.AS/.BR/.DE/.PA/.MI/.ST/.CO/ .OL/.HE or untickered private-EU). 28 names matched the live filter on 2026-06-05 (out of 336 total dossier corpus).
- Scoring: each issuer row uses the unmodified output of
companyRisk(slug)
in lib/company-risk.ts. Buyer-relative scoring (concentration + policy pressure + import reliance + substitutability + price stress, weighted by production-footprint × geopolitical alignment vs the material's top refiner) is the existing engine — Gate 8 is aggregation, not new scoring.
- Aggregation: equal-weighted basket statistics (basket = 28-name cohort).
Market-cap weighting deferred to chunk-5 (real index constituents).
- Stress scenario: one named, real, on-disk IPTM action used as the
escalation anchor — docs/iptm/actions/2025-10-09-china-mofcom-rare-earths-extraterritorial-export-controls.md (severity 5, action_type export-control). Scenario hand-written; generalised scenario engine deferred to chunk-6.
The roll-up — 28 EU-listed names, ranked by buyer-relative companyScore
| Issuer | Ticker | HQ | Sector | Score | Binding | Controller | Alignment | Breadth | Laws | Named |
|---|---|---|---|---|---|---|---|---|---|---|
| Porsche AG | P911.DE | DE | automotive | 100 | dysprosium:100 | CN | adversarial chokepoint | 16 | 85 | 0 |
| Saab AB | SAAB-B.ST | SE | defence | 100 | dysprosium:100 | CN | adversarial chokepoint | 16 | 77 | 0 |
| Volvo AB | VOLV-B.ST | SE | automotive | 100 | dysprosium:100 | CN | adversarial chokepoint | 16 | 85 | 0 |
| Vestas | VWS.CO | DK | wind-turbine-OEM | 100 | dysprosium:100 | CN | adversarial chokepoint | 12 | 74 | 0 |
| Saint-Gobain | SGO.PA | FR | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 0 |
| Siltronic | WAF.DE | DE | semiconductor | 98 | tungsten:98 | CN | adversarial chokepoint | 12 | 66 | 0 |
| SKF | SKF-B.ST | SE | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 0 |
| SSAB | SSAB-B.ST | SE | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 4 |
| Sulzer | SUN.SW | CH | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 0 |
| Tomra | TOM.OL | NO | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 0 |
| Vaisala | VAIAS.HE | FI | electronics | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 72 | 0 |
| Valmet | VALMT.HE | FI | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 0 |
| VAT Group | VACN.SW | CH | semi-equipment | 98 | tungsten:98 | CN | adversarial chokepoint | 7 | 65 | 0 |
| Voestalpine | VOE.VI | AT | industrial | 98 | tungsten:98 | CN | adversarial chokepoint | 11 | 78 | 3 |
| ASML | ASML.AS | NL | semi-equipment | 98 | tungsten:98 | CN | adversarial chokepoint | 5 | 49 | 0 |
| Solvay | SOLB.BR | BE | essential-chemicals | 96 | dysprosium:100 | CN | adversarial chokepoint | 4 | 40 | 0 |
| Safran | SAF.PA | FR | aerospace | 94 | tungsten:93 | CN | adversarial chokepoint | 10 | 68 | 0 |
| Prysmian | PRY.MI | IT | industrial | 93 | tungsten:91 | CN | adversarial chokepoint | 11 | 78 | 0 |
| Umicore | UMI.BR | BE | specialty-chemicals | 92 | neodymium:90 | CN | adversarial chokepoint | 6 | 57 | 1 |
| Sandvik | SAND.ST | SE | industrial | 91 | neodymium:89 | CN | adversarial chokepoint | 11 | 78 | 0 |
| Siemens | SIE.DE | DE | industrial | 88 | tungsten:85 | CN | neutral exposure | 11 | 78 | 0 |
| AkzoNobel | AKZA.AS | NL | chemicals | 84 | lithium:80 | CN | adversarial chokepoint | 9 | 46 | 0 |
| Syensqo | SYENS.BR | BE | chemicals | 84 | lithium:80 | CN | adversarial chokepoint | 9 | 46 | 0 |
| Imerys | NK.PA | FR | industrial-minerals | 83 | graphite:94 | CN | adversarial chokepoint | 2 | 37 | 0 |
| Rheinmetall | RHM.DE | DE | defence | 82 | neodymium:97 | CN | adversarial chokepoint | 1 | 40 | 0 |
| Wacker | WCH.DE | DE | chemicals | 79 | lithium:74 | CN | adversarial chokepoint | 9 | 46 | 0 |
| Covestro | 1COV.DE | DE | chemicals | 57 | cobalt:66 | CN | neutral exposure | 1 | 20 | 1 |
| Norsk Hydro | NHY.OL | NO | aluminium-VI | 45 | aluminium:51 | CN | adversarial chokepoint | 1 | 14 | 0 |
Columns: Score = companyRisk.companyScore (0–100 buyer-relative). Binding = highest-risk material : its buyer-relative material score. Controller = the material's top-refining country. Alignment = footprint-weighted geopolitical-alignment label vs controller. Breadth = count of distinct scored critical-mineral exposures. Laws = count of restrictive IPTM actions touching this issuer's at-risk materials. Named = count of IPTM actions naming this issuer directly via company_refs.
Basket-level statistics (the FI roll-up shape)
Equal-weighted basket (n = 28):
- Mean
companyScore: 89.4 / 100 — adversarial chokepoint dominance. - Median: 94. 14 of 28 (50%) at score ≥ 98.
- 95th-percentile single-issuer score: 100.
- 5th-percentile single-issuer score: 49.5 (Norsk Hydro, Covestro).
- Mean restrictive-law count per issuer: 63.4 distinct IPTM actions
threaten an average basket constituent.
- Issuers with ≥1 IPTM action naming them directly (
Named> 0): 4 of 28
(SSAB, Voestalpine, Umicore, Covestro).
Binding-material concentration (the controller-mix view):
| Binding material | Issuers | % of basket |
|---|---|---|
| dysprosium | 5 | 18% |
| tungsten | 11 | 39% |
| neodymium | 4 | 14% |
| lithium | 3 | 11% |
| graphite | 1 | 4% |
| cobalt | 1 | 4% |
| aluminium | 1 | 4% |
| (other) | 2 | 7% |
Controller-country mix: 28 of 28 binding-material rows → CN. The basket itself is structurally concentrated against one statecraft controller — this is the FI takeaway, not a bug. Diversification across binding controllers is what a portfolio manager would have to engineer; the current EU-industrials basket does not provide it.
Stress scenario — full escalation of MOFCOM Announcements 61 + 62
Anchor action: 2025-10-09-china-mofcom-rare-earths-extraterritorial-export-controls.md (MOFCOM Announcements No. 61 + 62, severity 5, export-control). The action introduces an extraterritorial perimeter on REE-containing items, with the operative instrument being licensing not prohibition.
Scenario: the licensing regime tightens from "case-by-case approval with humanitarian carve-outs" to "supply suspension in response to a named geopolitical trigger" (the precedent is the 2024-12-03 MOFCOM Ga/Ge/Sb full-ban-on-US escalation that followed BIS HBM controls 24 hours earlier — see case docs/intelligence/cases/2024-china-ga-ge-retaliation-cycle.md).
Exposed basket lines (issuers whose `bindingMaterial ∈ {dysprosium, neodymium}` AND `controller = CN`):
- Direct hit (binding REE): 9 of 28 issuers — Porsche, Saab, Volvo,
Vestas, Solvay, Umicore, Sandvik, Rheinmetall, plus the 4 score-100 rows above. Equal-weighted basket exposure at scenario hit = 32% of names.
- Mean restrictive-law count among hit issuers: 71.6 — these are the
most-cross-targeted names in the basket already.
- Issuers explicitly named in MOFCOM Announcements 61/62 in our action
frontmatter: 0 (the action is rule-based, not entity-listed) — the scenario does not reduce to "is your ticker on the list," which is part of why an FI risk pack needs the bindingMaterial × controller × policy family attribution and not just an entity-list join.
Modelled drawdown: not computed in v1. Productionised chunk-6 wires returns data (Yahoo, which is in our existing pipeline) to compute event- study-style abnormal returns on the 9 hit issuers vs ACWI over T+5 / T+20 trading days. Anchoring sample sizes: this is identical to the engine behind docs/intelligence/audits/event-study-chip-controls.md (Gate 7 claim #1, n=30, p=0.005); the FI scenario is a forward-looking projection of the same event-study mechanism.
What an FI reader can do with this page (the test condition for Gate 8)
A portfolio manager looking at this v1 prototype can:
1. Identify their portfolio's basket-mean economic-statecraft exposure at a single number (89.4 here) and a single attribution column (CN- adversarial-chokepoint dominance). 2. Identify the binding-material distribution — what to hedge if they want to reduce what drives the score (Nd-Pr-Dy magnet sourcing, then tungsten carbide, then lithium-chemicals). 3. Identify the 4 named-action issuers in their basket (SSAB, Voestalpine, Umicore, Covestro) — the highest-resolution near-term policy-targeting signal, where statecraft has already moved from thematic to entity-specific. 4. Run one stress scenario — the MOFCOM REE escalation — and read off the share of basket lines that take a first-order hit (9/28). 5. Trace each issuer's score in two clicks to a primary government source: the dossier → its cited IPTM action(s) → the action's primary-source URL.
This is the shape the pivot's §8 calls "would a portfolio manager say 'I'd want this in my risk pack'." Whether the answer is yes is an external- reader question (FI reader-test kit, future strategy tick).
Honest caveats
1. Score saturation in the heavy industrials cluster. 14 of 28 names land at 98–100. The basket-mean is therefore close to the basket-max — the differentiator between issuers at the FI portfolio level is not companyScore alone but the multi-column attribution (Laws, Named, Binding, Breadth). The chunk-3 aggregation must expose these as first-class outputs, not flatten them into a weighted-mean. 2. Single-controller concentration is structural, not a bug. The basket shows 28/28 binding-controller = CN. For an FI risk-pack reader this IS the headline. For a sensitivity analyst, it means the basket-level bindingControllerHHI would saturate at 1.0 — a stricter alternative would be a footprint-weighted controller-HHI (using all scored exposures' top-refiners, not just the binding one), which adds resolution but is one further removed from the buyer-relative score. 3. The basket is cohort-defined, not market-defined. Chunk-1 uses "EU- listed dossiers we already have" not "STOXX 600 constituents." Chunk-5 replaces this with a real public-index basket. Until then the basket-mean cannot be compared to a real ETF return distribution. 4. No market-cap weighting in v1. A market-cap-weighted version would under-weight defence + materials and over-weight ASML / Siemens / AkzoNobel — the score distribution looks different. Chunk-5 covers this. 5. Companies absent from the dossier corpus are silently absent from the roll-up. If a real FI portfolio holds Roche or Nestlé, their economic- statecraft exposure isn't zero, it's unmeasured. A productionised roll-up should distinguish measured-low from unmeasured in the basket-mean denominator (this is a chunk-3 design point). 6. The stress scenario is illustrative, not parameterised. Chunk-1 names ONE escalation anchor (MOFCOM 61/62). A productionised version parameterises over a small library of named precedents (Ga/Ge 2023, antimony 2024, REE 2025, magnet 2025, plus US-side BIS HBM 2024 / AI- Diffusion 2025) so the FI buyer can stress-test multiple regime escalations from the same input portfolio. 7. `materialActions` (the laws column) is currently issuer-agnostic per material — it counts laws touching the issuer's at-risk materials, not laws that name the issuer. That is the right denominator for breadth of exposure but the wrong one for "are you actively in the crosshairs." The Named column already separates these; chunk-3 must keep them distinct in the basket-level rollup.
What changes for chunks 3-6 because of this v1
The v1's most important finding for the productionised path: basket- level `weighted_avg(companyScore)` is not enough. A clean basket-mean plateaus in the high-90s on any EU-industrials basket and the FI buyer cannot distinguish two such baskets. Chunk-3 (lib/portfolio-exposure.ts) must therefore output:
- basket-mean companyScore (compressed, still useful as a single headline);
- basket-mean restrictive-law count (varies 14–85 here, doesn't saturate);
- binding-material concentration (HHI over the bound material distribution);
- binding-controller concentration (HHI over top-refiners);
- count of named-action issuers (currently a 0–4 column);
- a stress-scenario hit-rate per scenario.
The MSCI-ESG analogy from the pivot doc is now load-bearing: institutional ESG consumers de-emphasised the composite score over time and consumed pillar-level scores and the controversy/event feed instead. Same shape will apply here — the v1 plateau is exactly the same failure mode and the productionised version must front-run it.
Verification
Reproduce the issuer rows by running tsx scripts/probe.ts with this body:
``ts import { allCompanyRisks } from '../lib/company-risk' const EU = new Set([/* 28 slugs above */]) const rows = allCompanyRisks().filter(r => EU.has(r.slug)) .sort((a,b) => b.companyScore - a.companyScore) console.log(rows.map(r => ${r.slug} ${r.companyScore}).join('\n')) ``
(Probe is throwaway; the productionised version lives in lib/portfolio- exposure.ts per project chunk-3.) Numbers above were generated 2026-06-05 against the live 336-dossier corpus + 1,196-action register.