Skip to content

Commit

Permalink
feat: add completion transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-canon committed Dec 13, 2024
1 parent 428cded commit d6622c6
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 35 deletions.
142 changes: 134 additions & 8 deletions completion_aggregator/xapi.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,104 @@
"""
Transformers for completion aggregation.
"""

from event_routing_backends.processors.openedx_filters.decorators import openedx_filter
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
from tincan import Activity, ActivityDefinition, Extensions, LanguageMap, Result, Verb
from tincan import Activity, ActivityDefinition, LanguageMap, Result, Verb

from django.utils.functional import cached_property

XAPI_ACTIVITY_LESSON = "http://adlnet.gov/expapi/activities/lesson"


class BaseCompletionTransformer(XApiTransformer):
"""
Base transformer for completion events.
"""

_verb = Verb(
id=constants.XAPI_VERB_COMPLETED,
display=LanguageMap({constants.EN: constants.COMPLETED}),
)
object_type = None
object_id = None

@openedx_filter(
filter_type="completion_aggregator.xapi.completion.get_object",
)
def get_object(self):
"""
Get object for xAPI transformed event.
Returns
-------
`Activity`
"""
if not self.object_type or not self.object_id:
raise NotImplementedError() # pragma: no cover

return Activity(
id=self.object_id,
definition=ActivityDefinition(
type=self.object_type,
),
)


@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.chapter")
@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.sequential")
class ModuleCompletionTransformer(BaseCompletionTransformer):
"""
Transformer for events generated when a user completes a section or subsection.
"""

object_type = constants.XAPI_ACTIVITY_MODULE

@cached_property
def object_id(self):
"""Returns the object identifier for the module completion transformer."""
return super().get_object_iri("xblock", self.get_data("data.block_id", required=True))


@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.vertical")
class LessonCompletionTransformer(ModuleCompletionTransformer):
"""
Transformer for events generated when a user completes an unit.
"""

object_type = getattr(constants, "XAPI_ACTIVITY_LESSON", XAPI_ACTIVITY_LESSON)


@XApiTransformersRegistry.register("openedx.completion_aggregator.completion.course")
class CourseCompletionTransformer(BaseCompletionTransformer):
"""
Transformer for event generated when a user completes a course.
"""

object_type = constants.XAPI_ACTIVITY_COURSE

@cached_property
def object_id(self):
"""Returns the object identifier for the course completion transformer."""
return super().get_object_iri("courses", self.get_data("data.course_id", required=True))

def get_context_activities(self):
"""
Retunrs context activities property.
The XApiTransformer class implements this method and returns in the parent key
an activity that contains the course metadata however this is not necessary in
cases where a transformer uses the course metadata as object since the data is
redundant and a course cannot be its own parent, therefore this must return None.
Returns
-------
None
"""
return None


class BaseProgressTransformer(XApiTransformer):
Expand All @@ -32,7 +124,7 @@ def get_object(self) -> Activity:
raise NotImplementedError() # pragma: no cover

