# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## What This Is 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). ## Commands ```bash npm install npm run start:dev # Watch mode dev server (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:watch # Jest watch mode npm run test:cov # Coverage report npm run test:e2e # End-to-end tests jest --testPathPattern=palm-oil # Run a single test file ``` Set `PORT` env var to override port 3000. Set `N8N_WEBHOOK_URL` in `.env` for chat proxy and agent readiness probing. ## Architecture ### Modules **`PalmOilModule`** (`src/palm-oil/`) **`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 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 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= PORT=3000 # optional ``` ### WebSocket Event Contracts | 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[]) |