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:
- 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.
- Update
PracticeSelectionConfig and get_eligible_practice_problems() to exclude problems whose createdAt is newer than now - min_problem_age_days.
- Update exam
SelectionPolicyConfig and select_problems() to exclude problems whose createdAt is newer than the policy minimum age.
- Wire the new setting into
/practice/stats, /practice/next, and /exams creation.
- For exam creation, construct the selection policy from current settings so
configSnapshot.selectionPolicy includes the minimum-age value used.
- 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.
- 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:
- Run the relevant automated backend test suite.
- Manually verify that a fresh problem does not appear in practice or exams with the default threshold.
- Manually verify that setting
PROBLEM_SELECTION_MIN_AGE_DAYS=0 allows fresh problems to appear.
- Verify that existing practice cooldown behavior based on
tracking.lastTestedAt still works.
- 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:
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.
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 usesPracticeSelectionConfig.cooldown_daysto skip recently tested problems, while exam mode usesselect_problems()to weight candidates by last-tested recency and failure rate. Newly ingested or manually created problems havecreatedAt = nowand 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 whennow - createdAt < configured_threshold. The default threshold should be 3 days.Assumptions:
Problem.createdAtfield.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:
now - createdAt < 3 daysare not eligible.now - createdAt >= 3 daysare eligible if they pass the existing rules.Scope
This issue should cover:
Out of Scope
This issue should not cover:
tracking.lastTestedAtpractice cooldown semantics.Chosen Implementation Approach
Use one shared settings value for both modes, named
problem_selection_min_age_days, exposed via the environment variablePROBLEM_SELECTION_MIN_AGE_DAYS, with a default of3and validationge=0.Add the setting to the selection configs used by each mode:
min_problem_age_days: int = 3toPracticeSelectionConfig.minProblemAgeDays: int = 3toSelectionPolicyConfigso new examconfigSnapshot.selectionPolicyrecords the policy used.Selection should compare UTC-normalized
createdAtagainstnow - timedelta(days=min_age_days). IfcreatedAt > cutoff, exclude the problem. A value of0should preserve immediate eligibility.To avoid accidental historical-data issues, ensure existing exam records or tests with older
selectionPolicypayloads remain readable if they do not containminProblemAgeDays.Implementation Plan
The implementor should:
problem_selection_min_age_daystoSettingsin backend configuration with default3,ge=0, and environment variable support through the existing Pydantic settings pattern.PracticeSelectionConfigandget_eligible_practice_problems()to exclude problems whosecreatedAtis newer thannow - min_problem_age_days.SelectionPolicyConfigandselect_problems()to exclude problems whosecreatedAtis newer than the policy minimum age./practice/stats,/practice/next, and/examscreation.configSnapshot.selectionPolicyincludes the minimum-age value used.createdAtold enough or override the config threshold to0, depending on what the test is verifying.Relevant Files / Areas
Likely relevant areas:
backend/app/infrastructure/config/settings.pybackend/app/domain/practice_selection.pybackend/app/domain/selection.pybackend/app/domain/models.pybackend/app/presentation/practice.pybackend/app/presentation/exams.pybackend/app/presentation/exam_helpers.pybackend/app/presentation/settings.pybackend/tests/domain/test_practice_selection.pybackend/tests/domain/test_selection.pybackend/tests/api/test_practice.pybackend/tests/api/test_exams.pyDEVELOPMENT.md.env.exampleif presentTests Required
The implementor must add or update automated tests covering:
createdAtnewer than the configured threshold.createdAtexactly at the threshold boundary./practice/nextreturnsno_eligiblewhen all otherwise practiceable problems are too new.NO_ELIGIBLE_PROBLEMSwhen all otherwise eligible problems are too new.configSnapshot.selectionPolicy.0allows newly created problems to remain immediately eligible.At minimum, tests should verify:
now - createdAt == threshold.lastTestedAtcooldown.Manual Verification / Self-Check
Before claiming this issue is done, the implementor must:
PROBLEM_SELECTION_MIN_AGE_DAYS=0allows fresh problems to appear.tracking.lastTestedAtstill works.Suggested verification commands:
Reviewer Acceptance Checklist
The reviewer should verify that:
0preserves immediate eligibility.Dependencies
None.
Follow-Up Work
Potential future follow-up, not part of this issue:
Definition of Done
This issue is done when: