""" Klausur-Service - Abitur/Vorabitur Klausurkorrektur Microservice Eigenstaendiger Service fuer: - Klausurverwaltung (Abitur/Vorabitur) - OCR-Verarbeitung handschriftlicher Arbeiten - KI-gestuetzte Bewertung - Gutachten-Generierung - 15-Punkte-Notensystem - BYOEH (Bring-Your-Own-Expectation-Horizon) This is the main entry point. All functionality is organized in modular packages: - models/: Data models and Pydantic schemas - routes/: API endpoint handlers - services/: Business logic - storage.py: In-memory data storage - config.py: Configuration constants """ import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse # Configuration from config import EH_UPLOAD_DIR, FRONTEND_PATH # Routes from routes import api_router # External module routers (already modular) from admin_api import router as admin_router from zeugnis_api import router as zeugnis_router from training_api import router as training_router from mail.api import router as mail_router from trocr_api import router as trocr_router # BYOEH Qdrant initialization from qdrant_service import init_qdrant_collection # ============================================= # APP SETUP # ============================================= @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan manager for startup and shutdown events.""" print("Klausur-Service starting...") # Initialize Qdrant collection for BYOEH try: await init_qdrant_collection() print("Qdrant BYOEH collection initialized") except Exception as e: print(f"Warning: Qdrant initialization failed: {e}") # Ensure EH upload directory exists os.makedirs(EH_UPLOAD_DIR, exist_ok=True) yield print("Klausur-Service shutting down...") app = FastAPI( title="Klausur-Service", description="Abitur/Vorabitur Klausurkorrektur Microservice", version="1.0.0", lifespan=lifespan ) # CORS Middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ============================================= # INCLUDE ROUTERS # ============================================= # Main API routes (modular) app.include_router(api_router) # External module routers app.include_router(admin_router) # NiBiS Ingestion app.include_router(zeugnis_router) # Zeugnis Rights-Aware Crawler app.include_router(training_router) # Training Management app.include_router(mail_router) # Unified Inbox Mail app.include_router(trocr_router) # TrOCR Handwriting OCR # ============================================= # HEALTH CHECK # ============================================= @app.get("/health") async def health(): """Health check endpoint.""" return {"status": "healthy", "service": "klausur-service"} # ============================================= # SERVE FRONTEND # ============================================= if os.path.exists(FRONTEND_PATH): app.mount("/assets", StaticFiles(directory=f"{FRONTEND_PATH}/assets"), name="assets") @app.get("/") async def serve_frontend(): """Serve the React frontend.""" return FileResponse(f"{FRONTEND_PATH}/index.html") @app.get("/{path:path}") async def serve_frontend_routes(path: str): """Serve index.html for all non-API routes (SPA routing).""" if not path.startswith("api/") and not path.startswith("health"): return FileResponse(f"{FRONTEND_PATH}/index.html") from fastapi import HTTPException raise HTTPException(status_code=404) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8086)