Truflation as a proxy for US CPI¶
Algebris Data Analytics Case Study | Shi Yuchen | Data snapshot 18 October 2026
This walkthrough uses Truflation to inform a calibrated CPI forecast and tests its value against simple baselines. Results use the included source archive. Q4 covers 14 morning/evening CTP-ISW reports from 28 February to 6 March 2026, collected retrospectively in October, with saved DeepSeek extractions and a separate synthetic software example.
Run the notebook from its folder or the project root. It needs the dependencies in
requirements-lock.txt; the offline run needs no credentials or internet connection.
The companion Dash app is started with python -m dashboard.app from the project root.
from pathlib import Path
import sys, json, hashlib, tempfile
import pandas as pd
from IPython.display import display, HTML, Markdown
ROOT = Path.cwd()
if not (ROOT / 'inflation_monitor').exists():
ROOT = ROOT.parent
sys.path.insert(0, str(ROOT))
from inflation_monitor.config import Settings
settings = Settings(data_dir=ROOT / 'data', log_dir=ROOT / 'logs')
print('Project:', ROOT.name)
print('Python:', sys.version.split()[0])
Project: Algebris_Case_Study_Shi_Yuchen Python: 3.12.14
1 Data engineering¶
The pipeline separates API acquisition, immutable raw snapshots, parsing, quality checks and derived tables. Each adapter implements the same source contract. BLS is the primary official CPI source, with FRED as fallback. EIA gasoline is an optional modelling input; Brent and NBER recessions provide context. API keys are read from environment variables, never committed.
python -m inflation_monitor.pipeline run fetches live data. run --offline rebuilds from the
included archive. schedule --time 07:30 --timezone Europe/London invokes the same pipeline
daily while the scheduler process is running. External scheduler examples are in scheduling/.
No permanent scheduler is installed.
The raw Truflation stream values are already percentage YoY rates: applying another 12-month
percentage change to them would be incorrect. Official CPI YoY is 100 * (I_t / I_(t-12) - 1).
Missing months remain missing.
manifest = [json.loads(line) for line in (settings.raw_dir / 'manifest.jsonl').read_text().splitlines() if line.strip()]
for item in manifest:
raw = (settings.raw_dir / item['path']).read_bytes()
assert hashlib.sha256(raw).hexdigest() == item['sha256']
display(pd.DataFrame(manifest)[['source', 'series_id', 'fetched_at', 'bytes', 'sha256']])
print('All raw snapshot hashes verified.')
| source | series_id | fetched_at | bytes | sha256 | |
|---|---|---|---|---|---|
| 0 | truflation | TRUFLATION_US_CPI_YOY | 2026-10-18T10:43:12+00:00 | 356244 | e9f615a2a7c2019f7bfc9fecb8cbe33c002d98b3cc165e... |
| 1 | bls | cpi_2008-2017 | 2026-10-18T10:43:13+00:00 | 106273 | 6052315a770c6d0de58bffffe1ace87561366a9d410feb... |
| 2 | bls | cpi_2018-2026 | 2026-10-18T10:43:13+00:00 | 93723 | 0c5f83190f7ece924daf252525ff4c3c70961893b11080... |
| 3 | eia | GASREGW | 2026-10-18T10:43:26+00:00 | 127488 | 668bee46e7b136d492434654e8723fce18e278761aac68... |
| 4 | eia | DCOILBRENTEU | 2026-10-18T10:43:47+00:00 | 452608 | ae240e004d70e279e1e6a0e5297d1558c168c2279b7752... |
| 5 | nber | USREC | 2026-10-18T10:43:50+00:00 | 91028 | 457028612263f0cff09bf2a3dca379b16b37a7a6f815ab... |
All raw snapshot hashes verified.
from inflation_monitor.pipeline import run_pipeline
result = run_pipeline(offline=True, settings=settings)
assert result.ok, result.error
print('Rebuild:', result.status)
display(pd.DataFrame(list(result.rows.items()), columns=['Output table', 'Rows']))
display(pd.DataFrame(result.issues))
Rebuild: success_with_warnings
| Output table | Rows | |
|---|---|---|
| 0 | truflation_daily | 6134 |
| 1 | cpi_monthly | 225 |
| 2 | comparison_monthly | 202 |
| 3 | recessions | 12 |
| 4 | context_prices | 4576 |
| 5 | analysis_correlations | 5 |
| 6 | analysis_lead_lag | 13 |
| 7 | analysis_rolling_corr | 200 |
| 8 | analysis_divergences | 5 |
| 9 | analysis_gap_decomposition | 202 |
| 10 | nowcast_backtest_18 | 980 |
| 11 | nowcast_backtest_eom | 980 |
| 12 | nowcast_live | 7 |
| 13 | nowcast_horizon_profile | 98 |
| 14 | nowcast_blackout | 24 |
| level | check | message | |
|---|---|---|---|
| 0 | WARN | jump | Truflation YoY: 1 one-step move(s) > 0.5pp in ... |
| 1 | INFO | truflation_fresh | Truflation last print 2026-10-17 (1 day(s) old) |
| 2 | INFO | known_gap | CPI 2025-10 not published by BLS (2025 lapse i... |
| 3 | INFO | next_release | Next CPI release: 2026-10 on 2026-11-10 |
2 Interactive comparison¶
Both traces use the same YoY-percent axis. CPI is displayed by reference month, not its later publication date. This chart describes the series; forecast tests apply release-date cutoffs. Grey bands are NBER recessions. The slider supports zooming. The Dash app adds date presets, daily/7-day/30-day/monthly Truflation views, CPI-measure selection, model evidence, data export and a manual refresh button.
from dashboard.app import DashboardData
from dashboard import figures as F
d = DashboardData.load(settings)
display(HTML(F.comparison_figure(d.comp, d.truf, d.rec, start='2020-01-01',
nowcast=d.nowcast, cpi_col='cpi_yoy_nsa').to_html(full_html=False, include_plotlyjs=True)))
3 Analysis and the next CPI release¶
The calculations below distinguish correlations in levels from correlations in monthly changes. Persistent level gaps can coexist with useful directional information. Gap episodes are descriptive; the shelter comparison is not a causal attribution. Missing months are preserved before differencing.
display(d.correlations)
display(d.divergences)
display(HTML(F.rolling_corr_figure(d.rolling).to_html(full_html=False, include_plotlyjs=False)))
display(HTML(F.gap_decomposition_figure(d.gaps, start='2020-01-01').to_html(full_html=False, include_plotlyjs=False)))
| period | months | change_pairs | corr levels | corr monthly changes | corr levels vs CPI ex-shelter | mean gap (Truflation - CPI), pp | gap std, pp | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2011-2019 | 108 | 107 | 0.926033 | 0.779305 | 0.924331 | 0.078060 | 0.395111 |
| 1 | 2020-2022 | 36 | 35 | 0.978741 | 0.919545 | 0.995152 | 1.264408 | 1.359940 |
| 2 | 2023-2026 | 44 | 42 | 0.856273 | 0.809244 | 0.544367 | -0.852849 | 0.511806 |
| 3 | Live era 2022+ | 56 | 54 | 0.973167 | 0.814180 | 0.958332 | -0.266912 | 1.302770 |
| 4 | Full 2011-2026 | 188 | 186 | 0.942392 | 0.844367 | 0.955951 | 0.087361 | 0.985127 |
| start | end | months | direction | mean_spread_pp | max_abs_spread_pp | |
|---|---|---|---|---|---|---|
| 0 | 2010-07 | 2011-01 | 7 | Truflation above CPI | 1.340250 | 1.606289 |
| 1 | 2011-04 | 2011-06 | 3 | Truflation above CPI | 1.173828 | 1.246644 |
| 2 | 2021-03 | 2022-09 | 19 | Truflation above CPI | 2.422331 | 3.544006 |
| 3 | 2023-04 | 2023-08 | 5 | Truflation below CPI | -1.135778 | 1.313238 |
| 4 | 2026-02 | 2026-09 | 8 | Truflation below CPI | -1.650096 | 2.209950 |
Forecast design and evaluation¶
The target is the NSA headline CPI 12-month rate. Baselines include a no-change forecast and an autoregression. Bridge models anchor on the last published CPI and add the change in Truflation and/or EIA gasoline inflation. Coefficients are fitted with an expanding window. Each fit uses only outcomes released before the simulated forecast cutoff. Source-specific publication lags are applied to high-frequency observations.
This is a pseudo-real-time test using current data vintages and conservative release lags. Truflation history before December 2021 is back-cast; later revisions are also possible. The 2022+ results are shown separately, but that same sample was used to select the preferred model. P-values are exploratory after comparing several models. The preferred model's nominal 80% bands covered about 73% of full-sample outcomes.
display(d.metrics)
display(d.by_period[d.by_period['period'].str.startswith('Live era')])
display(HTML(F.rmse_bar_figure(d.metrics, d.nowcast['preferred_model']).to_html(full_html=False, include_plotlyjs=False)))
display(d.blackout)
display(HTML(F.blackout_figure(d.blackout).to_html(full_html=False, include_plotlyjs=False)))
| model | n | RMSE | MAE | bias | RMSE vs RW | DM stat | DM p-value | direction hit | exact print (0.1pp) | within 0.2pp | 80% interval coverage | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Random walk | 140 | 0.407883 | 0.303491 | 0.026113 | 1.000000 | NaN | NaN | NaN | 0.085714 | 0.421429 | 0.735714 |
| 1 | AR(1) on changes | 140 | 0.361191 | 0.276444 | 0.021312 | 0.885528 | -2.242086 | 2.653978e-02 | 0.685484 | 0.114286 | 0.450000 | 0.785714 |
| 2 | Truflation level (face value) | 140 | 1.115377 | 0.743382 | -0.063398 | 2.734555 | 5.324030 | 3.977579e-07 | 0.693548 | 0.042857 | 0.264286 | 0.664286 |
| 3 | Truflation change, b=1 | 140 | 0.287996 | 0.212717 | 0.021475 | 0.706077 | -3.556508 | 5.143854e-04 | 0.870968 | 0.192857 | 0.564286 | 0.728571 |
| 4 | Truflation bridge (OLS) | 140 | 0.242207 | 0.183108 | 0.010148 | 0.593816 | -4.295090 | 3.258681e-05 | 0.862903 | 0.257143 | 0.657143 | 0.735714 |
| 5 | Gasoline bridge (OLS) | 140 | 0.237755 | 0.168758 | -0.016637 | 0.582901 | -4.321253 | 2.936010e-05 | 0.895161 | 0.228571 | 0.714286 | 0.742857 |
| 6 | Truflation + gasoline (OLS) | 140 | 0.194481 | 0.139458 | -0.008604 | 0.476807 | -4.866814 | 3.035835e-06 | 0.927419 | 0.221429 | 0.785714 | 0.728571 |
| model | period | n | RMSE | MAE | RMSE vs RW | DM p-value | |
|---|---|---|---|---|---|---|---|
| 28 | Random walk | Live era (Truflation published, 2022+) | 56 | 0.409530 | 0.318698 | 1.000000 | NaN |
| 29 | AR(1) on changes | Live era (Truflation published, 2022+) | 56 | 0.393865 | 0.316448 | 0.961749 | 0.628916 |
| 30 | Truflation level (face value) | Live era (Truflation published, 2022+) | 56 | 1.355138 | 1.106546 | 3.309007 | 0.000003 |
| 31 | Truflation change, b=1 | Live era (Truflation published, 2022+) | 56 | 0.323506 | 0.245324 | 0.789945 | 0.123909 |
| 32 | Truflation bridge (OLS) | Live era (Truflation published, 2022+) | 56 | 0.282729 | 0.220942 | 0.690374 | 0.006783 |
| 33 | Gasoline bridge (OLS) | Live era (Truflation published, 2022+) | 56 | 0.270483 | 0.201902 | 0.660472 | 0.001930 |
| 34 | Truflation + gasoline (OLS) | Live era (Truflation published, 2022+) | 56 | 0.232733 | 0.177201 | 0.568292 | 0.000783 |
| blackout_months | model | sample | n | RMSE | MAE | |
|---|---|---|---|---|---|---|
| 0 | 1 | Random walk | Full sample | 140 | 0.391408 | 0.289309 |
| 1 | 1 | Random walk | Live era 2022+ | 56 | 0.407141 | 0.313741 |
| 2 | 1 | Truflation + gasoline (OLS) | Full sample | 140 | 0.182810 | 0.125911 |
| 3 | 1 | Truflation + gasoline (OLS) | Live era 2022+ | 56 | 0.212857 | 0.155095 |
| 4 | 1 | Truflation bridge (OLS) | Full sample | 140 | 0.211256 | 0.164546 |
| 5 | 1 | Truflation bridge (OLS) | Live era 2022+ | 56 | 0.251552 | 0.200329 |
| 6 | 2 | Random walk | Full sample | 140 | 0.671787 | 0.491438 |
| 7 | 2 | Random walk | Live era 2022+ | 56 | 0.667467 | 0.533247 |
| 8 | 2 | Truflation + gasoline (OLS) | Full sample | 140 | 0.303585 | 0.203344 |
| 9 | 2 | Truflation + gasoline (OLS) | Live era 2022+ | 56 | 0.368719 | 0.277029 |
| 10 | 2 | Truflation bridge (OLS) | Full sample | 140 | 0.332161 | 0.256420 |
| 11 | 2 | Truflation bridge (OLS) | Live era 2022+ | 56 | 0.392428 | 0.303209 |
| 12 | 3 | Random walk | Full sample | 140 | 0.888523 | 0.645088 |
| 13 | 3 | Random walk | Live era 2022+ | 56 | 0.872752 | 0.692551 |
| 14 | 3 | Truflation + gasoline (OLS) | Full sample | 140 | 0.393037 | 0.270774 |
| 15 | 3 | Truflation + gasoline (OLS) | Live era 2022+ | 56 | 0.495657 | 0.388104 |
| 16 | 3 | Truflation bridge (OLS) | Full sample | 140 | 0.409218 | 0.315307 |
| 17 | 3 | Truflation bridge (OLS) | Live era 2022+ | 56 | 0.488396 | 0.366215 |
| 18 | 6 | Random walk | Full sample | 140 | 1.382790 | 1.004652 |
| 19 | 6 | Random walk | Live era 2022+ | 56 | 1.488621 | 1.088658 |
| 20 | 6 | Truflation + gasoline (OLS) | Full sample | 140 | 0.556057 | 0.383121 |
| 21 | 6 | Truflation + gasoline (OLS) | Live era 2022+ | 56 | 0.752726 | 0.600143 |
| 22 | 6 | Truflation bridge (OLS) | Full sample | 140 | 0.524550 | 0.407631 |
| 23 | 6 | Truflation bridge (OLS) | Live era 2022+ | 56 | 0.644445 | 0.512523 |
n = d.nowcast
print('Forecast cutoff:', n['asof'], '| target month:', n['target_month'], '| scheduled release:', n['release_date'])
print('Selected model:', n['preferred_model'])
print('Information set:', json.dumps(n['information_set'], indent=2))
summary = pd.DataFrame([{ 'Model': name, 'Implied YoY %': m['yoy'], 'Band lower': m['lo80'],
'Band upper': m['hi80'], 'Conditional SA m/m %': m['print']['mom_sa_pct'] }
for name, m in n['models'].items()])
display(summary)
display(HTML(F.nowcast_models_figure(n).to_html(full_html=False, include_plotlyjs=False)))
Forecast cutoff: 2026-10-18 | target month: 2026-10 | scheduled release: 2026-11-10
Selected model: Truflation + gasoline (OLS)
Information set: {
"target": "2026-10",
"asof": "2026-10-18",
"last_cpi_month": "2026-09",
"steps": 1,
"y_last": 3.737684729064039,
"y_prev": 3.3965478924364856,
"truf_t": 2.8972765625,
"truf_last": 2.3885172,
"truf_days": 16,
"d_truf": 0.5087593625000002,
"momentum": 0.34113683662755356,
"gap_last": 1.3491675290640393,
"gas_t": 40.43115423854942,
"gas_last": 37.72323494535617,
"d_gas": 2.7079192931932496,
"gas_weeks": 2,
"actual": null,
"actual_release": "2026-11-10"
}
| Model | Implied YoY % | Band lower | Band upper | Conditional SA m/m % | |
|---|---|---|---|---|---|
| 0 | Random walk | 3.738 | 3.388 | 4.070 | 0.126 |
| 1 | AR(1) on changes | 3.903 | 3.547 | 4.183 | 0.285 |
| 2 | Truflation level (face value) | 2.897 | 3.086 | 4.516 | -0.685 |
| 3 | Truflation change, b=1 | 4.246 | 3.920 | 4.597 | 0.617 |
| 4 | Truflation bridge (OLS) | 4.090 | 3.818 | 4.385 | 0.467 |
| 5 | Gasoline bridge (OLS) | 3.873 | 3.655 | 4.020 | 0.257 |
| 6 | Truflation + gasoline (OLS) | 4.013 | 3.800 | 4.195 | 0.392 |
Interpretation for the Portfolio Manager¶
The PM memo (reports/PM_commentary.pdf) explains the next-release scenario. BLS lacks the
October 2025 headline CPI base and will not publish the October 2026 12-month comparison.
The implied YoY is counterfactual. The conditional SA m/m translation interpolates the missing
base and carries forward the latest seasonal gap; it has not been independently backtested.
reports/model_audit.md records the calculation and timing assumptions.
4 ISW and LLM system design¶
The design extracts source-supported events that may affect energy supply, transport, insurance, food inputs and inflation expectations. It separates reported claims from economic interpretation. Source and model interfaces, plus separate parsing and storage components, make the system extensible. Pydantic checks the structured fields; the extractor checks each quote against its source paragraph.
See reports/Q4_System_Design.pdf for the complete OOP mapping, schema, operational design,
research references and failure handling. reports/Q4_Live_Experiment.pdf presents the real
DeepSeek experiment, coverage, automated semantic review and costs. OpenRouter remains
an optional adapter tested with mocks. The fixture below is synthetic and is kept separate
from the real article results.
from isw_llm.pipeline import run_demo
with tempfile.TemporaryDirectory(prefix='isw-demo-') as tmp:
demo = run_demo(Path(tmp))
print(json.dumps({k: v for k, v in demo.items() if k != "aggregate_file"}, indent=2, default=str))
print((Path(tmp) / 'extraction.json').read_text())
print((Path(tmp) / 'aggregate.json').read_text())
{
"synthetic": true,
"status": "accepted",
"extraction_key": "dc809af1a9315af924b8c2df1792f2c2802b5d3ecd6a294823ab424e5f58c754",
"event_count": 1,
"notice": "Entirely synthetic offline fixture; no actual ISW report was acquired and no real LLM was called."
}
{
"extraction_key": "dc809af1a9315af924b8c2df1792f2c2802b5d3ecd6a294823ab424e5f58c754",
"article": {
"canonical_url": "https://example.invalid/synthetic-isw-demo",
"title": "SYNTHETIC DEMO: fictional conflict and inflation report",
"publication_date": "2026-03-02",
"retrieved_at": "2026-10-18T13:57:48.291571Z",
"source_kind": "synthetic_fixture",
"raw_sha256": "b6c764dcc2b737a3e46cfd231f6f7b8c9b263847eb61f9b38e23030b082976fb",
"text_sha256": "ae3a70cd13f1f7efd15f863f69b57171e29edd59e3b28c8e0ed595e53d427888",
"parser_version": "html-paragraphs-2-browser-dom",
"paragraphs": [
{
"paragraph_id": "p0001",
"text": "This document is an invented software test fixture. It is not an ISW article and describes no actual event.",
"links": [],
"reference_ids": []
},
{
"paragraph_id": "p0002",
"text": "Fictional Port Aurora's operator said oil loading was temporarily suspended on March 1, 2026.",
"links": [],
"reference_ids": []
},
{
"paragraph_id": "p0003",
"text": "The fictional operator gave no interruption duration, capacity loss or verified volume estimate.",
"links": [],
"reference_ids": []
},
{
"paragraph_id": "p0004",
"text": "A fictional analyst said a prolonged interruption could require longer shipping routes. This is a conditional assessment.",
"links": [],
"reference_ids": []
}
],
"references": {},
"acquisition_metadata": {},
"narrative_character_count": 420,
"reference_character_count": 0,
"body_selector": "article",
"media_links": [],
"parsing_warnings": []
},
"extracted_at": "2026-10-18T13:57:48.298084Z",
"model_id": "synthetic/fake-client-v1",
"prompt_version": "conflict-inflation-3",
"schema_version": "1.0",
"payload": {
"schema_version": "1.0",
"relevant": true,
"summary": "SYNTHETIC DEMO: a fictional port interruption illustrates structured extraction.",
"events": [
{
"event_type": "shipping_disruption",
"claim_status": "attributed_claim",
"event_date": "2026-03-01",
"date_text": "March 1, 2026",
"actors": [
"Fictional Port Aurora operator"
],
"location": "Fictional Port Aurora",
"commodities": [
"oil"
],
"summary": "SYNTHETIC: the fictional port operator reported a temporary loading suspension.",
"quantities": [],
"evidence": [
{
"paragraph_id": "p0002",
"quote": "Fictional Port Aurora's operator said oil loading was temporarily suspended on March 1, 2026."
}
],
"impacts": [
{
"channel": "shipping_freight",
"direction": "up",
"horizon": "weeks",
"materiality": "unknown",
"basis": "analyst_inference",
"rationale": "If an actual interruption persisted, detours could raise freight costs; this fictional example cannot quantify CPI."
}
],
"confidence": "high",
"uncertainties": [
"Entire event is fictional; duration and volumes are unspecified."
]
}
],
"limitations": [
"Synthetic fixture only; no real ISW article or live LLM output."
]
},
"attempts": [
{
"attempt": 1,
"model_id": "synthetic/fake-client-v1",
"validation_version": "exact-quotes-2",
"request_id": "synthetic-call-1",
"usage": {
"synthetic": true
},
"response_sha256": "d89849bc13d1abb9ad3a6ba5912e7e18bc6a4ed110d528b388ece7589384bdb5",
"response_text": "{\"schema_version\": \"1.0\", \"relevant\": true, \"summary\": \"SYNTHETIC DEMO: a fictional port interruption illustrates structured extraction.\", \"events\": [{\"event_type\": \"shipping_disruption\", \"claim_status\": \"attributed_claim\", \"event_date\": \"2026-03-01\", \"date_text\": \"March 1, 2026\", \"actors\": [\"Fictional Port Aurora operator\"], \"location\": \"Fictional Port Aurora\", \"commodities\": [\"oil\"], \"summary\": \"SYNTHETIC: the fictional port operator reported a temporary loading suspension.\", \"quantities\": [], \"evidence\": [{\"paragraph_id\": \"p0002\", \"quote\": \"Fictional Port Aurora's operator said oil loading was temporarily suspended on March 1, 2026.\"}], \"impacts\": [{\"channel\": \"shipping_freight\", \"direction\": \"up\", \"horizon\": \"weeks\", \"materiality\": \"unknown\", \"basis\": \"analyst_inference\", \"rationale\": \"If an actual interruption persisted, detours could raise freight costs; this fictional example cannot quantify CPI.\"}], \"confidence\": \"high\", \"uncertainties\": [\"Entire event is fictional; duration and volumes are unspecified.\"]}], \"limitations\": [\"Synthetic fixture only; no real ISW article or live LLM output.\"]}",
"status": "accepted"
}
],
"human_review_status": "unreviewed"
}
{
"asof": "2026-10-18T13:57:48.310335+00:00",
"include_synthetic": true,
"report_count": 1,
"event_mentions": 1,
"days": {
"2026-03-02": {
"report_count": 1,
"relevant_report_count": 1,
"event_mentions": 1,
"channel_direction_mentions": {
"shipping_freight:up": 1
},
"synthetic_reports": 1
}
},
"events": [
{
"extraction_key": "dc809af1a9315af924b8c2df1792f2c2802b5d3ecd6a294823ab424e5f58c754",
"event_index": 0,
"source_url": "https://example.invalid/synthetic-isw-demo",
"publication_date": "2026-03-02",
"available_at": "2026-10-18T13:57:48.299084+00:00",
"last_observed_at": "2026-10-18T13:57:48.291571+00:00",
"observation_id": "5814abe54740447db8ace6b10246de83",
"observation_time_basis": "fetch",
"first_extracted_at": "2026-10-18T13:57:48.298084+00:00",
"source_kind": "synthetic_fixture",
"event_date": "2026-03-01",
"claim_status": "attributed_claim",
"summary": "SYNTHETIC: the fictional port operator reported a temporary loading suspension.",
"evidence": [
{
"paragraph_id": "p0002",
"quote": "Fictional Port Aurora's operator said oil loading was temporarily suspended on March 1, 2026.",
"paragraph_links": []
}
],
"impacts": [
{
"channel": "shipping_freight",
"direction": "up",
"horizon": "weeks",
"materiality": "unknown",
"basis": "analyst_inference",
"rationale": "If an actual interruption persisted, detours could raise freight costs; this fictional example cannot quantify CPI."
}
],
"human_review_status": "unreviewed"
}
],
"quarantined_attempts": 0,
"failed_source_urls": [],
"limitations": [
"Counts are report mentions, not unique independently verified events.",
"Latest observation of accepted content per URL is used; failed revisions can leave the last successful observation stale.",
"No-report days are unknown coverage, not zero conflict or zero inflation pressure.",
"Publication-date bins are descriptive; use available_at for point-in-time applications.",
"Economic direction is a conditional qualitative assessment, not a CPI forecast."
]
}
import inspect
from isw_llm import schemas
print(inspect.getsource(schemas))
"""Versioned extraction contract. Provenance is supplied by code, never by the LLM."""
from __future__ import annotations
from datetime import date, datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, model_validator
SCHEMA_VERSION = "1.0"
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True, allow_inf_nan=False)
class Evidence(StrictModel):
paragraph_id: str = Field(pattern=r"^p\d{4}$", description="ID in the supplied article.")
quote: str = Field(min_length=8, max_length=500, description="Exact excerpt from that paragraph.")
class Quantity(StrictModel):
"""A source-reported magnitude, not an LLM-calculated economic effect."""
metric: str = Field(min_length=1, max_length=100)
value: float | None = Field(description="Use null when only a qualitative statement is available.")
unit: str = Field(min_length=1, max_length=80, description="Preserve source units; never silently convert.")
qualifier: Literal["exact", "approximate", "at_least", "at_most", "unknown"]
time_period: str | None = Field(max_length=100, description="Source-stated period, else null.")
evidence: Evidence
class InflationImpact(StrictModel):
channel: Literal["energy_supply", "shipping_freight", "food_fertilizer", "insurance_risk",
"expectations_fx", "demand", "other"]
direction: Literal["up", "down", "ambiguous", "none", "unknown"]
horizon: Literal["days", "weeks", "months", "unknown"]
materiality: Literal["low", "medium", "high", "unknown"]
basis: Literal["source_assessment", "analyst_inference"] = Field(
description="Use analyst_inference unless the report itself states the economic assessment.")
rationale: str = Field(min_length=10, max_length=600,
description="Explain conditional transmission, not a numeric CPI forecast.")
class ConflictEvent(StrictModel):
event_type: Literal["energy_asset_damage", "supply_disruption", "shipping_disruption",
"sanctions_policy", "military_escalation", "deescalation", "other"]
claim_status: Literal["reported_observation", "attributed_claim", "source_assessment", "forecast"]
event_date: date | None = Field(description="Exact event day only if supported; otherwise null.")
date_text: str | None = Field(max_length=150, description="Original timing phrase, else null.")
actors: list[str] = Field(max_length=20)
location: str | None = Field(max_length=160)
commodities: list[Literal["oil", "natural_gas", "refined_fuels", "food", "fertilizer", "other"]] = Field(max_length=6)
summary: str = Field(min_length=10, max_length=600)
quantities: list[Quantity] = Field(max_length=10)
evidence: list[Evidence] = Field(min_length=1, max_length=8)
impacts: list[InflationImpact] = Field(max_length=7)
confidence: Literal["low", "medium", "high"] = Field(
description="Confidence in faithful extraction, not probability that the underlying claim is true.")
uncertainties: list[str] = Field(max_length=10)
class ExtractionPayload(StrictModel):
schema_version: Literal["1.0"]
relevant: bool
summary: str = Field(min_length=10, max_length=1000)
events: list[ConflictEvent] = Field(max_length=60)
limitations: list[str] = Field(max_length=20)
@model_validator(mode="after")
def relevance_matches_events(self):
if self.relevant != bool(self.events):
raise ValueError("relevant must be true exactly when extracted events are present")
return self
class Paragraph(StrictModel):
paragraph_id: str = Field(pattern=r"^p\d{4}$")
text: str = Field(min_length=1)
links: list[str]
reference_ids: list[str] = Field(default_factory=list)
class SourceReference(StrictModel):
reference_id: str
label: str
text: str
links: list[str]
alias_of: str | None = None
class Article(StrictModel):
canonical_url: str
title: str
publication_date: date | None
retrieved_at: datetime
source_kind: Literal["isw_http", "local_html", "browser_dom", "synthetic_fixture"]
raw_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
text_sha256: str = Field(pattern=r"^[a-f0-9]{64}$")
parser_version: str
paragraphs: list[Paragraph] = Field(min_length=1)
references: dict[str, SourceReference] = Field(default_factory=dict)
acquisition_metadata: dict = Field(default_factory=dict)
narrative_character_count: int = Field(default=0, ge=0)
reference_character_count: int = Field(default=0, ge=0)
body_selector: str = "unspecified"
media_links: list[str] = Field(default_factory=list)
parsing_warnings: list[str] = Field(default_factory=list)
@model_validator(mode="after")
def aware_retrieval(self):
if self.retrieved_at.utcoffset() is None:
raise ValueError("retrieved_at must include a timezone")
return self
class StoredExtraction(StrictModel):
extraction_key: str
article: Article
extracted_at: datetime
model_id: str
prompt_version: str
schema_version: str
payload: ExtractionPayload
attempts: list[dict]
human_review_status: Literal["unreviewed"] = "unreviewed"
def strict_json_schema() -> dict:
"""Use required-but-nullable fields and closed objects for provider strict mode."""
return ExtractionPayload.model_json_schema()
First-week article coverage¶
The source manifest includes both daily editions for 28 February through 6 March. The official directory omitted the 4 March evening report; its official page was independently found and included. Every narrative paragraph was sent in a paragraph-preserving chunk. Images/maps were not OCRed, and linked external references were not fetched. The DOM exports, hashes, publication dates, actual retrieval times, responses and failed attempts are retained.
The cells below read saved results without model calls. Automated semantic review records exclusions and corrections alongside the original responses. This review used AI, not human annotators; it does not measure precision/recall or verify the underlying conflict claims.
q4 = json.loads((ROOT / 'data/isw_live/analysis_summary.json').read_text(encoding='utf-8'))
display(pd.DataFrame([{
'Articles': q4['article_count'], 'Paragraphs': q4['paragraph_count'],
'Accepted chunks': q4['chunk_count'], 'Missing paragraphs': q4['missing_paragraph_count'],
'Raw event mentions': q4['raw_event_mentions'],
'Mentions after automated review': q4['analysis_event_mentions'],
'Paid requests incl. pilots/repairs': q4['paid_request_count_including_pilots'],
'Conservative cost (CNY)': q4['conservative_cost_cny_including_pilots']
}]))
display(pd.read_csv(ROOT / 'data/isw_live/daily_summary.csv'))
print(q4['cost_note'])
print(q4['interpretation'])
| Articles | Paragraphs | Accepted chunks | Missing paragraphs | Raw event mentions | Mentions after automated review | Paid requests incl. pilots/repairs | Conservative cost (CNY) | |
|---|---|---|---|---|---|---|---|---|
| 0 | 14 | 520 | 71 | 0 | 76 | 59 | 80 | 1.22984 |
| publication_date | articles | paragraphs | chunks | raw_mentions | analysis_mentions | |
|---|---|---|---|---|---|---|
| 0 | 2026-02-28 | 2 | 66 | 8 | 4 | 2 |
| 1 | 2026-03-01 | 2 | 68 | 11 | 11 | 7 |
| 2 | 2026-03-02 | 2 | 82 | 12 | 22 | 18 |
| 3 | 2026-03-03 | 2 | 94 | 11 | 14 | 10 |
| 4 | 2026-03-04 | 2 | 83 | 12 | 14 | 12 |
| 5 | 2026-03-05 | 2 | 78 | 9 | 9 | 8 |
| 6 | 2026-03-06 | 2 | 49 | 8 | 2 | 2 |
Estimated at peak uncached prices: CNY 2/M input and 8/M output tokens. Includes pilots and repairs; actual provider billing may be lower. Counts are report mentions after automated semantic review. Repeated reports can describe one incident. Dates are publication dates; October collection prevents a historical trading backtest. Human verification and extraction-accuracy measurement remain outstanding.
import plotly.express as px
channels = pd.read_csv(ROOT / 'data/isw_live/daily_channels.csv')
fig = px.bar(channels, x='publication_date', y='mentions', color='channel',
hover_data=['direction'], labels={'publication_date':'Report publication date',
'mentions':'Reviewed channel mentions'},
title='Reported transmission channels by publication date')
fig.update_layout(template='plotly_white', legend_title_text='Channel')
display(HTML(fig.to_html(full_html=False, include_plotlyjs=False)))
timeline = json.loads((ROOT / 'reports/q4_timeline.json').read_text(encoding='utf-8'))
for item in timeline['days']:
display(Markdown('**' + item['date'] + '** — ' + item['assessment'] + '\n\n' +
'Sources: ' + ', '.join('['+s['label']+']('+s['url']+')' for s in item['sources'])))
2026-02-28 — Warnings and voluntary avoidance precede evidence of a military blockade. The evening report attributes a 70% decline in Hormuz commercial vessel traffic to MarineTraffic and reports an IRGC warning via an EU official. This is a traffic measure, not a 70% loss of global oil supply; the report did not establish a blockade.
Sources: 2026-02-28 evening
2026-03-01 — Reports move from warnings to attacks on vessels and commercial ports. UKMTO reports incidents, while Iranian officials deny an intention to close Hormuz at that time. The reported Persian Gulf and Hormuz traffic declines refer to different locations/dates; they cannot be spliced into one measured series. Insurance and transit-cost claims remain attributed.
Sources: 2026-03-01 morning, 2026-03-01 evening
2026-03-02 — Reported production interruptions broaden the shipping-risk channel: QatarEnergy LNG suspension, the precautionary Ras Tanura refinery shutdown and Kurdish oil-field stoppages. Ras Tanura's precautionary closure does not establish physical damage. Qatar's LNG share describes exposure; it does not quantify the supply loss.
Sources: 2026-03-02 evening
2026-03-03 — Downstream and storage constraints emerge: QatarEnergy announces stoppages including urea, while the report connects Rumaila's closure to storage filling as tankers struggle to leave. These reports add fertilizer and export/storage channels. US political-risk insurance and possible tanker escorts are proposed mitigations; any reduction in freight or insurance costs remains to be measured.
Sources: 2026-03-03 morning, 2026-03-03 evening
2026-03-04 — Maersk booking suspensions widen logistics restrictions, with specified port exceptions. The report cites an approximately 90% Hormuz vessel-traffic decline and a forecast of at least one month for Qatar to regain normal LNG output. The latter concerns recovery to normal output, not a month of zero production; traffic counts do not measure lost energy volumes.
Sources: 2026-03-04 evening
2026-03-05 — Reported fires at Sarsang and BAPCO add concrete facility incidents, while the Sonangol Namibe case extends merchant-vessel risk into the northern Gulf. Saudi rerouting through Red Sea pipelines and continuing Iranian cargo/oil movements indicate partial substitution and residual flows. Regional export flows therefore continued in part.
Sources: 2026-03-05 evening
2026-03-06 — UKMTO reports an attack on a tug assisting Safeen Prestige; the evening report also describes attacks on oil-related targets around Basra without confirmed physical damage. Maritime and energy risks persist, but fewer extracted mentions on this day cannot establish de-escalation, recovery, or a lower future CPI print.
Sources: 2026-03-06 evening
Inspect an extraction and its review¶
Each review records changes to attribution, dates, quantities or relevance. Counts describe mentions: repeated reports may cover one incident, and absence from a later report does not establish recovery or lower inflation pressure.
reviewed = json.loads((ROOT / 'data/isw_live/reviewed_events.json').read_text(encoding='utf-8'))
example = next(e for e in reviewed if e['analysis_event']['quantities'])
print(json.dumps({k: example[k] for k in ['event_id','title','source_url','retrieved_at',
'raw_event','analysis_event','ai_review','human_review_status']}, indent=2, ensure_ascii=False))
display(pd.read_csv(ROOT / 'data/isw_live/reviewed_events.csv')[
['publication_date','edition','claim_status','summary','disposition']])
{
"event_id": "2026-02-28-iran-update-evening-special-report-february-28-2026-e4dfe81f508d:e002",
"title": "Iran Update Evening Special Report: February 28, 2026",
"source_url": "https://www.criticalthreats.org/analysis/iran-update-evening-special-report-february-28-2026",
"retrieved_at": "2026-10-18T12:17:09.681000Z",
"raw_event": {
"event_type": "shipping_disruption",
"claim_status": "attributed_claim",
"event_date": null,
"date_text": null,
"actors": [
"MarineTraffic",
"United States",
"Israel",
"Iran"
],
"location": "Strait of Hormuz",
"commodities": [
"oil"
],
"summary": "Commercial ship traffic in the Strait of Hormuz dropped by 70 percent in response to the combined US and Israeli strike campaign on Iran, according to ship tracking platform MarineTraffic. The passage notes 20 percent of global oil supply transits the strait.",
"quantities": [
{
"metric": "commercial ship traffic decline in Strait of Hormuz",
"value": 70.0,
"unit": "percent",
"qualifier": "exact",
"time_period": null,
"evidence": {
"paragraph_id": "p0039",
"quote": "Commercial ship traffic dropped by 70 percent in the Strait of Hormuz in response to the United States and Israel’s combined strike campaign on Iran, according to ship tracking platform MarineTraffic."
}
},
{
"metric": "share of global oil supply passing through Strait of Hormuz",
"value": 20.0,
"unit": "percent",
"qualifier": "exact",
"time_period": null,
"evidence": {
"paragraph_id": "p0039",
"quote": "Twenty percent of the global oil supply passes through the strait."
}
}
],
"evidence": [
{
"paragraph_id": "p0039",
"quote": "Commercial ship traffic dropped by 70 percent in the Strait of Hormuz in response to the United States and Israel’s combined strike campaign on Iran, according to ship tracking platform MarineTraffic."
},
{
"paragraph_id": "p0039",
"quote": "Twenty percent of the global oil supply passes through the strait."
}
],
"impacts": [
{
"channel": "shipping_freight",
"direction": "up",
"horizon": "weeks",
"materiality": "high",
"basis": "analyst_inference",
"rationale": "A reported 70 percent drop in commercial transits through a chokepoint carrying about a fifth of global oil supply could raise freight and insurance costs and tighten energy logistics, conditional on the disruption persisting."
},
{
"channel": "energy_supply",
"direction": "up",
"horizon": "weeks",
"materiality": "medium",
"basis": "analyst_inference",
"rationale": "Reduced tanker transits through Hormuz could constrain crude flows and support oil prices if shippers continue avoiding the strait; the passage does not quantify price effects."
}
],
"confidence": "medium",
"uncertainties": [
"Traffic figure is attributed to MarineTraffic via the report, not independently observed.",
"No event date is given for the traffic drop; article publication date is not used as event date.",
"Duration and persistence of the disruption are unknown."
]
},
"analysis_event": {
"event_type": "shipping_disruption",
"claim_status": "attributed_claim",
"event_date": null,
"date_text": null,
"actors": [
"MarineTraffic",
"United States",
"Israel",
"Iran"
],
"location": "Strait of Hormuz",
"commodities": [
"oil"
],
"summary": "Commercial ship traffic in the Strait of Hormuz dropped by 70 percent in response to the combined US and Israeli strike campaign on Iran, according to ship tracking platform MarineTraffic. The passage notes 20 percent of global oil supply transits the strait.",
"quantities": [
{
"metric": "commercial ship traffic decline in Strait of Hormuz",
"value": 70.0,
"unit": "percent",
"qualifier": "exact",
"time_period": null,
"evidence": {
"paragraph_id": "p0039",
"quote": "Commercial ship traffic dropped by 70 percent in the Strait of Hormuz in response to the United States and Israel’s combined strike campaign on Iran, according to ship tracking platform MarineTraffic."
}
},
{
"metric": "share of global oil supply passing through Strait of Hormuz",
"value": 20.0,
"unit": "percent",
"qualifier": "exact",
"time_period": null,
"evidence": {
"paragraph_id": "p0039",
"quote": "Twenty percent of the global oil supply passes through the strait."
}
}
],
"evidence": [
{
"paragraph_id": "p0039",
"quote": "Commercial ship traffic dropped by 70 percent in the Strait of Hormuz in response to the United States and Israel’s combined strike campaign on Iran, according to ship tracking platform MarineTraffic."
},
{
"paragraph_id": "p0039",
"quote": "Twenty percent of the global oil supply passes through the strait."
}
],
"impacts": [
{
"channel": "shipping_freight",
"direction": "up",
"horizon": "weeks",
"materiality": "high",
"basis": "analyst_inference",
"rationale": "A reported 70 percent drop in commercial transits through a chokepoint carrying about a fifth of global oil supply could raise freight and insurance costs and tighten energy logistics, conditional on the disruption persisting."
},
{
"channel": "energy_supply",
"direction": "up",
"horizon": "weeks",
"materiality": "medium",
"basis": "analyst_inference",
"rationale": "Reduced tanker transits through Hormuz could constrain crude flows and support oil prices if shippers continue avoiding the strait; the passage does not quantify price effects."
}
],
"confidence": "medium",
"uncertainties": [
"Traffic figure is attributed to MarineTraffic via the report, not independently observed.",
"No event date is given for the traffic drop; article publication date is not used as event date.",
"Duration and persistence of the disruption are unknown."
]
},
"ai_review": {
"event_id": "2026-02-28-iran-update-evening-special-report-february-28-2026-e4dfe81f508d:e002",
"article_id": "2026-02-28-iran-update-evening-special-report-february-28-2026-e4dfe81f508d",
"event_index": 2,
"raw_event_sha256": "281a5b08f671c22ff44bdb77c525572a72117d21060b0ad291df528f1a1ef31f",
"disposition": "include_with_annotation",
"issues": [
"quantity_context"
],
"annotation": "Faithful MarineTraffic-attributed traffic claim. The 70% figure concerns commercial ship traffic with no baseline/window specified; the 20% oil-supply share is background exposure, not a measured 20% supply loss. No realised price change follows from these numbers.",
"overrides": {},
"source_paragraph_ids": [
"p0039"
],
"checks": {
"relevance": true,
"quotation": true,
"attribution": true,
"event_type": true,
"event_date": true,
"quantities": true,
"full_cited_paragraph_context": true
}
},
"human_review_status": "unreviewed"
}
| publication_date | edition | claim_status | summary | disposition | |
|---|---|---|---|---|---|
| 0 | 2026-02-28 | evening | attributed_claim | Commercial ship traffic in the Strait of Hormu... | include_with_annotation |
| 1 | 2026-02-28 | evening | attributed_claim | An EU official told Reuters on February 28 tha... | include_with_annotation |
| 2 | 2026-03-01 | morning | source_assessment | CTP-ISW assesses that Iran may have started at... | include_with_annotation |
| 3 | 2026-03-01 | morning | attributed_claim | UKMTO reported two vessels struck by unspecifi... | include_with_annotation |
| 4 | 2026-03-01 | morning | attributed_claim | Report attributes to Emirati and other reporti... | include_with_annotation |
| 5 | 2026-03-01 | evening | attributed_claim | Report states Iran reportedly struck Zayed Por... | include_with_annotation |
| 6 | 2026-03-01 | evening | attributed_claim | UKMTO reported on March 1 that a projectile ex... | include_with_annotation |
| 7 | 2026-03-01 | evening | attributed_claim | Commercially available maritime data indicate ... | include_with_annotation |
| 8 | 2026-03-01 | evening | attributed_claim | Iranian Foreign Affairs Minister Abbas Araghch... | include_with_annotation |
| 9 | 2026-03-02 | morning | source_assessment | CTP-ISW assesses that Iran is increasingly tar... | include_with_annotation |
| 10 | 2026-03-02 | morning | attributed_claim | Report states Iran reportedly struck the Jebel... | include_with_annotation |
| 11 | 2026-03-02 | morning | attributed_claim | Report states Iranian drones attempted to stri... | include_with_annotation |
| 12 | 2026-03-02 | morning | attributed_claim | Report states Iran targeted an energy facility... | include_with_annotation |
| 13 | 2026-03-02 | morning | attributed_claim | Report states an Iranian drone struck the Mesa... | include_with_annotation |
| 14 | 2026-03-02 | morning | attributed_claim | Report states Iran struck the Aluminum Bahrain... | include_with_annotation |
| 15 | 2026-03-02 | evening | source_assessment | The report assesses that Iran is attempting to... | include_with_annotation |
| 16 | 2026-03-02 | evening | source_assessment | The report states that Iranian attacks targeti... | include_with_annotation |
| 17 | 2026-03-02 | evening | attributed_claim | Qatar announced it ceased all LNG and associat... | include_with_annotation |
| 18 | 2026-03-02 | evening | attributed_claim | Saudi state-owned Saudi Aramco shut down its R... | include_with_annotation |
| 19 | 2026-03-02 | evening | attributed_claim | Several oil companies in Iraqi Kurdistan repor... | include |
| 20 | 2026-03-02 | evening | attributed_claim | The report states natural gas and oil prices i... | include_with_annotation |
| 21 | 2026-03-02 | evening | attributed_claim | IRGC adviser to former Supreme Leader Ali Kham... | include_with_annotation |
| 22 | 2026-03-02 | evening | attributed_claim | The Iranian Khatam ol Anbia Central Headquarte... | include_with_annotation |
| 23 | 2026-03-02 | evening | attributed_claim | UKMTO reported on March 1 that an unspecified ... | include_with_annotation |
| 24 | 2026-03-02 | evening | attributed_claim | An Iraqi OSINT account claimed that Iranian-ba... | include_with_annotation |
| 25 | 2026-03-02 | evening | source_assessment | CTP assesses that the Houthis would likely dir... | include_with_annotation |
| 26 | 2026-03-02 | evening | source_assessment | CTP assesses the Houthis could leverage ties w... | include_with_annotation |
| 27 | 2026-03-03 | morning | source_assessment | The report assesses that Iranian attacks targe... | include_with_annotation |
| 28 | 2026-03-03 | morning | attributed_claim | An Israeli analyst published imagery of fires ... | include_with_annotation |
| 29 | 2026-03-03 | morning | attributed_claim | Iranian drone fragments reportedly caused a ma... | include_with_annotation |
| 30 | 2026-03-03 | morning | attributed_claim | An Iranian drone reportedly struck the Port of... | include_with_annotation |
| 31 | 2026-03-03 | morning | attributed_claim | Several Iranian drones reportedly struck a fue... | include_with_annotation |
| 32 | 2026-03-03 | morning | attributed_claim | State-owned QatarEnergy announced it halted do... | include_with_annotation |
| 33 | 2026-03-03 | morning | attributed_claim | Iraq announced a halt of oil production at the... | include_with_annotation |
| 34 | 2026-03-03 | evening | attributed_claim | Report states Iran conducted four attacks on c... | include_with_annotation |
| 35 | 2026-03-03 | evening | attributed_claim | Report states traffic through the Strait of Ho... | include_with_annotation |
| 36 | 2026-03-03 | evening | attributed_claim | Report states US President Trump ordered the U... | include_with_annotation |
| 37 | 2026-03-04 | morning | reported_observation | Geolocated footage posted March 4 reportedly s... | include_with_annotation |
| 38 | 2026-03-04 | evening | forecast | CTP-ISW assesses that Iran will continue attem... | include_with_annotation |
| 39 | 2026-03-04 | evening | attributed_claim | Maersk reportedly suspended cargo booking acce... | include_with_annotation |
| 40 | 2026-03-04 | evening | attributed_claim | Vessel traffic through the Strait of Hormuz is... | include_with_annotation |
| 41 | 2026-03-04 | evening | attributed_claim | CTP-ISW reports that Qatar fully shut down gas... | include_with_annotation |
| 42 | 2026-03-04 | evening | attributed_claim | CTP-ISW repeats a report that Iraq previously ... | include_with_annotation |
| 43 | 2026-03-04 | evening | forecast | Companies reportedly warned other Gulf states,... | include_with_annotation |
| 44 | 2026-03-04 | evening | attributed_claim | European natural gas prices have reportedly su... | include_with_annotation |
| 45 | 2026-03-04 | evening | attributed_claim | CTP-ISW reports that some oil and fuel buyers,... | include_with_annotation |
| 46 | 2026-03-04 | evening | forecast | The report assesses that degradation of Iran's... | include_with_annotation |
| 47 | 2026-03-04 | evening | attributed_claim | CTP-ISW relays five UKMTO maritime-security in... | include_with_annotation |
| 48 | 2026-03-04 | evening | attributed_claim | An unspecified senior Iranian naval commander ... | include_with_annotation |
| 49 | 2026-03-05 | morning | source_assessment | CTP-ISW characterizes Iranian targeting since ... | include_with_annotation |
| 50 | 2026-03-05 | evening | attributed_claim | A likely Iranian or Iranian-backed militia dro... | include_with_annotation |
| 51 | 2026-03-05 | evening | attributed_claim | At least two Iranian ballistic missiles struck... | include_with_annotation |
| 52 | 2026-03-05 | evening | attributed_claim | Saudi Arabia has shifted crude exports from St... | include_with_annotation |
| 53 | 2026-03-05 | evening | attributed_claim | The report states that Qatar shut down gas liq... | include_with_annotation |
| 54 | 2026-03-05 | evening | attributed_claim | UKMTO reported on March 4 an explosion on the ... | include_with_annotation |
| 55 | 2026-03-05 | evening | attributed_claim | The IRGC Navy stated that it would target US, ... | include_with_annotation |
| 56 | 2026-03-05 | evening | attributed_claim | Reuters, citing ship-tracking data, reported t... | include_with_annotation |
| 57 | 2026-03-06 | evening | attributed_claim | UKMTO reported on March 6 that unknown project... | include_with_annotation |
| 58 | 2026-03-06 | evening | attributed_claim | The report describes likely militia rocket and... | include_with_annotation |
Reproducibility¶
The code, raw snapshots, SHA-256 manifest, processed tables, release calendar, tested dependency
lock, unit/integration tests and this executed notebook are included. Run python -m pytest
from the project root. reports/validation.json records the actual final validation run.
AI usage and verification describes the tools used, checks performed and
responsibility for the analysis.
Primary references: BLS CPI, BLS API, BLS CPI schedule, Truflation methodology, Truflation 2026 weights, Truflation methodology updates, EIA gasoline, NBER dates. Additional claim-specific sources are in the reports and model audit.