feat: frise sub-lanes + event manager + MA indicators + absorption

Frise chronologique:
- Sub-lane stacking (assignSubLanes) — overlapping events se décalent verticalement
- Zone d'overlap semi-transparente sur la période commune entre 2 événements
- Hauteur dynamique selon nb de sub-lanes par niveau
- Événements en cours avec flèche ▶ à droite, gradient de fin
- Tri par start_date pour placement greedy

Event Manager (composant EventManager.tsx):
- Tableau filtrable par niveau (Long/Moyen/Court)
- Edit modal complet : tous les champs + absorption_pct éditable
- Bouton "IA — Enrichir" par événement → POST /api/timeline/events/{id}/ai-enrich
  → GPT-4o-mini suggère absorption_pct + indicateurs pertinents par niveau temporel
- Delete avec confirmation double-clic
- Expand row pour voir description + indicateurs
- Intégré Timeline page via bouton "Gérer événements"

Backend:
- Nouvelles colonnes market_events: absorption_pct + relevant_indicators (ALTER idempotent)
- DELETE /api/timeline/events/{id}
- POST /api/timeline/events/{id}/ai-enrich

Snapshot Externe:
- AbsorptionBar par événement dans cellule Géopolitique
- MA indicators : fetch 200j history, compute MA10/MA20/MA100 per level (short/med/long)
- Affichage prix vs MA + % écart dans CellMarkets
- Si relevant_indicators configurés sur l'event → utilise ces symbols au lieu des défauts
- Calendar : horizons exclusifs (short 0-7j, medium 8-30j, long 31-90j) — bug corrigé

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-24 20:31:57 +02:00
parent 43b4816596
commit aeb5233deb
6 changed files with 950 additions and 175 deletions

View File

@@ -746,9 +746,20 @@ def init_db():
market_impact TEXT DEFAULT '',
affected_assets TEXT DEFAULT '[]',
impact_score REAL DEFAULT 0.5,
absorption_pct REAL DEFAULT NULL,
relevant_indicators TEXT DEFAULT '[]',
parent_event_id INTEGER REFERENCES market_events(id),
created_at TEXT DEFAULT (datetime('now'))
)""")
# Add columns to existing DBs (idempotent via catch)
for _col, _def in [
("absorption_pct", "REAL DEFAULT NULL"),
("relevant_indicators", "TEXT DEFAULT '[]'"),
]:
try:
c.execute(f"ALTER TABLE market_events ADD COLUMN {_col} {_def}")
except Exception:
pass
c.execute("""CREATE TABLE IF NOT EXISTS timeline_context (
ref_date TEXT PRIMARY KEY,
@@ -4487,12 +4498,25 @@ def update_market_event(event_id: int, ev: Dict[str, Any]) -> bool:
try:
conn.execute("""UPDATE market_events SET
name=?, start_date=?, end_date=?, level=?, category=?, description=?,
market_impact=?, affected_assets=?, impact_score=?
market_impact=?, affected_assets=?, impact_score=?,
absorption_pct=?, relevant_indicators=?
WHERE id=?""",
(ev["name"], ev["start_date"], ev.get("end_date"),
ev["level"], ev.get("category", "macro"), ev.get("description", ""),
ev.get("market_impact", ""), json.dumps(ev.get("affected_assets", [])),
ev.get("impact_score", 0.5), event_id))
ev.get("impact_score", 0.5),
ev.get("absorption_pct"), json.dumps(ev.get("relevant_indicators", [])),
event_id))
conn.commit()
return True
finally:
conn.close()
def delete_market_event(event_id: int) -> bool:
conn = get_conn()
try:
conn.execute("DELETE FROM market_events WHERE id=?", (event_id,))
conn.commit()
return True
finally: