"""Slot-core daemon entrypoint (REQ-005). Builds the FastAPI app wired to PostgreSQL adapters, applies migrations and preallocates the trading day's slots on startup (lifecycle, not a public endpoint), then serves via uvicorn. Console script: `trex-core-daemon`. """ from __future__ import annotations from contextlib import asynccontextmanager from datetime import datetime, timezone from fastapi import FastAPI from trex_core.adapters.api.app import create_app from trex_core.adapters.persistence.migrate import apply_migrations from trex_core.adapters.persistence.in_memory_intent_store import InMemoryIntentStore # noqa: F401 from trex_core.adapters.persistence.postgresql import ( PostgreSQLIntentStore, PostgreSQLSlotRepository, ) from trex_core.application.intents import IntentService from trex_core.domain.slot import SlotService from trex_core.runtime.config import DaemonConfig def build_app(config: DaemonConfig) -> FastAPI: """Construct the daemon app with PostgreSQL adapters + startup lifecycle.""" slot_repo = PostgreSQLSlotRepository(config.dsn) intent_store = PostgreSQLIntentStore(config.dsn) slot_service = SlotService(slot_repo) intent_service = IntentService(slot_service, slot_repo, intent_store) base = create_app( slot_service=slot_service, slot_repo=slot_repo, intent_service=intent_service, default_total_slots=config.total_slots, ) @asynccontextmanager async def lifespan(_: FastAPI): # Startup: migrate schema, preallocate today's slots (idempotent). apply_migrations(config.dsn) today = datetime.now(tz=timezone.utc).date() slot_service.preallocate_daily_slots(today, config.total_slots) yield # Shutdown: connection-per-call adapters hold no pooled state to close. base.router.lifespan_context = lifespan return base def main() -> None: # pragma: no cover - process entrypoint import uvicorn config = DaemonConfig.from_env() app = build_app(config) uvicorn.run(app, host=config.host, port=config.port) if __name__ == "__main__": # pragma: no cover main()