- Split app.py (66 routes) into 3 blueprints: pages (public), api (JSON), admin (@admin_required) - Split data.py (4,360 LOC) into 7 domain modules: drafts, authors, ratings, gaps, analysis, search, proposals - Add data/__init__.py re-exporting all public functions for backward compatibility - Add custom 404/500 error pages matching dark theme - Add request timing logging via before_request/after_request hooks - Refactor app.py into create_app() factory pattern - All 106 tests pass, all 66 routes preserved Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
47 lines
1.4 KiB
Python
47 lines
1.4 KiB
Python
"""Shared utilities for webui data modules."""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
# Ensure project src is on path
|
|
_project_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
if str(_project_root) not in sys.path:
|
|
sys.path.insert(0, str(_project_root / "src"))
|
|
|
|
from ietf_analyzer.config import Config
|
|
from ietf_analyzer.db import Database
|
|
from ietf_analyzer.readiness import compute_readiness, compute_readiness_batch
|
|
|
|
# Simple TTL cache for expensive computations (t-SNE, clustering, similarity)
|
|
_cache: dict[str, tuple[float, object]] = {}
|
|
_CACHE_TTL = 300 # 5 minutes
|
|
|
|
|
|
def _extract_month(time_str: str | None) -> str:
|
|
"""Normalize a date string to YYYY-MM format."""
|
|
if not time_str:
|
|
return "unknown"
|
|
if len(time_str) >= 7 and time_str[4] == '-':
|
|
return time_str[:7] # Already YYYY-MM-DD
|
|
if len(time_str) >= 6 and time_str[:4].isdigit():
|
|
return time_str[:4] + '-' + time_str[4:6] # YYYYMMDD → YYYY-MM
|
|
return time_str[:7]
|
|
|
|
def _cached(key: str, fn, ttl: float = _CACHE_TTL):
|
|
"""Return cached result or compute and cache it."""
|
|
now = time.monotonic()
|
|
if key in _cache:
|
|
ts, val = _cache[key]
|
|
if now - ts < ttl:
|
|
return val
|
|
val = fn()
|
|
_cache[key] = (now, val)
|
|
return val
|
|
|
|
def get_db() -> Database:
|
|
"""Get a Database instance using default config."""
|
|
config = Config.load()
|
|
return Database(config)
|