Daily audio: first-class DailyAudioSubscription (config, on/off, schedule)#645
Merged
Conversation
…dule) 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>
- 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>
…moval - 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>
…ints 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Introduces a per-(user, language)
DailyAudioSubscriptionas the source of truth for what daily audio lesson to generate and when — replacing the implicitdaily_audio_lesson_type_<lang>/_suggestion_<lang>UserPreferencerows with config-as-data, and adding turn-off and weekday-schedule groundwork.Built on top of #643: it reuses that PR's engagement-pause primitives (
DailyAudioLesson.is_engaged/waiting_paused_for) rather than duplicating them. The subscription owns config only (type, subject,enabled, schedule); the "don't pile up unheard lessons" pause stays computed from the latest lesson.Changes
daily_audio_subscriptiontable;backfill_daily_audio_subscriptions.pypopulates it from the legacy preference keys (idempotent, handles thecn→zh-CNcode quirk).generate_daily_audio_lessons.py): subscription-driven — skips disabled subscriptions and non-scheduled days, then applies Daily audio: pause generation when the last lesson wasn't engaged with #643'stoday_lesson_exists+waiting_paused_forgates unchanged.save_user_preferencesmirrors the legacy keys into the subscription, so currently-deployed clients keep working (empty type = turn off).get_todays_lessonnow returnssubscription_status(not_subscribed|active|off) andnext_lesson_datealongside Daily audio: pause generation when the last lesson wasn't engaged with #643'spaused.set_daily_subscription_enabledfor the turn-off toggle.weekday_maskgroundwork for Mon/Wed/Fri schedules (onlydailyis active for now).Companion PR
Web: zeeguu/web#1152 (subscription-driven Today view + turn-off + live-regeneration fix). Deploy this API PR first.
Migration / rollout
tools/migrations/26-05-29--add_daily_audio_subscription.sql.python -m tools.backfill_daily_audio_subscriptions(try--dry-runfirst).New response fields are additive; old clients keep working via the preference mirror.
Test plan
python -m tools.generate_daily_audio_lessons --dry-run --days 30— buckets: not-subscribed / not-due / exists / paused / would-generate.not-due).set_daily_subscription_enabledoff → cron skips (not-subscribed);get_todays_lesson→subscription_status="off".get_todays_lesson: active-with-lesson (next_lesson_dateset), engagement-paused (paused=true), not-subscribed.🤖 Generated with Claude Code