- Workflow n8n tdfInZns7cqo8fU2 actif (15 min): snapshot HA compact -> prompt FR -> LLM local Spark (modèle dynamique via /v1/models) avec fallback OpenRouter claude-haiku-4.5 (credential chiffré, clé hors git) -> validation stricte (enums, bornes, rejet total si invalide) -> helpers advisor_* + divergence + compteurs accord/divergence + logbook. - GARANTIE OMBRE structurelle: écritures limitées à advisor_*/logbook, vérifiée par tests/advisor/run.mjs (16 tests, bloquants au push). - Kill-switch input_boolean.evcs_advisor_enabled. - Extension R2 multi-workflows (lib-node-map WORKFLOWS[], build/extract/push par fichier, n8n-create.mjs, gen-advisor-workflow.mjs). - Helpers HA via package NFS + carte dashboard EVCS Master (backup fait). - 1re reco réelle: auto_solar_plus, cible 35%, accord avec le réel. - Fix sécurité n8n-push: plus de fallback d'id implicite vers le workflow EVCS.
422 lines
23 KiB
JSON
422 lines
23 KiB
JSON
{
|
|
"name": "EVCS Advisor Shadow V1",
|
|
"nodes": [
|
|
{
|
|
"name": "Schedule Trigger",
|
|
"type": "n8n-nodes-base.scheduleTrigger",
|
|
"typeVersion": 1.2,
|
|
"position": [
|
|
0,
|
|
0
|
|
],
|
|
"parameters": {
|
|
"rule": {
|
|
"interval": [
|
|
{
|
|
"field": "minutes",
|
|
"minutesInterval": 15
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "HA Snapshot Advisor",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
200,
|
|
0
|
|
],
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "http://10.10.10.17:8123/api/template",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpBearerAuth",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "Accept",
|
|
"value": "text/plain"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"bodyParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "template",
|
|
"value": "{{ {\n \"advisor_enabled\": states(\"input_boolean.evcs_advisor_enabled\"),\n \"advisor_agree_count\": states(\"input_number.evcs_advisor_agree_count\"),\n \"advisor_diverge_count\": states(\"input_number.evcs_advisor_diverge_count\"),\n \"advisor_prev_strategy\": states(\"input_text.evcs_advisor_strategy\"),\n\n \"soc_percent\": states(\"sensor.victron_battery_soc\"),\n \"battery_current_a\": states(\"sensor.victron_battery_current\"),\n \"pv_power_w\": states(\"sensor.victron_pv_power_system\"),\n \"pv_power_10m_avg_w\": states(\"sensor.victron_pv_power_10m_avg\"),\n \"house_ac_power_w\": states(\"sensor.victron_consumption_power_l1\"),\n \"grid_state\": states(\"switch.edf_general_ok_switch\"),\n \"acin1_available\": states(\"sensor.victron_ac_in1_available\"),\n\n \"evcs_vehicle_connected\": states(\"binary_sensor.victron_evcs_vehicle_connected\"),\n \"evcs_charging\": states(\"switch.victron_evcs_charging\"),\n \"evcs_current_a\": states(\"sensor.victron_evcs_current\"),\n \"evcs_surplus_state\": states(\"input_text.victron_evcs_surplus_state\"),\n \"evcs_source_mode\": states(\"input_text.victron_evcs_source_mode\"),\n\n \"user_strategy\": states(\"input_select.victron_evcs_strategy\"),\n \"allow_grid_hc_night\": states(\"input_boolean.victron_evcs_allow_grid_hc_night\"),\n \"allow_grid_hc_midday\": states(\"input_boolean.victron_evcs_allow_grid_hc_midday\"),\n \"night_target_soc_actual\": states(\"input_number.victron_evcs_night_target_soc\"),\n \"current_max_auto_a\": states(\"input_number.victron_evcs_current_max_auto_a\"),\n \"plafond_edf_on_a\": states(\"input_number.evcs_plafond_courant_edf_on\"),\n\n \"departure_time\": states(\"input_datetime.victron_evcs_departure_time\"),\n \"departure_priority_enabled\": states(\"input_boolean.victron_evcs_departure_priority_enabled\"),\n \"required_energy_kwh\": states(\"input_number.victron_evcs_required_energy_kwh\"),\n\n \"today_remaining_kwh\": states(\"sensor.energy_production_today_remaining\"),\n \"tomorrow_kwh\": states(\"sensor.energy_production_tomorrow\")\n} | tojson }}\n"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"timeout": 15000
|
|
}
|
|
},
|
|
"credentials": {
|
|
"httpBearerAuth": {
|
|
"id": "uv3sknYYigxBkaVN",
|
|
"name": "Bearer Auth account 2"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "BUILD_PROMPT",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
400,
|
|
0
|
|
],
|
|
"parameters": {
|
|
"jsCode": "// =====================================================\n// EVCS ADVISOR SHADOW V1 — BUILD_PROMPT\n// Construit le prompt LLM à partir du snapshot HA compact.\n// MODE OMBRE : ce workflow n'écrit que des helpers advisor_*.\n// =====================================================\n\nfunction num(v, d) {\n const n = Number.parseFloat(v);\n return Number.isFinite(n) ? n : d;\n}\nfunction onOff(v) {\n return (v ?? 'off').toString().trim().toLowerCase() === 'on';\n}\n\nconst raw = $input.first().json;\nconst input = typeof raw.data === 'string' ? JSON.parse(raw.data) : (raw.data || raw);\n\nconst ctx = {\n advisorEnabled: onOff(input.advisor_enabled),\n agreeCount: num(input.advisor_agree_count, 0),\n divergeCount: num(input.advisor_diverge_count, 0),\n prevAdvisorStrategy: (input.advisor_prev_strategy || 'unknown').toString(),\n\n soc: num(input.soc_percent, NaN),\n batteryCurrentA: num(input.battery_current_a, NaN),\n pvW: num(input.pv_power_w, 0),\n pvAvg10mW: num(input.pv_power_10m_avg_w, 0),\n houseW: num(input.house_ac_power_w, 0),\n gridOn: onOff(input.grid_state),\n acinAvailable: num(input.acin1_available, 0) === 1,\n\n vehicleConnected: onOff(input.evcs_vehicle_connected),\n evcsCharging: onOff(input.evcs_charging),\n evcsCurrentA: num(input.evcs_current_a, 0),\n surplusState: (input.evcs_surplus_state || '').toString(),\n sourceMode: (input.evcs_source_mode || '').toString(),\n\n userStrategy: (input.user_strategy || 'auto_solar_plus').toString().trim().toLowerCase(),\n allowGridHcNight: onOff(input.allow_grid_hc_night),\n allowGridHcMidday: onOff(input.allow_grid_hc_midday),\n nightTargetActual: num(input.night_target_soc_actual, 35),\n currentMaxAutoA: num(input.current_max_auto_a, 22),\n plafondEdfOnA: num(input.plafond_edf_on_a, 20),\n\n departureTime: (input.departure_time || '').toString(),\n departurePriority: onOff(input.departure_priority_enabled),\n requiredEnergyKwh: num(input.required_energy_kwh, 0),\n\n todayRemainingKwh: num(input.today_remaining_kwh, 0),\n tomorrowKwh: num(input.tomorrow_kwh, 0),\n};\n\n// Heure Paris\nconst parisFmt = new Intl.DateTimeFormat('fr-FR', {\n timeZone: 'Europe/Paris',\n weekday: 'long', day: '2-digit', month: '2-digit',\n hour: '2-digit', minute: '2-digit', hour12: false,\n});\nconst nowParis = parisFmt.format(new Date());\n\nconst systemPrompt = [\n 'Tu es le PLANIFICATEUR ÉNERGIE d\\'une maison autonome française. Tu es en MODE OBSERVATION :',\n 'tes recommandations sont enregistrées pour comparaison, elles ne pilotent rien.',\n '',\n 'INSTALLATION : Victron ESS SANS injection réseau. Batterie 17,5 kWh utile (plancher sécurité 22%).',\n 'PV ~9 kWc. Borne EVCS 6-28 A. Réseau EDF commutable (cher en HP, bon marché en HC).',\n 'Heures creuses : 01:24-07:24 et 12:24-14:24. Véhicules : Jeep, moto Zéro, VW hybride (arrive en fin d\\'après-midi).',\n '',\n 'STRATÉGIES POSSIBLES (input_select) :',\n '- auto_solar_plus : automatique, solaire prioritaire + réseau HC la nuit si véhicule branché (défaut recommandé)',\n '- auto_solar : automatique, solaire strict (jamais de réseau)',\n '- force_grid_now : charge VE immédiate sur réseau EDF (à réserver aux urgences/gros besoins)',\n '- force_charge_now : charge locale immédiate quelle que soit la source (secours manuel)',\n '- stop : tout couper',\n '',\n 'PRINCIPES : maximiser l\\'autoconsommation solaire ; réseau seulement en HC ou urgence ;',\n 'ne jamais viser un SOC matin < 22% ; forecast demain fort (>=18 kWh) => cible matin basse (~35%),',\n 'moyen (>=10) => ~55%, faible => ~85%. Plafond courant : 20 A max si EDF ON, sinon 22 A auto.',\n '',\n 'Réponds UNIQUEMENT avec un objet JSON valide, sans texte autour, au format EXACT :',\n '{\"strategy\":\"auto_solar_plus|auto_solar|force_grid_now|force_charge_now|stop\",',\n '\"night_target_soc\":<entier 22-90>,\"current_max_a\":<entier 6-28>,',\n '\"ev_charge_tonight\":\"oui|non|sans_objet\",\"confidence\":<0.0-1.0>,',\n '\"reason\":\"une phrase courte en français (max 200 caractères)\"}',\n].join('\\n');\n\nconst userPrompt = [\n `Nous sommes ${nowParis} (heure de Paris).`,\n '',\n 'ÉTAT ACTUEL :',\n `- Batterie : SOC ${ctx.soc}%, courant ${ctx.batteryCurrentA} A (positif = charge)`,\n `- PV : ${Math.round(ctx.pvW)} W instantané, ${Math.round(ctx.pvAvg10mW)} W moyenne 10 min`,\n `- Maison : ${Math.round(ctx.houseW)} W | Réseau EDF : ${ctx.gridOn ? 'ON' : 'OFF'} (AC-IN ${ctx.acinAvailable ? 'présent' : 'absent'})`,\n `- Véhicule : ${ctx.vehicleConnected ? 'BRANCHÉ' : 'non branché'}${ctx.evcsCharging ? `, en charge à ${ctx.evcsCurrentA} A` : ''}`,\n `- État contrôleur : ${ctx.surplusState} | Source : ${ctx.sourceMode}`,\n `- Stratégie réelle actuelle : ${ctx.userStrategy}`,\n `- Besoin véhicule : ${ctx.requiredEnergyKwh} kWh, départ ${ctx.departureTime}${ctx.departurePriority ? ' (priorité départ ON)' : ''}`,\n `- Prévision solaire : ${ctx.todayRemainingKwh} kWh restant aujourd'hui, ${ctx.tomorrowKwh} kWh demain`,\n `- Cible SOC matin actuelle : ${ctx.nightTargetActual}% | HC nuit autorisé : ${ctx.allowGridHcNight ? 'oui' : 'non'}`,\n '',\n 'Quelle est ta recommandation ? JSON uniquement.',\n].join('\\n');\n\nreturn [{\n json: {\n enabled: ctx.advisorEnabled,\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: userPrompt },\n ],\n context: ctx,\n }\n}];\n"
|
|
}
|
|
},
|
|
{
|
|
"name": "IF_ENABLED",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
600,
|
|
0
|
|
],
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "loose",
|
|
"version": 2
|
|
},
|
|
"combinator": "and",
|
|
"conditions": [
|
|
{
|
|
"leftValue": "={{ $json.enabled }}",
|
|
"rightValue": "",
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "LLM_LOCAL_MODELS",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
800,
|
|
-100
|
|
],
|
|
"parameters": {
|
|
"method": "GET",
|
|
"url": "http://10.10.10.8:8000/v1/models",
|
|
"options": {
|
|
"timeout": 5000
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput",
|
|
"alwaysOutputData": true
|
|
},
|
|
{
|
|
"name": "LLM_LOCAL",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1000,
|
|
-100
|
|
],
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "http://10.10.10.8:8000/v1/chat/completions",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ JSON.stringify({ model: $('LLM_LOCAL_MODELS').first().json.data[0].id, messages: $('BUILD_PROMPT').first().json.messages, temperature: 0.1, max_tokens: 6000 }) }}",
|
|
"options": {
|
|
"timeout": 120000
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput",
|
|
"alwaysOutputData": true
|
|
},
|
|
{
|
|
"name": "IF_LOCAL_OK",
|
|
"type": "n8n-nodes-base.if",
|
|
"typeVersion": 2.2,
|
|
"position": [
|
|
1200,
|
|
-100
|
|
],
|
|
"parameters": {
|
|
"conditions": {
|
|
"options": {
|
|
"caseSensitive": true,
|
|
"leftValue": "",
|
|
"typeValidation": "loose",
|
|
"version": 2
|
|
},
|
|
"combinator": "and",
|
|
"conditions": [
|
|
{
|
|
"leftValue": "={{ $json.choices !== undefined && $json.choices.length > 0 }}",
|
|
"rightValue": "",
|
|
"operator": {
|
|
"type": "boolean",
|
|
"operation": "true",
|
|
"singleValue": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "LLM_CLOUD",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
1400,
|
|
40
|
|
],
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "https://openrouter.ai/api/v1/chat/completions",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpHeaderAuth",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ JSON.stringify({ model: 'anthropic/claude-haiku-4.5', messages: $('BUILD_PROMPT').first().json.messages, temperature: 0.1, max_tokens: 1000 }) }}",
|
|
"options": {
|
|
"timeout": 60000
|
|
}
|
|
},
|
|
"credentials": {
|
|
"httpHeaderAuth": {
|
|
"id": "eOM9xofDHT0KMoeY",
|
|
"name": "OpenRouter Advisor"
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput",
|
|
"alwaysOutputData": true
|
|
},
|
|
{
|
|
"name": "VALIDATE_PARSE",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1600,
|
|
-30
|
|
],
|
|
"parameters": {
|
|
"jsCode": "// =====================================================\n// EVCS ADVISOR SHADOW V1 — VALIDATE_PARSE\n// Valide STRICTEMENT la sortie LLM (locale OU cloud, format OpenAI).\n// Sortie invalide = REJET TOTAL (status invalid), jamais de clamp silencieux.\n// =====================================================\n\nconst resp = $input.first().json;\nconst context = $('BUILD_PROMPT').first().json.context;\n\nconst STRATEGIES = ['auto_solar_plus', 'auto_solar', 'force_grid_now', 'force_charge_now', 'stop'];\nconst TONIGHT = ['oui', 'non', 'sans_objet'];\n\nfunction fail(status, detail) {\n return [{\n json: {\n status,\n detail: (detail || '').toString().slice(0, 200),\n advice: null,\n modelUsed: (resp?.model || 'aucun').toString().slice(0, 60),\n context,\n }\n }];\n}\n\n// 1. Réponse LLM présente ? (les deux backends ont échoué => item d'erreur n8n)\nconst content = resp?.choices?.[0]?.message?.content;\nif (typeof content !== 'string' || !content.trim()) {\n return fail('llm_offline', resp?.error?.message || resp?.error || 'aucune réponse LLM');\n}\n\n// 2. Nettoyage : balises de raisonnement (<think>...</think>) et fences markdown\nlet text = content\n .replace(/<think>[\\s\\S]*?<\\/think>/gi, '')\n .replace(/```(?:json)?/gi, '')\n .trim();\n\n// 3. Extraction du premier objet JSON équilibré\nconst start = text.indexOf('{');\nif (start === -1) return fail('invalid', 'aucun JSON dans la réponse');\nlet depth = 0, end = -1;\nfor (let i = start; i < text.length; i++) {\n if (text[i] === '{') depth++;\n else if (text[i] === '}') { depth--; if (depth === 0) { end = i; break; } }\n}\nif (end === -1) return fail('invalid', 'JSON non terminé');\n\nlet obj;\ntry {\n obj = JSON.parse(text.slice(start, end + 1));\n} catch (e) {\n return fail('invalid', `JSON invalide: ${e.message}`);\n}\n\n// 4. Validation stricte du schéma (enums + bornes)\nconst strategy = (obj.strategy ?? '').toString().trim().toLowerCase();\nif (!STRATEGIES.includes(strategy)) return fail('invalid', `strategy hors enum: ${strategy}`);\n\nconst nightTarget = Number(obj.night_target_soc);\nif (!Number.isFinite(nightTarget) || nightTarget < 22 || nightTarget > 90) {\n return fail('invalid', `night_target_soc hors bornes: ${obj.night_target_soc}`);\n}\n\nconst currentMax = Number(obj.current_max_a);\nif (!Number.isFinite(currentMax) || currentMax < 6 || currentMax > 28) {\n return fail('invalid', `current_max_a hors bornes: ${obj.current_max_a}`);\n}\n\nconst tonight = (obj.ev_charge_tonight ?? '').toString().trim().toLowerCase();\nif (!TONIGHT.includes(tonight)) return fail('invalid', `ev_charge_tonight hors enum: ${tonight}`);\n\nconst confidence = Number(obj.confidence);\nif (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {\n return fail('invalid', `confidence hors bornes: ${obj.confidence}`);\n}\n\nconst reason = (obj.reason ?? '').toString().trim();\nif (!reason) return fail('invalid', 'reason vide');\n\nreturn [{\n json: {\n status: 'ok',\n detail: '',\n advice: {\n strategy,\n nightTargetSoc: Math.round(nightTarget),\n currentMaxA: Math.round(currentMax),\n evChargeTonight: tonight,\n confidence: Math.round(confidence * 100) / 100,\n reason: reason.slice(0, 250),\n },\n modelUsed: (resp.model || 'inconnu').toString().slice(0, 60),\n context,\n }\n}];\n"
|
|
}
|
|
},
|
|
{
|
|
"name": "BUILD_HA_CALLS",
|
|
"type": "n8n-nodes-base.code",
|
|
"typeVersion": 2,
|
|
"position": [
|
|
1800,
|
|
-30
|
|
],
|
|
"parameters": {
|
|
"jsCode": "// =====================================================\n// EVCS ADVISOR SHADOW V1 — BUILD_HA_CALLS\n// Écrit les helpers advisor_* + divergence + compteurs + logbook.\n// GARANTIE MODE OMBRE : ce node ne produit QUE des écritures vers\n// des entités advisor_* et le logbook. AUCUN switch, AUCUNE stratégie\n// réelle, AUCUNE commande borne. Ne pas ajouter d'autres entités ici.\n// =====================================================\n\nconst { status, detail, advice, modelUsed, context } = $input.first().json;\n\nfunction stampParis() {\n const p = new Intl.DateTimeFormat('fr-FR', {\n timeZone: 'Europe/Paris', day: '2-digit', month: '2-digit',\n hour: '2-digit', minute: '2-digit', hour12: false,\n }).formatToParts(new Date());\n const g = t => p.find(x => x.type === t)?.value || '';\n return `${g('day')}/${g('month')} ${g('hour')}:${g('minute')}`;\n}\n\nconst calls = [];\nfunction push(domain, service, data) {\n calls.push({ json: { domain, service, data } });\n}\nfunction setText(entity, value) {\n push('input_text', 'set_value', { entity_id: entity, value: (value ?? '').toString().slice(0, 250) });\n}\nfunction setNumber(entity, value) {\n push('input_number', 'set_value', { entity_id: entity, value });\n}\n\n// Statut + horodatage + modèle : écrits à CHAQUE run\nsetText('input_text.evcs_advisor_status', status + (detail ? `: ${detail}` : ''));\nsetText('input_text.evcs_advisor_last_run', stampParis());\nsetText('input_text.evcs_advisor_model', modelUsed);\n\nif (status === 'ok' && advice) {\n // Recommandations\n setText('input_text.evcs_advisor_strategy', advice.strategy);\n setNumber('input_number.evcs_advisor_night_target_soc', advice.nightTargetSoc);\n setNumber('input_number.evcs_advisor_current_max_a', advice.currentMaxA);\n setText('input_text.evcs_advisor_ev_charge_tonight', advice.evChargeTonight);\n setText('input_text.evcs_advisor_reason', `[${Math.round(advice.confidence * 100)}%] ${advice.reason}`);\n\n // Divergence vs réel (stratégie + cible nuit)\n const diffs = [];\n if (advice.strategy !== context.userStrategy) {\n diffs.push(`stratégie: agent=${advice.strategy} ≠ réel=${context.userStrategy}`);\n }\n if (Math.abs(advice.nightTargetSoc - context.nightTargetActual) > 10) {\n diffs.push(`cible nuit: agent=${advice.nightTargetSoc}% vs réel=${context.nightTargetActual}%`);\n }\n const divergenceText = diffs.length ? diffs.join(' | ') : 'accord avec le réel';\n setText('input_text.evcs_advisor_divergence', divergenceText);\n\n // Compteurs accord/divergence sur la stratégie (base des critères de bascule)\n if (advice.strategy === context.userStrategy) {\n setNumber('input_number.evcs_advisor_agree_count', context.agreeCount + 1);\n } else {\n setNumber('input_number.evcs_advisor_diverge_count', context.divergeCount + 1);\n }\n\n // Trace logbook pour l'historique long terme\n push('logbook', 'log', {\n name: 'EVCS Advisor (ombre)',\n message: `${advice.strategy} | cible nuit ${advice.nightTargetSoc}% | ${advice.evChargeTonight === 'oui' ? 'charge VE cette nuit' : advice.evChargeTonight === 'non' ? 'pas de charge nuit' : 'VE sans objet'} | ${divergenceText} | ${advice.reason}`,\n entity_id: 'input_text.evcs_advisor_strategy',\n });\n}\n\nreturn calls;\n"
|
|
}
|
|
},
|
|
{
|
|
"name": "Execute HA Service Advisor",
|
|
"type": "n8n-nodes-base.httpRequest",
|
|
"typeVersion": 4.2,
|
|
"position": [
|
|
2000,
|
|
-30
|
|
],
|
|
"parameters": {
|
|
"method": "POST",
|
|
"url": "={{ 'http://10.10.10.17:8123/api/services/' + $json.domain + '/' + $json.service }}",
|
|
"authentication": "genericCredentialType",
|
|
"genericAuthType": "httpBearerAuth",
|
|
"sendHeaders": true,
|
|
"headerParameters": {
|
|
"parameters": [
|
|
{
|
|
"name": "Content-Type",
|
|
"value": "application/json"
|
|
},
|
|
{
|
|
"name": "accept",
|
|
"value": "application/json"
|
|
}
|
|
]
|
|
},
|
|
"sendBody": true,
|
|
"specifyBody": "json",
|
|
"jsonBody": "={{ $json.data }}",
|
|
"options": {
|
|
"timeout": 15000
|
|
}
|
|
},
|
|
"credentials": {
|
|
"httpBearerAuth": {
|
|
"id": "uv3sknYYigxBkaVN",
|
|
"name": "Bearer Auth account 2"
|
|
}
|
|
},
|
|
"onError": "continueRegularOutput"
|
|
}
|
|
],
|
|
"connections": {
|
|
"Schedule Trigger": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "HA Snapshot Advisor",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"HA Snapshot Advisor": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "BUILD_PROMPT",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"BUILD_PROMPT": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "IF_ENABLED",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"IF_ENABLED": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "LLM_LOCAL_MODELS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[]
|
|
]
|
|
},
|
|
"LLM_LOCAL_MODELS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "LLM_LOCAL",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"LLM_LOCAL": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "IF_LOCAL_OK",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"IF_LOCAL_OK": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "VALIDATE_PARSE",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"node": "LLM_CLOUD",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"LLM_CLOUD": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "VALIDATE_PARSE",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"VALIDATE_PARSE": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "BUILD_HA_CALLS",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
},
|
|
"BUILD_HA_CALLS": {
|
|
"main": [
|
|
[
|
|
{
|
|
"node": "Execute HA Service Advisor",
|
|
"type": "main",
|
|
"index": 0
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
"settings": {
|
|
"executionOrder": "v1",
|
|
"callerPolicy": "workflowsFromSameOwner"
|
|
},
|
|
"staticData": null,
|
|
"id": "tdfInZns7cqo8fU2"
|
|
}
|