156 lines
4.5 KiB
Python
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))
|