Files
worldmonitor/scripts
Elie Habib 3d7e60ca7d fix(digest): never skip AI summary when userPreferences are missing (#2939)
* fix(digest): never skip AI summary when userPreferences are missing

Users who enabled the AI executive summary toggle on their notification
rule still received digest emails without the summary. The Railway log
pinpointed it:

  [digest] No preferences for user_... skipping AI summary
  [digest] Email delivered to ...

Root cause chain:
  convex/http.ts:591            /relay/user-preferences returns literal
                                null when no userPreferences row exists
                                for (userId, variant).
  scripts/lib/user-context.cjs  fetchUserPreferences forwards that as
                                { data: null, error: false }.
  scripts/seed-digest-notifications.mjs:458
                                generateAISummary bails with return null.

The AI-summary toggle lives on the alertRules table. userPreferences is
a SEPARATE table (the SPA app-settings blob: watchlist, airports,
panels). A user can have an alertRule (with aiDigestEnabled: true)
without having ever saved userPreferences, or only under a different
variant. Missing prefs must NOT silently disable the feature the user
explicitly enabled. The correct behavior is to degrade to a
non-personalized summary.

Fix: remove the early return in generateAISummary. Call
extractUserContext(null), which already returns a safe empty context,
and formatUserProfile(ctx, 'full') returns "Variant: full" alone. The
LLM then generates a generic daily brief instead of nothing. An info
log still reports the missing-prefs case for observability.

Regression coverage: tests/user-context.test.mjs (new, 10 cases) locks
in that extractUserContext(null|undefined|{}|"") returns the empty
shape and formatUserProfile(emptyCtx, variant) returns exactly
"Variant: {variant}". Any future refactor that reintroduces the
null-bail will fail the tests.

Note: the same log also shows the rule fired at 13:01 Dubai instead
of 8 AM / 8 PM. That is a separate issue in isDue or rule-save flow
and needs more log data to diagnose; not included here.

* fix(digest): distinguish transient prefs fetch failure from missing row

Addresses Greptile P2 review feedback on PR #2939.

fetchUserPreferences returns { data, error } where:
  error: true  = transient fetch failure (network, non-OK HTTP, env missing)
  error: false = the (userId, variant) row genuinely does not exist

The previous log treated both cases identically as "No stored preferences",
which was misleading when the real cause was an unreachable Convex endpoint.
Behavior is unchanged (both still degrade to a non-personalized summary),
only the log line differentiates them so transient fetch failures are
visible in observability.
2026-04-11 17:10:06 +04:00
..