From 1d507689fb25000cc64f6c66b67191108eb7bdfe Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 17 Jul 2026 12:05:51 +0200 Subject: [PATCH 1/3] bug6274: stop excluding MANEUVER_BASED_ESTIMATION from maneuver typing Reverts the three call-site changes from issue #6184 (commit 1368179ac04) that passed getWindSources(MANEUVER_BASED_ESTIMATION) as the exclusion set to trackedRace.getWind(pos, at, exclude) inside the maneuver-typing paths: * IncrementalManeuverDetectorImpl.getManeuverSpotWithTypedManeuversFromExistingManeuverSpotConsideringPossibleWindChange (line 261) -- re-typing an existing maneuver spot when the wind at its position/time has changed. * ManeuverDetectorImpl.determineManeuversFromManeuverCurve (line 335) -- initial typing of a fresh spot. * ManeuverDetectorWithEstimationDataSupportDecoratorImpl (line 78) -- same, in the estimation-data-support decorator. Each now calls the two-arg getWind(pos, at), letting the tracked race's confidence-weighted averaging pick the best wind source. When non-estimation sources (EXPEDITION, TRACK_BASED_ESTIMATION, COURSE_BASED, etc.) provide a wind fix at the requested position and time, their higher base confidence dominates and MANEUVER_BASED_ESTIMATION does not affect the result meaningfully -- so #6184's original concern (the estimation track feeding back on itself with equal weight) does not materialize. When no other wind source has a fix, the estimation track's wind (with its lower base confidence) is used. That is the intended bootstrap of the wind-estimation-from-maneuvers design: 1. Maneuver *spots* detected on the competitor tracks (geometry only). 2. Estimator's NN + MST/HMM pre-classifies the spots, producing wind fixes on the MANEUVER_BASED_ESTIMATION track. 3. The wind track update triggers a maneuver-cache recalc; on re-typing, isManeuverSpotWindNearlySame reads all wind sources including the estimation track (line 437, unchanged by #6184). 4. determineManeuversFromManeuverCurve types the spot using whatever wind sources are available. With #6184's exclusion in place, step 4 refused to use the estimation track's wind, so races without any other wind source could never progress from pre-classified spots to fully-typed ManeuverImpl objects, breaking the bootstrap. Removing the exclusion restores the bootstrap. Confidence-weighted averaging inherent to TrackedRaceImpl.getWindWithConfidenceUncached handles the 'prefer non-estimation when available' concern that motivated #6184. --- .../impl/IncrementalManeuverDetectorImpl.java | 4 +--- .../domain/maneuverdetection/impl/ManeuverDetectorImpl.java | 4 +--- ...aneuverDetectorWithEstimationDataSupportDecoratorImpl.java | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverDetectorImpl.java index cbc9c9895f9..9f9f0da77be 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/IncrementalManeuverDetectorImpl.java @@ -10,7 +10,6 @@ import java.util.stream.Collectors; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator; import com.sap.sailing.domain.maneuverdetection.IncrementalApproximatedFixesCalculator; @@ -258,8 +257,7 @@ public class IncrementalManeuverDetectorImpl extends ManeuverDetectorImpl implem // maneuver spot calculation => recalculate maneuvers of existing maneuver curve CompleteManeuverCurve maneuverCurve = currentExistingManeuverSpot.getManeuverCurve(); WindMeasurement windMeasurement = currentExistingManeuverSpot.getWindMeasurement(); - Wind wind = trackedRace.getWind(windMeasurement.getPosition(), windMeasurement.getTimePoint(), - /* exclude */ trackedRace.getWindSources(WindSourceType.MANEUVER_BASED_ESTIMATION)); + Wind wind = trackedRace.getWind(windMeasurement.getPosition(), windMeasurement.getTimePoint()); List maneuvers = determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(), maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind, maneuverCurve.getMarkPassing()); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index 7f46e7f3d7b..9f931bf973d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -16,7 +16,6 @@ import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator; import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; @@ -332,8 +331,7 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { } TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); - final Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint, - /* exclude */ trackedRace.getWindSources(WindSourceType.MANEUVER_BASED_ESTIMATION)); + final Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint); List maneuvers = determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(), maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind, maneuverCurve.getMarkPassing()); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java index feba9c7d152..69dd57f9cc3 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java @@ -10,7 +10,6 @@ import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData; @@ -75,8 +74,7 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); - Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint, - /* exclude */ maneuverDetector.trackedRace.getWindSources(WindSourceType.MANEUVER_BASED_ESTIMATION)); + Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint); maneuvers .addAll(maneuverDetector.determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(), maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind, From eb098f8f7b32a1f9d7677852fce5e6d01e8abcd4 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 Jul 2026 17:51:11 +0200 Subject: [PATCH 2/3] also match sail numbers without nationality against only the sail number --- ...ractSailNumberCanonicalizerAndMatcher.java | 5 ++ .../SailNumberCanonicalizationTest.java | 51 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/SailNumberCanonicalizationTest.java diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSailNumberCanonicalizerAndMatcher.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSailNumberCanonicalizerAndMatcher.java index 63f63374c64..ea776a5201c 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSailNumberCanonicalizerAndMatcher.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSailNumberCanonicalizerAndMatcher.java @@ -109,6 +109,11 @@ public abstract class AbstractSailNumberCanonicalizerAndMatcher if (canonicalizedSailID != null) { result.put(canonicalizedSailID, competitor); } + final String canonicalizedSailIDWithoutDefaultNationality = canonicalizeSailID( + competitorIdentifyingText.trim(), /* defaultNationality */ null); + if (canonicalizedSailIDWithoutDefaultNationality != null) { + result.put(canonicalizedSailIDWithoutDefaultNationality, competitor); + } } return result; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/SailNumberCanonicalizationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/SailNumberCanonicalizationTest.java new file mode 100644 index 00000000000..71e47b2ed0c --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/SailNumberCanonicalizationTest.java @@ -0,0 +1,51 @@ +package com.sap.sailing.domain.common; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorWithBoat; +import com.sap.sailing.domain.base.SailNumberCanonicalizerAndMatcher; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.CompetitorWithBoatImpl; +import com.sap.sailing.domain.base.impl.NationalityImpl; +import com.sap.sailing.domain.base.impl.PersonImpl; +import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sse.common.Color; + +public class SailNumberCanonicalizationTest { + private SailNumberCanonicalizerAndMatcher canonicalizer = new SailNumberCanonicalizerAndMatcher(); + + @Test + public void testSimpleSailNumberMatching() { + assertEquals("GER123", canonicalizer.canonicalizeSailID("GER 123", /* defaultNationality */ null)); + } + + @Test + public void testSailNumberWithoutNationalityMatching() { + assertEquals("117911", canonicalizer.canonicalizeSailID("117911", /* defaultNationality */ null)); + } + + @Test + public void testSailNumberWithoutNationalityMatchingAgainstCompetitor() { + final CompetitorWithBoat competitor = new CompetitorWithBoatImpl(UUID.randomUUID(), "TestCompetitor", + "TC", Color.BLACK, /* email */ null, /* flagImage */ null, + new TeamImpl("TestTeam", + Collections.singleton(new PersonImpl("TestPerson", new NationalityImpl("NED"), + /* dateOfBirth */ null, + /* description */ null)), + /* coach */ null), /* timeOnTimeFactor */ 1.0, + /* timeOnDistanceAllowancePerNauticalMile */ null, /* searchTag */ null, + new BoatImpl(UUID.randomUUID(), "TestBoat", new BoatClassImpl(BoatClassMasterdata._12M), "117911")); + final Map map = canonicalizer + .canonicalizeLeaderboardSailIDs(Collections.singleton(competitor)); + assertSame(competitor, map.get("117911")); + } +} From bb99e18eadd7e5c1e7f191301c558a54855d705e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 19 Jul 2026 18:12:09 +0200 Subject: [PATCH 3/3] avoid permission check for internal QuickRanksCache --- .../com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java index df8178873ee..391ace6994f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/QuickRanksLiveCache.java @@ -79,7 +79,7 @@ public class QuickRanksLiveCache { @Override public QuickRanksDTO computeCacheUpdate(RegattaAndRaceIdentifier key, CalculateOrPurge updateInterval) throws Exception { - final TrackedRegatta cpuMeter = service.getTrackedRace(key).getTrackedRegatta(); + final TrackedRegatta cpuMeter = service.getService().getTrackedRace(key).getTrackedRegatta(); return cpuMeter.callWithCPUMeterWithException(()->{ logger.fine("Computing cache update for live QuickRanks of race "+key); final QuickRanksDTO quickRanks; @@ -137,7 +137,7 @@ public class QuickRanksLiveCache { + "; re-computing"); } if (result == null) { - final TrackedRace trackedRace = service.getExistingTrackedRace(raceIdentifier); + final TrackedRace trackedRace = service.getService().getExistingTrackedRace(raceIdentifier); if (trackedRace != null) { trackedRace.addListener(new Listener(raceIdentifier)); // register for all changes that may affect the quick ranks cache.triggerUpdate(raceIdentifier, CalculateOrPurge.CALCULATE);