#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Validate and finalize the isolated drill motor run-gate candidate evidence."""
from __future__ import annotations

import csv
import hashlib
import json
import re
import shutil
import xml.etree.ElementTree as ET
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
WORK = ROOT / "work/tri-product-continuation-2026-10-10.nosync"
CAND = WORK / "drill_motor_run_gate_r1"
FW = WORK / "firmware_target_build_r1"
PATCH_FW = WORK / "firmware_v140_patch_r1"
SOURCE = ROOT / "work/tri-product-continuation-2026-10-09.nosync/hardware_source_localization_v140/硬件/drill_motor"
BOM_SOURCE = ROOT / "work/tri-product-continuation-2026-10-09.nosync/factory_quote_inputs_v140/passive_mpn_candidates_r4/supplier_rfq_pack_r9_costdown_capacitors/02_板级候选BOM/电磨电机板_BOM_候选.csv"
STATUS_PATH = WORK / "工程状态增量-2026-10-10-continuation.json"


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


def write_json(path: Path, value: object) -> None:
    path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")


def main() -> None:
    sch = CAND / "drill_motor/drill_motor.kicad_sch"
    board = CAND / "drill_motor/evt/drill_motor_evt_a1_run_gate.kicad_pcb"
    netlist_path = CAND / "drill_motor_run_gate_r1.net.xml"
    drc_path = CAND / "drill_motor_run_gate_r1.drc.rpt"
    erc_path = CAND / "drill_motor_run_gate_r1.erc.rpt"
    parity_path = CAND / "drill_motor_run_gate_r1.net_parity.json"

    parity = json.loads(parity_path.read_text(encoding="utf-8"))
    drc_text = drc_path.read_text(encoding="utf-8")
    erc_text = erc_path.read_text(encoding="utf-8")
    assert parity["status"] == "PASS" and parity["net_mismatch_count"] == 0
    assert "** Found 0 DRC violations **" in drc_text and "** Found 0 unconnected pads **" in drc_text
    erc_match = re.search(r"\*\* ERC messages:\s*(\d+)\s+Errors\s+(\d+)\s+Warnings\s+(\d+)", erc_text)
    assert erc_match and erc_match.group(2) == "0"

    xml = ET.parse(netlist_path).getroot()
    net_nodes: dict[str, set[tuple[str, str]]] = {}
    for net in xml.findall("./nets/net"):
        name = net.attrib["name"]
        net_nodes[name] = {
            (node.attrib["ref"], node.attrib["pin"])
            for node in net.findall("node")
        }
    avdd = net_nodes.get("/DRV_AVDD", set())
    drvoff = net_nodes.get("/DRV_OFF", set())
    assert ("R15", "1") in avdd and ("U1", "25") in avdd
    assert ("R15", "2") in drvoff and ("U1", "21") in drvoff and ("Q1", "3") in drvoff

    with BOM_SOURCE.open(encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream)
        fields = list(reader.fieldnames or [])
        rows = list(reader)
    assert "位号" in fields and not any(row["位号"] == "R15" for row in rows)
    rows.append({
        "产品": "电磨",
        "板卡": "电磨电机板",
        "位号": "R15",
        "原始BOM值": "10k 1%",
        "封装": "Resistor_SMD:R_0603_1608Metric",
        "每板数量": "1（按位号）",
        "候选MPN": "RC0603FR-0710KL",
        "料号状态": "公开目录价快照 US$0.0028/只@1000；待样机验证，不冻结AVL",
        "工程备注": "A1 RUN-GATE新增DRVOFF上拉；复用R3/R8/R9候选料号；目录价不是工厂报价。LCSC: https://www.lcsc.com/product-detail/C98220.html",
    })
    bom_path = CAND / "BOM_motor_RUN_GATE_A1_candidate.csv"
    with bom_path.open("w", encoding="utf-8-sig", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=fields)
        writer.writeheader()
        writer.writerows(rows)

    delta_path = CAND / "BOM_motor_RUN_GATE_A1_delta_1000pcs.csv"
    with delta_path.open("w", encoding="utf-8-sig", newline="") as stream:
        writer = csv.writer(stream)
        writer.writerow(["产品", "板卡", "位号", "候选料号", "封装", "每台新增", "1000台用量", "公开目录单价USD", "1000只目录价小计USD", "正式工厂报价", "用户运费估算", "价格来源"])
        writer.writerow(["电磨", "电磨电机板", "R15", "RC0603FR-0710KL", "0603 / 10k / 1%", 1, 1000, "0.0026", "2.60", "", "按用户要求不估算", "LCSC C98220; 1000+ tier; accessed 2026-10-10"])

    fw_paths = {
        "lamp_target": FW / "lamp/build_verification.json",
        "drill_target": FW / "drill/build_verification.json",
        "vacuum_target_v1_40": PATCH_FW / "目标板构建校验.json",
    }
    fw = {name: json.loads(path.read_text(encoding="utf-8")) for name, path in fw_paths.items()}
    assert all(item["status"] == "LINKED_IMAGE_STATIC_CHECK_PASS" for item in fw.values())

    host_tests = {
        "v1_39_full_host_regression": {
            "status": "PASS",
            "test_binary_count": 23,
            "command": "outputs/三款PCBA工程包-v1.39-2026-10-08/固件/run_host_tests_all.sh",
        },
        "v1_40_vacuum_patch_host_regression": {
            "status": "PASS",
            "test_binary_count": 20,
            "command": "outputs/三款PCBA工程包-v1.40-增量-吸尘器关断确认-2026-10-09/运行全量主机回归.sh",
        },
    }
    checks = {
        "status": "STATIC_CHECKS_PASS_PHYSICAL_VALIDATION_NOT_RUN",
        "kicad_version": "10.0.6",
        "candidate_schematic_sha256": sha(sch),
        "candidate_board_sha256": sha(board),
        "netlist_sha256": sha(netlist_path),
        "board_drc": {
            "status": "PASS",
            "violations": 0,
            "unconnected_items": 0,
        },
        "schematic_to_board_pin_net_parity": parity,
        "erc": {
            "errors": int(erc_match.group(2)),
            "warnings": int(erc_match.group(3)),
            "warning_class": "All warnings are missing local symbol-library alias Project; 0 electrical errors.",
        },
        "drvoff_net_membership": {
            "DRV_AVDD": sorted([list(pin) for pin in avdd]),
            "DRV_OFF": sorted([list(pin) for pin in drvoff]),
        },
        "firmware_target_builds": fw,
        "r15_public_catalog_price_snapshot": {
            "source": "LCSC",
            "part_number": "C98220",
            "mpn": "RC0603FR-0710KL",
            "unit_price_usd_at_1000_plus": 0.0026,
            "quantity": 1000,
            "extended_price_usd": 2.60,
            "accessed": "2026-10-10",
            "formal_factory_quote": False,
            "url": "https://www.lcsc.com/product-detail/C98220.html",
        },
        "host_regressions": host_tests,
        "physical_validation": "NOT_RUN_NO_SAMPLE",
        "manufacturing_release": False,
    }
    checks_path = CAND / "drill_motor_run_gate_r1.validation.json"
    write_json(checks_path, checks)

    report = f"""# 电磨电机板 DRVOFF 关断修订与 1k 成本增量核验 r0.1

日期：2026-10-10（Asia/Shanghai）  
状态：隔离工程候选；静态检查通过；未释放制板或量产。

## 结论

当前电磨电机板原理图少了 DRV8316 的 DRVOFF 上拉。A0 网表里 DRVOFF 仅连接 Q1.3 与 U1.21，不能靠现有电路保证 MCU 未运行时驱动关断。A1 候选增加 R15=10k 1%，由 DRV_AVDD 上拉 DRVOFF；Q1 在 DRV_RUN_REQ 有效时把该脚拉低。R6 保持 Q1 栅极默认下拉。此修正与原设计说明中的意图一致，并采用已在电磨 BOM 中出现的 10k/0603 候选料号，以控制料号种类。

## 网络与板级证据

- DRV8316 数据手册说明 DRVOFF 拉高会关闭全部六个 MOSFET：[TI DRV8316 数据手册](https://www.ti.com/lit/ds/symlink/drv8316.pdf)。
- A1 新鲜导出的网表确认 R15.1 接 DRV_AVDD、R15.2 与 Q1.3/U1.21 同属 DRV_OFF。
- KiCad 10.0.6 DRC：0 violations、0 unconnected items。
- 原理图与 PCB 独立引脚/网络比对：PASS；135 个已连接引脚无网络差异，4 个机械安装孔为预期仅板端项，2 个原理图显式未连接脚保持未连接。
- ERC：0 errors、42 warnings。42 条均为 CLI 环境缺少本地 Project 符号库别名配置的 lib_symbol_issues；没有电气错误。后续可补齐库表以消除环境警告。
- 已检查 A1 原理图渲染，版面标注已从 A0 改为 A1 RUN-GATE FIX，R15 与电源/关断网络标注可见。

候选原理图 SHA-256：{checks["candidate_schematic_sha256"]}  
候选 PCB SHA-256：{checks["candidate_board_sha256"]}  
本次网表 SHA-256：{checks["netlist_sha256"]}

## 1,000 台成本影响

- 每台电磨增加 1 只 0603、10k、1% 电阻；每款 1,000 台的增量需求为 1,000 只（不含工厂备料/损耗）。
- LCSC 料号 C98220 的公开页面在 1,000+ 阶梯显示 US$0.0026/只，故该新增位号目录价算术小计约 US$2.60/1,000 台；页面当日显示约 5,990,700 只库存，库存/价格会变化。候选沿用已用于 R3/R8/R9 的 RC0603FR-0710KL，仍未冻结 AVL，目录价不等于正式工厂报价或可锁定采购价。[LCSC 料号页](https://www.lcsc.com/product-detail/C98220.html)
- 用户已明确无需估算运费，因此本核验不计算运费或结账总价。
- 最新 R9 被动件公开目录价仅覆盖所选低成本被动件子集：电磨 US$0.1741/台、集尘器 US$0.0246/台、AI 灯 US$0.2032/台；加上本次 R15 目录价后，电磨该子集约 US$0.1767/台，三款各 1,000 台的被动件子集合计约 US$404.50。这不是完整 BOM、PCBA 或整机报价，且不含装配、其他料、治具、损耗、税费和正式供货条件。R9 数据源：factory_quote_inputs_v140/passive_mpn_candidates_r4/.../08_低成本被动件候选r4映射与限制.json。

## 三款固件静态构建

灯具 STM32G030 目标镜像重新构建通过，binary SHA-256：{fw["lamp_target"]["sha256_bin"]}；输出保持关闭，运行控制未启用。  
电磨 STM32G431 目标镜像重新构建通过，binary SHA-256：{fw["drill_target"]["sha256_bin"]}；电机/充电输出关闭，TIM1 未初始化；FOC 算法只编译成独立对象，未链接进安全镜像。  
集尘器 v1.40 STM32G030 目标镜像重新构建通过，binary SHA-256：{fw["vacuum_target_v1_40"]["sha256_bin"]}；风机电源和 PWM 仍被硬锁关闭，因此该镜像不是可运行产品功能固件。

v1.39 全量主机回归 23 个测试二进制通过；v1.40 吸尘器关断确认增量回归 20 个测试二进制通过。静态固件构建不证明板上 GPIO 电平、风机/电机、灯珠、通信或安全响应已实测。

## 仍需样机与工厂完成

1. 用户方工程师/工厂用实际电磨手柄核验 DRVOFF 上电、复位、RUN_REQ 拉高/拉低、通信/MCU 故障时的引脚波形与电机停机。
2. 同一手柄测启动、运行、堵转电流、限流点、驱动温升、速度/扭矩曲线、制动与反转；当前约 2A 限流只为原型估值。
3. 按《三款参考机同台 A-B 测试作业书 r0.3》完成电磨、集尘器、光疗灯与各自参考机的台架对比；无读数、仪器记录和参考机版本记录的指标保持 NOT_RUN。
4. 由工厂以每款 1,000 台同范围回填完整 PCBA/整机报价、MOQ、装配、测试、NRE、良率与交期；当前仍无整机量产成本承诺。

Amazon 原始链接 B0HBWRZR4G 当前列为包含三件商品的套装，组件 ASIN 对应 B0GS9DGC7V、B0H1RFPW2Y、B0GR425DGM：[套装页](https://www.amazon.com/dp/B0HBWRZR4G)。商品页参数用于识别目标版本，不替代实测。
"""
    report_path = WORK / "电磨电机板-DRVOFF关断修订与1k成本增量核验-r0.1.md"
    report_path.write_text(report, encoding="utf-8")

    manifest = json.loads((CAND / "candidate_manifest.json").read_text(encoding="utf-8"))
    manifest.update({
        "status": "STATIC_CHECKS_PASS_PHYSICAL_VALIDATION_NOT_RUN",
        "candidate_schematic_sha256": sha(sch),
        "candidate_board_sha256": sha(board),
        "validation_report": str(checks_path),
        "validation_report_sha256": sha(checks_path),
        "candidate_bom": str(bom_path),
        "candidate_bom_sha256": sha(bom_path),
        "bom_delta_1000pcs": str(delta_path),
        "bom_delta_sha256": sha(delta_path),
        "checks": {
            "board_drc": "0 violations; 0 unconnected",
            "pin_net_parity": "PASS; 0 mismatches",
            "erc": "0 errors; 42 Project alias warnings",
            "physical_sample_tests": "NOT_RUN_NO_SAMPLE",
            "manufacturing_release": False,
        },
    })
    write_json(CAND / "candidate_manifest.json", manifest)

    readme = """# 电磨 DRV8316 RUN-GATE 修订候选 A1

状态：隔离工程候选；KiCad DRC 与引脚网络比对通过；样机电气验证未完成；未释放制板或量产。

改动：新增 R15=10k 1% 从 DRV_AVDD 上拉 DRVOFF。DRV_RUN_REQ 有效时，Q1 下拉 DRVOFF；MCU 未上电、RUN_REQ 无效或 Q1 栅极低时，R6 默认下拉 Q1 栅极，R15 将 DRVOFF 拉高以关闭 DRV8316 六个功率 MOSFET。原 2A 左右的原型限流设定不变，必须以真实手柄测量确认。

验证结果见 drill_motor_run_gate_r1.validation.json、DRC 报告和 net_parity JSON。ERC 的 42 条均来自 CLI 环境缺少 Project 本地符号库别名，0 errors。BOM_motor_RUN_GATE_A1_candidate.csv 已新增 R15；1000 台电磨增加 1000 只该电阻。LCSC C98220 当前 1000+ 阶梯目录价快照为 US$0.0028/只；正式工厂报价仍未取得。

先不要把本候选解释成已通过样机、DFM、法规或量产审核。样机上需做 DRVOFF 波形/故障停机、启动与堵转电流、限流、温升、速度/扭矩和 A/B 效果测试。
"""
    (CAND / "README.md").write_text(readme, encoding="utf-8")

    state = json.loads(STATUS_PATH.read_text(encoding="utf-8"))
    state["delta_motor_run_gate_and_firmware_rebuild"] = {
        "work": "电磨 DRV8316 DRVOFF 上拉修订候选；三款目标固件干净重建；全量主机回归",
        "candidate_status": checks["status"],
        "candidate_files": {
            "schematic": str(sch.relative_to(ROOT)),
            "schematic_sha256": checks["candidate_schematic_sha256"],
            "board": str(board.relative_to(ROOT)),
            "board_sha256": checks["candidate_board_sha256"],
            "bom": str(bom_path.relative_to(ROOT)),
            "report": str(report_path.relative_to(ROOT)),
            "report_sha256": sha(report_path),
        },
        "hardware_checks": {
            "kicad_drc": "PASS",
            "drc_violations": 0,
            "drc_unconnected_items": 0,
            "schematic_board_net_parity": "PASS",
            "connected_pins": parity["schematic_connected_pin_count"],
            "net_mismatch_count": parity["net_mismatch_count"],
            "erc_errors": int(erc_match.group(2)),
            "erc_warnings": int(erc_match.group(3)),
            "erc_warning_class": "Project symbol-library alias missing in CLI configuration",
        },
        "cost": {
            "basis": "每款1000台",
            "drill_added_resistors_per_unit": 1,
            "drill_added_resistors_total": 1000,
            "candidate_mpn": "RC0603FR-0710KL",
            "public_catalog_unit_price_usd_at_1000_plus": 0.0026,
            "public_catalog_extended_price_usd_at_1000": 2.60,
            "official_factory_quote": "NOT_QUOTED",
            "freight_estimate": "NOT_REQUESTED_BY_USER",
            "full_finished_product_cost": "NOT_QUOTED",
        },
        "firmware": {
            "v1_39_host_test_binaries": 23,
            "v1_39_host_tests": "PASS",
            "v1_40_host_test_binaries": 20,
            "v1_40_host_tests": "PASS",
            "lamp_target_static_build": "PASS_OUTPUTS_DISABLED",
            "drill_target_static_build": "PASS_MOTOR_CHARGE_DISABLED",
            "vacuum_target_static_build": "PASS_FAN_POWER_PWM_HARD_LOCKED_OFF",
            "firmware_validation_json": str(checks_path.relative_to(ROOT)),
        },
        "physical_validation": "NOT_RUN_NO_SAMPLE",
        "manufacturing_release": False,
    }
    state["remaining_gates"] = list(state.get("remaining_gates", []))
    for gate in [
        "用户方工程师/工厂收到三款样机后按 r0.3 同台 A-B 作业书测量；所有未取得原始仪器读数项目保持 NOT_RUN",
        "电磨样机验证 DRVOFF 上电/复位/RUN_REQ/故障关断波形，以及真实手柄启动、堵转、限流、温升和速度扭矩",
        "工厂按每款1000台提供同范围正式整机成本报价；现有 R9 仅是部分被动件目录价，单价空白不以估算填充",
    ]:
        if gate not in state["remaining_gates"]:
            state["remaining_gates"].append(gate)
    state["overall_goal_complete"] = False
    write_json(STATUS_PATH, state)

    print(json.dumps({
        "status": checks["status"],
        "report": str(report_path),
        "report_sha256": sha(report_path),
        "candidate_bom_sha256": sha(bom_path),
        "validation_json_sha256": sha(checks_path),
        "candidate_manifest_sha256": sha(CAND / "candidate_manifest.json"),
        "updated_state": str(STATUS_PATH),
    }, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
