This commit is contained in:
David Rice
2026-04-30 13:52:15 +01:00
parent 70b2b6acc3
commit e14d79a0a3
7 changed files with 34 additions and 21 deletions

View File

@@ -39,8 +39,8 @@ def _sfp_patched(self, **kw):
_SFP.__init__ = _sfp_patched
# ──────────────────────────────────────────────────────────────────────────────
BOM_DIR = Path("BoM")
OCTO_FILE = Path("OCTO/octo.xlsx")
BOM_DIR = Path("BoM")
OCTO_DIR = Path("OCTO")
COST_HEADER = "Unit Cost EUR @1000"
SKIP_MPNS = {
@@ -59,26 +59,18 @@ log = logging.getLogger(__name__)
# ── Load Octopart data ─────────────────────────────────────────────────────────
def load_octo(path: Path) -> tuple[dict[tuple[str,str], float], dict[str, float]]:
"""
Returns:
exact_map (manufacturer_lower, mpn_lower) → lowest unit price
mpn_map mpn_lower → lowest unit price (fallback)
"""
log.info(f"Reading Octopart data from {path}")
def _load_single(path: Path, exact_map: dict, mpn_map: dict) -> int:
"""Load one Octopart xlsx into the shared maps. Returns number of entries added."""
wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
exact_map: dict[tuple[str, str], float] = {}
mpn_map: dict[str, float] = {}
added = 0
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
headers: Optional[dict[str, int]] = None # col_name → 0-based index
headers: Optional[dict[str, int]] = None
for row in ws.iter_rows(values_only=True):
row = list(row)
if headers is None:
# Find header row
row_lower = [str(v).strip().lower() if v is not None else "" for v in row]
if "original part" in row_lower and "original manufacturer" in row_lower:
headers = {str(row[i]).strip(): i for i in range(len(row)) if row[i] is not None}
@@ -87,15 +79,15 @@ def load_octo(path: Path) -> tuple[dict[tuple[str,str], float], dict[str, float]
if not any(row):
continue
mpn_col = _find_col(headers, "original part")
mfr_col = _find_col(headers, "original manufacturer")
mpn_col = _find_col(headers, "original part")
mfr_col = _find_col(headers, "original manufacturer")
price_col = _find_col(headers, "unit price")
if mpn_col is None or price_col is None:
continue
mpn = str(row[mpn_col]).strip() if mpn_col < len(row) and row[mpn_col] is not None else ""
mfr = str(row[mfr_col]).strip() if mfr_col is not None and mfr_col < len(row) and row[mfr_col] is not None else ""
mpn = str(row[mpn_col]).strip() if mpn_col < len(row) and row[mpn_col] is not None else ""
mfr = str(row[mfr_col]).strip() if mfr_col is not None and mfr_col < len(row) and row[mfr_col] is not None else ""
price_raw = row[price_col] if price_col < len(row) else None
if not mpn or mpn.lower() in SKIP_MPNS:
@@ -112,14 +104,35 @@ def load_octo(path: Path) -> tuple[dict[tuple[str,str], float], dict[str, float]
key = (mfr.lower(), mpn.lower())
if key not in exact_map or price < exact_map[key]:
exact_map[key] = price
added += 1
mpn_k = mpn.lower()
if mpn_k not in mpn_map or price < mpn_map[mpn_k]:
mpn_map[mpn_k] = price
wb.close()
return added
log.info(f" Loaded {len(exact_map)} unique (manufacturer, part) entries from Octopart")
def load_octo(octo_dir: Path) -> tuple[dict[tuple[str, str], float], dict[str, float]]:
"""
Reads every .xlsx file in octo_dir into shared lookup maps.
exact_map (manufacturer_lower, mpn_lower) → lowest unit price
mpn_map mpn_lower → lowest unit price (fallback)
"""
files = sorted(octo_dir.glob("*.xlsx"))
if not files:
log.error(f"No .xlsx files found in {octo_dir}/")
sys.exit(1)
exact_map: dict[tuple[str, str], float] = {}
mpn_map: dict[str, float] = {}
for f in files:
added = _load_single(f, exact_map, mpn_map)
log.info(f" {f.name}: {added} entries loaded")
log.info(f"Octopart total: {len(exact_map)} unique (manufacturer, part) entries")
return exact_map, mpn_map
@@ -311,10 +324,10 @@ def fill_boms(
# ── Main ───────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
for p in (BOM_DIR, OCTO_FILE):
for p in (BOM_DIR, OCTO_DIR):
if not p.exists():
log.error(f"Not found: {p}")
sys.exit(1)
exact_map, mpn_map = load_octo(OCTO_FILE)
exact_map, mpn_map = load_octo(OCTO_DIR)
fill_boms(BOM_DIR, exact_map, mpn_map)