"""Accessibility Architecture - TASK_57 Phase 1 Implementation.
Core accessibility types and compliance framework for automated testing and validation.
Provides comprehensive WCAG compliance, assistive technology integration, and
accessibility testing.
Architecture: Accessibility Types + WCAG Standards + Compliance Framework
+ Testing Integration
Performance: <50ms compliance checks, efficient accessibility validation
Security: Safe accessibility testing, secure assistive technology integration
"""
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from typing import Any, NewType
# Branded Types for Accessibility Management
AccessibilityTestId = NewType("AccessibilityTestId", str)
ComplianceReportId = NewType("ComplianceReportId", str)
AssistiveTechId = NewType("AssistiveTechId", str)
AccessibilityRuleId = NewType("AccessibilityRuleId", str)
TestResultId = NewType("TestResultId", str)
class WCAGVersion(Enum):
"""WCAG version standards."""
WCAG_2_0 = "2.0"
WCAG_2_1 = "2.1"
WCAG_2_2 = "2.2"
WCAG_3_0 = "3.0"
class ConformanceLevel(Enum):
"""WCAG conformance levels."""
A = "A"
AA = "AA"
AAA = "AAA"
class AccessibilityStandard(Enum):
"""Accessibility standards and guidelines."""
WCAG = "wcag"
SECTION_508 = "section508"
ADA = "ada"
EN_301_549 = "en301549"
DDA = "dda"
AODA = "aoda"
class TestType(Enum):
"""Types of accessibility tests."""
AUTOMATED = "automated"
MANUAL = "manual"
ASSISTIVE_TECH = "assistive_tech"
USABILITY = "usability"
COMPLIANCE = "compliance"
PERFORMANCE = "performance"
class SeverityLevel(Enum):
"""Accessibility issue severity levels."""
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
INFO = "info"
class AssistiveTechnology(Enum):
"""Types of assistive technologies."""
SCREEN_READER = "screen_reader"
VOICE_CONTROL = "voice_control"
SWITCH_ACCESS = "switch_access"
EYE_TRACKING = "eye_tracking"
MAGNIFICATION = "magnification"
KEYBOARD_NAVIGATION = "keyboard_navigation"
HEARING_AIDS = "hearing_aids"
MOTOR_ASSISTANCE = "motor_assistance"
class AccessibilityPrinciple(Enum):
"""WCAG accessibility principles."""
PERCEIVABLE = "perceivable"
OPERABLE = "operable"
UNDERSTANDABLE = "understandable"
ROBUST = "robust"
class TestStatus(Enum):
"""Test execution status."""
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
SKIPPED = "skipped"
CANCELLED = "cancelled"
@dataclass(frozen=True)
class WCAGCriterion:
"""WCAG success criterion definition."""
criterion_id: str # e.g., "1.1.1", "2.4.3"
title: str
description: str
level: ConformanceLevel
principle: AccessibilityPrinciple
guideline: str
techniques: list[str] = field(default_factory=list)
failures: list[str] = field(default_factory=list)
def __post_init__(self):
if not self.criterion_id or not self.title:
raise ValueError("WCAG criterion must have ID and title")
@dataclass(frozen=True)
class AccessibilityRule:
"""Accessibility validation rule."""
rule_id: AccessibilityRuleId
name: str
description: str
standard: AccessibilityStandard
wcag_criteria: list[str] = field(default_factory=list)
test_type: TestType = TestType.AUTOMATED
severity: SeverityLevel = SeverityLevel.MEDIUM
enabled: bool = True
rule_logic: dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
if not self.name.strip():
raise ValueError("Accessibility rule must have a name")
@dataclass(frozen=True)
class AccessibilityIssue:
"""Accessibility issue found during testing."""
issue_id: str
rule_id: AccessibilityRuleId
element_selector: str
description: str
severity: SeverityLevel
wcag_criteria: list[str] = field(default_factory=list)
suggested_fix: str = ""
code_snippet: str = ""
screenshot_path: str | None = None
coordinates: tuple[int, int] | None = None
def __post_init__(self):
if not self.description.strip():
raise ValueError("Accessibility issue must have a description")
@dataclass(frozen=True)
class TestResult:
"""Result of an accessibility test."""
result_id: TestResultId
test_id: AccessibilityTestId
status: TestStatus
start_time: datetime
end_time: datetime | None = None
total_checks: int = 0
passed_checks: int = 0
failed_checks: int = 0
issues: list[AccessibilityIssue] = field(default_factory=list)
compliance_score: float = 0.0
details: dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
if not (0.0 <= self.compliance_score <= 100.0):
raise ValueError("Compliance score must be between 0.0 and 100.0")
@property
def duration_ms(self) -> float | None:
"""Calculate test duration in milliseconds."""
if self.end_time:
return (self.end_time - self.start_time).total_seconds() * 1000
return None
@property
def success_rate(self) -> float:
"""Calculate test success rate."""
if self.total_checks == 0:
return 0.0
return (self.passed_checks / self.total_checks) * 100.0
@dataclass(frozen=True)
class AssistiveTechConfig:
"""Configuration for assistive technology testing."""
tech_id: AssistiveTechId
technology: AssistiveTechnology
name: str
version: str
settings: dict[str, Any] = field(default_factory=dict)
test_scenarios: list[str] = field(default_factory=list)
compatibility_requirements: dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
if not self.name.strip():
raise ValueError("Assistive technology config must have a name")
@dataclass(frozen=True)
class AccessibilityTest:
"""Comprehensive accessibility test definition."""
test_id: AccessibilityTestId
name: str
description: str
target_url: str | None = None
target_element: str | None = None
test_type: TestType = TestType.AUTOMATED
standards: set[AccessibilityStandard] = field(default_factory=set)
wcag_version: WCAGVersion = WCAGVersion.WCAG_2_1
conformance_level: ConformanceLevel = ConformanceLevel.AA
rules: list[AccessibilityRuleId] = field(default_factory=list)
assistive_tech: list[AssistiveTechId] = field(default_factory=list)
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
created_by: str = "system"
def __post_init__(self):
if not self.name.strip():
raise ValueError("Accessibility test must have a name")
@dataclass(frozen=True)
class ComplianceReport:
"""Comprehensive accessibility compliance report."""
report_id: ComplianceReportId
title: str
test_results: list[TestResultId]
standards_tested: set[AccessibilityStandard]
wcag_version: WCAGVersion
conformance_level: ConformanceLevel
overall_score: float
total_issues: int
critical_issues: int
high_issues: int
medium_issues: int
low_issues: int
generated_at: datetime = field(default_factory=lambda: datetime.now(UTC))
generated_by: str = "system"
summary: str = ""
recommendations: list[str] = field(default_factory=list)
def __post_init__(self):
if not (0.0 <= self.overall_score <= 100.0):
raise ValueError("Overall score must be between 0.0 and 100.0")
if self.total_issues != (
self.critical_issues
+ self.high_issues
+ self.medium_issues
+ self.low_issues
):
raise ValueError("Total issues must equal sum of issue counts by severity")
@property
def compliance_status(self) -> str:
"""Get compliance status based on score."""
if self.overall_score >= 95.0:
return "Excellent"
if self.overall_score >= 85.0:
return "Good"
if self.overall_score >= 70.0:
return "Fair"
if self.overall_score >= 50.0:
return "Poor"
return "Critical"
@property
def has_blocking_issues(self) -> bool:
"""Check if report has blocking accessibility issues."""
return self.critical_issues > 0 or self.high_issues > 5
# WCAG 2.1 Success Criteria Database
WCAG_2_1_CRITERIA: dict[str, WCAGCriterion] = {
"1.1.1": WCAGCriterion(
criterion_id="1.1.1",
title="Non-text Content",
description="All non-text content has a text alternative that serves the equivalent purpose",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.PERCEIVABLE,
guideline="1.1 Text Alternatives",
techniques=["H37", "H36", "H24", "H2", "H53", "H86"],
failures=["F3", "F13", "F20", "F30", "F38", "F39", "F65", "F67", "F71", "F72"],
),
"1.3.1": WCAGCriterion(
criterion_id="1.3.1",
title="Info and Relationships",
description="Information, structure, and relationships can be programmatically determined",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.PERCEIVABLE,
guideline="1.3 Adaptable",
techniques=["H42", "H43", "H44", "H51", "H63", "H71", "H85"],
failures=[
"F2",
"F33",
"F34",
"F42",
"F43",
"F46",
"F68",
"F87",
"F90",
"F91",
"F92",
],
),
"2.1.1": WCAGCriterion(
criterion_id="2.1.1",
title="Keyboard",
description="All functionality is available from a keyboard",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.OPERABLE,
guideline="2.1 Keyboard Accessible",
techniques=["G90", "H91", "SCR20", "SCR35"],
failures=["F54", "F55", "F42"],
),
"2.4.3": WCAGCriterion(
criterion_id="2.4.3",
title="Focus Order",
description="Keyboard focus proceeds in a logical sequence",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.OPERABLE,
guideline="2.4 Navigable",
techniques=["G59", "H4", "C27", "SCR26", "SCR37"],
failures=["F44", "F85"],
),
"3.1.1": WCAGCriterion(
criterion_id="3.1.1",
title="Language of Page",
description="The default language of the page can be programmatically determined",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.UNDERSTANDABLE,
guideline="3.1 Readable",
techniques=["H57", "PDF16", "PDF19"],
failures=["F25"],
),
"4.1.1": WCAGCriterion(
criterion_id="4.1.1",
title="Parsing",
description="Markup has complete start and end tags and is nested according to specification",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.ROBUST,
guideline="4.1 Compatible",
techniques=["G134", "G192", "H88", "H74", "H93", "H94"],
failures=["F70", "F77"],
),
"4.1.2": WCAGCriterion(
criterion_id="4.1.2",
title="Name, Role, Value",
description="UI components have accessible names, roles, and values",
level=ConformanceLevel.A,
principle=AccessibilityPrinciple.ROBUST,
guideline="4.1 Compatible",
techniques=["G10", "H44", "H64", "H65", "H88", "H91"],
failures=["F15", "F20", "F68", "F79", "F86", "F89"],
),
}
# Default Accessibility Rules
DEFAULT_ACCESSIBILITY_RULES: list[AccessibilityRule] = [
AccessibilityRule(
rule_id=AccessibilityRuleId("alt_text_missing"),
name="Missing Alt Text",
description="Images must have descriptive alternative text",
standard=AccessibilityStandard.WCAG,
wcag_criteria=["1.1.1"],
test_type=TestType.AUTOMATED,
severity=SeverityLevel.HIGH,
rule_logic={"selector": "img:not([alt])", "check": "missing_attribute"},
),
AccessibilityRule(
rule_id=AccessibilityRuleId("heading_structure"),
name="Proper Heading Structure",
description="Headings must follow proper hierarchical structure",
standard=AccessibilityStandard.WCAG,
wcag_criteria=["1.3.1"],
test_type=TestType.AUTOMATED,
severity=SeverityLevel.MEDIUM,
rule_logic={"selector": "h1,h2,h3,h4,h5,h6", "check": "heading_hierarchy"},
),
AccessibilityRule(
rule_id=AccessibilityRuleId("keyboard_focus"),
name="Keyboard Focus Indicators",
description="Interactive elements must have visible focus indicators",
standard=AccessibilityStandard.WCAG,
wcag_criteria=["2.4.3", "2.1.1"],
test_type=TestType.AUTOMATED,
severity=SeverityLevel.HIGH,
rule_logic={
"selector": "a,button,input,select,textarea",
"check": "focus_indicator",
},
),
AccessibilityRule(
rule_id=AccessibilityRuleId("color_contrast"),
name="Color Contrast",
description="Text must have sufficient color contrast",
standard=AccessibilityStandard.WCAG,
wcag_criteria=["1.4.3"],
test_type=TestType.AUTOMATED,
severity=SeverityLevel.HIGH,
rule_logic={"selector": "*", "check": "color_contrast", "ratio": 4.5},
),
AccessibilityRule(
rule_id=AccessibilityRuleId("form_labels"),
name="Form Labels",
description="Form inputs must have proper labels",
standard=AccessibilityStandard.WCAG,
wcag_criteria=["1.3.1", "4.1.2"],
test_type=TestType.AUTOMATED,
severity=SeverityLevel.CRITICAL,
rule_logic={"selector": "input,select,textarea", "check": "has_label"},
),
]
# Helper Functions
def create_accessibility_test_id() -> AccessibilityTestId:
"""Create a new unique accessibility test ID."""
return AccessibilityTestId(f"test_{uuid.uuid4().hex[:12]}")
def create_compliance_report_id() -> ComplianceReportId:
"""Create a new unique compliance report ID."""
return ComplianceReportId(f"report_{uuid.uuid4().hex[:12]}")
def create_assistive_tech_id() -> AssistiveTechId:
"""Create a new unique assistive technology ID."""
return AssistiveTechId(f"at_{uuid.uuid4().hex[:12]}")
def create_accessibility_rule_id() -> AccessibilityRuleId:
"""Create a new unique accessibility rule ID."""
return AccessibilityRuleId(f"rule_{uuid.uuid4().hex[:12]}")
def create_test_result_id() -> TestResultId:
"""Create a new unique test result ID."""
return TestResultId(f"result_{uuid.uuid4().hex[:12]}")
def get_wcag_criteria_by_level(
level: ConformanceLevel,
version: WCAGVersion = WCAGVersion.WCAG_2_1,
) -> list[WCAGCriterion]:
"""Get all WCAG criteria for a specific conformance level."""
if version != WCAGVersion.WCAG_2_1:
# For now, only WCAG 2.1 is fully implemented
return []
criteria = []
for criterion in WCAG_2_1_CRITERIA.values():
if (
(level == ConformanceLevel.A and criterion.level == ConformanceLevel.A)
or (
level == ConformanceLevel.AA
and criterion.level in [ConformanceLevel.A, ConformanceLevel.AA]
)
or level == ConformanceLevel.AAA
):
criteria.append(criterion)
return criteria
def get_wcag_criteria_by_principle(
principle: AccessibilityPrinciple,
version: WCAGVersion = WCAGVersion.WCAG_2_1,
) -> list[WCAGCriterion]:
"""Get all WCAG criteria for a specific accessibility principle."""
if version != WCAGVersion.WCAG_2_1:
return []
return [
criterion
for criterion in WCAG_2_1_CRITERIA.values()
if criterion.principle == principle
]
def validate_wcag_criterion_id(criterion_id: str) -> bool:
"""Validate WCAG criterion ID format."""
import re
pattern = r"^\d+\.\d+\.\d+$"
return bool(re.match(pattern, criterion_id))
# Accessibility Error Types
class AccessibilityError(Exception):
"""Base class for accessibility-related errors."""
class ComplianceValidationError(AccessibilityError):
"""Error during compliance validation."""
class AssistiveTechError(AccessibilityError):
"""Error with assistive technology integration."""
class TestExecutionError(AccessibilityError):
"""Error during accessibility test execution."""
class ReportGenerationError(AccessibilityError):
"""Error during report generation."""