feat: add Reset Decision button to clear portfolio stage and re-run
- ReportStore.clear_portfolio_stage(date, portfolio_id): deletes pm_decision (.json + .md) and execution_result files for a given date/portfolio - DELETE /api/run/portfolio-stage endpoint: calls clear_portfolio_stage and returns list of deleted files - Dashboard: 'Reset Decision' button calls the endpoint, then user can run Auto to re-run Phase 3 from scratch while skipping Phase 1 & 2 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2a17ea0cca
commit
860968835c
|
|
@ -104,6 +104,27 @@ async def trigger_auto(
|
|||
background_tasks.add_task(_run_and_store, run_id, engine.run_auto(run_id, params or {}))
|
||||
return {"run_id": run_id, "status": "queued"}
|
||||
|
||||
@router.delete("/portfolio-stage")
|
||||
async def reset_portfolio_stage(
|
||||
params: Dict[str, Any],
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Delete PM decision and execution result for a given date/portfolio_id.
|
||||
|
||||
After calling this, an auto run will re-run Phase 3 from scratch
|
||||
(Phases 1 & 2 are skipped if their cached results still exist).
|
||||
"""
|
||||
from tradingagents.portfolio.report_store import ReportStore
|
||||
date = params.get("date")
|
||||
portfolio_id = params.get("portfolio_id")
|
||||
if not date or not portfolio_id:
|
||||
raise HTTPException(status_code=422, detail="date and portfolio_id are required")
|
||||
store = ReportStore()
|
||||
deleted = store.clear_portfolio_stage(date, portfolio_id)
|
||||
logger.info("reset_portfolio_stage date=%s portfolio=%s deleted=%s user=%s", date, portfolio_id, deleted, user["user_id"])
|
||||
return {"deleted": deleted, "date": date, "portfolio_id": portfolio_id}
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_runs(user: dict = Depends(get_current_user)):
|
||||
# Filter by user in production
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import {
|
|||
Collapse,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { LayoutDashboard, Wallet, Settings, Terminal as TerminalIcon, ChevronRight, Eye, Search, BarChart3, Bot, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { LayoutDashboard, Wallet, Settings, Terminal as TerminalIcon, ChevronRight, Eye, Search, BarChart3, Bot, ChevronDown, ChevronUp, Trash2 } from 'lucide-react';
|
||||
import { MetricHeader } from './components/MetricHeader';
|
||||
import { AgentGraph } from './components/AgentGraph';
|
||||
import { PortfolioViewer } from './components/PortfolioViewer';
|
||||
|
|
@ -371,6 +371,27 @@ export const Dashboard: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const resetPortfolioStage = async () => {
|
||||
if (!params.date || !params.portfolio_id) {
|
||||
toast({ title: 'Date and Portfolio ID are required', status: 'warning', duration: 3000, isClosable: true, position: 'top' });
|
||||
setShowParams(true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await axios.delete(`${API_BASE}/run/portfolio-stage`, { data: { date: params.date, portfolio_id: params.portfolio_id } });
|
||||
const deleted: string[] = res.data.deleted;
|
||||
toast({
|
||||
title: deleted.length ? `Cleared: ${deleted.join(', ')}` : 'Nothing to clear — no decision files found',
|
||||
status: deleted.length ? 'success' : 'info',
|
||||
duration: 4000,
|
||||
isClosable: true,
|
||||
position: 'top',
|
||||
});
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to reset portfolio stage', status: 'error', duration: 3000, isClosable: true, position: 'top' });
|
||||
}
|
||||
};
|
||||
|
||||
/** Open the full-screen event detail modal */
|
||||
const openModal = useCallback((evt: AgentEvent) => {
|
||||
setModalEvent(evt);
|
||||
|
|
@ -483,6 +504,19 @@ export const Dashboard: React.FC = () => {
|
|||
);
|
||||
})}
|
||||
<Divider orientation="vertical" h="20px" />
|
||||
<Tooltip label="Clear PM decision & execution result for this date/portfolio, then re-run Auto to start Phase 3 fresh">
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<Trash2 size={14} />}
|
||||
colorScheme="red"
|
||||
variant="outline"
|
||||
onClick={resetPortfolioStage}
|
||||
isDisabled={isRunning}
|
||||
>
|
||||
Reset Decision
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Divider orientation="vertical" h="20px" />
|
||||
<Tag size="sm" colorScheme={status === 'streaming' ? 'green' : status === 'completed' ? 'blue' : status === 'error' ? 'red' : 'gray'}>
|
||||
{status.toUpperCase()}
|
||||
</Tag>
|
||||
|
|
|
|||
|
|
@ -303,6 +303,23 @@ class ReportStore:
|
|||
path = self._portfolio_dir(date) / f"{portfolio_id}_execution_result.json"
|
||||
return self._read_json(path)
|
||||
|
||||
def clear_portfolio_stage(self, date: str, portfolio_id: str) -> list[str]:
|
||||
"""Delete PM decision and execution result files for a given date/portfolio.
|
||||
|
||||
Returns a list of deleted file names so the caller can log what was removed.
|
||||
"""
|
||||
targets = [
|
||||
self._portfolio_dir(date) / f"{portfolio_id}_pm_decision.json",
|
||||
self._portfolio_dir(date) / f"{portfolio_id}_pm_decision.md",
|
||||
self._portfolio_dir(date) / f"{portfolio_id}_execution_result.json",
|
||||
]
|
||||
deleted = []
|
||||
for path in targets:
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
deleted.append(path.name)
|
||||
return deleted
|
||||
|
||||
def list_pm_decisions(self, portfolio_id: str) -> list[Path]:
|
||||
"""Return all saved PM decision JSON paths for portfolio_id, newest first.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue