Start autonomous Belgium and North Sea RC
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-18 00:12:34 +02:00
parent 9513f8613e
commit 9c402e0df2
36 changed files with 2235 additions and 115 deletions
+50 -7
View File
@@ -1,5 +1,9 @@
from __future__ import annotations
import logging
import uuid
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
@@ -9,6 +13,11 @@ from app.api.routes import analysis, areas, assistant, datasets, demo, detection
from app.core.config import get_settings
from app.core.errors import AppError
from app.core.logging import configure_logging
from app.db.session import SessionLocal
from app.services.runtime_reconciliation_service import RuntimeReconciliationService
logger = logging.getLogger("geointel")
def _to_error_payload(
@@ -27,13 +36,33 @@ def _to_error_payload(
def create_app() -> FastAPI:
settings = get_settings()
configure_logging(settings.log_level)
configure_logging(settings.log_level, settings.sql_log_level)
@asynccontextmanager
async def lifespan(_: FastAPI):
if settings.reconcile_interrupted_runs_on_startup:
db = SessionLocal()
try:
result = RuntimeReconciliationService.reconcile(db)
logger.info(
"Runtime reconciliation completed: jobs=%s analysis_runs=%s",
result.interrupted_jobs,
result.interrupted_analysis_runs,
)
except Exception:
db.rollback()
logger.exception("Runtime reconciliation failed")
raise
finally:
db.close()
yield
app = FastAPI(
title="GeoIntel Kempen",
title="GeoIntel",
version=settings.app_version,
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan,
)
app.add_middleware(
@@ -60,6 +89,14 @@ def create_app() -> FastAPI:
app.include_router(temporal.router, prefix=settings.api_prefix)
app.include_router(assistant.router, prefix=settings.api_prefix)
@app.middleware("http")
async def request_identity(request: Request, call_next):
request_id = request.headers.get("x-request-id") or str(uuid.uuid4())
request.state.request_id = request_id
response = await call_next(request)
response.headers["x-request-id"] = request_id
return response
@app.exception_handler(AppError)
async def app_error(request: Request, exc: AppError): # noqa: ARG001
return JSONResponse(
@@ -68,7 +105,7 @@ def create_app() -> FastAPI:
exc.code,
exc.message,
exc.details,
request_id=request.headers.get("x-request-id"),
request_id=request.state.request_id,
),
)
@@ -88,7 +125,7 @@ def create_app() -> FastAPI:
code,
message,
details,
request_id=request.headers.get("x-request-id"),
request_id=request.state.request_id,
),
)
@@ -100,19 +137,25 @@ def create_app() -> FastAPI:
"VALIDATION_ERROR",
"Validation failed",
exc.errors(),
request_id=request.headers.get("x-request-id"),
request_id=request.state.request_id,
),
)
@app.exception_handler(Exception)
async def unexpected_error(request: Request, exc: Exception): # noqa: ARG001
async def unexpected_error(request: Request, exc: Exception):
logger.exception(
"Unhandled request error request_id=%s method=%s path=%s",
request.state.request_id,
request.method,
request.url.path,
)
return JSONResponse(
status_code=500,
content=_to_error_payload(
"INTERNAL_ERROR",
"Unexpected server error",
{"type": exc.__class__.__name__},
request_id=request.headers.get("x-request-id"),
request_id=request.state.request_id,
),
)