Files
Alpha/workflows/4ZIEGck9n4l5qaDt.json

464 lines
18 KiB
JSON

{
"id": "4ZIEGck9n4l5qaDt",
"name": "📅 Pompeo — Calendar Agent [Schedule]",
"nodes": [
{
"id": "5162caf6661f4aa8",
"name": "⏰ Schedule",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
0,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/30 * * * *"
}
]
}
}
},
{
"id": "63a2b1a9ba7a4cf2",
"name": "📅 Imposta Range",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
260,
300
],
"parameters": {
"jsCode": "const now = new Date();\nconst start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0).toISOString();\nconst end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7, 23, 59, 59).toISOString();\nreturn [{json: {start, end}}];"
}
},
{
"id": "89ee9f58cd2f4d4d",
"name": "🔑 Ottieni Token Copilot",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
520,
300
],
"parameters": {
"url": "https://api.github.com/copilot_internal/v2/token",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth",
"options": {}
},
"credentials": {
"httpHeaderAuth": {
"id": "vBwUxlzKrX3oDHyN",
"name": "GitHub Copilot OAuth Token"
}
}
},
{
"id": "ffb3d86cf2a54659",
"name": "📋 Prepara Calendari",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
780,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const start = $('📅 Imposta Range').first().json.start;\nconst end = $('📅 Imposta Range').first().json.end;\nconst HA = 'http://10.30.20.100:8123';\n\n// Note: calendar.films and calendar.serie_tv removed — handled by Media Agent (direct Radarr/Sonarr API)\nconst calendars = [\n {entity_id:'calendar.calendar', name:'Lavoro', category:'work', user_id:'martin'},\n {entity_id:'calendar.famiglia', name:'Famiglia', category:'personal',user_id:'martin'},\n {entity_id:'calendar.spazzatura', name:'Spazzatura', category:'chores', user_id:'martin'},\n {entity_id:'calendar.pulizie', name:'Pulizie', category:'chores', user_id:'martin'},\n {entity_id:'calendar.formula_1', name:'Formula 1', category:'leisure', user_id:'martin'},\n {entity_id:'calendar.lm_wec_fia_world_endurance_championship', name:'WEC', category:'leisure', user_id:'martin'},\n {entity_id:'calendar.inter_calendar', name:'Inter', category:'leisure', user_id:'martin'},\n {entity_id:'calendar.birthdays', name:'Compleanni', category:'social', user_id:'martin'},\n {entity_id:'calendar.varie', name:'Varie', category:'misc', user_id:'martin'},\n {entity_id:'calendar.festivita_in_italia', name:'Festività Italia',category:'holiday', user_id:'shared'}\n];\n\nreturn calendars.map(cal => ({json: {...cal, start, end, ha_base_url: HA}}));"
}
},
{
"id": "7985f2d26aa64e69",
"name": "📡 HA - Scarica Calendario",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
1040,
300
],
"parameters": {
"url": "={{ $json.ha_base_url + '/api/calendars/' + $json.entity_id + '?start=' + $json.start + '&end=' + $json.end }}",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "httpHeaderAuth",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"credentials": {
"httpHeaderAuth": {
"id": "u0JCseXGnDG5hS9F",
"name": "Home Assistant API"
}
},
"onError": "continueRegularOutput"
},
{
"id": "9ed15cad33234465",
"name": "🏷️ Estrai ed Etichetta",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1300,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const results = [];\nconst allCals = $('📋 Prepara Calendari').all();\n\nfor (let i = 0; i < $input.all().length; i++) {\n const httpItem = $input.all()[i];\n const pairedIdx = (typeof httpItem.pairedItem?.item === 'number') ? httpItem.pairedItem.item : i;\n const cal = allCals[pairedIdx]?.json ?? {};\n\n const events = Array.isArray(httpItem.json) ? httpItem.json : [];\n\n for (const ev of events) {\n const startVal = ev.start?.dateTime || ev.start?.date || null;\n const endVal = ev.end?.dateTime || ev.end?.date || null;\n const uid = ev.uid ||\n `${cal.entity_id}_${(ev.summary||'').replace(/\\s+/g,'_')}_${startVal}`;\n\n results.push({json: {\n uid,\n summary: ev.summary || '',\n description: ev.description || null,\n location: ev.location || null,\n start_dt: startVal,\n end_dt: endVal,\n all_day: !ev.start?.dateTime,\n calendar_name: cal.name || 'Unknown',\n calendar_entity: cal.entity_id || '',\n calendar_category:cal.category || 'misc',\n user_id: cal.user_id || 'martin'\n }});\n }\n}\n\nreturn results.length > 0 ? results : [{json:{skip:true}}];"
}
},
{
"id": "9a2b47a3ce774ca1",
"name": "📝 Prepara Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1560,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const allItems = $input.all().map(i => i.json).filter(e => !e.skip);\nif (!allItems.length) return [{json:{skip:true, events:[], prompt:null, event_count:0}}];\n\n// Dedup by uid\nconst seen = new Set();\nconst events = allItems.filter(e => {\n if (!e.uid || seen.has(e.uid)) return false;\n seen.add(e.uid); return true;\n});\n\nconst fmt = dt => dt ? dt.substring(0,16).replace('T',' ') : '';\n\nconst eventList = events.map((e, idx) =>\n `${idx+1}. [${e.calendar_name}] ${e.summary}` +\n (e.all_day ? ' | Tutto il giorno' : ` | ${fmt(e.start_dt)} → ${fmt(e.end_dt)}`) +\n (e.location ? ` | 📍 ${e.location}` : '') +\n (e.description ? ` | ${e.description.substring(0,80)}` : '') +\n ` | uid: ${e.uid}`\n).join('\\n');\n\nconst prompt =\n`Sei Pompeo, l'assistente AI di Martin Tahiraj. Analizza questi eventi calendario (prossimi 7 giorni) e classificali.\n\nEVENTI:\n${eventList}\n\nRispondi SOLO con JSON valido nel formato:\n{\n \"events\": [\n {\n \"uid\": \"<uid originale>\",\n \"category\": \"work|personal|chores|leisure|social|holiday|misc\",\n \"action_required\": false,\n \"action_text\": null,\n \"do_not_disturb\": false,\n \"priority\": \"high|medium|low\",\n \"behavioral_context\": \"es: work_meeting|sport_event|home_chores|birthday\",\n \"home_presence_expected\": true,\n \"pompeo_note\": \"Nota breve in italiano per il briefing\"\n }\n ]\n}`;\n\nreturn [{json:{events, prompt, event_count: events.length}}];"
}
},
{
"id": "5589a6ce6f1a4f81",
"name": "🤖 GPT-4.1 - Analizza Calendari",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
1820,
300
],
"parameters": {
"method": "POST",
"url": "https://api.githubcopilot.com/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ 'Bearer ' + $('🔑 Ottieni Token Copilot').first().json.token }}"
},
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "Copilot-Integration-Id",
"value": "vscode-chat"
},
{
"name": "Editor-Version",
"value": "vscode/1.85.0"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({\"model\":\"gpt-4.1\",\"messages\":[{\"role\":\"system\",\"content\":\"Sei Pompeo, l'assistente AI di Martin. Rispondi SOLO con JSON valido.\"},{\"role\":\"user\",\"content\":$json.prompt}],\"response_format\":{\"type\":\"json_object\"},\"max_tokens\":2048}) }}",
"options": {}
}
},
{
"id": "89505d194e014900",
"name": "📋 Parse Risposta GPT",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2080,
300
],
"parameters": {
"jsCode": "const raw = $input.first().json;\nconst content = (raw.choices || [])[0]?.message?.content || '{}';\nlet parsed;\ntry { parsed = JSON.parse(content); }\ncatch(e) { throw new Error('GPT non JSON: ' + content.substring(0,300)); }\n\nconst gptEvents = parsed.events || [];\nconst originalEvents = $('📝 Prepara Prompt').first().json.events || [];\n\nif (!originalEvents.length) return [{json:{skip:true}}];\n\nreturn originalEvents.map((orig, i) => {\n const enriched = gptEvents.find(e => e.uid === orig.uid) || gptEvents[i] || {};\n return {json:{\n ...orig,\n category: enriched.category || orig.calendar_category || 'misc',\n action_required: enriched.action_required || false,\n action_text: enriched.action_text || null,\n do_not_disturb: enriched.do_not_disturb || false,\n priority: enriched.priority || 'low',\n behavioral_context: enriched.behavioral_context || null,\n home_presence_expected: enriched.home_presence_expected ?? null,\n pompeo_note: enriched.pompeo_note || orig.summary\n }};\n});"
}
},
{
"id": "5bb69072497546d3",
"name": "💾 Postgres - Salva Evento",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
2860,
300
],
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO memory_facts (user_id, source, source_ref, category, subject, detail, action_required, action_text, expires_at)\nVALUES (\n '{{ $json.user_id }}',\n 'calendar',\n '{{ $json.uid }}',\n '{{ $json.category }}',\n '{{ $json.summary }}',\n '{{ JSON.stringify({start:$json.start_dt,end:$json.end_dt,all_day:$json.all_day,location:$json.location,calendar:$json.calendar_name}) }}'::jsonb,\n {{ $json.action_required ? 'true' : 'false' }},\n '{{ $json.action_text || \"\" }}',\n '{{ $json.expires_at }}'::timestamptz\n)\nON CONFLICT ON CONSTRAINT memory_facts_dedup_idx DO UPDATE SET\n subject = EXCLUDED.subject,\n detail = EXCLUDED.detail,\n action_required = EXCLUDED.action_required,\n action_text = EXCLUDED.action_text,\n expires_at = EXCLUDED.expires_at",
"options": {}
},
"credentials": {
"postgres": {
"id": "mRqzxhSboGscolqI",
"name": "Pompeo — PostgreSQL"
}
}
},
{
"id": "53f04d2895cf40b2",
"name": "📦 Aggrega Risultati",
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
3120,
300
],
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "events"
}
},
{
"id": "5573e6e8d378427e",
"name": "✍️ Prepara Messaggio",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3380,
300
],
"parameters": {
"jsCode": "const events = ($json.events || []).map(i => i.json || i).filter(e => !e.skip);\nif (!events.length) return [{json:{message:'📅 *Briefing Calendario*\\n\\nNessun evento nei prossimi 7 giorni.'}}];\n\nconst emojis = {work:'🗂',personal:'👨‍👩‍👧',chores:'🧹',leisure:'🏎',social:'🎂',holiday:'🎉',misc:'📌'};\nconst byCal = {};\nfor (const e of events) {\n const k = e.calendar_name || 'Varie';\n if (!byCal[k]) byCal[k] = {cat: e.calendar_category, evs:[]};\n byCal[k].evs.push(e);\n}\n\nconst today = new Date().toLocaleDateString('it-IT',{weekday:'long',day:'numeric',month:'long'});\nlet msg = `📅 *Briefing Calendario — Prossimi 7 giorni*\\n_(${today})_\\n\\n`;\n\nfor (const [cal, {cat, evs}] of Object.entries(byCal)) {\n msg += `${emojis[cat]||'📌'} *${cal}*\\n`;\n for (const e of evs) {\n const dt = e.all_day ? '' : (e.start_dt ? ' `' + e.start_dt.substring(11,16) + '`' : '');\n const flag = e.action_required ? ' ⚡' : '';\n msg += `• ${e.summary}${dt}${flag}\\n`;\n }\n msg += '\\n';\n}\nmsg += `_${events.length} eventi — generato da Pompeo_`;\nreturn [{json:{message:msg}}];"
}
},
{
"id": "420db8250bf9492f",
"name": "📱 Telegram - Briefing Giornaliero",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1,
"position": [
3640,
300
],
"parameters": {
"resource": "message",
"operation": "sendMessage",
"chatId": "-4814221197",
"text": "={{ $json.message }}",
"additionalFields": {
"parse_mode": "Markdown"
}
},
"credentials": {
"telegramApi": {
"id": "uTXHLqcCJxbOvqN3",
"name": "Telegram account"
}
}
},
{
"id": "7fd81a1892184b61",
"name": "🗑️ Cleanup Cancellati",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
2340,
300
],
"parameters": {
"operation": "executeQuery",
"query": "DELETE FROM memory_facts\nWHERE source = 'calendar'\n AND source_ref IS NOT NULL\n AND (detail->>'start')::timestamptz >= '{{ $('📅 Imposta Range').first().json.start }}'::timestamptz\n AND (detail->>'start')::timestamptz < '{{ $('📅 Imposta Range').first().json.end }}'::timestamptz\n AND source_ref NOT IN ({{ $('📋 Parse Risposta GPT').all().filter(i => i.json.uid).map(i => \"'\" + String(i.json.uid).replace(/'/g,\"''\") + \"'\").join(',') || \"'__no_uid__'\" }})",
"options": {}
},
"credentials": {
"postgres": {
"id": "mRqzxhSboGscolqI",
"name": "Pompeo — PostgreSQL"
}
}
},
{
"id": "f8922396b255475a",
"name": "🔀 Riemetti Eventi",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2600,
300
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "return $('📋 Parse Risposta GPT').all().filter(i => !i.json.skip);"
}
}
],
"connections": {
"⏰ Schedule": {
"main": [
[
{
"node": "📅 Imposta Range",
"type": "main",
"index": 0
}
]
]
},
"📅 Imposta Range": {
"main": [
[
{
"node": "🔑 Ottieni Token Copilot",
"type": "main",
"index": 0
}
]
]
},
"🔑 Ottieni Token Copilot": {
"main": [
[
{
"node": "📋 Prepara Calendari",
"type": "main",
"index": 0
}
]
]
},
"📋 Prepara Calendari": {
"main": [
[
{
"node": "📡 HA - Scarica Calendario",
"type": "main",
"index": 0
}
]
]
},
"📡 HA - Scarica Calendario": {
"main": [
[
{
"node": "🏷️ Estrai ed Etichetta",
"type": "main",
"index": 0
}
]
]
},
"🏷️ Estrai ed Etichetta": {
"main": [
[
{
"node": "📝 Prepara Prompt",
"type": "main",
"index": 0
}
]
]
},
"📝 Prepara Prompt": {
"main": [
[
{
"node": "🤖 GPT-4.1 - Analizza Calendari",
"type": "main",
"index": 0
}
]
]
},
"🤖 GPT-4.1 - Analizza Calendari": {
"main": [
[
{
"node": "📋 Parse Risposta GPT",
"type": "main",
"index": 0
}
]
]
},
"📋 Parse Risposta GPT": {
"main": [
[
{
"node": "🗑️ Cleanup Cancellati",
"type": "main",
"index": 0
}
]
]
},
"💾 Postgres - Salva Evento": {
"main": [
[
{
"node": "📦 Aggrega Risultati",
"type": "main",
"index": 0
}
]
]
},
"📦 Aggrega Risultati": {
"main": [
[
{
"node": "✍️ Prepara Messaggio",
"type": "main",
"index": 0
}
]
]
},
"✍️ Prepara Messaggio": {
"main": [
[
{
"node": "📱 Telegram - Briefing Giornaliero",
"type": "main",
"index": 0
}
]
]
},
"🗑️ Cleanup Cancellati": {
"main": [
[
{
"node": "🔀 Riemetti Eventi",
"type": "main",
"index": 0
}
]
]
},
"🔀 Riemetti Eventi": {
"main": [
[
{
"node": "💾 Postgres - Salva Evento",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"triggerCount": 1,
"versionId": "5b03aaed-af76-4d6a-bff0-5a2949c24dfe",
"owner": {
"type": "personal",
"projectId": "Hdttz401OqqtObPo",
"projectName": "Martin Tahiraj <tahiraj.martin@gmail.com>",
"personalEmail": "tahiraj.martin@gmail.com"
},
"parentFolderId": null,
"isArchived": false
}