  # #region ResourceServiceModule [C:5] [TYPE Module] [SEMANTICS resource, git, task, status, superset]
  # @defgroup Services Module group.
  # @BRIEF Shared service for fetching resource data with Git status and task status
  # @LAYER Service
  # @RELATION DEPENDS_ON -> [SupersetClient]
  # @RELATION DEPENDS_ON -> [TaskManagerPackage]
  # @RELATION DEPENDS_ON -> [TaskManagerModels]
  # @RELATION DEPENDS_ON -> [GitService]
  # @INVARIANT  All resources include metadata about their current state
  # @SIDE_EFFECT  Queries multiple backends for status
  # @DATA_CONTRACT  ResourceQuery -> ResourceStatusSummary
  
> import asyncio
> from datetime import UTC, datetime
> from typing import Any
  
> from ..core.logger import belief_scope, logger
> from ..core.task_manager.models import Task
> from ..core.utils.client_registry import get_superset_client
> from ..services.git_service import GitService
  
  
  # #region ResourceService [C:3] [TYPE Class]
  # @defgroup Services Module group.
  # @BRIEF Provides centralized access to resource data with enhanced metadata
  # @RELATION DEPENDS_ON -> [SupersetClient]
  # @RELATION DEPENDS_ON -> [GitService]
> class ResourceService:
  
      # region ResourceService_init [TYPE Function]
      # @PURPOSE: Initialize the resource service with dependencies
      # @PRE  None
      # @POST  ResourceService is ready to fetch resources
>     def __init__(self):
>         with belief_scope("ResourceService.__init__"):
>             self.git_service = GitService()
>             logger.reason("Initialized ResourceService", extra={"src": "ResourceService"})
      # endregion ResourceService_init
  
      # region get_dashboards_with_status [TYPE Function]
      # @PURPOSE: Fetch dashboards from environment with Git status and last task status
      # @PRE  env is a valid Environment object
      # @POST  Returns list of dashboards with enhanced metadata
      # @PARAM  env (Environment) - The environment to fetch from
      # @PARAM  tasks (List[Task]) - List of tasks to check for status
      # @RETURN  List[Dict] - Dashboards with git_status and last_task fields
      # @RELATION CALLS -> [SupersetClientGetDashboardsSummary]
      # @RELATION CALLS ->[EXT:method:_get_git_status_for_dashboard]
      # @RELATION CALLS ->[EXT:method:_get_last_llm_task_for_dashboard]
>     async def get_dashboards_with_status(
>         self,
>         env: Any,
>         tasks: list[Task] | None = None,
>         include_git_status: bool = True,
>         require_slug: bool = False,
>     ) -> list[dict[str, Any]]:
>         with belief_scope("get_dashboards_with_status", f"env={env.id}"):
>             client = await get_superset_client(env)
>             dashboards = await client.get_dashboards_summary(require_slug=require_slug)
  
              # Enhance each dashboard with Git status and task status
>             result = []
>             for dashboard in dashboards:
                  # dashboard is already a dict, no need to call .dict()
>                 dashboard_dict = dashboard
>                 dashboard_id = dashboard_dict.get('id')
  
                  # Git status can be skipped for list endpoints and loaded lazily on UI side.
>                 if include_git_status:
>                     git_status = self._get_git_status_for_dashboard(dashboard_id)
>                     dashboard_dict['git_status'] = git_status
>                 else:
>                     dashboard_dict['git_status'] = None
  
                  # Show status of the latest LLM validation for this dashboard.
>                 last_task = self._get_last_llm_task_for_dashboard(
>                     dashboard_id,
>                     env.id,
>                     tasks,
>                 )
>                 dashboard_dict['last_task'] = last_task
  
>                 result.append(dashboard_dict)
  
>             logger.info(f"[ResourceService][Coherence:OK] Fetched {len(result)} dashboards with status")
>             return result
      # endregion get_dashboards_with_status
  
      # region get_dashboards_page_with_status [TYPE Function]
      # @PURPOSE: Fetch one dashboard page from environment and enrich only that page with status metadata.
      # @PRE  env is valid; page >= 1; page_size > 0.
      # @POST  Returns page items plus total counters without scanning all pages locally.
      # @PARAM  env (Environment) - Source environment.
      # @PARAM  tasks (Optional[List[Task]]) - Tasks for latest LLM status.
      # @PARAM  page (int) - 1-based page number.
      # @PARAM  page_size (int) - Page size.
      # @RETURN  Dict[str, Any] - {"dashboards": List[Dict], "total": int, "total_pages": int}
      # @RELATION CALLS -> [SupersetClientGetDashboardsSummaryPage]
      # @RELATION CALLS ->[EXT:method:_get_git_status_for_dashboard]
      # @RELATION CALLS ->[EXT:method:_get_last_llm_task_for_dashboard]
>     async def get_dashboards_page_with_status(
>         self,
>         env: Any,
>         tasks: list[Task] | None = None,
>         page: int = 1,
>         page_size: int = 10,
>         search: str | None = None,
>         include_git_status: bool = True,
>         require_slug: bool = False,
>     ) -> dict[str, Any]:
>         with belief_scope(
>             "get_dashboards_page_with_status",
>             f"env={env.id}, page={page}, page_size={page_size}, search={search}",
>         ):
>             client = await get_superset_client(env)
>             total, dashboards_page = await client.get_dashboards_summary_page(
>                 page=page,
>                 page_size=page_size,
>                 search=search,
>                 require_slug=require_slug,
>             )
  
>             result = []
>             for dashboard in dashboards_page:
>                 dashboard_dict = dashboard
>                 dashboard_id = dashboard_dict.get("id")
  
>                 if include_git_status:
>                     dashboard_dict["git_status"] = self._get_git_status_for_dashboard(dashboard_id)
>                 else:
>                     dashboard_dict["git_status"] = None
  
>                 dashboard_dict["last_task"] = self._get_last_llm_task_for_dashboard(
>                     dashboard_id,
>                     env.id,
>                     tasks,
>                 )
>                 result.append(dashboard_dict)
  
>             total_pages = (total + page_size - 1) // page_size if total > 0 else 1
>             logger.info(
>                 "[ResourceService][Coherence:OK] Fetched dashboards page %s/%s (%s items, total=%s)",
>                 page,
>                 total_pages,
>                 len(result),
>                 total,
>             )
>             return {
>                 "dashboards": result,
>                 "total": total,
>                 "total_pages": total_pages,
>             }
      # endregion get_dashboards_page_with_status
  
      # region _get_last_llm_task_for_dashboard [TYPE Function]
      # @PURPOSE: Get most recent LLM validation task for a dashboard in an environment
      # @PRE  dashboard_id is a valid integer identifier
      # @POST  Returns the newest llm_dashboard_validation task summary or None
      # @PARAM  dashboard_id (int) - The dashboard ID
      # @PARAM  env_id (Optional[str]) - Environment ID to match task params
      # @PARAM  tasks (Optional[List[Task]]) - List of tasks to search
      # @RETURN  Optional[Dict] - Task summary with task_id and status
      # @RELATION CALLS ->[EXT:method:_normalize_datetime_for_compare]
      # @RELATION CALLS ->[EXT:method:_normalize_validation_status]
      # @RELATION CALLS ->[EXT:method:_normalize_task_status]
>     def _get_last_llm_task_for_dashboard(
>         self,
>         dashboard_id: int,
>         env_id: str | None,
>         tasks: list[Task] | None = None,
>     ) -> dict[str, Any] | None:
>         if not tasks:
>             return None
  
>         dashboard_id_str = str(dashboard_id)
>         matched_tasks = []
  
>         for task in tasks:
>             if getattr(task, "plugin_id", None) != "llm_dashboard_validation":
>                 continue
  
>             params = getattr(task, "params", {}) or {}
>             if str(params.get("dashboard_id")) != dashboard_id_str:
>                 continue
  
>             if env_id is not None:
>                 task_env = params.get("environment_id") or params.get("env")
>                 if str(task_env) != str(env_id):
>                     continue
  
>             matched_tasks.append(task)
  
>         if not matched_tasks:
>             return None
  
>         def _task_time(task_obj: Any) -> datetime:
>             raw_time = (
>                 getattr(task_obj, "started_at", None)
>                 or getattr(task_obj, "finished_at", None)
>                 or getattr(task_obj, "created_at", None)
>             )
>             return self._normalize_datetime_for_compare(raw_time)
  
