bug5877: interims commit, using distinct medal *series* for quarter/semi/grand final

This commit is contained in:
Axel Uhl
2023-07-21 15:28:15 +02:00
parent 031483b169
commit b616d76ccb
5 changed files with 235 additions and 32 deletions
@@ -1,39 +1,137 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.sap.sailing.domain.base.Competitor;
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.leaderboard.impl.LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal;
public class LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinalTest {
private Regatta regatta;
private LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal scoringScheme;
private RaceColumnInSeries quarterFinal;
private Series quarterFinalSeries;
private RaceColumnInSeries semiFinal;
private Series semiFinalSeries;
private RaceColumnInSeries grandFinal;
private Series grandFinalSeries;
private HashMap<Competitor, Integer> competitorsRankedByOpeningSeries;
private Competitor c1, c2, c3, c4, c5;
@Before
public void setUp() {
regatta = mock(Regatta.class);
scoringScheme = new LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal();
quarterFinal = mock(RaceColumnInSeries.class);
quarterFinalSeries = mockMedalSeries(regatta, quarterFinal);
semiFinal = mock(RaceColumnInSeries.class);
semiFinalSeries = mockMedalSeries(regatta, semiFinal);
grandFinal = mock(RaceColumnInSeries.class);
grandFinalSeries = mockMedalSeries(regatta, grandFinal);
when(regatta.getSeries()).thenAnswer(new Answer<Iterable<Series>>() {
@Override
public Iterable<Series> answer(InvocationOnMock invocation) throws Throwable {
return Arrays.asList(quarterFinalSeries, semiFinalSeries, grandFinalSeries);
}
});
when(quarterFinal.getSeries()).thenReturn(quarterFinalSeries);
when(semiFinal.getSeries()).thenReturn(semiFinalSeries);
when(grandFinal.getSeries()).thenReturn(grandFinalSeries);
c1 = mock(Competitor.class);
c2 = mock(Competitor.class);
c3 = mock(Competitor.class);
c4 = mock(Competitor.class);
c5 = mock(Competitor.class);
competitorsRankedByOpeningSeries = new HashMap<>();
competitorsRankedByOpeningSeries.put(c1, 1);
competitorsRankedByOpeningSeries.put(c2, 2);
competitorsRankedByOpeningSeries.put(c3, 3);
competitorsRankedByOpeningSeries.put(c4, 4);
competitorsRankedByOpeningSeries.put(c5, 5);
}
@Test
public void testCountingPromotedCompetitors() {
final LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal scoringScheme = new LowPointWithEliminatingMedalSeriesPromotingOneToFinalAndTwoToSemifinal();
final RaceColumnInSeries quarterFinal = mock(RaceColumnInSeries.class);
final RaceColumnInSeries semiFinal = mock(RaceColumnInSeries.class);
final RaceColumnInSeries grandFinal = mock(RaceColumnInSeries.class);
public void testCountingCompetitorsBetterThanThoseSailingInSeries() {
assertEquals(0, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInSeries(grandFinalSeries));
assertEquals(1, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInSeries(semiFinalSeries));
assertEquals(3, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInSeries(quarterFinalSeries));
}
@Test
public void testCountingCompetitorsAdvancingFromOpeningSeries() {
assertEquals(1, scoringScheme.getNumberOfCompetitorsAdvancingFromOpeningSeriesToOrThroughSeries(grandFinalSeries));
assertEquals(3, scoringScheme.getNumberOfCompetitorsAdvancingFromOpeningSeriesToOrThroughSeries(semiFinalSeries));
assertEquals(3, scoringScheme.getNumberOfCompetitorsAdvancingFromOpeningSeriesToOrThroughSeries(quarterFinalSeries));
}
@Test
public void testQuarterFinalParticipationForPromotedCompetitors() {
assertTrue(scoringScheme.isParticipatingInMedalRace(c1, /* competitorMedalRaceScore */ null, quarterFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c2, /* competitorMedalRaceScore */ null, quarterFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c3, /* competitorMedalRaceScore */ null, quarterFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c4, /* competitorMedalRaceScore */ null, quarterFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c5, /* competitorMedalRaceScore */ null, quarterFinal, ()->competitorsRankedByOpeningSeries));
}
@Test
public void testSemiFinalParticipationForPromotedCompetitors() {
assertTrue(scoringScheme.isParticipatingInMedalRace(c1, /* competitorMedalRaceScore */ null, semiFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c2, /* competitorMedalRaceScore */ null, semiFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c3, /* competitorMedalRaceScore */ null, semiFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c4, /* competitorMedalRaceScore */ null, semiFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c5, /* competitorMedalRaceScore */ null, semiFinal, ()->competitorsRankedByOpeningSeries));
}
@Test
public void testGrandFinalParticipationForPromotedCompetitors() {
assertTrue(scoringScheme.isParticipatingInMedalRace(c1, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c2, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c3, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c4, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c5, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
}
@Test
public void testGrandFinalParticipationForScoringCompetitors() {
assertTrue(scoringScheme.isParticipatingInMedalRace(c1, /* competitorMedalRaceScore */ 3.0, grandFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c2, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c3, /* competitorMedalRaceScore */ 1.0, grandFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c4, /* competitorMedalRaceScore */ 2.0, grandFinal, ()->competitorsRankedByOpeningSeries));
assertFalse(scoringScheme.isParticipatingInMedalRace(c5, /* competitorMedalRaceScore */ null, grandFinal, ()->competitorsRankedByOpeningSeries));
}
@Test
public void testMedalRaceParticipationForScoringCompetitors() {
assertTrue(scoringScheme.isParticipatingInMedalRace(c1, /* competitorMedalRaceScore */ 4.0, quarterFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c4, /* competitorMedalRaceScore */ 2.0, quarterFinal, ()->competitorsRankedByOpeningSeries));
assertTrue(scoringScheme.isParticipatingInMedalRace(c5, /* competitorMedalRaceScore */ 3.0, quarterFinal, ()->competitorsRankedByOpeningSeries));
}
private Series mockMedalSeries(Regatta regatta, RaceColumnInSeries... raceColumns) {
final Iterable<RaceColumnInSeries> medalSeriesColumns = Arrays.asList(raceColumns);
final Series medalSeries = mock(Series.class);
final Iterable<RaceColumnInSeries> medalSeriesColumns = Arrays.asList(quarterFinal, semiFinal, grandFinal);
when(medalSeries.getRegatta()).thenReturn(regatta);
when(medalSeries.isMedal()).thenReturn(true);
when(medalSeries.getRaceColumns()).thenAnswer(new Answer<Iterable<RaceColumnInSeries>>() {
@Override
public Iterable<RaceColumnInSeries> answer(InvocationOnMock invocation) throws Throwable {
return medalSeriesColumns;
}
});
when(quarterFinal.getSeries()).thenReturn(medalSeries);
when(semiFinal.getSeries()).thenReturn(medalSeries);
when(grandFinal.getSeries()).thenReturn(medalSeries);
assertEquals(0, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInRace(grandFinal));
assertEquals(1, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInRace(semiFinal));
assertEquals(3, scoringScheme.getNumberOfCompetitorsBetterThanThoseSailingInRace(quarterFinal));
return medalSeries;
}
}
@@ -13,12 +13,14 @@ import java.util.function.Supplier;
import com.sap.sailing.domain.base.Competitor;
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.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.impl.AbstractSimpleLeaderboardImpl;
import com.sap.sailing.domain.leaderboard.impl.LeaderboardTotalRankComparator;
import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
@@ -121,7 +123,8 @@ public interface ScoringScheme extends Serializable {
* With a lesser result encoding "better" the direction of default integer comparison between the two parameters is
* reversed.
*/
default int compareByMedalRaceParticipation(int zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, int zeroBasedIndexOfLastMedalSeriesInWhichO2Scored) {
default int compareByMedalRaceParticipation(int zeroBasedIndexOfLastMedalSeriesInWhichO1Scored,
int zeroBasedIndexOfLastMedalSeriesInWhichO2Scored) {
return -Integer.compare(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored);
}
@@ -373,4 +376,17 @@ public interface ScoringScheme extends Serializable {
BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache, TimePoint timePoint, int zeroBasedIndexOfLastMedalSeriesInWhichBothScored, int numberOfMedalRacesWonO1, int numberOfMedalRacesWonO2) {
return 0;
}
LeaderboardTotalRankComparator getOpeningSeriesRankComparator(boolean nullScoresAreBetter, TimePoint timePoint,
Leaderboard leaderboard, BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache);
/**
* This default implementation decides competitor participation in a medal race by having scored a non-{@code null}
* score.
*/
default boolean isParticipatingInMedalRace(Competitor competitor, Double competitorMedalRaceScore,
RaceColumnInSeries medalRaceColumn, Supplier<Map<Competitor, Integer>> competitorsRankedByOpeningSeries) {
return competitorMedalRaceScore != null;
}
}
@@ -183,7 +183,8 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme {
return Util.filter(leaderboard.getRaceColumns(), rc->!rc.isMedalRace());
}
protected LeaderboardTotalRankComparator getOpeningSeriesRankComparator(boolean nullScoresAreBetter,
@Override
public LeaderboardTotalRankComparator getOpeningSeriesRankComparator(boolean nullScoresAreBetter,
TimePoint timePoint, Leaderboard leaderboard,
BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
@@ -8,6 +8,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
@@ -62,6 +63,26 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
private final TimePoint timePoint;
private final WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache;
/**
* An on-demand map of competitors with their opening series rank, 1-based.<p>
*
* Some scoring schemes that support medal series may need to break ties or determine promotions to
* later rounds/stages based on the "opening series" results. The opening series then is considered
* to contain all races excluding {@link RaceColumn#isMedalRace() medal races}.<p>
*
* When using this comparator in multiple comparisons, the opening series ranking may then be required
* in many of these comparisons. It would be unnecessarily expensive to compute that ranking upon each
* such comparison. Instead, once requested by this comparator, it is cached and can be re-used by later
* comparisons performed by this comparator's {@link #compare(Competitor, Competitor)} method.<p>
*
* The implementation of the {@link Supplier} synchronizes on itself when {@link Supplier#get() asked}
* for its value. If the opening series ranking has not been computed yet then this happens while
* holding the supplier's monitor locked. This way, other threads waiting for the opening series
* ranking will wait for the computation to complete. If the ranking has already been computed it
* is returned immediately and the monitor is released quickly.
*/
private final Supplier<Map<Competitor, Integer>> competitorsRankedByOpeningSeries;
/**
* Considers only the race columns specified in <code>raceColumnsToConsider</code> and behaves as if the other columns
* were filled with <code>null</code> values. Those columns not considered do not count for determining the discards either.
@@ -114,6 +135,21 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
totalPointsCache.put(key, totalPoints);
}
}
competitorsRankedByOpeningSeries = new Supplier<Map<Competitor, Integer>>() {
private volatile Map<Competitor, Integer> openingSeriesRanking = null;
@Override
public synchronized Map<Competitor, Integer> get() {
if (openingSeriesRanking == null) {
final List<Competitor> result = Util.asList(leaderboard.getCompetitors());
Collections.sort(result, scoringScheme.getOpeningSeriesRankComparator(nullScoresAreBetter, timePoint, leaderboard, totalPointsSupplier, cache));
openingSeriesRanking = new HashMap<>();
for (int i=0; i<result.size(); i++) {
openingSeriesRanking.put(result.get(i), i+1);
}
}
return openingSeriesRanking;
}
};
}
protected Leaderboard getLeaderboard() {
@@ -208,10 +244,10 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
o1CarryForwardScoreInMedals = o1Score;
o2CarryForwardScoreInMedals = o2Score;
}
if (o1Score != null && raceColumn instanceof RaceColumnInSeries) {
if (raceColumn instanceof RaceColumnInSeries && scoringScheme.isParticipatingInMedalRace(o1, o1Score, (RaceColumnInSeries) raceColumn, competitorsRankedByOpeningSeries)) {
zeroBasedIndexOfLastMedalSeriesInWhichO1Scored = getZeroBasedIndexOfSeries((RaceColumnInSeries) raceColumn);
}
if (o2Score != null && raceColumn instanceof RaceColumnInSeries) {
if (raceColumn instanceof RaceColumnInSeries && scoringScheme.isParticipatingInMedalRace(o2, o2Score, (RaceColumnInSeries) raceColumn, competitorsRankedByOpeningSeries)) {
zeroBasedIndexOfLastMedalSeriesInWhichO2Scored = getZeroBasedIndexOfSeries((RaceColumnInSeries) raceColumn);
}
// similar to compareByFleet, however, tracking is not required; having medal race column points
@@ -2,11 +2,29 @@ package com.sap.sailing.domain.leaderboard.impl;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.function.Supplier;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sse.common.Util;
/**
* A special low-point scoring scheme that uses one or more medal series to eliminate some competitors
* during each such medal series. Competitors may be promoted to later series based on their opening
* series results. For example, the scheme may define that the winner of the opening series automatically
* advances to the final medal series, and the second and third ranking competitors advance to the last-but-one
* medal series already; all others have to race a medal race/series to qualify for the next series, and
* some of those will be eliminated on the way.<p>
*
* The number of competitors from the opening series that qualify to later medal series is provided as
* an array to the {@link #LowPointWithEliminatingMedalSeriesWithPromotions(int[]) constructor}.
*
* @author Axel Uhl (d043530)
*
*/
public abstract class LowPointWithEliminatingMedalSeriesWithPromotions extends LowPoint {
private static final long serialVersionUID = 7759999270911627798L;
@@ -22,14 +40,17 @@ public abstract class LowPointWithEliminatingMedalSeriesWithPromotions extends L
/**
*
* @param numberOfPromotedCompetitorsIntoLastMedalRaces
* The last index for this array refers to the last medal race; e.g., the "Grand Final" race in an iQFOil
* regatta with the medal series consisting of a "Quarter Final," a "Semi Final" and a "Grand Final". The
* last-but-one index in the example would then refer to the Semi Final, and so on. The array may be
* The last index for this array refers to the last medal series; e.g., the "Grand Final" race in an iQFOil
* regatta with three medal series ("Quarter Final," "Semi Final" and "Grand Final"). The
* last-but-one index in the example would then refer to the Semi Final series, and so on. The array may be
* empty, meaning that no competitor is promoted into any medal race. The field always has to refer to a
* valid array.
*/
public LowPointWithEliminatingMedalSeriesWithPromotions(int[] numberOfPromotedCompetitorsIntoLastMedalRaces) {
super();
if (numberOfPromotedCompetitorsIntoLastMedalRaces == null) {
throw new NullPointerException("array specifying number of promoted competitors must not be null");
}
this.numberOfPromotedCompetitorsIntoLastMedalRaces = numberOfPromotedCompetitorsIntoLastMedalRaces;
}
@@ -38,26 +59,57 @@ public abstract class LowPointWithEliminatingMedalSeriesWithPromotions extends L
return super.getType();
}
public int getNumberOfCompetitorsBetterThanThoseSailingInRace(RaceColumnInSeries medalRace) {
final List<? extends RaceColumnInSeries> allMedalRacesInSeries = Util.asList(medalRace.getSeries().getRaceColumns());
/**
* Counts the number of competitors in a medal series that, without sailing in it, will be ranked better
* than those that do sail in it. This is because these many competitors have already advanced to later
* medal series based on their opening series rank.
*/
public int getNumberOfCompetitorsBetterThanThoseSailingInSeries(Series medalSeries) {
final List<? extends Series> allMedalSeries = Util.asList(Util.filter(medalSeries.getRegatta().getSeries(), series->series.isMedal()));
int result = 0;
int indexInNumberOfPromotedCompetitorsIntoLastMedalRaces = numberOfPromotedCompetitorsIntoLastMedalRaces.length-1;
if (allMedalRacesInSeries.size() > 1) {
for (final ListIterator<? extends RaceColumnInSeries> i=allMedalRacesInSeries.listIterator(allMedalRacesInSeries.size()); i.hasPrevious() && i.previous() != medalRace; ) {
if (allMedalSeries.size() > 1) {
for (final ListIterator<? extends Series> i=allMedalSeries.listIterator(allMedalSeries.size()); i.hasPrevious() && i.previous() != medalSeries; ) {
result += numberOfPromotedCompetitorsIntoLastMedalRaces[indexInNumberOfPromotedCompetitorsIntoLastMedalRaces--];
}
}
return result;
}
public int getNumberOfCompetitorsAdvancingFromOpeningSeriesToOrThroughSeries(Series medalSeries) {
final List<? extends Series> allMedalSeries = Util.asList(Util.filter(medalSeries.getRegatta().getSeries(), series->series.isMedal()));
int result = 0;
if (numberOfPromotedCompetitorsIntoLastMedalRaces.length > 0) {
int indexInNumberOfPromotedCompetitorsIntoLastMedalRaces = numberOfPromotedCompetitorsIntoLastMedalRaces.length;
if (!allMedalSeries.isEmpty()) {
result = numberOfPromotedCompetitorsIntoLastMedalRaces[--indexInNumberOfPromotedCompetitorsIntoLastMedalRaces];
for (final ListIterator<? extends Series> i=allMedalSeries.listIterator(allMedalSeries.size());
indexInNumberOfPromotedCompetitorsIntoLastMedalRaces > 0 && i.hasPrevious() && i.previous() != medalSeries; ) {
result += numberOfPromotedCompetitorsIntoLastMedalRaces[--indexInNumberOfPromotedCompetitorsIntoLastMedalRaces];
}
}
}
return result;
}
/**
* In addition to the default implementation (assumed to check for a non-{@code null} medal race score}, this
* specialized implementation is aware of the promotion scheme and defines competitors as "participants" of the
* medal race represented by {@code medalRaceColumn} if their opening series rank was
* {@link #getNumberOfCompetitorsBetterThanThoseSailingInSeries(RaceColumnInSeries) good enough} to have been promoted
* to {@code medalRaceColumn}'s or a later medal series already. In this case it is not necessary for the competitor
* to have <em>scored</em> in {@code medalRaceColumn} yet.<p>
*
* Note: Since the promotion decision is made based on the opening series rank, promotion is considered even if
* more opening series races are to come. This, however, will not change the results because the promotion scheme
* will keep the rank ordering among those promoted.
*/
@Override
public int compareByMedalRaceParticipation(int zeroBasedIndexOfLastMedalSeriesInWhichO1Scored,
int zeroBasedIndexOfLastMedalSeriesInWhichO2Scored) {
// TODO We'd like to consider those as "having participated" in a medal race that may not have a score but have been promoted already to a later medal race based on their opening series result;
// For that we need to check all medal race columns and need to know the two competitors.
// If promoted to a later medal race but not sailed in the one currently considered, consider as "has participated."
// If both participated, move on to next medal race column, if any. If last medal race column, return 0.
// If one competitor is considered as "has participated" and the other one not, prefer the one that "has participated" over the one that has not.
return super.compareByMedalRaceParticipation(zeroBasedIndexOfLastMedalSeriesInWhichO1Scored, zeroBasedIndexOfLastMedalSeriesInWhichO2Scored);
public boolean isParticipatingInMedalRace(Competitor competitor, Double competitorMedalRaceScore,
RaceColumnInSeries medalRaceColumn, Supplier<Map<Competitor, Integer>> competitorsRankedByOpeningSeries) {
final Integer openingSeriesRank;
return super.isParticipatingInMedalRace(competitor, competitorMedalRaceScore, medalRaceColumn, competitorsRankedByOpeningSeries) ||
((openingSeriesRank = competitorsRankedByOpeningSeries.get().get(competitor)) != null &&
openingSeriesRank <= getNumberOfCompetitorsAdvancingFromOpeningSeriesToOrThroughSeries(medalRaceColumn.getSeries()));
}
}