Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from typing import Any, List, Optional | |
| from pydantic import BaseModel | |
| from viral_script_engine.environment.observations import DebateRound, RewardComponents | |
| _SEVERITY_ORDER = {"high": 3, "medium": 2, "low": 1} | |
| _CRITIQUE_TO_ACTION = { | |
| "hook_weakness": "hook_rewrite", | |
| "pacing_issue": "section_reorder", | |
| "cultural_mismatch": "cultural_ref_sub", | |
| "cta_buried": "cta_placement", | |
| "coherence_break": "section_reorder", | |
| "retention_risk": "hook_rewrite", | |
| } | |
| _ACTION_TO_TARGET = { | |
| "hook_rewrite": "hook", | |
| "section_reorder": "body", | |
| "cultural_ref_sub": "body", | |
| "cta_placement": "cta", | |
| } | |
| class TrajectoryType: | |
| CRITIC_FIRST = "critic_first" # Trajectory A: act on Critic's top claim first | |
| DEFENDER_FIRST = "defender_first" # Trajectory B: act on Defender's concern first | |
| class Trajectory(BaseModel): | |
| trajectory_id: str | |
| trajectory_type: str | |
| initial_script: str | |
| current_script: str | |
| steps: List[Any] = [] | |
| cumulative_reward: float = 0.0 | |
| final_reward_components: Optional[Any] = None | |
| terminated: bool = False | |
| step_count: int = 0 | |
| def get_forced_first_action( | |
| self, | |
| critic_claims: List[Any], | |
| defender_output: Any, | |
| ) -> dict: | |
| """ | |
| Returns the forced first action based on trajectory type. | |
| CRITIC_FIRST: pick the action that addresses the highest-severity CritiqueClaim. | |
| DEFENDER_FIRST: pick the action that preserves the core_strength_quote. | |
| If core_strength is in hook → hook_rewrite is risky → pick cta_placement first. | |
| """ | |
| if self.trajectory_type == TrajectoryType.CRITIC_FIRST: | |
| return self._critic_first_action(critic_claims) | |
| return self._defender_first_action(critic_claims, defender_output) | |
| def _critic_first_action(self, critic_claims: List[Any]) -> dict: | |
| if not critic_claims: | |
| return _fallback_action("C1") | |
| sorted_claims = sorted( | |
| critic_claims, | |
| key=lambda c: _SEVERITY_ORDER.get(getattr(c, "severity", "low"), 0), | |
| reverse=True, | |
| ) | |
| top = sorted_claims[0] | |
| action_type = _CRITIQUE_TO_ACTION.get( | |
| getattr(top, "critique_class", ""), "hook_rewrite" | |
| ) | |
| return { | |
| "action_type": action_type, | |
| "target_section": _ACTION_TO_TARGET.get(action_type, "hook"), | |
| "instruction": ( | |
| f"Address the top critic concern: " | |
| f"{getattr(top, 'claim_text', '')[:100]}" | |
| ), | |
| "critique_claim_id": getattr(top, "claim_id", "C1"), | |
| "reasoning": ( | |
| f"CRITIC_FIRST: targeting highest-severity " | |
| f"{getattr(top, 'critique_class', '')} claim ({getattr(top, 'severity', '')})." | |
| ), | |
| } | |
| def _defender_first_action(self, critic_claims: List[Any], defender_output: Any) -> dict: | |
| core_quote = "" | |
| flagged: set = set() | |
| if defender_output is not None: | |
| if hasattr(defender_output, "core_strength_quote"): | |
| core_quote = defender_output.core_strength_quote or "" | |
| flagged = set(getattr(defender_output, "flagged_critic_claims", [])) | |
| elif isinstance(defender_output, dict): | |
| core_quote = defender_output.get("core_strength_quote", "") | |
| flagged = set(defender_output.get("flagged_critic_claims", [])) | |
| # Core strength is "in the hook" if its first 20 chars appear in the leading 100 chars | |
| hook_portion = self.current_script[:100].lower() | |
| core_in_hook = bool(core_quote) and core_quote.lower()[:20] in hook_portion | |
| if core_in_hook: | |
| # Hook is precious — choose a safe non-hook action first | |
| action_type = "cta_placement" | |
| target = "cta" | |
| instruction = ( | |
| "Improve CTA positioning to boost completion rate " | |
| "without altering the hook." | |
| ) | |
| claim_id = ( | |
| getattr(critic_claims[0], "claim_id", "C1") | |
| if critic_claims else "C1" | |
| ) | |
| else: | |
| # Core is in body — safe to improve the hook | |
| action_type = "hook_rewrite" | |
| target = "hook" | |
| instruction = ( | |
| "Rewrite the hook for stronger attention capture " | |
| "while preserving the core body voice." | |
| ) | |
| unflagged = [ | |
| c for c in critic_claims | |
| if getattr(c, "claim_id", "") not in flagged | |
| ] | |
| claim = unflagged[0] if unflagged else (critic_claims[0] if critic_claims else None) | |
| claim_id = getattr(claim, "claim_id", "C1") if claim else "C1" | |
| return { | |
| "action_type": action_type, | |
| "target_section": target, | |
| "instruction": instruction, | |
| "critique_claim_id": claim_id, | |
| "reasoning": ( | |
| "DEFENDER_FIRST: preserving Defender's core strength " | |
| "and regional voice before addressing critic claims." | |
| ), | |
| } | |
| def _fallback_action(claim_id: str = "C1") -> dict: | |
| return { | |
| "action_type": "hook_rewrite", | |
| "target_section": "hook", | |
| "instruction": "Rewrite the hook to open with a strong immediate claim.", | |
| "critique_claim_id": claim_id, | |
| "reasoning": "Fallback: no critic claims available.", | |
| } | |