#!/usr/bin/env python3
import csv
import hashlib
import json
import zipfile
from pathlib import Path

root=Path(__file__).resolve().parent
base=root.parent
h=base/'engineering_handoff_local'
d=json.loads((root/'bom_candidate_checks.json').read_text())
with (root/'three_board_candidate_bom_A23.csv').open(encoding='utf-8-sig',newline='') as f:
    rows=list(csv.DictReader(f))
grouped={}
for r in rows:
    if not r['CandidateMPN']:continue
    k=(r['CandidateManufacturer'],r['CandidateMPN'])
    g=grouped.setdefault(k,{'ManufacturerCandidate':k[0],'MPN':k[1],'DrillQtyFor1000':'0','VacuumQtyFor1000':'0','LampQtyFor1000':'0','TotalRFQQty':'0','SupplierQuotedMPN':'','SupplierConfirmedManufacturer':'','UnitPriceUSD':'','LineTotalUSD':'','MOQ':'','PackQty':'','StockQty':'','LeadTimeDays':'','QuoteDate':'','QuoteURLOrReceipt':'','Status':'CANDIDATE_ONLY_NOT_AVL_OR_ORDER'})
    col={'drill':'DrillQtyFor1000','vacuum':'VacuumQtyFor1000','lamp':'LampQtyFor1000'}[r['Product']]
    g[col]=str(int(g[col])+1000*int(r['QtyPerBoard']))
    g['TotalRFQQty']=str(int(g['TotalRFQQty'])+1000*int(r['QtyPerBoard']))
rfq=list(grouped.values())
with (root/'three_board_1000_each_RFQ_A23.csv').open('w',encoding='utf-8-sig',newline='') as f:
    w=csv.DictWriter(f,fieldnames=list(rfq[0]));w.writeheader();w.writerows(rfq)
assert sum(int(r['TotalRFQQty']) for r in rfq)==85000
assert all(not r['UnitPriceUSD'] for r in rfq)
sources=json.loads((root/'manufacturer_source_fetch.json').read_text())
qod={'board_sha256':d['boards']['lamp']['source_pcb_sha256'],
     'refs':{'R13':{'resistance_ohm':100,'footprint':'0603','nets':['/VLED_PRE_SENSE','/LED_QOD']},'U3':{'device':'TPS22992SRXNR','QOD_pin':'5'},'C5_C6':{'net':'/SYS_5V0_ALWAYS_ON','location':'UPSTREAM_NOT_SWITCHED_DOWNSTREAM_CAPACITANCE'}},
     'nominal_voltage_V':5.0,'R_ext_ohm':100,
     'power_formula':'V^2*R_ext/(R_ext+R_qod)^2; capacitive discharge only',
     'examples':[{'R_qod_ohm':0,'basis':'conservative bound ignoring internal resistance','initial_W':0.25},{'R_qod_ohm':25,'basis':'datasheet typical at 25C; not minimum','initial_W':0.16}],
     'ordinary_RC0603_continuous_W_at_70C':0.1,
     'unknowns':['downstream capacitance','actual maximum voltage','repetition','pulse curve and derating','backfeed','temperature'],
     'decision':'Do not freeze ordinary RC0603 solely on nominal/DC data; pulse qualification and physical tests required.',
     'sources':[d['manufacturer_nominal_checks']['RC0603FR-07100RL'],{'url':sources['tps22992']['url'],'sha256':sources['tps22992']['sha256']}],
     'status':'OPEN_QOD_PULSE_POWER_QUALIFICATION'}
for example in qod['examples']:
    actual=5**2*100/(100+example['R_qod_ohm'])**2
    assert abs(actual-example['initial_W'])<1e-9
(root/'lamp_R13_QOD_power_gate_A23.json').write_text(json.dumps(qod,indent=2)+'\n')
receipt={k:{field:value for field,value in v.items() if field!='text_preview'} for k,v in sources.items()}
(root/'manufacturer_source_receipts_A23.json').write_text(json.dumps(receipt,indent=2)+'\n')
report=h/'A23-BOM-review.md'
report.write_text(report.read_text().replace('{{RFQ_GROUPS}}',str(len(rfq))))
include=[report,root/'build_candidate_bom.py',root/'package_A23.py',root/'bom_candidate_checks.json',root/'manufacturer_source_receipts_A23.json',root/'lamp_R13_QOD_power_gate_A23.json']
include+=sorted(root.glob('*_A23.csv'))
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
manifest='\n'.join(sha(p)+'  A23/'+p.relative_to(base).as_posix() for p in include)+'\n'
zpath=h/'BOM-candidates-and-1k-RFQ-A23.zip'
with zipfile.ZipFile(zpath,'w',compression=zipfile.ZIP_DEFLATED,compresslevel=6) as z:
    for p in include:z.write(p,'A23/'+p.relative_to(base).as_posix())
    z.writestr('A23/SHA256SUMS.txt',manifest)
with zipfile.ZipFile(zpath) as z:
    assert z.testzip() is None
    for line in z.read('A23/SHA256SUMS.txt').decode().splitlines():
        wanted,p=line.split('  ',1);assert hashlib.sha256(z.read(p)).hexdigest()==wanted,p
(h/'A23-SHA256SUMS.txt').write_text(manifest)
ip=h/'LOCAL-PACKAGES.json'
idx=json.loads(ip.read_text())
entry={'product':'Three named board candidate BOM and 1k RFQ A23','archive':zpath.name,'sha256':sha(zpath),'bytes':zpath.stat().st_size,'zip_entries':len(include)+1,'manifest_entries':len(include),'zip_crc':'PASS','manifest_readback':'PASS','mpn_coverage':'Drill 31/39; vacuum 33/43; lamp 21/28; candidate identities only','new_candidate_positions':46,'rfq_groups':len(rfq),'new_priced_rows':0,'physical_validation':'NOT_RUN_NO_SAMPLE','manufacturing_status':'FABRICATION_HOLD'}
idx['packages']=[x for x in idx['packages'] if x.get('archive')!=zpath.name]+[entry]
idx['status']='A23_CANDIDATE_MPN_85_OF_110_NO_QUOTE_ALL_FABRICATION_HOLD'
idx['latest_report']=report.name
idx['latest_machine_report']='../bom_candidates_A23/bom_candidate_checks.json'
idx['latest_manifest']='A23-SHA256SUMS.txt'
idx['latest_bom_candidates']={k:'../bom_candidates_A23/'+k+'_candidate_bom_A23.csv' for k in d['boards']}
ip.write_text(json.dumps(idx,ensure_ascii=False,indent=2)+'\n')
print(json.dumps(entry,indent=2))
