# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What This Is NestJS backend optimized for ARM/Android (Termux) deployment. Provides server-side YOLOv8 ONNX inference via WASM (single-threaded, no native bindings), SQLite history persistence (sql.js + disk image archiving), a unified FIS-protocol WebSocket gateway, n8n chat proxying, and a process surveillance monitor. Detection classes: `Ripe`, `Unripe`, `Underripe`, `Overripe`, `Abnormal`, `Empty_Bunch` (MPOB standard, indices 0–5). ## Commands ```bash npm install npm run start:dev # Watch mode dev server (HTTPS, port 3000) npm run start # Single-run dev server npm run start:prod # Production (from dist/) npm run build # Compile → dist/ npm run lint # ESLint with auto-fix npm run test # Jest unit tests npm run test:e2e # End-to-end tests ``` Set `PORT` env var to override port 3000. Set `N8N_WEBHOOK_URL` in `.env` for chat proxy and n8n process discovery. ## Architecture ### Modules **`PalmOilModule`** (`src/palm-oil/`) **`OnnxWasmProvider`** (`src/palm-oil/providers/onnx-wasm.provider.ts`) — WASM-based inference engine, `IScannerProvider` implementation: - Loaded on `OnModuleInit`; `ort.env.wasm.numThreads = 1` for ARM/Termux safety - Model loaded from `best.onnx` in the project root - Execution provider: `['wasm']` - `preprocess(buffer)` — Jimp decode → resize 640×640 → RGBA→CHW strip → normalize `[0.0, 1.0]` → `[1, 3, 640, 640]` Float32Array - `inference(tensor)` — runs ONNX session with `{ images: ortTensor }` input, extracts first output key - `postprocess(tensor, origW, origH, threshold=0.25)` — output shape `[1, N, 6]` (`x1, y1, x2, y2, confidence, class_index`): - Captures first 5 rows as `raw_tensor_sample` - Filters by confidence threshold (default 0.25) - 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. Measure original image dimensions via Jimp 2. Preprocess → inference → postprocess via `ScannerProvider` 3. Capture timing (`inference_ms`, `processing_ms`) 4. Write image buffer to `archive/` directory on disk 5. Persist to SQLite (`HistoryEntity`) — **fully awaited before returning** (ordering guarantee: DB write completes before socket emit) 6. Return `AnalysisResponse` with Base64 image, `archive_id`, full `technical_evidence` block Additional methods: `getHistory()` (last 50 records), `getRecordByArchiveId()`, `deleteRecord()` (removes DB row + disk image), `clearAllHistory()`. **`VisionGateway`** (`src/palm-oil/vision.gateway.ts`) — Unified FIS-protocol WebSocket gateway (root namespace, no prefix): **Inbound**: single `request` event carrying `FisAppMessage` envelope `{ serviceId, operation, requestId, payload }`. **Outbound**: `response` event with `FisAppResponse` packets (two-packet streaming pattern). Route table (`${serviceId}:${operation}`): | Route | Payload | Behavior | |---|---|---| | `PalmVision:analyze` | `{ frame: string, sourceLabel?, batchId? }` | Base64 decode → Buffer → `PalmOilService.analyzeImage()` → emit two-packet response with `technical_evidence` | | `History:getAll` | — | Query SQLite (last 50), emit array response | | `History:delete` | `{ archiveId: string }` | Delete record + disk image, emit `{ deleted: boolean }` | | `History:clearAll` | — | Wipe all records + images, emit `{ deleted: number }` | | `Chat:send` | `{ message: string }` | POST to `N8N_WEBHOOK_URL` server-to-server, unwrap array to first element, emit response | | `Chat:clear` | — | Generate new session UUID, emit `{ status: 'success' }` | | `PalmHistory:GetImage` | `{ archiveId: string }` | Load image from disk, encode to Base64 data URL, emit response | Client lifecycle: - On connect: push latest surveillance snapshot (if available) - Store session UUID in `client.data.sessionId` (used for Chat payloads) - Every 500ms tick: broadcasts `response` packet with `SystemMetrics` to all connected clients (via `SurveillanceService` callback registered in `onModuleInit`) **`HistoryEntity`** (`src/palm-oil/entities/history.entity.ts`) — 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 → class name (`0: Empty_Bunch`, `1: Underripe`, `2: Abnormal`, `3: Ripe`, `4: Unripe`, `5: Overripe`) - `GRADE_COLORS`: class name → hex color - `HEALTH_ALERT_CLASSES`: `['Abnormal', 'Empty_Bunch']` --- **`SurveillanceModule`** (`src/surveillance/`) **`SurveillanceService`** — 500ms process monitor via `systeminformation`: - `OnModuleInit`: starts polling interval; `OnModuleDestroy`: clears it - Per tick: queries CPU load, memory, process list via `systeminformation` - Discovers processes by identity: - **NestJS**: match by `process.pid` - **n8n**: command substring match (only if `N8N_WEBHOOK_URL` contains `localhost`) - **Ollama**: command substrings `['ollama', 'ollama_llama_server']` - Classifies each as `ACTIVE` (CPU > 1.0%), `IDLE` (CPU ≤ 1.0%), or `OFFLINE` (not found) - Stores `SystemMetrics` snapshot; triggers registered callback for `VisionGateway` broadcast - Public API: `getLatestMetrics()`, `registerMetricsCallback(cb)` ```typescript interface ServiceStatus { service: string; pid: number | null; status: 'ACTIVE'|'IDLE'|'OFFLINE'; cpu: number; memory: number; } interface SystemMetrics { cpuLoad: number; memUsed: number; memTotal: number; uptime: number; services: ServiceStatus[]; timestamp: Date; } ``` Note: No separate `SurveillanceGateway`. Surveillance broadcasts are handled inside `VisionGateway` via the registered callback. ### Database SQLite file `palm_history.db` in project root. Managed by TypeORM with driver `sqljs` (`sql.js`) and `autoSave: true`. No external DB setup required. `synchronize: true` auto-creates/migrates tables on startup (dev only). ### Key Interfaces (`src/palm-oil/interfaces/`) ```typescript interface DetectionResult { bunch_id: number; // 1-based sequential count class: string; // MPOB class name confidence: number; // [0, 1] is_health_alert: boolean; // true if Abnormal | Empty_Bunch box: [x1, y1, x2, y2]; // pixel coords in original image dims } interface AnalysisResponse { detections: DetectionResult[]; archive_id: string; image_base64: string; // Base64-encoded image technical_evidence: { engine: 'NestJS-ONNX'; archive_id: string; total_count: number; threshold: number; // 0.25 industrial_summary: Record; raw_tensor_sample: number[][]; // first 5 ONNX output rows }; inference_ms: number; processing_ms: number; } ``` ### Required Files - `best.onnx` — YOLOv8 ONNX model in **project root** (not `src/`). Loaded at startup. - `cert/127.0.0.1+1-key.pem` and `cert/127.0.0.1+1.pem` — TLS certificates. HTTPS is always on (no HTTP fallback). - `archive/` — Auto-created by `PalmOilService` on first write. - `tflite/` — TFLite model files (present but not loaded by current server code). ### Configuration (`.env`) ``` N8N_WEBHOOK_URL= # Optional; required for Chat:send and n8n process discovery PORT=3000 # Optional HOST=0.0.0.0 # Default bind address ``` ### CORS Whitelist Origins hardcoded in `main.ts`: - `https://192.168.100.100:4200` - `https://192.168.100.79:4200` - `https://localhost:4200` Update `main.ts` to add new origins (e.g., new Android device IPs). ### Key Differences from `nestjs/` (server-desktop) | Concern | `nestjs/` | `server-android/` | |---|---|---| | ONNX runtime | `onnxruntime-node` (native) | `onnxruntime-web` (WASM, `numThreads=1`) | | Image processing | `sharp` | `Jimp` | | SQLite driver | `sqlite3` | `sql.js` (TypeORM `sqljs`) | | WebSocket namespaces | `/vision` + `/monitor` (separate) | Root namespace (unified) | | WebSocket protocol | Flat events (`vision:analyze`) | FIS envelope (`request`/`response`) | | Surveillance | Separate `SurveillanceGateway` on `/monitor` | Inline broadcast in `VisionGateway` | | Process metrics | `pidusage` | `systeminformation` |