7from statistics
import median
23CHALLENGE_CLASSWORK = 2
24CHALLENGE_CLASSPARTICIPATION = 3
30CHALLENGE_NEEDS_ANALYSIS = 0x01
31CHALLENGE_USE_STUDYLIST = 0x02
34CHALLENGE_DIDNT_TRY = 0
36CHALLENGE_COMPLETED = 2
40 """In the Pafera Learning System, we handle homework and classwork
41 by issuing challenges to students. Points are then calculated based
42 upon how the students did, and grades are automatically assigned by
45 There are a variety of challenges available, so check challenges.js
46 to see what they are
and test them to see what you can do
with them.
48 Problems
for challenges can be assigned by adding lessons, individual
49 problems,
or can be added straight
from their study list.
51 Upon a challenge ending, the next time that you visit /learn/home.html,
52 student scores will be automatically calculated
and applied to their
53 grades. You can then see such statistics
as the average score, what
54 percentage of students passed, which problems were often answered
55 incorrectly,
and such
in the analysis section.
59 'rid': (
'INTEGER',
'PRIMARY KEY NOT NULL',),
61 'starttime': (
'DATETIME',
'NOT NULL',),
62 'endtime': (
'DATETIME',
'NOT NULL',),
65 'instructions': (
'TRANSLATION',
'NOT NULL',),
66 'question': (
'TRANSLATION',
'NOT NULL',),
67 'image': (
'IMAGEFILE',
'NOT NULL',),
68 'sound': (
'SOUNDFILE',
'NOT NULL',),
69 'video': (
'VIDEOFILE',
'NOT NULL',),
70 'files': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
71 'lessontitles': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
72 'lessonids': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
73 'problemids': (
'NEWLINELIST',
"NOT NULL DEFAULT ''",),
74 'results': (
'DICT',
"NOT NULL DEFAULT ''",),
75 'analysis': (
'DICT',
"NOT NULL DEFAULT ''",),
76 'averagescore': (
'INT16',
'NOT NULL DEFAULT 0',),
77 'averageright': (
'INT16',
'NOT NULL DEFAULT 0',),
78 'averagepercent': (
'INT16',
'NOT NULL DEFAULT 0',),
79 'percenttried': (
'INT16',
'NOT NULL DEFAULT 0',),
80 'percentcomplete': (
'INT16',
'NOT NULL DEFAULT 0',),
81 'minscore': (
'INT16',
'NOT NULL DEFAULT 0',),
82 'minpercentage': (
'INT16',
'NOT NULL DEFAULT 0',),
84 'didnttrypenalty': (
'INT16',
'NOT NULL DEFAULT 0', ),
86 'numproblems': (
'INT16',
'NOT NULL DEFAULT 0', ),
87 'timelimit': (
'INT16',
'NOT NULL DEFAULT 0',),
88 'challengetype': (
'INT16',
'NOT NULL DEFAULT 0',),
89 'flags': (
'INT16',
'NOT NULL DEFAULT 0',),
111 """Returns the number of remaining tries available to the current
114 As a side effect, all previous results will be available in
115 obj.previousresults
as learn_challengeresult objects.
117 if not hasattr(self,
'previousresults'):
119 apps.learn.challengeresult.learn_challengeresult,
120 'WHERE challengeid = ? AND userid = ?',
125 fields =
'rid, problemresults, score, percentright',
133 """Calculates all scores and saves statistics for this challenge.
135 Typically, this will be done automatically for you after endtime
144 cls = g.db.Load(apps.learn.schoolclass.learn_schoolclass, self.classid)
146 currenttime = time.time()
159 apps.learn.answer.learn_answer,
160 'WHERE classid = ? AND NOT (flags & ?)',
163 apps.learn.answer.ANSWER_TEACHER
165 fields =
'rid, userid, displayname, numright, numwrong, bonusscore, score'
168 answers[r.userid] = r
173 for k, v
in answers.items():
174 o = v.ToJSON(
'displayname, numright, numwrong, bonusscore, score')
176 if v.numright + v.numwrong:
177 o[
'percentage'] = int(v.numright / (v.numright + v.numwrong) * 100)
185 if time.time() > self.endtime.timestamp():
186 self.
Set(flags = self.flags & (~CHALLENGE_NEEDS_ANALYSIS))
190 for k, v
in answers.items():
191 cls.UpdateGrades(g, k)
197 for r
in g.db.Linked(cls, apps.learn.student.learn_student, fields =
'userid, displayname'):
200 studentnames[r.userid] = r.displayname
202 if studentid
not in self.
results:
203 self.
results[studentid] = [r.displayname, CHALLENGE_DIDNT_TRY, 0, 0, -self.didnttrypenalty]
207 apps.learn.challengeresult.learn_challengeresult,
208 'WHERE challengeid = ?',
214 if r.userid
not in studentnames:
215 studentnames[r.userid] =
'[Removed Student ' + studentid +
']'
216 self.
results[studentid] = [studentnames[r.userid], CHALLENGE_DIDNT_TRY, r.score, r.percentright, -self.didnttrypenalty]
218 if r.flags & apps.learn.challengeresult.CHALLENGERESULT_PASSED:
219 self.
results[studentid] = [studentnames[r.userid], CHALLENGE_COMPLETED, r.score, r.percentright, self.numpoints]
220 elif self.
results[studentid][1] == CHALLENGE_DIDNT_TRY
or self.
results[studentid][2] > r.score:
221 self.
results[studentid] = [studentnames[r.userid], CHALLENGE_FAILED, r.score, r.percentright, 0]
224 for p
in r.problemresults:
226 if p[
'status'] ==
'wrong':
227 if p[
'id']
not in wrongproblems:
228 problemtext =
'[Text not found]'
231 problemtext = g.db.Execute(f
'''
234 JOIN learn_card ON learn_problem.problemid = learn_card.rid
235 WHERE learn_problem.rid = ?
239 except Exception
as e:
240 print(
'>>> Problem getting problem text', e)
242 wrongproblems[ p[
'id'] ] = {
243 'problemtext': problemtext,
247 wrongproblems[ p[
'id'] ][
'studentids'].append(
ToShortCode(r.userid))
250 if self.
challengetype == CHALLENGE_CLASSWORK
and self.didnttrypenalty
and self.endtime.timestamp() < currenttime:
251 for k, v
in self.
results.items():
252 if v[1] == CHALLENGE_DIDNT_TRY:
255 if userid
not in answers:
256 if userid
not in studentnames:
257 studentnames[userid] = {
258 'en': f
'[Removed Student {k}]'
261 newanswer = apps.learn.answer.learn_answer()
263 classid = self.classid,
265 displayname = studentnames[userid],
268 newanswer.AddHomeworkScores(g)
270 g.db.Insert(newanswer)
273 answers[userid] = newanswer
275 answerobj = answers[userid]
278 bonusscore = answerobj.bonusscore - self.didnttrypenalty,
281 answerobj.UpdateScore()
283 g.db.Update(answerobj)
288 'wrongproblems': wrongproblems
299 for v
in self.
results.values():
300 if v[1] == CHALLENGE_DIDNT_TRY:
305 averagescore.append(v[2])
306 averagepercent.append(v[3])
308 if v[1] == CHALLENGE_COMPLETED:
311 totalids = numdidnttry + numfailed + numcompleted
313 percenttried = numfailed / totalids * 100
314 percentcomplete = numcompleted / totalids * 100
317 percenttried = percenttried,
318 percentcomplete = percentcomplete,
319 averagescore = median(averagescore)
if averagescore
else 0,
320 averagepercent = median(averagepercent)
if averagepercent
else 0,
325 if currenttime > self.endtime.timestamp():
326 self.
Set(flags = self.flags & (~CHALLENGE_NEEDS_ANALYSIS))
332 """Returns a list of all currently running challenges for the logged in user.
334 Useful for seeing what this student has finished
and what they still
339 currenttime = time.time()
343 'WHERE classid = ? AND starttime < ? AND endtime > ? AND challengetype = ?',
350 fields =
'rid, challenge, title, endtime, numpoints, numtries, flags',
356 apps.learn.learn_challengeresult,
357 'WHERE challengeid = ? AND userid = ?',
358 [r.rid, g.session.userid],
359 fields =
'rid, flags'
363 if s.flags & apps.learn.challengeresult.CHALLENGERESULT_PASSED:
367 c = r.ToJSON(
'rid, challenge, title, endtime, numpoints, numtries, flags')
369 c[
'triesleft'] =
Bound(r.numtries - len(results), 0, 9999)
370 c[
'completed'] = completed
371 c[
'timeremaining'] = int(r.endtime.timestamp() - currenttime)
In the Pafera Learning System, we handle homework and classwork by issuing challenges to students.
def __init__(self)
Initialize all fields at creation like a good programmer should.
def GetNumRemainingAttempts(self, g)
Returns the number of remaining tries available to the current user.
def Analyze(self, g)
Calculates all scores and saves statistics for this challenge.
Base class for all database models.
def Set(self, **kwargs)
We use this method instead of direct attribute access in order to keep track of what values have been...
Throws an exception on blank values.
def GetCurrentChallenges(g, classid, challengetype=CHALLENGE_HOMEWORK)
Returns a list of all currently running challenges for the logged in user.
def Bound(val, min, max)
Returns a value that is no smaller than min or larger than max.
def FromShortCode(code, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a six character alphanumeric code into a 32-bit value.
def ToShortCode(val, chars='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_')
Turns a 32-bit value into a six character alphanumeric code.