Skip to content

Commit

Permalink
added auto correction feature
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelkoelle committed Oct 22, 2019
1 parent f8cfcbc commit 916aa76
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 8 deletions.
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:<br>
\<**Aufgabenname**>[**)**|**:**] \<**Lösung**><br>
\<**Aufgabenname**>[**)**|**:**] \<**Lösung**><br>
\<**Aufgabenname**>[**)**|**:**] \<**Lösung**><br>
...<br>
<br>
Ü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:<br>
\<**Aufgabenname**>[**)**|**:**] \<**Lösung**> \<**Bemerkung**><br>
\<**Aufgabenname**>[**)**|**:**] \<**Lösung**> \<**Bemerkung**><br>
\<**Aufgabenname**>[**)**|**:**] \<**Lösung**> \<**Bemerkung**><br>
...<br>
<br>
**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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -22,7 +28,10 @@ public class ExerciseRatingController implements ChangeListener {
public Spinner<Double> 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<Double> spinnerValue;

Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -57,14 +78,14 @@ 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());
}

public void onMissingSolution(ActionEvent actionEvent) {
e.setRating(0);
e.setComment("fehlt");
e.setComment("Lösung fehlt");
}
}
145 changes: 144 additions & 1 deletion src/main/java/com/koellemichael/controller/MenuController.java
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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<String> 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<String> 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<ButtonType> 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<String, String> solutionMap = new HashMap<>();
Map<String, String> 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<File> files = mainController.getSolutionsFromCorrection(c);
final int[] found = {0};
Map<String, String> 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<String> 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<Exercise> 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();
}
}
});
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/koellemichael/model/ExerciseRating.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/koellemichael/utils/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 "";
}

}
4 changes: 3 additions & 1 deletion src/main/java/com/koellemichael/utils/PreferenceKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";


}
4 changes: 2 additions & 2 deletions src/main/resources/layout/exercise.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<VBox alignment="TOP_CENTER" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.koellemichael.controller.ExerciseRatingController">
<VBox fx:id="exercise_rating_container" alignment="TOP_CENTER" xmlns="http://javafx.com/javafx/8.0.999-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.koellemichael.controller.ExerciseRatingController">
<children>
<HBox alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="35.0" prefWidth="502.0">
<HBox fx:id="exercise_label_container" alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="35.0" prefWidth="502.0">
<children>
<Label fx:id="lbl_exercise_name" text="Exercise Name">
<font>
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/layout/menu.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
</items>
</Menu>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="mi_auto_correction" mnemonicParsing="false" onAction="#onAutoCorrection" text="Automatische Korrektur" />
<MenuItem fx:id="mi_export_zip" mnemonicParsing="false" onAction="#onExportAsZIP" text="Als ZIP exportieren">
<accelerator>
<KeyCodeCombination alt="UP" code="E" control="UP" meta="UP" shift="UP" shortcut="DOWN" />
Expand Down

0 comments on commit 916aa76

Please sign in to comment.