Spaces:
Sleeping
Sleeping
| import json | |
| import re | |
| from difflib import SequenceMatcher | |
| from pathlib import Path | |
| from typing import Dict, List | |
| from pydantic import BaseModel | |
| class OriginalityFlag(BaseModel): | |
| template_type: str | |
| matched_pattern: str | |
| script_excerpt: str | |
| suggestion: str | |
| class OriginalityOutput(BaseModel): | |
| flags: List[OriginalityFlag] | |
| originality_score: float | |
| is_generic: bool | |
| unique_elements: List[str] | |
| _TEMPLATE_TYPE_MAP = { | |
| "overused_hooks": "overused_hook", | |
| "overused_structures": "overused_structure", | |
| "overused_cta_phrases": "overused_cta", | |
| "overused_transitions": "overused_transition", | |
| } | |
| _SUGGESTIONS = { | |
| "overused_hook": "Rewrite the hook with a specific data point, personal story, or unexpected angle.", | |
| "overused_structure": "Try an unconventional narrative arc — start mid-story or end with the question.", | |
| "overused_cta": "Replace with a specific, contextual call-to-action tied to the video's content.", | |
| "overused_transition": "Cut the transition filler and jump directly to the next point.", | |
| } | |
| _FUZZY_THRESHOLD = 0.75 | |
| def _split_script(script: str) -> Dict[str, str]: | |
| sentences = re.split(r'(?<=[.!?])\s+', script.strip()) | |
| if len(sentences) <= 5: | |
| return {"hook": script, "body": "", "cta": ""} | |
| hook = " ".join(sentences[:3]) | |
| cta = " ".join(sentences[-2:]) | |
| body = " ".join(sentences[3:-2]) | |
| return {"hook": hook, "body": body, "cta": cta} | |
| def _fuzzy_match(text: str, pattern: str) -> bool: | |
| text_lower = text.lower() | |
| pattern_lower = pattern.lower() | |
| if pattern_lower in text_lower: | |
| return True | |
| ratio = SequenceMatcher(None, text_lower, pattern_lower).ratio() | |
| return ratio >= _FUZZY_THRESHOLD | |
| class OriginalityAgent: | |
| """ | |
| Measures how distinct the script sounds compared to overused Reels formats. | |
| Zero LLM calls — fuzzy string matching against viral_templates.json. | |
| Uses difflib.SequenceMatcher (threshold: 0.75 similarity). | |
| """ | |
| def __init__(self, templates_path: str = "data/viral_templates.json"): | |
| resolved = Path(templates_path) | |
| if not resolved.is_absolute(): | |
| resolved = Path(__file__).parent.parent / templates_path | |
| with open(resolved) as f: | |
| self._templates: Dict[str, List[str]] = json.load(f) | |
| def check(self, script: str) -> OriginalityOutput: | |
| sections = _split_script(script) | |
| flags: List[OriginalityFlag] = [] | |
| matched_sections = set() | |
| for category, patterns in self._templates.items(): | |
| template_type = _TEMPLATE_TYPE_MAP.get(category, category) | |
| suggestion = _SUGGESTIONS.get(template_type, "Make this section more original.") | |
| for pos, text in sections.items(): | |
| if not text: | |
| continue | |
| sentences = re.split(r'(?<=[.!?])\s+', text.strip()) | |
| for sentence in sentences: | |
| for pattern in patterns: | |
| if _fuzzy_match(sentence, pattern): | |
| section_key = f"{pos}:{sentence[:40]}" | |
| matched_sections.add(section_key) | |
| flags.append(OriginalityFlag( | |
| template_type=template_type, | |
| matched_pattern=pattern, | |
| script_excerpt=sentence[:80], | |
| suggestion=suggestion, | |
| )) | |
| all_sentences = re.split(r'(?<=[.!?])\s+', script.strip()) | |
| total = max(len(all_sentences), 1) | |
| matched_count = len(matched_sections) | |
| originality_score = max(0.0, min(1.0, 1.0 - (matched_count / total))) | |
| unique_elements = [ | |
| s for s in all_sentences | |
| if not any( | |
| _fuzzy_match(s, pattern) | |
| for patterns in self._templates.values() | |
| for pattern in patterns | |
| ) | |
| ] | |
| return OriginalityOutput( | |
| flags=flags, | |
| originality_score=originality_score, | |
| is_generic=originality_score < 0.4, | |
| unique_elements=unique_elements, | |
| ) | |