Files
SlabUploader/docs/contracts/DATA-MODEL.md
T
Ty 2ba5428269 docs(m3): place create decisions in the contracts
- DATA-MODEL.md: storage engine (stdlib sqlite3 + schema.sql, no ORM/migrations),
  pragmas, per-test isolation.
- API.md: revision coupling, async job model, PUT check order, server-derived ready.
- PROMPTS.md: no prompt files ship; the DB is the only prompt source.
2026-09-19 10:12:32 -07:00

155 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Data model contract
Status: SPEC · 2026-09-18. Store: SQLite.
## Requirements impact
| Requirement | Impact on this design |
|---|---|
| APP-5 | slab status enum and transitions |
| APP-20 | ids, timestamps, revisions |
| APP-40 | derived numbers stored, never recomputed |
| CAPTURE-25, CAPTURE-30, CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90, CAPTURE-95, CAPTURE-105, CAPTURE-115, CAPTURE-130, CAPTURE-135, CAPTURE-160 | slab and photo columns, units, PNG |
| METADATA-5, METADATA-10, METADATA-15, METADATA-30 | taxonomy cache tables and slab taxonomy columns |
| METADATA-35, METADATA-45, METADATA-55, METADATA-66, METADATA-105 | inference columns, prompts tables, logs |
| PUBLISH-25, PUBLISH-30 | purge on success, append-only log |
| REVIEW-10, REVIEW-25, REVIEW-45, REVIEW-50 | prose, price, SKU, field limits |
| SETTINGS-5, SETTINGS-10, SETTINGS-20, SETTINGS-25, SETTINGS-30, SETTINGS-35, SETTINGS-50, SETTINGS-51, SETTINGS-55, SETTINGS-65, SETTINGS-90 | settings, crypto, rates, taxonomy stamps |
## 1. Conventions
- Slab ids are client-generated UUIDv4; all other ids are integer autoincrement.
- Timestamps are UTC ISO-8601 text.
- Slab status is one of `draft`, `calibrated`, `ready`, `publishing`, `published`, `failed`.
- Inches are stored as the rounded 1/8-inch value the user entered.
- `sqft` and `bdft` are stored to 2 decimals half-up.
- `price` is stored to 2 decimals half-up.
- Multi-select taxonomy values are JSON arrays of Woo ids.
- Secrets are stored as `value_enc = nonce(12) || ciphertext`, AES-GCM; the master key is the env
`SLAB_AES_KEY` and is never in the DB.
- On successful publish the local working slab row is discarded (photos purged, device draft
cleared); `sync_log` and `inference_log` are append-only and retained. Nothing is deleted from
WooCommerce.
- Soft delete sets `deleted_at` (text, null until then) on unpublished drafts; deleted rows are
excluded from all GET responses.
- Photo bytes live in `slab_photos` BLOBs; the `/images` volume is not a second store. It holds a
health-probe file and short-lived staging for 1024 px Call-1 JPEGs and Woo media uploads, purged
after use.
- The store is SQLite through the stdlib `sqlite3` module and a checked-in `schema.sql`
(`CREATE TABLE IF NOT EXISTS`); there is no ORM and no migration framework. Pragmas are
`journal_mode=WAL`, `foreign_keys=ON`, `busy_timeout`, `synchronous=NORMAL`, with one
process-wide connection behind a lock. The database path and image directory are explicit so a
test runs against isolated storage.
## 2. `slabs`
| Column | Type | Constraint / default |
|---|---|---|
| `id` | text | PK, client UUIDv4 |
| `status` | text | enum, default `draft` |
| `sku` | text | 3–24, alphanumeric and hyphen; unique locally |
| `length_in` | real | 1/8-inch step |
| `thickness_in` | real | 1/8-inch step |
| `sqft` | real | 2 dp |
| `bdft` | real | 2 dp |
| `width_min_in`, `width_max_in`, `width_avg_in` | real | 6-inch stations |
| `species_id` | int | FK `woo_categories`, one leaf |
| `wood_category_ids` | json | one or more leaf ids |
| `edge_type_term_id` | int | one term |
| `figure_term_ids` | json | one or more terms |
| `grade_term_id` | int | exactly one term |
| `thickness_term_id` | int | round-up band term |
| `moisture_term_id` | int | default kiln-dried term |
| `fig_tag_ids` | json | optional |
| `feat_tag_ids` | json | optional |
| `price` | real | 2 dp |
| `price_source` | text | `recommendation` \| `override` |
| `title`, `short_description`, `description` | text | required non-empty; title warn >60, short_description warn >160 |
| `content_source` | text | `manual` \| `llm` \| `template` |
| `species_confidence`, `figure_confidence`, `grade_confidence` | real | null below 0.7 |
| `inference_status` | text | null \| `inferring` \| `done` \| `failed` |
| `calibration_photo_id` | text | FK `slab_photos` |
| `client_rev`, `server_rev` | int | default 1 |
| `woo_product_id` | int | null until publish |
| `published_at` | text | null until publish |
| `deleted_at` | text | null until soft-deleted; unpublished drafts only |
| `created_at`, `updated_at` | text | UTC ISO-8601 |
No `inference_thread_id` column exists; provider threads are not used.
## 3. `slab_photos`
| Column | Type | Constraint |
|---|---|---|
| `id` | text | PK, UUID |
| `slab_id` | text | FK `slabs` |
| `seq` | int | capture order |
| `is_calibration` | int (bool) | exactly one true per slab |
| `original_blob` | blob | retained while draft/retry |
| `processed_blob` | blob | transparent PNG, alpha preserved |
| `width_px`, `height_px` | int | processed dimensions |
| `created_at` | text | UTC ISO-8601 |
The calibration photo exists; other batch photos are visual inventory only. Rows are purged on
successful publish.
## 4. `pricing_rules`
| Column | Type | Constraint |
|---|---|---|
| `id` | int | PK |
| `species_id` | int | FK `woo_categories`, UNIQUE |
| `price_per_bdft` | real | flat, no tiers |
| `created_at`, `updated_at` | text | UTC ISO-8601 |
## 5. Taxonomy cache
- `woo_categories` — species and wood-category leaves only; parents group and are never assigned.
- `woo_attributes` / `woo_attribute_terms` — exactly the five locked attributes Edge Type, Figure,
Grade, Thickness, Moisture, with their terms.
- `woo_tags` — only the fig-* and feat-* families; other store tags are ignored.
Rebuild is one transaction; on failure the previous cache is kept.
## 6. `settings`
| Key | Storage | Default |
|---|---|---|
| `woo_wp_username` | plain | — |
| `woo_app_password` | `value_enc` | — |
| `inference_base_url` | `value_enc` | — |
| `inference_api_key` | `value_enc` | — |
| `inference_model` | plain | — |
| `inference_enabled` | plain | `false` |
| `content_llm_enabled` | plain | `false` |
| `brand_voice` | plain | — |
| `geo_context` | plain | — |
| `woo_create_status` | plain | `draft` |
| `taxonomy_last_sync_at` | plain | — |
| `taxonomy_anchor` | plain | — |
`WOO_BASE_URL` is not a settings row; it is env, HTTPS only, read-only.
## 7. `prompts` and `prompt_revisions`
Prompts live in the database with all revisions; each call uses the active revision and the
history is retained.
- `prompts`: `id`, `name` (`call1` | `call2`), `active_revision_id`, `created_at`, `updated_at`.
- `prompt_revisions`: `id`, `prompt_id` FK, `content` text, `created_at`, `created_by`.
## 8. `sync_log`
Append-only; never deleted on publish. Columns: `id`, `slab_id`, `woo_product_id`, `status`
(`success` | `failed`), `payload_hash` (sha256 of the outbound payload), `message`, `created_at`.
## 9. `inference_log`
Append-only audit. Columns: `id`, `slab_id`, `call` (1 | 2), `prompt_revision_id` FK, `model`,
`request_payload`, `response_payload`, `latency_ms`, `created_at`.
## 10. Status transitions
`draft` → `calibrated` → `ready` → `publishing` → `published`, with `failed` reachable from any
step and retryable. `calibrated` is set when the draft uploads; `ready` requires the full set.