From ab408999d1f084420bef5725d08d910d8dff0291 Mon Sep 17 00:00:00 2001 From: shallifa <stuart.hallifax@univ-lyon3.fr> Date: Wed, 17 Apr 2019 17:16:10 +0200 Subject: [PATCH] Renamed Action to Observation. Added timesince last update to Observations. Added GameElementUpdate Operation. --- LogReader.py | 2 +- Traces/{Action.py => Observation.py} | 66 +++++++++++++++------------- Traces/Operation.py | 20 +++++++-- Traces/OperationTransformer.py | 29 +++++++----- User/User.py | 16 +++---- 5 files changed, 78 insertions(+), 55 deletions(-) rename Traces/{Action.py => Observation.py} (79%) diff --git a/LogReader.py b/LogReader.py index 2d20f7d..db07dec 100644 --- a/LogReader.py +++ b/LogReader.py @@ -65,7 +65,7 @@ if __name__ == "__main__" : transformedTimeline = transformOperation(todayTimeline) - selectedUser.actionsPerformed = transformedTimeline + selectedUser.observations = transformedTimeline for action in transformedTimeline: print(action) diff --git a/Traces/Action.py b/Traces/Observation.py similarity index 79% rename from Traces/Action.py rename to Traces/Observation.py index 81d1092..d80f147 100644 --- a/Traces/Action.py +++ b/Traces/Observation.py @@ -8,11 +8,11 @@ GameElement = NewType("GameElement", str) #TODO: Renommer les classes en "Observation" - changer le nom du fichier (refactor) #TODO : Créer une classe remonter depuis un quiz avec un attribut qui donne jusqu'ou on remonte (section, course). Pour les Observations Visiter la page X pendant un quiz -#TODO : Ajouter l'attribut date since last game element update -class Action: - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement): + +class Observation: + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, previousUpdate: datetime): """ :param start: A datetime at the start of the Action @@ -25,9 +25,13 @@ class Action: self.user = user self.gameElement = gameElement self.actionName = "Action" + self.previousUpdate = previousUpdate def __str__(self): - return str(self.actionName) + ";" + str(self.user.name) + ";" + str(self.gameElement) + ";" + str(self.start) + ";" + str(self.end) + ";" + if self.previousUpdate is None: + return str(self.actionName) + ";" + str(self.user.name) + ";" + str(self.gameElement) + ";" + str(self.start) + ";" + str(self.end) + "; ;" + else : + return str(self.actionName) + ";" + str(self.user.name) + ";" + str(self.gameElement) + ";" + str(self.start) + ";" + str(self.end) + ";" + str(self.start-self.previousUpdate) + ";" def __lt__(self, other): @@ -38,8 +42,8 @@ class Action: else: return self.end < other.end -class Pause(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, op1: Operation, op2: Operation): +class Pause(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, op1: Operation, op2: Operation, previousUpdate: datetime): """ :param start: A datetime at the start of the Action @@ -49,7 +53,7 @@ class Pause(Action): :param op1: The first operation :param op2: The second operation """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.op1 = op1 self.op2 = op2 self.actionName = "Pause" @@ -64,8 +68,8 @@ class Pause(Action): """ return self.end - self.start -class AttemptQuiz(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int): +class AttemptQuiz(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int, previousUpdate: datetime): """ :param start: A datetime at the start of the Action @@ -75,7 +79,7 @@ class AttemptQuiz(Action): :param quizId: The Id of the attempted quiz :param attemptNumber: The attempt number """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.quizId = quizId self.attemptNumber = attemptNumber self.actionName = "AttemptQuiz" @@ -83,8 +87,8 @@ class AttemptQuiz(Action): def __str__(self): return super().__str__() + "QuizId:"+ self.quizId + ";" + "Attempt:" + str(self.attemptNumber) -class ResumeQuizAttempt(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int): +class ResumeQuizAttempt(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int, previousUpdate: datetime): """ :param start: A datetime at the start of the Action @@ -94,7 +98,7 @@ class ResumeQuizAttempt(Action): :param quizId: The Id of the attempted quiz :param attemptNumber: The attempt number """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.quizId = quizId self.attemptNumber = attemptNumber self.actionName = "ResumeQuizAttempt" @@ -102,8 +106,8 @@ class ResumeQuizAttempt(Action): def __str__(self): return super().__str__() + "QuizId:"+ self.quizId + ";" + "Attempt:" + str(self.attemptNumber) -class CompleteQuestion(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, success: bool = True): +class CompleteQuestion(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, previousUpdate: datetime, success: bool = True): """ :param start: A datetime at the start of the Action @@ -112,15 +116,15 @@ class CompleteQuestion(Action): :param gameElement: The game element that the user had when the performed the Action :param success: Whether the question was answered correctly or not """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.success = success self.actionName = "CompleteQuestion" def __str__(self): return super().__str__() + "Success:" + str(self.success) -class CompleteQuiz(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int, interruption: bool = False, success: bool = True): +class CompleteQuiz(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int, previousUpdate: datetime, interruption: bool = False, success: bool = True): """ @@ -133,7 +137,7 @@ class CompleteQuiz(Action): :param interruption: Indicate whether the quiz was interrupted or not :param success: Indicate whether the student validated the quiz or not """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.quizId = quizId self.attemptNumber = attemptNumber self.interruption = interruption @@ -144,8 +148,8 @@ class CompleteQuiz(Action): def __str__(self): return super().__str__() + "QuizId:"+ self.quizId + ";" + "Attempt:" + str(self.attemptNumber) + ";Interruption:" + str(self.interruption) + ";Success:" + str(self.success) -class RestartQuiz(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int, afterSuccess: bool = False, +class RestartQuiz(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int, previousUpdate: datetime, afterSuccess: bool = False, inCurrentLesson: bool = True, interruptCurrentLesson: bool = False): """ @@ -160,7 +164,7 @@ class RestartQuiz(Action): :param inCurrentLesson: Indicate whether the quiz was restarted during a later lesson :param interruptCurrentLesson: Indicate whether the quiz was restarted before the current lesson was completed """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.quizId = quizId self.attemptNumber = attemptNumber self.afterSuccess = afterSuccess @@ -171,9 +175,9 @@ class RestartQuiz(Action): def __str__(self): return super().__str__() + "QuizId:"+ self.quizId + ";" + "Attempt:" + str(self.attemptNumber) + ";AfterSuccess:" + str(self.afterSuccess) + ";InCurrentLesson:" + str(self.inCurrentLesson) + ";InterruptCurrentLesson:" + str(self.interruptCurrentLesson) -class AbandonQuiz(Action): +class AbandonQuiz(Observation): def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, - attemptNumber: int, numberOfQuestions: int, correctQuestions: int): + attemptNumber: int, numberOfQuestions: int, correctQuestions: int, previousUpdate: datetime): """ :param start: A datetime at the start of the Action @@ -185,7 +189,7 @@ class AbandonQuiz(Action): :param numberOfQuestions: The number of attempted questions :param correctQuestions: The number of correct questions """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.quizId = quizId self.attemptNumber = attemptNumber self.numberOfQuestions = numberOfQuestions @@ -195,8 +199,8 @@ class AbandonQuiz(Action): def __str__(self): return super().__str__() + "QuizId:" + self.quizId + ";Attempt:" + str(self.attemptNumber) + ";NumberOfQuestions:" + str(self.numberOfQuestions) + ";CorrectionQuestions" + str(self.correctQuestions) -class StartLesson(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, lessonId: str): +class StartLesson(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, lessonId: str, previousUpdate: datetime): """ :param start: A datetime at the start of the Action @@ -205,15 +209,15 @@ class StartLesson(Action): :param gameElement: The game element that the user had when the performed the Action :param lessonId: The Id of the lesson started """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.lessonId = lessonId self.actionName = "StartLesson" def __str__(self): return super().__str__() + "LessonId:"+ self.lessonId -class CompleteLesson(Action): - def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, lessonId: str, success: bool = True): +class CompleteLesson(Observation): + def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, lessonId: str, previousUpdate: datetime, success: bool = True): """ :param start: A datetime at the start of the Action @@ -223,7 +227,7 @@ class CompleteLesson(Action): :param lessonId: The Id of the lesson started :param success: Has the lesson been completed fully or not """ - super().__init__(start, end, user, gameElement) + super().__init__(start, end, user, gameElement, previousUpdate) self.lessonId = lessonId self.success = success self.actionName = "CompleteLesson" diff --git a/Traces/Operation.py b/Traces/Operation.py index 9029191..8868178 100644 --- a/Traces/Operation.py +++ b/Traces/Operation.py @@ -1,5 +1,5 @@ from datetime import datetime - +from User import User def buildOperation(logRow, user): #Row structure @@ -45,7 +45,8 @@ def buildOperation(logRow, user): return QuizResults(timestamp, user, gameElement, parameters[0], parameters[1]) #Game element related trace - #TODO : Ajouter Operation _update (Mise a jour de l'élément ludique) + elif "_update" in op: + return GameElementUpdate(timestamp, user, gameElement) #TODO: Les Operations Visiter Calendrier, Page de prefs, page de profil @@ -182,4 +183,17 @@ class QuizResults(Operation): self.quizResults = quizResults def __str__(self): - return "Quiz results :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber + self.quizResults \ No newline at end of file + return "Quiz results :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber + self.quizResults + +class GameElementUpdate(Operation): + def __init__(self, timestamp: datetime, user: User, gameElement: str): + """ + + :param timestamp: + :param user: + :param gameElement: + """ + super().__init__(timestamp, user, gameElement) + + def __str__(self): + return "Game element update : " + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') \ No newline at end of file diff --git a/Traces/OperationTransformer.py b/Traces/OperationTransformer.py index 413d8de..1c00bf0 100644 --- a/Traces/OperationTransformer.py +++ b/Traces/OperationTransformer.py @@ -1,9 +1,9 @@ from Traces.Operation import * -from Traces.Action import * +from Traces.Observation import * from datetime import timedelta # STATIC VALUES -pauseDelta = timedelta(seconds=5) +pauseDelta = timedelta(minutes=2) passRate = 0.7 #The % of correct answers to consider that a quiz is successful def transformOperation(timeline): @@ -20,7 +20,7 @@ def transformOperation(timeline): correctQuestions = 0.0 previousQuizzes = {} success = '' - + lastUpdate = None timestamps = sorted(timeline.keys()) i = 0 @@ -33,9 +33,14 @@ def transformOperation(timeline): # Get current operation op : Operation = timeline[timestamps[i]] + if type(op) is GameElementUpdate: + lastUpdate = op.timestamp + i = i+1 + continue + if not inQuiz: if type(op) in [QuizPageview]: - transformedTimeline.append(ResumeQuizAttempt(op.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber)) + transformedTimeline.append(ResumeQuizAttempt(op.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, lastUpdate)) quizStart = op inQuiz = True interruptedQuiz = True @@ -59,8 +64,8 @@ def transformOperation(timeline): interruptedQuiz = True if (type(op) is QuizPageview) and (op.quizId != quizStart.quizId): - transformedTimeline.append(AbandonQuiz(op.timestamp, op.timestamp, op.user, op.gameElement, quizStart.quizId, quizStart.attemptNumber, numberOfQuestions, correctQuestions)) - transformedTimeline.append(ResumeQuizAttempt(op.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber)) + transformedTimeline.append(AbandonQuiz(op.timestamp, op.timestamp, op.user, op.gameElement, quizStart.quizId, quizStart.attemptNumber, numberOfQuestions, correctQuestions, lastUpdate)) + transformedTimeline.append(ResumeQuizAttempt(op.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, lastUpdate)) quizStart = op inQuiz = True interruptedQuiz = True @@ -82,7 +87,7 @@ def transformOperation(timeline): # Pause check if timestamps[i] - timestamps[i-1] >= pauseDelta: - transformedTimeline.append(Pause(timestamps[i-1], timestamps[i], op.user, op.gameElement, previousOp, op)) + transformedTimeline.append(Pause(timestamps[i-1], timestamps[i], op.user, op.gameElement, previousOp, op, lastUpdate)) i+=1 continue @@ -90,12 +95,12 @@ def transformOperation(timeline): if (type(op) is QuizAttemptStarted) and (type(previousOp) is QuizModuleview): #if op.quizId == previousOp.quizId: #This check doesn't currently work as ids don't match ! if inQuiz: - transformedTimeline.append(AbandonQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, quizStart.quizId, quizStart.attemptNumber, numberOfQuestions, correctQuestions)) + transformedTimeline.append(AbandonQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, quizStart.quizId, quizStart.attemptNumber, numberOfQuestions, correctQuestions, lastUpdate)) if op.quizId not in previousQuizzes.keys(): - transformedTimeline.append(AttemptQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber)) + transformedTimeline.append(AttemptQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, lastUpdate)) else: - transformedTimeline.append(RestartQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, previousQuizzes[op.quizId])) + transformedTimeline.append(RestartQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, lastUpdate, previousQuizzes[op.quizId], lastUpdate)) quizStart = op inQuiz = True @@ -113,7 +118,7 @@ def transformOperation(timeline): # Complete question check if (type(op) in [QuestionGradedRight, QuestionGradedWrong]) and (type(previousOp) is QuizPageview): if op.quizId == previousOp.quizId: - transformedTimeline.append(CompleteQuestion(previousOp.timestamp, op.timestamp, op.user, op.gameElement, type(op) is QuestionGradedRight)) + transformedTimeline.append(CompleteQuestion(previousOp.timestamp, op.timestamp, op.user, op.gameElement, lastUpdate, type(op) is QuestionGradedRight)) i+=1 numberOfQuestions += 1 @@ -129,7 +134,7 @@ def transformOperation(timeline): if success != "Unsure" : success = correctQuestions/numberOfQuestions > passRate - transformedTimeline.append(CompleteQuiz(quizStart.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, interruptedQuiz, success)) + transformedTimeline.append(CompleteQuiz(quizStart.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, lastUpdate, interruptedQuiz, success)) inQuiz = False interruptedQuiz = False diff --git a/User/User.py b/User/User.py index 342835b..2aca829 100644 --- a/User/User.py +++ b/User/User.py @@ -1,5 +1,5 @@ import User.UserProfileData as upd -from Traces.Action import * +from Traces.Observation import * class User: def __init__(self, name, gameElement, userProfileData = upd.UserProfileData(0,0,0,0)): @@ -7,7 +7,7 @@ class User: self.gameElements = [gameElement] self.currentGameElement = gameElement self.operationsPerformed = {} - self.actionsPerformed = {} + self.observations = {} self.userProfile = userProfileData def addGameElement(self, gameElement): @@ -39,17 +39,17 @@ class User: def getNumberOfPauses(self): i = 0 - for timestamp in self.actionsPerformed : - if type(self.actionsPerformed[timestamp]) is Pause: + for timestamp in self.observations : + if type(self.observations[timestamp]) is Pause: i+=1 return i - def exportActions(self): - output = "Action;User;GameElement;Debut;Fin;Infos\n" + def exportObservations(self): + output = "Action;User;GameElement;Debut;Fin;Time since last update;Infos\n" with open("transformedTrace.csv", 'w') as file: - for action in self.actionsPerformed: - output += str(action) + "\n" + for observation in self.observations: + output += str(observation) + "\n" file.write(output) -- GitLab