From c01d88eaafcaac85f90a1d856cde6113b5eb7e51 Mon Sep 17 00:00:00 2001
From: Ruben Backx <r.w.backx@student.tudelft.nl>
Date: Mon, 27 Sep 2021 10:44:01 +0200
Subject: [PATCH] Improve script trains

---
 .../nl/tudelft/submit/config/AsyncConfig.java |   2 +
 .../submit/controller/GradeController.java    |   8 +-
 .../submit/controller/ScriptController.java   |  16 +-
 .../script/ScriptFeedbackReceiveDTO.java      |   3 -
 .../view/script/ScriptFeedbackViewDTO.java    |  43 ++
 .../dto/view/script/ScriptResultViewDTO.java  |  52 ++
 .../view/script/ScriptTrainResultViewDTO.java |  59 +++
 .../dto/view/script/ScriptTrainViewDTO.java   |  13 +-
 .../submit/dto/view/script/ScriptViewDTO.java |   9 +-
 .../view/script/ScriptWagonResultViewDTO.java |  59 +++
 .../dto/view/script/ScriptWagonViewDTO.java   |  11 +-
 .../submit/enums/GradeCalculation.java        |  24 +
 .../nl/tudelft/submit/enums/ScriptStatus.java |  24 +
 .../exception/InvalidFormulaException.java    |   6 +-
 .../java/nl/tudelft/submit/model/Version.java |   9 +-
 .../submit/model/grading/GradePart.java       |  45 ++
 .../model/script/AbstractScriptResult.java    |  52 ++
 .../tudelft/submit/model/script/Script.java   |  23 +-
 .../submit/model/script/ScriptFeedback.java   |  55 ++
 .../submit/model/script/ScriptResult.java     |  61 +++
 .../submit/model/script/ScriptTrain.java      |  23 +-
 .../model/script/ScriptTrainResult.java       |  40 ++
 .../submit/model/script/ScriptWagon.java      |  28 +-
 .../model/script/ScriptWagonResult.java       |  40 ++
 .../submit/model/script/SubmissionKey.java    |   3 -
 .../AbstractScriptResultRepository.java       |  46 ++
 .../script/ScriptFeedbackRepository.java      |  36 ++
 .../repository/script/ScriptRepository.java   |   3 +
 .../script/ScriptResultRepository.java        |  32 ++
 .../script/ScriptTrainResultRepository.java   |  30 ++
 .../script/ScriptWagonRepository.java         |   4 +
 .../script/ScriptWagonResultRepository.java   |  32 ++
 .../submit/security/AuthorizationService.java |  15 +-
 .../submit/service/FormulaService.java        |   4 +-
 .../tudelft/submit/service/GradeService.java  |  95 ++--
 .../tudelft/submit/service/ScriptService.java | 493 +++++++++++++-----
 .../submit/service/StatisticsService.java     |   6 +-
 src/main/resources/messages.properties        |   9 +
 .../resources/templates/assignment/view.html  |  14 +-
 .../{version => script}/add_script.html       |   0
 .../{version => script}/add_wagon.html        |   0
 .../{version => script}/edit_script.html      |   0
 .../{version => script}/edit_wagon.html       |   0
 .../{version => script}/remove_script.html    |   0
 .../{version => script}/remove_wagon.html     |   0
 .../resources/templates/script/status.html    |  72 +++
 .../templates/{version => script}/train.html  |  14 +-
 .../{version => script}/wagon_list.html       |   1 -
 .../templates/submission/feedback.html        |   2 +-
 src/test/resources/application.yaml           |   2 -
 50 files changed, 1382 insertions(+), 236 deletions(-)
 create mode 100644 src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java
 create mode 100644 src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java
 create mode 100644 src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java
 create mode 100644 src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java
 create mode 100644 src/main/java/nl/tudelft/submit/enums/GradeCalculation.java
 create mode 100644 src/main/java/nl/tudelft/submit/enums/ScriptStatus.java
 create mode 100644 src/main/java/nl/tudelft/submit/model/grading/GradePart.java
 create mode 100644 src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java
 create mode 100644 src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java
 create mode 100644 src/main/java/nl/tudelft/submit/model/script/ScriptResult.java
 create mode 100644 src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java
 create mode 100644 src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java
 create mode 100644 src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java
 create mode 100644 src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java
 create mode 100644 src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java
 create mode 100644 src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java
 create mode 100644 src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java
 rename src/main/resources/templates/{version => script}/add_script.html (100%)
 rename src/main/resources/templates/{version => script}/add_wagon.html (100%)
 rename src/main/resources/templates/{version => script}/edit_script.html (100%)
 rename src/main/resources/templates/{version => script}/edit_wagon.html (100%)
 rename src/main/resources/templates/{version => script}/remove_script.html (100%)
 rename src/main/resources/templates/{version => script}/remove_wagon.html (100%)
 create mode 100644 src/main/resources/templates/script/status.html
 rename src/main/resources/templates/{version => script}/train.html (85%)
 rename src/main/resources/templates/{version => script}/wagon_list.html (94%)

diff --git a/src/main/java/nl/tudelft/submit/config/AsyncConfig.java b/src/main/java/nl/tudelft/submit/config/AsyncConfig.java
index 9befdd6f..41fe001e 100644
--- a/src/main/java/nl/tudelft/submit/config/AsyncConfig.java
+++ b/src/main/java/nl/tudelft/submit/config/AsyncConfig.java
@@ -22,10 +22,12 @@ import java.util.concurrent.Executor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 @Configuration
 @EnableAsync
+@EnableScheduling
 public class AsyncConfig {
 
 	@Bean
diff --git a/src/main/java/nl/tudelft/submit/controller/GradeController.java b/src/main/java/nl/tudelft/submit/controller/GradeController.java
index c5cec0b6..ab90de82 100644
--- a/src/main/java/nl/tudelft/submit/controller/GradeController.java
+++ b/src/main/java/nl/tudelft/submit/controller/GradeController.java
@@ -136,10 +136,12 @@ public class GradeController {
 			}
 		}
 
-		Optional<CalculatedScore> score = gradeService.executeFormula(new TestGradingFormula(
-				null, formulaTest.getFormula(), formulaTest.getType()), variables);
+		Optional<Integer> score = gradeService.executeFormula(formulaTest.getFormula(), formulaTest.getType(),
+				variables);
 
-		return new TestGradeViewDTO(score.map(gradeService::getScoreDisplayString).orElse("Invalid formula"));
+		return new TestGradeViewDTO(
+				score.map(s -> gradeService.getScoreDisplayString(s, formulaTest.getType()))
+						.orElse("Invalid formula"));
 	}
 
 }
diff --git a/src/main/java/nl/tudelft/submit/controller/ScriptController.java b/src/main/java/nl/tudelft/submit/controller/ScriptController.java
index 1def27ab..0eb1040f 100644
--- a/src/main/java/nl/tudelft/submit/controller/ScriptController.java
+++ b/src/main/java/nl/tudelft/submit/controller/ScriptController.java
@@ -25,6 +25,7 @@ import nl.tudelft.submit.dto.id.ScriptTrainIdDTO;
 import nl.tudelft.submit.dto.id.ScriptWagonIdDTO;
 import nl.tudelft.submit.dto.patch.script.ScriptPatchDTO;
 import nl.tudelft.submit.dto.patch.script.ScriptWagonPatchDTO;
+import nl.tudelft.submit.dto.view.script.ScriptTrainResultViewDTO;
 import nl.tudelft.submit.model.Version;
 import nl.tudelft.submit.model.script.Script;
 import nl.tudelft.submit.model.script.ScriptTrain;
