from pathlib import Path
import sys,re,json,csv,hashlib,xml.etree.ElementTree as E,shutil
R=Path(__file__).resolve().parent;B=R.parent;D=R/'power_board';N='lamp_buck_lmr51450_a4_frontend';P=D/(N+'.kicad_pcb')
sys.path.insert(0,str(R/'verification_support'));from prepare_candidates import forms,run;from align_and_verify import netlist,footprint_rows
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
s=P.read_text();j=json.loads((D/'drc.json').read_text());assert not j['violations'] and not j['unconnected_items']
assert len(j['schematic_parity'])==4 and all(v['type']=='missing_footprint' and 'DNP' in v['description'] for v in j['schematic_parity'])
e=json.loads((D/'erc.json').read_text());assert not [v for sh in e['sheets'] for v in sh.get('violations',[])],e
fps,pins=netlist(D/'netlist.xml');seen={}
for _,_,f,ref,_ in footprint_rows(s):
 for _,_,pad in forms(f,'pad'):
  pin=re.match(r'\(pad "([^"]+)"',pad)[1];n=re.search(r'\(net "([^"]+)"',pad)
  if n:seen[(ref,pin)]=n[1].replace('{slash}','/')
assert all(seen[k]==n for k,n in pins.items() if k[0] not in ['C9','C10','D1','R5'])
assert ('C14','1') in seen and seen['C14','1']=='/CTRL_3V3' and seen['C14','2']=='/GND'
old=R/'comparison_only/power_A24.kicad_pcb';oldtext=old.read_text()
def canonical(f,tag):
 if tag=='footprint':
  props=[]
  for x,y,p in reversed(list(forms(f,'property'))):
   if p.startswith('(property "MPN" "ERJPA3F1000V"'):
    p=re.sub(r'\(thickness 0.15\)', '',p)
   props.append(p);f=f[:x]+f[y:]
  f+=''.join(sorted(props))
  f=f.replace('(roundrect_rratio 0.3076923077)','(roundrect_rratio 0.3076923076923)')
 return re.sub(r'\s+',' ',f)
def records(text,tag):return {re.search(r'\(uuid "([^\"]+)"',f)[1]:canonical(f,tag) for _,_,f in forms(text,tag)}
changes={}
for tag in ['footprint','segment','via']:
 a=records(oldtext,tag);b=records(s,tag);assert not a.keys()-b.keys();assert all(a[k]==b[k] for k in a)
 changes[tag]=len(b.keys()-a.keys())
assert changes=={'footprint':1,'segment':4,'via':4},changes
safety=R/'comparison_only/safety_A39_netlist.xml';sf,sp=netlist(safety)
assert sp['R10','1']=='/CTRL_3V3' and sp['U1','4']=='/CTRL_3V3' and sp['R10','2']=='/NTC_ADC' and sp['J4','4']=='/NTC_ADC'
rows=[]
for pin in range(1,6):
 assert pins['J2',str(pin)]==sp['J3',str(pin)],(pin,pins['J2',str(pin)],sp['J3',str(pin)])
 rows.append([pin,pins['J2',str(pin)],'power J2.'+str(pin),'safety J3.'+str(pin),'CANDIDATE_WIRE_PAIR_NOT_ASSEMBLED'])
with (R/'cross_board_5pin_mapping.csv').open('w',newline='') as f:
 w=csv.writer(f);w.writerow(['pin','net','power_connector','safety_connector','physical_status']);w.writerows(rows)
