mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-08 13:20:57 +00:00
working towards bug 3798: implemented scoring specials, optionally limiting the number of discards in a series
Change-Id: Ifa5ec9e14791aac6bfc9648450d91e80a49039ad
This commit is contained in:
1 parent
0450b59fa0
commit
60dbb42d67
4 files changed
+164
-5
No files matched your search
+102
@@ -36,6 +36,7 @@ import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnInSeries;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.Series;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
@@ -2101,4 +2102,105 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See bug 3798: special discarding rule that limits the number of discards for the final series; configured
|
||||
* by {@link Series#getMaximumNumberOfDiscards()}.
|
||||
*/
|
||||
@Test
|
||||
public void testDiscardingWithLimitOnFinalSeries() throws NoWindException {
|
||||
series = new ArrayList<Series>();
|
||||
// -------- qualification series ------------
|
||||
{
|
||||
List<Fleet> qualificationFleets = new ArrayList<Fleet>();
|
||||
for (String qualificationFleetName : new String[] { "Yellow", "Blue" }) {
|
||||
qualificationFleets.add(new FleetImpl(qualificationFleetName));
|
||||
}
|
||||
List<String> qualificationRaceColumnNames = new ArrayList<String>();
|
||||
for (int q=1; q<=7; q++) {
|
||||
qualificationRaceColumnNames.add("Q"+q);
|
||||
}
|
||||
Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualificationFleets, qualificationRaceColumnNames, /* trackedRegattaRegistry */ null);
|
||||
series.add(qualificationSeries);
|
||||
}
|
||||
|
||||
// -------- final series ------------
|
||||
{
|
||||
List<Fleet> finalFleets = new ArrayList<Fleet>();
|
||||
int fleetOrdering = 1;
|
||||
for (String finalFleetName : new String[] { "Gold", "Silver" }) {
|
||||
finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++));
|
||||
}
|
||||
List<String> finalRaceColumnNames = new ArrayList<String>();
|
||||
for (int f=1; f<=7; f++) {
|
||||
finalRaceColumnNames.add("F"+f);
|
||||
}
|
||||
Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null);
|
||||
finalSeries.setMaximumNumberOfDiscards(1);
|
||||
series.add(finalSeries);
|
||||
}
|
||||
final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true);
|
||||
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null,
|
||||
series, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", /* course area */null, OneDesignRankingMetric::new);
|
||||
final int TOTAL_NUMBER_OF_COMPETITORS = 100;
|
||||
List<Competitor> competitors = createCompetitors(TOTAL_NUMBER_OF_COMPETITORS);
|
||||
final int firstYellowCompetitorIndex = TOTAL_NUMBER_OF_COMPETITORS/4;
|
||||
List<Competitor> yellow = new ArrayList<>(competitors.subList(firstYellowCompetitorIndex, firstYellowCompetitorIndex+TOTAL_NUMBER_OF_COMPETITORS/2));
|
||||
List<Competitor> blue = new ArrayList<>(competitors);
|
||||
blue.removeAll(yellow);
|
||||
Collections.shuffle(yellow);
|
||||
Collections.shuffle(blue);
|
||||
final int firstGoldCompetitorIndex = TOTAL_NUMBER_OF_COMPETITORS/3;
|
||||
List<Competitor> gold = new ArrayList<>(competitors.subList(firstGoldCompetitorIndex, firstGoldCompetitorIndex+TOTAL_NUMBER_OF_COMPETITORS/2));
|
||||
List<Competitor> silver = new ArrayList<>(competitors);
|
||||
silver.removeAll(gold);
|
||||
Collections.shuffle(gold);
|
||||
Collections.shuffle(silver);
|
||||
|
||||
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[] { 5, 10 });
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
|
||||
for (int q = 1; q <= 7; q++) {
|
||||
RaceColumn qColumn = series.get(0).getRaceColumnByName("Q"+q);
|
||||
TrackedRace qYellow = new MockedTrackedRaceWithStartTimeAndRanks(now, yellow);
|
||||
qColumn.setTrackedRace(qColumn.getFleetByName("Yellow"), qYellow);
|
||||
TrackedRace qBlue = new MockedTrackedRaceWithStartTimeAndRanks(now, blue);
|
||||
qColumn.setTrackedRace(qColumn.getFleetByName("Blue"), qBlue);
|
||||
}
|
||||
for (int f = 1; f <= 7; f++) {
|
||||
RaceColumn fColumn = series.get(1).getRaceColumnByName("F"+f);
|
||||
TrackedRace f1Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, gold);
|
||||
fColumn.setTrackedRace(fColumn.getFleetByName("Gold"), f1Gold);
|
||||
TrackedRace f1Silver = new MockedTrackedRaceWithStartTimeAndRanks(now, silver);
|
||||
fColumn.setTrackedRace(fColumn.getFleetByName("Silver"), f1Silver);
|
||||
}
|
||||
|
||||
for (final Competitor c : competitors) {
|
||||
int totalDiscards = 0;
|
||||
int discardsInFinalSeries = 0;
|
||||
int numberOfRacesInFinalThatWereWorseThanWorstQualificationRace = 0;
|
||||
double worstQualificationScore = 0;
|
||||
for (final RaceColumn rc : leaderboard.getRaceColumns()) {
|
||||
double score = leaderboard.getTotalPoints(c, rc, later);
|
||||
if (rc instanceof RaceColumnInSeries && ((RaceColumnInSeries) rc).getSeries() == regatta.getSeriesByName("Qualification")) {
|
||||
if (score > worstQualificationScore) {
|
||||
worstQualificationScore = score;
|
||||
}
|
||||
} else {
|
||||
if (score > worstQualificationScore) {
|
||||
numberOfRacesInFinalThatWereWorseThanWorstQualificationRace++;
|
||||
}
|
||||
}
|
||||
if (leaderboard.isDiscarded(c, rc, later)) {
|
||||
totalDiscards++;
|
||||
if (rc instanceof RaceColumnInSeries && ((RaceColumnInSeries) rc).getSeries() == regatta.getSeriesByName("Final")) {
|
||||
discardsInFinalSeries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(2, totalDiscards);
|
||||
assertTrue(discardsInFinalSeries <= 1);
|
||||
assertTrue(numberOfRacesInFinalThatWereWorseThanWorstQualificationRace == 0 || discardsInFinalSeries > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,18 @@ public interface Series extends SeriesBase {
|
||||
|
||||
void setResultDiscardingRule(ThresholdBasedResultDiscardingRule resultDiscardingRule);
|
||||
|
||||
/**
|
||||
* If not {@code null}, defines an upper inclusive limit for the number of races that may be discarded from
|
||||
* this series. For example, when setting this to {@code 1} for a final series in a regatta that has a
|
||||
* qualification and a final series, when the second discard becomes available and the series don't define
|
||||
* their own discarding rules, two discards may be picked from the qualification series, but at most one
|
||||
* could be selected in the final even if another final race has a score worse than that of all
|
||||
* qualification races.
|
||||
*/
|
||||
Integer getMaximumNumberOfDiscards();
|
||||
|
||||
void setMaximumNumberOfDiscards(Integer maximumNumberOfDiscards);
|
||||
|
||||
Regatta getRegatta();
|
||||
|
||||
/**
|
||||
@@ -118,4 +130,5 @@ public interface Series extends SeriesBase {
|
||||
boolean hasSplitFleetContiguousScoring();
|
||||
|
||||
void setSplitFleetContiguousScoring(boolean hasSplitFleetScore);
|
||||
|
||||
}
|
||||
@@ -40,6 +40,16 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
|
||||
private Regatta regatta;
|
||||
private final RaceColumnListeners raceColumnListeners;
|
||||
private ThresholdBasedResultDiscardingRule resultDiscardingRule;
|
||||
|
||||
/**
|
||||
* If not {@code null}, defines an upper inclusive limit for the number of races that may be discarded from
|
||||
* this series. For example, when setting this to {@code 1} for a final series in a regatta that has a
|
||||
* qualification and a final series, when the second discard becomes available and the series don't define
|
||||
* their own discarding rules, two discards may be picked from the qualification series, but at most one
|
||||
* could be selected in the final even if another final race has a score worse than that of all
|
||||
* qualification races.
|
||||
*/
|
||||
private Integer maximumNumberOfDiscards;
|
||||
|
||||
/**
|
||||
* If set, the series doesn't take over the scores from any previous series but starts with zero scores for all its
|
||||
@@ -391,6 +401,16 @@ public class SeriesImpl extends RenamableImpl implements Series, RaceColumnListe
|
||||
this.resultDiscardingRule = resultDiscardingRule;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getMaximumNumberOfDiscards() {
|
||||
return maximumNumberOfDiscards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaximumNumberOfDiscards(Integer maximumNumberOfDiscards) {
|
||||
this.maximumNumberOfDiscards = maximumNumberOfDiscards;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean definesSeriesDiscardThresholds() {
|
||||
return getResultDiscardingRule() != null;
|
||||
|
||||
+29
-5
@@ -16,6 +16,8 @@ import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
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.ThresholdBasedResultDiscardingRule;
|
||||
@@ -77,7 +79,7 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes
|
||||
public Set<RaceColumn> getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard,
|
||||
Iterable<RaceColumn> raceColumnsToConsider, final TimePoint timePoint) {
|
||||
int resultsToDiscard = getNumberOfResultsToDiscard(competitor, raceColumnsToConsider, leaderboard, timePoint);
|
||||
Set<RaceColumn> result;
|
||||
final Set<RaceColumn> result;
|
||||
if (resultsToDiscard > 0) {
|
||||
final Map<RaceColumn, Double> totalPointsForCompetitorPerColumn = new HashMap<>();
|
||||
List<RaceColumn> sortedRaces = new ArrayList<RaceColumn>();
|
||||
@@ -101,14 +103,12 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes
|
||||
}
|
||||
};
|
||||
Collections.sort(sortedRaces, comparator);
|
||||
int i=0;
|
||||
Iterator<RaceColumn> badRacesIter = sortedRaces.iterator();
|
||||
while (badRacesIter.hasNext() && i<resultsToDiscard) {
|
||||
while (badRacesIter.hasNext() && result.size()<resultsToDiscard) {
|
||||
final RaceColumn badRace = badRacesIter.next();
|
||||
final MaxPointsReason maxPointsReason = leaderboard.getMaxPointsReason(competitor, badRace, timePoint);
|
||||
if (maxPointsReason == null || maxPointsReason.isDiscardable()) {
|
||||
result.add(badRace);
|
||||
i++;
|
||||
addDiscardIfWithinSeriesLimits(result, badRace);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -117,6 +117,30 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only adds the discard if the {@link Series} to which the {@code badRace} {@link RaceColumn} belongs
|
||||
* does not define a separate limit for the number of its races that may be discarded, or if that number
|
||||
* is less than the number of races discarded so far from that series.
|
||||
*/
|
||||
private void addDiscardIfWithinSeriesLimits(Set<RaceColumn> result, RaceColumn badRace) {
|
||||
final Series series;
|
||||
if (!(badRace instanceof RaceColumnInSeries) ||
|
||||
(series = ((RaceColumnInSeries) badRace).getSeries()).getMaximumNumberOfDiscards() == null ||
|
||||
getRacesDiscardedFromSeries(result, series) < series.getMaximumNumberOfDiscards()) {
|
||||
result.add(badRace);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRacesDiscardedFromSeries(Set<RaceColumn> result, Series series) {
|
||||
int count = 0;
|
||||
for (final RaceColumn rc : result) {
|
||||
if (rc instanceof RaceColumnInSeries && ((RaceColumnInSeries) rc).getSeries() == series) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int getNumberOfResultsToDiscard(Competitor competitor, Iterable<RaceColumn> raceColumnsToConsider,
|
||||
Leaderboard leaderboard, TimePoint timePoint) {
|
||||
int numberOfResultsToDiscard;
|
||||
|
||||
Reference in new issue
Block a user