"""Portable report arithmetic from county aggregates; standard library only.
Run from either extracted report bundle: python3 -I reproduce.py
The source workbooks are identified, not redistributed or parsed by this script.
"""
import csv,hashlib,json,pathlib,math
R=pathlib.Path(__file__).resolve().parent

def calculate(rows,kind):
    groups={}
    for r in rows:
        key=(r['release'],int(r['year']))
        groups.setdefault(key,{})
        if r['county'] in groups[key]:raise ValueError('Duplicate county/release/year')
        if r['status']=='no_matching_row':
            if r['records']!='' or r['base_possession']!='':raise ValueError('Missing must remain blank')
        elif r['status']=='selected_row_present':
            if not r['records'].isdigit() or not r['base_possession'].isdigit():raise ValueError('Invalid observed count')
            if int(r['base_possession'])>int(r['records']):raise ValueError('Subset exceeds total')
        else:raise ValueError('Unknown status')
        groups[key][r['county']]=r
    if any(len(g)!=254 for g in groups.values()):raise ValueError('Expected all 254 county statuses')
    def observed(key):return {c:r for c,r in groups[key].items() if r['status']=='selected_row_present'}
    def total(g,field='records'):return sum(int(r[field]) for r in g.values())
    if kind=='geography':
        if set(groups)!={('dps',y) for y in [2023,2024,2025]}:raise ValueError('Unexpected geography periods')
        annual=[];decomposition=[]
        for y in [2023,2024,2025]:
            g=observed(('dps',y));n=total(g);pop=sum(int(r['population']) for r in g.values());h=int(g['Harris']['records'])
            annual.append({'year':y,'records':n,'matching_counties':len(g),'population':pop,'rate_per_100000':n/pop*100000,'harris':h,'other_matched_counties':n-h,'base_possession':total(g,'base_possession')})
        a=observed(('dps',2024));b=observed(('dps',2025));assert set(a)==set(b)
        changes=[{'county':c,'records2024':int(a[c]['records']),'records2025':int(b[c]['records']),'change':int(b[c]['records'])-int(a[c]['records'])} for c in a]
        for prev,nxt in zip(annual,annual[1:]):
            hd=nxt['harris']-prev['harris'];od=nxt['other_matched_counties']-prev['other_matched_counties'];net=nxt['records']-prev['records'];assert hd+od==net
            decomposition.append({'period':str(prev['year'])+'–'+str(nxt['year']),'harris_change':hd,'other_249_change':od,'state_change':net})
        top=[];count=0;ordered=sorted(b,key=lambda c:(-int(b[c]['records']),c.casefold()));state=annual[-1]['records']
        for c in ordered:
            top.append(c);count+=int(b[c]['records'])
            if count*2>=state:break
        pop=sum(int(b[c]['population']) for c in top);texas_pop=sum(int(r['population']) for r in groups[('dps',2025)].values())
        return {'kind':kind,'annual':annual,'decomposition':decomposition,'county_changes':sorted(changes,key=lambda r:(-r['change'],r['county'].casefold())),'change_distribution':{'increased':sum(r['change']>0 for r in changes),'decreased':sum(r['change']<0 for r in changes),'unchanged':sum(r['change']==0 for r in changes),'gains':sum(r['change'] for r in changes if r['change']>0),'losses':sum(r['change'] for r in changes if r['change']<0)},'concentration':{'counties':top,'group_records':count,'total':state,'record_share':count/state*100,'group_population':pop,'texas_population':texas_pop,'population_share':pop/texas_pop*100},'missing':sorted(set(groups[('dps',2025)])-set(b)),'observed_zero_2025':sorted(c for c,r in b.items() if int(r['records'])==0)}
    if kind!='revisions':raise ValueError('Unknown report kind')
    old='harris:dps-2022-2024';new='dps'
    if set(groups)!={(release,y) for release in [old,new] for y in [2023,2024]}:raise ValueError('Unexpected revision periods')
    summary=[];details=[]
    for y in [2023,2024]:
        a=observed((old,y));b=observed((new,y));common=set(a)&set(b)
        for metric in ['records','base_possession']:
            differences=[{'year':y,'county':c,'metric':metric,'older':int(a[c][metric]),'newer':int(b[c][metric]),'change':int(b[c][metric])-int(a[c][metric])} for c in sorted(common)]
            details.extend(differences);sums=lambda which:sum(r[which] for r in differences)
            summary.append({'year':y,'metric':metric,'older_full':total(a,metric),'newer_full':total(b,metric),'common_counties':len(common),'older_common':sums('older'),'newer_common':sums('newer'),'changed_counties':sum(r['change']!=0 for r in differences),'increases':sum(r['change'] for r in differences if r['change']>0),'decreases':sum(r['change'] for r in differences if r['change']<0),'net_change':sums('change'),'older_only':sorted(set(a)-set(b)),'newer_only':sorted(set(b)-set(a))})
    sign=lambda x:(x>0)-(x<0);flips=[]
    common=set(observed((old,2023)))&set(observed((old,2024)))&set(observed((new,2023)))&set(observed((new,2024)))
    for c in sorted(common):
        od=int(groups[(old,2024)][c]['records'])-int(groups[(old,2023)][c]['records']);nd=int(groups[(new,2024)][c]['records'])-int(groups[(new,2023)][c]['records'])
        if sign(od)!=sign(nd):flips.append({'county':c,'older_2023':int(groups[(old,2023)][c]['records']),'older_2024':int(groups[(old,2024)][c]['records']),'newer_2023':int(groups[(new,2023)][c]['records']),'newer_2024':int(groups[(new,2024)][c]['records']),'older_change':od,'newer_change':nd,'strict_sign_reversal':od*nd<0})
    return {'kind':kind,'summary':summary,'county_revisions':details,'trend_labels_changed':flips,'strict_sign_reversals':sum(r['strict_sign_reversal'] for r in flips)}

def main():
    manifest=json.loads((R/'manifest.json').read_text())
    for name,want in manifest['files'].items():
        p=pathlib.PurePosixPath(name)
        if p.is_absolute() or '..' in p.parts:raise ValueError('Unsafe manifest path')
        if hashlib.sha256((R/name).read_bytes()).hexdigest()!=want:raise ValueError('Checksum mismatch: '+name)
    with (R/'county-inputs.csv').open(newline='') as f:
        reader=csv.DictReader(f)
        if reader.fieldnames!=['release','county','year','status','records','base_possession','population']:raise ValueError('Unexpected fields')
        result=calculate(list(reader),manifest['kind'])
    if result!=json.loads((R/'results.json').read_text()):raise ValueError('Recomputed report differs')
    print(json.dumps({'status':'reproduced','kind':manifest['kind'],'inputRows':manifest['inputRows'],'boundary':'Arithmetic from packaged aggregate inputs; not upstream workbook extraction or proof of reporting completeness.'},indent=2))
if __name__=='__main__':main()
