Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add quiz xAPI profile statements #270

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions src/ralph/models/edx/converters/xapi/problem_interaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Problem interaction event xAPI Converter."""

from ralph.models.converter import ConversionItem
from ralph.models.edx.problem_interaction.statements import ProblemCheck
from ralph.models.xapi.concepts.constants.cmi5_profile import (
CONTEXT_EXTENSION_SESSION_ID,
)
from ralph.models.xapi.quiz.statements import AssessmentAnsweredQuestion

from .base import BaseXapiConverter


class ProblemCheckToAssessmentAnsweredQuestion(BaseXapiConverter):
"""Converts a common edX `problem_check` event to xAPI."""

__src__ = ProblemCheck
__dest__ = AssessmentAnsweredQuestion

def _get_conversion_items(self):
"""Returns a set of ConversionItems used for conversion."""
conversion_items = super()._get_conversion_items()
return conversion_items.union(
{
ConversionItem(
"object__id",
None,
lambda event: self.platform_url
+ "/xblock/block-v1:"
+ event["context"]["course_id"]
+ event["event"]["problem_id"],
),
ConversionItem(
"context__extensions__" + CONTEXT_EXTENSION_SESSION_ID,
"session",
),
ConversionItem(
"context__contextActivites__definition__type", "context__referer"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the referer in the context field? I can't find it in the edX spec (or in our edX models), it seems to be in the BaseEdxModel alongside (and not inside) the context field.

),
ConversionItem(
"result__success",
None,
lambda event: event["event"]["success"] == "correct",
),
ConversionItem("result__score__max", "event__maxgrade"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
ConversionItem("result__score__max", "event__maxgrade"),
ConversionItem("result__score__max", "event__max_grade"),

ConversionItem("result__score__min", None, lambda _: 0),
ConversionItem("result__score__raw", "event__grade"),
ConversionItem(
"result__score__scaled",
None,
lambda event: event["event"]["grade"] / event["event"]["max_grade"],
),
ConversionItem(
"object__definition__interactionType",
None,
self.map_interaction_type,
),
},
)

@staticmethod
def map_interaction_type(event: str):
"""Maps `response_type` from EdX logs to xAPI `interactionType` values.

Returns a value of `interactionType`.
"""
response_type = event["event"]["submission"][0]["response_type"]
interaction_type = "other"

if response_type in (
"choiceresponse",
"multiplechoiceresponse",
"choicetextresponse",
"optionresponse",
):
interaction_type = "choice"
if response_type == "truefalseresponse":
interaction_type = "true-false"
if response_type in ("numericalresponse", "formularesponse"):
interaction_type = "numerical"
if response_type == "stringresponse":
interaction_type = "long-fill-in"
if response_type == "imageresponse":
interaction_type = "matching"

return interaction_type
6 changes: 6 additions & 0 deletions src/ralph/models/xapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
# flake8: noqa

from .navigation.statements import PageTerminated, PageViewed
from .quiz.statements import (
QuizCompleted,
QuizInitialized,
QuizLaunched,
QuizTerminated,
)
from .video.statements import (
VideoCompleted,
VideoEnableClosedCaptioning,
Expand Down
9 changes: 9 additions & 0 deletions src/ralph/models/xapi/navigation/fields/constants/quiz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Constants for `Quiz` xAPI profile."""

try:
from typing import Literal
except ImportError:
from typing_extensions import Literal

# Profile ID
PROFILE_ID_QUIZ = Literal["https://w3id.org/xapi/quiz"] # pylint:disable=invalid-name
1 change: 1 addition & 0 deletions src/ralph/models/xapi/quiz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# noqa: D104
59 changes: 59 additions & 0 deletions src/ralph/models/xapi/quiz/contexts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""`Assessment` xAPI events context fields definitions."""

from typing import List, Union

from ..base.contexts import BaseXapiContext, BaseXapiContextContextActivities
from ..base.unnested_objects import BaseXapiActivity, BaseXapiActivityDefinition
from ..concepts.constants.quiz import PROFILE_ID_QUIZ
from ..concepts.constants.scorm_profile import ACTIVITY_ID_PROFILE


class QuizContextActivitiesCategoryDefinition(BaseXapiActivityDefinition):
# noqa: D205
"""Pydantic model for quiz `context`.`contextActivities`.`category`.
`definition` property.

Attributes:
type (str): Consists of the value `http://adlnet.gov/expapi/activities/profile`.
"""

type: ACTIVITY_ID_PROFILE = ACTIVITY_ID_PROFILE.__args__[0]


class QuizContextActivitiesCategory(BaseXapiActivity):
# noqa: D205, D415
"""Pydantic model for quiz `context`.`contextActivities`.`category`
property.

Attributes:
id (str): Consists of the value `https://w3id.org/xapi/quiz`.
definition (dict): see QuizContextActivitiesCategoryDefinition.
"""

id: PROFILE_ID_QUIZ = PROFILE_ID_QUIZ.__args__[0]
definition: QuizContextActivitiesCategoryDefinition = (
QuizContextActivitiesCategoryDefinition()
)


class QuizContextActivities(BaseXapiContextContextActivities):
"""Pydantic model for quiz `context`.`contextActivities` property.

Attributes:
category (list): see QuizContextActivitiesCategory.
"""

category: Union[
QuizContextActivitiesCategory,
List[QuizContextActivitiesCategory],
]


class QuizContext(BaseXapiContext):
"""Pydantic model for quiz base `context` property.

