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