""" IRT Bank Soal - Adaptive Question Bank System Main FastAPI application entry point. Features: - CTT (Classical Test Theory) scoring with exact Excel formulas - IRT (Item Response Theory) support for adaptive testing - Multi-website support for WordPress integration - AI-powered question generation """ from contextlib import asynccontextmanager from typing import AsyncGenerator from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.admin import admin as admin_app from app.core.config import get_settings from app.database import close_db, init_db from app.routers import ( admin_router, ai_router, import_export_router, reports_router, sessions_router, tryouts_router, wordpress_router, ) settings = get_settings() @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: """ Application lifespan manager. Handles startup and shutdown events. """ # Startup: Initialize database await init_db() yield # Shutdown: Close database connections await close_db() # Initialize FastAPI application app = FastAPI( title="IRT Bank Soal", description=""" ## Adaptive Question Bank System with IRT/CTT Scoring This API provides a comprehensive backend for adaptive assessment systems. ### Features - **CTT Scoring**: Classical Test Theory with exact Excel formula compatibility - **IRT Support**: Item Response Theory for adaptive testing (1PL Rasch model) - **Multi-Site**: Single backend serving multiple WordPress sites - **AI Generation**: Automatic question variant generation ### Scoring Formulas (PRD Section 13.1) - **CTT p-value**: `p = Σ Benar / Total Peserta` - **CTT Bobot**: `Bobot = 1 - p` - **CTT NM**: `NM = (Total_Bobot_Siswa / Total_Bobot_Max) × 1000` - **CTT NN**: `NN = 500 + 100 × ((NM - Rataan) / SB)` ### Authentication Most endpoints require `X-Website-ID` header for multi-site isolation. """, version="1.0.0", docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", lifespan=lifespan, ) # Configure CORS middleware # Parse ALLOWED_ORIGINS from settings (comma-separated string) allowed_origins = settings.ALLOWED_ORIGINS if isinstance(allowed_origins, str): allowed_origins = [origin.strip() for origin in allowed_origins.split(",") if origin.strip()] app.add_middleware( CORSMiddleware, allow_origins=allowed_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Health check endpoint @app.get( "/", summary="Health check", description="Returns API status and version information.", tags=["health"], ) async def root(): """ Health check endpoint. Returns basic API information for monitoring and load balancer checks. """ return { "status": "healthy", "service": "IRT Bank Soal", "version": "1.0.0", "docs": "/docs", } @app.get( "/health", summary="Detailed health check", description="Returns detailed health status including database connectivity.", tags=["health"], ) async def health_check(): """ Detailed health check endpoint. Includes database connectivity verification. """ from app.database import engine from sqlalchemy import text db_status = "unknown" try: async with engine.connect() as conn: await conn.execute(text("SELECT 1")) db_status = "connected" except Exception as e: db_status = f"error: {str(e)}" return { "status": "healthy" if db_status == "connected" else "degraded", "service": "IRT Bank Soal", "version": "1.0.0", "database": db_status, "environment": settings.ENVIRONMENT, } # Include API routers with version prefix app.include_router( import_export_router, ) app.include_router( sessions_router, prefix=f"{settings.API_V1_STR}", ) app.include_router( tryouts_router, prefix=f"{settings.API_V1_STR}", ) app.include_router( wordpress_router, prefix=f"{settings.API_V1_STR}", ) app.include_router( ai_router, prefix=f"{settings.API_V1_STR}", ) app.include_router( reports_router, prefix=f"{settings.API_V1_STR}", ) # Mount FastAPI Admin panel app.mount("/admin", admin_app) # Include admin API router for custom actions app.include_router( admin_router, prefix=f"{settings.API_V1_STR}", ) # Placeholder routers for future implementation # These will be implemented in subsequent phases # app.include_router( # items_router, # prefix=f"{settings.API_V1_STR}", # tags=["items"], # ) if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host="0.0.0.0", port=8000, reload=settings.ENVIRONMENT == "development", )