Backend (@backend-dev): - Add ReportService with PDF/CSV generation (reportlab, pandas) - Implement Report API endpoints (POST, GET, DELETE, download) - Add ReportRepository and schemas - Configure storage with auto-cleanup (30 days) - Rate limiting: 10 downloads/minute - Professional PDF templates with charts support Frontend (@frontend-dev): - Integrate Recharts for data visualization - Add CostBreakdown, TimeSeries, ComparisonBar charts - Implement scenario comparison page with multi-select - Add dark/light mode toggle with ThemeProvider - Create Reports page with generation form and list - Add new UI components: checkbox, dialog, tabs, label, skeleton - Implement useComparison and useReports hooks QA (@qa-engineer): - Setup Playwright E2E testing framework - Create 7 test spec files with 94 test cases - Add visual regression testing with baselines - Configure multi-browser testing (Chrome, Firefox, WebKit) - Add mobile responsive tests - Create test fixtures and helpers - Setup GitHub Actions CI workflow Documentation (@spec-architect): - Create detailed kanban-v0.4.0.md with 27 tasks - Update progress.md with v0.4.0 tracking - Create v0.4.0 planning prompt Features: ✅ PDF/CSV Report Generation ✅ Interactive Charts (Pie, Area, Bar) ✅ Scenario Comparison (2-4 scenarios) ✅ Dark/Light Mode Toggle ✅ E2E Test Suite (94 tests) Dependencies added: - Backend: reportlab, pandas, slowapi - Frontend: recharts, date-fns, @radix-ui/react-checkbox/dialog/tabs - Testing: @playwright/test 27 tasks completed, 100% v0.4.0 implementation
55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
"""Report repository with specific methods."""
|
|
|
|
from typing import Optional, List
|
|
from uuid import UUID
|
|
from datetime import datetime
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, update, desc
|
|
|
|
from src.models.report import Report
|
|
from src.repositories.base import BaseRepository
|
|
|
|
|
|
class ReportRepository(BaseRepository[Report]):
|
|
"""Repository for Report model with specific methods."""
|
|
|
|
def __init__(self):
|
|
super().__init__(Report)
|
|
|
|
async def get_by_scenario(
|
|
self, db: AsyncSession, scenario_id: UUID, skip: int = 0, limit: int = 100
|
|
) -> List[Report]:
|
|
"""Get reports for a specific scenario."""
|
|
query = (
|
|
select(Report)
|
|
.where(Report.scenario_id == scenario_id)
|
|
.order_by(desc(Report.created_at))
|
|
.offset(skip)
|
|
.limit(limit)
|
|
)
|
|
result = await db.execute(query)
|
|
return result.scalars().all()
|
|
|
|
async def count_by_scenario(self, db: AsyncSession, scenario_id: UUID) -> int:
|
|
"""Count reports for a specific scenario."""
|
|
query = select(Report).where(Report.scenario_id == scenario_id)
|
|
result = await db.execute(query)
|
|
return len(result.scalars().all())
|
|
|
|
async def update_file_size(
|
|
self, db: AsyncSession, report_id: UUID, file_size_bytes: int
|
|
) -> Optional[Report]:
|
|
"""Update report file size."""
|
|
result = await db.execute(
|
|
update(Report)
|
|
.where(Report.id == report_id)
|
|
.values(file_size_bytes=file_size_bytes)
|
|
.returning(Report)
|
|
)
|
|
await db.commit()
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
# Singleton instance
|
|
report_repository = ReportRepository()
|