"""Offline synthetic practice checks. No external packages or network."""
import csv,json
from decimal import Decimal as D
from collections import Counter,defaultdict
from pathlib import Path
p=Path(__file__).parent
rows={k:list(csv.DictReader((p/f'ledger-{k.lower()}.csv').open())) for k in ['A','B']}
checks=0
def check(x):
 global checks
 assert x
 checks+=1
for k,rs in rows.items():
 check(len(rs)==(10 if k=='A' else 11));check(len({r['row_id'] for r in rs})==len(rs))
 check(all(set(r)=={'row_id','reference','scenario_date','account','unit','amount'} for r in rs))
 check(all(r['scenario_date']=='2026-11-03' and r['account']=='PRACTICE' and r['unit']=='FC' for r in rs))
 check(all(D(r['amount'])*100==int(D(r['amount'])*100) for r in rs))
 check(sum(D(r['amount']) for r in rs)==D('1050.74' if k=='A' else '1272.74'))
 check(sum(int(D(r['amount'])*100) for r in rs)==(105074 if k=='A' else 127274))
 check(len({r['reference'] for r in rs})==10)
g={k:defaultdict(list) for k in rows}
for k,rs in rows.items():
 for r in rs:g[k][r['reference']].append(r)
refs=sorted(set(g['A'])|set(g['B']));check(len(refs)==11)
matched=[];exceptions=[]
for ref in refs:
 a=g['A'][ref];b=g['B'][ref]
 exact=len(a)==len(b)==1 and all(a[0][c]==b[0][c] for c in ['reference','scenario_date','account','unit','amount'])
 (matched if exact else exceptions).append(ref)
check(matched==['R101','R102','R106','R107','R108','R109','R110'])
check(exceptions==['R103','R104','R105','R111'])
check(sum(D(g['A'][r][0]['amount']) for r in matched)==D('570.49'))
bridge={r:sum((D(x['amount']) for x in g['B'][r]),D(0))-sum((D(x['amount']) for x in g['A'][r]),D(0)) for r in exceptions}
check(bridge==dict(zip(exceptions,map(D,['240','-60','2','40']))))
check(sum(bridge.values())==D('222'))
check(sum(len(g['A'][r]) for r in exceptions)==3);check(sum(len(g['B'][r]) for r in exceptions)==4)
check(sum(D(r['amount']) for r in rows['B'] if r['row_id']!='B04')==D('1032.74'))
check(D('1032.74')-D('1050.74')==D('-18'))
check(sum(D(r['amount']) for r in rows['B'][:-1])==D('1232.74'))
# Wrong range and sign changes must fail expected raw totals, even when the net difference survives.
check(sum(D(r['amount']) for r in rows['A'] if D(r['amount'])>=0)!=D('1050.74'))
check(sum(abs(D(r['amount'])) for r in rows['A'])==D('1100.74'))
check(sum(int(D(r['amount'])*100) for r in rows['B'])-sum(int(D(r['amount'])*100) for r in rows['A'])==22200)
print(json.dumps({'status':'passed','assertions':checks,'raw_a':'1050.74','raw_b':'1272.74','b_minus_a':'222.00','clean_pairs':len(matched),'exception_groups':len(exceptions)},indent=2))
