from __future__ import annotations import logging from contextlib import asynccontextmanager import structlog import uvicorn from fastapi import FastAPI from app.config import load_settings from app.consumer import FraudConsumer from app.grpc_server import serve_grpc, stop_grpc from app.nats_bus import NatsBus from app.scoring import HeuristicScorer logger = structlog.get_logger() def _configure_logging(env: str) -> None: level = logging.DEBUG if env == "development" else logging.INFO logging.basicConfig(level=level, format="%(asctime)s %(levelname)s %(name)s %(message)s") @asynccontextmanager async def lifespan(app: FastAPI): settings = load_settings() _configure_logging(settings.env) bus = NatsBus(settings.nats_url, settings.stream_name) await bus.connect() scorer = HeuristicScorer() consumer = FraudConsumer(bus, settings.consumer_durable, scorer) await consumer.start() grpc_server = await serve_grpc(scorer, settings.grpc_addr) app.state.bus = bus app.state.consumer = consumer app.state.scorer = scorer app.state.grpc = grpc_server app.state.settings = settings try: yield finally: await stop_grpc(grpc_server) await consumer.stop() await bus.close() app = FastAPI(title="GuestGuard Fraud Engine", lifespan=lifespan) @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok"} @app.get("/health/ready") async def ready() -> dict[str, str]: bus = getattr(app.state, "bus", None) if bus is None: return {"status": "starting"} return {"status": "ok", "nats": "up"} def serve() -> None: settings = load_settings() uvicorn.run( "app.main:app", host=settings.host, port=settings.port, log_level="info", access_log=True, ) if __name__ == "__main__": serve()