Files
calminer/main.py
zwitschi 522b1e4105
Some checks failed
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / lint (push) Failing after 15s
CI / deploy (push) Has been skipped
feat: add scenarios list page with metrics and quick actions
- 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.
2025-11-13 16:21:36 +01:00

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")