# Research: LLM Analysis & Documentation Plugins v2

**Feature**: `017-llm-analysis-plugin`
**Updated**: 2026-05-31 — v2: dual-path execution, multi-chunk screenshots, dataset health checking, URL-based dashboards

## v2 Research Decisions

### 8. Task-Based Flow (replaces ad-hoc Validate)

**Decision**: Remove one-click «Validate» button; replace with «Create Validation Task» flow (name → sources → provider+prompt → schedule).

**Rationale**: Ad-hoc validation doesn't scale — users need repeatable, scheduled checks with full configuration control. The task list provides visibility into what's running and historical trends.

**Alternatives Considered**:
- Keep Validate button + add scheduling separately — rejected: two modes cause confusion; task-based is cleaner.
- Inline validation from dashboard detail — rejected: no scheduling, no multi-dashboard runs.

### 9. Dual Execution Paths (Screenshot vs Text-Only)

**Decision**: `screenshot_enabled` flag controls execution path:
- **Path A** (screenshot_enabled=true): Playwright → multi-chunk CDP screenshots → multimodal LLM
- **Path B** (screenshot_enabled=false): API calls → dashboard topology + chart params + dataset health + logs → text-only LLM

**Rationale**:
- Screenshot catches visual bugs (empty charts, overlap, rendering) but costs image tokens and requires a browser.
- Text-only catches data/KXD errors (dataset inaccessible, chart query timeout) and is lightweight.
- Together they cover both visual and data-layer failure modes. Users choose per task based on what matters.

**Alternatives Considered**:
- Always screenshot — rejected: expensive, slow, doesn't check dataset backend health.
- Always text-only — rejected: misses visual rendering bugs (broken charts with no error in logs).
- Hybrid: screenshot + dataset check in one call — rejected: mixes modalities, increases LLM context size, complicates prompt.

### 10. Multi-Chunk Screenshots (Path A)

**Decision**: Instead of one compressed full-page screenshot, capture each dashboard tab as a separate high-resolution screenshot (max 1920×1200 per chunk). Send all chunks in a single multimodal LLM request.

**Rationale**: Long dashboards (5000+ px) compressed to 1024×2048 lose readability — small chart labels become unreadable. Per-tab chunks preserve resolution, and LLM sees the dashboard in the same logical sections a human would.

**Alternatives Considered**:
- Single full-page at 4096px width — rejected: token cost too high for image processing.
- Overlapping chunks with stitching — rejected: complex, fragile to viewport changes.
- Per-chart screenshots — rejected: too many images (30+ charts → excessive tokens), loses layout context.

### 11. Dataset Health Checking (Path B)

**Decision**: For Path B, call `GET /api/v1/dataset/{id}` for every unique dataset used by dashboard charts. Verify: HTTP 200, database backend, kind (virtual/physical). Optionally execute `POST /api/v1/chart/data` (off by default).

**Rationale**: The primary purpose of LLM validation is verifying the KXD→dataset→chart→dashboard chain works. Screenshot-only misses silent data failures (empty results, partial data, wrong time range). Dataset health check catches KXD connection errors that produce no visual error but return empty/incomplete data.

**Alternatives Considered**:
- Logs-only (no dataset API calls) — rejected: logs show past failures, not current KXD connectivity. A dataset can be broken right now with no recent log entry.
- Always execute chart data — rejected: overloads Superset (15 charts × 30 dashboards = 450 queries). Optional toggle lets users enable selectively.

### 12. URL-Based Dashboard Selection

**Decision**: Support pasting full Superset dashboard URLs as task sources. Parse via `SupersetContextExtractor` (same code path as dataset review feature). Extract: dashboard_id, native_filters, activeTabs, anchor.

**Rationale**: Users copy dashboard URLs from browser with filters already applied. Parsing the URL preserves the exact filter/tab state — the validation checks the dashboard in the same state the user sees. Reusing `SupersetContextExtractor` avoids duplicating URL parsing logic.

**URL Formats Supported** (same as dataset review):
- `https://superset.example.com/superset/dashboard/42/` — numeric ID
- `https://superset.example.com/superset/dashboard/my-dashboard/` — slug
- `https://superset.example.com/superset/dashboard/p/abc123/` — permalink
- `https://superset.example.com/superset/dashboard/42/?native_filters_key=xyz` — filter state key

