Data model and storage¶
Everything lives in one SQLite file, inventory.sqlite3 under DATA_DIR, beside the spend
ledger model_api_spend.jsonl. The schema is versioned; the service migrates forward at
startup and refuses to open a file written by a newer version.
Tables¶
| Table | Since | Holds |
|---|---|---|
items |
v1 | one row per item: name, item_type, condition, faults, tests_done, accessories, location, notes, intent, availability, parent_id for a separated part, profile (v2), version, times, deleted_at (hidden, never erased) |
assertions |
v1 | every value ever recorded for a field of an item: value, unit, provenance, confidence, source, raw, review, at; the item's current fields are read off these (evidence model) |
events |
v1 | the item's history: actor, change, the version it produced (Events and history) |
measurements |
v2 | a capture per feature: the result as JSON (method, value, uncertainty, checks, marks), the photo it was made on, the assertion_id it proposed |
drafts |
v3 | listing drafts per item and mode, each with the item_version it was written from, its content and its checklist state |
price_guidance |
v4 | each guidance written, with its item_version and content (the four quantities, comparables, sources, strategies) |
price_reports |
v4 | sales the owner reports: price, currency, sold_on, note; never merged with what a search finds |
searches |
v5 | missing-item requests: need_item_id or a profile, title, content (spec, radius, prices), status, handoff_url |
candidates |
v5 | listings brought back: source_kind, observed_on, state, content (the reading, the assessment, the questions) |
preferences |
v6 | one row: the owner's profile and what they want from a build; skills in it is a list of {skill, level}, and a plain name kept before the ratings reads as some |
projects |
v6 | template, title, status, version, content (requirements, result, effort, spend, unknowns, steps, progress, the outcome) |
allocations |
v6 | an item on a project for a requirement, committed or exploratory, with why |
packs |
v7 | build packs: version per project, digest of the inputs, the markdown |
uses |
v11 | what an item is good for, one row per asking: version per item, the item_version it was written from, content (the templates the rules found it fills, the model's proposals, the model and the cost), created_at |
scenarios |
v13 | what to do with an item, one row per asking: version per item, the item_version it was written from, content (the keep and fix, reuse and sell cards, the recommendation and its reason, whether it was written for a profile, the model and the cost), created_at |
items.public, captures.public |
v10 | 1 when the owner shows the row to the demo visitor (the demo loader marks what it loads); the demo visitor's reads are limited to these rows |
access_keys |
v9 | access codes issued as records: the hash (SHA-256), a label, when issued, expires_at, revoked_at; the code itself is shown once and never kept |
access_keys.parent_id, access_keys.master |
v12 | for a phone's own code, what paired it: the issued record's id, or a short digest of the master code; the code stands only while that record is unrevoked and unexpired, or while the master code is the same |
sell_details |
v14 | the listing details per item, one row per save: version and content (condition in the owner's words, what is included, faults, what was not tested, the data line, handover, area, asking price, and the private lowest price and reason, never sent to a model or a visitor) |
marketplace_events |
v14 | what happened after posting: kind (listed, sold, given), on_date, price, the listing url when given |
captures |
v8 | what the phone sent in one go: status (received, processed, failed), content (the line, the name, the counts, the item it became, the error), times; the media under DATA_DIR/captures/<id>/ |
content columns hold JSON validated by the Pydantic models of the owning package; the
shape of each is the one the HTTP API returns.
What is kept, and for how long¶
- A photo sent to a reading route (
/api/extract,/api/segment) is read in memory and dropped with the request. The web form then keeps the photos a saved item was read from underDATA_DIR/items/<id>/(POST /api/items/{id}/photos), and an item read from a capture gets the capture's photos the same way; they are shown in the item dialog beside the values to confirm and removed when the item is deleted. The photo a measurement was made on is kept with the measurement, on the item. Photos are re-encoded on ingest, so no metadata is ever stored. - A picture drawn on request (
POST /api/items/{id}/picture, withPICTURE_ENABLED) is kept in the same folder aspicture-<n>.jpgwith apicture-<n>.jsonrecord (what it was drawn as, the model, the cost, when), never among the photos and never read as evidence; it goes with the item. A picture of a reuse card's finished project is kept the same way inDATA_DIR/items/<id>/scenarios-<scenarios id>-reuse-<card>/and goes with the item; one of a Build project inDATA_DIR/projects/<id>/, cleared with the inventory by./dev demo. - A capture from the phone is kept as sent, re-encoded photos, the clip and the WAV note, under
DATA_DIR/captures/<id>/, until the capture is deleted; the item read from it stays. - A voice recording sent to
/api/items/{id}/voiceis transcribed and dropped; the transcript stays on the item. A capture's note stays with the capture. - Assertions, events and drafts are never rewritten: a correction is a new row beside the old one, and a hidden item keeps its history.
- The spend ledger is appended to and survives restarts; delete the file to reset the total.
RETENTION_HOURSis a setting without a job behind it yet: nothing is purged on a timer; captures are deleted by hand, from the Inbox.
Backups¶
Stop the stack, copy DATA_DIR, start it again. The database is plain SQLite and opens with any
client; the measurement photos are JPEG blobs inside it, and the captures' media and the
items' photos are files beside it under captures/ and items/.