Skip to content

AssessmentWorkflow.staff_score_exists() always returns False (looks up get_latest_assessment; the staff API defines get_latest_staff_assessment), so the ORA Summary Report's is_staff_grade_received is always 0 #2455

Description

@shaurya416

AssessmentWorkflow.staff_score_exists() always returns False (looks up get_latest_assessment; the staff API defines get_latest_staff_assessment), so the ORA Summary Report's is_staff_grade_received is always 0

openassessment/workflow/models.py:558-571 (read at eee6b7b, current master)

def staff_score_exists(self):
    """
    Check if a staff score exists for this submission.
    """
    steps = self._get_steps()
    step_for_name = {step.name: step for step in steps}
    staff_step = step_for_name.get(self.STAFF_STEP_NAME)
    if staff_step is not None:
        get_latest_func = getattr(staff_step.api(), 'get_latest_assessment', None)   # :566
        if get_latest_func is not None:
            staff_assessment = get_latest_func(self.submission_uuid)
            if staff_assessment is not None:
                return True
    return False

For the staff step, api() resolves to openassessment.assessment.api.staff (models.py:934-936). _get_steps() always creates that step (models.py:472-505), so this path runs for every AssessmentWorkflow. The staff module defines get_latest_staff_assessment (staff.py:163) and no get_latest_assessment, and it has no alias or module-level __getattr__. teams.py is the same (teams.py:157). getattr(..., None) therefore returns None on every call, and the function falls through to return False.

Repo-wide, get_latest_assessment appears only at models.py:566 and as test method names in assessment/test/test_team.py:152,160, which call get_latest_staff_assessment. The lookup has been mismatched since the method was introduced in #758, the same PR that added get_latest_staff_assessment to staff.py.

Two things read this function:

  • OraAggregateData.collect_ora2_summary() (data.py:816): is_staff_grade_received = 1 if aw.staff_score_exists() else 0. This backs the instructor-dashboard ORA Summary Report (upload_ora2_summary in openedx-platform lms/djangoapps/instructor_task/tasks_helper/misc.py:282-291). Per the code path, the is_staff_grade_received column is 0 for every row, including learners who have a staff assessment.
  • AssessmentWorkflow.set_score() (models.py:551): if not self.staff_score_exists(): sub_api.set_score(...). The guard never blocks, but its practical reach is narrow. update_from_assessments (models.py:453-456) already skips set_score when the score carries a staff_id, and get_score (models.py:254-293) returns the staff score first whenever one exists. The only call where the guard would change behaviour is cancel() (models.py:605-607).

Measured

This was a modeled reproduction, not an observation on a running Open edX instance. No repository code was imported, installed or run. The function text of staff_score_exists, set_score and cancel was copied verbatim from models.py (ast segments) and executed in a plain Python process with no network. Substitutions used:

  • a SimpleNamespace in place of the workflow instance
  • a Step class whose api() returns a stand-in module
  • a stand-in module exposing exactly the top-level def names that ast finds in api/staff.py (only get_latest_staff_assessment has behaviour)
  • a recorder in place of sub_api.set_score
  • a logger stub

The data.py:816 line was executed as read from the file.

Case staff_score_exists() is_staff_grade_received sub_api.set_score writes
A. staff assessment exists, real staff.py names False 0 1 (guard did not block)
B. no staff assessment, real staff.py names False 0 1
C. staff assessment exists, module exposes get_latest_assessment True 1 0
D. no staff assessment, module exposes get_latest_assessment False 0 1

A and B are indistinguishable. C and D are the controls: they show the function body behaves correctly once the name resolves.

cancel() (models.py:605-607) with a 7/10 staff score present, using the copied cancel text:

Surface points written to the submissions API status
real staff.py names (as shipped) [0] cancelled
module exposing get_latest_assessment [] cancelled

Consequence

  • The ORA Summary Report's is_staff_grade_received column cannot report a staff grade. It is 0 for staff-graded learners as well as ungraded ones.
  • The existing test_collect_ora2_summary (openassessment/tests/test_data.py:914) expects is_staff_grade_received=0 on both rows. Its fixture has only peer assessments, so 0 is correct there; the test does not cover a staff-graded submission and cannot catch this.
  • The set_score guard has never been live. cancel() currently zeroes a staff-scored submission only because the guard is dead; its docstring says "Set the points earned to 0 and workflow status to cancelled." Correcting the name alone would make set_score refuse inside cancel(), so a cancelled submission would keep its staff points in the score table.

Suggested fix

Look up get_latest_staff_assessment, the name already used by api/staff.py:60, api/teams.py:55 and xblock/apis/assessments/staff_assessment_api.py:51. Because the guard has never run, the rename should not land alone. Land it together with:

  1. cancel() writing the zero directly through sub_api.set_score (or a force argument on set_score), so that cancellation still zeroes a staff score.
  2. Tests:
    • staff_score_exists: True with a staff assessment, False without.
    • cancel() on a staff-scored workflow: points end at 0.
    • A staff-graded row in test_collect_ora2_summary asserting is_staff_grade_received=1.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions