Files
SlabUploader/docs/ARCHITECTURE.md
T
Hermes Agent bee3bcc278 docs: full implementation plan (PRD decisions locked + specs 1-4 + impl plan)
- PRD: 20 locked decisions, open items cleared, fence fixed
- docs/TECH-SPEC-PIPELINE.md: deterministic math + 9 test vectors (deliverable #1)
- docs/ARCHITECTURE.md, DATA-MODEL.md, OPENAPI.md (deliverable #2),
  CONTENT-WOO.md, DEPLOYMENT.md (deliverable #4), IMPL-PLAN.md
- README rewritten; .gitignore added

AGENTS.md pending Ty's approval (protected file write was blocked).
2026-08-24 12:27:20 -07:00

10 KiB
Raw Blame History

Architecture

Status: SPEC · 2026-08-24 · SlabUploader Companion to: PRD (root), TECH-SPEC-PIPELINE.md, DATA-MODEL.md, OPENAPI.md, DEPLOYMENT.md.

1. Service topology (2 containers + 1 reverse proxy)

Per Decision 11 the original Node service is folded into FastAPI. Final v1 topology:

                        ┌──────────────────────────────────────────┐
  mobile (PWA)          │              .201 host                    │
 ┌──────────────┐ HTTPS │  ┌─────────┐        ┌──────────────────┐  │
 │  SvelteKit   │───────┼─▶│  Caddy  │───────▶│  frontend (nginx │  │
 │  browser     │       │  │  :443   │  :5173 │  static + /api   │  │
 │  IndexedDB   │       │  │  slab.* │  ─────▶│  proxy)         │  │
 │  camera API  │       │  └─────────┘        └──────────────────┘  │
 └──────────────┘       │                          │ /api → :8000  │
        │  (offline:    │                          ▼               │
         all local)     │                   ┌──────────────────┐   │
                        │                   │  fastapi         │   │
                        │                   │  :8000           │   │
                        │                   │  ┌────────────┐  │   │
                        │                   │  │ pipeline   │  │   │
                        │                   │  │ (determin. │  │   │
                        │                   │  └────────────┘  │   │
                        │                   │  ┌────────────┐  │   │
                        │                   │  │ woo client │  │   │
                        │                   │  └────────────┘  │   │
                        │                   │  ┌────────────┐  │   │
                        │                   │  │ inference  │  │   │
                        │                   │  └────────────┘  │   │
                        │                   └───────┬──────────┘   │
                        │                           │              │
                        │        SQLite + image vols (named volumes)│
                        └──────────────────────────────────────────┘
                                          │ (configurable OpenAI-compat
                                          ▼  vision endpoint, e.g. .202)
  • frontend — SvelteKit (SSG/SPA mode) + Tailwind. Built to static, served by an nginx container. Pure client app; talks to /api on the same origin. No build-time secrets. This is the PWA (SW + IndexedDB).
  • fastapi — one Python service owning: deterministic image pipeline, bdft math, pricing engine, WooCommerce REST client, taxonomy sync, inference wrapper, content generation. SQLite + image storage in named volumes.
  • caddy — reverse proxy + automatic HTTPS. Fronts frontend and routes /api to fastapi. No separate Traefik/systemd — compose is the supervisor (Decision 13).

No message broker in v1. Long jobs (normalization, publish) run in-process via a small background worker (FastAPI BackgroundTasks + a lightweight in-process queue, or arq on SQLite/Redis only if needed). See §6.

2. Client/server split (authoritative vs hint)

Per TECH-SPEC §1.5: the client runs a light pass for instant feedback; the server runs the authoritative pass. Stored and published values are always the server's.

Capability Client (hint) Server (authoritative)
SKU OCR Tesseract.js (accept/manual) re-run; on mismatch surface both, user picks
Ruler scale OpenCV.js quick check full tick detection + cross-check
Widths / bdft none (needs server geometry) full pipeline
Green removal / crop preview only full normalization
Species/character none inference (vision) + manual
Pricing none pricing engine
Content (title/desc) none templates + optional LLM

The client never publishes. Only the server writes to WooCommerce (FR24/FR27).

3. Offline-first PWA (Decision: full offline per PRD)

3.1 What lives on the client (IndexedDB)

  • Draft slabs: full metadata (all review-screen fields) + photo blobs (originals). Calibration photos included. IndexedDB object stores: slabs, photos (blob), syncQueue.
  • Service Worker: precache app shell (SvelteKit build). API is not cached — network-first for API, but the capture flow itself needs no network.

3.2 Capture → queue → sync flow

  1. Capture is fully offline: photos → IndexedDB, OCR/ruler hints run locally.
  2. Draft persists to IndexedDB after every step (no "save" button — always saved).
  3. Upload queue: when online, the PWA pushes pending drafts (metadata + photo blobs) to POST /api/slabs/{id}/upload. Server runs the authoritative pipeline, returns the enriched draft. Client merges enriched values, marks synced.
  4. Publish is always an online action (needs Woo). If offline, the slab stays ready; publish is enabled when connectivity returns.
  5. Reconciliation: each draft carries a client_rev (monotonic int). Server rejects an upload whose client_rev < the stored rev for that slab (stale). Conflict surfaces in UI for the user to resolve. v1 has a single owner, so conflicts are rare; the mechanism is there for correctness.

3.3 Connectivity detection

navigator.onLine + a lightweight GET /api/health probe. UI shows offline banner and disables publish. No hard dependency on either — capture works regardless.

4. Data flow (happy path)

[client] capture (offline)
   → OCR SKU (hint) / ruler (hint) / 1–5 photos → IndexedDB draft (rev=1)
[online] PWA → POST /api/slabs            {draft, rev=1}
        POST /api/slabs/{id}/upload       {photo blobs}
[server] authoritative pipeline:
   ruler→scale → geometry → widths → bdft → species/char (inference, opt)
   → pricing → normalization → content (opt LLM)
   → store enriched draft (status=review, rev=2), store original+normalized images
[client] GET /api/slabs/{id} → review screen (all fields editable)
[client] PUT /api/slabs/{id}             {overrides, rev=2}
[client] POST /api/slabs/{id}/publish
[server] taxonomy sync (if needed) → Woo create (draft/pending) → store woo_product_id
   → status=published → purge images (Decision 12)

5. Determinism boundary

Everything left of the "inference" and "content" boxes is deterministic and unit- tested (PRD "Deterministic core"). The two inference-touching paths are:

  • species/character detection (vision) — toggleable, returns confidence
  • optional content refinement (text LLM) — toggleable

Both are wrapped so that toggling them OFF leaves a fully functional deterministic app. The app must pass its entire test suite with inference disabled.

6. Concurrency & jobs

  • v1 is single-owner, single-slab workflow (no bulk). Concurrency is minimal.
  • Long ops (normalization ~3–5s, publish with image upload) use FastAPI BackgroundTasks + a per-slab status field (processing/ready/publishing/ published/failed). Client polls GET /api/slabs/{id} (short, exponential backoff).
  • If a job dies, the slab is failed with a reason; re-publish is idempotent by SKU-dedupe (FR24a) — but v1 has no update path, so a failed publish that already created the Woo product is surfaced for manual handling (see IMPL-PLAN risk).
  • No external broker (Redis) in v1. If the in-process queue proves insufficient, add arq + a single Redis container — a documented, reversible extension.

7. Security (Decision 10, PRD Security)

  • No app-level auth. App is reachable only on the trusted LAN/Tailscale (domain slab.tyubumini.local). HTTPS enforced by Caddy.
  • Secrets (Woo consumer key/secret, inference key, AES master key) stored encrypted at rest (AES-GCM) in SQLite; the AES key itself is an env var injected by compose, never in the DB. Config is editable via the Settings UI (FR32) but values are re-encrypted on save.
  • Client stores no persistent secrets. The PWA needs none — all secrets server-side.
  • All client↔server traffic HTTPS.

8. Deployment target

  • Host: .201 (no inference there; inference is a remote configurable endpoint).
  • Stack: Docker Compose (frontend, fastapi) + Caddy (container or host-managed — see DEPLOYMENT.md). Domain: slab.tyubumini.local (Decision: LAN/Tailscale only).
  • Named volumes: slab_db (SQLite), slab_images (original+normalized). Backed up per DEPLOYMENT.md.

9. Extensibility (documented, not built)

  • Postgres swap for SQLite (DATA-MODEL keeps the schema portable).
  • Redis/arq if job volume grows.
  • Update path for existing Woo products (v2; v1 is create-only by SKU dedupe).
  • Multi-slab bulk (explicit non-goal v1).
  • Staff accounts/roles (explicit non-goal v1).

10. Tech stack (pinned intent — exact versions at scaffold)

Layer Choice
Client SvelteKit, TailwindCSS, Tesseract.js, OpenCV.js, IndexedDB, Camera API, Workbox/SW
Server Python 3.12, FastAPI, Pydantic v2, OpenCV (cv2), numpy, Pillow, RemBG/U²Net (onnx), httpx
Woo WooCommerce REST v3 via httpx (consumer key/secret, Basic auth)
DB SQLite (stdlib sqlite3 or SQLAlchemy; see DATA-MODEL)
Inference OpenAI-compatible client (configurable base_url/key/model; must be vision-capable)
Proxy/HTTPS Caddy
Packaging Docker Compose