added new handicapping services
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -35,9 +35,9 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
<version>4.12</version>
|
<version>5.9.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
package com.poststats.golf.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
|
||||||
|
public interface CourseHoleService {
|
||||||
|
|
||||||
|
List<? extends FlexMap> getHolesByNineTee(long nineteeId);
|
||||||
|
|
||||||
|
List<? extends FlexMap> getHolesByEighteenTee(long eighteenteeId);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
package com.poststats.golf.service;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
|
||||||
|
public interface CourseRatingService {
|
||||||
|
|
||||||
|
FlexMap getNineTeeRating(long ntratingId);
|
||||||
|
|
||||||
|
FlexMap getEighteenTeeRating(long etratingId);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,63 @@
|
|||||||
|
package com.poststats.golf.service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public interface HandicapIndexService<I, R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method computes a golfer's handicap index as of today, inclusive.
|
||||||
|
*
|
||||||
|
* This will consider the use of all individually played non-event and event
|
||||||
|
* rounds. Each implementation may exclude more rounds, like unsigned or unrated
|
||||||
|
* courses.
|
||||||
|
*
|
||||||
|
* @param personId A unique identifier for the golfer.
|
||||||
|
* @return A handicap for the golfer.
|
||||||
|
*/
|
||||||
|
default I computeGolferIndex(long personId) {
|
||||||
|
return this.computeGolferIndex(personId, LocalDate.now().plusDays(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method computes a golfer's handicap index as of the specified date,
|
||||||
|
* inclusive.
|
||||||
|
*
|
||||||
|
* This will consider the use of all individually played non-event and event
|
||||||
|
* rounds. Each implementation may exclude more rounds, like unsigned or unrated
|
||||||
|
* courses.
|
||||||
|
*
|
||||||
|
* @param personId A unique identifier for the golfer.
|
||||||
|
* @param beforeDay A date to exclude all rounds on or after.
|
||||||
|
* @return A handicap for the golfer.
|
||||||
|
*/
|
||||||
|
I computeGolferIndex(long personId, LocalDate beforeDay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method computes a golf course eighteen/tee combination's difficulty
|
||||||
|
* rating as of today, inclusive.
|
||||||
|
*
|
||||||
|
* In some cases, there is no computation, but a simple fetch from an external
|
||||||
|
* resource. In other cases the ratings will need to be computed. In other
|
||||||
|
* cases, a rating cannot be determined.
|
||||||
|
*
|
||||||
|
* @param etratingId A unique identifier for a golf course eighteen/tee
|
||||||
|
* combination (gender) rating.
|
||||||
|
* @return A rating for the course; `null` if one cannot be determined.
|
||||||
|
*/
|
||||||
|
R computeEighteenTeeRatingIndex(long etratingId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method computes a golf course nine/tee combination's difficulty rating
|
||||||
|
* as of today, inclusive.
|
||||||
|
*
|
||||||
|
* In some cases, there is no computation, but a simple fetch from an external
|
||||||
|
* resource. In other cases the ratings will need to be computed. In other
|
||||||
|
* cases, a rating cannot be determined.
|
||||||
|
*
|
||||||
|
* @param ntratingId A unique identifier for a golf course nine/tee combination
|
||||||
|
* (gender) rating.
|
||||||
|
* @return A rating for the course; `null` if one cannot be determined.
|
||||||
|
*/
|
||||||
|
R computeNineTeeRatingIndex(long ntratingId);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
package com.poststats.golf.service;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
|
||||||
|
public interface PersonRoundService {
|
||||||
|
|
||||||
|
public enum Selection { ScoreToPar, StrokeHandicapIndex }
|
||||||
|
|
||||||
|
public enum Filter { AttestedOnly, CourseRatedOnly }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves recent round meta-data about the specified golfer.
|
||||||
|
*
|
||||||
|
* The rounds are retrieved in reverse chronological order. Both Non-event and
|
||||||
|
* event rounds are included.
|
||||||
|
*
|
||||||
|
* @param personId A unique identifier for the golfer.
|
||||||
|
* @param roundCount A maximum number of rounds to return.
|
||||||
|
* @param selection Include `ScoreToPar` or `StrokeHandicapIndex` values in the
|
||||||
|
* results.
|
||||||
|
* @param filter An array of filters.
|
||||||
|
* @return A list of recent rounds played by the golfer.
|
||||||
|
*/
|
||||||
|
default List<? extends FlexMap> findRecent(long personId, short roundCount, Selection selection,
|
||||||
|
Filter... filters) {
|
||||||
|
return this.findBefore(personId, LocalDate.now().plusDays(1L), roundCount, selection, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves recent round meta-data about the specified golfer.
|
||||||
|
*
|
||||||
|
* The rounds are retrieved in reverse chronological order. Both Non-event and
|
||||||
|
* event rounds are included.
|
||||||
|
*
|
||||||
|
* @param personId A unique identifier for the golfer.
|
||||||
|
* @param beforeDay A date to start excluding rounds on or after.
|
||||||
|
* @param roundCount A maximum number of rounds to return.
|
||||||
|
* @param selection Include `ScoreToPar` or `StrokeHandicapIndex` values in the
|
||||||
|
* results.
|
||||||
|
* @param filter An array of filters.
|
||||||
|
* @return A list of recent rounds played by the golfer.
|
||||||
|
*/
|
||||||
|
List<? extends FlexMap> findBefore(long personId, LocalDate beforeDay, short roundCount, Selection selection,
|
||||||
|
Filter... filters);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.poststats.golf.service.HandicapIndexService;
|
||||||
|
import com.poststats.golf.service.PersonRoundService;
|
||||||
|
import com.poststats.golf.service.PersonRoundService.Filter;
|
||||||
|
import com.poststats.golf.service.PersonRoundService.Selection;
|
||||||
|
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
public abstract class AbstractHandicapIndexService<I, R> implements HandicapIndexService<I, R> {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private PersonRoundService personRoundService;
|
||||||
|
|
||||||
|
protected short getMinimumRounds() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract short getMaximumRounds();
|
||||||
|
|
||||||
|
protected abstract Selection getRoundSelection();
|
||||||
|
|
||||||
|
protected Filter[] getRoundFilter() {
|
||||||
|
return new Filter[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public I computeGolferIndex(long personId, LocalDate beforeDay) {
|
||||||
|
List<? extends FlexMap> rounds = this.personRoundService.findBefore(personId, beforeDay,
|
||||||
|
this.getMaximumRounds(), this.getRoundSelection(), this.getRoundFilter());
|
||||||
|
if (rounds.size() < this.getMinimumRounds())
|
||||||
|
throw new IllegalStateException("The person does not have enough rounds to compute a handicap index");
|
||||||
|
|
||||||
|
return this.compute(rounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract I compute(List<? extends FlexMap> rounds);
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,225 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.brianlong.util.MapUtil;
|
||||||
|
import com.poststats.golf.service.PersonRoundService.Selection;
|
||||||
|
import com.poststats.golf.service.model.PointHandicapIndex;
|
||||||
|
|
||||||
|
public abstract class AbstractPointHandicapIndexService extends AbstractHandicapIndexService<PointHandicapIndex, Byte> {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getMaximumRounds() {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected short getBestRoundsToToss(short roundCount) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract short getWorstRoundsToToss(short roundCount);
|
||||||
|
|
||||||
|
protected abstract short getPointTarget();
|
||||||
|
|
||||||
|
protected abstract float getAccelerant();
|
||||||
|
|
||||||
|
protected float getHomeCourseZeroRate() {
|
||||||
|
return 0.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getHomeCourseMultiplier() {
|
||||||
|
return 4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Selection getRoundSelection() {
|
||||||
|
return Selection.ScoreToPar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PointHandicapIndex compute(List<? extends FlexMap> rounds) {
|
||||||
|
short target = this.getPointTarget();
|
||||||
|
short bestRoundsToToss = this.getBestRoundsToToss((short) rounds.size());
|
||||||
|
short worstRoundsToToss = this.getWorstRoundsToToss((short) rounds.size());
|
||||||
|
short roundsToCount = (short) (rounds.size() - bestRoundsToToss - worstRoundsToToss);
|
||||||
|
float forgiveness = 0.5f;
|
||||||
|
|
||||||
|
Map<Integer, Float> computedPoints = new HashMap<>();
|
||||||
|
PointHandicapIndex bestLowPhi = null;
|
||||||
|
PointHandicapIndex bestHighPhi = null;
|
||||||
|
|
||||||
|
Pair<Integer, Float> homeCourseAdj = this.computeHomeCourseAdjustment(rounds);
|
||||||
|
this.logger.debug("Computed home course adjustment: {}", homeCourseAdj);
|
||||||
|
|
||||||
|
short[] scoreToParCounts = this.computeScoreToParCounts(rounds);
|
||||||
|
byte[] mostCommonScoreToPar = this.determineMostCommonStoresToPar(scoreToParCounts, (byte) 2);
|
||||||
|
this.logger.debug("Determined most common score-to-par: {}", mostCommonScoreToPar);
|
||||||
|
|
||||||
|
PointHandicapIndex phi = this.generateIndexBy2MostCommon(mostCommonScoreToPar[0],
|
||||||
|
scoreToParCounts[(mostCommonScoreToPar[0] + 10) % 10], mostCommonScoreToPar[1],
|
||||||
|
scoreToParCounts[(mostCommonScoreToPar[1] + 10) % 10], target, this.getAccelerant());
|
||||||
|
this.logger.debug("Base point handicap index: {}", phi);
|
||||||
|
|
||||||
|
byte lastHighScoreToPar = 6;
|
||||||
|
byte lastLowScoreToPar = -4;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
List<Integer> roundsPoints = new ArrayList<>(rounds.size());
|
||||||
|
for (FlexMap round : rounds) {
|
||||||
|
int points = round.getByte("bogey5") * phi.getQuintupleBogeyPoints()
|
||||||
|
+ round.getByte("bogey4") * phi.getQuadrupleBogeyPoints()
|
||||||
|
+ round.getByte("bogey3") * phi.getTripleBogeyPoints()
|
||||||
|
+ round.getByte("bogey2") * phi.getDoubleBogeyPoints()
|
||||||
|
+ round.getByte("bogey") * phi.getBogeyPoints() + round.getByte("par") * phi.getParPoints()
|
||||||
|
+ round.getByte("birdie") * phi.getBirdiePoints()
|
||||||
|
+ round.getByte("eagle") * phi.getEaglePoints()
|
||||||
|
+ round.getByte("alby") * phi.getAlbatrossPoints() + round.getByte("pointAdj");
|
||||||
|
if (round.getInteger("courseID").equals(homeCourseAdj.getKey())) {
|
||||||
|
// always negative, so we are making the course seem easier for the home course
|
||||||
|
// player
|
||||||
|
points += homeCourseAdj.getValue().intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
roundsPoints.add(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(roundsPoints);
|
||||||
|
float points = 0f;
|
||||||
|
for (Integer roundPoints : roundsPoints.subList(worstRoundsToToss, roundsPoints.size() - bestRoundsToToss))
|
||||||
|
points += roundPoints.floatValue();
|
||||||
|
points /= roundsToCount;
|
||||||
|
computedPoints.put(phi.getId(), points);
|
||||||
|
this.logger.debug("Computed {} points with index: {}", points, phi);
|
||||||
|
|
||||||
|
if (Math.abs(points - target) < forgiveness) {
|
||||||
|
System.out.println(points);
|
||||||
|
return phi;
|
||||||
|
}
|
||||||
|
forgiveness += 0.02f;
|
||||||
|
|
||||||
|
if (points > target) {
|
||||||
|
this.logger.debug("{} points are higher than the target {}; trying something lower", points, target);
|
||||||
|
if (lastHighScoreToPar < -2)
|
||||||
|
lastHighScoreToPar = 6;
|
||||||
|
lastHighScoreToPar--;
|
||||||
|
phi = phi.decrement(lastHighScoreToPar);
|
||||||
|
} else {
|
||||||
|
this.logger.debug("{} points are lower than the target {}; trying something higher", points, target);
|
||||||
|
if (lastLowScoreToPar > 4)
|
||||||
|
lastLowScoreToPar = -4;
|
||||||
|
lastLowScoreToPar++;
|
||||||
|
phi = phi.increment(lastLowScoreToPar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we are not guaranteed to be outside the bounds of the best low/high
|
||||||
|
// if the best low/high with nothing possible in between, pick the one that is
|
||||||
|
// closest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PointHandicapIndex generateIndexBy2MostCommon(byte mostCommonScoreToParIndex,
|
||||||
|
short mostCommonScoreToParCount, byte nextMostCommonScoreToParIndex, short nextMostCommonScoreToParCount,
|
||||||
|
short pointTarget, float accelerant) {
|
||||||
|
boolean roundDown = nextMostCommonScoreToParIndex < mostCommonScoreToParCount;
|
||||||
|
float targetPerHole = pointTarget / 18f;
|
||||||
|
int[] pointsForScoreToPar = new int[10];
|
||||||
|
int targetScoreToParIndex = (mostCommonScoreToParIndex + 10) % 10;
|
||||||
|
double factor = Math.pow(Math.E, accelerant);
|
||||||
|
|
||||||
|
// floor the target, so we target low
|
||||||
|
byte lastPointsForScoreToPar = roundDown ? (byte) targetPerHole : (byte) Math.ceil(targetPerHole);
|
||||||
|
pointsForScoreToPar[targetScoreToParIndex] = lastPointsForScoreToPar;
|
||||||
|
|
||||||
|
// go forward: higher scores; lower points; eventually 0 points
|
||||||
|
for (byte scoreToPar = (byte) (mostCommonScoreToParIndex + 1); scoreToPar < 6; scoreToPar++) {
|
||||||
|
int scoreToParIndex = (scoreToPar + 10) % 10;
|
||||||
|
lastPointsForScoreToPar = (byte) Math.max(0,
|
||||||
|
Math.min(lastPointsForScoreToPar - 1, Math.ceil(lastPointsForScoreToPar / factor)));
|
||||||
|
pointsForScoreToPar[scoreToParIndex] = lastPointsForScoreToPar;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPointsForScoreToPar = (byte) pointsForScoreToPar[targetScoreToParIndex];
|
||||||
|
|
||||||
|
// go backwards; lower scores; higher points
|
||||||
|
for (byte scoreToPar = (byte) (mostCommonScoreToParIndex - 1); scoreToPar > -4; scoreToPar--) {
|
||||||
|
int scoreToParIndex = (scoreToPar + 10) % 10;
|
||||||
|
lastPointsForScoreToPar = (byte) Math.max(lastPointsForScoreToPar + 1,
|
||||||
|
Math.floor(lastPointsForScoreToPar * factor));
|
||||||
|
pointsForScoreToPar[scoreToParIndex] = lastPointsForScoreToPar;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PointHandicapIndex(pointsForScoreToPar);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Pair<Integer, Float> computeHomeCourseAdjustment(List<? extends FlexMap> rounds) {
|
||||||
|
Pair<Integer, Float> maxHomeCourseAdj = Pair.of(null, 0f);
|
||||||
|
float zeroRate = this.getHomeCourseMultiplier() * this.getHomeCourseZeroRate();
|
||||||
|
|
||||||
|
Map<Integer, Integer> courseIdCounts = MapUtil.countKeys(rounds, "courseID", Integer.class);
|
||||||
|
for (Entry<Integer, Integer> courseIdCount : courseIdCounts.entrySet()) {
|
||||||
|
float homeCourseAdj = courseIdCount.getValue() * -this.getHomeCourseMultiplier() / rounds.size() + zeroRate;
|
||||||
|
if (homeCourseAdj < maxHomeCourseAdj.getRight())
|
||||||
|
maxHomeCourseAdj = Pair.of(courseIdCount.getKey(), homeCourseAdj);
|
||||||
|
if (homeCourseAdj <= -1f)
|
||||||
|
// over 50%; we are done
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxHomeCourseAdj;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected short[] computeScoreToParCounts(List<? extends FlexMap> rounds) {
|
||||||
|
short[] scoreToParTotalHoles = new short[10];
|
||||||
|
|
||||||
|
for (FlexMap round : rounds) {
|
||||||
|
scoreToParTotalHoles[5] += round.getByte("bogey5");
|
||||||
|
scoreToParTotalHoles[4] += round.getByte("bogey4");
|
||||||
|
scoreToParTotalHoles[3] += round.getByte("bogey3");
|
||||||
|
scoreToParTotalHoles[2] += round.getByte("bogey2");
|
||||||
|
scoreToParTotalHoles[1] += round.getByte("bogey");
|
||||||
|
scoreToParTotalHoles[0] += round.getByte("par");
|
||||||
|
scoreToParTotalHoles[9] += round.getByte("birdie");
|
||||||
|
scoreToParTotalHoles[8] += round.getByte("eagle");
|
||||||
|
scoreToParTotalHoles[7] += round.getByte("alby");
|
||||||
|
}
|
||||||
|
|
||||||
|
return scoreToParTotalHoles;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] determineMostCommonStoresToPar(short[] scoreToParCounts, byte xNumberOfMostCommonIndex) {
|
||||||
|
byte[] maxScoreToPar = new byte[xNumberOfMostCommonIndex];
|
||||||
|
for (byte i = 0; i < xNumberOfMostCommonIndex; i++)
|
||||||
|
maxScoreToPar[i] = 6; // unused and always 0
|
||||||
|
|
||||||
|
for (byte scoreToPar = 0; scoreToPar < 10; scoreToPar++) {
|
||||||
|
for (int i = 0; i < xNumberOfMostCommonIndex; i++) {
|
||||||
|
if (scoreToParCounts[maxScoreToPar[i]] < scoreToParCounts[scoreToPar]) {
|
||||||
|
for (int i2 = xNumberOfMostCommonIndex - 1; i2 > 0; i2--) {
|
||||||
|
maxScoreToPar[i2] = maxScoreToPar[i2 - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxScoreToPar[i] = scoreToPar;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < maxScoreToPar.length; i++)
|
||||||
|
maxScoreToPar[i] = maxScoreToPar[i] < 0 ? (byte) (maxScoreToPar[i] - 10) : maxScoreToPar[i];
|
||||||
|
return maxScoreToPar;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,117 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.poststats.golf.service.PersonRoundService.Selection;
|
||||||
|
import com.poststats.golf.service.model.StrokeCourseRating;
|
||||||
|
|
||||||
|
public abstract class AbstractStrokeHandicapIndexService
|
||||||
|
extends AbstractHandicapIndexService<Float, StrokeCourseRating> {
|
||||||
|
|
||||||
|
private final Comparator<FlexMap> roundComparator;
|
||||||
|
|
||||||
|
public AbstractStrokeHandicapIndexService() {
|
||||||
|
this.roundComparator = new Comparator<FlexMap>() {
|
||||||
|
@Override
|
||||||
|
public int compare(FlexMap round1, FlexMap round2) {
|
||||||
|
Float sh1 = round1.getFloat(getStrokeHandicapColumn());
|
||||||
|
Float sh2 = round2.getFloat(getStrokeHandicapColumn());
|
||||||
|
return sh1.compareTo(sh2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String getStrokeHandicapColumn();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Selection getRoundSelection() {
|
||||||
|
return Selection.StrokeHandicapIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected short getRoundsToCount(short rounds) {
|
||||||
|
switch (rounds) {
|
||||||
|
case 0:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
return 1;
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
return 2;
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
return 3;
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
case 14:
|
||||||
|
return 4;
|
||||||
|
case 15:
|
||||||
|
case 16:
|
||||||
|
return 5;
|
||||||
|
case 17:
|
||||||
|
case 18:
|
||||||
|
return 6;
|
||||||
|
case 19:
|
||||||
|
return 7;
|
||||||
|
case 20:
|
||||||
|
return 8;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float compute(List<? extends FlexMap> rounds) {
|
||||||
|
Collections.sort(rounds, this.roundComparator);
|
||||||
|
short roundsToCount = this.getRoundsToCount((short) rounds.size());
|
||||||
|
return this.compute(rounds.subList(0, roundsToCount), (short) rounds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float compute(List<? extends FlexMap> rounds, short roundsConsidered) {
|
||||||
|
float sh = 0f;
|
||||||
|
for (FlexMap round : rounds)
|
||||||
|
sh += round.getFloat(this.getStrokeHandicapColumn());
|
||||||
|
sh /= rounds.size();
|
||||||
|
|
||||||
|
float adjustment = this.getHandicapAdjustment(roundsConsidered);
|
||||||
|
return sh + adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StrokeCourseRating computeEighteenTeeRatingIndex(long etratingId) {
|
||||||
|
// TODO call WHS service for the information
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StrokeCourseRating computeNineTeeRatingIndex(long ntratingId) {
|
||||||
|
// TODO call WHS service for the information
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float getHandicapAdjustment(short rounds) {
|
||||||
|
switch (rounds) {
|
||||||
|
case 0:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
case 1:
|
||||||
|
return -3f;
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
return -2f;
|
||||||
|
case 4:
|
||||||
|
case 6:
|
||||||
|
return -1f;
|
||||||
|
default:
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,122 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.poststats.golf.service.CourseRatingService;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class LegacyPostStatsPointHandicapIndexService extends AbstractPointHandicapIndexService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CourseRatingService courseRatingService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected float getAccelerant() {
|
||||||
|
return 0.6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getPointTarget() {
|
||||||
|
return 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getWorstRoundsToToss(short roundCount) {
|
||||||
|
switch (roundCount) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
return 0;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
return 1;
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
return 2;
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
case 14:
|
||||||
|
return 3;
|
||||||
|
case 15:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getBestRoundsToToss(short roundCount) {
|
||||||
|
return (short) (roundCount < 13 ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Byte computeEighteenTeeRatingIndex(long etratingId) {
|
||||||
|
FlexMap etrating = this.courseRatingService.getEighteenTeeRating(etratingId);
|
||||||
|
return this.computeEighteenTeeRatingIndex(etrating);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Byte computeEighteenTeeRatingIndex(FlexMap etrating) {
|
||||||
|
Short slopeRating = etrating.getShort("slopeRating");
|
||||||
|
Float courseRating = etrating.getFloat("courseRating");
|
||||||
|
char gender = etrating.getString("gender").charAt(0);
|
||||||
|
short yards = etrating.getShort("yards");
|
||||||
|
byte par = etrating.getByte("par");
|
||||||
|
return this.computeRatingIndex(slopeRating, courseRating, gender, yards, par);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Byte computeNineTeeRatingIndex(long ntratingId) {
|
||||||
|
FlexMap ntrating = this.courseRatingService.getNineTeeRating(ntratingId);
|
||||||
|
return this.computeNineTeeRatingIndex(ntrating);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Byte computeNineTeeRatingIndex(FlexMap ntrating) {
|
||||||
|
Short slopeRating = ntrating.getShort("slopeRating");
|
||||||
|
Float courseRating = ntrating.getFloat("courseRating");
|
||||||
|
char gender = ntrating.getString("gender").charAt(0);
|
||||||
|
short yards = ntrating.getShort("yards");
|
||||||
|
byte par = ntrating.getByte("par");
|
||||||
|
return this.computeRatingIndex(slopeRating, courseRating, gender, yards, par);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Byte computeRatingIndex(Short slopeRating, Float courseRating, char gender, short yards, byte par) {
|
||||||
|
// A par X hole assumes (X-2) non-putts and 2 putts to complete par
|
||||||
|
// So an 18 hole course assumes 36 putts and (X-36) non-putts to complete par
|
||||||
|
int nonPuttPar = par - 36;
|
||||||
|
// we normalize those (X-2) full strokes, so par 60 courses get a normalization
|
||||||
|
// ratio of 36/24 or 1.5
|
||||||
|
// that means we need to 1.5x any yards/par/ratings to make it look like a
|
||||||
|
// standard par 72 course
|
||||||
|
double normalizedRatioNonPuttPar = 36.0 / nonPuttPar;
|
||||||
|
// we normalize the yards to look like a standard par 72 course
|
||||||
|
double normalizedYards = yards * normalizedRatioNonPuttPar;
|
||||||
|
|
||||||
|
// we are linearly applying the yards, where:
|
||||||
|
// 6000 yd par 72 has 0 points
|
||||||
|
// 6350 yd par 72 has 6 points
|
||||||
|
// 6500 yd par 70 has 9 points
|
||||||
|
// 7000 yd par 71 has 18 points
|
||||||
|
// 3000 yd par 54 has 0 points
|
||||||
|
// 2000 yd par 54 has -34 points
|
||||||
|
int genderYards = gender == 'M' ? 6000 : 5000;
|
||||||
|
double adjYards = normalizedYards - genderYards;
|
||||||
|
byte yardBonus = (byte) (adjYards * 3.0 / 175.0);
|
||||||
|
|
||||||
|
if (slopeRating == null)
|
||||||
|
return (byte) (yardBonus * 2);
|
||||||
|
|
||||||
|
// a 130 slope course has 10 points
|
||||||
|
// a 155 slope (max) course has 21 points
|
||||||
|
// a 55 slope (min) course has -21 points
|
||||||
|
byte slopeBonus = (byte) ((slopeRating - 105) * 3 / 7);
|
||||||
|
return (byte) (yardBonus + slopeBonus);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,136 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.poststats.golf.service.CourseHoleService;
|
||||||
|
import com.poststats.golf.service.CourseRatingService;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class PostStatsPointHandicapIndexService extends AbstractPointHandicapIndexService {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CourseRatingService courseRatingService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private CourseHoleService courseHoleService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected float getAccelerant() {
|
||||||
|
return 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getPointTarget() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getWorstRoundsToToss(short roundCount) {
|
||||||
|
switch (roundCount) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
return 0;
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
return 1;
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
return 2;
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
case 14:
|
||||||
|
return 3;
|
||||||
|
case 15:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getBestRoundsToToss(short roundCount) {
|
||||||
|
return (short) (roundCount < 10 ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Byte computeEighteenTeeRatingIndex(long etratingId) {
|
||||||
|
FlexMap etrating = this.courseRatingService.getEighteenTeeRating(etratingId);
|
||||||
|
List<? extends FlexMap> holes = this.courseHoleService.getHolesByEighteenTee(etrating.getLong("eighteenteeID"));
|
||||||
|
return this.computeRatingIndex(etrating, holes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Byte computeNineTeeRatingIndex(long ntratingId) {
|
||||||
|
FlexMap ntrating = this.courseRatingService.getNineTeeRating(ntratingId);
|
||||||
|
List<? extends FlexMap> holes = this.courseHoleService.getHolesByEighteenTee(ntrating.getLong("nineteeID"));
|
||||||
|
return this.computeRatingIndex(ntrating, holes);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Byte computeRatingIndex(FlexMap netrating, List<? extends FlexMap> holes) {
|
||||||
|
// M normalized to 150 yd (150 + 3 * 8) par 3s
|
||||||
|
// F normalized to 125 yd (125 + 3 * 8) par 3s
|
||||||
|
char gender = netrating.getString("gender").charAt(0);
|
||||||
|
float divisorBase = gender == 'M' ? 165f : 140f;
|
||||||
|
|
||||||
|
// capture running total for points
|
||||||
|
float points = 0;
|
||||||
|
|
||||||
|
for (FlexMap hole : holes) {
|
||||||
|
short yards = hole.getShort("yards");
|
||||||
|
byte par = hole.getByte("par");
|
||||||
|
|
||||||
|
// par assumes 2 putts per hole
|
||||||
|
int nonPuttPar = par - 2;
|
||||||
|
float nonPuttYardsPerStroke = 1f * yards / nonPuttPar;
|
||||||
|
|
||||||
|
// make longer par 4s/5s correlate with shorter par 3s, as it is harder to
|
||||||
|
// "score" (birdies/eagles) on par 3s than par 5s
|
||||||
|
float exponentDivisor = divisorBase - (par * 5);
|
||||||
|
|
||||||
|
// 80 yd par 3 will be -3.89 pts
|
||||||
|
// 100 yd par 3 will be -3.16 pts
|
||||||
|
// 125 yd par 3 (272/4 or 446/5) will be -2.1 pts
|
||||||
|
// 150 yd par 3 (326/4 or 536/5) will be -0.85 pts
|
||||||
|
// 200 yd par 3 (435/4 or 714/5) will be +2.38 pts
|
||||||
|
// 225 yd par 3 (489/4 or 804/5) will be +4.45 pts
|
||||||
|
points += Math.pow(Math.E, nonPuttYardsPerStroke / exponentDivisor) * 3f - 9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netrating.isNotEmpty("etratingID")) {
|
||||||
|
this.logger.debug("computed course rating: et #{} => {}", netrating.get("etratingID"), points);
|
||||||
|
} else {
|
||||||
|
this.logger.debug("computed course rating: nt #{} => {}", netrating.get("ntratingID"), points);
|
||||||
|
}
|
||||||
|
|
||||||
|
Float courseRating = netrating.getFloat("courseRating");
|
||||||
|
if (courseRating == null)
|
||||||
|
return (byte) points;
|
||||||
|
|
||||||
|
byte par = netrating.getByte("par");
|
||||||
|
float difficulty = courseRating - par;
|
||||||
|
|
||||||
|
// 69 rating on par 72 gets -9 pts
|
||||||
|
// 72 rating on par 72 gets +0 pts
|
||||||
|
// 73 rating on par 72 gets +3 pts
|
||||||
|
// 75 rating on par 72 gets +9 pts
|
||||||
|
double ratingAdj = 3 * difficulty;
|
||||||
|
|
||||||
|
return (byte) (points + ratingAdj);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class PostStatsStrokeHandicapIndexService extends AbstractStrokeHandicapIndexService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getMaximumRounds() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getStrokeHandicapColumn() {
|
||||||
|
return "strokeHandicapIndex";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import com.poststats.golf.service.PersonRoundService.Filter;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class WhsStrokeHandicapIndexService extends AbstractStrokeHandicapIndexService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getMinimumRounds() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected short getMaximumRounds() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getStrokeHandicapColumn() {
|
||||||
|
return "whsStrokeHandicapIndex";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Filter[] getRoundFilter() {
|
||||||
|
return new Filter[] {
|
||||||
|
Filter.AttestedOnly, Filter.CourseRatedOnly
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,320 @@
|
|||||||
|
package com.poststats.golf.service.db;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.brianlong.sql.FlexPreparedStatement;
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.poststats.golf.provider.GolfProvider;
|
||||||
|
import com.poststats.golf.service.PersonRoundService;
|
||||||
|
import com.poststats.provider.NonTransactionalProvider;
|
||||||
|
import com.poststats.provider.Statement;
|
||||||
|
import com.poststats.provider.StatementProvider;
|
||||||
|
import com.poststats.service.ServiceException;
|
||||||
|
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class PersonRoundServiceDAO implements PersonRoundService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends FlexMap> findBefore(long personId, LocalDate beforeDay, short roundCount, Selection selection,
|
||||||
|
Filter... filters) {
|
||||||
|
StatementProvider stmt = this.getRoundStatementProvider(selection, filters);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FlexPreparedStatement fps = stmt.buildPreparedStatement();
|
||||||
|
try {
|
||||||
|
fps.setIntegerU(1, personId); // for non-event rounds
|
||||||
|
fps.setDate(2, beforeDay);
|
||||||
|
fps.setIntegerU(3, personId); // for 18-hole event rounds
|
||||||
|
fps.setDate(4, beforeDay);
|
||||||
|
fps.setIntegerU(5, personId); // for 9-hole event rounds
|
||||||
|
fps.setDate(6, beforeDay);
|
||||||
|
fps.setSmallint(7, roundCount); // limit
|
||||||
|
return fps.executeQuery().getAllRows();
|
||||||
|
} finally {
|
||||||
|
fps.close();
|
||||||
|
}
|
||||||
|
} catch (SQLException se) {
|
||||||
|
throw new ServiceException(se);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private StatementProvider getRoundStatementProvider(Selection selection, Filter... filters) {
|
||||||
|
Set<Filter> filterSet = new HashSet<>();
|
||||||
|
for (Filter filter : filters)
|
||||||
|
filterSet.add(filter);
|
||||||
|
|
||||||
|
switch (selection) {
|
||||||
|
case StrokeHandicapIndex:
|
||||||
|
if (filterSet.contains(Filter.AttestedOnly)) {
|
||||||
|
if (filterSet.contains(Filter.CourseRatedOnly)) {
|
||||||
|
return this.sqlSelectSignedRatedRoundsWithStrokeHandicap;
|
||||||
|
} else {
|
||||||
|
return this.sqlSelectSignedRoundsWithStrokeHandicap;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (filterSet.contains(Filter.CourseRatedOnly)) {
|
||||||
|
return this.sqlSelectRatedRoundsWithStrokeHandicap;
|
||||||
|
} else {
|
||||||
|
return this.sqlSelectRoundsWithStrokeHandicap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ScoreToPar:
|
||||||
|
if (filterSet.contains(Filter.AttestedOnly)) {
|
||||||
|
if (filterSet.contains(Filter.CourseRatedOnly)) {
|
||||||
|
return this.sqlSelectSignedRatedRoundsWithScoreToPar;
|
||||||
|
} else {
|
||||||
|
return this.sqlSelectSignedRoundsWithScoreToPar;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (filterSet.contains(Filter.CourseRatedOnly)) {
|
||||||
|
return this.sqlSelectRatedRoundsWithScoreToPar;
|
||||||
|
} else {
|
||||||
|
return this.sqlSelectRoundsWithScoreToPar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static String nonEventRoundSqlSelectClause = "SELECT R.roundID, NULL proundID, NULL linkProundID, R.etratingID, R.courseID, R.teedate, R.teetime, R.strokes, ";
|
||||||
|
private final static String nonEventRoundSqlFromClause = "FROM ~g~.Round R "
|
||||||
|
+ " INNER JOIN ~p~.Person P ON (R.personID=P.personID) ";
|
||||||
|
private final static String nonEventRoundSqlWhereClause = "WHERE R.personID=? AND R.complete IS TRUE AND R.teedate<? "
|
||||||
|
+ " AND (P.healthSetback IS NULL OR R.teedate>P.healthSetback) ";
|
||||||
|
|
||||||
|
private final static String event18RoundSqlSelectClause = "SELECT NULL roundID, EPR.proundID, NULL linkProundID, EPR.etratingID, EPR.courseID, ER.date, ERP.teetime, EPR.strokes, ";
|
||||||
|
private final static String event18RoundSqlFromClause = "FROM ~g~.EventPersonRound EPR "
|
||||||
|
+ " INNER JOIN ~g~.EventRound ER ON (EPR.eroundID=ER.eroundID) "
|
||||||
|
+ " INNER JOIN ~g~.EventPerson EP ON (EPR.epersonID=EP.epersonID) "
|
||||||
|
+ " INNER JOIN ~p~.Person P ON (EP.personID=P.personID) "
|
||||||
|
+ " LEFT JOIN ~g~.EventRoundPairing ERP ON (EPR.pairingID=ERP.pairingID) ";
|
||||||
|
private final static String event18RoundSqlWhereClause = "WHERE EP.personID=? AND EPR.complete IS TRUE AND EPR.etratingID IS NOT NULL AND ER.date<? "
|
||||||
|
+ " AND (P.healthSetback IS NULL OR ER.date>P.healthSetback) ";
|
||||||
|
|
||||||
|
private final static String event9RoundSqlSelectClause = "SELECT NULL roundID, EPR.proundID, EPR.linkProundID, CETR.etratingID, EPR.courseID, ER.date, ERP.teetime, "
|
||||||
|
+ " (EPR.strokes+EPR2.strokes) strokes, ";
|
||||||
|
private final static String event9RoundSqlFromClause = "FROM ~g~.EventPersonRound EPR "
|
||||||
|
+ " INNER JOIN ~g~.EventPersonRound EPR2 ON (EPR.linkProundID=EPR2.proundID AND EPR.proundID>EPR2.proundID) " // don't
|
||||||
|
// include
|
||||||
|
// 9-hole
|
||||||
|
// twice
|
||||||
|
+ " INNER JOIN ~g~.CourseNineTeeRating CNTR1 ON (EPR.ntratingID=CNTR1.ntratingID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseNineTeeRating CNTR2 ON (EPR2.ntratingID=CNTR2.ntratingID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseEighteenTee CET ON ((CNTR1.nineteeID=CET.nineteeID1 AND CNTR2.nineteeID=CET.nineteeID2) "
|
||||||
|
+ " OR (CNTR2.nineteeID=CET.nineteeID1 AND CNTR1.nineteeID=CET.nineteeID2)) "
|
||||||
|
+ " INNER JOIN ~g~.CourseEighteenTeeRating CETR ON (CET.eighteenteeID=CETR.eighteenteeID "
|
||||||
|
+ " AND CNTR1.gender=CETR.gender "
|
||||||
|
+ " AND CETR.liveline<=ER.date AND (CETR.deadline IS NULL OR ER.date<=CETR.deadline)) "
|
||||||
|
+ " INNER JOIN ~g~.EventRound ER ON (EPR.eroundID=ER.eroundID) "
|
||||||
|
+ " INNER JOIN ~g~.EventPerson EP ON (EPR.epersonID=EP.epersonID) "
|
||||||
|
+ " INNER JOIN ~p~.Person P ON (EP.personID=P.personID) "
|
||||||
|
+ " LEFT JOIN ~g~.EventRoundPairing ERP ON (EPR.pairingID=ERP.pairingID) ";
|
||||||
|
private final static String event9RoundSqlWhereClause = "WHERE EP.personID=? AND EPR.complete IS TRUE AND ER.date<? "
|
||||||
|
+ " AND (P.healthSetback IS NULL OR ER.date>P.healthSetback) ";
|
||||||
|
|
||||||
|
private final static String nonEventRoundSqlSelectStrokeHandicap = nonEventRoundSqlSelectClause
|
||||||
|
+ " R.strokeHandicapIndex, R.whsStrokeHandicapIndex "
|
||||||
|
+ nonEventRoundSqlFromClause
|
||||||
|
+ nonEventRoundSqlWhereClause;
|
||||||
|
|
||||||
|
private final static String event18RoundSqlSelectStrokeHandicap = event18RoundSqlSelectClause
|
||||||
|
+ " EPR.strokeHandicapIndex, EPR.whsStrokeHandicapIndex "
|
||||||
|
+ event18RoundSqlFromClause
|
||||||
|
+ event18RoundSqlWhereClause;
|
||||||
|
|
||||||
|
private final static String event9RoundSqlSelectStrokeHandicap = event9RoundSqlSelectClause
|
||||||
|
+ " (EPR.strokeHandicapIndex+EPR2.strokeHandicapIndex) strokeHandicapIndex, "
|
||||||
|
+ " (EPR.whsStrokeHandicapIndex+EPR2.whsStrokeHandicapIndex) whsStrokeHandicapIndex "
|
||||||
|
+ event9RoundSqlFromClause
|
||||||
|
+ event9RoundSqlWhereClause;
|
||||||
|
|
||||||
|
private final static String nonEventRoundSqlSelectScoreToPar = nonEventRoundSqlSelectClause
|
||||||
|
+ " SUM(CASE WHEN (RX.strokes - CNTH.par) = 5 THEN 1 ELSE 0 END) bogey5, "
|
||||||
|
+ " SUM(CASE WHEN (RX.strokes - CNTH.par) = 4 THEN 1 ELSE 0 END) bogey4, "
|
||||||
|
+ " SUM(CASE WHEN (RX.strokes - CNTH.par) = 3 THEN 1 ELSE 0 END) bogey3, "
|
||||||
|
+ " SUM(CASE WHEN (RX.strokes - CNTH.par) = 2 THEN 1 ELSE 0 END) bogey2, "
|
||||||
|
+ " SUM(CASE WHEN (RX.strokes - CNTH.par) = 1 THEN 1 ELSE 0 END) bogey, "
|
||||||
|
+ " SUM(CASE WHEN RX.strokes = CNTH.par THEN 1 ELSE 0 END) par, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - RX.strokes) = 1 THEN 1 ELSE 0 END) birdie, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - RX.strokes) = 2 THEN 1 ELSE 0 END) eagle, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - RX.strokes) = 3 THEN 1 ELSE 0 END) alby, "
|
||||||
|
+ " CETR.pointAdj "
|
||||||
|
+ nonEventRoundSqlFromClause
|
||||||
|
+ " INNER JOIN ~g~.RoundScore RX ON (R.roundID=RX.roundID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseNineTeeHole CNTH ON (RX.holeID=CNTH.holeID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseEighteenTeeRating CETR ON (R.etratingID=CETR.etratingID) "
|
||||||
|
+ nonEventRoundSqlWhereClause
|
||||||
|
+ "GROUP BY R.roundID ";
|
||||||
|
|
||||||
|
private final static String event18RoundSqlSelectScoreToPar = event18RoundSqlSelectClause
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 5 THEN 1 ELSE 0 END) bogey5, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 4 THEN 1 ELSE 0 END) bogey4, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 3 THEN 1 ELSE 0 END) bogey3, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 2 THEN 1 ELSE 0 END) bogey2, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 1 THEN 1 ELSE 0 END) bogey, "
|
||||||
|
+ " SUM(CASE WHEN EPRS.strokes = CNTH.par THEN 1 ELSE 0 END) par, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - EPRS.strokes) = 1 THEN 1 ELSE 0 END) birdie, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - EPRS.strokes) = 2 THEN 1 ELSE 0 END) eagle, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - EPRS.strokes) = 3 THEN 1 ELSE 0 END) alby,"
|
||||||
|
+ " CETR.pointAdj "
|
||||||
|
+ event18RoundSqlFromClause
|
||||||
|
+ " INNER JOIN ~g~.EventPersonRoundScore EPRS ON (EPR.proundID=EPRS.proundID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseNineTeeHole CNTH ON (EPRS.holeID=CNTH.holeID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseEighteenTeeRating CETR ON (EPR.etratingID=CETR.etratingID) "
|
||||||
|
+ event18RoundSqlWhereClause
|
||||||
|
+ "GROUP BY EPR.proundID ";
|
||||||
|
|
||||||
|
private final static String event9RoundSqlSelectScoreToPar = event9RoundSqlSelectClause
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 5 THEN 1 ELSE 0 END) bogey5, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 4 THEN 1 ELSE 0 END) bogey4, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 3 THEN 1 ELSE 0 END) bogey3, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 2 THEN 1 ELSE 0 END) bogey2, "
|
||||||
|
+ " SUM(CASE WHEN (EPRS.strokes - CNTH.par) = 1 THEN 1 ELSE 0 END) bogey, "
|
||||||
|
+ " SUM(CASE WHEN EPRS.strokes = CNTH.par THEN 1 ELSE 0 END) par, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - EPRS.strokes) = 1 THEN 1 ELSE 0 END) birdie, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - EPRS.strokes) = 2 THEN 1 ELSE 0 END) eagle, "
|
||||||
|
+ " SUM(CASE WHEN (CNTH.par - EPRS.strokes) = 3 THEN 1 ELSE 0 END) alby,"
|
||||||
|
+ " CETR.pointAdj "
|
||||||
|
+ event9RoundSqlFromClause
|
||||||
|
+ " INNER JOIN ~g~.EventPersonRoundScore EPRS ON (EPR.proundID=EPRS.proundID OR EPR.linkProundID=EPRS.proundID) "
|
||||||
|
+ " INNER JOIN ~g~.CourseNineTeeHole CNTH ON (EPRS.holeID=CNTH.holeID) "
|
||||||
|
+ event9RoundSqlWhereClause
|
||||||
|
+ "GROUP BY EPR.proundID ";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectStrokeHandicap
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectStrokeHandicap
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectStrokeHandicap
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectRoundsWithStrokeHandicap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND R.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectRatedRoundsWithStrokeHandicap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND EXISTS (SELECT RS.signerID FROM ~g~.RoundSigning RS WHERE RS.roundID=R.roundID) "
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectStrokeHandicap
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectStrokeHandicap
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectSignedRoundsWithStrokeHandicap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND R.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ " AND EXISTS (SELECT RS.signerID FROM ~g~.RoundSigning RS WHERE RS.roundID=R.roundID) "
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectStrokeHandicap
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectSignedRatedRoundsWithStrokeHandicap;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectScoreToPar
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectScoreToPar
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectScoreToPar
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectRoundsWithScoreToPar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectScoreToPar
|
||||||
|
+ " AND R.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectScoreToPar
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectScoreToPar
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectRatedRoundsWithScoreToPar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectScoreToPar
|
||||||
|
+ " AND EXISTS (SELECT RS.signerID FROM ~g~.RoundSigning RS WHERE RS.roundID=R.roundID) "
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectScoreToPar
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectScoreToPar
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectSignedRoundsWithScoreToPar;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@NonTransactionalProvider
|
||||||
|
@GolfProvider
|
||||||
|
@Statement(
|
||||||
|
sql = nonEventRoundSqlSelectScoreToPar
|
||||||
|
+ " AND R.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ " AND EXISTS (SELECT RS.signerID FROM ~g~.RoundSigning RS WHERE RS.roundID=R.roundID) "
|
||||||
|
+ "UNION "
|
||||||
|
+ event18RoundSqlSelectScoreToPar
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "UNION "
|
||||||
|
+ event9RoundSqlSelectScoreToPar
|
||||||
|
+ " AND EPR.whsStrokeHandicapIndex IS NOT NULL "
|
||||||
|
+ "ORDER BY teedate DESC, teetime DESC "
|
||||||
|
+ "LIMIT ?"
|
||||||
|
)
|
||||||
|
private StatementProvider sqlSelectSignedRatedRoundsWithScoreToPar;
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,404 @@
|
|||||||
|
package com.poststats.golf.service.model;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class PointHandicapIndex implements Comparable<PointHandicapIndex> {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private final int pointIndexId;
|
||||||
|
|
||||||
|
public PointHandicapIndex(int pointIndexId) {
|
||||||
|
this.pointIndexId = pointIndexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex(byte alby, byte eagle, byte birdie, byte par, byte bogey, byte bogey2, byte bogey3,
|
||||||
|
byte bogey4, byte bogey5) {
|
||||||
|
int pointIndexId = 0;
|
||||||
|
|
||||||
|
if (bogey5 > 1)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 5);
|
||||||
|
pointIndexId += bogey5;
|
||||||
|
|
||||||
|
if (bogey4 > 3 || bogey4 > 0 && bogey5 >= bogey4)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 4);
|
||||||
|
pointIndexId += bogey4;
|
||||||
|
|
||||||
|
if (bogey3 > 3 || bogey3 > 0 && bogey4 >= bogey3)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 3);
|
||||||
|
pointIndexId += bogey3;
|
||||||
|
|
||||||
|
if (bogey2 > 7 || bogey2 > 0 && bogey3 >= bogey2)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 2);
|
||||||
|
pointIndexId += bogey2;
|
||||||
|
|
||||||
|
if (bogey > 7 || bogey > 0 && bogey2 >= bogey)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 1);
|
||||||
|
pointIndexId += bogey;
|
||||||
|
|
||||||
|
if (par > 15 || par > 0 && bogey >= par)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 0);
|
||||||
|
pointIndexId += par;
|
||||||
|
|
||||||
|
if (birdie > 15 || birdie > 0 && par >= birdie)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) -1);
|
||||||
|
pointIndexId += birdie;
|
||||||
|
|
||||||
|
if (eagle > 31 || eagle > 0 && birdie >= eagle)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) -2);
|
||||||
|
pointIndexId += eagle;
|
||||||
|
|
||||||
|
if (alby > 63 || alby > 0 && eagle >= alby)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) -3);
|
||||||
|
pointIndexId += alby;
|
||||||
|
|
||||||
|
this.pointIndexId = pointIndexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex(int alby, int eagle, int birdie, int par, int bogey, int bogey2, int bogey3, int bogey4,
|
||||||
|
int bogey5) {
|
||||||
|
int pointIndexId = 0;
|
||||||
|
|
||||||
|
if (bogey5 > 1)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 5);
|
||||||
|
pointIndexId += bogey5;
|
||||||
|
|
||||||
|
if (bogey4 > 3 || bogey4 > 0 && bogey5 >= bogey4)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 4);
|
||||||
|
pointIndexId += bogey4;
|
||||||
|
|
||||||
|
if (bogey3 > 3 || bogey3 > 0 && bogey4 >= bogey3)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 3);
|
||||||
|
pointIndexId += bogey3;
|
||||||
|
|
||||||
|
if (bogey2 > 7 || bogey2 > 0 && bogey3 >= bogey2)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 2);
|
||||||
|
pointIndexId += bogey2;
|
||||||
|
|
||||||
|
if (bogey > 7 || bogey > 0 && bogey2 >= bogey)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 1);
|
||||||
|
pointIndexId += bogey;
|
||||||
|
|
||||||
|
if (par > 15 || par > 0 && bogey >= par)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) 0);
|
||||||
|
pointIndexId += par;
|
||||||
|
|
||||||
|
if (birdie > 15 || birdie > 0 && par >= birdie)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) -1);
|
||||||
|
pointIndexId += birdie;
|
||||||
|
|
||||||
|
if (eagle > 31 || eagle > 0 && birdie >= eagle)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) -2);
|
||||||
|
pointIndexId += eagle;
|
||||||
|
|
||||||
|
if (alby > 63 || alby > 0 && eagle >= alby)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
pointIndexId <<= this.getBits((byte) -3);
|
||||||
|
pointIndexId += alby;
|
||||||
|
|
||||||
|
this.pointIndexId = pointIndexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex(int[] scoresToPar) {
|
||||||
|
this(scoresToPar[7], scoresToPar[8], scoresToPar[9], scoresToPar[0], scoresToPar[1], scoresToPar[2],
|
||||||
|
scoresToPar[3], scoresToPar[4], scoresToPar[5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return this.pointIndexId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getPointsWithScoreToPar(byte scoreToPar) {
|
||||||
|
int shift = this.getShift(scoreToPar);
|
||||||
|
int bits = this.getBits(scoreToPar);
|
||||||
|
int mask = ((1 << bits) - 1) << (shift - bits);
|
||||||
|
return (byte) ((this.pointIndexId & mask) >> (shift - bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getQuintupleBogeyPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getQuadrupleBogeyPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getTripleBogeyPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getDoubleBogeyPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getBogeyPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getParPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getBirdiePoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getEaglePoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getAlbatrossPoints() {
|
||||||
|
return this.getPointsWithScoreToPar((byte) -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getShift(byte scoreToPar) {
|
||||||
|
if (scoreToPar < -2) {
|
||||||
|
return 6; // 0-63
|
||||||
|
} else switch (scoreToPar) {
|
||||||
|
case -2:
|
||||||
|
return 11; // 0-31
|
||||||
|
case -1:
|
||||||
|
return 15; // 0-15
|
||||||
|
case 0:
|
||||||
|
return 19; // 0-15
|
||||||
|
case 1:
|
||||||
|
return 22; // 0-7
|
||||||
|
case 2:
|
||||||
|
return 25; // 0-7
|
||||||
|
case 3:
|
||||||
|
return 27; // 0-3
|
||||||
|
case 4:
|
||||||
|
return 29; // 0-3
|
||||||
|
case 5:
|
||||||
|
return 30; // 0-1
|
||||||
|
default:
|
||||||
|
return 31; // 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBits(byte scoreToPar) {
|
||||||
|
if (scoreToPar < -2) {
|
||||||
|
return 6; // 0-63
|
||||||
|
} else switch (scoreToPar) {
|
||||||
|
case -2:
|
||||||
|
return 5; // 0-31
|
||||||
|
case -1:
|
||||||
|
return 4; // 0-15
|
||||||
|
case 0:
|
||||||
|
return 4; // 0-15
|
||||||
|
case 1:
|
||||||
|
return 3; // 0-7
|
||||||
|
case 2:
|
||||||
|
return 3; // 0-7
|
||||||
|
case 3:
|
||||||
|
return 2; // 0-3
|
||||||
|
case 4:
|
||||||
|
return 2; // 0-3
|
||||||
|
case 5:
|
||||||
|
return 1; // 0-1
|
||||||
|
default:
|
||||||
|
return 0; // 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof PointHandicapIndex))
|
||||||
|
return false;
|
||||||
|
return this.getId() == ((PointHandicapIndex) obj).getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return this.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringBuilder().append(this.getQuintupleBogeyPoints()).append('/')
|
||||||
|
.append(this.getQuadrupleBogeyPoints()).append('/').append(this.getTripleBogeyPoints()).append('/')
|
||||||
|
.append(this.getDoubleBogeyPoints()).append('/').append(this.getBogeyPoints()).append('/')
|
||||||
|
.append(this.getParPoints()).append('/').append(this.getBirdiePoints()).append('/')
|
||||||
|
.append(this.getEaglePoints()).append('/').append(this.getAlbatrossPoints()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(PointHandicapIndex o) {
|
||||||
|
float compare = this.diff(o);
|
||||||
|
if (Float.isNaN(compare)) {
|
||||||
|
return 0;
|
||||||
|
} else if (compare == 0f) {
|
||||||
|
return 0;
|
||||||
|
} else if (compare < 0f) {
|
||||||
|
return (int) Math.floor(compare);
|
||||||
|
} else {
|
||||||
|
return (int) Math.ceil(compare);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float diff(PointHandicapIndex o) {
|
||||||
|
if (this.getId() == o.getId())
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
boolean pos = false;
|
||||||
|
boolean neg = false;
|
||||||
|
|
||||||
|
int compare = Byte.compare(this.getAlbatrossPoints(), o.getAlbatrossPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getEaglePoints(), o.getEaglePoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getBirdiePoints(), o.getBirdiePoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getParPoints(), o.getParPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getBogeyPoints(), o.getBogeyPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getDoubleBogeyPoints(), o.getDoubleBogeyPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getTripleBogeyPoints(), o.getTripleBogeyPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getQuadrupleBogeyPoints(), o.getQuadrupleBogeyPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
if (neg && pos)
|
||||||
|
return Float.NaN;
|
||||||
|
|
||||||
|
compare = Byte.compare(this.getQuintupleBogeyPoints(), o.getQuintupleBogeyPoints());
|
||||||
|
if (compare < 0)
|
||||||
|
neg = true;
|
||||||
|
else if (compare > 0)
|
||||||
|
pos = true;
|
||||||
|
|
||||||
|
if (neg && pos) {
|
||||||
|
return Float.NaN;
|
||||||
|
} else if (neg) {
|
||||||
|
return -1;
|
||||||
|
} else if (pos) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
this.logger.warn("This should never happen");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex decrement(byte scoreToPar) {
|
||||||
|
return this.minus(scoreToPar, (byte) 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex increment(byte scoreToPar) {
|
||||||
|
return this.plus(scoreToPar, (byte) 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex minus(byte scoreToPar, byte points) {
|
||||||
|
return this.plus(scoreToPar, (byte) -points);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex plus(byte scoreToPar, byte points) {
|
||||||
|
int shift = this.getShift(scoreToPar);
|
||||||
|
int bits = this.getBits(scoreToPar);
|
||||||
|
int mask = ((1 << bits) - 1) << (shift - bits);
|
||||||
|
int oldPointIndexMasked = this.pointIndexId & mask;
|
||||||
|
byte oldPoints = (byte) (oldPointIndexMasked >> (shift - bits));
|
||||||
|
|
||||||
|
if (points < 0) {
|
||||||
|
if (oldPoints == 0)
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
if (oldPoints + points >= this.getPointsWithScoreToPar((byte) (scoreToPar - 1)))
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldPoints + points >= Math.pow(2, bits))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
int pointIndexMasked = (oldPoints + points) << (shift - bits);
|
||||||
|
|
||||||
|
return new PointHandicapIndex(this.pointIndexId - oldPointIndexMasked + pointIndexMasked);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PointHandicapIndex avg(PointHandicapIndex phi, int roundingBias) {
|
||||||
|
return new PointHandicapIndex(
|
||||||
|
this.computeAvg(this.getAlbatrossPoints(), phi.getAlbatrossPoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getEaglePoints(), phi.getEaglePoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getBirdiePoints(), phi.getBirdiePoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getParPoints(), phi.getParPoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getBogeyPoints(), phi.getBogeyPoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getDoubleBogeyPoints(), phi.getDoubleBogeyPoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getTripleBogeyPoints(), phi.getTripleBogeyPoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getQuadrupleBogeyPoints(), phi.getQuadrupleBogeyPoints(), roundingBias),
|
||||||
|
this.computeAvg(this.getQuintupleBogeyPoints(), phi.getQuintupleBogeyPoints(), roundingBias));
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte computeAvg(byte points1, byte points2, int roundingBias) {
|
||||||
|
if (points1 == points2)
|
||||||
|
return points1;
|
||||||
|
float avg = (points1 + points2) / 2f;
|
||||||
|
if (roundingBias == 0) {
|
||||||
|
return (byte) Math.round(avg);
|
||||||
|
} else if (roundingBias < 0) {
|
||||||
|
return (byte) Math.floor(avg);
|
||||||
|
} else {
|
||||||
|
return (byte) Math.ceil(avg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.poststats.golf.service.model;
|
||||||
|
|
||||||
|
public class StrokeCourseRating {
|
||||||
|
|
||||||
|
private final short slopeRating;
|
||||||
|
private final float courseRating;
|
||||||
|
|
||||||
|
public StrokeCourseRating(short slopeRating, float courseRating) {
|
||||||
|
this.slopeRating = slopeRating;
|
||||||
|
this.courseRating = courseRating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public short getSlopeRating() {
|
||||||
|
return this.slopeRating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getCourseRating() {
|
||||||
|
return this.courseRating;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class ComputeLegacyPointCourseRatingUnitTest {
|
||||||
|
|
||||||
|
private static LegacyPostStatsPointHandicapIndexService service;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void stage() {
|
||||||
|
service = new LegacyPostStatsPointHandicapIndexService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cypressLandingGold() {
|
||||||
|
Assertions.assertEquals(-4f, service.computeRatingIndex((short) 117, 66.8f, 'M', (short) 5473, (byte) 72), 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cypressLandingWhite() {
|
||||||
|
Assertions.assertEquals(12f, service.computeRatingIndex((short) 127, 69.7f, 'M', (short) 6124, (byte) 72), 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cypressLandingBlue() {
|
||||||
|
Assertions.assertEquals(19f, service.computeRatingIndex((short) 131, 71.1f, 'M', (short) 6442, (byte) 72), 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,124 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
|
||||||
|
public class ComputePointCourseRatingUnitTest {
|
||||||
|
|
||||||
|
private static PostStatsPointHandicapIndexService service;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void stage() {
|
||||||
|
service = new PostStatsPointHandicapIndexService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cypressLandingGold() {
|
||||||
|
Assertions.assertEquals(-25f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 72, 66.8f),
|
||||||
|
Arrays.asList(this.mockHole(301, 4), this.mockHole(295, 4), this.mockHole(136, 3),
|
||||||
|
this.mockHole(297, 4), this.mockHole(460, 5), this.mockHole(349, 4), this.mockHole(140, 3),
|
||||||
|
this.mockHole(492, 5), this.mockHole(323, 4), this.mockHole(314, 4), this.mockHole(118, 3),
|
||||||
|
this.mockHole(461, 5), this.mockHole(301, 4), this.mockHole(351, 4), this.mockHole(113, 3),
|
||||||
|
this.mockHole(279, 4), this.mockHole(450, 5), this.mockHole(293, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cypressLandingWhite() {
|
||||||
|
Assertions.assertEquals(5f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 72, 69.7f),
|
||||||
|
Arrays.asList(this.mockHole(345, 4), this.mockHole(316, 4), this.mockHole(136, 3),
|
||||||
|
this.mockHole(371, 4), this.mockHole(503, 5), this.mockHole(404, 4), this.mockHole(184, 3),
|
||||||
|
this.mockHole(501, 5), this.mockHole(348, 4), this.mockHole(341, 4), this.mockHole(166, 3),
|
||||||
|
this.mockHole(532, 5), this.mockHole(318, 4), this.mockHole(385, 4), this.mockHole(129, 3),
|
||||||
|
this.mockHole(303, 4), this.mockHole(489, 5), this.mockHole(353, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void cypressLandingBlue() {
|
||||||
|
Assertions.assertEquals(22f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 72, 71.1f),
|
||||||
|
Arrays.asList(this.mockHole(373, 4), this.mockHole(316, 4), this.mockHole(166, 3),
|
||||||
|
this.mockHole(389, 4), this.mockHole(520, 5), this.mockHole(412, 4), this.mockHole(203, 3),
|
||||||
|
this.mockHole(520, 5), this.mockHole(366, 4), this.mockHole(374, 4), this.mockHole(185, 3),
|
||||||
|
this.mockHole(554, 5), this.mockHole(319, 4), this.mockHole(406, 4), this.mockHole(144, 3),
|
||||||
|
this.mockHole(318, 4), this.mockHole(489, 5), this.mockHole(388, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void heronGlenBlue() {
|
||||||
|
Assertions.assertEquals(26f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 72, 71.7f),
|
||||||
|
Arrays.asList(this.mockHole(395, 4), this.mockHole(581, 5), this.mockHole(424, 4),
|
||||||
|
this.mockHole(361, 4), this.mockHole(179, 3), this.mockHole(371, 4), this.mockHole(146, 3),
|
||||||
|
this.mockHole(512, 5), this.mockHole(378, 4), this.mockHole(545, 5), this.mockHole(308, 4),
|
||||||
|
this.mockHole(155, 3), this.mockHole(514, 5), this.mockHole(378, 4), this.mockHole(204, 3),
|
||||||
|
this.mockHole(473, 5), this.mockHole(158, 3), this.mockHole(449, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void metamoreFieldsBlue() {
|
||||||
|
Assertions.assertEquals(17f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 71, 70f),
|
||||||
|
Arrays.asList(this.mockHole(388, 4), this.mockHole(121, 3), this.mockHole(523, 5),
|
||||||
|
this.mockHole(326, 4), this.mockHole(170, 3), this.mockHole(526, 5), this.mockHole(338, 4),
|
||||||
|
this.mockHole(181, 3), this.mockHole(427, 4), this.mockHole(392, 4), this.mockHole(152, 3),
|
||||||
|
this.mockHole(485, 5), this.mockHole(344, 4), this.mockHole(164, 3), this.mockHole(385, 4),
|
||||||
|
this.mockHole(504, 5), this.mockHole(382, 4), this.mockHole(401, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pinonHillsBlue() {
|
||||||
|
Assertions.assertEquals(35f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 72, 71.7f),
|
||||||
|
Arrays.asList(this.mockHole(387, 4), this.mockHole(398, 4), this.mockHole(412, 4),
|
||||||
|
this.mockHole(178, 3), this.mockHole(331, 4), this.mockHole(198, 3), this.mockHole(395, 4),
|
||||||
|
this.mockHole(537, 5), this.mockHole(572, 5), this.mockHole(404, 4), this.mockHole(397, 4),
|
||||||
|
this.mockHole(200, 3), this.mockHole(505, 5), this.mockHole(321, 4), this.mockHole(140, 3),
|
||||||
|
this.mockHole(405, 4), this.mockHole(520, 5), this.mockHole(446, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void worthingtonHillsBlue() {
|
||||||
|
Assertions.assertEquals(48f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 71, 72.7f),
|
||||||
|
Arrays.asList(this.mockHole(419, 4), this.mockHole(388, 4), this.mockHole(187, 3),
|
||||||
|
this.mockHole(520, 5), this.mockHole(420, 4), this.mockHole(560, 5), this.mockHole(200, 3),
|
||||||
|
this.mockHole(351, 4), this.mockHole(430, 4), this.mockHole(441, 4), this.mockHole(345, 4),
|
||||||
|
this.mockHole(192, 3), this.mockHole(365, 4), this.mockHole(398, 4), this.mockHole(172, 3),
|
||||||
|
this.mockHole(448, 4), this.mockHole(364, 4), this.mockHole(521, 5))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void darbyCreekBlue() {
|
||||||
|
Assertions.assertEquals(34f, service.computeRatingIndex(this.mockEighteenTeeRating('M', (byte) 72, 72.2f),
|
||||||
|
Arrays.asList(this.mockHole(350, 4), this.mockHole(374, 4), this.mockHole(169, 3),
|
||||||
|
this.mockHole(529, 5), this.mockHole(339, 4), this.mockHole(486, 5), this.mockHole(144, 3),
|
||||||
|
this.mockHole(418, 4), this.mockHole(438, 4), this.mockHole(309, 4), this.mockHole(168, 3),
|
||||||
|
this.mockHole(411, 4), this.mockHole(435, 4), this.mockHole(492, 5), this.mockHole(419, 4),
|
||||||
|
this.mockHole(188, 3), this.mockHole(540, 5), this.mockHole(452, 4))),
|
||||||
|
1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlexMap mockEighteenTeeRating(char gender, int par, float courseRating) {
|
||||||
|
FlexMap map = new FlexMap();
|
||||||
|
map.put("etratingID", 0L);
|
||||||
|
map.put("gender", String.valueOf(gender));
|
||||||
|
map.put("par", (byte) par);
|
||||||
|
map.put("courseRating", courseRating);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlexMap mockHole(int yards, int par) {
|
||||||
|
FlexMap map = new FlexMap();
|
||||||
|
map.put("yards", (short) yards);
|
||||||
|
map.put("par", (byte) par);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
package com.poststats.golf.service.compute;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import com.brianlong.util.FlexMap;
|
||||||
|
import com.poststats.golf.service.model.PointHandicapIndex;
|
||||||
|
|
||||||
|
public class ComputePointHandicapIndexUnitTest {
|
||||||
|
|
||||||
|
private static PostStatsPointHandicapIndexService service;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
public static void stage() {
|
||||||
|
service = new PostStatsPointHandicapIndexService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void brian() {
|
||||||
|
PointHandicapIndex phi = service.compute(Arrays.asList(this.mockRound(0, 22, 0, 0, 1, 5, 5, 6, 1, 0, 0),
|
||||||
|
this.mockRound(0, 22, 0, 1, 2, 5, 6, 4, 0, 0, 0), this.mockRound(0, 22, 0, 0, 1, 3, 7, 7, 0, 0, 0),
|
||||||
|
this.mockRound(0, 22, 0, 2, 2, 4, 8, 1, 1, 0, 0), this.mockRound(1, 26, 0, 0, 1, 5, 5, 6, 1, 0, 0),
|
||||||
|
this.mockRound(2, 17, 0, 1, 0, 4, 7, 6, 0, 0, 0), this.mockRound(2, 17, 0, 1, 2, 4, 5, 6, 0, 0, 0),
|
||||||
|
this.mockRound(2, 17, 0, 2, 0, 2, 7, 6, 1, 0, 0), this.mockRound(2, 17, 1, 0, 0, 3, 4, 8, 2, 0, 0),
|
||||||
|
this.mockRound(3, 35, 0, 3, 2, 2, 6, 4, 1, 0, 0), this.mockRound(3, 35, 0, 0, 1, 5, 4, 8, 0, 0, 0),
|
||||||
|
this.mockRound(3, 35, 1, 1, 3, 3, 3, 6, 1, 0, 0), this.mockRound(3, 35, 0, 2, 2, 5, 5, 4, 0, 0, 0)));
|
||||||
|
System.out.println(phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void ridgeway() {
|
||||||
|
PointHandicapIndex phi = service.compute(Arrays.asList(this.mockRound(0, 22, 0, 0, 0, 0, 7, 9, 2, 0, 0),
|
||||||
|
this.mockRound(0, 22, 0, 0, 0, 4, 5, 7, 2, 0, 0), this.mockRound(0, 22, 0, 0, 0, 0, 5, 11, 2, 0, 0),
|
||||||
|
this.mockRound(1, 48, 0, 0, 0, 2, 5, 9, 2, 0, 0), this.mockRound(2, 34, 0, 0, 0, 1, 3, 8, 6, 0, 0),
|
||||||
|
this.mockRound(1, 48, 0, 0, 0, 1, 4, 11, 2, 0, 0), this.mockRound(2, 34, 0, 0, 0, 1, 2, 12, 3, 0, 0),
|
||||||
|
this.mockRound(2, 34, 0, 0, 0, 2, 4, 8, 4, 0, 0), this.mockRound(3, 17, 0, 0, 0, 0, 4, 10, 4, 0, 0),
|
||||||
|
this.mockRound(3, 17, 0, 0, 0, 0, 3, 11, 4, 0, 0), this.mockRound(3, 17, 0, 0, 0, 0, 5, 10, 3, 0, 0)));
|
||||||
|
System.out.println(phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlexMap mockRound(int courseId, int pointAdj, int... scoreToParCounts) {
|
||||||
|
FlexMap map = new FlexMap();
|
||||||
|
map.put("courseID", courseId);
|
||||||
|
map.put("pointAdj", (byte) pointAdj);
|
||||||
|
map.put("bogey5", (byte) scoreToParCounts[0]);
|
||||||
|
map.put("bogey4", (byte) scoreToParCounts[1]);
|
||||||
|
map.put("bogey3", (byte) scoreToParCounts[2]);
|
||||||
|
map.put("bogey2", (byte) scoreToParCounts[3]);
|
||||||
|
map.put("bogey", (byte) scoreToParCounts[4]);
|
||||||
|
map.put("par", (byte) scoreToParCounts[5]);
|
||||||
|
map.put("birdie", (byte) scoreToParCounts[6]);
|
||||||
|
map.put("eagle", (byte) scoreToParCounts[7]);
|
||||||
|
map.put("alby", (byte) scoreToParCounts[8]);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,90 @@
|
|||||||
|
package com.poststats.golf.service.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class PointHandicapIndexUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void zero() {
|
||||||
|
PointHandicapIndex phi = new PointHandicapIndex(0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
Assertions.assertEquals(0, phi.getId());
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) -10));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) -3));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 5));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alby1() {
|
||||||
|
PointHandicapIndex phi = new PointHandicapIndex(1, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
Assertions.assertEquals(1, phi.getPointsWithScoreToPar((byte) -3));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) -2));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 5));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alby2eagle1() {
|
||||||
|
PointHandicapIndex phi = new PointHandicapIndex(2, 1, 0, 0, 0, 0, 0, 0, 0);
|
||||||
|
Assertions.assertEquals(2, phi.getPointsWithScoreToPar((byte) -3));
|
||||||
|
Assertions.assertEquals(1, phi.getPointsWithScoreToPar((byte) -2));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) -1));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 5));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void typicalScratch() {
|
||||||
|
PointHandicapIndex phi = new PointHandicapIndex(25, 14, 9, 2, 0, 0, 0, 0, 0);
|
||||||
|
Assertions.assertEquals(25, phi.getPointsWithScoreToPar((byte) -3));
|
||||||
|
Assertions.assertEquals(14, phi.getPointsWithScoreToPar((byte) -2));
|
||||||
|
Assertions.assertEquals(9, phi.getPointsWithScoreToPar((byte) -1));
|
||||||
|
Assertions.assertEquals(2, phi.getPointsWithScoreToPar((byte) 0));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 1));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 5));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void typicalBogey() {
|
||||||
|
PointHandicapIndex phi = new PointHandicapIndex(25, 16, 7, 4, 2, 1, 0, 0, 0);
|
||||||
|
Assertions.assertEquals(25, phi.getPointsWithScoreToPar((byte) -3));
|
||||||
|
Assertions.assertEquals(16, phi.getPointsWithScoreToPar((byte) -2));
|
||||||
|
Assertions.assertEquals(7, phi.getPointsWithScoreToPar((byte) -1));
|
||||||
|
Assertions.assertEquals(4, phi.getPointsWithScoreToPar((byte) 0));
|
||||||
|
Assertions.assertEquals(2, phi.getPointsWithScoreToPar((byte) 1));
|
||||||
|
Assertions.assertEquals(1, phi.getPointsWithScoreToPar((byte) 2));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void typicalBad() {
|
||||||
|
PointHandicapIndex phi = new PointHandicapIndex(40, 18, 10, 6, 4, 3, 1, 0, 0);
|
||||||
|
Assertions.assertEquals(40, phi.getPointsWithScoreToPar((byte) -3));
|
||||||
|
Assertions.assertEquals(18, phi.getPointsWithScoreToPar((byte) -2));
|
||||||
|
Assertions.assertEquals(10, phi.getPointsWithScoreToPar((byte) -1));
|
||||||
|
Assertions.assertEquals(6, phi.getPointsWithScoreToPar((byte) 0));
|
||||||
|
Assertions.assertEquals(4, phi.getPointsWithScoreToPar((byte) 1));
|
||||||
|
Assertions.assertEquals(3, phi.getPointsWithScoreToPar((byte) 2));
|
||||||
|
Assertions.assertEquals(1, phi.getPointsWithScoreToPar((byte) 3));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 4));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 5));
|
||||||
|
Assertions.assertEquals(0, phi.getPointsWithScoreToPar((byte) 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void plusBogey() {
|
||||||
|
PointHandicapIndex basephi = new PointHandicapIndex(40, 18, 10, 6, 4, 3, 1, 0, 0);
|
||||||
|
PointHandicapIndex phi = basephi.plus((byte) 1, (byte) 1);
|
||||||
|
Assertions.assertEquals(5, phi.getPointsWithScoreToPar((byte) 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void plus2Birdie() {
|
||||||
|
PointHandicapIndex basephi = new PointHandicapIndex(40, 18, 10, 6, 4, 3, 1, 0, 0);
|
||||||
|
PointHandicapIndex phi = basephi.plus((byte) -1, (byte) 2);
|
||||||
|
Assertions.assertEquals(12, phi.getPointsWithScoreToPar((byte) -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user