"""Reproduce FinanceFirst report v1.1 arithmetic using Python 3 only.

From a clean extraction of research-data-kit.zip, run:
    python reproduce.py
Inputs are under data/raw; checked outputs are under data/derived.
Writes verification/results.json. No network access or third-party packages.
Hypothetical scenarios are not an insurance settlement calculator.
"""
from pathlib import Path
from decimal import Decimal, ROUND_HALF_UP
import csv,json
ROOT=Path(__file__).resolve().parent
checks=[]
def check(condition,label):
    if not condition: raise AssertionError(label)
    checks.append(label)
def rows(relative):
    with (ROOT/relative).open(encoding='utf-8',newline='') as f:return list(csv.DictReader(f))
def display(value):return str(Decimal(str(value)).quantize(Decimal('0.1'),rounding=ROUND_HALF_UP))
def payment(eligible,limit,deductible):
    if any(v<0 for v in (eligible,limit,deductible)):raise ValueError('Inputs must be nonnegative')
    return min(limit,max(eligible-deductible,0))
def main():
    table=rows('data/raw/gao-table1.csv');check(len(table)==4,'all four source table rows')
    columns=['inside_sfha','outside_sfha','unmapped','total']
    for row in table:
        check(sum(int(row[c]) for c in columns[:3])==int(row['total']),'source row total '+row['risk'])
    for c in columns:check(sum(int(r[c]) for r in table[:3])==int(table[3][c]),'source column total '+c)
    h=next(r for r in table if r['risk']=='high');total=int(h['total'])
    check(total==16029433,'high-risk denominator 16029433 properties')
    expected_counts={'inside_sfha':2990193,'outside_sfha':12797264,'unmapped':241976}
    expected_display={'inside_sfha':'18.7','outside_sfha':'79.8','unmapped':'1.5'}
    location=rows('data/derived/high-risk-location.csv');check(len(location)==3,'three mutually exclusive location outputs')
    shares={}
    for c,n in expected_counts.items():
        check(int(h[c])==n,'published high-risk count '+c)
        share=Decimal(n)/Decimal(total)*100;shares[c]=str(share)
        check(display(share)==expected_display[c],'published one-decimal share '+c)
        saved=next(r for r in location if r['category']==c)
        check(int(saved['properties'])==n and int(saved['denominator'])==total,'CSV count and denominator '+c)
        check(abs(Decimal(saved['share_pct'])-share)<Decimal('0.00000001'),'full-precision CSV share '+c)
    combined=expected_counts['outside_sfha']+expected_counts['unmapped']
    combined_share=Decimal(combined)/Decimal(total)*100
    check(combined==13039240,'published combined count 13039240')
    check(display(combined_share)=='81.3','published combined share 81.3 percent')
    check(sum(expected_counts.values())==total,'location categories partition denominator')
    check(sum(Decimal(v) for v in expected_display.values())==100,'displayed category shares sum to 100.0')
    inputs=rows('data/raw/hypothetical-inputs.csv');saved=rows('data/derived/household-scenarios.csv')
    check(len(inputs)==len(saved)==6,'six hypothetical inputs and outputs')
    expected=[(0,0,106000,106000),(78000,0,106000,28000),(78000,18000,106000,10000),(75000,15000,106000,16000),(78000,10000,106000,18000),(250000,18000,326000,58000)]
    scenarios=[]
    for i,(row,target) in enumerate(zip(inputs,expected)):
        n={k:int(v) for k,v in row.items() if k!='scenario'}
        check(all(v>=0 for v in n.values()),'nonnegative hypothetical inputs '+str(i+1))
        check(n['building_eligible']<=n['building_replacement_loss'] and n['contents_eligible']<=n['contents_replacement_loss'],'eligible valuation within replacement loss '+str(i+1))
        bp=payment(n['building_eligible'],n['building_limit'],n['building_deductible'])
        cp=payment(n['contents_eligible'],n['contents_limit'],n['contents_deductible'])
        cost=n['building_replacement_loss']+n['contents_replacement_loss']+n['living_cost'];remaining=cost-bp-cp
        check((bp,cp,cost,remaining)==target,'independent expected scenario '+str(i+1))
        output={'building_payment':bp,'contents_payment':cp,'total_cost':cost,'total_payment':bp+cp,'remaining_cost':remaining}
        check(saved[i]['scenario']==row['scenario'] and all(int(saved[i][k])==v for k,v in output.items()),'saved CSV output '+str(i+1))
        check(all(saved[i][k]==row[k] for k in row),'saved CSV raw inputs '+str(i+1))
        scenarios.append({'scenario':row['scenario'],**output})
    check(payment(1000,250000,2000)==0,'loss below deductible floor')
    check(payment(0,250000,2000)==0,'zero loss')
    check(payment(300000,250000,2000)==250000,'binding coverage cap')
    check(payment(80000,0,0)==0,'zero limit means no modeled coverage')
    try: payment(-1,250000,2000)
    except ValueError:check(True,'negative-input rejection')
    else:raise AssertionError('negative-input rejection')
    result={'status':'PASS','report_version':'1.1','checks_passed':len(checks),'combined_properties':combined,'combined_share_pct':str(combined_share),'combined_display_pct':display(combined_share),'location_share_pct':shares,'location_display_pct':expected_display,'scenarios':scenarios,'checks':checks,'limits':'Arithmetic verification only. Does not validate GAO proprietary risk estimates or determine an actual insurance claim.'}
    (ROOT/'verification').mkdir(exist_ok=True)
    (ROOT/'verification/results.json').write_bytes((json.dumps(result,indent=2)+'\n').encode('utf-8'))
    print(json.dumps({k:result[k] for k in ['status','report_version','checks_passed','combined_properties','combined_display_pct']},indent=2))
    for r in scenarios:print(r['scenario']+': remaining USD '+format(r['remaining_cost'],','))
if __name__=='__main__':main()
