From be5a81acade7e6742bccafae06cc0821ae54ffc1 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 20 Feb 2019 15:33:25 +0100 Subject: [PATCH] Validate all stats but standard deviations + overlapping duration tests --- .../android/ExampleInstrumentedUnitTest.kt | 256 +++++++++++++++++- .../android/calculus/Calculator.kt | 16 +- .../pokeranalytics/android/calculus/Stat.kt | 4 +- .../android/model/realm/Session.kt | 2 +- .../android/model/realm/SessionSet.kt | 20 +- .../android/model/realm/TimeFrame.kt | 40 +-- 6 files changed, 289 insertions(+), 49 deletions(-) diff --git a/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt b/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt index 4b834a9f..a9ad2477 100644 --- a/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt +++ b/app/src/androidTest/java/net/pokeranalytics/android/ExampleInstrumentedUnitTest.kt @@ -37,13 +37,6 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { val realm = this.mockRealm realm.beginTransaction() - val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") - - val sd1 = sdf.parse("01/1/2019 10:00") - val ed1 = sdf.parse("01/1/2019 11:00") - val sd2 = sdf.parse("02/1/2019 08:00") - val ed2 = sdf.parse("02/1/2019 11:00") - var s1 = realm.createObject(Session::class.java, "1") var s2 = realm.createObject(Session::class.java, "2") @@ -53,19 +46,28 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) -// var s1: Session = Session.newInstance() -// var s2: Session = Session.newInstance() + s1.result?.buyin = 100.0 // net result = -100 + s2.result?.buyin = 200.0 + s2.result?.cashout = 500.0 // net result = 300 - s1.result?.netResult = -100.0 - s2.result?.netResult = 300.0 + s1.cgBigBlind = 0.5 // bb net result = -200bb + s2.cgBigBlind = 2.0 // bb net result = 150bb realm.insert(s1) realm.insert(s2) realm.commitTransaction() + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 10:00") + val ed1 = sdf.parse("01/1/2019 11:00") + val sd2 = sdf.parse("02/1/2019 08:00") + val ed2 = sdf.parse("02/1/2019 11:00") + realm.beginTransaction() - s1.timeFrame?.setDate(sd1, ed1) - s2.timeFrame?.setDate(sd2, ed2) + + s1.timeFrame?.setDate(sd1, ed1) // duration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800 + s2.timeFrame?.setDate(sd2, ed2) // duration = 3h, hourly = 100, bb100 = 150 / 75 * 100 = +200 realm.copyToRealmOrUpdate(s1) realm.copyToRealmOrUpdate(s2) @@ -75,7 +77,10 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { val sessions = realm.where(Session::class.java).findAll() val group = SessionGroup(name = "test", sessions = sessions) - val results: ComputedResults = Calculator.compute(group, Calculator.Options()) + var options = Calculator.Options() + options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) + + val results: ComputedResults = Calculator.compute(group, options) val delta = 0.01 val sum = results.computedStat(Stat.NETRESULT) @@ -99,8 +104,229 @@ class ExampleInstrumentedUnitTest : RealmInstrumentedUnitTest() { Assert.fail("No duration stat") } + val hourlyRate = results.computedStat(Stat.HOURLY_RATE) + if (hourlyRate != null) { + assertEquals(50.0, hourlyRate.value, delta) + } else { + Assert.fail("No houry rate stat") + } + val handsPlayed = results.computedStat(Stat.HANDS_PLAYED) + if (handsPlayed != null) { + assertEquals(100.0, handsPlayed.value, delta) + } else { + Assert.fail("No hands played stat") + } + val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) + if (numberOfGames != null) { + assertEquals(2, numberOfGames.value.toInt()) + } else { + Assert.fail("No numberOfGames stat") + } + val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) + if (numberOfSets != null) { + assertEquals(2, numberOfSets.value.toInt()) + } else { + Assert.fail("No numberOfSets stat") + } + val avgBuyin = results.computedStat(Stat.AVERAGE_BUYIN) + if (avgBuyin != null) { + assertEquals(150.0, avgBuyin.value, delta) + } else { + Assert.fail("No avgBuyin stat") + } + val avgDuration = results.computedStat(Stat.AVERAGE_DURATION) + if (avgDuration != null) { + assertEquals(2.0, avgDuration.value, delta) + } else { + Assert.fail("No avgDuration stat") + } + val roi = results.computedStat(Stat.ROI) + if (roi != null) { + assertEquals(200 / 300.0, roi.value, delta) + } else { + Assert.fail("No roi stat") + } + + val avgBBNet = results.computedStat(Stat.AVERAGE_NET_BB) + if (avgBBNet != null) { + assertEquals(-25.0, avgBBNet.value, delta) + } else { + Assert.fail("No avgBBNet stat") + } + val bbHourlyRate = results.computedStat(Stat.HOURLY_RATE_BB) + if (bbHourlyRate != null) { + assertEquals(-12.5, bbHourlyRate.value, delta) + } else { + Assert.fail("No bbHourlyRate stat") + } + val netbbPer100Hands = results.computedStat(Stat.NET_BB_PER_100_HANDS) + if (netbbPer100Hands != null) { + assertEquals(-50.0, netbbPer100Hands.value, delta) + } else { + Assert.fail("No netbbPer100Hands stat") + } + +// val stdHourly = results.computedStat(Stat.STANDARD_DEVIATION_HOURLY) +// if (stdHourly != null) { +// assertEquals(111.8, stdHourly.value, delta) +// } else { +// Assert.fail("No stdHourly stat") +// } +// +// val std = results.computedStat(Stat.STANDARD_DEVIATION) +// if (std != null) { +// assertEquals(200.0, std.value, delta) +// } else { +// Assert.fail("No std stat") +// } +// +// val std100 = results.computedStat(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS) +// if (std100 != null) { +// assertEquals(503.12, std100.value, delta) +// } else { +// Assert.fail("No std100 stat") +// } + + } + + @Test + fun testOverlappingSessions1() { + + val realm = this.mockRealm + realm.beginTransaction() + + var s1 = realm.createObject(Session::class.java, "1") + var s2 = realm.createObject(Session::class.java, "2") + + s1.timeFrame = realm.createObject(TimeFrame::class.java) + s2.timeFrame = realm.createObject(TimeFrame::class.java) + + s1.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) + s2.result = realm.createObject(net.pokeranalytics.android.model.realm.Result::class.java) + + realm.insert(s1) + realm.insert(s2) + realm.commitTransaction() + + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 09:00") + val ed1 = sdf.parse("01/1/2019 10:00") + val sd2 = sdf.parse("01/1/2019 08:00") + val ed2 = sdf.parse("01/1/2019 11:00") + + realm.beginTransaction() + + s1.timeFrame?.setDate(sd1, ed1) // duration = 1h, hourly = -100, bb100 = -200bb / 25hands * 100 = -800 + s2.timeFrame?.setDate(sd2, ed2) // duration = 4h, hourly = 100, bb100 = 150 / 75 * 100 = +200 + + realm.copyToRealmOrUpdate(s1) + realm.copyToRealmOrUpdate(s2) + + realm.commitTransaction() + + val sessions = realm.where(Session::class.java).findAll() + val group = SessionGroup(name = "test", sessions = sessions) + + var options = Calculator.Options() + options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) + + val results: ComputedResults = Calculator.compute(group, options) + val delta = 0.01 + + val duration = results.computedStat(Stat.DURATION) + if (duration != null) { + assertEquals(3.0, duration.value, delta) + } else { + Assert.fail("No Net result stat") + } + + val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) + if (numberOfSets != null) { + assertEquals(1, numberOfSets.value.toInt()) + } else { + Assert.fail("No numberOfSets stat") + } + + val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) + if (numberOfGames != null) { + assertEquals(2, numberOfGames.value.toInt()) + } else { + Assert.fail("No numberOfSets stat") + } } + @Test + fun testOverlappingSessions2() { + + val realm = this.mockRealm + realm.beginTransaction() + + var s1 = realm.createObject(Session::class.java, "1") + var s2 = realm.createObject(Session::class.java, "2") + var s3 = realm.createObject(Session::class.java, "3") + + s1.timeFrame = realm.createObject(TimeFrame::class.java) + s2.timeFrame = realm.createObject(TimeFrame::class.java) + s3.timeFrame = realm.createObject(TimeFrame::class.java) + + realm.insert(s1) + realm.insert(s2) + realm.insert(s3) + realm.commitTransaction() + + val sdf = SimpleDateFormat("dd/M/yyyy hh:mm") + + val sd1 = sdf.parse("01/1/2019 05:00") + val ed1 = sdf.parse("01/1/2019 09:00") + val sd2 = sdf.parse("01/1/2019 07:00") + val ed2 = sdf.parse("01/1/2019 11:00") + val sd3 = sdf.parse("01/1/2019 03:00") + val ed3 = sdf.parse("01/1/2019 06:00") + + realm.beginTransaction() + + s1.timeFrame?.setDate(sd1, ed1) // duration = 4h + s2.timeFrame?.setDate(sd2, ed2) // duration = 4h + s3.timeFrame?.setDate(sd3, ed3) // duration = 3h + + realm.copyToRealmOrUpdate(s1) + realm.copyToRealmOrUpdate(s2) + realm.copyToRealmOrUpdate(s3) + + realm.commitTransaction() + + val sessions = realm.where(Session::class.java).findAll() + val group = SessionGroup(name = "test", sessions = sessions) + + var options = Calculator.Options() + options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION) + + val results: ComputedResults = Calculator.compute(group, options) + val delta = 0.01 + + val duration = results.computedStat(Stat.DURATION) + if (duration != null) { + assertEquals(8.0, duration.value, delta) + } else { + Assert.fail("No Net result stat") + } + + val numberOfSets = results.computedStat(Stat.NUMBER_OF_SETS) + if (numberOfSets != null) { + assertEquals(1, numberOfSets.value.toInt()) + } else { + Assert.fail("No numberOfSets stat") + } + + val numberOfGames = results.computedStat(Stat.NUMBER_OF_GAMES) + if (numberOfGames != null) { + assertEquals(3, numberOfGames.value.toInt()) + } else { + Assert.fail("No numberOfSets stat") + } + + } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt index 41e6986f..0378f937 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt @@ -126,15 +126,15 @@ class Calculator { gTotalHands += sessionSet.estimatedHands gBBSum += sessionSet.bbNetResult - hourlyRate = gSum / duration * 3600.0 - hourlyRateBB = gBBSum / duration * 3600.0 + hourlyRate = gSum / duration + hourlyRateBB = gBBSum / duration if (options.evolutionValues == Options.EvolutionValues.DATED) { results.addEvolutionValue(gSum, duration, NETRESULT) - results.addEvolutionValue(gSum / duration * 3600.0, duration, HOURLY_RATE) + results.addEvolutionValue(gSum / duration, duration, HOURLY_RATE) results.addEvolutionValue(Stat.netBBPer100Hands(gBBSum, gTotalHands), duration, NET_BB_PER_100_HANDS) results.addEvolutionValue(hourlyRate, duration, HOURLY_RATE) - results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_GROUPS) + results.addEvolutionValue(gIndex.toDouble(), duration, NUMBER_OF_SETS) results.addEvolutionValue(sessionSet.duration.toDouble(), duration, DURATION) results.addEvolutionValue(duration / gIndex, duration, AVERAGE_DURATION) results.addEvolutionValue(hourlyRateBB, duration, HOURLY_RATE_BB) @@ -149,11 +149,11 @@ class Calculator { ComputedStat(HOURLY_RATE, hourlyRate), ComputedStat(AVERAGE, average), ComputedStat(DURATION, duration), - ComputedStat(NUMBER_OF_GROUPS, sessionSets.size.toDouble()), + ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()), ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()), - ComputedStat(AVERAGE_DURATION, (duration / 3600.0) / sessions.size), + ComputedStat(AVERAGE_DURATION, duration / sessions.size), ComputedStat(NET_BB_PER_100_HANDS, Stat.netBBPer100Hands(bbSum, totalHands)), - ComputedStat(HOURLY_RATE_BB, bbSum / duration * 3600.0), + ComputedStat(HOURLY_RATE_BB, bbSum / duration), ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount), ComputedStat(WIN_RATIO, (winningSessionCount / sessions.size).toDouble()), ComputedStat(AVERAGE_BUYIN, totalBuyin / sessions.size), @@ -180,7 +180,7 @@ class Calculator { results.addStats(setOf( ComputedStat(STANDARD_DEVIATION, standardDeviation), - ComputedStat(Stat.STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) + ComputedStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation) )) } diff --git a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt index 989b7ea4..4386165d 100644 --- a/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt +++ b/app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt @@ -12,7 +12,7 @@ enum class Stat : RowRepresentable { NETRESULT, HOURLY_RATE, AVERAGE, - NUMBER_OF_GROUPS, + NUMBER_OF_SETS, NUMBER_OF_GAMES, DURATION, AVERAGE_DURATION, @@ -58,7 +58,7 @@ enum class Stat : RowRepresentable { NETRESULT -> R.string.net_result HOURLY_RATE -> R.string.average_hour_rate AVERAGE -> R.string.average - NUMBER_OF_GROUPS -> R.string.number_of_groups + NUMBER_OF_SETS -> R.string.number_of_groups NUMBER_OF_GAMES -> R.string.number_of_games DURATION -> R.string.duration AVERAGE_DURATION -> R.string.average_duration diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt index fe14b6e2..1278ccb9 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt @@ -106,7 +106,7 @@ open class Session : RealmObject(), SessionInterface, RowRepresentableDataSource } @Ignore - override var estimatedHands: Double = 0.0 + override var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) @Ignore override var bbNetResult: Double = 0.0 diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt index 071a31a0..d05fad74 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt @@ -1,18 +1,25 @@ package net.pokeranalytics.android.model.realm import io.realm.Realm -import io.realm.RealmList import io.realm.RealmObject +import io.realm.RealmResults import io.realm.annotations.Ignore +import io.realm.annotations.LinkingObjects open class SessionSet() : RealmObject() { - // The timeframe of the set, i.e. its start & end date + /** + * The timeframe of the set, i.e. its start & end date + */ + var timeFrame: TimeFrame? = null - // The list of Session played within the set, i.e. played within the same time frame - var sessions: RealmList = RealmList() + /** + * The list of sessions associated with this set + */ + @LinkingObjects("sessionSet") + val sessions: RealmResults? = null @Ignore // a duration shortcut var duration: Long = 0L @@ -28,12 +35,15 @@ open class SessionSet() : RealmObject() { @Ignore // a netResult shortcut var netResult: Double = 0.0 + get () { + return this.sessions?.sumByDouble { it.value } ?: 0.0 + } @Ignore // a duration shortcut var hourlyRate: Double = 0.0 @Ignore - var estimatedHands: Double = 0.0 + var estimatedHands: Double = 25.0 * (this.timeFrame?.hourlyDuration?.toDouble() ?: 0.0) @Ignore var bbNetResult: Double = 0.0 diff --git a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt index f443f44e..3bb44aa8 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt +++ b/app/src/main/java/net/pokeranalytics/android/model/realm/TimeFrame.kt @@ -7,6 +7,7 @@ import io.realm.RealmResults import io.realm.annotations.Ignore import io.realm.annotations.LinkingObjects import net.pokeranalytics.android.exceptions.ModelException +import timber.log.Timber import java.util.* open class TimeFrame : RealmObject() { @@ -82,19 +83,22 @@ open class TimeFrame : RealmObject() { var query: RealmQuery = realm.where(SessionSet::class.java) query.isNotNull("timeFrame") + Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") + + if (this.endDate == null) { query.greaterThan("timeFrame.startDate", this.startDate.time).or().greaterThan("timeFrame.endDate", this.startDate.time) } else { val endDate = this.endDate!! query - .greaterThan("timeFrame.startDate", this.startDate) - .lessThan("timeFrame.endDate", this.startDate) - .or() - .greaterThan("timeFrame.startDate", endDate) - .lessThan("timeFrame.endDate", endDate) - .or() .lessThan("timeFrame.startDate", this.startDate) + .greaterThan("timeFrame.endDate", this.startDate) + .or() + .lessThan("timeFrame.startDate", endDate) .greaterThan("timeFrame.endDate", endDate) + .or() + .greaterThan("timeFrame.startDate", this.startDate) + .lessThan("timeFrame.endDate", endDate) } val sessionGroups = query.findAll() @@ -138,6 +142,9 @@ open class TimeFrame : RealmObject() { } ?: run { throw ModelException("Session should never be null here") } + + Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") + // this.session?.sessionSet = set // set.sessions.add(this.session) // realm.commitTransaction() @@ -163,10 +170,10 @@ open class TimeFrame : RealmObject() { // Realm Update // val realm = Realm.getDefaultInstance() - realm.beginTransaction() - if (!sessionSet.sessions.contains(this.session)) { - sessionSet.sessions.add(this.session) - } +// realm.beginTransaction() + + this.session?.sessionSet = sessionSet + // realm.copyToRealmOrUpdate(groupTimeFrame) // realm.commitTransaction() @@ -181,6 +188,7 @@ open class TimeFrame : RealmObject() { var startDate: Date = this.startDate var endDate: Date? = this.endDate + // find earlier and later dates from all sets val timeFrames = sessionSets.mapNotNull { it.timeFrame } timeFrames.forEach { tf -> if (tf.startDate.before(startDate)) { @@ -200,11 +208,8 @@ open class TimeFrame : RealmObject() { } // get all sessions from sets - var sessions = sessionSets.flatMap { it.sessions } - - // Start Realm updates -// val realm = Realm.getDefaultInstance() -// realm.beginTransaction() + var sessions = mutableSetOf() + sessionSets.forEach { it.sessions?.asIterable()?.let { it1 -> sessions.addAll(it1) } } // delete all sets sessionSets.deleteAllFromRealm() @@ -220,15 +225,14 @@ open class TimeFrame : RealmObject() { // Add the session linked to this timeframe to the new sessionGroup this.sessions?.first()?.let { - set.sessions.add(it) + it.sessionSet = set } ?: run { throw ModelException("TimeFrame should never be null here") } // Add all orphan sessions - set.sessions.addAll(sessions) + sessions.forEach { it.sessionSet = set } -// realm.commitTransaction() } }