feat: Data Management tab — purge endpoints for all major data stores

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 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-22 18:25:01 +02:00
parent 303ecc2a3a
commit 66f6607568
7 changed files with 221 additions and 3 deletions

View File

@@ -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}

View File

@@ -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}

View File

@@ -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()

View File

@@ -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

View File

@@ -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}

View File

@@ -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:

View File

@@ -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' && <RiskProfilesCard />}
{/* ── Data Management ── */}
{configTab === 'data' && <DataManagementCard />}
</div>
)
}
// ── 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<string | null>(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 (
<div className={`border ${borderCol} ${bgCol} rounded-lg p-4`}>
<div className="flex items-start justify-between gap-3">
<div className="flex-1">
<div className="text-sm font-semibold text-slate-200 mb-0.5">{label}</div>
<div className="text-xs text-slate-500 mb-1.5">{description}</div>
<div className="flex flex-wrap gap-1">
{tables.map(t => (
<span key={t} className="text-[10px] font-mono bg-slate-800 text-slate-400 px-1.5 py-0.5 rounded">{t}</span>
))}
</div>
</div>
<div className="flex-shrink-0 flex flex-col items-end gap-1.5">
{done && <span className="text-[10px] text-emerald-400">{done}</span>}
{!confirm ? (
<button onClick={() => { setConfirm(true); setDone(null) }}
className={`text-xs px-3 py-1.5 rounded text-white font-semibold ${btnCol}`}>
<Trash2 className="w-3 h-3 inline mr-1" />Purge
</button>
) : (
<div className="flex items-center gap-2">
<span className="text-xs text-red-400 font-semibold">Are you sure?</span>
<button onClick={handlePurge} disabled={loading}
className={`text-xs px-3 py-1.5 rounded text-white font-semibold ${btnCol}`}>
{loading ? 'Deleting…' : 'Yes, delete all'}
</button>
<button onClick={() => setConfirm(false)} className="text-xs text-slate-500 hover:text-slate-300">Cancel</button>
</div>
)}
</div>
</div>
</div>
)
}
function DataManagementCard() {
return (
<div className="space-y-4">
<div className="flex items-center gap-2 mb-2">
<DatabaseBackup className="w-4 h-4 text-red-400" />
<h2 className="text-base font-bold text-white">Data Management</h2>
<span className="text-xs text-slate-500 ml-2">Irreversible operations all deletes are permanent</span>
</div>
<PurgeButton
label="System Logs"
description="Clear all system logs immediately (normally kept 30 days)."
tables={['system_logs']}
endpoint="/api/logs/purge-all"
color="orange"
/>
<PurgeButton
label="AI Reports (Rapport IA)"
description="Delete all generated cycle reports and trade assessments."
tables={['cycle_reports', 'ai_reports']}
endpoint="/api/reports/purge-all"
color="orange"
/>
<PurgeButton
label="Portfolio — All Positions"
description="Delete every position (open + closed) and all trade entry price history. Cannot be undone."
tables={['portfolio', 'trade_entry_prices']}
endpoint="/api/portfolio/purge-all"
color="red"
/>
<PurgeButton
label="Analytics History"
description="Purge all pattern score history, regime clusters, embeddings, cycle run logs, and macro/geo alert history."
tables={['pattern_score_history', 'regime_clusters', 'pattern_embeddings', 'cycle_runs', 'macro_regime_history', 'geo_alert_history']}
endpoint="/api/analytics/purge-all"
color="red"
/>
<PurgeButton
label="VaR & PnL Snapshots"
description="Delete all Value-at-Risk snapshots and daily PnL snapshots."
tables={['var_snapshots', 'pnl_snapshots']}
endpoint="/api/var/purge-all"
color="orange"
/>
<PurgeButton
label="Pattern Lab Runs"
description="Delete all Pattern Lab backtest runs (event presets + instrument scans)."
tables={['backtest_lab_runs']}
endpoint="/api/pattern-lab/purge-all"
color="orange"
/>
<div className="mt-2 p-3 border border-slate-700/40 rounded-lg bg-dark-700/30">
<p className="text-xs text-slate-500">
<span className="font-semibold text-slate-400">Note:</span> 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.
</p>
</div>
</div>
)
}