diff --git a/checks/extra_carousel_validation_check.py b/checks/extra_carousel_validation_check.py index 98b9866..e6fd724 100644 --- a/checks/extra_carousel_validation_check.py +++ b/checks/extra_carousel_validation_check.py @@ -193,17 +193,16 @@ def validate_record_features(record: Dict[str, Any], working_dir: str, item_inde } # Check each feature for matching images using substring search + # Only match files containing 'extra' to avoid false matches with MEC/BAU backgrounds matched_features = [] - found_filenames = [] missing_features = [] for feature in features: - # Case-sensitive substring search in filenames - matching_files = [f for f in available_files if feature in f] + # Case-sensitive substring search in filenames containing 'extra' + matching_files = [f for f in available_files if feature in f and 'extra' in f.lower()] if matching_files: matched_features.append(feature) - found_filenames.extend(matching_files) else: missing_features.append(feature) @@ -214,11 +213,11 @@ def validate_record_features(record: Dict[str, Any], working_dir: str, item_inde "status": "success", "record_index": record_index, "item_index": item_index, - "features": features, + "total_features": len(features), "coverage": round(coverage, 1), + "features": features, # Include full features list for aggregation "matched_features": matched_features, - "missing_features": missing_features, - "found_filenames": sorted(list(set(found_filenames))) # Remove duplicates and sort + "missing_features": missing_features } def check_feature_in_filename(feature: str, filename: str) -> bool: diff --git a/checks/html_reporter.py b/checks/html_reporter.py index 0aa386a..c7eda31 100755 --- a/checks/html_reporter.py +++ b/checks/html_reporter.py @@ -301,6 +301,9 @@ class HTMLReporter: formatted_parts.append(HTMLReporter._format_business_data(details)) elif 'unreferenced_files' in details: formatted_parts.append(HTMLReporter._format_file_list(details['unreferenced_files'], "Unreferenced Files")) + elif 'record_results' in details and 'overall_coverage' in details: + # Extra carousel validation check + formatted_parts.append(HTMLReporter._format_extra_carousel_validation(details)) # If no special formatting applied so far, use generic formatting elif not formatted_parts: formatted_parts.append(f''' @@ -1302,6 +1305,107 @@ class HTMLReporter:
{json.dumps(details, indent=2, default=str)}
'''
+ @staticmethod
+ def _format_extra_carousel_validation(details: dict) -> str:
+ """
+ Format extra carousel validation results with focus on problems only.
+ Shows overall summary, brief passed records, and detailed failed records.
+ """
+ try:
+ if not details:
+ return 'Coverage: {len(record.get('matched_features', []))}/{record.get('total_features', 0)} features matched ({record.get('coverage', 0)}%)
+ {f'Missing WERS Codes:
{wers}No missing features
'} +The following WERS codes are missing from extra carousel images:
+{wers}' for wers in missing_features_summary)}
+ {json.dumps(details, indent=2, default=str)}
+ '''
+
if __name__ == "__main__":
import sys
diff --git a/checks/image_format_check.py b/checks/image_format_check.py
index 57fc143..3f26df4 100644
--- a/checks/image_format_check.py
+++ b/checks/image_format_check.py
@@ -55,11 +55,12 @@ def run_check(config):
('exterior', 'layeroptext'): 'PNG',
('interior', 'layeroptint'): 'PNG',
('carousel', 'extra'): 'JPEG',
- ('carousel', 'powertrain'): 'JPEG',
+ ('carousel', 'powertrain'): 'JPEG', # BAU powertrain only
('exterior', 'showroom'): 'PNG',
('carousel', 'colour'): 'JPEG',
('carousel', 'bodystyle'): 'JPEG',
- ('carousel', 'series'): 'JPEG',
+ ('exterior', 'series'): 'JPEG', # Per latest guidance (newer format)
+ ('carousel', 'series'): 'JPEG', # Legacy format (older packs)
('carousel', 'trim'): 'JPEG',
# Adding lifestyle and inventory requirements
('lifestyle', None): 'JPEG',
@@ -73,19 +74,42 @@ def run_check(config):
"""Check if this is a base exterior or interior asset."""
return (viewtype == 'exterior' and imagetype is None) or (viewtype == 'interior' and imagetype is None)
- def validate_base_asset_format(filename, actual_format, pack_type, viewtype):
+ def is_mec_powertrain(viewtype, imagetype, conditions):
+ """
+ Check if this is a MEC powertrain image.
+ MEC powertrains are identified by:
+ - viewtype="exterior"
+ - imagetype=None
+ - experienceCondition="2d-background"
+ - angle=30
+ """
+ return (
+ viewtype == 'exterior' and
+ imagetype is None and
+ conditions.get('experienceCondition') == '2d-background' and
+ conditions.get('angle') == 30
+ )
+
+ def validate_base_asset_format(filename, actual_format, asset_type, viewtype):
"""
Validate base asset format with flexible rules:
- - MEC base assets: Strict JPG only (fail if PNG)
- - BAU base assets: JPG preferred, PNG acceptable with warning
+ - MEC base assets (with experienceCondition="2d-background"): Strict JPG only (fail if PNG)
+ - BAU base assets (without experienceCondition): JPG preferred, PNG acceptable with warning
+
+ Args:
+ filename: The image filename
+ actual_format: The detected image format (e.g., "JPEG", "PNG")
+ asset_type: "MEC" or "BAU" - indicates if this specific asset has experienceCondition
+ viewtype: The viewtype of the asset (e.g., "exterior", "interior")
+
Returns: (is_valid, warning_message)
"""
- if pack_type == "MEC":
+ if asset_type == "MEC":
# MEC base assets must be JPG only
if actual_format != "JPEG":
return False, None
return True, None
- else: # BAU pack
+ else: # BAU asset
# BAU base assets: JPG preferred, PNG acceptable with warning
if actual_format == "JPEG":
return True, None
@@ -111,14 +135,31 @@ def run_check(config):
logging.debug(f"Skipping item with missing viewtype: {conditions}")
continue
- # Check if this is a base asset that needs special handling
- if is_base_asset(viewtype, imagetype):
- # Base assets use flexible validation logic
- expected_format = None # Will be handled in the validation function
+ # Determine asset type and validation strategy
+ is_mec_powertrain_asset = False
+ is_base_asset_flag = False
+ base_asset_type = None
+ expected_format = None
+
+ # Check 1: Is this a MEC powertrain? (must check before base asset check)
+ if is_mec_powertrain(viewtype, imagetype, conditions):
+ is_mec_powertrain_asset = True
+ expected_format = "JPEG"
+
+ # Check 2: Is this a base asset? (exterior/interior with no imagetype)
+ elif is_base_asset(viewtype, imagetype):
+ is_base_asset_flag = True
+ # Determine if this specific base asset is MEC or BAU version
+ # MEC version has experienceCondition="2d-background" (but not angle=30)
+ if conditions.get('experienceCondition') == '2d-background':
+ base_asset_type = "MEC"
+ else:
+ base_asset_type = "BAU"
+ # expected_format will be handled in the validation function
+
+ # Check 3: Regular assets - look up in format requirements
else:
- # Non-base assets use the standard format requirements
key = (viewtype, imagetype)
- expected_format = None
if key in format_requirements:
expected_format = format_requirements[key]
@@ -146,7 +187,7 @@ def run_check(config):
# Check actual image format
# Special handling for AVIF files - use extension validation since PIL may not support them
- if not is_base_asset(viewtype, imagetype) and expected_format == "AVIF":
+ if not is_base_asset_flag and expected_format == "AVIF":
# For AVIF files, validate by extension instead of PIL
if filename.lower().endswith('.avif'):
# AVIF file with correct extension - pass validation
@@ -168,21 +209,21 @@ def run_check(config):
with Image.open(image_path) as img:
actual_format = img.format
- if is_base_asset(viewtype, imagetype):
+ if is_base_asset_flag:
# Use flexible validation for base assets
- is_valid, warning_message = validate_base_asset_format(filename, actual_format, pack_type, viewtype)
+ is_valid, warning_message = validate_base_asset_format(filename, actual_format, base_asset_type, viewtype)
if not is_valid:
# Only add an entry if this filename not yet in the dict
if filename not in failed_images_dict:
- expected_desc = "JPEG" if pack_type == "MEC" else "JPEG (preferred) or PNG (acceptable)"
+ expected_desc = "JPEG" if base_asset_type == "MEC" else "JPEG (preferred) or PNG (acceptable)"
failed_images_dict[filename] = {
"filename": filename,
"viewtype": viewtype,
- "imagetype": imagetype,
+ "imagetype": imagetype if imagetype else "base",
"expected_format": expected_desc,
"actual_format": actual_format,
- "pack_type": pack_type
+ "asset_type": base_asset_type
}
elif warning_message:
# Add warning but don't fail
@@ -190,8 +231,19 @@ def run_check(config):
"filename": filename,
"message": warning_message
})
+ elif is_mec_powertrain_asset:
+ # Validate MEC powertrain format (strict JPEG)
+ if actual_format != expected_format:
+ if filename not in failed_images_dict:
+ failed_images_dict[filename] = {
+ "filename": filename,
+ "viewtype": viewtype,
+ "imagetype": "powertrain (MEC)",
+ "expected_format": expected_format,
+ "actual_format": actual_format
+ }
else:
- # Standard validation for non-base assets
+ # Standard validation for regular assets
# PIL reports "JPEG" for JPG files
if actual_format != expected_format:
# Only add an entry if this filename not yet in the dict
@@ -207,11 +259,18 @@ def run_check(config):
except Exception as e:
# If we can't open the image, mark it as failed
if filename not in failed_images_dict:
- expected_desc = expected_format if expected_format else ("JPEG" if pack_type == "MEC" and is_base_asset(viewtype, imagetype) else "Unknown")
+ # Determine expected description based on asset type
+ if is_base_asset_flag:
+ expected_desc = "JPEG" if base_asset_type == "MEC" else "JPEG (preferred) or PNG (acceptable)"
+ elif expected_format:
+ expected_desc = expected_format
+ else:
+ expected_desc = "Unknown"
+
failed_images_dict[filename] = {
"filename": filename,
"viewtype": viewtype,
- "imagetype": imagetype,
+ "imagetype": imagetype if imagetype else ("base" if is_base_asset_flag else None),
"expected_format": expected_desc,
"actual_format": f"Error: {str(e)}"
}