Repository navigation
Fix capacity over-estimate for charge sessions first seen mid-charge - #132
Merged
Merged
Conversation
chargeSessions() paired the SOC swing between the first and last charging samples the app saw with charge_energy_added and charge_miles_added_rated, which the car reports as running totals since its own session began. A session first seen mid-charge (app opened late, link dropped, phone away) therefore over-estimated capacity and full range: plug-in at 20 %, first sample 50 % with 22.5 kWh, last 80 % with 45 kWh read as 150 kWh, not 75. - Energy and rated miles are now the growth of each counter from the first to the last sample of a session. A null first value counts as zero; a counter that goes down inside a session was restarted by the car, so the last value is used. The existing positivity checks stay. - Sessions are cut by charging state. "Charging" and "Starting" samples form a run, a "Stopped"/"NoPower" pause shorter than ten minutes does not end it, and "Complete", "Disconnected", any other state or a longer pause does. Blips no longer inflate the count behind the learning gate. - ChargeSession and chargeSessions() move from app to core/health so the core tests cover them. The file is ChargeSession.kt because detekt's MatchingDeclarationName wants a single class to name its file. - The comment above LEARNING_SESSIONS described the twenty sessions of HIGH_SESSIONS; it now describes the three, the other sentence moves to HIGH_SESSIONS, and the value stays 3. - core gets the kotlin.test test dependency that ADR-0007 asks new core tests to use. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
Contributor
ScreenshotsCaptured from the demo build at 1bd232d. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Closes #114
chargeSessions()paired the SOC swing between the first and last charging samples the app saw withcharge_energy_added/charge_miles_added_rated, which the car reports as running totals since its session began. A session first seen mid-charge (app opened late, link dropped, phone away) therefore over-estimated capacity and full range: plug-in at 20 %, first sample at 50 % with 22.5 kWh, last at 80 % with 45 kWh read as 45 / 30 % = 150 kWh instead of 75 kWh. The rated-miles backbone was inflated the same way.last - first). A null first value counts as 0. A counter that goes down inside a session was restarted by the car (for example a stale total on aStartingsample), so the last value is used. The existing> 0checks stay.chargeSessions()): a session is a run ofCharging/Startingsamples; aStopped/NoPowerpause shorter than 10 minutes (last charging sample to the resuming one) does not end it;Complete,Disconnected, any other state (missing,Unknown,Calibrating) or a longer pause does. Blips no longer inflate the count behind the learning gate.ChargeSessionandchargeSessions()now live incore/.../health/ChargeSession.kt(withMIN_SWING_PERCENT);healthSummary()imports them.BatterySampleis untouched. The file isChargeSession.kt, notChargeSessions.kt, because detekt'sMatchingDeclarationNamerequires a file with one top-level class to be named after it.LEARNING_SESSIONS = 3described the twenty sessions ofHIGH_SESSIONS. It now describes the three; the twenty-session sentence moved toHIGH_SESSIONS. Value unchanged.core/build.gradle.ktsaddstestImplementation(kotlin("test")): ADR-0007 asks new core tests to usekotlin.test, and the new tests do. Existing JUnit tests are unchanged.Judgment calls worth a look:
Stopped/NoPowersamples can start the 10-minute clock. The counters are cumulative, so the delta covers the unobserved stretch.Type
Testing
./gradlew :core:test: run as a core-only scratch build, because Google Maven is unreachable here and the app modules cannot be built.:core:test,:core:ktlintCheckand:core:detektpass (135 tests, 17 of them new inChargeSessionsTest: mid-charge capacity 75 kWh not 150, counter reset,StartingthenCharging, shortStoppedblip, longStoppedgap,Complete,Disconnected, and more).Startingexcluded,>instead of>=at the 10-minute boundary, ...) fails at least one new test../gradlew :app:testDebugUnitTest: not run. Instead the changedHealthSummary.ktand the unchangedHealthSummaryTest.ktwere copied into the scratch core project (scratch only) and compiled, linted and run against the new core code: 6/6 pass. CI still has to compile the app../gradlew :app:lintDebug: not run (see above)Checklist
expected.txtregenerated, not hand-edited (n/a: no protocol change)docs/master-plan.md, and the "Battery health v1" item stays open)SPDX-License-Identifier: AGPL-3.0-only🤖 Generated with Claude Code
https://claude.ai/code/session_0129RzhbEE2cboMcYyZMuxqX
Generated by Claude Code