semantics: complete DEF-to-region migration, fix regressions

- Convert legacy [DEF🆔Type] anchors to #region/#endregion across 329 files
- Reinstate _normalize_timestamp_value in sql_generator.py
- Fix MarkerLogger→logger migration in events.py (molecular CoT markers)
- Fix dataset_review orchestrator dependencies (_build_execution_snapshot)
- Fix config_manager stale-record deletion (moved to save path only)
- Add 77 missing [/DEF:] closers in 5 unbalanced test files
- Update assistant_chat.integration.test.js for #region format
- Apply molecular-cot-logging markers (REASON/REFLECT/EXPLORE) via logger.* methods
This commit is contained in:
2026-05-12 23:54:55 +03:00
parent fe8978f716
commit 306c5ae742
331 changed files with 9630 additions and 10312 deletions

View File

@@ -1,4 +1,4 @@
# #region backend/src/plugins/llm_analysis/__init__.py [TYPE Module]
# [DEF:backend/src/plugins/llm_analysis/__init__.py:Module]
"""
LLM Analysis Plugin for automated dashboard validation and dataset documentation.
@@ -8,4 +8,4 @@ from .plugin import DashboardValidationPlugin, DocumentationPlugin
__all__ = ['DashboardValidationPlugin', 'DocumentationPlugin']
# #endregion backend/src/plugins/llm_analysis/__init__.py
# [/DEF:backend/src/plugins/llm_analysis/__init__.py:Module]

View File

@@ -1,16 +1,18 @@
# #region TestClientHeaders [C:3] [TYPE Module] [SEMANTICS tests, llm-client, openrouter, headers]
# @BRIEF Verify OpenRouter client initialization includes provider-specific headers.
# @RELATION BELONGS_TO -> [SrcRoot]
# [DEF:TestClientHeaders:Module]
# @RELATION: BELONGS_TO -> SrcRoot
# @COMPLEXITY: 3
# @SEMANTICS: tests, llm-client, openrouter, headers
# @PURPOSE: Verify OpenRouter client initialization includes provider-specific headers.
from src.plugins.llm_analysis.models import LLMProviderType
from src.plugins.llm_analysis.service import LLMClient
# #region test_openrouter_client_includes_referer_and_title_headers [TYPE Function]
# @BRIEF OpenRouter requests should carry site/app attribution headers for compatibility.
# @PRE Client is initialized for OPENROUTER provider.
# @POST Async client headers include Authorization, HTTP-Referer, and X-Title.
# @RELATION BINDS_TO -> [TestClientHeaders]
# [DEF:test_openrouter_client_includes_referer_and_title_headers:Function]
# @RELATION: BINDS_TO -> TestClientHeaders
# @PURPOSE: OpenRouter requests should carry site/app attribution headers for compatibility.
# @PRE: Client is initialized for OPENROUTER provider.
# @POST: Async client headers include Authorization, HTTP-Referer, and X-Title.
def test_openrouter_client_includes_referer_and_title_headers(monkeypatch):
monkeypatch.setenv("OPENROUTER_SITE_URL", "http://localhost:8000")
monkeypatch.setenv("OPENROUTER_APP_NAME", "ss-tools-test")
@@ -26,5 +28,5 @@ def test_openrouter_client_includes_referer_and_title_headers(monkeypatch):
assert headers["Authorization"] == "Bearer sk-test-provider-key-123456"
assert headers["HTTP-Referer"] == "http://localhost:8000"
assert headers["X-Title"] == "ss-tools-test"
# #endregion test_openrouter_client_includes_referer_and_title_headers
# #endregion TestClientHeaders
# [/DEF:test_openrouter_client_includes_referer_and_title_headers:Function]
# [/DEF:TestClientHeaders:Module]

View File

@@ -1,17 +1,19 @@
# #region TestScreenshotService [C:3] [TYPE Module] [SEMANTICS tests, screenshot-service, navigation, timeout-regression]
# @BRIEF Protect dashboard screenshot navigation from brittle networkidle waits.
# @RELATION VERIFIES -> [src.plugins.llm_analysis.service.ScreenshotService]
# [DEF:TestScreenshotService:Module]
# @RELATION: VERIFIES ->[src.plugins.llm_analysis.service.ScreenshotService]
# @COMPLEXITY: 3
# @SEMANTICS: tests, screenshot-service, navigation, timeout-regression
# @PURPOSE: Protect dashboard screenshot navigation from brittle networkidle waits.
import pytest
from src.plugins.llm_analysis.service import ScreenshotService
# #region test_iter_login_roots_includes_child_frames [TYPE Function]
# @BRIEF Login discovery must search embedded auth frames, not only the main page.
# @PRE Page exposes child frames list.
# @POST Returned roots include page plus child frames in order.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_iter_login_roots_includes_child_frames:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Login discovery must search embedded auth frames, not only the main page.
# @PRE: Page exposes child frames list.
# @POST: Returned roots include page plus child frames in order.
def test_iter_login_roots_includes_child_frames():
frame_a = object()
frame_b = object()
@@ -23,14 +25,14 @@ def test_iter_login_roots_includes_child_frames():
assert roots == [fake_page, frame_a, frame_b]
# #endregion test_iter_login_roots_includes_child_frames
# [/DEF:test_iter_login_roots_includes_child_frames:Function]
# #region test_response_looks_like_login_page_detects_login_markup [TYPE Function]
# @BRIEF Direct login fallback must reject responses that render the login screen again.
# @PRE Response body contains stable login-page markers.
# @POST Helper returns True so caller treats fallback as failed authentication.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_response_looks_like_login_page_detects_login_markup:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Direct login fallback must reject responses that render the login screen again.
# @PRE: Response body contains stable login-page markers.
# @POST: Helper returns True so caller treats fallback as failed authentication.
def test_response_looks_like_login_page_detects_login_markup():
service = ScreenshotService(env=type("Env", (), {})())
@@ -50,14 +52,14 @@ def test_response_looks_like_login_page_detects_login_markup():
assert result is True
# #endregion test_response_looks_like_login_page_detects_login_markup
# [/DEF:test_response_looks_like_login_page_detects_login_markup:Function]
# #region test_find_first_visible_locator_skips_hidden_first_match [TYPE Function]
# @BRIEF Locator helper must not reject a selector collection just because its first element is hidden.
# @PRE First matched element is hidden and second matched element is visible.
# @POST Helper returns the second visible candidate.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_find_first_visible_locator_skips_hidden_first_match:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Locator helper must not reject a selector collection just because its first element is hidden.
# @PRE: First matched element is hidden and second matched element is visible.
# @POST: Helper returns the second visible candidate.
@pytest.mark.anyio
async def test_find_first_visible_locator_skips_hidden_first_match():
class _FakeElement:
@@ -91,14 +93,14 @@ async def test_find_first_visible_locator_skips_hidden_first_match():
assert result.label == "visible"
# #endregion test_find_first_visible_locator_skips_hidden_first_match
# [/DEF:test_find_first_visible_locator_skips_hidden_first_match:Function]
# #region test_submit_login_via_form_post_uses_browser_context_request [TYPE Function]
# @BRIEF Fallback login must submit hidden fields and credentials through the context request cookie jar.
# @PRE Login DOM exposes csrf hidden field and request context returns authenticated HTML.
# @POST Helper returns True and request payload contains csrf_token plus credentials plus request options.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_submit_login_via_form_post_uses_browser_context_request:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Fallback login must submit hidden fields and credentials through the context request cookie jar.
# @PRE: Login DOM exposes csrf hidden field and request context returns authenticated HTML.
# @POST: Helper returns True and request payload contains csrf_token plus credentials plus request options.
@pytest.mark.anyio
async def test_submit_login_via_form_post_uses_browser_context_request():
class _FakeInput:
@@ -202,14 +204,14 @@ async def test_submit_login_via_form_post_uses_browser_context_request():
]
# #endregion test_submit_login_via_form_post_uses_browser_context_request
# [/DEF:test_submit_login_via_form_post_uses_browser_context_request:Function]
# #region test_submit_login_via_form_post_accepts_authenticated_redirect [TYPE Function]
# @BRIEF Fallback login must treat non-login 302 redirect as success without waiting for redirect target.
# @PRE Request response is 302 with Location outside login path.
# @POST Helper returns True.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_submit_login_via_form_post_accepts_authenticated_redirect:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Fallback login must treat non-login 302 redirect as success without waiting for redirect target.
# @PRE: Request response is 302 with Location outside login path.
# @POST: Helper returns True.
@pytest.mark.anyio
async def test_submit_login_via_form_post_accepts_authenticated_redirect():
class _FakeInput:
@@ -277,14 +279,14 @@ async def test_submit_login_via_form_post_accepts_authenticated_redirect():
assert result is True
# #endregion test_submit_login_via_form_post_accepts_authenticated_redirect
# [/DEF:test_submit_login_via_form_post_accepts_authenticated_redirect:Function]
# #region test_submit_login_via_form_post_rejects_login_markup_response [TYPE Function]
# @BRIEF Fallback login must fail when POST response still contains login form content.
# @PRE Login DOM exposes csrf hidden field and request response renders login markup.
# @POST Helper returns False.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_submit_login_via_form_post_rejects_login_markup_response:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Fallback login must fail when POST response still contains login form content.
# @PRE: Login DOM exposes csrf hidden field and request response renders login markup.
# @POST: Helper returns False.
@pytest.mark.anyio
async def test_submit_login_via_form_post_rejects_login_markup_response():
class _FakeInput:
@@ -360,14 +362,14 @@ async def test_submit_login_via_form_post_rejects_login_markup_response():
assert result is False
# #endregion test_submit_login_via_form_post_rejects_login_markup_response
# [/DEF:test_submit_login_via_form_post_rejects_login_markup_response:Function]
# #region test_goto_resilient_falls_back_from_domcontentloaded_to_load [TYPE Function]
# @BRIEF Pages with unstable primary wait must retry with fallback wait strategy.
# @PRE First page.goto call raises; second succeeds.
# @POST Helper returns second response and attempts both wait modes in order.
# @RELATION BINDS_TO -> [TestScreenshotService]
# [DEF:test_goto_resilient_falls_back_from_domcontentloaded_to_load:Function]
# @RELATION: BINDS_TO ->[TestScreenshotService]
# @PURPOSE: Pages with unstable primary wait must retry with fallback wait strategy.
# @PRE: First page.goto call raises; second succeeds.
# @POST: Helper returns second response and attempts both wait modes in order.
@pytest.mark.anyio
async def test_goto_resilient_falls_back_from_domcontentloaded_to_load():
class _FakePage:
@@ -398,5 +400,5 @@ async def test_goto_resilient_falls_back_from_domcontentloaded_to_load():
]
# #endregion test_goto_resilient_falls_back_from_domcontentloaded_to_load
# #endregion TestScreenshotService
# [/DEF:test_goto_resilient_falls_back_from_domcontentloaded_to_load:Function]
# [/DEF:TestScreenshotService:Module]

View File

@@ -1,6 +1,8 @@
# #region TestService [C:3] [TYPE Module] [SEMANTICS tests, llm-analysis, fallback, provider-error, unknown-status]
# @BRIEF Verify LLM analysis transport/provider failures do not masquerade as dashboard FAIL results.
# @RELATION BELONGS_TO -> [SrcRoot]
# [DEF:TestService:Module]
# @RELATION: BELONGS_TO -> SrcRoot
# @COMPLEXITY: 3
# @SEMANTICS: tests, llm-analysis, fallback, provider-error, unknown-status
# @PURPOSE: Verify LLM analysis transport/provider failures do not masquerade as dashboard FAIL results.
import pytest
@@ -8,11 +10,11 @@ from src.plugins.llm_analysis.models import LLMProviderType
from src.plugins.llm_analysis.service import LLMClient
# #region test_test_runtime_connection_uses_json_completion_transport [TYPE Function]
# @BRIEF Provider self-test must exercise the same chat completion transport as runtime analysis.
# @PRE get_json_completion is available on initialized client.
# @POST Self-test forwards a lightweight user message into get_json_completion and returns its payload.
# @RELATION BINDS_TO -> [TestService]
# [DEF:test_test_runtime_connection_uses_json_completion_transport:Function]
# @RELATION: BINDS_TO -> TestService
# @PURPOSE: Provider self-test must exercise the same chat completion transport as runtime analysis.
# @PRE: get_json_completion is available on initialized client.
# @POST: Self-test forwards a lightweight user message into get_json_completion and returns its payload.
@pytest.mark.anyio
async def test_test_runtime_connection_uses_json_completion_transport(monkeypatch):
client = LLMClient(
@@ -34,14 +36,14 @@ async def test_test_runtime_connection_uses_json_completion_transport(monkeypatc
assert result == {"ok": True}
assert recorded["messages"][0]["role"] == "user"
assert "Return exactly this JSON object" in recorded["messages"][0]["content"]
# #endregion test_test_runtime_connection_uses_json_completion_transport
# [/DEF:test_test_runtime_connection_uses_json_completion_transport:Function]
# #region test_analyze_dashboard_provider_error_maps_to_unknown [TYPE Function]
# @BRIEF Infrastructure/provider failures must produce UNKNOWN analysis status rather than FAIL.
# @PRE LLMClient.get_json_completion raises provider/auth exception.
# @POST Returned payload uses status=UNKNOWN and issue severity UNKNOWN.
# @RELATION BINDS_TO -> [TestService]
# [DEF:test_analyze_dashboard_provider_error_maps_to_unknown:Function]
# @RELATION: BINDS_TO -> TestService
# @PURPOSE: Infrastructure/provider failures must produce UNKNOWN analysis status rather than FAIL.
# @PRE: LLMClient.get_json_completion raises provider/auth exception.
# @POST: Returned payload uses status=UNKNOWN and issue severity UNKNOWN.
@pytest.mark.anyio
async def test_analyze_dashboard_provider_error_maps_to_unknown(monkeypatch, tmp_path):
screenshot_path = tmp_path / "shot.jpg"
@@ -64,5 +66,5 @@ async def test_analyze_dashboard_provider_error_maps_to_unknown(monkeypatch, tmp
assert result["status"] == "UNKNOWN"
assert "Failed to get response from LLM" in result["summary"]
assert result["issues"][0]["severity"] == "UNKNOWN"
# #endregion test_analyze_dashboard_provider_error_maps_to_unknown
# #endregion TestService
# [/DEF:test_analyze_dashboard_provider_error_maps_to_unknown:Function]
# [/DEF:TestService:Module]

View File

@@ -1,9 +1,9 @@
# #region LLMAnalysisModels [C:3] [TYPE Module] [SEMANTICS pydantic, models, llm]
# @BRIEF Define Pydantic models for LLM Analysis plugin.
# @LAYER Domain
# @RELATION DEPENDS_ON -> [pydantic]
# @RELATION DEPENDS_on -> [pydantic]
# @RELATION DEPENDs_on -> [pydantic]
# @LAYER: Domain
# @RELATION DEPENDS_ON -> pydantic
# @RELATION DEPENDS_on -> pydantic
# @RELATION DEPENDs_on -> pydantic
from typing import List, Optional
from pydantic import BaseModel, Field
@@ -18,22 +18,6 @@ class LLMProviderType(str, Enum):
KILO = "kilo"
# #endregion LLMProviderType
# #region FetchModelsRequest [TYPE Class]
# @BRIEF Request model to fetch available models from a provider.
class FetchModelsRequest(BaseModel):
base_url: str = Field(description="Provider base URL (e.g. https://api.openai.com/v1)")
api_key: Optional[str] = Field(None, description="Optional API key for authenticated providers")
provider_type: LLMProviderType = Field(LLMProviderType.OPENAI, description="Provider type for auth headers")
provider_id: Optional[str] = Field(None, description="Existing provider ID — resolves api_key from DB if not provided directly")
# #endregion FetchModelsRequest
# #region FetchModelsResponse [TYPE Class]
# @BRIEF Response with available model IDs from a provider.
class FetchModelsResponse(BaseModel):
models: List[str] = Field(description="List of available model IDs")
total: int = Field(description="Total number of models found")
# #endregion FetchModelsResponse
# #region LLMProviderConfig [TYPE Class]
# @BRIEF Configuration for an LLM provider.
class LLMProviderConfig(BaseModel):

View File

@@ -1,12 +1,14 @@
# #region backend/src/plugins/llm_analysis/plugin.py [C:3] [TYPE Module] [SEMANTICS plugin, llm, analysis, documentation]
# [DEF:backend/src/plugins/llm_analysis/plugin.py:Module]
# @COMPLEXITY: 3
# @SEMANTICS: plugin, llm, analysis, documentation
# @BRIEF Implements DashboardValidationPlugin and DocumentationPlugin.
# @LAYER Domain
# @INVARIANT All LLM interactions must be executed as asynchronous tasks.
# @LAYER: Domain
# @RELATION INHERITS -> [PluginBase]
# @RELATION CALLS -> [ScreenshotService]
# @RELATION CALLS -> [LLMClient]
# @RELATION CALLS -> [LLMProviderService]
# @RELATION USES -> [TaskContext]
# @RELATION USES -> TaskContext
# @INVARIANT: All LLM interactions must be executed as asynchronous tasks.
from typing import Dict, Any, Optional
import os
@@ -31,8 +33,8 @@ from ...services.llm_prompt_templates import (
# #region _is_masked_or_invalid_api_key [TYPE Function]
# @BRIEF Guards against placeholder or malformed API keys in runtime.
# @PRE value may be None.
# @POST Returns True when value cannot be used for authenticated provider calls.
# @PRE: value may be None.
# @POST: Returns True when value cannot be used for authenticated provider calls.
def _is_masked_or_invalid_api_key(value: Optional[str]) -> bool:
key = (value or "").strip()
if not key:
@@ -45,8 +47,8 @@ def _is_masked_or_invalid_api_key(value: Optional[str]) -> bool:
# #region _json_safe_value [TYPE Function]
# @BRIEF Recursively normalize payload values for JSON serialization.
# @PRE value may be nested dict/list with datetime values.
# @POST datetime values are converted to ISO strings.
# @PRE: value may be nested dict/list with datetime values.
# @POST: datetime values are converted to ISO strings.
def _json_safe_value(value: Any):
if isinstance(value, datetime):
return value.isoformat()
@@ -59,7 +61,7 @@ def _json_safe_value(value: Any):
# #region DashboardValidationPlugin [TYPE Class]
# @BRIEF Plugin for automated dashboard health analysis using LLMs.
# @RELATION IMPLEMENTS -> [backend.src.core.plugin_base.PluginBase]
# @RELATION IMPLEMENTS -> backend.src.core.plugin_base.PluginBase
class DashboardValidationPlugin(PluginBase):
@property
def id(self) -> str:
@@ -88,8 +90,8 @@ class DashboardValidationPlugin(PluginBase):
"required": ["dashboard_id", "environment_id", "provider_id"]
}
# #region DashboardValidationPlugin.execute [TYPE Function]
# @BRIEF Executes the dashboard validation task with TaskContext support.
# [DEF:DashboardValidationPlugin.execute:Function]
# @PURPOSE: Executes the dashboard validation task with TaskContext support.
# @PARAM: params (Dict[str, Any]) - Validation parameters.
# @PARAM: context (Optional[TaskContext]) - Task context for logging with source attribution.
# @PRE: params contains dashboard_id, environment_id, and provider_id.
@@ -317,12 +319,12 @@ class DashboardValidationPlugin(PluginBase):
finally:
db.close()
# #endregion DashboardValidationPlugin.execute
# [/DEF:DashboardValidationPlugin.execute:Function]
# #endregion DashboardValidationPlugin
# #region DocumentationPlugin [TYPE Class]
# @BRIEF Plugin for automated dataset documentation using LLMs.
# @RELATION IMPLEMENTS -> [backend.src.core.plugin_base.PluginBase]
# @RELATION IMPLEMENTS -> backend.src.core.plugin_base.PluginBase
class DocumentationPlugin(PluginBase):
@property
def id(self) -> str:
@@ -351,8 +353,8 @@ class DocumentationPlugin(PluginBase):
"required": ["dataset_id", "environment_id", "provider_id"]
}
# #region DocumentationPlugin.execute [TYPE Function]
# @BRIEF Executes the dataset documentation task with TaskContext support.
# [DEF:DocumentationPlugin.execute:Function]
# @PURPOSE: Executes the dataset documentation task with TaskContext support.
# @PARAM: params (Dict[str, Any]) - Documentation parameters.
# @PARAM: context (Optional[TaskContext]) - Task context for logging with source attribution.
# @PRE: params contains dataset_id, environment_id, and provider_id.
@@ -473,7 +475,7 @@ class DocumentationPlugin(PluginBase):
finally:
db.close()
# #endregion DocumentationPlugin.execute
# [/DEF:DocumentationPlugin.execute:Function]
# #endregion DocumentationPlugin
# #endregion backend/src/plugins/llm_analysis/plugin.py
# [/DEF:backend/src/plugins/llm_analysis/plugin.py:Module]

View File

@@ -1,6 +1,8 @@
# #region backend/src/plugins/llm_analysis/scheduler.py [C:3] [TYPE Module] [SEMANTICS scheduler, task, automation]
# [DEF:backend/src/plugins/llm_analysis/scheduler.py:Module]
# @COMPLEXITY: 3
# @SEMANTICS: scheduler, task, automation
# @BRIEF Provides helper functions to schedule LLM-based validation tasks.
# @LAYER Domain
# @LAYER: Domain
# @RELATION DEPENDS_ON -> [SchedulerService]
from typing import Dict, Any
@@ -9,9 +11,6 @@ from ...core.logger import belief_scope, logger
# #region schedule_dashboard_validation [TYPE Function]
# @BRIEF Schedules a recurring dashboard validation task.
# @PARAM: dashboard_id (str) - ID of the dashboard to validate.
# @PARAM: cron_expression (str) - Standard cron expression for scheduling.
# @PARAM: params (Dict[str, Any]) - Task parameters (environment_id, provider_id).
# @SIDE_EFFECT: Adds a job to the scheduler service.
def schedule_dashboard_validation(dashboard_id: str, cron_expression: str, params: Dict[str, Any]):
with belief_scope("schedule_dashboard_validation", f"dashboard_id={dashboard_id}"):
@@ -41,8 +40,6 @@ def schedule_dashboard_validation(dashboard_id: str, cron_expression: str, param
# #region _parse_cron [TYPE Function]
# @BRIEF Basic cron parser placeholder.
# @PARAM: cron (str) - Cron expression.
# @RETURN: Dict[str, str] - Parsed cron parts.
def _parse_cron(cron: str) -> Dict[str, str]:
# Basic cron parser placeholder
parts = cron.split()
@@ -57,4 +54,4 @@ def _parse_cron(cron: str) -> Dict[str, str]:
}
# #endregion _parse_cron
# #endregion backend/src/plugins/llm_analysis/scheduler.py
# [/DEF:backend/src/plugins/llm_analysis/scheduler.py:Module]

View File

@@ -1,10 +1,12 @@
# #region backend/src/plugins/llm_analysis/service.py [C:3] [TYPE Module] [SEMANTICS service, llm, screenshot, playwright, openai]
# [DEF:backend/src/plugins/llm_analysis/service.py:Module]
# @COMPLEXITY: 3
# @SEMANTICS: service, llm, screenshot, playwright, openai
# @BRIEF Services for LLM interaction and dashboard screenshots.
# @LAYER Domain
# @INVARIANT Screenshots must be 1920px width and capture full page height.
# @RELATION DEPENDS_ON -> [playwright]
# @RELATION DEPENDS_ON -> [openai]
# @RELATION DEPENDS_ON -> [tenacity]
# @LAYER: Domain
# @RELATION DEPENDS_ON -> playwright
# @RELATION DEPENDS_ON -> openai
# @RELATION DEPENDS_ON -> tenacity
# @INVARIANT: Screenshots must be 1920px width and capture full page height.
import asyncio
import base64
@@ -26,17 +28,17 @@ from ...services.llm_prompt_templates import DEFAULT_LLM_PROMPTS, render_prompt
# #region ScreenshotService [TYPE Class]
# @BRIEF Handles capturing screenshots of Superset dashboards.
class ScreenshotService:
# #region ScreenshotService.__init__ [TYPE Function]
# @BRIEF Initializes the ScreenshotService with environment configuration.
# @PRE env is a valid Environment object.
# [DEF:ScreenshotService.__init__:Function]
# @PURPOSE: Initializes the ScreenshotService with environment configuration.
# @PRE: env is a valid Environment object.
def __init__(self, env: Environment):
self.env = env
# #endregion ScreenshotService.__init__
# [/DEF:ScreenshotService.__init__:Function]
# #region ScreenshotService._find_first_visible_locator [TYPE Function]
# @BRIEF Resolve the first visible locator from multiple Playwright locator strategies.
# @PRE candidates is a non-empty list of locator-like objects.
# @POST Returns a locator ready for interaction or None when nothing matches.
# [DEF:ScreenshotService._find_first_visible_locator:Function]
# @PURPOSE: Resolve the first visible locator from multiple Playwright locator strategies.
# @PRE: candidates is a non-empty list of locator-like objects.
# @POST: Returns a locator ready for interaction or None when nothing matches.
async def _find_first_visible_locator(self, candidates) -> Any:
for locator in candidates:
try:
@@ -48,12 +50,12 @@ class ScreenshotService:
except Exception:
continue
return None
# #endregion ScreenshotService._find_first_visible_locator
# [/DEF:ScreenshotService._find_first_visible_locator:Function]
# #region ScreenshotService._iter_login_roots [TYPE Function]
# @BRIEF Enumerate page and child frames where login controls may be rendered.
# @PRE page is a Playwright page-like object.
# @POST Returns ordered roots starting with main page followed by frames.
# [DEF:ScreenshotService._iter_login_roots:Function]
# @PURPOSE: Enumerate page and child frames where login controls may be rendered.
# @PRE: page is a Playwright page-like object.
# @POST: Returns ordered roots starting with main page followed by frames.
def _iter_login_roots(self, page) -> List[Any]:
roots = [page]
page_frames = getattr(page, "frames", [])
@@ -64,12 +66,12 @@ class ScreenshotService:
except Exception:
pass
return roots
# #endregion ScreenshotService._iter_login_roots
# [/DEF:ScreenshotService._iter_login_roots:Function]
# #region ScreenshotService._extract_hidden_login_fields [TYPE Function]
# @BRIEF Collect hidden form fields required for direct login POST fallback.
# @PRE Login page is loaded.
# @POST Returns hidden input name/value mapping aggregated from page and child frames.
# [DEF:ScreenshotService._extract_hidden_login_fields:Function]
# @PURPOSE: Collect hidden form fields required for direct login POST fallback.
# @PRE: Login page is loaded.
# @POST: Returns hidden input name/value mapping aggregated from page and child frames.
async def _extract_hidden_login_fields(self, page) -> Dict[str, str]:
hidden_fields: Dict[str, str] = {}
for root in self._iter_login_roots(page):
@@ -85,21 +87,21 @@ class ScreenshotService:
except Exception:
continue
return hidden_fields
# #endregion ScreenshotService._extract_hidden_login_fields
# [/DEF:ScreenshotService._extract_hidden_login_fields:Function]
# #region ScreenshotService._extract_csrf_token [TYPE Function]
# @BRIEF Resolve CSRF token value from main page or embedded login frame.
# @PRE Login page is loaded.
# @POST Returns first non-empty csrf token or empty string.
# [DEF:ScreenshotService._extract_csrf_token:Function]
# @PURPOSE: Resolve CSRF token value from main page or embedded login frame.
# @PRE: Login page is loaded.
# @POST: Returns first non-empty csrf token or empty string.
async def _extract_csrf_token(self, page) -> str:
hidden_fields = await self._extract_hidden_login_fields(page)
return str(hidden_fields.get("csrf_token") or "").strip()
# #endregion ScreenshotService._extract_csrf_token
# [/DEF:ScreenshotService._extract_csrf_token:Function]
# #region ScreenshotService._response_looks_like_login_page [TYPE Function]
# @BRIEF Detect when fallback login POST returned the login form again instead of an authenticated page.
# @PRE response_text is normalized HTML or text from login POST response.
# @POST Returns True when login-page markers dominate the response body.
# [DEF:ScreenshotService._response_looks_like_login_page:Function]
# @PURPOSE: Detect when fallback login POST returned the login form again instead of an authenticated page.
# @PRE: response_text is normalized HTML or text from login POST response.
# @POST: Returns True when login-page markers dominate the response body.
def _response_looks_like_login_page(self, response_text: str) -> bool:
normalized = str(response_text or "").strip().lower()
if not normalized:
@@ -113,23 +115,23 @@ class ScreenshotService:
'name="csrf_token"',
]
return sum(marker in normalized for marker in markers) >= 3
# #endregion ScreenshotService._response_looks_like_login_page
# [/DEF:ScreenshotService._response_looks_like_login_page:Function]
# #region ScreenshotService._redirect_looks_authenticated [TYPE Function]
# @BRIEF Treat non-login redirects after form POST as successful authentication without waiting for redirect target.
# @PRE redirect_location may be empty or relative.
# @POST Returns True when redirect target does not point back to login flow.
# [DEF:ScreenshotService._redirect_looks_authenticated:Function]
# @PURPOSE: Treat non-login redirects after form POST as successful authentication without waiting for redirect target.
# @PRE: redirect_location may be empty or relative.
# @POST: Returns True when redirect target does not point back to login flow.
def _redirect_looks_authenticated(self, redirect_location: str) -> bool:
normalized = str(redirect_location or "").strip().lower()
if not normalized:
return True
return "/login" not in normalized
# #endregion ScreenshotService._redirect_looks_authenticated
# [/DEF:ScreenshotService._redirect_looks_authenticated:Function]
# #region ScreenshotService._submit_login_via_form_post [TYPE Function]
# @BRIEF Fallback login path that submits credentials directly with csrf token.
# @PRE login_url is same-origin and csrf token can be read from DOM.
# @POST Browser context receives authenticated cookies when login succeeds.
# [DEF:ScreenshotService._submit_login_via_form_post:Function]
# @PURPOSE: Fallback login path that submits credentials directly with csrf token.
# @PRE: login_url is same-origin and csrf token can be read from DOM.
# @POST: Browser context receives authenticated cookies when login succeeds.
async def _submit_login_via_form_post(self, page, login_url: str) -> bool:
hidden_fields = await self._extract_hidden_login_fields(page)
csrf_token = str(hidden_fields.get("csrf_token") or "").strip()
@@ -186,12 +188,12 @@ class ScreenshotService:
f"[DEBUG] Direct form login fallback response: status={response_status} url={response_url} login_markup={looks_like_login_page} snippet={text_snippet!r}"
)
return not looks_like_login_page
# #endregion ScreenshotService._submit_login_via_form_post
# [/DEF:ScreenshotService._submit_login_via_form_post:Function]
# #region ScreenshotService._find_login_field_locator [TYPE Function]
# @BRIEF Resolve login form input using semantic label text plus generic visible-input fallbacks.
# @PRE field_name is `username` or `password`.
# @POST Returns a locator for the corresponding input or None.
# [DEF:ScreenshotService._find_login_field_locator:Function]
# @PURPOSE: Resolve login form input using semantic label text plus generic visible-input fallbacks.
# @PRE: field_name is `username` or `password`.
# @POST: Returns a locator for the corresponding input or None.
async def _find_login_field_locator(self, page, field_name: str) -> Any:
normalized = str(field_name or "").strip().lower()
for root in self._iter_login_roots(page):
@@ -226,12 +228,12 @@ class ScreenshotService:
return locator
return None
# #endregion ScreenshotService._find_login_field_locator
# [/DEF:ScreenshotService._find_login_field_locator:Function]
# #region ScreenshotService._find_submit_locator [TYPE Function]
# @BRIEF Resolve login submit button from main page or embedded auth frame.
# @PRE page is ready for login interaction.
# @POST Returns visible submit locator or None.
# [DEF:ScreenshotService._find_submit_locator:Function]
# @PURPOSE: Resolve login submit button from main page or embedded auth frame.
# @PRE: page is ready for login interaction.
# @POST: Returns visible submit locator or None.
async def _find_submit_locator(self, page) -> Any:
selectors = [
lambda root: root.get_by_role("button", name="Sign in", exact=False),
@@ -246,12 +248,12 @@ class ScreenshotService:
if locator:
return locator
return None
# #endregion ScreenshotService._find_submit_locator
# [/DEF:ScreenshotService._find_submit_locator:Function]
# #region ScreenshotService._goto_resilient [TYPE Function]
# @BRIEF Navigate without relying on networkidle for pages with long-polling or persistent requests.
# @PRE page is a valid Playwright page and url is non-empty.
# @POST Returns last navigation response or raises when both primary and fallback waits fail.
# [DEF:ScreenshotService._goto_resilient:Function]
# @PURPOSE: Navigate without relying on networkidle for pages with long-polling or persistent requests.
# @PRE: page is a valid Playwright page and url is non-empty.
# @POST: Returns last navigation response or raises when both primary and fallback waits fail.
async def _goto_resilient(
self,
page,
@@ -267,13 +269,13 @@ class ScreenshotService:
f"[ScreenshotService._goto_resilient] Primary navigation wait '{primary_wait_until}' failed for {url}: {primary_error}"
)
return await page.goto(url, wait_until=fallback_wait_until, timeout=timeout)
# #endregion ScreenshotService._goto_resilient
# [/DEF:ScreenshotService._goto_resilient:Function]
# #region ScreenshotService.capture_dashboard [TYPE Function]
# @BRIEF Captures a full-page screenshot of a dashboard using Playwright and CDP.
# @PRE dashboard_id is a valid string, output_path is a writable path.
# @POST Returns True if screenshot is saved successfully.
# @SIDE_EFFECT Launches a browser, performs UI login, switches tabs, and writes a PNG file.
# [DEF:ScreenshotService.capture_dashboard:Function]
# @PURPOSE: Captures a full-page screenshot of a dashboard using Playwright and CDP.
# @PRE: dashboard_id is a valid string, output_path is a writable path.
# @POST: Returns True if screenshot is saved successfully.
# @SIDE_EFFECT: Launches a browser, performs UI login, switches tabs, and writes a PNG file.
# @UX_STATE: [Navigating] -> Loading dashboard UI
# @UX_STATE: [TabSwitching] -> Iterating through dashboard tabs to trigger lazy loading
# @UX_STATE: [CalculatingHeight] -> Determining dashboard dimensions
@@ -669,15 +671,15 @@ class ScreenshotService:
await browser.close()
return True
# #endregion ScreenshotService.capture_dashboard
# [/DEF:ScreenshotService.capture_dashboard:Function]
# #endregion ScreenshotService
# #region LLMClient [TYPE Class]
# @BRIEF Wrapper for LLM provider APIs.
class LLMClient:
# #region LLMClient.__init__ [TYPE Function]
# @BRIEF Initializes the LLMClient with provider settings.
# @PRE api_key, base_url, and default_model are non-empty strings.
# [DEF:LLMClient.__init__:Function]
# @PURPOSE: Initializes the LLMClient with provider settings.
# @PRE: api_key, base_url, and default_model are non-empty strings.
def __init__(self, provider_type: LLMProviderType, api_key: str, base_url: str, default_model: str):
self.provider_type = provider_type
normalized_key = (api_key or "").strip()
@@ -715,12 +717,12 @@ class LLMClient:
default_headers=default_headers,
http_client=http_client,
)
# #endregion LLMClient.__init__
# [/DEF:LLMClient.__init__:Function]
# #region LLMClient._supports_json_response_format [TYPE Function]
# @BRIEF Detect whether provider/model is likely compatible with response_format=json_object.
# @PRE Client initialized with base_url and default_model.
# @POST Returns False for known-incompatible combinations to avoid avoidable 400 errors.
# [DEF:LLMClient._supports_json_response_format:Function]
# @PURPOSE: Detect whether provider/model is likely compatible with response_format=json_object.
# @PRE: Client initialized with base_url and default_model.
# @POST: Returns False for known-incompatible combinations to avoid avoidable 400 errors.
def _supports_json_response_format(self) -> bool:
base = (self.base_url or "").lower()
model = (self.default_model or "").lower()
@@ -735,13 +737,13 @@ class LLMClient:
if any(token in model for token in incompatible_tokens):
return False
return True
# #endregion LLMClient._supports_json_response_format
# [/DEF:LLMClient._supports_json_response_format:Function]
# #region LLMClient.get_json_completion [TYPE Function]
# @BRIEF Helper to handle LLM calls with JSON mode and fallback parsing.
# @PRE messages is a list of valid message dictionaries.
# @POST Returns a parsed JSON dictionary.
# @SIDE_EFFECT Calls external LLM API.
# [DEF:LLMClient.get_json_completion:Function]
# @PURPOSE: Helper to handle LLM calls with JSON mode and fallback parsing.
# @PRE: messages is a list of valid message dictionaries.
# @POST: Returns a parsed JSON dictionary.
# @SIDE_EFFECT: Calls external LLM API.
def _should_retry(exception: Exception) -> bool:
"""Custom retry predicate that excludes authentication errors."""
# Don't retry on authentication errors
@@ -857,13 +859,13 @@ class LLMClient:
return json.loads(json_str)
else:
raise
# #endregion LLMClient.get_json_completion
# [/DEF:LLMClient.get_json_completion:Function]
# #region LLMClient.test_runtime_connection [TYPE Function]
# @BRIEF Validate provider credentials using the same chat completions transport as runtime analysis.
# @PRE Client is initialized with provider credentials and default_model.
# @POST Returns lightweight JSON payload when runtime auth/model path is valid.
# @SIDE_EFFECT Calls external LLM API.
# [DEF:LLMClient.test_runtime_connection:Function]
# @PURPOSE: Validate provider credentials using the same chat completions transport as runtime analysis.
# @PRE: Client is initialized with provider credentials and default_model.
# @POST: Returns lightweight JSON payload when runtime auth/model path is valid.
# @SIDE_EFFECT: Calls external LLM API.
async def test_runtime_connection(self) -> Dict[str, Any]:
with belief_scope("test_runtime_connection"):
messages = [
@@ -873,13 +875,13 @@ class LLMClient:
}
]
return await self.get_json_completion(messages)
# #endregion LLMClient.test_runtime_connection
# [/DEF:LLMClient.test_runtime_connection:Function]
# #region LLMClient.analyze_dashboard [TYPE Function]
# @BRIEF Sends dashboard data (screenshot + logs) to LLM for health analysis.
# @PRE screenshot_path exists, logs is a list of strings.
# @POST Returns a structured analysis dictionary (status, summary, issues).
# @SIDE_EFFECT Reads screenshot file and calls external LLM API.
# [DEF:LLMClient.analyze_dashboard:Function]
# @PURPOSE: Sends dashboard data (screenshot + logs) to LLM for health analysis.
# @PRE: screenshot_path exists, logs is a list of strings.
# @POST: Returns a structured analysis dictionary (status, summary, issues).
# @SIDE_EFFECT: Reads screenshot file and calls external LLM API.
async def analyze_dashboard(
self,
screenshot_path: str,
@@ -945,7 +947,7 @@ class LLMClient:
"summary": f"Failed to get response from LLM: {str(e)}",
"issues": [{"severity": "UNKNOWN", "message": "LLM provider returned empty or invalid response"}]
}
# #endregion LLMClient.analyze_dashboard
# [/DEF:LLMClient.analyze_dashboard:Function]
# #endregion LLMClient
# #endregion backend/src/plugins/llm_analysis/service.py
# [/DEF:backend/src/plugins/llm_analysis/service.py:Module]