feat/insta-trends #4

Merged
gahusb merged 8 commits from feat/insta-trends into main 2026-05-17 08:52:50 +09:00
2 changed files with 140 additions and 2 deletions
Showing only changes of commit 41225b3337 - Show all commits

View File

@@ -15,7 +15,7 @@ from .config import (
CORS_ALLOW_ORIGINS, NAVER_CLIENT_ID, ANTHROPIC_API_KEY,
INSTA_DATA_PATH, DB_PATH, DEFAULT_CATEGORY_SEEDS, KEYWORDS_PER_CATEGORY,
)
from . import db, news_collector, keyword_extractor, card_writer, card_renderer
from . import db, news_collector, keyword_extractor, card_writer, card_renderer, trend_collector
logger = logging.getLogger(__name__)
app = FastAPI()
@@ -119,7 +119,13 @@ def extract_keywords(req: ExtractRequest, bg: BackgroundTasks):
@app.get("/api/insta/keywords")
def list_keywords(category: Optional[str] = None, used: Optional[bool] = None):
def list_keywords(
category: Optional[str] = None,
used: Optional[bool] = None,
source: Optional[str] = None,
):
if source:
return {"items": db.list_trends(source=source, category=category, days=30)}
return {"items": db.list_trending_keywords(category=category, used=used)}
@@ -243,3 +249,52 @@ def get_prompt(name: str):
def upsert_prompt(name: str, body: TemplateBody):
db.upsert_prompt_template(name, body.template, body.description)
return db.get_prompt_template(name)
# ── Trends ───────────────────────────────────────────────────────
class TrendsCollectRequest(BaseModel):
categories: Optional[list[str]] = None
async def _bg_collect_trends(task_id: str, categories: list[str]):
try:
db.update_task(task_id, "processing", 10, "외부 트렌드 수집 중")
result = trend_collector.collect_all(categories)
msg = f"naver:{result['naver_popular']}, google:{result['google_trends']}"
db.update_task(task_id, "succeeded", 100, msg, result_id=sum(result.values()))
except Exception as e:
logger.exception("trends collect failed")
db.update_task(task_id, "failed", 0, "", error=str(e))
@app.post("/api/insta/trends/collect")
def collect_trends(req: TrendsCollectRequest, bg: BackgroundTasks):
cats = req.categories or list(DEFAULT_CATEGORY_SEEDS.keys())
tid = db.create_task("trends_collect", {"categories": cats})
bg.add_task(_bg_collect_trends, tid, cats)
return {"task_id": tid, "categories": cats}
@app.get("/api/insta/trends")
def list_trends_endpoint(
source: Optional[str] = None,
category: Optional[str] = None,
days: int = Query(1, ge=1, le=90),
):
return {"items": db.list_trends(source=source, category=category, days=days)}
# ── Preferences ──────────────────────────────────────────────────
class PreferencesBody(BaseModel):
categories: dict[str, float]
@app.get("/api/insta/preferences")
def get_preferences_endpoint():
return {"categories": db.get_preferences()}
@app.put("/api/insta/preferences")
def put_preferences_endpoint(body: PreferencesBody):
db.upsert_preferences(body.categories)
return {"categories": db.get_preferences()}

View File

@@ -0,0 +1,83 @@
import os
import gc
import tempfile
import pytest
from fastapi.testclient import TestClient
from app import db as db_module
@pytest.fixture
def client(monkeypatch):
fd, path = tempfile.mkstemp(suffix=".db")
os.close(fd)
monkeypatch.setattr(db_module, "DB_PATH", path)
db_module.init_db()
from app import main
monkeypatch.setattr(main, "DB_PATH", path)
with TestClient(main.app) as c:
yield c
gc.collect()
for ext in ("", "-wal", "-shm"):
try:
os.remove(path + ext)
except OSError:
pass
def test_get_preferences_returns_defaults(client):
resp = client.get("/api/insta/preferences")
assert resp.status_code == 200
cats = {p["category"]: p["weight"] for p in resp.json()["categories"]}
assert cats == {"economy": 1.0, "psychology": 1.0, "celebrity": 1.0}
def test_put_preferences_upsert(client):
resp = client.put("/api/insta/preferences",
json={"categories": {"economy": 0.7, "psychology": 0.2, "tech": 0.5}})
assert resp.status_code == 200
cats = {p["category"]: p["weight"] for p in resp.json()["categories"]}
assert cats["economy"] == 0.7
assert cats["tech"] == 0.5
def test_list_trends_filter(client):
db_module.add_external_trend({"keyword": "A", "category": "economy",
"source": "naver_popular", "score": 1.0})
db_module.add_external_trend({"keyword": "B", "category": "celebrity",
"source": "google_trends", "score": 0.8})
resp = client.get("/api/insta/trends?source=naver_popular")
items = resp.json()["items"]
assert {it["keyword"] for it in items} == {"A"}
def test_collect_trends_kicks_background(client, monkeypatch):
from app import main, trend_collector
captured = {"called": False}
def fake_collect_all(cats):
captured["called"] = True
return {"naver_popular": 3, "google_trends": 2}
monkeypatch.setattr(trend_collector, "collect_all", fake_collect_all)
resp = client.post("/api/insta/trends/collect", json={})
assert resp.status_code == 200
task_id = resp.json()["task_id"]
for _ in range(20):
st = client.get(f"/api/insta/tasks/{task_id}").json()
if st["status"] in ("succeeded", "failed"):
break
assert st["status"] == "succeeded"
assert captured["called"] is True
def test_list_keywords_filters_by_source(client):
db_module.add_trending_keyword({"keyword": "M", "category": "economy",
"score": 0.4, "articles_count": 1, "source": "manual"})
db_module.add_external_trend({"keyword": "N", "category": "economy",
"source": "naver_popular", "score": 0.9})
resp = client.get("/api/insta/keywords?source=manual")
items = resp.json()["items"]
assert {it["keyword"] for it in items} == {"M"}