From 1d3d61d31e6bd1f19a8b4e66d277dec5886618ff Mon Sep 17 00:00:00 2001 From: hyungi Date: Tue, 12 May 2026 21:44:00 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(briefing):=20lower=20clustering=20thres?= =?UTF-8?q?hold=200.78=20=E2=86=92=200.70?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배포 후 관측 결과 (2026-05-13 새벽): - 126 docs / 7 countries 인데 THRESHOLD=0.78 로 raw_clusters=124, dropped_min_articles=122, kept=1. - 거의 매 article 이 별 cluster 로 갈려 토픽 묶음 실패. - 같은 cron 어제 (5/12) 는 101 docs 에서 6 topics 성공 — 그날 뉴스가 우연히 같은 토픽으로 더 모인 case. 수동 측정 (5/13 동일 docs): - 0.78 → kept=1 - 0.70 → kept=5 (allowed) 영구 변경 = THRESHOLD=0.70. cross-country 필터 (MIN_COUNTRIES≥2) + min_articles(≥2) 그대로 유지하므로 noise topic 위험은 제한적. 원본 주석 (0.75~0.80 중간값) 도 갱신. --- app/services/briefing/clustering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/briefing/clustering.py b/app/services/briefing/clustering.py index 2b62569..6896f6e 100644 --- a/app/services/briefing/clustering.py +++ b/app/services/briefing/clustering.py @@ -5,7 +5,7 @@ Phase 4 와 axis 반대: country 별 cluster 가 아닌 **전체 doc 합쳐서 t 파라미터 (5h 윈도우용): - LAMBDA = ln(2)/2h ≈ 0.347 (2시간 반감기, 야간 5h 윈도우라 빠른 감쇠) -- threshold = 0.78 고정 (Phase 4 0.75~0.80 중간값) +- threshold = 0.70 (2026-05-13 조정 — 0.78 에서 spread case kept=1 발생 후 완화) - MIN_ARTICLES_PER_TOPIC = 2 (야간 sparse 대비 완화) - MIN_COUNTRIES_PER_TOPIC = 2 (cross-country 가치 핵심) - MAX_TOPICS = 7 (1페이지 분량) @@ -22,7 +22,7 @@ from services.clustering_common import ( logger = setup_logger("briefing_clustering") LAMBDA = math.log(2) / (2.0 / 24.0) # 2시간 반감기 (단위: 일) -THRESHOLD = 0.78 +THRESHOLD = 0.70 CENTROID_ALPHA = 0.7 MIN_ARTICLES_PER_TOPIC = 2 MIN_COUNTRIES_PER_TOPIC = 2 From 5a86e045f197d26bf12b3fbc7c742cb114ed5091 Mon Sep 17 00:00:00 2001 From: hyungi Date: Tue, 12 May 2026 21:44:30 +0000 Subject: [PATCH 2/2] feat(news): seed 14 tech/AI news sources (8 countries) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit briefing/digest 의 cross-country tech 토픽 다양성 확보용 source seed. - KR ×2: GeekNews (Hada), AI Times - US ×4: Hacker News, ArsTechnica AI, The Verge Tech, TechCrunch - GB ×2: The Register, BBC Technology - DE ×1: Heise Online - JP ×2: ITmedia News, Gigazine - CN ×1: 36Kr - FR ×1: ZDNet France - IN ×1: Analytics India Magazine idempotent: WHERE NOT EXISTS (name). 운영 DB 에는 이미 적용됨, 백업 복원/신규 deploy 환경에서 자동 시드. 수집 검증 (2026-05-13 1차 fire, 8 source): - 성공: Hacker News 30 / ArsTechnica AI 20 / Verge 10 / TC 20 / Register 50 / Heise 153 (총 283건 신규) - 후속 fix: GeekNews 의 http redirect → feedburner 직접 URL, AI Times URL 오타 → S1N1.xml. content category 는 news_sources.category (Tech / AI) 로 보존, briefing 의 country 필터 (MIN_COUNTRIES_PER_TOPIC ≥ 2) 와 호환. --- migrations/262_seed_tech_ai_news_sources.sql | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 migrations/262_seed_tech_ai_news_sources.sql diff --git a/migrations/262_seed_tech_ai_news_sources.sql b/migrations/262_seed_tech_ai_news_sources.sql new file mode 100644 index 0000000..6866a48 --- /dev/null +++ b/migrations/262_seed_tech_ai_news_sources.sql @@ -0,0 +1,26 @@ +-- 2026-05-13 — 기술/AI 뉴스 source seed (14건, 8개국) +-- WHERE NOT EXISTS 로 idempotent. 기존 row 보존, 신규만 insert. +-- briefing/digest 의 cross-country tech 토픽 cluster 다양성 확보. +-- 8 country: CN, DE, FR, GB, IN, JP, KR, US. category = Tech / AI. + +INSERT INTO news_sources (name, country, language, feed_type, feed_url, category, enabled) +SELECT v.name, v.country, v.language, v.feed_type, v.feed_url, v.category, v.enabled +FROM (VALUES + ('GeekNews (Hada)', 'KR', 'ko', 'rss', 'https://feeds.feedburner.com/geeknews-feed', 'Tech', true), + ('AI Times', 'KR', 'ko', 'rss', 'https://www.aitimes.com/rss/S1N1.xml', 'AI', true), + ('Hacker News', 'US', 'en', 'rss', 'https://hnrss.org/frontpage?count=30', 'Tech', true), + ('ArsTechnica AI', 'US', 'en', 'rss', 'https://arstechnica.com/ai/feed/', 'AI', true), + ('The Verge Tech', 'US', 'en', 'rss', 'https://www.theverge.com/rss/index.xml', 'Tech', true), + ('TechCrunch', 'US', 'en', 'rss', 'https://techcrunch.com/feed/', 'Tech', true), + ('The Register', 'GB', 'en', 'rss', 'https://www.theregister.com/headlines.atom', 'Tech', true), + ('Heise Online', 'DE', 'de', 'rss', 'https://www.heise.de/rss/heise-atom.xml', 'Tech', true), + ('ITmedia News', 'JP', 'ja', 'rss', 'https://rss.itmedia.co.jp/rss/2.0/aiplus.xml', 'AI', true), + ('Gigazine', 'JP', 'ja', 'rss', 'https://gigazine.net/news/rss_2.0/', 'Tech', true), + ('36Kr', 'CN', 'zh', 'rss', 'https://36kr.com/feed', 'Tech', true), + ('Numerama', 'FR', 'fr', 'rss', 'https://www.numerama.com/feed', 'Tech', true), + ('YourStory', 'IN', 'en', 'rss', 'https://yourstory.com/feed', 'Tech', true), + ('BBC Technology', 'GB', 'en', 'rss', 'https://feeds.bbci.co.uk/news/technology/rss.xml', 'Tech', true) +) AS v(name, country, language, feed_type, feed_url, category, enabled) +WHERE NOT EXISTS ( + SELECT 1 FROM news_sources ns WHERE ns.name = v.name +);