feat: instrument model

This commit is contained in:
OpenSquared
2026-07-03 22:16:02 +02:00
parent 3be72e44cc
commit 9778c74ae3

View File

@@ -1639,6 +1639,22 @@ def simulate_timeline(
vals_struct = evaluate_graph(gj_struct, inputs_struct) vals_struct = evaluate_graph(gj_struct, inputs_struct)
static_structural = round(float(vals_struct.get(output_id, 0.0)), 1) static_structural = round(float(vals_struct.get(output_id, 0.0)), 1)
# Unités par nœud input_manual — pour corriger les valeurs brutes de ff_calendar
# (NFP: raw=228000 → attendu=228 K; PMI: raw=54.7 → attendu=4.7 pts écart/50)
macro_node_units: dict[str, str] = {
n["id"]: n.get("unit", "")
for n in graph_def.get("nodes", [])
if n.get("node_type") == "input_manual"
}
def _unit_correct(v: float, unit: str) -> float:
"""Convertit les valeurs brutes ff_calendar en valeur attendue par le nœud."""
if unit == "K" and abs(v) >= 1_000:
return round(v / 1_000, 1)
if unit == "pts" and v > 10:
return round(v - 50.0, 1)
return v
# ── Auto-anchor : offset pour que la synthétique parte du prix réel à date_from ── # ── Auto-anchor : offset pour que la synthétique parte du prix réel à date_from ──
# Calculé une seule fois sur le prix réel à date_from (ou le plus proche disponible). # Calculé une seule fois sur le prix réel à date_from (ou le plus proche disponible).
# Cet offset compense l'écart de calibration sans changer la dynamique du modèle. # Cet offset compense l'écart de calibration sans changer la dynamique du modèle.
@@ -1660,6 +1676,7 @@ def simulate_timeline(
# Valeur la plus proche avant date_from # Valeur la plus proche avant date_from
v = next((tl[d] for d in sorted(tl) if d <= str(date_from)), next(iter(tl.values()), None)) v = next((tl[d] for d in sorted(tl) if d <= str(date_from)), next(iter(tl.values()), None))
if v is not None: if v is not None:
v = _unit_correct(v, macro_node_units.get(node_id, ""))
anchor_overrides[node_id] = {"value": v, "note": "anchor", "set_at": ""} anchor_overrides[node_id] = {"value": v, "note": "anchor", "set_at": ""}
gj_a = _graph_json_for_eval(graph_def, {}) gj_a = _graph_json_for_eval(graph_def, {})
in_a = _build_inputs(graph_def, anchor_overrides, {}, saturation=True) in_a = _build_inputs(graph_def, anchor_overrides, {}, saturation=True)
@@ -1677,7 +1694,7 @@ def simulate_timeline(
for node_id, tl in macro_node_timelines.items(): for node_id, tl in macro_node_timelines.items():
v0 = tl.get(str(date_from)) v0 = tl.get(str(date_from))
if v0 is not None: if v0 is not None:
macro_last[node_id] = v0 macro_last[node_id] = _unit_correct(v0, macro_node_units.get(node_id, ""))
# Catégories d'events gérées par les nœuds input_event du graphe # Catégories d'events gérées par les nœuds input_event du graphe
# Les catégories sans nœud correspondant sont appliquées comme choc direct sur l'output. # Les catégories sans nœud correspondant sont appliquées comme choc direct sur l'output.
@@ -1733,6 +1750,7 @@ def simulate_timeline(
for node_id, tl in macro_node_timelines.items(): for node_id, tl in macro_node_timelines.items():
v = tl.get(cur_str) v = tl.get(cur_str)
if v is not None: if v is not None:
v = _unit_correct(v, macro_node_units.get(node_id, ""))
macro_last[node_id] = v macro_last[node_id] = v
else: else:
v = macro_last.get(node_id) # gap-fill (weekend) v = macro_last.get(node_id) # gap-fill (weekend)