Skip to content

Commit

Permalink
Merge pull request #435 from softwareconstruction240/430-fullstack-en…
Browse files Browse the repository at this point in the history
…hance-late-penalty-applied-presentation-experience

430 fullstack enhance late penalty applied presentation experience
  • Loading branch information
19mdavenport authored Oct 21, 2024
2 parents 22ac932 + c0a1266 commit b1a230e
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 89 deletions.
2 changes: 1 addition & 1 deletion src/main/java/edu/byu/cs/autograder/Grader.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private void handleException(GradingException ge, CommitVerificationResult cvr)
return;
}
try {
Submission submission = new Scorer(gradingContext).generateSubmissionObject(ge.asRubric(), cvr, 0, 0f, ge.getMessage());
Submission submission = new Scorer(gradingContext).generateSubmissionObject(ge.asRubric(), cvr, 0, new Scorer.ScorePair(0f, 0f), ge.getMessage());
DaoService.getSubmissionDao().insertSubmission(submission);
observer.notifyError(ge.getMessage(), submission);
} catch (Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ public LateDayCalculator() {
}

public int calculateLateDays(Phase phase, String netId) throws GradingException, DataAccessException {
ZonedDateTime handInDate = ScorerHelper.getHandInDateZoned(netId);
return calculateLateDays(phase, netId, handInDate);
}

public int calculateLateDays(Phase phase, String netId, ZonedDateTime handInDate) throws GradingException, DataAccessException {
if (!ApplicationProperties.useCanvas()) return 0;

int assignmentNum = PhaseUtils.getPhaseAssignmentNumber(phase);
Expand All @@ -58,6 +53,7 @@ public int calculateLateDays(Phase phase, String netId, ZonedDateTime handInDate
throw new GradingException("Failed to get due date for assignment " + assignmentNum + " for user " + netId, e);
}

ZonedDateTime handInDate = ScorerHelper.getHandInDateZoned(netId);
return Math.min(getNumDaysLate(handInDate, dueDate), MAX_LATE_DAYS_TO_PENALIZE);
}

Expand Down
66 changes: 33 additions & 33 deletions src/main/java/edu/byu/cs/autograder/score/Scorer.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.EnumMap;
Expand Down Expand Up @@ -61,21 +60,21 @@ public Submission score(Rubric rubric, CommitVerificationResult commitVerificati

// Exit early when the score isn't important
if (gradingContext.admin() || !PhaseUtils.isPhaseGraded(gradingContext.phase())) {
return generateSubmissionObject(rubric, commitVerificationResult, 0, getScore(rubric), "");
return generateSubmissionObject(rubric, commitVerificationResult, 0, getScores(rubric), "");
}

int daysLate = new LateDayCalculator().calculateLateDays(gradingContext.phase(), gradingContext.netId());
Rubric lateAppliedRubric = applyLatePenalty(rubric, daysLate);
float thisScore = getScore(lateAppliedRubric);
rubric = applyLatePenalty(rubric, daysLate);
ScorePair scores = getScores(rubric);

// Validate several conditions before submitting to the grade-book
if (!rubric.passed()) {
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, thisScore, "");
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, scores, "");
} else if (!commitVerificationResult.verified()) {
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, thisScore, commitVerificationResult.failureMessage());
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, scores, commitVerificationResult.failureMessage());
} else {
// The student (may) receive a score in canvas!
return successfullyProcessSubmission(rubric, lateAppliedRubric, commitVerificationResult, daysLate, thisScore);
return successfullyProcessSubmission(rubric, commitVerificationResult, daysLate, scores);
}
}

