Files
ss-tools/specs/017-llm-analysis-plugin/contracts/modules.md
2026-05-31 17:38:43 +03:00

287 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Module Contracts: LLM Analysis Plugin v2
**Feature**: `017-llm-analysis-plugin`
**Updated**: 2026-05-31 — v2: dual-path contracts, URL parsing, dataset health checking
**Protocol**: GRACE-Poly v2.6
## Backend Modules
### Validation Task Manager (new)
```python
# #region ValidationTaskService [C:4] [TYPE Module] [SEMANTICS validation, task, schedule, policy, CRUD]
# @BRIEF CRUD and lifecycle management for ValidationPolicy and ValidationSource.
# @LAYER Service
# @RELATION DEPENDS_ON -> [ValidationPolicy]
# @RELATION DEPENDS_ON -> [ValidationSource]
# @RELATION CALLS -> [SupersetContextExtractor]
# #region create_task [TYPE Function]
# @PURPOSE Create a new validation task with sources (IDs + URLs), provider, prompt, schedule.
# @PRE environment_id resolves to existing environment.
# @POST ValidationPolicy + ValidationSource rows persisted. URL sources parsed via SupersetContextExtractor for preview/validation.
# @SIDE_EFFECT Calls Superset API to resolve dashboard slugs and validate URLs.
# @RATIONALE URL sources are parsed at creation time for validation and preview — user immediately sees dashboard title, filters, tabs. Re-parsing on every execution (per FR-057) guarantees up-to-date permalinks and filter state.
# @REJECTED URL parsing only at creation time (without execution-time re-parse) rejected — permalinks expire, dashboards get renamed, filters change. Creation-time parse = preview; execution-time re-parse = authoritative resolution.
# #endregion create_task
# #region parse_dashboard_url [TYPE Function]
# @PURPOSE Resolve a Superset dashboard URL to structured context using SupersetContextExtractor.
# @PRE url is a valid absolute Superset dashboard URL.
# @POST Returns {dashboard_id, title, native_filters, activeTabs, anchor, partial_recovery, warnings}.
# @RELATION CALLS -> [SupersetContextExtractor.parse_superset_link]
# @SIDE_EFFECT Calls Superset API for permalink resolution and slug→ID lookup.
# #endregion parse_dashboard_url
# #endregion ValidationTaskService
```
### Dashboard Validation Plugin (refactored)
```python
# #region DashboardValidationPlugin [C:5] [TYPE Module] [SEMANTICS validation, plugin, dual-path, llm, screenshot, text-only]
# @BRIEF Orchestrates dashboard validation via Path A (Playwright screenshot) or Path B (text-only API).
# @LAYER Plugin
# @RELATION IMPLEMENTS -> [PluginBase]
# @RELATION CALLS -> [ScreenshotService] # Path A only
# @RELATION CALLS -> [LLMClient] # Both paths
# @RELATION CALLS -> [SupersetClient] # Both paths
# @RELATION CALLS -> [DatasetHealthChecker] # Path B only
# @RELATION DEPENDS_ON -> [TaskContext]
# @INVARIANT Execution path (A/B) is determined by `screenshot_enabled` flag — never mixed within one run.
# @INVARIANT Path B MUST check every unique dataset referenced by dashboard charts.
# @RATIONALE Two paths with different prompts and different data sources for LLM: screenshot path for visual issues, text path for data/KXD issues. Both paths serve different use cases.
# @REJECTED Single «screenshot + text» prompt rejected — mixes two different analysis modalities, increases request size, complicates LLM response parsing.
# #region execute [TYPE Function] [C:4]
# @PURPOSE Execute one validation run for a dashboard from ValidationPolicy.
# @PRE params contains dashboard_id, provider_id, screenshot_enabled, prompt_template, logs_enabled.
# @POST ValidationRecord persisted with status, issues, timings.
# @SIDE_EFFECT Launches browser (Path A), calls Superset API (both paths), calls LLM API.
# #endregion execute
# #region _execute_path_a [TYPE Function] [C:3]
# @PURPOSE Path A: Playwright screenshot → multi-chunk capture → multimodal LLM.
# @PRE dashboard_id is valid, provider is multimodal, screenshot_enabled=true.
# @POST Returns {status, summary, issues, screenshot_paths, tab_screenshots, token_usage}.
# @SIDE_EFFECT Launches browser, logs in, switches tabs, captures CDP screenshots.
# @RATIONALE Multi-chunk screenshots (one per tab) replace single full-page — LLM sees each tab at high resolution instead of one compressed full-dashboard image.
# @REJECTED Single full-page screenshot rejected — long dashboards (5000+px) compress to unreadable size (D9 in spec.md).
# #endregion _execute_path_a
# #region _execute_path_b [TYPE Function] [C:3]
# @PURPOSE Path B: API calls → dashboard structure + chart params + dataset health + logs → text-only LLM.
# @PRE screenshot_enabled=false.
# @POST Returns {status, summary, issues, dataset_health, chart_data_results, token_usage}.
# @SIDE_EFFECT Calls Superset API (dashboard, chart, dataset, chart/data, log endpoints).
# @RATIONALE Dataset health checking is mandatory — the purpose of validation is to verify the dashboard opens correctly with KXD data. Without dataset checks, silent KXD errors (connection refused, timeout) are invisible.
# @REJECTED Logs-only checking without dataset health rejected — logs show past errors but don't guarantee current KXD connectivity state.
# #endregion _execute_path_b
# #endregion DashboardValidationPlugin
```
### Screenshot Service (Path A — refactored for multi-chunk)
```python
# #region ScreenshotService [C:4] [TYPE Module] [SEMANTICS screenshot, playwright, cdp, tab-switching, multi-chunk]
# @BRIEF Captures per-tab dashboard screenshots via Playwright + CDP.
# @LAYER Plugin
# @INVARIANT Each tab produces a separate high-resolution screenshot (max 1920×1200 per chunk).
# @INVARIANT Multi-chunk: one screenshot per tab, NOT one full-page.
# @RATIONALE Многочанковый подход даёт LLM детальные скриншоты каждого таба вместо одного сжатого full-page.
# @REJECTED Single full-page CDP screenshot отвергнут — D9: длинные дашборды теряют читаемость.
# #region capture_dashboard_chunks [TYPE Function] [C:3]
# @PURPOSE Capture per-tab screenshots: login → navigate → switch tabs → per-tab CDP screenshots.
# @PRE dashboard_id is valid, browser available.
# @POST Returns list of {tab_name, screenshot_path}.
# @SIDE_EFFECT Launches browser, logs in, switches tabs.
# #endregion capture_dashboard_chunks
# #endregion ScreenshotService
```
### Dataset Health Checker (Path B — new)
```python
# #region DatasetHealthChecker [C:3] [TYPE Module] [SEMANTICS dataset, health, kxd, api, validation]
# @BRIEF Checks dataset accessibility and KXD connectivity via Superset API.
# @LAYER Service
# @RELATION CALLS -> [SupersetClient]
# @INVARIANT Every unique dataset referenced by dashboard charts is checked.
# @RATIONALE Без проверки датасетов невозможно определить, может ли дашборд корректно открыться с данными из КХД. Скриншот показывает визуал, но не показывает, что данные пришли (а не кэш).
# #region check_dataset_health [TYPE Function]
# @PURPOSE Fetch dataset schema and verify accessibility.
# @PRE dataset_id is a valid Superset dataset ID.
# @POST Returns DatasetHealth: {accessible, database, backend, kind, error}.
# @SIDE_EFFECT Calls GET /api/v1/dataset/{id}.
# #endregion check_dataset_health
# #region check_dashboard_datasets [TYPE Function]
# @PURPOSE For every unique dataset in dashboard charts, check health and optionally execute chart data.
# @PRE dashboard_id is valid, chart list available.
# @POST Returns {datasets: [DatasetHealth], chart_data: [ChartDataResult]}.
# @SIDE_EFFECT Calls GET /api/v1/dataset/{id} × N, optionally POST /api/v1/chart/data × M.
# #endregion check_dashboard_datasets
# #endregion DatasetHealthChecker
```
### LLM Client (updated for dual-path)
```python
# #region LLMClient [C:4] [TYPE Module] [SEMANTICS llm, api, multimodal, text-only, json-mode]
# @BRIEF LLM provider wrapper supporting both multimodal (Path A) and text-only (Path B) analysis.
# @LAYER Infrastructure
# @RELATION DEPENDS_ON -> [EXT:Library:openai]
# @RELATION DEPENDS_ON -> [EXT:Library:tenacity]
# @INVARIANT API keys never logged in plain text.
# @RATIONALE Единый клиент для обоих путей — различия только в формате messages (multimodal vs text-only) и промпте.
# #region analyze_dashboard_multimodal [TYPE Function]
# @PURPOSE Path A: send multi-chunk screenshots + logs to multimodal LLM.
# @PRE screenshot_paths is non-empty list.
# @POST Returns structured JSON {status, summary, issues}.
# #endregion analyze_dashboard_multimodal
# #region analyze_dashboard_text [TYPE Function]
# @PURPOSE Path B: send dashboard topology + dataset health + logs to text-only LLM.
# @PRE dashboard_structure and dataset_health are populated.
# @POST Returns structured JSON {status, summary, issues}.
# @RATIONALE Текстовый промпт включает: описание топологии дашборда (табы, чарты, параметры), статус датасетов (доступность, бэкенд, ошибки), логи выполнения. LLM анализирует консистентность данных, а не визуал.
# #endregion analyze_dashboard_text
# #endregion LLMClient
```
### Provider Bindings (updated)
```python
# #region DEFAULT_LLM_PROVIDER_BINDINGS [C:1] [TYPE Constant]
# @BRIEF Provider bindings retained: documentation, git_commit, assistant_planner.
# @RATIONALE dashboard_validation binding удалён — провайдер выбирается per-task в форме создания задачи (D6, FR-047).
# #endregion DEFAULT_LLM_PROVIDER_BINDINGS
```
### Prompt Defaults (updated)
```python
# #region DEFAULT_LLM_PROMPTS [C:2] [TYPE Constant]
# @BRIEF Path-specific validation prompts: multimodal (Path A) and text (Path B).
# Legacy dashboard_validation_prompt retained for v1 compat, MUST NOT be used as default for new tasks.
# @RATIONALE FR-048: null prompt_template → auto-select path-specific default.
# Single prompt field per task avoids the risk of Path A using text prompt or vice versa.
# #endregion DEFAULT_LLM_PROMPTS
```
## Frontend Components
### Validation Task List Page (new)
```html
<!-- #region ValidationTasksPage [C:3] [TYPE Page] [SEMANTICS sveltekit, validation, task-list, schedule]
<!-- @BRIEF Page: /validation-tasks — list of all validation tasks with status, schedule, last run.
<!-- @LAYER Page -->
<!-- @RELATION CALLS -> /api/validation-tasks -->
<!-- @UX_STATE Loading -> Skeleton table rows -->
<!-- @UX_STATE Loaded -> Task cards/rows with status badges, schedule, dashboard count -->
<!-- @UX_STATE Empty -> "No validation tasks yet" + "Create First Task" CTA -->
```
### Validation Task Create/Edit Form (new)
```html
<!-- #region ValidationTaskForm [C:4] [TYPE Component] [SEMANTICS svelte, form, validation, task-create, url-parser]
<!-- @BRIEF Multi-step form: name → sources (IDs + URLs) → provider + prompt → schedule.
<!-- @LAYER Component -->
<!-- @RELATION CALLS -> /api/llm/providers (list multimodal providers) -->
<!-- @RELATION CALLS -> /api/validation-tasks/parse-url (URL validation) -->
<!-- @UX_STATE Idle -> Empty form -->
<!-- @UX_STATE Parsing URL -> Spinner on URL input; result banner (dashboard title, filters count) -->
<!-- @UX_STATE Submitting -> All fields disabled, progress bar -->
<!-- @UX_STATE Error -> Field-level errors, URL parse failure banner -->
<!-- @UX_FEEDBACK URL parse success: green banner with dashboard title, native_filters count, activeTabs -->
<!-- @UX_FEEDBACK Provider selector filters multimodal-only when screenshot_enabled=true -->
```
### Validation Run (new — groups per-dashboard results)
```python
# #region ValidationRun [C:3] [TYPE Model] [SEMANTICS run, grouping, aggregate, execution]
# @BRIEF Groups ValidationRecords from one task execution (manual or scheduled).
# One run = N dashboards validated. Aggregate counts computed on finalize.
# @INVARIANT Every ValidationRecord belongs to exactly one ValidationRun.
# @RATIONALE Without run grouping, a 15-dashboard task produces 15 disconnected records — no way to see aggregate status or link them as one execution unit.
# Run groups them for reporting: "3 PASS, 1 WARN, 0 FAIL across 4 dashboards".
# #endregion ValidationRun
```
### Validation Run Detail Page (updated for multi-dashboard)
```html
<!-- #region ValidationRunDetailPage [C:3] [TYPE Page] [SEMANTICS sveltekit, run, report, multi-dashboard, aggregate]
<!-- @BRIEF Page: /validation-tasks/{policyId}/runs/{runId} — multi-dashboard run report.
<!-- Run header with aggregate counts → collapsible dashboard list → per-dashboard detail (issues, screenshots/dataset-health, logs).
<!-- @LAYER Page -->
<!-- @RELATION CALLS -> GET /api/validation-tasks/{policy_id}/runs/{run_id} -->
<!-- @UX_STATE Loading -> Skeleton placeholders -->
<!-- @UX_STATE Loaded -> Run header + dashboard list with expand/collapse -->
<!-- @UX_STATE Expanded -> Per-dashboard: Path A screenshots (tab selector) or Path B dataset health table -->
<!-- @UX_STATE Mixed -> Single run may contain both Path A and Path B dashboards — sections render per dashboard -->
<!-- @RATIONALE 15-dashboard run: collapse keeps page scannable. Expand for drill-down. Aggregate counts at top give instant health overview.
```
### Legacy Report Redirect
```python
# #region LegacyReportRedirect [C:1] [TYPE Route]
# @BRIEF GET /reports/llm/{taskId} → 302 redirect to canonical /validation-tasks/{policy_id}/runs/{record_id}.
# @RATIONALE Backward compat for v1 notification links, external bookmarks, and assistant-generated URLs.
# Canonical URL is per FR-055b. Returns 404 if taskId has no matching ValidationRecord.
# #endregion LegacyReportRedirect
```
### Dashboard Validation Indicator (new)
```html
<!-- #region DashboardValidationIndicator [C:2] [TYPE Component] [SEMANTICS svelte, dashboard, validation, indicator, batch]
<!-- @BRIEF Colored dot indicator (PASS/WARN/FAIL/UNKNOWN) next to dashboard title in hub rows.
<!-- Click → dropdown: last 3 runs (date, status, task name) + "View all" link.
<!-- @LAYER Component -->
<!-- @RELATION CALLS -> GET /api/dashboards/{env_id}/validation-status?dashboard_ids=... -->
<!-- @RATIONALE Replaces removed LLM column without adding visual noise.
<!-- Batch endpoint avoids N+1 requests for dashboard hub with 50+ rows.
```
### ValidationTaskReport (new component)
```html
<!-- #region ValidationTaskReport [C:3] [TYPE Component] [SEMANTICS svelte, report, validation, issues, dataset-health]
<!-- @BRIEF Shared report component: check result badge, summary, issues table, execution path indicator, dataset health (Path B), screenshots (Path A).
<!-- @LAYER Component -->
```
### Dashboard Hub (simplified)
```html
<!-- #region DashboardHub [C:4] [TYPE Page] [SEMANTICS sveltekit, dashboard, hub]
<!-- @BRIEF LLM validation column REMOVED. "Validate" action REMOVED from action dropdown.
<!-- Added "Create Validation Task" button in toolbar that pre-fills selected dashboard IDs.
<!-- @RATIONALE D1, FR-051, FR-052: validation moves to task-based flow; ad-hoc "Validate" is deprecated.
```
### LLM Settings (simplified)
```html
<!-- #region LLMSettingsPage [C:3] [TYPE Page] [SEMANTICS sveltekit, settings, llm, provider]
<!-- @BRIEF Provider bindings for dashboard_validation REMOVED.
<!-- Retained: documentation, git_commit, assistant_planner provider bindings.
<!-- @RATIONALE D8, FR-049: dashboard validation provider is per-task, not global.
```
## ADR Continuity
| ADR | Impact |
|-----|--------|
| ADR-0001 (module layout) | v2 changes stay within `backend/src/plugins/llm_analysis/` and `frontend/src/routes/validation-tasks/` |
| ADR-0004 (plugin architecture) | `DashboardValidationPlugin` remains a PluginBase implementation; new `DatasetHealthChecker` is a service, not a plugin |
| ADR-0009 (SSL) | Unchanged — LLM client SSL configuration applies to both Path A and Path B |
| ADR-0008 (assistant tool registry) | `_tool_llm_validation.py` — assistant tool should be updated to create task instead of triggering ad-hoc validation |