Files
SlabUploader/docs/contracts/DATA-MODEL.md
T
Ty 0eb2b0f697 feat(backend): S0 foundation (schema + stdlib sqlite3 db factory + health/meta + JSON logs)
- backend/db/schema.sql: all 12 tables from DATA-MODEL.md (taxonomy empty)
- backend/db: stdlib sqlite3 factory, WAL/FK/busy_timeout/synchronous pragmas,
  single locked connection, explicit SLAB_DB_PATH/SLAB_IMAGES_DIR
- backend/app.py: /api/health (non-200 on storage not writable), /api/v1/meta,
  JSON logging with request-id threading + secret redaction, startup guards
  (SETTINGS-10 WOO_BASE_URL https, SETTINGS-25 SLAB_AES_KEY)
- tests: schema-loads, health happy/failure, startup guards (TDD)
- docs/contracts: add requirement-ID citations to 12 sections so
  check_requirements.py passes (pre-existing gate debt)

Gates: check_requirements.py + check_coverage.py exit 0; pytest 5 passed
2026-09-19 15:34:15 -07:00

169 lines
7.5 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, METADATA-106 | inference columns, prompts tables, logs, revision interface |
| 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
Cites METADATA-5, SETTINGS-50, SETTINGS-51, SETTINGS-55.
- `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`
`docs/contracts/PROMPTS.md` owns the prompt structure, the seed text, and the revision interface; this
section owns the storage shape (`METADATA-105`, `METADATA-106`).
- `prompts`: `id`, `name` (`call1` | `call2`), `active_revision_id` FK, `created_at`, `updated_at`.
- `prompt_revisions`: `id`, `prompt_id` FK, `content` text, `content_hash` text, `created_at`,
`created_by`.
Revisions are append-only: a save inserts a new row and moves `active_revision_id`; a row is never
updated or deleted, so reset-to-revision and reset-to-default are pointer moves. Each prompt has a
revision 0 seeded on first run from the seed text in `docs/contracts/PROMPTS.md` §6.
`inference_log.call` records which call ran, and `inference_log.prompt_revision_id` records the
revision it used (`METADATA-66`).
## 8. `sync_log`
Cites PUBLISH-25, PUBLISH-30.
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
Cites APP-5.
`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.