diff --git a/OCTO/octo_1.xlsx b/OCTO/octo_1.xlsx new file mode 100644 index 0000000..d19cc31 Binary files /dev/null and b/OCTO/octo_1.xlsx differ diff --git a/OCTO/octo_2.xlsx b/OCTO/octo_2.xlsx new file mode 100644 index 0000000..e7c8b2c Binary files /dev/null and b/OCTO/octo_2.xlsx differ diff --git a/OCTO/octo_3.xlsx b/OCTO/octo_3.xlsx new file mode 100644 index 0000000..9c6da26 Binary files /dev/null and b/OCTO/octo_3.xlsx differ diff --git a/OUTPUT/bom_parts_1_of_3.xlsx b/OUTPUT/bom_parts_1_of_3.xlsx new file mode 100644 index 0000000..77fc325 Binary files /dev/null and b/OUTPUT/bom_parts_1_of_3.xlsx differ diff --git a/OUTPUT/bom_parts_2_of_3.xlsx b/OUTPUT/bom_parts_2_of_3.xlsx new file mode 100644 index 0000000..42f4a9c Binary files /dev/null and b/OUTPUT/bom_parts_2_of_3.xlsx differ diff --git a/OUTPUT/bom_parts_3_of_3.xlsx b/OUTPUT/bom_parts_3_of_3.xlsx new file mode 100644 index 0000000..fbb8f47 Binary files /dev/null and b/OUTPUT/bom_parts_3_of_3.xlsx differ diff --git a/octo_fill.py b/octo_fill.py index 5f57426..4f82a08 100644 --- a/octo_fill.py +++ b/octo_fill.py @@ -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)