"""Read-only mapping coverage and omission tests for the pinned synthetic history.
Usage: python historical_mapping_audit.py SOURCE_MONTHLY_CLOSE --output result.json
Get source from panes-office-workflows commit 0c4f1841e21528ec2fd1210602f361a0d198f1a5.
No imports from its generator; no Office execution or file modification.
"""
import argparse,csv,hashlib,json,platform,time
from collections import Counter,defaultdict
from decimal import Decimal
from pathlib import Path
PIN='0c4f1841e21528ec2fd1210602f361a0d198f1a5'
def rows(path):
 with path.open(newline='',encoding='utf-8-sig') as f:return list(csv.DictReader(f))
def cents(value):return int(Decimal(value)*100)
def require_mapped(data, mapping):
 missing=sorted({r['GLAccount'] for r in data if r['GLAccount'] not in mapping})
 if missing:raise ValueError('Unmapped accounts: '+','.join(missing))
def audit(root):
 start=time.perf_counter(); drive=root/'Alder Mechanical - Finance'; cp=drive/'00 Reference/Reporting COA.csv'; coa_rows=rows(cp)
 accounts=[r['Account'] for r in coa_rows]
 if len(accounts)!=len(set(accounts)):raise ValueError('Duplicate account mapping')
 coa={r['Account']:r for r in coa_rows}; inventory=[]; periods=[]; account_periods=defaultdict(Counter);dept_periods=defaultdict(Counter);total_rows=0;unknown=[];all_gl=[];raw_gl=[]
 for path in sorted(drive.glob('FY20??/[0-1][0-9] */01 ERP exports/GL detail*.csv')):
  period=path.stem[-7:]; data=rows(path); require_mapped(data,coa); raw_gl.extend(data); periods.append(period); total_rows+=len(data); inventory.append({'path':str(path.relative_to(root)),'sha256':hashlib.sha256(path.read_bytes()).hexdigest(),'rows':len(data)})
  for r in data:
   account=r['GLAccount'];account_periods[account][period]+=1;dept_periods[r['Department']][period]+=1
   if account not in coa:unknown.append({'period':period,'account':account});continue
   if coa[account]['Type']=='PL' and r['Source'] not in ('Year close','Opening'):
    value=cents(r['Debit'])-cents(r['Credit']);all_gl.append((period,account,r['Department'],value))
 if unknown:raise ValueError('Unmapped source account')
 mutations=[]
 for account in ('6205','5100','6265'):
  affected=[r for r in all_gl if r[1]==account]; amount=sum(r[3] for r in affected)
  reduced=dict(coa);del reduced[account]
  try:require_mapped(raw_gl,reduced);outcome='ACCEPT'
  except ValueError:outcome='REJECT'
  mutations.append({'removed_account':account,'line':coa[account]['Reporting line'],'affected_rows':len(affected),'unmapped_row_guard':outcome,'omitted_debit_less_credit_usd':str(Decimal(amount)/100),'first_period':min(r[0] for r in affected),'last_period':max(r[0] for r in affected)})
 profiles={a:{'first_period':min(ps),'last_period':max(ps),'rows':sum(ps.values()),'reporting_line':coa[a]['Reporting line']} for a,ps in sorted(account_periods.items())}
 departments={d:{'first_period':min(ps),'last_period':max(ps),'rows':sum(ps.values())} for d,ps in sorted(dept_periods.items())}
 assert len(periods)==38 and periods==sorted(set(periods))
 assert all(m['unmapped_row_guard']=='REJECT' for m in mutations)
 return dict(experiment='historical-mapping-coverage',source_commit=PIN,source_is_synthetic=True,execution='Local Python read-only CSV analysis, not a new native Office run',python=platform.python_version(),periods=len(periods),first_period=min(periods),last_period=max(periods),gl_rows=total_rows,reference_accounts=len(coa),observed_accounts=len(account_periods),unknown_accounts=unknown,account_profiles=profiles,department_profiles=departments,omission_tests=mutations,source_files=[{'path':str(cp.relative_to(root)),'sha256':hashlib.sha256(cp.read_bytes()).hexdigest(),'rows':len(coa_rows)}]+inventory,analysis_seconds=time.perf_counter()-start,per_task_usd=None,result='PASS')
if __name__=='__main__':
 p=argparse.ArgumentParser(description=__doc__);p.add_argument('source',type=Path);p.add_argument('--output',type=Path,required=True);a=p.parse_args();result=audit(a.source);a.output.write_text(json.dumps(result,indent=2)+'\n');print(json.dumps({k:v for k,v in result.items() if k not in ('source_files','account_profiles','department_profiles')},indent=2))
