From 7ef2960254389525d319a8c6cf19967016a1d680 Mon Sep 17 00:00:00 2001 From: grabowski Date: Thu, 9 Jul 2026 18:06:39 +0700 Subject: [PATCH] Station flow: ID first, EAN optional, re-measure by ID Every run opens with the freshly allocated L#### and then asks for the EAN, which Enter skips. Typing an existing ID re-measures that lamp with all identification and rated values pre-filled, which also rewrites old bundles in the current metrics format. Unused IDs are never burned since numbering derives from written bundles. --- README.md | 10 ++++--- lamp_station.py | 70 +++++++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index d2c4b4e..1ab4967 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,13 @@ end to end. Every lamp gets an internal ID (L0001, L0002, ...) as primary key; the EAN barcode is optional metadata, so one barcode can cover daylight/warm white variants (distinct IDs + variant field) and unbranded lamps work too. -Per lamp: scan the box (or 'n' for no barcode), the EAN is checksum-validated -and resolved to manufacturer/model (previously measured lamps, local cache, -then upcitemdb.com; GS1-restricted 20-29 barcodes are flagged as +Every run opens with a freshly allocated ID (the ID only becomes real once a +bundle is written, so aborting never burns a number). Then: scan the box's EAN +(optional, Enter skips it), or type an existing ID like L0004 to re-measure +that lamp with everything pre-filled, which also rewrites its bundle in the +current metrics format. Scanned EANs are checksum-validated and resolved to +manufacturer/model (previously measured lamps, local cache, then +upcitemdb.com; GS1-restricted 20-29 barcodes are flagged as retailer-internal), lamps already measured under the same EAN are offered for re-measurement, you confirm the identification and type the ADVERTISED values from the packaging (flux, CCT, power, CRI, lifetime, equivalent W -> stored diff --git a/lamp_station.py b/lamp_station.py index 6de0483..9e49232 100644 --- a/lamp_station.py +++ b/lamp_station.py @@ -7,11 +7,15 @@ EAN barcode is optional metadata. This keeps lamps distinct when one barcode covers several versions (daylight / warm white in the same box art) and supports unbranded lamps without any barcode. - 1. Scan the box with a keyboard-wedge barcode scanner (types EAN + Enter), - or type 'n' for a lamp without a barcode. - 2. The EAN is checksum-validated. Lamps already measured with the same - EAN are listed: pick one to re-measure, or continue as a new lamp - (that is how same-EAN variants get their own IDs). Identification is + 1. Every run opens with a freshly allocated ID (L0012, ...). The ID only + becomes real once the bundle is written, so aborting never burns one. + 2. Scan the box with a keyboard-wedge barcode scanner (types EAN + + Enter), press Enter for a lamp without a barcode, or type an existing + ID (e.g. L0004) to re-measure that lamp with everything pre-filled, + which also rewrites its bundle in the current metrics format. Scanned + EANs are checksum-validated; lamps already measured with the same EAN + are listed (pick one to re-measure, or continue as the new lamp: that + is how same-EAN variants get their own IDs). Identification is suggested from previous lamps, a local cache, then upcitemdb.com (best effort; GS1-restricted 20-29 barcodes skip the online lookup). 3. Confirm or edit manufacturer / model / variant / notes, then type the @@ -203,31 +207,32 @@ def commit_bundle(data_repo, lamp_id, label, existed, push): print("Pushed.") -def measure_one(ean, args, data_repo): - lamps = lamp_index(data_repo) - - # Same-EAN lamps already in the database: re-measure or new variant. - lamp_id, previous = None, {} - if ean: +def measure_one(new_id, ean, lamps, args, data_repo, existing_id=None): + # The run already owns its fresh ID (new_id). Typing an existing ID at + # the scan prompt re-measures that lamp instead (all its identification + # pre-filled, bundle rewritten in the current format); an EAN may also + # redirect to a re-measure when it matches an existing lamp. + lamp_id, previous = new_id, {} + if existing_id: + lamp_id = existing_id + previous = lamps[existing_id] + ean = previous.get("ean", "") + print(f" Re-measuring {lamp_id}" + (f" (EAN {ean})" if ean else "")) + elif ean: same = {i: m for i, m in lamps.items() if m.get("ean") == ean} if same: print(" Lamps already measured with this EAN:") for i, m in same.items(): variant = f", {m['variant']}" if m.get("variant") else "" print(f" {i}: {m.get('manufacturer', '')} {m.get('model', '')}{variant}") - choice = prompt("Re-measure one of these (type its ID) or Enter for a NEW lamp/variant").strip() + choice = prompt(f"Re-measure one of these (type its ID) or Enter to continue as {new_id}").strip() if choice: if choice.upper() not in same: - print(f" Unknown ID {choice!r}, aborting this scan.") + print(f" Unknown ID {choice!r}, aborting this run.") return lamp_id = choice.upper() previous = same[lamp_id] - - if lamp_id is None: - lamp_id = next_lamp_id(data_repo) - print(f" New lamp: {lamp_id}" + (f" (EAN {ean})" if ean else " (no barcode)")) - else: - print(f" Re-measuring {lamp_id}") + print(f" Re-measuring {lamp_id}") if previous: manufacturer, model = previous.get("manufacturer", ""), previous.get("model", "") @@ -323,25 +328,34 @@ def main(): print(f"ERROR: {data_repo} is not a git checkout of comparison-data.") sys.exit(1) - print("Lamp station ready. Scan a barcode, 'n' for a lamp without one, 'q' to quit.") + print("Lamp station ready. Every run starts a new lamp ID; the EAN is optional") + print("(scan it, or press Enter to skip). 'q' quits.") while True: + # A fresh ID opens every run. It only becomes real once a bundle is + # written, so quitting or re-measuring never burns a number. + lamps = lamp_index(data_repo) + new_id = next_lamp_id(data_repo) + print(f"\n--- New lamp {new_id} ---") try: - code = input("\nScan barcode: ").strip() + code = input("Scan EAN (Enter to skip, L#### re-measures an existing lamp, q quits): ").strip() except (EOFError, KeyboardInterrupt): break - if code == "": - continue if code.lower() in ("q", "quit", "exit"): break - if code.lower() in ("n", "none"): - ean = "" - else: + existing_id = None + ean = "" + if ID_RE.match(code.upper()): + existing_id = code.upper() + if existing_id not in lamps: + print(f" No lamp {existing_id} in the database.") + continue + elif code and code.lower() not in ("n", "none"): ean = ean_normalize(code) if not ean: - print(f" Not a valid EAN-13/UPC-A: {code!r}") + print(f" Not a valid EAN-13/UPC-A: {code!r} (Enter would skip the EAN)") continue try: - measure_one(ean, args, data_repo) + measure_one(new_id, ean, lamps, args, data_repo, existing_id=existing_id) except KeyboardInterrupt: print("\n Aborted; lamp skipped. (PSU is switched off by the driver.)") except (subprocess.CalledProcessError, SystemExit) as e: