Skip to content

Daily audio: pause generation when the last lesson wasn't engaged with#643

Merged
mircealungu merged 2 commits into
masterfrom
daily-audio-pause
May 27, 2026
Merged

Daily audio: pause generation when the last lesson wasn't engaged with#643
mircealungu merged 2 commits into
masterfrom
daily-audio-pause

Conversation

@mircealungu
Copy link
Copy Markdown
Member

Stops the nightly job piling up unheard lessons: if a learner doesn't get at least halfway through their most recent lesson, daily generation pauses until they come back and listen.

"Engaged" = completed, or pause_position ≥ 50% of duration — DailyAudioLesson.is_engaged.

Changes

  • Cron (tools/generate_daily_audio_lessons.py): per user, before any LLM work — skip if today's lesson already exists (exists), or if the most recent lesson isn't engaged (paused). Dry-run reports the paused count.
  • get_todays_lesson_for_user(include_paused=True): when there's no lesson today and the latest wasn't engaged, returns that waiting lesson flagged paused so the app shows it rather than triggering a new generation. Default stays today-only, so prepare_lesson_generation and on-demand creation (change-topic / first day) are unaffected.
  • get_daily_audio_status: a waiting paused lesson reports as ready, so the nav dot keeps nudging the learner back to it.
  • Model: is_engaged + latest_for_language(user, language_id).

Notes

Test

source ~/code/zeeguu/api/.venv/bin/activate && python tools/generate_daily_audio_lessons.py --dry-run --user-id <id>

Expect a user whose last lesson is <50% listened to report paused.

🤖 Generated with Claude Code

Avoids piling up unheard lessons. 'Engaged' = completed or listened past the
halfway point (DailyAudioLesson.is_engaged).

- Nightly cron skips a user whose most recent lesson isn't engaged (paused),
  before doing any LLM work.
- get_todays_lesson_for_user(include_paused=True) returns that waiting lesson
  flagged `paused` for the app to show, instead of nothing. Generation callers
  use the default (today-only) so a paused older lesson never blocks on-demand
  creation (change-topic / first day).
- get_daily_audio_status surfaces a waiting paused lesson as 'ready' so the nav
  dot still nudges the learner back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

ArchLens - No architecturally relevant changes to the existing views

- is_engaged: fall back to 'started it' when duration_seconds is missing,
  rather than pausing a user forever on a data anomaly. ENGAGEMENT_THRESHOLD
  constant replaces the inline 0.5.
- Single source of truth: DailyAudioLesson.waiting_paused_for(user, language_id)
  used by the endpoint, get_daily_audio_status, and the cron (gate + dry-run) —
  removes the duplicated 'latest and not is_engaged' checks (and the
  learned_language.id vs learned_language_id drift).
- Cron uses a cheap today_lesson_exists() scalar query instead of building a
  full lesson response just to test existence — also avoids mislabeling a
  stale (audio-missing) today lesson as 'paused'.
- Remove now-dead find_latest_for_user (zero callers; near-duplicate of
  latest_for_language with divergent ordering).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mircealungu mircealungu merged commit 57eda0e into master May 27, 2026
3 checks passed
mircealungu added a commit that referenced this pull request May 29, 2026
…dule) (#645)

* Daily audio: first-class DailyAudioSubscription (config, on/off, schedule)

Introduces a per-(user, language) DailyAudioSubscription as the source of truth
for WHAT to generate and WHEN, replacing the daily_audio_lesson_type_<lang> /
_suggestion_<lang> UserPreference rows. Builds on #643's engagement pause —
reuses DailyAudioLesson.is_engaged / waiting_paused_for rather than duplicating
it; the subscription owns config only (type, subject, enabled, schedule).

- New table + model + backfill tool from the legacy preference keys.
- Cron is subscription-driven: skips disabled subs and non-scheduled days, then
  applies #643's today-exists + engagement-pause gates as before.
- save_user_preferences mirrors the legacy keys into the subscription so
  currently-deployed clients keep working (turning off = empty type).
- get_todays_lesson adds subscription_status (not_subscribed|active|off) and
  next_lesson_date alongside #643's `paused` flag.
- set_daily_subscription_enabled endpoint for the turn-off toggle.
- weekday_mask groundwork for Mon/Wed/Fri schedules (only 'daily' active now).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Daily audio: address code-review findings

- Mirror (#2): key on type OR suggestion keys so a suggestion-only save still
  syncs the subscription; resolve the language with find (not find_or_create,
  which committed mid-request — #5); cap raw_suggestion at 128.
- next_lesson_date (#3): only promise a date when today's lesson already exists;
  return None otherwise so the UI can offer an explicit "generate now" instead
  of a date the cron may not honor (cron miss / first day / timezone).
- Align suggestion length to 128 (#4): subscription + daily_audio_lesson columns
  widened, input capped at 128 (was 80/100/255).
- Remove unused DailyAudioSubscription.find_or_create (#6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Daily audio: dedicated configure endpoints; mark legacy compat for removal

- GET /daily_subscription returns the current (user, learned_language) config
  from DailyAudioSubscription, replacing the legacy preference-key read path.
- POST /configure_daily_subscription upserts type/subject directly into the
  subscription, replacing the daily-audio path of /save_user_preferences.
- Mark the legacy daily-audio block + mirror in save_user_preferences for
  removal once the deployed clients are on the new endpoint (~2026-06-05).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Daily audio: explicit language on subscription + today's-lesson endpoints

Race fix: when the learner switches language quickly, the server's
user.learned_language can lag the UI and reads/writes hit the wrong language.
get_todays_lesson, daily_subscription (GET/POST), and
set_daily_subscription_enabled now take an explicit `language` query/form param
(with a fallback to user.learned_language for clients not yet updated). The
generator's get_todays_lesson_for_user and _subscription_fields plumb the
Language through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant