#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Recheck the lamp hardwired safety gate against localized KiCad netlist data."""
from __future__ import annotations
import hashlib
import itertools
import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
BASE = ROOT / "work/tri-product-continuation-2026-10-09.nosync/hardware_source_localization_v140"
NETLIST = BASE / "early_drc/lamp_safety_root_netlist.xml"
BOARD_AUDIT = BASE / "early_drc/lamp_safety_current_board_audit.json"
BOARD = BASE / "硬件/lamp_safety/evt/lamp_safety_evt_a0.kicad_pcb"
SCHEMATIC = BASE / "硬件/lamp_safety/evt/lamp_safety_evt_a0.kicad_sch"
POWER_NETLIST = BASE / "early_drc/lamp_power_root_netlist.xml"
POWER_PARITY = BASE / "early_drc/lamp_power_net_parity.json"
POWER_AUDIT = BASE / "early_drc/lamp_power_current_board_audit.json"
POWER_BOARD = BASE / "硬件/lamp_power/evt/lamp_power_evt_a1.kicad_pcb"
POWER_SCHEMATIC = BASE / "硬件/lamp_power/lamp_power.kicad_sch"
OUT_JSON = Path(__file__).with_name("lamp_safety_logic_audit_r0_1.json")
OUT_MD = Path(__file__).with_name("灯具硬件安全门控逻辑核验-r0.1.md")

