AnasSarkiz/rp2040-bldc-servo-controller

RP2040-based motor-control board integrating a DRV8316 three-phase gate driver, AS5047P magnetic position sensor, USB-C power/communications, STUSB4500 USB-PD controller, SPI flash, and THVD1450 RS-485 transceiver.

Version
0.0.5
License
unset
Stars
0

scripts/check-sourcing.py

"""Check audit BOM against generated hardware records, never fabrication approval."""

import csv
import hashlib
import json
from collections import Counter
from pathlib import Path


def load_json(path):
    return json.loads(Path(path).read_text())


def check_sourcing():
    circuit_path = Path("dist/index/circuit.json")
    circuit = load_json(circuit_path)
    with Path("BOM.csv").open(newline="") as bom_file:
        bom_rows = list(csv.DictReader(bom_file))
    components = {
        record["name"]: record
        for record in circuit
        if record["type"] == "source_component"
    }
    primary_listings = (
        load_json("evidence/tooling-20261026/core-stock-browser.json")
        + load_json("evidence/tooling-20261026/basic-stock-browser.json")
        + [load_json("evidence/tooling-20261026/boost-stock-browser.json")]
    )
    primary_by_supplier = {listing["lcsc"]: listing for listing in primary_listings}
    assert len(primary_by_supplier) == len(primary_listings)
    expected_parts = {
        "U1": "C2040",
        "U2": "C5218861",
        "U3": "C2678061",
        "U4": "C131025",
        "U5": "C2671361",
        "U6": "C962063",
        "J1": "C37616412",
    }
    assert len(bom_rows) == len(components) == len(expected_parts)
    assert {row["Reference Designator"] for row in bom_rows} == set(components)
    for bom_row in bom_rows:
        reference_designator = bom_row["Reference Designator"]
        component = components[reference_designator]
        supplier_number = bom_row["LCSC Number"]
        assert supplier_number == expected_parts[reference_designator]
        assert component["supplier_part_numbers"]["jlcpcb"] == [supplier_number]
        assert bom_row["Manufacturer Part Number"] == component["manufacturer_part_number"]
        assert int(bom_row["Quantity"]) == 1
        assert bom_row["Assembly Side"] == "TOP"
        assert bom_row["Verified Import Status"]
        assert "incomplete" in bom_row["BOM Scope"]
        listing = primary_by_supplier[supplier_number]
        assert listing["identity_visible"] and listing["assembly_support_visible"]
        assert bom_row["Manufacturer Part Number"] == listing["mpn"]
        assert int(bom_row["Stock"]) == listing["stock"]
        assert int(bom_row["Available Order Quantity"]) == listing["available_order_quantity"]
        assert bom_row["Checked Date"] == listing["checked_date"]
        assert bom_row["JLCPCB Class"] == listing["classification"]

    board = next(record for record in circuit if record["type"] == "pcb_board")
    assert (board["width"], board["height"], board["num_layers"]) == (42, 42, 4)
    placed_components = [record for record in circuit if record["type"] == "pcb_component"]
    pads = [record for record in circuit if record["type"] == "pcb_smtpad"]
    assert len(placed_components) == 7
    assert all(component["layer"] == "top" for component in placed_components)
    assert all(pad["layer"] == "top" for pad in pads)
    sheets = [record for record in circuit if record["type"] == "schematic_sheet"]
    assert len(sheets) == 3 and all(sheet["sheet_size"] == "a4" for sheet in sheets)
    record_counts = Counter(record["type"] for record in circuit)
    assert not record_counts["source_trace"]
    assert not record_counts["pcb_trace"]
    assert not record_counts["pcb_via"]
    assert load_json("tscircuit.config.json")["build"]["routingDisabled"] is True

    with Path("SOURCING-CANDIDATES.csv").open(newline="") as candidate_file:
        candidate_rows = list(csv.DictReader(candidate_file))
    assert {row["LCSC Number"] for row in candidate_rows} == {
        "C165948", "C1525", "C52923", "C25744", "C25076", "C169167"
    }
    assert all(int(row["Allocated Quantity"]) == 0 for row in candidate_rows)
    for candidate in candidate_rows:
        listing = primary_by_supplier[candidate["LCSC Number"]]
        assert candidate["Manufacturer Part Number"] == listing["mpn"]
        assert int(candidate["Stock"]) == listing["stock"]
        assert int(candidate["Available Order Quantity"]) == listing["available_order_quantity"]
        assert candidate["JLCPCB Class"] == listing["classification"]
        if "footprint" in listing:
            assert candidate["Footprint"] == listing["footprint"]

    # These counts expose the still-failing hardware build. They are not waived
    # by a successful consistency audit, and are not prerequisites for its exit.
    summary = {
        "revision": f'toolchain-review-prototype-{load_json("package.json")["version"]}',
        "audit_bom_matches_generated_fixture": True,
        "bom_component_rows": len(bom_rows),
        "unallocated_candidate_rows": len(candidate_rows),
        "outline_mm": [board["width"], board["height"]],
        "layers": board["num_layers"],
        "placed_component_side": "top",
        "smt_pad_side": "top",
        "smt_pad_count": len(pads),
        "required_pin_error_count": record_counts["source_pin_must_be_connected_error"],
        "electrical_design_complete": False,
        "fabrication_ready": False,
        "circuit_json_sha256": hashlib.sha256(circuit_path.read_bytes()).hexdigest(),
    }
    Path("evidence/bom-consistency.json").write_text(json.dumps(summary, indent=2) + "\n")
    print(json.dumps(summary, indent=2))


if __name__ == "__main__":
    check_sourcing()