Expand All @@ -100,30 +99,28 @@ private Rubric transformRubric(Rubric rubric) throws GradingException, DataAcces
* <br>
* Calling this method constitutes a successful, verified submission that will be submitted to canvas.
*
* @param rubric The original rubric for saving to the database and display to users
* @param lateAppliedRubric Rubric with late penalties applied for sending to canvas
* @param rubric The rubric for the submission
* @param commitVerificationResult Required when originally creating a submission.
* Can be null when sending scores to Canvas; this will disable
* any automatic point deductions for verification, and also result in
* <code>null</code> being returned instead of a {@link Submission}.
* @param daysLate Required. Used to add a note to the resulting submission object.
* @param thisScore Required. Used to place a value in the {@link Submission} object.
* @param scores Required. Used to place values in the {@link Submission} object.
* The Canvas grade is based entirely on the provided {@link Rubric}.
* @return A construction Submission for continued processing
* @throws DataAccessException When the database can't be reached.
* @throws GradingException When other conditions fail.
*/
private Submission successfullyProcessSubmission(Rubric rubric, Rubric lateAppliedRubric,
CommitVerificationResult commitVerificationResult, int daysLate,
float thisScore) throws DataAccessException, GradingException {
private Submission successfullyProcessSubmission(Rubric rubric, CommitVerificationResult commitVerificationResult,
int daysLate, ScorePair scores) throws DataAccessException, GradingException {

if (!ApplicationProperties.useCanvas()) {
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, thisScore,
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, scores,
"Would have attempted grade-book submission, but skipped due to application properties.");
}

AssessmentSubmittalRemnants submittalRemnants = attemptSendToCanvas(lateAppliedRubric, commitVerificationResult);
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, thisScore, submittalRemnants.notes);
AssessmentSubmittalRemnants submittalRemnants = attemptSendToCanvas(rubric, commitVerificationResult);
return generateSubmissionObject(rubric, commitVerificationResult, daysLate, scores, submittalRemnants.notes);
}

/**
Expand All @@ -132,21 +129,18 @@ private Submission successfullyProcessSubmission(Rubric rubric, Rubric lateAppli
* Note that this operation will be performed carefully so that existing RubricItem's in Canvas
* will not be overwritten by the operation.
*
* @param submission Submission object of approved submission
* @param rubric A {@link Rubric} containing values to set in Canvas.
* Any items not set will be populated with their value from Canvas.
* @param phase The phase being graded
* @param netId Net ID of student being
* @param penaltyPct The approved GIT_COMMITS penalty percentage
* @throws GradingException When preconditions are not met.
* @throws DataAccessException When the database cannot be reached.
*/
public static void attemptSendApprovedScoreToCanvas(Submission submission, int penaltyPct, String commitPenaltyMsg) throws GradingException, DataAccessException {
Phase phase = submission.phase();
String netId = submission.netId();
public static void attemptSendToCanvas(Rubric rubric, Phase phase, String netId, int penaltyPct, String commitPenaltyMsg) throws GradingException, DataAccessException {
int canvasUserId = getCanvasUserId(netId);
int assignmentNum = PhaseUtils.getPhaseAssignmentNumber(phase);

//TODO: undo this change when developing more permanent solution
int daysLate = new LateDayCalculator().calculateLateDays(phase, netId, submission.timestamp().atZone(ZoneId.of("America/Denver")));
Rubric rubric = applyLatePenalty(submission.rubric(), daysLate);

CanvasRubricAssessment newAssessment = constructCanvasRubricAssessment(rubric, phase);

if (PhaseUtils.phaseHasCommitPenalty(phase)) {
Expand Down Expand Up @@ -292,13 +286,14 @@ private boolean passed(Rubric rubric) {
return true;
}

private static Rubric applyLatePenalty(Rubric rubric, int daysLate) {
private Rubric applyLatePenalty(Rubric rubric, int daysLate) {
EnumMap<Rubric.RubricType, Rubric.RubricItem> items = new EnumMap<>(Rubric.RubricType.class);
float lateAdjustment = daysLate * PER_DAY_LATE_PENALTY;
for(Map.Entry<Rubric.RubricType, Rubric.RubricItem> entry : rubric.items().entrySet()) {
Rubric.Results results = entry.getValue().results();
results = new Rubric.Results(results.notes(),
results.score() * (1 - lateAdjustment),
results.score(),
results.possiblePoints(),
results.testResults(),
results.textResults());
Expand Down Expand Up @@ -348,25 +343,27 @@ private static float totalPoints(CanvasRubricAssessment assessment) {
}

/**
* Gets the score for the phase
* Gets the score and rawScore for the rubric and phase
*
* @return the score as a percentage value from [0-1].
* @return a ScorePair with both the score and rawScore as a percentage value from [0-1].
*/
private float getScore(Rubric rubric) throws GradingException, DataAccessException {
private ScorePair getScores(Rubric rubric) throws GradingException, DataAccessException {
int totalPossiblePoints = DaoService.getRubricConfigDao().getPhaseTotalPossiblePoints(gradingContext.phase());

if (totalPossiblePoints == 0) {
throw new GradingException("Total possible points for phase " + gradingContext.phase() + " is 0");
}

float score = 0;
float rawScore = 0;
for (Rubric.RubricType type : Rubric.RubricType.values()) {
var rubricItem = rubric.items().get(type);
if (rubricItem == null) continue;
score += rubricItem.results().score();
rawScore += rubricItem.results().rawScore();
}

return score / totalPossiblePoints;
return new ScorePair(score / totalPossiblePoints, rawScore / totalPossiblePoints);
}

/**
Expand All @@ -383,12 +380,12 @@ private float getScore(Rubric rubric) throws GradingException, DataAccessExcepti
* @param numDaysLate The number of days late this submission was handed-in.
* For note generating purposes only; this is not used to
* calculate any penalties.
* @param score The final approved score on the submission represented in points.
* @param scores The final approved score and rawScore on the submission represented in points.
* @param notes Any notes that are associated with the submission.
* More comments may be added to this string while preparing the Submission.
*/
public Submission generateSubmissionObject(Rubric rubric, CommitVerificationResult commitVerificationResult,
int numDaysLate, float score, String notes)
int numDaysLate, ScorePair scores, String notes)
throws GradingException, DataAccessException {
if (commitVerificationResult == null) {
return null; // This is allowed.
Expand All @@ -409,7 +406,7 @@ public Submission generateSubmissionObject(Rubric rubric, CommitVerificationResu
verifiedStatus = VerifiedStatus.Unapproved;
}
if (commitVerificationResult.penaltyPct() > 0) {
score = prepareModifiedScore(score, commitVerificationResult.penaltyPct());
scores = new ScorePair(prepareModifiedScore(scores.score(), commitVerificationResult.penaltyPct()), scores.rawScore());
notes += "Commit history approved with a penalty of %d%%".formatted(commitVerificationResult.penaltyPct());
}

Expand All @@ -420,7 +417,8 @@ public Submission generateSubmissionObject(Rubric rubric, CommitVerificationResu
handInDate.toInstant(),
gradingContext.phase(),
rubric.passed(),
score,
scores.score(),
scores.rawScore(),
notes,
rubric,
gradingContext.admin(),
Expand All @@ -446,4 +444,6 @@ private static void sendToCanvas(int userId, int assignmentNum, CanvasRubricAsse
public static float prepareModifiedScore(float originalScore, int penaltyPct) {
return originalScore * (100 - penaltyPct) / 100f;
}

public record ScorePair(float score, float rawScore) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public void manuallyApproveSubmission(Submission targetSubmission, Float newScor
submission.phase(),
submission.passed(),
newScore, // Changed
submission.rawScore(),
submission.notes(),
submission.rubric(),
submission.admin(),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/edu/byu/cs/dataAccess/sql/SqlDb.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ PRIMARY KEY (`net_id`)
`phase` VARCHAR(9) NOT NULL,
`passed` BOOL NOT NULL,
`score` FLOAT NOT NULL,
`raw_score` FLOAT NOT NULL,
`notes` TEXT,
`rubric` JSON,
`verified_status` VARCHAR(30),
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/edu/byu/cs/dataAccess/sql/SubmissionSqlDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class SubmissionSqlDao implements SubmissionDao {
new ColumnDefinition<Submission>("phase", s -> s.phase().toString()),
new ColumnDefinition<Submission>("passed", Submission::passed),
new ColumnDefinition<Submission>("score", Submission::score),
new ColumnDefinition<Submission>("raw_score", Submission::rawScore),
new ColumnDefinition<Submission>("head_hash", Submission::headHash),
new ColumnDefinition<Submission>("notes", Submission::notes),
new ColumnDefinition<Submission>("rubric", s -> Serializer.serialize(s.rubric())),
Expand All @@ -47,6 +48,7 @@ private static Submission readSubmission(ResultSet rs) throws SQLException {
Phase phase = Phase.valueOf(rs.getString("phase"));
Boolean passed = rs.getBoolean("passed");
float score = rs.getFloat("score");
float rawScore = rs.getFloat("raw_score");
String notes = rs.getString("notes");
Rubric rubric = Serializer.deserialize(rs.getString("rubric"), Rubric.class);
Boolean admin = rs.getBoolean("admin");
Expand All @@ -60,7 +62,7 @@ private static Submission readSubmission(ResultSet rs) throws SQLException {

return new Submission(
netId, repoUrl, headHash, timestamp, phase,
passed, score, notes, rubric,
passed, score, rawScore, notes, rubric,
admin, verifiedStatus, scoreVerification);
}

Expand Down Expand Up @@ -117,7 +119,7 @@ public Collection<Submission> getAllLatestSubmissions(int batchSize) throws Data
try (var connection = SqlDb.getConnection()) {
var statement = connection.prepareStatement(
"""
SELECT s.net_id, s.repo_url, s.timestamp, s.phase, s.passed, s.score, s.head_hash, s.notes, s.rubric, s.admin, s.verification, s.verified_status
SELECT s.net_id, s.repo_url, s.timestamp, s.phase, s.passed, s.score, s.raw_score, s.head_hash, s.notes, s.rubric, s.admin, s.verification, s.verified_status
FROM submission s
INNER JOIN (
SELECT net_id, phase, MAX(timestamp) AS max_timestamp
Expand Down
20 changes: 5 additions & 15 deletions src/main/java/edu/byu/cs/model/Rubric.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@ public record Rubric(
String notes
) {

/**
* Calculates the total number of points in all items
*
* @return total number of points contained by this rubric
*/
public float getTotalPoints() {
float total = 0f;
for(RubricItem item : items.values()) {
if(item != null) {
total += item.results().score();
}
}
return total;
}

/**
* Represents a single rubric item
*
Expand All @@ -55,10 +40,15 @@ public record RubricItem(
public record Results(
String notes,
Float score,
Float rawScore,
Integer possiblePoints,
TestAnalysis testResults,
String textResults
) {
public Results(String notes, Float score, Integer possiblePoints, TestAnalysis testResults, String textResults) {
this(notes, score, score, possiblePoints, testResults, textResults);
}

public static Results testError(String notes, TestAnalysis testResults) {
return new Results(notes, 0f, 0, testResults, null);
}
Expand Down
17 changes: 1 addition & 16 deletions src/main/java/edu/byu/cs/model/Submission.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public record Submission(
Phase phase,
Boolean passed,
Float score,
Float rawScore,
String notes,
Rubric rubric,
Boolean admin,
Expand Down Expand Up @@ -143,20 +144,4 @@ public static String serializeVerifiedStatus(@Nullable VerifiedStatus verifiedSt
return verifiedStatus == null ? null : verifiedStatus.name();
}

public Submission replaceRubric(Rubric rubric) {
return new Submission(
this.netId,
this.repoUrl,
this.headHash,
this.timestamp,
this.phase,
this.passed,
this.score,
this.notes,
rubric,
this.admin,
this.verifiedStatus,
this.verification
);
}
}
3 changes: 2 additions & 1 deletion src/main/java/edu/byu/cs/util/SubmissionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ public static void approveSubmission(
// Send score to Grade-book
float approvedScore = Scorer.prepareModifiedScore(withheldSubmission.score(), penaltyPct);
String gitCommitsComment = "Submission initially blocked due to low commits. Submission approved by admin " + approverNetId;
Scorer.attemptSendApprovedScoreToCanvas(withheldSubmission, penaltyPct, gitCommitsComment);
Scorer.attemptSendToCanvas(withheldSubmission.rubric(), withheldSubmission.phase(), withheldSubmission.netId(),
penaltyPct, gitCommitsComment);

// Done
LOGGER.info("Approved submission for %s on phase %s with score %f. Approval by %s. Affected %d submissions."
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/frontend/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
/*font-weight: normal;*/
}

body {
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/frontend/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type TestResult = {
export type RubricItemResults = {
notes: string,
score: number,
rawScore: number,
possiblePoints: number,
testResults: TestResult,
textResults: string,
Expand Down Expand Up @@ -72,6 +73,7 @@ export type Submission = {
timestamp: string,
phase: Phase,
score: number,
rawScore: number,
notes: string,
rubric: Rubric,
passed: boolean,
Expand Down
Loading

0 comments on commit b1a230e

Please sign in to comment.