From 916aa767b4beff01c7125b4761d65941c21bf3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=B6lle?= Date: Tue, 22 Oct 2019 15:29:12 +0200 Subject: [PATCH] added auto correction feature --- README.md | 24 ++- .../controller/ExerciseRatingController.java | 25 ++- .../controller/MenuController.java | 145 +++++++++++++++++- .../koellemichael/model/ExerciseRating.java | 9 ++ .../com/koellemichael/utils/FileUtils.java | 9 ++ .../koellemichael/utils/PreferenceKeys.java | 4 +- src/main/resources/layout/exercise.fxml | 4 +- src/main/resources/layout/menu.fxml | 1 + 8 files changed, 213 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2e33898..dcc8c00 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Correctinator [![CircleCI](https://circleci.com/gh/koellemichael/correctinator/tree/master.svg?style=svg)](https://circleci.com/gh/koellemichael/correctinator/tree/master) Ein Korrekturprogramm ([Download](#download)) mit Media Viewer für UniWorx Bewertungsdateien. +![alt text](https://i.imgur.com/RHtnqcW.png "Correctinator") ## Workflow @@ -29,12 +30,31 @@ Beispiel: - **Automatischer Kommentar**: Bei Änderung der Bewertung bzw. nach dem Wechseln der Abgabe (momentan nur Button "Nächste Abgabe") wird zur Abgabe ein vordefinierter Kommentar, abhängig von der erreichten Punktzahl, hinzugefügt. Weitere Informationen im Abschnitt [Automatischer Kommentar](#automatischer-kommentar). - **Verbose**: Zeigt nach dem Öffnen von Abgaben eine Zusammenfassung des Imports an. -### Media Viewer +## Media Viewer - **PDF Viewer**: Es wird eine Beta Version der Open-Scource Library PDF.js verwendet. Es kann also zu Darstellungfehlern kommen. Wenn man sich nicht sicher ist, sollte die Datei über die "Ordner öffnen" Funktion in einem anderen Viewer geöffnet werden. Bei großen PDF-Dateien und PDF-Dateien, die Bilder enthalten, kommt es zu etwas längeren Ladezeiten (je nach Leistung des PCs). - **Image Viewer**: Man kann zoomen und das Bild per Drag and Drop verschieben. Und es gibt Touchpad Support für Mac :) - **Text Viewer**: Das Encoding wird nicht immer erkannt. Es gibt kein Text-Highlighting. Aus irgendeinem Grund gibt es einen Zeilenumbruch, wenn der Text länger ist als der Viewer. -### Automatischer Kommentar +## Automatische Korrektur von Single Choice Aufgaben +Single Choice Aufgaben können automatisch korrigiert werden. Dazu muss die Lösung der Aufgabe in einer separaten .txt Datei abgegeben werden und folgendem Format entsprechen:
+\<**Aufgabenname**>[**)**|**:**] \<**Lösung**>
+\<**Aufgabenname**>[**)**|**:**] \<**Lösung**>
+\<**Aufgabenname**>[**)**|**:**] \<**Lösung**>
+...
+
+Über den Menüpunkt "Automatische Korrektur" wird die Aufgabe ausgewählt, die Lösung eingegeben und die Korrektur durchgeführt. +Änhlich wie die Abgabe der Studenten muss auch die Lösung ein bestimmtes Format haben:
+\<**Aufgabenname**>[**)**|**:**] \<**Lösung**> \<**Bemerkung**>
+\<**Aufgabenname**>[**)**|**:**] \<**Lösung**> \<**Bemerkung**>
+\<**Aufgabenname**>[**)**|**:**] \<**Lösung**> \<**Bemerkung**>
+...
+
+**Ein Beispiel**: +![alt text](https://i.imgur.com/SUV77Cw.png "Beispiel Automatische Korrektur") + +Die Bemerkung wird dem Studenten bei einer inkorrekten Antwort als Kommentar eingefügt. + +## Automatischer Kommentar Mit dieser Funktion können abhängig von der erreichten Punktzahl automatische Kommentare zur Abgabe hinzugefügt werden. Man kann Kommentare über den Menüpunkt "Automatischer Kommentar Einstellungen" definieren. Die Kommentare sollten jedoch nur einmal definiert werden. Beim Import von älteren Abgaben und einer veränderten Definition kann es zu doppelten Kommentaren kommen, da das Programm keinen Verlauf der bisherigen Kommentardefinitionen speichert. # Download diff --git a/src/main/java/com/koellemichael/controller/ExerciseRatingController.java b/src/main/java/com/koellemichael/controller/ExerciseRatingController.java index 305e194..90b0db3 100644 --- a/src/main/java/com/koellemichael/controller/ExerciseRatingController.java +++ b/src/main/java/com/koellemichael/controller/ExerciseRatingController.java @@ -8,11 +8,17 @@ import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.event.ActionEvent; +import javafx.geometry.Insets; +import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.Spinner; import javafx.scene.control.SpinnerValueFactory; import javafx.scene.control.TextArea; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import java.awt.*; import java.io.IOException; import java.util.prefs.Preferences; @@ -22,7 +28,10 @@ public class ExerciseRatingController implements ChangeListener { public Spinner sp_rating; public Label lbl_max_points; public TextArea ta_comments; + public VBox exercise_rating_container; + public HBox exercise_label_container; private ExerciseRating e; + private Label autoGenLabel; private ObjectProperty spinnerValue; @@ -38,6 +47,13 @@ public void initialize(ExerciseRating e) { ta_comments.textProperty().bindBidirectional(e.commentProperty()); + autoGenLabel = new Label("Diese Aufgabe wurde automatisch korrigiert"); + autoGenLabel.setBackground(new Background(new BackgroundFill(Color.web("#90ee90",0.5), new CornerRadii(3), Insets.EMPTY))); + + if(e.isAutoGen()){ + exercise_rating_container.getChildren().add(1,autoGenLabel); + } + //TODO falls die listener weg sind funktioniert das binding nicht mehr richtig weil .asObject vom gc entfernt wird e.ratingProperty().addListener(this); e.commentProperty().addListener(this); @@ -48,6 +64,11 @@ public void initialize(ExerciseRating e) { public void changed(ObservableValue observable, Object oldValue, Object newValue) { e.changed(); + if(e.isAutoGen()){ + //exercise_label_container.setBackground(new Background(new BackgroundFill(Color.web("#f4f4f4"), CornerRadii.EMPTY, Insets.EMPTY))); + exercise_rating_container.getChildren().remove(autoGenLabel); + } + Preferences pref = Preferences.userRoot(); if(pref.getBoolean(PreferenceKeys.AUTOSAVE_PREF, false)){ try { @@ -57,7 +78,7 @@ public void changed(ObservableValue observable, Object oldValue, Object newValue } public void onSetMaxPoints(ActionEvent actionEvent) { - if(e.getComment().equals("fehlt")){ + if(e.getComment().equals("Lösung fehlt")){ e.setComment(""); } e.setRating(e.getMaxPoints()); @@ -65,6 +86,6 @@ public void onSetMaxPoints(ActionEvent actionEvent) { public void onMissingSolution(ActionEvent actionEvent) { e.setRating(0); - e.setComment("fehlt"); + e.setComment("Lösung fehlt"); } } diff --git a/src/main/java/com/koellemichael/controller/MenuController.java b/src/main/java/com/koellemichael/controller/MenuController.java index ba96671..a5679a0 100644 --- a/src/main/java/com/koellemichael/controller/MenuController.java +++ b/src/main/java/com/koellemichael/controller/MenuController.java @@ -1,19 +1,36 @@ package com.koellemichael.controller; import com.koellemichael.model.Correction; +import com.koellemichael.model.Exercise; +import com.koellemichael.model.ExerciseLabel; +import com.koellemichael.model.ExerciseRating; import com.koellemichael.utils.*; +import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.*; +import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; import javafx.stage.*; +import org.mozilla.universalchardet.UniversalDetector; +import java.io.File; +import java.io.FileFilter; import java.io.IOException; -import java.util.Optional; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.SQLOutput; +import java.util.*; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; public class MenuController { @@ -298,4 +315,130 @@ public void onResetProgramData(ActionEvent actionEvent) { } }); } + + + + public void onAutoCorrection(ActionEvent actionEvent) { + Alert initDialog = new Alert(Alert.AlertType.CONFIRMATION); + initDialog.setTitle("Automatische Korrektur"); + initDialog.setHeaderText("Automatische Korrektur von Single Choice Aufgaben"); + initDialog.getButtonTypes().clear(); + initDialog.getButtonTypes().add(ButtonType.CANCEL); + initDialog.getButtonTypes().add(ButtonType.FINISH); + TextArea textArea = new TextArea(); + + ObservableList list = FXCollections.observableArrayList(mainController.corrections.get(0).getExercise().getSubExercises().stream().flatMap(Utils::flatten).filter(exercise -> exercise instanceof ExerciseRating).map(Exercise::getName).collect(Collectors.toList())); + ChoiceBox choiceBox = new ChoiceBox<>(list); + choiceBox.getSelectionModel().selectLast(); + + VBox vBox = new VBox(choiceBox, textArea); + textArea.setText(preferences.get(PreferenceKeys.SINGLE_CHOICE_SOLUTION, "")); + initDialog.getDialogPane().setContent(vBox); + Optional b = initDialog.showAndWait(); + b.ifPresent(buttonType -> { + if(buttonType==ButtonType.FINISH){ + int[] autoCorrectionCount = {0}; + String solution = textArea.getText(); + preferences.put(PreferenceKeys.SINGLE_CHOICE_SOLUTION, solution); + + Pattern p = Pattern.compile("(\\w)[.|\\)]\\s*\\(?([i|v|x]+)\\)?(?> *(.*))?",Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Matcher matcher = p.matcher(solution); + Map solutionMap = new HashMap<>(); + Map solutionTextMap = new HashMap<>(); + while(matcher.find()){ + String task = matcher.group(1); + String sol = matcher.group(2); + String solText = matcher.group(3); + + solutionMap.put(task, sol); + solutionTextMap.put(task, solText); + } + + mainController.corrections.forEach((c) -> { + ArrayList files = mainController.getSolutionsFromCorrection(c); + final int[] found = {0}; + Map submission = new HashMap<>(); + files.forEach(f -> { + if(FileUtils.getFileExtension(f).equals("txt")){ + System.out.println("Text File gefunden: " + f.getPath()); + //Determine Encoding + String encoding = null; + try { + encoding = UniversalDetector.detectCharset(f); + } catch (IOException e) { + e.printStackTrace(); + } + if(encoding == null){ + encoding = Charset.defaultCharset().toString(); + } + String contents = FileUtils.readStringFromFile(f, encoding); + + Matcher matcher1 = p.matcher(contents); + boolean foundMatch = false; + + while(matcher1.find()){ + foundMatch = true; + String task = matcher1.group(1); + String sol = matcher1.group(2); + submission.put(task, sol); + } + + if(foundMatch){ + found[0]++; + } + } + }); + + if(found[0] == 1){ + double[] score = {0.0}; + ArrayList incorrectTasks = new ArrayList<>(); + solutionMap.forEach((k,v) ->{ + if(submission.containsKey(k)){ + if(v.equals(submission.get(k))){ + score[0] += 1.0; + }else{ + incorrectTasks.add(k); + } + } + }); + + Optional e = c.getExercise().getSubExercises().stream().flatMap(Utils::flatten).filter(exercise -> exercise instanceof ExerciseRating && exercise.getName().equals(choiceBox.getValue())).findFirst(); + if(e.isPresent()){ + ExerciseRating exercise = (ExerciseRating) e.get(); + exercise.setAutoGen(true); + exercise.ratingProperty().set(score[0]); + exercise.commentProperty().set(incorrectTasks.stream().map(task -> task + ") " + solutionMap.get(task)+ " " + solutionTextMap.get(task)).reduce("", (s, acc) -> acc + "\n" + s)); + c.setChanged(true); + + autoCorrectionCount[0]++; + + if(preferences.getBoolean(PreferenceKeys.AUTOSAVE_PREF,true)){ + try { + RatingFileParser.saveRatingFile(c); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + + } + + } + + }); + + if(autoCorrectionCount[0]>0){ + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("Zusammenfassung"); + alert.setHeaderText(null); + StringBuilder content = new StringBuilder(); + + content.append("Es wurde die ").append(choiceBox.getValue().replace(":","").replace(")","")).append(" bei ").append(autoCorrectionCount[0]).append(" Abgaben automatisch korrigiert!"); + + alert.setContentText(content.toString()); + alert.showAndWait(); + } + } + }); + } } diff --git a/src/main/java/com/koellemichael/model/ExerciseRating.java b/src/main/java/com/koellemichael/model/ExerciseRating.java index 8e63154..9b80d08 100644 --- a/src/main/java/com/koellemichael/model/ExerciseRating.java +++ b/src/main/java/com/koellemichael/model/ExerciseRating.java @@ -7,6 +7,7 @@ public class ExerciseRating extends Exercise { private SimpleDoubleProperty maxPoints; private SimpleStringProperty comment; private SimpleDoubleProperty rating; + private boolean autoGen = false; public ExerciseRating(String name, Exercise parent) { super(name, parent); @@ -59,4 +60,12 @@ public SimpleStringProperty commentProperty() { public void setComment(String comment) { this.comment.set(comment); } + + public boolean isAutoGen() { + return autoGen; + } + + public void setAutoGen(boolean autoGen) { + this.autoGen = autoGen; + } } diff --git a/src/main/java/com/koellemichael/utils/FileUtils.java b/src/main/java/com/koellemichael/utils/FileUtils.java index 5d7f11a..4b9d144 100644 --- a/src/main/java/com/koellemichael/utils/FileUtils.java +++ b/src/main/java/com/koellemichael/utils/FileUtils.java @@ -124,4 +124,13 @@ public static String readStringFromFile(File file, String encoding){ } } + public static String getFileExtension(File f){ + String fileName = f.getName(); + int i = fileName.lastIndexOf('.'); + if (i > 0) { + return fileName.substring(i+1); + } + return ""; + } + } diff --git a/src/main/java/com/koellemichael/utils/PreferenceKeys.java b/src/main/java/com/koellemichael/utils/PreferenceKeys.java index ef9d146..f24a6d6 100644 --- a/src/main/java/com/koellemichael/utils/PreferenceKeys.java +++ b/src/main/java/com/koellemichael/utils/PreferenceKeys.java @@ -15,6 +15,8 @@ public class PreferenceKeys { public static final String COMMENT_20_PREF = "Comment20"; public static final String COMMENT_0_PREF = "Comment0"; public static final String LAST_OPENED_DIR_PREF = "lastopeneddir"; - public static final String WRAP_TEXT_PREF = "lastopeneddir"; + public static final String WRAP_TEXT_PREF = "wraptext"; + public static final String SINGLE_CHOICE_SOLUTION = "singlechoicesolution"; + } diff --git a/src/main/resources/layout/exercise.fxml b/src/main/resources/layout/exercise.fxml index 5eae3e4..4d2007f 100644 --- a/src/main/resources/layout/exercise.fxml +++ b/src/main/resources/layout/exercise.fxml @@ -9,9 +9,9 @@ - + - +