|
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
|
|
|
|
|
## What This Is
|
|
## What This Is
|
|
|
|
|
|
|
|
-NestJS backend for the PalmOilAI system. Provides server-side YOLOv8 ONNX inference, SQLite history persistence, WebSocket gateways for real-time vision streaming and chat proxying, and a process surveillance monitor for n8n and Ollama.
|
|
|
|
|
|
|
+NestJS backend for the PalmOilAI system. Provides server-side YOLOv8 ONNX inference, SQLite history persistence (with disk image archiving), WebSocket gateways for real-time vision streaming and n8n chat proxying, and a process surveillance monitor for n8n and Ollama.
|
|
|
|
|
|
|
|
Detection classes: `Ripe`, `Unripe`, `Underripe`, `Overripe`, `Abnormal`, `Empty_Bunch` (MPOB standard).
|
|
Detection classes: `Ripe`, `Unripe`, `Underripe`, `Overripe`, `Abnormal`, `Empty_Bunch` (MPOB standard).
|
|
|
|
|
|
|
@@ -24,32 +24,80 @@ npm run test:e2e # End-to-end tests
|
|
|
jest --testPathPattern=palm-oil # Run a single test file
|
|
jest --testPathPattern=palm-oil # Run a single test file
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-Set `PORT` env var to override the default port 3000. Set `N8N_WEBHOOK_URL` in `.env` for chat proxy and agent readiness probing.
|
|
|
|
|
|
|
+Set `PORT` env var to override port 3000. Set `N8N_WEBHOOK_URL` in `.env` for chat proxy and agent readiness probing.
|
|
|
|
|
|
|
|
## Architecture
|
|
## Architecture
|
|
|
|
|
|
|
|
### Modules
|
|
### Modules
|
|
|
|
|
|
|
|
-**`PalmOilModule`** (`src/palm-oil/`) — Core feature module:
|
|
|
|
|
-- `ScannerProvider` — ONNX inference pipeline: `sharp` resizes to 640×640, strips alpha, transposes HWC→CHW, normalizes to `[0.0, 1.0]`, feeds `[1, 3, 640, 640]` tensor to `onnxruntime-node`. Output is `[1, N, 6]` (`x1, y1, x2, y2, confidence, class_index`), filtered at 0.25 confidence by default.
|
|
|
|
|
-- `PalmOilService` — Orchestrates inference + SQLite persistence. `analyzeImage()` is the main entry point; it fully awaits the `historyRepository.save()` before returning (intentional blocking — guarantees DB write before socket emit).
|
|
|
|
|
-- `VisionGateway` — WebSocket gateway on `/vision` namespace. Handles `vision:analyze` (Base64 image → ONNX inference → `vision:result`) and `chat:send` (proxies to n8n webhook → `chat:result`). Receives raw, uncompressed Base64 strings only — no binary frames, no WebRTC.
|
|
|
|
|
-- `HistoryEntity` — TypeORM entity; fields: `archive_id`, `filename`, `total_count`, `industrial_summary` (JSON), `detections` (JSON), `inference_ms`, `processing_ms`, `created_at`.
|
|
|
|
|
-- `mpob-standards.ts` — Source of truth for class names, grade colors, and health alert flag list (`Abnormal`, `Empty_Bunch`).
|
|
|
|
|
|
|
+**`PalmOilModule`** (`src/palm-oil/`)
|
|
|
|
|
|
|
|
-**`SurveillanceModule`** (`src/surveillance/`) — Process monitoring module:
|
|
|
|
|
-- `SurveillanceService` — Boots on `OnModuleInit`. Discovers PIDs for n8n (Node.js process containing "n8n" in cmd, port 5678 must be accepting TCP connections) and Ollama (`ollama_llama_server` or `ollama`). Polls every 500ms via `pidusage`. Probes `N8N_WEBHOOK_URL` every 10s to determine agent readiness. PIDs are evicted if the process dies or n8n's port goes silent.
|
|
|
|
|
-- `SurveillanceGateway` — WebSocket gateway on `/monitor` namespace. Broadcasts `monitor:data` (CPU/memory metrics) on every 500ms tick and `monitor:status` (n8n webhook ready/not ready) when status changes. Pushes an immediate snapshot on client connect.
|
|
|
|
|
|
|
+**`ScannerProvider`** — ONNX inference engine loaded on `OnModuleInit`:
|
|
|
|
|
+- `preprocess(buffer)` — `sharp` resize to 640×640, strip alpha, convert HWC→CHW, normalize to `[0.0, 1.0]`, output `[1, 3, 640, 640]` tensor
|
|
|
|
|
+- `inference(tensor)` — runs `onnxruntime-node` session
|
|
|
|
|
+- `postprocess(tensor, origW, origH, threshold=0.25)` — output is `[1, N, 6]` (`x1, y1, x2, y2, confidence, class_index`):
|
|
|
|
|
+ - Captures first 5 raw rows as `raw_tensor_sample` for technical evidence
|
|
|
|
|
+ - Filters by confidence threshold
|
|
|
|
|
+ - Scales coords to original image dimensions
|
|
|
|
|
+ - Maps `class_index` → `MPOB_CLASSES`
|
|
|
|
|
+ - Sets `is_health_alert` for `Abnormal` | `Empty_Bunch`
|
|
|
|
|
+
|
|
|
|
|
+**`PalmOilService`** — Orchestrates inference + persistence. `analyzeImage(imageBuffer, filename, batchId?)` pipeline:
|
|
|
|
|
+1. Preprocess via `ScannerProvider`
|
|
|
|
|
+2. Inference with timing (`inference_ms`, `processing_ms`)
|
|
|
|
|
+3. Postprocess → detections + `raw_tensor_sample`
|
|
|
|
|
+4. Save image buffer to `archive/` directory on disk
|
|
|
|
|
+5. Persist to SQLite (`HistoryEntity`) — **fully awaited before returning** (guarantees DB write before socket emit)
|
|
|
|
|
+6. Returns `AnalysisResponse` with Base64 image, `archive_id`, full `technical_evidence` block
|
|
|
|
|
+
|
|
|
|
|
+Additional methods: `getHistory()` (last 50 records), `getRecordByArchiveId()`, `deleteRecord()` (removes DB row + disk image), `clearAllHistory()` (removes all records + disk images).
|
|
|
|
|
+
|
|
|
|
|
+**`VisionGateway`** — WebSocket gateway on `/vision` namespace:
|
|
|
|
|
+- `vision:analyze` handler: receives `VisionStreamPayload` (`frame: string, sourceLabel?, batchId?`), strips data-URI prefix, decodes Base64 → Buffer, calls `PalmOilService.analyzeImage()`, emits `vision:result` or `vision:error`
|
|
|
|
|
+- `chat:send` handler: receives `ChatPayload`, POSTs to `N8N_WEBHOOK_URL` server-to-server (bypasses browser CORS), unwraps array response to first element, emits `chat:result` or `chat:error`
|
|
|
|
|
+- Hard rule: accepts raw, uncompressed Base64 strings only — no binary frames, no WebRTC
|
|
|
|
|
+
|
|
|
|
|
+**`HistoryEntity`** — TypeORM entity fields:
|
|
|
|
|
+- `id` (PK auto), `archive_id` (unique), `batch_id` (nullable, indexed), `filename`
|
|
|
|
|
+- `total_count`, `industrial_summary` (simple-json), `detections` (simple-json array)
|
|
|
|
|
+- `inference_ms`, `processing_ms`, `image_path` (disk reference)
|
|
|
|
|
+- `created_at` (auto timestamp)
|
|
|
|
|
+
|
|
|
|
|
+**`mpob-standards.ts`** (`src/palm-oil/constants/`) — Source of truth:
|
|
|
|
|
+- `MPOB_CLASSES`: index 0–5 → class name strings
|
|
|
|
|
+- `GRADE_COLORS`: class name → hex color
|
|
|
|
|
+- `HEALTH_ALERT_CLASSES`: `['Abnormal', 'Empty_Bunch']`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+**`SurveillanceModule`** (`src/surveillance/`)
|
|
|
|
|
+
|
|
|
|
|
+**`SurveillanceService`** — Boots on `OnModuleInit`, runs two background loops:
|
|
|
|
|
+- **500ms poll loop**: discovers PIDs for n8n (Node.js process containing "n8n" in cmd) and Ollama (`ollama_llama_server` or `ollama`), then calls `pidusage` for CPU/memory metrics
|
|
|
|
|
+- **Port-level heartbeat**: n8n PID only included if port 5678 actively accepts TCP connections (800ms timeout); evicts PID if port goes silent or process dies
|
|
|
|
|
+- Exposes: `getLatestMetrics()`, callback registration for tick events
|
|
|
|
|
+
|
|
|
|
|
+**`SurveillanceGateway`** — WebSocket gateway on `/monitor` namespace:
|
|
|
|
|
+- Wires `SurveillanceService` callbacks: every 500ms tick broadcasts `monitor:data` to all clients
|
|
|
|
|
+- On client connect: immediately pushes latest snapshot (no blank load screen)
|
|
|
|
|
+- `monitor:subscribe` handler: acknowledges and re-sends current snapshot
|
|
|
|
|
|
|
|
### Database
|
|
### Database
|
|
|
-SQLite file `palm_history.db` in the project root. Managed by TypeORM with `synchronize: true` (auto-creates tables — dev only). No external DB setup required.
|
|
|
|
|
|
|
+SQLite file `palm_history.db` in the project root. Managed by TypeORM with `synchronize: true` (auto-creates/migrates tables — dev only). No external DB setup required.
|
|
|
|
|
|
|
|
-### Required File
|
|
|
|
|
-`best.onnx` must be placed in the **project root directory** (not `src/`). This is the YOLOv8 ONNX model loaded by `ScannerProvider` at inference time.
|
|
|
|
|
|
|
+### Required Files
|
|
|
|
|
+- `best.onnx` — YOLOv8 ONNX model, must be placed in the **project root** (not `src/`). Loaded by `ScannerProvider` at startup.
|
|
|
|
|
+- `archive/` directory — created automatically by `PalmOilService` for disk image storage.
|
|
|
|
|
+
|
|
|
|
|
+### Configuration (`.env`)
|
|
|
|
|
+```
|
|
|
|
|
+N8N_WEBHOOK_URL=<n8n webhook URL>
|
|
|
|
|
+PORT=3000 # optional
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
### WebSocket Event Contracts
|
|
### WebSocket Event Contracts
|
|
|
-| Namespace | Client → Server | Server → Client |
|
|
|
|
|
-|---|---|---|
|
|
|
|
|
-| `/vision` | `vision:analyze` `{ frame: string, sourceLabel?: string }` | `vision:result`, `vision:error` |
|
|
|
|
|
-| `/vision` | `chat:send` `{ message: string }` | `chat:result`, `chat:error` |
|
|
|
|
|
-| `/monitor` | `monitor:subscribe` | `monitor:data` (MonitorPayload[]), `monitor:status` |
|
|
|
|
|
|
|
+| Namespace | Client → Server | Payload | Server → Client |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| `/vision` | `vision:analyze` | `{ frame: string, sourceLabel?: string, batchId?: string }` | `vision:result`, `vision:error` |
|
|
|
|
|
+| `/vision` | `chat:send` | `{ message: string }` | `chat:result`, `chat:error` |
|
|
|
|
|
+| `/monitor` | `monitor:subscribe` | — | `monitor:data` (MonitorPayload[]) |
|