- 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>
59 lines
1.7 KiB
Python
59 lines
1.7 KiB
Python
"""뉴스/공시 목록 API."""
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from sqlalchemy import text
|
|
|
|
from app.db.connection import get_engine
|
|
|
|
router = APIRouter(prefix="/api/news", tags=["news"])
|
|
|
|
|
|
@router.get("/{code}")
|
|
def list_news(
|
|
code: str,
|
|
limit: int = Query(default=20, ge=1, le=200),
|
|
source: str | None = Query(default=None, description="naver_finance / google_rss / dart"),
|
|
) -> dict:
|
|
eng = get_engine()
|
|
with eng.connect() as conn:
|
|
sym = conn.execute(
|
|
text("SELECT code, name FROM symbols WHERE code = :c"),
|
|
{"c": code},
|
|
).first()
|
|
if not sym:
|
|
raise HTTPException(status_code=404, detail=f"unknown code: {code}")
|
|
where = "code = :c"
|
|
params: dict = {"c": code, "lim": limit}
|
|
if source:
|
|
where += " AND source = :src"
|
|
params["src"] = source
|
|
rows = conn.execute(
|
|
text(
|
|
f"""
|
|
SELECT source, published_at, title, url, sentiment_score, sentiment_label
|
|
FROM news
|
|
WHERE {where}
|
|
ORDER BY published_at DESC
|
|
LIMIT :lim
|
|
"""
|
|
),
|
|
params,
|
|
).all()
|
|
return {
|
|
"code": sym[0],
|
|
"name": sym[1],
|
|
"count": len(rows),
|
|
"items": [
|
|
{
|
|
"source": r[0],
|
|
"published_at": r[1].isoformat() if r[1] else None,
|
|
"title": r[2],
|
|
"url": r[3],
|
|
"sentiment_score": float(r[4]) if r[4] is not None else None,
|
|
"sentiment_label": r[5],
|
|
}
|
|
for r in rows
|
|
],
|
|
}
|