from pathlib import Path
import json,hashlib,zipfile,shutil
R=Path(__file__).resolve().parent;B=R.parent;H=B/'engineering_handoff_local';sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest();j=json.loads((R/'checks.json').read_text())
for k in ['ERC','DRC','unconnected_items','schematic_parity']:assert j[k]==0
assert sha(R/'board/lamp_safety_evt_a3_IR_PCB.kicad_pcb')==j['PCB_sha256']
shutil.copy2(B/'lamp_IR_hardware_A38/IR_interface_contract.json',R/'IR_interface_contract.json')
(R/'run_native_checks.sh').write_text('''#!/bin/sh
set -eu
HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
CLI=${KICAD_CLI:-/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli}
P="$HERE/board/lamp_safety_evt_a3_IR_PCB"
mkdir -p "$HERE/recheck"
"$CLI" sch erc --severity-all --format json --output "$HERE/recheck/erc.json" "$P.kicad_sch"
"$CLI" pcb drc --severity-all --all-track-errors --schematic-parity --refill-zones --format json --output "$HERE/recheck/drc.json" "$P.kicad_pcb"
python3 - "$HERE/recheck" <<'CHECK'
import json,sys
from pathlib import Path
r=Path(sys.argv[1]);e=json.loads((r/'erc.json').read_text());d=json.loads((r/'drc.json').read_text())
assert not [v for sh in e['sheets'] for v in sh.get('violations',[])]
assert not d['violations'] and not d['unconnected_items'] and not d['schematic_parity']
print('PASS: native ERC/DRC/connectivity/parity; no hardware qualification')
CHECK
''')
report=f'''# 灯具红外接口 PCB 配对候选 A39

状态：可编辑原理图与PCB已配对，原生ERC/DRC/未连线/parity全部0；仅工程评审，FABRICATION_HOLD。整机20项功能效果验收仍0项实测。

## 完成范围与证据

A38新增IR原理图已建立配对PCB `board/lamp_safety_evt_a3_IR_PCB.*`，新增J6三针3.3V域主动高推挽输入接口、R20串联1k1%、R21节点下拉47k1%、C8对地10n，连接 U1物理脚3 PC15。38个器件、129个网表脚位逐项与PCB焊盘网名核对一致；原有34个器件的位置、焊盘、参数均保持，仅U1.3由NC改成IR_HAND。

为了给MCU红外出线留出过孔位置，移除并重新绕线了一段WDOG_OK和一段THERM_OK，连通关系不变；其他全部原有走线/过孔记录逐项核验未变。新铜皮含{j['added_copper_counts']['segment']}段线和{j['added_copper_counts']['via']}个过孔，包括这两段旧网的替代路径及新接口连接。候选新过孔采用0.5mm孔盘/0.3mm钻孔，环宽0.1mm需要工厂DFM确认；不能把原生DRC作为工厂工艺承诺。C8放在MCU附近区域，通过较短分支接MCU侧IR节点；尚无信号/噪声实测。

KiCad10在最终区域重填充并保存后复测：ERC0、DRC0、未连线0、原理图—PCB一致性0。检查使用项目检查配置，具体ignored_checks见JSON；没有通过忽略新问题实现通过。修复了候选过程中发现的孔间距/重复孔问题。最终板SHA256：`{j['PCB_sha256']}`。

已从最终板导出9层Gerber、PTH/NPTH钻孔、32个正面SMD贴片坐标（6个通孔连接器不在该SMD文件中）、38项候选BOM。导出文件只用于CAM/报价工程评审，不是制造放行。原理图与PCB采用同一版本，不要混用旧A38 PC15仍NC的元数据PCB。全板BOM精确MPN和价格仍未锁定，不能用此表报价整机总成本。

## 剩余门槛

A37仍为最新目标固件，物理红外仍未绑定，PC15的GPIO/RTC/LSE初始化与输入消抖尚待实现。该固件仍关断LED，不能表现原产品的自动感应点亮效果。接口是候选3.3V主动高推挽协议；具体红外传感器、光学位置、供电负载、线缆/ESD、环境光/肤色/手套等条件尚未选定或验证。不能直接接5V或主动低/开漏模块。

本板为PCA9956B安全控制分支。不能与IS31FL3236A使能极性不同的分支混用。新增接口和被绕线的温度保护/看门狗路径，都需实测电压、复位/断线/温控开路、关断和延时；样机未烧录、未通电。NTC/电源ADC、驱动反馈、输出闭环、整机结构及三款实测验收仍未完成，目标尚未达成。

## 使用与后续

工程包保留可编辑KiCad源文件、项目符号库、检查JSON/日志、CAM和贴片/BOM文件及SHA清单。`sh run_native_checks.sh`可原生复核，默认macOS KiCad路径，可通过KICAD_CLI指定另一工具位置；需要Python3。未采购、联系供应商或下单制造。下一步绑定PC15/RTC及红外消抖，再接完整安全检测和输出闭环，同时落实实际传感器与台架验证。
'''
(R/'README.md').write_text(report);(H/'A39-lamp-IR-PCB-paired-review.md').write_text(report)
files=list((R/'board').rglob('*'));files=[p for p in files if p.is_file() and p.suffix!='.kicad_prl']
files += [p for p in (R/'recheck').rglob('*') if p.is_file()]
files += [R/n for n in ['checks.json','safety_board_38refs_candidate_BOM.csv','IR_interface_contract.json','README.md','run_native_checks.sh','export_verification.log','native_all_checks.log']]
manifest=''.join(f'{sha(p)}  {p.relative_to(R).as_posix()}\n' for p in sorted(files));(H/'A39-SHA256SUMS.txt').write_text(manifest)
z=H/'Lamp-IR-PCB-paired-CAM-review-A39.zip'
with zipfile.ZipFile(z,'w',zipfile.ZIP_DEFLATED) as a:
 for p in files:a.write(p,p.relative_to(R).as_posix())
 a.writestr('SHA256SUMS.txt',manifest)
