Merge branch 'master' into bug5741

This commit is contained in:
Axel Uhl
2022-06-27 09:30:54 +02:00
13 changed files with 88 additions and 36 deletions
@@ -34,6 +34,7 @@ import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.FlexibleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRuleImpl;
@@ -334,6 +335,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
Collections.sort(ranksOfNonMedalStartedRaces);
int carryInt = (carry == null ? 0 : carry);
int netPoints = carryInt;
final ScoringScheme scoringScheme = leaderboard.getScoringScheme();
int medalRacePoints = getMedalRacePoints(competitorWithBoat, now, defaultFleet);
for (TrackedRace race : testRaces) {
RaceColumn raceColumn = raceColumnsInLeaderboard.get(race);
@@ -344,8 +346,8 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest {
assertEquals(rank, leaderboard.getContent(now).get(key).getTrackedRank());
assertEquals(rank, leaderboard.getEntry(competitorWithBoat, raceColumn, now).getTrackedRank());
assertEquals(rank, leaderboard.getTotalPoints(competitorWithBoat, raceColumn, now), 0.000000001);
assertEquals(rank, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001);
assertEquals(rank, leaderboard.getEntry(competitorWithBoat, raceColumn, now).getTotalPoints(), 0.000000001);
assertEquals(rank*scoringScheme.getScoreFactor(raceColumn), leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001);
assertEquals(rank*scoringScheme.getScoreFactor(raceColumn), leaderboard.getEntry(competitorWithBoat, raceColumn, now).getTotalPoints(), 0.000000001);
// One race is discarded because four races were started, and for [3-6) one race can be discarded.
// The discarded race is the worst of those started, so the one with rank 4.
int expectedNumberOfDiscardedRaces =
@@ -55,7 +55,7 @@ public class LeaderboardScoringAndRankingTestBase extends AbstractLeaderboardTes
double[][] scoresAfterNRaces, TimePoint timePoint, Competitor[] competitors) throws NoWindException {
for (int competitorIndex=0; competitorIndex<scoresAfterNRaces.length; competitorIndex++) {
final Set<RaceColumn> discardedRaceColumns = leaderboard.getResultDiscardingRule()
.getDiscardedRaceColumns(competitors[competitorIndex], leaderboard, raceColumnsToConsider, timePoint);
.getDiscardedRaceColumns(competitors[competitorIndex], leaderboard, raceColumnsToConsider, timePoint, leaderboard.getScoringScheme());
for (int raceColumnIndex=0; raceColumnIndex<raceColumnsToConsider.size(); raceColumnIndex++) {
assertEquals(scoresAfterNRaces[competitorIndex][raceColumnIndex],
leaderboard.getNetPoints(competitors[competitorIndex], raceColumnsToConsider.get(raceColumnIndex),
@@ -178,7 +178,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
*
* @param discardedRaceColumns
* expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint, ScoringScheme)
* getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
*/
Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint, Set<RaceColumn> discardedRaceColumns) throws NoWindException;
@@ -439,7 +439,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* Fetches all entries for all competitors of all races tracked by this leaderboard in one sweep. This saves some
* computational effort compared to fetching all entries separately, particularly because all
* {@link #isDiscarded(Competitor, RaceColumn, TimePoint) discarded races} of a competitor are computed in one
* sweep using {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)} only once.
* sweep using {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint, ScoringScheme)} only once.
* Note that in order to get the {@link #getNetPoints(Competitor, TimePoint) total points} for a competitor
* for the entire leaderboard, the {@link #getCarriedPoints(Competitor) carried-over points} need to be added.
*/
@@ -630,7 +630,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
*
* @param discardedRaceColumns
* expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint, ScoringScheme)
* getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
*/
Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
@@ -21,19 +21,20 @@ public interface ResultDiscardingRule extends Serializable {
/**
* @param raceColumnsToConsider if a column is <code>not</code> contained, its existence will be ignored for determining the
* columns to discard. It affects the count of races. Only columns contained can be part of the result.
* @param scoringScheme TODO
*/
Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint);
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint, ScoringScheme scoringScheme);
default Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
ScoringScheme scoringScheme, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return getDiscardedRaceColumns(competitor, leaderboard, raceColumnsToConsider, timePoint,
raceColumn->leaderboard.getTotalPoints(competitor, raceColumn, timePoint, cache), cache);
scoringScheme, raceColumn->leaderboard.getTotalPoints(competitor, raceColumn, timePoint, cache), cache);
}
Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint,
Function<RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache);
ScoringScheme scoringScheme,
Function<RaceColumn, Double> totalPointsSupplier, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache);
}
@@ -94,7 +94,7 @@ public interface ScoringScheme extends Serializable {
* scores of the second competitor, in the order of race columns in the leaderboard
* @param discardedRaceColumnsPerCompetitor
* for each competitor holds the result of {@link Leaderboard#getResultDiscardingRule()
* Leaderborad.getResultDiscardingRule()}{@code .}{@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
* Leaderborad.getResultDiscardingRule()}{@code .}{@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint, ScoringScheme)
* getDiscardedRaceColumns(...)}. This accelerates things considerable because we do not have to make this expensive calculation
* for each competitor again.
*/
@@ -117,7 +117,7 @@ public interface ScoringScheme extends Serializable {
* <p>
*
* <em>"A8.2 If a tie remains between two or more boats, they shall be ranked in order of their scores in the last
* race. Any remaining ties shall be broken by using the tied boats scores in the next-to-last race and so on until
* race. Any remaining ties shall be broken by using the tied boats scores in the next-to-last race and so on until
* all ties are broken. These scores shall be used even if some of them are excluded scores."</em>
*/
int compareByLastRace(List<Util.Pair<RaceColumn, Double>> o1Scores, List<Util.Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, Competitor o1, Competitor o2);
@@ -509,7 +509,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
addOverallDetailsToRow(timePoint, competitor, row);
}
result.competitors.add(competitorDTO);
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint, getScoringScheme());
for (final RaceColumn raceColumn : this.getRaceColumns()) {
// in case boats can't change set the also the boat on the row to simplify access
if (result.canBoatsOfCompetitorsChangePerRace == false && row.boat == null) {
@@ -34,6 +34,7 @@ import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetche
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
@@ -426,7 +427,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
raceColumnsToConsider, timePoint);
raceColumnsToConsider, timePoint, getScoringScheme());
return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns);
}
@@ -438,7 +439,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
*
* @param discardedRaceColumns
* expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint, ScoringScheme)
* getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
*/
private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint,
@@ -456,7 +457,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable<RaceColumn> raceColumnsToConsider,
TimePoint timePoint) {
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
raceColumnsToConsider, timePoint);
raceColumnsToConsider, timePoint, getScoringScheme());
return getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns);
}
@@ -468,7 +469,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
*
* @param discardedRaceColumns
* expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}.
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)
* {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint, ScoringScheme)
* getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}.
*/
@Override
@@ -508,7 +509,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
boolean needToResetScoreUponNextNonEmptyEntry = false;
double result = getCarriedPoints(competitor);
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
raceColumnsToConsider, timePoint);
raceColumnsToConsider, timePoint, getScoringScheme());
for (RaceColumn raceColumn : raceColumnsToConsider) {
if (raceColumn.isStartsWithZeroScore()) {
needToResetScoreUponNextNonEmptyEntry = true;
@@ -662,7 +663,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint)
throws NoWindException {
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
getRaceColumns(), timePoint);
getRaceColumns(), timePoint, getScoringScheme());
return getEntry(competitor, race, timePoint, discardedRaceColumns);
}
@@ -674,7 +675,9 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme());
boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns);
final Double correctedScore = correctedResults.getCorrectedScore();
return new EntryImpl(trackedRankProvider, correctedScore, () -> correctedResults.getUncorrectedScore(),
final Double correctedScoreScaledByColumnFactor = correctedScore == null ? null
: Double.valueOf((correctedScore * getScoringScheme().getScoreFactor(race)));
return new EntryImpl(trackedRankProvider, correctedScoreScaledByColumnFactor, () -> correctedResults.getUncorrectedScore(),
correctedResults.isCorrected(),
discarded ? DOUBLE_0
: correctedScore == null ? null : Double.valueOf(correctedScore * getScoringScheme().getScoreFactor(race)),
@@ -743,16 +746,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
if (discardedRacesForCompetitor == null) {
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this,
getRaceColumns(), timePoint);
getRaceColumns(), timePoint, getScoringScheme());
discardedRaces.put(competitor, discardedRacesForCompetitor);
}
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
final Double correctedScore = correctedResults.getCorrectedScore();
Entry entry = new EntryImpl(trackedRankProvider, correctedScore,
final Double correctedScoreScaledByColumnFactor = correctedScore == null ? null
: Double.valueOf((correctedScore * getScoringScheme().getScoreFactor(raceColumn)));
Entry entry = new EntryImpl(trackedRankProvider, correctedScoreScaledByColumnFactor,
() -> correctedResults.getUncorrectedScore(), correctedResults.isCorrected(),
discarded ? DOUBLE_0
: (correctedScore == null ? null
: Double.valueOf((correctedScore * getScoringScheme().getScoreFactor(raceColumn)))),
discarded ? DOUBLE_0 : correctedScoreScaledByColumnFactor,
correctedResults.getMaxPointsReason(), discarded, raceColumn.getFleetOfCompetitor(competitor));
result.put(new com.sap.sse.common.Util.Pair<Competitor, RaceColumn>(competitor, raceColumn), entry);
}
@@ -93,7 +93,7 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
discardedRaceColumnsPerCompetitor = new HashMap<>();
for (Competitor competitor : leaderboard.getCompetitors()) {
Set<RaceColumn> discardedRaceColumns = leaderboard.getResultDiscardingRule().getDiscardedRaceColumns(
competitor, leaderboard, raceColumnsToConsider, timePoint);
competitor, leaderboard, raceColumnsToConsider, timePoint, leaderboard.getScoringScheme());
this.discardedRaceColumnsPerCompetitor.put(competitor, discardedRaceColumns);
for (RaceColumn raceColumn : raceColumnsToConsider) {
Pair<Competitor, RaceColumn> key = new Util.Pair<Competitor, RaceColumn>(competitor, raceColumn);
@@ -11,6 +11,7 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
@@ -35,19 +36,21 @@ public class PerSeriesResultDiscardingRuleImpl implements ResultDiscardingRule {
@Override
public Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
return getDiscardedRaceColumns(competitor, leaderboard, raceColumnsToConsider, timePoint, new LeaderboardDTOCalculationReuseCache(timePoint));
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint, ScoringScheme scoringScheme) {
return getDiscardedRaceColumns(competitor, leaderboard, raceColumnsToConsider, timePoint, scoringScheme, new LeaderboardDTOCalculationReuseCache(timePoint));
}
@Override
public Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint,
Function<RaceColumn, Double> totalPointsSupplier, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
ScoringScheme scoringScheme, Function<RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
final Set<RaceColumn> result = new HashSet<>();
for (final Series s : regatta.getSeries()) {
if (s.getResultDiscardingRule() != null) {
final Iterable<RaceColumn> columnsToConsiderInSeries = getColumnsToConsiderInSeries(s, raceColumnsToConsider);
result.addAll(s.getResultDiscardingRule().getDiscardedRaceColumns(competitor, leaderboard, columnsToConsiderInSeries, timePoint, totalPointsSupplier, cache));
result.addAll(s.getResultDiscardingRule().getDiscardedRaceColumns(competitor, leaderboard, columnsToConsiderInSeries,
timePoint, scoringScheme, totalPointsSupplier, cache));
}
}
return result;
@@ -21,6 +21,7 @@ import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
@@ -80,14 +81,15 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes
@Override
public Set<RaceColumn> getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, final TimePoint timePoint) {
return getDiscardedRaceColumns(competitor, leaderboard, raceColumnsToConsider, timePoint, new LeaderboardDTOCalculationReuseCache(timePoint));
Iterable<RaceColumn> raceColumnsToConsider, final TimePoint timePoint, ScoringScheme scoringScheme) {
return getDiscardedRaceColumns(competitor, leaderboard, raceColumnsToConsider, timePoint, scoringScheme, new LeaderboardDTOCalculationReuseCache(timePoint));
}
@Override
public Set<RaceColumn> getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, final TimePoint timePoint,
Function<RaceColumn, Double> totalPointsSupplier, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
ScoringScheme scoringScheme, Function<RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
int resultsToDiscard = getNumberOfResultsToDiscard(competitor, raceColumnsToConsider, leaderboard, timePoint);
final Set<RaceColumn> result;
if (resultsToDiscard > 0) {
@@ -96,7 +98,9 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes
for (final RaceColumn raceColumn : raceColumnsToConsider) {
if (raceColumn.isDiscardable()) {
sortedRaces.add(raceColumn);
totalPointsForCompetitorPerColumn.put(raceColumn, totalPointsSupplier.apply(raceColumn));
final Double totalPoints = totalPointsSupplier.apply(raceColumn);
totalPointsForCompetitorPerColumn.put(raceColumn,
totalPoints == null ? null : (totalPoints * scoringScheme.getScoreFactor(raceColumn)));
}
}
result = new HashSet<RaceColumn>();
@@ -11,6 +11,10 @@
did not properly grant its permissions to requests.</li>
<li>Fixed a regression regarding the availability of <tt>/sailingserver/api/v1/regattas/{regatta-name}/datamining/...</tt>
requests; a refactoring attempt accidentally introduced this problem which has now been resolved.</li>
<li>Fixed an issue with column factors other than 1 in combination with excluding worst scores; previously,
the points before multiplication by the column factor were compared for discarding.
With this, the display of those discards has also been fixed such that the scaled number is displayed
with a strike-through.</li>
</ul>
<h5 class="articleSubheadline">May 2022</h5>
<ul class="bulletList">
@@ -141,7 +141,7 @@ public class MarkPassingsJsonSerializer extends AbstractTrackedRaceDataJsonSeria
return totalPointsForRaceColumn;
};
final Set<RaceColumn> discardedRaceColumns = leaderboard.getResultDiscardingRule().getDiscardedRaceColumns(competitor, leaderboard,
leaderboard.getRaceColumns(), markPassing.getTimePoint(), totalPointsSupplier, cache);
leaderboard.getRaceColumns(), markPassing.getTimePoint(), leaderboard.getScoringScheme(), totalPointsSupplier, cache);
final Double netPoints = leaderboard.getNetPoints(competitor, raceColumnAndFleet.getA(),
markPassing.getTimePoint(), discardedRaceColumns, ()->totalPoints);
markPassingJson.put(POINTS_BASED_ON_PASSING_ORDER, totalPoints);
@@ -94,6 +94,41 @@ public class LeaderboardDiscardingRulesTest {
assertEquals(123.+123., netPointsHasso, 0.000000001);
}
/**
* See bug 892: If a competitor has two or more equally-scored races of which the to-be-discarded number
* has to be discarded, ensure that not only one but all of them are discarded and that the sailor isn't awarded
* more races sailed than the competition
*/
@Test
public void testDiscardingRulesWithColumnFactor() throws NoWindException {
racingEventService.removeLeaderboard(LEADERBOARDNAME);
racingEventService.addFlexibleLeaderboard(LEADERBOARDNAME, null, new int[] { 2 }, new LowPoint(), Collections.emptySet());
FlexibleLeaderboard leaderboard = (FlexibleLeaderboard) racingEventService.getLeaderboardByName(LEADERBOARDNAME);
assertNotNull(leaderboard);
BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("505", /* typicallyStartsUpwind */ true);
CompetitorWithBoat hasso = AbstractLeaderboardTest.createCompetitorWithBoat("Dr. Hasso Plattner");
final TrackedRace race1 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 5, /* started */ true, boatClass);
leaderboard.addRace(race1, "R1", /* medalRace */false).setFactor(2.0); // results in score 10
final TrackedRace race2 = new MockedTrackedRaceWithFixedRank(hasso, /* rank */ 9, /* started */ true, boatClass);
leaderboard.addRace(race2, "R2", /* medalRace */false);
CompetitorWithBoat wolfgang = AbstractLeaderboardTest.createCompetitorWithBoat("Wolfgang Hunger");
final TrackedRace race3 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 123, /* started */ true, boatClass);
leaderboard.addRace(race3, "R3", /* medalRace */false);
final TrackedRace race4 = new MockedTrackedRaceWithFixedRank(wolfgang, /* rank */ 124, /* started */ true, boatClass);
leaderboard.addRace(race4, "R4", /* medalRace */false);
leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R3"), 6.);
leaderboard.getScoreCorrection().correctScore(hasso, leaderboard.getRaceColumnByName("R4"), 3.);
leaderboard.getScoreCorrection().correctScore(wolfgang, leaderboard.getRaceColumnByName("R1"), 1.); // results in score 2.0
leaderboard.getScoreCorrection().correctScore(wolfgang, leaderboard.getRaceColumnByName("R2"), 2.);
TimePoint now = MillisecondsTimePoint.now();
// Hunger scores better than Plattner in this case because Hunger's two discards are 123/124, keeping 122/122
// whereas Plattner is to discard two of the four 123 results, keeping 123/123, being two points worse than Hunger:
double netPointsHasso = leaderboard.getNetPoints(hasso, now);
double netPointsWolfgang = leaderboard.getNetPoints(wolfgang, now);
assertEquals(123. + 2. + 2., netPointsWolfgang, 0.0000000001); // discards the 124.0 in R4
assertEquals(9. + 6. + 3., netPointsHasso, 0.000000001);
}
/**
* See bug 892: If a competitor has two or more equally-scored races of which the to-be-discarded number
* has to be discarded, ensure that not only one but all of them are discarded and that the sailor isn't awarded