use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExamService method calculateExamScores.
/**
* Puts students, result and exerciseGroups together for ExamScoresDTO
*
* @param examId the id of the exam
* @return return ExamScoresDTO with students, scores and exerciseGroups for exam
*/
public ExamScoresDTO calculateExamScores(Long examId) {
Exam exam = examRepository.findWithExerciseGroupsAndExercisesById(examId).orElseThrow(() -> new EntityNotFoundException("Exam", examId));
// without test run participations
List<StudentParticipation> studentParticipations = studentParticipationRepository.findByExamIdWithSubmissionRelevantResult(examId);
// Adding exam information to DTO
ExamScoresDTO scores = new ExamScoresDTO(exam.getId(), exam.getTitle(), exam.getMaxPoints());
// setting multiplicity of correction rounds
scores.hasSecondCorrectionAndStarted = false;
// Counts how many participants each exercise has
Map<Long, Long> exerciseIdToNumberParticipations = studentParticipations.stream().collect(Collectors.groupingBy(studentParticipation -> studentParticipation.getExercise().getId(), Collectors.counting()));
// Adding exercise group information to DTO
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
// Find the maximum points for this exercise group
OptionalDouble optionalMaxPointsGroup = exerciseGroup.getExercises().stream().mapToDouble(Exercise::getMaxPoints).max();
Double maxPointsGroup = optionalMaxPointsGroup.orElse(0);
// Counter for exerciseGroup participations. Is calculated by summing up the number of exercise participations
long numberOfExerciseGroupParticipants = 0;
// Add information about exercise groups and exercises
var exerciseGroupDTO = new ExamScoresDTO.ExerciseGroup(exerciseGroup.getId(), exerciseGroup.getTitle(), maxPointsGroup);
for (Exercise exercise : exerciseGroup.getExercises()) {
Long participantsForExercise = exerciseIdToNumberParticipations.get(exercise.getId());
// If no participation exists for an exercise then no entry exists in the map
if (participantsForExercise == null) {
participantsForExercise = 0L;
}
numberOfExerciseGroupParticipants += participantsForExercise;
exerciseGroupDTO.containedExercises.add(new ExamScoresDTO.ExerciseGroup.ExerciseInfo(exercise.getId(), exercise.getTitle(), exercise.getMaxPoints(), participantsForExercise, exercise.getClass().getSimpleName()));
}
exerciseGroupDTO.numberOfParticipants = numberOfExerciseGroupParticipants;
scores.exerciseGroups.add(exerciseGroupDTO);
}
// Adding registered student information to DTO
// fetched without test runs
Set<StudentExam> studentExams = studentExamRepository.findByExamId(examId);
ObjectMapper objectMapper = new ObjectMapper();
for (StudentExam studentExam : studentExams) {
User user = studentExam.getUser();
var studentResult = new ExamScoresDTO.StudentResult(user.getId(), user.getName(), user.getEmail(), user.getLogin(), user.getRegistrationNumber(), studentExam.isSubmitted());
// Adding student results information to DTO
List<StudentParticipation> participationsOfStudent = studentParticipations.stream().filter(studentParticipation -> studentParticipation.getStudent().get().getId().equals(studentResult.userId)).toList();
studentResult.overallPointsAchieved = 0.0;
studentResult.overallPointsAchievedInFirstCorrection = 0.0;
for (StudentParticipation studentParticipation : participationsOfStudent) {
Exercise exercise = studentParticipation.getExercise();
// Relevant Result is already calculated
if (studentParticipation.getResults() != null && !studentParticipation.getResults().isEmpty()) {
Result relevantResult = studentParticipation.getResults().iterator().next();
// Note: It is important that we round on the individual exercise level first and then sum up.
// This is necessary so that the student arrives at the same overall result when doing his own recalculation.
// Let's assume that the student achieved 1.05 points in each of 5 exercises.
// In the client, these are now displayed rounded as 1.1 points.
// If the student adds up the displayed points, he gets a total of 5.5 points.
// In order to get the same total result as the student, we have to round before summing.
double achievedPoints = roundScoreSpecifiedByCourseSettings(relevantResult.getScore() / 100.0 * exercise.getMaxPoints(), exam.getCourse());
// points earned in NOT_INCLUDED exercises do not count towards the students result in the exam
if (!exercise.getIncludedInOverallScore().equals(IncludedInOverallScore.NOT_INCLUDED)) {
studentResult.overallPointsAchieved += achievedPoints;
}
// collect points of first correction, if a second correction exists
if (exam.getNumberOfCorrectionRoundsInExam() == 2 && !exercise.getIncludedInOverallScore().equals(IncludedInOverallScore.NOT_INCLUDED)) {
Optional<Submission> latestSubmission = studentParticipation.findLatestSubmission();
if (latestSubmission.isPresent()) {
Submission submission = latestSubmission.get();
// Check if second correction already started
if (submission.getManualResults().size() > 1) {
if (!scores.hasSecondCorrectionAndStarted) {
scores.hasSecondCorrectionAndStarted = true;
}
Result firstManualResult = submission.getFirstManualResult();
double achievedPointsInFirstCorrection = 0.0;
if (firstManualResult != null) {
Double resultScore = firstManualResult.getScore();
achievedPointsInFirstCorrection = resultScore != null ? roundScoreSpecifiedByCourseSettings(resultScore / 100.0 * exercise.getMaxPoints(), exam.getCourse()) : 0.0;
}
studentResult.overallPointsAchievedInFirstCorrection += achievedPointsInFirstCorrection;
}
}
}
// Check whether the student attempted to solve the exercise
boolean hasNonEmptySubmission = hasNonEmptySubmission(studentParticipation.getSubmissions(), exercise, objectMapper);
studentResult.exerciseGroupIdToExerciseResult.put(exercise.getExerciseGroup().getId(), new ExamScoresDTO.ExerciseResult(exercise.getId(), exercise.getTitle(), exercise.getMaxPoints(), relevantResult.getScore(), achievedPoints, hasNonEmptySubmission));
}
}
if (scores.maxPoints != null) {
studentResult.overallScoreAchieved = (studentResult.overallPointsAchieved / scores.maxPoints) * 100.0;
var overallScoreAchievedInFirstCorrection = (studentResult.overallPointsAchievedInFirstCorrection / scores.maxPoints) * 100.0;
// Sets grading scale related properties for exam scores
Optional<GradingScale> gradingScale = gradingScaleRepository.findByExamId(examId);
if (gradingScale.isPresent()) {
// Calculate current student grade
GradeStep studentGrade = gradingScaleRepository.matchPercentageToGradeStep(studentResult.overallScoreAchieved, gradingScale.get().getId());
GradeStep studentGradeInFirstCorrection = gradingScaleRepository.matchPercentageToGradeStep(overallScoreAchievedInFirstCorrection, gradingScale.get().getId());
studentResult.overallGrade = studentGrade.getGradeName();
studentResult.overallGradeInFirstCorrection = studentGradeInFirstCorrection.getGradeName();
studentResult.hasPassed = studentGrade.getIsPassingGrade();
}
}
scores.studentResults.add(studentResult);
}
// Updating exam information in DTO
double sumOverallPoints = scores.studentResults.stream().mapToDouble(studentResult -> studentResult.overallPointsAchieved).sum();
int numberOfStudentResults = scores.studentResults.size();
if (numberOfStudentResults != 0) {
scores.averagePointsAchieved = sumOverallPoints / numberOfStudentResults;
}
return scores;
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExamService method evaluateQuizExercises.
/**
* Evaluates all the quiz exercises of an exam
*
* @param examId id of the exam for which the quiz exercises should be evaluated
* @return number of evaluated exercises
*/
public Integer evaluateQuizExercises(Long examId) {
var exam = examRepository.findWithExerciseGroupsAndExercisesById(examId).orElseThrow(() -> new EntityNotFoundException("Exam", examId));
// Collect all quiz exercises for the given exam
Set<QuizExercise> quizExercises = new HashSet<>();
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
for (Exercise exercise : exerciseGroup.getExercises()) {
if (exercise instanceof QuizExercise) {
quizExercises.add((QuizExercise) exercise);
}
}
}
long start = System.nanoTime();
log.info("Evaluating {} quiz exercises in exam {}", quizExercises.size(), examId);
// Evaluate all quizzes for that exercise
quizExercises.forEach(quiz -> examQuizService.evaluateQuizAndUpdateStatistics(quiz.getId()));
log.info("Evaluated {} quiz exercises in exam {} in {}", quizExercises.size(), examId, TimeLogUtil.formatDurationFrom(start));
return quizExercises.size();
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExamService method reset.
/**
* Deletes all elements associated with the exam but not the exam itself in order to reset it.
*
* The deleted elements are:
* <ul>
* <li>All StudentExams</li>
* <li>Everything that has been submitted by students to the exercises that are part of the exam,
* but not the exercises themself. See {@link ExerciseDeletionService#reset}</li>
* </ul>
* @param examId the ID of the exam to be reset
*/
public void reset(@NotNull Long examId) {
User user = userRepository.getUser();
Exam exam = examRepository.findOneWithEagerExercisesGroupsAndStudentExams(examId);
log.info("User {} has requested to reset the exam {}", user.getLogin(), exam.getTitle());
AuditEvent auditEvent = new AuditEvent(user.getLogin(), Constants.RESET_EXAM, "exam=" + exam.getTitle());
auditEventRepository.add(auditEvent);
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
if (exerciseGroup != null) {
for (Exercise exercise : exerciseGroup.getExercises()) {
exerciseDeletionService.reset(exercise);
}
}
}
studentExamRepository.deleteAll(exam.getStudentExams());
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ExamService method delete.
/**
* Fetches the exam and eagerly loads all required elements and deletes all elements associated with the
* exam including:
* <ul>
* <li>The Exam</li>
* <li>All ExerciseGroups</li>
* <li>All Exercises including:
* Submissions, Participations, Results, Repositories and build plans, see {@link ExerciseDeletionService#delete}</li>
* <li>All StudentExams</li>
* <li>The exam Grading Scale if such exists</li>
* </ul>
* Note: StudentExams and ExerciseGroups are not explicitly deleted as the delete operation of the exam is cascaded by the database.
*
* @param examId the ID of the exam to be deleted
*/
public void delete(@NotNull long examId) {
User user = userRepository.getUser();
Exam exam = examRepository.findOneWithEagerExercisesGroupsAndStudentExams(examId);
log.info("User {} has requested to delete the exam {}", user.getLogin(), exam.getTitle());
AuditEvent auditEvent = new AuditEvent(user.getLogin(), Constants.DELETE_EXAM, "exam=" + exam.getTitle());
auditEventRepository.add(auditEvent);
for (ExerciseGroup exerciseGroup : exam.getExerciseGroups()) {
if (exerciseGroup != null) {
for (Exercise exercise : exerciseGroup.getExercises()) {
exerciseDeletionService.delete(exercise.getId(), true, true);
}
}
}
deleteGradingScaleOfExam(exam);
examRepository.deleteById(exam.getId());
}
use of de.tum.in.www1.artemis.domain.exam.ExerciseGroup in project Artemis by ls1intum.
the class ProgrammingExerciseExportImportResource method importProgrammingExercise.
/**
* POST /programming-exercises/import: Imports an existing programming exercise into an existing course
* <p>
* This will import the whole exercise, including all base build plans (template, solution) and repositories
* (template, solution, test). Referenced entities, s.a. the test cases or the hints will get cloned and assigned
* a new id. For a concrete list of what gets copied and what not have a look
* at {@link ProgrammingExerciseImportService#importProgrammingExerciseBasis(ProgrammingExercise, ProgrammingExercise)}
*
* @param sourceExerciseId The ID of the original exercise which should get imported
* @param newExercise The new exercise containing values that should get overwritten in the imported exercise, s.a. the title or difficulty
* @param recreateBuildPlans Option determining whether the build plans should be copied or re-created from scratch
* @param updateTemplate Option determining whether the template files should be updated with the most recent template version
* @return The imported exercise (200), a not found error (404) if the template does not exist, or a forbidden error
* (403) if the user is not at least an instructor in the target course.
* @see ProgrammingExerciseImportService#importProgrammingExerciseBasis(ProgrammingExercise, ProgrammingExercise)
* @see ProgrammingExerciseImportService#importBuildPlans(ProgrammingExercise, ProgrammingExercise)
* @see ProgrammingExerciseImportService#importRepositories(ProgrammingExercise, ProgrammingExercise)
*/
@PostMapping(IMPORT)
@PreAuthorize("hasRole('EDITOR')")
@FeatureToggle(Feature.ProgrammingExercises)
public ResponseEntity<ProgrammingExercise> importProgrammingExercise(@PathVariable long sourceExerciseId, @RequestBody ProgrammingExercise newExercise, @RequestParam(defaultValue = "false") boolean recreateBuildPlans, @RequestParam(defaultValue = "false") boolean updateTemplate) {
if (sourceExerciseId < 0) {
throw new BadRequestAlertException("Invalid source id when importing programming exercises", ENTITY_NAME, "invalidSourceExerciseId");
}
// Valid exercises have set either a course or an exerciseGroup
newExercise.checkCourseAndExerciseGroupExclusivity(ENTITY_NAME);
log.debug("REST request to import programming exercise {} into course {}", sourceExerciseId, newExercise.getCourseViaExerciseGroupOrCourseMember().getId());
newExercise.validateGeneralSettings();
newExercise.validateProgrammingSettings();
validateStaticCodeAnalysisSettings(newExercise);
final var user = userRepository.getUserWithGroupsAndAuthorities();
Course course = courseService.retrieveCourseOverExerciseGroupOrCourseId(newExercise);
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, course, user);
// Validate course settings
programmingExerciseRepository.validateCourseSettings(newExercise, course);
final var originalProgrammingExercise = programmingExerciseRepository.findByIdWithEagerTestCasesStaticCodeAnalysisCategoriesHintsAndTemplateAndSolutionParticipationsAndAuxReposAndTasksWithTestCases(sourceExerciseId).orElseThrow(() -> new EntityNotFoundException("ProgrammingExercise", sourceExerciseId));
// The static code analysis flag can only change, if the build plans are recreated and the template is upgraded
if (newExercise.isStaticCodeAnalysisEnabled() != originalProgrammingExercise.isStaticCodeAnalysisEnabled() && !(recreateBuildPlans && updateTemplate)) {
throw new BadRequestAlertException("Static code analysis can only change, if the recreation of build plans and update of template files is activated", ENTITY_NAME, "staticCodeAnalysisCannotChange");
}
// If the new exercise has a submission policy, it must be validated.
if (newExercise.getSubmissionPolicy() != null) {
submissionPolicyService.validateSubmissionPolicy(newExercise.getSubmissionPolicy());
}
// Check if the user has the rights to access the original programming exercise
Course originalCourse = courseService.retrieveCourseOverExerciseGroupOrCourseId(originalProgrammingExercise);
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, originalCourse, user);
newExercise.generateAndSetProjectKey();
programmingExerciseService.checkIfProjectExists(newExercise);
final var importedProgrammingExercise = programmingExerciseImportService.importProgrammingExerciseBasis(originalProgrammingExercise, newExercise);
programmingExerciseImportService.importRepositories(originalProgrammingExercise, importedProgrammingExercise);
// Update the template files
if (updateTemplate) {
TemplateUpgradeService upgradeService = templateUpgradePolicy.getUpgradeService(importedProgrammingExercise.getProgrammingLanguage());
upgradeService.upgradeTemplate(importedProgrammingExercise);
}
HttpHeaders responseHeaders;
// Copy or recreate the build plans
try {
if (recreateBuildPlans) {
// Create completely new build plans for the exercise
programmingExerciseService.setupBuildPlansForNewExercise(importedProgrammingExercise);
} else {
// We have removed the automatic build trigger from test to base for new programming exercises.
// We also remove this build trigger in the case of an import as the source exercise might still have this trigger.
// The importBuildPlans method includes this process
programmingExerciseImportService.importBuildPlans(originalProgrammingExercise, importedProgrammingExercise);
}
responseHeaders = HeaderUtil.createEntityCreationAlert(applicationName, true, ENTITY_NAME, importedProgrammingExercise.getTitle());
} catch (Exception e) {
responseHeaders = HeaderUtil.createFailureAlert(applicationName, true, ENTITY_NAME, "importExerciseTriggerPlanFail", "Unable to trigger imported build plans");
}
programmingExerciseService.scheduleOperations(importedProgrammingExercise.getId());
// Remove unnecessary fields
importedProgrammingExercise.setTestCases(null);
importedProgrammingExercise.setStaticCodeAnalysisCategories(null);
importedProgrammingExercise.setTemplateParticipation(null);
importedProgrammingExercise.setSolutionParticipation(null);
importedProgrammingExercise.setExerciseHints(null);
importedProgrammingExercise.setTasks(null);
return ResponseEntity.ok().headers(responseHeaders).body(importedProgrammingExercise);
}
Aggregations