feat(insta-lab): 발행 상태 컬럼 + set_slate_decision/list_recent_issued_topics
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,13 @@ def init_db() -> None:
|
|||||||
(cat, 1.0),
|
(cat, 1.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 발행 상태 컬럼 (idempotent ALTER) — 자율 발급 파이프라인
|
||||||
|
cs_cols = [r[1] for r in conn.execute("PRAGMA table_info(card_slates)").fetchall()]
|
||||||
|
if "published_at" not in cs_cols:
|
||||||
|
conn.execute("ALTER TABLE card_slates ADD COLUMN published_at TEXT")
|
||||||
|
if "decision_at" not in cs_cols:
|
||||||
|
conn.execute("ALTER TABLE card_slates ADD COLUMN decision_at TEXT")
|
||||||
|
|
||||||
|
|
||||||
# ── news_articles ────────────────────────────────────────────────
|
# ── news_articles ────────────────────────────────────────────────
|
||||||
def add_news_article(row: Dict[str, Any]) -> int:
|
def add_news_article(row: Dict[str, Any]) -> int:
|
||||||
@@ -217,6 +224,39 @@ def update_slate_status(slate_id: int, status: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_slate_decision(slate_id: int, decision: str) -> None:
|
||||||
|
"""승인/반려 결정 기록. approved→published(+published_at), rejected→rejected.
|
||||||
|
멱등: 이미 published면 published_at 유지."""
|
||||||
|
now = "strftime('%Y-%m-%dT%H:%M:%fZ','now')"
|
||||||
|
with _conn() as conn:
|
||||||
|
if decision == "approved":
|
||||||
|
conn.execute(
|
||||||
|
f"UPDATE card_slates SET status='published', "
|
||||||
|
f"published_at=COALESCE(published_at, {now}), decision_at={now} "
|
||||||
|
f"WHERE id=?",
|
||||||
|
(slate_id,),
|
||||||
|
)
|
||||||
|
elif decision == "rejected":
|
||||||
|
conn.execute(
|
||||||
|
f"UPDATE card_slates SET status='rejected', decision_at={now} WHERE id=?",
|
||||||
|
(slate_id,),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"invalid decision: {decision}")
|
||||||
|
|
||||||
|
|
||||||
|
def list_recent_issued_topics(window_days: int = 14) -> List[Dict[str, Any]]:
|
||||||
|
"""최근 window_days 내 published/rejected 슬레이트의 (keyword, category). dedup용."""
|
||||||
|
with _conn() as conn:
|
||||||
|
rows = conn.execute(
|
||||||
|
"SELECT keyword, category FROM card_slates "
|
||||||
|
"WHERE status IN ('published','rejected') "
|
||||||
|
"AND COALESCE(published_at, decision_at) >= datetime('now', ?)",
|
||||||
|
(f"-{int(window_days)} days",),
|
||||||
|
).fetchall()
|
||||||
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
def get_card_slate(slate_id: int) -> Optional[Dict[str, Any]]:
|
def get_card_slate(slate_id: int) -> Optional[Dict[str, Any]]:
|
||||||
with _conn() as conn:
|
with _conn() as conn:
|
||||||
row = conn.execute("SELECT * FROM card_slates WHERE id=?", (slate_id,)).fetchone()
|
row = conn.execute("SELECT * FROM card_slates WHERE id=?", (slate_id,)).fetchone()
|
||||||
|
|||||||
47
insta-lab/tests/test_db_decision.py
Normal file
47
insta-lab/tests/test_db_decision.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
from app import db, config
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fresh_db(tmp_path, monkeypatch):
|
||||||
|
monkeypatch.setattr(config, "DB_PATH", str(tmp_path / "insta.db"))
|
||||||
|
monkeypatch.setattr(db, "DB_PATH", str(tmp_path / "insta.db"))
|
||||||
|
db.init_db()
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_slate_decision_approved_publishes(fresh_db):
|
||||||
|
sid = db.add_card_slate({"keyword": "금리", "category": "economy"})
|
||||||
|
db.set_slate_decision(sid, "approved")
|
||||||
|
s = db.get_card_slate(sid)
|
||||||
|
assert s["status"] == "published"
|
||||||
|
assert s["published_at"] is not None
|
||||||
|
assert s["decision_at"] is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_slate_decision_rejected(fresh_db):
|
||||||
|
sid = db.add_card_slate({"keyword": "환율", "category": "economy"})
|
||||||
|
db.set_slate_decision(sid, "rejected")
|
||||||
|
s = db.get_card_slate(sid)
|
||||||
|
assert s["status"] == "rejected"
|
||||||
|
assert s["decision_at"] is not None
|
||||||
|
assert s["published_at"] is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_slate_decision_idempotent(fresh_db):
|
||||||
|
sid = db.add_card_slate({"keyword": "주식", "category": "economy"})
|
||||||
|
db.set_slate_decision(sid, "approved")
|
||||||
|
first = db.get_card_slate(sid)["published_at"]
|
||||||
|
db.set_slate_decision(sid, "approved")
|
||||||
|
assert db.get_card_slate(sid)["published_at"] == first
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_recent_issued_topics(fresh_db):
|
||||||
|
a = db.add_card_slate({"keyword": "금리", "category": "economy"})
|
||||||
|
b = db.add_card_slate({"keyword": "우울증", "category": "psychology"})
|
||||||
|
db.set_slate_decision(a, "approved")
|
||||||
|
db.set_slate_decision(b, "rejected")
|
||||||
|
topics = db.list_recent_issued_topics(window_days=14)
|
||||||
|
pairs = {(t["keyword"], t["category"]) for t in topics}
|
||||||
|
assert ("금리", "economy") in pairs
|
||||||
|
assert ("우울증", "psychology") in pairs
|
||||||
Reference in New Issue
Block a user