File size: 3,630 Bytes
6bdfadc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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")

        # Build recommendations
        recommendations = []

        # 1. Check if current arc matches optimal
        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', '')
            })

        # 2. Check missing elements
        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', '')
            })

        # 3. Story presence recommendation
        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"
            })

        # Sort by priority
        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

        # Normalize
        current_norm = current.lower().replace('-', '').replace(' ', '')
        optimal_norm = optimal.lower().replace('-', '').replace(' ', '')

        return current_norm == optimal_norm