Skip to content

feat: add gasoline ICE cost comparison from user assumptions - #336

Open
raykellyjr wants to merge 9 commits into
vide:mainfrom
raykellyjr:cursor/ice-cost-comparison-e380
Open

feat: add gasoline ICE cost comparison from user assumptions#336
raykellyjr wants to merge 9 commits into
vide:mainfrom
raykellyjr:cursor/ice-cost-comparison-e380

Conversation

@raykellyjr

Copy link
Copy Markdown

Summary

Adds an optional, neutral "vs gasoline" cost comparison for the Cost Analytics screen. Users configure fuel economy (L/100 km or mpg) and fuel price (per liter or per gallon) in Settings, and the app shows what the same distance would have cost in an equivalent ICE car — no marketing tone, just numbers.

Changes

  • Domain: new `IceAssumptions` + `IceComparisonResult` + `IceComparisonCalculator` under `com.matedroid.domain`. Distance from TeslaMate is never converted; only the ICE assumptions are converted when they don't match the length unit already in use. Handles metric ↔ imperial economy (mpg ↔ L/100 km) and price ($/L ↔ $/gal).
  • Settings: new persisted fields (`iceFuelEconomyValue` + unit, `iceFuelPrice` + unit) in `SettingsDataStore`. Rendered as a "Gasoline comparison" section under the utility rates with FilterChip unit toggles.
  • Cost Analytics: repository loads the assumptions, runs `IceComparisonCalculator.compare(...)` against the effective (recorded + estimated) charging cost and the total distance in range, and returns an `IceComparisonResult`. The screen shows a new "vs gasoline" card (Tesla / gasoline equivalent / difference in currency + percent / distance / gasoline used) only when the user has configured assumptions and there is a positive distance.
  • Localization: strings added to all 6 locales (en, it, es, ca, de, zh) with neutral wording.
  • CHANGELOG: Unreleased "Added" bullet, neutral tone.
  • Tests: `IceComparisonCalculatorTest` covers metric path, imperial path, mixed unit conversion (mpg + $/gal vs distance in km, and vice-versa), missing assumptions, zero / negative distance, null Tesla cost, and negative savings (Tesla more expensive).

Verify

```
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
./gradlew :app:testDebugUnitTest --tests "com.matedroid.domain.Ice*" :app:lintDebug
```

Both pass locally. `assembleDebug` also builds cleanly.

Raymond Kelly added 9 commits July 18, 2026 02:03
Only show cost per 100 distance when every charge in the filtered
trips is priced. Keep known totals visible with clearer coverage
notes, including when charges exist but none are priced.
- TripIntelligenceCalculator surfaces per-trip standouts on top of
  the existing TripsSummaryCalculator: most/least efficient, cheapest
  and priciest per 100 km/mi (Complete cost-coverage only), longest,
  biggest charging-overhead share, plus ensemble energy gap and
  average charging minutes per trip.
- New IntelligenceCard on the Trips screen renders those highlights
  under the summary card; tapping a trip-backed row opens the trip.
- New BatteryHealthSnapshot Room entity + DAO records the max-range
  figure every time the Battery screen loads, debounced to at most
  once per day per car and pruned to 90 rows.
- Room migration 12 -> 13 adds the snapshots table; DatabaseModule
  provides the new DAO to Hilt consumers.
- BatteryScreen renders a max-range trend using OptimizedLineChart
  once at least two snapshots exist, with 5 X labels and a tap
  tooltip per the project chart rules.
- Full set of strings localized across en/it/es/ca/de/zh and the
  changelog entry updated.
- CostAnalyticsCalculator turns a range of ChargeSummary rows + total
  distance into an honest set of cost metrics: known cost, cost/100
  distance (Complete coverage only), avg cost/kWh, energy added, and
  missing/free session counts. Reuses TripCostCoverage semantics.
- CostAnalyticsRepository loads charges + distance from Room using the
  new ChargeSummaryDao.getInRange query, and stitches in the user's
  currency + unit-of-length from SettingsDataStore. All work happens on
  Dispatchers.IO and never touches the API.
- CostAnalyticsScreen (Material3, car palette) shows range chips
  (7/30/90/all-time), a headline card with the cost, energy and distance
  figures, and a coverage card that explains partial or missing pricing.
  Reachable from the dashboard via a new "Costs" nav button.
- CostSummaryWidget is a read-only Glance widget that mirrors the same
  numbers on the home screen with 7/30/90-day ranges, configurable per
  car. All formatting happens in the worker; the widget itself only
  renders preformatted strings and never talks to TeslaMate.
- CostSummaryWidgetUpdateWorker owns DB access + formatting; scheduled
  by the receiver on install (periodic 6h) and by DataSyncWorker after
  each successful sync so widgets refresh with the freshest cache.
- SettingsDataStore caches teslamate_units.unit_of_length under a new
  key; DashboardViewModel and SettingsViewModel update the cache when
  they fetch global settings so widgets can format distances offline.
- MainActivity honours EXTRA_SKIP_CAR_WIDGET_UPDATE so tapping the cost
  widget does not schedule an unrelated car-status refresh.
- New DAO queries for cost coverage: countWithCostInRange,
  countMissingCostInRange, countZeroCostInRange, getInRange.
- Unit tests cover the calculator's complete/partial/missing/zero-cost
  cases, the widget formatter's coverage semantics, and the widget's
  date-range helper.
Document the read-only widget stack and Room v13 battery history
cache so contributors know where cost/trip intelligence lives.
Use cached TeslaMate unit-of-length as a Cost Analytics fallback,
refresh units from global settings in the cost widget worker, and
update the cost widget after summary sync even when detail sync retries.
Cache unit-of-length during summary sync and stop live-fetching
TeslaMate units in the cost widget/worker so Room values and labels
cannot drift after a unit change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants