Skip to content

Commit

Permalink
Merge pull request #341 from openedx/cag/add-grading-events
Browse files Browse the repository at this point in the history
feat: add xapi transformer for grading events
  • Loading branch information
Ian2012 committed Aug 29, 2023
2 parents 143e821 + d58e709 commit 5191224
Show file tree
Hide file tree
Showing 15 changed files with 482 additions and 3 deletions.
4 changes: 4 additions & 0 deletions docs/event-mapping/Supported_events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Course grading events
-----------------------

* edx.course.grade.passed.first_time | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.course.grade.passed.first_time.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-course-grade-passed-first-time>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.course.grade.passed.first_time.json>`__ | Caliper `map <./Caliper_mapping.rst#edx-course-grade-passed-first-time>`__ , `sample <../../event_routing_backends/processors/caliper/tests/fixtures/expected/edx.course.grade.passed.first_time.json>`__
* `edx.grades.subsection.grade_calculated`_ | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.grades.subsection.grade_calculated.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-grades-subsection-grade-calculated>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.subsection.grade_calculated.json>`__
* `edx.grades.course.grade_calculated`_ | edX `sample <../../event_routing_backends/processors/tests/fixtures/current/edx.grades.course.grade_calculated.json>`__ | xAPI `map <./xAPI_mapping.rst#edx-grades-course-grade-calculated>`__ , `sample <../../event_routing_backends/processors/xapi/tests/fixtures/expected/edx.grades.course.grade_calculated.json>`__

Problem interaction events
---------------------------
Expand Down Expand Up @@ -74,6 +76,8 @@ Forum events
.. _edx.course.enrollment.activated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-course-enrollment-activated-and-edx-course-enrollment-deactivated
.. _edx.course.enrollment.deactivated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-course-enrollment-activated-and-edx-course-enrollment-deactivated
.. _edx.course.enrollment.mode_changed: https://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#edx-course-enrollment-mode-changed
.. _edx.grades.subsection.grade_calculated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-grades-subsection-grade-calculated
.. _edx.grades.course.grade_calculated: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-grades-course-grade-calculated
.. _edx.grades.problem.submitted: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/course_team_event_types.html#edx-grades-problem-submitted
.. _problem_check: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#problem-check
.. _showanswer: http://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/tracking_logs/student_event_types.html#showanswer
Expand Down
2 changes: 1 addition & 1 deletion event_routing_backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Various backends for receiving edX LMS events..
"""

__version__ = '5.5.6'
__version__ = '5.6.0'
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "edx.grades.course.grade_calculated",
"context": {
"course_id": "course-v1:edX+DemoX+Demo_Course",
"course_user_tags": {},
"user_id": 3,
"path": "/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2/handler/xmodule_handler/problem_check",
"org_id": "edX",
"enterprise_uuid": "",
"module": {
"display_name": "Checkboxes",
"usage_key": "block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2"
}
},
"username": "edX",
"session": "056aca2a1c6b76742b283e73d3424453",
"ip": "172.19.0.1",
"agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
"host": "localhost:18000",
"referer": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@ea9e33c0ad15445195f96d3c42ebc9f0?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=Homework",
"accept_language": "en-US,en;q=0.9,es;q=0.8",
"data": {
"user_id": "3",
"course_id": "course-v1:edX+DemoX+Demo_Course",
"course_version": "64e64c6835fe1d2093ba4d69",
"percent_grade": 1.0,
"letter_grade": "Pass",
"course_edited_timestamp": "2023-08-23 18:14:00.070241+00:00",
"event_transaction_id": "a47772a0-3c7f-4a0e-9c11-7e903a03e503",
"event_transaction_type": "edx.grades.problem.submitted",
"grading_policy_hash": "ChVp0lHGQGCevD0t4njna/C44zQ="
},
"time": "2023-08-23T20:37:45.526615+00:00",
"event_type": "edx.grades.course.grade_calculated",
"event_source": "server",
"page": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "edx.grades.course.grade_calculated",
"context": {
"course_id": "course-v1:edX+DemoX+Demo_Course",
"course_user_tags": {},
"user_id": 3,
"path": "/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2/handler/xmodule_handler/problem_check",
"org_id": "edX",
"enterprise_uuid": "",
"module": {
"display_name": "Checkboxes",
"usage_key": "block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2"
}
},
"username": "edX",
"session": "056aca2a1c6b76742b283e73d3424453",
"ip": "172.19.0.1",
"agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
"host": "localhost:18000",
"referer": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@ea9e33c0ad15445195f96d3c42ebc9f0?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=Homework",
"accept_language": "en-US,en;q=0.9,es;q=0.8",
"data": {
"user_id": "3",
"course_id": "course-v1:edX+DemoX+Demo_Course",
"course_version": "64e64c6835fe1d2093ba4d69",
"percent_grade": 0.02,
"letter_grade": "",
"course_edited_timestamp": "2023-08-23 18:14:00.070241+00:00",
"event_transaction_id": "a47772a0-3c7f-4a0e-9c11-7e903a03e503",
"event_transaction_type": "edx.grades.problem.submitted",
"grading_policy_hash": "ChVp0lHGQGCevD0t4njna/C44zQ="
},
"time": "2023-08-23T20:37:45.526615+00:00",
"event_type": "edx.grades.course.grade_calculated",
"event_source": "server",
"page": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "edx.grades.subsection.grade_calculated",
"context": {
"course_id": "course-v1:edX+DemoX+Demo_Course",
"course_user_tags": {},
"user_id": 3,
"path": "/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2/handler/xmodule_handler/problem_check",
"org_id": "edX",
"enterprise_uuid": "",
"module": {
"display_name": "Checkboxes",
"usage_key": "block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2"
}
},
"username": "edX",
"session": "056aca2a1c6b76742b283e73d3424453",
"ip": "172.19.0.1",
"agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
"host": "localhost:18000",
"referer": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@ea9e33c0ad15445195f96d3c42ebc9f0?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=Homework",
"accept_language": "en-US,en;q=0.9,es;q=0.8",
"data": {
"user_id": "4",
"course_id": "course-v1:edX+DemoX+Demo_Course",
"block_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@a2184f8ebf9d478ba57edcc4f288854a",
"course_version": "64e64c6835fe1d2093ba4d69",
"weighted_total_earned": 3.0,
"weighted_total_possible": 3.0,
"weighted_graded_earned": 3.0,
"weighted_graded_possible": 3.0,
"first_attempted": "2023-08-22 17:58:21.710578+00:00",
"subtree_edited_timestamp": "2023-08-23 18:14:00.070241+00:00",
"event_transaction_id": "a47772a0-3c7f-4a0e-9c11-7e903a03e503",
"event_transaction_type": "edx.grades.problem.submitted",
"visible_blocks_hash": "7bcnLlOwpFfmByYbwqBmWHskIEY="
},
"time": "2023-08-23T20:37:44.920732+00:00",
"event_type": "edx.grades.subsection.grade_calculated",
"event_source": "server",
"page": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "edx.grades.subsection.grade_calculated",
"context": {
"course_id": "course-v1:edX+DemoX+Demo_Course",
"course_user_tags": {},
"user_id": 3,
"path": "/courses/course-v1:edX+DemoX+Demo_Course/xblock/block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2/handler/xmodule_handler/problem_check",
"org_id": "edX",
"enterprise_uuid": "",
"module": {
"display_name": "Checkboxes",
"usage_key": "block-v1:edX+DemoX+Demo_Course+type@problem+block@185ffd73e23843168ca02e8d070ffcf2"
}
},
"username": "edX",
"session": "056aca2a1c6b76742b283e73d3424453",
"ip": "172.19.0.1",
"agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
"host": "localhost:18000",
"referer": "http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@ea9e33c0ad15445195f96d3c42ebc9f0?show_title=0&show_bookmark_button=0&recheck_access=1&view=student_view&format=Homework",
"accept_language": "en-US,en;q=0.9,es;q=0.8",
"data": {
"user_id": "4",
"course_id": "course-v1:edX+DemoX+Demo_Course",
"block_id": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@a2184f8ebf9d478ba57edcc4f288854a",
"course_version": "64e64c6835fe1d2093ba4d69",
"weighted_total_earned": 0.0,
"weighted_total_possible": 0.0,
"weighted_graded_earned": 0.0,
"weighted_graded_possible": 0.0,
"first_attempted": "2023-08-22 17:58:21.710578+00:00",
"subtree_edited_timestamp": "2023-08-23 18:14:00.070241+00:00",
"event_transaction_id": "a47772a0-3c7f-4a0e-9c11-7e903a03e503",
"event_transaction_type": "edx.grades.problem.submitted",
"visible_blocks_hash": "7bcnLlOwpFfmByYbwqBmWHskIEY="
},
"time": "2023-08-23T20:37:44.920732+00:00",
"event_type": "edx.grades.subsection.grade_calculated",
"event_source": "server",
"page": null
}
5 changes: 4 additions & 1 deletion event_routing_backends/processors/xapi/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
XAPI_VERB_VOTED = 'https://w3id.org/xapi/openedx/verb/voted'
XAPI_VERB_REPORTED = 'https://w3id.org/xapi/acrossx/verbs/reported'
XAPI_VERB_UNREPORTED = 'https://w3id.org/xapi/openedx/verb/unreported'
XAPI_VERB_EARNED = 'http://id.tincanapi.com/verb/earned'

XAPI_VERB_TERMINATED = 'http://adlnet.gov/expapi/verbs/terminated'
XAPI_VERB_ASKED = 'http://adlnet.gov/expapi/verbs/asked'
Expand All @@ -47,7 +48,8 @@
XAPI_ACTIVITY_TOTAL_COUNT = 'https://w3id.org/xapi/acrossx/extensions/total-items'
XAPI_ACTIVITY_MODE = 'https://w3id.org/xapi/acrossx/extensions/type'
XAPI_ACTIVITY_ATTEMPT = 'http://id.tincanapi.com/extension/attempt-id'

XAPI_ACTIVITY_GRADE_CLASSIFICATION = 'http://www.tincanapi.co.uk/activitytypes/grade_classification'
XAPI_ACTIVITY_GRADE = 'http://www.tincanapi.co.uk/extensions/result/classification'
# xAPI context
XAPI_CONTEXT_VIDEO_LENGTH = 'https://w3id.org/xapi/video/extensions/length'
XAPI_CONTEXT_VIDEO_CC_LANGUAGE = 'https://w3id.org/xapi/video/extensions/cc-subtitle-lang'
Expand Down Expand Up @@ -97,6 +99,7 @@
VOTED = 'voted'
REPORTED = 'reported'
UNREPORTED = 'unreported'
EARNED = 'earned'

TERMINATED = 'terminated'
NAVIGATED = 'navigated'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
ThreadViewedTransformer,
ThreadVotedTransformer,
)
from event_routing_backends.processors.xapi.event_transformers.grading_events import (
CourseGradedTransformer,
SubsectionGradedTransformer,
)
from event_routing_backends.processors.xapi.event_transformers.navigation_events import (
LinkClickedTransformer,
OutlineSelectedTransformer,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""
Transformers for grading related events.
"""
from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result, Verb

