package utils import it.danieleverducci.lunatracker.entities.LunaEvent import java.util.Calendar /** * Data classes for statistics results */ data class DailySummary( val date: Long, val totalBottleMl: Int, val bottleCount: Int, val totalBreastfeedingMin: Int, val breastfeedingCount: Int, val breastfeedingLeftCount: Int, val breastfeedingRightCount: Int, val totalSleepMin: Int, val sleepCount: Int, val diaperPooCount: Int, val diaperPeeCount: Int, val totalFoodCount: Int, val latestWeight: Int?, val latestTemperature: Int? ) data class FeedingStats( val dailyBottleTotals: Map, val dailyBreastfeedingTotals: Map, val avgBottleMlPerDay: Float, val avgBreastfeedingMinPerDay: Float, val leftBreastCount: Int, val rightBreastCount: Int, val bothBreastCount: Int, val avgBreastfeedingDuration: Float, val avgFeedingIntervalMinutes: Long ) data class DiaperStats( val dailyPooCount: Map, val dailyPeeCount: Map, val avgDiapersPerDay: Float, val avgPooPerDay: Float, val avgPeePerDay: Float, val lastPooTime: Long? ) data class SleepStats( val dailyTotals: Map, val avgSleepMinPerDay: Float, val avgNapsPerDay: Float, val avgNapDurationMin: Float, val longestSleepMin: Int, val lastSleepTime: Long? ) data class WeightPoint( val time: Long, val weightGrams: Int ) data class TemperaturePoint( val time: Long, val temperatureDeciCelsius: Int ) /** * Calculator for statistics based on LunaEvent data */ class StatisticsCalculator(private val events: List) { private fun getStartOfDay(unixTimeSeconds: Long): Long { val cal = Calendar.getInstance() cal.timeInMillis = unixTimeSeconds * 1000 cal.set(Calendar.HOUR_OF_DAY, 0) cal.set(Calendar.MINUTE, 0) cal.set(Calendar.SECOND, 0) cal.set(Calendar.MILLISECOND, 0) return cal.timeInMillis / 1000 } private fun getEventsInRange(startUnix: Long, endUnix: Long): List { return events.filter { it.time >= startUnix && it.time < endUnix } } private fun getEventsForDays(days: Int): List { val now = System.currentTimeMillis() / 1000 val startOfToday = getStartOfDay(now) val startTime = startOfToday - (days - 1) * 24 * 60 * 60 return events.filter { it.time >= startTime } } /** * Get summary for a specific day (unix timestamp in seconds) */ fun getDailySummary(dayUnix: Long): DailySummary { val startOfDay = getStartOfDay(dayUnix) val endOfDay = startOfDay + 24 * 60 * 60 val dayEvents = getEventsInRange(startOfDay, endOfDay) val bottleEvents = dayEvents.filter { it.type == LunaEvent.TYPE_BABY_BOTTLE } val breastfeedingEvents = dayEvents.filter { it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE || it.type == LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE || it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE } val sleepEvents = dayEvents.filter { it.type == LunaEvent.TYPE_SLEEP } val pooEvents = dayEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_POO } val peeEvents = dayEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_PEE } val foodEvents = dayEvents.filter { it.type == LunaEvent.TYPE_FOOD } val weightEvents = dayEvents.filter { it.type == LunaEvent.TYPE_WEIGHT } val tempEvents = dayEvents.filter { it.type == LunaEvent.TYPE_TEMPERATURE } return DailySummary( date = startOfDay, totalBottleMl = bottleEvents.sumOf { it.quantity }, bottleCount = bottleEvents.size, totalBreastfeedingMin = breastfeedingEvents.sumOf { it.quantity }, breastfeedingCount = breastfeedingEvents.size, breastfeedingLeftCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE }, breastfeedingRightCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE }, totalSleepMin = sleepEvents.sumOf { it.quantity }, sleepCount = sleepEvents.size, diaperPooCount = pooEvents.size, diaperPeeCount = peeEvents.size, totalFoodCount = foodEvents.size, latestWeight = weightEvents.maxByOrNull { it.time }?.quantity, latestTemperature = tempEvents.maxByOrNull { it.time }?.quantity ) } /** * Get today's summary */ fun getTodaySummary(): DailySummary { return getDailySummary(System.currentTimeMillis() / 1000) } /** * Get feeding statistics for the last N days */ fun getFeedingStats(days: Int): FeedingStats { val relevantEvents = getEventsForDays(days) val now = System.currentTimeMillis() / 1000 val startOfToday = getStartOfDay(now) // Daily totals val dailyBottleTotals = mutableMapOf() val dailyBreastfeedingTotals = mutableMapOf() for (i in 0 until days) { val dayStart = startOfToday - i * 24 * 60 * 60 dailyBottleTotals[dayStart] = 0 dailyBreastfeedingTotals[dayStart] = 0 } val bottleEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_BABY_BOTTLE } val breastfeedingEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE || it.type == LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE || it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE } bottleEvents.forEach { event -> val dayStart = getStartOfDay(event.time) dailyBottleTotals[dayStart] = (dailyBottleTotals[dayStart] ?: 0) + event.quantity } breastfeedingEvents.forEach { event -> val dayStart = getStartOfDay(event.time) dailyBreastfeedingTotals[dayStart] = (dailyBreastfeedingTotals[dayStart] ?: 0) + event.quantity } // Breastfeeding side distribution val leftCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE } val rightCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE } val bothCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE } // Average breastfeeding duration val avgBreastfeedingDuration = if (breastfeedingEvents.isNotEmpty()) { breastfeedingEvents.sumOf { it.quantity }.toFloat() / breastfeedingEvents.size } else 0f // Average feeding interval (all feeding events sorted by time) val allFeedingEvents = (bottleEvents + breastfeedingEvents).sortedBy { it.time } val avgFeedingIntervalMinutes = if (allFeedingEvents.size > 1) { var totalInterval = 0L for (i in 1 until allFeedingEvents.size) { totalInterval += allFeedingEvents[i].time - allFeedingEvents[i-1].time } (totalInterval / (allFeedingEvents.size - 1)) / 60 } else 0L return FeedingStats( dailyBottleTotals = dailyBottleTotals, dailyBreastfeedingTotals = dailyBreastfeedingTotals, avgBottleMlPerDay = if (days > 0) dailyBottleTotals.values.sum().toFloat() / days else 0f, avgBreastfeedingMinPerDay = if (days > 0) dailyBreastfeedingTotals.values.sum().toFloat() / days else 0f, leftBreastCount = leftCount, rightBreastCount = rightCount, bothBreastCount = bothCount, avgBreastfeedingDuration = avgBreastfeedingDuration, avgFeedingIntervalMinutes = avgFeedingIntervalMinutes ) } /** * Get diaper statistics for the last N days */ fun getDiaperStats(days: Int): DiaperStats { val relevantEvents = getEventsForDays(days) val now = System.currentTimeMillis() / 1000 val startOfToday = getStartOfDay(now) val dailyPooCount = mutableMapOf() val dailyPeeCount = mutableMapOf() for (i in 0 until days) { val dayStart = startOfToday - i * 24 * 60 * 60 dailyPooCount[dayStart] = 0 dailyPeeCount[dayStart] = 0 } val pooEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_POO } val peeEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_PEE } pooEvents.forEach { event -> val dayStart = getStartOfDay(event.time) dailyPooCount[dayStart] = (dailyPooCount[dayStart] ?: 0) + 1 } peeEvents.forEach { event -> val dayStart = getStartOfDay(event.time) dailyPeeCount[dayStart] = (dailyPeeCount[dayStart] ?: 0) + 1 } val totalDiapers = pooEvents.size + peeEvents.size return DiaperStats( dailyPooCount = dailyPooCount, dailyPeeCount = dailyPeeCount, avgDiapersPerDay = if (days > 0) totalDiapers.toFloat() / days else 0f, avgPooPerDay = if (days > 0) pooEvents.size.toFloat() / days else 0f, avgPeePerDay = if (days > 0) peeEvents.size.toFloat() / days else 0f, lastPooTime = pooEvents.maxByOrNull { it.time }?.time ) } /** * Get sleep statistics for the last N days */ fun getSleepStats(days: Int): SleepStats { val relevantEvents = getEventsForDays(days) val now = System.currentTimeMillis() / 1000 val startOfToday = getStartOfDay(now) val dailyTotals = mutableMapOf() for (i in 0 until days) { val dayStart = startOfToday - i * 24 * 60 * 60 dailyTotals[dayStart] = 0 } val sleepEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_SLEEP } sleepEvents.forEach { event -> val dayStart = getStartOfDay(event.time) dailyTotals[dayStart] = (dailyTotals[dayStart] ?: 0) + event.quantity } val totalSleepMin = sleepEvents.sumOf { it.quantity } val avgNapDuration = if (sleepEvents.isNotEmpty()) { totalSleepMin.toFloat() / sleepEvents.size } else 0f val longestSleep = sleepEvents.maxOfOrNull { it.quantity } ?: 0 return SleepStats( dailyTotals = dailyTotals, avgSleepMinPerDay = if (days > 0) totalSleepMin.toFloat() / days else 0f, avgNapsPerDay = if (days > 0) sleepEvents.size.toFloat() / days else 0f, avgNapDurationMin = avgNapDuration, longestSleepMin = longestSleep, lastSleepTime = sleepEvents.maxByOrNull { it.time }?.time ) } /** * Get weight history (all weight measurements) */ fun getWeightHistory(): List { return events .filter { it.type == LunaEvent.TYPE_WEIGHT && it.quantity > 0 } .sortedBy { it.time } .map { WeightPoint(it.time, it.quantity) } } /** * Get temperature history */ fun getTemperatureHistory(): List { return events .filter { it.type == LunaEvent.TYPE_TEMPERATURE && it.quantity > 0 } .sortedBy { it.time } .map { TemperaturePoint(it.time, it.quantity) } } /** * Calculate weight gain over the last N days */ fun getWeightGainForDays(days: Int): Int? { val weights = getWeightHistory() if (weights.size < 2) return null val now = System.currentTimeMillis() / 1000 val startTime = now - days * 24 * 60 * 60 val recentWeight = weights.lastOrNull() ?: return null val olderWeight = weights.filter { it.time <= startTime }.lastOrNull() ?: weights.firstOrNull() ?: return null if (recentWeight.time == olderWeight.time) return null return recentWeight.weightGrams - olderWeight.weightGrams } /** * Get average daily values for a type of event over N days */ fun getAverageDailyCount(type: String, days: Int): Float { val relevantEvents = getEventsForDays(days).filter { it.type == type } return if (days > 0) relevantEvents.size.toFloat() / days else 0f } /** * Get average daily quantity sum for a type of event over N days */ fun getAverageDailyQuantity(type: String, days: Int): Float { val relevantEvents = getEventsForDays(days).filter { it.type == type } val total = relevantEvents.sumOf { it.quantity } return if (days > 0) total.toFloat() / days else 0f } }