import os import base64 from pathlib import Path def generate_combined_report(results, out_dir): """ results is a list of dicts, each containing: - 'before_path': Path or None - 'after_path': Path or None - 'status': 'INTACT' | 'DAMAGED' | 'MISSING' | 'ADDED' - 'cls_score': float - 'patch_score': float - 'geo_inliers': int - 'confidence': str - 'stage_used': int - 'damage_description': str - 'target_phrases_list': list of str - 'masked_after_path': Path or None """ out = Path(out_dir) out.mkdir(parents=True, exist_ok=True) def b64img(path): if not path or not os.path.exists(path): return "" try: p = Path(path) data = base64.b64encode(p.read_bytes()).decode() ext = p.suffix.lstrip(".") or "jpeg" return f"data:image/{ext};base64,{data}" except Exception as e: print(f"Error encoding image {path}: {e}") return "" cards = "" for r in results: status = r['status'] # Determine styling based on status if status == 'INTACT': color = "#10b981" # Emerald bg_color = "rgba(16, 185, 129, 0.05)" border_c = "rgba(16, 185, 129, 0.2)" icon = "✅" label = "INTACT / سليم" elif status == 'DAMAGED': color = "#f59e0b" # Amber bg_color = "rgba(245, 158, 11, 0.05)" border_c = "rgba(245, 158, 11, 0.25)" icon = "⚠️" label = "DAMAGED / تالف" elif status == 'MISSING': color = "#ef4444" # Red bg_color = "rgba(239, 68, 68, 0.05)" border_c = "rgba(239, 68, 68, 0.2)" icon = "❌" label = "MISSING / مفقود" else: # ADDED color = "#3b82f6" # Blue bg_color = "rgba(59, 130, 246, 0.05)" border_c = "rgba(59, 130, 246, 0.25)" icon = "➕" label = "ADDED / مضاف" before_html = "" if r['before_path']: before_src = b64img(r['before_path']) before_name = Path(r['before_path']).name before_html = f"""
BEFORE IMAGE / الصورة قبل
{before_name}
{before_name}
""" after_html = "" if r['after_path']: after_src = b64img(r['after_path']) after_name = Path(r['after_path']).name after_html = f""" {"" if status == "ADDED" else '
'}
AFTER IMAGE / الصورة بعد
{after_name}
{after_name}
""" mask_html = "" if status == 'DAMAGED' and r['masked_after_path']: mask_src = b64img(r['masked_after_path']) mask_name = Path(r['masked_after_path']).name mask_html = f"""
🎯 DETECTED DAMAGE (SAM MASK) / الضرر المكتشف (قناع SAM)
{mask_name}
{mask_name}
""" stage_labels = {1: "DINOv2 CLS", 2: "DINOv2 Patch", 3: "SIFT+RANSAC"} stage_str = stage_labels.get(r.get('stage_used', 0), "None") # Matching metrics block scores_html = "" if status in ['INTACT', 'DAMAGED'] and r['after_path']: scores_html = f"""
Match / مطابقة: {stage_str}
CLS score / درجة CLS: {r['cls_score']:.3f}
""" if r['patch_score']: scores_html += f'
Patch score / درجة الرقعة: {r["patch_score"]:.3f}
' if r['geo_inliers']: scores_html += f'
Geo Inliers / مطابقة هندسية: {r["geo_inliers"]}
' if r.get('confidence'): scores_html += f'
Confidence / ثقة: {r["confidence"]}
' # Damage Report Text Block report_details_html = "" if status == 'DAMAGED': phrases_badges = "".join([f'{p}' for p in r['target_phrases_list']]) report_details_html = f"""

📝 AI Damage Assessment Report / تقرير فحص التلفيات بالذكاء الاصطناعي

{r['damage_description']}

Segmented features / الأجزاء المحددة:
{phrases_badges}
""" elif status == 'INTACT': report_details_html = f"""

✨ AI analysis confirms this item is intact. No major damage or negative changes were detected. / يؤكد تحليل الذكاء الاصطناعي أن هذا العنصر سليم، ولم يتم رصد أي تلفيات أو تغييرات سلبية.

""" elif status == 'MISSING': report_details_html = f"""

🔍 This item from the before inventory could not be matched with any item in the after inventory. It may have been moved, stolen, or removed. / لم يتم العثور على مطابقة لهذا العنصر في صور البعد، قد يكون تم نقله، سرقته أو إزالته.

""" else: # ADDED report_details_html = f"""

➕ New item added in the after inventory that was not present in the before inventory. / تم رصد عنصر جديد مضاف في صور البعد لم يكن موجوداً في صور القبل.

""" cards += f"""
{icon} {label}
{scores_html}
{before_html} {after_html} {mask_html}
{report_details_html}
""" n_total = len(results) n_intact = sum(1 for r in results if r['status'] == 'INTACT') n_damaged = sum(1 for r in results if r['status'] == 'DAMAGED') n_missing = sum(1 for r in results if r['status'] == 'MISSING') n_added = sum(1 for r in results if r['status'] == 'ADDED') html = f""" AI Inventory & Damage Report

🏠 Property Inventory & Damage Report / تقرير جرد وتحديد تلفيات الممتلكات

AI Matching & Inspection System · نظام المطابقة والفحص الذكي بالذكاء الاصطناعي (DINOv2 + Grounded-SAM + MLLM)

{n_intact}
✅ Intact / سليم
{n_damaged}
⚠️ Damaged / تالف
{n_missing}
❌ Missing / مفقود
{n_added}
➕ Added / مضاف
{n_total}
Total Items / إجمالي العناصر
{cards}
""" # Escape curly braces in css and scripts properly (using %% for format double escape or format string) report_file = out / "inventory_damage_report.html" report_file.write_text(html, encoding="utf-8") return str(report_file)