# 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=<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[]) |
