from pathlib import Path
import pcbnew as k,wx,csv,json,hashlib,math,re
P=Path(__file__).resolve().parent;app=wx.App(False);quiet=wx.LogNull();b=k.LoadBoard(str(P/'baseline-A90.kicad_pcb'));fps={f.GetReference():f for f in b.GetFootprints()}
r=json.loads((P/'header-primary-review-A91.json').read_text());s={x['MPN']:x for x in json.loads((P/'source-fetch-A91.json').read_text())}
for m in r['parts']:
 assert hashlib.sha256((P/s[m]['file']).read_bytes()).hexdigest()==s[m]['sha256']
 t=(P/'sources'/(m+'.txt')).read_text();assert m in t and 'Rated Current' in t and '20' in t and 'mΩ' in t
 assert re.search(r'Pins\s+'+str(r['parts'][m]['pins'])+r'\s',t)
worst_diag=math.sqrt(2)*(r['square_pin_mm']+r['square_pin_tolerance_mm'])
nom_diag=math.sqrt(2)*r['square_pin_mm'];audit=[]
for ref,m in r['refs'].items():
 f=fps[ref];pads=list(f.Pads());part=r['parts'][m];assert len(pads)==part['pins']
 xs=sorted(set(p.GetPosition().x for p in pads));ys=sorted(set(p.GetPosition().y for p in pads));assert len(xs)==part['rows'] and len(ys)==part['pins']//part['rows']
 for coords in [xs,ys]:
  for a,z in zip(coords,coords[1:]):assert round(k.ToMM(z-a),6)==2.54
 holes=set(k.ToMM(p.GetDrillSize().x) for p in pads);assert holes=={1.0}
 assert nom_diag<1.0<worst_diag
 audit.append({'Ref':ref,'CandidateMPN':m,'pads':len(pads),'pitch_mm':2.54,'native_drill_mm':1.0,'nominal_square_pin_diagonal_mm':nom_diag,'worst_declared_square_pin_diagonal_mm':worst_diag,'nominal_hole_within_manufacturer_recommended_range':True,'worst_case_pin_fit_proven':False,'geometry_status':'HOLE_TOLERANCE_STACK_HOLD','power_application_status':'CURRENT_TEMPERATURE_MATING_HOLD' if ref in ['J2','J3'] else 'HARNESS_POLARITY_MATING_DFM_OPEN'})
rows=list(csv.DictReader((P/'baseline-BOM-A90.csv').open(encoding='utf-8-sig')))
for row in rows:
 if row['Ref'] in r['refs']:
  m=r['refs'][row['Ref']];row.update(Manufacturer='Wurth Elektronik',CandidateMPN=m,SelectionStatus='A91_ENGINEERING_HEADER_NOMINAL_MATCH_HOLE_TOLERANCE_HOLD',SourceURL=s[m]['URL'],SourceSHA256=s[m]['sha256'],Qualification='1.0mm drill not proven for worst declared square-pin size. Finished hole tolerance/mating harness/polarity/DFM/price/stock open; actual current and temperature also open for J2/J3. Engineering carrier candidate only')
assert len(rows)==68 and all(x['CandidateMPN'] for x in rows)
with (P/'motor-A90-candidate-BOM-A91.csv').open('w',encoding='utf-8-sig',newline='') as f:
 w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
groups={}
for row in rows:
 key=(row['CandidateMPN'],row['PCBFootprint'],row['DNP']);g=groups.setdefault(key,{'CandidateMPN':row['CandidateMPN'],'PCBFootprint':row['PCBFootprint'],'DNP':row['DNP'],'Refs':[],'QtyPerBoard':0,'Qty1000BoardsNoAttrition':0,'UnitPriceUSD1000':'','QuoteStatus':'NO_QUOTE','ManufacturingRelease':False});g['Refs'].append(row['Ref']);g['QtyPerBoard']+=int(row['QtyPerBoard']);g['Qty1000BoardsNoAttrition']+=int(row['Qty1000BoardsNoAttrition'])
for g in groups.values():g['Refs']=' '.join(g['Refs'])
with (P/'motor-A90-1000boards-RFQ-review-A91.csv').open('w',encoding='utf-8-sig',newline='') as f:
 w=csv.DictWriter(f,fieldnames=list(next(iter(groups.values()))));w.writeheader();w.writerows(groups.values())
result={'status':'A91_HEADER_CANDIDATES_IDENTIFIED_HOLE_TOLERANCE_HOLD','electrical_BOM_rows':68,'candidate_MPN_covered':68,'DNP_refs':['R3'],'fully_application_qualified':False,'unassigned_MPN_refs':[],'native_header_geometry_audit':audit,'manufacturing_release':False,'motor_run_permission':False,'price_or_stock_verified':False,'PCB_sha256':hashlib.sha256((P/'baseline-A90.kicad_pcb').read_bytes()).hexdigest()}
(P/'header-verification-A91.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result))