return Activity(
id=self.get_object_iri("xblock", self.get_data("data.block_id")),
id=self.object_id,
definition=ActivityDefinition(
type=self.object_type,
),
Expand All @@ -45,22 +137,35 @@ def get_result(self) -> Result:
progress = self.get_data("data.percent") or 0
return Result(
completion=progress == 1.0,
extensions=Extensions({
constants.XAPI_ACTIVITY_PROGRESS: (progress * 100),
}),
score={
"scaled": self.get_data("data.percent") or 0
}
)


@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.chapter")
@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.sequential")
@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.vertical")
class ModuleProgressTransformer(BaseProgressTransformer):
"""
Transformer for event generated when a user makes progress in a section, subsection or unit.
Transformer for event generated when a user makes progress in a section or subsection.
"""

object_type = constants.XAPI_ACTIVITY_MODULE

@cached_property
def object_id(self):
"""Returns the object identifier for the module progress transformer."""
return super().get_object_iri("xblock", self.get_data("data.block_id"))


@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.vertical")
class LessonProgressTransformer(ModuleProgressTransformer):
"""
Transformer for event generated when a user makes progress in an unit.
"""

object_type = getattr(constants, "XAPI_ACTIVITY_LESSON", XAPI_ACTIVITY_LESSON)


@XApiTransformersRegistry.register("openedx.completion_aggregator.progress.course")
class CourseProgressTransformer(BaseProgressTransformer):
Expand All @@ -69,3 +174,24 @@ class CourseProgressTransformer(BaseProgressTransformer):
"""

object_type = constants.XAPI_ACTIVITY_COURSE

@cached_property
def object_id(self):
"""Returns the object identifier for the course progress transformer."""
return super().get_object_iri("courses", self.get_data("data.course_id"))

def get_context_activities(self):
"""
Retunrs context activities property.
The XApiTransformer class implements this method and returns in the parent key
an activity that contains the course metadata however this is not necessary in
cases where a transformer uses the course metadata as object since the data is
redundant and a course cannot be its own parent, therefore this must return None.
Returns
-------
None
"""
return None
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"actor": {
"objectType": "Agent",
"account": {
"name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb",
"homePage": "http://localhost:18000"
}
},
"id":"484fe8d7-7a5b-52ff-a0ab-3d3d8c1a8b27",
"object":{
"id":"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@chapter+block@b443e0d6bc4d43c1bed991dbd8a10d42",
"definition":{
"type":"http://adlnet.gov/expapi/activities/module"
},
"objectType":"Activity"
},
"verb":{
"id":"http://adlnet.gov/expapi/verbs/completed",
"display":{
"en":"completed"
}
},
"version":"1.0.3",
"context":{
"contextActivities":{
"parent":[
{
"id":"http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course",
"objectType":"Activity",
"definition":{
"name":{
"en-US":"Demonstration Course"
},
"type":"http://adlnet.gov/expapi/activities/course"
}
}
]
},
"extensions":{
"https://w3id.org/xapi/openedx/extension/transformer-version":"event-routing-backends@1.1.1",
"https://w3id.org/xapi/openedx/extensions/session-id":"056aca2a1c6b76742b283e73d3424453"
}
},
"timestamp":"2023-12-05T21:34:52.909063+00:00"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"actor": {
"objectType": "Agent",
"account": {
"name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb",
"homePage": "http://localhost:18000"
}
},
"id":"484fe8d7-7a5b-52ff-a0ab-3d3d8c1a8b27",
"object":{
"id":"http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course",
"definition":{
"type":"http://adlnet.gov/expapi/activities/course"
},
"objectType":"Activity"
},
"verb":{
"id":"http://adlnet.gov/expapi/verbs/completed",
"display":{
"en":"completed"
}
},
"version":"1.0.3",
"context":{
"extensions":{
"https://w3id.org/xapi/openedx/extension/transformer-version":"event-routing-backends@1.1.1",
"https://w3id.org/xapi/openedx/extensions/session-id":"056aca2a1c6b76742b283e73d3424453"
}
},
"timestamp":"2023-12-05T21:34:52.909063+00:00"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"actor": {
"objectType": "Agent",
"account": {
"name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb",
"homePage": "http://localhost:18000"
}
},
"id":"484fe8d7-7a5b-52ff-a0ab-3d3d8c1a8b27",
"object":{
"id":"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@sequential+block@bf1eac10ebb649e3aaf9cc07325f8e04",
"definition":{
"type":"http://adlnet.gov/expapi/activities/module"
},
"objectType":"Activity"
},
"verb":{
"id":"http://adlnet.gov/expapi/verbs/completed",
"display":{
"en":"completed"
}
},
"version":"1.0.3",
"context":{
"contextActivities":{
"parent":[
{
"id":"http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course",
"objectType":"Activity",
"definition":{
"name":{
"en-US":"Demonstration Course"
},
"type":"http://adlnet.gov/expapi/activities/course"
}
}
]
},
"extensions":{
"https://w3id.org/xapi/openedx/extension/transformer-version":"event-routing-backends@1.1.1",
"https://w3id.org/xapi/openedx/extensions/session-id":"056aca2a1c6b76742b283e73d3424453"
}
},
"timestamp":"2023-12-05T21:34:52.909063+00:00"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"actor": {
"objectType": "Agent",
"account": {
"name": "32e08e30-f8ae-4ce2-94a8-c2bfe38a70cb",
"homePage": "http://localhost:18000"
}
},
"id":"484fe8d7-7a5b-52ff-a0ab-3d3d8c1a8b27",
"object":{
"id":"http://localhost:18000/xblock/block-v1:edX+DemoX+Demo_Course+type@vertical+block@e1fabd9fa55f441caa75580f258ffbc3",
"definition":{
"type":"http://adlnet.gov/expapi/activities/lesson"
},
"objectType":"Activity"
},
"verb":{
"id":"http://adlnet.gov/expapi/verbs/completed",
"display":{
"en":"completed"
}
},
"version":"1.0.3",
"context":{
"contextActivities":{
"parent":[
{
"id":"http://localhost:18000/course/course-v1:edX+DemoX+Demo_Course",
"objectType":"Activity",
"definition":{
"name":{
"en-US":"Demonstration Course"
},
"type":"http://adlnet.gov/expapi/activities/course"
}
}
]
},
"extensions":{
"https://w3id.org/xapi/openedx/extension/transformer-version":"event-routing-backends@1.1.1",
"https://w3id.org/xapi/openedx/extensions/session-id":"056aca2a1c6b76742b283e73d3424453"
}
},
"timestamp":"2023-12-05T21:34:52.909063+00:00"
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
},
"result":{
"completion":false,
"extensions": {
"https://w3id.org/xapi/cmi5/result/extensions/progress":50
"score": {
"scaled": 0.5
}
},
"timestamp":"2023-12-05T21:34:52.909063+00:00"
Expand Down
Loading

0 comments on commit d6622c6

Please sign in to comment.