# 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 ``` ### Validation Task Create/Edit Form (new) ```html ``` ### 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 ``` ### Dashboard Hub (simplified) ```html