chore(webui): remove timeline animation page

The animated t-SNE 'embedding landscape' added no real analytical value and
its cumulative-by-month logic was inherently confusing (ancient drafts
appearing late in the animation). Removed entirely rather than maintained.

Drops the /timeline route, template, nav link, data builder
(get_timeline_animation_data / _compute_timeline_animation_data) and its test.
The Overview mini-timeline and /api/timeline (separate features) are untouched.
This commit is contained in:
2026-05-23 21:52:30 +02:00
parent e166091388
commit 89df70a6c0
6 changed files with 0 additions and 355 deletions

View File

@@ -69,7 +69,6 @@ from webui.data.analysis import ( # noqa: F401
get_timeline_data,
get_similarity_graph,
get_idea_clusters,
get_timeline_animation_data,
get_monitor_status,
get_citation_graph,
get_landscape_tsne,

View File

@@ -502,81 +502,6 @@ def _compute_idea_clusters(db: Database) -> dict:
"empty": False,
}
def get_timeline_animation_data(db: Database) -> dict:
"""Timeline animation (cached for 5 min)."""
return _cached("timeline_animation", lambda: _compute_timeline_animation_data(db))
def _compute_timeline_animation_data(db: Database) -> dict:
"""Compute t-SNE on all drafts, return points with month info + category_monthly.
t-SNE is computed once on ALL drafts so coordinates are stable across
animation frames. Each point carries a ``month`` field (YYYY-MM) so the
front-end can build cumulative animation frames.
"""
embeddings = db.all_embeddings()
if len(embeddings) < 5:
return {"points": [], "months": [], "category_monthly": {}}
pairs = db.drafts_with_ratings(limit=1000)
rating_map = {d.name: r for d, r in pairs}
draft_map = {d.name: d for d, _ in pairs}
# Filter to drafts that have both embeddings and ratings
names = [n for n in embeddings if n in rating_map]
if len(names) < 5:
return {"points": [], "months": [], "category_monthly": {}}
matrix = np.array([embeddings[n] for n in names])
try:
tsne = TSNE(n_components=2, perplexity=min(30, len(names) - 1),
random_state=42, max_iter=500)
coords = tsne.fit_transform(matrix)
except Exception:
return {"points": [], "months": [], "category_monthly": {}}
# Build points with month
points = []
month_set: set[str] = set()
category_monthly: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
for i, name in enumerate(names):
r = rating_map[name]
d = draft_map.get(name)
month = _extract_month(d.time if d else None)
if month == "unknown":
continue # Undated docs (e.g. ISO/ETSI) can't be placed on a temporal animation
cat = r.categories[0] if r.categories else "Other"
month_set.add(month)
category_monthly[month][cat] += 1
points.append({
"name": name,
"title": d.title if d else name,
"x": round(float(coords[i, 0]), 3),
"y": round(float(coords[i, 1]), 3),
"category": cat,
"score": round(r.composite_score, 2),
"month": month,
})
# Deliver points in chronological order so the front-end's cumulative
# filter (p.month <= frame) is append-only. Otherwise new points get
# inserted mid-array and Plotly's index-based frame transition animates
# existing markers flying to other drafts' coordinates ("jumping points").
points.sort(key=lambda p: (p["month"], p["name"]))
months = sorted(month_set)
# Convert defaultdict to plain dict for JSON
cat_monthly_plain = {m: dict(cats) for m, cats in category_monthly.items()}
return {
"points": points,
"months": months,
"category_monthly": cat_monthly_plain,
}
def get_monitor_status(db: Database) -> MonitorStatus:
"""Return monitoring status data for dashboard."""
runs = db.get_monitor_runs(limit=20)