|
|
from typing import Dict |
|
|
|
|
|
from benchmarks import get_benchmark, get_missing_element_recommendation, GOAL_MAPPING |
|
|
|
|
|
|
|
|
class ReportGenerator: |
|
|
def generate( |
|
|
self, |
|
|
analysis: Dict, |
|
|
industry: str, |
|
|
campaign_goal: str |
|
|
) -> Dict: |
|
|
""" |
|
|
Generate actionable report with recommendations. |
|
|
|
|
|
Args: |
|
|
analysis: Output from NarrativeClassifier |
|
|
industry: Selected industry |
|
|
campaign_goal: Selected campaign goal |
|
|
|
|
|
Returns: |
|
|
Complete report with summary, segments, and recommendations |
|
|
""" |
|
|
benchmark = get_benchmark(industry, campaign_goal) |
|
|
goal_key = GOAL_MAPPING.get(campaign_goal, "retention") |
|
|
|
|
|
|
|
|
recommendations = [] |
|
|
|
|
|
|
|
|
current_arc = analysis.get('story_arc', 'Unknown') |
|
|
optimal_arc = benchmark.get('best_arc', 'Unknown') |
|
|
|
|
|
arc_matches = self._arcs_match(current_arc, optimal_arc) |
|
|
|
|
|
if not arc_matches and optimal_arc != 'Unknown': |
|
|
recommendations.append({ |
|
|
"priority": "HIGH", |
|
|
"type": "arc_mismatch", |
|
|
"action": f"Consider restructuring to {optimal_arc} arc", |
|
|
"expected_impact": f"+{benchmark.get('uplift_percent', '?')}% {goal_key}", |
|
|
"reasoning": benchmark.get('recommendation', '') |
|
|
}) |
|
|
|
|
|
|
|
|
missing = analysis.get('missing_elements', []) |
|
|
for element in missing: |
|
|
rec = get_missing_element_recommendation(element) |
|
|
recommendations.append({ |
|
|
"priority": "MEDIUM" if element in ['Hook', 'Call-to-Action'] else "LOW", |
|
|
"type": "missing_element", |
|
|
"action": f"Add {element}", |
|
|
"expected_impact": rec.get('impact', ''), |
|
|
"reasoning": rec.get('suggestion', '') |
|
|
}) |
|
|
|
|
|
|
|
|
if not analysis.get('has_story', False): |
|
|
recommendations.append({ |
|
|
"priority": "MEDIUM", |
|
|
"type": "no_story", |
|
|
"action": "Consider adding narrative elements", |
|
|
"expected_impact": "+5-10% retention", |
|
|
"reasoning": "Ads with stories show 5-10% better retention than feature-focused ads" |
|
|
}) |
|
|
|
|
|
|
|
|
priority_order = {"HIGH": 0, "MEDIUM": 1, "LOW": 2} |
|
|
recommendations.sort(key=lambda x: priority_order.get(x['priority'], 3)) |
|
|
|
|
|
return { |
|
|
"summary": { |
|
|
"has_story": analysis.get('has_story', False), |
|
|
"story_explanation": analysis.get('story_explanation', ''), |
|
|
"detected_arc": current_arc, |
|
|
"optimal_arc_for_goal": optimal_arc, |
|
|
"arc_matches_optimal": arc_matches, |
|
|
"potential_uplift": f"+{benchmark.get('uplift_percent', '?')}%" |
|
|
}, |
|
|
"segments": analysis.get('segments', []), |
|
|
"detected_sequence": analysis.get('detected_sequence', []), |
|
|
"missing_elements": missing, |
|
|
"recommendations": recommendations, |
|
|
"benchmark": benchmark |
|
|
} |
|
|
|
|
|
def _arcs_match(self, current: str, optimal: str) -> bool: |
|
|
"""Check if arcs match (fuzzy matching).""" |
|
|
if current == optimal: |
|
|
return True |
|
|
|
|
|
|
|
|
current_norm = current.lower().replace('-', '').replace(' ', '') |
|
|
optimal_norm = optimal.lower().replace('-', '').replace(' ', '') |
|
|
|
|
|
return current_norm == optimal_norm |
|
|
|