Download src/api/main.py from kr4phy/SchoolSustainabilityPrediction: direct link, hf CLI and curl.
- Browser
- Download file 6.01 kB
-
https://huggingface.co/spaces/kr4phy/SchoolSustainabilityPrediction/resolve/main/src/api/main.py
- Command line
-
hf download hf://spaces/kr4phy/SchoolSustainabilityPrediction/src/api/main.py
-
curl -L -o main.py https://huggingface.co/spaces/kr4phy/SchoolSustainabilityPrediction/resolve/main/src/api/main.py
6.01 kB
| """ | |
| FastAPI ์ ํ๋ฆฌ์ผ์ด์ ์ง์ ์ . | |
| ์คํ ๋ฐฉ๋ฒ: | |
| uvicorn src.api.main:app --host 0.0.0.0 --port 8000 --reload | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import os as _os | |
| import time | |
| import uuid | |
| from contextlib import asynccontextmanager | |
| from typing import AsyncGenerator, Callable | |
| import structlog | |
| from fastapi import FastAPI, Request, Response | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import JSONResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from src.api.routes import demo, diagnostics, predictions, regions, schools | |
| from src.config import get_settings | |
| # โโ ๋ก๊น ์ค์ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _configure_logging(log_level: str) -> None: | |
| """structlog ์ JSON ํฌ๋งท์ผ๋ก ์ค์ ํฉ๋๋ค.""" | |
| structlog.configure( | |
| processors=[ | |
| structlog.contextvars.merge_contextvars, | |
| structlog.processors.add_log_level, | |
| structlog.processors.TimeStamper(fmt="iso"), | |
| structlog.processors.JSONRenderer(), | |
| ], | |
| wrapper_class=structlog.make_filtering_bound_logger( | |
| logging.getLevelName(log_level) | |
| ), | |
| logger_factory=structlog.PrintLoggerFactory(), | |
| ) | |
| # ํ์ค logging ๋ ๋์ผ ๋ ๋ฒจ๋ก ์ค์ | |
| logging.basicConfig(level=log_level) | |
| # โโ ์ ํ๋ฆฌ์ผ์ด์ ์๋ช ์ฃผ๊ธฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: | |
| cfg = get_settings() | |
| _configure_logging(cfg.log_level) | |
| logger = structlog.get_logger() | |
| logger.info( | |
| "์๋ฒ ์์", | |
| env=cfg.app_env, | |
| port=cfg.api_port, | |
| granite_model=cfg.granite_model_id, | |
| ) | |
| yield | |
| logger.info("์๋ฒ ์ข ๋ฃ") | |
| # โโ FastAPI ์ฑ ์์ฑ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| cfg = get_settings() | |
| app = FastAPI( | |
| title="AI ๊ธฐ๋ฐ ํ๊ต ์ง์ ๊ฐ๋ฅ์ฑ ์ง๋จ ์์คํ ", | |
| description=( | |
| "๊ณต๊ณต๋ฐ์ดํฐ์ IBM Granite Time Series ๋ชจ๋ธ์ ๊ฒฐํฉํ์ฌ " | |
| "ํ๊ต์ ์ง์ ๊ฐ๋ฅ์ฑ์ ์ง๋จํ๊ณ ์์ธกํ๋ API ์๋ฒ์ ๋๋ค.\n\n" | |
| "์ฃผ์ ๊ธฐ๋ฅ:\n" | |
| "- ํ๊ต๊ธฐ๋ณธ์ ๋ณด ์กฐํ (NEIS Open API ์ฐ๋)\n" | |
| "- ํ์ ์ ์๊ณ์ด ์์ธก (Granite TTM / AutoARIMA fallback)\n" | |
| "- ์ข ํฉ ์ง๋จ (SHAP XAI + ์ง์ ๊ฐ๋ฅ์ฑ ์ ์ + ์ ์ฑ ์๋ฎฌ๋ ์ด์ )\n" | |
| "- ์ง์ญ ์ํ์ง๋ ๋ฐ์ดํฐ ์ ๊ณต\n" | |
| ), | |
| version="1.0.0", | |
| docs_url="/docs", | |
| redoc_url="/redoc", | |
| lifespan=lifespan, | |
| ) | |
| # โโ CORS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"] if cfg.app_env == "development" else [], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # โโ ์์ฒญ ID / ์ฒ๋ฆฌ ์๊ฐ ๋ฏธ๋ค์จ์ด โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def request_id_middleware(request: Request, call_next: Callable) -> Response: | |
| request_id = str(uuid.uuid4()) | |
| structlog.contextvars.clear_contextvars() | |
| structlog.contextvars.bind_contextvars(request_id=request_id) | |
| start = time.perf_counter() | |
| response: Response = await call_next(request) | |
| elapsed_ms = round((time.perf_counter() - start) * 1000, 1) | |
| response.headers["X-Request-ID"] = request_id | |
| response.headers["X-Response-Time-Ms"] = str(elapsed_ms) | |
| return response | |
| # โโ ์ ์ญ ์์ธ ํธ๋ค๋ฌ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse: | |
| logger = structlog.get_logger() | |
| logger.error("์ฒ๋ฆฌ๋์ง ์์ ์์ธ", exc_info=exc, path=str(request.url)) | |
| return JSONResponse( | |
| status_code=500, | |
| content={ | |
| "code": "INTERNAL_SERVER_ERROR", | |
| "message": "์๋ฒ ๋ด๋ถ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ธ์.", | |
| "detail": str(exc), | |
| }, | |
| ) | |
| # โโ ๋ผ์ฐํฐ ๋ฑ๋ก โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| app.include_router(schools.router) | |
| app.include_router(predictions.router) | |
| app.include_router(diagnostics.router) | |
| app.include_router(regions.router) | |
| app.include_router(demo.router) | |
| # โโ ํฌ์ค ์ฒดํฌ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| async def health_check() -> dict: | |
| """์๋ฒ ์ํ ๋ฐ ์ค์ ์ ๋ณด๋ฅผ ๋ฐํํฉ๋๋ค.""" | |
| return { | |
| "status": "ok", | |
| "env": cfg.app_env, | |
| "granite_model": cfg.granite_model_id, | |
| } | |
| # โโ ํ๋ก ํธ์๋ ์ ์ ํ์ผ ์๋น โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| _frontend_dir = _os.path.join(_os.path.dirname(__file__), "..", "..", "frontend") | |
| if _os.path.isdir(_frontend_dir): | |
| app.mount("/", StaticFiles(directory=_frontend_dir, html=True), name="frontend") | |