Phase 1: replace legacy spreadsheet with validated, version-controlled inventory - #1
Merged
Merged
Conversation
Plans the replacement for the spreadsheet in legacy/: git-backed CSVs as the source of truth, a static search site for reading, issue-form-driven PRs for writing, and an optional Claude vision pipeline for photo intake. Records two settled decisions: - Public repo. This puts the entire privacy burden on the photo path, so PHOTO_POLICY.md ships now rather than with Phase 4, and three guardrails become mandatory: strip all EXIF, review the image itself and not just the items derived from it, and allow rejection on privacy grounds alone. A merged photo is permanent -- it survives in history, forks, and scrapes -- so pre-merge review is the only real control. - @sr320 and @kubu4 both maintain, via CODEOWNERS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Converts legacy/Roberts Lab Inventory.xlsx into data/items.csv (499 items),
data/locations.csv (173 locations), and data/review_queue.csv (16 rows), with
CI validation gating every future edit.
The migration never guesses silently. Where the legacy data is ambiguous the
row goes to review_queue.csv with a stated reason instead of into items.csv:
- Semicolons are a reliable delimiter here and are split automatically.
Commas are not -- "Microcentrifuge tubes, conical screw top tubes, screw
caps" is three items but "DNeasy 96 blood and tissue kit (4) & (50)" is one
-- so comma-only cells stay whole and carry a suggested split.
- Five rows on the drawers/cabinets sheet are coarse restatements of the
dedicated Rm 213 shelf sheet. Importing them would have produced ~40
duplicate items, so they go to the queue for reconciliation.
- The date column held commentary ("Very likely all expired", "n.d.",
"08/2010 from FHL"). That text moves to notes rather than being dropped,
and partial dates keep their real precision (08/2010 -> 2010-08) instead of
having a day invented for them.
Every migrated row carries status=unverified and last_verified=2021-08-29,
the spreadsheet's own claim about when it was last touched. Five years of
staleness becomes a queryable field that validate.py reports on every run,
rather than a caveat someone has to remember. The source column (legacy-xlsx
| manual | photo-llm) makes provenance permanent, so a later audit can always
ask which rows a model wrote.
location_id is kept short and stable (209-CAB-01, 213-F20-S02-D08) because it
becomes a URL fragment and a QR-sticker payload; deriving ids from prose
labels produced 60-character identifiers. Prose-numbered places get an X01
sequence so they can never collide with numerically-numbered ones.
Categories include glassware and office alongside the lab ones -- without
them roughly half the inventory landed in `other`, which would make the
planned category filter useless.
validate.py is stdlib-only so there is nothing to install and nothing to
break when a dependency moves. Verified against nine injected faults
(duplicate ids, dangling location refs, non-ISO dates, self-parenting,
unknown category, photo-llm row missing photo_id): all caught, exit 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scope is five rooms: 209, 213, 228, 230, and the -80C room. Declaring them in
data/rooms.csv makes scope an explicit, enforced fact rather than a side
effect of whatever the legacy spreadsheet happened to contain -- validate.py
now fails if a location names an undeclared room.
Room 228 appears nowhere in the legacy spreadsheet, so there is nothing to
migrate for it and it starts with zero locations; it gets populated by walking
in with a phone. Rather than let an in-scope-but-unrecorded room quietly be
forgotten, validate.py emits a standing warning on every run until it has
locations.
Coverage is uneven and now visible: 209=70, 213=99, 228=0, 230=2, M80=2.
Room labels are deliberately generic ("Room 228") rather than guessing at a
purpose the spreadsheet never stated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the spreadsheet in
legacy/with version-controlled, validated data, and records the plan for the rest of the system.Two commits: the plan and governance docs, then the migration tooling and generated data.
What's here
PLAN.mdPHOTO_POLICY.md.github/CODEOWNERS.github/workflows/validate.ymldata/scripts/migrate_legacy.pyscripts/validate.pydata/*.csvDecisions this encodes
Public repo. This puts the whole privacy burden on the photo path, so
PHOTO_POLICY.mdships now rather than with Phase 4, and three guardrails are mandatory rather than nice-to-have: strip all EXIF, review the image itself and not just the items derived from it, and allow rejection on privacy grounds alone. The reasoning worth agreeing on before Phase 4: a merged photo is permanent — it survives in history, in every fork, and in whatever already scraped it — so pre-merge review is the only control that actually works.Both maintainers can merge. Enough that nobody is a single point of failure, few enough that review actually happens.
Migration judgment calls
The migration never guesses silently. Ambiguous legacy rows go to
data/review_queue.csvwith a stated reason instead of intoitems.csv:Microcentrifuge tubes, conical screw top tubes, screw capsis three items, butDNeasy 96 blood and tissue kit (4) & (50)is one. Comma-only cells stay whole and carry a suggested split for a human to accept or reject.Very likely all expired,n.d.,08/2010 from FHL. That text moves tonotesrather than being discarded, and partial dates keep their real precision (08/2010→2010-08) instead of having a day invented for them.The staleness is now data
Every migrated row carries
status=unverifiedandlast_verified=2021-08-29— the spreadsheet's own claim about when it was last touched.validate.pyreports it on every run:Five years of drift is a queryable field instead of a caveat someone has to remember. The
sourcecolumn (legacy-xlsx|manual|photo-llm) makes provenance permanent, so a later audit can always ask which rows a model wrote.Two schema additions worth a look
glasswareandofficecategories alongside the lab ones. Without them roughly half the inventory landed inother, which would make the planned category filter useless. Now 25%, which is honest for a drawer containing a Nintendo.ownercolumn, since the legacy storage bins tag contents- Goetz.Say so if you'd rather not track either.
Verification
validate.pyis stdlib-only, so there's nothing to install and nothing to break when a dependency moves. Tested against nine injected faults — duplicate item ids, dangling location refs, non-ISO dates, self-parenting locations, unknown category, and aphoto-llmrow with nophoto_id— all nine caught, exit 1.location_idis deliberately short and stable (209-CAB-01,213-F20-S02-D08) because it becomes a URL fragment and a QR-sticker payload; deriving ids from prose labels produced 60-character identifiers. Prose-numbered places get anX01sequence so they can never collide with numerically-numbered ones.Open questions
Next
Phase 2 — search UI and QR stickers. Deliberately before the add-item forms: search value has to land before anyone is asked to enter data, or this goes the way of the spreadsheet.
🤖 Generated with Claude Code