Spaces:
Sleeping
Sleeping
| import re | |
| from dataclasses import dataclass, field | |
| from typing import Dict | |
| from viral_script_engine.rewards.base import BaseReward | |
| from viral_script_engine.platforms.platform_spec import PlatformRegistry | |
| _DEAD_OPENERS = [ | |
| "hey guys", "welcome back", "today i want to", "so today", | |
| "in this video", "what's up everyone", "hey everyone", | |
| "guys today", "hello everyone", "so basically", | |
| ] | |
| _COMMON_WORDS = { | |
| 'i', 'the', 'a', 'an', 'my', 'your', 'its', 'it', 'is', 'are', | |
| 'was', 'were', 'be', 'been', "i've", "i'm", "it's", "here's", | |
| 'today', 'and', 'but', 'so', 'that', 'this', 'these', 'those', | |
| } | |
| class HookRewardResult: | |
| score: float | |
| checks_passed: int | |
| check_details: Dict[str, bool] = field(default_factory=dict) | |
| def _extract_hook(text: str) -> str: | |
| sentences = re.split(r'(?<=[.!?])\s+', text.strip()) | |
| hook = " ".join(sentences[:3]) if len(sentences) >= 3 else text | |
| words = hook.split() | |
| return " ".join(words[:50]) if len(words) > 50 else hook | |
| class HookStrengthReward(BaseReward): | |
| def __init__(self): | |
| self.platform_registry = PlatformRegistry() | |
| def score(self, script: str, platform: str = "Reels") -> HookRewardResult: | |
| spec = self.platform_registry.get(platform) | |
| hook = _extract_hook(script) | |
| hook_lower = hook.lower() | |
| first_sentence = re.split(r'(?<=[.!?])\s+', hook.strip())[0].lower() | |
| checks = { | |
| "promise": self._check_promise(hook_lower), | |
| "curiosity": self._check_curiosity(hook_lower), | |
| "specificity": self._check_specificity(hook), | |
| "front_load": self._check_front_load(first_sentence), | |
| "anti_filler": self._check_anti_filler(hook_lower), | |
| } | |
| passed = sum(checks.values()) | |
| base_score = passed / 5 | |
| # Platform-aware length fit: hook should be within spec.hook_length_words | |
| hook_word_count = len(hook.split()) | |
| if hook_word_count <= spec.hook_length_words: | |
| length_score = 1.0 | |
| else: | |
| overrun = hook_word_count - spec.hook_length_words | |
| length_score = max(0.0, 1.0 - overrun / spec.hook_length_words) | |
| final_score = base_score * 0.85 + length_score * 0.15 | |
| checks["length_fit"] = hook_word_count <= spec.hook_length_words | |
| return HookRewardResult( | |
| score=min(1.0, max(0.0, final_score)), | |
| checks_passed=passed, | |
| check_details=checks, | |
| ) | |
| def _check_promise(self, hook: str) -> bool: | |
| bad = ["hey guys", "welcome back", "today we're talking about"] | |
| if any(b in hook for b in bad): | |
| return False | |
| patterns = [ | |
| r'\d', | |
| r'\bhow to\b', | |
| r'\bwhy\b', | |
| r'\bwhat happens when\b', | |
| r'\bi made\b', | |
| ] | |
| return any(re.search(p, hook) for p in patterns) | |
| def _check_curiosity(self, hook: str) -> bool: | |
| patterns = [ | |
| r'\?', | |
| r"but here'?s the thing", | |
| r"most \w+ don'?t know", | |
| r"the secret is", | |
| r"nobody tells you", | |
| r"most people don'?t", | |
| ] | |
| if not any(re.search(p, hook) for p in patterns): | |
| return False | |
| first = re.split(r'(?<=[.!?])\s+', hook)[0] | |
| if re.search(r'\?', first) and re.search(r'\b(is|are|was|were|means|equals)\b', first): | |
| return False | |
| return True | |
| def _check_specificity(self, hook: str) -> bool: | |
| if re.search(r'\d', hook): | |
| return True | |
| sentences = re.split(r'(?<=[.!?])\s+', hook) | |
| for sentence in sentences: | |
| words = sentence.split()[1:] | |
| for w in words: | |
| clean = w.strip('.,!?;:\'"') | |
| if clean and clean[0].isupper() and clean.lower() not in _COMMON_WORDS: | |
| return True | |
| return False | |
| def _check_front_load(self, first_sentence: str) -> bool: | |
| signals = 0 | |
| if re.search(r'\d', first_sentence): | |
| signals += 1 | |
| promise_patterns = [r'\bhow to\b', r'\bwhy\b', r'\bwhat happens when\b', r'\bi made\b'] | |
| if any(re.search(p, first_sentence) for p in promise_patterns): | |
| signals += 1 | |
| if re.search(r'\?', first_sentence): | |
| signals += 1 | |
| return signals >= 2 | |
| def _check_anti_filler(self, hook: str) -> bool: | |
| return not any(hook.startswith(opener) for opener in _DEAD_OPENERS) | |