feat: Market Events — catégories, cleanup sidebar, rename

- Supprime Timeline & ImpactMonitor du routing (redirects /impact + /timeline → /market-events)
- Renomme 'Instrument Snap.' → 'Instrument Analysis' dans la sidebar
- Onglet 'Catégories & Defaults' dans Market Events: CRUD complet (créer/éditer/supprimer)
  chaque catégorie a une table d'impacts par défaut (instrument, sensibilité, direction, notes)
- impact_service: meilleur matching catégorie (sub_type fuzzy + type fallback)
- DB: delete_event_category() + DELETE /api/impact/categories/{name}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-25 21:11:24 +02:00
parent bd28b6a73a
commit 0b1fcff49c
6 changed files with 401 additions and 26 deletions

View File

@@ -42,6 +42,15 @@ def list_categories() -> List[Dict[str, Any]]:
return cats
@router.delete("/categories/{name}")
def delete_category(name: str) -> Dict[str, Any]:
from services.database import delete_event_category
ok = delete_event_category(name)
if not ok:
raise HTTPException(404, f"Category '{name}' not found")
return {"status": "deleted", "name": name}
@router.put("/categories/{name}")
def update_category(name: str, body: CategoryUpdate) -> Dict[str, Any]:
from services.database import upsert_event_category

View File

@@ -4750,6 +4750,16 @@ def upsert_event_category(cat: Dict[str, Any]) -> int:
conn.close()
def delete_event_category(name: str) -> bool:
conn = get_conn()
try:
cur = conn.execute("DELETE FROM event_categories WHERE name=?", (name,))
conn.commit()
return cur.rowcount > 0
finally:
conn.close()
# ── Instrument impacts ────────────────────────────────────────────────────────
def save_instrument_impacts(impacts: List[Dict[str, Any]]) -> int:

View File

@@ -121,23 +121,39 @@ def evaluate_event_impacts(
if force and existing:
delete_impacts_for_source("event", event_id)
# Get category defaults for context
# Get category defaults — try exact name match first, then fuzzy by sub_type/type
category_defaults = None
cat_name = event.get("sub_type") or event.get("category", "")
if cat_name:
for possible in [
cat_name,
f"FOMC — Décision de taux" if "FOMC" in cat_name.upper() else None,
f"NFP — Non-Farm Payrolls" if "NFP" in cat_name.upper() else None,
]:
if possible:
cat = get_event_category(possible)
if cat:
try:
category_defaults = json.loads(cat.get("default_impacts") or "[]")
except Exception:
pass
break
from services.database import get_all_event_categories
all_cats = get_all_event_categories()
def _find_cat(candidates):
for c in candidates:
if c is None:
continue
row = next((x for x in all_cats if x["name"].lower() == c.lower()), None)
if row:
try:
return json.loads(row.get("default_impacts") or "[]")
except Exception:
pass
return None
sub = (event.get("sub_type") or "").strip()
cat = (event.get("category") or "").strip()
name = (event.get("name") or "").upper()
# Priority: exact sub_type name → fuzzy keyword in category name → type match
candidates = [sub]
for row in all_cats:
rname = row["name"].upper()
if sub and sub.upper() in rname:
candidates.append(row["name"])
elif any(k in name for k in ["FOMC", "FED"] if k in rname):
candidates.append(row["name"])
elif row.get("type") == cat and not sub:
candidates.append(row["name"])
category_defaults = _find_cat(candidates)
api_key = _get_api_key()
if not api_key: