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 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 03:52:02 +01:00
parent dea36e931a
commit 61cdab16b9
4 changed files with 712 additions and 9 deletions

356
FEATURE_BACKLOG.md Normal file
View File

@@ -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/<author_id>`) 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/<idea_id>` page showing:
- Idea title, type, description, novelty score
- Source draft (link)
- 510 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/<category>` 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/<slug>` 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 12): 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 34): 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 ~1015 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**: ~1530 min, no new DB tables, reuses existing functions
- **Medium**: 13 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

View File

@@ -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/<name>`, `/gaps`, `/gaps/<id>`, 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.

View File

@@ -78,8 +78,17 @@ def create_app(dev: bool = False) -> Flask:
return application return application
# Module-level app instance for backward compatibility (import from webui.app import app) # Lazy module-level app for backward compatibility (import from webui.app import app)
app = create_app(dev=False) # 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__": if __name__ == "__main__":

View File

@@ -19,7 +19,6 @@ from flask import abort, g
# Module-level flag set by init_auth() # Module-level flag set by init_auth()
_dev_mode: bool = False _dev_mode: bool = False
_initialized: bool = False
def is_admin() -> bool: def is_admin() -> bool:
@@ -38,14 +37,10 @@ def admin_required(f):
def init_auth(app, dev: bool = False): def init_auth(app, dev: bool = False):
"""Set the auth mode and register Flask hooks (once only).""" """Set the auth mode and register Flask hooks."""
global _dev_mode, _initialized global _dev_mode
_dev_mode = dev _dev_mode = dev
if _initialized:
return
_initialized = True
@app.before_request @app.before_request
def set_admin_flag(): def set_admin_flag():
g.is_admin = is_admin() g.is_admin = is_admin()