Skip to content

Add minimum problem age rule to practice and exam selection #247

@brianlan

Description

@brianlan

Summary

Add a shared problem selection minimum-age rule so newly added problems do not appear in practice mode or exam mode until they are at least 3 days old by default.

Background / Context

Problem selection currently has recency and cooldown logic, but it is based on tracking.lastTestedAt, not the date when the problem was added. Practice mode uses PracticeSelectionConfig.cooldown_days to skip recently tested problems, while exam mode uses select_problems() to weight candidates by last-tested recency and failure rate. Newly ingested or manually created problems have createdAt = now and can currently appear immediately in both practice and exams as long as they are not deleted and have a non-empty correct answer.

The requested behavior is to add a separate selection-age rule based on Problem.createdAt: block a problem when now - createdAt < configured_threshold. The default threshold should be 3 days.

Assumptions:

  • "Problem added date" means the existing Problem.createdAt field.
  • A problem exactly 3 days old should be eligible.
  • The same threshold should apply to practice and exam selection.
  • Practice stats should use the same filtering as practice selection so the UI count matches actual availability.

Problem

There is no current rule that prevents recently added problems from appearing immediately in practice mode or exam mode. This makes fresh problems eligible before the user has had a desired settling period after ingestion or creation.

Goal / Expected Behavior

Practice mode and exam mode should exclude problems that are newer than a configurable minimum age.

By default:

  • Problems with now - createdAt < 3 days are not eligible.
  • Problems with now - createdAt >= 3 days are eligible if they pass the existing rules.
  • Existing deleted-problem, missing-answer, practice cooldown, recency weighting, and failure weighting behavior should continue to work.

Scope

This issue should cover:

  • Add a shared backend configuration setting for minimum problem age, defaulting to 3 days.
  • Apply the minimum-age rule to practice eligibility.
  • Apply the minimum-age rule to exam selection.
  • Keep practice stats consistent with practice selection.
  • Persist the exam selection policy snapshot for newly created exams with the minimum-age value used.
  • Add or update automated tests for domain selection and API behavior.
  • Update developer configuration documentation and environment examples if present.

Out of Scope

This issue should not cover:

  • Frontend UI changes beyond any behavior that naturally follows from backend responses.
  • New per-user or per-mode settings screens.
  • Database migrations for existing problems.
  • Retuning recency or failure scoring weights.
  • Changing the existing tracking.lastTestedAt practice cooldown semantics.

Chosen Implementation Approach

Use one shared settings value for both modes, named problem_selection_min_age_days, exposed via the environment variable PROBLEM_SELECTION_MIN_AGE_DAYS, with a default of 3 and validation ge=0.

Add the setting to the selection configs used by each mode:

  • Add min_problem_age_days: int = 3 to PracticeSelectionConfig.
  • Add minProblemAgeDays: int = 3 to SelectionPolicyConfig so new exam configSnapshot.selectionPolicy records the policy used.

Selection should compare UTC-normalized createdAt against now - timedelta(days=min_age_days). If createdAt > cutoff, exclude the problem. A value of 0 should preserve immediate eligibility.

To avoid accidental historical-data issues, ensure existing exam records or tests with older selectionPolicy payloads remain readable if they do not contain minProblemAgeDays.

Implementation Plan

The implementor should:

  1. Add problem_selection_min_age_days to Settings in backend configuration with default 3, ge=0, and environment variable support through the existing Pydantic settings pattern.
  2. Update PracticeSelectionConfig and get_eligible_practice_problems() to exclude problems whose createdAt is newer than now - min_problem_age_days.
  3. Update exam SelectionPolicyConfig and select_problems() to exclude problems whose createdAt is newer than the policy minimum age.
  4. Wire the new setting into /practice/stats, /practice/next, and /exams creation.
  5. For exam creation, construct the selection policy from current settings so configSnapshot.selectionPolicy includes the minimum-age value used.
  6. Update tests that create immediately eligible problems to set createdAt old enough or override the config threshold to 0, depending on what the test is verifying.
  7. Update developer docs and env examples where backend settings are listed.

