feat: instrument model

This commit is contained in:
OpenSquared
2026-07-03 21:48:55 +02:00
parent 716d8fa56c
commit 3be72e44cc
4 changed files with 178 additions and 6 deletions

View File

@@ -1679,6 +1679,23 @@ def simulate_timeline(
if v0 is not None:
macro_last[node_id] = v0
# 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.
graph_event_cats: set[str] = {
n.get("event_category", "")
for n in graph_def.get("nodes", [])
if n.get("node_type") == "input_event"
} - {""}
# Baseline linéaire pour la sensibilité temporelle des nœuds macro
# Utilisé uniquement pour calculer le DELTA par rapport au point d'ancrage.
# Le linéaire amplifie la sensibilité aux changements de taux/CPI/NFP qui
# sinon disparaissent dans la zone de saturation tanh.
base_linear_pips = float(evaluate_graph(
_graph_json_for_eval(graph_def, {}),
_build_inputs(graph_def, overrides, {}, saturation=False)
).get(output_id, 0.0)) if has_macro else 0.0
timeline = []
cur = date_from
while cur <= today:
@@ -1706,6 +1723,10 @@ def simulate_timeline(
"category": cat,
})
# Split ev_by_cat : catégories avec nœud dédié vs choc direct
routed_ev = {cat: v for cat, v in ev_by_cat.items() if cat in graph_event_cats}
direct_shock = sum(v for cat, v in ev_by_cat.items() if cat not in graph_event_cats)
# ── Overrides pour ce jour (statiques + time-varying macro) ───────────
if has_macro:
cur_overrides = dict(overrides)
@@ -1718,18 +1739,26 @@ def simulate_timeline(
if v is not None:
cur_overrides[node_id] = {"value": v, "note": "macro_guidance", "set_at": ""}
# Structural pips time-varying (sans events, avec macro overrides du jour)
# Structural pips : baseline saturé + delta linéaire (sensibilité amplifiée)
gj_s = _graph_json_for_eval(graph_def, {})
in_s = _build_inputs(graph_def, cur_overrides, {}, saturation=True)
vs_s = evaluate_graph(gj_s, in_s)
structural_pips_t = round(float(vs_s.get(output_id, 0.0)), 1)
sat_pips_t = float(vs_s.get(output_id, 0.0))
# Delta linéaire = (linéaire_courant linéaire_base) → sensibilité temporelle
in_s_lin = _build_inputs(graph_def, cur_overrides, {}, saturation=False)
vs_s_lin = evaluate_graph(gj_s, in_s_lin)
lin_pips_t = float(vs_s_lin.get(output_id, 0.0))
delta_lin = lin_pips_t - base_linear_pips
structural_pips_t = round(sat_pips_t + delta_lin, 1)
if ev_by_cat:
ri = detect_regime(ev_by_cat)
gj_ = _graph_json_for_eval(graph_def, ri["weights"])
in_ = _build_inputs(graph_def, cur_overrides, ev_by_cat, saturation=True)
in_ = _build_inputs(graph_def, cur_overrides, routed_ev, saturation=True)
vals = evaluate_graph(gj_, in_)
net = round(float(vals.get(output_id, 0.0)), 1)
net = round(float(vals.get(output_id, 0.0)) + delta_lin + direct_shock, 1)
regime_label = ri["regime"]
else:
vals = vs_s
@@ -1741,9 +1770,9 @@ def simulate_timeline(
if ev_by_cat:
ri = detect_regime(ev_by_cat)
gj = _graph_json_for_eval(graph_def, ri["weights"])
inputs = _build_inputs(graph_def, overrides, ev_by_cat, saturation=True)
inputs = _build_inputs(graph_def, overrides, routed_ev, saturation=True)
vals = evaluate_graph(gj, inputs)
net = round(float(vals.get(output_id, 0.0)), 1)
net = round(float(vals.get(output_id, 0.0)) + direct_shock, 1)
regime_label = ri["regime"]
else:
vals = vals_struct