From 7d7064ae93496b31e190154a693873b57caa01fa Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 31 May 2026 22:12:28 +0900 Subject: [PATCH] feat(stock): holdings intel API (intel/history/run) Co-Authored-By: Claude Opus 4.8 (1M context) --- stock/app/main.py | 23 ++++++++++++++++++- stock/app/test_holdings_api.py | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 stock/app/test_holdings_api.py diff --git a/stock/app/main.py b/stock/app/main.py index 8f32835..58a7d95 100644 --- a/stock/app/main.py +++ b/stock/app/main.py @@ -3,7 +3,7 @@ import json import logging from datetime import date as date_type from typing import Optional -from fastapi import FastAPI, Query, Header, Depends, HTTPException +from fastapi import FastAPI, Query, Header, Depends, HTTPException, BackgroundTasks from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware import requests @@ -27,6 +27,7 @@ from .price_fetcher import get_current_prices, get_current_prices_detail from .ai_summarizer import summarize_news, OllamaError from .auth import verify_webai_key from . import webai_cache +from . import holdings_intel app = FastAPI() install_access_log(app) @@ -652,5 +653,25 @@ def remove_sell_history(record_id: int): return {"ok": True} +# --- Holdings Intelligence API --- + +@app.get("/api/stock/holdings/intel") +def holdings_intel_brief(): + """보유종목 인텔리전스 브리핑 (최신 시그널 + 포트 건강)""" + return holdings_intel.build_holdings_brief() + + +@app.get("/api/stock/holdings/intel/history") +def holdings_intel_history(ticker: str, days: int = 30): + """종목별 시그널 이력 조회""" + from . import db + return {"ticker": ticker, "history": db.get_holdings_signal_history(ticker, days)} + + +@app.post("/api/stock/holdings/intel/run") +def holdings_intel_run(background_tasks: BackgroundTasks, use_llm: bool = True): + """보유종목 시그널 계산 트리거 (BackgroundTask)""" + background_tasks.add_task(holdings_intel.compute_and_store, None, use_llm) + return {"ok": True, "queued": True} diff --git a/stock/app/test_holdings_api.py b/stock/app/test_holdings_api.py new file mode 100644 index 0000000..d812364 --- /dev/null +++ b/stock/app/test_holdings_api.py @@ -0,0 +1,42 @@ +import os +import sys +import tempfile + +from fastapi.testclient import TestClient + + +def _client(monkeypatch): + # Add web-backend root to sys.path so _shared can be imported by main.py + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + from app import db + monkeypatch.setattr(db, "DB_PATH", os.path.join(tempfile.mkdtemp(), "stock.db")) + db.init_db() + from app.main import app + return TestClient(app) + + +def test_holdings_intel_endpoint(monkeypatch): + client = _client(monkeypatch) + r = client.get("/api/stock/holdings/intel") + assert r.status_code == 200 + body = r.json() + assert "holdings" in body and "portfolio_health" in body + + +def test_holdings_intel_history_endpoint(monkeypatch): + client = _client(monkeypatch) + r = client.get("/api/stock/holdings/intel/history?ticker=005930") + assert r.status_code == 200 + body = r.json() + assert body["ticker"] == "005930" + assert "history" in body + assert isinstance(body["history"], list) + + +def test_holdings_intel_run_endpoint(monkeypatch): + client = _client(monkeypatch) + r = client.post("/api/stock/holdings/intel/run") + assert r.status_code == 200 + body = r.json() + assert body["ok"] is True + assert body["queued"] is True