From e1b1944f432313e4674dff772bf39d19c32a816a Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 11 Jun 2026 02:55:46 +0900 Subject: [PATCH] feat(insta): dedup_window_days config end-to-end wiring (spec 6.4) - insta-lab ranked_keywords: add dedup_window_days Query param (default 14, ge=1, le=90); pass to db.list_recent_issued_topics - service_proxy.insta_ranked: add dedup_window_days param (default 14); include in GET params - InstaAgent.on_schedule: read dedup_window_days from custom_config (default 14); pass to insta_ranked call - test_ranked_respects_dedup_window: verifies window param gates eligible flag correctly Co-Authored-By: Claude Opus 4.8 (1M context) --- agent-office/app/agents/insta.py | 3 +- agent-office/app/service_proxy.py | 4 +-- insta-lab/app/main.py | 8 ++++-- insta-lab/tests/test_ranked_decision_api.py | 31 +++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/agent-office/app/agents/insta.py b/agent-office/app/agents/insta.py index 2c308ed..44702a0 100644 --- a/agent-office/app/agents/insta.py +++ b/agent-office/app/agents/insta.py @@ -74,6 +74,7 @@ class InstaAgent(BaseAgent): autonomous = bool(custom.get("autonomous_issue", False)) threshold = float(custom.get("select_threshold", 0.6)) max_per_day = int(custom.get("max_per_day", 2)) + dedup_window_days = int(custom.get("dedup_window_days", 14)) task_id = create_task(self.agent_id, "insta_daily", {"auto_select": auto_select}, requires_approval=False) @@ -86,7 +87,7 @@ class InstaAgent(BaseAgent): add_log(self.agent_id, f"insta preferences unavailable: {_pref_err}", "warning", task_id) await self._run_collect_and_extract() if autonomous: - ranked = await service_proxy.insta_ranked(threshold=threshold, limit=20) + ranked = await service_proxy.insta_ranked(threshold=threshold, limit=20, dedup_window_days=dedup_window_days) eligible = [r for r in ranked if r.get("eligible")][:max_per_day] if not eligible: await messaging.send_raw("๐Ÿ“ฐ [์ธ์Šคํƒ€ ํ๋ ˆ์ดํ„ฐ] ์˜ค๋Š˜์€ ๋ฐœํ–‰ํ•  ๊ฐ€์น˜ ์žˆ๋Š” ์ฃผ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.") diff --git a/agent-office/app/service_proxy.py b/agent-office/app/service_proxy.py index 5727e9f..15af394 100644 --- a/agent-office/app/service_proxy.py +++ b/agent-office/app/service_proxy.py @@ -228,11 +228,11 @@ async def insta_put_preferences(weights: Dict[str, float]) -> Dict[str, Any]: return resp.json() -async def insta_ranked(threshold: float = 0.6, limit: int = 20) -> list: +async def insta_ranked(threshold: float = 0.6, limit: int = 20, dedup_window_days: int = 14) -> list: async with httpx.AsyncClient(timeout=120) as client: r = await client.get( f"{INSTA_LAB_URL}/api/insta/keywords/ranked", - params={"threshold": threshold, "limit": limit}, + params={"threshold": threshold, "limit": limit, "dedup_window_days": dedup_window_days}, ) r.raise_for_status() return r.json()["items"] diff --git a/insta-lab/app/main.py b/insta-lab/app/main.py index 29d885d..52aa1ca 100644 --- a/insta-lab/app/main.py +++ b/insta-lab/app/main.py @@ -154,11 +154,15 @@ def list_keywords( @app.get("/api/insta/keywords/ranked") -def ranked_keywords(limit: int = Query(20, ge=1, le=100), threshold: float = Query(0.6, ge=0.0, le=1.0)): +def ranked_keywords( + limit: int = Query(20, ge=1, le=100), + threshold: float = Query(0.6, ge=0.0, le=1.0), + dedup_window_days: int = Query(14, ge=1, le=90), +): candidates = db.list_trending_keywords(used=False) if not candidates: return {"items": []} - issued = db.list_recent_issued_topics(window_days=14) + issued = db.list_recent_issued_topics(window_days=dedup_window_days) prefs = {p["category"]: p["weight"] for p in db.get_preferences()} claude_scores = selection_judge.judge_candidates(candidates) now_iso = datetime.now(timezone.utc).isoformat() diff --git a/insta-lab/tests/test_ranked_decision_api.py b/insta-lab/tests/test_ranked_decision_api.py index ad1aa34..dc34183 100644 --- a/insta-lab/tests/test_ranked_decision_api.py +++ b/insta-lab/tests/test_ranked_decision_api.py @@ -45,3 +45,34 @@ def test_decision_invalid_400(client): def test_decision_unknown_slate_404(client): r = client.post("/api/insta/slates/99999/decision", json={"decision": "approved"}) assert r.status_code == 404 + + +def test_ranked_respects_dedup_window(client): + """dedup_window_days param์ด list_recent_issued_topics window์— ๋ฐ˜์˜๋˜๋Š”์ง€ ๊ฒ€์ฆ. + + '๊ธˆ๋ฆฌ' ํ‚ค์›Œ๋“œ๋ฅผ ๋ฐฉ๊ธˆ approved(published) ์ƒํƒœ๋กœ ๊ธฐ๋กํ•œ ๋’ค: + - dedup_window_days=30 โ†’ ๋ฐฉ๊ธˆ ๋ฐœํ–‰ = window ์•ˆ โ†’ eligible False + - dedup_window_days=1 โ†’ DB datetime์ด ์ •๊ฐ ๊ฒฝ๊ณ„ ์ง์ „์ด๋ผ๋„ ์—ฌ์ „ํžˆ 1์ผ ์•ˆ์ด๋ฏ€๋กœ eligible False + (ํ™•์ธ: ๋ฐ˜๋“œ์‹œ eligible=False) + ์ถ”๊ฐ€๋กœ ๋‘ ๋ฒˆ์งธ ํ‚ค์›Œ๋“œ(word2)๋Š” ์•„์ง ๋ฐœํ–‰ ์ด๋ ฅ ์—†์œผ๋ฏ€๋กœ window ๋ฌด๊ด€ํ•˜๊ฒŒ eligible True. + """ + # ๋ฐฉ๊ธˆ ๋ฐœํ–‰๋œ ํ‚ค์›Œ๋“œ ๋“ฑ๋ก + ์Šฌ๋ ˆ์ดํŠธ approved ์ฒ˜๋ฆฌ + db.add_trending_keyword({"keyword": "๊ธˆ๋ฆฌ", "category": "economy", "score": 0.9}) + sid = db.add_card_slate({"keyword": "๊ธˆ๋ฆฌ", "category": "economy"}) + db.set_slate_decision(sid, "approved") # published_at = now + + # ๋ฐœํ–‰ ์ด๋ ฅ ์—†๋Š” ํ‚ค์›Œ๋“œ ์ถ”๊ฐ€ + db.add_trending_keyword({"keyword": "ํ™˜์œจ", "category": "economy", "score": 0.8}) + + # window=30 โ†’ '๊ธˆ๋ฆฌ'๋Š” ์ตœ๊ทผ ๋ฐœํ–‰์ด๋ผ dedup ๋Œ€์ƒ โ†’ eligible False + r = client.get("/api/insta/keywords/ranked?threshold=0.0&limit=10&dedup_window_days=30") + assert r.status_code == 200 + items = r.json()["items"] + keumni = next((i for i in items if i["keyword"] == "๊ธˆ๋ฆฌ"), None) + assert keumni is not None, "'๊ธˆ๋ฆฌ' ํ•ญ๋ชฉ์ด ranked ์‘๋‹ต์— ์—†์Œ" + assert keumni["eligible"] is False, "dedup_window_days=30 ๋‚ด ๋ฐœํ–‰ โ†’ eligible์€ False์—ฌ์•ผ ํ•จ" + + # ๋ฐœํ–‰ ์ด๋ ฅ ์—†๋Š” 'ํ™˜์œจ'์€ ์–ด๋–ค window์—์„œ๋„ eligible True + hwanul = next((i for i in items if i["keyword"] == "ํ™˜์œจ"), None) + assert hwanul is not None, "'ํ™˜์œจ' ํ•ญ๋ชฉ์ด ranked ์‘๋‹ต์— ์—†์Œ" + assert hwanul["eligible"] is True, "๋ฐœํ–‰ ์ด๋ ฅ ์—†๋Š” ํ‚ค์›Œ๋“œ๋Š” eligible True์—ฌ์•ผ ํ•จ"