master
John Lauer Add adom-verified, adom-models and adom-footprint-variants (moved from john/ pages) with their tools 7acd92d 16d ago
#!/usr/bin/env python3
"""Add a record to a component page's verification log (verification.json, schema adom/verification@1).

Run it inside a clone of the component page. Every --file is hashed (sha256) so the ::adom-verified widget
can tell readers, byte for byte, whether what was verified is still what the page serves.

  verify_log.py add --by "John Lauer" --handle john --title "Footprint matches the datasheet" \\
      --scope footprint --variant '*' --check "0.7 x 0.7 mm pads" --check "note:the datasheet gives no reason" \\
      --evidence "pad proof=docs/img/pad-proof.png" --file WS2812B-2020.kicad_mod [--status open] [--date 2026-10-04]
  verify_log.py variants --set "WS2812B-2020-V6=12 mA, 3.3-5.3 V" --stocked WS2812B-2020-V6
  verify_log.py show

A check prefixed "fail:" or "note:" records that result; the default is pass."""
import argparse, datetime, hashlib, json, os, sys

PATH = "verification.json"


def load():
    if os.path.exists(PATH): return json.load(open(PATH))
    pj = json.load(open("page.json")) if os.path.exists("page.json") else {}
    return {"schema": "adom/verification@1", "part": (pj.get("component") or {}).get("mpn") or pj.get("title"), "variants": [], "entries": []}


def save(d): json.dump(d, open(PATH, "w"), indent=1, ensure_ascii=False); open(PATH, "a").write("\n")


def main():
    ap = argparse.ArgumentParser(); sub = ap.add_subparsers(dest="cmd", required=True)
    a = sub.add_parser("add")
    a.add_argument("--by", required=True); a.add_argument("--handle", default="")
    a.add_argument("--title", required=True); a.add_argument("--status", default="verified", choices=["verified", "open", "superseded"])
    a.add_argument("--date", default=datetime.date.today().isoformat())
    a.add_argument("--scope", action="append", default=[]); a.add_argument("--variant", action="append", default=[])
    a.add_argument("--check", action="append", default=[]); a.add_argument("--evidence", action="append", default=[])
    a.add_argument("--file", action="append", default=[])
    v = sub.add_parser("variants"); v.add_argument("--set", action="append", default=[]); v.add_argument("--stocked", action="append", default=[])
    sub.add_parser("show")
    o = ap.parse_args(); d = load()
    if o.cmd == "add":
        checks = []
        for c in o.check:
            res = "pass"
            for p in ("fail:", "note:"):
                if c.startswith(p): res, c = p[:-1], c[len(p):]
            checks.append({"text": c.strip(), "result": res})
        ev = [{"label": e.split("=", 1)[0], "href": e.split("=", 1)[1]} for e in o.evidence]
        files = {}
        for f in o.file:
            if not os.path.exists(f): sys.exit("no such file: " + f)
            files[f] = hashlib.sha256(open(f, "rb").read()).hexdigest()
        d["entries"].append({"date": o.date, "by": {"name": o.by, "handle": o.handle}, "status": o.status, "title": o.title,
                             "scope": o.scope, "variants": o.variant or ["*"], "checks": checks, "evidence": ev, "files": files})
        save(d); print(json.dumps({"entries": len(d["entries"]), "added": o.title, "files": len(files)}))
    elif o.cmd == "variants":
        by = {x["mpn"]: x for x in d["variants"]}
        for s in o.set:
            mpn, note = (s.split("=", 1) + [""])[:2]; by.setdefault(mpn, {"mpn": mpn})["note"] = note
        for x in by.values(): x["stocked"] = x["mpn"] in o.stocked if o.stocked else x.get("stocked", False)
        d["variants"] = list(by.values()); save(d); print(json.dumps(d["variants"]))
    else:
        print(json.dumps(d, indent=1))


if __name__ == "__main__":
    main()