- 4개 검사 엔진: HTML/CSS, 접근성(WCAG), SEO, 성능/보안 (총 50개 항목) - FastAPI 백엔드 (9개 API, SSE 실시간 진행, PDF/JSON 리포트) - Next.js 15 프론트엔드 (6개 페이지, 29개 컴포넌트, 반원 게이지 차트) - Docker Compose 배포 (Backend:8011, Frontend:3011, MongoDB:27022, Redis:6392) - 전체 테스트 32/32 PASS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
109 lines
3.3 KiB
Python
109 lines
3.3 KiB
Python
"""
|
|
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,
|
|
)
|