fix(overview): Call a case exactly one charge short "not enough"

The reading is an interval that excludes its upper end, so an upper end
landing exactly on one full charge still leaves every reachable value
below one. AirPods Gen 4 at 10% and Pro 3 at 40% read neutral instead of
orange because of it.

Fixes review finding F1
This commit is contained in:
darken
2026-08-25 22:33:42 +02:00
committed by Matthias Urhahn
parent 43140005f2
commit 70830cf2ca
2 changed files with 12 additions and 1 deletions
@@ -28,6 +28,8 @@ data class CaseCharges(
* makes a claim; anything straddling it is [CaseCharges.Adequacy.UNCERTAIN]. That also absorbs the * makes a claim; anything straddling it is [CaseCharges.Adequacy.UNCERTAIN]. That also absorbs the
* bounce between adjacent readings, so no state has to be remembered between frames. * bounce between adjacent readings, so no state has to be remembered between frames.
* *
* The interval excludes its upper end, so an upper end of exactly one charge is still short of one.
*
* A spec that is itself a lower bound has no upper end, so it can never claim "not enough". * A spec that is itself a lower bound has no upper end, so it can never claim "not enough".
* *
* Null when the model publishes no case figures or the reading is missing or nonsense — the caller * Null when the model publishes no case figures or the reading is missing or nonsense — the caller
@@ -44,7 +46,7 @@ fun caseCharges(spec: PodModel.CaseSpec?, reading: BatteryReading?): CaseCharges
val adequacy = when { val adequacy = when {
lowest >= 1f -> CaseCharges.Adequacy.ENOUGH lowest >= 1f -> CaseCharges.Adequacy.ENOUGH
spec.isLowerBound -> CaseCharges.Adequacy.UNCERTAIN spec.isLowerBound -> CaseCharges.Adequacy.UNCERTAIN
highest < 1f -> CaseCharges.Adequacy.NOT_ENOUGH highest <= 1f -> CaseCharges.Adequacy.NOT_ENOUGH
else -> CaseCharges.Adequacy.UNCERTAIN else -> CaseCharges.Adequacy.UNCERTAIN
} }
@@ -64,6 +64,15 @@ class CaseChargesTest : BaseTest() {
charges(exact, 0.20f, percent)!!.adequacy shouldBe CaseCharges.Adequacy.NOT_ENOUGH charges(exact, 0.20f, percent)!!.adequacy shouldBe CaseCharges.Adequacy.NOT_ENOUGH
} }
@Test
fun `an interval ending exactly on one charge is not enough`() {
// the top of the interval is excluded, so 5.0 x (0.10 + 0.10) = 1.0 is still short
charges(PodModel.CaseSpec(fullPairRecharges = 5.0f), 0.10f, decile)!!
.adequacy shouldBe CaseCharges.Adequacy.NOT_ENOUGH
// 4.0 x (0.24 + 0.01) = 1.0
charges(exact, 0.24f, percent)!!.adequacy shouldBe CaseCharges.Adequacy.NOT_ENOUGH
}
@Test @Test
fun `an interval straddling one charge claims nothing`() { fun `an interval straddling one charge claims nothing`() {
// 4.0 x 0.20 = 0.8, 4.0 x 0.30 = 1.2 // 4.0 x 0.20 = 0.8, 4.0 x 0.30 = 1.2