"""Scriptwriter prompt builders (SPEC_NEW §7.5) — built in Phase 4. Constraints encoded in the system prompt: spoken colloquial Swedish (talspråk — "asså", "typ", natural contractions); each segment ≤ MAX_SPOKEN_WORDS words AND sized so duration.decide_duration accepts it (split long bodies, never overrun an 8s clip — SPEC_NEW §12.1/§12.2); hook front-loads the claim in the first ~5 words; CTA names the action concretely; numbers spelled out in Swedish words; no digits, no quote characters. The validator (prompts.veo_segment.validate_spoken_text + duration.decide_duration) rejects oversize segments before they ever reach Veo — this prompt aims to never trip it. """ from __future__ import annotations import json from typing import Any from ..duration import MAX_SPOKEN_WORDS SCRIPTWRITER_SYSTEM = f"""You write one short Swedish UGC video-ad script that an \ avatar says straight to camera. You are given a creative BRIEF (angle, hypothesis), \ the AVATAR's persona/voice, and a few WINNING HOOK examples to echo the style of. You output three parts: a hook, zero to four body segments, and a cta. Each part is \ ONE spoken line — one clip in which the avatar says exactly that line to camera. HARD RULES — every line must obey all of them: - Colloquial spoken Swedish (talspråk): natural contractions, fillers like "asså", \ "typ", "liksom" where they fit. It must sound like a real person talking, not ad copy. - EVERY line is at most {MAX_SPOKEN_WORDS} spoken words. Aim 6–14 words per body \ segment. If an idea needs more than {MAX_SPOKEN_WORDS} words, SPLIT it into two body \ segments — never overrun a single clip. Shorter lines sit on shorter clips (about \ nine words → four seconds, ten to fourteen → six seconds, fifteen to eighteen → \ eight seconds), so keep each line tight. - The HOOK front-loads the main claim in the first ~5 words — the scroll-stopper \ comes first, no warm-up. - The CTA names the action concretely (what to do next), in the avatar's voice. - Spell out EVERY number or price in Swedish words ("trehundra kronor", never \ "300 kr"). NO digits anywhere. - Do NOT use any quotation marks of any kind inside a line. - Stay true to the brief's angle and hypothesis and the avatar's persona; do not \ invent forbidden or unsupported claims. Output ONLY this JSON object — no prose, no markdown fences: {{"hook": "string", "body_segments": ["string", ...], "cta": "string"}}""" def build_user_prompt( brief: dict[str, Any], avatar: dict[str, Any], hook_examples: list[str] | None = None, ) -> str: """Render the brief + avatar persona + winning-hook examples into the user message (SPEC_NEW §7.5).""" persona = avatar.get("persona") or {} examples = [h for h in (hook_examples or []) if h and str(h).strip()] examples_block = ( "\n".join(f"- {h}" for h in examples) if examples else "- (no proven hooks yet — write a fresh scroll-stopper)" ) return ( f"BRIEF\n" f' angle: {brief.get("angle") or ""}\n' f' mode: {brief.get("mode") or ""}\n' f' hypothesis: {brief.get("hypothesis") or ""}\n' f' rationale: {brief.get("rationale") or ""}\n\n' f"AVATAR\n" f' name: {avatar.get("name") or ""}\n' f" persona: {json.dumps(persona, ensure_ascii=False)}\n\n" f"WINNING HOOK EXAMPLES (echo this register, do not copy verbatim):\n" f"{examples_block}\n\n" f"Write the script as the JSON object {{hook, body_segments[], cta}}. " f"Keep every line short and in spoken Swedish." )