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:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user