Essai du 12/09 : avec la seule liste des ecrivains, le conseiller voyait le conflit mais accusait les deux workflows n8n — l intrus etait une automatisation Home Assistant. La carte sait lire QUI PEUT ecrire, elle ne peut pas deviner QUI DEVRAIT : le proprietaire est une decision humaine, desormais declaree et commentee dans l outil. Resultat : 3 ecrivains hors proprietaire sur le contacteur reseau, 1 sur le courant de la borne, 1 sur le verrou. Injectee dans le briefing entre bornes explicites, regeneree par --injecte. Le conseiller enonce maintenant la bonne forme (« contradiction entre le proprietaire et un autre ecrivain ») mais ne nomme toujours pas lequel : il lui manque l ATTRIBUTION — rien ne relie une bascule a son auteur. Le contexte Home Assistant distingue un appel de service d une automatisation ; c est la prochaine piste. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GwpFpy2aQ2rJok6tNQSbVj
290 lines
18 KiB
JavaScript
290 lines
18 KiB
JavaScript
#!/usr/bin/env node
|
|
// Campagne de rejeu du conseiller IA sur des instants REELS.
|
|
// Chaque cas rejoue un moment mesure dont on connait la suite. Cinq passages
|
|
// par cas : un seul ne mesure rien, le modele n'est pas deterministe, et
|
|
// l'ECART entre passages est souvent plus parlant que la justesse.
|
|
// node tools/campagne-conseiller.mjs [passages]
|
|
import fs from 'fs';
|
|
import vm from 'vm';
|
|
|
|
const PASSAGES = Number(process.argv[2] || 5);
|
|
const URL = 'http://10.10.10.8:8000/v1/chat/completions';
|
|
const code = fs.readFileSync('src/advisor/BUILD_PROMPT.js', 'utf8');
|
|
|
|
function buildPrompt(snapshot, nowIso) {
|
|
const ctx = vm.createContext({
|
|
$input: { first: () => ({ json: { data: JSON.stringify(snapshot) } }) },
|
|
Intl, Date: class extends Date { constructor(...a) { super(...(a.length ? a : [nowIso])); } },
|
|
Math, Number, String, JSON, console,
|
|
});
|
|
return vm.runInContext(`(function(){ ${code} })()`, ctx)[0].json;
|
|
}
|
|
|
|
const BASE = {
|
|
advisor_enabled: 'on', advisor_agree_count: '0', advisor_diverge_count: '0',
|
|
advisor_prev_strategy: 'auto_solar_plus',
|
|
soc_percent: '50', battery_current_a: '0',
|
|
pv_power_w: '0', pv_power_10m_avg_w: '0', house_ac_power_w: '700',
|
|
grid_state: 'off', acin1_available: '0',
|
|
evcs_vehicle_connected: 'off', evcs_charging: 'off', evcs_current_a: '0',
|
|
evcs_surplus_state: 'vehicle_not_connected', evcs_source_mode: 'Bloqué',
|
|
user_strategy: 'auto_solar_plus',
|
|
allow_grid_hc_night: 'on', allow_grid_hc_midday: 'off',
|
|
night_target_soc_actual: '35',
|
|
current_max_auto_a: '22', plafond_edf_on_a: '20',
|
|
departure_time: '', departure_priority_enabled: 'off', required_energy_kwh: '0',
|
|
today_remaining_kwh: '0', tomorrow_kwh: '30',
|
|
balance_status: 'ok', balance_last_run: 'recent',
|
|
balance_required_soc: '40', balance_required_soc_prudent: '46',
|
|
balance_energy_to_cover_kwh: '2.0', balance_crossing_hour: '09 h',
|
|
balance_grid_needed: 'off', balance_grid_energy_needed_kwh: '0',
|
|
balance_pv_censored: 'off', balance_censor_reason: '',
|
|
balance_storage_shortfall_kwh: '6.8',
|
|
balance_next_deficit_kwh: '2.0', balance_next_deficit_window: '(cette nuit)',
|
|
balance_storm_level: 'Vert', balance_storm_reserve_soc: '38',
|
|
modules_en_service: '4',
|
|
bms_max_discharge_current_a: '100', bms_max_charge_current_a: '100',
|
|
grid_setpoint_w: '700',
|
|
mppt_operation_modes: 'off,off,off,off,off,off',
|
|
vehicle_selected: 'VW',
|
|
evenements_recents: '',
|
|
};
|
|
const LIM = Array(6).fill('voltage_current_limited').join(',');
|
|
const MAX = Array(6).fill('mppt_active').join(',');
|
|
|
|
export const CAS = [
|
|
// ─────────── NUIT ET RESEAU ───────────
|
|
{ id:'1-nuit-bloquee-sans-vehicule', t:'2026-09-12T04:00:00+02:00', certain:true,
|
|
titre:"12/09 04:00 — SOC 20, HC ouvertes, aucun vehicule, bilan 59,1 %",
|
|
attendu:"AGIR : engager le reseau, cible ~59 %",
|
|
s:{ soc_percent:'20', battery_current_a:'0', house_ac_power_w:'612',
|
|
grid_state:'on', acin1_available:'1', night_target_soc_actual:'59.1',
|
|
tomorrow_kwh:'75.2', balance_required_soc:'59.1', balance_required_soc_prudent:'66.2',
|
|
balance_energy_to_cover_kwh:'3.13', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'3.4', balance_storage_shortfall_kwh:'6.77' } },
|
|
|
|
{ id:'2-nuit-relachee-trop-tot', t:'2026-09-03T03:00:00+02:00', certain:true,
|
|
titre:"03/09 03:00 — SOC 45, bilan 50,4 % (releche a tort : batterie a 20 % a 06:30)",
|
|
attendu:"AGIR : tenir le reseau, cible ~50 %",
|
|
s:{ soc_percent:'45', battery_current_a:'2', house_ac_power_w:'700',
|
|
grid_state:'on', acin1_available:'1', evcs_vehicle_connected:'on',
|
|
evcs_surplus_state:'night_session_done_idle', evcs_source_mode:'Réseau HC autorisé',
|
|
night_target_soc_actual:'50.4', tomorrow_kwh:'33.9',
|
|
balance_required_soc:'50.4', balance_required_soc_prudent:'57',
|
|
balance_energy_to_cover_kwh:'4.3', balance_grid_needed:'on', balance_grid_energy_needed_kwh:'1.1' } },
|
|
|
|
{ id:'3-nuit-couverte-ne-rien-faire', t:'2026-09-12T04:00:00+02:00', certain:true,
|
|
titre:"CONTREFACTUEL — meme nuit mais SOC 70, au-dessus des 59,1 % requis",
|
|
attendu:"NE RIEN FAIRE : auto_solar_plus, pas de reseau",
|
|
s:{ soc_percent:'70', battery_current_a:'-12', house_ac_power_w:'612',
|
|
grid_state:'off', acin1_available:'0', night_target_soc_actual:'59.1',
|
|
tomorrow_kwh:'75.2', balance_required_soc:'59.1', balance_required_soc_prudent:'66.2',
|
|
balance_energy_to_cover_kwh:'3.13', balance_grid_needed:'off' } },
|
|
|
|
{ id:'4-hiver-nuit-longue', t:'2026-12-15T23:30:00+01:00', certain:false,
|
|
titre:"HIVER — 15/12 23:30, prevision 6 kWh demain, croisement tardif a 11 h",
|
|
attendu:"OUVERT : quelle cible pour une nuit d hiver ?",
|
|
s:{ soc_percent:'55', battery_current_a:'-18', house_ac_power_w:'1400',
|
|
grid_state:'off', acin1_available:'0', night_target_soc_actual:'85',
|
|
tomorrow_kwh:'6.2', balance_required_soc:'88', balance_required_soc_prudent:'97',
|
|
balance_energy_to_cover_kwh:'9.4', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'5.1', balance_crossing_hour:'11 h',
|
|
balance_storage_shortfall_kwh:'8.2' } },
|
|
|
|
// ─────────── SOLAIRE ET VOITURE ───────────
|
|
{ id:'5-soir-deficit-ne-pas-demarrer', t:'2026-09-03T19:44:00+02:00', certain:true,
|
|
titre:"03/09 19:44 — PV 758 W mais maison 1202 W : excedent NEGATIF (-444 W)",
|
|
attendu:"NE PAS DEMARRER la charge",
|
|
s:{ soc_percent:'94', battery_current_a:'-12.2', pv_power_w:'758', pv_power_10m_avg_w:'900',
|
|
house_ac_power_w:'1202', evcs_vehicle_connected:'on', vehicle_selected:'VW',
|
|
evcs_source_mode:'Potentiel solaire', mppt_operation_modes:MAX,
|
|
balance_required_soc:'99.7', balance_required_soc_prudent:'100',
|
|
balance_energy_to_cover_kwh:'11.66', balance_grid_needed:'on', tomorrow_kwh:'33.9' } },
|
|
|
|
{ id:'6-censure-demarrer', t:'2026-09-03T18:50:00+02:00', certain:true,
|
|
titre:"03/09 18:50 — PV 769 W, maison 755 W, chargeurs BRIDES : ressource refusee",
|
|
attendu:"DEMARRER : la ressource est gratuite",
|
|
s:{ soc_percent:'94', battery_current_a:'0', pv_power_w:'769', pv_power_10m_avg_w:'800',
|
|
house_ac_power_w:'755', evcs_vehicle_connected:'on', vehicle_selected:'VW',
|
|
evcs_source_mode:'Potentiel solaire', mppt_operation_modes:LIM,
|
|
balance_pv_censored:'on', balance_censor_reason:'batterie_pleine_sans_puits',
|
|
balance_required_soc:'99.7', balance_required_soc_prudent:'100',
|
|
balance_energy_to_cover_kwh:'11.66', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'4.9', tomorrow_kwh:'33.9' } },
|
|
|
|
{ id:'7-boost-evince-le-solaire', t:'2026-09-11T17:02:00+02:00', certain:true,
|
|
titre:"11/09 17:02 — BOOST 28 A, consigne 8260 W, PV effondre a 121 W",
|
|
attendu:"AGIR : reduire la demande reseau, le solaire est evince",
|
|
s:{ soc_percent:'96', battery_current_a:'0', pv_power_w:'121', pv_power_10m_avg_w:'150',
|
|
house_ac_power_w:'7695', evcs_vehicle_connected:'on', evcs_charging:'on',
|
|
evcs_current_a:'28', vehicle_selected:'JEEP', user_strategy:'force_grid_now',
|
|
grid_state:'on', acin1_available:'1', grid_setpoint_w:'8260',
|
|
evcs_source_mode:'Réseau forcé', evcs_surplus_state:'charging_hold_grid',
|
|
mppt_operation_modes:LIM, plafond_edf_on_a:'28',
|
|
balance_pv_censored:'on', balance_censor_reason:'production_bridee',
|
|
balance_required_soc:'62', balance_required_soc_prudent:'70',
|
|
balance_energy_to_cover_kwh:'5.2', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'1.4', balance_crossing_hour:'09 h',
|
|
today_remaining_kwh:'8.4', tomorrow_kwh:'75.2' } },
|
|
|
|
{ id:'8-voiture-pleine-ne-pas-reessayer', t:'2026-09-07T14:28:00+02:00', certain:true,
|
|
titre:"07/09 14:28 — borne annonce charge terminee, vehicule branche immobile",
|
|
attendu:"NE RIEN FAIRE : ne pas relancer la charge",
|
|
s:{ soc_percent:'97', battery_current_a:'1', pv_power_w:'2114', pv_power_10m_avg_w:'2000',
|
|
house_ac_power_w:'1965', evcs_vehicle_connected:'on', evcs_current_a:'0',
|
|
vehicle_selected:'JEEP', evcs_surplus_state:'day_vehicle_full_idle',
|
|
evcs_source_mode:'Bloqué', mppt_operation_modes:LIM,
|
|
balance_pv_censored:'on', balance_censor_reason:'batterie_pleine_sans_puits',
|
|
balance_required_soc:'58', balance_required_soc_prudent:'66',
|
|
balance_energy_to_cover_kwh:'4.6', balance_grid_needed:'off',
|
|
balance_crossing_hour:'09 h', today_remaining_kwh:'12', tomorrow_kwh:'30' } },
|
|
|
|
// ─────────── PROTECTION DU MATERIEL ───────────
|
|
{ id:'9-pointe-cuisson-proteger-le-parc', t:'2026-09-05T20:40:00+02:00', certain:true,
|
|
titre:"05/09 20:40 — maison 4944 W, batterie a -97,4 A pour une limite de 100 A",
|
|
attendu:"AGIR : appeler le reseau, le parc est a sa limite",
|
|
s:{ soc_percent:'79', battery_current_a:'-97.4', pv_power_w:'320', pv_power_10m_avg_w:'500',
|
|
house_ac_power_w:'4944', grid_state:'off', acin1_available:'0',
|
|
balance_required_soc:'62', balance_required_soc_prudent:'70',
|
|
balance_energy_to_cover_kwh:'5.1', balance_grid_needed:'on', tomorrow_kwh:'32' } },
|
|
|
|
{ id:'10-nuage-sur-charge-28a', t:'2026-09-11T18:00:00+02:00', certain:true,
|
|
titre:"11/09 18:00 — voiture a 28 A, consigne descendue a 1281 W, PV 6058 W",
|
|
attendu:"AGIR : garder de la marge reseau, un nuage tomberait sur la batterie",
|
|
s:{ soc_percent:'96', battery_current_a:'-0.7', pv_power_w:'6058', pv_power_10m_avg_w:'5500',
|
|
house_ac_power_w:'6845', evcs_vehicle_connected:'on', evcs_charging:'on',
|
|
evcs_current_a:'28', vehicle_selected:'JEEP', user_strategy:'force_grid_now',
|
|
grid_state:'on', acin1_available:'1', grid_setpoint_w:'1281',
|
|
evcs_source_mode:'Réseau forcé', evcs_surplus_state:'charging_hold_grid',
|
|
mppt_operation_modes:MAX, plafond_edf_on_a:'28',
|
|
balance_required_soc:'62', balance_required_soc_prudent:'70',
|
|
balance_energy_to_cover_kwh:'5.2', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'1.4', balance_crossing_hour:'09 h',
|
|
today_remaining_kwh:'4', tomorrow_kwh:'75.2' } },
|
|
|
|
// ─────────── CAS OUVERTS ───────────
|
|
{ id:'11-batterie-pleine-midi', t:'2026-09-11T12:00:00+02:00', certain:false,
|
|
titre:"11/09 12:00 — SOC 98, production refusee, AUCUN vehicule branche",
|
|
attendu:"OUVERT : y a-t-il quelque chose a faire ?",
|
|
s:{ soc_percent:'98', battery_current_a:'2.2', pv_power_w:'810', pv_power_10m_avg_w:'900',
|
|
house_ac_power_w:'702', mppt_operation_modes:LIM,
|
|
balance_pv_censored:'on', balance_censor_reason:'batterie_pleine_sans_puits',
|
|
balance_required_soc:'55', balance_required_soc_prudent:'63',
|
|
balance_energy_to_cover_kwh:'4.1', balance_grid_needed:'off',
|
|
balance_crossing_hour:'09 h', balance_next_deficit_kwh:'4.1',
|
|
balance_next_deficit_window:'(ce soir 20 h -> 09 h)',
|
|
today_remaining_kwh:'45', tomorrow_kwh:'75.2' } },
|
|
|
|
{ id:'12-orage-annonce', t:'2026-09-12T15:30:00+02:00', certain:false,
|
|
titre:"Vigilance ORANGE annoncee, SOC 72, coupures frequentes sur ce site",
|
|
attendu:"OUVERT : jusqu ou constituer la reserve ?",
|
|
s:{ soc_percent:'72', battery_current_a:'35', pv_power_w:'4200', pv_power_10m_avg_w:'4000',
|
|
house_ac_power_w:'900', mppt_operation_modes:MAX,
|
|
balance_storm_level:'Orange', balance_storm_reserve_soc:'88',
|
|
balance_required_soc:'55', balance_required_soc_prudent:'62',
|
|
balance_energy_to_cover_kwh:'3.2', balance_grid_needed:'off',
|
|
balance_crossing_hour:'09 h', today_remaining_kwh:'18', tomorrow_kwh:'40' } },
|
|
|
|
{ id:'13-voiture-empruntee-echeance', t:'2026-09-11T17:00:00+02:00', certain:false,
|
|
titre:"11/09 17:00 — voiture EMPRUNTEE a rendre a 19 h, 28 A possible, soleil present",
|
|
attendu:"OUVERT : vitesse contre solaire, arbitrage humain",
|
|
s:{ soc_percent:'96', battery_current_a:'-10', pv_power_w:'4255', pv_power_10m_avg_w:'4000',
|
|
house_ac_power_w:'4888', evcs_vehicle_connected:'on', evcs_charging:'on',
|
|
evcs_current_a:'20', vehicle_selected:'JEEP', user_strategy:'force_charge_now',
|
|
grid_state:'on', acin1_available:'1', grid_setpoint_w:'700',
|
|
evcs_source_mode:'Solaire', evcs_surplus_state:'charging_hold_manual_local',
|
|
mppt_operation_modes:MAX, plafond_edf_on_a:'28',
|
|
required_energy_kwh:'12', departure_time:'19:00', departure_priority_enabled:'on',
|
|
balance_required_soc:'62', balance_required_soc_prudent:'70',
|
|
balance_energy_to_cover_kwh:'5.2', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'1.4', balance_crossing_hour:'09 h',
|
|
today_remaining_kwh:'6', tomorrow_kwh:'75.2' } },
|
|
|
|
{ id:'4b-hiver-parc-12', t:'2026-12-15T23:30:00+01:00', certain:false,
|
|
titre:"HIVER avec le parc de cet hiver — 12 equivalents (4 US2000C + 4 US5000)",
|
|
attendu:"OUVERT : la cible baisse-t-elle quand la reserve triple ?",
|
|
s:{ soc_percent:'55', battery_current_a:'-18', house_ac_power_w:'1400',
|
|
grid_state:'off', acin1_available:'0', night_target_soc_actual:'85',
|
|
tomorrow_kwh:'6.2', balance_required_soc:'52', balance_required_soc_prudent:'59',
|
|
balance_energy_to_cover_kwh:'9.4', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'0', balance_crossing_hour:'11 h',
|
|
balance_storage_shortfall_kwh:'0',
|
|
modules_en_service:'12', bms_max_discharge_current_a:'300',
|
|
bms_max_charge_current_a:'300' } },
|
|
|
|
{ id:'4c-hiver-parc-16', t:'2026-12-15T23:30:00+01:00', certain:false,
|
|
titre:"HIVER avec 16 equivalents (vieux modules repares)",
|
|
attendu:"OUVERT : la cible baisse-t-elle encore ?",
|
|
s:{ soc_percent:'55', battery_current_a:'-18', house_ac_power_w:'1400',
|
|
grid_state:'off', acin1_available:'0', night_target_soc_actual:'85',
|
|
tomorrow_kwh:'6.2', balance_required_soc:'44', balance_required_soc_prudent:'50',
|
|
balance_energy_to_cover_kwh:'9.4', balance_grid_needed:'off',
|
|
balance_grid_energy_needed_kwh:'0', balance_crossing_hour:'11 h',
|
|
balance_storage_shortfall_kwh:'0',
|
|
modules_en_service:'16', bms_max_discharge_current_a:'400',
|
|
bms_max_charge_current_a:'400' } },
|
|
|
|
{ id:'15-oscillation-contacteur', t:'2026-09-12T06:36:00+02:00', certain:true,
|
|
titre:"INCIDENT 12/09 06:25-06:35 — le contacteur bascule six fois en dix minutes",
|
|
attendu:"REPERER : gravite haute, nommer le conflit entre ecrivains",
|
|
s:{ soc_percent:'35', battery_current_a:'20', pv_power_w:'0', house_ac_power_w:'950',
|
|
grid_state:'on', acin1_available:'1', night_target_soc_actual:'42.8',
|
|
balance_required_soc:'42.8', balance_required_soc_prudent:'49',
|
|
balance_energy_to_cover_kwh:'1.56', balance_grid_needed:'on',
|
|
balance_grid_energy_needed_kwh:'1.1', balance_crossing_hour:'09 h',
|
|
grid_setpoint_w:'5759', tomorrow_kwh:'75.2',
|
|
evenements_recents:'06:35:06 Contacteur EDF : off -> on|06:34:02 Contacteur EDF : on -> off|06:30:49 Contacteur EDF : off -> on|06:29:44 Contacteur EDF : on -> off|06:26:31 Contacteur EDF : off -> on|06:25:27 Contacteur EDF : on -> off' } },
|
|
|
|
{ id:'16-systeme-calme', t:'2026-09-12T07:00:00+02:00', certain:true,
|
|
titre:"CONTREFACTUEL — meme nuit, contacteur stable depuis 90 minutes",
|
|
attendu:"NE RIEN SIGNALER : gravite aucune",
|
|
s:{ soc_percent:'42', battery_current_a:'20', pv_power_w:'0', house_ac_power_w:'950',
|
|
grid_state:'on', acin1_available:'1', night_target_soc_actual:'42.8',
|
|
balance_required_soc:'42.8', balance_required_soc_prudent:'49',
|
|
balance_energy_to_cover_kwh:'0.9', balance_grid_needed:'on',
|
|
balance_crossing_hour:'09 h', grid_setpoint_w:'5759', tomorrow_kwh:'75.2',
|
|
evenements_recents:'05:02:51 Contacteur EDF : off -> on|05:02:51 Reprise reseau nuit : off -> on' } },
|
|
|
|
{ id:'14-bilan-indisponible', t:'2026-09-12T04:00:00+02:00', certain:true,
|
|
titre:"ROBUSTESSE — meme nuit que le cas 1 mais bilan DEGRADE",
|
|
attendu:"Le dire dans sa raison et rester prudent",
|
|
s:{ soc_percent:'20', battery_current_a:'0', house_ac_power_w:'612',
|
|
grid_state:'on', acin1_available:'1', night_target_soc_actual:'35',
|
|
tomorrow_kwh:'75.2', balance_status:'degrade',
|
|
balance_required_soc:'unknown', balance_energy_to_cover_kwh:'unknown' } },
|
|
];
|
|
|
|
async function demander(messages) {
|
|
const r = await fetch(URL, { method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({ model:'qwen3-coder', temperature:0.1, max_tokens:600, messages }) });
|
|
const j = await r.json();
|
|
const c = j?.choices?.[0]?.message?.content ?? '';
|
|
const t = c.replace(/<think>[\s\S]*?<\/think>/gi,'').replace(/```(?:json)?/gi,'').trim();
|
|
const a = t.indexOf('{'); if (a === -1) return { erreur: t.slice(0,120) };
|
|
let d=0,b=-1; for (let i=a;i<t.length;i++){ if(t[i]==='{')d++; else if(t[i]==='}'){d--; if(!d){b=i;break;}} }
|
|
if (b === -1) return { erreur: 'JSON non termine' };
|
|
try { return JSON.parse(t.slice(a,b+1)); } catch(e) { return { erreur: e.message }; }
|
|
}
|
|
|
|
const resultats = [];
|
|
for (const cas of CAS) {
|
|
const p = buildPrompt({ ...BASE, ...cas.s }, cas.t);
|
|
const passages = [];
|
|
for (let i = 0; i < PASSAGES; i++) passages.push(await demander(p.messages));
|
|
resultats.push({ ...cas, passages });
|
|
const cibles = passages.map(x => x.night_target_soc).filter(Number.isFinite);
|
|
const ecart = cibles.length ? Math.max(...cibles) - Math.min(...cibles) : null;
|
|
console.log(`\n═══ ${cas.id}${cas.certain ? '' : ' [OUVERT]'}`);
|
|
console.log(` ${cas.titre}`);
|
|
console.log(` attendu : ${cas.attendu}`);
|
|
for (const x of passages) {
|
|
console.log(x.erreur ? ` ✖ ${x.erreur}`
|
|
: ` [${String(x.gravite).padEnd(7)}] ${String(x.constat).slice(0,88).padEnd(90)} ${String(x.strategy).padEnd(16)} cible ${String(x.night_target_soc).padStart(3)}`);
|
|
if (!x.erreur && x.ce_qui_cloche) console.log(` ↳ ${String(x.ce_qui_cloche).slice(0,120)}`);
|
|
}
|
|
if (ecart !== null) console.log(` → ecart de cible entre passages : ${ecart} pts`);
|
|
}
|
|
fs.writeFileSync('docs/mesures/campagne-conseiller-' + new Date().toISOString().slice(0,10) + '.json',
|
|
JSON.stringify(resultats, null, 2));
|
|
console.log('\nJournal complet : docs/mesures/campagne-conseiller-*.json');
|