From 96c2a6c60776913e7efcd1d36b4038848f712d72 Mon Sep 17 00:00:00 2001
From: Stuart Hallifax <stuart.hallifax@univ-lyon3.fr>
Date: Mon, 18 Mar 2019 13:40:27 +0100
Subject: [PATCH] Added code base

---
 Context/ContextData.py         |   3 +
 LogReader.py                   |  71 +++++++++++
 ReadFileHandler.py             |  17 +++
 Traces/Action.py               | 227 +++++++++++++++++++++++++++++++++
 Traces/Operation.py            | 177 +++++++++++++++++++++++++
 Traces/OperationTransformer.py | 142 +++++++++++++++++++++
 User/User.py                   |  58 +++++++++
 User/UserProfileData.py        |  51 ++++++++
 ludimoodle_trace.log           | 125 ++++++++++++++++++
 9 files changed, 871 insertions(+)
 create mode 100644 Context/ContextData.py
 create mode 100644 LogReader.py
 create mode 100644 ReadFileHandler.py
 create mode 100644 Traces/Action.py
 create mode 100644 Traces/Operation.py
 create mode 100644 Traces/OperationTransformer.py
 create mode 100644 User/User.py
 create mode 100644 User/UserProfileData.py
 create mode 100644 ludimoodle_trace.log

diff --git a/Context/ContextData.py b/Context/ContextData.py
new file mode 100644
index 0000000..5112edc
--- /dev/null
+++ b/Context/ContextData.py
@@ -0,0 +1,3 @@
+class ContextData:
+    def __init__(self):
+        print('Init for context data not yet implemented')
\ No newline at end of file
diff --git a/LogReader.py b/LogReader.py
new file mode 100644
index 0000000..2d20f7d
--- /dev/null
+++ b/LogReader.py
@@ -0,0 +1,71 @@
+import csv
+import pickle
+import os
+import time
+
+from User import User
+from Traces.Operation import *
+from Traces.OperationTransformer import transformOperation
+from datetime import datetime
+
+if __name__ == "__main__" :
+
+    maxFileAge = 600 # 600 seconds, 10 minutes
+
+    needToCreateNewFile = True # not os.path.isfile('./users.pickle')
+
+    if not needToCreateNewFile:
+        print("Backup found")
+        if time.time()-os.stat("./users.pickle").st_mtime < maxFileAge:
+            print("Loading from backup")
+            with open('users.pickle', 'rb') as backupfile:
+                users = pickle.load(backupfile)
+        else:
+            print("Backup too old")
+            needToCreateNewFile = True
+
+    if needToCreateNewFile:
+        print("Creating new file")
+        unknowns = 0
+        users = {}
+        with open("ludimoodle_trace.log", 'r') as file:
+            csvReader = csv.reader(file, delimiter=",")# delimiter = ";"
+
+            for row in csvReader:
+                tmp = User.User(row[1].strip(), row[2])
+
+                if tmp.name not in users:
+                    users[tmp.name] = tmp
+                else:
+                    users[tmp.name].addGameElement(row[2])
+
+                user = users[tmp.name]
+
+                op = buildOperation(row, user)
+
+                if type(op) != Operation:
+                    user.addOperation(op)
+
+                # if type(op) == Operation:
+                #     op.addOpType(row[3])
+                #     unknowns += 1
+
+        with open('users.pickle', 'wb') as backupfile:
+            pickle.dump(users, backupfile, protocol=pickle.HIGHEST_PROTOCOL)
+
+    print("Created new user timeline")
+
+
+    selectedUser : User = users["score.stuart@univ-lyon3.fr"]
+
+    todayTimeline = selectedUser.operationsPerformed
+
+    #for timestamp in sorted(todayTimeline.keys()):
+    #    print(todayTimeline[timestamp])
+
+    transformedTimeline = transformOperation(todayTimeline)
+
+    selectedUser.actionsPerformed = transformedTimeline
+
+    for action in transformedTimeline:
+        print(action)
diff --git a/ReadFileHandler.py b/ReadFileHandler.py
new file mode 100644
index 0000000..dc6bf73
--- /dev/null
+++ b/ReadFileHandler.py
@@ -0,0 +1,17 @@
+from watchdog.events import FileSystemEventHandler
+import os
+
+class FileReaderHandler(FileSystemEventHandler):
+
+    currentLine = 0
+
+    def on_modified(self, event):
+        print('File has been modified')
+        print(event.src_path)
+
+        with open(event.src_path, 'rb') as f:
+            f.seek(-2, os.SEEK_END)
+            while f.read(1) != b'\n':
+                f.seek(-2, os.SEEK_CUR)
+            print(f.readline().decode())
+
diff --git a/Traces/Action.py b/Traces/Action.py
new file mode 100644
index 0000000..0f32925
--- /dev/null
+++ b/Traces/Action.py
@@ -0,0 +1,227 @@
+from datetime import datetime
+from datetime import timedelta
+from User import User
+from typing import NewType
+from Traces import Operation
+
+GameElement = NewType("GameElement", str)
+
+class Action:
+    def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        """
+        self.start = start
+        self.end = end
+        self.user = user
+        self.gameElement = gameElement
+        self.actionName = "Action"
+
+    def __str__(self):
+        return str(self.actionName) + ";" + str(self.user.name) + ";" + str(self.gameElement) + ";" + str(self.start) + ";" + str(self.end) + ";"
+
+
+    def __lt__(self, other):
+        if self.start < other.start:
+            return True
+        elif self.start > other.start:
+            return False
+        else:
+            return self.end < other.end
+
+class Pause(Action):
+    def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, op1: Operation, op2: Operation):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        :param op1: The first operation
+        :param op2: The second operation
+        """
+        super().__init__(start, end, user, gameElement)
+        self.op1 = op1
+        self.op2 = op2
+        self.actionName = "Pause"
+
+    def __str__(self):
+        return super().__str__() + "Duration:" + str(self.getPauseDuration())+ ";" + str(self.op1) + ";" + str(self.op2)
+
+    def getPauseDuration(self) -> timedelta:
+        """
+        Get the duration of the Pause Action
+        :return: The timedelta between the start and the end
+        """
+        return self.end - self.start
+
+class AttemptQuiz(Action):
+    def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str, attemptNumber: int):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        :param quizId: The Id of the attempted quiz
+        :param attemptNumber: The attempt number
+        """
+        super().__init__(start, end, user, gameElement)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+        self.actionName = "AttemptQuiz"
+
+    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):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        :param quizId: The Id of the attempted quiz
+        :param attemptNumber: The attempt number
+        """
+        super().__init__(start, end, user, gameElement)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+        self.actionName = "ResumeQuizAttempt"
+
+    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):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the 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)
+        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):
+
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        :param quizId: The Id of the attempted quiz
+        :param attemptNumber: The attempt number
+        :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)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+        self.interruption = interruption
+        self.success = success
+        self.actionName = "CompleteQuiz"
+
+
+    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,
+                 inCurrentLesson: bool = True,
+                 interruptCurrentLesson: bool = False):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        :param quizId: The Id of the attempted quiz
+        :param attemptNumber: The attempt number
+        :param afterSuccess: Indicate whether the quiz was restarted after a successful quiz or not
+        :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)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+        self.afterSuccess = afterSuccess
+        self.inCurrentLesson = inCurrentLesson
+        self.interruptCurrentLesson = interruptCurrentLesson
+        self.actionName = "RestartQuiz"
+
+    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):
+    def __init__(self, start: datetime, end: datetime, user: User, gameElement: GameElement, quizId: str,
+                 attemptNumber: int, numberOfQuestions: int, correctQuestions: int):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the Action
+        :param quizId: The Id of the attempted quiz
+        :param attemptNumber: The attempt number
+        :param numberOfQuestions: The number of attempted questions
+        :param correctQuestions: The number of correct questions
+        """
+        super().__init__(start, end, user, gameElement)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+        self.numberOfQuestions = numberOfQuestions
+        self.correctQuestions = correctQuestions
+        self.actionName = "AbandonQuiz"
+
+    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):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the 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)
+        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):
+        """
+
+        :param start: A datetime at the start of the Action
+        :param end:  A date time at the end of the Action
+        :param user: The user that performed the Action
+        :param gameElement: The game element that the user had when the performed the 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)
+        self.lessonId = lessonId
+        self.success = success
+        self.actionName = "CompleteLesson"
+
+    def __str__(self):
+        return super().__str__() +  "LessonId:"+ self.lessonId + ";Success:" + str(self.success)
\ No newline at end of file
diff --git a/Traces/Operation.py b/Traces/Operation.py
new file mode 100644
index 0000000..4b48ea0
--- /dev/null
+++ b/Traces/Operation.py
@@ -0,0 +1,177 @@
+from datetime import datetime
+
+
+def buildOperation(logRow, user):
+    #Row structure
+    timestamp = logRow[0].strip()
+    gameElement = logRow[2].strip()
+    op = logRow[3].strip()
+
+    if len(logRow) > 4:
+        parameters = logRow[4:]
+
+    #General operations
+    if op == "login":
+        return Login(timestamp, user, gameElement)
+    elif op == "course_pageview" :
+        return CoursePageview(timestamp, user, gameElement, parameters[0])
+    elif op == "feature_change" : #Not yet implemented
+        return FeatureChange(timestamp, user, gameElement)
+    elif op == "dashboard" :
+        return Dashboard(timestamp, user, gameElement)
+
+    #Quiz related operations
+    elif op == "quiz_moduleview":
+        return QuizModuleview(timestamp, user, gameElement, parameters[1])
+    elif op == "quiz_attempt_started" :
+        return QuizAttemptStarted(timestamp, user, gameElement, parameters[0], parameters[1], parameters[2])
+    elif op == "quiz_attempt_finished" :
+        return QuizAttemptFinished(timestamp, user, gameElement, parameters[0], parameters[1], parameters[2])
+    #elif op == "quiz_start":
+    #    return QuizAttemptStarted(timestamp, user, gameElement, parameters[0], 1)
+    elif op == "quiz_pageview":
+        return QuizPageview(timestamp, user, gameElement, parameters[1], parameters[2], parameters[3])
+    elif op == "question_gradedright":
+        return QuestionGradedRight(timestamp, user, gameElement, parameters[3], parameters[0], parameters[2])
+    elif op == "question_gradedwrong":
+        return QuestionGradedWrong(timestamp, user, gameElement, parameters[3], parameters[0], parameters[2])
+    elif op == "quiz_review":
+        return QuizReview(timestamp, user, gameElement, parameters[0], parameters[1])
+    elif op == "quiz_submit":
+        return QuizSubmit(timestamp, user, gameElement, parameters[0], parameters[1])
+    elif op == "quiz_results": #Not yet active
+        return QuizResults(timestamp, user, gameElement, parameters[0], parameters[1])
+    else:
+        return Operation(timestamp, user, gameElement)
+
+class Operation:
+    def __init__(self, timestamp, user, gameElement):
+        self.timestamp = datetime.utcfromtimestamp(int(timestamp))
+        self.user = user
+        self.gameElement = gameElement
+
+    def print(self):
+        return "Simple Operation"
+
+    def addOpType(self, opType):
+        self.opType = opType
+
+    def __str__(self):
+        return "Unknown operation :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + " " + self.opType
+
+
+class Login(Operation):
+    def __init__(self, timestamp, user, gameElement):
+        super().__init__(timestamp, user, gameElement)
+
+    def __str__(self):
+        return "Login :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
+
+class Dashboard(Operation):
+    def __init__(self, timestamp, user, gameElement):
+        super().__init__(timestamp, user, gameElement)
+
+    def __str__(self):
+        return "Dashboard :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
+
+class CoursePageview(Operation):
+    def __init__(self, timestamp, user, gameElement, page):
+        super().__init__(timestamp, user, gameElement)
+        self.page = page
+
+    def __str__(self):
+        return "Pageview :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + " page : " + self.page
+
+class FeatureChange(Operation):
+    def __init__(self , timestamp, user, gameElement):
+        super().__init__(timestamp, user, gameElement)
+
+    def __str__(self):
+        return "FeatureChange :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
+
+class QuizModuleview(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId.strip()
+
+    def __str__(self):
+        return "Quiz module view :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId
+
+class QuizAttemptStarted(Operation):
+    def __init__(self, timestamp, user, gameElement, attemptNumber, course, quizId):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId.strip()
+        self.attemptNumber = attemptNumber
+        self.course = course
+
+    def __str__(self):
+        return "Quiz attempt started :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + str(self.attemptNumber)
+
+class QuizAttemptFinished(Operation):
+    def __init__(self, timestamp, user, gameElement, attemptNumber, course, quizId):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId.strip()
+        self.attemptNumber = attemptNumber
+        self.course = course
+
+    def __str__(self):
+        return "Quiz attempt finished:" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + str(self.attemptNumber)
+
+
+class QuizPageview(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId, attemptNumber, pageNumber):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId.strip()
+        self.attemptNumber = attemptNumber
+        self.pageNumber = pageNumber
+
+    def __str__(self):
+        return "Quiz page view :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber + self.pageNumber
+
+class QuestionGradedRight(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId, attemptNumber, pageNumber):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId.strip()
+        self.attemptNumber = attemptNumber.strip()
+        self.pageNumber = pageNumber.strip()
+
+    def __str__(self):
+        return "Question graded right :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber + self.pageNumber
+
+class QuestionGradedWrong(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId, attemptNumber, pageNumber):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId.strip()
+        self.attemptNumber = attemptNumber.strip()
+        self.pageNumber = pageNumber.strip()
+
+    def __str__(self):
+        return "Question graded right :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber + self.pageNumber
+
+class QuizReview(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId, attemptNumber):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+
+    def __str__(self):
+        return "Quiz review :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber
+
+class QuizSubmit(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId, attemptNumber):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+
+    def __str__(self):
+        return "Quiz submit :" + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + self.quizId + self.attemptNumber
+
+class QuizResults(Operation):
+    def __init__(self, timestamp, user, gameElement, quizId, attemptNumber, quizResults):
+        super().__init__(timestamp, user, gameElement)
+        self.quizId = quizId
+        self.attemptNumber = attemptNumber
+        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
diff --git a/Traces/OperationTransformer.py b/Traces/OperationTransformer.py
new file mode 100644
index 0000000..48fd0f0
--- /dev/null
+++ b/Traces/OperationTransformer.py
@@ -0,0 +1,142 @@
+from Traces.Operation import *
+from Traces.Action import *
+from datetime import timedelta
+
+# STATIC VALUES
+pauseDelta = timedelta(minutes=5)
+passRate = 0.7 #The % of correct answers to consider that a quiz is successful
+
+def transformOperation(timeline):
+
+    # The transformed timeline that contains the start timestamp and corresponding Action
+    transformedTimeline = []
+
+    # VARIABLES USED
+    attemptNumber = ""
+    quizStart = ''
+    inQuiz = False
+    interruptedQuiz = False
+    numberOfQuestions = 0.0
+    correctQuestions = 0.0
+    previousQuizzes = {}
+    success = ''
+
+
+    timestamps = sorted(timeline.keys())
+    i = 0
+
+
+
+    while i< len(timestamps):
+        # Get current operation
+        op : Operation = timeline[timestamps[i]]
+
+        if not inQuiz:
+            if type(op) in [QuizPageview]:
+                transformedTimeline.append(ResumeQuizAttempt(op.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber))
+                quizStart = op
+                inQuiz = True
+                interruptedQuiz = True
+
+                try:
+                    numberOfQuestions = previousQuizzes[quizStart.quizId][1]
+                    correctQuestions = previousQuizzes[quizStart.quizId][2]
+                except KeyError:
+                    print("No previous info about this past quiz")
+                    numberOfQuestions = 0.0
+                    correctQuestions = 0.0
+                    success = "Unsure"
+
+                    previousQuizzes[quizStart.quizId] = [False, numberOfQuestions, correctQuestions]
+
+        if i != 0:
+            previousOp = timeline[timestamps[i-1]]
+
+            if inQuiz:
+                if type(op) not in [QuizPageview, QuestionGradedRight, QuestionGradedWrong, QuizAttemptFinished]:
+                    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))
+                    quizStart = op
+                    inQuiz = True
+                    interruptedQuiz = True
+
+                    try:
+                        numberOfQuestions = previousQuizzes[quizStart.quizId][1]
+                        correctQuestions = previousQuizzes[quizStart.quizId][2]
+                    except KeyError:
+                        print("No previous info about this past quiz")
+                        numberOfQuestions = 0.0
+                        correctQuestions = 0.0
+                        success = "Unsure"
+
+                        previousQuizzes[quizStart.quizId] = [False, numberOfQuestions, correctQuestions]
+
+                    i+=1
+                    continue
+
+
+            # Pause check
+            if timestamps[i] - timestamps[i-1] >= pauseDelta:
+                transformedTimeline.append(Pause(timestamps[i-1], timestamps[i], op.user, op.gameElement, previousOp, op))
+                i+=1
+                continue
+
+            # Attempt quiz check
+            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))
+
+                if op.quizId not in previousQuizzes.keys():
+                    transformedTimeline.append(AttemptQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber))
+                else:
+                    transformedTimeline.append(RestartQuiz(previousOp.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, previousQuizzes[op.quizId]))
+
+                quizStart = op
+                inQuiz = True
+                interruptedQuiz = False
+                numberOfQuestions = 0.0
+                correctQuestions = 0.0
+                success = False
+
+                previousQuizzes[quizStart.quizId] = [False, numberOfQuestions, correctQuestions]
+
+
+                i+=1
+                continue
+
+            # 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))
+                i+=1
+                numberOfQuestions += 1
+
+                if type(op) is QuestionGradedRight:
+                    correctQuestions += 1
+
+                previousQuizzes[quizStart.quizId][1] = numberOfQuestions
+                previousQuizzes[quizStart.quizId][2] = correctQuestions
+                continue
+
+            #Complete quiz check
+            if type(op) is QuizAttemptFinished:
+                if success != "Unsure" :
+                    success = correctQuestions/numberOfQuestions > passRate
+
+                transformedTimeline.append(CompleteQuiz(quizStart.timestamp, op.timestamp, op.user, op.gameElement, op.quizId, op.attemptNumber, interruptedQuiz, success))
+                inQuiz = False
+                interruptedQuiz = False
+
+                previousQuizzes[op.quizId] = [success, numberOfQuestions, correctQuestions]
+
+                numberOfQuestions = 0.0
+                correctQuestions = 0.0
+
+        i+=1
+
+
+    return transformedTimeline
\ No newline at end of file
diff --git a/User/User.py b/User/User.py
new file mode 100644
index 0000000..342835b
--- /dev/null
+++ b/User/User.py
@@ -0,0 +1,58 @@
+import User.UserProfileData as upd
+from Traces.Action import *
+
+class User:
+    def __init__(self, name, gameElement, userProfileData = upd.UserProfileData(0,0,0,0)):
+        self.name = name
+        self.gameElements = [gameElement]
+        self.currentGameElement = gameElement
+        self.operationsPerformed = {}
+        self.actionsPerformed = {}
+        self.userProfile = userProfileData
+
+    def addGameElement(self, gameElement):
+        if gameElement not in self.gameElements:
+            self.gameElements.append(gameElement)
+
+        self.currentGameElement = gameElement
+
+    def numberOfGameElements(self):
+        return len(self.gameElements)
+
+    def addOperation(self, operation):
+        self.operationsPerformed[operation.timestamp] = operation
+
+    def printTimeline(self):
+        for timestamp in sorted(self.operationsPerformed.keys()):
+            print(self.operationsPerformed[timestamp])
+
+    def getTimeline(self):
+        return self.operationsPerformed
+
+    def getTimelineFromDate(self, date):
+        tmp = {}
+        for timestamp in self.operationsPerformed:
+           if timestamp.date() == date.date():
+               tmp[timestamp] = self.operationsPerformed[timestamp]
+
+        return tmp
+
+    def getNumberOfPauses(self):
+        i = 0
+        for timestamp in self.actionsPerformed :
+            if type(self.actionsPerformed[timestamp]) is Pause:
+                i+=1
+        return i
+
+    def exportActions(self):
+        output = "Action;User;GameElement;Debut;Fin;Infos\n"
+
+        with open("transformedTrace.csv", 'w') as file:
+            for action in self.actionsPerformed:
+                output += str(action) + "\n"
+
+            file.write(output)
+
+
+    def __eq__(self, other):
+        return self.name == other.name
\ No newline at end of file
diff --git a/User/UserProfileData.py b/User/UserProfileData.py
new file mode 100644
index 0000000..3ace757
--- /dev/null
+++ b/User/UserProfileData.py
@@ -0,0 +1,51 @@
+class HexadScores:
+    @classmethod
+    def fromQuestionnaire(cls, questionnaireScoreList):
+        print("from questionnaireScores not yet implemented")
+        return HexadScores(0,0,0,0,0,0)
+
+    def __init__(self, achieverScore, playerScore, socialiserScore, freeSpiritScore, disruptorScore, philanthropistScore):
+        self.achiever = achieverScore
+        self.player = playerScore
+        self.socialiser = socialiserScore
+        self.freeSpirit = freeSpiritScore
+        self.disruptor = disruptorScore
+        self.philanthropist = philanthropistScore
+
+
+class MotivationScores:
+    @classmethod
+    def fromQuestionnaire(cls, questionnaireScoreList):
+        print("method not yet implemented")
+        return MotivationScores()
+
+    # Types de motivation
+    # Intrinseque :
+    #     MICO : a la connaissance
+    #     MIAC : a l'accomplissement
+    #     MIST : a la simulation
+    # Extrinseque :
+    #     MEID : a la regulation identifiee
+    #     MEIN : a la regulation introjectee
+    #     MERE : a la regulation externe
+    # Amotivation :
+    #     AMOT : amotivation
+
+    def __init__(self, mico, miac, mist, meid, mein, mere, amot):
+        self.mico = mico
+        self.miac = miac
+        self.mist = mist
+        self.meid = meid
+        self.mein = mein
+        self.mere = mere
+        self.amot = amot
+
+    def variation(self, motivationScores):
+        return MotivationScores(motivationScores.mico - self.mico, motivationScores.miac - self.miac, motivationScores.mist - self.mist, motivationScores.meid - self.meid, motivationScores.mein - self.mein, motivationScores.mere - self.mere, motivationScores.amot - self.amot)
+
+class UserProfileData:
+    def __init__(self, hexadScores, age, gender, initialMotivationScores):
+        self.hexadScores = hexadScores
+        self.age = age
+        self.gender = gender
+        self.initialMotivationScores = initialMotivationScores
\ No newline at end of file
diff --git a/ludimoodle_trace.log b/ludimoodle_trace.log
new file mode 100644
index 0000000..55875ff
--- /dev/null
+++ b/ludimoodle_trace.log
@@ -0,0 +1,125 @@
+1551973816, score.stuart@univ-lyon3.fr , score , login , username : score.stuart@univ-lyon3.fr,,,,,,
+1551973817, score.stuart@univ-lyon3.fr , score , dashboard , ,,,,,,
+1551973819, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019,,,,,,
+1551973821, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 2,,,,,
+1551973824, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 138,,,,,
+1551973826, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973830, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973830, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 1 , quiz : Exercice 1.1 : QCM , section : 2 , sequence : 1 , state : gradedright
+1551973831, score.stuart@univ-lyon3.fr , score , score_update , course : 206 , property : points , section :  , value : 1000,,,
+1551973834, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973838, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 2 , quiz : Exercice 1.1 : QCM , section : 2 , sequence : 1 , state : gradedright
+1551973839, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973839, score.stuart@univ-lyon3.fr , score , score_update , course : 206 , property : points , section :  , value : 2000,,,
+1551973843, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973847, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973847, score.stuart@univ-lyon3.fr , score , score_update , course : 206 , property : points , section :  , value : 3000,,,
+1551973847, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 3 , quiz : Exercice 1.1 : QCM , section : 2 , sequence : 1 , state : gradedright
+1551973850, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973855, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973855, score.stuart@univ-lyon3.fr , score , score_update , course : 206 , property : points , section :  , value : 4000,,,
+1551973855, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 4 , quiz : Exercice 1.1 : QCM , section : 2 , sequence : 1 , state : gradedright
+1551973859, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973862, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973862, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 5 , quiz : Exercice 1.1 : QCM , section : 2 , sequence : 1 , state : gradedright
+1551973863, score.stuart@univ-lyon3.fr , score , score_update , course : 206 , property : points , section :  , value : 5000,,,
+1551973866, score.stuart@univ-lyon3.fr , score , quiz_summaryview , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973868, score.stuart@univ-lyon3.fr , score , quiz_review , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973868, score.stuart@univ-lyon3.fr , score , quiz_submit , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , quiz_attempts : 1263 , section : 2,,,
+1551973868, score.stuart@univ-lyon3.fr , score , quiz_attempt_finished , attempt : 1 , course : calcul-litteral-2019 , quiz : Exercice 1.1 : QCM , section : 2,,,
+1551973874, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 143,,,,,
+1551973876, score.stuart@univ-lyon3.fr , score , quiz_start , course : calcul-litteral-2019 , quiz_attempts : 1267,,,,,
+1551973876, score.stuart@univ-lyon3.fr , score , quiz_attempt_started , attempt : 1 , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , section : 2,,,
+1551973877, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973882, score.stuart@univ-lyon3.fr , score , question_gradedwrong , attempt : 1 , course : calcul-litteral-2019 , question : 1 , quiz : Exercice 1.2 : QCM , section : 2 , sequence : 1 , state : gradedwrong
+1551973883, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973886, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973890, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973890, score.stuart@univ-lyon3.fr , score , question_gradedwrong , attempt : 1 , course : calcul-litteral-2019 , question : 2 , quiz : Exercice 1.2 : QCM , section : 2 , sequence : 1 , state : gradedwrong
+1551973893, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973898, score.stuart@univ-lyon3.fr , score , question_gradedwrong , attempt : 1 , course : calcul-litteral-2019 , question : 3 , quiz : Exercice 1.2 : QCM , section : 2 , sequence : 1 , state : gradedwrong
+1551973899, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973902, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973906, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973906, score.stuart@univ-lyon3.fr , score , question_gradedwrong , attempt : 1 , course : calcul-litteral-2019 , question : 4 , quiz : Exercice 1.2 : QCM , section : 2 , sequence : 1 , state : gradedwrong
+1551973908, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973919, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973919, score.stuart@univ-lyon3.fr , score , score_update , course : 211 , property : points , section :  , value : 1000,,,
+1551973919, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 5 , quiz : Exercice 1.2 : QCM , section : 2 , sequence : 1 , state : gradedright
+1551973922, score.stuart@univ-lyon3.fr , score , quiz_summaryview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973924, score.stuart@univ-lyon3.fr , score , quiz_review , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973924, score.stuart@univ-lyon3.fr , score , quiz_submit , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1267 , section : 2,,,
+1551973924, score.stuart@univ-lyon3.fr , score , quiz_attempt_finished , attempt : 1 , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , section : 2,,,
+1551973929, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019,,,,,,
+1551973935, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 2,,,,,
+1551973935, score.stuart@univ-lyon3.fr , score , score_update , course : calcul-litteral-2019 , property : points , section : 2 , value : 11000,,,
+1552399610, score.stuart@univ-lyon3.fr , score , login , username : score.stuart@univ-lyon3.fr,,,,,,
+1552399611, score.stuart@univ-lyon3.fr , score , dashboard , ,,,,,,
+1552399613, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019,,,,,,
+1552399613, score.stuart@univ-lyon3.fr , score , score_update , course :  , property : points , section :  , value : 22000,,,
+1552399616, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 2,,,,,
+1552399619, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 143,,,,,
+1552399626, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 2,,,,,
+1552399628, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 138,,,,,
+1552399632, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019,,,,,,
+1552399634, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 3,,,,,
+1552399636, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 146,,,,,
+1552399638, score.stuart@univ-lyon3.fr , score , quiz_start , course : calcul-litteral-2019 , quiz_attempts : 1271,,,,,
+1552399638, score.stuart@univ-lyon3.fr , score , quiz_attempt_started , attempt : 1 , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , section : 3,,,
+1552399639, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399644, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 1 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399645, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399645, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 1000,,,
+1552399648, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399654, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399654, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 2000,,,
+1552399654, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 2 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399657, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399668, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399668, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 3000,,,
+1552399668, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 3 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399676, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399680, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019,,,,,,
+1552399682, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 2,,,,,
+1552399684, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 143,,,,,
+1552399686, score.stuart@univ-lyon3.fr , score , quiz_start , course : calcul-litteral-2019 , quiz_attempts : 1272,,,,,
+1552399686, score.stuart@univ-lyon3.fr , score , quiz_attempt_started , attempt : 2 , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , section : 2,,,
+1552399686, score.stuart@univ-lyon3.fr , score , quiz_attempt_unfinished , attempt : 2 , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , section : 2,,,
+1552399687, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 1.2 : QCM , quiz_attempts : 1272 , section : 2,,,
+1552399689, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019,,,,,,
+1552399691, score.stuart@univ-lyon3.fr , score , course_pageview , course : calcul-litteral-2019 , coursesectionnumber : 3,,,,,
+1552399691, score.stuart@univ-lyon3.fr , score , score_update , course : calcul-litteral-2019 , property : points , section : 3 , value : 4000,,,
+1552399693, score.stuart@univ-lyon3.fr , score , quiz_moduleview , course : calcul-litteral-2019 , quiz : 146,,,,,
+1552399696, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399701, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 4 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399702, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399702, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 4000,,,
+1552399705, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399710, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399710, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 5000,,,
+1552399710, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 5 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399714, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399719, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399719, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 6000,,,
+1552399719, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 6 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399723, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399729, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 7 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399730, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399730, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 7000,,,
+1552399733, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399738, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 8 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399739, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399739, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 8000,,,
+1552399742, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399748, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 9 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399749, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399749, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 9000,,,
+1552399752, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399760, score.stuart@univ-lyon3.fr , score , quiz_pageview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399760, score.stuart@univ-lyon3.fr , score , score_update , course : 214 , property : points , section :  , value : 10000,,,
+1552399760, score.stuart@univ-lyon3.fr , score , question_gradedright , attempt : 1 , course : calcul-litteral-2019 , question : 10 , quiz : Exercice 2.1 : simplification de produit , section : 3 ,,
+1552399765, score.stuart@univ-lyon3.fr , score , quiz_summaryview , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399767, score.stuart@univ-lyon3.fr , score , quiz_submit , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399767, score.stuart@univ-lyon3.fr , score , quiz_attempt_finished , attempt : 1 , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , section : 3,,,
+1552399768, score.stuart@univ-lyon3.fr , score , quiz_review , course : calcul-litteral-2019 , quiz : Exercice 2.1 : simplification de produit , quiz_attempts : 1271 , section : 3,,,
+1552399771, score.stuart@univ-lyon3.fr , score , dashboard , ,,,,,,
-- 
GitLab