Files
crypto_trader/backend/api/reports.py

156 lines
4.5 KiB
Python

"""Reports API endpoints for background report generation."""
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel
router = APIRouter()
class ReportRequest(BaseModel):
"""Request model for report generation."""
report_type: str # 'performance', 'trades', 'tax', 'backtest'
format: str = "pdf" # 'pdf' or 'csv'
year: Optional[int] = None # For tax reports
method: Optional[str] = "fifo" # For tax reports: 'fifo', 'lifo'
class ExportRequest(BaseModel):
"""Request model for data export."""
export_type: str # 'orders', 'positions'
@router.post("/generate")
async def generate_report(request: ReportRequest):
"""Generate a report in the background.
This endpoint queues a report generation task and returns immediately.
Use /api/tasks/{task_id} to monitor progress.
Supported report types:
- performance: Portfolio performance report
- trades: Trade history export
- tax: Tax report with capital gains calculation
Returns:
Task ID for monitoring
"""
try:
from src.worker.tasks import generate_report_task
params = {
"format": request.format,
}
if request.report_type == "tax":
from datetime import datetime
params["year"] = request.year or datetime.now().year
params["method"] = request.method
task = generate_report_task.delay(
report_type=request.report_type,
params=params
)
return {
"task_id": task.id,
"status": "queued",
"report_type": request.report_type,
"format": request.format
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/export")
async def export_data(request: ExportRequest):
"""Export data in the background.
This endpoint queues a data export task and returns immediately.
Use /api/tasks/{task_id} to monitor progress.
Supported export types:
- orders: Order history
- positions: Current/historical positions
Returns:
Task ID for monitoring
"""
try:
from src.worker.tasks import export_data_task
task = export_data_task.delay(
export_type=request.export_type,
params={}
)
return {
"task_id": task.id,
"status": "queued",
"export_type": request.export_type
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/list")
async def list_reports():
"""List available reports in the reports directory."""
try:
from pathlib import Path
import os
reports_dir = Path(os.path.expanduser("~/.local/share/crypto_trader/reports"))
if not reports_dir.exists():
return {"reports": []}
reports = []
for f in reports_dir.iterdir():
if f.is_file():
stat = f.stat()
reports.append({
"name": f.name,
"path": str(f),
"size": stat.st_size,
"created": stat.st_mtime,
"type": f.suffix.lstrip(".")
})
# Sort by creation time, newest first
reports.sort(key=lambda x: x["created"], reverse=True)
return {"reports": reports}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{filename}")
async def delete_report(filename: str):
"""Delete a generated report."""
try:
from pathlib import Path
import os
reports_dir = Path(os.path.expanduser("~/.local/share/crypto_trader/reports"))
filepath = reports_dir / filename
if not filepath.exists():
raise HTTPException(status_code=404, detail="Report not found")
# Security check: ensure the file is actually in reports dir
if not str(filepath.resolve()).startswith(str(reports_dir.resolve())):
raise HTTPException(status_code=403, detail="Access denied")
filepath.unlink()
return {"status": "deleted", "filename": filename}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))