Merge remote-tracking branch 'origin/master' into bug4930

This commit is contained in:
Allard
2023-04-25 09:34:48 -04:00
21 changed files with 214 additions and 39 deletions
@@ -6,7 +6,7 @@ public enum ScoringSchemeType {
HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, HIGH_POINT_WINNER_GETS_EIGHT,
HIGH_POINT_WINNER_GETS_EIGHT_AND_INTERPOLATION, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT_2017, LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07,
LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY, LOW_POINT_FIRST_TO_WIN_TWO_RACES, LOW_POINT_FIRST_TO_WIN_THREE_RACES,
HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD;
HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD, LOW_POINT_A82_ONLY, LOW_POINT_FIRST_TO_WIN_THREE_RACES_A82_ONLY;
public static double getScaledScore(double columnFactor, double unscaledScore, boolean oneAlwaysStaysOne) {
return unscaledScore * columnFactor - (oneAlwaysStaysOne ? columnFactor-1 : 0);
@@ -14,6 +14,10 @@ public enum RaceLogRaceStatus {
RaceLogRaceStatus(int orderNumber) {
this.orderNumber = orderNumber;
}
public boolean isAbortingFlagFromPreviousPassValid() {
return this == UNSCHEDULED || this == PRESCHEDULED;
}
public static boolean isPreRunning(RaceLogRaceStatus status) {
return status != null &&
@@ -242,8 +242,9 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
RaceLogFlagEvent abortingFlagEvent = null;
for (final RaceLog raceLog : trackedRace.getAttachedRaceLogs()) {
if (abortingFlagEvent == null) {
final RaceLogRaceStatus status = ReadonlyRaceStateImpl.getOrCreate(trackedRace.getRaceLogResolver(), raceLog).getStatus();
if (status == RaceLogRaceStatus.UNSCHEDULED || status == RaceLogRaceStatus.PRESCHEDULED) {
final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.getOrCreate(trackedRace.getRaceLogResolver(), raceLog);
final RaceLogRaceStatus status = raceState.getStatus();
if (status.isAbortingFlagFromPreviousPassValid()) {
final AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog);
abortingFlagEvent = abortingFlagFinder.analyze();
}
@@ -57,7 +57,9 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsEightAndInterp
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive;
import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.leaderboard.impl.LowPointA82Only;
import com.sap.sailing.domain.leaderboard.impl.LowPointFirstToWinThreeRaces;
import com.sap.sailing.domain.leaderboard.impl.LowPointFirstToWinThreeRacesA82Only;
import com.sap.sailing.domain.leaderboard.impl.LowPointFirstToWinTwoRaces;
import com.sap.sailing.domain.leaderboard.impl.LowPointForLeagueOverallLeaderboard;
import com.sap.sailing.domain.leaderboard.impl.LowPointTieBreakBasedOnLastSeriesOnly;
@@ -158,6 +160,10 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl<RaceLogAndTracked
return new LowPointFirstToWinThreeRaces();
case HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD:
return new HighPointByWinsTiesLastlyBrokenByOtherLeaderboard();
case LOW_POINT_A82_ONLY:
return new LowPointA82Only();
case LOW_POINT_FIRST_TO_WIN_THREE_RACES_A82_ONLY:
return new LowPointFirstToWinThreeRacesA82Only();
}
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* Eliminates the A8.1 comparison (sort scores by better/worst and compare one by one) and hence
* defaults to A8.2 (last race, second-to-last race, etc.).
*
* @author Axel Uhl (d043530)
*
*/
public class LowPointA82Only extends LowPoint {
private static final long serialVersionUID = 4715596377202890920L;
@Override
public int compareByBetterScore(Competitor o1, List<Pair<RaceColumn, Double>> o1Scores, Competitor o2,
List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint,
Leaderboard leaderboard, Map<Competitor, Set<RaceColumn>> discardedRaceColumnsPerCompetitor,
BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return 0;
}
@Override
public ScoringSchemeType getType() {
return ScoringSchemeType.LOW_POINT_A82_ONLY;
}
}
@@ -345,7 +345,7 @@ public class LowPointFirstToWinThreeRaces extends LowPoint {
BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier, WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
final int result;
if (!hasMedalScores(o1Scores)) {
result = super.compareByBetterScore(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard,
result = compareByA81TieBreak(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard,
discardedRaceColumnsPerCompetitor, totalPointsSupplier, cache);
} else {
final Iterable<RaceColumn> openingSeriesRaceColumns = getOpeningSeriesRaceColumns(leaderboard);
@@ -357,6 +357,23 @@ public class LowPointFirstToWinThreeRaces extends LowPoint {
return result;
}
/**
* Computes a tie break; this default implementation delegates to the super-class implementation which is
* expected to compute a regular A8.1 tie break, sorting the scores so the best are first, and then looking
* for the first difference.<p>
*
* Subclasses may change this behavior, e.g., so that 0 is returned and hence A8.1 is ignored; this will
* usually then default to a comparison based on A8.2 (last race).
*/
protected int compareByA81TieBreak(Competitor o1, List<Pair<RaceColumn, Double>> o1Scores, Competitor o2,
List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint,
Leaderboard leaderboard, Map<Competitor, Set<RaceColumn>> discardedRaceColumnsPerCompetitor,
BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return super.compareByBetterScore(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard,
discardedRaceColumnsPerCompetitor, totalPointsSupplier, cache);
}
/**
* Merge non-medal series columns, preserving order across {@code raceColumnsO1} and {@code raceColumnsO2}.
*/
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingAndORCPerformanceCurveCache;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
/**
* Turns off the A8.1 comparison for opening series tie-breaking, thus defaulting to A8.2 (last race,
* then second-to-last, etc.).
*
* @author Axel Uhl (d043530)
*
*/
public class LowPointFirstToWinThreeRacesA82Only extends LowPointFirstToWinThreeRaces {
private static final long serialVersionUID = 8820858236331467431L;
@Override
protected int compareByA81TieBreak(Competitor o1, List<Pair<RaceColumn, Double>> o1Scores, Competitor o2,
List<Pair<RaceColumn, Double>> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint,
Leaderboard leaderboard, Map<Competitor, Set<RaceColumn>> discardedRaceColumnsPerCompetitor,
BiFunction<Competitor, RaceColumn, Double> totalPointsSupplier,
WindLegTypeAndLegBearingAndORCPerformanceCurveCache cache) {
return 0;
}
@Override
public ScoringSchemeType getType() {
return ScoringSchemeType.LOW_POINT_FIRST_TO_WIN_THREE_RACES_A82_ONLY;
}
}
@@ -220,8 +220,8 @@ public class RaceContext {
lastFlagsDisplayedStateChanged = previousFlagState.hasPoleChanged(mostInterestingFlagPole);
}
}
switch (state.getStatus()) {
case FINISHED:
final RaceLogRaceStatus status = state.getStatus();
if (status == RaceLogRaceStatus.FINISHED) {
TimeRange protestTime = state.getProtestTime();
if (protestTime != null) {
lastUpperFlag = Flags.BRAVO;
@@ -229,8 +229,7 @@ public class RaceContext {
lastFlagsAreDisplayed = true;
lastFlagsDisplayedStateChanged = true;
}
break;
case UNSCHEDULED:
} else if (state.getStatus().isAbortingFlagFromPreviousPassValid()) {
// search for race aborting in last pass
AbortingFlagFinder abortingFlagFinder = new AbortingFlagFinder(raceLog);
RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze();
@@ -240,20 +239,14 @@ public class RaceContext {
lastFlagsAreDisplayed = abortingFlagEvent.isDisplayed();
lastFlagsDisplayedStateChanged = true;
}
break;
case FINISHING:
case RUNNING:
case SCHEDULED:
case PRESCHEDULED:
case STARTPHASE:
case UNKNOWN:
break;
}
;
final FlagStateDTO result;
if (lastUpperFlag != null || lastLowerFlag != null) {
return new FlagStateDTO(lastUpperFlag, lastLowerFlag, lastFlagsAreDisplayed, lastFlagsDisplayedStateChanged);
result = new FlagStateDTO(lastUpperFlag, lastLowerFlag, lastFlagsAreDisplayed, lastFlagsDisplayedStateChanged);
} else {
result = null;
}
return null;
return result;
}
private String getCourseAreaOrNull() {
@@ -592,7 +585,7 @@ public class RaceContext {
RaceLogFlagEvent abortingFlagEvent = abortingFlagFinder.analyze();
if (abortingFlagEvent != null) {
RaceLogRaceStatus lastStatus = state.getStatus();
if (lastStatus.equals(RaceLogRaceStatus.UNSCHEDULED)) {
if (lastStatus.isAbortingFlagFromPreviousPassValid()) {
result = abortingFlagEvent;
}
}
@@ -2426,4 +2426,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboard();
String scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboardDescription();
String helptextLinkingRaces();
String scoringSchemeLowPointA82Only();
String scoringSchemeLowPointA82OnlyDescription();
String scoringSchemeLowPointSystemFirstThreeWinsA82Only();
String scoringSchemeLowPointSystemFirstThreeWinsA82OnlyDescription();
}
@@ -2463,3 +2463,7 @@ otherTieBreakingLeaderboard=Other tie-breaking leaderboard
scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboard=High Point System, Match Racing, Tie-Break Based on other Leaderboard
scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboardDescription=The winner of a race gets 1 point, all others 0; ties are broken on last race, then based on another leaderboard
helptextLinkingRaces=In order to link races to tracked races, you must left-click a race in the table below and after that left-click the corresponding entry in the tracked races table on the right.
scoringSchemeLowPointA82Only=Low Point System; Tie-Break According to RRS A8.2 (Last Race)
scoringSchemeLowPointA82OnlyDescription=Low Point System; Tie-Break According to RRS A8.2; If a tie remains after looking at the medal race participation and scores, compare the scores in the last race (including scores excluded), then the second-to-last race, etc., until the tie is broken.
scoringSchemeLowPointSystemFirstThreeWinsA82Only=Low Point System, First with three wins in medals is winner; A8.2 tie-break (last race)
scoringSchemeLowPointSystemFirstThreeWinsA82OnlyDescription=Low Point System. The first in the medal series to win three races wins the medal series. A carry-forward column in the medal series can be used to model wins carried over. Tie-breaking in the opening series is based on A8.2 (last race, then second-to-last, etc.).
@@ -2458,4 +2458,8 @@ goToSelfServicePortalDialogText=Self-Service Portal des Abonementanbieters in ei
failedFetchingSelfServicePortalSession=Self-Service Portal konnte nicht geöffnet werden.
oneAlwaysStaysOne=Wertung 1 bleibt 1, unabhängig vom Spalten-Faktor
otherTieBreakingLeaderboard=Andere Rangliste zum Tie-Breaking
helptextLinkingRaces=Um unverknüpfte Wettfahrten mit getrackten Rennen zu verknüpfen, müssen sie in der untenstehenden Tabelle eine Wettfahrt linksklicken und danach das entsprechende getrackte Rennen in der rechten Ansicht linksklicken.
helptextLinkingRaces=Um unverknüpfte Wettfahrten mit getrackten Rennen zu verknüpfen, müssen sie in der untenstehenden Tabelle eine Wettfahrt linksklicken und danach das entsprechende getrackte Rennen in der rechten Ansicht linksklicken.
scoringSchemeLowPointA82Only=Low Point System; Tie-Break gemäß RRS A8.2 (Letztes Rennen)
scoringSchemeLowPointA82OnlyDescription=Low Point System; Tie-Break gemäß RRS A8.2; Falls ein Unentschieden nach dem Vergleich der Medal-Rennen verbleibt, vergleiche die Wertungen im letzten non-Medal Rennen, dann dem zweitletzten Rennen usw., bis ein Unterschied auftritt. Gestrichene Ergebnisse werden dabei ebenfalls berücksichtigt.
scoringSchemeLowPointSystemFirstThreeWinsA82Only=Low Point System, Erster mit drei Siegen in Medaillen-Rennen gewinnt; Tie-Break nach A8.2 (Letztes Rennen)
scoringSchemeLowPointSystemFirstThreeWinsA82OnlyDescription=Low Point System. Der Erste, der drei Rennen in den Medaillen-Rennen gewinnt, zählt als Sieger der Medaillen-Serie. Eine Übertragsspalte in der Medaillen-Serie kann für vorgetragene Siege verwendet werden; Tie-Break in der Eröffnungs-Serie gemäß A8.2 (letztes Rennen, dann zweitletztes Rennen usw.)
@@ -52,6 +52,10 @@ public class ScoringSchemeTypeFormatter {
return stringMessages.scoringSchemeLowPointSystemFirstThreeWins();
case HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD:
return stringMessages.scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboard();
case LOW_POINT_A82_ONLY:
return stringMessages.scoringSchemeLowPointA82Only();
case LOW_POINT_FIRST_TO_WIN_THREE_RACES_A82_ONLY:
return stringMessages.scoringSchemeLowPointSystemFirstThreeWinsA82Only();
}
return null;
}
@@ -84,6 +88,10 @@ public class ScoringSchemeTypeFormatter {
return stringMessages.scoringSchemeLowPointSystemFirstThreeWinsDescription();
case HIGH_POINT_BY_WINS_TIES_LASTLY_BROKEN_BY_OTHER_LEADERBOARD:
return stringMessages.scoringSchemeHighPointsByWindTiesLastlyBrokenByOtherLeaderboardDescription();
case LOW_POINT_A82_ONLY:
return stringMessages.scoringSchemeLowPointA82OnlyDescription();
case LOW_POINT_FIRST_TO_WIN_THREE_RACES_A82_ONLY:
return stringMessages.scoringSchemeLowPointSystemFirstThreeWinsA82OnlyDescription();
default:
return format(scoringSchemeType, stringMessages);
}
@@ -1062,8 +1062,7 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
if (abortingFlagEvent != null) {
raceInfoDTO.isRaceAbortedInPassBefore = true;
raceInfoDTO.abortingTimeInPassBefore = abortingFlagEvent.getLogicalTimePoint().asDate();
if (raceInfoDTO.lastStatus == RaceLogRaceStatus.UNSCHEDULED || raceInfoDTO.lastStatus == RaceLogRaceStatus.PRESCHEDULED) {
if (raceInfoDTO.lastStatus.isAbortingFlagFromPreviousPassValid()) {
raceInfoDTO.lastUpperFlag = abortingFlagEvent.getUpperFlag();
raceInfoDTO.lastLowerFlag = abortingFlagEvent.getLowerFlag();
raceInfoDTO.lastFlagsAreDisplayed = abortingFlagEvent.isDisplayed();
@@ -128,6 +128,7 @@ public class LandscapeManagementPanel extends SimplePanel {
//Spacing for Setupbar (AWS/SSH/REGION)
private static final int DEFAULT_SETUPBAR_HEIGHT = 300;
private static final int DEFAULT_AMOUNT_SSHKEYS_PER_PAGE = 3;
public LandscapeManagementPanel(StringMessages stringMessages, UserService userService,
AdminConsoleTableResources tableResources, ErrorReporter errorReporter) {
@@ -155,7 +156,7 @@ public class LandscapeManagementPanel extends SimplePanel {
}
};
sshKeyManagementPanel = new SshKeyManagementPanel(stringMessages, userService,
landscapeManagementService, tableResources, errorReporter, /* access key provider */ mfaLoginWidget, regionsTable.getSelectionModel());
landscapeManagementService, tableResources, errorReporter, /* access key provider */ mfaLoginWidget, regionsTable.getSelectionModel(), DEFAULT_AMOUNT_SSHKEYS_PER_PAGE);
final CaptionPanel sshKeysCaptionPanel = new CaptionPanel(stringMessages.sshKeys());
sshKeysCaptionPanel.setHeight("" + DEFAULT_SETUPBAR_HEIGHT + "px");
regionsTable.addColumn(new TextColumn<String>() {
@@ -55,7 +55,7 @@ public class SshKeyManagementPanel extends VerticalPanel {
public SshKeyManagementPanel(StringMessages stringMessages, UserService userService,
LandscapeManagementWriteServiceAsync landscapeManagementService, AdminConsoleTableResources tableResources,
ErrorReporter errorReporter, AwsAccessKeyProvider awsAccessKeyProvider,
RefreshableSingleSelectionModel<String> regionSelectionModel) {
RefreshableSingleSelectionModel<String> regionSelectionModel, int countKeysPerPage) {
this.regionSelectionModel = regionSelectionModel;
this.landscapeManagementService = landscapeManagementService;
this.errorReporter = errorReporter;
@@ -92,6 +92,7 @@ public class SshKeyManagementPanel extends VerticalPanel {
return Arrays.asList(t.getName(), t.getRegionId(), t.getCreatorName());
}
};
sshKeyTable.getTable().setPageSize(countKeysPerPage);
sshKeyTable.addColumn(object->object.getRegionId(), stringMessages.region());
sshKeyTable.addColumn(object->object.getName(), stringMessages.name());
sshKeyTable.addColumn(object->object.getCreatorName(), stringMessages.creator());
@@ -95,10 +95,10 @@ public class IncrementalAnyOrderLeastSquaresImpl implements IncrementalLeastSqua
}
public IncrementalAnyOrderLeastSquaresImpl(int polynomialOrder, boolean hasIntercept,
boolean useSymbollicInversionIfPossible) {
boolean useSymbolicInversionIfPossible) {
this.hasIntercept = hasIntercept;
this.polynomialOrder = polynomialOrder;
this.useSymbollicInversionIfPossible = useSymbollicInversionIfPossible;
this.useSymbollicInversionIfPossible = useSymbolicInversionIfPossible;
if (hasIntercept) {
matrixOfXSums = new double[polynomialOrder + 1][polynomialOrder + 1];
vectorOfXYMultSums = new double[polynomialOrder + 1];
@@ -1,13 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/model/SimpleModelsTrainingPart2.java"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.windestimation.model.SimpleModelsTrainingPart2"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.windestimation.lab"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Dmongo.port=10202 -Dmongo.dbName=windestimation"/>
<booleanAttribute key="org.eclipse.debug.core.ATTR_FORCE_SYSTEM_CONSOLE_ENCODING" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.windestimation.lab/src/com/sap/sailing/windestimation/model/SimpleModelsTrainingPart2.java"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE" value="true"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.sap.sailing.windestimation.model.SimpleModelsTrainingPart2"/>
<stringAttribute key="org.eclipse.jdt.launching.MODULE_NAME" value="com.sap.sailing.windestimation.lab"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.windestimation.lab"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-ea -Dmongo.port=10200 -Dmongo.dbName=windestimation"/>
</launchConfiguration>
@@ -23,9 +23,13 @@
<div class="mainContent">
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
<div class="innerContent">
<h2 class="articleSubheadline">March 2023</h2>
<h2 class="articleSubheadline">April 2023</h2>
<ul class="bulletList">
<li>Added boat classes "IMOCA", "VO65" and "VO60"</li>
<li>Implemented a new scoring scheme that ignores A8.1 tie-breaking and only uses A8.2 (last race,
second-to-last, etc.)</li>
<li>Implemented a new scoring scheme that supports the Formula Kite medal series format but
ignores A8.1 tie-breaking and only uses A8.2 (last race, second-to-last, etc.)</li>
</ul>
<h2 class="articleSubheadline">March 2023</h2>
<ul class="bulletList">