Files
calminer/routes/dashboard.py

114 lines
3.9 KiB
Python

from __future__ import annotations
from datetime import datetime
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from dependencies import get_unit_of_work
from models import ScenarioStatus
from services.unit_of_work import UnitOfWork
router = APIRouter(tags=["Dashboard"])
templates = Jinja2Templates(directory="templates")
def _format_timestamp(moment: datetime | None) -> str | None:
if moment is None:
return None
return moment.strftime("%Y-%m-%d")
def _format_timestamp_with_time(moment: datetime | None) -> str | None:
if moment is None:
return None
return moment.strftime("%Y-%m-%d %H:%M")
def _load_metrics(uow: UnitOfWork) -> dict[str, object]:
total_projects = uow.projects.count()
active_scenarios = uow.scenarios.count_by_status(ScenarioStatus.ACTIVE)
pending_simulations = uow.scenarios.count_by_status(ScenarioStatus.DRAFT)
last_import_at = uow.financial_inputs.latest_created_at()
return {
"total_projects": total_projects,
"active_scenarios": active_scenarios,
"pending_simulations": pending_simulations,
"last_import": _format_timestamp(last_import_at),
}
def _load_recent_projects(uow: UnitOfWork) -> list:
return list(uow.projects.recent(limit=5))
def _load_simulation_updates(uow: UnitOfWork) -> list[dict[str, object]]:
updates: list[dict[str, object]] = []
scenarios = uow.scenarios.recent(limit=5, with_project=True)
for scenario in scenarios:
project_name = scenario.project.name if scenario.project else f"Project #{scenario.project_id}"
timestamp_label = _format_timestamp_with_time(scenario.updated_at)
updates.append(
{
"title": f"{scenario.name} · {scenario.status.value.title()}",
"description": f"Latest update recorded for {project_name}.",
"timestamp": scenario.updated_at,
"timestamp_label": timestamp_label,
}
)
return updates
def _load_scenario_alerts(
request: Request, uow: UnitOfWork
) -> list[dict[str, object]]:
alerts: list[dict[str, object]] = []
drafts = uow.scenarios.list_by_status(
ScenarioStatus.DRAFT, limit=3, with_project=True
)
for scenario in drafts:
project_name = scenario.project.name if scenario.project else f"Project #{scenario.project_id}"
alerts.append(
{
"title": f"Draft scenario: {scenario.name}",
"message": f"{project_name} has a scenario awaiting validation.",
"link": request.url_for(
"projects.view_project", project_id=scenario.project_id
),
}
)
if not alerts:
archived = uow.scenarios.list_by_status(
ScenarioStatus.ARCHIVED, limit=3, with_project=True
)
for scenario in archived:
project_name = scenario.project.name if scenario.project else f"Project #{scenario.project_id}"
alerts.append(
{
"title": f"Archived scenario: {scenario.name}",
"message": f"Review archived scenario insights for {project_name}.",
"link": request.url_for(
"scenarios.view_scenario", scenario_id=scenario.id
),
}
)
return alerts
@router.get("/", response_class=HTMLResponse, include_in_schema=False, name="dashboard.home")
def dashboard_home(
request: Request,
uow: UnitOfWork = Depends(get_unit_of_work),
) -> HTMLResponse:
context = {
"metrics": _load_metrics(uow),
"recent_projects": _load_recent_projects(uow),
"simulation_updates": _load_simulation_updates(uow),
"scenario_alerts": _load_scenario_alerts(request, uow),
}
return templates.TemplateResponse(request, "dashboard.html", context)