Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions src/creative_agent/data/standard_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,9 +321,7 @@ def create_responsive_render(
asset_type=AssetType.video,
required=True,
requirements={
"parameters_from_format_id": True,
"acceptable_formats": ["mp4", "mov", "webm"],
"description": "Video file matching format_id duration",
},
),
],
Expand All @@ -342,13 +340,27 @@ def create_responsive_render(
asset_type=AssetType.video,
required=True,
requirements={
"parameters_from_format_id": True,
"acceptable_formats": ["mp4", "mov", "webm"],
"description": "Video file matching format_id dimensions",
},
),
],
),
# Template format - VAST tag with any duration
CreativeFormat(
format_id=create_format_id("video_vast"),
name="VAST Video",
type=FormatCategory.video,
description="Video ad via VAST tag (supports any duration)",
accepts_parameters=[FormatIdParameter.duration],
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
assets_required=[
create_asset_required(
asset_id="vast_tag",
asset_type=AssetType.vast,
required=True,
),
],
),
# Concrete formats for backward compatibility
CreativeFormat(
format_id=create_format_id("video_standard_30s"),
Expand Down Expand Up @@ -397,7 +409,7 @@ def create_responsive_render(
assets_required=[
create_asset_required(
asset_id="vast_tag",
asset_type=AssetType.text,
asset_type=AssetType.vast,
required=True,
requirements={
"description": "VAST 4.x compatible tag",
Expand Down Expand Up @@ -546,9 +558,7 @@ def create_responsive_render(
asset_type=AssetType.image,
required=True,
requirements={
"parameters_from_format_id": True,
"acceptable_formats": ["jpg", "png", "gif", "webp"],
"description": "Banner image matching format_id dimensions",
},
),
create_asset_required(
Expand Down Expand Up @@ -766,9 +776,7 @@ def create_responsive_render(
asset_type=AssetType.html,
required=True,
requirements={
"parameters_from_format_id": True,
"max_file_size_mb": 0.5,
"description": "HTML5 creative code matching format_id dimensions",
},
),
],
Expand Down Expand Up @@ -897,6 +905,27 @@ def create_responsive_render(
),
]

# Display Formats - JavaScript
# Template format for JavaScript-based display ads
DISPLAY_JS_FORMATS = [
# Template format - supports any dimensions
CreativeFormat(
format_id=create_format_id("display_js"),
name="Display Banner - JavaScript",
type=FormatCategory.display,
description="JavaScript-based display ad (supports any dimensions)",
accepts_parameters=[FormatIdParameter.dimensions],
supported_macros=COMMON_MACROS,
assets_required=[
create_asset_required(
asset_id="js_creative",
asset_type=AssetType.javascript,
required=True,
),
],
),
]

# Native Formats
NATIVE_FORMATS = [
CreativeFormat(
Expand Down Expand Up @@ -1348,6 +1377,7 @@ def create_responsive_render(
+ VIDEO_FORMATS
+ DISPLAY_IMAGE_FORMATS
+ DISPLAY_HTML_FORMATS
+ DISPLAY_JS_FORMATS
+ NATIVE_FORMATS
+ AUDIO_FORMATS
+ DOOH_FORMATS
Expand Down
76 changes: 25 additions & 51 deletions tests/integration/test_template_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@ def test_standard_formats_include_templates(self):
"""STANDARD_FORMATS should include template formats with accepts_parameters."""
template_formats = [f for f in STANDARD_FORMATS if getattr(f, "accepts_parameters", None)]

assert len(template_formats) == 5, f"Expected 5 template formats, found {len(template_formats)}"
assert len(template_formats) == 7, f"Expected 7 template formats, found {len(template_formats)}"

# Verify template format IDs
template_ids = {f.format_id.id for f in template_formats}
expected_ids = {
"display_generative",
"display_image",
"display_html",
"display_js",
"video_standard",
"video_dimensions",
"video_vast",
}
assert template_ids == expected_ids, f"Template IDs mismatch: {template_ids} != {expected_ids}"

Expand Down Expand Up @@ -85,7 +87,7 @@ def test_list_creative_formats_returns_templates(self):

# Check for template formats
template_formats = [f for f in response.formats if getattr(f, "accepts_parameters", None)]
assert len(template_formats) == 5, f"Expected 5 template formats in response, found {len(template_formats)}"
assert len(template_formats) == 7, f"Expected 7 template formats in response, found {len(template_formats)}"


class TestTemplateFormatFiltering:
Expand All @@ -100,11 +102,11 @@ def test_filter_by_type_includes_templates(self):

# Should include template formats
template_displays = [f for f in display_formats if getattr(f, "accepts_parameters", None)]
assert len(template_displays) == 3, f"Expected 3 display templates, found {len(template_displays)}"
assert len(template_displays) == 4, f"Expected 4 display templates, found {len(template_displays)}"

# Verify IDs
template_ids = {f.format_id.id for f in template_displays}
expected_ids = {"display_generative", "display_image", "display_html"}
expected_ids = {"display_generative", "display_image", "display_html", "display_js"}
assert template_ids == expected_ids

def test_filter_by_type_video_includes_templates(self):
Expand All @@ -113,13 +115,13 @@ def test_filter_by_type_video_includes_templates(self):

video_formats = filter_formats(type=FormatCategory.video)

# Should include 2 video templates + 9 concrete
# Should include 3 video templates + 9 concrete
template_videos = [f for f in video_formats if getattr(f, "accepts_parameters", None)]
assert len(template_videos) == 2, f"Expected 2 video templates, found {len(template_videos)}"
assert len(template_videos) == 3, f"Expected 3 video templates, found {len(template_videos)}"

# Verify IDs
template_ids = {f.format_id.id for f in template_videos}
expected_ids = {"video_standard", "video_dimensions"}
expected_ids = {"video_standard", "video_dimensions", "video_vast"}
assert template_ids == expected_ids

def test_dimension_filter_includes_templates(self):
Expand All @@ -133,9 +135,9 @@ def test_dimension_filter_includes_templates(self):
f for f in template_results if FormatIdParameter.dimensions in getattr(f, "accepts_parameters", [])
]

# Should have display_generative, display_image, display_html, video_dimensions
assert len(dimension_templates) >= 3, (
f"Expected at least 3 dimension templates for 468x60, found {len(dimension_templates)}"
# Should have display_generative, display_image, display_html, display_js, video_dimensions
assert len(dimension_templates) >= 4, (
f"Expected at least 4 dimension templates for 468x60, found {len(dimension_templates)}"
)

# All should accept dimensions parameter
Expand All @@ -153,7 +155,7 @@ def test_max_width_filter_includes_templates(self):
]

# Template formats can satisfy any dimension requirement
assert len(dimension_templates) >= 3, "Should include dimension-accepting templates"
assert len(dimension_templates) >= 4, "Should include dimension-accepting templates"


class TestTemplateFormatLookup:
Expand Down Expand Up @@ -211,10 +213,10 @@ def test_get_video_template_by_base_id(self):


class TestTemplateAssetRequirements:
"""Test asset requirements with parameters_from_format_id."""
"""Test asset requirements for template vs concrete formats."""

def test_template_assets_have_parameters_from_format_id(self):
"""Template format assets should use parameters_from_format_id."""
def test_template_formats_have_assets_required(self):
"""Template formats should have assets_required defined."""
# Get display_image template
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="display_image")
fmt = get_format_by_id(format_id)
Expand All @@ -232,35 +234,8 @@ def test_template_assets_have_parameters_from_format_id(self):

assert image_asset is not None, "Should have banner_image asset"

# Check for parameters_from_format_id in requirements
requirements = getattr(image_asset, "requirements", None)
assert requirements is not None, "Image asset should have requirements"
assert "parameters_from_format_id" in requirements, "Image asset should specify parameters_from_format_id"
assert requirements["parameters_from_format_id"] is True

def test_video_template_assets_have_parameters_from_format_id(self):
"""Video template assets should use parameters_from_format_id for duration."""
# Get video_standard template
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="video_standard")
fmt = get_format_by_id(format_id)

assert fmt is not None
assert fmt.assets_required is not None
assert len(fmt.assets_required) > 0

# Find the video asset
video_asset = fmt.assets_required[0]
assert video_asset.asset_id == "video_file"

# Check for parameters_from_format_id
requirements = getattr(video_asset, "requirements", None)
assert requirements is not None
assert requirements.get("parameters_from_format_id") is True, (
"Video asset should use parameters_from_format_id for duration"
)

def test_concrete_formats_have_explicit_requirements(self):
"""Concrete formats should have explicit dimension requirements, not parameters_from_format_id."""
"""Concrete formats should have explicit dimension requirements."""
# Get concrete format
format_id = FormatId(agent_url=AnyUrl(str(AGENT_URL)), id="display_300x250_image")
fmt = get_format_by_id(format_id)
Expand All @@ -286,9 +261,6 @@ def test_concrete_formats_have_explicit_requirements(self):
assert requirements["width"] == 300
assert requirements["height"] == 250

# Should NOT use parameters_from_format_id
assert requirements.get("parameters_from_format_id") is not True


class TestTemplateFormatSerialization:
"""Test that template formats serialize correctly for ADCP."""
Expand Down Expand Up @@ -373,10 +345,11 @@ def test_have_display_dimension_templates(self):

template_ids = {f.format_id.id for f in display_templates}

# Should have templates for: generative, image, html
# Should have templates for: generative, image, html, js
assert "display_generative" in template_ids
assert "display_image" in template_ids
assert "display_html" in template_ids
assert "display_js" in template_ids

def test_have_video_templates(self):
"""Should have template formats for video with both duration and dimensions."""
Expand All @@ -388,7 +361,8 @@ def test_have_video_templates(self):
duration_templates = [f for f in video_templates if FormatIdParameter.duration in f.accepts_parameters]
dimension_templates = [f for f in video_templates if FormatIdParameter.dimensions in f.accepts_parameters]

assert len(duration_templates) >= 1, "Should have video duration template"
# video_standard and video_vast accept duration, video_dimensions accepts dimensions
assert len(duration_templates) >= 2, "Should have video duration templates (video_standard, video_vast)"
assert len(dimension_templates) >= 1, "Should have video dimension template"

def test_template_to_concrete_ratio(self):
Expand All @@ -397,11 +371,11 @@ def test_template_to_concrete_ratio(self):
concrete = [f for f in STANDARD_FORMATS if not getattr(f, "accepts_parameters", None)]

# With templates, we should have far fewer total formats than without
# Expected: 5 templates + 42 concrete = 47 total
assert len(templates) == 5, f"Expected 5 templates, found {len(templates)}"
# Expected: 7 templates + 42 concrete = 49 total
assert len(templates) == 7, f"Expected 7 templates, found {len(templates)}"
assert len(concrete) == 42, f"Expected 42 concrete formats, found {len(concrete)}"
assert len(STANDARD_FORMATS) == 47
assert len(STANDARD_FORMATS) == 49

# Ratio should be roughly 1:8 (5 templates replace ~40 potential concrete formats)
# Ratio should be roughly 1:6 (7 templates replace ~42 potential concrete formats)
ratio = len(concrete) / len(templates)
assert ratio > 5, f"Should have healthy template-to-concrete ratio, got {ratio}"
5 changes: 2 additions & 3 deletions tests/unit/test_filter_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,8 @@ def test_no_filters_returns_all_formats(self):
"""No filters returns all standard formats."""
results = filter_formats()
# Should return all formats including audio, video, display, native, dooh, info card
assert (
len(results) == 47
) # 8 generative (1 template + 7 concrete) + 11 video (2 templates + 9 concrete) + 8 display_image (1 template + 7 concrete) + 7 display_html (1 template + 6 concrete) + 2 native + 3 audio + 4 dooh + 4 info card
# 8 generative (1 template + 7 concrete) + 12 video (3 templates + 9 concrete) + 8 display_image (1 template + 7 concrete) + 7 display_html (1 template + 6 concrete) + 1 display_js template + 2 native + 3 audio + 4 dooh + 4 info card = 49
assert len(results) == 49
# Verify we have multiple types
types = {fmt.type for fmt in results}
assert FormatCategory.audio in types
Expand Down
Loading