import re import io from typing import List, Any from pydantic import BaseModel from abc import ABC, abstractmethod try: from PIL import Image except ImportError: Image = None class ValidationResult(BaseModel): valid: bool errors: List[str] class IBatchValidator(ABC): @abstractmethod def validate_batch_size(self, batch: Any) -> ValidationResult: pass @abstractmethod def check_sequence_continuity(self, batch: Any, expected_start: int) -> ValidationResult: pass @abstractmethod def validate_naming_convention(self, filenames: List[str]) -> ValidationResult: pass @abstractmethod def validate_format(self, image_data: bytes) -> ValidationResult: pass class BatchValidator(IBatchValidator): """H08: Validates image batch integrity, sequence continuity, and format.""" def validate_batch_size(self, batch: Any) -> ValidationResult: if len(batch.images) < 10: return ValidationResult(valid=False, errors=[f"Batch size {len(batch.images)} below minimum 10"]) if len(batch.images) > 50: return ValidationResult(valid=False, errors=[f"Batch size {len(batch.images)} exceeds maximum 50"]) return ValidationResult(valid=True, errors=[]) def check_sequence_continuity(self, batch: Any, expected_start: int) -> ValidationResult: try: seqs = [int(re.match(r"AD(\d{6})\.", f, re.I).group(1)) for f in batch.filenames] if seqs[0] != expected_start: return ValidationResult(valid=False, errors=[f"Expected start {expected_start}"]) for i in range(len(seqs) - 1): if seqs[i+1] != seqs[i] + 1: return ValidationResult(valid=False, errors=["Gap detected"]) return ValidationResult(valid=True, errors=[]) except Exception as e: return ValidationResult(valid=False, errors=[str(e)]) def validate_naming_convention(self, filenames: List[str]) -> ValidationResult: ptn = re.compile(r"^AD\d{6}\.(jpg|JPG|png|PNG)$") errs = [f"Invalid naming for {f}" for f in filenames if not ptn.match(f)] return ValidationResult(valid=len(errs) == 0, errors=errs) def validate_format(self, image_data: bytes) -> ValidationResult: if len(image_data) > 10 * 1024 * 1024: return ValidationResult(valid=False, errors=["Size > 10MB"]) if not Image: return ValidationResult(valid=True, errors=[]) try: img = Image.open(io.BytesIO(image_data)); img.verify() except Exception as e: return ValidationResult(valid=False, errors=[f"Corrupted: {e}"]) return ValidationResult(valid=True, errors=[])