49 lines
1.7 KiB
Python
49 lines
1.7 KiB
Python
import datetime as dt
|
|
from collections import defaultdict
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .config import settings
|
|
from .models import Article
|
|
from .textutil import topic_label as _topic_label
|
|
|
|
|
|
def build_clusters(session: Session) -> list[dict]:
|
|
cutoff = dt.datetime.utcnow() - dt.timedelta(hours=settings.article_window_hours)
|
|
rows = session.execute(
|
|
select(Article)
|
|
.where(Article.cluster_key.is_not(None))
|
|
.where(Article.published_at >= cutoff)
|
|
.order_by(Article.published_at.desc())
|
|
).scalars().all()
|
|
|
|
groups: dict[str, list[Article]] = defaultdict(list)
|
|
for article in rows:
|
|
groups[article.cluster_key].append(article)
|
|
|
|
clusters = []
|
|
for key, articles in groups.items():
|
|
lat = sum(a.lat for a in articles) / len(articles)
|
|
lon = sum(a.lon for a in articles) / len(articles)
|
|
sources = sorted({a.source for a in articles})
|
|
clusters.append(
|
|
{
|
|
"cluster_key": key,
|
|
"lat": lat,
|
|
"lon": lon,
|
|
"location_name": articles[0].location_name,
|
|
"country": articles[0].country,
|
|
"article_count": len(articles),
|
|
"source_count": len(sources),
|
|
"sources": sources,
|
|
"topic": _topic_label([a.title for a in articles]),
|
|
"latest_published_at": max(a.published_at for a in articles).isoformat(),
|
|
"headline": articles[0].title,
|
|
"article_ids": [a.id for a in articles],
|
|
}
|
|
)
|
|
|
|
clusters.sort(key=lambda c: c["article_count"], reverse=True)
|
|
return clusters
|