import sys,json,re,csv,urllib.request,hashlib,zipfile
from pathlib import Path
ROOT=Path(__file__).resolve().parent;BASE=ROOT.parent;H=BASE/'engineering_handoff_local'
sys.path.insert(0,str(BASE/'parity_metadata_closure_A22'))
from prepare_candidates import forms,sha
from align_and_verify import footprint_rows,netlist
DST=BASE/'mosfet_pin_audit_A25/vacuum_corrected';stem='vacuum_control_evt_a2_pmos_pinfix'
sch=DST/(stem+'.kicad_sch');pcb=DST/(stem+'.kicad_pcb')
sources={
 'U1':('HUSB238_004DD','https://www.hynetek.com/uploadfiles/site/219/news/c16af076-8c40-4e8d-b126-b4f9b83e86ea.pdf',4),
 'U2':('USBLC6-2SC6','https://www.st.com/resource/en/datasheet/usblc6-2.pdf',1),
 'U3':('INA180A1IDBVR','https://www.ti.com/lit/ds/symlink/ina180.pdf',4),
 'U4':('TPS62162DSGR','https://www.ti.com/lit/ds/symlink/tps62162.pdf',4)}
# Independently transcribed manufacturer pin-function tables, not schematic-derived.
expected_funcs={
 'U1':['VIN','D+','D-','CC1','CC2','SDA','SCL','VSET','ISET','GATE','GND'],
 'U2':['I/O1','GND','I/O2','I/O2','VBUS','I/O1'],
 'U3':['OUT','GND','IN+','IN-','VS'],
 'U4':['PGND','VIN','EN','AGND','FB','VOS','SW','PG','EP']}
expected_nets={
 'U1':{str(p):n for p,n in enumerate(['/VBUS_RAW','/USB_DP','/USB_DM','/USB_CC1','/USB_CC2','unconnected-(U1-SDA-Pad6)','unconnected-(U1-SCL-Pad7)','/PD_VSET','/PD_ISET','/PD_GATE','/GND'],1)},
 'U2':{str(p):n for p,n in enumerate(['/USB_DP','/GND','/USB_DM','/USB_DM','/VBUS_RAW','/USB_DP'],1)},
 'U3':{str(p):n for p,n in enumerate(['/ADC_IIN','/GND','/FAN_SW','/V12_SYS','/CTRL_3V3'],1)},
 'U4':{str(p):n for p,n in enumerate(['/GND','/VBUS_SW','/VBUS_SW','/GND','/GND','/CTRL_3V3','/BUCK_SW','unconnected-(U4-PG-Pad8)','/GND'],1)}}
libids={};definitions={}
for _,_,b in forms(sch.read_text(),'symbol'):
 if b.startswith('(symbol "Project:'):
  ident=re.match(r'\(symbol "([^"]+)"',b).group(1);fs={}
  for _,_,p in forms(b,'pin'):
   num=re.search(r'\(number "([^\"]+)"',p);name=re.search(r'\(name "([^\"]*)"',p)
   if num and name:fs[num.group(1)]={'':'OUT','+':'IN+','-':'IN-','V+':'VS','PAD':'EP'}.get(name.group(1),name.group(1))
  definitions[ident]=fs
 elif re.match(r'\(symbol\s+\(lib_id',b):
  ref=re.search(r'\(property "Reference" "([^"]+)"',b).group(1);libids[ref]=re.search(r'\(lib_id "([^"]+)"',b).group(1)
_,pins=netlist(DST/'netlist.xml');board={}
for _,_,b,r,_ in footprint_rows(pcb.read_text()):
 if r not in sources:continue
 board[r]={}
 for _,_,p in forms(b,'pad'):
  num=re.match(r'\(pad "([^\"]*)"',p).group(1);n=re.search(r'\(net "([^\"]*)"',p)
  if num and n:board[r][num]=n.group(1).replace('{slash}','/')
records=[];(ROOT/'sources').mkdir(exist_ok=True)
for ref,(mpn,url,page) in sources.items():
 p=ROOT/'sources'/(ref+'.pdf')
 if not p.exists() and ref!='U2':
  req=urllib.request.Request(url,headers={'User-Agent':'Mozilla/5.0'})
  with urllib.request.urlopen(req,timeout=40) as r:p.write_bytes(r.read())
 if p.exists():assert p.read_bytes().startswith(b'%PDF')
 functions={str(i):f for i,f in enumerate(expected_funcs[ref],1)}
 assert definitions[libids[ref]]==functions,(ref,definitions[libids[ref]],functions)
 for pin,n in expected_nets[ref].items():
  assert pins[(ref,pin)]==n
  if not n.startswith('unconnected-'):assert board[ref][pin]==n
 records.append({'ref':ref,'mpn':mpn,'symbol_id':libids[ref],'manufacturer_functions':functions,'schematic_nets':expected_nets[ref],'symbol_function_match':True,'connected_board_pad_match':True,'source_url':url,'source_sha256':sha(p) if p.exists() else '', 'source_access':'Official web tool PDF text read verified; direct PDF download timed out, no local-byte receipt' if not p.exists() else 'Official web tool text verified and local PDF bytes hashed', 'pin_table_page':page,'application_status':'REJECT_CURRENT_PD_VBUS_CONNECTION' if ref=='U2' else 'PIN_AND_LISTED_NET_MAP_ONLY_NOT_FULL_APPLICATION_QUALIFICATION'})
