5d2d27c499
Prep prose (services/journal_prep.py): - Emit explicit "WEATHER: none available — do NOT mention weather" absent-marker so a small model can't invent partly-cloudy/temperature prose when both configured locations have empty addresses. - Replace negative-only system rule with positive-anchored guidance forbidding weather/temp/precip mentions unless a numeric WEATHER section is present; also bans echoing parenthetical labels verbatim. - Reword overdue header to "(past their due date, still open — backlog, not today's work)" and render lines as "was due <date>, N day(s) overdue" with correct singular/plural. Supersedes the wording noted in Fable task #159. - Deterministic fabricated-weather reconciler: low-false-positive regex detects fabricated weather phrasing; on trip with an empty section, regenerate once with a corrective. Persistent fabrication logs ERROR rather than mangling prose. Journal route (routes/journal.py): - Override message_count with len(messages) in _day_payload. The chat path already does this; the journal path was hitting the Conversation.to_dict() fallback to 0 because messages aren't eager-loaded on that instance. Tests: - tests/test_journal_message_count.py — pins the model-level trap and the override contract (3 cases). - tests/test_journal_prep_hardening.py — 11 cases covering the fabricated-weather reconciler and absent-marker rendering. - tests/test_journal_prep_filtering.py — updated one stale assertion. Tracks Fable task #171. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
61 lines
2.4 KiB
Python
61 lines
2.4 KiB
Python
"""Regression coverage for the journal `message_count: 0` bug.
|
|
|
|
`_day_payload` (routes/journal.py) loads messages in a separate query and
|
|
serializes the Conversation with `conv.to_dict()`. `Conversation.to_dict()`
|
|
derives `message_count` from the `messages` relationship, which is NOT
|
|
eager-loaded on that instance — so it silently fell back to 0 for every
|
|
journal day (observed via the fable MCP: conversation #291 reported
|
|
message_count 0 with 9 messages present).
|
|
|
|
Full HTTP coverage of `_day_payload` needs a live DB (not available in the
|
|
unit-test env — see test_events_routes.py). These tests pin the model-level
|
|
contract that necessitates the route-side override.
|
|
"""
|
|
from datetime import date, datetime, timezone
|
|
|
|
from fabledassistant.models.conversation import Conversation, Message
|
|
|
|
|
|
def _conv() -> Conversation:
|
|
now = datetime(2026, 5, 19, 9, 0, tzinfo=timezone.utc)
|
|
return Conversation(
|
|
user_id=1,
|
|
conversation_type="journal",
|
|
day_date=date(2026, 5, 19),
|
|
title="2026-05-19",
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
|
|
|
|
def test_to_dict_reports_zero_when_messages_relationship_not_loaded():
|
|
"""The trap: an untouched `messages` relationship is absent from the
|
|
instance state, so to_dict() reports 0 regardless of DB rows. This is
|
|
exactly the journal path's situation and why the route must override."""
|
|
conv = _conv()
|
|
assert conv.to_dict()["message_count"] == 0
|
|
|
|
|
|
def test_to_dict_counts_when_messages_loaded():
|
|
"""When the relationship IS populated the count is correct — confirming
|
|
the model isn't broken, the journal path just never loaded it."""
|
|
conv = _conv()
|
|
conv.messages = [
|
|
Message(conversation_id=1, role="assistant", content="prep"),
|
|
Message(conversation_id=1, role="user", content="hi"),
|
|
]
|
|
assert conv.to_dict()["message_count"] == 2
|
|
|
|
|
|
def test_day_payload_override_yields_true_count():
|
|
"""Pins the fix contract: _day_payload already holds the real message
|
|
list and overrides message_count with len(messages), the same way the
|
|
chat-list path (services/chat.py) supplies its own count."""
|
|
conv = _conv() # messages relationship deliberately not loaded
|
|
messages = [object(), object(), object()] # stand-ins for the loaded rows
|
|
|
|
conv_dict = conv.to_dict()
|
|
conv_dict["message_count"] = len(messages)
|
|
|
|
assert conv_dict["message_count"] == 3
|