bug4881: added a few TODOs and added Leaderboard.mapRegattaScoreCorrections

This commit is contained in:
Axel Uhl
2020-02-07 18:32:03 +01:00
parent 2e3c6dd892
commit d1184428fa
7 changed files with 176 additions and 1 deletions
@@ -25,6 +25,9 @@ import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.RegattaScoreCorrections;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.security.SecuredDomainType;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
@@ -763,4 +766,28 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns {
* given as the key, and as the second pair component the number of computations in that time period.
*/
Map<Duration, Pair<Duration, Integer>> getComputationTimeStatistics();
/**
* Matches the results in {@code regattaScoreCorrections} to the {@link RaceColumn}s and {@link Competitor}s in this
* leaderboard. The race columns are identified in {@code regattaScoreCorrections} by the
* {@link ScoreCorrectionsForRace#getRaceNameOrNumber()} result and the ordering of the
* {@link ScoreCorrectionsForRace} objects as delivered by
* {@link RegattaScoreCorrections#getScoreCorrectionsForRaces()} as compared to the {@link #getRaceColumns()}
* ordering of this leaderboard, furthermore the explicit mappings specified in
* {@code raceNumberOrNameToRaceColumnMap}. The competitor mapping happens based on the
* {@link ScoreCorrectionForCompetitorInRace#getSailID()} result that is compared to the {@link Competitor}'s
* {@link Competitor#getShortName() short name} if the boats can change in this leaderboard, or to the
* {@link Boat#getSailID()} result of the competitor's boat, overruled by the explicit mappings in
* {@code sailIdToCompetitorMap}.
*
* @param allowRaceDefaultsByOrder
* if {@code true}, an attempt will be made to map the race names/numbers from the
* {@link RegattaScoreCorrections} to the leaderboard's {@link RaceColumn}s by their ordering, one by
* one, but only for those that are not mapped explicitly by {@code raceNumberOrNameToRaceColumnMap}.
* If there are excess race names/numbers in the {@link RegattaScoreCorrections} objects beyond the
* number of race columns in this leaderboard, no mapping will be inferred for the excess races.
*/
ScoreCorrectionMapping mapRegattaScoreCorrections(RegattaScoreCorrections regattaScoreCorrections,
Map<String, RaceColumn> raceNumberOrNameToRaceColumnMap, Map<String, Competitor> sailIdToCompetitorMap,
boolean allowRaceDefaultsByOrder);
}
@@ -0,0 +1,36 @@
package com.sap.sailing.domain.leaderboard;
import java.util.Map;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RegattaScoreCorrections;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sse.common.Util.Pair;
/**
* Describes the result of
* {@link Leaderboard#mapRegattaScoreCorrections(com.sap.sailing.domain.common.RegattaScoreCorrections, java.util.Map, java.util.Map, boolean)
* mapping} a {@link RegattaScoreCorrections} object to an existing {@link Leaderboard}. For each
* {@link RegattaScoreCorrections#getScoreCorrectionsForRaces() race} for which there are score corrections, the mapping of the
* {@link ScoreCorrectionsForRace#getRaceNameOrNumber() race name or number} to a {@link RaceColumn} in the leaderboard is captured.
* If no matching race column was assigned, that race name/number is mapped to {@code null} instead. Likewise, for all sail IDs/numbers
* (that may alternatively be interpreted as competitor short names if the boat assignments can change per race in the leaderboard)
* the matching {@link Competitor} is recorded where a {@code null} value again means that for the key sail ID/number/shortname no
* matching competitor was identified.<p>
*
* All {@link ScoreCorrectionForCompetitorInRace} from the original {@link RegattaScoreCorrections} object that were mapped
* successfully to a {@link RaceColumn} and a {@link Competitor} can be obtained using the {@link #getScoreCorrections} method.
*
* @author Axel Uhl (D043530)
*
*/
public interface ScoreCorrectionMapping {
Map<String, RaceColumn> getRaceMappings();
Map<String, Competitor> getCompetitorMappings();
Map<RaceColumn, Map<Competitor, Pair<Double, MaxPointsReason>>> getScoreCorrections();
}
@@ -47,6 +47,8 @@ import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.RegattaScoreCorrections;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.dto.BasicRaceDTO;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
@@ -64,6 +66,7 @@ import com.sap.sailing.domain.common.tracking.BravoExtendedFix;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.ScoreCorrectionMapping;
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
@@ -1371,4 +1374,29 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
}
return false;
}
@Override
public ScoreCorrectionMapping mapRegattaScoreCorrections(RegattaScoreCorrections regattaScoreCorrections,
Map<String, RaceColumn> raceNumberOrNameToRaceColumnMap, Map<String, Competitor> sailIdToCompetitorMap,
boolean allowRaceDefaultsByOrder) {
final Map<String, RaceColumn> raceMappings = new HashMap<>(raceNumberOrNameToRaceColumnMap);
final Map<String, Competitor> competitorMappings = new HashMap<>(sailIdToCompetitorMap);
final Iterator<RaceColumn> raceColumnIterator = getRaceColumns().iterator();
for (final ScoreCorrectionsForRace raceCorrection : regattaScoreCorrections.getScoreCorrectionsForRaces()) {
final RaceColumn currentRaceColumn = raceColumnIterator.hasNext() ? raceColumnIterator.next() : null;
raceMappings.computeIfAbsent(raceCorrection.getRaceNameOrNumber(), raceNameOrNumber->
allowRaceDefaultsByOrder ? currentRaceColumn : null);
for (final String sailIdOrShortName : raceCorrection.getSailIDs()) {
competitorMappings.computeIfAbsent(sailIdOrShortName, sailIdsOrShortName->findBestMatchingCompetitorBySailNumberOrShortName(sailIdsOrShortName));
}
}
return new ScoreCorrectionMappingImpl(raceMappings, competitorMappings, regattaScoreCorrections);
}
private Competitor findBestMatchingCompetitorBySailNumberOrShortName(String sailIdsOrShortName) {
// TODO move MatchAndApplyScoreCorrectionsDialog.canonicalizeSailID and getCompetitorIdentifyingText to a common package both sides can see and use
// TODO then use it to match this leaderboard's competitors against the sailIdsOrShortName, passed through canonicalizeSailID
// TODO Implement AbstractLeaderboardWithCache.findBestMatchingCompetitorBySailNumberOrShortName(...)
return null;
}
}
@@ -0,0 +1,57 @@
package com.sap.sailing.domain.leaderboard.impl;
import java.util.HashMap;
import java.util.Map;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RegattaScoreCorrections;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionForCompetitorInRace;
import com.sap.sailing.domain.common.RegattaScoreCorrections.ScoreCorrectionsForRace;
import com.sap.sailing.domain.leaderboard.ScoreCorrectionMapping;
import com.sap.sse.common.Util.Pair;
public class ScoreCorrectionMappingImpl implements ScoreCorrectionMapping {
private final Map<String, RaceColumn> raceMappings;
private final Map<String, Competitor> competitorMappings;
private final RegattaScoreCorrections regattaScoreCorrections;
public ScoreCorrectionMappingImpl(Map<String, RaceColumn> raceMappings, Map<String, Competitor> competitorMappings,
RegattaScoreCorrections regattaScoreCorrections) {
super();
this.raceMappings = raceMappings;
this.competitorMappings = competitorMappings;
this.regattaScoreCorrections = regattaScoreCorrections;
}
@Override
public Map<String, RaceColumn> getRaceMappings() {
return raceMappings;
}
@Override
public Map<String, Competitor> getCompetitorMappings() {
return competitorMappings;
}
@Override
public Map<RaceColumn, Map<Competitor, Pair<Double, MaxPointsReason>>> getScoreCorrections() {
final Map<RaceColumn, Map<Competitor, Pair<Double, MaxPointsReason>>> result = new HashMap<>();
for (final ScoreCorrectionsForRace raceCorrection : regattaScoreCorrections.getScoreCorrectionsForRaces()) {
final RaceColumn raceColumn = getRaceMappings().get(raceCorrection.getRaceNameOrNumber());
if (raceColumn != null) {
final Map<Competitor, Pair<Double, MaxPointsReason>> competitorMapForRace = result.computeIfAbsent(raceColumn, k->new HashMap<>());
for (final String sailIdOrShortName : raceCorrection.getSailIDs()) {
final Competitor competitor = getCompetitorMappings().get(sailIdOrShortName);
final ScoreCorrectionForCompetitorInRace competitorCorrection = raceCorrection.getScoreCorrectionForCompetitor(sailIdOrShortName);
if (competitor != null && competitorCorrection != null) {
competitorMapForRace.put(competitor, new Pair<>(competitorCorrection.getPoints(), competitorCorrection.getMaxPointsReason()));
}
}
}
}
return result;
}
}
@@ -230,11 +230,17 @@ public class LeaderboardsResourceTest extends AbstractJaxRsApiTest {
@Test
public void testXrrScoreImport() throws Exception {
dropAndCreateRegatta();
final TimePoint validityTimePoint = MillisecondsTimePoint.now();
final String comment = "A Comment";
Response leaderboardResponse = leaderboardsResource.uploadResults(regatta.getName(),
/* scoreCorrectionProviderName */ "ISAF XML Regatta Result (XRR) Importer",
/* validity time point */ validityTimePoint.asMillis(),
/* comment */ comment,
getClass().getClassLoader().getResourceAsStream("YES_29er_XRR.xml"));
assertNotNull(leaderboardResponse);
JSONObject jsonObject = (JSONObject) JSONValue.parse((String) leaderboardResponse.getEntity());
assertNotNull(jsonObject);
assertEquals(comment, regattaLeaderboard.getScoreCorrection().getComment());
assertEquals(validityTimePoint, regattaLeaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity());
}
}
@@ -133,6 +133,7 @@ import com.sap.sailing.server.interfaces.RacingEventService;
import com.sap.sailing.server.operationaltransformation.RemoveAndUntrackRace;
import com.sap.sailing.server.operationaltransformation.StopTrackingRace;
import com.sap.sailing.server.operationaltransformation.UpdateLeaderboard;
import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardScoreCorrectionMetadata;
import com.sap.sailing.server.security.PermissionAwareRaceTrackingHandler;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.Bearing;
@@ -146,6 +147,7 @@ import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.DegreeBearingImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.security.SessionUtils;
import com.sap.sse.security.shared.HasPermissions.DefaultActions;
import com.sap.sse.security.shared.OwnershipAnnotation;
import com.sap.sse.security.shared.WithQualifiedObjectIdentifier;
@@ -1625,6 +1627,8 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource {
@Produces(MediaType.APPLICATION_JSON)
public Response uploadResults(@PathParam("name") String leaderboardName,
@QueryParam("scoreCorrectionProvider") String scoreCorrectionProviderName,
@QueryParam("timePointOfLastCorrectionValidityMillis") Long timePointOfLastCorrectionValidityMillis,
@QueryParam("comment") String comment,
InputStream inputStream) throws Exception {
final Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName);
if (leaderboard == null) {
@@ -1636,6 +1640,24 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource {
throw new NotFoundException("score correction provider with name " + scoreCorrectionProviderName + " not found");
}
final RegattaScoreCorrections scoreCorrection = scoreCorrectionProvider.get().getScoreCorrections(inputStream);
if (comment != null || timePointOfLastCorrectionValidityMillis != null) {
final String finalComment;
if (comment == null) {
finalComment = leaderboard.getScoreCorrection().getComment();
} else {
finalComment = comment;
}
final TimePoint timePointOfLastCorrectionValidity;
if (timePointOfLastCorrectionValidityMillis == null) {
timePointOfLastCorrectionValidity = leaderboard.getScoreCorrection().getTimePointOfLastCorrectionsValidity();
} else {
timePointOfLastCorrectionValidity = new MillisecondsTimePoint(timePointOfLastCorrectionValidityMillis);
}
logger.info("Applying score correction comment \""+finalComment+"\" and validity time point "+timePointOfLastCorrectionValidity+
" to leaderboard "+leaderboardName+" on behalf of "+SessionUtils.getPrincipal());
getService().apply(
new UpdateLeaderboardScoreCorrectionMetadata(leaderboardName, timePointOfLastCorrectionValidity, finalComment));
}
return applyScoreCorrectionToLeaderboard(leaderboard, scoreCorrection);
}
@@ -51,5 +51,4 @@ public class UpdateLeaderboardScoreCorrectionMetadata extends AbstractLeaderboar
updateStoredLeaderboard(toState, leaderboard);
return null;
}
}