benikigai/ec-1Public
EC-1 Wireless Controller
SHA-256 32bf63bee2bce758914e1290527c7818ae8c6f618deb03306f7df7efb8b48028
"""Rebuild every derived number in opd.yaml from raw/, and check they match.
Run from the OPD folder: python analysis/spreads.py
Needs Python 3.10+ and pyyaml. Exits non-zero on any mismatch.
It checks three things:
1. Each feature spread (mean, sd, n) against raw/measurements.csv and raw/eol-sample.csv.
2. Baseline RTY against the product of station first-pass yields.
3. The BOM roll-up against bom/bom.csv.
"""
import csv, math, pathlib, statistics, sys
import yaml
HERE = pathlib.Path(__file__).resolve().parent.parent
doc = yaml.safe_load((HERE / "opd.yaml").read_text())
problems = []
raw = {}
with open(HERE / "raw" / "measurements.csv") as f:
for row in csv.DictReader(f):
raw.setdefault(row["feature"], []).append(float(row["value"]))
for part in doc["parts"]:
for feat in part.get("features", []):
vals = raw.get(feat["id"])
if vals is None:
problems.append(f"{feat['id']}: no raw values")
continue
got = dict(mean=round(statistics.mean(vals), 4), sd=round(statistics.stdev(vals), 4), n=len(vals))
want = {k: feat["spread"][k] for k in got}
if got != want:
problems.append(f"{feat['id']}: raw gives {got}, opd.yaml says {want}")
print(f"{feat['id']:<28} mean {got['mean']:>9} sd {got['sd']:>7} n {got['n']}")
base = doc["variants"]["baseline"]["scores"]["yield"]
rty = round(math.prod(s["value"] for s in base["fpy_by_station"].values()), 3)
if rty != base["rty"]["value"]:
problems.append(f"RTY: stations multiply to {rty}, opd.yaml says {base['rty']['value']}")
print(f"baseline RTY from station FPY: {rty}")
with open(HERE / "bom" / "bom.csv") as f:
rows = list(csv.DictReader(f))
total = sum(float(r["unit_cost_usd"]) * float(r["qty"]) for r in rows if r["unit_cost_usd"] and r["parent"] != "part:main-pcba")
print(f"BOM roll-up: {total:.2f} USD")
with open(HERE / "raw" / "eol-sample.csv") as f:
eol = list(csv.DictReader(f))
fails = sum(r["result"] == "fail" for r in eol)
print(f"end-of-line sample: {len(eol)} units, {fails} failed ({fails / len(eol):.1%})")
if problems:
print("\n".join(["", "MISMATCH:"] + problems))
sys.exit(1)
print("\nEvery derived number matches raw/.")