Files
SlabUploader/docs/DATA-MODEL.md
T
grokbot_ty 4ec478f383 docs: finish Gate A contradiction patches
Align PRD/README/CONTENT-WOO/DATA-MODEL/OPENAPI/DEPLOYMENT with AGENTS.md
and the POC review package: hybrid client SoT, bdft without /12, server AES
creds, woo_create_status draft|publish with SLAB-UAT-* firebreak, deploy smoke
and health path /api/health.
2026-09-01 03:59:46 +00:00

7.0 KiB
Raw Blame History

Data Model

Status: SPEC · 2026-08-27 · SlabUploader Storage: single SQLite file. Schema portable to Postgres.

Conventions:

  • IDs: client-generated UUIDv4 for slabs so the PWA can reference a slab before it exists server-side.
  • Timestamps: UTC ISO-8601 TEXT.
  • Soft state via status column; no hard deletes for slabs (audit).
  • Money: float64 USD, rounded half-up to cents at write.
  • All rounded values are what gets stored. No full-precision internal storage.
  • Inches stored as 1/8" increments. Sqft/bdft to 2 decimals.
  • Multi-select fields stored as JSON arrays of Woo IDs.

slabs

Draft-only. Deleted on successful publish. WooCommerce is the source of truth for product data.

Column Type Notes
id TEXT PK client-generated UUID
sku TEXT UNIQUE NOT NULL 3-24 [A-Z0-9-]
status TEXT NOT NULL draft | calibrated | ready | publishing | published | failed
length_in REAL NOT NULL 1/8" step
thickness_in REAL NOT NULL 1/8" step
sqft REAL 2 decimals
bdft REAL 2 decimals
width_min_in REAL min width at 6" stations, 2 decimals
width_max_in REAL max width at 6" stations, 2 decimals
width_avg_in REAL avg width at 6" stations, 2 decimals
species_id INTEGER → woo_categories (leaf only, 1 required)
wood_category_ids INTEGER[] JSON → woo_categories (leaf, 1+)
edge_type_term_id INTEGER → woo_attribute_terms (1 required)
figure_term_ids INTEGER[] JSON → woo_attribute_terms (1+)
grade_term_ids INTEGER[] JSON → woo_attribute_terms (1+)
thickness_term_id INTEGER → woo_attribute_terms (round-up)
moisture_term_id INTEGER → woo_attribute_terms (1 required, defaults to kiln-dried term; user overrides; not inferred)
fig_tag_ids INTEGER[] JSON → woo_tags (fig-*, 1+)
feat_tag_ids INTEGER[] JSON → woo_tags (feat-*, 0+)
price_per_bdft REAL from pricing_rules for species
price REAL NOT NULL 2 decimals
price_source TEXT 'recommendation' | 'override'
title TEXT
short_title TEXT
description TEXT
content_source TEXT 'manual' | 'llm'
client_rev INTEGER NOT NULL DEFAULT 1
server_rev INTEGER NOT NULL DEFAULT 1
created_at TEXT NOT NULL
updated_at TEXT NOT NULL

Indexes: sku (unique), status, updated_at.

Status state machine

draft → calibrated → ready → publishing → published
         │              │                ↑
         └→ failed  ────┴────────────────┘ (woo_product_id in sync_log)
any → failed (with error)
  1. draft — photos taken, no mask confirmed.
  2. calibrated — BG edge confirmed, length axis confirmed, length/thickness/SKU entered. Client computed sqft/bdft/widths. Draft uploaded to server.
  3. ready — all mandatory fields populated. Values from Call 1, manual, or mix. Inference is optional. Publishable.
  4. publishing — Woo create in flight. Poll for result.
  5. published — Woo product created. This row + all photos deleted. sync_log retained.
  6. failed — pipeline, inference, or publish error. Error detail stored. Retry available.

slab_photos

Column Type Notes
id TEXT PK
slab_id TEXT FK → slabs.id ON DELETE CASCADE
kind TEXT NOT NULL 'inventory'
role TEXT 'topdown' | 'extra'
seq INTEGER display order
original_path TEXT relative to images volume
processed_path TEXT relative to images volume
created_at TEXT

Rule: only inventory photos. Calibration photos do not exist in this design.


pricing_rules

Column Type Notes
id TEXT PK UUID
species TEXT NOT NULL matches woo_categories.name (leaf)
price_per_bdft REAL NOT NULL flat, no tiers
created_at TEXT
updated_at TEXT

UNIQUE(species). Seeded from synced Woo categories. User sets price per species.


woo_categories

Column Type Notes
woo_id INTEGER PK
name TEXT
slug TEXT
parent_id INTEGER NULL for root
synced_at TEXT

Only species + wood category. Sync pulls all Woo categories; the app filters to the two we use.

UNIQUE(woo_id).


woo_attributes

Column Type Notes
woo_id INTEGER PK
name TEXT
slug TEXT
type TEXT 'select'
synced_at TEXT

Only the five first-class attributes: Edge Type, Figure, Grade, Thickness, Moisture.

UNIQUE(woo_id).


woo_attribute_terms

Column Type Notes
woo_id INTEGER PK
attribute_id INTEGER FK → woo_attributes.woo_id
name TEXT
slug TEXT
synced_at TEXT

The actual selectable values. E.g., "Cathedral" under Figure, "1"–1 1/2"" under Thickness.

UNIQUE(woo_id).


woo_tags

Column Type Notes
woo_id INTEGER PK
name TEXT
slug TEXT
synced_at TEXT

Only fig-* and feat-* tags. Other Woo tags ignored. WooCommerce is the authoritative source for tag definitions; this table caches synced Woo tags.

UNIQUE(woo_id).


settings

Column Type Notes
key TEXT PK
value_enc BLOB AES-GCM ciphertext
updated_at TEXT

Keys:

  • woo_base_url, woo_consumer_key, woo_consumer_secret
  • inference_base_url, inference_api_key, inference_model
  • inference_enabled, content_llm_enabled
  • brand_voice, geo_context
  • woo_create_status ('draft' | 'publish'), default draft — Woo product create status; not slab lifecycle
  • aspect_ratio ('3:4'), output_px (1600), output_px_min (1600), fill_target (0.80)
  • user_sensitivity, user_edge_offset, user_feather, user_sheet_mode

Prompt files — not in the settings table. Stored as text files in /data/prompts/ (server volume). Read fresh on each invocation. Default prompts shipped with the app; owner edits directly. A reset-to-default script restores originals.

AES master key is an env var (SLAB_AES_KEY, 32 bytes base64). Never in the DB. Each value_enc = nonce(12) || ciphertext.


sync_log

Append-only history. Not deleted on publish.

Column Type Notes
id INTEGER PK AUTOINCREMENT
slab_id TEXT FK
sku TEXT
woo_product_id INTEGER
status TEXT 'success' | 'failed'
http_status INTEGER
error TEXT machine-readable code
detail TEXT Woo error body
payload_hash TEXT sha256 of Woo payload sent
created_at TEXT

inference_log

Append-only audit.

Column Type Notes
id INTEGER PK AUTOINCREMENT
slab_id TEXT FK
call TEXT '1' | '2'
request_payload TEXT JSON of what was sent
response_payload TEXT JSON of what came back
created_at TEXT