Attributes:
contextActivities: see QuizContextActivities.
"""

contextActivities: QuizContextActivities
162 changes: 162 additions & 0 deletions src/ralph/models/xapi/quiz/statements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""`Quiz` xAPI event definitions."""

from datetime import datetime

from ...selector import selector
from ..base.statements import BaseXapiStatement
from ..concepts.activity_types.scorm_profile import CMIInteractionActivity
from ..concepts.verbs.adl_vocabulary import LaunchedVerb
from ..concepts.verbs.scorm_profile import (
CompletedVerb,
FailedVerb,
InitializedVerb,
PassedVerb,
TerminatedVerb,
)
from .contexts import QuizContext

# Mandatory statements


class QuizInitialized(BaseXapiStatement):
"""Pydantic model for quiz initialized statement.

Example:

Attributes:
verb (dict): See InitializedVerb.
object (dict): See CMIInteractionActivity.
context (dict): See QuizContext.
timestamp (datetime): Consists of the timestamp of when the event occurred.
"""

__selector__ = selector(
verb__id="http://adlnet.gov/expapi/verbs/initialized",
object__definition__type=(
"http://adlnet.gov/expapi/activities/cmi.interaction"
),
)

verb: InitializedVerb = InitializedVerb()
object: CMIInteractionActivity
context: QuizContext
timestamp: datetime


class QuizLaunched(BaseXapiStatement):
"""Pydantic model for quiz launched statement.

Example:

Attributes:
verb (dict): See LaunchedVerb.
object (dict): See CMIInteractionActivity.
context (dict): See QuizContext.
timestamp (datetime): Consists of the timestamp of when the event occurred.
"""

__selector__ = selector(
verb__id="http://adlnet.gov/expapi/verbs/launched",
object__definition__type=(
"http://adlnet.gov/expapi/activities/cmi.interaction"
),
)

verb: LaunchedVerb = LaunchedVerb()
object: CMIInteractionActivity
context: QuizContext
timestamp: datetime


class QuizPassed(BaseXapiStatement):
"""Pydantic model for quiz passed statement.

Example:

Attributes:
verb (dict): See PassedVerb.
object (dict): See CMIInteractionActivity.
context (dict): See QuizContext.
timestamp (datetime): Consists of the timestamp of when the event occurred.
"""

__selector__ = selector(
verb__id="http://adlnet.gov/expapi/verbs/passed",
object__definition__type=(
"http://adlnet.gov/expapi/activities/cmi.interaction"
),
)

verb: PassedVerb = PassedVerb()
object: CMIInteractionActivity
context: QuizContext
timestamp: datetime


class QuizFailed(BaseXapiStatement):
"""Pydantic model for quiz failed statement.

Example:

Attributes:
verb (dict): See FailedVerb.
object (dict): See CMIInteractionActivity.
context (dict): See QuizContext.
timestamp (datetime): Consists of the timestamp of when the event occurred.
"""

__selector__ = selector(
verb__id="http://adlnet.gov/expapi/verbs/passed",
object__definition__type=(
"http://adlnet.gov/expapi/activities/cmi.interaction"
),
)

verb: FailedVerb = FailedVerb()
object: CMIInteractionActivity
context: QuizContext
timestamp: datetime


class QuizCompleted(BaseXapiStatement):
"""Pydantic model for quiz completed statement.

Example:

Attributes:
verb (dict): See CompletedVerb.
result (dict): See CMIInteractionActivity.
context (dict): See .
"""

__selector__ = selector(
object__definition__type="http://adlnet.gov/expapi/activities/cmi.interaction",
verb__id="http://adlnet.gov/expapi/verbs/completed",
)

verb: CompletedVerb = CompletedVerb()
result: CMIInteractionActivity
context: QuizContext
timestamp: datetime


class QuizTerminated(BaseXapiStatement):
"""Pydantic model for quiz terminated statement.

Example:

Attributes:
verb (dict): See CompletedVerb.
result (dict): See CMIInteractionActivity.
context (dict): See .
"""

__selector__ = selector(
object__definition__type="http://adlnet.gov/expapi/activities/cmi.interaction",
verb__id="http://adlnet.gov/expapi/verbs/terminated",
)

verb: TerminatedVerb = TerminatedVerb()
result: CMIInteractionActivity
context: QuizContext
timestamp: datetime
26 changes: 26 additions & 0 deletions src/ralph/models/xapi/video/fields/adl_vocabulary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""`ADL Vocabulary` activity types definitions."""

from ...base.unnested_objects import BaseXapiActivity, BaseXapiActivityDefinition
from ..constants.adl_vocabulary import ACTIVITY_ID_QUESTION


# Question
class QuestionActivityDefinition(BaseXapiActivityDefinition):
"""Pydantic model for question `Activity` type `definition` property.

Attributes:
type (str): Consists of the value
`http://adlnet.gov/expapi/activities/question`.
"""

type: ACTIVITY_ID_QUESTION = ACTIVITY_ID_QUESTION.__args__[0]


class QuestionActivity(BaseXapiActivity):
"""Pydantic model for question `Activity` type.

Attributes:
definition (dict): see QuestionActivityDefinition.
"""

definition: QuestionActivityDefinition = QuestionActivityDefinition()
Loading