Pith - kiln
kiln/app/src/main/java/com/vgmlr/kiln/KilnCyclePredictor.kt [2.6 kb]
Modified: 00:02:27 153 026 (19 Aug 026)
5 Days Ago
package com.vgmlr.kiln

import java.time.LocalDate
import java.time.temporal.ChronoUnit
import kotlin.math.roundToInt

object KilnCyclePredictor {
    const val DEFAULT_CYCLE = 28
    const val RUN_GAP = 3L
    private val PLAUSIBLE = 15L..60L

    fun periodStarts(records: List<KilnDayRecord>, buttons: KilnButtonSet): List<LocalDate> {
        val days = records.filter { buttons.isPeriodSighting(it) }
            .map { LocalDate.ofEpochDay(it.epochDay) }
            .sorted()
        val starts = mutableListOf<LocalDate>()
        var prev: LocalDate? = null
        for (d in days) {
            if (prev == null || ChronoUnit.DAYS.between(prev, d) > RUN_GAP) starts += d
            prev = d
        }
        return starts
    }

    fun avgCycleExact(starts: List<LocalDate>): Float {
        val diffs = starts.zipWithNext { a, b -> ChronoUnit.DAYS.between(a, b) }
            .filter { it in PLAUSIBLE }
        if (diffs.isEmpty()) return DEFAULT_CYCLE.toFloat()
        return (DEFAULT_CYCLE + diffs.sum()).toFloat() / (diffs.size + 1)
    }

    fun avgCycleLength(starts: List<LocalDate>): Int = avgCycleExact(starts).roundToInt()

    fun measurableStarts(starts: List<LocalDate>): List<LocalDate> =
        starts.zipWithNext { a, b -> b.takeIf { ChronoUnit.DAYS.between(a, b) in PLAUSIBLE } }
            .filterNotNull()

    fun cycleSeries(starts: List<LocalDate>): List<Pair<LocalDate, Int>> =
        starts.zipWithNext { a, b ->
            val d = ChronoUnit.DAYS.between(a, b)
            if (d in PLAUSIBLE) b to d.toInt() else null
        }.filterNotNull()

    fun periodRuns(
        records: List<KilnDayRecord>,
        buttons: KilnButtonSet
    ): List<Pair<LocalDate, Int>> {
        val days = records.filter { buttons.isPeriodSighting(it) }.map { it.epochDay }.sorted()
        val runs = mutableListOf<Pair<LocalDate, Int>>()
        var start = days.firstOrNull() ?: return runs
        var prev = start
        for (d in days.drop(1)) {
            if (d - prev > RUN_GAP) {
                runs += LocalDate.ofEpochDay(start) to (prev - start + 1).toInt()
                start = d
            }
            prev = d
        }
        runs += LocalDate.ofEpochDay(start) to (prev - start + 1).toInt()
        return runs
    }

    fun periodLengths(records: List<KilnDayRecord>, buttons: KilnButtonSet): List<Int> =
        periodRuns(records, buttons).map { it.second }

    fun avgPeriodLength(records: List<KilnDayRecord>, buttons: KilnButtonSet): Float? {
        val lengths = periodLengths(records, buttons)
        return if (lengths.isEmpty()) null else lengths.average().toFloat()
    }
}
Updates
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026
Grit - Firefox/Chrome 154.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00216
257,107 (+361)