**Alternatives Considered**:
- Dashboard ID only — rejected: loses filter context, user must manually re-apply filters.
- Custom URL format — rejected: users already have Superset URLs in browser; pasting them is natural.

### 13. Provider & Prompt Per-Task

**Decision**: Provider selection and prompt template configuration move from global LLM settings to per-task configuration in the task creation form. Global LLM settings retain provider bindings only for: documentation, git_commit, assistant_planner.

**Rationale**: Different dashboards may need different models (e.g., GPT-4o for complex visual analysis, a cheaper model for text-only). Different teams may want different prompt emphasis (performance vs. data quality). Global settings are too coarse.

**Alternatives Considered**:
- Global binding + per-task override — rejected: adds complexity; per-task is simpler and more flexible.
- Environment-level provider binding — rejected: too coarse; same environment may have different validation needs per dashboard group.

## v1 Research Decisions (preserved)

### 1. LLM Provider Integration

**Decision**: Use a unified `LLMProviderService` that abstracts OpenAI-compatible APIs.
**Rationale**: OpenRouter, Kilo, LiteLLM, and OpenAI all support the standard OpenAI API format.

### 2. Dashboard Screenshot Capture

**Decision**: `ScreenshotService` with Playwright + CDP for Path A.
**Rationale**: Provides accurate «user-view» render; CDP avoids font loading timeouts in headless mode.

### 3. Multimodal Analysis Prompting

**Decision**: Structured prompt template accepting base64-encoded images + text logs.
**Rationale**: GPT-4o / Claude / Gemini models support this natively.

### 4. Documentation Persistence

**Decision**: Update Dataset/Column models in metadata database.
**Rationale**: Documentation co-located with assets.

### 5. Git Commit Integration

**Decision**: REST endpoint `/api/git/generate-message`.
**Rationale**: Heavy lifting (LLM, diff processing) on backend.

### 6. Security & Storage

**Decision**: AES-256 encrypted API keys.
**Rationale**: Keys must not be stored in plain text.

### 7. Retry Logic

**Decision**: `tenacity` with exponential backoff (5 attempts, 5-60s wait).
**Rationale**: Standard, robust retry for transient LLM API failures.

### 14. Image Pipeline: PNG → JPEG (LLM) + WebP (Archive)

**Decision**: CDP captures PNG (browser-native, lossless). Convert to JPEG quality=60 for LLM (universal provider compatibility). Convert to WebP quality=80 for archive (93% disk savings vs PNG). Delete PNG and JPEG intermediates after successful save.

**Rationale**: JPEG universally supported by all providers. WebP has silent failure risk on unknown/closed providers (model accepts request but doesn't process image → false PASS). Pipeline decoupled: LLM failure doesn't affect archive; archive failure falls back to PNG with WARN.

**Alternatives Considered**:
- All WebP (LLM + archive) — rejected: silent failure risk.
- All JPEG (LLM + archive) — rejected: no archive space savings, re-encoding artifacts.
- WebP for LLM with `_supports_webp()` per-provider check — rejected: adds complexity; JPEG is simpler and universally safe.

**Impact**: T017a, T017b, T017c in task plan. FR-059 in spec. D10 design decision. ~93% disk savings vs PNG archive.

### 15. Path B Batching (experimental v2.1)

**Decision**: Default `llm_batch_size=1` (full isolation, one LLM call per dashboard). Values >1 are experimental and reduce cost at risk of cross-dashboard context contamination.

**Rationale**: Multiple dashboards in one LLM call saves API calls (5→1) but risks the LLM confusing which issue belongs to which dashboard. Structured prompt with per-dashboard labeled sections + strict JSON `{dashboards: [{dashboard_id, ...}]}` schema mitigates but does not eliminate this risk. For validation (where accuracy > cost), isolation is the safer default.

**Alternatives Considered**:
- Always batch (default 5) — rejected: accuracy risk for validation feature. Can't guarantee LLMs won't cross-contaminate context.
- Never batch (always 1) — rejected: legitimate use case for large fleet monitoring where cost matters more than per-dashboard precision.
- Per-policy toggle only — accepted as the right balance: default safe, user opts into batching when they understand the tradeoff.

**Impact**: FR-045a in spec. `llm_batch_size` field in ValidationPolicy, default 1. Batch_size >1 is documented as experimental with known accuracy risk.
