from pathlib import Path
import pcbnew as k,wx,json,hashlib,csv,math
P=Path(__file__).resolve().parent;app=wx.App(False);quiet=wx.LogNull()
r=json.loads((P/'capacitor-primary-review-A89.json').read_text());b=k.LoadBoard(str(P/'A86-PCB-snapshot.kicad_pcb'));fps={f.GetReference():f for f in b.GetFootprints()}
assert hashlib.sha256((P/r['Panasonic']['datasheet_file']).read_bytes()).hexdigest()==r['Panasonic']['datasheet_sha256']
audit=[]
for ref in ['C7','C8']:
 f=fps[ref];assert f.GetValue()=='22u 50V';pads=sorted(f.Pads(),key=lambda p:p.GetNumber());assert len(pads)==2
 size=[k.ToMM(pads[0].GetSize().x),k.ToMM(pads[0].GetSize().y)];centers=k.ToMM(int(math.hypot(pads[1].GetPosition().x-pads[0].GetPosition().x,pads[1].GetPosition().y-pads[0].GetPosition().y)))
 measured={'A_inner_gap':round(centers-size[0],5),'B_pad_length':size[0],'C_pad_width':size[1]}
 mismatch={key:val for key,val in measured.items() if not r['TDK']['recommended_land_mm'][key][0]<=val<=r['TDK']['recommended_land_mm'][key][1]}
 assert mismatch.keys()=={'A_inner_gap','C_pad_width'}
 audit.append({'Ref':ref,'CandidateMPN':r['TDK']['MPN'],'native_pad_dimensions_mm':measured,'outside_recommended_dimensions':mismatch,'status':'LAND_PATTERN_HOLD'})
f=fps['C9'];assert f.GetValue()=='220u 35V';pads=sorted(f.Pads(),key=lambda p:p.GetNumber());pitch=k.ToMM(int(math.hypot(pads[1].GetPosition().x-pads[0].GetPosition().x,pads[1].GetPosition().y-pads[0].GetPosition().y)));drill=k.ToMM(pads[0].GetDrillSize().x)
assert pitch==3.5 and drill==0.8 and str(f.GetFPID().GetLibItemName())=='CP_Radial_D8.0mm_P3.50mm'
assert r['Panasonic']['lead_pitch_mm']==pitch and r['Panasonic']['lead_diameter_mm']<drill
assert {p.GetNumber():p.GetNetname() for p in pads}=={'1':'/VM','2':'/GND'}
audit.append({'Ref':'C9','CandidateMPN':r['Panasonic']['MPN'],'native_pitch_mm':pitch,'native_drill_mm':drill,'polarity_networks':{'1':'/VM','2':'/GND'},'status':'NOMINAL_GEOMETRY_MATCH_THERMAL_RIPPLE_TRANSIENT_HEIGHT_OPEN'})
rows=list(csv.DictReader((P/'A88-BOM-snapshot.csv').open(encoding='utf-8-sig')))
for row in rows:
 ref=row['Ref']
 if ref in ['C7','C8','C9']:
  s=r['Panasonic' if ref=='C9' else 'TDK'];row.update(Manufacturer='Panasonic' if ref=='C9' else 'TDK',CandidateMPN=s['MPN'],SelectionStatus='A89_C9_NOMINAL_GEOMETRY_CHECKED_APPLICATION_OPEN' if ref=='C9' else 'A89_NOMINAL_VALUE_MATCH_LAND_PATTERN_HOLD',SourceURL=s['URL'],SourceSHA256=s.get('datasheet_sha256',''),Qualification='Actual ripple/peak voltage/temperature/height/DFM/price/stock open' if ref=='C9' else 'LAND PATTERN HOLD: current gap1.8mm and width2.7mm outside recommended; DC bias/effective capacitance and ripple/thermal/price/stock open')
with (P/'motor-A86-candidate-BOM-A89.csv').open('w',encoding='utf-8-sig',newline='') as f:
 w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
result={'status':'A89_BULK_CAP_SCREEN_COMPLETE_NOT_FABRICATION_RELEASE','candidate_MPN_covered':sum(bool(x['CandidateMPN']) for x in rows),'open_MPN_refs':[x['Ref'] for x in rows if not x['CandidateMPN']],'explicit_land_HOLD_refs':['C7','C8'],'native_geometry_audit':audit,'actual_measurements':False,'factory_DFM':False,'manufacturing_release':False,'motor_run_permission':False,'PCB_sha256':hashlib.sha256((P/'A86-PCB-snapshot.kicad_pcb').read_bytes()).hexdigest()}
assert result['candidate_MPN_covered']==63 and len(result['open_MPN_refs'])==5
(P/'capacitor-verification-A89.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result))
