Skip to content

feat: exclude archived criteria and groups from the status rollup - #3

Open
alezconsultant wants to merge 1 commit into
alezconsultant/643-rollup-competency-statusesfrom
alezconsultant/815-exclude-archived-from-rollup
Open

alezconsultant wants to merge 1 commit into
alezconsultant/643-rollup-competency-statusesfrom
alezconsultant/815-exclude-archived-from-rollup

Conversation

@alezconsultant

Copy link
Copy Markdown

Description

An archived CompetencyCriterion or CompetencyCriteriaGroup stops counting toward a learner's
status the next time the rollup runs. Every status a learner already holds is kept. The rollup
combines only live children, so an archived requirement the learner never met cannot keep a group
short of "demonstrated".

The rollup reads a group's children through two queries, _child_groups and _child_criteria.
Both require archived=False, so an archived child is invisible to its parent at the point
it is fetched. combine_children returns no status when no child has one, and raise_status never lowers or
deletes a stored row, so the kept-status and empty-group cases follow from the two filters.

Changes

  • src/openedx_learning/applets/cbe/rollup.py:
    • _child_groups and _child_criteria filter archived=False.
    • _roll_up_groups skips both status writes at a group that is itself archived, and still adds
      its parent group (or, for a root, its competency tag) to the walk.
    • The root-group query in _log_unearnable_sub_competencies also requires archived=False.
    • The docstrings of the touched functions and of roll_up_competency_statuses say so.
  • docs/openedx_learning/decisions/0002-competency-criteria-model.rst: Decision 6 bullet and changelog entry (see ADR).
  • tests/openedx_learning/applets/cbe/test_rollup_api.py and test_tag_rollup.py: see Tests.

Behavior

  • Filters are applied where children are fetched. Nothing else in the combination logic knows
    about archived. Leaf evaluation at grade time already skips archived criteria. The rollup's entry query does not, so a rollup call naming an archived criterion's content still recomputes that group from its live children; the scenario "a previously demonstrated requirement changes nothing until the rollup runs" relies on that.
  • An archived group is walked through but gets no row. If the walk (the rollup's level-by-level pass from the graded criteria up to each root group, then up the competency tags) reaches an archived group,
    it writes no StudentCompetencyCriteriaGroupStatus for it, and if that group is a root it
    writes no StudentCompetencyStatus either. Its parent is still recomputed from the parent's
    live children, and a root's competency still goes into the parent-competency walk. When other
    graded tags already reach a level of the parent walk, the archived root's tag adds no reads and two queries per additional parent (four on a first write). When it is the only tag reaching a level, it adds that level's six batched reads plus two per parent, at every level to the top. Compared with feat: roll up learner competency statuses above a graded criterion openedx/openedx-core#870 this change adds no queries, because feat: roll up learner competency statuses above a graded criterion openedx/openedx-core#870 already fed that tag into the walk, and it removes the archived root's own writes.
  • Nothing is lowered or removed. A group with no live children gets no status, which means no
    write. An existing row is never deleted, and raise_status never writes a lower value. A
    learner who earned mastery through a requirement that was archived later keeps it.
  • _own_criteria_statuses is unfiltered, on purpose. A stored status on a parent
    competency's archived root group still counts toward that competency's own result, so a parent
    that already reached "demonstrated" through it keeps doing so. This is consistent with "already
    earned is kept".
  • Investigation warning. A sub-competency whose root group is archived is reported by the
    "has no criteria beneath it and can never be demonstrated" warning, the same as one that was
    never given requirements. A live root whose criteria are all archived is not reported, because
    the query does not look at criteria (a known limitation from the ticket).
  • Archiving never triggers a rollup. Nothing here calls the rollup from a save or signal. A
    group status changes when the rollup next runs for a criterion in that group or beneath it, or
    when the operator recomputation command ([BE] Recompute learner competency statuses on operator command openedx/openedx-core#774, not built yet) runs. Leaf evaluation skips
    archived criteria, so a grade for an archived requirement writes no leaf row and returns a zero
    count. The task drafted in openedx/edx-platform #39179 enqueues the rollup only when that count
    is non-zero, so under that draft the acceptance scenario's "new grade for that requirement"
    trigger is reachable only through the operator command.

Tests

Seventeen new tests. Ten of them cover the eleven acceptance scenarios and are named after the
scenario; the archived-branch and never-authored investigation cases share one test.

Six of the seventeen pass on the rollup without this change: the all-archived, kept-mastery, kept-unmet-status, and two parent-competency scenario tests, and the query-count guard. They pin the existing behavior each scenario relies on and give every scenario a named test; the other ten fail when the corresponding filter or guard is removed.

And in test_tag_rollup.py: an archived root group still starts the parent-competency walk. The
archived child gets no competency row, and the parent still reads its live sibling's stored
"demonstrated" and ends at "partially attempted".

The empty-collection case ("an AND group whose every child is archived") is the pure function
combine_children(op, []), which test_rollup_rules.py::test_combine_children already covers.

Verification

  • pytest tests/openedx_learning --no-cov: 430 passed on SQLite.
  • MySQL 8 on the Tutor devstack: the same CBE suite, 430 passed.
  • pylint, pycodestyle, isort --check-only, and pydocstyle on the changed files: clean.
  • mypy over the whole repository: no issues.
  • lint-imports: all contracts kept.
  • doc8 on the changed ADR: clean.
  • Sphinx HTML build with -W: clean.
  • Manual test on a Tutor devstack (MySQL 8): the script above, run with this branch's code against
    a scratch database migrated from scratch. Every expected value matched: both archived-requirement
    cases ended at (30, 30), kept mastery stayed at 30 with modified unchanged, the investigation
    warning named the parent and the archived sub-competency once and the parent ended at 20, and
    the unmet group status stayed at 10.

Notes for reviewers

Related to openedx#815

🤖 Generated with Claude Code

Filter archived rows out of the two queries that fetch a group's children, so an archived
criterion or criteria group does not count when a learner's group and competency statuses
are combined. A group whose children are all archived has no status to report, so the rollup
writes nothing for it.

An archived group that the walk reaches is passed through, so its parent is still recomputed
from its live children, but it gets no status row and, if it is a root, no competency row.
The investigation warning for a sub-competency with no criteria beneath it ignores archived
root groups, so a fully archived branch warns like one that was never authored.
ADR-0002 Decision 6 records the evaluation rule.

Statuses a learner already holds are never lowered or removed, so mastery earned through a
requirement that was archived later stays earned. A parent competency's own archived root
group counts through its stored status. Archiving does not trigger a rollup; a
recomputation happens on the next grade for any criterion in that group or beneath it.
Leaf evaluation skips archived criteria when a grade is recorded. The rollup's entry
query does not, so a rollup call naming an archived criterion's content still recomputes
that group from its live children.

Related to openedx#815

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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