Files
web-inspector/backend/app/models/schemas.py
jungwoo choi bffce65aca feat: 접근성 검사 표준 선택 기능 — WCAG/KWCAG 버전별 선택 지원
3가지 검사 모드(한 페이지, 사이트 크롤링, 목록 업로드) 모두에서 접근성 표준을
선택할 수 있도록 추가. WCAG 2.0 A/AA, 2.1 AA, 2.2 AA와 KWCAG 2.1, 2.2를
지원하며, KWCAG 선택 시 axe-core 결과를 KWCAG 검사항목으로 자동 매핑.

- KWCAG 2.2 (33항목) / 2.1 (24항목) ↔ WCAG 매핑 테이블 (kwcag_mapping.py)
- AccessibilityChecker에 표준 파싱 및 KWCAG 변환 로직 추가
- 전체 API 파이프라인에 accessibility_standard 파라미터 전파
- 프론트엔드 3개 폼에 공용 표준 선택 드롭다운 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 08:36:14 +09:00

191 lines
4.2 KiB
Python

"""
Pydantic models for request/response validation and serialization.
"""
from pydantic import BaseModel, Field, HttpUrl
from typing import Optional
from datetime import datetime
from enum import Enum
# --- Enums ---
class InspectionStatus(str, Enum):
RUNNING = "running"
COMPLETED = "completed"
ERROR = "error"
class Severity(str, Enum):
CRITICAL = "critical"
MAJOR = "major"
MINOR = "minor"
INFO = "info"
class CategoryName(str, Enum):
HTML_CSS = "html_css"
ACCESSIBILITY = "accessibility"
SEO = "seo"
PERFORMANCE_SECURITY = "performance_security"
# --- Request ---
class StartInspectionRequest(BaseModel):
url: HttpUrl
accessibility_standard: str = Field(
default="wcag_2.1_aa",
description="접근성 검사 표준 (wcag_2.0_a, wcag_2.0_aa, wcag_2.1_aa, wcag_2.2_aa, kwcag_2.1, kwcag_2.2)",
)
# --- Core Data Models ---
class Issue(BaseModel):
code: str
category: str
severity: Severity
message: str
element: Optional[str] = None
line: Optional[int] = None
suggestion: str
wcag_criterion: Optional[str] = None
# KWCAG mapping fields (populated when standard is kwcag_2.1 or kwcag_2.2)
kwcag_criterion: Optional[str] = None
kwcag_name: Optional[str] = None
kwcag_principle: Optional[str] = None
kwcag_criteria_all: Optional[list[str]] = None
class CategoryResult(BaseModel):
score: int = Field(ge=0, le=100)
grade: str
total_issues: int
critical: int = 0
major: int = 0
minor: int = 0
info: int = 0
issues: list[Issue] = []
# Category-specific fields
wcag_level: Optional[str] = None
meta_info: Optional[dict] = None
sub_scores: Optional[dict] = None
metrics: Optional[dict] = None
class IssueSummary(BaseModel):
total_issues: int
critical: int
major: int
minor: int
info: int
# --- Response Models ---
class StartInspectionResponse(BaseModel):
inspection_id: str
status: str = "running"
url: str
stream_url: str
class InspectionResult(BaseModel):
inspection_id: str
url: str
status: InspectionStatus
created_at: datetime
completed_at: Optional[datetime] = None
duration_seconds: Optional[float] = None
overall_score: int = Field(ge=0, le=100)
grade: str
categories: dict[str, CategoryResult]
summary: IssueSummary
accessibility_standard: Optional[str] = None
class InspectionResultResponse(BaseModel):
"""Response model for GET /api/inspections/{id} (without nested issues)."""
inspection_id: str
url: str
status: InspectionStatus
created_at: datetime
completed_at: Optional[datetime] = None
duration_seconds: Optional[float] = None
overall_score: int = Field(ge=0, le=100)
grade: str
categories: dict[str, CategoryResult]
summary: IssueSummary
accessibility_standard: Optional[str] = None
class IssueListResponse(BaseModel):
inspection_id: str
total: int
filters: dict
issues: list[Issue]
class InspectionListItem(BaseModel):
inspection_id: str
url: str
created_at: datetime
overall_score: int
grade: str
total_issues: int
class PaginatedResponse(BaseModel):
items: list[InspectionListItem]
total: int
page: int
limit: int
total_pages: int
class TrendDataPoint(BaseModel):
inspection_id: str
created_at: datetime
overall_score: int
html_css: int
accessibility: int
seo: int
performance_security: int
class TrendResponse(BaseModel):
url: str
data_points: list[TrendDataPoint]
class HealthResponse(BaseModel):
status: str
timestamp: str
services: dict[str, str]
# --- Utility functions ---
def calculate_grade(score: int) -> str:
"""Calculate letter grade from numeric score."""
if score >= 90:
return "A+"
if score >= 80:
return "A"
if score >= 70:
return "B"
if score >= 60:
return "C"
if score >= 50:
return "D"
return "F"
def calculate_overall_score(categories: dict[str, CategoryResult]) -> int:
"""Calculate overall score as simple average of category scores."""
scores = [cat.score for cat in categories.values()]
if not scores:
return 0
return round(sum(scores) / len(scores))