- GET /api/symbols/search?q=...&seed_only= : trigram + prefix + ILIKE 합산 정렬
- GET /api/symbols/{code} : 메타
- GET /api/chart/{code}?days=N&include_* : OHLCV + 일별 감성 + 외인기관거래대금
- POST /api/predict/{code}?horizons=1,3,5 : on-demand 앙상블 예측 + DB 적재
(user_triggered=TRUE)
- GET /api/predict/{code}/latest : 최신 base_date 의 예측 묶음 + base_close
(UI 가 차트 마지막 점에 이어 붙임)
- GET /api/metrics/{code}?window_days=N : 종목 단위 hit_rate / mae (model, horizon 별)
- GET /api/metrics?window_days=N : 전체 누적
- GET /api/news/{code}?source=&limit= : 최신순 뉴스/공시 목록 (감성 점수 포함)
main.py 에 6개 라우터 모두 include.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from app.api.chart import router as chart_router
|
|
from app.api.metrics import router as metrics_router
|
|
from app.api.news import router as news_router
|
|
from app.api.predict import router as predict_router
|
|
from app.api.refresh import router as refresh_router
|
|
from app.api.symbols import router as symbols_router
|
|
from app.config import settings
|
|
from app.db.connection import ping as db_ping
|
|
from app.fetch import dart as dart_mod
|
|
from app.fetch import kis as kis_mod
|
|
from app.pipelines.scheduler import shutdown_scheduler, start_scheduler
|
|
|
|
logging.basicConfig(
|
|
level=settings.log_level,
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI):
|
|
# 스케줄러는 옵션. CI/테스트에서 disable 하고 싶으면 SCHEDULER_DISABLED 같은 env 추가 가능.
|
|
try:
|
|
start_scheduler()
|
|
except Exception: # noqa: BLE001
|
|
logger.exception("scheduler start failed")
|
|
yield
|
|
shutdown_scheduler()
|
|
|
|
|
|
app = FastAPI(title="stock_chart_site", version="0.1.0", lifespan=lifespan)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(refresh_router)
|
|
app.include_router(symbols_router)
|
|
app.include_router(chart_router)
|
|
app.include_router(predict_router)
|
|
app.include_router(metrics_router)
|
|
app.include_router(news_router)
|
|
|
|
|
|
def _resolved_device() -> str:
|
|
if settings.model_device != "auto":
|
|
return settings.model_device
|
|
try:
|
|
import torch # noqa: WPS433
|
|
return "cuda" if torch.cuda.is_available() else "cpu"
|
|
except Exception: # noqa: BLE001
|
|
return "cpu"
|
|
|
|
|
|
@app.get("/health")
|
|
def health() -> dict[str, object]:
|
|
return {"ok": True, "device": _resolved_device(), "version": "0.1.0"}
|
|
|
|
|
|
@app.get("/health/db")
|
|
def health_db() -> dict[str, object]:
|
|
return {"ok": True, **db_ping()}
|
|
|
|
|
|
@app.get("/health/keys")
|
|
def health_keys() -> dict[str, object]:
|
|
"""등록된 외부 키들 ping (key 값은 노출하지 않음)."""
|
|
return {
|
|
"kis": kis_mod.ping(),
|
|
"dart": dart_mod.ping(),
|
|
# huggingface 는 모델 다운로드 시점에 확인 (별도 ping 호출 비용 회피)
|
|
}
|