// EVCS Energy Balance — BUILD_FORECAST_URL // ===================================================================== // Construit l'URL d'appel à Forecast.Solar. Rien n'est codé en dur : la clé, // les plans et les coordonnées viennent tous de Home Assistant. // // ⚠️ CONVENTION D'AZIMUT : l'API utilise 0 = sud, −90 = est, +90 = ouest, // alors que Home Assistant utilise 0 = nord. Le helper // `evcs_balance_forecast_planes` contient DÉJÀ les valeurs converties // (azimut_API = azimut_HA − 180), au format attendu : des triplets // dec/azimut/kWc successifs séparés par des barres obliques. // Les listes séparées par des virgules sont refusées par l'API (erreur 422). // // ⚠️ La clé apparaît dans l'URL, donc dans les données d'exécution n8n si // l'enregistrement des erreurs est actif. Compromis assumé : l'API n'accepte // la clé que dans le chemin, et l'instance n8n est privée. // ===================================================================== function unwrapSnapshot(raw) { if (raw && typeof raw.data === 'string') { try { return JSON.parse(raw.data); } catch { return raw; } } return (raw && raw.data) || raw; } const input = unwrapSnapshot($input.first().json); const enabled = (input.enabled ?? 'on').toString().toLowerCase() === 'on'; const key = (input.api_key || '').toString().trim(); const planes = (input.planes || '').toString().trim(); const lat = Number.parseFloat(input.latitude); const lon = Number.parseFloat(input.longitude); // Refus explicite plutôt qu'un appel malformé : une URL incomplète // consommerait le quota pour rien et renverrait une erreur opaque. const problems = []; if (!enabled) problems.push('bilan désactivé (kill-switch)'); if (!key || ['unknown', 'unavailable'].includes(key.toLowerCase())) problems.push('clé API absente'); if (!/^[\d.\-\/]+$/.test(planes) || planes.split('/').length % 3 !== 0) problems.push('plans invalides'); if (!Number.isFinite(lat) || !Number.isFinite(lon)) problems.push('coordonnées absentes'); if (problems.length) { return [{ json: { ok: false, problems, url: null } }]; } return [{ json: { ok: true, nowMs: Number.parseFloat(input.nowMs), planeCount: planes.split('/').length / 3, url: `https://api.forecast.solar/${key}/estimate/watthours/period/${lat}/${lon}/${planes}`, }, }];