diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eaf8a9..c5f9dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,64 @@ Tutte le modifiche significative al progetto ALPHA_PROJECT sono documentate qui. --- +## [2026-03-21] Media Agent — Blocco A+B completo + Calendar Agent fix + +### Nuovi workflow n8n + +#### đŸŽŦ Jellyfin Playback Agent [Webhook] (`AyrKWvboPldzZPsM`) — Blocco A ✅ + +Webhook in real-time che intercetta PlaybackStart / PlaybackStop da Jellyfin. + +**Bug fix applicati (debugging session):** +1. **Webhook path** — `webhookId` deve essere top-level nel nodo JSON (non in `parameters`), altrimenti n8n v2.5.2 in queue mode registra path come `{workflowId}/{nodeName}/{path}` non raggiungibile +2. **SSL Patroni** — `NODE_TLS_REJECT_UNAUTHORIZED=0` aggiunto a `n8n-app` e `n8n-app-worker` deployments (Patroni forza `hostssl`) +3. **Postgres queryParams** — Postgres node v2 non valuta correttamente `$json.*` in `queryParams` → migrato a SQL inline con `{{ $json.field }}` +4. **Jellyfin body format** — il plugin Webhook di Jellyfin invia il body come stringa JSON embedded (`$json.body = '{"ServerId":...}'`) → aggiunto `JSON.parse()` nel Code node +5. **Jellyfin field names** — campi reali: `Name` (non `ItemName`), `DeviceName`/`Client`, `UserId` (senza trattini) + +**Comportamento verificato:** +- PlaybackStart → INSERT `behavioral_context` (`do_not_disturb=true`) +- PlaybackStop (chiusura player) → UPDATE `end_at`, `do_not_disturb=false` +- `agent_messages` → `â–ļī¸ Ghost in the Shell (Chrome - PC)` / `âšī¸ â€Ļ` +- La pausa NON triggerizza PlaybackStop (solo la chiusura del player lo fa) + +--- + +#### đŸŽŦ Media Library Sync [Schedule] (`o3uM1xDLTAKw4D6E`) — Blocco B1 ✅ + +Weekly cron (domenica 03:00). Radarr + Sonarr → GPT-4.1 → Postgres + Qdrant. + +- Fetch Radarr `/radarr/api/v3/movie` + Sonarr `/sonarr/api/v3/series` +- Merge e normalizzazione: `{type, title, year, genres, status, source, source_id}` +- GPT-4.1 (GitHub Copilot): analisi taste → `{top_genres, preferred_types, library_stats, taste_summary, notable_patterns}` +- Postgres upsert: `memory_facts` (source=`media_library`, source_ref=`media_preferences_summary`, expires +7d) +- Loop per ogni titolo: Ollama `nomic-embed-text` (768-dim) → Qdrant `media_preferences` (Cosine) + +**Qdrant collection `media_preferences` creata** (PUT `/collections/media_preferences`, vettori 768-dim Cosine). + +--- + +#### đŸŽžī¸ Jellyfin Watch History Sync [Schedule] (`K07e4PPANXDkmQsr`) — Blocco B2 ✅ + +Daily cron (04:00). Jellyfin history → GPT-4.1 → Postgres. + +- Fetch `/Users/{martin_id}/Items` ultimi 100 played, filtro PlayCount > 0 e last 90 days +- GPT-4.1: `{recent_favorites, preferred_genres, watch_patterns, completion_rate, notes}` +- Postgres upsert: `memory_facts` (source=`jellyfin`, source_ref=`watch_history_summary`, expires +30d) + +**Jellyfin API token** (`d153606c1ca54574a20d2b40fcf1b02e`) creato via `POST /Auth/Keys?app=Pompeo` con sessione admin (`admin` / `__Montecarlo00!`). + +--- + +### Fix workflow esistenti + +#### 📅 Calendar Agent (`4ZIEGck9n4l5qaDt`) — 2 bug fix + +1. **`đŸ—‘ī¸ Cleanup Cancellati` — `column "undefined" does not exist`**: l'espressione `.map(i => "'" + i.json.uid.replace(...)+"'")` chiamava `.replace()` su `undefined` quando Parse GPT restituisce `{skip:true}` → l'intera espressione `{{ }}` valutava a `undefined` (non quotato) → PostgreSQL interpretava `undefined` come identificatore. Fix: aggiunto `.filter(i => i.json.uid)` prima del `.map()` + `String()` wrapper. +2. **`💾 Postgres - Salva Evento` — `updated_at`**: ON CONFLICT UPDATE includeva `updated_at = NOW()` ma la colonna non esiste in `memory_facts`. Rimosso. + +--- + ## [2026-03-21] Paperless Upload — integrazione memoria Postgres + Qdrant ### Modifiche al workflow `📄 Paperless — Upload Documento [Multi]` (`GBPFFq8rmbdFrNn9`) diff --git a/README.md b/README.md index d9c9146..5e56c00 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,62 @@ Imports bank CSV statements (Banca Sella format) into Actual Budget via Telegram **Common pattern across Paperless + Actual workflows**: GitHub Copilot token is obtained fresh at each run (`GET https://api.github.com/copilot_internal/v2/token`), then used for `POST https://api.githubcopilot.com/chat/completions` with model `gpt-4.1`. +### đŸŽŦ Pompeo — Jellyfin Playback Agent [Webhook] (`AyrKWvboPldzZPsM`) ✅ Active + +Webhook-based — triggered in real time by the Jellyfin Webhook plugin on every `PlaybackStart` / `PlaybackStop` event. + +- Webhook path: `jellyfin-playback` (via `https://orchestrator.mt-home.uk/webhook/jellyfin-playback`) +- Normalizes the Jellyfin payload (body is a JSON-encoded string → `JSON.parse` required) +- On **PlaybackStart**: INSERT into `behavioral_context` (`event_type=watching_media`, `do_not_disturb=true`, metadata in `notes` JSONB: `item`, `device`, `item_type`) +- On **PlaybackStop**: UPDATE `behavioral_context` → set `end_at=now()`, `do_not_disturb=false` +- Both events write to `agent_messages` (subject: `â–ļī¸ {title} ({device})` or `âšī¸ â€Ļ`) +- **User filter**: only processes events for `UserId = 42369255a7c64917a28fc26d4c7f8265` (Martin) +- **Jellyfin stop behavior**: `PlaybackStop` fires only on player close, NOT on pause + +Known quirks fixed: +- Jellyfin webhook plugin sends body as `$json.body` (JSON string) — must `JSON.parse()` before reading fields +- Real Jellyfin field names: `Name` (not `ItemName`), `DeviceName` / `Client`, `UserId` (no dashes) + +--- + +### đŸŽŦ Pompeo — Media Library Sync [Schedule] (`o3uM1xDLTAKw4D6E`) ✅ Active + +Weekly cron — every **Sunday at 03:00**. + +- Fetches all movies from **Radarr** (`/radarr/api/v3/movie`) and all series from **Sonarr** (`/sonarr/api/v3/series`) +- Merges into unified list: `{type, title, year, genres, status: available|missing|monitored|unmonitored, source, source_id}` +- **GPT-4.1 analysis**: extracts `top_genres`, `preferred_types`, `library_stats`, `taste_summary`, `notable_patterns` +- **Postgres upsert**: `memory_facts` (source=`media_library`, source_ref=`media_preferences_summary`, expires +7d) +- **Per-item loop**: for each movie/series → Ollama `nomic-embed-text` (768-dim) → Qdrant upsert into `media_preferences` collection + - Qdrant payload: `{title, year, type, genres, status, source, source_id, expires_at (+6 months)}` + +Internal endpoints used: +- `http://radarr.media.svc.cluster.local:7878/radarr/api/v3/movie?apikey=922d1405ab1147019d98a2997d941765` +- `http://sonarr.media.svc.cluster.local:8989/sonarr/api/v3/series?apikey=22140655993a4ff6bf12314813ec6982` +- `http://ollama.ai.svc.cluster.local:11434/api/embeddings` (model: `nomic-embed-text`) +- `http://qdrant.persistence.svc.cluster.local:6333` (api-key: sealed secret `qdrant-api-secret`) + +--- + +### đŸŽžī¸ Pompeo — Jellyfin Watch History Sync [Schedule] (`K07e4PPANXDkmQsr`) ✅ Active + +Daily cron — every day at **04:00**. + +- Fetches last 100 played/partially-played items for Martin from Jellyfin API + - Endpoint: `/Users/42369255a7c64917a28fc26d4c7f8265/Items?Recursive=true&IncludeItemTypes=Movie,Episode&SortBy=DatePlayed&SortOrder=Descending&Limit=100` + - Auth: `Authorization: MediaBrowser Token="d153606c1ca54574a20d2b40fcf1b02e"` (Pompeo API key) +- Filters items with `PlayCount > 0` and `LastPlayedDate` within 90 days +- **GPT-4.1 analysis**: extracts `recent_favorites`, `preferred_genres`, `watch_patterns`, `completion_rate`, `notes` +- **Postgres upsert**: `memory_facts` (source=`jellyfin`, source_ref=`watch_history_summary`, expires +30d) +- Skips silently if no played items found (IF node guards the LLM call) + +Jellyfin credentials: +- API Token (app: Pompeo): `d153606c1ca54574a20d2b40fcf1b02e` — created via `POST /Auth/Keys?app=Pompeo` with admin session +- Martin UserId: `42369255a7c64917a28fc26d4c7f8265` (from Jellyfin SQLite DB / webhook payload) +- Admin user `admin` with password `__Montecarlo00!` (local auth, SSO via Authentik is separate) + +--- + ### 📅 Pompeo — Calendar Agent [Schedule] (`4ZIEGck9n4l5qaDt`) ✅ Active Runs every morning at 06:30 (and on-demand via manual trigger). @@ -407,6 +463,15 @@ Calendars: Lavoro, Famiglia, Spazzatura, Pulizie, Formula 1, WEC, Inter, Complea | `ZIVFNgI3esCKuYXc` | Google Calendar account | Google Calendar OAuth2 (also used for Tasks API) | | `u0JCseXGnDG5hS9F` | Home Assistant API | HTTP Header Auth (long-lived HA token) | | `mRqzxhSboGscolqI` | Pompeo — PostgreSQL | Postgres (database: `pompeo`, user: `martin`) | +| `u0JCseXGnDG5hS9F` | Home Assistant API | HTTP Header Auth (long-lived HA token) | + +### Qdrant Collections + +| Collection | Dimensions | Distance | Model | Used by | +|---|---|---|---|---| +| `media_preferences` | 768 | Cosine | `nomic-embed-text` (Ollama) | Media Library Sync (B1) | + +Qdrant API key: sealed secret `qdrant-api-secret` in namespace `persistence` → `__Montecarlo00!` --- @@ -437,8 +502,8 @@ Calendars: Lavoro, Famiglia, Spazzatura, Pulizie, Formula 1, WEC, Inter, Complea - Tabelle: `user_profile`, `memory_facts` (+ `source_ref` + dedup index), `finance_documents`, `behavioral_context`, `agent_messages` - Multi-tenancy: campo `user_id` su tutte le tabelle, seed `martin` + `shared` - Script DDL: `alpha/db/postgres.sql` -- [ ] Verify embedding endpoint via Copilot (`text-embedding-3-small`) as bootstrap fallback -- [ ] Plan migration to local Ollama embedding model once LLM server is online +- [x] Verify embedding endpoint via Copilot (`text-embedding-3-small`) as bootstrap fallback +- [x] ~~Plan migration to local Ollama embedding model once LLM server is online~~ ✅ Active — `nomic-embed-text` via `http://ollama.ai.svc.cluster.local:11434` (768-dim, multilingual) - [ ] Create `ha_sensor_config` table in Postgres and seed initial sensor patterns --- @@ -465,6 +530,17 @@ Calendars: Lavoro, Famiglia, Spazzatura, Pulizie, Formula 1, WEC, Inter, Complea - Telegram daily briefing at 06:30 - **Phase 2**: add weekly Qdrant embedding for semantic retrieval +- [x] ~~**Jellyfin Playback Agent**~~ ✅ 2026-03-21 — `AyrKWvboPldzZPsM` + - Webhook: PlaybackStart → `behavioral_context` INSERT (`do_not_disturb=true`), PlaybackStop → UPDATE (`end_at`, `do_not_disturb=false`) + - `agent_messages` populated with `â–ļī¸`/`âšī¸` + title + device + - User filter: Martin only (UserId `42369255â€Ļ`) + +- [x] ~~**Media Library Sync (B1)**~~ ✅ 2026-03-21 — `o3uM1xDLTAKw4D6E` + - Weekly (Sunday 03:00): Radarr + Sonarr → GPT-4.1 taste analysis → `memory_facts` + Qdrant `media_preferences` (768-dim, nomic-embed-text) + +- [x] ~~**Jellyfin Watch History Sync (B2)**~~ ✅ 2026-03-21 — `K07e4PPANXDkmQsr` + - Daily (04:00): Jellyfin play history (90d window) → GPT-4.1 pattern analysis → `memory_facts` + - [ ] **Finance Agent** (extend beyond Paperless) - Read Actual Budget export or API - Persist transactions, monthly summaries to `finance_documents`