diff --git a/AGENTS.md b/AGENTS.md index 8694f97..f8f5ca3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ Indonesian (`karung`=sack, `truk`=truck); YOLO class names are English office `:5721` only (403 on `:5000`); **`ocr_engine` POST both ports** (synced via `GET /api/do/settings`). Discard batch only if **both** `net_sack==0` and `net_box==0`. DO helpers: `src/do_batch.py`, OCR: `src/do_ocr.py`; - ERD: `docs/do-erd.md`. + ERD: `ERD.md` (repo root). - **Counting filters by class name, not ID**: `SackDetector`/`BoxDetector`/ `TruckDetector` filter via `BaseDetector(class_filter)` (`src/detection.py`); tracker keeps `("sack", "truck", "box")` (`src/tracking.py`); counting uses diff --git a/CHANGELOG.md b/CHANGELOG.md index 407acc8..4be3159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ version tags are cut in this repo — POC stage, `main` is the release line). (`extract_do_fields` tesseract|paddle|none). - Operator DO panel + OCR toggle; monitoring 3-way mode switch + model mode + OCR/require-plate controls; history/export plate, DO, expected, net columns. -- `docs/do-erd.md` (Mermaid ERD); tests `tests/test_do_batch.py`. +- `ERD.md` (Mermaid ERD, repo root; superseded `docs/do-erd.md`); tests `tests/test_do_batch.py`. ### Changed - Default batch mode seed **`auto`** (was hard-coded `manual` when diff --git a/ERD.md b/ERD.md new file mode 100644 index 0000000..bff5089 --- /dev/null +++ b/ERD.md @@ -0,0 +1,115 @@ +# 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). diff --git a/README.md b/README.md index 90857dd..3bb6b2f 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Office (`:5721`) switches mode / model mode / require_plate (403 on operator POST). **DO manual flow:** open `http://:5000/operator` on a phone → Ambil Foto DO → review/edit OCR fields → Mulai Batch → count → Selesai (soft-warn if zone busy). Discard only when both sack and box nets are 0. -ERD: [`docs/do-erd.md`](docs/do-erd.md). +ERD: [`ERD.md`](ERD.md). ## Repository layout @@ -131,7 +131,7 @@ Script-by-script reference: [`docs/scripts.md`](docs/scripts.md). - [`docs/models.md`](docs/models.md) — model inventory & detection-mode support matrix - [`docs/configuration.md`](docs/configuration.md) — all config files/variables - [`docs/deployment.md`](docs/deployment.md) — Jetson services, TensorRT, deploy flow -- [`docs/do-erd.md`](docs/do-erd.md) — delivery-order / batch ERD (Mermaid) +- [`ERD.md`](ERD.md) — SQLite ERD (Mermaid): batches / delivery_orders / daily_summaries - [`docs/operator-tiles-history-plan.md`](docs/operator-tiles-history-plan.md) — operator tiles + history cleanup plan - [`docs/scripts.md`](docs/scripts.md) — entry points & utility scripts - [`CHANGELOG.md`](CHANGELOG.md) — release history diff --git a/docs/architecture.md b/docs/architecture.md index c8cfe0c..2b5226e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -147,7 +147,7 @@ dashboard integration is file-based.) DO pipeline (dashboard): smartphone upload → OCR draft → edit/stage → start gates → active batch → stop-preview soft-warn → stop → `batches` + DO columns. -ERD: [`docs/do-erd.md`](do-erd.md). UI plan: [`docs/operator-tiles-history-plan.md`](operator-tiles-history-plan.md). +ERD: [`../ERD.md`](../ERD.md). UI plan: [`docs/operator-tiles-history-plan.md`](operator-tiles-history-plan.md). ## Retired (`archive/`, not imported) diff --git a/docs/do-erd.md b/docs/do-erd.md deleted file mode 100644 index 47cde62..0000000 --- a/docs/do-erd.md +++ /dev/null @@ -1,81 +0,0 @@ -# ERD — DO-gated manual batch (delivery orders) - -Mermaid ER for karung feedmill counter. Soft FK: no DB foreign keys enforced; -`delivery_orders.batch_id` is informational once a batch row exists. While a -batch is active, membership lives in `current_batch.json` (`do_ids`). - -```mermaid -erDiagram - batches ||--o{ delivery_orders : "soft via do_numbers / batch_id" - batches ||--o{ daily_summaries : "rolls up by counting_date" - - batches { - INTEGER id PK - TEXT counting_date - INTEGER batch_number - TEXT camera_name - TEXT object_label - INTEGER count - TEXT start_time - TEXT end_time - INTEGER box_loading - INTEGER box_unloading - TEXT model_mode - TEXT plate - TEXT do_numbers - INTEGER expected_sack - INTEGER expected_box - INTEGER net_sack - INTEGER net_box - TIMESTAMP created_at - } - - delivery_orders { - INTEGER id PK - TEXT counting_date - TEXT photo_path - TEXT no_do - TEXT plate - INTEGER expected_sack - INTEGER expected_box - TEXT ocr_raw - TEXT status - INTEGER batch_id - TIMESTAMP created_at - TIMESTAMP updated_at - } - - daily_summaries { - INTEGER id PK - TEXT counting_date - TEXT camera_name - TEXT object_label - INTEGER total_count - INTEGER total_batches - TIMESTAMP updated_at - } -``` - -## 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/label; stop recompute `SUM(count)`. | - -## Status (`delivery_orders.status`) - -`draft` → `staged` (saved/edited) → `attached` (in active/stopped batch) | -`discarded` (deleted). Discarded stop (both nets 0) returns DOs to `staged`. - -## 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) | - -Schema DDL: `counter_dashboard.py` `_ensure_db()` (additive PRAGMA migrations -on `batches` + `CREATE TABLE delivery_orders`).