debug: expose full inputs/outputs in Recalculer response for diagnosis
_run_auto_analysis now returns a rich dict {ok, inputs, node_values, actual_moves, error}
instead of bool. The refresh endpoint captures and forwards:
- inputs: what values were fed to evaluate_graph
- node_keys: which nodes were computed in prediction
- actual_moves: actual price pips fetched
- run_error: exception message if it failed
Frontend shows all of this after clicking Recalculer so the root cause is visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1127,11 +1127,10 @@ def _build_graph_json_from_spec(spec: dict) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _run_auto_analysis(event: dict, template_id: int) -> bool:
|
def _run_auto_analysis(event: dict, template_id: int) -> dict:
|
||||||
"""
|
"""
|
||||||
Causal analysis for the auto_template pipeline.
|
Causal analysis for the auto_template pipeline.
|
||||||
Builds inputs from event fields, evaluates the graph, fetches actual prices,
|
Returns {"ok": bool, "inputs": dict, "node_values": dict, "actual_moves": dict, "error": str}.
|
||||||
and upserts into causal_event_analyses so the instrument frise can score it.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from services.database import get_conn
|
from services.database import get_conn
|
||||||
@@ -1142,7 +1141,7 @@ def _run_auto_analysis(event: dict, template_id: int) -> bool:
|
|||||||
if not tmpl:
|
if not tmpl:
|
||||||
conn.close()
|
conn.close()
|
||||||
logger.warning(f"[auto_analysis] template #{template_id} introuvable")
|
logger.warning(f"[auto_analysis] template #{template_id} introuvable")
|
||||||
return False
|
return {"ok": False, "error": f"template #{template_id} introuvable"}
|
||||||
|
|
||||||
graph = tmpl["graph_json"]
|
graph = tmpl["graph_json"]
|
||||||
inputs = {}
|
inputs = {}
|
||||||
@@ -1310,11 +1309,17 @@ def _run_auto_analysis(event: dict, template_id: int) -> bool:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
logger.info(f"[auto_analysis] upserted event #{event_id}, tmpl #{template_id}, actual_moves={actual_moves}")
|
logger.info(f"[auto_analysis] upserted event #{event_id}, tmpl #{template_id}, actual_moves={actual_moves}")
|
||||||
return True
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"inputs": inputs,
|
||||||
|
"node_values": node_values,
|
||||||
|
"actual_moves": actual_moves,
|
||||||
|
"instruments": instruments,
|
||||||
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[auto_analysis] event #{event.get('id')}: {e}")
|
logger.error(f"[auto_analysis] event #{event.get('id')}: {e}", exc_info=True)
|
||||||
return False
|
return {"ok": False, "error": str(e)}
|
||||||
|
|
||||||
|
|
||||||
def auto_assign_template(event_id: int) -> dict:
|
def auto_assign_template(event_id: int) -> dict:
|
||||||
@@ -1481,13 +1486,16 @@ def refresh_auto_analyses():
|
|||||||
detail["actual_value"] = ev.get("actual_value")
|
detail["actual_value"] = ev.get("actual_value")
|
||||||
detail["expected_value"] = ev.get("expected_value")
|
detail["expected_value"] = ev.get("expected_value")
|
||||||
detail["start_date"] = ev.get("start_date")
|
detail["start_date"] = ev.get("start_date")
|
||||||
ok = _run_auto_analysis(ev, row["template_id"])
|
res = _run_auto_analysis(ev, row["template_id"])
|
||||||
detail["result"] = "ok" if ok else "failed"
|
detail["result"] = "ok" if res["ok"] else "failed"
|
||||||
if ok:
|
detail["inputs"] = res.get("inputs", {})
|
||||||
|
detail["node_keys"] = list(res.get("node_values", {}).keys())
|
||||||
|
detail["actual_moves"] = res.get("actual_moves", {})
|
||||||
|
detail["run_error"] = res.get("error")
|
||||||
|
if res["ok"]:
|
||||||
refreshed += 1
|
refreshed += 1
|
||||||
else:
|
else:
|
||||||
failed += 1
|
failed += 1
|
||||||
detail["result"] = "run_returned_false"
|
|
||||||
except Exception as _e:
|
except Exception as _e:
|
||||||
logger.warning(f"[refresh_auto_analyses] cea#{row['cea_id']}: {_e}")
|
logger.warning(f"[refresh_auto_analyses] cea#{row['cea_id']}: {_e}")
|
||||||
detail["result"] = f"exception: {_e}"
|
detail["result"] = f"exception: {_e}"
|
||||||
|
|||||||
@@ -1152,13 +1152,16 @@ function ExplanationScore({
|
|||||||
: <span>Refresh: {debugInfo.refreshed} ok / {debugInfo.failed} err / {debugInfo.total} total</span>
|
: <span>Refresh: {debugInfo.refreshed} ok / {debugInfo.failed} err / {debugInfo.total} total</span>
|
||||||
}
|
}
|
||||||
{debugInfo.details?.map((d: any, i: number) => (
|
{debugInfo.details?.map((d: any, i: number) => (
|
||||||
<div key={i} className="mt-0.5">
|
<div key={i} className="mt-1 border-t border-slate-800/40 pt-1">
|
||||||
#{d.event_id} {(d.event_name ?? '').slice(0, 22)} → <span className={d.result === 'ok' ? 'text-emerald-600' : 'text-red-500'}>{d.result}</span>
|
<span className={d.result === 'ok' ? 'text-emerald-500' : 'text-red-500'}>{d.result}</span>
|
||||||
{d.surprise_pct != null
|
<span className="text-slate-500 ml-1">#{d.event_id} {(d.event_name ?? '').slice(0, 20)}</span>
|
||||||
? <span className="text-cyan-700 ml-1">surp:{Number(d.surprise_pct).toFixed(1)}%</span>
|
{d.run_error && <span className="text-red-400 ml-1">err:{d.run_error.slice(0, 40)}</span>}
|
||||||
: d.actual_value != null
|
<div className="ml-2 text-slate-600">
|
||||||
? <span className="text-amber-700 ml-1">act:{d.actual_value} exp:{d.expected_value ?? '?'}</span>
|
surp:{d.surprise_pct != null ? Number(d.surprise_pct).toFixed(1)+'%' : `null (act=${d.actual_value} exp=${d.expected_value})`}
|
||||||
: <span className="text-red-800 ml-1">surp=null</span>}
|
{' | '}inputs:{JSON.stringify(d.inputs ?? {})}
|
||||||
|
{' | '}pred_keys:[{(d.node_keys ?? []).join(',')}]
|
||||||
|
{' | '}actual:{JSON.stringify(d.actual_moves ?? {})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user