Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Inventory data and tooling require review from a maintainer before merge.
# Enforced by branch protection on `main` (Settings > Branches > require review
# from Code Owners). Either maintainer can approve and merge.
* @sr320 @kubu4

# Photos carry the most risk in a public repo -- see PHOTO_POLICY.md. Reviewers
# are checking the image itself for accidentally-captured notebook pages,
# screens, and sample manifests, not just the item list derived from it.
/photos/ @sr320 @kubu4
/descriptions/ @sr320 @kubu4
49 changes: 49 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: validate inventory data

on:
pull_request:
paths:
- "data/**"
- "scripts/**"
- ".github/workflows/validate.yml"
push:
branches: [main]
paths:
- "data/**"
- "scripts/**"
workflow_dispatch:

permissions:
contents: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

# validate.py is stdlib-only on purpose, so there is nothing to install
# and nothing to break when a dependency moves.
- name: Validate data/*.csv
run: python3 scripts/validate.py

- name: Check migration is reproducible
if: github.event_name == 'pull_request'
run: |
pip install -q -r requirements.txt
python3 scripts/migrate_legacy.py --out /tmp/regen > /dev/null
# The legacy spreadsheet is frozen, so re-running the migration must
# reproduce byte-identical output. A diff here means either the
# migration script changed behaviour (re-run it and commit the
# result) or data/ was hand-edited in a way that is fine -- in which
# case this check should be dropped once real edits start landing.
if ! diff -q /tmp/regen/locations.csv data/locations.csv; then
echo "::warning::locations.csv differs from a fresh migration"
fi
if ! diff -q /tmp/regen/items.csv data/items.csv; then
echo "::warning::items.csv differs from a fresh migration (expected once hand edits land)"
fi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
.claude/settings.local.json
32 changes: 32 additions & 0 deletions PHOTO_POLICY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Photo policy

**This repository is public.** Anything committed here is world-readable, permanently, and will be scraped and mirrored within hours. Photos are the only part of this inventory that can leak something you didn't intend, so this policy is the whole privacy control.

## Before you take the photo

Frame the shelf, drawer, or bin — nothing else. Check the edges of the frame for:

- **Lab notebook pages** — open notebooks, loose protocol pages, printouts
- **Screens** — monitors, laptops, tablets, instrument displays, phones
- **Whiteboards and posted notes** — including sticky notes on cabinet doors
- **People** — hands are fine, faces and name badges are not
- **Sample manifests** — printed sample lists, freezer maps, plate maps taped to equipment
- **Anything with a person's name, a subject ID, or an unpublished result on it**

Tube labels and reagent bottle labels are fine and are the point of the exercise. A printed sheet listing 96 sample IDs is not.

## What happens on ingest

The intake pipeline strips **all** EXIF metadata (not just GPS) before anything is committed. Do not rely on that for anything but metadata — it cannot see what's in the frame.

## Review is the only real control

A photo gets reviewed by [@sr320](https://github.com/sr320) or [@kubu4](https://github.com/kubu4) before merge. Reviewers check **the image itself**, not just the item list the model derived from it. A photo can be rejected on privacy grounds alone even if the extracted items are perfect.

**This must happen before merge, because after merge it is too late.** Deleting a photo in a later commit does not remove it — it stays in git history, it stays in every clone and fork, and it has already been indexed. Removing it for real means rewriting history and force-pushing, which breaks everyone's clone and still doesn't recall what was scraped.

If you realize a merged photo shouldn't be public: say so immediately in an issue and tag both maintainers. Don't quietly delete the file — that leaves it in history while making it look handled.

## If you're unsure

Don't commit it. Ask in the issue first, or just retake the photo with the questionable thing out of frame. Retaking a photo costs 30 seconds.
281 changes: 281 additions & 0 deletions PLAN.md

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Roberts Lab Inventory

Searchable, version-controlled inventory of lab reagents, kits, consumables, and equipment — replacing the spreadsheet in [`legacy/`](legacy/).

Development plan and architecture: **[PLAN.md](PLAN.md)** · Photo rules (public repo): **[PHOTO_POLICY.md](PHOTO_POLICY.md)**

## Status

**Phase 1 complete** — the spreadsheet has been migrated to canonical CSVs with CI validation. Search UI, add-item forms, and photo intake are still to come; see [PLAN.md §5](PLAN.md#5-phases).

| | |
|---|---|
| Items | 499 |
| Locations | 173 |
| Verified since 2021 | **0%** — everything carries `status: unverified` |
| Rows needing a human pass | 16, in [`data/review_queue.csv`](data/review_queue.csv) |

## The data

| File | What it is |
|---|---|
| [`data/items.csv`](data/items.csv) | One row per item. The inventory. |
| [`data/locations.csv`](data/locations.csv) | Controlled vocabulary of physical places, nested via `parent_id`. |
| [`data/rooms.csv`](data/rooms.csv) | The declared scope: rooms 209, 213, 228, 230, and the -80˚C room. |
| [`data/review_queue.csv`](data/review_queue.csv) | Legacy rows too ambiguous to migrate automatically. Each has a stated reason. |

Coverage today is uneven — `209=70, 213=99, 228=0, 230=2, M80=2` locations. **Room 228 is in scope but appears nowhere in the legacy spreadsheet**, so it has nothing to migrate and needs an inventory pass; `validate.py` warns about it on every run until it does.

`location_id` is the stable key — `209-CAB-01`, `213-F20-S02-D08`, `209-FRIDGE-DOOR-SHELF-1`. It's built to be short because it becomes a URL fragment and a QR-sticker payload.

Two fields carry more weight than they look like they do:

- **`last_verified`** — every migrated row says `2021-08-29`, because that's what the spreadsheet's own sheet names claim. This makes five years of staleness a queryable fact instead of a caveat.
- **`source`** — `legacy-xlsx`, `manual`, or `photo-llm`. Permanent provenance, so an audit can always ask which rows a model wrote.

## Working with it

```bash
pip install -r requirements.txt
```

Validate before opening a PR (CI runs the same thing):

```bash
python3 scripts/validate.py
```

Regenerate the CSVs from the frozen spreadsheet:

```bash
python3 scripts/migrate_legacy.py
```

Note that re-running the migration **overwrites `data/`**, discarding hand edits. It's a one-time tool kept for reproducibility and for re-tuning the item categorizer; once real edits start landing, `items.csv` is the source of truth and the spreadsheet is history.

## Conventions

- Dates are ISO 8601, and partial dates are allowed where that's the honest precision: `2019`, `2019-11`, `2019-11-27`.
- `category`, `status`, `kind`, and `source` are closed sets, enforced in CI. Adding a value means editing `scripts/validate.py` in the same PR.
- `quantity` is a number or empty. Ranges, guesses, and "about half a box" go in `notes`.
- Don't block an entry on details you don't have. A name and a location is a useful row; the rest can be filled in later.

## Maintainers

[@sr320](https://github.com/sr320) and [@kubu4](https://github.com/kubu4) — either can review and merge.
Loading
Loading