Skip to content

Health/range: estimators merged ahead of use never run in the app, and "full range" has three inconsistent definitions #122

Closed

Description

@dzid26

Code that never runs in the app

  • FullRangeScaleEstimator (core/history/FullRangeScale.kt)`` has no caller in app/.
  • TeslaCommands.Charge.chargingMph has no caller. charge_rate_mph(_float) is parsed but never stored in BatterySample.
  • chargeProjection(samples, chargeRateMph, fullRangeMiles) is always called with the default arguments (CarScreen.kt#L531), so its miles-first branch never runs.
  • healthSummary(samples) is always called without epaRatedRangeMiles or newCapacityKwh (CarScreen.kt#L110), and nothing in the UI can set them. As a result RatedRangeEstimator and the SoH branch of EnergyDeltaEstimator never execute, and HealthFusion.fuse is only ever called with (null, null). The health card still promises "A state-of-health percentage appears once the car's factory range is set" (BatteryHealthCard.kt#L100-L104).``

Each of these has its own tests (FullRangeScaleTest, RatedRangeEstimatorTest, HealthFusionTest, parts of BatteryChartTest and HealthSummaryTest). Coverage therefore looks healthy, but those tests don't exercise anything the app actually does. The master plan calls the range backbone "Core landed", which is true of the code but misleading about the product.

"Full range" is computed three different ways

The same data can give three different answers:

  • FullRangeScale: intersection of brackets derived from integer-SOC rounding;
  • ChargeSession.fullRangeMiles in the app's health code: rated miles added ÷ SOC swing;
  • the single-sample fallback in healthSummary: ratedRange / (level / 100).

Robustness once it is wired

FullRangeScaleEstimator.estimate intersects brackets across all samples, with no window. Any real drift — degradation, a firmware rescale, temperature effects, all raised in docs/research/range-as-soc-signal.md — or a single outlier makes upper <= lower. From then on the estimator returns null permanently, and rejectsContradictorySamples tests for exactly that. The UI shouldn't depend on it until it uses a window (e.g. the last N sessions) or an outlier-tolerant fit.

Smaller leftovers in the same area

Proposal

Activity

  1. dzid26 commented on Oct 21, 2026

    @dzid26
    CollaboratorAuthor

    Checked against main (8cbaab0) on 2026-10-21.

    Done

    • FullRangeScaleEstimator now has callers in CarScreen.kt, DriveHistoryCard.kt, TripNotification.kt and HealthSummary.kt.
    • chargeProjection is called with fullRangeMiles = scale?.fullRangeMiles, so its miles-first branch runs.

    Still open

    • healthSummary is still called without epaRatedRangeMiles or newCapacityKwh (CarScreen.kt:164), so the rated-range and SoH branches and HealthFusion with real inputs still never run.
    • TeslaCommands.Charge.chargingMph / charge_rate_mph has no app caller.
    • BatterySample.percent is still a pure alias of batteryLevel (BatteryHistory.kt:43).
    • Not verified: whether the three "full range" definitions and the unwindowed estimator are now consistent.

    Keeping this open for the remaining items.


    Generated by Claude Code

  2. dzid26 commented on Oct 21, 2026

    @dzid26
    CollaboratorAuthor

    Update: BatterySample.percent (the pure alias of batteryLevel) is removed, with callers moved to batteryLevel (#322, merged as 239bb90).


    Generated by Claude Code

  3. dzid26 commented on Oct 21, 2026

    @dzid26
    CollaboratorAuthor

    #333 is merged. It closes the "full range has three definitions" and "estimator never wired" points:

    • FullRangeScale stores one slope (milesPerPercent) and an offset. The range at a level comes from ratedMilesAt, and the range at 100% from milesAtFull. The Health card, the trip estimate, the past-trips card, the chart cursor and the charge projection all read those.
    • FullRangeScales fits each car once and shares the result with every card, the health summary and the trip notification.
    • The window and outlier rejection from earlier already cover the "returns null permanently" concern.

    Still open in this issue: epaRatedRangeMiles and newCapacityKwh are never passed to healthSummary, so the state-of-health branch of EnergyDeltaEstimator and HealthFusion never run in the app, and the health card text promises a percentage it can't show. charge_rate_mph is parsed but never stored, so the miles-first branch of chargeProjection is only reached from tests. Next step is a decision on what to keep, listed in the project thread.


    Generated by Claude Code

  4. dzid26 commented on Oct 23, 2026

    @dzid26
    CollaboratorAuthor

    Closing as done, checked against main at 9162ac2:

    • FullRangeScaleEstimator runs in the app (FullRangeScales), and its fit is a weighted line over level steps, so one outlier no longer makes it return null for good.
    • chargeRateMph is stored in BatterySample and drives the charge projection.
    • healthSummary now gets the rated range from the new Edit car dialog (feat(app): edit car dialog with VIN, new-car range and capacity #354), so SoH shows. The health footer's promise only appears while it is missing.
    • One definition of full range: the fitted scale. The single-sample fallback and the session-based figure are gone.
    • HealthFusion, the energy-delta SoH and the typed capacity were removed: SoH is the fitted range over the EPA rated range.
    • The BatterySample.percent alias and the stale vehicleId KDoc are gone.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions