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
* 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".
*
* 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 {
lowest >= 1f -> CaseCharges.Adequacy.ENOUGH
spec.isLowerBound -> CaseCharges.Adequacy.UNCERTAIN
highest < 1f -> CaseCharges.Adequacy.NOT_ENOUGH
highest <= 1f -> CaseCharges.Adequacy.NOT_ENOUGH
else -> CaseCharges.Adequacy.UNCERTAIN
}
@@ -64,6 +64,15 @@ class CaseChargesTest : BaseTest() {
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
fun `an interval straddling one charge claims nothing`() {
// 4.0 x 0.20 = 0.8, 4.0 x 0.30 = 1.2