From fc6d0b3e24ea94226188c199c6993689891c5b14 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 29 Apr 2016 17:32:54 +0200 Subject: [PATCH] fixing bug in ScoreCorrectionImpl regarding the delayed application of MaxPointsReason and scores; added test case; use live time point for winner determination Change-Id: I15606fe99c52b32580bf9191e1a592828642c346 --- .../LeaderboardScoringAndRankingTest.java | 59 +++++++++++++ .../leaderboard/impl/ScoreCorrectionImpl.java | 87 ++++++++++++++----- .../sailing/gwt/home/server/RaceContext.java | 11 +-- 3 files changed, 128 insertions(+), 29 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index fd072fb4fdb..f9deaa0607c 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1605,6 +1605,65 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(0., leaderboard1.getTotalPoints(c[0], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 } + /** + * When a DNS score correction is made then both, the MaxPointsReason and the score shall apply at the start of the race. + */ + @Test + public void testDNSScoreAppliesBeforeEndOfRace() throws NoWindException { + Competitor[] c = createCompetitors(4).toArray(new Competitor[0]); + final TimePoint endOfR1 = MillisecondsTimePoint.now(); + final TimePoint withinR1 = endOfR1.minus(1000); + final TimePoint startOfR1 = withinR1.minus(10000); + final TimePoint beforeStartOfR1 = startOfR1.minus(10000); + final TimePoint afterEndOfR1 = endOfR1.plus(1000); + FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Leaderboard 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), + new LowPoint(), null); + leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(startOfR1, Arrays.asList(c)) { + private static final long serialVersionUID = 8705622361027154428L; + public TimePoint getEndOfRace() { + return endOfR1; + } + }, "R1", /* medalRace */false); + leaderboard1.getScoreCorrection().correctScore(c[0], leaderboard1.getRaceColumnByName("R1"), 123.); + leaderboard1.getScoreCorrection().setMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), MaxPointsReason.DNS); + assertEquals(MaxPointsReason.NONE, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); + assertEquals(MaxPointsReason.DNS, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1)); + assertEquals(MaxPointsReason.DNS, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1)); + assertNull(leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); + assertEquals(123., leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1), 0.00000001); + assertEquals(123., leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1), 0.00000001); + } + + /** + * When a DNS score correction is made then both, the MaxPointsReason and the score shall apply at the start of the race. + */ + @Test + public void testDNFScoreDoesNotApplyBeforeEndOfRace() throws NoWindException { + Competitor[] c = createCompetitors(4).toArray(new Competitor[0]); + final TimePoint endOfR1 = MillisecondsTimePoint.now(); + final TimePoint withinR1 = endOfR1.minus(1000); + final TimePoint startOfR1 = withinR1.minus(10000); + final TimePoint beforeStartOfR1 = startOfR1.minus(10000); + final TimePoint afterEndOfR1 = endOfR1.plus(1000); + FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Leaderboard 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), + new LowPoint(), null); + leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(startOfR1, Arrays.asList(c)) { + private static final long serialVersionUID = 8705622361027154428L; + public TimePoint getEndOfRace() { + return endOfR1; + } + }, "R1", /* medalRace */false); + leaderboard1.getScoreCorrection().correctScore(c[0], leaderboard1.getRaceColumnByName("R1"), 123.); + leaderboard1.getScoreCorrection().setMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), MaxPointsReason.DNF); + assertEquals(MaxPointsReason.NONE, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); + assertEquals(MaxPointsReason.NONE, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1)); + assertEquals(MaxPointsReason.DNF, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1)); + assertNull(leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); + assertEquals(1.0, leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1), 0.00000001); // tracked rank is 1 + assertEquals(123., leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1), 0.00000001); + } + + /** * A test case for bug 1802. Competitors that didn't have a fleet (null fleet) were sorted to the end of the column even * if they had a score correction assigned. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java index ee0aff0bcfe..c4cbb39ca47 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java @@ -324,11 +324,39 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { @Override public MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { - MaxPointsReason result = maxPointsReasons.get(raceColumn.getKey(competitor)); - if (result == null || !isMaxPointsReasonApplicable(result, timePoint, raceColumn, competitor)) { - result = MaxPointsReason.NONE; + return getAnnotatedMaxPointsReason(competitor, raceColumn, timePoint).getMaxPointsReason(); + } + + private static class AnnotatedMaxPointsReason { + private final MaxPointsReason maxPointsReason; + private final boolean maxPointsReasonExistsButIsNotApplicableForTimePoint; + public AnnotatedMaxPointsReason(MaxPointsReason maxPointsReason, + boolean maxPointsReasonExistsButIsNotApplicableForTimePoint) { + super(); + this.maxPointsReason = maxPointsReason; + this.maxPointsReasonExistsButIsNotApplicableForTimePoint = maxPointsReasonExistsButIsNotApplicableForTimePoint; } - return result; + public MaxPointsReason getMaxPointsReason() { + return maxPointsReason; + } + public boolean isMaxPointsReasonExistsButIsNotApplicableForTimePoint() { + return maxPointsReasonExistsButIsNotApplicableForTimePoint; + } + } + + private AnnotatedMaxPointsReason getAnnotatedMaxPointsReason(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + MaxPointsReason maxPointsReason = maxPointsReasons.get(raceColumn.getKey(competitor)); + boolean maxPointsReasonExistsButIsNotApplicableForTimePoint; + if (maxPointsReason == null) { + maxPointsReason = MaxPointsReason.NONE; + maxPointsReasonExistsButIsNotApplicableForTimePoint = false; + } else if (!isMaxPointsReasonApplicable(maxPointsReason, timePoint, raceColumn, competitor)) { + maxPointsReason = MaxPointsReason.NONE; + maxPointsReasonExistsButIsNotApplicableForTimePoint = true; + } else { + maxPointsReasonExistsButIsNotApplicableForTimePoint = false; + } + return new AnnotatedMaxPointsReason(maxPointsReason, maxPointsReasonExistsButIsNotApplicableForTimePoint); } private boolean isMaxPointsReasonApplicable(MaxPointsReason maxPointsReason, TimePoint timePoint, RaceColumn raceColumn, Competitor competitor) { @@ -360,15 +388,21 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, final ScoringScheme scoringScheme) { Double result; - final MaxPointsReason maxPointsReason = getMaxPointsReason(competitor, raceColumn, timePoint); - if (maxPointsReason == MaxPointsReason.NONE) { - result = getCorrectedNonMaxedScore(competitor, raceColumn, trackedRankProvider, scoringScheme, numberOfCompetitorsInLeaderboardFetcher, timePoint); + final AnnotatedMaxPointsReason maxPointsReason = getAnnotatedMaxPointsReason(competitor, raceColumn, timePoint); + if (maxPointsReason.getMaxPointsReason() == MaxPointsReason.NONE) { + // could be that there is a MaxPointsReason that just doesn't apply yet at timePoint; in this + // case ignore the score correction and deliver the uncorrected result + if (maxPointsReason.isMaxPointsReasonExistsButIsNotApplicableForTimePoint()) { + result = getUncorrectedScore(competitor, raceColumn, trackedRankProvider, scoringScheme, numberOfCompetitorsInLeaderboardFetcher, timePoint); + } else { + result = getCorrectedNonMaxedScore(competitor, raceColumn, trackedRankProvider, scoringScheme, numberOfCompetitorsInLeaderboardFetcher, timePoint); + } } else { // allow explicit override even when max points reason is specified; calculation may be wrong, // e.g., in case we have an untracked race and the number of competitors is estimated incorrectly Double correctedNonMaxedScore = correctedScores.get(raceColumn.getKey(competitor)); if (correctedNonMaxedScore == null) { - result = scoringScheme.getPenaltyScore(raceColumn, competitor, maxPointsReason, + result = scoringScheme.getPenaltyScore(raceColumn, competitor, maxPointsReason.getMaxPointsReason(), getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher), numberOfCompetitorsInLeaderboardFetcher); } else { @@ -379,7 +413,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { return new Result() { @Override public MaxPointsReason getMaxPointsReason() { - return maxPointsReason; + return maxPointsReason.getMaxPointsReason(); } @Override @@ -446,25 +480,34 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { Double correctedNonMaxedScore = correctedScores.get(raceColumn.getKey(competitor)); Double result; if (correctedNonMaxedScore == null || isCertainlyBeforeRaceFinish(timePoint, raceColumn, competitor)) { - try { - int trackedRank = trackedRankProvider.call(); - result = scoringScheme.getScoreForRank(leaderboard, raceColumn, competitor, - trackedRank, new Callable() { - @Override - public Integer call() { - return getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher); - } - }, numberOfCompetitorsInLeaderboardFetcher, timePoint); - } catch (Exception e) { - logger.log(Level.SEVERE, "Exception while computing corrected non maxed score", e); - throw new RuntimeException(e); - } + result = getUncorrectedScore(competitor, raceColumn, trackedRankProvider, scoringScheme, + numberOfCompetitorsInLeaderboardFetcher, timePoint); } else { result = correctedNonMaxedScore; } return result; } + private Double getUncorrectedScore(final Competitor competitor, final RaceColumn raceColumn, + Callable trackedRankProvider, ScoringScheme scoringScheme, + final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) { + final Double result; + try { + int trackedRank = trackedRankProvider.call(); + result = scoringScheme.getScoreForRank(leaderboard, raceColumn, competitor, + trackedRank, new Callable() { + @Override + public Integer call() { + return getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher); + } + }, numberOfCompetitorsInLeaderboardFetcher, timePoint); + } catch (Exception e) { + logger.log(Level.SEVERE, "Exception while computing uncorrected score", e); + throw new RuntimeException(e); + } + return result; + } + @Override public Double getExplicitScoreCorrection(Competitor competitor, RaceColumn raceColumn) { return correctedScores.get(raceColumn.getKey(competitor)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java index 2548380bd4e..4fe52c27d2e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java @@ -445,10 +445,7 @@ public class RaceContext { } // TODO do not calculate the winner if the blue flag is currently shown. try { - TimePoint finishTime = trackedRace.getEndOfRace(); - if (finishTime == null) { - finishTime = getLiveTimePoint(); - } + TimePoint finishTime = getLiveTimePoint(); List competitors = leaderboard.getCompetitorsFromBestToWorst(raceColumn, finishTime); if (competitors == null || competitors.isEmpty()) { return null; @@ -551,7 +548,7 @@ public class RaceContext { if (finishTime != null && now.after(finishTime)) { return RaceViewState.FINISHED; } - if(raceLog != null) { + if (raceLog != null) { RaceLogFlagEvent abortingFlagEvent = checkForAbortFlagEvent(); if (abortingFlagEvent != null) { Flags upperFlag = abortingFlagEvent.getUpperFlag(); @@ -567,10 +564,10 @@ public class RaceContext { } } ScoreCorrection scoreCorrection = leaderboard.getScoreCorrection(); - if(trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn)) { + if (trackedRace == null && scoreCorrection != null && scoreCorrection.hasCorrectionForNonTrackedFleet(raceColumn)) { return RaceViewState.FINISHED; } - if(startTime != null) { + if (startTime != null) { return RaceViewState.RUNNING; } return RaceViewState.PLANNED;