Relevant Files / Areas

Likely relevant areas:

  • backend/app/infrastructure/config/settings.py
  • backend/app/domain/practice_selection.py
  • backend/app/domain/selection.py
  • backend/app/domain/models.py
  • backend/app/presentation/practice.py
  • backend/app/presentation/exams.py
  • backend/app/presentation/exam_helpers.py
  • backend/app/presentation/settings.py
  • backend/tests/domain/test_practice_selection.py
  • backend/tests/domain/test_selection.py
  • backend/tests/api/test_practice.py
  • backend/tests/api/test_exams.py
  • DEVELOPMENT.md
  • .env.example if present

Tests Required

The implementor must add or update automated tests covering:

  • Practice selection excludes a problem with createdAt newer than the configured threshold.
  • Practice selection includes a problem with createdAt exactly at the threshold boundary.
  • Practice selection includes a problem older than the threshold when other existing rules pass.
  • Practice stats exclude too-new problems.
  • /practice/next returns no_eligible when all otherwise practiceable problems are too new.
  • Exam selection excludes too-new problems.
  • Exam creation returns NO_ELIGIBLE_PROBLEMS when all otherwise eligible problems are too new.
  • Exam creation succeeds with old-enough problems and records the minimum-age value in configSnapshot.selectionPolicy.
  • Setting the threshold to 0 allows newly created problems to remain immediately eligible.

At minimum, tests should verify:

  • The main happy path for old-enough problems in both practice and exam modes.
  • The too-new exclusion behavior in both practice and exam modes.
  • The boundary condition where now - createdAt == threshold.
  • Regression behavior for deleted problems, missing correct answers, and practice lastTestedAt cooldown.

Manual Verification / Self-Check

Before claiming this issue is done, the implementor must:

  1. Run the relevant automated backend test suite.
  2. Manually verify that a fresh problem does not appear in practice or exams with the default threshold.
  3. Manually verify that setting PROBLEM_SELECTION_MIN_AGE_DAYS=0 allows fresh problems to appear.
  4. Verify that existing practice cooldown behavior based on tracking.lastTestedAt still works.
  5. Record the exact commands run and their results in the PR description.

Suggested verification commands:

cd backend
uv run pytest tests/domain/test_practice_selection.py tests/domain/test_selection.py
uv run pytest tests/api/test_practice.py tests/api/test_exams.py

Reviewer Acceptance Checklist

The reviewer should verify that:

  • The implementation matches the expected behavior described above.
  • The chosen implementation approach was followed, or any deviation is clearly justified.
  • The change is appropriately scoped and does not include unrelated work.
  • Required automated tests were added or updated.
  • The tests fail or would have failed before the fix where applicable.
  • The implementor included self-verification results in the PR.
  • Edge cases and regression risks were considered.
  • Documentation, comments, or user-facing text were updated if needed.
  • Practice stats, practice next selection, and exam creation all use the same minimum-age threshold.
  • The exam config snapshot records the minimum-age policy used for newly created exams.
  • A threshold of 0 preserves immediate eligibility.

Dependencies

None.

Follow-Up Work

Potential future follow-up, not part of this issue:

  • Add a user-facing settings UI if users need to change this value without environment configuration.
  • Add per-mode thresholds if practice and exam behavior need to diverge later.

Definition of Done

This issue is done when:

  • Practice and exam selection both exclude problems younger than the configured minimum age.
  • The default minimum age is 3 days.
  • A configured minimum age of 0 allows immediate eligibility.
  • Practice stats match the actual practice selection eligibility.
  • New exam snapshots record the minimum-age selection policy.
  • Automated tests cover the required scenarios.
  • Developer configuration documentation is updated.
  • The PR description includes test commands, results, and any manual verification performed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions