Skip to content

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

Merged
mircealungu merged 4 commits into
masterfrom
daily-audio-scheduling
May 29, 2026
Merged

Daily audio: first-class DailyAudioSubscription (config, on/off, schedule)#645
mircealungu merged 4 commits into
masterfrom
daily-audio-scheduling

Conversation

@mircealungu
Copy link
Copy Markdown
Member

Summary

Introduces a per-(user, language) DailyAudioSubscription as the source of truth for what daily audio lesson to generate and when — replacing the implicit daily_audio_lesson_type_<lang> / _suggestion_<lang> UserPreference rows 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

  • Model + migration + backfill: daily_audio_subscription table; backfill_daily_audio_subscriptions.py populates it from the legacy preference keys (idempotent, handles the cnzh-CN code quirk).
  • Cron (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's today_lesson_exists + waiting_paused_for gates unchanged.
  • Compat: save_user_preferences mirrors the legacy keys into the subscription, so currently-deployed clients keep working (empty type = turn off).
  • Read path: get_todays_lesson now returns subscription_status (not_subscribed|active|off) and next_lesson_date alongside Daily audio: pause generation when the last lesson wasn't engaged with #643's paused.
  • Endpoint: set_daily_subscription_enabled for the turn-off toggle.
  • weekday_mask groundwork for Mon/Wed/Fri schedules (only daily is 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

  1. Run tools/migrations/26-05-29--add_daily_audio_subscription.sql.
  2. Run python -m tools.backfill_daily_audio_subscriptions (try --dry-run first).
    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.
  • Seed a Mon/Wed/Fri schedule → cron skips off-days (not-due).
  • Toggle set_daily_subscription_enabled off → cron skips (not-subscribed); get_todays_lessonsubscription_status="off".
  • get_todays_lesson: active-with-lesson (next_lesson_date set), engagement-paused (paused=true), not-subscribed.
  • Backfill count == distinct (user, lang) with a daily-audio type pref.

🤖 Generated with Claude Code

…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>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 29, 2026

ArchLens detected architectural changes in the following views:
diff
diff

mircealungu and others added 3 commits May 29, 2026 10:43
- 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>
@mircealungu mircealungu merged commit 6d5e2d5 into master May 29, 2026
1 of 3 checks passed
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