diff --git a/app/services/openrouter.py b/app/services/openrouter.py
index 539c553..a542b98 100644
--- a/app/services/openrouter.py
+++ b/app/services/openrouter.py
@@ -508,6 +508,12 @@ def build_user_prompt(
return "\n".join(parts)
+def _digest_tone_clause(tone: str) -> str:
+ if tone.upper() == "NOVICE":
+ return "Use plain English. Define any jargon on first use."
+ return "Write for a reader who already speaks markets fluently."
+
+
def build_daily_digest_prompt(
*,
tone: str,
@@ -521,21 +527,19 @@ def build_daily_digest_prompt(
Different from the hourly log: the daily digest reflects on the past
24h and looks forward to the upcoming session. Longer, less
'live-blogging,' more contextual. Target ~600 words."""
- tone_clause = (
- "Use plain English. Define any jargon on first use."
- if tone.upper() == "NOVICE"
- else "Write for a reader who already speaks markets fluently."
- )
system = (
"You write the daily editorial digest for Read the Markets. "
- f"Audience tone: {tone.upper()}. {tone_clause} "
+ f"Audience tone: {tone.upper()}. {_digest_tone_clause(tone)} "
"Cover: (1) what mattered yesterday, (2) what to watch in today's "
"EU and US sessions, (3) one cross-asset thread connecting them. "
"No predictions of price level, no buy/sell language. Target ~600 "
"words. Output HTML using only
,
, , - , , "
" — no , , or wrapper, no inline styles."
)
- user = _digest_user_prompt(today, quotes_by_group, headlines_by_bucket, reference_line)
+ user = _digest_user_prompt(
+ today=today, quotes_by_group=quotes_by_group,
+ headlines_by_bucket=headlines_by_bucket, reference_line=reference_line,
+ )
return system, user
@@ -550,14 +554,9 @@ def build_weekly_digest_prompt(
"""System + user prompt for the Sunday weekly recap + look-ahead.
Sent to ALL opt-in users (free and paid). Target ~900 words."""
- tone_clause = (
- "Use plain English. Define any jargon on first use."
- if tone.upper() == "NOVICE"
- else "Write for a reader who already speaks markets fluently."
- )
system = (
"You write the Sunday weekly digest for Read the Markets. "
- f"Audience tone: {tone.upper()}. {tone_clause} "
+ f"Audience tone: {tone.upper()}. {_digest_tone_clause(tone)} "
"Cover: (1) the week behind — what moved and why, "
"(2) the week ahead — releases, earnings, central-bank meetings, "
"(3) the cross-asset story to keep in mind. "
@@ -565,11 +564,20 @@ def build_weekly_digest_prompt(
"words. Output HTML using only
,
, , - , , "
" — no , , or wrapper, no inline styles."
)
- user = _digest_user_prompt(today, quotes_by_group, headlines_by_bucket, reference_line)
+ user = _digest_user_prompt(
+ today=today, quotes_by_group=quotes_by_group,
+ headlines_by_bucket=headlines_by_bucket, reference_line=reference_line,
+ )
return system, user
-def _digest_user_prompt(today, quotes_by_group, headlines_by_bucket, reference_line):
+def _digest_user_prompt(
+ *,
+ today,
+ quotes_by_group: dict,
+ headlines_by_bucket: dict,
+ reference_line: str,
+) -> str:
"""Shared user-message body used by both digest prompts. Same data
shape as the hourly user prompt; reformatted for the digest context."""
today_str = today.strftime("%A %d %B %Y") if hasattr(today, "strftime") else str(today)
diff --git a/tests/test_digest_prompts.py b/tests/test_digest_prompts.py
index ce5480e..97a9755 100644
--- a/tests/test_digest_prompts.py
+++ b/tests/test_digest_prompts.py
@@ -46,3 +46,19 @@ def test_prompts_return_strings():
sys_, usr = fn(tone="INTERMEDIATE", **_ctx())
assert isinstance(sys_, str) and isinstance(usr, str)
assert len(sys_) > 50 and len(usr) > 50
+
+
+def test_prompts_tolerate_empty_data():
+ """No quotes, no headlines — builders must still produce non-empty
+ prompts without raising. Guards the `if headlines_by_bucket` and
+ `if quotes_by_group` branches in _digest_user_prompt."""
+ empty_ctx = dict(
+ today=datetime(2026, 5, 25, 6, 30, tzinfo=timezone.utc),
+ quotes_by_group={},
+ headlines_by_bucket={},
+ reference_line="S&P 7,501 (ATH)",
+ )
+ for fn in (build_daily_digest_prompt, build_weekly_digest_prompt):
+ sys_, usr = fn(tone="INTERMEDIATE", **empty_ctx)
+ assert "S&P 7,501" in usr
+ assert len(sys_) > 50