with zipfile.ZipFile(z) as a:
 assert a.testzip() is None
 for line in manifest.splitlines():
  digest,name=line.split('  ',1);assert hashlib.sha256(a.read(name)).hexdigest()==digest
 count=len(a.namelist())
ip=H/'LOCAL-PACKAGES.json';idx=json.loads(ip.read_text());idx['status']=j['status'];idx['latest_report']='A39-lamp-IR-PCB-paired-review.md';idx['latest_machine_report']='../lamp_IR_PCB_A39/checks.json';idx['latest_manifest']='A39-SHA256SUMS.txt'
idx['packages']=[x for x in idx['packages'] if x.get('archive')!=z.name]
idx['packages'].append({'archive':z.name,'sha256':sha(z),'bytes':z.stat().st_size,'zip_entries':count,'manifest_entries':len(files),'zip_crc':'PASS','manifest_readback':'PASS','manufacturing_status':'FABRICATION_HOLD'})
idx['latest_candidate_sources'].pop('lamp_safety_PCA_metadata',None);idx['latest_candidate_sources']['lamp_safety_PCA']='../lamp_IR_PCB_A39/board/lamp_safety_evt_a3_IR_PCB.kicad_pcb'
idx['latest_lamp_IR_hardware_A39']={'source':'../lamp_IR_PCB_A39/board/lamp_safety_evt_a3_IR_PCB.kicad_sch','PCB':'../lamp_IR_PCB_A39/board/lamp_safety_evt_a3_IR_PCB.kicad_pcb','PCB_sha256':j['PCB_sha256'],'ERC':0,'DRC':0,'unconnected_items':0,'schematic_parity':0,'physical_IR_firmware_bound':False,'physical_test_release':False,'sensor_MPN':'UNSELECTED'}
idx['manufacturing_exclusions']['lamp_A39']='Paired IR PCB/CAM is an engineering candidate only. A37 target still has no PC15 physical IR binding; sensor, DFM and physical gate tests incomplete. No fabrication release.'
ip.write_text(json.dumps(idx,ensure_ascii=False,indent=2)+'\n');print(json.dumps(idx['packages'][-1],indent=2))