def sha(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()

root = ET.parse(NETLIST).getroot()
components = {c.get("ref"): c for c in root.findall(".//components/comp")}
values = {ref: comp.findtext("value") for ref, comp in components.items()}
nets: dict[tuple[str, str], str] = {}
for net in root.findall(".//nets/net"):
    name = net.get("name", "")
    for node in net.findall("node"):
        nets[(node.get("ref", ""), node.get("pin", ""))] = name

def pin(ref: str, number: int) -> str:
    return nets.get((ref, str(number)), "unconnected")

checks = {
    "U3_is_74HC21D": values.get("U3") == "74HC21D,653",
    "U3_gate_inputs_and_output": [pin("U3", n) for n in (1, 2, 4, 5, 6)] == [
        "/LED_PWR_REQ", "/WDOG_OK", "/THERM_OK", "/INTERLOCK_OK", "/LED_PWR_PERMIT"
    ],
    "U3_noconn_pins": pin("U3", 3) == "unconnected-(U3-NC-Pad3)" and pin("U3", 11) == "unconnected-(U3-NC-Pad11)",
    "U3_supply": pin("U3", 7) == "/GND" and pin("U3", 14) == "/CTRL_3V3",
    "unused_U3_gate_inputs_grounded": all(pin("U3", n) == "/GND" for n in (9, 10, 12, 13)) and pin("U3", 8) == "unconnected-(U3-2Y-Pad8)",
    "interlock_net": all(pin("J4", n) == "/INTERLOCK_OK" for n in (3,)) and pin("U1", 7) == "/INTERLOCK_OK" and pin("U3", 5) == "/INTERLOCK_OK" and pin("R11", 1) == "/INTERLOCK_OK" and pin("R11", 2) == "/GND" and values.get("R11") == "10k",
    "thermal_net_and_watchdog_clear": pin("J4", 6) == "/THERM_OK" and pin("U1", 17) == "/THERM_OK" and pin("U2", 3) == "/THERM_OK" and pin("U3", 4) == "/THERM_OK" and pin("R18", 1) == "/THERM_OK" and pin("R18", 2) == "/GND" and values.get("R18") == "10k",
    "MCU_request_default_low": pin("U1", 11) == "/LED_PWR_REQ_MCU" and pin("R6", 1) == "/LED_PWR_REQ_MCU" and pin("R6", 2) == "/LED_PWR_REQ" and pin("R7", 1) == "/LED_PWR_REQ" and pin("R7", 2) == "/GND" and values.get("R7") == "100k",
    "watchdog_output_and_kick": pin("U1", 16) == "/WDOG_KICK" and pin("U2", 2) == "/WDOG_KICK" and pin("U2", 5) == "/WDOG_OK" and pin("U3", 2) == "/WDOG_OK",
    "permit_exits_to_power_board": pin("U3", 6) == "/LED_PWR_PERMIT" and pin("J3", 3) == "/LED_PWR_PERMIT",
}

board_audit = json.loads(BOARD_AUDIT.read_text())
power_audit = json.loads(POWER_AUDIT.read_text())
power_parity = json.loads(POWER_PARITY.read_text())
power_root = ET.parse(POWER_NETLIST).getroot()
power_components = {c.get("ref"): c for c in power_root.findall(".//components/comp")}
power_values = {ref: comp.findtext("value") for ref, comp in power_components.items()}
power_nets: dict[tuple[str, str], str] = {}
for net in power_root.findall(".//nets/net"):
    name = net.get("name", "")
    for node in net.findall("node"):
        power_nets[(node.get("ref", ""), node.get("pin", ""))] = name

def power_pin(ref: str, number: int) -> str:
    return power_nets.get((ref, str(number)), "unconnected")

checks["board_matches_schematic_netlist"] = (
    sha(BOARD) == board_audit.get("board_sha256")
    and sha(SCHEMATIC) == board_audit.get("schematic_sha256")
    and sha(NETLIST) == board_audit.get("parity", {}).get("netlist_sha256")
    and board_audit.get("parity", {}).get("net_mismatch_count") == 0
)
checks["power_board_matches_schematic_netlist"] = (
    sha(POWER_BOARD) == power_audit.get("board_sha256")
    and sha(POWER_SCHEMATIC) == power_audit.get("schematic_sha256")
    and sha(POWER_NETLIST) == power_parity.get("netlist_sha256")
    and power_parity.get("status") == "PASS"
    and power_parity.get("net_mismatch_count") == 0
)
embedded_power_parity = json.loads(power_audit["net_parity_stdout"])
provenance_notes = []
if embedded_power_parity.get("netlist_sha256") != sha(POWER_NETLIST):
    provenance_notes.append(
        "lamp_power_current_board_audit.json embeds an older netlist SHA-256; current root-netlist bytes match the standalone lamp_power_net_parity.json and lamp_power_net_parity_run.json instead."
    )

checks["cross_board_LED_power_gate"] = (
    power_values.get("U3", "").startswith("TPS22992S")
    and power_pin("J2", 3) == "/LED_PWR_PERMIT"
    and power_pin("U3", 8) == "/LED_PWR_PERMIT"
    and power_pin("R2", 1) == "/LED_PWR_PERMIT"
    and power_pin("R2", 2) == "/GND"
    and power_values.get("R2", "").startswith("100k")
    and power_pin("U3", 1) == "/SYS_5V0_ALWAYS_ON"
    and power_pin("U3", 2) == "/SYS_5V0_ALWAYS_ON"
    and power_pin("U3", 6) == "/VLED_PRE_SENSE"
)

truth_table = []
for req, watchdog, thermal, interlock in itertools.product((0, 1), repeat=4):
    permit = req & watchdog & thermal & interlock
    truth_table.append({"REQ": req, "WDOG_OK": watchdog, "THERM_OK": thermal, "INTERLOCK_OK": interlock, "LED_PWR_PERMIT": permit})
checks["truth_table_only_all_high_permits"] = sum(row["LED_PWR_PERMIT"] for row in truth_table) == 1 and truth_table[-1]["LED_PWR_PERMIT"] == 1

failed = [k for k, passed in checks.items() if not passed]
if failed:
    print("FAILED:", ", ".join(failed), file=sys.stderr)
    raise SystemExit(1)

result = {
    "status": "SOURCE_NETLIST_LOGIC_CHECK_PASS_PHYSICAL_SAFETY_TEST_NOT_RUN",
    "created_local_date": "2026-10-10",
    "scope": "lamp safety board hardwired power-permit logic only",
    "source_files": {
        "schematic": str(SCHEMATIC.relative_to(ROOT)),
        "schematic_sha256": sha(SCHEMATIC),
        "pcb": str(BOARD.relative_to(ROOT)),
        "pcb_sha256": sha(BOARD),
        "exported_netlist": str(NETLIST.relative_to(ROOT)),
        "netlist_sha256": sha(NETLIST),
        "power_schematic": str(POWER_SCHEMATIC.relative_to(ROOT)),
        "power_schematic_sha256": sha(POWER_SCHEMATIC),
        "power_pcb": str(POWER_BOARD.relative_to(ROOT)),
        "power_pcb_sha256": sha(POWER_BOARD),
        "power_exported_netlist": str(POWER_NETLIST.relative_to(ROOT)),
        "power_netlist_sha256": sha(POWER_NETLIST),
        "power_net_parity_record": str(POWER_PARITY.relative_to(ROOT)),
        "power_net_parity_sha256": sha(POWER_PARITY),
    },
    "ic_reference": {
        "manufacturer": "Nexperia",
        "part": "74HC21D,653",
        "official_datasheet": "https://assets.nexperia.com/documents/data-sheet/74HC21.pdf",
        "function": "1Y pin 6 is the AND of inputs pins 1, 2, 4, and 5; pins 3 and 11 are NC; pin 7 GND; pin 14 VCC.",
    },
    "power_switch_reference": {
        "manufacturer": "Texas Instruments",
        "part": power_values.get("U3"),
        "official_datasheet": "https://www.ti.com/lit/gpn/tps22992",
        "function": "ON enable is pin 8; the localized lamp_power schematic routes LED_PWR_PERMIT to this pin.",
    },
    "permit_equation": "LED_PWR_PERMIT = LED_PWR_REQ AND WDOG_OK AND THERM_OK AND INTERLOCK_OK",
    "checks": checks,
    "provenance_notes": provenance_notes,
    "truth_table": truth_table,
    "physical_validation": {
        "status": "NOT_RUN_NO_SAMPLE",
        "test_owner": "user-side engineer or factory, as previously specified",
        "required_before_release": [
            "Power safety board from current-limited supply with LED driver disconnected; verify no permit during reset/boot.",
            "With output shielded and interlock fixture active, verify permit drops for open/low interlock and returns only after re-authorization.",
            "Verify thermal input low/open asynchronously inhibits permit and clears watchdog output.",
            "Stop MCU watchdog kicks; measure timeout and verify LED rail shuts down and remains off.",
            "Measure LED-driver rail/output decay and any residual UV emission after each fault; do not infer this from a low permit voltage alone.",
            "Confirm hardware NTC/thermostat ownership and trip thresholds against the assembled lamp sensor/harness before declaring thermal protection equivalent.",
        ],
    },
}
OUT_JSON.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")

rows = "\n".join(
    f"| {r['REQ']} | {r['WDOG_OK']} | {r['THERM_OK']} | {r['INTERLOCK_OK']} | {r['LED_PWR_PERMIT']} |"
    for r in truth_table
)
OUT_MD.write_text(f'''# 光疗灯硬件安全门控逻辑核验 r0.1\n\n**状态：原理图/网表逻辑核验通过；实体安全验证未做；不构成量产放行。**\n\n## 核验对象\n\n- U3：Nexperia 74HC21D,653；官方数据手册列出 1Y=6 脚，四个输入为 1、2、4、5 脚；3、11 脚 NC。\n- 当前网表：`LED_PWR_PERMIT = LED_PWR_REQ AND WDOG_OK AND THERM_OK AND INTERLOCK_OK`，输出连接到 J3.3。\n- 跨板追踪：安全板 J3.3 对接电源板 J2.3，并进入 TPS22992S U3.8 `ON`；R2=100 kΩ 将该使能下拉到地；负载开关从 `SYS_5V0_ALWAYS_ON` 切换到 `VLED_PRE_SENSE`。两个候选板各自 116/116 与 88/88 个连通管脚均通过网表对照。TI 数据手册确认 TPS22992 的 ON 脚为 8 脚：[TI TPS22992](https://www.ti.com/lit/gpn/tps22992)。\n- 来源记录差异：`lamp_power_current_board_audit.json` 内嵌的网表 SHA-256 与现存 XML 不同；XML 哈希与独立 `lamp_power_net_parity.json`、`lamp_power_net_parity_run.json` 一致。本核验保留该差异并按现存 XML 与独立对照记录检查，不把旧内嵌哈希当作当前证据。\n- `INTERLOCK_OK` 经 J4.3 进入逻辑，R11=10 kΩ 下拉；`THERM_OK` 经 J4.6 进入 U3 和 U2 的清零脚，R18=10 kΩ 下拉；MCU 的 `LED_PWR_REQ` 由 R7=100 kΩ 下拉。开路/掉电预期走低电平失能路径。\n- 闲置的 U3 第二 AND 门四个输入接地，输出不连接。当前板与原理图网表针脚/网络哈希一致；源文件哈希和逐项断言见同目录 JSON。\n\n官方器件依据：[Nexperia 74HC21 数据手册](https://assets.nexperia.com/documents/data-sheet/74HC21.pdf)。\n\n## 16 状态逻辑表\n\n表中 `1` 表示允许/有效，`0` 表示失效或未请求。只有四个输入全为 1 时，逻辑输出应为 1。\n\n| LED_PWR_REQ | WDOG_OK | THERM_OK | INTERLOCK_OK | LED_PWR_PERMIT |\n|---:|---:|---:|---:|---:|\n{rows}\n\n## 工厂/工程台架实测步骤\n\n当前没有样机，以下状态仍是 `NOT_RUN`。测试时先断开 LED 驱动器，以限流电源给安全板供电；需要测量输出关断、保持关闭、复位/重新授权行为。之后再在遮光且联锁的治具内接入 LED 驱动器，测电源轨关断时间、残余 UV 辐射及其重复性。禁止仅以 `LED_PWR_PERMIT` 低电平代替 LED 熄灭/辐射衰减实测。逐项验证：\n\n1. 上电、复位、固件启动期间输出请求默认低，LED 电源不得误使能。\n2. 保持其余条件有效，断开/拉低舱盖互锁；许可应立即撤销，恢复后须按定义重新授权。\n3. 拉低/断开温度允许；确认硬件看门狗清零和 LED 电源许可撤销。\n4. 停止 MCU 喂狗；实测超时时间、LED 电源轨衰减和输出保持关闭。\n5. 验证传感器断线、卡死、通讯丢失、过温及 MCU 复位对应的完整 LED 关断。确认 NTC ADC 与硬件温度输入分别对应的实物传感器、阈值和线束。\n6. 每项故障清除后确认不会自动恢复照射，直到满足产品定义的重新授权条件。\n\n**未覆盖**：比较机 UV 光谱/辐照/固化效果、温升、结构与适用安全标准；这些仍需参考机 A/B 台架和合规测试。\n''', encoding="utf-8")
print(json.dumps({"status": result["status"], "passed_checks": len(checks), "truth_table_rows": len(truth_table), "report": str(OUT_MD), "json": str(OUT_JSON)}, ensure_ascii=False))
