- Introduced a new template for listing scenarios associated with a project. - Added metrics for total, active, draft, and archived scenarios. - Implemented quick actions for creating new scenarios and reviewing project overview. - Enhanced navigation with breadcrumbs for better user experience. refactor: update Opex and Profitability templates for consistency - Changed titles and button labels for clarity in Opex and Profitability templates. - Updated form IDs and action URLs for better alignment with new naming conventions. - Improved navigation links to include scenario and project overviews. test: add integration tests for Opex calculations - Created new tests for Opex calculation HTML and JSON flows. - Validated successful calculations and ensured correct data persistence. - Implemented tests for currency mismatch and unsupported frequency scenarios. test: enhance project and scenario route tests - Added tests to verify scenario list rendering and calculator shortcuts. - Ensured scenario detail pages link back to the portfolio correctly. - Validated project detail pages show associated scenarios accurately.
120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
import logging
|
|
from contextlib import asynccontextmanager
|
|
from typing import Awaitable, Callable
|
|
|
|
from fastapi import FastAPI, Request, Response
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
|
|
from config.settings import get_settings
|
|
from middleware.auth_session import AuthSessionMiddleware
|
|
from middleware.metrics import MetricsMiddleware
|
|
from middleware.validation import validate_json
|
|
from routes.auth import router as auth_router
|
|
from routes.dashboard import router as dashboard_router
|
|
from routes.calculations import router as calculations_router
|
|
from routes.imports import router as imports_router
|
|
from routes.exports import router as exports_router
|
|
from routes.projects import router as projects_router
|
|
from routes.reports import router as reports_router
|
|
from routes.scenarios import router as scenarios_router
|
|
from routes.ui import router as ui_router
|
|
from routes.navigation import router as navigation_router
|
|
from monitoring import router as monitoring_router
|
|
from services.bootstrap import bootstrap_admin, bootstrap_pricing_settings
|
|
from scripts.init_db import init_db as init_db_script
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def _bootstrap_startup() -> None:
|
|
settings = get_settings()
|
|
admin_settings = settings.admin_bootstrap_settings()
|
|
pricing_metadata = settings.pricing_metadata()
|
|
try:
|
|
try:
|
|
init_db_script()
|
|
except Exception:
|
|
logger.exception(
|
|
"DB initializer failed; continuing to bootstrap (non-fatal)")
|
|
|
|
role_result, admin_result = bootstrap_admin(settings=admin_settings)
|
|
pricing_result = bootstrap_pricing_settings(metadata=pricing_metadata)
|
|
logger.info(
|
|
"Admin bootstrap completed: roles=%s created=%s updated=%s rotated=%s assigned=%s",
|
|
role_result.ensured,
|
|
admin_result.created_user,
|
|
admin_result.updated_user,
|
|
admin_result.password_rotated,
|
|
admin_result.roles_granted,
|
|
)
|
|
try:
|
|
seed = pricing_result.seed
|
|
slug = getattr(seed.settings, "slug", None) if seed and getattr(
|
|
seed, "settings", None) else None
|
|
created = getattr(seed, "created", None)
|
|
updated_fields = getattr(seed, "updated_fields", None)
|
|
impurity_upserts = getattr(seed, "impurity_upserts", None)
|
|
logger.info(
|
|
"Pricing settings bootstrap completed: slug=%s created=%s updated_fields=%s impurity_upserts=%s projects_assigned=%s",
|
|
slug,
|
|
created,
|
|
updated_fields,
|
|
impurity_upserts,
|
|
pricing_result.projects_assigned,
|
|
)
|
|
except Exception:
|
|
logger.info(
|
|
"Pricing settings bootstrap completed (partial): projects_assigned=%s",
|
|
pricing_result.projects_assigned,
|
|
)
|
|
except Exception: # pragma: no cover - defensive logging
|
|
logger.exception(
|
|
"Failed to bootstrap administrator or pricing settings")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def app_lifespan(_: FastAPI):
|
|
await _bootstrap_startup()
|
|
yield
|
|
|
|
|
|
app = FastAPI(lifespan=app_lifespan)
|
|
|
|
app.add_middleware(AuthSessionMiddleware)
|
|
app.add_middleware(MetricsMiddleware)
|
|
|
|
|
|
@app.middleware("http")
|
|
async def json_validation(
|
|
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
) -> Response:
|
|
return await validate_json(request, call_next)
|
|
|
|
|
|
@app.get("/health", summary="Container health probe")
|
|
async def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|
|
|
|
|
|
@app.get("/favicon.ico", include_in_schema=False)
|
|
async def favicon() -> Response:
|
|
static_directory = "static"
|
|
favicon_img = "favicon.ico"
|
|
return FileResponse(f"{static_directory}/{favicon_img}")
|
|
|
|
|
|
app.include_router(dashboard_router)
|
|
app.include_router(calculations_router)
|
|
app.include_router(auth_router)
|
|
app.include_router(imports_router)
|
|
app.include_router(exports_router)
|
|
app.include_router(projects_router)
|
|
app.include_router(scenarios_router)
|
|
app.include_router(reports_router)
|
|
app.include_router(ui_router)
|
|
app.include_router(monitoring_router)
|
|
app.include_router(navigation_router)
|
|
|
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|