@@ -52,8 +53,7 @@ public class ScriptController {
 	@PostMapping(value = "/feedback", consumes = MediaType.APPLICATION_JSON_VALUE)
 	@PreAuthorize("@authorizationService.isSubmissionKeyValid(#feedback.submissionId, #feedback.scriptId, #feedback.key)")
 	public ResponseEntity<Void> addScriptFeedback(@RequestBody ScriptFeedbackReceiveDTO feedback) {
-		scriptService.addScriptFeedback(feedback,
-				scriptService.canKeyBeUsedForGrading(feedback.getSubmissionId(), feedback.getScriptId()));
+		scriptService.addScriptFeedback(feedback);
 		return ResponseEntity.ok(null);
 	}
 
@@ -72,7 +72,17 @@ public class ScriptController {
 		model.addAttribute("script", ScriptCreateDTO.builder().wagon(new ScriptWagonIdDTO()).build());
 		model.addAttribute("scriptPatch", new ScriptPatchDTO());
 
-		return "/version/train";
+		return "/script/train";
+	}
+
+	@GetMapping("/status/{trainId}/{submissionId}")
+	@PreAuthorize("@authorizationService.canViewSubmission(#submissionId)")
+	public String getScriptStatus(@PathVariable Long trainId, @PathVariable Long submissionId, Model model) {
+		ScriptTrainResultViewDTO train = scriptService.getScriptTrainResult(trainId, submissionId);
+
+		model.addAttribute("train", train);
+
+		return "/script/status";
 	}
 
 	@PostMapping("/train/assignment/{assignmentId}")
diff --git a/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java b/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java
index 3bf86ea4..3e9a8e27 100644
--- a/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/create/script/ScriptFeedbackReceiveDTO.java
@@ -23,7 +23,6 @@ import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
-import nl.tudelft.labracore.api.dto.GradeCreateDTO;
 import nl.tudelft.labracore.api.dto.RoleSummaryDTO;
 
 @Data
@@ -43,8 +42,6 @@ public class ScriptFeedbackReceiveDTO {
 
 	private String textualFeedback;
 
-	private GradeCreateDTO.SchemeEnum scoreType;
-
 	private Integer score;
 
 	@NotNull
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java
new file mode 100644
index 00000000..49826a60
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptFeedbackViewDTO.java
@@ -0,0 +1,43 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.dto.view.script;
+
+import javax.persistence.*;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.tudelft.labracore.api.dto.RoleSummaryDTO;
+import nl.tudelft.submit.model.script.ScriptResult;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ScriptFeedbackViewDTO {
+
+	private Long id;
+
+	private String feedback;
+
+	private RoleSummaryDTO.TypeEnum visibleFor;
+
+	private ScriptResult result;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java
new file mode 100644
index 00000000..d755bc29
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptResultViewDTO.java
@@ -0,0 +1,52 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.dto.view.script;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import lombok.*;
+import nl.tudelft.librador.dto.view.View;
+import nl.tudelft.submit.enums.ScriptStatus;
+import nl.tudelft.submit.model.labracore.SubmitSubmission;
+import nl.tudelft.submit.model.script.ScriptResult;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class ScriptResultViewDTO extends View<ScriptResult> {
+
+	private Long id;
+
+	private SubmitSubmission submission;
+
+	private ScriptStatus status;
+
+	private Integer score;
+
+	private ScriptViewDTO script;
+
+	private LocalDateTime timestamp;
+
+	private String autaSubmissionId;
+
+	private List<ScriptFeedbackViewDTO> feedback;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java
new file mode 100644
index 00000000..8f390546
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainResultViewDTO.java
@@ -0,0 +1,59 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.dto.view.script;
+
+import java.util.List;
+
+import lombok.*;
+import nl.tudelft.librador.SpringContext;
+import nl.tudelft.librador.dto.view.View;
+import nl.tudelft.submit.enums.ScriptStatus;
+import nl.tudelft.submit.model.labracore.SubmitSubmission;
+import nl.tudelft.submit.model.script.ScriptTrainResult;
+import nl.tudelft.submit.repository.script.ScriptWagonResultRepository;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class ScriptTrainResultViewDTO extends View<ScriptTrainResult> {
+
+	private Long id;
+
+	private SubmitSubmission submission;
+
+	private ScriptStatus status;
+
+	private Integer score;
+
+	private ScriptTrainViewDTO train;
+
+	@PostApply
+	private List<ScriptWagonResultViewDTO> wagons;
+
+	@Override
+	public void postApply() {
+		this.wagons = View.convert(SpringContext.getBean(ScriptWagonResultRepository.class)
+				.findAllByWagonTrainIdAndSubmissionId(train.getId(), submission.getId()),
+				ScriptWagonResultViewDTO.class);
+
+		super.postApply();
+	}
+
+}
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java
index fd50a515..2dda76a0 100644
--- a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptTrainViewDTO.java
@@ -17,11 +17,10 @@
  */
 package nl.tudelft.submit.dto.view.script;
 
-import java.util.List;
-
 import lombok.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.dto.id.VersionIdDTO;
+import nl.tudelft.submit.model.grading.GradePart;
 import nl.tudelft.submit.model.script.ScriptTrain;
 
 @Data
@@ -33,8 +32,12 @@ public class ScriptTrainViewDTO extends View<ScriptTrain> {
 
 	private Long id;
 
-	private VersionIdDTO version;
+	private String name;
+
+	private String formula;
+
+	private GradeScheme gradeScheme;
 
-	private List<ScriptWagonViewDTO> wagons;
+	private GradePart gradePart = new GradePart();
 
 }
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java
index 2fa28d4e..d93293ba 100644
--- a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptViewDTO.java
@@ -17,9 +17,12 @@
  */
 package nl.tudelft.submit.dto.view.script;
 
+import javax.persistence.*;
+
 import lombok.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.dto.id.ScriptWagonIdDTO;
+import nl.tudelft.submit.model.grading.GradePart;
 import nl.tudelft.submit.model.script.Script;
 
 @Data
@@ -35,6 +38,8 @@ public class ScriptViewDTO extends View<Script> {
 
 	private String link;
 
-	private ScriptWagonIdDTO wagon;
+	private GradeScheme gradeScheme;
+
+	private GradePart gradePart;
 
 }
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java
new file mode 100644
index 00000000..41af2915
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonResultViewDTO.java
@@ -0,0 +1,59 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.dto.view.script;
+
+import java.util.List;
+
+import lombok.*;
+import nl.tudelft.librador.SpringContext;
+import nl.tudelft.librador.dto.view.View;
+import nl.tudelft.submit.enums.ScriptStatus;
+import nl.tudelft.submit.model.labracore.SubmitSubmission;
+import nl.tudelft.submit.model.script.ScriptWagonResult;
+import nl.tudelft.submit.repository.script.ScriptResultRepository;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode(callSuper = false)
+public class ScriptWagonResultViewDTO extends View<ScriptWagonResult> {
+
+	private Long id;
+
+	private SubmitSubmission submission;
+
+	private ScriptStatus status;
+
+	private Integer score;
+
+	private ScriptWagonViewDTO wagon;
+
+	@PostApply
+	private List<ScriptResultViewDTO> scripts;
+
+	@Override
+	public void postApply() {
+		this.scripts = View.convert(SpringContext.getBean(ScriptResultRepository.class)
+				.findAllByScriptWagonIdAndSubmissionId(wagon.getId(), submission.getId()),
+				ScriptResultViewDTO.class);
+
+		super.postApply();
+	}
+
+}
diff --git a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java
index 499d3c4b..d298c959 100644
--- a/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java
+++ b/src/main/java/nl/tudelft/submit/dto/view/script/ScriptWagonViewDTO.java
@@ -17,11 +17,12 @@
  */
 package nl.tudelft.submit.dto.view.script;
 
-import java.util.List;
+import javax.persistence.*;
 
 import lombok.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
 import nl.tudelft.librador.dto.view.View;
-import nl.tudelft.submit.dto.id.ScriptTrainIdDTO;
+import nl.tudelft.submit.model.grading.GradePart;
 import nl.tudelft.submit.model.script.ScriptWagon;
 
 @Data
@@ -35,8 +36,10 @@ public class ScriptWagonViewDTO extends View<ScriptWagon> {
 
 	private String name;
 
-	private List<ScriptViewDTO> scripts;
+	private String formula;
 
-	private ScriptTrainIdDTO train;
+	private GradeScheme gradeScheme;
+
+	private GradePart gradePart = new GradePart();
 
 }
diff --git a/src/main/java/nl/tudelft/submit/enums/GradeCalculation.java b/src/main/java/nl/tudelft/submit/enums/GradeCalculation.java
new file mode 100644
index 00000000..8d2963c8
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/enums/GradeCalculation.java
@@ -0,0 +1,24 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.enums;
+
+public enum GradeCalculation {
+
+	FORMULA, AUTOMATIC;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/enums/ScriptStatus.java b/src/main/java/nl/tudelft/submit/enums/ScriptStatus.java
new file mode 100644
index 00000000..46b742e7
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/enums/ScriptStatus.java
@@ -0,0 +1,24 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.enums;
+
+public enum ScriptStatus {
+
+	NOT_STARTED, RUNNING, DONE, FAILED, PROCESSED;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java b/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java
index 54f68c67..a859b06c 100644
--- a/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java
+++ b/src/main/java/nl/tudelft/submit/exception/InvalidFormulaException.java
@@ -17,12 +17,10 @@
  */
 package nl.tudelft.submit.exception;
 
-import nl.tudelft.submit.model.grading.GradingFormula;
-
 public class InvalidFormulaException extends Exception {
 
-	public InvalidFormulaException(GradingFormula<?> formula) {
-		super("Formula is invalid: " + formula.getFormula());
+	public InvalidFormulaException(String formula) {
+		super("Formula is invalid: " + formula);
 	}
 
 }
diff --git a/src/main/java/nl/tudelft/submit/model/Version.java b/src/main/java/nl/tudelft/submit/model/Version.java
index 7f2e0422..c8e51e8d 100644
--- a/src/main/java/nl/tudelft/submit/model/Version.java
+++ b/src/main/java/nl/tudelft/submit/model/Version.java
@@ -26,10 +26,7 @@ import javax.persistence.Id;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
+import lombok.*;
 import nl.tudelft.submit.dto.helper.VersionDescription;
 import nl.tudelft.submit.model.labracore.SubmitGroup;
 import nl.tudelft.submit.model.script.ScriptTrain;
@@ -55,10 +52,12 @@ public class Version {
 	@NotNull
 	private Long assignmentId;
 
-	@Builder.Default
 	@ManyToMany
+	@Builder.Default
+	@ToString.Exclude
 	private List<SubmitGroup> groups = new ArrayList<>();
 
 	@OneToOne(mappedBy = "version")
+	@ToString.Exclude
 	private ScriptTrain scriptTrain;
 }
diff --git a/src/main/java/nl/tudelft/submit/model/grading/GradePart.java b/src/main/java/nl/tudelft/submit/model/grading/GradePart.java
new file mode 100644
index 00000000..0245bcc1
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/model/grading/GradePart.java
@@ -0,0 +1,45 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.model.grading;
+
+import javax.persistence.Embeddable;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@Embeddable
+@NoArgsConstructor
+@AllArgsConstructor
+public class GradePart {
+
+	@Min(0)
+	@NotNull
+	@Builder.Default
+	private Integer weight = 0;
+
+	@NotNull
+	@Builder.Default
+	private Boolean required = false;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java b/src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java
new file mode 100644
index 00000000..c2052729
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/model/script/AbstractScriptResult.java
@@ -0,0 +1,52 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.model.script;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import nl.tudelft.submit.enums.ScriptStatus;
+import nl.tudelft.submit.model.labracore.SubmitSubmission;
+
+@Data
+@SuperBuilder
+@MappedSuperclass
+@NoArgsConstructor
+@AllArgsConstructor
+public abstract class AbstractScriptResult {
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.SEQUENCE)
+	private Long id;
+
+	@NotNull
+	@ManyToOne
+	private SubmitSubmission submission;
+
+	@NotNull
+	@Builder.Default
+	private ScriptStatus status = ScriptStatus.NOT_STARTED;
+
+	private Integer score;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/model/script/Script.java b/src/main/java/nl/tudelft/submit/model/script/Script.java
index 1b9b8a62..154e6e6d 100644
--- a/src/main/java/nl/tudelft/submit/model/script/Script.java
+++ b/src/main/java/nl/tudelft/submit/model/script/Script.java
@@ -17,10 +17,16 @@
  */
 package nl.tudelft.submit.model.script;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import javax.persistence.*;
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 
 import lombok.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
+import nl.tudelft.submit.model.grading.GradePart;
 
 @Data
 @Entity
@@ -33,15 +39,31 @@ public class Script {
 	@GeneratedValue(strategy = GenerationType.SEQUENCE)
 	private Long id;
 
+	@NotBlank
 	private String name;
 
 	@NotBlank
 	private String link;
 
+	@NotNull
+	@Builder.Default
+	private GradeScheme gradeScheme = GradeScheme.DUTCH_GRADE;
+
+	@NotNull
+	@Embedded
+	@Builder.Default
+	private GradePart gradePart = new GradePart();
+
 	@ManyToOne
 	@ToString.Exclude
 	private ScriptWagon wagon;
 
+	@Builder.Default
+	@ToString.Exclude
+	@EqualsAndHashCode.Exclude
+	@OneToMany(mappedBy = "script")
+	private List<ScriptResult> results = new ArrayList<>();
+
 	/**
 	 * Returns whether the script is a script for AuTA.
 	 *
@@ -50,5 +72,4 @@ public class Script {
 	public boolean isAutaScript() {
 		return link.startsWith("auta:");
 	}
-
 }
diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java b/src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java
new file mode 100644
index 00000000..67be967b
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/model/script/ScriptFeedback.java
@@ -0,0 +1,55 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.model.script;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import nl.tudelft.labracore.api.dto.RoleSummaryDTO;
+
+@Data
+@Entity
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ScriptFeedback {
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.SEQUENCE)
+	private Long id;
+
+	@Lob
+	@Size(max = 16384)
+	@NotBlank
+	private String feedback;
+
+	@NotNull
+	@Builder.Default
+	private RoleSummaryDTO.TypeEnum visibleFor = RoleSummaryDTO.TypeEnum.STUDENT;
+
+	@NotNull
+	@ManyToOne
+	private ScriptResult result;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptResult.java b/src/main/java/nl/tudelft/submit/model/script/ScriptResult.java
new file mode 100644
index 00000000..42ad80d7
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/model/script/ScriptResult.java
@@ -0,0 +1,61 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.model.script;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+import nl.tudelft.submit.enums.ScriptStatus;
+
+@Data
+@Entity
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class ScriptResult extends AbstractScriptResult {
+
+	@NotNull
+	@ManyToOne
+	private Script script;
+
+	@NotNull
+	@Builder.Default
+	private LocalDateTime timestamp = LocalDateTime.now();
+
+	private String autaSubmissionId;
+
+	@NotNull
+	@Builder.Default
+	@ToString.Exclude
+	@OneToMany(mappedBy = "result")
+	private List<ScriptFeedback> feedback = new ArrayList<>();
+
+	public void setStatus(ScriptStatus status) {
+		super.setStatus(status);
+		this.timestamp = LocalDateTime.now();
+	}
+
+}
diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java b/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java
index bc969f31..8bfcef8e 100644
--- a/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java
+++ b/src/main/java/nl/tudelft/submit/model/script/ScriptTrain.java
@@ -21,26 +21,45 @@ import java.util.ArrayList;
 import java.util.List;
 
 import javax.persistence.*;
+import javax.validation.constraints.NotNull;
 
 import lombok.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
 import nl.tudelft.submit.model.Version;
+import nl.tudelft.submit.model.grading.GradePart;
 
 @Data
 @Entity
 @Builder
-@AllArgsConstructor
 @NoArgsConstructor
+@AllArgsConstructor
 public class ScriptTrain {
 
 	@Id
 	@GeneratedValue(strategy = GenerationType.SEQUENCE)
 	private Long id;
 
+	private String formula;
+
+	@NotNull
+	@Builder.Default
+	private GradeScheme gradeScheme = GradeScheme.DUTCH_GRADE;
+
+	@Embedded
+	private GradePart gradePart;
+
 	@OneToOne
 	private Version version;
 
-	@Builder.Default
 	@OneToMany(mappedBy = "train")
+	@Builder.Default
+	@ToString.Exclude
 	@EqualsAndHashCode.Exclude
 	private List<ScriptWagon> wagons = new ArrayList<>();
+
+	@Builder.Default
+	@ToString.Exclude
+	@EqualsAndHashCode.Exclude
+	@OneToMany(mappedBy = "train")
+	private List<ScriptTrainResult> results = new ArrayList<>();
 }
diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java b/src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java
new file mode 100644
index 00000000..b96c6e3d
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/model/script/ScriptTrainResult.java
@@ -0,0 +1,40 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.model.script;
+
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.validation.constraints.NotNull;
+
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+
+@Data
+@Entity
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class ScriptTrainResult extends AbstractScriptResult {
+
+	@NotNull
+	@ManyToOne
+	private ScriptTrain train;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java b/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java
index c3eaa150..10f7f76e 100644
--- a/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java
+++ b/src/main/java/nl/tudelft/submit/model/script/ScriptWagon.java
@@ -21,8 +21,12 @@ import java.util.ArrayList;
 import java.util.List;
 
 import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 
 import lombok.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
+import nl.tudelft.submit.model.grading.GradePart;
 
 @Data
 @Entity
@@ -35,14 +39,34 @@ public class ScriptWagon {
 	@GeneratedValue(strategy = GenerationType.SEQUENCE)
 	private Long id;
 
+	@NotBlank
 	private String name;
 
+	private String formula;
+
+	@NotNull
+	@Builder.Default
+	private GradeScheme gradeScheme = GradeScheme.DUTCH_GRADE;
+
+	@NotNull
+	@Embedded
+	@Builder.Default
+	private GradePart gradePart = new GradePart();
+
+	@ManyToOne
+	@ToString.Exclude
+	private ScriptTrain train;
+
 	@OneToMany(mappedBy = "wagon")
 	@Builder.Default
+	@ToString.Exclude
 	@EqualsAndHashCode.Exclude
 	private List<Script> scripts = new ArrayList<>();
 
-	@ManyToOne
+	@Builder.Default
 	@ToString.Exclude
-	private ScriptTrain train;
+	@EqualsAndHashCode.Exclude
+	@OneToMany(mappedBy = "wagon")
+	private List<ScriptWagonResult> results = new ArrayList<>();
+
 }
diff --git a/src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java b/src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java
new file mode 100644
index 00000000..17a980f8
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/model/script/ScriptWagonResult.java
@@ -0,0 +1,40 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.model.script;
+
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.validation.constraints.NotNull;
+
+import lombok.*;
+import lombok.experimental.SuperBuilder;
+
+@Data
+@Entity
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class ScriptWagonResult extends AbstractScriptResult {
+
+	@NotNull
+	@ManyToOne
+	private ScriptWagon wagon;
+
+}
diff --git a/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java b/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java
index 79debd59..2e5e86e7 100644
--- a/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java
+++ b/src/main/java/nl/tudelft/submit/model/script/SubmissionKey.java
@@ -47,9 +47,6 @@ public class SubmissionKey {
 	@NotNull
 	private String key;
 
-	@NotNull
-	private Boolean canGrade;
-
 	@NotNull
 	private LocalDateTime expires;
 
diff --git a/src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java
new file mode 100644
index 00000000..0443dfcf
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/repository/script/AbstractScriptResultRepository.java
@@ -0,0 +1,46 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.repository.script;
+
+import java.util.List;
+
+import nl.tudelft.submit.enums.ScriptStatus;
+import nl.tudelft.submit.model.script.AbstractScriptResult;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.repository.NoRepositoryBean;
+import org.springframework.data.rest.webmvc.ResourceNotFoundException;
+
+@NoRepositoryBean
+public interface AbstractScriptResultRepository<R extends AbstractScriptResult>
+		extends JpaRepository<R, Long> {
+
+	/**
+	 * Finds the Result by id or throws a ResourceNotFoundException.
+	 *
+	 * @param  id the id of the script
+	 * @return    the Result object
+	 */
+	default R findByIdOrThrow(Long id) {
+		return findById(id)
+				.orElseThrow(() -> new ResourceNotFoundException("Result was not found: " + id));
+	}
+
+	List<R> findAllByStatus(ScriptStatus status);
+
+}
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java
new file mode 100644
index 00000000..4f455e3c
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptFeedbackRepository.java
@@ -0,0 +1,36 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.repository.script;
+
+import java.util.List;
+
+import nl.tudelft.submit.model.script.ScriptFeedback;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface ScriptFeedbackRepository extends JpaRepository<ScriptFeedback, Long> {
+
+	List<ScriptFeedback> findAllByResultId(Long resultId);
+
+	default List<ScriptFeedback> findAllByTrainId(Long trainId) {
+		return findAllByResultScriptWagonTrainId(trainId);
+	}
+
+	List<ScriptFeedback> findAllByResultScriptWagonTrainId(Long trainId);
+
+}
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java
index 5f5fe576..d92f7fa2 100644
--- a/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptRepository.java
@@ -17,6 +17,7 @@
  */
 package nl.tudelft.submit.repository.script;
 
+import java.util.List;
 import java.util.Optional;
 
 import nl.tudelft.submit.model.script.Script;
@@ -39,4 +40,6 @@ public interface ScriptRepository extends JpaRepository<Script, Long> {
 
 	Optional<Script> findById(Long id);
 
+	List<Script> findAllByWagonId(Long id);
+
 }
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java
new file mode 100644
index 00000000..c699370d
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptResultRepository.java
@@ -0,0 +1,32 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.repository.script;
+
+import java.util.List;
+
+import nl.tudelft.submit.model.script.ScriptResult;
+
+public interface ScriptResultRepository extends AbstractScriptResultRepository<ScriptResult> {
+
+	ScriptResult findByScriptIdAndSubmissionId(Long scriptId, Long submissionId);
+
+	List<ScriptResult> findAllByScriptId(Long scriptId);
+
+	List<ScriptResult> findAllByScriptWagonIdAndSubmissionId(Long wagonId, Long submissionId);
+
+}
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java
new file mode 100644
index 00000000..3d37ecd9
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptTrainResultRepository.java
@@ -0,0 +1,30 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.repository.script;
+
+import java.util.List;
+
+import nl.tudelft.submit.model.script.ScriptTrainResult;
+
+public interface ScriptTrainResultRepository extends AbstractScriptResultRepository<ScriptTrainResult> {
+
+	List<ScriptTrainResult> findAllByTrainId(Long trainId);
+
+	ScriptTrainResult findByTrainIdAndSubmissionId(Long trainId, Long submissionId);
+
+}
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java
index 8a268a4a..18bdb253 100644
--- a/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonRepository.java
@@ -17,6 +17,8 @@
  */
 package nl.tudelft.submit.repository.script;
 
+import java.util.List;
+
 import nl.tudelft.submit.model.script.ScriptWagon;
 
 import org.springframework.data.jpa.repository.JpaRepository;
@@ -35,4 +37,6 @@ public interface ScriptWagonRepository extends JpaRepository<ScriptWagon, Long>
 				.orElseThrow(() -> new ResourceNotFoundException("ScriptWagonId was not found: " + id));
 	}
 
+	List<ScriptWagon> findAllByTrainId(Long trainId);
+
 }
diff --git a/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java
new file mode 100644
index 00000000..4c847620
--- /dev/null
+++ b/src/main/java/nl/tudelft/submit/repository/script/ScriptWagonResultRepository.java
@@ -0,0 +1,32 @@
+/*
+ * Submit
+ * Copyright (C) 2020 - Delft University of Technology
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+package nl.tudelft.submit.repository.script;
+
+import java.util.List;
+
+import nl.tudelft.submit.model.script.ScriptWagonResult;
+
+public interface ScriptWagonResultRepository extends AbstractScriptResultRepository<ScriptWagonResult> {
+
+	List<ScriptWagonResult> findAllByWagonId(Long wagonId);
+
+	ScriptWagonResult findByWagonIdAndSubmissionId(Long wagonId, Long submissionId);
+
+	List<ScriptWagonResult> findAllByWagonTrainIdAndSubmissionId(Long wagonId, Long submissionId);
+
+}
diff --git a/src/main/java/nl/tudelft/submit/security/AuthorizationService.java b/src/main/java/nl/tudelft/submit/security/AuthorizationService.java
index a4cc6798..a1ea8dd1 100644
--- a/src/main/java/nl/tudelft/submit/security/AuthorizationService.java
+++ b/src/main/java/nl/tudelft/submit/security/AuthorizationService.java
@@ -27,7 +27,7 @@ import nl.tudelft.labracore.lib.security.LabradorUserDetails;
 import nl.tudelft.labracore.lib.security.user.DefaultRole;
 import nl.tudelft.labracore.lib.security.user.Person;
 import nl.tudelft.submit.cache.*;
-import nl.tudelft.submit.dto.view.FeedbackViewDTO;
+import nl.tudelft.submit.model.Feedback;
 import nl.tudelft.submit.model.script.SubmissionKey;
 import nl.tudelft.submit.repository.SubmissionKeyRepository;
 import nl.tudelft.submit.repository.VersionRepository;
@@ -976,6 +976,17 @@ public class AuthorizationService {
 
 	// region Submission
 
+	/**
+	 * Checks whether the authenticated user can view a submission.
+	 *
+	 * @param  submissionId The id of the submission
+	 * @return              True iff the user can view the submission
+	 */
+	public boolean canViewSubmission(Long submissionId) {
+		Long groupId = submissionCache.getOrThrow(submissionId).getGroup().getId();
+		return canViewGroup(groupId);
+	}
+
 	/**
 	 * Checks whether the authenticated user can resubmit a submission.
 	 *
@@ -1130,7 +1141,7 @@ public class AuthorizationService {
 	 * @param  feedback The feedback to view
 	 * @return          True iff the user can view the feedback
 	 */
-	public boolean canViewGroupFeedback(Long groupId, FeedbackViewDTO feedback) {
+	public boolean canViewGroupFeedback(Long groupId, Feedback feedback) {
 		if (feedback.getVisibleFor() == null) {
 			return true;
 		}
diff --git a/src/main/java/nl/tudelft/submit/service/FormulaService.java b/src/main/java/nl/tudelft/submit/service/FormulaService.java
index 94695033..50b730fa 100644
--- a/src/main/java/nl/tudelft/submit/service/FormulaService.java
+++ b/src/main/java/nl/tudelft/submit/service/FormulaService.java
@@ -176,9 +176,9 @@ public class FormulaService {
 	 * @param  formula The formula to parse
 	 * @return         The resulting expression
 	 */
-	public Expression parseFormula(GradingFormula<?> formula) throws InvalidFormulaException {
+	public Expression parseFormula(String formula) throws InvalidFormulaException {
 		Result<Token, Expression> result = parser
-				.parse(new TokenInput(tokenise(input(formula.getFormula()))));
+				.parse(new TokenInput(tokenise(input(formula))));
 		if (!result.isSuccess()) {
 			throw new InvalidFormulaException(formula);
 		}
diff --git a/src/main/java/nl/tudelft/submit/service/GradeService.java b/src/main/java/nl/tudelft/submit/service/GradeService.java
index f514d5ad..594d691e 100644
--- a/src/main/java/nl/tudelft/submit/service/GradeService.java
+++ b/src/main/java/nl/tudelft/submit/service/GradeService.java
@@ -116,13 +116,15 @@ public class GradeService {
 	 * Executes a grading formula.
 	 *
 	 * @param  formula The formula to execute
+	 * @param  scheme  The grading scheme
+	 * @parma          variables The formula variables
 	 * @return         The resulting score
 	 */
-	public <E_ID extends Serializable> Optional<CalculatedScore> executeFormula(
-			GradingFormula<E_ID> formula, Map<String, Object> variables) {
+	public Optional<Integer> executeFormula(String formula, GradeScheme scheme,
+			Map<String, Object> variables) {
 		try {
 			Object result = formulaService.parseFormula(formula).execute(variables);
-			return Optional.ofNullable(convertToScore(formula, result));
+			return Optional.ofNullable(convertToScore(result, scheme));
 		} catch (InvalidFormulaException e) {
 			return Optional.empty();
 		} catch (Exception e) {
@@ -132,27 +134,24 @@ public class GradeService {
 	}
 
 	/**
-	 * Converts a grading formula and its result to a CalculatedScore.
+	 * Converts a grading result to a score.
 	 *
-	 * @param  formula The formula used to calculate the result
-	 * @param  result  The result to convert
-	 * @return         The calculated score object
+	 * @param result The result to convert
+	 * @param scheme The grading scheme
 	 */
-	public <E_ID extends Serializable, FORMULA extends GradingFormula<E_ID>> CalculatedScore convertToScore(
-			FORMULA formula, Object result) {
+	public Integer convertToScore(Object result, GradeScheme scheme) {
 		if (result == null) {
 			return null;
 		}
-		if ((result.equals("PASSED") || result.equals("FAILED"))
-				&& formula.getType() != GradeScheme.PASS_FAIL) {
+		if ((result.equals("PASSED") || result.equals("FAILED")) && scheme != GradeScheme.PASS_FAIL) {
 			return null;
 		}
 
-		Double score = result.equals("PASSED") ? 1.0
-				: result.equals("FAILED") ? 0.0
-						: result instanceof Integer ? ((Integer) result).doubleValue() : (Double) result;
-
-		return formula.calculateScore(score);
+		Integer res = result.equals("PASSED") ? 1
+				: result.equals("FAILED") ? 0
+						: result instanceof Integer ? (Integer) result
+								: (int) ((double) result * (scheme.getMax() / 10));
+		return scheme.getMin() <= res && scheme.getMax() >= res ? res : null;
 	}
 
 	/**
@@ -269,12 +268,15 @@ public class GradeService {
 		}
 
 		// Calculate grade
-		executeFormula(formula, variables).ifPresent(score -> {
-			score.setId(scoreId);
-			moduleScoreRepository.save((ModuleCalculatedScore) score);
-
-			sendUpdateNotification(module, person, score);
-		});
+		//		executeFormula(formula.getFormula(), formula.getType(), variables).ifPresent(score -> {
+		//			moduleScoreRepository.save(ModuleCalculatedScore.builder()
+		//                            .id(scoreId)
+		//                            .score(score)
+		//                            .type(formula.getType())
+		//                    .build());
+		//
+		//			sendUpdateNotification(module, person, score);
+		//		}); TODO
 	}
 
 	/**
@@ -406,9 +408,11 @@ public class GradeService {
 				.max(Comparator.comparing(SubmissionDetailsDTO::getSubmissionTime));
 		if (latest.isPresent() && !latest.get().getGrades().isEmpty()) {
 			GradeSummaryDTO score = getScoreToLoad(latest.get());
-			variables.put(varName, toFormulaVariable(score));
+			variables.put(varName,
+					toFormulaVariable(score.getScore(), GradeScheme.valueOf(score.getScheme().name())));
 			variables.put(varName + "_isScript", score.getIsScriptGraded());
-			variables.put(varName + "_noScript", score.getIsScriptGraded() ? 0.0 : toFormulaVariable(score));
+			variables.put(varName + "_noScript", score.getIsScriptGraded() ? 0.0
+					: toFormulaVariable(score.getScore(), GradeScheme.valueOf(score.getScheme().name())));
 		} else {
 			variables.put(varName, 0.0);
 			variables.put(varName + "_isScript", false);
@@ -484,10 +488,10 @@ public class GradeService {
 					moduleScoreRepository.findById(module.getId(), personId)
 							.map(CalculatedScore::getScore).orElse(0.0));
 		}
-		executeFormula(formula, variables).ifPresent(score -> {
-			score.setId(new CalculatedScore.Id(editionId.getId(), personId.getId()));
-			editionScoreRepository.save((EditionCalculatedScore) score);
-		});
+		//		executeFormula(formula, variables).ifPresent(score -> {
+		//			score.setId(new CalculatedScore.Id(editionId.getId(), personId.getId()));
+		//			editionScoreRepository.save((EditionCalculatedScore) score);
+		//		});
 	}
 
 	/**
@@ -585,6 +589,26 @@ public class GradeService {
 		return score.getScore() >= edition.getPassingScore();
 	}
 
+	/**
+	 * Returns the display string for a score.
+	 *
+	 * @param  score  The score to display
+	 * @param  scheme The grade scheme
+	 * @return        The formatted score
+	 */
+	public String getScoreDisplayString(Integer score, GradeScheme scheme) {
+		switch (scheme) {
+			case PASS_FAIL:
+				return score == 0 ? "Failed" : "Passed";
+			case DUTCH_GRADE:
+				return GRADE_FORMAT_10.format(score / 10.0);
+			case DUTCH_GRADE_1000:
+				return GRADE_FORMAT_100.format(score / 100.0);
+			default:
+				return Integer.toString(score);
+		}
+	}
+
 	/**
 	 * Returns the display string for a score.
 	 *
@@ -643,16 +667,19 @@ public class GradeService {
 	 *
 	 * @return The value of this score as usable object for a variable
 	 */
-	public Object toFormulaVariable(GradeSummaryDTO score) {
-		switch (score.getScheme()) {
+	public Object toFormulaVariable(Integer score, GradeScheme scheme) {
+		if (score == null || score == null) {
+			return 0.0;
+		}
+		switch (scheme) {
 			case PASS_FAIL:
-				return score.getScore() == 0 ? 0.0 : 1.0;
+				return score == 0 ? 0.0 : 1.0;
 			case DUTCH_GRADE:
-				return score.getScore() / 10.0;
+				return score / 10.0;
 			case DUTCH_GRADE_1000:
-				return score.getScore() / 100.0;
+				return score / 100.0;
 			default:
-				return score.getScore().doubleValue();
+				return score.doubleValue();
 		}
 	}
 
diff --git a/src/main/java/nl/tudelft/submit/service/ScriptService.java b/src/main/java/nl/tudelft/submit/service/ScriptService.java
index 921b8570..f16fa6d2 100644
--- a/src/main/java/nl/tudelft/submit/service/ScriptService.java
+++ b/src/main/java/nl/tudelft/submit/service/ScriptService.java
@@ -20,7 +20,6 @@ package nl.tudelft.submit.service;
 import java.io.File;
 import java.time.LocalDateTime;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 import javax.transaction.Transactional;
@@ -29,6 +28,7 @@ import nl.tudelft.labracore.api.AssignmentControllerApi;
 import nl.tudelft.labracore.api.StudentGroupControllerApi;
 import nl.tudelft.labracore.api.SubmissionControllerApi;
 import nl.tudelft.labracore.api.dto.*;
+import nl.tudelft.labracore.lib.api.GradeScheme;
 import nl.tudelft.librador.dto.view.View;
 import nl.tudelft.submit.dto.create.FeedbackCreateDTO;
 import nl.tudelft.submit.dto.create.NotificationCreateDTO;
@@ -41,17 +41,15 @@ import nl.tudelft.submit.dto.id.VersionIdDTO;
 import nl.tudelft.submit.dto.patch.script.ScriptPatchDTO;
 import nl.tudelft.submit.dto.patch.script.ScriptTrainPatchDTO;
 import nl.tudelft.submit.dto.patch.script.ScriptWagonPatchDTO;
-import nl.tudelft.submit.dto.view.script.ScriptWagonViewDTO;
+import nl.tudelft.submit.dto.view.script.ScriptTrainResultViewDTO;
 import nl.tudelft.submit.enums.NotificationPreference;
+import nl.tudelft.submit.enums.ScriptStatus;
 import nl.tudelft.submit.model.Version;
-import nl.tudelft.submit.model.script.Script;
-import nl.tudelft.submit.model.script.ScriptTrain;
-import nl.tudelft.submit.model.script.ScriptWagon;
-import nl.tudelft.submit.model.script.SubmissionKey;
+import nl.tudelft.submit.model.labracore.SubmitSubmission;
+import nl.tudelft.submit.model.script.*;
 import nl.tudelft.submit.repository.SubmissionKeyRepository;
-import nl.tudelft.submit.repository.script.ScriptRepository;
-import nl.tudelft.submit.repository.script.ScriptTrainRepository;
-import nl.tudelft.submit.repository.script.ScriptWagonRepository;
+import nl.tudelft.submit.repository.labracore.SubmitSubmissionRepository;
+import nl.tudelft.submit.repository.script.*;
 
 import org.modelmapper.ModelMapper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -60,6 +58,7 @@ import org.springframework.core.io.FileSystemResource;
 import org.springframework.core.io.Resource;
 import org.springframework.http.*;
 import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
@@ -103,9 +102,24 @@ public class ScriptService {
 	@Autowired
 	private ScriptWagonRepository scriptWagonRepository;
 
+	@Autowired
+	private ScriptTrainResultRepository scriptTrainResultRepository;
+
+	@Autowired
+	private ScriptWagonResultRepository scriptWagonResultRepository;
+
+	@Autowired
+	private ScriptResultRepository scriptResultRepository;
+
 	@Autowired
 	private SubmissionKeyRepository submissionKeyRepository;
 
+	@Autowired
+	private SubmitSubmissionRepository submissionRepository;
+
+	@Autowired
+	private ScriptFeedbackRepository scriptFeedbackRepository;
+
 	@Autowired
 	private SubmissionService submissionService;
 
@@ -133,6 +147,9 @@ public class ScriptService {
 	@Autowired
 	private NotificationService notificationService;
 
+	@Autowired
+	private FormulaService formulaService;
+
 	@Autowired
 	private EmailService emailService;
 
@@ -191,41 +208,32 @@ public class ScriptService {
 	 * @param  autaToken        A valid access token
 	 * @param  autaAssignmentId The id of the assignment in AuTA
 	 * @param  autaSubmissionId The id of the submission in AuTA
-	 * @return                  The feedback from AuTA
+	 * @return                  The feedback from AuTA, if it is done
 	 */
-	public AutaFeedback pollAuta(String autaToken, String autaAssignmentId, String autaSubmissionId) {
-		AutaFeedback feedback;
-		int secondsPassed = 0;
-		do {
-			try {
-				Thread.sleep(10000L);
-				secondsPassed += 10;
-			} catch (InterruptedException e) {
-				e.printStackTrace();
-			}
-
-			HttpHeaders headers = new HttpHeaders();
-			headers.set("Auth-Token", autaToken);
+	public Optional<AutaFeedback> pollAuta(String autaToken, String autaAssignmentId,
+			String autaSubmissionId) {
+		HttpHeaders headers = new HttpHeaders();
+		headers.set("Auth-Token", autaToken);
 
-			HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(
-					new LinkedMultiValueMap<>(), headers);
+		HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(
+				new LinkedMultiValueMap<>(), headers);
 
-			ResponseEntity<AutaFeedback> response = restTemplate.exchange(
-					autaUrl + "api/v1/assignment/{aid}/submission/{sid}", HttpMethod.GET, requestEntity,
-					AutaFeedback.class, autaAssignmentId, autaSubmissionId);
-			feedback = response.getBody();
-		} while (feedback.getPipelineLog().getReportDone() == null && secondsPassed <= timeout);
+		ResponseEntity<AutaFeedback> response = restTemplate.exchange(
+				autaUrl + "api/v1/assignment/{aid}/submission/{sid}", HttpMethod.GET, requestEntity,
+				AutaFeedback.class, autaAssignmentId, autaSubmissionId);
+		AutaFeedback feedback = response.getBody();
 
-		return feedback;
+		return feedback.getPipelineLog().getReportDone() == null ? Optional.of(feedback) : Optional.empty();
 	}
 
 	/**
 	 * Saves feedback from AuTA in the database as feedback and grade.
 	 *
+	 * @param scriptId     The id of the script
 	 * @param submissionId The id of the submission
 	 * @param autaFeedback The feedback from AuTA
 	 */
-	public boolean saveAutaFeedback(Long submissionId, AutaFeedback autaFeedback, boolean canGrade) {
+	public boolean saveAutaFeedback(Long scriptId, Long submissionId, AutaFeedback autaFeedback) {
 		List<AutaResults> results = getAutaResults(autaFeedback.getResults());
 		List<AutaReport> reports = results.stream().flatMap(r -> r.getReports().stream())
 				.collect(Collectors.toList());
@@ -235,44 +243,33 @@ public class ScriptService {
 			return false;
 		}
 
-		StringBuilder feedback = new StringBuilder();
+		StringBuilder feedbackBuilder = new StringBuilder();
 		boolean verdictPassed = true;
 		for (AutaReport report : reports) {
 			if (report.getVerdict().equals("FAIL")) {
 				verdictPassed = false;
 			}
 			for (AutaNote note : report.getNotes().getNotes()) {
-				feedback.append(note.getMessage()).append("\n");
+				feedbackBuilder.append(note.getMessage()).append("\n");
 			}
 		}
 
-		LocalDateTime now = LocalDateTime.now();
-
-		Long gradeId = canGrade ? gradeService.addGrade(new GradeCreateDTO()
-				.isScriptGraded(true)
-				.scheme(GradeCreateDTO.SchemeEnum.PASS_FAIL)
-				.gradedAt(now)
-				.score(verdictPassed ? 1 : 0)
-				.submission(new SubmissionIdDTO().id(submissionId))) : null;
+		ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(scriptId, submissionId);
+		String feedback = feedbackBuilder.toString();
 
-		String textualFeedback = feedback.toString();
-		if (!textualFeedback.isBlank()) {
-			feedbackService.addFeedback(FeedbackCreateDTO.builder()
-					.isScriptFeedback(true)
+		if (!feedback.isBlank()) {
+			ScriptFeedback f = scriptFeedbackRepository.save(ScriptFeedback.builder()
+					.feedback(feedback)
 					.visibleFor(RoleSummaryDTO.TypeEnum.STUDENT)
-					.authorId(-1L)
-					.textualFeedback(textualFeedback)
-					.timestamp(now)
-					.submissionId(submissionId)
-					.gradeId(gradeId)
+					.result(result)
 					.build());
+			result.getFeedback().add(f);
 		}
 
-		if (canGrade) {
-			sendUpdateNotification(submissionId, gradeId);
-			submissionService.setPendingState(submissionId, false);
-			gradeService.updateGradesAfterGradedSubmission(submissionId);
-		}
+		result.setScore(verdictPassed ? 1 : 0);
+
+		result.setStatus(ScriptStatus.DONE);
+		scriptResultRepository.save(result);
 
 		return verdictPassed;
 	}
@@ -342,18 +339,18 @@ public class ScriptService {
 	/**
 	 * Sends a submission to AuTA.
 	 *
-	 * @param  submissionId     The id of the submission
-	 * @param  autaAssignmentId The id of the assignment in AuTA
-	 * @return                  A future with the feedback
+	 * @param submissionId The id of the submission
+	 * @param script       The script to run
 	 */
-	@Async
-	public CompletableFuture<AutaFeedback> runAutaScript(Long submissionId, String autaAssignmentId) {
+	@Transactional
+	public void runAutaScript(Long submissionId, Script script) {
 		String token = loginToAuta();
-		String autaSubmissionId = uploadSubmissionToAuta(token, autaAssignmentId, submissionId);
-
-		AutaFeedback feedback = pollAuta(token, autaAssignmentId, autaSubmissionId);
+		String autaSubmissionId = uploadSubmissionToAuta(token, script.getLink().substring(5), submissionId);
 
-		return CompletableFuture.completedFuture(feedback);
+		ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(script.getId(),
+				submissionId);
+		result.setAutaSubmissionId(autaSubmissionId);
+		scriptResultRepository.save(result);
 	}
 
 	// endregion
@@ -382,6 +379,19 @@ public class ScriptService {
 		return scriptTrainRepository.findByIdOrThrow(id);
 	}
 
+	/**
+	 * Retrieves a script train result from the database.
+	 *
+	 * @param  trainId      The id of the script train.
+	 * @param  submissionId The id of the submission.
+	 * @return              The script train result view.
+	 */
+	@Transactional
+	public ScriptTrainResultViewDTO getScriptTrainResult(Long trainId, Long submissionId) {
+		return View.convert(scriptTrainResultRepository.findByTrainIdAndSubmissionId(trainId, submissionId),
+				ScriptTrainResultViewDTO.class);
+	}
+
 	/**
 	 * Retrieves a scriptTrain from the database or creates a new scriptTrain.
 	 *
@@ -407,8 +417,8 @@ public class ScriptService {
 	 * @param  id The id of the scriptWagon.
 	 * @return    The scriptWagon in View object.
 	 */
-	public ScriptWagonViewDTO getScriptWagonById(Long id) {
-		return View.convert(scriptWagonRepository.findByIdOrThrow(id), ScriptWagonViewDTO.class);
+	public ScriptWagon getScriptWagonById(Long id) {
+		return scriptWagonRepository.findByIdOrThrow(id);
 	}
 
 	/**
@@ -432,18 +442,6 @@ public class ScriptService {
 		return scriptTrainRepository.findByIdOrThrow(trainId).getVersion().getAssignmentId();
 	}
 
-	/**
-	 * Checks whether a submission id can be graded by a script.
-	 *
-	 * @param  submissionId The id of the submission
-	 * @param  scriptId     The id of the script
-	 * @return              True iff the submission can be graded
-	 */
-	public boolean canKeyBeUsedForGrading(Long submissionId, Long scriptId) {
-		return submissionKeyRepository.findBySubmissionIdAndScriptIdOrThrow(submissionId, scriptId)
-				.getCanGrade();
-	}
-
 	// endregion
 
 	// region Add methods
@@ -581,56 +579,71 @@ public class ScriptService {
 	 * @param submissionId The id of the submission
 	 */
 	@Async
+	@Transactional
 	public void runScriptTrain(Long trainId, Long submissionId) {
-		submissionService.setPendingState(submissionId, true);
+		SubmitSubmission submission = submissionRepository.findByIdOrThrow(submissionId);
+		submission.setScriptPending(true);
+
 		ScriptTrain train = getScriptTrainById(trainId);
-		Iterator<ScriptWagon> wagons = train.getWagons().iterator();
-		while (wagons.hasNext()) {
-			ScriptWagon wagon = wagons.next();
-			if (!runScriptWagon(wagon, submissionId, !wagons.hasNext()))
-				break;
+		scriptTrainResultRepository
+				.save(ScriptTrainResult.builder().train(train).submission(submission).build());
+		for (ScriptWagon wagon : train.getWagons()) {
+			initScriptResultsInWagon(wagon, submission);
+		}
+	}
+
+	/**
+	 * Inits the results for a script wagon.
+	 *
+	 * @param wagon      The wagon
+	 * @param submission The submission the wagon is for
+	 */
+	private void initScriptResultsInWagon(ScriptWagon wagon, SubmitSubmission submission) {
+		scriptWagonResultRepository
+				.save(ScriptWagonResult.builder().wagon(wagon).submission(submission).build());
+		for (Script script : wagon.getScripts()) {
+			scriptResultRepository.save(ScriptResult.builder().script(script).submission(submission).build());
 		}
 	}
 
 	/**
 	 * Runs all scripts in a wagon.
 	 *
-	 * @param  wagon        The wagon
-	 * @param  submissionId The id of the submission
-	 * @param  isLast       Whether the wagon is the last in the train
-	 * @return              TODO Whether the scripts in the wagon are passing
+	 * @param wagon        The wagon
+	 * @param submissionId The id of the submission
 	 */
-	public boolean runScriptWagon(ScriptWagon wagon, Long submissionId, boolean isLast) {
-		Iterator<Script> scripts = wagon.getScripts().iterator();
-		while (scripts.hasNext()) {
-			Script script = scripts.next();
-			runScript(script, submissionId, isLast && !scripts.hasNext());
+	@Transactional
+	public void runScriptWagon(ScriptWagon wagon, Long submissionId) {
+		for (Script script : scriptRepository.findAllByWagonId(wagon.getId())) {
+			runScript(script, submissionId);
 		}
-		return true;
+		ScriptWagonResult result = scriptWagonResultRepository.findByWagonIdAndSubmissionId(wagon.getId(),
+				submissionId);
+		result.setStatus(ScriptStatus.RUNNING);
+		scriptWagonResultRepository.save(result);
 	}
 
 	/**
 	 * Sends a submission to a script.
 	 *
-	 * @param  script       The script
-	 * @param  submissionId The id of the submission
-	 * @param  isLast       Whether the script is the last in the train
-	 * @return              True iff the feedback is passing (only works for AuTA scripts atm).
+	 * @param script       The script
+	 * @param submissionId The id of the submission
 	 */
-	private boolean runScript(Script script, Long submissionId, boolean isLast) {
+	@Transactional
+	public void runScript(Script script, Long submissionId) {
 		if (script.isAutaScript()) {
-			CompletableFuture<AutaFeedback> feedback = runAutaScript(submissionId,
-					script.getLink().substring(5));
-			return feedback
-					.thenApply(f -> saveAutaFeedback(submissionId, f, isLast))
-					.join();
+			runAutaScript(submissionId, script);
 		} else {
 			List<File> files = fileService.getAllFiles(storageDir + "/submissions", submissionId);
 			Resource resource = new FileSystemResource(files.get(0));
 			sendFileToURL(resource, script.getLink(), submissionId, script.getId(),
-					generateKey(submissionId, script.getId(), isLast), isLast);
-			return true;
+					generateKey(submissionId, script.getId()), script.getGradeScheme());
 		}
+
+		ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(script.getId(),
+				submissionId);
+		result.setStatus(ScriptStatus.RUNNING);
+		scriptResultRepository.save(result);
 	}
 
 	/**
@@ -638,10 +651,9 @@ public class ScriptService {
 	 *
 	 * @param  submissionId The id of the submission
 	 * @param  scriptId     The id of the script
-	 * @param  grade        Whether the script is allowed to grade the submission
 	 * @return              The key
 	 */
-	private String generateKey(Long submissionId, Long scriptId, boolean grade) {
+	private String generateKey(Long submissionId, Long scriptId) {
 		String keyHash = Hashing.sha512()
 				.hashString(scriptId + Long.toString(System.currentTimeMillis()) + submissionId,
 						Charsets.UTF_8)
@@ -650,7 +662,6 @@ public class ScriptService {
 				.submissionId(submissionId)
 				.key(keyHash)
 				.scriptId(scriptId)
-				.canGrade(grade)
 				.expires(LocalDateTime.now().plusHours(1))
 				.build();
 		submissionKeyRepository.findBySubmissionIdAndScriptId(submissionId, scriptId)
@@ -661,11 +672,15 @@ public class ScriptService {
 	/**
 	 * Sends a file to a URL.
 	 *
-	 * @param file The file to send
-	 * @param url  The URL to send to
+	 * @param file         The file to send
+	 * @param url          The URL to send to
+	 * @param submissionId The id of the submission
+	 * @param scriptId     The id of the script
+	 * @param key          The key
+	 * @param gradeScheme  The scheme to grade with
 	 */
 	private void sendFileToURL(Resource file, String url, Long submissionId, Long scriptId, String key,
-			boolean grade) {
+			GradeScheme gradeScheme) {
 		HttpHeaders headers = new HttpHeaders();
 		headers.setContentType(MediaType.MULTIPART_FORM_DATA);
 
@@ -674,7 +689,7 @@ public class ScriptService {
 		body.add("submissionId", submissionId);
 		body.add("scriptId", scriptId);
 		body.add("key", key);
-		body.add("grade", grade);
+		body.add("gradeScheme", gradeScheme);
 
 		HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
 
@@ -686,37 +701,237 @@ public class ScriptService {
 	 * Adds the feedback from a script.
 	 *
 	 * @param feedback The feedback to add
-	 * @param grade    Whether the feedback should be used for a grade
 	 */
-	public void addScriptFeedback(ScriptFeedbackReceiveDTO feedback, boolean grade) {
-		LocalDateTime now = LocalDateTime.now();
-
-		FeedbackCreateDTO feedbackCreate = FeedbackCreateDTO.builder()
-				.submissionId(feedback.getSubmissionId())
-				.isScriptFeedback(true)
-				.textualFeedback(feedback.getTextualFeedback())
-				.authorId(feedback.getScriptId())
-				.timestamp(now)
-				.visibleFor(feedback.getVisibleFor())
-				.build();
+	@Transactional
+	public void addScriptFeedback(ScriptFeedbackReceiveDTO feedback) {
+		ScriptResult result = scriptResultRepository.findByScriptIdAndSubmissionId(feedback.getScriptId(),
+				feedback.getSubmissionId());
+
+		if (feedback.getTextualFeedback() != null && !feedback.getTextualFeedback().isBlank()) {
+			ScriptFeedback f = scriptFeedbackRepository.save(ScriptFeedback.builder()
+					.feedback(feedback.getTextualFeedback())
+					.visibleFor(feedback.getVisibleFor())
+					.result(result)
+					.build());
+			result.getFeedback().add(f);
+		}
 
-		if (grade) {
-			GradeCreateDTO gradeCreate = new GradeCreateDTO()
-					.gradedAt(now)
-					.isScriptGraded(true)
-					.scheme(feedback.getScoreType())
-					.submission(new SubmissionIdDTO().id(feedback.getSubmissionId()))
-					.score(feedback.getScore());
-			Long gradeId = gradeService.addGrade(gradeCreate);
-			sendUpdateNotification(feedback.getSubmissionId(), gradeId);
-			gradeService.updateGradesAfterGradedSubmission(feedback.getSubmissionId());
-			feedbackCreate.setGradeId(gradeId);
-			submissionService.setPendingState(feedback.getSubmissionId(), false);
+		if (result.getScore() == null && feedback.getScore() != null) {
+			result.setScore(feedback.getScore());
 		}
 
-		feedbackService.addFeedback(feedbackCreate);
+		result.setStatus(ScriptStatus.DONE);
 	}
 
 	// endregion
 
+	// region Scheduled train management
+
+	@Scheduled(fixedRate = 60000)
+	public void checkScriptStatuses() {
+		checkScriptsForTimeouts();
+
+		finishDoneWagons();
+		finishDoneTrains();
+
+		startWaitingTrains();
+		startWaitingWagons();
+
+		processFinishedTrains();
+
+		System.out.println(scriptTrainResultRepository.findAll());
+		System.out.println(scriptWagonResultRepository.findAll());
+		System.out.println(scriptResultRepository.findAll());
+	}
+
+	@Transactional
+	protected void checkScriptsForTimeouts() {
+		List<ScriptResult> running = scriptResultRepository.findAllByStatus(ScriptStatus.RUNNING);
+
+		LocalDateTime timeoutBefore = LocalDateTime.now().minusSeconds(timeout);
+		List<ScriptResult> timedOut = running.stream().filter(r -> r.getTimestamp().isBefore(timeoutBefore))
+				.collect(Collectors.toList());
+		for (ScriptResult r : timedOut) {
+			r.setStatus(ScriptStatus.FAILED);
+
+			if (r.getScript().getGradePart().getRequired()) {
+				ScriptWagonResult wagonRes = scriptWagonResultRepository.findByWagonIdAndSubmissionId(
+						r.getScript().getWagon().getId(), r.getSubmission().getId());
+				wagonRes.setStatus(ScriptStatus.FAILED);
+				scriptWagonResultRepository.save(wagonRes);
+
+				ScriptTrainResult trainRes = scriptTrainResultRepository.findByTrainIdAndSubmissionId(
+						r.getScript().getWagon().getTrain().getId(), r.getSubmission().getId());
+				trainRes.setStatus(ScriptStatus.FAILED);
+				scriptTrainResultRepository.save(trainRes);
+			}
+		}
+	}
+
+	@Transactional
+	protected void finishDoneWagons() {
+		List<ScriptWagonResult> running = scriptWagonResultRepository.findAllByStatus(ScriptStatus.RUNNING);
+		for (ScriptWagonResult wagon : running) {
+			List<ScriptResult> res = scriptResultRepository.findAllByScriptWagonIdAndSubmissionId(
+					wagon.getWagon().getId(), wagon.getSubmission().getId());
+			if (res.stream().allMatch(
+					r -> r.getStatus() == ScriptStatus.DONE || r.getStatus() == ScriptStatus.FAILED)) {
+				wagon.setStatus(ScriptStatus.DONE);
+
+				if (wagon.getWagon().getFormula() != null) {
+					Map<String, Object> variables = res.stream().collect(Collectors.toMap(
+							r -> gradeService.generateVariableName(r.getScript().getName()),
+							r -> gradeService.toFormulaVariable(r.getScore(),
+									r.getScript().getGradeScheme())));
+					gradeService
+							.executeFormula(wagon.getWagon().getFormula(), wagon.getWagon().getGradeScheme(),
+									variables)
+							.ifPresent(wagon::setScore);
+				} else if (wagon.getWagon().getGradeScheme() == GradeScheme.PASS_FAIL) {
+					boolean fail = res.stream()
+							.filter(r -> r.getScript().getGradeScheme() == GradeScheme.PASS_FAIL)
+							.filter(r -> r.getScript().getGradePart().getRequired())
+							.anyMatch(r -> r.getScore() != null && r.getScore() == 0);
+					wagon.setScore(fail ? 0 : 1);
+				} else {
+					Optional<Integer> weightedSum = res.stream()
+							.filter(r -> r.getScript().getGradeScheme() != GradeScheme.PASS_FAIL)
+							.map(r -> r.getScore() == null ? 0
+									: r.getScript().getGradePart().getWeight() * r.getScore())
+							.reduce(Integer::sum);
+					Optional<Integer> sumOfWeights = res.stream()
+							.filter(r -> r.getScript().getGradeScheme() != GradeScheme.PASS_FAIL)
+							.map(r -> r.getScript().getGradePart().getWeight())
+							.reduce(Integer::sum)
+							.filter(s -> s != 0);
+					weightedSum.flatMap(w -> sumOfWeights.map(s -> (int) Math.round((double) w / s)))
+							.ifPresent(wagon::setScore);
+				}
+
+				scriptWagonResultRepository.save(wagon);
+			}
+		}
+	}
+
+	@Transactional
+	protected void finishDoneTrains() {
+		List<ScriptTrainResult> running = scriptTrainResultRepository.findAllByStatus(ScriptStatus.RUNNING);
+		for (ScriptTrainResult train : running) {
+			List<ScriptWagonResult> res = scriptWagonResultRepository.findAllByWagonTrainIdAndSubmissionId(
+					train.getTrain().getId(), train.getSubmission().getId());
+			if (res.stream().allMatch(r -> r.getStatus() == ScriptStatus.DONE)) {
+				train.setStatus(ScriptStatus.DONE);
+
+				if (train.getTrain().getFormula() != null) {
+					Map<String, Object> variables = res.stream().collect(Collectors.toMap(
+							r -> gradeService.generateVariableName(r.getWagon().getName()),
+							r -> gradeService.toFormulaVariable(r.getScore(),
+									r.getWagon().getGradeScheme())));
+					gradeService
+							.executeFormula(train.getTrain().getFormula(), train.getTrain().getGradeScheme(),
+									variables)
+							.ifPresent(train::setScore);
+				} else if (train.getTrain().getGradeScheme() == GradeScheme.PASS_FAIL) {
+					boolean fail = res.stream()
+							.filter(r -> r.getWagon().getGradeScheme() == GradeScheme.PASS_FAIL)
+							.filter(r -> r.getWagon().getGradePart().getRequired())
+							.anyMatch(r -> r.getScore() != null && r.getScore() == 0);
+					train.setScore(fail ? 0 : 1);
+				} else {
+					Optional<Integer> weightedSum = res.stream()
+							.filter(r -> r.getWagon().getGradeScheme() != GradeScheme.PASS_FAIL)
+							.map(r -> r.getScore() == null ? 0
+									: r.getWagon().getGradePart().getWeight() * r.getScore())
+							.reduce(Integer::sum);
+					Optional<Integer> sumOfWeights = res.stream()
+							.filter(r -> r.getWagon().getGradeScheme() != GradeScheme.PASS_FAIL)
+							.map(r -> r.getWagon().getGradePart().getWeight())
+							.reduce(Integer::sum)
+							.filter(s -> s != 0);
+					weightedSum.flatMap(w -> sumOfWeights.map(s -> (int) Math.round((double) w / s)))
+							.ifPresent(train::setScore);
+				}
+
+				scriptTrainResultRepository.save(train);
+			}
+		}
+	}
+
+	@Transactional
+	protected void startWaitingTrains() {
+		List<ScriptTrainResult> waiting = scriptTrainResultRepository
+				.findAllByStatus(ScriptStatus.NOT_STARTED);
+		for (ScriptTrainResult train : waiting) {
+			List<ScriptWagon> wagons = scriptWagonRepository.findAllByTrainId(train.getTrain().getId());
+			if (!wagons.isEmpty()) {
+				runScriptWagon(wagons.get(0), train.getSubmission().getId());
+				train.setStatus(ScriptStatus.RUNNING);
+				scriptTrainResultRepository.save(train);
+			}
+		}
+	}
+
+	@Transactional
+	protected void startWaitingWagons() {
+		List<ScriptWagonResult> waiting = scriptWagonResultRepository
+				.findAllByStatus(ScriptStatus.NOT_STARTED);
+		for (ScriptWagonResult wagon : waiting) {
+			ScriptWagonResult prev = getPreviousWagon(wagon);
+			if (prev != null && prev.getStatus() == ScriptStatus.DONE && isNotRequiredOrPassing(prev)) {
+				runScriptWagon(wagon.getWagon(), wagon.getSubmission().getId());
+			}
+		}
+	}
+
+	private ScriptWagonResult getPreviousWagon(ScriptWagonResult wagon) {
+		List<ScriptWagon> wagons = scriptWagonRepository
+				.findAllByTrainId(wagon.getWagon().getTrain().getId());
+		int index = wagons.stream().map(ScriptWagon::getId).collect(Collectors.toList())
+				.indexOf(wagon.getWagon().getId());
+		if (index == 0) {
+			return null;
+		}
+		return scriptWagonResultRepository.findByWagonIdAndSubmissionId(wagons.get(index - 1).getId(),
+				wagon.getSubmission().getId());
+	}
+
+	private boolean isNotRequiredOrPassing(ScriptWagonResult wagon) {
+		return !wagon.getWagon().getGradePart().getRequired()
+				|| (wagon.getScore() != null && wagon.getWagon().getGradeScheme() != GradeScheme.PASS_FAIL)
+				|| Integer.valueOf(1).equals(wagon.getScore());
+	}
+
+	@Transactional
+	protected void processFinishedTrains() {
+		List<ScriptTrainResult> done = scriptTrainResultRepository.findAllByStatus(ScriptStatus.DONE);
+		for (ScriptTrainResult train : done) {
+			LocalDateTime now = LocalDateTime.now();
+
+			Long gradeId = null;
+			if (train.getScore() != null) {
+				gradeId = gradeService.addGrade(new GradeCreateDTO()
+						.score(train.getScore())
+						.scheme(GradeCreateDTO.SchemeEnum.valueOf(train.getTrain().getGradeScheme().name()))
+						.submission(new SubmissionIdDTO().id(train.getSubmission().getId()))
+						.gradedAt(now)
+						.isScriptGraded(true));
+			}
+
+			feedbackService.addFeedback(FeedbackCreateDTO.builder()
+					.visibleFor(RoleSummaryDTO.TypeEnum.STUDENT)
+					.submissionId(train.getSubmission().getId())
+					.authorId(train.getTrain().getId())
+					.gradeId(gradeId)
+					.isScriptFeedback(true)
+					.textualFeedback("Script feedback: " + train.getTrain().getId())
+					.timestamp(now).build());
+
+			train.setStatus(ScriptStatus.PROCESSED);
+			train.getSubmission().setScriptPending(false);
+			scriptTrainResultRepository.save(train);
+			submissionRepository.save(train.getSubmission());
+		}
+	}
+
+	// endregion
 }
diff --git a/src/main/java/nl/tudelft/submit/service/StatisticsService.java b/src/main/java/nl/tudelft/submit/service/StatisticsService.java
index 9fb972df..d4f6f367 100644
--- a/src/main/java/nl/tudelft/submit/service/StatisticsService.java
+++ b/src/main/java/nl/tudelft/submit/service/StatisticsService.java
@@ -726,8 +726,7 @@ public class StatisticsService {
 			GradeScheme gradeType) {
 		double average = grades.values().stream().mapToDouble(GradeSummaryDTO::getScore).average()
 				.getAsDouble();
-		return (Double) gradeService.toFormulaVariable(new GradeSummaryDTO().score((int) Math.round(average))
-				.scheme(GradeSummaryDTO.SchemeEnum.valueOf(gradeType.name())));
+		return (Double) gradeService.toFormulaVariable((int) Math.round(average), gradeType);
 	}
 
 	/**
@@ -756,8 +755,7 @@ public class StatisticsService {
 		double median = sorted.length % 2 == 0
 				? (sorted[sorted.length / 2] + sorted[sorted.length / 2 + 1]) / 2.0
 				: sorted[sorted.length / 2];
-		return (Double) gradeService.toFormulaVariable(new GradeSummaryDTO().score((int) Math.round(median))
-				.scheme(GradeSummaryDTO.SchemeEnum.valueOf(gradeType.name())));
+		return (Double) gradeService.toFormulaVariable((int) Math.round(median), gradeType);
 	}
 
 	// endregion
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index 55ea0d2b..8f50150d 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -333,3 +333,12 @@ script.remove_script.confirm = Are you sure you want to remove {0}?
 script.link = Link
 script.link.enter = Enter link
 script.grading_script = Grading
+script.status = Script status
+script.weight = Weight
+script.score = Score
+
+script.status.not_started = Not started
+script.status.running = Running
+script.status.done = Done
+script.status.failed = Failed
+script.status.processed = Processed
diff --git a/src/main/resources/templates/assignment/view.html b/src/main/resources/templates/assignment/view.html
index b6412c5d..6e2be1c1 100644
--- a/src/main/resources/templates/assignment/view.html
+++ b/src/main/resources/templates/assignment/view.html
@@ -130,7 +130,7 @@
 
     <div th:unless="${@authorizationService.isStudentInAssignment(assignment.id)}" th:if="${isTransparent}" th:remove="tag">
         <h2 class="subtitle" th:text="#{script.train}"></h2>
-        <div th:replace="~{version/wagon_list :: list}"></div>
+        <div th:replace="~{script/wagon_list :: list}"></div>
     </div>
 
     <div th:if="${@authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag">
@@ -170,14 +170,14 @@
     <div th:replace="~{assignment/submit :: overlay('assignment')}"></div>
     <div th:replace="~{assignment/add_train :: overlay}"></div>
     <div th:if="${isTransparent and not @authorizationService.isStudentInAssignment(assignment.id)}" th:remove="tag">
-        <div th:replace="~{version/add_wagon :: overlay}"></div>
+        <div th:replace="~{script/add_wagon :: overlay}"></div>
         <div th:each="wagon : ${train.wagons}" th:remove="tag">
-            <div th:replace="~{version/add_script :: overlay}"></div>
-            <div th:replace="~{version/edit_wagon :: overlay}"></div>
-            <div th:replace="~{version/remove_wagon :: overlay}"></div>
+            <div th:replace="~{script/add_script :: overlay}"></div>
+            <div th:replace="~{script/edit_wagon :: overlay}"></div>
+            <div th:replace="~{script/remove_wagon :: overlay}"></div>
             <div th:each="script : ${wagon.scripts}" th:remove="tag">
-                <div th:replace="~{version/edit_script :: overlay}"></div>
-                <div th:replace="~{version/remove_script :: overlay}"></div>
+                <div th:replace="~{script/edit_script :: overlay}"></div>
+                <div th:replace="~{script/remove_script :: overlay}"></div>
             </div>
         </div>
     </div>
diff --git a/src/main/resources/templates/version/add_script.html b/src/main/resources/templates/script/add_script.html
similarity index 100%
rename from src/main/resources/templates/version/add_script.html
rename to src/main/resources/templates/script/add_script.html
diff --git a/src/main/resources/templates/version/add_wagon.html b/src/main/resources/templates/script/add_wagon.html
similarity index 100%
rename from src/main/resources/templates/version/add_wagon.html
rename to src/main/resources/templates/script/add_wagon.html
diff --git a/src/main/resources/templates/version/edit_script.html b/src/main/resources/templates/script/edit_script.html
similarity index 100%
rename from src/main/resources/templates/version/edit_script.html
rename to src/main/resources/templates/script/edit_script.html
diff --git a/src/main/resources/templates/version/edit_wagon.html b/src/main/resources/templates/script/edit_wagon.html
similarity index 100%
rename from src/main/resources/templates/version/edit_wagon.html
rename to src/main/resources/templates/script/edit_wagon.html
diff --git a/src/main/resources/templates/version/remove_script.html b/src/main/resources/templates/script/remove_script.html
similarity index 100%
rename from src/main/resources/templates/version/remove_script.html
rename to src/main/resources/templates/script/remove_script.html
diff --git a/src/main/resources/templates/version/remove_wagon.html b/src/main/resources/templates/script/remove_wagon.html
similarity index 100%
rename from src/main/resources/templates/version/remove_wagon.html
rename to src/main/resources/templates/script/remove_wagon.html
diff --git a/src/main/resources/templates/script/status.html b/src/main/resources/templates/script/status.html
new file mode 100644
index 00000000..0f6ef405
--- /dev/null
+++ b/src/main/resources/templates/script/status.html
@@ -0,0 +1,72 @@
+<!--
+
+    Submit
+    Copyright (C) 2020 - Delft University of Technology
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+-->
+<!DOCTYPE html>
+<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
+      layout:decorate="~{container}">
+<head>
+    <title th:text="#{script.status}"></title>
+</head>
+
+<body>
+
+<div layout:fragment="breadcrumbs">
+</div>
+
+<div layout:fragment="sidebar" class="hidden">
+</div>
+
+<div layout:fragment="content">
+
+    <h1 class="title" th:text="#{script.status}"></h1>
+
+    <table class="table">
+        <tr class="table_header">
+            <th th:text="#{general.name}"></th>
+            <th th:text="#{script.weight}"></th>
+            <th th:text="#{script.status}"></th>
+            <th th:text="#{script.score}"></th>
+            <th></th>
+        </tr>
+        <th:block th:each="wagon : ${train.wagons}">
+            <tr>
+                <td th:text="${wagon.wagon.name}"></td>
+                <td th:text="${wagon.wagon.gradePart.weight}"></td>
+                <td th:text="#{|script.status.${#strings.toLowerCase(wagon.status.name())}|}"></td>
+                <td th:text="${wagon.score == null} ? '' : ${@gradeService.getScoreDisplayString(wagon.score, wagon.wagon.gradeScheme)}"></td>
+                <td><div class="table_actions">
+                </div></td>
+            </tr>
+            <th:block th:each="script : ${wagon.scripts}">
+                <tr>
+                    <td th:text="${script.script.name}"></td>
+                    <td th:text="${script.script.gradePart.weight}"></td>
+                    <td th:text="#{|script.status.${#strings.toLowerCase(script.status.name())}|}"></td>
+                    <td th:text="${script.score == null} ? '' : ${@gradeService.getScoreDisplayString(script.score, script.script.gradeScheme)}"></td>
+                    <td><div class="table_actions">
+                    </div></td>
+                </tr>
+            </th:block>
+        </th:block>
+    </table>
+
+</div>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/resources/templates/version/train.html b/src/main/resources/templates/script/train.html
similarity index 85%
rename from src/main/resources/templates/version/train.html
rename to src/main/resources/templates/script/train.html
index 7ac80db2..cb05c4de 100644
--- a/src/main/resources/templates/version/train.html
+++ b/src/main/resources/templates/script/train.html
@@ -50,16 +50,16 @@
 <div layout:fragment="content">
 
     <h1 class="title" th:text="#{script.train}"></h1>
-    <div th:replace="~{version/wagon_list :: list}"></div>
+    <div th:replace="~{script/wagon_list :: list}"></div>
 
-    <div th:replace="~{version/add_wagon :: overlay}"></div>
+    <div th:replace="~{script/add_wagon :: overlay}"></div>
     <div th:each="wagon : ${train.wagons}" th:remove="tag">
-        <div th:replace="~{version/add_script :: overlay}"></div>
-        <div th:replace="~{version/edit_wagon :: overlay}"></div>
-        <div th:replace="~{version/remove_wagon :: overlay}"></div>
+        <div th:replace="~{script/add_script :: overlay}"></div>
+        <div th:replace="~{script/edit_wagon :: overlay}"></div>
+        <div th:replace="~{script/remove_wagon :: overlay}"></div>
         <div th:each="script : ${wagon.scripts}" th:remove="tag">
-            <div th:replace="~{version/edit_script :: overlay}"></div>
-            <div th:replace="~{version/remove_script :: overlay}"></div>
+            <div th:replace="~{script/edit_script :: overlay}"></div>
+            <div th:replace="~{script/remove_script :: overlay}"></div>
         </div>
     </div>
 
diff --git a/src/main/resources/templates/version/wagon_list.html b/src/main/resources/templates/script/wagon_list.html
similarity index 94%
rename from src/main/resources/templates/version/wagon_list.html
rename to src/main/resources/templates/script/wagon_list.html
index 604fe6da..0be105eb 100644
--- a/src/main/resources/templates/version/wagon_list.html
+++ b/src/main/resources/templates/script/wagon_list.html
@@ -40,7 +40,6 @@
             <div th:each="script, sIter : ${wagon.scripts}" class="list_subitem">
                 <div class="list_subitem_content">
                     <span th:text="${script.name}"></span>
-                    <span th:if="${iter.count == iter.size and sIter.count == sIter.size}" class="tag" th:text="#{script.grading_script}"></span>
                 </div>
                 <div class="list_actions">
                     <button class="text-button" th:onclick="|toggleOverlay('edit-script-${script.id}-overlay')|" th:text="#{general.edit}"></button>
diff --git a/src/main/resources/templates/submission/feedback.html b/src/main/resources/templates/submission/feedback.html
index 2a366632..c2e1b881 100644
--- a/src/main/resources/templates/submission/feedback.html
+++ b/src/main/resources/templates/submission/feedback.html
@@ -57,7 +57,7 @@
 
             <span th:if="${submission.feedback.isEmpty()}" th:text="#{submission.no_feedback}"></span>
             <div th:each="feedback : ${submission.feedback}" th:if="${@authorizationService.canViewGroupFeedback(submission.group.id, feedback)}" th:remove="tag">
-                <span th:if="${feedback.isScriptFeedback}" th:text="#{submission.feedback_from(${feedback.authorId == -1 ? 'script' : @scriptService.getScriptById(feedback.authorId).name})}"></span>
+                <span th:if="${feedback.isScriptFeedback}" th:text="#{submission.feedback_from('script')}"></span>
                 <span th:unless="${feedback.isScriptFeedback}" th:text="#{submission.feedback_by(${@personCacheManager.getOrThrow(feedback.authorId)?.username})}"></span>
                 <div>
                     <span class="tag" th:text="${feedback.gradeId == null} ? #{grading.no_grade} : ${@gradeService.getScoreDisplayString(grades[feedback.gradeId])}"></span>
diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml
index 965ef0fe..3f0f3cde 100644
--- a/src/test/resources/application.yaml
+++ b/src/test/resources/application.yaml
@@ -18,8 +18,6 @@
 
 submit:
   websocketURL: http://localhost:8083
-  cache:
-    person-timeout: 60
   filesys:
     # changing these options mid-operation may cause loss of data
     block-size: 2
-- 
GitLab