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>
This commit is contained in:
@ -46,6 +46,7 @@ class BatchInspectionService:
|
||||
name: str,
|
||||
urls: list[str],
|
||||
concurrency: int = 4,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> str:
|
||||
"""
|
||||
Start a batch inspection.
|
||||
@ -92,6 +93,7 @@ class BatchInspectionService:
|
||||
"completed_at": None,
|
||||
"config": {
|
||||
"concurrency": concurrency,
|
||||
"accessibility_standard": accessibility_standard,
|
||||
},
|
||||
"source_urls": urls,
|
||||
"discovered_pages": discovered_pages,
|
||||
@ -100,13 +102,13 @@ class BatchInspectionService:
|
||||
await self.db.batch_inspections.insert_one(doc)
|
||||
|
||||
logger.info(
|
||||
"Batch inspection started: id=%s, name=%s, total_urls=%d, concurrency=%d",
|
||||
batch_inspection_id, name, len(urls), concurrency,
|
||||
"Batch inspection started: id=%s, name=%s, total_urls=%d, concurrency=%d, standard=%s",
|
||||
batch_inspection_id, name, len(urls), concurrency, accessibility_standard,
|
||||
)
|
||||
|
||||
# Launch background task
|
||||
asyncio.create_task(
|
||||
self._inspect_all(batch_inspection_id, urls, concurrency)
|
||||
self._inspect_all(batch_inspection_id, urls, concurrency, accessibility_standard)
|
||||
)
|
||||
|
||||
return batch_inspection_id
|
||||
@ -205,6 +207,7 @@ class BatchInspectionService:
|
||||
batch_inspection_id: str,
|
||||
urls: list[str],
|
||||
concurrency: int = 4,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""
|
||||
Background task that inspects all URLs in parallel.
|
||||
@ -225,6 +228,7 @@ class BatchInspectionService:
|
||||
page_url=url,
|
||||
page_index=idx,
|
||||
total_pages=len(urls),
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
for idx, url in enumerate(urls)
|
||||
]
|
||||
@ -287,6 +291,7 @@ class BatchInspectionService:
|
||||
page_url: str,
|
||||
page_index: int,
|
||||
total_pages: int,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""Inspect a single page with semaphore-controlled concurrency."""
|
||||
async with semaphore:
|
||||
@ -295,6 +300,7 @@ class BatchInspectionService:
|
||||
page_url=page_url,
|
||||
page_index=page_index,
|
||||
total_pages=total_pages,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
|
||||
async def _inspect_single_page(
|
||||
@ -303,6 +309,7 @@ class BatchInspectionService:
|
||||
page_url: str,
|
||||
page_index: int,
|
||||
total_pages: int,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""Run inspection for a single page in the batch."""
|
||||
inspection_id = str(uuid.uuid4())
|
||||
@ -347,6 +354,7 @@ class BatchInspectionService:
|
||||
url=page_url,
|
||||
inspection_id=inspection_id,
|
||||
progress_callback=page_progress_callback,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
|
||||
overall_score = result.get("overall_score", 0)
|
||||
|
||||
@ -49,7 +49,11 @@ class InspectionService:
|
||||
self.db = db
|
||||
self.redis = redis
|
||||
|
||||
async def start_inspection(self, url: str) -> str:
|
||||
async def start_inspection(
|
||||
self,
|
||||
url: str,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> str:
|
||||
"""
|
||||
Start an inspection and return the inspection_id.
|
||||
1. Validate URL accessibility (timeout 10s)
|
||||
@ -70,7 +74,7 @@ class InspectionService:
|
||||
|
||||
# 4. Run inspection as background task
|
||||
asyncio.create_task(
|
||||
self._run_inspection(inspection_id, url, response)
|
||||
self._run_inspection(inspection_id, url, response, accessibility_standard)
|
||||
)
|
||||
|
||||
return inspection_id
|
||||
@ -80,6 +84,7 @@ class InspectionService:
|
||||
url: str,
|
||||
inspection_id: Optional[str] = None,
|
||||
progress_callback: Optional[object] = None,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> tuple[str, dict]:
|
||||
"""
|
||||
Run a full inspection synchronously (inline) and return the result.
|
||||
@ -93,6 +98,7 @@ class InspectionService:
|
||||
inspection_id: Optional pre-generated ID. If None, a new UUID is generated.
|
||||
progress_callback: Optional async callback(category, progress, current_step).
|
||||
If None, progress is not reported.
|
||||
accessibility_standard: Accessibility standard to use for inspection.
|
||||
|
||||
Returns:
|
||||
(inspection_id, result_dict) where result_dict is the MongoDB document.
|
||||
@ -120,7 +126,10 @@ class InspectionService:
|
||||
# Create 4 checker engines
|
||||
checkers = [
|
||||
HtmlCssChecker(progress_callback=progress_callback),
|
||||
AccessibilityChecker(progress_callback=progress_callback),
|
||||
AccessibilityChecker(
|
||||
progress_callback=progress_callback,
|
||||
standard=accessibility_standard,
|
||||
),
|
||||
SeoChecker(progress_callback=progress_callback),
|
||||
PerformanceSecurityChecker(progress_callback=progress_callback),
|
||||
]
|
||||
@ -190,6 +199,7 @@ class InspectionService:
|
||||
grade=grade,
|
||||
categories=categories,
|
||||
summary=summary,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
|
||||
# Store in MongoDB
|
||||
@ -203,14 +213,18 @@ class InspectionService:
|
||||
await cache_result(inspection_id, doc)
|
||||
|
||||
logger.info(
|
||||
"Inspection %s completed (inline): score=%d, duration=%.1fs",
|
||||
inspection_id, overall_score, duration,
|
||||
"Inspection %s completed (inline): score=%d, duration=%.1fs, standard=%s",
|
||||
inspection_id, overall_score, duration, accessibility_standard,
|
||||
)
|
||||
|
||||
return inspection_id, doc
|
||||
|
||||
async def _run_inspection(
|
||||
self, inspection_id: str, url: str, response: httpx.Response
|
||||
self,
|
||||
inspection_id: str,
|
||||
url: str,
|
||||
response: httpx.Response,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""
|
||||
Execute 4 category checks in parallel and store results.
|
||||
@ -234,7 +248,10 @@ class InspectionService:
|
||||
# Create 4 checker engines
|
||||
checkers = [
|
||||
HtmlCssChecker(progress_callback=progress_callback),
|
||||
AccessibilityChecker(progress_callback=progress_callback),
|
||||
AccessibilityChecker(
|
||||
progress_callback=progress_callback,
|
||||
standard=accessibility_standard,
|
||||
),
|
||||
SeoChecker(progress_callback=progress_callback),
|
||||
PerformanceSecurityChecker(progress_callback=progress_callback),
|
||||
]
|
||||
@ -322,6 +339,7 @@ class InspectionService:
|
||||
grade=grade,
|
||||
categories=categories,
|
||||
summary=summary,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
|
||||
# Store in MongoDB
|
||||
@ -347,8 +365,8 @@ class InspectionService:
|
||||
})
|
||||
|
||||
logger.info(
|
||||
"Inspection %s completed: score=%d, duration=%.1fs",
|
||||
inspection_id, overall_score, duration,
|
||||
"Inspection %s completed: score=%d, duration=%.1fs, standard=%s",
|
||||
inspection_id, overall_score, duration, accessibility_standard,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@ -50,6 +50,7 @@ class SiteInspectionService:
|
||||
max_pages: int = 20,
|
||||
max_depth: int = 2,
|
||||
concurrency: int = 4,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> str:
|
||||
"""
|
||||
Start a site-wide inspection.
|
||||
@ -84,6 +85,7 @@ class SiteInspectionService:
|
||||
"max_pages": max_pages,
|
||||
"max_depth": max_depth,
|
||||
"concurrency": concurrency,
|
||||
"accessibility_standard": accessibility_standard,
|
||||
},
|
||||
"discovered_pages": [],
|
||||
"aggregate_scores": None,
|
||||
@ -91,13 +93,16 @@ class SiteInspectionService:
|
||||
await self.db.site_inspections.insert_one(doc)
|
||||
|
||||
logger.info(
|
||||
"Site inspection started: id=%s, url=%s, max_pages=%d, max_depth=%d, concurrency=%d",
|
||||
site_inspection_id, url, max_pages, max_depth, concurrency,
|
||||
"Site inspection started: id=%s, url=%s, max_pages=%d, max_depth=%d, concurrency=%d, standard=%s",
|
||||
site_inspection_id, url, max_pages, max_depth, concurrency, accessibility_standard,
|
||||
)
|
||||
|
||||
# Launch background task
|
||||
asyncio.create_task(
|
||||
self._crawl_and_inspect(site_inspection_id, url, max_pages, max_depth, concurrency)
|
||||
self._crawl_and_inspect(
|
||||
site_inspection_id, url, max_pages, max_depth, concurrency,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
)
|
||||
|
||||
return site_inspection_id
|
||||
@ -272,6 +277,7 @@ class SiteInspectionService:
|
||||
max_pages: int,
|
||||
max_depth: int,
|
||||
concurrency: int = 4,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""
|
||||
Background task that runs in two phases:
|
||||
@ -366,6 +372,7 @@ class SiteInspectionService:
|
||||
page_url=page["url"],
|
||||
page_index=idx,
|
||||
total_pages=len(discovered_pages),
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
for idx, page in enumerate(discovered_pages)
|
||||
]
|
||||
@ -428,6 +435,7 @@ class SiteInspectionService:
|
||||
page_url: str,
|
||||
page_index: int,
|
||||
total_pages: int,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""Inspect a single page with semaphore-controlled concurrency."""
|
||||
async with semaphore:
|
||||
@ -436,6 +444,7 @@ class SiteInspectionService:
|
||||
page_url=page_url,
|
||||
page_index=page_index,
|
||||
total_pages=total_pages,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
|
||||
async def _inspect_single_page(
|
||||
@ -444,6 +453,7 @@ class SiteInspectionService:
|
||||
page_url: str,
|
||||
page_index: int,
|
||||
total_pages: int,
|
||||
accessibility_standard: str = "wcag_2.1_aa",
|
||||
) -> None:
|
||||
"""Run inspection for a single discovered page."""
|
||||
inspection_id = str(uuid.uuid4())
|
||||
@ -480,6 +490,7 @@ class SiteInspectionService:
|
||||
url=page_url,
|
||||
inspection_id=inspection_id,
|
||||
progress_callback=page_progress_callback,
|
||||
accessibility_standard=accessibility_standard,
|
||||
)
|
||||
|
||||
overall_score = result.get("overall_score", 0)
|
||||
|
||||
Reference in New Issue
Block a user