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>
Two related gaps in the journal weather panel:
1. Saving locations via PUT /journal/config didn't trigger a weather
fetch, so newly-entered sites had no cache row (or a stale one) until
the user manually clicked the panel's refresh button. The panel
rendered "two sites with empty values" against pre-existing cache
rows that no longer matched what the user had configured.
2. get_cached_weather_rows returned every WeatherCache row for the user
regardless of whether the location was still in journal_config.
Briefing-era rows survived migration 0040 (which only deleted the
briefing_config setting, not the cache table) and showed up as
ghost tabs in the UI.
Changes:
- get_cached_weather_rows accepts an optional valid_keys filter; rows
whose location_key is not in the set are excluded.
- routes/journal.py:
- put_config kicks off a background refresh_location_cache for any
saved location with valid lat/lon.
- GET /weather and POST /weather/refresh both pass valid_keys derived
from the current config so orphaned rows don't surface.
- services/journal_prep.py filters the weather section to currently-
configured locations as well; uses a lazy import of get_journal_config
to avoid a cycle (journal_scheduler imports journal_prep).
153 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The /api/journal/weather route was filtering out cache rows older than 24
hours via parse_weather_card_data, while journal_prep.py read the same
rows raw without freshness checking. Result: the daily prep referenced
"home" and "work" temperatures while the right-rail UI showed nothing —
two surfaces, same backing data, inconsistent visibility.
Two changes:
1. parse_weather_card_data no longer returns None for stale data.
WeatherCard already exposes fetched_at and gracefully hides
today_high / forecast fields when they're absent, so old data renders
with whatever fields the cached forecast still covers.
2. The /weather route opportunistically schedules a background refresh
for any cache row older than 4 hours. If the user's journal_config
has lat/lon for that location_key, the refresh runs and the next
page load gets fresh data; if no usable config, the refresh is a
silent no-op and the stale cache is still served.
This makes prep and UI consistent. It also self-heals over time — once
locations are configured, stale caches get refreshed on the next page
load instead of waiting indefinitely.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The journal UI was over-stripped earlier — weather panel, current-conditions
poll, and the upcoming-events sidebar were all dropped. Restored those (calls
the new /api/journal/weather, /api/journal/weather/current,
/api/journal/weather/refresh, /api/journal/weather/geocode endpoints).
Also: the daily-prep system message was rendering as flat text inside
ChatPanel because there's no .role-system bubble styling. Added a hideMessage
guard in ChatMessage so daily-prep system messages don't render in the chat
stream — they're already shown above as a structured prep card.
News / RSS reactions / article-discuss are intentionally NOT in the journal
(scoped out per user direction). The broader RSS infrastructure cleanup is
a separate, larger task.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>