from event_routing_backends.helpers import get_course_from_id
from event_routing_backends.processors.xapi import constants
from event_routing_backends.processors.xapi.registry import XApiTransformersRegistry
from event_routing_backends.processors.xapi.transformer import XApiTransformer


@XApiTransformersRegistry.register("edx.grades.subsection.grade_calculated")
class SubsectionGradedTransformer(XApiTransformer):
"""
Transformer for event generated when an subsection is graded.
"""

verb = Verb(
id=constants.XAPI_VERB_EARNED,
display=LanguageMap({constants.EN: constants.EARNED}),
)

additional_fields = ("result",)

def get_object(self):
"""
Get object for xAPI transformed event related to subsection grading.
Returns:
`Activity`
"""

return Activity(
id=self.get_object_iri("xblock", self.get_data("data.block_id", True)),
definition=ActivityDefinition(
type=constants.XAPI_ACTIVITY_RESOURCE,
),
)

def get_result(self):
"""
Get result for xAPI transformed event.
Returns:
`Result`
"""
event_data = self.get_data("data")
weighted_possible = event_data["weighted_total_possible"] or 0
weighted_earned = event_data["weighted_total_earned"] or 0

if weighted_possible > 0:
scaled = weighted_earned / weighted_possible
else:
scaled = 0
return Result(
success=weighted_earned >= weighted_possible,
score={
"min": 0,
"max": weighted_possible,
"raw": weighted_earned,
"scaled": scaled,
},
)


@XApiTransformersRegistry.register("edx.grades.course.grade_calculated")
class CourseGradedTransformer(XApiTransformer):
"""
Transformer for event generated when an course is graded.
"""

verb = Verb(
id=constants.XAPI_VERB_EARNED,
display=LanguageMap({constants.EN: constants.EARNED}),
)

additional_fields = ("result",)

def get_object(self):
"""
Get object for xAPI transformed event related to course grading.
Returns:
`Activity`
"""
course_id = self.get_data("context.course_id", True)
object_id = self.get_object_iri("course", course_id)
course = get_course_from_id(course_id)
display_name = course["display_name"]

return Activity(
id=object_id,
definition=ActivityDefinition(
type=constants.XAPI_ACTIVITY_COURSE,
name=LanguageMap(
**({constants.EN: display_name} if display_name is not None else {})
),
),
)

def get_result(self):
"""
Get result for xAPI transformed event.
Returns:
`Result`
"""
event_data = self.get_data("data")
weighted_possible = 1.0
weighted_earned = event_data["percent_grade"] or 0

letter_grade = self.get_data("data.letter_grade") or "Fail"

return Result(
score={
"min": 0,
"max": weighted_possible,
"raw": weighted_earned,
"scaled": weighted_earned,
},
extensions=Extensions(
{
constants.XAPI_ACTIVITY_GRADE_CLASSIFICATION: letter_grade
}
),
)
Loading

0 comments on commit 5191224

Please sign in to comment.