def readLineWithSpecificContents(f, specificContents, nbLinesRead, isEOF_OK): """ Reads file f waiting to read string specificContents. Reads file f until it finds a non-empty line. If this non-empty line is specificContents (differences in number of spaces are accepted), it returns it. Otherwise, it displays an error message mentioning filename and nbLinesRead. If it finds EOF during its search If isEOF_OK is True, just returns "". Otherwise displays an error message and stops. Parameters ---------- f : file File descriptor to read specificContents : str String which should be read. nbLinesRead : list containing one int Number of lines read until now in the file. This number is incremented during execution isEOF_OK : bool If true, if EOF is detected, returns False. Otherwise, generates an error. Returns ------- str String containing specificContents or "" if EOF """ line = lookForNonBlankLine(f, nbLinesRead, isEOF_OK, specificContents) if line == "": if isEOF_OK: return "" sys.exit( """ERREUR: Dans le fichier "{}", à la ligne {}, le programme s'attendait à la ligne "{}" et est arrivé à la fin du fichier.""" .format(f.name, nbLinesRead[0], specificContents)) if line.replace(" ", "") != specificContents.replace(" ", ""): sys.exit( """ERREUR: Dans le fichier "{}", à la ligne {}, le programme s'attendait à la ligne "{}" et a lu la ligne "{}".""" .format(f.name, nbLinesRead[0], specificContents, line)) return line
def __init__(self, filename): """Opens file named filename and reads all configuration data""" self.confData = {} # We read all of the configuration data (without taking care of the type) f = openWithErrorManagement(filename, "r", encoding="utf8") nbLinesRead = [0] confKeys = floatConfKeys + intConfKeys + strConfKeys line = lookForNonBlankLine( f, nbLinesRead, True, """Une ligne de la forme "Cle = Valeur" """) while (line != ""): pos = line.find("=") if pos < 0: sys.exit( """ERREUR: Dans le fichier "{}", à la ligne {}, cette ligne contient "{}" qui ne respecte pas le format "Clé = Valeur" """ .format(f.name, nbLinesRead[0], line)) key = line[:pos].strip(" \t\n") value = line[pos + 1:].strip(" \t\n") if key not in confKeys: sys.exit( """ERREUR: Dans le fichier "{}", à la ligne {}, cette ligne contient "{}" qui définit la clé "{}" qui est inconnue (problème d'orthographe ?)""" .format(f.name, nbLinesRead[0], line, key)) self.confData[key] = value line = lookForNonBlankLine( f, nbLinesRead, True, """Une ligne de la forme "Cle = Valeur" """) f.close() # We check that we found all keys in file for key in confKeys: try: self.confData[key] except KeyError: sys.exit( """ERREUR: Il manque la clé "{}" dans le fichier de configuration "{}".""" .format(key, filename)) # We take care of float data for key in floatConfKeys: try: result = float(self.confData[key]) except ValueError: sys.exit( """ERREUR: Dans le fichier "{}", la clé "{}" a pour valeur "{}" qui n'est pas un flottant.""" .format(f.name, key, confData[key])) self.confData[key] = result # We take care of int data for key in intConfKeys: try: result = int(self.confData[key]) except ValueError: sys.exit( """ERREUR: Dans le fichier "{}", la clé "{}" a pour valeur "{}" qui n'est pas un entier.""" .format(f.name, key, confData[key])) self.confData[key] = result # Initialize opinionType2CommentBound self.opinionType2CommentBound = ( self.confData["negativeCommentBound"], "Erreur (Valeur non utilisée dans self.opinionType2CommentBound)", self.confData["positiveCommentBound"]) # If necessary, we add a '/' at the end of self.confData["rootDirectory"] rootDir = self.confData["rootDirectory"] if rootDir != "": if rootDir[len(rootDir) - 1] != '/': self.confData["rootDirectory"] += '/'
def main(): """Main entry point of the application""" # # Call parameter analysis # if len(sys.argv) != 3: print("ERREUR: Pas assez de parametres") usage() return if sys.argv[1][0] != '-': print( """ERREUR: Le premier parametre devrait etre "-1" ou "-2" (sans les guillemets)""" ) usage() return if len(sys.argv[1]) == 1 or sys.argv[1][1] not in "13": print( """ERREUR: Le premier parametre devrait etre "-1" ou "-3" (sans les guillemets)""" ) usage() return # # We display cos version before starting the processing # print() print("cos version 2.0.2") print() # # Read all configuration data # conf = Conf(sys.argv[2]) # # Read the list of defenses # defenses = [] f = openWithErrorManagement(key2inputFileName("defensesFilename", conf), "r", encoding=conf.get("encoding")) nbLinesRead = [0] name = lookForNonBlankLine(f, nbLinesRead, True, "Nom soutenance") while name != "": defenses.append(Defense(name)) name = lookForNonBlankLine(f, nbLinesRead, True, "Nom soutenance") f.close() # # Read the list of students and the title of their defense # students = [] f = openWithErrorManagement(key2inputFileName("studentsFilename", conf), "r", encoding=conf.get("encoding")) nbLinesRead = [0] lookForNonBlankLine( f, nbLinesRead, True, "Nom soutenance") # We ignore the line giving the title of the columns studentLine = lookForNonBlankLine(f, nbLinesRead, True, "Nom soutenance") while studentLine != "": info = splitCsvLine(studentLine, conf.get("csvSeparator")) # We look for info in the list of defense names found = False for defense in defenses: if info[1] == defense.name: # OK, this defense name is known students.append(Student(info[0], defense, defenses)) found = True break if not found: sys.exit( """ERREUR: Dans le fichier "{}", la ligne {} ("{}") fait référence à une soutenance intitulée "{}" qui n'apparaît pas dans le fichier "{}".""" .format(conf.get("studentsFilename"), nbLinesRead[0], studentLine, info[1], conf.get("defensesFilename"))) studentLine = lookForNonBlankLine(f, nbLinesRead, True, "Nom soutenance") f.close() # # Read the list of criteria types # criteriaTypes = [] f = openWithErrorManagement(key2inputFileName("criteriaTypesFilename", conf), "r", encoding=conf.get("encoding")) nbLinesRead = [0] criteriaType = lookForNonBlankLine(f, nbLinesRead, True, "Type de critère") while criteriaType != "": criteriaTypes.append(criteriaType) criteriaType = lookForNonBlankLine(f, nbLinesRead, True, "Type de critère") f.close() # # Read the list of criterias # criterias = [] f = openWithErrorManagement(key2inputFileName("criteriasFilename", conf), "r", encoding=conf.get("encoding")) nbLinesRead = [0] lookForNonBlankLine( f, nbLinesRead, True, "Nom critère") # We ignore the line giving the title of the columns criteriaLine = lookForNonBlankLine(f, nbLinesRead, True, "Nom critère") while criteriaLine != "": info = splitCsvLine(criteriaLine, conf.get("csvSeparator")) # We look for info[0] in the list of criteria types found = False for criteriaType in criteriaTypes: if info[0] == criteriaType: found = True break if not found: sys.exit( """ERREUR: Dans fichier "{}", la ligne {} ("{}") fait référence à un type de critère intitulée "{}" qui n'apparaît pas dans le fichier "{}".""" .format(f.name, nbLinesRead[0], criteriaLine, info[0], conf.get("criteriaTypesFilename"))) # OK, this citeriaType is known try: floatValue = float(info[2]) except ValueError: sys.exit( """ERREUR: Dans fichier "{}", la ligne {} ("{}") a son 3ème champ ("{}") qui n'est pas un flottant.""" .format(f.name, nbLinesRead[0], criteriaLine, info[2])) criterias.append( Criteria(info[0], info[1], floatValue, conf.get("ratioCriteriaKO"), conf.get("ratioCriteriaOK"))) criteriaLine = lookForNonBlankLine(f, nbLinesRead, True, "Nom critère") f.close() # # Prepare dateTime string which may be used for names of output files # date = datetime.datetime.now() s = str(date) dateTime = s[:s.find('.')] # # Remaining work depends on what the user asks for # if sys.argv[1][1] == '1': generateModels(conf, defenses, students, criteriaTypes, criterias, dateTime) else: analyzeTeacherData(conf, defenses, criteriaTypes, criterias) analyzeStudentsData(conf, defenses, students, criteriaTypes, criterias) generateResults(conf, defenses, students, criteriaTypes, criterias, dateTime) # # We display an end of execution message # if sys.argv[1][1] == '1': print( """OK, exécution de la phase {} terminée : les fichiers "{}", "{}" et "{}" ont été générés.""" .format( sys.argv[1][1], key2ouputFileName("nominativeSheetsFilename", conf, dateTime), key2ouputFileName("genericSheetFilename", conf, dateTime), key2ouputFileName("genericTeacherMarksFilename", conf, dateTime))) else: print( """OK, exécution de la phase {} terminée : les fichiers "{}", "{}" et "{}" ont été générés.""" .format( sys.argv[1][1], key2ouputFileName("synthesisCommentsFilename", conf, dateTime), key2ouputFileName("evaluationCommentsFilename", conf, dateTime), key2ouputFileName("studentsMarksSheetFilename", conf, dateTime))) print() return
def analyzeStudentsData(conf, defenses, students, criteriaTypes, criterias): """ Enrich students information with the contents of conf.get("filledNominativeSheetsFilename") Parameters ---------- conf : Conf Configuration information f : file File on which to write defenses : list of Defense List of defenses students : liste of Student List of students criteriaTypes : liste of str List of criteria types criterias : liste of Criteria List of criterias Returns ------- void """ # # Analyze the contents of conf.get("filledNominativeSheetsFilename"), # f = openWithErrorManagement(key2inputFileName( "filledNominativeSheetsFilename", conf), "r", encoding=conf.get("encoding")) nbLinesRead = [0] while readLineWithSpecificContents(f, conf.get("studentBound"), nbLinesRead, True) != "": # Determine student index in students studentLine = lookForNonBlankLine(f, nbLinesRead, False, "Ligne contenant un nom d'etudiant") studentLine = studentLine[:studentLine.rfind( "(" )] # To suppress the name of the defense which is between parenthesis at the end of the line studentIndex = findName(studentLine, students, nbLinesRead, f, conf.get("studentsFilename")) if students[studentIndex].alreadyProcessed: sys.exit( """ERREUR: Dans le fichier "{}", derriere la ligne {}, l'etudiant ("{}") apparaît pour la deuxième fois.""" .format(conf.get("filledNominativeSheetsFilename"), nbLinesRead[0], studentLine)) students[studentIndex].alreadyProcessed = True # Skip delimiter of student name readLineWithSpecificContents(f, conf.get("studentBound"), nbLinesRead, False) # Analyze answers for the different defenses evaluated by the student # NB: There is one less defense evaluated by the student, as he does not # evalkuate his own defense. for unusedIndex in list(range(len(defenses) - 1)): # Skip delimiter of defense name readLineWithSpecificContents(f, conf.get("defenseBound"), nbLinesRead, False) # Determine defense index in defenses defenseLine = lookForNonBlankLine( f, nbLinesRead, False, "Ligne contenant un nom de projet") defenseIndex = findName(defenseLine, defenses, nbLinesRead, f, conf.get("defensesFilename")) if defenses[defenseIndex] == students[studentIndex].defense: sys.exit( """ERREUR: Dans le fichier "{}", derriere la ligne {}, l'étudiant "{}"a un commentaire de son propre projet "{}" : tentative de triche ?.""" .format(conf.get("filledNominativeSheetsFilename"), nbLinesRead[0], students[studentIndex].name, defenses[defenseIndex].name)) # Skip delimiter of defense name readLineWithSpecificContents(f, conf.get("defenseBound"), nbLinesRead, False) # For each criteria type for criteriaType in criteriaTypes: # Skip criteria type readLineWithSpecificContents(f, criteriaType, nbLinesRead, False) # Handle each critera in this criteria type for criteria in criterias: if criteria.criteriaType == criteriaType: criteriaLine = lookForNonBlankLine( f, nbLinesRead, False, "Ligne contenant un critère") if criteriaLine[0] in "+-": # We skip '+' or '-' sign which is at the beginning of the line criteriaIndex = findName( criteriaLine[1:].strip(" \t"), criterias, nbLinesRead, f, conf.get("criteriasFilename")) elif criteriaLine[-1] in "+-": # We skip '+' or '-' sign which is at the end of the line criteriaIndex = findName( criteriaLine[:-1].strip(" \t"), criterias, nbLinesRead, f, conf.get("criteriasFilename")) else: criteriaIndex = findName( criteriaLine, criterias, nbLinesRead, f, conf.get("criteriasFilename")) if criteriaLine[0] == '+' or criteriaLine[-1] == '+': students[studentIndex].opinionsPerDefense[ defenseIndex][ POSITIVE_OPINION].criteriaIndex = criteriaIndex students[studentIndex].opinionsPerDefense[ defenseIndex][ POSITIVE_OPINION].nbCriteriaIndex += 1 elif criteriaLine[0] == '-' or criteriaLine[-1] == '-': students[studentIndex].opinionsPerDefense[ defenseIndex][ NEGATIVE_OPINION].criteriaIndex = criteriaIndex students[studentIndex].opinionsPerDefense[ defenseIndex][ NEGATIVE_OPINION].nbCriteriaIndex += 1 # We now take care of opinion comments for opinionType in list_opinions: # Skip line introducing comment readLineWithSpecificContents(f, conf.getCommentBound(opinionType), nbLinesRead, False) # Take care of comment s = f.readline() nbLinesRead[0] += 1 if s == "": sys.exit( """ERREUR: Dans le fichier "{}", derriere la ligne {}, il devrait y avoir un commentaire {} et non la fin du fichier.""" .format(conf.get("filledNominativeSheetsFilename"), nbLinesRead[0], opinionType2str[opinionType])) s = removeComment(s) s = s.strip(" \t\n") if s == "": # It may happen that a student fills up the second line of the comment instead of the first line. # If the first line is empty, we read the second line in case. s = f.readline() nbLinesRead[0] += 1 if s == "": sys.exit( """ERREUR: Dans le fichier "{}", derriere la ligne {}, il devrait y avoir un commentaire {} et non la fin du fichier.""" .format(conf.get("filledNominativeSheetsFilename"), nbLinesRead[0], opinionType2str[opinionType])) s = removeComment(s) s = s.strip(" \t\n") students[studentIndex].opinionsPerDefense[defenseIndex][ opinionType].comment = s # Compute bonus for comment if students[studentIndex].opinionsPerDefense[defenseIndex][ opinionType].nbCriteriaIndex > 1: # Student has given a "+" (or a "-") to several criteras ==> # We cannot say for which criteria is this comment ==> # We ignore this comment. print( """ATTENTION: Pour la soutenance "{}", l'étudiant "{}" a mis le commentaire ({})\n"{}"\nMais, il a mis le signe "{}" sur plusieurs critères\n==> COS ne peut donc pas prendre en compte ce commentaire\n==> Regardez si vous pouvez ne garder qu'un "{}" dans "{}" qui correspondrait à ce commentaire.\n""" .format( defenses[defenseIndex].name, studentLine, opinionType2str[opinionType], students[studentIndex].opinionsPerDefense[ defenseIndex][opinionType].comment, opinionType2sign[opinionType], opinionType2sign[opinionType], f.name)) students[studentIndex].opinionsPerDefense[defenseIndex][ opinionType].comment = "" if students[studentIndex].opinionsPerDefense[defenseIndex][ opinionType].comment != "": criteriaIndex = students[studentIndex].opinionsPerDefense[ defenseIndex][opinionType].criteriaIndex if criteriaIndex < 0: print( """ATTENTION: Pour la soutenance "{}", l'étudiant "{}" a mis un commentaire {} sans sélectionner de critère {}\n==> Regardez si vous pouvez mettre un "{}" dans "{}" qui correspondrait à ce commentaire.\n""" .format(defenses[defenseIndex].name, studentLine, opinionType2str[opinionType], opinionType2str[opinionType], opinionType2sign[opinionType], f.name)) students[studentIndex].opinionsPerDefense[ defenseIndex][opinionType].comment = "" elif defenses[defenseIndex].teacherOpinionsPerCriteria[ criteriaIndex].opinionType == opinionType: # Student gave the same mak as the teacher ==> bonus students[studentIndex].bonus += conf.get( "bonusCriteriaOK") elif (((opinionType == POSITIVE_OPINION and defenses[defenseIndex].teacherBestOpinionType == AVERAGE_OPINION) or (opinionType == NEGATIVE_OPINION and defenses[defenseIndex].teacherWorstOpinionType == AVERAGE_OPINION)) and defenses[defenseIndex]. teacherOpinionsPerCriteria[criteriaIndex].opinionType == AVERAGE_OPINION): # The teacher found no criteria with opinionType and, for this criteria which index is criteriaIndex, # the teacher gave a mark signifying AVERAGE_OPINION. As the student cannot give an average opinion, # we consider that this is a good answer. students[studentIndex].bonus += conf.get( "bonusCriteriaOK") f.close()
def analyzeTeacherData(conf, defenses, criteriaTypes, criterias): """ Enrich defenses information with the contents of conf.get("teacherMarksFilename") Parameters ---------- conf : Conf Configuration information f : file File on which to write defenses : list of Defense List of defenses students : liste of Student List of students criteriaTypes : liste of str List of criteria types criterias : liste of Criteria List of criterias Returns ------- void """ # # Analyze the contents of conf.get("teacherMarksFilename"), # f = openWithErrorManagement(key2inputFileName("teacherMarksFilename", conf), "r", encoding=conf.get("encoding")) nbLinesRead = [0] lookForNonBlankLine(f, nbLinesRead, False, "Ligne de titre des colonnesNom soutenance" ) # We ignore the line giving the title of the columns # We deal with each criteria for criteria in criterias: # Read the marks for this criteria lineMarks = lookForNonBlankLine( f, nbLinesRead, False, "Ligne contenant les notes pour un critère donné") marks = splitCsvLine(lineMarks, conf.get("csvSeparator")) # Read the comments for this criteria lineComments = lookForNonBlankLine( f, nbLinesRead, False, "Ligne contenant les commentaires pour un critère donné") comments = splitCsvLine(lineComments, conf.get("csvSeparator")) # Add (mark, comment) to each defense column = 3 # We set column to 3 in order to skip : # - column 0 which contains title of the line, # - column 1 which contains value for maxCriteriaKO # - column 2 which contains value for minCriteriaOK for defense in defenses: marks[column] = marks[column].replace(conf.get("decimalSeparator"), '.') try: mark = float(marks[column]) except ValueError: sys.exit( """Dans le fichier "{}", la soutenance "{}" a son critère "{}" qui a reçu la note "{}" qui n'est n'est pas un flottant compris entre 0 et {}.""" .format(f.name, defense.name, criteria.name, marks[column], criteria.maxPoints)) if (mark < 0 or mark > criteria.maxPoints): sys.exit( """Dans le fichier "{}", la soutenance "{}" a son critère "{}" qui a reçu la note de {} qui n'est pas comprise entre 0 et {}.""" .format(f.name, defense.name, criteria.name, mark, criteria.maxPoints)) defense.addMarkComment( TeacherOpinion(mark, comments[column], criteria)) column += 1 lookForNonBlankLine(f, nbLinesRead, False, "Ligne de titre des colonnesNom soutenance" ) # We ignore the line left intentionally blank lineGeneralComments = lookForNonBlankLine( f, nbLinesRead, False, "Ligne contenant les commentaires généraux de chaque projet") generalComments = splitCsvLine(lineGeneralComments, conf.get("csvSeparator")) column = 3 # We set column to 3 in order to skip column 0 which contains title of the line, # column 1 which contains Note max critere KO, and column 2 which contains # Note min critere OK for defense in defenses: defense.generalComment = generalComments[column] column += 1 f.close() # # Update each defense, now that we know all the marks given by teacher # for defense in defenses: defense.update()