From 61cdab16b99023028856ce8edb28c56e6ccfc530 Mon Sep 17 00:00:00 2001 From: Christian Nennemann Date: Mon, 9 Mar 2026 03:52:02 +0100 Subject: [PATCH] fix: dev mode auth regression from blueprint refactor The _initialized singleton in auth.py prevented hooks from registering on the correct app instance when create_app() was called twice (once eagerly at import, once from __main__). Removed the guard and made the module-level app lazy. Also adds feature backlog and architecture assessment from the review team. Co-Authored-By: Claude Opus 4.6 --- FEATURE_BACKLOG.md | 356 ++++++++++++++++++ .../architecture-assessment-2026-03-09.md | 343 +++++++++++++++++ src/webui/app.py | 13 +- src/webui/auth.py | 9 +- 4 files changed, 712 insertions(+), 9 deletions(-) create mode 100644 FEATURE_BACKLOG.md create mode 100644 data/reports/architecture-assessment-2026-03-09.md diff --git a/FEATURE_BACKLOG.md b/FEATURE_BACKLOG.md new file mode 100644 index 0000000..584c44e --- /dev/null +++ b/FEATURE_BACKLOG.md @@ -0,0 +1,356 @@ +# IETF Draft Analyzer — Feature & UX Backlog + +**Scope**: Analysis of web UI (33 templates, 66 routes), CLI (36 commands), data layer (100+ computed functions), and DB schema (10 core tables). + +**Date**: 2026-03-09 +**Analyst**: UX Scout Task #3 + +--- + +## Summary + +The application is **feature-rich but siloed**: extensive data exists in the DB and is computed in data.py, but only ~60% is exposed in the web UI. Cross-linking between pages is incomplete (e.g., author pages don't link to gap gaps, drafts don't link to related proposals). CLI has powerful analysis commands (embed-ideas, idea-overlap, co-occurrence) but no web UI counterparts. + +**Quick wins**: 5 features that reuse existing infrastructure with minimal effort. +**Medium effort**: 7 features requiring new pages or moderate refactoring. +**Design debt**: Navigation flows have dead ends (e.g., single idea clusters → nowhere, gaps → proposals missing web UI). + +--- + +## Detailed Backlog + +### 🟢 HIGH IMPACT × SMALL EFFORT + +#### 1. **Author detail page with draft linkage** [IMPACT: HIGH | EFFORT: SMALL] +- **Current**: Authors page shows network graph only; clicking author in draft_detail searches "author name" in drafts +- **Missing**: Individual author page (`/authors/`) showing: + - Author metadata (affiliation, co-authors) + - List of drafts authored (with ratings/scores) + - Co-author graph (2-hop collaboration network) + - Ideas contributed (via their drafts) + - Gaps they could address (category match with drafts) +- **Why**: Closes gap between draft_detail → author → their other work. Reuses existing `get_author_network_full()` and co-author logic. +- **Effort**: New template + 2 route handlers + 1 data.py function to fetch author profile + co-authors. +- **Reuses**: `authors.py`, `get_coauthor_network()`, existing draft query logic. + +--- + +#### 2. **Idea detail page with "related ideas" clustering** [IMPACT: HIGH | EFFORT: SMALL] +- **Current**: Idea clusters shown as page, but no single-idea view; clicking idea in draft_detail has no action +- **Missing**: `/ideas/` page showing: + - Idea title, type, description, novelty score + - Source draft (link) + - 5–10 semantically similar ideas (from other drafts) + - Ideas this relates to (same cluster) + - Gaps this could address (keyword + semantic match) +- **Why**: Idea-centric navigation. Currently all idea access is through drafts or clusters. +- **Effort**: New template + route + 1 data.py function (k-nearest neighbors in idea embedding space). +- **Reuses**: Idea embeddings already computed; `get_idea_clusters()` logic. + +--- + +#### 3. **Gap-to-draft reverse link** [IMPACT: HIGH | EFFORT: SMALL] +- **Current**: Gap detail shows what drafts exist on that topic; no link back to gaps from draft_detail +- **Missing**: Add section to draft_detail showing: + - Which gaps this draft helps address (topic keyword match + semantic) + - Related proposals (from `/proposals` system) +- **Why**: Closes loop: gap → drafts and draft → gaps. Helps user understand how a draft fits into the roadmap. +- **Effort**: New section in draft_detail template + 1 data.py function (`get_gaps_for_draft(draft_name)`). +- **Reuses**: Existing gap data + keyword/semantic matching. + +--- + +#### 4. **Inline draft comparison widget in drafts list** [IMPACT: MEDIUM | EFFORT: SMALL] +- **Current**: `/compare` page exists but requires manual draft selection via checkboxes, no shortcuts +- **Missing**: + - Add "Compare" checkbox to each draft row in `/drafts` list + - Sticky "Compare Selected" button at bottom + - Quick-compare overlay showing side-by-side: score, category, authors, novelty +- **Why**: Low-friction way to discover overlaps without navigating away. +- **Effort**: Template changes + 1 JS handler + reuse existing `/api/compare` endpoint. + +--- + +#### 5. **Export audit trail: "What changed?" in monitoring** [IMPACT: MEDIUM | EFFORT: SMALL] +- **Current**: Monitor page shows last run stats (new drafts, duration) but no detailed change log +- **Missing**: Expandable section per run showing: + - New drafts added (names, count) + - False positives removed + - Ideas extracted (by category) + - API costs (tokens used) +- **Why**: Transparency into pipeline. Supports cost tracking and debugging. +- **Effort**: New template section + 1 data.py function to fetch run details from monitoring tables. +- **Reuses**: Existing monitoring data; minor DB schema extension (one `audit_log` table). + +--- + +### 🟡 HIGH IMPACT × MEDIUM EFFORT + +#### 6. **Category detail pages** [IMPACT: HIGH | EFFORT: MEDIUM] +- **Current**: Category counts shown in overview; drafts filterable by category in `/drafts?cat=X`; no dedicated page +- **Missing**: `/categories/` page showing: + - Category description, count, average score + - All drafts in category (sorted by score) + - Top ideas in category (clustered) + - Top authors in category (by draft count) + - Trends: are drafts in this category getting more diverse? Maturing faster? + - Gaps specific to this category (e.g., "Authentication" category lacks "revocation" ideas) +- **Why**: Makes categories first-class entities; easier content discovery. +- **Effort**: 1 template + 1 route + 3 data.py functions (top ideas, top authors, trends). +- **Reuses**: Existing category logic, idea clustering, author ranking. + +--- + +#### 7. **Proposal detail → drafts linkage (web UI)** [IMPACT: HIGH | EFFORT: MEDIUM] +- **Current**: `/proposals` exists (admin-only) but is disconnected from public UI; no way to see "which drafts might form a proposal?" +- **Missing**: + - Make `/proposals` public (read-only) + - Add `/proposals/` route showing: + - Proposal title, status, description, intended WG + - Linked gaps (current feature) + - **Suggested/related drafts** (category + author match) + - Links to existing or draft RFCs/BCP refs + - Add "Proposal intake" link from gap detail: "This gap could become a proposal. Start here →" +- **Why**: Connects gap analysis to standards development workflow. +- **Effort**: Route changes + template + 2 data.py functions (related drafts, related RFCs). +- **Requires**: Remove admin_required on `/proposals` routes (review auth first). + +--- + +#### 8. **Citation/reference graph improvements** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: Citation page shows RFC/draft references as graph +- **Missing**: + - **Reverse citations**: "Which other drafts cite this one?" + - **Citation patterns**: "What RFC/BCP are most cited in agent drafts?" + - **Missing references**: "This draft should cite RFC X because it covers the same topic" (suggest related RFCs) +- **Why**: Helps identify foundational standards and co-dependencies. +- **Effort**: 2 new data functions + template enhancements. +- **Reuses**: Existing `draft_refs` table; parsing logic for RFC extraction. + +--- + +#### 9. **Dashboard export to Obsidian/Markdown** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: `/export/obsidian` exists but exports only notes, not full dashboard state +- **Missing**: Export multiple formats: + - **Markdown vault**: Obsidian-compatible markdown with frontmatter (category, score, authors, ideas) + - **JSON snapshot**: Full state dump (drafts + ratings + ideas + gaps) for offline analysis + - **CSV multi-export**: Separate CSVs for each entity type (drafts, authors, ideas, gaps, proposals) +- **Why**: Supports research workflows (offline reading, external tool integration). +- **Effort**: 2 new route handlers + 2 data.py functions (schema converters). +- **Reuses**: Existing `obsidian_export.py`; CSV export logic already in place. + +--- + +#### 10. **Search filters: advanced query builder** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: Search page shows simple text box and keyword search; `/drafts` has basic filters (category, score, source) +- **Missing**: Advanced query UI: + - Filter by: category, score range, publication date, author, WG, false positive status + - Sort by: score, novelty, overlap, momentum, relevance, date + - Save/name favorite searches + - Full-text search highlighting in results +- **Why**: Power users (researchers, standards bodies) need fine-grained filtering. +- **Effort**: New template (filter form) + route enhancements + 1 data.py function (advanced search builder). +- **Reuses**: Existing FTS5 search; current filter parameters. + +--- + +### 🔵 MEDIUM IMPACT × MEDIUM EFFORT + +#### 11. **Local LLM (Ollama) analysis shortcuts** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: Ollama used for embeddings + classification only; Claude handles all analysis +- **Missing**: Expose local-only analysis tools in web UI: + - **Idea re-classification**: "Reclassify ideas for this draft using local model" (cheaper) + - **Draft similarity** (local embeddings): "Find drafts most similar to this one" + - **Batch idea extraction preview**: "Show how ideas would be extracted from this draft (preview, no API call)" +- **Why**: Reduces Claude API costs; good for data exploration before expensive operations. +- **Effort**: 3 new API routes + Flask handlers. +- **Reuses**: Existing `embeddings.py` + `classifier.py` + Ollama integration. + +--- + +#### 12. **Idea overlap/gap matrix UI** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: CLI has `idea-overlap` and `co-occurrence` commands (generate reports); no web UI +- **Missing**: Interactive web page at `/ideas/overlap`: + - Matrix showing which ideas overlap (appear in multiple drafts) + - Co-occurrence frequency (if ideas A and B frequently appear together, high signal) + - Suggests "merge these ideas" or "these might be the same standard" +- **Why**: Helps identify redundant/overlapping work; supports standards consolidation. +- **Effort**: 1 new route + 1 data.py function + 1 Plotly heatmap template. +- **Reuses**: CLI logic from `idea-overlap` and `co-occurrence` commands. + +--- + +#### 13. **Trends analysis page** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: `get_trends_data()` computed but not exposed (used by complexity page); data.py has trends functions +- **Missing**: Dedicated `/trends` page showing: + - New drafts per month (trend line) + - Score distribution over time (are recent drafts more mature?) + - Category growth trends + - Author activity (new vs. returning) + - API cost trend (tokens/month) +- **Why**: Understand field maturity and adoption patterns. +- **Effort**: 1 route + 1 template + use existing `get_trends_data()`. +- **Reuses**: Existing data; Plotly timeseries. + +--- + +#### 14. **Readiness scorer exposé** [IMPACT: MEDIUM | EFFORT: MEDIUM] +- **Current**: `readiness` module exists; shown in CLI `show` command; not in web UI +- **Missing**: + - Add readiness score to draft_detail page (already computed) + - `/readiness` page: heatmap of all drafts × readiness factors + - "How close to standardization?" indicator +- **Why**: Helps standards bodies identify which proposals are most mature. +- **Effort**: 1 template update + 1 route + reuse existing `readiness` module. + +--- + +#### 15. **Geospatial author view** [IMPACT: LOW | EFFORT: MEDIUM] +- **Current**: Author affiliations stored in DB (org) but not geospatial +- **Missing**: If org data can be geo-coded (rough): + - Map showing author distribution (bubbles by region/country) + - "Where is AI/agent standardization happening?" +- **Why**: Novel visualization; supports diversity analysis. +- **Effort**: Requires org → geo mapping (manual or external API); map widget setup. +- **Blocker**: Geo-coding affiliation strings is non-trivial. + +--- + +### 🟠 MEDIUM/LOW IMPACT × LARGE EFFORT + +#### 16. **Real-time monitoring dashboard** [IMPACT: MEDIUM | EFFORT: LARGE] +- **Current**: Monitor page is static; shows last run results +- **Missing**: WebSocket-based live updates during fetch/analyze/embed pipeline runs + - Real-time progress bars + - Live log streaming + - Ability to pause/cancel runs +- **Why**: Better UX for long-running operations. +- **Effort**: WebSocket layer (Flask-SocketIO), background task queue (Celery), run state machine. +- **Note**: High complexity; consider only if pipeline runs are frequent bottleneck. + +--- + +#### 17. **Semantic search (vs. full-text)** [IMPACT: MEDIUM | EFFORT: LARGE] +- **Current**: Search uses FTS5 (lexical); embeddings exist but not used for search +- **Missing**: + - "Ask" page queries: should embed question, find semantically similar drafts + - Extend to all search endpoints (global search) +- **Why**: Better recall for conceptual queries ("How do drafts handle agent authorization?") +- **Effort**: Requires re-querying embeddings for every search (perf issue) or building vector index (Faiss/Milvus). +- **Note**: Good for future; current FTS5 is sufficient. + +--- + +#### 18. **Collaborative annotations & tags** [IMPACT: LOW | EFFORT: LARGE] +- **Current**: Admin-only notes & tags on draft_detail +- **Missing**: + - Multi-user collaborative notes (comments, threads) + - Tag suggestions (based on similar drafts) + - Tag autocomplete +- **Why**: Supports community review of drafts. +- **Effort**: Auth rework, comments table, collaborative editing. +- **Note**: Out of scope unless multi-user access is planned. + +--- + +#### 19. **Custom report builder** [IMPACT: MEDIUM | EFFORT: LARGE] +- **Current**: Fixed reports (overview, landscape, digest, gaps, etc.) +- **Missing**: UI to build custom reports: + - Select entities (drafts, ideas, authors, gaps) + - Choose dimensions (score, category, date) + - Choose output format (PDF, HTML, Markdown) +- **Why**: Non-technical users (standards committees) can create reports without CLI. +- **Effort**: Report template DSL, PDF generation (weasyprint), export logic. + +--- + +#### 20. **Draft family/lineage tracking** [IMPACT: MEDIUM | EFFORT: LARGE] +- **Current**: `pipeline/family.py` exists; tracks draft families (related drafts across revisions/forks) +- **Missing**: Web UI showing draft lineage: + - "This draft evolved from draft-X, also related to draft-Y" + - Family tree visualization + - Automatic linking of superseding drafts +- **Why**: Helps track standardization progress (e.g., "agent-framework-v1 → v2 → RFC"). +- **Effort**: Draft relationship inferencing, visualization. +- **Note**: Requires careful matching logic; already partially implemented in `family.py`. + +--- + +## Data Utilization Gaps + +### Functions computed but not exposed: + +- `get_trends_data()` → only in complexity page +- `get_complexity_data()` → complexity page only +- `get_citation_influence()` → citations page, could be more prominent +- `get_bcp_analysis()` → no dedicated page +- `get_false_positive_profile()` → part of monitor, could be own page +- `get_source_comparison()` → no dedicated page (only API) + +**Quick fix**: Create `/analytics/trends` and `/analytics/sources` pages to expose these. + +--- + +## Navigation & Cross-Linking Issues + +| Page | Outbound links | Gaps | +|------|---|---| +| Draft detail | Gap, Author search, Idea clusters | No: "I'm an author, show all my drafts" link; "Reverse gaps" (gaps this draft addresses) | +| Author network | Co-authors, drafts (search) | No: Author detail page; no link to gaps this author could address | +| Gap detail | Related drafts | No: Reverse link to draft; no "start proposal" CTA | +| Idea cluster | Drafts (list) | No: Individual idea page; no link to related gaps | +| Proposal (admin) | Gaps | No: Public proposal discovery; no related drafts; no standards WG link | +| `/ask` | Search results | No: Persistent query history; no "save search" | + +--- + +## Low-Hanging Fruit (Quick Wins) + +1. ✨ **Add "Source draft" link to idea_detail page** (if page created) — 15 min +2. ✨ **Add readiness score to draft_detail** — 10 min (already computed) +3. ✨ **Show proposals on gap_detail** (reverse link) — 20 min +4. ✨ **Author search in draft_detail → author detail page** (if page created) — 30 min +5. ✨ **Export monitor audit log to CSV** — 15 min + +--- + +## Recommended Priority + +### Phase 1 (Week 1–2): Navigation foundation +1. Author detail page +2. Idea detail page +3. Gap-to-draft reverse links +4. Make proposals public + improve detail page + +### Phase 2 (Week 3–4): Discoverability +5. Category detail pages +6. Search filters (advanced query) +7. Trends/analytics pages + +### Phase 3 (Optional): Depth +8. Idea overlap matrix +9. Citation improvements +10. Obsidian/JSON export + +--- + +## Architecture Notes + +- **DB schema ready**: All needed relationships exist (`draft_refs`, `draft_authors`, `ideas`, `gaps`, `generated_drafts`) +- **Data layer mature**: Most data functions exist in `data.py`; just need exposure +- **Template patterns consistent**: Easy to add new pages following existing conventions (Tailwind + Plotly) +- **CLI-to-web asymmetry**: Some powerful CLI commands (idea-overlap, draft-gen) have no web UI +- **Performance**: Current app uses ~10–15 queries per page load; acceptable for 361 drafts. No N+1 issues observed; caching in place via `_cached()` with 5-min TTL. + +--- + +## Cost/Effort Estimates + +- **Small**: ~15–30 min, no new DB tables, reuses existing functions +- **Medium**: 1–3 hours, possible minor DB updates, new routes + templates +- **Large**: 4+ hours, significant refactoring or new infrastructure (WebSockets, embeddings, auth) + +--- + +**Report compiled by**: UX Scout (Task #3) +**Date**: 2026-03-09 diff --git a/data/reports/architecture-assessment-2026-03-09.md b/data/reports/architecture-assessment-2026-03-09.md new file mode 100644 index 0000000..9083064 --- /dev/null +++ b/data/reports/architecture-assessment-2026-03-09.md @@ -0,0 +1,343 @@ +# IETF Draft Analyzer — Architectural Assessment + +**Date:** 2026-03-09 +**Scope:** Core source code analysis (src/, tests/) +**Project Size:** ~7.6 MB, 19,662 lines of Python + +--- + +## 1. File Sizes and Complexity + +### God Files (largest, highest complexity risk) + +| File | LOC | Severity | Issue | +|------|-----|----------|-------| +| `webui/data.py` | 4,360 | HIGH | Service/data access layer doing too much | +| `cli.py` | 3,438 | MEDIUM | 96 functions, 40+ Click commands, hard to navigate | +| `reports.py` | 2,739 | MEDIUM | Single Reporter class with many report generation methods | +| `db.py` | 1,690 | MEDIUM | 100+ methods, schema + CRUD + business logic mixed | + +### Healthy Modules (< 500 LOC, focused) + +- `models.py` (104 LOC) — Domain models only +- `config.py` (108 LOC) — Configuration with env overrides +- `embeddings.py` (205 LOC) — Ollama embedding wrapper +- `authors.py` (137 LOC) — Author network fetching +- `fetcher.py` (204 LOC) — Datatracker API client + +--- + +## 2. Module Boundaries: Core vs. Web + +### Clean Separation ✓ +- **Core layer (`src/ietf_analyzer/`):** Self-contained, no Flask dependencies +- **Web layer (`src/webui/`):** Depends on core, not vice-versa +- **No circular imports detected** + +### Problem: webui/data.py Violates Single Responsibility + +**What it does:** +1. Wraps `Database` (4,360 lines!) +2. Implements domain logic (clustering, readiness scoring, similarity graphs) +3. Prepares data for JSON/Jinja2 serialization +4. Defines TypedDicts for response shapes +5. Calls `sklearn` for TSNE/hierarchical clustering +6. Builds visualization data (radar, histogram, network graphs) + +**Risk:** Tests only `test_web_data.py` — hard to regression-test domain logic when mixed with presentation layer. + +--- + +## 3. Flask Structure (`app.py`) + +### Routes Count +- **72 functions** (includes helpers) +- **~40+ @app.route()** handlers +- **No blueprints** — monolithic Flask app + +### Route Categories +1. **Overview pages** (5) — `/`, `/landscape`, `/timeline`, `/idea-clusters`, `/ratings` +2. **Detail pages** (6) — `/drafts`, `/drafts/`, `/gaps`, `/gaps/`, etc. +3. **Feature pages** (8) — `/search`, `/ask`, `/compare`, `/monitor`, `/admin/analytics`, etc. +4. **API endpoints** (20+) — `/api/drafts`, `/api/stats`, `/api/search`, `/api/ask`, etc. +5. **Helpers** (5+) — auth, rate limiting, CSV export, DB context + +### Issues + +| Issue | Effort | Impact | +|-------|--------|--------| +| **No blueprint organization** — Mix of concerns (pages, APIs, admin) in one file | SMALL | Makes navigation hard | +| **Tight coupling to data.py** — 50 imports from data.py | SMALL | Hard to refactor data layer | +| **Mixed JSON/HTML rendering** — Some routes render both based on Accept header | SMALL | Should be separate APIs | +| **Admin functions inline** — `/admin/analytics` uses `@admin_required` decorator | SMALL | Should be separate blueprint | + +**Recommendation:** Split into 4 blueprints: +- `blueprints/pages.py` — HTML pages +- `blueprints/api.py` — JSON endpoints +- `blueprints/admin.py` — Admin routes +- `blueprints/helpers.py` — Shared utilities + +--- + +## 4. Database Layer (`db.py`) + +### Structure: Single `Database` Class + +**100+ methods** doing: +1. **Schema definition** — `SCHEMA` constant, `ensure_tables()` +2. **CRUD operations** — `add_draft()`, `update_draft()`, `get_draft()`, `delete_draft()` +3. **Bulk operations** — `add_drafts()`, `update_ratings()` +4. **Complex queries** — `get_drafts_by_category()`, `search_fts()`, `most_cited()`, `co_authors()` +5. **Business logic** — Rating aggregations, clustering, similarity ranking +6. **Cache management** — `llm_cache` table operations +7. **Stats** — `count_drafts()`, `count_by_source()`, aggregations + +### Issues + +| Issue | Evidence | Refactor Effort | +|-------|----------|-----------------| +| **Mixed concerns** | Methods scattered: schema, CRUD, queries, business logic | LARGE | +| **No transaction support** | `add_draft()` does 3+ INSERT statements without explicit tx | MEDIUM | +| **Hard to unit test** | Database class touches 8+ tables; need fixtures for each | MEDIUM | +| **Tight coupling to models** | Direct `Author`, `Draft`, `Rating` dataclass deps | SMALL | +| **No query builders** | Raw SQL in 20+ methods (injection risk if not careful) | MEDIUM | + +### Refactoring Path (4-step) + +```python +# Current: db.Database (100 methods) +# +# Refactored: +# - db.Schema — @dataclass fixtures, schema def (10 methods) +# - db.Repository — CRUD base class (15 methods) +# - db.DraftRepository, AuthorRepository, etc. — domain-specific CRUD +# - db.Queries — Complex queries as static methods or separate class +# - db.Cache — LLM cache operations (10 methods) +``` + +**Effort:** LARGE (4+ hours) +**Benefit:** Testability, reusability, transaction support, easier migrations + +--- + +## 5. Pipeline Architecture (`pipeline/`) + +### Structure: Modular design ✓ +- `context.py` — ContextBuilder (domain logic for draft generation) +- `generator.py` — DraftGenerator (Claude-based content generation) +- `family.py` — Family/relationships logic +- `formatter.py` — Output formatting +- `quality.py` — Quality checks +- `prompts.py` — System prompts +- `PROMPTS` constant — Shared across modules + +### Assessment +✓ **Good separation** — Each module has a single responsibility +✓ **Testable** — Pure functions + dependency injection via Config/Database +✓ **Extensible** — Can add new stages without touching existing code + +**No refactoring needed** for pipeline itself. + +--- + +## 6. Sources Architecture (`sources/`) + +### Structure: Plugin pattern ✓ +- `base.py` — `SourceDocument` dataclass, `SourceFetcher` protocol +- `ietf.py`, `w3c.py`, `etsi.py`, `itu.py`, `iso.py`, `nist.py` — Concrete fetchers + +### Assessment +✓ **Excellent separation** — Base protocol + concrete implementations +✓ **Testable** — Mock fetchers easy to create +✓ **Extensible** — New source = new file, no changes to orchestrator + +**No refactoring needed**. + +--- + +## 7. Config Management (`config.py`) + +### Structure +- Single `Config` dataclass (40 fields) +- `load()` class method with env var override support +- `save()` method to persist to JSON +- Validation in `_validate()` + +### Assessment +✓ **Clean** — Single responsibility +✓ **Testable** — No I/O except file read/write +✓ **Env support** — `_ENV_OVERRIDES` dict maps env vars + +**Minor issue:** Could use structured logging of which env vars override config (currently silent). + +**No refactoring needed** unless config grows beyond 50 fields. + +--- + +## 8. CLI Structure (`cli.py`) + +### Count: 96 functions + +**Command groups:** +- `fetch` — Datatracker + multi-source fetching +- `classify` — Ollama-based pre-filtering +- `list`, `search`, `show`, `annotate` — Draft browsing +- `analyze` — Claude analysis (rate, ideas, gaps) +- `ask`, `compare` — Interactive queries +- `embed`, `embed-ideas` — Ollama embeddings +- `similar`, `clusters` — Embedding-based search +- `report` (group) → `overview`, `landscape`, `digest`, `timeline`, etc. +- `monitor` — Background pipeline automation +- `pipeline`, `pipeline-status`, `pipeline-auto-heal` — Orchestration +- `observatory` — Multi-source dashboard +- `readiness` — Release readiness analysis +- `export` — Generate drafts from gaps +- `web` — Flask app launcher + +### Issues + +| Issue | Severity | Solution | +|-------|----------|----------| +| **96 functions in one file** | MEDIUM | Hard to navigate, should split into subcommands or files | +| **Long help text inline** | LOW | Could use .help-txt files for docstrings | +| **Late imports** | LOW | Some imports inside `@pass_cfg_db` functions to save startup time (OK pattern) | +| **Global console object** | LOW | OK for Click, allows colorized output | + +### Recommended Split (5 files) + +```python +# cli.py (main entry, 200 lines) +# └─ cli/fetching.py (fetch, classify) — 400 lines +# └─ cli/analysis.py (analyze, ask, compare) — 600 lines +# └─ cli/reporting.py (report *, export, observatory) — 800 lines +# └─ cli/admin.py (monitor, pipeline, web) — 400 lines +``` + +**Effort:** SMALL (1 hour) +**Benefit:** Easier to navigate, faster to find commands, clearer testing boundaries. + +--- + +## 9. Circular Dependencies + +### Check Results +**✓ No circular imports detected** + +- `cli.py` → `db`, `config`, `models` ✓ +- `app.py` → `webui.data`, `webui.auth`, `ietf_analyzer.*` ✓ +- `webui.data` → `ietf_analyzer.db`, `ietf_analyzer.config` ✓ +- `db.py` → `models`, `config` ✓ + +--- + +## 10. Test Structure + +**Test files:** 8 modules covering: +- `test_db.py` — Database operations +- `test_analyzer.py` — Claude analysis +- `test_search.py` — Similarity + FTS +- `test_web_data.py` — Data layer for web +- `test_models.py` — Domain models +- `test_obsidian_export.py` — Export + +**Coverage gaps:** +- No tests for `cli.py` (big commands, hard to test without mocking db) +- No tests for `app.py` routes (would need Flask test client + fixtures) +- No tests for pipeline modules (context, generator, family) +- No tests for sources (would need HTTP mocks) + +**Effort to add 30% CLI coverage:** MEDIUM (2-3 hours) + +--- + +## Summary: Refactoring Roadmap + +| Priority | Module | Issue | Effort | Benefit | +|----------|--------|-------|--------|---------| +| **HIGH** | `webui/data.py` | 4,360 LOC, mixed concerns (CRUD + domain logic + presentation) | LARGE | Separates presentation from domain, enables better testing | +| **MEDIUM** | `db.py` | 100 methods, mixed schema/CRUD/logic | LARGE | Testability, transaction support, query builders | +| **MEDIUM** | `cli.py` | 96 functions, hard to navigate | SMALL | Better organization, easier to find commands | +| **MEDIUM** | `app.py` | 40+ routes, no blueprints | SMALL | Clearer structure, easier to refactor | +| **LOW** | Config | 40 fields, working well | NONE | Monitor for growth beyond 50 fields | +| **LOW** | Pipeline/Sources | Well-structured, testable | NONE | No changes needed | + +--- + +## Recommendations (Priority Order) + +### 1. **Extract webui/data.py → presentation + domain** (4 hours) +``` +webui/ +├── data.py (current 4,360 → 1,000) — Only JSON serialization +└── services/ + ├── drafts.py (200) — Draft filtering/sorting logic + ├── analytics.py (400) — Dashboard stats + visualizations + ├── search.py (300) — Search + clustering + └── readiness.py (200) — Readiness scoring +``` +**Cost:** 4 hours +**Benefit:** Can reuse analytics for CLI reports, easier to test + +### 2. **Refactor db.py → Repository pattern** (4 hours) +``` +db/ +├── __init__.py (exports Database facade) +├── schema.py (100) — Schema definition +├── repository.py (200) — Base CRUD class +├── drafts.py (300) — DraftRepository (get, add, update, delete drafts) +├── ratings.py (200) — RatingRepository +├── authors.py (150) — AuthorRepository +├── queries.py (400) — Complex queries (search, similarity, aggregations) +└── cache.py (150) — LLM cache operations +``` +**Cost:** 4 hours +**Benefit:** 80% reduction in method count per class, transaction support, testability + +### 3. **Split cli.py into subcommand groups** (1 hour) +``` +cli/ +├── __init__.py (main entry, ~200 lines) +├── fetching.py (fetch, classify) +├── analysis.py (analyze, ask, compare) +├── reporting.py (report *, export, observatory) +└── admin.py (monitor, pipeline, web) +``` +**Cost:** 1 hour +**Benefit:** Easier to navigate, clearer boundaries, faster to find commands + +### 4. **Convert Flask app to blueprints** (1.5 hours) +``` +webui/ +├── app.py (core Flask setup, ~100 lines) +└── blueprints/ + ├── pages.py (HTML routes: /, /drafts, /landscape, etc.) + ├── api.py (JSON endpoints: /api/*) + ├── admin.py (/admin/*) + └── helpers.py (rate limiting, auth, CSV export) +``` +**Cost:** 1.5 hours +**Benefit:** Clearer separation of concerns, easier to add/remove features + +### 5. **Add CLI tests** (3 hours) +- Mock database for each command +- Test success paths + error cases +- Quick smoke tests for all 15+ major commands + +**Cost:** 3 hours +**Benefit:** Catch regressions early, safe refactoring + +--- + +## Current State Assessment + +| Dimension | Score | Notes | +|-----------|-------|-------| +| **Modularity** | 7/10 | Good separation of concerns; core vs. web clean; webui/data.py is the weak point | +| **Testability** | 6/10 | DB layer hard to unit test; pipeline/sources good; no CLI tests | +| **Maintainability** | 6/10 | Many large files; well-documented; consistent patterns throughout | +| **Extensibility** | 8/10 | Plugin pattern for sources; easy to add new reports; CLI is open-ended | +| **Performance** | 8/10 | Caching, FTS5, lazy imports in CLI; no N+1 queries detected | + +**Overall:** 7/10 — **Production-ready but showing signs of technical debt accumulation.** + +The project is well-organized at a high level but needs refactoring of large monolithic files to stay maintainable as it grows. The webui/data.py and db.py files should be prioritized first. diff --git a/src/webui/app.py b/src/webui/app.py index 0b2f565..b41e96b 100644 --- a/src/webui/app.py +++ b/src/webui/app.py @@ -78,8 +78,17 @@ def create_app(dev: bool = False) -> Flask: return application -# Module-level app instance for backward compatibility (import from webui.app import app) -app = create_app(dev=False) +# Lazy module-level app for backward compatibility (import from webui.app import app) +# Don't eagerly create — let __main__ or the importer control dev mode. +app: Flask | None = None + + +def get_app(dev: bool = False) -> Flask: + """Get or create the app singleton.""" + global app + if app is None: + app = create_app(dev=dev) + return app if __name__ == "__main__": diff --git a/src/webui/auth.py b/src/webui/auth.py index e7a2f36..7c078e7 100644 --- a/src/webui/auth.py +++ b/src/webui/auth.py @@ -19,7 +19,6 @@ from flask import abort, g # Module-level flag set by init_auth() _dev_mode: bool = False -_initialized: bool = False def is_admin() -> bool: @@ -38,14 +37,10 @@ def admin_required(f): def init_auth(app, dev: bool = False): - """Set the auth mode and register Flask hooks (once only).""" - global _dev_mode, _initialized + """Set the auth mode and register Flask hooks.""" + global _dev_mode _dev_mode = dev - if _initialized: - return - _initialized = True - @app.before_request def set_admin_flag(): g.is_admin = is_admin()