| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- from __future__ import annotations
- """Deduplicate article entries inside stored clusters.
- This cleans existing SQLite payloads so a cluster only keeps one article record
- per canonical article key (preferably the URL path/article id).
- Usage:
- ./.venv/bin/python scripts/dedup_articles_in_clusters.py --dry-run
- ./.venv/bin/python scripts/dedup_articles_in_clusters.py
- """
- import argparse
- import json
- import sys
- from pathlib import Path
- ROOT = Path(__file__).resolve().parents[1]
- sys.path.insert(0, str(ROOT))
- from news_mcp.config import DB_PATH
- from news_mcp.storage.sqlite_store import SQLiteClusterStore, sanitize_cluster_payload
- def main() -> None:
- parser = argparse.ArgumentParser(description="Deduplicate article entries inside stored clusters")
- parser.add_argument("--db", type=Path, default=DB_PATH)
- parser.add_argument("--dry-run", action="store_true")
- parser.add_argument("--limit", type=int, default=None)
- args = parser.parse_args()
- store = SQLiteClusterStore(args.db)
- with store._conn() as conn: # noqa: SLF001 - maintenance script
- rows = conn.execute("SELECT cluster_id, topic, payload FROM clusters ORDER BY updated_at ASC").fetchall()
- if args.limit is not None:
- rows = rows[: args.limit]
- total = 0
- updated = 0
- print(f"starting article dedup: clusters={len(rows)} dry_run={args.dry_run}")
- for cluster_id, topic, payload_json in rows:
- total += 1
- try:
- cluster = json.loads(payload_json)
- except Exception:
- continue
- sanitized = sanitize_cluster_payload(cluster)
- original_articles = cluster.get("articles", []) or []
- deduped = sanitized.get("articles", []) or []
- if deduped == original_articles:
- continue
- cluster = sanitized
- if not args.dry_run:
- store.upsert_clusters([cluster], topic=topic or cluster.get("topic", "other"))
- updated += 1
- if updated % 25 == 0:
- print(f"updated={updated} processed={total}")
- print({"total_scanned": total, "updated": updated, "dry_run": args.dry_run})
- if __name__ == "__main__":
- main()
|