128 lines
4.6 KiB
Python
128 lines
4.6 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, require_authenticated_user
|
|
from models import ScenarioStatus, User
|
|
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]:
|
|
if not uow.projects or not uow.scenarios or not uow.financial_inputs:
|
|
raise RuntimeError("UnitOfWork repositories not initialised")
|
|
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:
|
|
if not uow.projects:
|
|
raise RuntimeError("Project repository not initialised")
|
|
return list(uow.projects.recent(limit=5))
|
|
|
|
|
|
def _load_simulation_updates(uow: UnitOfWork) -> list[dict[str, object]]:
|
|
updates: list[dict[str, object]] = []
|
|
if not uow.scenarios:
|
|
raise RuntimeError("Scenario repository not initialised")
|
|
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]] = []
|
|
|
|
if not uow.scenarios:
|
|
raise RuntimeError("Scenario repository not initialised")
|
|
|
|
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,
|
|
_: User = Depends(require_authenticated_user),
|
|
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),
|
|
"export_modals": {
|
|
"projects": request.url_for("exports.modal", dataset="projects"),
|
|
"scenarios": request.url_for("exports.modal", dataset="scenarios"),
|
|
},
|
|
}
|
|
return templates.TemplateResponse(request, "dashboard.html", context)
|