diff --git a/src/webui/blueprints/pages.py b/src/webui/blueprints/pages.py index b16cc9b..b301ca1 100644 --- a/src/webui/blueprints/pages.py +++ b/src/webui/blueprints/pages.py @@ -11,7 +11,6 @@ from webui.data import ( get_draft_detail, get_rating_distributions, get_timeline_data, - get_timeline_animation_data, get_ideas_by_type, get_top_authors, get_org_data, @@ -127,12 +126,6 @@ def ratings(): ) -@pages_bp.route("/timeline") -def timeline_animation(): - data = get_timeline_animation_data(db()) - return render_template("timeline.html", animation=data) - - @pages_bp.route("/idea-clusters") def idea_clusters(): data = get_idea_clusters(db()) diff --git a/src/webui/data/__init__.py b/src/webui/data/__init__.py index 6ac956e..338c79d 100644 --- a/src/webui/data/__init__.py +++ b/src/webui/data/__init__.py @@ -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, diff --git a/src/webui/data/analysis.py b/src/webui/data/analysis.py index 0a6af66..48559e6 100644 --- a/src/webui/data/analysis.py +++ b/src/webui/data/analysis.py @@ -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) diff --git a/src/webui/templates/base.html b/src/webui/templates/base.html index 75f1144..a02e7e1 100644 --- a/src/webui/templates/base.html +++ b/src/webui/templates/base.html @@ -156,10 +156,6 @@ Blog Drafts {% endif %} - - - Timeline - {% if is_admin %} diff --git a/src/webui/templates/timeline.html b/src/webui/templates/timeline.html deleted file mode 100644 index 2cb20c0..0000000 --- a/src/webui/templates/timeline.html +++ /dev/null @@ -1,263 +0,0 @@ -{% extends "base.html" %} -{% set active_page = "timeline" %} - -{% block title %}Timeline — IETF Draft Analyzer{% endblock %} - -{% block extra_head %}{% endblock %} - -{% block content %} -
-

Timeline Animation

-

Watch the AI/agent draft landscape evolve month by month

-
- - -
-
- - -
-

Animated Embedding Landscape

-

t-SNE projection with cumulative drafts per month. Color = category, size = composite score. Press Play to animate.

-
- -
-
-
- - -
-

Category Submissions Over Time

-

Stacked area chart showing draft submissions by category per month.

-
-
-{% endblock %} - -{% block extra_scripts %} - -{% endblock %} diff --git a/tests/test_web.py b/tests/test_web.py index 3e6b142..9f808d2 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -61,11 +61,6 @@ def test_authors_page(client): assert resp.status_code == 200 -def test_timeline_page(client): - resp = client.get("/timeline") - assert resp.status_code == 200 - - def test_search_page_empty(client): resp = client.get("/search") assert resp.status_code == 200