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).
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user