audit={'scope':'Four vacuum ICs, 31 logical pins; manufacturer pin-function vs symbol and netlist vs physical pad net audit; not a complete board audit','board_sha256':sha(pcb),'schematic_sha256':sha(sch),'records':records,'critical_findings':[{'ref':'U2','net':'/VBUS_RAW','physical_pin':'5','planned_PD_voltage_V':12,'source_leakage_test_voltage_V':5.25,'source_minimum_breakdown_V':6,'decision':'Reject USBLC6-2SC6 in present PD-rail connection; do not apply 12V; change ESD topology and reroute before any powered prototype','reason':'A 6V-class VBUS shunt is connected directly to the planned 12V rail'}],'HUSB238_variant_note':'Manufacturer product-table Request 5V means mismatch action, not fixed-only-5V. R10 10k VSET corresponds to 12V; R11 16.5k ISET to 2.5A. Exact fuse settings and physical adapter contract remain unverified.','replacement_research':{'mpn':'TPD2E2U06DCKR','source_url':'https://www.ti.com/lit/ds/symlink/tpd2e2u06.pdf','status':'RESEARCH_ONLY_NOT_SELECTED_OR_AVL','package':'DCK SC70-3; NOT pin/footprint-compatible with existing SOT23-6','functions':{'1':'IO1','2':'IO2','3':'GND'},'rails':'No VBUS supply connection; 0..5.5V data-line operating envelope; ESD/system clamp behavior and layout require qualification'},'physical_validation':'NOT_RUN_NO_SAMPLE','manufacturing_status':'FABRICATION_HOLD'}
(ROOT/'power_pin_audit.json').write_text(json.dumps(audit,ensure_ascii=False,indent=2)+'\n')
def read(p):
 with p.open(encoding='utf-8-sig',newline='') as f:return list(csv.DictReader(f))
def write(p,rows):
 with p.open('w',encoding='utf-8-sig',newline='') as f:
  w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
rows=read(BASE/'r13_candidate_A24/three_board_candidate_bom_A24.csv')
for r in rows:
 if r['Product']=='vacuum':r['Board']='Vacuum A25 PMOS pinfix; A26 ESD rework HOLD'
 if r['Product']=='vacuum' and r['Ref']=='U2':
  assert r['CandidateMPN']=='USBLC6-2SC6'
  r.update({'CandidateMPN':'','CandidateManufacturer':'','SelectionStatus':'REJECTED_CURRENT_12V_PD_APPLICATION_A26','PrimarySourceURL':sources['U2'][1],'PrimarySourceSHA256':next(x['source_sha256'] for x in records if x['ref']=='U2'),'ChecksPerformed':'Physical pin mapping correct, but pin5 tied to 12V PD input conflicts with 6V-class VBUS shunt. Prior USBLC6-2SC6 identity remains in DesignValue for traceability.','RemainingQualification':'Replace ESD topology, exact package and pin map, reroute, native checks, powered ESD/PD/legacy detection tests. TPD2E2U06DCKR researched only.','UnitPriceUSD1000':'','QuoteStatus':'REJECT_PRIOR_PRICE_ANCHOR_FOR_CURRENT_APPLICATION'})