assert pins['U2','5']=='/CTRL_3V3' and pins['U2','2']=='/GND' and pins['U2','3']=='/VLED_PRE_SENSE' and pins['U2','4']=='/VLED_GUARDED'
assert pins['R6','1']=='/VLED_PRE_SENSE' and pins['R6','2']=='/VLED_GUARDED'
assert pins['R7','1']=='/VLED_GUARDED' and pins['R7','2']==pins['R8','1'] and pins['R8','2']=='/GND'
assert pins['U2','1']==pins['R10','1'] and pins['R10','2']==pins['J2','5']
assert sp['R14','1']==sp['J3','5'] and sp['R14','2']==sp['U1','13']
assert sp['R13','1']==sp['J3','4'] and sp['R13','2']==sp['U1','12']
for x in ['gerbers','drill']:(D/x).mkdir(exist_ok=True)
for args,log in [(['pcb','export','gerbers','--layers','F.Cu,B.Cu,F.Paste,B.Paste,F.Silkscreen,B.Silkscreen,F.Mask,B.Mask,Edge.Cuts','--output',str(D/'gerbers')+'/',str(P)],'gerber.log'),(['pcb','export','drill','--format','excellon','--excellon-separate-th','--output',str(D/'drill')+'/',str(P)],'drill.log'),(['pcb','export','pos','--format','csv','--units','mm','--side','front','--smd-only','--exclude-dnp','--output',str(D/'centroid-front.csv'),str(P)],'centroid.log')]:assert run(args,D/log)==0
root=E.parse(D/'netlist.xml').getroot();values={c.get('ref'):c.findtext('value') for c in root.findall('./components/comp')}
assert values['R7'].startswith('100k') and values['R8'].startswith('100k') and values['R6'].startswith('10mR') and values['U2'].startswith('INA180A1IDBVR')
ratio_low=99000/(101000+99000);ratio_high=101000/(99000+101000)
calc={'voltage_nominal_ratio':0.5,'resistor_1pct_ratio_low':ratio_low,'resistor_1pct_ratio_high':ratio_high,'LED5V_scenario_ADC_high_V':5*ratio_high,'LED5p5V_scenario_ADC_high_V':5.5*ratio_high,'current_nominal_V_per_A':0.01*20,'5A_scenario_amplifier_output_V':1.0,'5A_scenario_shunt_power_W':0.25,'5A_scenario_shunt_drop_V':0.05,'gain_accuracy_scope':'TI INA180 gain-error guarantee Vout0.5 to VS-0.5; nominal current below2.5A outside that particular guarantee with10mR/gain20. Output swing/nonlinearity/offset still apply. No low-current accuracy claim.','datasheet_12V_common_mode_5V_supply_25C_offset_max_uV':500,'offset_only_equivalent_current_mA_at10mR_under_datasheet_conditions':50,'voltage_network':'power100k/100k divider; series1k C11 100n followed by safety1k C4 100n; two-stage loaded RC, not single RC','voltage_slowest_pole_ms_nominal':10.15024629943666,'conditional_inputs':'5/5.5V and5A are arithmetic scenarios, not established whole-board rating. VDDA/minrail/sequencing/input protection and thermal/transient limits unqualified.'}
# Compute actual nominal divider+two-cap ladder poles; Rth50k, R9+R13=2k.
import math
Rth=50000;series1=1000;series2=1000;cap=100e-9
# Node1 power monitor after R9: to source viaRth+R9; node2 aftersafetyR13.
a=1/((Rth+series1)*cap)+1/(series2*cap);d=1/(series2*cap);off=-1/(series2*cap)
lambda_low=(a+d-math.sqrt((a-d)**2+4*off**2))/2
calc['voltage_slowest_pole_ms_nominal']=1000/lambda_low
(R/'frontend_static_arithmetic.json').write_text(json.dumps(calc,indent=2)+'\n')
checks={'status':'A43_LAMP_CROSS_BOARD_FRONTEND_AUDITED_DECOUPLING_NATIVE_PASS_FABRICATION_HOLD','ERC':0,'DRC':0,'unconnected_items':0,'schematic_parity_DNP_warnings':4,'preserved_DNP_refs':['C9','C10','D1','R5'],'PCB_sha256':sha(P),'schematic_sha256':sha(D/(N+'.kicad_sch')),'original_footprints_and_copper_unchanged':True,'native_serialization_only':'R13 roundrect_rratio 0.3076923076923 normalized to0.3076923077; property order normalized and hidden MPN default font thickness0.15 added; only these metadata differences allowed','added_records':changes,'cross_board_connector_pairs_checked':5,'voltage_current_ADC_paths_checked':True,'physical_validation':False,'firmware_calibrated_safety_bound':False,'latest_lamp_firmware':'A42','NTC_exact_MPN':'UNSELECTED','outputs_enabled':False,'physical_verified_requirements':0,'reference_requirements':20}
(R/'checks.json').write_text(json.dumps(checks,indent=2)+'\n')
print(json.dumps(checks,indent=2));print('voltage ladder slow pole ms',calc['voltage_slowest_pole_ms_nominal'])

