""" BaseChecker abstract class - foundation for all inspection engines. """ from abc import ABC, abstractmethod from typing import Callable, Optional from app.models.schemas import CategoryResult, Issue, Severity, calculate_grade class BaseChecker(ABC): """ Abstract base class for all inspection engines. Provides progress callback mechanism and common utility methods. """ def __init__(self, progress_callback: Optional[Callable] = None): self.progress_callback = progress_callback async def update_progress(self, progress: int, current_step: str) -> None: """Update progress via Redis callback.""" if self.progress_callback: await self.progress_callback( category=self.category_name, progress=progress, current_step=current_step, ) @property @abstractmethod def category_name(self) -> str: """Category identifier (e.g., 'html_css').""" pass @abstractmethod async def check(self, url: str, html_content: str, headers: dict) -> CategoryResult: """Execute inspection and return results.""" pass def _create_issue( self, code: str, severity: str, message: str, suggestion: str, element: Optional[str] = None, line: Optional[int] = None, wcag_criterion: Optional[str] = None, ) -> Issue: """Helper to create a standardized Issue object.""" return Issue( code=code, category=self.category_name, severity=Severity(severity), message=message, element=element, line=line, suggestion=suggestion, wcag_criterion=wcag_criterion, ) def _calculate_score_by_deduction(self, issues: list[Issue]) -> int: """ Calculate score by deduction: score = 100 - (Critical*15 + Major*8 + Minor*3 + Info*1) Minimum 0, Maximum 100 """ severity_weights = { "critical": 15, "major": 8, "minor": 3, "info": 1, } deduction = sum( severity_weights.get(issue.severity.value, 0) for issue in issues ) return max(0, 100 - deduction) def _build_result( self, category: str, score: int, issues: list[Issue], wcag_level: Optional[str] = None, meta_info: Optional[dict] = None, sub_scores: Optional[dict] = None, metrics: Optional[dict] = None, ) -> CategoryResult: """Build a CategoryResult with computed severity counts.""" critical = sum(1 for i in issues if i.severity == Severity.CRITICAL) major = sum(1 for i in issues if i.severity == Severity.MAJOR) minor = sum(1 for i in issues if i.severity == Severity.MINOR) info = sum(1 for i in issues if i.severity == Severity.INFO) return CategoryResult( score=score, grade=calculate_grade(score), total_issues=len(issues), critical=critical, major=major, minor=minor, info=info, issues=issues, wcag_level=wcag_level, meta_info=meta_info, sub_scores=sub_scores, metrics=metrics, )