assert len(rows)==110 and sum(bool(r['CandidateMPN']) for r in rows)==85
write(ROOT/'three_board_candidate_bom_A26.csv',rows)
for k in ['drill','vacuum','lamp']:write(ROOT/(k+'_candidate_bom_A26.csv'),[r for r in rows if r['Product']==k])
rfq=read(BASE/'r13_candidate_A24/three_board_1000_each_RFQ_A24.csv')
rfq=[r for r in rfq if r['MPN']!='USBLC6-2SC6']
assert len(rfq)==37 and sum(int(r['TotalRFQQty']) for r in rfq)==85000
write(ROOT/'three_board_1000_each_RFQ_A26.csv',rfq)
report=H/'A26-power-pin-audit.md'
report.write_text('''# A26 吸尘器电源与 ESD 原厂引脚审核

状态：FABRICATION_HOLD。本轮未改 PCB，A25 的规则检查通过不能作为电路应用正确性的证明。

## 审核结果

独立抄录原厂引脚表并对照当前符号、网表及 PCB 焊盘，覆盖 HUSB238_004DD、USBLC6-2SC6、INA180A1IDBVR、TPS62162DSGR 共 31 个逻辑引脚。引脚功能和已连接焊盘网络相符，NC 单独核对。该范围不包含 MCU、所有离散器件、连接器及完整应用/布局验证。

## 必须修复的 ESD 电压冲突

U2 USBLC6-2SC6 的 5 脚接 /VBUS_RAW。HUSB238 的 R10=10k、R11=16.5k 对应候选申请 12V/2.5A；USBLC6-2 原厂仅给出 VRM=5.25V 漏电规格、VBUS-GND 最小击穿 6V。将此保护支路接在拟议 12V 输入会进入击穿区，不能保留当前连接后加电。没有测得实际故障电流，不编造损坏或实测结论。

已撤销 U2 的候选选料和千台 RFQ 行；历史 BOM 的器件身份保留在 DesignValue 以便追溯。候选 MPN 覆盖从 86/110 调整到 85/110，千台 RFQ 从 38 组调整到 37 组。这表示剔除不合适料，不表示成本下降。旧 U2 价格锚点不适用于后续方案。

拟采用不依赖高压 VBUS 供电的双通道 ESD 保护结构。TI TPD2E2U06DCKR 已作为研究对象核对：SC70-3，1=IO1、2=IO2、3=GND，数据线工作范围 0–5.5V；与原 SOT23-6 不兼容，需新符号、封装、重布线、启动/掉电/传统充电识别及 ESD 验证。它尚未计入候选 BOM，不直接换料，不将典型钳位曲线当保证值。

## 型号与验证边界

HUSB238 型号表中的 Request 5V 是 Mismatch Action，不是只能申请 5V。当前电阻设置为 12V/2.5A；实际内部熔丝选项、适配器 PDO 和协商结果仍需样机验证。TPS62162 的输入范围为 3–17V，12V 仅为候选正常输入；瞬态和故障边界没有实测。

A25 的 MOSFET 引脚修复继续有效，但整块吸尘器板必须保持制造与上电测试禁止放行状态，直至修复 ESD 电路并完成重新审核。三款功能等效、热、EMC 和整机成本尚未确认。

原厂来源：
- https://www.hynetek.com/uploadfiles/site/219/news/c16af076-8c40-4e8d-b126-b4f9b83e86ea.pdf
- https://www.hynetek.com/2421.html
- https://www.st.com/resource/en/datasheet/usblc6-2.pdf
- https://www.ti.com/lit/ds/symlink/ina180.pdf
- https://www.ti.com/lit/ds/symlink/tps62162.pdf
- https://www.ti.com/lit/ds/symlink/tpd2e2u06.pdf
''',encoding='utf-8')
files=sorted([report]+[p for p in ROOT.iterdir() if p.is_file()])
manifest='\n'.join(sha(p)+'  A26/'+p.relative_to(BASE).as_posix() for p in files)+'\n'
zp=H/'Vacuum-power-pin-and-ESD-audit-A26.zip'
with zipfile.ZipFile(zp,'w',zipfile.ZIP_DEFLATED) as z:
 for p in files:z.write(p,'A26/'+p.relative_to(BASE).as_posix())
 z.writestr('A26/SHA256SUMS.txt',manifest)
with zipfile.ZipFile(zp) as z:
 assert z.testzip() is None
 for ln in z.read('A26/SHA256SUMS.txt').decode().splitlines():
  dig,name=ln.split('  ',1);assert hashlib.sha256(z.read(name)).hexdigest()==dig
(H/'A26-SHA256SUMS.txt').write_text(manifest)
idxp=H/'LOCAL-PACKAGES.json';idx=json.loads(idxp.read_text());entry={'archive':zp.name,'sha256':sha(zp),'bytes':zp.stat().st_size,'zip_crc':'PASS','manifest_readback':'PASS','manifest_entries':len(files),'zip_entries':len(files)+1,'physical_validation':'NOT_RUN_NO_SAMPLE','manufacturing_status':'FABRICATION_HOLD','scope':'31 logical IC pins audited; U2 rejected for planned 12V PD rail; no CAD modification'}
idx['packages']=[x for x in idx['packages'] if x.get('archive')!=zp.name]+[entry]
idx['status']='A26_VACUUM_ESD_12V_CONFLICT_MPN_85_OF_110_ALL_FABRICATION_HOLD'
idx['latest_report']=report.name;idx['latest_machine_report']='../power_pin_audit_A26/power_pin_audit.json';idx['latest_manifest']='A26-SHA256SUMS.txt'
idx['latest_bom_candidates']={k:'../power_pin_audit_A26/'+k+'_candidate_bom_A26.csv' for k in ['drill','vacuum','lamp']}
idx['vacuum_ESD_application_gate']={'ref':'U2','status':'REJECT_CURRENT_CONNECTION_REWORK_REQUIRED','reason':'USBLC6-2SC6 pin5 connected to planned 12V PD VBUS; 6V-class shunt','powered_test_release':False,'prior_U2_price_anchor_applicable':False}
idxp.write_text(json.dumps(idx,ensure_ascii=False,indent=2)+'\n');print(json.dumps(entry,indent=2))
