feat: desk ia
This commit is contained in:
@@ -1125,6 +1125,146 @@ def _build_graph_json_from_spec(spec: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _run_auto_analysis(event: dict, template_id: int) -> bool:
|
||||
"""
|
||||
Lightweight causal analysis for the auto_template pipeline.
|
||||
Builds inputs from event fields (no yfinance), evaluates the graph,
|
||||
and upserts into causal_event_analyses so the instrument frise can read it.
|
||||
"""
|
||||
try:
|
||||
from services.database import get_conn
|
||||
from services.causal_graphs import get_template, evaluate_graph
|
||||
|
||||
conn = get_conn()
|
||||
tmpl = get_template(conn, template_id)
|
||||
if not tmpl:
|
||||
conn.close()
|
||||
logger.warning(f"[auto_analysis] template #{template_id} introuvable")
|
||||
return False
|
||||
|
||||
graph = tmpl["graph_json"]
|
||||
inputs = {}
|
||||
mapping = graph.get("input_mapping", {})
|
||||
edate_str = event["start_date"][:10]
|
||||
|
||||
for input_id, cfg in mapping.items():
|
||||
src = cfg.get("source", "")
|
||||
key = cfg.get("key", "")
|
||||
field = cfg.get("field", "close")
|
||||
|
||||
if src == "economic_events" and key:
|
||||
row_e = conn.execute(
|
||||
"""SELECT actual_value, forecast_value FROM economic_events
|
||||
WHERE series_id = ? AND event_date <= ? AND actual_value IS NOT NULL
|
||||
ORDER BY event_date DESC LIMIT 1""",
|
||||
(key, edate_str),
|
||||
).fetchone()
|
||||
if row_e:
|
||||
if field == "surprise" and row_e["forecast_value"] is not None:
|
||||
inputs[input_id] = round(float(row_e["actual_value"]) - float(row_e["forecast_value"]), 4)
|
||||
else:
|
||||
inputs[input_id] = float(row_e["actual_value"])
|
||||
|
||||
elif src == "ff_calendar" and key:
|
||||
row_f = conn.execute(
|
||||
"""SELECT actual_value, forecast_value FROM ff_calendar
|
||||
WHERE event_name = ? AND event_date <= ? AND actual_value IS NOT NULL
|
||||
ORDER BY event_date DESC LIMIT 1""",
|
||||
(key, edate_str),
|
||||
).fetchone()
|
||||
if row_f and row_f["actual_value"]:
|
||||
try:
|
||||
actual = float(row_f["actual_value"])
|
||||
if field == "surprise" and row_f["forecast_value"]:
|
||||
inputs[input_id] = round(actual - float(row_f["forecast_value"]), 4)
|
||||
else:
|
||||
inputs[input_id] = actual
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
elif src == "surprise_bps" and event.get("surprise_pct") is not None:
|
||||
raw = float(event["surprise_pct"])
|
||||
node_range = cfg.get("range")
|
||||
if node_range and len(node_range) == 2:
|
||||
lo, hi = float(node_range[0]), float(node_range[1])
|
||||
inputs[input_id] = round(max(lo, min(hi, raw)), 4)
|
||||
else:
|
||||
inputs[input_id] = round(raw, 4)
|
||||
|
||||
elif src == "surprise" and event.get("surprise_pct") is not None:
|
||||
raw = float(event["surprise_pct"])
|
||||
node_range = cfg.get("range")
|
||||
if node_range and len(node_range) == 2:
|
||||
lo, hi = float(node_range[0]), float(node_range[1])
|
||||
bound = max(abs(lo), abs(hi))
|
||||
if bound <= 10:
|
||||
inputs[input_id] = round(max(lo, min(hi, raw / 100 * bound)), 4)
|
||||
else:
|
||||
inputs[input_id] = round(max(lo, min(hi, raw)), 4)
|
||||
else:
|
||||
inputs[input_id] = round(raw / 100, 4)
|
||||
|
||||
elif src == "impact_score_scaled" and event.get("impact_score") is not None:
|
||||
inputs[input_id] = float(event["impact_score"]) / 10.0
|
||||
|
||||
elif src == "impact_score_pct" and event.get("impact_score") is not None:
|
||||
inputs[input_id] = float(event["impact_score"])
|
||||
|
||||
elif src == "actual_value" and event.get("actual_value") is not None:
|
||||
inputs[input_id] = float(event["actual_value"])
|
||||
|
||||
node_values = evaluate_graph(graph, inputs, {})
|
||||
|
||||
instruments = list(set(tmpl.get("instruments", []))) or ["EURUSD"]
|
||||
all_instruments_csv = ",".join(sorted(instruments)) or "EURUSD"
|
||||
analyzed_at = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
event_id = event["id"]
|
||||
|
||||
logger.info(
|
||||
f"[auto_analysis] event #{event_id} → tmpl #{template_id} | "
|
||||
f"inputs={inputs} | instruments={all_instruments_csv}"
|
||||
)
|
||||
|
||||
existing = conn.execute(
|
||||
"SELECT id FROM causal_event_analyses WHERE market_event_id=? AND template_id=?",
|
||||
(event_id, template_id),
|
||||
).fetchone()
|
||||
|
||||
if existing:
|
||||
conn.execute("""
|
||||
UPDATE causal_event_analyses
|
||||
SET instrument=?, inputs_json=?, override_params=?, prediction_json=?,
|
||||
actual_json=?, activation_score=?, drift_json=?, analyzed_at=?
|
||||
WHERE market_event_id=? AND template_id=?
|
||||
""", (
|
||||
all_instruments_csv, json.dumps(inputs), json.dumps({}),
|
||||
json.dumps(node_values), json.dumps({}), None,
|
||||
json.dumps({}), analyzed_at,
|
||||
event_id, template_id,
|
||||
))
|
||||
else:
|
||||
conn.execute("""
|
||||
INSERT INTO causal_event_analyses
|
||||
(market_event_id, template_id, instrument, inputs_json,
|
||||
override_params, prediction_json, actual_json,
|
||||
activation_score, drift_json, analyzed_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||
""", (
|
||||
event_id, template_id, all_instruments_csv,
|
||||
json.dumps(inputs), json.dumps({}),
|
||||
json.dumps(node_values), json.dumps({}),
|
||||
None, json.dumps({}), analyzed_at,
|
||||
))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
logger.info(f"[auto_analysis] causal_event_analyses upserted → event #{event_id}, tmpl #{template_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[auto_analysis] event #{event.get('id')}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def auto_assign_template(event_id: int) -> dict:
|
||||
"""
|
||||
Auto-assign or auto-create a causal template for a market event.
|
||||
@@ -1162,6 +1302,7 @@ def auto_assign_template(event_id: int) -> dict:
|
||||
conn.commit()
|
||||
row = conn.execute("SELECT name FROM causal_graph_templates WHERE id = ?", (tmpl_id,)).fetchone()
|
||||
conn.close()
|
||||
_run_auto_analysis(event, tmpl_id)
|
||||
return {"template_id": tmpl_id, "action": "assigned", "name": row["name"] if row else "", "confidence": confidence}
|
||||
|
||||
# Step 2 — no good match → generate a new template
|
||||
@@ -1220,6 +1361,7 @@ Règles : coef intermédiaire ∈ [-5,5] ; coef output en pips, |val| ∈ [30,20
|
||||
conn2.close()
|
||||
|
||||
logger.info(f"[auto_template] Created template #{new_id} '{spec.get('name')}' for event #{event_id}")
|
||||
_run_auto_analysis(event, new_id)
|
||||
return {"template_id": new_id, "action": "created", "name": spec.get("name", "Template IA"), "confidence": confidence}
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -1658,17 +1658,30 @@ def check_new_market_events(
|
||||
# Auto-assign or auto-create causal templates for eco events
|
||||
if "eco" in sources and bool(eco_cfg.get("auto_template", False)):
|
||||
eco_events = results.get("eco", [])
|
||||
logger.info(f"[check_events/auto_template] auto_template=ON, {len(eco_events)} new eco events to process")
|
||||
if eco_events:
|
||||
try:
|
||||
from routers.causal_lab import auto_assign_template
|
||||
for ev in eco_events:
|
||||
eid = ev.get("event_id")
|
||||
eid = ev.get("event_id")
|
||||
ename = ev.get("name", "?")
|
||||
logger.info(f"[check_events/auto_template] processing event #{eid} '{ename}'")
|
||||
if eid:
|
||||
res = auto_assign_template(eid)
|
||||
action = res.get("action", "error")
|
||||
name = res.get("name", "")
|
||||
logger.info(f"[check_events/auto_template] event #{eid} → {action}: '{name}'")
|
||||
if "error" in res:
|
||||
logger.warning(f"[check_events/auto_template] event #{eid} error: {res['error']}")
|
||||
else:
|
||||
action = res.get("action", "?")
|
||||
name = res.get("name", "")
|
||||
tmpl_id = res.get("template_id")
|
||||
confidence = res.get("confidence", 0)
|
||||
logger.info(
|
||||
f"[check_events/auto_template] event #{eid} → {action}: "
|
||||
f"'{name}' (tmpl #{tmpl_id}, conf={confidence:.2f})"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"[check_events/auto_template] failed: {e}")
|
||||
logger.warning(f"[check_events/auto_template] failed: {e}", exc_info=True)
|
||||
else:
|
||||
logger.info("[check_events/auto_template] no new eco events this cycle")
|
||||
|
||||
return results
|
||||
|
||||
Reference in New Issue
Block a user