Skip to content

Commit c9df237

Browse files
authored
Merge pull request #58 from SoryRawyer/rds/add-grading-events
feat: add grade_calculated events (FC-0033)
2 parents e0d1ac3 + 33c3fbe commit c9df237

File tree

4 files changed

+134
-6
lines changed

4 files changed

+134
-6
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ docs/_build/
99
.tox/
1010
coverage.xml
1111
dist/
12+
.venv
13+
.dir-locals.el

setup.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
"""
22
Setup for xapi-db-load.
33
"""
4-
import os
5-
import re
6-
74
from setuptools import find_packages, setup
85

96
from xapi_db_load import __version__

xapi_db_load/generate_load.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from random import choice, choices, randrange
88

99
from .course_configs import CourseConfigLarge, CourseConfigMedium, CourseConfigSmall, CourseConfigWhopper
10-
from .xapi.xapi_grade import FirstTimePassed
10+
from .xapi.xapi_grade import CourseGradeCalculated, FirstTimePassed
1111
from .xapi.xapi_hint_answer import ShowAnswer, ShowHint
1212
from .xapi.xapi_navigation import LinkClicked, NextNavigation, PreviousNavigation, TabSelectedNavigation
1313
from .xapi.xapi_problem import BrowserProblemCheck, ServerProblemCheck
@@ -34,8 +34,8 @@
3434
(PlayedVideo, 24.519),
3535
(PausedVideo, 14.912),
3636
(StoppedVideo, 3.671),
37-
(PositionChangedVideo, 13.105),
38-
(BrowserProblemCheck, 8.726),
37+
(PositionChangedVideo, 12.105),
38+
(BrowserProblemCheck, 8.226),
3939
(ServerProblemCheck, 8.593),
4040
(NextNavigation, 6.05),
4141
(PreviousNavigation, 0.811),
@@ -46,6 +46,7 @@
4646
(ShowAnswer, 1.373),
4747
(TranscriptEnabled, 0.05),
4848
(TranscriptDisabled, 0.05),
49+
(CourseGradeCalculated, 1.5),
4950
)
5051

5152
EVENTS = [i[0] for i in EVENT_LOAD]

xapi_db_load/xapi/xapi_grade.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Fake xAPI statements for various grading events.
33
"""
44
import json
5+
import random
56
from uuid import uuid4
67

78
from .xapi_common import XAPIBase
@@ -66,3 +67,130 @@ def get_randomized_event(self, event_id, account, course, create_time):
6667
}
6768

6869
return json.dumps(event)
70+
71+
72+
class GradeCalculated(XAPIBase):
73+
"""
74+
Base xAPI event for grade_calculated events.
75+
"""
76+
77+
verb = "http://id.tincanapi.com/verb/earned"
78+
verb_display = "earned"
79+
object_type = None
80+
81+
def get_data(self):
82+
"""
83+
Generate and return the event dict, including xAPI statement as "event".
84+
"""
85+
event_id = str(uuid4())
86+
actor_id = self.parent_load_generator.get_actor()
87+
course = self.parent_load_generator.get_course()
88+
emission_time = course.get_random_emission_time()
89+
90+
e = self.get_randomized_event(event_id, actor_id, course, emission_time)
91+
return {
92+
"event_id": event_id,
93+
"verb": self.verb,
94+
"actor_id": actor_id,
95+
"org": course.org,
96+
"course_run_id": course.course_url,
97+
"emission_time": emission_time,
98+
"event": e,
99+
}
100+
101+
def get_randomized_event(self, event_id, actor_id, course, emission_time):
102+
"""
103+
Given the inputs, return an xAPI statement for a grade_calculated event.
104+
"""
105+
max_score = random.randint(1, 100)
106+
raw_score = random.randint(0, max_score)
107+
scaled_score = raw_score / max_score
108+
score_obj = {
109+
"scaled": scaled_score,
110+
"raw": raw_score,
111+
"min": 0.0,
112+
"max": max_score
113+
}
114+
115+
event = {
116+
"actor": {
117+
"account": {
118+
"homePage": "http://localhost:18000",
119+
"name": actor_id
120+
},
121+
"objectType": "Agent"
122+
},
123+
"id": event_id,
124+
"verb": {
125+
"id": self.verb,
126+
"display": {
127+
"en": self.verb_display
128+
}
129+
},
130+
"context": {
131+
"contextActivities": {
132+
"parent": [
133+
{
134+
"id": course.course_url,
135+
"objectType": "Activity",
136+
"definition": {
137+
"name": {
138+
"en-US": "Demonstration Course"
139+
},
140+
"type": "http://adlnet.gov/expapi/activities/course"
141+
},
142+
}
143+
]
144+
},
145+
"extensions": {
146+
"https://w3id.org/xapi/openedx/extension/transformer-version": "[email protected]"
147+
},
148+
},
149+
"version": "1.0.3",
150+
"timestamp": emission_time.isoformat(),
151+
}
152+
153+
if self.object_type == "course":
154+
grade_classification = "Pass" if scaled_score > 0.65 else "Fail"
155+
course_fields = {
156+
"object": {
157+
"id": course.course_url,
158+
"definition": {
159+
"name": {"en": "Demonstration Course"},
160+
"type": "http://adlnet.gov/expapi/activities/course",
161+
},
162+
"objectType": "Activity",
163+
},
164+
"result": {
165+
"score": score_obj,
166+
"extensions": {
167+
"http://www.tincanapi.co.uk/activitytypes/grade_classification": grade_classification
168+
}
169+
}
170+
}
171+
event.update(course_fields)
172+
elif self.object_type == "subsection":
173+
subsection_fields = {
174+
"object": {
175+
"id": course.get_random_sequential_id(),
176+
"definition": {
177+
"type": "http://id.tincanapi.com/activitytype/resource"
178+
},
179+
"objectType": "Activity"
180+
},
181+
"result": {
182+
"score": score_obj,
183+
"success": random.choice([True, False]),
184+
}
185+
}
186+
event.update(subsection_fields)
187+
188+
return json.dumps(event)
189+
190+
191+
class CourseGradeCalculated(GradeCalculated):
192+
object_type = "course"
193+
194+
195+
class SubsectionGradeCalculated(GradeCalculated):
196+
object_type = "subsection"

0 commit comments

Comments
 (0)