Skip to content

Stop filtering discovery by CEFR level - #704

Merged
mircealungu merged 1 commit into
masterfrom
wt/drop-cefr-feed-filter
Aug 17, 2026
Merged

Stop filtering discovery by CEFR level#704
mircealungu merged 1 commit into
masterfrom
wt/drop-cefr-feed-filter

Conversation

@mircealungu

Copy link
Copy Markdown
Member

Why

A learner reported a feed with too few articles. It is not the recommender — it is the level filter, which on-demand simplification (#698) turned into a starvation filter.

available_cefr_levels lists the levels for which a version of an article was already generated: the original's own level, plus its simplified children. Once crawl-time simplification was retired there are no children, so the field collapsed to a single value — and a learner saw only the articles a publisher happened to write at exactly their band.

Measured on the live index, Danish, last 7 days:

candidate pool for an A2 reader documents
with the filter (A2, A1/A2) 33
without it 479

And 266 of those articles already carry an A2 ArticleLevelSummary — content generated for precisely that reader, which the filter guaranteed they would never see. The level-summary overlay and the level filter have been contradicting each other since #698.

What

The premise behind the filter no longer holds: an article above the learner's level is not something to hide when the card carries a level-appropriate summary and the body simplifies on request. So the filter is dropped, not widened — from all three discovery paths (recommender feed, video recommendations, search). Above-level readers are the mirror case and get the same treatment once we complexify.

Two further empty-feed causes go with it:

  • Articles whose LLM assessment failed have no cefr_level, so they matched nothing. Every assessment outage — the Aug-13 article_type bug, the Anthropic usage-cap outages — emptied feeds outright. Feeds no longer depend on assessment liveness. (During the Aug 16–17 cap outage, every article crawled for ~12h was invisible in every language.)
  • document_from_video never writes an available_cefr_levels field, so requiring a level in it excluded every video from every leveled user.

feed_delivery_health_check reached its per-segment inventory through the recommender builder, so this change would have silently degraded it into six copies of one language-wide query. It now appends the CEFR clause itself, and the docstrings state what that clause measures now: assessment coverage, not feed delivery.

Notes

  • No reindex needed. The field stays indexed; we just stop filtering on it. Effective on deploy.
  • UserPreference.SHOW_EASIER_ARTICLES is now inert — there is nothing left to widen. Left in place and flagged; the web toggle in Settings → Feed Preferences should be removed or repurposed in a follow-up.
  • Videos will now appear in feeds for the first time. Easy to revert separately if that should wait.
  • Separately spotted, not fixed here: User.cefr_level_for_language does [...][cefr_level - 1], so a stored cefr_level of 0 maps to C2. Many users have 0. Moot for the feed now, but wrong wherever else it is called.

Tests

  • New zeeguu/core/test/test_elastic_query_builder.py pins the no-filter behavior on all three queries so it cannot creep back.
  • zeeguu/core/test: 226 passed.
  • zeeguu/api/test: 16 failures, all pre-existing on clean master (test_bookmark_positions, test_verbal_flashcards) — verified by stashing.

🤖 Generated with Claude Code

On-demand simplification (#698) quietly turned the feed's level filter into
a starvation filter. `available_cefr_levels` lists the levels for which a
version of an article was ALREADY generated: the original's own level plus
its simplified children. Once crawl-time simplification was retired there
are no children, so the field collapsed to a single value and a learner saw
only articles a publisher happened to write at exactly their band. Measured
on the live index for Danish over 7 days: 33 documents matched an A2 reader,
out of 479 in the language. Meanwhile 266 of those articles already carried
an A2 ArticleLevelSummary — content written for precisely that reader, which
the filter guaranteed they would never see.

The premise no longer holds. An article being above the learner's level is
not a reason to hide it when the card shows a level-appropriate summary and
the body simplifies on request. So drop the filter from all three discovery
paths — recommender feed, video recommendations, search — rather than widen
it. Above-level readers are the mirror case and get the same treatment once
we complexify.

Two things this also fixes, both of which produced empty feeds:

- Articles whose LLM assessment failed have no cefr_level and so matched
  nothing. Every assessment outage (the Aug-13 article_type bug, the
  Anthropic usage-cap outages) therefore emptied feeds outright. Feeds are
  now independent of assessment liveness.
- document_from_video never writes an available_cefr_levels field, so
  requiring a level in it excluded every video from every leveled user.

feed_delivery_health_check reached its per-segment inventory THROUGH the
recommender builder, so it would have silently degraded into six copies of
one language-wide query. It now appends the CEFR clause itself, and says
what that clause means now: a canary on assessment coverage, not on feed
delivery.

UserPreference.SHOW_EASIER_ARTICLES is inert as a result — nothing left to
widen. Left in place, and flagged, until the web toggle is removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ArchLens detected architectural changes in the following views:
diff
diff

@mircealungu
mircealungu merged commit 5a86957 into master Aug 17, 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