#!/usr/bin/env python3
"""Reproduce the published warehouse-fire calculations from verified printed inputs.

Python 3.10+, standard library only. This script does not query a live incident system
or re-verify a source. It preserves the values printed in NFPA's February 2026 Table 1.
Run: python reproduce_warehouse_fire_data.py [output_directory]
"""
from __future__ import annotations
import csv
import json
from datetime import date
from pathlib import Path
import sys

VERIFIED = '2026-09-11'
NFPA_TABLES = 'https://content.nfpa.org/-/media/Project/Storefront/Catalog/Files/Research/NFPA-Research/Building-and-life-safety/oswarehousetables.pdf?hash=EA17F8268ED44E027238DE51DFA529EB&rev=6718cdc007944fa295124afe2f03e115'
NFPA_REPORT = 'https://content.nfpa.org/-/media/Project/Storefront/Catalog/Files/Research/NFPA-Research/Building-and-life-safety/oswarehouse.pdf?rev=082bc2bc4eca42c09414cd8b8fb80ded'
IDHS = 'https://www.in.gov/dhs/fire-and-building-safety/community-risk-reduction/'
FW2023 = 'https://www.cityoffortwayne.in.gov/Archive.aspx?ADID=220'
FW2024 = 'https://www.cityoffortwayne.in.gov/Archive.aspx?ADID=222'
IDHS2026 = 'https://www.in.gov/dhs/files/IDHS_FINAL_HB-1427-Report_AM-Edits.pdf'
EIA = 'https://www.eia.gov/consumption/commercial/data/2018/bc/pdf/b11.pdf'
NFFF = 'https://www.firehero.org/2025/02/27/plainfield-mega-warehouse-fire-report/'
RAW = '''1980 4700 60 229
1981 3800 84 289
1982 3500 58 188
1983 2800 100 315
1984 2900 52 157
1985 3100 148 431
1986 2600 48 137
1987 2500 68 188
1988 2300 213 565
1989 1900 51 129
1990 1900 95 228
1991 1800 46 106
1992 1500 36 81
1993 1400 57 124
1994 1500 62 131
1995 1300 81 167
1996 1400 155 310
1997 1300 49 96
1998 1200 40 77
1999 1359 433 815
2000 1537 165 300
2001 1471 140 249
2002 1382 135 236
2003 1390 157 267
2004 1384 138 229
2005 1262 128 205
2006 1356 76 118
2007 1399 142 215
2008 1404 311 453
2009 1138 152 222
2010 1168 123 177
2011 1223 212 296
2012 1297 162 221
2013 1246 126 170
2014 1300 112 149
2015 1506 162 214
2016 1339 271 354
2017 1559 84 108
2018 1357 168 210
2019 1627 219 268
2020 1385 671 813
2021 1486 347 402
2022 1685 211 226
2023 1473 156 161
2024 1689 184 184'''

