From 66f660756888321d6143fcbcbf4d763f53ab4aa8 Mon Sep 17 00:00:00 2001 From: OpenSquared Date: Mon, 22 Jun 2026 18:25:01 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Data=20Management=20tab=20=E2=80=94=20p?= =?UTF-8?q?urge=20endpoints=20for=20all=20major=20data=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend — new DELETE /purge-all endpoints: - /api/logs/purge-all → truncate system_logs (immediate, no 30d wait) - /api/reports/purge-all → cycle_reports + ai_reports - /api/portfolio/purge-all → portfolio + trade_entry_prices - /api/analytics/purge-all → pattern_score_history, regime_clusters, pattern_embeddings, cycle_runs, macro_regime_history, geo_alert_history - /api/var/purge-all → var_snapshots + pnl_snapshots - /api/pattern-lab/purge-all → backtest_lab_runs Frontend — Config.tsx: new 'Data Management' tab - PurgeButton component with inline double-confirm (click Purge → confirm) - Shows table names affected + row count deleted - 6 purge actions: Logs, AI Reports, Portfolio, Analytics, VaR, Pattern Lab Co-Authored-By: Claude Sonnet 4.6 --- backend/routers/analytics.py | 17 ++++ backend/routers/logs.py | 12 +++ backend/routers/pattern_lab.py | 11 +++ backend/routers/portfolio.py | 13 +++ backend/routers/reports.py | 14 +++- backend/routers/var.py | 13 +++ frontend/src/pages/Config.tsx | 144 ++++++++++++++++++++++++++++++++- 7 files changed, 221 insertions(+), 3 deletions(-) diff --git a/backend/routers/analytics.py b/backend/routers/analytics.py index e3e3d8b..0e3b16f 100644 --- a/backend/routers/analytics.py +++ b/backend/routers/analytics.py @@ -100,3 +100,20 @@ def trigger_regime_detect(n_clusters: int = Query(default=4, ge=2, le=6)): def pattern_embeddings_summary(): """Liste des patterns avec embedding vectoriel disponible.""" return {"embeddings": get_all_pattern_embeddings_summary()} + + +@router.delete("/purge-all") +def purge_all_analytics(): + """Purge all analytics history: score history, regime clusters, embeddings, cycle runs.""" + from services.database import get_conn + conn = get_conn() + conn.execute("DELETE FROM pattern_score_history") + conn.execute("DELETE FROM regime_clusters") + conn.execute("DELETE FROM pattern_embeddings") + conn.execute("DELETE FROM cycle_runs") + conn.execute("DELETE FROM macro_regime_history") + conn.execute("DELETE FROM geo_alert_history") + n = conn.total_changes + conn.commit() + conn.close() + return {"deleted": n} diff --git a/backend/routers/logs.py b/backend/routers/logs.py index 18e36df..f81f7fc 100644 --- a/backend/routers/logs.py +++ b/backend/routers/logs.py @@ -73,3 +73,15 @@ def clear_old_logs(older_than_days: int = 30): from services.database import clear_system_logs deleted = clear_system_logs(older_than_days) return {"deleted": deleted, "older_than_days": older_than_days} + + +@router.delete("/purge-all") +def purge_all_logs(): + """Truncate the entire system_logs table.""" + from services.database import get_conn + conn = get_conn() + conn.execute("DELETE FROM system_logs") + deleted = conn.total_changes + conn.commit() + conn.close() + return {"deleted": deleted} diff --git a/backend/routers/pattern_lab.py b/backend/routers/pattern_lab.py index fc86ff1..ed74ce3 100644 --- a/backend/routers/pattern_lab.py +++ b/backend/routers/pattern_lab.py @@ -164,6 +164,17 @@ def get_run(run_id: str): return _row_to_dict(_get_run(run_id)) +@router.delete("/purge-all") +def purge_all_runs(): + """Delete all Pattern Lab runs.""" + conn = get_conn() + conn.execute("DELETE FROM backtest_lab_runs") + n = conn.total_changes + conn.commit() + conn.close() + return {"deleted": n} + + @router.delete("/runs/{run_id}") def delete_run(run_id: str): conn = get_conn() diff --git a/backend/routers/portfolio.py b/backend/routers/portfolio.py index 4f57771..3386ded 100644 --- a/backend/routers/portfolio.py +++ b/backend/routers/portfolio.py @@ -261,6 +261,19 @@ def close_pos(pos_id: str, req: ClosePositionRequest): return close_position(pos_id, req.close_value) +@router.delete("/purge-all") +def purge_all_positions(): + """Delete ALL portfolio positions (open + closed) and related trade entry prices.""" + from services.database import get_conn + conn = get_conn() + conn.execute("DELETE FROM portfolio") + conn.execute("DELETE FROM trade_entry_prices") + n = conn.total_changes + conn.commit() + conn.close() + return {"deleted": n} + + @router.delete("/{pos_id}") def delete_pos(pos_id: str): from services.database import get_conn diff --git a/backend/routers/reports.py b/backend/routers/reports.py index caada2c..e3864cf 100644 --- a/backend/routers/reports.py +++ b/backend/routers/reports.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, HTTPException from services.database import ( get_cycle_reports, get_cycle_report, get_latest_cycle_report, - get_trade_assessments, get_latest_trade_assessments, + get_trade_assessments, get_latest_trade_assessments, get_conn, ) router = APIRouter(prefix="/api/reports", tags=["reports"]) @@ -38,3 +38,15 @@ def assessments_latest(limit: int = 20): def assessments_by_run(run_id: str): """Options technical assessments for a specific cycle run.""" return {"assessments": get_trade_assessments(run_id)} + + +@router.delete("/purge-all") +def purge_all_reports(): + """Delete all AI cycle reports and trade assessments.""" + conn = get_conn() + conn.execute("DELETE FROM cycle_reports") + conn.execute("DELETE FROM ai_reports") + n = conn.total_changes + conn.commit() + conn.close() + return {"deleted": n} diff --git a/backend/routers/var.py b/backend/routers/var.py index b4b8b9c..ff41553 100644 --- a/backend/routers/var.py +++ b/backend/routers/var.py @@ -109,6 +109,19 @@ class SchedulerConfig(BaseModel): pnl_hours: float | None = None +@router.delete("/purge-all") +def purge_all_var(): + """Delete all VaR snapshots and PnL snapshots.""" + from services.database import get_conn + conn = get_conn() + conn.execute("DELETE FROM var_snapshots") + conn.execute("DELETE FROM pnl_snapshots") + n = conn.total_changes + conn.commit() + conn.close() + return {"deleted": n} + + @router.post("/scheduler/config") def scheduler_config(cfg: SchedulerConfig): if cfg.var_enabled is not None: diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index 07acb27..9504a60 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig } from '../hooks/useApi' -import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert } from 'lucide-react' +import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup } from 'lucide-react' import clsx from 'clsx' const API = '' @@ -353,7 +353,7 @@ export default function Config() { const [fredKey, setFredKey] = useState('') const [showKeys, setShowKeys] = useState(false) const [savedMsg, setSavedMsg] = useState('') - const [configTab, setConfigTab] = useState<'ia' | 'cycle' | 'sources' | 'options' | 'profiles'>('ia') + const [configTab, setConfigTab] = useState<'ia' | 'cycle' | 'sources' | 'options' | 'profiles' | 'data'>('ia') // IV gate local state const [ivGateEnabled, setIvGateEnabled] = useState(true) @@ -517,6 +517,7 @@ export default function Config() { { key: 'sources', label: 'Sources', icon: Globe }, { key: 'options', label: 'Options — Settings', icon: TrendingUp }, { key: 'profiles', label: 'Risk Profiles', icon: SlidersHorizontal }, + { key: 'data', label: 'Data Management', icon: DatabaseBackup }, ] as const return ( @@ -1500,6 +1501,145 @@ export default function Config() { {/* ── Profils de risque ── */} {configTab === 'profiles' && } + {/* ── Data Management ── */} + {configTab === 'data' && } + + + ) +} + +// ── Data Management card ─────────────────────────────────────────────────────── + +function PurgeButton({ label, description, tables, endpoint, color = 'red' }: { + label: string + description: string + tables: string[] + endpoint: string + color?: 'red' | 'orange' +}) { + const qc = useQueryClient() + const [confirm, setConfirm] = useState(false) + const [loading, setLoading] = useState(false) + const [done, setDone] = useState(null) + + const handlePurge = async () => { + setLoading(true) + try { + const res = await fetch(endpoint, { method: 'DELETE' }) + const data = await res.json() + setDone(`Deleted ${data.deleted ?? '?'} rows`) + setConfirm(false) + qc.invalidateQueries() + } catch (e: any) { + setDone(`Error: ${e?.message}`) + } finally { + setLoading(false) + } + } + + const borderCol = color === 'red' ? 'border-red-800/40' : 'border-orange-800/40' + const bgCol = color === 'red' ? 'bg-red-900/5' : 'bg-orange-900/5' + const btnCol = color === 'red' + ? 'bg-red-700 hover:bg-red-600 disabled:opacity-50' + : 'bg-orange-700 hover:bg-orange-600 disabled:opacity-50' + + return ( +
+
+
+
{label}
+
{description}
+
+ {tables.map(t => ( + {t} + ))} +
+
+
+ {done && {done}} + {!confirm ? ( + + ) : ( +
+ Are you sure? + + +
+ )} +
+
+
+ ) +} + +function DataManagementCard() { + return ( +
+
+ +

Data Management

+ Irreversible operations — all deletes are permanent +
+ + + + + + + + + + + + + +
+

+ Note: Custom patterns, risk profiles, config, API keys, and the Knowledge Base are managed from their respective sections and are not affected by the purges above. +

+
) }