This commit is contained in:
David Rice
2026-04-09 15:27:23 +01:00
parent a6c417569b
commit e667fbe56c

View File

@@ -100,9 +100,11 @@ query GetSheetComponents($projectId: ID!, $cursor: String) {
component { component {
name name
description description
manufacturerParts { details {
partNumber parameters {
companyName name
value
}
} }
} }
} }
@@ -256,6 +258,19 @@ def _prefix_match(a: str, b: str) -> bool:
return bool(na and nb and (na == nb or na.startswith(nb) or nb.startswith(na))) return bool(na and nb and (na == nb or na.startswith(nb) or nb.startswith(na)))
def _mpn_match(a: str, b: str) -> bool:
"""True if MPNs match, accounting for packaging suffix (last character) differences."""
na, nb = normalise(a), normalise(b)
if not na or not nb:
return False
if na == nb or na.startswith(nb) or nb.startswith(na):
return True
# Strip last character (e.g. R=reel, T=tape) and retry
if len(na) > 1 and len(nb) > 1 and na[:-1] == nb[:-1]:
return True
return False
def load_bom_data(bom_path: str) -> list | None: def load_bom_data(bom_path: str) -> list | None:
"""Load BOM xlsx and return a list of entry dicts. """Load BOM xlsx and return a list of entry dicts.
Returns None if no 'bom' tab is found.""" Returns None if no 'bom' tab is found."""
@@ -297,7 +312,7 @@ def _find_bom_match(bom_data: list, mfr_name: str, mpn: str) -> dict | None:
"""Return the highest unit cost BOM entry that prefix-matches both manufacturer and MPN.""" """Return the highest unit cost BOM entry that prefix-matches both manufacturer and MPN."""
candidates = [ candidates = [
e for e in bom_data e for e in bom_data
if _prefix_match(e["manufacturer"], mfr_name) and _prefix_match(e["mpn"], mpn) if _prefix_match(e["manufacturer"], mfr_name) and _mpn_match(e["mpn"], mpn)
] ]
return max(candidates, key=lambda e: e["unit_cost"]) if candidates else None return max(candidates, key=lambda e: e["unit_cost"]) if candidates else None
@@ -331,18 +346,19 @@ def export_to_xlsx(project_name: str, variant_name: str, schematics: list, bom_d
nodes = (sheet.get("designItems") or {}).get("nodes") or [] nodes = (sheet.get("designItems") or {}).get("nodes") or []
for node in sorted(nodes, key=lambda n: n.get("designator") or ""): for node in sorted(nodes, key=lambda n: n.get("designator") or ""):
comp = node.get("component") or {} comp = node.get("component") or {}
mfr_parts = comp.get("manufacturerParts") or [] params = {p["name"]: (p.get("value") or "").strip()
for p in (comp.get("details") or {}).get("parameters") or []
if p.get("name")}
# Collect up to MAX_MFR_PARTS manufacturer/MPN pairs # Collect up to MAX_MFR_PARTS manufacturer/MPN pairs from named parameters
pairs = [] pairs = []
for mp in mfr_parts[:MAX_MFR_PARTS]: for n in range(1, MAX_MFR_PARTS + 1):
mfr_name = mp.get("companyName") or "-" mfr_name = params.get(f"Manufacturer {n}", "").strip()
mpn = mp.get("partNumber") or "-" mpn = params.get(f"Manufacturer Part Number {n}", "").strip()
pairs.append((mfr_name, mpn)) if not mfr_name or not mpn or mfr_name == "-" or mpn == "-":
# Pad with '-' if fewer than MAX_MFR_PARTS
while len(pairs) < MAX_MFR_PARTS:
pairs.append(("-", "-")) pairs.append(("-", "-"))
else:
pairs.append((mfr_name, mpn))
flat_pairs = [v for pair in pairs for v in pair] flat_pairs = [v for pair in pairs for v in pair]
row = [node.get("designator") or "", comp.get("name") or "", comp.get("description") or ""] + flat_pairs row = [node.get("designator") or "", comp.get("name") or "", comp.get("description") or ""] + flat_pairs