"""Phase S: 생성 HTML 콘텐츠 검증 + 재시도 루프. 생성기(html_generator)와 완전히 분리된 독립 검증. 코드 기반 검증을 먼저, LLM 검증은 코드가 못 잡는 것만. 검증 계층: Layer 1: 텍스트 보존 검증 (코드, $0) Layer 2: 금지 콘텐츠 검증 (코드, $0) Layer 3: 구조 검증 (코드, $0) Layer 4: 오버플로 검증 (Selenium, $0) — slide_measurer.py 재사용 Layer 5: 시각 품질 검증 (Opus 비전, $$) — kei_client.py 재사용 """ from __future__ import annotations import logging import re from dataclasses import dataclass, field from difflib import SequenceMatcher from html.parser import HTMLParser logger = logging.getLogger(__name__) # ═══════════════════════════════════════════════════════════ # 데이터 구조 # ═══════════════════════════════════════════════════════════ @dataclass class VerificationResult: """단일 영역의 검증 결과.""" passed: bool area_name: str checks: dict[str, bool] = field(default_factory=dict) score: float = 0.0 errors: list[str] = field(default_factory=list) warnings: list[str] = field(default_factory=list) # ═══════════════════════════════════════════════════════════ # HTML 텍스트 추출 # ═══════════════════════════════════════════════════════════ class _TextExtractor(HTMLParser): """HTML에서 가시 텍스트만 추출.