CSV import improvements

dev
Laurent 7 years ago
parent 3b65082794
commit dca9a7a8f6
  1. 6
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  2. 2
      app/src/main/java/net/pokeranalytics/android/api/CurrencyConverterApi.kt
  3. 2
      app/src/main/java/net/pokeranalytics/android/model/Limit.kt
  4. 35
      app/src/main/java/net/pokeranalytics/android/model/TableSize.kt
  5. 8
      app/src/main/java/net/pokeranalytics/android/model/TournamentType.kt
  6. 21
      app/src/main/java/net/pokeranalytics/android/model/realm/Bankroll.kt
  7. 15
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  8. 2
      app/src/main/java/net/pokeranalytics/android/model/retrofit/ConvertResult.kt
  9. 2
      app/src/main/java/net/pokeranalytics/android/model/utils/Seed.kt
  10. 2
      app/src/main/java/net/pokeranalytics/android/ui/activity/HomeActivity.kt
  11. 2
      app/src/main/java/net/pokeranalytics/android/ui/fragment/data/BankrollDataFragment.kt
  12. 2
      app/src/main/java/net/pokeranalytics/android/util/URL.kt
  13. 17
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVDescriptor.kt
  14. 3
      app/src/main/java/net/pokeranalytics/android/util/csv/CSVImporter.kt
  15. 213
      app/src/main/java/net/pokeranalytics/android/util/csv/SessionCSVDescriptor.kt
  16. 28
      app/src/main/java/net/pokeranalytics/android/util/csv/TypedField.kt
  17. 6
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt

@ -40,6 +40,12 @@ class PokerAnalyticsApplication : Application() {
.build()
Realm.setDefaultConfiguration(realmConfiguration)
// val realm = Realm.getDefaultInstance()
// realm.executeTransaction {
// realm.where(Session::class.java).findAll().deleteAllFromRealm()
// }
// realm.close()
// Set up Crashlytics, disabled for debug builds
val crashlyticsKit = Crashlytics.Builder()
.core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())

