{"count":8,"recipes":[{"id":"betting-guide","name":"Betting Guide Article","type":"article","trigger":"match-scheduled","triggerOffset":"-18h","dataInputs":{"match":"matches/{eventId}","insights":"raw/valuestats/{eventId}","standings":"leagues/{leagueId}","odds":"raw/kambi/{eventId}"},"promptTemplate":"Write a betting guide article analyzing the odds markets and implied probabilities for this match. Focus on data-driven analysis only.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nDATE: {{ match.kickoff | format_date }}\n{% if match.venue %}VENUE: {{ match.venue }}{% endif %}\n\nHOME TEAM — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position: {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored: {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded: {{ match.homeTeam.goalsConceded }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5): {{ match.homeTeam.form | join(', ') }}{% endif %}\n{% if match.homeTeam.matchesPlayed %}- Matches played: {{ match.homeTeam.matchesPlayed }}{% endif %}\n\nAWAY TEAM — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position: {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored: {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded: {{ match.awayTeam.goalsConceded }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5): {{ match.awayTeam.form | join(', ') }}{% endif %}\n{% if match.awayTeam.matchesPlayed %}- Matches played: {{ match.awayTeam.matchesPlayed }}{% endif %}\n\n{% if match.odds %}\nCURRENT ODDS (vid skrivande stund):\n- 1 (Home): {{ match.odds.home }}\n- X (Draw): {{ match.odds.draw }}\n- 2 (Away): {{ match.odds.away }}\n{% if match.odds.overUnder %}\n- Over/Under {{ match.odds.overUnder.line }}: Over {{ match.odds.overUnder.over }}, Under {{ match.odds.overUnder.under }}\n{% endif %}\n{% if match.odds.btts %}\n- BTTS (Yes): {{ match.odds.btts.yes }}, BTTS (No): {{ match.odds.btts.no }}\n{% endif %}\n{% if match.odds.asianHandicap %}\n- Asian Handicap available\n{% endif %}\n{% endif %}\n\n{% if match.h2h and match.h2h.matches and match.h2h.matches.length %}\nH2H HISTORICAL CONTEXT:\n{% for m in match.h2h.matches | take(10) %}\n- {{ m.date }}: {{ m.result }}\n{% endfor %}\n{% if match.h2h.homeWins %}\n- Home Team H2H Wins: {{ match.h2h.homeWins }}\n{% endif %}\n{% if match.h2h.draws %}\n- H2H Draws: {{ match.h2h.draws }}\n{% endif %}\n{% if match.h2h.awayWins %}\n- Away Team H2H Wins: {{ match.h2h.awayWins }}\n{% endif %}\n{% endif %}\n\n{% if match.insights and match.insights.length %}\nKEY STATISTICAL INSIGHTS (sorted by confidence):\n{% for insight in match.insights | sort_by('confidence') | take(10) %}\n- {{ insight.criterion }}: {{ insight.suggestion }} (confidence: {{ insight.confidence }})\n{% endfor %}\n{% endif %}\n\nARTICLE STRUCTURE:\n{{ journalist.articleStructure if journalist else \"Use 4-6 sections analyzing different betting markets.\" }}\n\nTASK:\nAnalyze the betting odds and markets available for this match.\nCalculate implied probabilities from the odds and compare them to actual historical win rates.\nDiscuss specific betting markets: 1X2 (Match Result), Over/Under, Both Teams to Score, etc.\nPresent the data neutrally — show what the odds imply vs. what history suggests.\n\nCRITICAL RULES:\n- Use ONLY the data provided above. NEVER invent odds, probabilities, or statistics not present in this prompt.\n- NEVER encourage gambling or recommend specific bets. Present data analysis only.\n- Do not encourage betting — present data and analysis only (write any in-body copy in the locale's language).\n- If odds data is missing, state this clearly and work with historical data instead.\n- If a data point is missing, do NOT mention it or guess. Skip that aspect.\n- Every statistic and odd you cite must appear in the data above or be derived directly from provided numbers.\n\nOUTPUT FORMAT — return EXACTLY this JSON structure:\n{\n  \"match_id\": \"{eventId}\",\n  \"headline\": \"max 80 chars, written in the brand's target language (locale: {{ locale }}), no period at the end\",\n  \"manchet\": \"1-2 sentences, max 180 chars, hook the reader\",\n  \"read_time_minutes\": 3,\n  \"timestamp\": \"{{ match.kickoff }}\",\n  \"body\": [\n    {\n      \"sub_headline\": \"3-8 words, max 50 chars\",\n      \"paragraphs\": [\"paragraph 1\", \"paragraph 2\"]\n    }\n  ]\n}\n\nRULES:\n- 4-6 body sections, 600-1200 words total\n- Each section focuses on a different betting market or analysis angle\n- Each section: sub_headline + 2-4 paragraphs\n- No bullet points in body text (data can be structured in prose)\n- No tables, no emojis in body text\n- No invented statistics — use ONLY the data provided above\n- NEVER recommend specific bets or encourage gambling\n- Headline and manchet must NOT repeat the same information\n- Respond with valid JSON only\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":3000,"journalist":null},{"id":"h2h-deep-dive","name":"Head-to-Head Deep Dive Article","type":"article","trigger":"match-scheduled","triggerOffset":"-48h","dataInputs":{"match":"matches/{eventId}","insights":"raw/valuestats/{eventId}","standings":"leagues/{leagueId}","odds":"raw/kambi/{eventId}"},"promptTemplate":"Write a deep-dive head-to-head analysis article focusing on the historical patterns and trends between these two teams.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nDATE: {{ match.kickoff | format_date }}\n{% if match.venue %}VENUE: {{ match.venue }}{% endif %}\n\nHOME TEAM — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position: {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored (season): {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded (season): {{ match.homeTeam.goalsConceded }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5): {{ match.homeTeam.form | join(', ') }}{% endif %}\n\nAWAY TEAM — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position: {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored (season): {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded (season): {{ match.awayTeam.goalsConceded }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5): {{ match.awayTeam.form | join(', ') }}{% endif %}\n\n{% if match.h2h and match.h2h.matches and match.h2h.matches.length %}\nH2H HISTORY (senaste möten):\n{% for m in match.h2h.matches | take(10) %}\n- {{ m.date }}: {{ m.result }}\n{% endfor %}\n{% if match.h2h.summary %}\nH2H SUMMARY: {{ match.h2h.summary }}\n{% endif %}\n{% if match.h2h.homeWins %}\n- Home Team Wins: {{ match.h2h.homeWins }}\n{% endif %}\n{% if match.h2h.draws %}\n- Draws: {{ match.h2h.draws }}\n{% endif %}\n{% if match.h2h.awayWins %}\n- Away Team Wins: {{ match.h2h.awayWins }}\n{% endif %}\n{% if match.h2h.homeGoalsFor %}\n- Home Team Goals For: {{ match.h2h.homeGoalsFor }}\n{% endif %}\n{% if match.h2h.homeGoalsAgainst %}\n- Home Team Goals Against: {{ match.h2h.homeGoalsAgainst }}\n{% endif %}\n{% else %}\nNOTE: Insufficient H2H data available for this matchup. Use current season form and standings as alternative analysis framework.\n{% endif %}\n\n{% if match.insights and match.insights.length %}\nKEY INSIGHTS (sorted by confidence):\n{% for insight in match.insights | sort_by('confidence') | take(10) %}\n- {{ insight.criterion }}: {{ insight.suggestion }} (confidence: {{ insight.confidence }})\n{% endfor %}\n{% endif %}\n\nARTICLE STRUCTURE:\n{{ journalist.articleStructure if journalist else \"Use 4-6 sections analyzing H2H patterns, trends, and historical context.\" }}\n\nTASK:\nAnalyze the historical relationship between these two teams. Identify patterns in their matchups.\nLook for trends: Does one team dominate at home? Away performance patterns? Scoring trends?\nConnect H2H history to current form and league position.\n\nCRITICAL RULES:\n- Use ONLY the data provided above. NEVER invent H2H records, dates, results, or statistics not present in this prompt.\n- If H2H data is missing or insufficient (no previous matches), state this clearly and analyze current season performance instead.\n- If a data point is missing, do NOT mention it or guess. Skip that aspect.\n- Every statistic you cite must appear in the data above or be derived directly from the provided numbers.\n\nOUTPUT FORMAT — return EXACTLY this JSON structure:\n{\n  \"match_id\": \"{eventId}\",\n  \"headline\": \"max 80 chars, written in the brand's target language (locale: {{ locale }}), no period at the end\",\n  \"manchet\": \"1-2 sentences, max 180 chars, hook the reader\",\n  \"read_time_minutes\": 3,\n  \"timestamp\": \"{{ match.kickoff }}\",\n  \"body\": [\n    {\n      \"sub_headline\": \"3-8 words, max 50 chars\",\n      \"paragraphs\": [\"paragraph 1\", \"paragraph 2\"]\n    }\n  ]\n}\n\nRULES:\n- 4-6 body sections, 600-1200 words total\n- Each section: sub_headline + 2-4 paragraphs\n- No bullet points, no tables, no emojis in body text\n- No invented statistics — use ONLY the data provided above\n- Headline and manchet must NOT repeat the same information\n- Respond with valid JSON only\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":3000,"journalist":null},{"id":"player-spotlight","name":"Player Spotlight Article","type":"article","trigger":"match-scheduled","triggerOffset":"-36h","dataInputs":{"match":"matches/{eventId}","insights":"raw/valuestats/{eventId}","standings":"leagues/{leagueId}","odds":"raw/kambi/{eventId}"},"promptTemplate":"Write a player spotlight article focusing on the key players and player matchups in this upcoming match.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nDATE: {{ match.kickoff | format_date }}\n{% if match.venue %}VENUE: {{ match.venue }}{% endif %}\n\nHOME TEAM — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position: {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored: {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded: {{ match.homeTeam.goalsConceded }}{% endif %}\n{% if match.homeTeam.matchesPlayed %}- Matches played: {{ match.homeTeam.matchesPlayed }}{% endif %}\n\nAWAY TEAM — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position: {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored: {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded: {{ match.awayTeam.goalsConceded }}{% endif %}\n{% if match.awayTeam.matchesPlayed %}- Matches played: {{ match.awayTeam.matchesPlayed }}{% endif %}\n\n{% if match.insights and match.insights.length %}\nKEY INSIGHTS (sorted by confidence — use player-relevant insights for your spotlight):\n{% for insight in match.insights | sort_by('confidence') | take(15) %}\n- {{ insight.criterion }}: {{ insight.suggestion }} (confidence: {{ insight.confidence }})\n{% endfor %}\n{% endif %}\n\n{% if match.h2h and match.h2h.matches and match.h2h.matches.length %}\nH2H HISTORY (senaste möten):\n{% for m in match.h2h.matches | take(5) %}\n- {{ m.date }}: {{ m.result }}\n{% endfor %}\n{% endif %}\n\nARTICLE STRUCTURE:\n{{ journalist.articleStructure if journalist else \"Use 4-6 sections with clear subheadings, focusing on individual players and their matchups.\" }}\n\nTASK:\nIdentify the most interesting player matchups and influential players from the available data.\nFocus on their season statistics, roles in their teams, recent form, and why this particular matchup matters.\nIf player-specific data is limited, analyze the team statistics to infer key player contributions.\n\nCRITICAL RULES:\n- Use ONLY the data provided above. NEVER invent player statistics, names, or performance metrics not present in this prompt.\n- If a data point is missing (e.g., no specific player stats, no goals by player), do NOT mention it or guess. Skip that aspect.\n- Every statistic you cite must appear in the data above or be derived directly from the provided numbers.\n\nOUTPUT FORMAT — return EXACTLY this JSON structure:\n{\n  \"match_id\": \"{eventId}\",\n  \"headline\": \"max 80 chars, written in the brand's target language (locale: {{ locale }}), no period at the end\",\n  \"manchet\": \"1-2 sentences, max 180 chars, hook the reader\",\n  \"read_time_minutes\": 3,\n  \"timestamp\": \"{{ match.kickoff }}\",\n  \"body\": [\n    {\n      \"sub_headline\": \"3-8 words, max 50 chars\",\n      \"paragraphs\": [\"paragraph 1\", \"paragraph 2\"]\n    }\n  ]\n}\n\nRULES:\n- 4-5 body sections, 600-1200 words total, each focused on a player or player matchup\n- Each section: sub_headline + 2-4 paragraphs\n- No bullet points, no tables, no emojis in body text\n- No invented statistics — use ONLY the data provided above\n- Headline and manchet must NOT repeat the same information\n- Respond with valid JSON only\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":3000,"journalist":null},{"id":"post-match-recap","name":"Post-Match Recap Article","type":"article","trigger":"match-finished","triggerOffset":"+30m","dataInputs":{"match":"matches/{eventId}","insights":"raw/valuestats/{eventId}","standings":"leagues/{leagueId}"},"promptTemplate":"Write a post-match recap article for this match. The match is OVER.\nUse past tense throughout. NEVER preview, predict, or speculate about\nthis fixture as if it hasn't been played — it has.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nDATE: {{ match.kickoff | format_date }}\n{% if match.venue %}VENUE: {{ match.venue }}{% endif %}\n\nFINAL SCORE:\n{% if match.score %}- {{ match.homeTeam.name }} {{ match.score.home }} - {{ match.score.away }} {{ match.awayTeam.name }}{% endif %}\n\nHOME TEAM — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position (after match): {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored (season): {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded (season): {{ match.homeTeam.goalsConceded }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5 incl. this match): {{ match.homeTeam.form | join(', ') }}{% endif %}\n{% if match.homeTeam.matchesPlayed %}- Matches played: {{ match.homeTeam.matchesPlayed }}{% endif %}\n\nAWAY TEAM — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position (after match): {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored (season): {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded (season): {{ match.awayTeam.goalsConceded }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5 incl. this match): {{ match.awayTeam.form | join(', ') }}{% endif %}\n{% if match.awayTeam.matchesPlayed %}- Matches played: {{ match.awayTeam.matchesPlayed }}{% endif %}\n\n{% if match.insights and match.insights.length %}\nKEY DATA POINTS (sorted by confidence):\n{% for insight in match.insights | sort_by('confidence') | take(10) %}\n- {{ insight.criterion }}: {{ insight.suggestion }} (confidence: {{ insight.confidence }})\n{% endfor %}\n{% endif %}\n\n{% if match.h2h and match.h2h.matches and match.h2h.matches.length %}\nH2H BACKDROP (recent meetings before today):\n{% for m in match.h2h.matches | take(5) %}\n- {{ m.date }}: {{ m.result }}\n{% endfor %}\n{% if match.h2h.summary %}\nSummary: {{ match.h2h.summary }}\n{% endif %}\n{% endif %}\n\nARTICLE STRUCTURE:\n{{ journalist.articleStructure if journalist else \"Use 4-6 sections with clear subheadings.\" }}\n\nCRITICAL RULES:\n- The match is FINISHED. Write in past tense about events on the pitch.\n- Use ONLY the data provided above. NEVER invent statistics, xG values,\n  percentages, goal scorers, minutes, or any numbers not present in the prompt.\n- You do NOT have a goal-by-goal timeline. Do NOT invent scorer names,\n  minutes, or chronological narratives. Frame the recap around the final\n  score, the standings/form impact, and the H2H backdrop — these are the\n  facts you actually have.\n- If a data point is missing (e.g. no insights, no h2h), do NOT mention\n  it or guess. Skip that aspect.\n- Every number you cite must appear in the data above.\n- NEVER use future-tense framing like \"kicks off\", \"tonight's match\",\n  \"the upcoming clash\". The match has been played.\n\n{% if availableWidgets and availableWidgets.length %}\nWIDGET EMBEDDING:\nThe following match widgets are available. For each body section, you MAY attach\nthe single most relevant widget. Use the \"widget\" field on the section object.\n\nAvailable widgets for this match:\n{% for w in availableWidgets %}\n- template: \"{{ w.template }}\"{% if w.summary %} — {{ w.summary }}{% endif %}\n{% endfor %}\n\nWidget rules:\n1. Each widget template may appear AT MOST ONCE across all sections.\n2. Not every section needs a widget — intro and conclusion often work better without.\n3. Aim for 3-4 widget placements per article.\n4. Only use templates listed above. Never invent template names.\n5. The \"highlight\" object should contain 1-3 key data points from the widget data\n   that your section text specifically references. Keys must match real fields in the\n   widget data. NEVER invent numbers — only highlight values from the data above.\n6. The \"reason\" field is a short English sentence explaining why this widget fits.\n{% endif %}\n\nOUTPUT FORMAT — return EXACTLY this JSON structure:\n{\n  \"match_id\": \"{eventId}\",\n  \"headline\": \"max 80 chars, written in the brand's target language (locale: {{ locale }}), no period at the end, past-tense recap framing\",\n  \"manchet\": \"1-2 sentences, max 180 chars, hook the reader with what happened\",\n  \"read_time_minutes\": 3,\n  \"timestamp\": \"{{ match.kickoff }}\",\n  \"body\": [\n    {\n      \"sub_headline\": \"3-8 words, max 50 chars\",\n      \"paragraphs\": [\"paragraph 1\", \"paragraph 2\"],\n      \"widget\": {\n        \"template\": \"form-guide\",\n        \"highlight\": { \"draws\": 4 },\n        \"reason\": \"Section discusses the run of draws that led into this match\"\n      }\n    }\n  ]\n}\n\nNOTE: The \"widget\" field is OPTIONAL on each section. Omit it entirely for\nsections that don't need a widget. Do NOT include \"widget\": null.\n\nRULES:\n- 4-6 body sections, 600-1200 words total\n- Each section: sub_headline + 2-4 paragraphs + optional widget\n- No bullet points, no tables, no emojis in body text\n- No invented statistics — use ONLY the data provided above\n- No fabricated goal scorers, minutes, or chronological play-by-play\n- Headline and manchet must NOT repeat the same information\n- Past tense throughout — the match is over\n- Respond with valid JSON only\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":3000,"journalist":null},{"id":"pre-match-preview","name":"Pre-Match Preview Article","type":"article","trigger":"match-scheduled","triggerOffset":"-24h","dataInputs":{"match":"matches/{eventId}","insights":"raw/valuestats/{eventId}","standings":"leagues/{leagueId}","odds":"raw/kambi/{eventId}"},"promptTemplate":"Write a pre-match analysis article for this match.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nDATE: {{ match.kickoff | format_date }}\n{% if match.venue %}VENUE: {{ match.venue }}{% endif %}\n\nHOME TEAM — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position: {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored: {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded: {{ match.homeTeam.goalsConceded }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5): {{ match.homeTeam.form | join(', ') }}{% endif %}\n{% if match.homeTeam.matchesPlayed %}- Matches played: {{ match.homeTeam.matchesPlayed }}{% endif %}\n\nAWAY TEAM — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position: {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored: {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded: {{ match.awayTeam.goalsConceded }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5): {{ match.awayTeam.form | join(', ') }}{% endif %}\n{% if match.awayTeam.matchesPlayed %}- Matches played: {{ match.awayTeam.matchesPlayed }}{% endif %}\n\n{% if match.odds %}\nODDS (vid skrivande stund):\n- 1: {{ match.odds.home }}\n- X: {{ match.odds.draw }}\n- 2: {{ match.odds.away }}\n{% if match.odds.overUnder %}- O/U {{ match.odds.overUnder.line }}: Over {{ match.odds.overUnder.over }}, Under {{ match.odds.overUnder.under }}{% endif %}\n{% if match.odds.btts %}- BTTS: Yes {{ match.odds.btts.yes }}, No {{ match.odds.btts.no }}{% endif %}\n{% endif %}\n\n{% if match.insights and match.insights.length %}\nTOP INSIGHTS (sorted by confidence):\n{% for insight in match.insights | sort_by('confidence') | take(10) %}\n- {{ insight.criterion }}: {{ insight.suggestion }} (confidence: {{ insight.confidence }})\n{% endfor %}\n{% endif %}\n\n{% if match.h2h and match.h2h.matches and match.h2h.matches.length %}\nH2H (senaste möten):\n{% for m in match.h2h.matches | take(5) %}\n- {{ m.date }}: {{ m.result }}\n{% endfor %}\n{% if match.h2h.summary %}\nSummary: {{ match.h2h.summary }}\n{% endif %}\n{% endif %}\n\nARTICLE STRUCTURE:\n{{ journalist.articleStructure if journalist else \"Use 4-6 sections with clear subheadings.\" }}\n\nCRITICAL RULES:\n- Use ONLY the data provided above. NEVER invent statistics, xG values, percentages, or any numbers not present in this prompt.\n- If a data point is missing (e.g. no league position, no form data), do NOT mention it or guess. Skip that aspect.\n- Every number you cite must appear in the data above.\n\n{% if availableWidgets and availableWidgets.length %}\nWIDGET EMBEDDING:\nThe following match widgets are available. For each body section, you MAY attach\nthe single most relevant widget. Use the \"widget\" field on the section object.\n\nAvailable widgets for this match:\n{% for w in availableWidgets %}\n- template: \"{{ w.template }}\"{% if w.summary %} — {{ w.summary }}{% endif %}\n{% endfor %}\n\nWidget rules:\n1. Each widget template may appear AT MOST ONCE across all sections.\n2. Not every section needs a widget — intro and conclusion often work better without.\n3. Aim for 3-4 widget placements per article.\n4. Only use templates listed above. Never invent template names.\n5. The \"highlight\" object should contain 1-3 key data points from the widget data\n   that your section text specifically references. Keys must match real fields in the\n   widget data. NEVER invent numbers — only highlight values from the data above.\n6. The \"reason\" field is a short English sentence explaining why this widget fits.\n{% endif %}\n\nOUTPUT FORMAT — return EXACTLY this JSON structure:\n{\n  \"match_id\": \"{eventId}\",\n  \"headline\": \"max 80 chars, written in the brand's target language (locale: {{ locale }}), no period at the end\",\n  \"manchet\": \"1-2 sentences, max 180 chars, hook the reader\",\n  \"read_time_minutes\": 3,\n  \"timestamp\": \"{{ match.kickoff }}\",\n  \"body\": [\n    {\n      \"sub_headline\": \"3-8 words, max 50 chars\",\n      \"paragraphs\": [\"paragraph 1\", \"paragraph 2\"],\n      \"widget\": {\n        \"template\": \"head-to-head\",\n        \"highlight\": { \"awayWins\": 3, \"homeWins\": 1 },\n        \"reason\": \"Section discusses H2H record\"\n      }\n    }\n  ]\n}\n\nNOTE: The \"widget\" field is OPTIONAL on each section. Omit it entirely for\nsections that don't need a widget. Do NOT include \"widget\": null.\n\nRULES:\n- 4-6 body sections, 600-1200 words total\n- Each section: sub_headline + 2-4 paragraphs + optional widget\n- No bullet points, no tables, no emojis in body text\n- No invented statistics — use ONLY the data provided above\n- Headline and manchet must NOT repeat the same information\n- Respond with valid JSON only\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":3000,"journalist":null},{"id":"tactical-breakdown","name":"Tactical Breakdown Article","type":"article","trigger":"match-scheduled","triggerOffset":"-12h","dataInputs":{"match":"matches/{eventId}","insights":"raw/valuestats/{eventId}","standings":"leagues/{leagueId}","odds":"raw/kambi/{eventId}"},"promptTemplate":"Write a tactical breakdown article analyzing how these two teams play, their tactical approaches, and where the match will be decided.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nDATE: {{ match.kickoff | format_date }}\n{% if match.venue %}VENUE: {{ match.venue }}{% endif %}\n\nHOME TEAM — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position: {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored: {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded: {{ match.homeTeam.goalsConceded }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5): {{ match.homeTeam.form | join(', ') }}{% endif %}\n{% if match.homeTeam.matchesPlayed %}- Matches played: {{ match.homeTeam.matchesPlayed }}{% endif %}\n{% if match.homeTeam.avgGoalsScored %}- Avg goals/match: {{ match.homeTeam.avgGoalsScored }}{% endif %}\n{% if match.homeTeam.avgGoalsConceded %}- Avg goals conceded/match: {{ match.homeTeam.avgGoalsConceded }}{% endif %}\n\nAWAY TEAM — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position: {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored: {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded: {{ match.awayTeam.goalsConceded }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5): {{ match.awayTeam.form | join(', ') }}{% endif %}\n{% if match.awayTeam.matchesPlayed %}- Matches played: {{ match.awayTeam.matchesPlayed }}{% endif %}\n{% if match.awayTeam.avgGoalsScored %}- Avg goals/match: {{ match.awayTeam.avgGoalsScored }}{% endif %}\n{% if match.awayTeam.avgGoalsConceded %}- Avg goals conceded/match: {{ match.awayTeam.avgGoalsConceded }}{% endif %}\n\n{% if match.insights and match.insights.length %}\nTACTICAL INSIGHTS (sorted by confidence):\n{% for insight in match.insights | sort_by('confidence') | take(15) %}\n- {{ insight.criterion }}: {{ insight.suggestion }} (confidence: {{ insight.confidence }})\n{% endfor %}\n{% endif %}\n\n{% if match.h2h and match.h2h.matches and match.h2h.matches.length %}\nH2H CONTEXT (senaste möten):\n{% for m in match.h2h.matches | take(3) %}\n- {{ m.date }}: {{ m.result }}\n{% endfor %}\n{% endif %}\n\nARTICLE STRUCTURE:\n{{ journalist.articleStructure if journalist else \"Use 4-6 sections analyzing tactical setup, key battles, and decisive factors.\" }}\n\nTASK:\nAnalyze the tactical approaches of both teams based on their season statistics.\nIdentify strengths and weaknesses: Who attacks more? Who defends better? Where are the vulnerabilities?\nDiscuss how each team's tactical approach might work (or fail) against the opponent.\nHighlight the key tactical battle that will likely decide the match.\n\nCRITICAL RULES:\n- Use ONLY the data provided above. NEVER invent formation names, tactical concepts, or statistics not present in this prompt.\n- Base tactical analysis on the provided numbers: goals scored/conceded, form, league position, averages.\n- If a data point is missing (e.g., no specific tactical data), do NOT mention it or guess. Derive what you can from available stats.\n- Every statistic you cite must appear in the data above or be derived directly from the provided numbers.\n\nOUTPUT FORMAT — return EXACTLY this JSON structure:\n{\n  \"match_id\": \"{eventId}\",\n  \"headline\": \"max 80 chars, written in the brand's target language (locale: {{ locale }}), no period at the end\",\n  \"manchet\": \"1-2 sentences, max 180 chars, hook the reader\",\n  \"read_time_minutes\": 3,\n  \"timestamp\": \"{{ match.kickoff }}\",\n  \"body\": [\n    {\n      \"sub_headline\": \"3-8 words, max 50 chars\",\n      \"paragraphs\": [\"paragraph 1\", \"paragraph 2\"]\n    }\n  ]\n}\n\nRULES:\n- 4-6 body sections, 600-1200 words total\n- Each section: sub_headline + 2-4 paragraphs\n- Focus on tactical themes, not player spotlights\n- No bullet points, no tables, no emojis in body text\n- No invented statistics — use ONLY the data provided above\n- Headline and manchet must NOT repeat the same information\n- Respond with valid JSON only\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":3000,"journalist":null},{"id":"avatar-postmatch-recap-9x16","name":"Avatar Post-Match Recap (9:16)","type":"video","trigger":"match-finished","triggerOffset":"+1h","dataInputs":{"match":"matches/{eventId}","standings":"leagues/{leagueId}"},"promptTemplate":"You are writing a short, punchy voiceover script for a 9:16 portrait avatar\nvideo RECAPPING a football match that has already been played. The video runs\n30-60 seconds at natural TTS pace (roughly 100-160 words).\n\nLANGUAGE: Write the script in {{ locale_name }}. Match journalist tone — authoritative,\nconversational, direct. No clichés like \"It promised to be a blockbuster.\"\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSNAPSHOT TIME (now): {{ now_iso }}\n\nMATCH PHASE: POST-MATCH\n➜ This match HAS BEEN PLAYED. Frame the entire script as a recap.\n   Use PAST TENSE about events on the pitch. Reference the final result.\n   Never use future-tense framing like \"kicks off tonight\" or\n   \"the upcoming clash\" — the match is OVER.\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nKICKOFF (ISO UTC): {{ kickoff_iso }}\nKICKOFF (LOCAL READABLE): {{ kickoff_local }}\nKICKOFF (RELATIVE TO NOW): {{ kickoff_relative }}\n{% if venue %}VENUE: {{ venue }}{% endif %}\n{% if referee %}REFEREE: {{ referee.name }}{% if referee.nationality %} ({{ referee.nationality }}){% endif %}{% endif %}\n\nFINAL SCORE:\n{% if match.score %}- {{ match.homeTeam.name }} {{ match.score.home }} - {{ match.score.away }} {{ match.awayTeam.name }}{% endif %}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOPENING RULES — the first 3 seconds matter most:\n\n  • Start with a CONCRETE INSIGHT or RESULT-DRIVEN HOOK. No greeting,\n    no \"in today's recap\", no \"let's take a look\", no \"welcome\".\n  • First sentence must be ≤ 7.0 seconds of speech at voiceSpeed 1.1.\n    Aim for 10-16 Swedish words / 12-20 English words. Shorter is\n    punchier. End with a period, never a comma.\n  • No framing phrases. FORBIDDEN openers include:\n    \"Hej\", \"Hejsan\", \"Välkomna\", \"Välkommen\",\n    \"I dagens video/recap\", \"Idag tittar vi\", \"Vi tittar på\",\n    \"Hi\", \"Hello\", \"Welcome\", \"In today's video/recap\",\n    \"Let's take a look\", \"Stay tuned\", \"Coming up\".\n  • The first sentence's HOOK must show up again as the text of the\n    t=0 editorial-headline overlay (compressed into 2-6 words).\n\n  Examples of good recap openers (Swedish):\n    \"Häcken bröt fyra raka mot GAIS — äntligen.\"\n    \"Djurgården vann sin första bortaseger på sju matcher.\"\n    \"Malmö släppte två poäng i en match de kontrollerade.\"\n\n  Examples of good recap openers (English):\n    \"Brighton ended a five-game winless run at the Amex.\"\n    \"Arsenal kept their fourth clean sheet in five away matches.\"\n    \"Spurs lost the points despite outshooting Liverpool two-to-one.\"\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nMANDATORY CONTENT — every script MUST include:\n  1. The names of both teams.\n  2. The competition name ({{ match.league.name }}).\n  3. The final score (use the FINAL SCORE block above verbatim).\n  4. Past-tense framing — the match has been played.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nHOME — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position (after match): {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5 incl. this match): {{ match.homeTeam.form | join(', ') }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored (season): {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded (season): {{ match.homeTeam.goalsConceded }}{% endif %}\n\nAWAY — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position (after match): {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5 incl. this match): {{ match.awayTeam.form | join(', ') }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored (season): {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded (season): {{ match.awayTeam.goalsConceded }}{% endif %}\n\nCRITICAL RULES:\n- Use ONLY the data above. NEVER invent stats, xG, percentages, scorers,\n  minutes, or numbers.\n- You do NOT have a goal-by-goal timeline. Do NOT name the scorers,\n  minutes, or any chronological play-by-play. Frame the recap around\n  the final score, the standings/form impact, and any H2H backdrop.\n- If a data point is missing, do NOT mention it. Skip it.\n- No emojis, no bullet points, no markdown in the script body.\n- The full script string must be plain prose that flows naturally when read aloud.\n- NEVER use future-tense framing — the match is over.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOVERLAY TIMELINE — the interactive part\n\nSame cue-phrase contract as the pre-match recipe. For each overlay,\npick a `cueInPhrase` and `cueOutPhrase` that are 2-5 consecutive\nwords appearing VERBATIM exactly ONCE in `script.full`. The backend\nresolves them to ms offsets via ElevenLabs char-level timestamps.\n\nRules:\n  - Every cueInPhrase and cueOutPhrase appears exactly ONCE in script.full\n  - cueOutPhrase comes AFTER cueInPhrase\n  - Pick distinctive phrases that won't accidentally appear elsewhere\n  - An overlay is on-screen roughly 4-8 seconds\n\nELIGIBLE WIDGET TEMPLATES FOR THIS MATCH:\n{% if available_widgets and available_widgets.length %}\n{% for tmpl in available_widgets %}  - {{ tmpl }}\n{% endfor %}{% else %}  (none — fall back to editorial-headline overlays){% endif %}\n\nWIDGET GUIDE — what each eligible template shows. Pick the widget\nwhose description best matches the sentence you are writing.\nTHIS LIST IS COMPUTED BY THE BACKEND AT RENDER TIME from the eligible\ntemplates above — never paraphrase, never invent template names.\n{% if widget_guide %}\n{{ widget_guide }}\n{% else %}\n    (no widget_guide registered — fall back to editorial-headlines only)\n{% endif %}\n\nOVERLAY TYPES YOU MAY EMIT:\n\n1. `widget` — a real Hemmaklubben widget.\n   REQUIRED FIELDS:\n     { \"type\": \"widget\", \"cueInPhrase\": \"…\", \"cueOutPhrase\": \"…\",\n       \"position\": \"center\",\n       \"widget\": { \"template\": \"<from eligible list>\", \"data\": {} } }\n   Leave `data` as an empty object — the pipeline fills it in.\n\n   SPECIAL case — the `match-header` widget:\n     • ALWAYS included once, ALWAYS as the t=0 intro card.\n     • `position` MUST be `bottom-sheet`, `z` MUST be `10`.\n     • `cueInPhrase` MUST be the first 2-5 words of `script.full`.\n     • `cueOutPhrase` MUST come from the SECOND or THIRD sentence\n       (5-8 seconds in).\n     • match-header.cueOutPhrase MUST appear LATER in script.full\n       than the editorial-headline.cueOutPhrase.\n     • `widget.data` is `{}` — player renders from `article.match`.\n\n2. `editorial-headline` — MANDATORY t=0 intro + optional extra.\n   You MUST emit exactly ONE editorial-headline at t=0 with\n   style:'shout'. You MAY emit one optional non-intro headline later.\n\n   t=0 INTRO HEADLINE — required shape:\n     { \"type\": \"editorial-headline\",\n       \"cueInPhrase\": \"<first 2-5 words of script, verbatim>\",\n       \"cueOutPhrase\": \"<phrase that ends 1.5-3s later>\",\n       \"position\": \"top-banner\",\n       \"z\": 20,\n       \"headline\": {\n         \"text\": \"<2-6 word shout — compressed first sentence hook>\",\n         \"style\": \"shout\",\n         \"kicker\": \"<optional 1-3 word league label>\",\n         \"subline\": \"<optional 3-6 word hook amplifier>\",\n         \"accent\": \"<optional 1-2 word tag>\"\n       }\n     }\n   CONTRACT:\n     • cueInPhrase MUST be the first 2-5 words of script.full.\n     • text MUST share at least one ≥4-char token with sentence 1.\n     • cueOutPhrase MUST come from sentence 1 (last 2-4 words before\n       the first period).\n     • editorial-headline.cueOutPhrase MUST appear EARLIER in\n       script.full than match-header.cueOutPhrase.\n\nNOTE: NO odds-card overlays in post-match recap videos. The match is\nover — there is no live betting market for the result.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSTORY ARC (target 100-160 words, ~80-100s voiceover):\n\n 1. Opening hook       — concrete result-driven insight, ≤3s.\n                         Pair with the t=0 match-header (bottom-sheet, z:10)\n                         AND the t=0 editorial-headline (top-banner, z:20).\n 2. Form / table impact — what this result did to standings and form.\n                         Pair with `form-guide`, `league-table-mini`, or\n                         `momentum-duel`.\n 3. Tactical / pattern — what the data reveals about the result.\n                         Pair with `malprofil` or `kampindsigt`.\n 4. Key player / H2H   — one human angle, season pattern, or H2H thread.\n                         Pair with `spelarprofil`, `dolda-monster`, or\n                         `head-to-head`.\n                         Fallback rule: if `spelarprofil` is NOT in\n                         `available_widgets`, substitute `malprofil`.\n 5. Close              — one-line kicker about what comes next for\n                         each side, OR what this result means for the\n                         season story. NO odds card.\n\nMinimum overlay composition:\n  - 1 editorial-headline at t=0 (style:'shout', top-banner, z:20) ← MANDATORY\n  - 1 match-header widget at t=0 (bottom-sheet, z:10)             ← MANDATORY\n  - 3 content widgets covering steps 2, 3, and 4 of the arc\n  - 0 or 1 optional editorial-headline later for dramatic punctuation\nTotal: 5-6 overlays.\n\nNon-intro overlays MUST have animInMs ≥ 1000ms (cueInPhrase may not\nsit in the first second of the script).\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOUTPUT FORMAT — return EXACTLY this JSON structure, no markdown, no code fences:\n\n{\n  \"article\": {\n    \"headline\": \"max 80 chars, no trailing period, past-tense recap framing\",\n    \"manchet\": \"1-2 sentences, max 180 chars, hook the reader with what happened\",\n    \"teaserBody\": \"50-150 word prose body for the article page, \\n\\n between paragraphs\",\n    \"tags\": [\"lowercase\", \"slugged\", \"tags\"]\n  },\n  \"script\": {\n    \"full\": \"The complete voiceover script as one continuous string.\"\n  },\n  \"overlays\": [\n    {\n      \"id\": \"o0-headline\",\n      \"type\": \"editorial-headline\",\n      \"cueInPhrase\": \"Häcken bröt fyra\",\n      \"cueOutPhrase\": \"äntligen\",\n      \"position\": \"top-banner\",\n      \"z\": 20,\n      \"headline\": {\n        \"text\": \"ÄNTLIGEN — HÄCKEN VANN\",\n        \"style\": \"shout\",\n        \"kicker\": \"Allsvenskan • Omgång 5\",\n        \"subline\": \"Första segern mot GAIS på fyra möten\",\n        \"accent\": \"GENOMBROTT\"\n      }\n    },\n    {\n      \"id\": \"o0-header\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"Häcken bröt fyra\",\n      \"cueOutPhrase\": \"av matchen\",\n      \"position\": \"bottom-sheet\",\n      \"z\": 10,\n      \"widget\": { \"template\": \"match-header\", \"data\": {} }\n    },\n    {\n      \"id\": \"o1\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"Med segern klättrade\",\n      \"cueOutPhrase\": \"fyra omgångar kvar\",\n      \"position\": \"center\",\n      \"widget\": { \"template\": \"ihs-overview\", \"data\": {} }\n    },\n    {\n      \"id\": \"o2\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"målen kom efter paus\",\n      \"cueOutPhrase\": \"vände matchen\",\n      \"position\": \"center\",\n      \"widget\": { \"template\": \"ihs-goals\", \"data\": {} }\n    },\n    {\n      \"id\": \"o3\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"historiken talar nu\",\n      \"cueOutPhrase\": \"fyra av fem senaste\",\n      \"position\": \"center\",\n      \"widget\": { \"template\": \"ihs-previous-meetings\", \"data\": {} }\n    }\n  ]\n}\n\nRULES RECAP:\n- 100-160 words in script.full, all past tense\n- First sentence is a concrete result-driven hook, ≤7.0s, no greetings\n- 5-6 overlays total: 1 intro editorial-headline + 1 match-header\n  (both at t=0) + ≥3 content widgets + optional non-intro headline\n- NO odds-card overlays\n- Intro editorial-headline.text shares ≥1 token (≥4 chars) with sentence 1\n- match-header.cueOutPhrase ends AFTER editorial-headline.cueOutPhrase\n- Non-intro overlays must have cueInPhrase AFTER the first second\n- Every widget.template MUST be from the ELIGIBLE list above\n- widget.data MUST be left as `{}` — the pipeline fills it\n- Every cueInPhrase/cueOutPhrase appears exactly ONCE in script.full, verbatim\n- cueOutPhrase comes after cueInPhrase\n- NEVER invent scorers, minutes, xG, percentages, or any numbers not given\n- Respond with valid JSON only — no markdown, no code fences\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":2000,"journalist":null},{"id":"avatar-prematch-preview-9x16","name":"Avatar Pre-Match Preview (9:16)","type":"video","trigger":"match-scheduled","triggerOffset":"-24h","dataInputs":{"match":"matches/{eventId}","standings":"leagues/{leagueId}"},"promptTemplate":"You are writing a short, punchy voiceover script for a 9:16 portrait avatar\nvideo previewing a football match. The video runs 30–60 seconds at\nnatural TTS pace (roughly 100–160 words).\n\nLANGUAGE: Write the script in {{ locale_name }}. Match journalist tone — authoritative,\nconversational, direct. No clichés like \"It promises to be a blockbuster.\"\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSNAPSHOT TIME (now): {{ now_iso }}\n\nMATCH PHASE: {{ phase | upper }}\n{% if phase == 'pre-match' %}\n➜ This match has NOT been played yet. Frame it as a PREVIEW. Never use past tense\n   about the match itself. Say who plays, when, and what's at stake.\n   Mention the kickoff — exact day and time are provided below.\n   Forecast / preview language only. Do NOT narrate events as if they've happened.\n{% elif phase == 'live' %}\n➜ The match is CURRENTLY IN PROGRESS. Acknowledge it's underway. Reference\n   the kickoff that already happened and current scoreline if present.\n{% else %}\n➜ This match has ALREADY BEEN PLAYED. Frame it as a POST-MATCH recap.\n   Reference the final result / key stats. Use past tense about what happened\n   on the pitch. Never say \"kicks off tonight\" or similar future-tense framing.\n{% endif %}\n\nMATCH: {{ match.homeTeam.name }} vs {{ match.awayTeam.name }}\nCOMPETITION: {{ match.league.name }}{% if match.league.round %}, Round {{ match.league.round }}{% endif %}\nKICKOFF (ISO UTC): {{ kickoff_iso }}\nKICKOFF (LOCAL READABLE): {{ kickoff_local }}\nKICKOFF (RELATIVE TO NOW): {{ kickoff_relative }}\n{% if venue %}VENUE: {{ venue }}{% endif %}\n{% if referee %}REFEREE: {{ referee.name }}{% if referee.nationality %} ({{ referee.nationality }}){% endif %}{% endif %}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOPENING RULES — the first 3 seconds matter most (v1.3):\n\n  • Start with a CONCRETE INSIGHT. No greeting, no \"in today's video\",\n    no \"let's take a look\", no \"welcome\". The viewer is already\n    watching — earn the next 3 seconds with a fact, a stat, or a\n    pointed claim about THIS match.\n  • First sentence must be ≤ 7.0 seconds of speech at voiceSpeed 1.1.\n    Aim for 10–16 Swedish words / 12–20 English words. Shorter is\n    punchier. Use a period, never a comma, to end it. If the hook\n    needs a setup clause, keep it as ONE sentence — don't chain.\n  • No framing phrases. FORBIDDEN openers include (non-exhaustive):\n    \"Hej\", \"Hejsan\", \"Välkomna\", \"Välkommen\", \"God morgon/kväll\",\n    \"I dagens video\", \"Idag tittar vi\", \"Vi tittar på\",\n    \"Det här är\", \"Här kommer\",\n    \"Hi\", \"Hello\", \"Welcome\", \"Good morning/evening\",\n    \"In today's video\", \"Today we're looking at\",\n    \"Let's take a look\", \"Stay tuned\", \"Coming up\".\n  • The first sentence's HOOK must show up again as the text of the\n    t=0 editorial-headline overlay (see below) — compressed into\n    2–6 words.\n\n  Examples of good openers (Swedish):\n    \"Djurgården har inte slagit Malmö FF hemma på sju år.\"\n    \"Fyra raka förluster för Hammarby — krisen är på riktigt.\"\n    \"Malmö FF gör 72% av sina mål efter paus — Djurgården är varnade.\"\n\n  Examples of good openers (English):\n    \"Arsenal have not won at Anfield since 2012.\"\n    \"Brighton's away form is the best in the Premier League right now.\"\n    \"Tottenham concede 67% of their goals in the second half.\"\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nMANDATORY CONTENT — every script MUST include:\n  1. The names of both teams.\n  2. The competition name ({{ match.league.name }}).\n  3. The kickoff — reference the day of week AND clock time as given\n     in \"KICKOFF (LOCAL READABLE)\" above. Do NOT invent a day-of-week from\n     the ISO string; use the LOCAL READABLE label verbatim or paraphrase\n     it. It is a recorded bug when the avatar says a wrong day.\n  4. Correct PHASE framing per the rules above (preview vs. recap vs. live).\n  5. **At least TWO different betting markets named verbatim in\n     the script.** A bottom-of-screen odds strip carries up to 5\n     cards (1X2, Total Goals over/under, BTTS, Asian Total Goals,\n     Asian Handicap). The strip auto-jumps to the matching card\n     when the script says one of these phrases — so without the\n     phrases, the strip stays locked. Aim for 2–3 mentions woven\n     into the narrative, never a market dump. Use the EXACT keyword\n     vocabulary below (Swedish or English depending on the locale);\n     paraphrasing kills the auto-jump:\n       • 1X2 / match winner — \"favoriter\", \"marknaden\", \"match winner\",\n         \"1X2\", or just naming a side's odds (always present by default)\n       • Total Goals — \"över 2.5\", \"under 2.5\", \"total goals\",\n         \"mål totalt\", \"antal mål\", \"över/under\", \"målfest\"\n       • Both Teams To Score — \"båda lagen gör mål\", \"BTTS\",\n         \"both teams to score\", \"båda målas\"\n       • Asian Handicap — \"asiatiskt handikap\", \"Asian handicap\",\n         \"AH\", \"handicap line\", or naming the line (\"-1.0\", \"-0.25\")\n     Don't force a market that doesn't fit the data — but if the\n     form table shows lots of goals, mention Total Goals; if both\n     sides score frequently, mention BTTS; if there's a clear\n     favourite, mention the Asian Handicap. The script should read\n     like a tipster's narrative, not a market-shopping list.\n  {% if referee %}6. A brief mention of the referee ({{ referee.name }}) somewhere\n     in the script — one short line is enough.{% endif %}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nHOME — {{ match.homeTeam.name }}:\n{% if match.homeTeam.leaguePosition %}- League position: {{ match.homeTeam.leaguePosition }}{% endif %}\n{% if match.homeTeam.points %}- Points: {{ match.homeTeam.points }}{% endif %}\n{% if match.homeTeam.form and match.homeTeam.form.length %}- Form (last 5): {{ match.homeTeam.form | join(', ') }}{% endif %}\n{% if match.homeTeam.goalsScored %}- Goals scored: {{ match.homeTeam.goalsScored }}{% endif %}\n{% if match.homeTeam.goalsConceded %}- Goals conceded: {{ match.homeTeam.goalsConceded }}{% endif %}\n\nAWAY — {{ match.awayTeam.name }}:\n{% if match.awayTeam.leaguePosition %}- League position: {{ match.awayTeam.leaguePosition }}{% endif %}\n{% if match.awayTeam.points %}- Points: {{ match.awayTeam.points }}{% endif %}\n{% if match.awayTeam.form and match.awayTeam.form.length %}- Form (last 5): {{ match.awayTeam.form | join(', ') }}{% endif %}\n{% if match.awayTeam.goalsScored %}- Goals scored: {{ match.awayTeam.goalsScored }}{% endif %}\n{% if match.awayTeam.goalsConceded %}- Goals conceded: {{ match.awayTeam.goalsConceded }}{% endif %}\n\n{% if match.odds %}\nLIVE ODDS:\n- 1: {{ match.odds.home }}\n- X: {{ match.odds.draw }}\n- 2: {{ match.odds.away }}\n{% endif %}\n\nCRITICAL RULES:\n- Use ONLY the data above. NEVER invent stats, xG, percentages, or numbers.\n- If a data point is missing, do NOT mention it. Skip it.\n- Do NOT use emojis, bullet points, or markdown in the script body.\n- The full script string must be plain prose that flows naturally when read aloud.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOVERLAY TIMELINE — the interactive part\n\nYour job is to produce (1) the voiceover script, and (2) a list of\n2–4 overlays that appear in sync with specific moments in the script.\n\nCUE-PHRASE CONTRACT (extremely important — the pipeline fails if broken):\n\nFor each overlay, pick a `cueInPhrase` and a `cueOutPhrase`. These must be\n2–5 consecutive words that appear VERBATIM exactly ONCE in the `full`\nscript string. The backend finds the phrase via substring match and\nresolves it to a timestamp using ElevenLabs char-level timings. If a\nphrase is paraphrased, quoted differently, or appears twice, the job\nfails validation.\n\nRules:\n  - Every cueInPhrase and cueOutPhrase must appear exactly ONCE in the script.\n  - cueOutPhrase must come AFTER cueInPhrase in the script.\n  - Pick distinctive phrases that won't accidentally appear elsewhere.\n  - An overlay should be on-screen roughly 4–8 seconds.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nAVAILABLE OVERLAY TYPES — v1.2 (widget-first):\n\nThe overlay timeline is rendered on top of the avatar using REAL\nHemmaklubben widgets — the same components readers see on the\nfront page, the match-detail page, and the article page. Your job\nis to pick widgets from the eligible registry below and mark when\neach one appears via cueInPhrase/cueOutPhrase. The widget data is\nattached server-side from the same source that feeds\n/dataengine/feed/hemmaklubben/match/:eventId — you must NOT invent\n`widget.data`, just pick the `template`.\n\nELIGIBLE WIDGET TEMPLATES FOR THIS MATCH (match has data for these):\n{% if available_widgets and available_widgets.length %}\n{% for tmpl in available_widgets %}  - {{ tmpl }}\n{% endfor %}{% else %}  (none — the match data is thin; fall back to\neditorial-headline overlays for this video){% endif %}\n\nELIGIBLE ODDS TEMPLATES (interactive card, always bottom of screen):\n{% if available_odds_templates and available_odds_templates.length %}\n{% for tmpl in available_odds_templates %}  - {{ tmpl }}\n{% endfor %}{% else %}  (none — odds-card cannot be emitted for this\nmatch){% endif %}\n\nWIDGET GUIDE — what each eligible template shows. Pick the widget\nwhose description best matches the sentence you are writing.\nTHIS LIST IS COMPUTED BY THE BACKEND AT RENDER TIME from the eligible\ntemplates above — never paraphrase, never invent template names.\n{% if widget_guide %}\n{{ widget_guide }}\n{% else %}\n    (no widget_guide registered — fall back to editorial-headlines only)\n{% endif %}\n\nODDS GUIDE — pick the market that matches the sentence. **Stories on\nupcoming matches MUST always carry an odds card** (this is a hard\nproduct rule — the viewer should never see a preview without odds):\n  - `odds-1x2`              → full-time 1X2 result (\"marknaden har dem på…\")\n  - `odds-overunder`        → over/under on goals (\"en målrik tillställning\")\n  - `odds-btts`             → both teams to score (\"båda lagen letar efter nät\")\n  - `odds-asian-handicap`   → Asian handicap line (\"hemmaspärr på -0,25\")\n  - `odds-asian-overunder`  → Asian total goals (\"Över 2,25 är värdet\")\n\nOVERLAY TYPES YOU MAY EMIT:\n\n1. `widget` — a real Hemmaklubben widget.\n   REQUIRED FIELDS:\n     { \"type\": \"widget\", \"cueInPhrase\": \"…\", \"cueOutPhrase\": \"…\",\n       \"position\": \"center\",\n       \"widget\": { \"template\": \"<from eligible list>\", \"data\": {} } }\n   Leave `data` as an empty object — the pipeline fills it in from\n   the match-detail feed. You MUST pick from the eligible list\n   above; any other template is rejected.\n\n   SPECIAL v1.3 case — the `match-header` widget:\n     • ALWAYS included once, ALWAYS as the t=0 intro card.\n     • `position` MUST be `bottom-sheet`, `z` MUST be `10`.\n     • `cueInPhrase` MUST be the first 2–5 words of `script.full`\n       (verbatim) so animInMs resolves to 0.\n     • `cueOutPhrase` MUST come from the SECOND or THIRD sentence\n       of the script. The match-header stays on screen through the\n       opening hook AND the first framing sentence after it. Pick\n       a phrase that sits roughly 5–8 seconds into the voiceover.\n     • HARD RULE: the match-header's cueOutPhrase MUST appear\n       LATER in `script.full` than the editorial-headline's\n       cueOutPhrase. The headline dies first (end of sentence 1),\n       the card lingers (sentence 2 or 3). If you can't find a\n       later phrase, you picked the wrong phrases — re-do it.\n     • `widget.data` is `{}` — the player renders homeBadge,\n       awayBadge, kickoffLocal, venue, referee, round all straight\n       from `article.match`.\n\n2. `odds-card` — DO NOT EMIT. (2026-05-06 IHS rollout policy.)\n   The avatar video already carries an always-visible swipeable\n   `videoOddsStrip` at the bottom of the player with three markets\n   (1X2, Over/Under, Asian Handicap) that the FE keeps live. An\n   additional `odds-card` overlay would render the SAME odds in a\n   second floating panel at the same position — confusing and\n   redundant. So DON'T include `odds-card` overlays in the timeline.\n   Mention the market verbally in the script if relevant; the strip\n   under the avatar does the rendering.\n\n3. `editorial-headline` — MANDATORY v1.3 intro + optional extra.\n   You MUST emit exactly ONE editorial-headline at t=0 with\n   style:'shout' as the first overlay. You MAY emit one additional\n   non-intro editorial-headline later in the script for dramatic\n   punctuation (style:'stat', 'ticker', or 'quote'), but this is\n   optional — widgets carry the content.\n\n   t=0 INTRO HEADLINE — required shape:\n     { \"type\": \"editorial-headline\",\n       \"cueInPhrase\": \"<first 2–5 words of script, verbatim>\",\n       \"cueOutPhrase\": \"<phrase that ends 1.5–3s later>\",\n       \"position\": \"top-banner\",\n       \"z\": 20,\n       \"headline\": {\n         \"text\": \"<2–6 word shout — compressed version of the\n                  first sentence's hook>\",\n         \"style\": \"shout\",\n         \"kicker\": \"<optional 1-3 word league label, e.g.\n                    'Allsvenskan • Omgång 8'>\",\n         \"subline\": \"<optional 3-6 word hook amplifier>\",\n         \"accent\": \"<optional 1-2 word tag, e.g. 'HEMMAKLASS'>\"\n       }\n     }\n   CONTRACT:\n     • `cueInPhrase` MUST be the first 2–5 words of `script.full`\n       so animInMs resolves to 0ms.\n     • The `text` field MUST share at least one ≥4-char token with\n       the first sentence (the pipeline rejects if it doesn't).\n     • `cueOutPhrase` MUST come from the FIRST sentence of\n       `script.full` — pick the last 2–4 words before the first\n       period. The editorial-headline pops on at t=0 and dies with\n       the end of the opening hook (~2–3s in).\n     • The editorial-headline's cueOutPhrase MUST appear EARLIER\n       in `script.full` than the match-header's cueOutPhrase\n       (headline dies first, the card lingers into sentence 2–3).\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSTORY ARC (target 100–160 words, ~80–100s voiceover):\n\n 1. Opening hook       — concrete insight, ≤3s. Pair with the\n                         t=0 match-header widget (bottom-sheet,\n                         z:10) AND the t=0 editorial-headline\n                         (top-banner, z:20, style:'shout'). BOTH\n                         intro overlays are MANDATORY.\n 2. Form check         — how are they arriving? Pair with\n                         `form-guide`, `league-table-mini`, or\n                         `momentum-duel`.\n 3. Tactical angle     — where is this match won? Pair with\n                         `malprofil` or `kampindsigt`.\n 4. Key player/pattern — one human angle or surprising stat.\n                         Pair with `spelarprofil`, `dolda-monster`,\n                         `head-to-head`, or `domareffekten`.\n                         Fallback rule: if `spelarprofil` is NOT in\n                         `available_widgets` (player grades missing\n                         upstream), substitute `malprofil` for the\n                         step-4 slot — same scoring-profile angle,\n                         always available when the match has odds.\n 5. The market's view  — call out odds VERBALLY (no overlay\n                         needed — strip is always visible).\n 6. Close              — one-line kicker.\n\nMinimum overlay composition (v1.4 — IHS):\n  - 1 editorial-headline at t=0 (style:'shout', top-banner, z:20) ← MANDATORY\n  - 1 match-header widget at t=0 (bottom-sheet, z:10)             ← MANDATORY\n  - 3 content widgets covering steps 2, 3, and 4 of the arc\n  - 0 or 1 optional editorial-headline for dramatic punctuation later\nTotal: 5–6 overlays. NO odds-card.\n\nNon-intro overlays MUST have animInMs ≥ 1000ms (i.e. their\ncueInPhrase may not sit in the first second of the script).\n\nYou do NOT need to describe what's on screen — the widgets speak for\nthemselves. Let the script do the voiceover work, not the visual\ndescription.\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nOUTPUT FORMAT — return EXACTLY this JSON structure, no markdown, no code fences:\n\n{\n  \"article\": {\n    \"headline\": \"max 80 chars, no trailing period\",\n    \"manchet\": \"1-2 sentences, max 180 chars, hook the reader\",\n    \"teaserBody\": \"50-150 word prose body for the article page, \\n\\n between paragraphs\",\n    \"tags\": [\"lowercase\", \"slugged\", \"tags\"]\n  },\n  \"script\": {\n    \"full\": \"The complete voiceover script as one continuous string.\"\n  },\n  \"overlays\": [\n    {\n      \"id\": \"o0-headline\",\n      \"type\": \"editorial-headline\",\n      \"cueInPhrase\": \"Djurgården har inte\",\n      \"cueOutPhrase\": \"sju år\",\n      \"position\": \"top-banner\",\n      \"z\": 20,\n      \"headline\": {\n        \"text\": \"SJU ÅR UTAN SEGER\",\n        \"style\": \"shout\",\n        \"kicker\": \"Allsvenskan • Omgång 8\",\n        \"subline\": \"Djurgården möter Malmö FF hemma\",\n        \"accent\": \"HEMMAKLASS\"\n      }\n    },\n    {\n      \"id\": \"o0-header\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"Djurgården har inte\",\n      \"cueOutPhrase\": \"handlar om mer än tre poäng\",\n      \"position\": \"bottom-sheet\",\n      \"z\": 10,\n      \"widget\": { \"template\": \"match-header\", \"data\": {} }\n    },\n    {\n      \"id\": \"o1\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"arriving with confidence\",\n      \"cueOutPhrase\": \"four wins in five\",\n      \"position\": \"center\",\n      \"widget\": { \"template\": \"ihs-last-5-games\", \"data\": {} }\n    },\n    {\n      \"id\": \"o2\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"the battle for goals\",\n      \"cueOutPhrase\": \"league-best 2.4 per game\",\n      \"position\": \"center\",\n      \"widget\": { \"template\": \"ihs-goals\", \"data\": {} }\n    },\n    {\n      \"id\": \"o3\",\n      \"type\": \"widget\",\n      \"cueInPhrase\": \"the market has them\",\n      \"cueOutPhrase\": \"76 percent\",\n      \"position\": \"center\",\n      \"widget\": { \"template\": \"ihs-victory-probability\", \"data\": {} }\n    }\n  ]\n}\n\nRULES RECAP:\n- 100–160 words in script.full\n- First sentence is a concrete insight, ≤7.0s, no greetings/framing\n- 5–6 overlays total: 1 intro editorial-headline + 1 match-header\n  widget (both at t=0) + ≥3 content widgets + optional\n  non-intro headline. NO odds-card overlays — strip handles odds.\n- Intro editorial-headline.text shares ≥1 token (≥4 chars) with the\n  first sentence\n- match-header.cueOutPhrase ends AFTER editorial-headline.cueOutPhrase\n- Non-intro overlays must have cueInPhrase AFTER the first second\n  of the script (i.e. not inside the opening sentence)\n- Every `widget.template` MUST be from the ELIGIBLE WIDGET TEMPLATES list above\n- `widget.data` MUST be left as `{}` — the pipeline fills it\n- Every cueInPhrase/cueOutPhrase appears exactly ONCE in script.full, verbatim\n- cueOutPhrase comes after cueInPhrase\n- Odds-card position is always `bottom-sheet`\n- Respond with valid JSON only — no markdown, no code fences\n","model":"claude-sonnet-4-6","temperature":0.7,"maxTokens":2000,"journalist":null}]}