From af2fb577608b08a6fb126c1baaf9dc3e9e8ab381 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 02:14:48 +0900 Subject: [PATCH] =?UTF-8?q?feat(insta-lab):=20=EB=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=BB=AC=EB=9F=BC=20+=20set=5Fslate=5Fdec?= =?UTF-8?q?ision/list=5Frecent=5Fissued=5Ftopics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- insta-lab/app/db.py | 40 ++++++++++++++++++++++++ insta-lab/tests/test_db_decision.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 insta-lab/tests/test_db_decision.py diff --git a/insta-lab/app/db.py b/insta-lab/app/db.py index a85fd0c..0d4f4dd 100644 --- a/insta-lab/app/db.py +++ b/insta-lab/app/db.py @@ -124,6 +124,13 @@ def init_db() -> None: (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 ──────────────────────────────────────────────── 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]]: with _conn() as conn: row = conn.execute("SELECT * FROM card_slates WHERE id=?", (slate_id,)).fetchone() diff --git a/insta-lab/tests/test_db_decision.py b/insta-lab/tests/test_db_decision.py new file mode 100644 index 0000000..3da4304 --- /dev/null +++ b/insta-lab/tests/test_db_decision.py @@ -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