@ -16,7 +16,7 @@ import retrofit2.http.Query
import java.util.concurrent.TimeUnit
/**
* Currency Converter API
* CurrencyCode Converter API
*/
interface CurrencyConverterApi {

@ -16,7 +16,7 @@ enum class Limit : RowRepresentable {
return when (value) {
"No Limit" -> NO
"Pot Limit" -> POT
"Fixed Limit" -> FIXED
"Fixed Limit", "Limit" -> FIXED
"Mixed Limit" -> MIXED
"Spread Limit" -> SPREAD
else -> null

@ -6,21 +6,32 @@ import net.pokeranalytics.android.ui.view.RowRepresentable
import net.pokeranalytics.android.ui.view.RowViewType
class TableSize(var numberOfPlayer: Int, var rowViewType: Int = RowViewType.TITLE_GRID.ordinal) : RowRepresentable {
companion object {
val all : List<TableSize>
get() {
return Array(9, init =
{ index -> TableSize(index + 2) }).toList()
}
val all: List<TableSize>
get() {
return Array(9, init =
{ index -> TableSize(index + 2) }).toList()
}
fun valueForLabel(label: String) : Int? {
return when (label) {
"Full Ring", "Full-Ring" -> 10
"Short-Handed", "Short Handed" -> 6
"Heads-Up", "Heads Up" -> 2
else -> null
}
}
}
override fun getDisplayName(context: Context): String {
return if (this.numberOfPlayer == 2) {
return "HU"
} else {
"${this.numberOfPlayer}-max"
}
}
override fun getDisplayName(context: Context): String {
return if (this.numberOfPlayer == 2) {
return "HU"
} else {
"${this.numberOfPlayer}-max"
}
}
override val resId: Int?
get() {

@ -14,6 +14,14 @@ enum class TournamentType : RowRepresentable {
get() {
return TournamentType.values() as List<TournamentType>
}
fun getValueForLabel(label: String) : TournamentType? {
return when (label) {
"Single-Table" -> SNG
"Multi-Table" -> MTT
else -> null
}
}
}
override val resId: Int?

@ -96,4 +96,25 @@ open class Bankroll : RealmObject(), NameManageable, RowRepresentable {
else -> super.getFailedSaveMessage(status)
}
}
companion object {
fun getOrCreate(realm: Realm, name: String, live: Boolean = true, currencyCode: String? = null, currencyRate: Double? = null) : Bankroll {
val bankroll = realm.where<Bankroll>().equalTo("name", name).findFirst()
return if (bankroll != null) {
bankroll
} else {
val bankroll = Bankroll()
bankroll.name = name
val currency = Currency()
currency.code = currencyCode
currency.rate = currencyRate
bankroll.currency = currency
realm.copyToRealm(bankroll)
}
}
}
}

@ -50,7 +50,20 @@ open class Session : RealmObject(), Savable, Editable, StaticRowRepresentableDat
enum class Type {
CASH_GAME,
TOURNAMENT
TOURNAMENT;
companion object {
fun getValueFromString(string: String): Type? {
return when (string) {
"Cash", "Cash Game" -> CASH_GAME
"Tournament" -> TOURNAMENT
else -> null
}
}
}
}
companion object {

@ -3,7 +3,7 @@ package net.pokeranalytics.android.model.retrofit
import com.google.gson.annotations.SerializedName
/**
* Currency Converter mapping class
* CurrencyCode Converter mapping class
*/
class CurrencyConverterValue {
@SerializedName("val")

@ -30,7 +30,7 @@ class Seed(var context:Context) : Realm.Transaction {
private fun createDefaultCurrencyAndBankroll(realm: Realm) {
// Currency
// CurrencyCode
val localeCurrency = UserDefaults.getLocaleCurrency()
val defaultCurrency = Currency()
defaultCurrency.code = localeCurrency.currencyCode

@ -80,7 +80,7 @@ class HomeActivity : PokerAnalyticsActivity() {
fun csv() {
val path = "sdcard/Download/AllCashGames.csv"
val path = "sdcard/Download/RunGoodSessions1.csv"
val csv = CSVImporter(path)
csv.start()

@ -203,7 +203,7 @@ class BankrollDataFragment : EditableDataFragment(), StaticRowRepresentableDataS
}
/**
* Refresh the rate with the Currency Converter API
* Refresh the rate with the CurrencyCode Converter API
*/
private fun refreshRate() {

@ -18,7 +18,7 @@ enum class URL(var value: String) {
// Support
SUPPORT_EMAIL("support@pokeranalytics.net"),
// Currency Converter API
// CurrencyCode Converter API
API_CURRENCY_CONVERTER("https://free.currencyconverterapi.com/api/v5/")
}

@ -5,11 +5,17 @@ import io.realm.RealmModel
import org.apache.commons.csv.CSVRecord
abstract class DataCSVDescriptor<T : RealmModel>(vararg elements: Field) : CSVDescriptor(*elements) {
enum class DataSource {
POKER_INCOME,
POKER_BANKROLL_TRACKER,
RUNGOOD
}
abstract class DataCSVDescriptor<T : RealmModel>(source: DataSource, vararg elements: Field) : CSVDescriptor(source, *elements) {
val realmModels = mutableListOf<RealmModel>()
abstract fun parseData(realm: Realm, record: CSVRecord) : T?
abstract fun parseData(realm: Realm, record: CSVRecord): T?
override fun parse(realm: Realm, record: CSVRecord) {
@ -22,7 +28,7 @@ abstract class DataCSVDescriptor<T : RealmModel>(vararg elements: Field) : CSVDe
}
open class CSVDescriptor(vararg elements: Field) {
open class CSVDescriptor(var source: DataSource, vararg elements: Field) {
protected var fields: List<Field> = listOf()
protected var fieldMapping: MutableMap<Field, Int> = mutableMapOf()
@ -34,10 +40,11 @@ open class CSVDescriptor(vararg elements: Field) {
}
companion object {
val all: List<CSVDescriptor> = listOf(SessionCSVDescriptor.pokerIncomeCash)
val all: List<CSVDescriptor> =
listOf(SessionCSVDescriptor.pokerIncomeCash, SessionCSVDescriptor.pokerBankrollTracker, SessionCSVDescriptor.runGoodCashGames, SessionCSVDescriptor.runGoodTournaments)
}
fun matches(record: CSVRecord) : Boolean {
fun matches(record: CSVRecord): Boolean {
var count = 0
val headers = record.toSet()

@ -17,7 +17,7 @@ open class CSVImporter(var path: String) {
val reader = FileReader(this.path)
val parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader)
val parser = CSVFormat.DEFAULT.withAllowMissingColumnNames().parse(reader)
Timber.d("Starting import...")
@ -58,6 +58,7 @@ open class CSVImporter(var path: String) {
CSVDescriptor.all.forEach { descriptor ->
if (descriptor.matches(record)) {
this.currentDescriptor = descriptor
Timber.d("Identified source: ${descriptor.source}")
return
}
}

@ -2,63 +2,122 @@ package net.pokeranalytics.android.util.csv
import io.realm.Realm
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.TableSize
import net.pokeranalytics.android.model.TournamentType
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.utils.SessionUtils
import net.pokeranalytics.android.util.extensions.getOrCreate
import net.pokeranalytics.android.util.extensions.setHourMinutes
import org.apache.commons.csv.CSVRecord
import timber.log.Timber
import java.util.*
sealed class SessionField {
data class Start(
override var header: String,
override var callback: (() -> Unit)? = null,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
data class StartTime(
override var header: String,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
data class End(
override var header: String,
override var callback: (() -> Unit)? = null,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
data class EndTime(
override var header: String,
override var callback: ((String) -> Date?)? = null,
override val dateFormat: String? = null
) : DateField
data class Buyin(
override var header: String,
override var callback: (() -> Unit)? = null,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class NetResult(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class CashedOut(
override var header: String,
override var callback: (() -> Unit)? = null,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class Break(
override var header: String,
override var callback: (() -> Unit)? = null,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class Tips(
override var header: String,
override var callback: (() -> Unit)? = null,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class SmallBlind(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class BigBlind(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class Blind(override var header: String, override var callback: (() -> Unit)? = null) : BlindField
data class Game(override var header: String, override var callback: (() -> Unit)? = null) : Field
data class Location(override var header: String, override var callback: (() -> Unit)? = null) : Field
data class Bankroll(override var header: String, override var callback: (() -> Unit)? = null) : Field
data class LimitType(override var header: String, override var callback: (() -> Unit)? = null) : Field
data class Comment(override var header: String, override var callback: (() -> Unit)? = null) : Field
data class Blind(override var header: String, override var callback: ((String) -> Pair<Double, Double>?)? = null) : BlindField
data class Game(override var header: String) : Field
data class Location(override var header: String) : Field
data class LocationType(override var header: String) : Field
data class Bankroll(override var header: String) : Field
data class LimitType(override var header: String) : Field
data class Comment(override var header: String) : Field
data class SessionType(override var header: String) : Field
data class TableSize(override var header: String) : Field
data class CurrencyCode(override var header: String) : Field
data class TournamentName(override var header: String) : Field
data class TournamentType(override var header: String) : Field
data class CurrencyRate(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class TournamentPosition(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
data class TournamentNumberOfPlayers(
override var header: String,
override var callback: ((String) -> Double?)? = null,
override val numberFormat: String? = null
) : NumberField
}
class SessionCSVDescriptor(var isTournament: Boolean, vararg elements: Field) : DataCSVDescriptor<Session>(*elements) {
class SessionCSVDescriptor(source: DataSource, private var isTournament: Boolean, vararg elements: Field) : DataCSVDescriptor<Session>(source, *elements) {
companion object {
val pokerIncomeCash: CSVDescriptor = SessionCSVDescriptor(
DataSource.POKER_INCOME,
false,
SessionField.Start("Start Time"),
SessionField.End("End Time"),
@ -69,40 +128,129 @@ class SessionCSVDescriptor(var isTournament: Boolean, vararg elements: Field) :
SessionField.Game("Game"),
SessionField.Bankroll("Bankroll"),
SessionField.Location("Location"),
SessionField.Location("Location Type"),
SessionField.Comment("Note"),
SessionField.Tips("Tips"),
SessionField.Blind("Stake")
)
val pokerBankrollTracker: CSVDescriptor = SessionCSVDescriptor(
DataSource.POKER_BANKROLL_TRACKER,
true,
SessionField.Start("starttime", dateFormat = "MM/dd/yyyy HH:mm"),
SessionField.End("endtime", dateFormat = "MM/dd/yyyy HH:mm"),
SessionField.SessionType("variant"),
SessionField.Buyin("buyin"),
SessionField.CashedOut("cashout"),
SessionField.Break("breakminutes"),
SessionField.LimitType("limit"),
SessionField.Game("game"),
SessionField.Bankroll("currency"), // same as currency code
SessionField.Location("location"),
SessionField.Comment("sessionnote"),
SessionField.Tips("expensesfromstack"),
SessionField.SmallBlind("smallblind"),
SessionField.BigBlind("bigblind"),
SessionField.TournamentNumberOfPlayers("player"),
SessionField.TournamentPosition("place"),
SessionField.TournamentName("mttname"),
SessionField.CurrencyCode("currency"),
SessionField.CurrencyRate("exchangerate"),
SessionField.TableSize("tablesize")
)
val runGoodTournaments: CSVDescriptor = SessionCSVDescriptor(
DataSource.RUNGOOD,
true,
SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
SessionField.StartTime("Start Time"),
SessionField.End("End Date"),
SessionField.EndTime("End Time"),
SessionField.Buyin("Total Buy-In"),
SessionField.CashedOut("Winnings"),
SessionField.NetResult("Profit"),
SessionField.Break("Break"),
SessionField.LimitType("Limit Type"),
SessionField.Game("Game"),
SessionField.Bankroll("Bankroll"),
SessionField.TableSize("Table Type"),
SessionField.Location("Location"),
SessionField.LocationType("Location Type"),
SessionField.Comment("Notes"),
SessionField.CurrencyCode("Currency"),
SessionField.TournamentName("Event Name"),
SessionField.TournamentNumberOfPlayers("Total Players"),
SessionField.TournamentPosition("Finished Place"),
SessionField.TournamentType("Single-Table/Multi-Table")
)
val runGoodCashGames: CSVDescriptor = SessionCSVDescriptor(
DataSource.RUNGOOD,
false,
SessionField.Start("Start Date", dateFormat = "dd/MM/yyyy"),
SessionField.StartTime("Start Time", dateFormat = "HH:mm"),
SessionField.End("End Date", dateFormat = "dd/MM/yyyy"),
SessionField.EndTime("End Time", dateFormat = "HH:mm"),
SessionField.Buyin("Total Buy-In"),
SessionField.CashedOut("Cashed Out"),
SessionField.NetResult("Profit"),
SessionField.Break("Break"),
SessionField.LimitType("Limit Type"),
SessionField.Game("Game"),
SessionField.Bankroll("Bankroll"),
SessionField.TableSize("Table Type"),
SessionField.Location("Location"),
SessionField.LocationType("Location Type"),
SessionField.Comment("Notes"),
SessionField.CurrencyCode("Currency"),
SessionField.Blind("Stakes", callback = { value -> // $10/20
value.drop(1)
val blinds = value.split("/")
if (blinds.size == 2) {
return@Blind Pair(blinds.first().toDouble(), blinds.last().toDouble())
} else {
return@Blind null
}
})
)
}
override fun parseData(realm: Realm, record: CSVRecord): Session? {
val session = Session.newInstance(realm, this.isTournament)
fields.forEach {
var isLive = true
var bankrollName: String? = null
var currencyCode: String? = null
var currencyRate: Double? = null
this.fieldMapping[it]?.let { index ->
fields.forEach { field ->
this.fieldMapping[field]?.let { index ->
val value = record.get(index)
when (it) {
when (field) {
is SessionField.Start -> {
session.startDate = it.parse(value)
session.startDate = field.parse(value)
}
is SessionField.End -> {
session.endDate = it.parse(value)
session.endDate = field.parse(value)
}
is SessionField.Buyin -> session.result?.buyin = it.parse(value)
is SessionField.CashedOut -> session.result?.cashout = it.parse(value)
is SessionField.Tips -> session.result?.tips = it.parse(value)
is SessionField.StartTime -> { session.startDate?.setHourMinutes(value) }
is SessionField.EndTime -> { session.endDate?.setHourMinutes(value) }
is SessionField.Buyin -> session.result?.buyin = field.parse(value)
is SessionField.CashedOut -> session.result?.cashout = field.parse(value)
is SessionField.Tips -> session.result?.tips = field.parse(value)
is SessionField.Break -> {
it.parse(value)?.let {
session.breakDuration = it.toLong()
field.parse(value)?.let {
session.breakDuration = it.toLong() * 60 * 1000
}
}
is SessionField.Game -> session.game = realm.getOrCreate(value)
is SessionField.Location -> session.location = realm.getOrCreate(value)
is SessionField.Bankroll -> session.bankroll = realm.getOrCreate(value)
is SessionField.Bankroll -> bankrollName = value
is SessionField.LimitType -> session.limit = Limit.getInstance(value)?.ordinal
is SessionField.Comment -> session.comment = value
is SessionField.Blind -> { // 1/2
@ -114,13 +262,28 @@ class SessionCSVDescriptor(var isTournament: Boolean, vararg elements: Field) :
Timber.d("Blinds could not be parsed: $value")
}
}
else -> {
is SessionField.SmallBlind -> session.cgSmallBlind = field.parse(value)
is SessionField.BigBlind -> session.cgBigBlind = field.parse(value)
is SessionField.TableSize -> session.tableSize = TableSize.valueForLabel(value)
is SessionField.SessionType -> {
Session.Type.getValueFromString(value)?.let { type ->
session.type = type.ordinal
}
}
is SessionField.TournamentPosition -> session.result?.tournamentFinalPosition = field.parse(value)?.toInt()
is SessionField.TournamentName -> session.tournamentName = realm.getOrCreate(value)
is SessionField.TournamentType -> session.tournamentType = TournamentType.getValueForLabel(value)?.ordinal
is SessionField.TournamentNumberOfPlayers -> session.tournamentNumberOfPlayers = field.parse(value)?.toInt()
is SessionField.CurrencyCode -> currencyCode = value
is SessionField.CurrencyRate -> currencyRate = field.parse(value)
else -> { }
}
}
}
session.bankroll = Bankroll.getOrCreate(realm, bankrollName ?: "Import", isLive, currencyCode, currencyRate)
val startDate = session.startDate
val endDate = session.endDate
val net = session.result?.net

@ -1,8 +1,5 @@
package net.pokeranalytics.android.util.csv
import io.realm.Realm
import net.pokeranalytics.android.model.interfaces.NameManageable
import net.pokeranalytics.android.util.extensions.getOrCreate
import timber.log.Timber
import java.text.DateFormat
import java.text.NumberFormat
@ -12,9 +9,15 @@ import java.util.*
interface NumberField: TypedField<Double> {
val numberFormat: String?
override fun parse(value: String) : Double? {
if (value.isEmpty()) {
return null
}
val formatter = NumberFormat.getInstance()
return try {
@ -27,6 +30,7 @@ interface NumberField: TypedField<Double> {
}
interface DateField : TypedField<Date> {
val dateFormat: String?
override fun parse(value: String) : Date? {
@ -42,9 +46,13 @@ interface DateField : TypedField<Date> {
}
interface BlindField : TypedField<Double> {
interface BlindField : TypedField<Pair<Double, Double>> {
override fun parse(value: String) : Double? {
override fun parse(value: String) : Pair<Double, Double>? {
this.callback?.let { cb ->
return cb(value)
}
return null
}
@ -52,17 +60,9 @@ interface BlindField : TypedField<Double> {
interface TypedField<T> : Field {
fun parse(value: String) : T?
}
interface NamedDataField : Field {
fun <T : NameManageable> getOrCreate(realm: Realm, clazz: Class<T>, name: String) : T {
return realm.getOrCreate(clazz, name)
}
var callback: ((String) -> T?)?
}
interface Field {
val header: String
var callback: (() -> Unit)?
}

@ -160,4 +160,8 @@ fun Date.getNextMinuteInMilliseconds() : Long {
calendar.set(Calendar.SECOND, 0)
calendar.set(Calendar.MILLISECOND, 0)
return calendar.time.time - this.time
}
}
fun Date.setHourMinutes(value: String) {
}

Loading…
Cancel
Save