How is the percentage (progress) of a course calculated using XBlock?

I saw that in XBlock we have the “completion_mode” attribute and it is taken into account at various times, especially within the “completion-aggregator” plugin:

I’m trying to use XBlock:

I uploaded a spreadsheet, within the LMS, changing the score to 1.0, however, the progress always remains at 0.0%

In other words, from what I understood, I can’t mark this course as “complete” because I also depend on the percentage.

I tried to search for the “completion_mode” attribute within staff_graded, for example, but without success.
According to the “completion_aggregator” documentation:

When the attribute is not found, it will be considered by default as XBlockCompletionMode.COMPLETABLE

My questions are:

  • How does the percentage (progress) calculation of a block or course work?
  • Which part of the code excludes or adds the blocks according to their type when calculating the total?
  • If I have 4 XBlocks (3 HTML + 1 Staff Graded and then go through the contents of the first three in html, will the fourth block be automatically considered as “completed” too? (ignoring them from the calculations)

Refs, I’m using these two components for testing:

Application version:

Staff Graded XBlock does not implement any completion code (has_custom_completion is False; it doesn’t use CompletableXBlockMixin), so the logic for updating its completion is in the scorable_block_completion handler. When you upload the spreadsheet, it should be triggering the PROBLEM_WEIGHTED_SCORE_CHANGED signal, which should in turn trigger the completion update. You can try adding some logging to the scorable_block_completion handler to see why it’s not updating.

This code.

Since the Staff Graded component is a scorable block, it should be marked as completed once a score (grade) is issued (any score).

If the StaffGradedXBlock is not “completable”, why is it taken into account when dividing the percentage?

{
    "id": "block-v1:test+PERCENTAGE+2024_08_SGP+type@course+block@course",
    "display_name": "TEST COMPLETION PERCENTAGE - SGP",
    "type": "course",
    "short_description": "Activitiy to test completion percentage using staff graded points",
    "lms_web_url": "https://learninghub-lms-sta.prodeng-playground-internal.mercadolibre.com/courses/course-v1:test+PERCENTAGE+2024_08_SGP/jump_to/block-v1:test+PERCENTAGE+2024_08_SGP+type@course+block@course",
    "complete": false,
    "completion": {
        "percent": 0.6666666666666666
    }
}

Where is the part of the code inside completion responsible for taking and dividing the divisible blocks to get to 0.66?

Completion code: GitHub - openedx/completion: A library for tracking completion of blocks by learners in edX courses.

The block ID for the StaffGradedXBlock is: block-v1:test+PERCENTAGE+2024_08_SGP+type@staffgradedxblock+block@0a4c905ceedc4b93beda0b7c14f97d9f

Attached is the complete course JSON so you can take a better look at the attributes:
course-content-api.doc (46.5 KB)

According to the OpenedX code, when rendering this response, it is being filled with 0.0.

As you said @braden , StaffGraded is not completable, it is only scorable, so I understand that following the code logic, it is always falling into the else:

Link: edx-platform/lms/djangoapps/course_api/blocks/transformers/block_completion.py at open-release/maple.master · openedx/edx-platform · GitHub

        for block_key in block_structure.topological_traversal():
            if _is_block_an_aggregator_or_excluded(block_key):
                completion_value = None
            elif block_key in completions_dict:
                completion_value = completions_dict[block_key]
            else:
                completion_value = 0.0

Attention: I have already uploaded a score, that is, according to your comment, it should be marked as complete:

Reference: edx-platform/lms/djangoapps/course_api/blocks/transformers/block_completion.py at open-release/maple.master · openedx/edx-platform · GitHub

python manage.py lms shell

from completion.models import BlockCompletion
from django.contrib.auth.models import User

user = User.objects.filter(username='test').first()
course_key = "course-v1:test+PERCENTAGE+2024_08_SGP"

completions = BlockCompletion.objects.filter(
            user=user,
            context_key=course_key,
        ).values_list(
            'block_key',
            'completion',
        )

completions_dict = {
            block.map_into_course(course_key_object): completion
            for block, completion in completions
        }

(only the first two blocks are marked as complete, but, as you can see in the attachment, I have already uploaded the spreadsheet with the score=1.0)

block-v1_test+PERCENTAGE+2024_08_SGP+type@staffgradedxblock+block@0a4c905ceedc4b93beda0b7c14f97d9f (1).csv (310 Bytes)

{BlockUsageLocator(CourseLocator('test', 'PERCENTAGE', '2024_08_SGP', None, None), 'html', '4184448c218b4f8da2b42ad1f9d075f4'): 1.0, 

BlockUsageLocator(CourseLocator('test', 'PERCENTAGE', '2024_08_SGP', None, None), 'html', 'ca9c2211ad194983bdfe28b88c6e5540'): 1.0}

So, I don’t know where to go next, I think that:

  • Maybe we have a problem in the StaffGradedXBlock, where it should be considered as completion_mode = XBlockCompletionMode.EXCLUDED and be disregarded from the count, since it is not being marked as complete when uploaded.

  • Maybe the completion_mode attribute really shouldn’t exist in the XBlock, however, there may be a failure when marking as completion=1.0, since I didn’t even find the logic responsible for this.

  • One interesting thing, I noticed that when the completion is None, it is not even returned in the API search, which means that it may be the crucial point in determining which blocks will be included in the count and division until reaching the percentage: edx-platform/lms/djangoapps/course_api/blocks/serializers.py at open-release/maple.master · openedx/edx-platform · GitHub

Any idea of ​​what I can do in this Maple version? (testing on a more recent OpenedX version is out of the question for the company at this time)

First of all, I would like to thank you immensely for answering the first question I asked a while ago.

Any further help and clarification will be most welcome.

Kind regards,
JOSÉ VICTOR