AnasSarkiz/muse-voice-remote

Muse Voice Remote — bare ESP32-S3R8, 155 TOP JLCPCB parts with real supplier CAD, native 3D assembly and GLB exports. Revised 17 mm case allocation; 0.30 mm via drill /0.45 mm pad. Checked prototype PCB; mechanical, RF and MUSE qualification pending.

Version
1.3.2
License
unset
Stars
0

scripts/check-stock.py

#!/usr/bin/env python3
"""Fetch supplier inventory evidence; never equate HTTP failure with stock.

JLC search reports cached supplier inventory. Positive values are discovery
evidence only; final stock must be checked at the distributor before ordering.
"""
import concurrent.futures
import collections
import datetime
import json
import pathlib
import urllib.parse
import urllib.request

root = pathlib.Path(__file__).resolve().parents[1]
catalog = json.loads((root / "docs/component-catalog.json").read_text())
mpns = sorted({p["mpn"] for p in catalog if p.get("mpn")})

def lookup(item):
    mpn, supplier = item
    if supplier == "jlcpcb":
        url = "https://jlcsearch.tscircuit.com/api/search?" + urllib.parse.urlencode({"q":mpn,"limit":20})
    else:
        url = f"https://{supplier}search.tscircuit.com/components/list?" + urllib.parse.urlencode({"search":mpn,"json":"true"})
    record = {"mpn":mpn,"url":url,"checked_at":datetime.datetime.now(datetime.timezone.utc).isoformat(),
              "supplier":supplier,"status":"unverified","stock":None,"inventory_kind":"supplier search snapshot"}
    try:
        request = urllib.request.Request(url,headers={"User-Agent":"Muse-Voice-Remote-sourcing/1.0"})
        with urllib.request.urlopen(request,timeout=20) as response:
            data = json.load(response)
        # Match exact orderable MPN, not a similar search hit.
        matches = [p for p in data.get("components",[]) if str(p.get("mfr","")).upper()==mpn.upper()]
        record["meta"] = data.get("meta",{})
        record["exact_matches"] = matches
        quantities = [p["stock"] for p in matches if isinstance(p.get("stock"),(int,float))]
        if quantities:
            record["stock"] = max(quantities)
            record["status"] = "cached-stock-positive" if record["stock"]>0 else "cached-out-of-stock"
        elif not matches:
            record["status"] = "no-exact-match"
    except Exception as error:
        record["error"] = str(error)
    return record

with concurrent.futures.ThreadPoolExecutor(max_workers=6) as pool:
    evidence = list(pool.map(lookup,[(mpn,supplier) for mpn in mpns for supplier in ["digikey","mouser","jlcpcb"]]))
quantities = collections.Counter(p["mpn"] for p in catalog if p.get("mpn"))
positive_mpns = {p["mpn"] for p in evidence if p.get("stock") and p["stock"]>=quantities[p["mpn"]]
                 and not p.get("meta",{}).get("stale",False)
                 and any(not m.get("discontinued",False) and not m.get("marketplace",False) for m in p.get("exact_matches",[]))}
result = {"checked_at":datetime.datetime.now(datetime.timezone.utc).isoformat(),
          "all_parts_verified_in_stock":False,
          "all_populated_pcb_parts_have_positive_snapshot":set(mpns)<=positive_mpns,
          "unique_pcb_mpns":len(mpns),"populated_pcb_components":sum(quantities.values()),
          "required_quantities":dict(quantities),
          "note":"Cached inventory is not a purchase guarantee. Actual ER-TFT028A2-4 panel availability, protected pack/NTC and off-board speaker/motor/antenna qualification remain outside this populated PCB catalog.",
          "parts":evidence}
(root / "docs/stock-evidence.json").write_text(json.dumps(result,indent=2)+"\n")
print(json.dumps({"queried_supplier_records":len(evidence),"unique_pcb_mpns":len(mpns),"positive_mpns":len(positive_mpns),"cached_positive":sum(p["status"]=="cached-stock-positive" for p in evidence),
                  "unverified":sum(p["status"]=="unverified" for p in evidence)},indent=2))