def save_csv(path: Path, rows: list[dict]) -> None:
    if not rows:
        raise ValueError('Refusing to write an empty dataset')
    fields = list(dict.fromkeys(k for row in rows for k in row))
    with path.open('w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=fields)
        writer.writeheader()
        writer.writerows(rows)

def build(out: Path) -> dict:
    out.mkdir(parents=True, exist_ok=True)
    annual = []
    for line in RAW.splitlines():
        year, fires, nominal, real = map(int, line.split())
        annual.append(dict(year=year, geography='United States', estimated_fires=fires,
            nominal_direct_loss_usd_millions=nominal, direct_loss_2024_usd_millions=real,
            nominal_loss_per_estimated_fire_usd=round(nominal*1_000_000/fires, 6),
            nominal_loss_per_fire_formula='nominal_direct_loss_usd_millions * 1000000 / estimated_fires',
            measure_type='NFPA national estimates; per-fire ratio is secondary analysis',
            source_organization='National Fire Protection Association', source_url=NFPA_TABLES,
            source_edition='February 2026', source_locator='Table 1, printed pages 2-3',
            source_verified_date=VERIFIED,
            note='Published source inputs are rounded estimates; per-fire ratio is not a median, insurance payout, or building probability.'))
    assert [r['year'] for r in annual] == list(range(1980,2025))
    current = [r for r in annual if r['year'] >= 2020]
    total_fires = sum(r['estimated_fires'] for r in current)
    total_loss = sum(r['nominal_direct_loss_usd_millions'] for r in current)
    total_real = sum(r['direct_loss_2024_usd_millions'] for r in current)
    assert (total_fires, total_loss, total_real) == (7718,1569,1786)
    concentration=[]
    for r in current:
        concentration.append({**r,
            'analysis_period':'2020-2024',
            'share_of_period_fires_percent':round(100*r['estimated_fires']/total_fires,8),
            'share_of_period_nominal_loss_percent':round(100*r['nominal_direct_loss_usd_millions']/total_loss,8),
            'share_of_period_2024_dollar_loss_percent':round(100*r['direct_loss_2024_usd_millions']/total_real,8),
            'period_fire_denominator':total_fires,
            'period_nominal_loss_denominator_usd_millions':total_loss,
            'period_2024_dollar_loss_denominator_usd_millions':total_real,
            'share_formula':'100 * annual_value / sum_of_same_measure_2020_2024'})
    causes=[]
    for label,count,share in [('Unintentional',453,29),('Cause under investigation',295,19),
        ('Cause not reported',269,17),('Cause undetermined after investigation',224,14),
        ('Failure of equipment or heat source',150,10),('Other cause',32,2),
        ('Intentional',109,7),('Act of nature',11,1)]:
        causes.append(dict(category=label, period='2020-2024', geography='United States',
            annual_estimated_fires=count, nfpa_published_fire_share_percent=share,
            source_organization='National Fire Protection Association', source_url=NFPA_TABLES,
            source_edition='February 2026',source_locator='Table 7, printed page 7',source_verified_date=VERIFIED,
            note='Counts and percentages are independently rounded; category counts sum to 1543 while the printed total is 1544.'))
    patterns=[]
    for field,category,fires,loss,locator in [
        ('Time of day','Midnight to 6 a.m.',18,40,'Report Figure 5, printed page 4'),
        ('Time of day','6 a.m. to noon',25,16,'Report Figure 5, printed page 4'),
        ('Time of day','Noon to 6 p.m.',30,16,'Report Figure 5, printed page 4'),
        ('Time of day','6 p.m. to midnight',26,28,'Report Figure 5, printed page 4'),
        ('Structure status','Occupied and operating',71,92,'Supporting Table 2, printed page 3'),
        ('Structure status','Vacant and unsecured',15,2,'Supporting Table 2, printed page 3'),
        ('Structure status','Vacant and secured',6,3,'Supporting Table 2, printed page 3'),
        ('Area of origin','Storage areas',37,69,'Supporting Table 10, printed page 12')]:
        patterns.append(dict(statistical_field=field,category=category,period='2020-2024',
            geography='United States',nfpa_published_fire_share_percent=fires,
            nfpa_published_nominal_loss_share_percent=loss,
            loss_share_divided_by_fire_share=round(loss/fires,8),
            formula='nfpa_published_nominal_loss_share_percent / nfpa_published_fire_share_percent',
            source_organization='National Fire Protection Association',
            source_url=NFPA_REPORT if field=='Time of day' else NFPA_TABLES,
            source_edition='February 2026',source_locator=locator,source_verified_date=VERIFIED,
            note='Original descriptive index using rounded shares; 1 means equal shares. Not building risk or causal effect. Fields and selected subsets must not be pooled.'))
    regional=[]
    for year,count in [(2021,59),(2022,76),(2023,71),(2024,74)]:
        regional.append(dict(geography='Indiana',reporting_period=str(year),metric='IDHS commercial fire-investigation cases',
            value=count,unit='cases',source_organization='Indiana Department of Homeland Security',
            source_url=IDHS,source_locator='Fire Investigations table, Commercial row',source_verified_date=VERIFIED,
            status='Verified published administrative count',qualification='Not all commercial fires; not a warehouse-only count.'))
    for year,count,url in [(2023,300,FW2023),(2024,272,FW2024)]:
        regional.append(dict(geography='Fort Wayne Fire Department reporting scope',reporting_period=str(year),metric='Structure Fire category',
            value=count,unit='reported category count',source_organization='Fort Wayne Fire Department',source_url=url,
            source_locator='Annual report, FIRE Types chart, PDF page 7',source_verified_date=VERIFIED,
            status='Verified published administrative count',qualification='Not warehouse-only and not a combined Allen County total.'))
    regional.append(dict(geography='Indiana participating fire-service units',reporting_period='2025-07-01/2025-12-31',
        metric='Runs consisting of a fire',value=17090,unit='runs',source_organization='Indiana Department of Homeland Security',
        source_url=IDHS2026,source_locator='March 2026 report, printed page 1',source_verified_date=VERIFIED,
        status='Verified published administrative count',qualification='615 local units; 73.4% of known eligible entities. Not warehouse-only; not full-year.'))
    for geography in ['Indiana','Allen County']:
        regional.append(dict(geography=geography,reporting_period='',metric='Warehouse-only annual incident total',
            value='',unit='incidents',source_organization='',source_url='',source_locator='',source_verified_date=VERIFIED,
            status='Not established in this compilation',qualification='Missing value is not zero; no statewide/county warehouse extraction was performed.'))
    metrics={
        'period':'2020-2024','estimated_fires_sum':total_fires,'nominal_direct_loss_usd_millions_sum':total_loss,
        'direct_loss_2024_usd_millions_sum':total_real,
        'annual_fires_mean_from_printed_values':total_fires/5,
        'annual_nominal_loss_mean_usd_millions_from_printed_values':total_loss/5,
        'calendar_days':(date(2025,1,1)-date(2020,1,1)).days,
        'estimated_fires_per_calendar_day':total_fires/(date(2025,1,1)-date(2020,1,1)).days,
        'period_nominal_loss_per_estimated_fire_usd':total_loss*1_000_000/total_fires,
        'nominal_loss_per_estimated_fire_2021_2024_usd':898_000_000/6333,
        'annual_nominal_loss_mean_2021_2024_usd_millions':898/4,
        'fire_count_change_1980_2024_percent':100*(1689/4700-1),
        'fire_count_change_2009_2024_percent':100*(1689/1138-1),
        'fire_count_change_2023_2024_percent':100*(1689/1473-1),
        'unknown_status_annual_estimated_fires_sum':295+269+224,
        'unknown_status_share_of_published_total_percent':100*(295+269+224)/1544,
        'unknown_status_formula':'100 * (295 + 269 + 224) / 1544',
        'source_verified_date':VERIFIED}
    assert round(metrics['unknown_status_share_of_published_total_percent'],1)==51.0
    assert round(concentration[0]['share_of_period_nominal_loss_percent'],1)==42.8
    assert round(concentration[0]['share_of_period_fires_percent'],1)==17.9
    save_csv(out/'warehouse-fire-annual-1980-2024.csv', annual)
    save_csv(out/'warehouse-fire-loss-concentration-2020-2024.csv', concentration)
    save_csv(out/'warehouse-fire-cause-status-2020-2024.csv', causes)
    save_csv(out/'warehouse-fire-loss-share-indices-2020-2024.csv', patterns)
    save_csv(out/'warehouse-fire-indiana-source-audit.csv', regional)
    result={'dataset_name':'Warehouse Fire Loss-Concentration and Source-Scope Dataset',
        'version':VERIFIED,'creator':'Fort Wayne Dock Door Repair Research',
        'provenance_note':'Secondary compilation and original calculations, not newly measured fire incidents. Source rechecking is separate from running this script.',
        'annual_records':annual,'loss_concentration':concentration,'cause_status':causes,
        'loss_share_indices':patterns,'regional_source_audit':regional,'derived_metrics':metrics}
    (out/'warehouse-fire-statistics-2026-09-11.json').write_text(json.dumps(result,indent=2,ensure_ascii=False)+'\n',encoding='utf-8')
    print(json.dumps(metrics,indent=2))
    return result

if __name__=='__main__':
    build(Path(sys.argv[1]) if len(sys.argv)>1 else Path(__file__).resolve().parent)
