pdf-quiz-generator/backend/alembic/versions/e7f8091a2b3c_user_voice_preference.py
Daniel 3f57bda6aa feat: a reading voice in Settings, and a tutor that reads every figure
The voice picker was a dropdown in the quiz player, beside the question — the
one control on that screen with nothing to do with answering it, and one a
learner sets once and never touches. It is a setting now, on the user rather
than in a Redis blob, with a play button beside each voice because a voice is
worth hearing before it is chosen. Choosing nothing stays a real choice: it
means whatever an administrator marked default, so a site that changes its
default reaches everybody without a row being edited.

The tutor reads figures from `question_media` rather than the two legacy path
columns. Those agree exactly today, so nothing was being lost — the first
question given a second figure in the editor would have been the one that
broke it, silently and only for the tutor. The legacy columns remain as a
fallback for anything not projected into that table yet.

And the retrieval thresholds are written down in docs/retrieval-thresholds.md:
the three answers, the sixteen queries they were measured against, why they are
deliberately not the retrieval floor, and how to re-measure when the corpus
grows. Worth keeping the headline in mind — "discuss love" scores 0.491,
alongside "tell me a joke". A number in the 0.4s is noise, not a weak signal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-12 16:48:09 +02:00

25 lines
690 B
Python

"""A learner's own reading voice.
It was a dropdown in the quiz player, beside the question — the one control on
that screen with nothing to do with answering, and one a learner would set once
and never touch again. That is a setting.
Revision ID: e7f8091a2b3c
Revises: d6e7f8091a2b
"""
import sqlalchemy as sa
from alembic import op
revision = "e7f8091a2b3c"
down_revision = "d6e7f8091a2b"
branch_labels = None
depends_on = None
def upgrade():
if "tts_voice" not in {c["name"] for c in sa.inspect(op.get_bind()).get_columns("users")}:
op.add_column("users", sa.Column("tts_voice", sa.String(), nullable=True))
def downgrade():
op.drop_column("users", "tts_voice")