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.
This commit is contained in:
2026-07-09 18:06:39 +07:00
parent 6140e1d4c9
commit 7ef2960254
2 changed files with 49 additions and 31 deletions
+7 -3
View File
@@ -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
+42 -28
View File
@@ -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: