"""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_timeline_animation_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, ) 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/") 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("/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()) 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") 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)