Files
SlabUploader/docs/DATA-MODEL.md
T
Ty 4e97770373 docs: lock revised capture pipeline and hybrid architecture
Rewrite PRD, tech spec, data model, and OpenAPI around the 2026-08-27
decisions: client-side mask/measure/crop, U2Net on demand, Woo as product
source of truth, draft-only local state. Architecture records topology C
through the status machine. Drop the old CONTENT-WOO spec until the
Call 2 prompt review finishes.
2026-08-27 19:26:45 -07:00

6.6 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+)
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-* tags. Other Woo tags ignored.

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
  • prompts (JSON: call1_prompt, call2_prompt, reset-to-default)
  • publish_status ('published' | 'draft')
  • 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

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