Skip to content

feat: record competency status in the same save as a subsection grade - #39179

Draft
alezconsultant wants to merge 1 commit into
openedx:masterfrom
alezconsultant:alezconsultant/701-competency-status-on-grade
Draft

alezconsultant wants to merge 1 commit into
openedx:masterfrom
alezconsultant:alezconsultant/701-competency-status-on-grade

Conversation

@alezconsultant

Copy link
Copy Markdown
Contributor

Description

Recording a subsection grade used to save the grade alone. This PR makes the learner's competency criterion status part of the same save, using record_graded_object_statuses from openedx-core. The grade and the status are written in one transaction, so they commit or roll back together.

It covers both paths that persist a subsection grade: CreateSubsectionGrade.update_or_create_model (one subsection) and CreateSubsectionGrade.bulk_create_models (many subsections for one learner). Computing the levels above the criterion belongs to roll_up_competency_statuses in openedx-core; this PR only queues the task that calls it.

What changes:

  • Transaction. Both methods wrap the grade write and the competency call in transaction.atomic(). The competency call is not wrapped in try/except, so a storage failure fails the grade write and the caller sees the failure.
  • Fraction. The score passed to openedx-core is Decimal(str(percent_graded)) clamped to [0, 1]. On the single path it is read after the staff override has been applied, so a learner whose instructor raised their grade is credited. Subsections with no graded points available, or with no graded attempt, are not passed.
  • Roll-up task. When the competency call reports that at least one status changed, transaction.on_commit(..., robust=True) queues the new task roll_up_competency_statuses_for_user. It is queued only after the grade has committed. The bulk path queues one task for the whole batch. The task follows the shape of recalculate_subsection_grade_v3 and is routed to the same queue.
  • Grade-calculated event. The event is no longer emitted from PersistentSubsectionGrade.update_or_create_grade and bulk_create_grades. CreateSubsectionGrade emits it after the atomic block, so it is never sent for a grade that was rolled back. Those two model methods have no other production callers.
  • Setting. ENABLE_COMPETENCY_MASTERY_TRACKING (default False) gates the competency call and the enqueue. With it off, no competency code runs and no task is queued.

Behavior to be aware of

  • This PR depends on an unreleased openedx-core. subsection_grade.py imports GradedObjectScore and record_graded_object_statuses from openedx_learning.api at module level. The pinned openedx-core==1.4.0 does not contain them, so the grades module fails to import until the pin is bumped to a release that does. The pin bump is not part of this PR, and this PR stays a draft until it is. The roll-up function roll_up_competency_statuses must also exist in that release.
  • Event timing. The event fires after the atomic block exits. Under ATOMIC_REQUESTS that block is a savepoint, so on request paths the event fires before the outer commit, exactly as it did before this change.
  • Gradebook. The gradebook bulk update runs the recalculation task synchronously and the task retries on failure, so a competency storage failure there rolls back the grade recalculation while the endpoint still returns 202. This is how the endpoint already treats any recalculation failure.

Supporting information

Related to openedx/openedx-core#701

Testing instructions

Automated (run in the Tutor devstack LMS container with openedx-core mounted from the branch that contains the competency function):

  1. pytest lms/djangoapps/grades (the whole app).
  2. ruff check lms/envs/common.py lms/envs/production.py lms/djangoapps/grades.

Additional testing can be done when openedx/openedx-core#603 will be merged.

🤖 Generated with Claude Code

When a learner's subsection grade is persisted, the LMS now also updates the
learner's competency criterion status in the same database transaction, using
record_graded_object_statuses from openedx-core. The grade and the status
commit or roll back together, so a learner is never shown a grade without the
mastery it earned.

The behavior is gated by the new ENABLE_COMPETENCY_MASTERY_TRACKING setting,
which defaults to False.

Related to openedx/openedx-core#701

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @alezconsultant!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform-oncall.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

🔘 Update the status of your PR

Your PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate.


Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Oct 1, 2026

@tbain tbain left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, and Codex review didn't raise any actual concerns.


Raw Codex output:

Details Verdict: request changes on PR [[#39179](https://github.com//pull/39179)](https://github.com//pull/39179), reviewed against [[ticket #701](https://github.com/openedx/openedx-core/issues/701)](https://github.com/openedx/openedx-core/issues/701).

Additional review notes: AI attribution is present. The PR provides automated test commands but no manual testing steps. I did not rerun tests because the workspace is not checked out at the PR commit and requires the unreleased openedx-core APIs.

I think we can disregard the first bug, this ticket depends on upstream tickets being merged in so that's a known issue. Can you expand on the second bug, though, and provide a simple suggestion for how to address it?

Yes—setting aside the upstream dependency issue, the second concern is about losing the roll-up task.

The grade and criterion status are committed first. Then the on_commit callback calls apply_async():

transaction.on_commit(
    lambda: _enqueue_competency_rollup(student.id, object_ids),
    robust=True,
)

If the broker is unavailable:

  1. The grade and criterion status commit successfully.
  2. Task publication fails.
  3. robust=True causes Django to log and suppress the exception.
  4. The roll-up task is discarded with no retry.

The learner may therefore have an updated criterion status but stale higher-level competency statuses. The test test_single_path_enqueue_failure_does_not_skip_event_or_raise currently codifies this behavior.

Simple suggestion: record a pending roll-up request in the same database transaction, then have a retryable worker dispatch those requests to Celery. That preserves both requirements: the grade write is not rolled back because of a broker outage, and the roll-up cannot be silently lost.

Simply removing robust=True would expose the failure, but would not reliably recover the task and could make callers see an error after the grade already committed.

If we have a test named test_single_path_enqueue_failure_does_not_skip_event_or_raise the codifies this behavior, does that mean it should be expected? What AC are you looking at that makes you think this is a bug?

Yes. Given that test, enqueue failure is an intentional behavior: the grade/status save succeeds, and a broker failure does not propagate to the caller.

The AC I was relying on was:

“competency status storage failure rolls back the grade and tells the caller failure.”

But that concerns database/storage failure, not failure to enqueue the post-commit task. The other ACs require criterion statuses to be updated in the same grade operation; they do not require reliable delivery of the follow-up roll-up task.

So I’d withdraw this as a PR bug. At most, it is a separate operational concern: a broker outage could leave higher-level aggregates stale. Addressing that would require a new reliability requirement and likely an outbox/retry mechanism.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

3 participants