>         projected_tasks = []
>         for task in matched_tasks:
>             raw_result = getattr(task, "result", None)
>             validation_status = None
>             if isinstance(raw_result, dict):
>                 validation_status = self._normalize_validation_status(raw_result.get("status"))
>             projected_tasks.append(
>                 (
>                     task,
>                     validation_status,
>                     _task_time(task),
>                 )
>             )
  
>         projected_tasks.sort(key=lambda item: item[2], reverse=True)
>         latest_task, latest_validation_status, _ = projected_tasks[0]
>         decisive_task = next(
>             (
>                 item for item in projected_tasks
>                 if item[1] in {"PASS", "WARN", "FAIL"}
>             ),
>             None,
>         )
>         validation_status = latest_validation_status
>         if validation_status == "UNKNOWN" and decisive_task is not None:
>             validation_status = decisive_task[1]
  
>         return {
>             "task_id": str(getattr(latest_task, "id", "")),
>             "status": self._normalize_task_status(getattr(latest_task, "status", "")),
>             "validation_status": validation_status,
>         }
      # endregion _get_last_llm_task_for_dashboard
  
      # region _normalize_task_status [TYPE Function]
      # @PURPOSE: Normalize task status to stable uppercase values for UI/API projections
      # @PRE  raw_status can be enum or string
      # @POST  Returns uppercase status without enum class prefix
      # @PARAM  raw_status (Any) - Raw task status object/value
      # @RETURN  str - Normalized status token
      # @RELATION CALLED_BY -> [EXT:method:_get_last_llm_task_for_dashboard]
>     def _normalize_task_status(self, raw_status: Any) -> str:
>         if raw_status is None:
>             return ""
>         value = getattr(raw_status, "value", raw_status)
>         status_text = str(value).strip()
>         if "." in status_text:
>             status_text = status_text.split(".")[-1]
>         return status_text.upper()
      # endregion _normalize_task_status
  
      # region _normalize_validation_status [TYPE Function]
      # @PURPOSE: Normalize LLM validation status to PASS/FAIL/WARN/UNKNOWN
      # @PRE  raw_status can be any scalar type
      # @POST  Returns normalized validation status token or None
      # @PARAM  raw_status (Any) - Raw validation status from task result
      # @RETURN  Optional[str] - PASS|FAIL|WARN|UNKNOWN
      # @RELATION CALLED_BY -> [EXT:method:_get_last_llm_task_for_dashboard]
>     def _normalize_validation_status(self, raw_status: Any) -> str | None:
>         if raw_status is None:
>             return None
>         status_text = str(raw_status).strip().upper()
>         if status_text in {"PASS", "FAIL", "WARN"}:
>             return status_text
>         return "UNKNOWN"
      # endregion _normalize_validation_status
  
      # region _normalize_datetime_for_compare [TYPE Function]
      # @PURPOSE: Normalize datetime values to UTC-aware values for safe comparisons.
      # @PRE  value may be datetime or any scalar.
      # @POST  Returns UTC-aware datetime; non-datetime values map to minimal UTC datetime.
      # @PARAM  value (Any) - Candidate datetime-like value.
      # @RETURN  datetime - UTC-aware comparable datetime.
      # @RELATION CALLED_BY -> [EXT:method:_get_last_llm_task_for_dashboard]
      # @RELATION CALLED_BY -> [EXT:method:_get_last_task_for_resource]
>     def _normalize_datetime_for_compare(self, value: Any) -> datetime:
>         if isinstance(value, datetime):
>             if value.tzinfo is None:
>                 return value.replace(tzinfo=UTC)
>             return value.astimezone(UTC)
>         return datetime.min.replace(tzinfo=UTC)
      # endregion _normalize_datetime_for_compare
  
      # region get_datasets_with_status [TYPE Function]
      # @PURPOSE: Fetch datasets from environment with mapping progress and last task status
      # @PRE  env is a valid Environment object
      # @POST  Returns list of datasets with enhanced metadata
      # @PARAM  env (Environment) - The environment to fetch from
      # @PARAM  tasks (List[Task]) - List of tasks to check for status
      # @RETURN  List[Dict] - Datasets with mapped_fields, last_task and linked_dashboard_count fields
      # @RELATION CALLS -> [SupersetClientGetDatasetsSummary]
      # @RELATION CALLS -> [SupersetClientGetDatasetLinkedDashboardCount]
      # @RELATION CALLS ->[EXT:method:_get_last_task_for_resource]
      # @RATIONALE linked_dashboard_count was missing — get_datasets_summary() only returns id/table_name/schema/database
      # StatsBar showed linked_count=0 because the field was never populated in list endpoint.
      # Fix: fetch /dataset/{id}/related_objects for each dataset concurrently (semaphore=3, timeout=10s).
      # @REJECTED N+1 blocking calls rejected — would timeout at 26×1s. Semaphore + asyncio.to_thread + timeout prevents overload.
>     async def get_datasets_with_status(
>         self,
>         env: Any,
>         tasks: list[Task] | None = None
>     ) -> list[dict[str, Any]]:
>         with belief_scope("get_datasets_with_status", f"env={env.id}"):
>             client = await get_superset_client(env)
>             datasets = await client.get_datasets_summary()
  
              # Enhance each dataset with task status and linked dashboard count
>             sem = asyncio.Semaphore(3)  # limit concurrent Superset calls
  
>             async def fetch_linked_count(ds_id: int) -> int:
>                 async with sem:
>                     try:
>                         return await asyncio.wait_for(
>                             client.get_dataset_linked_dashboard_count(ds_id),
>                             timeout=10.0,
>                         )
>                     except (TimeoutError, Exception):
>                         logger.warning(
>                             f"[EXT:method:get_datasets_with_status][Warning] "
>                             f"Failed to fetch linked dashboard count for dataset {ds_id}"
>                         )
>                         return 0
  
>             result = []
>             linked_tasks = []
>             for dataset in datasets:
>                 dataset_dict = dataset
>                 dataset_id = dataset_dict.get('id')
  
                  # Get last task status
>                 last_task = self._get_last_task_for_resource(
>                     f"dataset-{dataset_id}",
>                     tasks
>                 )
>                 dataset_dict['last_task'] = last_task
  
>                 result.append(dataset_dict)
>                 linked_tasks.append(fetch_linked_count(dataset_id))
  
              # Fetch linked dashboard counts concurrently — FR-022
>             if linked_tasks:
>                 linked_counts = await asyncio.gather(*linked_tasks)
>                 for ds, count in zip(result, linked_counts):
>                     ds['linked_dashboard_count'] = count
  
>             logger.info(
>                 f"[ResourceService][Coherence:OK] "
>                 f"Fetched {len(result)} datasets with status and linked counts"
>             )
>             return result
      # endregion get_datasets_with_status
  
      # region get_activity_summary [TYPE Function]
      # @PURPOSE: Get summary of active and recent tasks for the activity indicator
      # @PRE  tasks is a list of Task objects
      # @POST  Returns summary with active_count and recent_tasks
      # @PARAM  tasks (List[Task]) - List of tasks to summarize
      # @RETURN  Dict - Activity summary
      # @RELATION CALLS ->[EXT:method:_extract_resource_name_from_task]
      # @RELATION CALLS ->[EXT:method:_extract_resource_type_from_task]
>     def get_activity_summary(self, tasks: list[Task]) -> dict[str, Any]:
>         with belief_scope("get_activity_summary"):
              # Count active (RUNNING, WAITING_INPUT) tasks
>             active_tasks = [
>                 t for t in tasks
>                 if t.status in ['RUNNING', 'WAITING_INPUT']
>             ]
  
              # Get recent tasks (last 5)
>             recent_tasks = sorted(
>                 tasks,
>                 key=lambda t: t.created_at,
>                 reverse=True
>             )[:5]
  
              # Format recent tasks for frontend
>             recent_tasks_formatted = []
>             for task in recent_tasks:
>                 resource_name = self._extract_resource_name_from_task(task)
>                 recent_tasks_formatted.append({
>                     'task_id': str(task.id),
>                     'resource_name': resource_name,
>                     'resource_type': self._extract_resource_type_from_task(task),
>                     'status': task.status,
>                     'started_at': task.created_at.isoformat() if task.created_at else None
>                 })
  
>             return {
>                 'active_count': len(active_tasks),
>                 'recent_tasks': recent_tasks_formatted
>             }
      # endregion get_activity_summary
  
      # region _get_git_status_for_dashboard [TYPE Function]
      # @PURPOSE: Get Git sync status for a dashboard
      # @PRE  dashboard_id is a valid integer
      # @POST  Returns git status or None if no repo exists
      # @PARAM  dashboard_id (int) - The dashboard ID
      # @RETURN  Optional[Dict] - Git status with branch and sync_status
      # @RELATION CALLS ->[EXT:method:get_repo]
>     def _get_git_status_for_dashboard(self, dashboard_id: int) -> dict[str, Any] | None:
>         try:
>             repo = self.git_service.get_repo(dashboard_id)
>             if not repo:
>                 return {
>                     'branch': None,
>                     'sync_status': 'NO_REPO',
>                     'has_repo': False,
>                     'has_changes_for_commit': False
>                 }
  
              # Check if there are uncommitted changes
>             try:
                  # Get current branch
>                 branch = repo.active_branch.name
  
                  # Check for uncommitted changes
>                 is_dirty = repo.is_dirty()
>                 has_changes_for_commit = repo.is_dirty(untracked_files=True)
  
                  # Check for unpushed commits
>                 unpushed = len(list(repo.iter_commits(f'{branch}@{{u}}..{branch}'))) if '@{u}' in str(repo.refs) else 0
  
>                 if is_dirty or unpushed > 0:
>                     sync_status = 'DIFF'
>                 else:
>                     sync_status = 'OK'
  
>                 return {
>                     'branch': branch,
>                     'sync_status': sync_status,
>                     'has_repo': True,
>                     'has_changes_for_commit': has_changes_for_commit
>                 }
>             except Exception:
>                 logger.warning(f"[ResourceService][Warning] Failed to get git status for dashboard {dashboard_id}")
>                 return {
>                     'branch': None,
>                     'sync_status': 'ERROR',
>                     'has_repo': True,
>                     'has_changes_for_commit': False
>                 }
>         except Exception:
              # No repo exists for this dashboard
>             return {
>                 'branch': None,
>                 'sync_status': 'NO_REPO',
>                 'has_repo': False,
>                 'has_changes_for_commit': False
>             }
      # endregion _get_git_status_for_dashboard
  
      # region _get_last_task_for_resource [TYPE Function]
      # @PURPOSE: Get the most recent task for a specific resource
      # @PRE  resource_id is a valid string
      # @POST  Returns task summary or None if no tasks found
      # @PARAM  resource_id (str) - The resource identifier (e.g., "dashboard-123")
      # @PARAM  tasks (Optional[List[Task]]) - List of tasks to search
      # @RETURN  Optional[Dict] - Task summary with task_id and status
      # @RELATION CALLS ->[EXT:method:_normalize_datetime_for_compare]
>     def _get_last_task_for_resource(
>         self,
>         resource_id: str,
>         tasks: list[Task] | None = None
>     ) -> dict[str, Any] | None:
>         if not tasks:
>             return None
  
          # Filter tasks for this resource
>         resource_tasks = []
>         for task in tasks:
>             params = task.params or {}
>             if params.get('resource_id') == resource_id:
>                 resource_tasks.append(task)
  
>         if not resource_tasks:
>             return None
  
          # Get most recent task with timezone-safe comparison.
>         last_task = max(
>             resource_tasks,
>             key=lambda t: self._normalize_datetime_for_compare(getattr(t, "created_at", None)),
>         )
  
>         return {
>             'task_id': str(last_task.id),
>             'status': last_task.status
>         }
      # endregion _get_last_task_for_resource
  
      # region _extract_resource_name_from_task [TYPE Function]
      # @PURPOSE: Extract resource name from task params
      # @PRE  task is a valid Task object
      # @POST  Returns resource name or task ID
      # @PARAM  task (Task) - The task to extract from
      # @RETURN  str - Resource name or fallback
      # @RELATION CALLED_BY -> [EXT:method:get_activity_summary]
>     def _extract_resource_name_from_task(self, task: Task) -> str:
>         params = task.params or {}
>         return params.get('resource_name', f"Task {task.id}")
      # endregion _extract_resource_name_from_task
  
      # region _extract_resource_type_from_task [TYPE Function]
      # @PURPOSE: Extract resource type from task params
      # @PRE  task is a valid Task object
      # @POST  Returns resource type or 'unknown'
      # @PARAM  task (Task) - The task to extract from
      # @RETURN  str - Resource type
      # @RELATION CALLED_BY -> [EXT:method:get_activity_summary]
>     def _extract_resource_type_from_task(self, task: Task) -> str:
>         params = task.params or {}
>         return params.get('resource_type', 'unknown')
      # endregion _extract_resource_type_from_task
  # #endregion ResourceService
  # #endregion ResourceServiceModule
