From 9fdb12b53348274020b789b4adbd462f64a7575a Mon Sep 17 00:00:00 2001 From: grabowski Date: Thu, 9 Jul 2026 15:38:53 +0700 Subject: [PATCH] Canonicalize brand names; slim the scaffold to rated specs Manufacturer input maps to one canonical spelling (seed list plus whatever the database and cache already use), so Philips is never philips/PHILIPS. Identity fields no longer duplicate into the device page specs (the site renders them from metrics.json); the weight scan ignores index.md so a fresh run starts at weight 1. --- lamp_station.py | 50 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/lamp_station.py b/lamp_station.py index 04ec45e..fdefeb0 100644 --- a/lamp_station.py +++ b/lamp_station.py @@ -98,6 +98,32 @@ def next_lamp_id(data_repo): return f"L{max(used, default=0) + 1:04d}" +# Canonical spellings for brands; extended at runtime by whatever is already +# in the database and the cache, so the first accepted spelling of a new brand +# becomes its canonical form for every later scan. +BRAND_SEED = [ + "Philips", "OSRAM", "LAMPTAN", "TKL", "ST", "IKEA", "Panasonic", + "Toshiba", "GE", "Sylvania", "Xiaomi", "Opple", "EVE", +] + + +def canonical_brand(name, lamps): + """Map a manufacturer string to its canonical capitalization.""" + name = " ".join(name.split()) + if not name: + return name + brands = {b.lower(): b for b in BRAND_SEED} + for entry in load_cache().values(): + b = entry.get("manufacturer", "") + if b: + brands.setdefault(b.lower(), b) + for m in lamps.values(): + b = m.get("manufacturer", "") + if b: + brands[b.lower()] = b # the database is the strongest source + return brands.get(name.lower(), name) + + def load_cache(): try: return json.loads(CACHE_FILE.read_text()) @@ -185,21 +211,16 @@ def scaffold_device_page(web_repo, lamp_id, meta): page = pages / f"{lamp_id}.md" if page.exists(): return - weights = [int(m.group(1)) for f in pages.glob("*.md") + weights = [int(m.group(1)) for f in pages.glob("*.md") if f.name != "index.md" for m in [re.search(r"^weight:\s*(\d+)", f.read_text(), re.M)] if m] title = meta["model"] or lamp_id if meta["variant"]: title = f"{title} ({meta['variant']})" + # Identity fields (ID, EAN, type, dimmable, variant) live in metrics.json, + # which the detail page renders directly — only human-editable extras like + # the rated values go into the page's specs front matter. rated = meta.get("rated", {}) - specs = [("ID", lamp_id)] - if meta["ean"]: - specs.append(("EAN", meta["ean"])) - if meta.get("type"): - specs.append(("Type", meta["type"].upper() if meta["type"] in ("led", "cfl") else meta["type"].capitalize())) - if meta.get("dimmable") is not None: - specs.append(("Dimmable", "Yes" if meta["dimmable"] else "No")) - if meta["variant"]: - specs.append(("Variant", meta["variant"])) + specs = [] if "Power_W" in rated: power = f"{rated['Power_W']:g} W" if "W_equiv" in rated: @@ -221,7 +242,8 @@ def scaffold_device_page(web_repo, lamp_id, meta): lines += [ f'description: "Household {type_word} lamp, measured in our integrating sphere."', f'csv: "/data/comparisons/lamps/{lamp_id}/spd.csv"', - "specs:", + # a bare "specs:" would parse as YAML null and fail the site's schema + *(["specs:"] if specs else []), *[f' - {{ label: "{k}", value: "{v}" }}' for k, v in specs], f"weight: {max(weights, default=0) + 1}", "---", @@ -269,7 +291,11 @@ def measure_one(ean, args, data_repo): else: manufacturer = model = "" - manufacturer = prompt("Manufacturer", manufacturer) + manufacturer = prompt("Manufacturer", canonical_brand(manufacturer, lamps)) + canon = canonical_brand(manufacturer, lamps) + if canon != manufacturer: + print(f" Using canonical brand name: {canon}") + manufacturer = canon model = prompt("Model", model) lamp_type = prompt("Type (led / halogen / cfl / incandescent / ...)", previous.get("type", "led")).lower().strip()