class ImageAnalyzer: def analyze_site(self, site_data): """ Analyzes the crawled site data for missing ALT text. :param site_data: Dict { page_url: [image_dicts] } :return: Dict containing summary and detailed report. """ total_images = 0 total_missing_alt = 0 total_poor_quality = 0 # Track all poor quality images total_short_alt_legacy = 0 # Track only strictly short ones pages_report = [] # Compile regex for filename patterns import re # Pattern 1: Common Camera Prefixes (e.g., DSC_001, IMG_1234, Screenshot-1) prefix_pattern = re.compile(r'^(dsc|img|pic|screenshot|photo)[\-_]?\d+', re.IGNORECASE) # Pattern 2: General Slug + Number (e.g., product-12345, item_01) - enforces no spaces slug_pattern = re.compile(r'^[a-z0-9\-_]+[-_]\d+$', re.IGNORECASE) for page_url, images in site_data.items(): missing_images = [] poor_quality_images = [] for img in images: total_images += 1 alt_text = img.get('alt') # 1. Strictly Missing if not alt_text or alt_text.strip() == "": missing_images.append(img['src']) total_missing_alt += 1 continue # Skip further checks if missing cleaned_alt = alt_text.strip() lower_alt = cleaned_alt.lower() word_count = len(cleaned_alt.split()) is_poor = False reason = "" # 2. Advanced Poor Quality Checks # A) File Name / Extension Check (Highest Priority) if any(lower_alt.endswith(ext) for ext in ['.jpg', '.png', '.jpeg', '.webp', '.gif', '.svg']): is_poor = True reason = "Filename extension detected" elif prefix_pattern.search(cleaned_alt): is_poor = True reason = "Filename pattern detected (prefix)" elif slug_pattern.match(cleaned_alt): is_poor = True reason = "Filename pattern detected (slug)" # B) Generic / Filler Text (Smart check) # Flag if alt equals or contains generic terms. # Exception: "Brand Name Logo" (usually > 2 words or specific) # If word count is low (< 3) AND contains a generic term, it's likely poor. # If it is EXACTLY a generic term, it is definitely poor. generic_terms = {'image', 'photo', 'picture', 'logo', 'banner', 'icon', 'thumbnail', 'placeholder', 'img', 'spacer'} if not is_poor: if lower_alt in generic_terms: # Exact match found (e.g., "logo", "image") -> Bad is_poor = True reason = "Generic filler text" else: # Check partial match for short phrases (likely "My Logo", "Chart Image") # If phrase is long (>= 3 words), it might be descriptive enough (e.g., "Eminent Tactiles Logo") # So we only flag partial generic matches if word count is small (< 3) has_generic = any(term in lower_alt.split() for term in generic_terms) if has_generic and word_count < 3: is_poor = True reason = "Generic filler text (partial match)" # C) Too Short / Sparse (Lowest Priority) if not is_poor: if len(cleaned_alt) < 5: is_poor = True reason = "Too short (< 5 chars)" elif word_count < 2: # Single word check is_poor = True reason = "Too few words (needs >= 2)" if is_poor: # Store with reason poor_quality_images.append({'src': img['src'], 'alt': alt_text, 'reason': reason}) total_poor_quality += 1 # User Intent: "short alt list and that count are add in poor not it show separately" # Action: Consolidate everything into 'poor_quality' and remove specific 'short_alt' reporting. pages_report.append({ "page_url": page_url, "missing_alt_count": len(missing_images), "poor_quality_count": len(poor_quality_images), # Contains ALL poor images (Generic, Filename, Short) "images_without_alt": list(set(missing_images)), "images_with_poor_alt": poor_quality_images }) return { "summary": { "total_pages_scanned": len(site_data), "total_images_found": total_images, "total_images_missing_alt": total_missing_alt, "total_images_poor_quality": total_poor_quality }, "details": pages_report }