Files
crypto_trader/backend/api/reports.py

156 lines
4.5 KiB
Python
Raw Normal View History

"""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))