p-2000/CLAUDE.md
edo 2c37d5934c Add P-2000 real-time monitor with WebSocket, map, and filter panel
- Koa server on port 10000 with WebSocket live feed from 112-nu.nl RSS
- PM2 watch mode for auto-restart on file changes
- Dark navy UI with per-type color accents (fire/ambulance/police/rescue)
- Slide-in filter panel with service type + 12 Dutch province filters
- Card click opens detail modal: parsed priority (A1/A2/MGS), vehicle
  number, rit/bon number, alarm type, meldkamer, and eenheden
- Server-side Nominatim geocoder (cached, rate-limited) powering an
  interactive Leaflet/OpenStreetMap map in the modal (CartoDB Voyager tiles)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 10:16:38 +00:00

98 lines
2.5 KiB
Markdown

# P-2000 Monitor
Real-time Dutch emergency services (P-2000) channel monitor. Koa server + WebSocket + RSS polling, dark shadcn-style UI.
## Stack
| Layer | Tech |
|-------------|-------------------------------------------------------|
| Server | Node.js 20, Koa 2, koa-static, koa-router |
| WebSocket | `ws` library — attached to same HTTP server |
| P-2000 data | RSS polling via axios + xml2js (30s interval) |
| Process mgr | PM2 with watch mode (`pm2.config.js`) |
| Frontend | Vanilla JS, TailwindCSS Play CDN, shadcn-style design |
## Port
`10000` (override via `PORT` env var)
## Running
```bash
# Install
npm install
# Dev (PM2 watch mode — auto-restart on src/ or server.js changes)
npm run dev
# Logs
npm run logs
# Stop
npm run stop
# Plain node (no PM2)
npm start
```
## Project layout
```
server.js # Koa HTTP + WebSocket server, P-2000 event bridge
src/p2000.js # EventEmitter: RSS fetcher, XML parser, type detector
public/index.html # Single-page frontend (TailwindCSS + vanilla JS)
pm2.config.js # PM2 watch config
logs/ # PM2 out/error logs (git-ignored)
```
## P-2000 Feed source
**Primary:** `https://112-nu.nl/hulpdiensten/rss` (all services combined, polled every 30s)
Per-service feeds also available but not used (combined is sufficient):
- `https://112-nu.nl/brandweer/rss`
- `https://112-nu.nl/ambulance/rss`
- `https://112-nu.nl/politie/rss`
Change the feed URL via `COMBINED_FEED` constant in `src/p2000.js`.
## Message types
Detected from `<category>` RSS tag first, then keyword fallback:
| Category code | Type |
|---------------|-------------|
| BRA, BRW | `fire` |
| AMB, MKA | `ambulance` |
| POL | `police` |
| KNRM, HELI | `rescue` |
Extend `CATEGORY_MAP` or `KEYWORD_MAP` in `src/p2000.js` to tune detection.
## WebSocket protocol
Client connects to `ws://localhost:10000/ws`
Server → Client messages:
```json
{ "type": "history", "messages": [ ...MessageObject ] }
{ "type": "message", "message": MessageObject }
```
`MessageObject`:
```json
{
"id": "unique-guid",
"title": "P1 Melding Brand - Rotterdam",
"description": "Extended alert text",
"pubDate": "2024-01-15T12:34:56.000Z",
"link": "https://...",
"type": "fire",
"region": "Zuid-Holland"
}
```
## Health check
`GET /health``{ status, uptime, messages }`