from __future__ import annotations import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from app.api.routes import router as api_router from app.core.config import get_settings from app.engine.detector import PlagiarismDetector from app.jobs.store import JobStore logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s") @asynccontextmanager async def lifespan(app: FastAPI): settings = get_settings() app.state.settings = settings app.state.detector = PlagiarismDetector(settings=settings) app.state.job_store = JobStore() import threading app.state.detector_lock = threading.Lock() logging.info( "Engine ready: version=%s, corpus_size=%d", settings.engine_version, app.state.detector.corpus_size, ) yield def rebuild_detector(app: FastAPI) -> int: """코퍼스 변경 후 인덱스 재빌드. 호출 시점에 lock으로 보호.""" settings = app.state.settings with app.state.detector_lock: app.state.detector = PlagiarismDetector(settings=settings) return app.state.detector.corpus_size app = FastAPI( title="O2O 저작권 침해 여부 탐지 API", description=( "오투오 1단계 산출물 - 콘텐츠 표절 여부 AI 탐지 모듈. " "본 응답 스키마는 커뮤니케이션북스(아카이빙) 및 바이칼AI(분석 보고서) 통합 기준." ), version="1.0.0", lifespan=lifespan, ) app.include_router(api_router) _STATIC_DIR = Path(__file__).resolve().parent / "static" if _STATIC_DIR.exists(): app.mount("/static", StaticFiles(directory=str(_STATIC_DIR)), name="static") @app.get("/", include_in_schema=False) async def root() -> FileResponse: """검토 콘솔 페이지 — 컴북스 측이 브라우저에서 직접 엔진 성능 확인.""" index = _STATIC_DIR / "index.html" if index.exists(): return FileResponse(str(index)) from fastapi.responses import JSONResponse return JSONResponse({"service": "o2o-plagiarism-api", "docs": "/docs"})