# ERD — karung counter (SQLite) Data model for the karung feedmill counter. SQLite DB at `$OUTPUT_DIR/jetson_counter.db` (`config.yaml` `output.dir`, default `/opt/jetson-counter/`; env override `DB_PATH`). **No DB foreign keys enforced** (zero `FOREIGN KEY` in code) — all relationships are soft, by naming/best-effort writes. ```mermaid erDiagram batches ||--o{ delivery_orders : "soft via batch_id / do_numbers" batches ||--o{ daily_summaries : "rollup by counting_date" batches { INTEGER id PK TEXT counting_date "NOT NULL, YYYY-MM-DD, cutoff-shifted" INTEGER batch_number "NOT NULL, per date+camera+label" TEXT camera_name "NOT NULL, default CC1" TEXT object_label "NOT NULL, default karung-pakan" INTEGER count "NOT NULL, gross sack loading" TEXT start_time "NOT NULL, ISO datetime" TEXT end_time "NOT NULL, ISO datetime" TIMESTAMP created_at "default CURRENT_TIMESTAMP" INTEGER box_loading "NOT NULL, default 0" INTEGER box_unloading "NOT NULL, default 0" TEXT model_mode "NOT NULL, default A" TEXT plate "NOT NULL, default ''" TEXT do_numbers "NOT NULL, JSON array of No. DO" INTEGER expected_sack "NOT NULL, default 0" INTEGER expected_box "NOT NULL, default 0" INTEGER net_sack "NOT NULL, default 0" INTEGER net_box "NOT NULL, default 0" } delivery_orders { INTEGER id PK TEXT counting_date "NOT NULL" TEXT photo_path "NOT NULL, relative under do_photos/" TEXT no_do "default ''" TEXT plate "default ''" INTEGER expected_sack "default 0" INTEGER expected_box "default 0" TEXT ocr_raw "nullable JSON: no_do, plate, ocr_text, engine, ocr_ok, ocr_error" TEXT status "default 'draft'" INTEGER batch_id "soft ref to batches.id, nullable" TIMESTAMP created_at "default CURRENT_TIMESTAMP" TIMESTAMP updated_at "default CURRENT_TIMESTAMP" } daily_summaries { INTEGER id PK TEXT counting_date "NOT NULL" TEXT camera_name "NOT NULL" TEXT object_label "NOT NULL" INTEGER total_count "NOT NULL, default 0, SUM(batches.count)" INTEGER total_batches "NOT NULL, default 0, COUNT(batches.id)" TIMESTAMP updated_at "default CURRENT_TIMESTAMP" } ``` ## Keys - `batches`: `UNIQUE(counting_date, batch_number, camera_name, object_label)` - `daily_summaries`: `UNIQUE(counting_date, camera_name, object_label)` - `delivery_orders`: `idx_do_date_status` on `(counting_date, status)` ## Relationships (logical) | From | To | How | |---|---|---| | `delivery_orders` → `batches` | N—1 | On stop: `batches.do_numbers` JSON array of `No. DO`; `delivery_orders.batch_id` set best-effort. Active batch: `current_batch.json.do_ids`. | | `batches` → `daily_summaries` | N—1 | Same `(counting_date, camera_name, object_label)`; stop recompute `SUM(count)`, `COUNT(id)`. | ## Derived columns - `net_sack = count − sack unloading`, `net_box = box_loading − box_unloading` (`src/do_batch.py:37`). - **Sack unloading has no column** — it only survives inside `net_sack` (legacy rows with `net_sack=0` from the `ALTER TABLE ... DEFAULT 0` migration are not backfillable). - History UI shows gross `count` / `box_loading` only (no Net). ## Status (`delivery_orders.status`) `draft` (photo upload) → `staged` (saved/edited) → `attached` (in active/stopped batch). Discard stop with both nets 0 returns DOs to `staged`. `discarded` appears in older notes but **no code writes it** — discard is a `DELETE`. ## Hazards - `INSERT OR REPLACE` on `batches` (UNIQUE match = delete+insert) changes `id` on re-stop → can orphan `delivery_orders.batch_id`. - Schema divergence: `predict.py init_db()` creates only `batches` + `daily_summaries` (3 extra cols, no `delivery_orders`); full 3-table schema (9 extra `batches` cols) comes from `counter_dashboard.py` `_ensure_db()` at import. Fresh DB touched only by `predict.py` is incomplete; its DO updates fail silently (`try/except: pass`). ## JSON sidecars + photo tree (not in SQLite) | Path | Role | |---|---| | `$OUTPUT_DIR/batch_mode.json` | `{mode: auto\|do_manual\|manual}` | | `$OUTPUT_DIR/current_batch.json` | active batch + `do_ids`, plate, expected_*, nets inputs | | `$OUTPUT_DIR/do_settings.json` | `require_plate`, `require_do`, `ocr_engine` | | `$OUTPUT_DIR/do_photos/YYYY-MM-DD/do_.jpg` | source photos (7-day retention) | ## Sources - DDL: `counter_dashboard.py:123-198` (`_ensure_db`, superset), `predict.py:117-166` (`init_db`, partial). - Writes: `predict.py:249-306`, `counter_dashboard.py:610-668`. - Doc flow: `docs/do-erd.md` (merged into this file).