Merge branch 'dev' of gitlab.com:stax-river/poker-analytics into dev

feature/top10
Aurelien Hubert 7 years ago
commit ebb31da9d5
  1. 74
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  2. 4
      app/src/main/java/net/pokeranalytics/android/calculus/Report.kt
  3. 38
      app/src/main/java/net/pokeranalytics/android/calculus/Stat.kt
  4. 10
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  5. 12
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  6. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/GraphFragment.kt

@ -8,8 +8,11 @@ import net.pokeranalytics.android.model.filter.QueryCondition
import net.pokeranalytics.android.model.filter.name import net.pokeranalytics.android.model.filter.name
import net.pokeranalytics.android.model.realm.ComputableResult import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.model.realm.SessionSet import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.util.extensions.startOfDay
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import kotlin.math.max
import kotlin.math.min
/** /**
* The class performing stats computation * The class performing stats computation
@ -62,7 +65,6 @@ class Calculator {
return true return true
} }
// var aggregation: Aggregation? = null
} }
companion object { companion object {
@ -161,7 +163,12 @@ class Calculator {
val computables = computableGroup.computables(realm) val computables = computableGroup.computables(realm)
Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables") Timber.d(">>>> Start computing group ${computableGroup.name}, ${computables.size} computables")
val results: ComputedResults = ComputedResults(computableGroup) val computeLongestStreak = options.displayedStats.contains(LONGEST_STREAKS)
if (computeLongestStreak) {
computables.sort("session.startDate")
}
val results = ComputedResults(computableGroup)
val sum = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble() val sum = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble()
val totalHands = computables.sum(ComputableResult.Field.ESTIMATED_HANDS.identifier).toDouble() val totalHands = computables.sum(ComputableResult.Field.ESTIMATED_HANDS.identifier).toDouble()
@ -170,10 +177,18 @@ class Calculator {
val winningSessionCount = computables.sum(ComputableResult.Field.IS_POSITIVE.identifier).toInt() val winningSessionCount = computables.sum(ComputableResult.Field.IS_POSITIVE.identifier).toInt()
val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble() val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble()
// Compute for each session val maxNetResult = computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
val minNetResult = computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
when (options.evolutionValues) { if (options.displayedStats.contains(LOCATIONS_PLAYED)) {
Options.EvolutionValues.STANDARD -> { results.addStat(LOCATIONS_PLAYED, computables.distinctBy { it.session?.location?.id }.size.toDouble())
}
val shouldIterateOverComputables =
(options.evolutionValues == Options.EvolutionValues.STANDARD || computeLongestStreak)
// Iterate for each session
if (shouldIterateOverComputables) {
var index: Int = 0 var index: Int = 0
var tSum = 0.0 var tSum = 0.0
@ -182,6 +197,7 @@ class Calculator {
var tWinningSessionCount = 0 var tWinningSessionCount = 0
var tBuyinSum = 0.0 var tBuyinSum = 0.0
var tHands = 0.0 var tHands = 0.0
var winStreak = 0; var loseStreak = 0; var currentStreak = 0
computables.forEach { computable -> computables.forEach { computable ->
index++ index++
@ -192,6 +208,23 @@ class Calculator {
tBuyinSum += computable.ratedBuyin tBuyinSum += computable.ratedBuyin
tHands += computable.estimatedHands tHands += computable.estimatedHands
if (computable.isPositive == 1) {
if (currentStreak >= 0) {
currentStreak++
} else {
currentStreak = 1
loseStreak = min(loseStreak, currentStreak)
}
}
if (computable.isPositive == 0) {
if (currentStreak <= 0) {
currentStreak--
} else {
currentStreak = -1
winStreak = max(winStreak, currentStreak)
}
}
val session = val session =
computable.session ?: throw IllegalStateException("Computing lone ComputableResult") computable.session ?: throw IllegalStateException("Computing lone ComputableResult")
results.addEvolutionValue(tSum, stat = NETRESULT, data = session) results.addEvolutionValue(tSum, stat = NETRESULT, data = session)
@ -214,10 +247,15 @@ class Calculator {
} }
} }
if (currentStreak >= 0) {
winStreak = max(winStreak, currentStreak)
} else {
loseStreak = min(loseStreak, currentStreak)
} }
else -> {
// nothing results.addStat(LONGEST_STREAKS, winStreak.toDouble(), loseStreak.toDouble())
}
} }
val sessionSets = computableGroup.sessionSets(realm) val sessionSets = computableGroup.sessionSets(realm)
@ -228,6 +266,7 @@ class Calculator {
val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble() val gSum = sessionSets.sum(SessionSet.Field.RATED_NET.identifier).toDouble()
val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble() val gTotalHands = sessionSets.sum(SessionSet.Field.ESTIMATED_HANDS.identifier).toDouble()
val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble() val gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
val maxDuration = sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.toDouble()
val hourlyRate = gSum / gHourlyDuration val hourlyRate = gSum / gHourlyDuration
// var bbHourlyRate = gBBSum / gDuration // var bbHourlyRate = gBBSum / gDuration
@ -242,6 +281,7 @@ class Calculator {
var tBBSum = 0.0 var tBBSum = 0.0
var tHourlyRate = 0.0 var tHourlyRate = 0.0
var tHourlyRateBB = 0.0 var tHourlyRateBB = 0.0
val daysSet = mutableSetOf<Date>()
sessionSets.forEach { sessionSet -> sessionSets.forEach { sessionSet ->
tIndex++ tIndex++
@ -252,6 +292,7 @@ class Calculator {
tHourlyRate = gSum / tHourlyDuration tHourlyRate = gSum / tHourlyDuration
tHourlyRateBB = gBBSum / tHourlyDuration tHourlyRateBB = gBBSum / tHourlyDuration
daysSet.add(sessionSet.startDate.startOfDay())
when (options.evolutionValues) { when (options.evolutionValues) {
Options.EvolutionValues.STANDARD -> { Options.EvolutionValues.STANDARD -> {
@ -308,6 +349,8 @@ class Calculator {
} }
} }
results.addStat(DAYS_PLAYED, daysSet.size.toDouble())
} }
} }
else -> { else -> {
@ -350,15 +393,24 @@ class Calculator {
ComputedStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration), ComputedStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration),
ComputedStat(AVERAGE_NET_BB, gBBSum / bbSessionCount), ComputedStat(AVERAGE_NET_BB, gBBSum / bbSessionCount),
ComputedStat(HANDS_PLAYED, totalHands) ComputedStat(HANDS_PLAYED, totalHands)
) )
) )
Stat.returnOnInvestment(sum, totalBuyin)?.let { roi -> Stat.returnOnInvestment(sum, totalBuyin)?.let { roi ->
results.addStats(setOf(ComputedStat(ROI, roi))) results.addStat(ROI, roi)
} }
Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 -> Stat.netBBPer100Hands(bbSum, totalHands)?.let { netBB100 ->
results.addStats(setOf(ComputedStat(NET_BB_PER_100_HANDS, netBB100))) results.addStat(NET_BB_PER_100_HANDS, netBB100)
}
maxNetResult?.let { max ->
results.addStat(MAXIMUM_NETRESULT, max)
}
minNetResult?.let { min ->
results.addStat(MINIMUM_NETRESULT, min)
}
maxDuration?.let { maxd ->
results.addStat(MAXIMUM_DURATION, maxd)
} }
val bbPer100Hands = bbSum / totalHands * 100 val bbPer100Hands = bbSum / totalHands * 100

@ -182,6 +182,10 @@ class ComputedResults(group: ComputableGroup) {
} }
} }
fun addStat(stat: Stat, value: Double, secondValue: Double? = null) {
this._computedStats[stat] = ComputedStat(stat, value, secondValue)
}
fun addStats(computedStats: Set<ComputedStat>) { fun addStats(computedStats: Set<ComputedStat>) {
computedStats.forEach { computedStats.forEach {
this._computedStats[it.stat] = it this._computedStats[it.stat] = it

@ -70,7 +70,14 @@ enum class Stat : RowRepresentable {
STANDARD_DEVIATION, STANDARD_DEVIATION,
STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_HOURLY,
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB_PER_100_HANDS,
HANDS_PLAYED; HANDS_PLAYED,
LOCATIONS_PLAYED,
LONGEST_STREAKS,
MAXIMUM_NETRESULT,
MINIMUM_NETRESULT,
MAXIMUM_DURATION,
DAYS_PLAYED
;
/** /**
* Returns whether the stat evolution numericValues requires a distribution sorting * Returns whether the stat evolution numericValues requires a distribution sorting
@ -120,6 +127,12 @@ enum class Stat : RowRepresentable {
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands
HANDS_PLAYED -> R.string.number_of_hands HANDS_PLAYED -> R.string.number_of_hands
LOCATIONS_PLAYED -> R.string.locations_played
LONGEST_STREAKS -> R.string.longest_streaks
MAXIMUM_NETRESULT -> R.string.max_net_result
MINIMUM_NETRESULT -> R.string.min_net_result
MAXIMUM_DURATION -> R.string.longest_session
DAYS_PLAYED -> R.string.days_played
} }
} }
@ -127,7 +140,7 @@ enum class Stat : RowRepresentable {
/** /**
* Formats the value of the stat to be suitable for display * Formats the value of the stat to be suitable for display
*/ */
fun format(value: Double, currency: Currency? = null, context: Context): TextFormat { fun format(value: Double, secondValue: Double? = null, currency: Currency? = null, context: Context): TextFormat {
if (value.isNaN()) { if (value.isNaN()) {
return TextFormat(NULL_TEXT, R.color.white) return TextFormat(NULL_TEXT, R.color.white)
@ -135,30 +148,33 @@ enum class Stat : RowRepresentable {
when (this) { when (this) {
// Amounts + red/green // Amounts + red/green
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { NETRESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NETRESULT, MINIMUM_NETRESULT -> {
val color = if (value >= this.threshold) R.color.green else R.color.red val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.toCurrency(currency), color) return TextFormat(value.toCurrency(currency), color)
} }
// Red/green numericValues // Red/green numericValues
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> { HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS -> {
val color = if (value >= this.threshold) R.color.green else R.color.red val color = if (value >= this.threshold) R.color.green else R.color.red
return TextFormat(value.formatted(), color) return TextFormat(value.formatted(), color)
} }
// white integers // white integers
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> { NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> {
return TextFormat("${value.toInt()}") return TextFormat("${value.toInt()}")
} // white durations } // white durations
Stat.DURATION, Stat.AVERAGE_DURATION -> { DURATION, AVERAGE_DURATION, MAXIMUM_DURATION -> {
return TextFormat(value.formattedHourlyDuration()) return TextFormat(value.formattedHourlyDuration())
} // red/green percentages } // red/green percentages
Stat.WIN_RATIO, Stat.ROI -> { WIN_RATIO, ROI -> {
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
return TextFormat("${(value * 100).formatted()}%", color) return TextFormat("${(value * 100).formatted()}%", color)
} // white amountsr } // white amountsr
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { STANDARD_DEVIATION_BB_PER_100_HANDS -> {
return TextFormat(value.toCurrency(currency)) return TextFormat(value.toCurrency(currency))
} }
LONGEST_STREAKS -> {
return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L")
}
else -> throw FormattingException("Stat formatting of ${this.name} not handled") else -> throw FormattingException("Stat formatting of ${this.name} not handled")
} }
} }
@ -228,7 +244,7 @@ enum class Stat : RowRepresentable {
/** /**
* ComputedStat contains a [stat] and their associated [value] * ComputedStat contains a [stat] and their associated [value]
*/ */
class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? = null) { class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) {
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
if (previousValue != null) { if (previousValue != null) {
@ -245,7 +261,7 @@ class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? =
* Formats the value of the stat to be suitable for display * Formats the value of the stat to be suitable for display
*/ */
fun format(context: Context): TextFormat { fun format(context: Context): TextFormat {
return this.stat.format(this.value, this.currency, context) return this.stat.format(this.value, this.secondValue, this.currency, context)
} }
} }

@ -581,7 +581,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(), title = getFormattedDuration(),
computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency) computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency = currency)
) )
) )
rows.add(SeparatorRow()) rows.add(SeparatorRow())
@ -591,7 +591,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
resId = R.string.pause, resId = R.string.pause,
computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency) computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency = currency)
) )
) )
rows.add(SeparatorRow()) rows.add(SeparatorRow())
@ -601,14 +601,14 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT_BIG, RowViewType.HEADER_TITLE_AMOUNT_BIG,
title = getFormattedDuration(), title = getFormattedDuration(),
computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency) computedStat = ComputedStat(Stat.NETRESULT, result?.net ?: 0.0, currency = currency)
) )
) )
rows.add( rows.add(
CustomizableRowRepresentable( CustomizableRowRepresentable(
RowViewType.HEADER_TITLE_AMOUNT, RowViewType.HEADER_TITLE_AMOUNT,
resId = R.string.hour_rate_without_pauses, resId = R.string.hour_rate_without_pauses,
computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, currency) computedStat = ComputedStat(Stat.HOURLY_RATE, this.hourlyRate, currency = currency)
) )
) )
@ -896,7 +896,7 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
} }
value?.let { value?.let {
return stat.format(it, currency, context) return stat.format(it, currency = currency, context = context)
} ?: run { } ?: run {
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }

@ -88,15 +88,15 @@ open class SessionSet() : RealmObject(), Timed, Filterable {
override fun formattedValue(stat: Stat, context: Context) : TextFormat { override fun formattedValue(stat: Stat, context: Context) : TextFormat {
return when (stat) { return when (stat) {
Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, null, context) Stat.NETRESULT, Stat.AVERAGE -> stat.format(this.ratedNet, currency = null, context = context)
Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), null, context) Stat.DURATION, Stat.AVERAGE_DURATION -> stat.format(this.netDuration.toDouble(), currency = null, context = context)
Stat.HOURLY_RATE -> stat.format(this.hourlyRate, null, context) Stat.HOURLY_RATE -> stat.format(this.hourlyRate, currency = null, context = context)
Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, null, context) Stat.HANDS_PLAYED -> stat.format(this.estimatedHands, currency = null, context = context)
Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, null, context) Stat.HOURLY_RATE_BB -> stat.format(this.bbHourlyRate, currency = null, context = context)
Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { Stat.NET_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> {
val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands) val netBBPer100Hands = Stat.netBBPer100Hands(this.bbNet, this.estimatedHands)
if (netBBPer100Hands != null) { if (netBBPer100Hands != null) {
return stat.format(this.estimatedHands, null, context) return stat.format(this.estimatedHands, currency = null, context = context)
} else { } else {
return TextFormat(NULL_TEXT) return TextFormat(NULL_TEXT)
} }

@ -198,7 +198,7 @@ class GraphFragment : PokerAnalyticsFragment(), OnChartValueSelectedListener, Co
val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate()) val formattedDate = DateFormat.getDateInstance(DateFormat.SHORT).format(it.startDate())
val entryValue = it.formattedValue(this.stat, requireContext()) val entryValue = it.formattedValue(this.stat, requireContext())
val totalStatValue = this.stat.format(e.y.toDouble(), null, requireContext()) val totalStatValue = this.stat.format(e.y.toDouble(), currency = null, context = requireContext())
this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue) this.legendView.setItemData(this.stat, formattedDate, entryValue, totalStatValue)
} }

Loading…
Cancel
Save