Files
ietf-draft-analyzer/src/webui/blueprints/pages.py
Christian Nennemann 89df70a6c0 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.
2026-05-23 21:52:46 +02:00

209 lines
5.6 KiB
Python

"""Public page routes (no admin required)."""
from __future__ import annotations
from flask import Blueprint, render_template, request, abort, g
from webui.data import (
get_db,
get_overview_stats,
get_category_counts,
get_drafts_page,
get_draft_detail,
get_rating_distributions,
get_timeline_data,
get_ideas_by_type,
get_top_authors,
get_org_data,
get_category_radar_data,
get_score_histogram,
get_author_network_full,
get_cross_org_data,
get_citation_graph,
get_idea_clusters,
get_category_summary,
global_search,
get_architecture,
get_ask_search,
get_citation_influence,
get_bcp_analysis,
get_author_detail,
)
pages_bp = Blueprint("pages", __name__)
def db():
if "db" not in g:
g.db = get_db()
return g.db
@pages_bp.route("/")
def overview():
stats = get_overview_stats(db())
categories = get_category_counts(db())
timeline = get_timeline_data(db())
scores = get_score_histogram(db())
radar = get_category_radar_data(db())
return render_template(
"overview.html",
stats=stats,
categories=categories,
timeline=timeline,
scores=scores,
radar=radar,
)
@pages_bp.route("/drafts")
def drafts():
page = request.args.get("page", 1, type=int)
search = request.args.get("q", "")
category = request.args.get("cat", "")
source = request.args.get("source", "")
min_score = request.args.get("min_score", 0.0, type=float)
sort = request.args.get("sort", "score")
sort_dir = request.args.get("dir", "desc")
result = get_drafts_page(
db(),
page=page,
search=search,
category=category,
min_score=min_score,
sort=sort,
sort_dir=sort_dir,
source=source,
)
categories = get_category_counts(db())
cat_summary = get_category_summary(db(), category) if category else None
return render_template(
"drafts.html",
result=result,
categories=categories,
cat_summary=cat_summary,
search=search,
current_cat=category,
current_source=source,
min_score=min_score,
sort=sort,
sort_dir=sort_dir,
)
@pages_bp.route("/drafts/<string:name>")
def draft_detail(name: str):
database = db()
detail = get_draft_detail(database, name)
if not detail:
abort(404)
# Build set of draft ref IDs that exist in our DB for internal linking
ref_draft_ids = [r["id"] for r in detail.get("refs", []) if r["type"] == "draft"]
known_drafts = set()
if ref_draft_ids:
placeholders = ",".join("?" * len(ref_draft_ids))
rows = database.conn.execute(
f"SELECT name FROM drafts WHERE name IN ({placeholders})", ref_draft_ids
).fetchall()
known_drafts = {r["name"] for r in rows}
return render_template("draft_detail.html", draft=detail, known_drafts=known_drafts)
@pages_bp.route("/ideas")
def ideas():
data = get_ideas_by_type(db())
return render_template("ideas.html", data=data)
@pages_bp.route("/ratings")
def ratings():
distributions = get_rating_distributions(db())
radar = get_category_radar_data(db())
return render_template(
"ratings.html",
dist=distributions,
radar=radar,
)
@pages_bp.route("/idea-clusters")
def idea_clusters():
data = get_idea_clusters(db())
return render_template("idea_clusters.html", clusters=data)
@pages_bp.route("/architecture")
def architecture():
data = get_architecture(db())
return render_template("architecture.html", arch=data)
@pages_bp.route("/authors/<int:person_id>")
def author_detail(person_id: int):
detail = get_author_detail(db(), person_id)
if not detail:
abort(404)
return render_template("author_detail.html", author=detail)
@pages_bp.route("/authors")
def authors():
top = get_top_authors(db(), limit=50)
orgs = get_org_data(db(), limit=20)
network = get_author_network_full(db())
cross_org = get_cross_org_data(db(), limit=20)
return render_template(
"authors.html",
authors=top,
orgs=orgs,
orgs_data=orgs,
network=network,
cross_org=cross_org,
)
@pages_bp.route("/citations")
def citations():
from webui.auth import is_admin as check_admin
graph = get_citation_graph(db())
influence = get_citation_influence(db()) if check_admin() else None
bcp = get_bcp_analysis(db()) if check_admin() else None
return render_template("citations.html", graph=graph, influence=influence, bcp=bcp)
@pages_bp.route("/about")
def about():
from ietf_analyzer.config import Config
cfg = Config.load()
stats = get_overview_stats(db())
return render_template("about.html", stats=stats, search_keywords=cfg.search_keywords,
fetch_since=cfg.fetch_since)
@pages_bp.route("/impressum")
def impressum():
return render_template("impressum.html")
@pages_bp.route("/datenschutz")
def datenschutz():
return render_template("datenschutz.html")
@pages_bp.route("/search")
def search():
q = request.args.get("q", "").strip()
results = global_search(db(), q) if q else {"drafts": [], "ideas": [], "authors": [], "gaps": []}
total = sum(len(v) for v in results.values())
return render_template("search_results.html", query=q, results=results, total=total)
@pages_bp.route("/ask")
def ask_page():
question = request.args.get("q", "")
result = None
if question:
top_k = request.args.get("top", 5, type=int)
result = get_ask_search(db(), question, top_k=top_k)
return render_template("ask.html", question=question, result=result)