Skip to content

feat: classify offline charges using newly geofence charge_type hint#5330

Draft
finikwashere wants to merge 3 commits into
teslamate-org:mainfrom
finikwashere:fix/offline-charge-type-classification
Draft

feat: classify offline charges using newly geofence charge_type hint#5330
finikwashere wants to merge 3 commits into
teslamate-org:mainfrom
finikwashere:fix/offline-charge-type-classification

Conversation

@finikwashere
Copy link
Copy Markdown

Problem

Closes #4401

When a car charges in a location with no internet (underground garage, gym, etc.), TeslaMate synthesises the charging session from the last known state before going offline and the first state after reconnecting. All charges rows for that session have NULL charger_phases and fast_charger_present, so every Grafana dashboard query that uses:

CASE WHEN NULLIF(mode() within group (order by charger_phases),0) IS NULL
  THEN 'DC' ELSE 'AC' END

incorrectly labels the session as DC — even for a plain home AC wallbox.

Affected dashboards: Charges, Charging Stats, Battery Health, Trip (9 SQL queries total).

Solution

Introduce an optional Default charge type hint on Geofences (AC / DC / auto-detect). The Grafana SQL falls back to it only when it cannot determine the type from live telemetry.

Priority order in the new CASE expression

  1. charger_phases IS NOT NULLAC (normal online session)
  2. fast_charger_present = trueDC (online DC session without phase data)
  3. geofence.charge_type IS NOT NULLuse configured hint (offline sessions at known locations)
  4. Fallback → DC

Changes

File What changed
priv/repo/migrations/20260510111557_add_charge_type_to_geofences.exs Add nullable charge_type varchar column to geofences
lib/teslamate/locations/geo_fence.ex Add charge_type Ecto.Enum field and include in changeset
lib/teslamate_web/live/geofence_live/form.html.heex Add "Default charge type" dropdown to geofence edit form
grafana/dashboards/charges.json Fix charge_type CASE expression
grafana/dashboards/charging-stats.json Fix 4 CASE expressions + add geofences JOIN
grafana/dashboards/battery-health.json Fix CASE expression + add geofences JOIN
grafana/dashboards/trip.json Fix 3 CASE expression variants + add geofences JOIN

Why this fixes existing data retroactively

The fix is applied at query time in Grafana. No UPDATE to historical rows is needed — setting charge_type = 'AC' on a geofence immediately corrects all past charging sessions at that location.

Testing

Tested on a live TeslaMate instance with an underground home garage (no cell reception during charging). Before the fix every Home charge showed as DC. After setting charge_type = 'AC' on the Home geofence, all historical home charges immediately show as AC while Supercharger and public DC fast-charger sessions remain correctly classified as DC.

When a car charges in a location with no internet (underground garage,
gym, etc.), TeslaMate synthesises the charging session from the last
known state before going offline and the first state after reconnecting.
All charge rows for that session have NULL charger_phases and
fast_charger_present, so every Grafana dashboard query that uses

  CASE WHEN NULLIF(mode() within group (order by charger_phases),0) IS NULL
    THEN 'DC' ELSE 'AC' END

incorrectly labels the session as DC.

This commit introduces a fallback mechanism:

1. DB migration: add a nullable charge_type column (AC | DC) to the
   geofences table.

2. GeoFence schema/changeset: expose the new field so TeslaMate can
   read and write it.

3. Geofence UI: add a 'Default charge type' dropdown (Auto-detect /
   AC / DC) to the geofence edit form.

4. Grafana dashboards (charges, charging-stats, battery-health, trip):
   replace the two-way CASE with a three-tier priority:
     a. charger_phases IS NOT NULL  → AC  (normal online session)
     b. fast_charger_present = true → DC  (online DC session w/o phases)
     c. geofence.charge_type IS NOT NULL → use the configured hint
     d. fallback → DC

   The fix applies at query time, so all existing historical data is
   corrected retroactively without any SQL UPDATE.

Fixes teslamate-org#4401
@netlify
Copy link
Copy Markdown

netlify Bot commented May 10, 2026

Deploy Preview for teslamate ready!

Name Link
🔨 Latest commit a979fff
🔍 Latest deploy log https://app.netlify.com/projects/teslamate/deploys/6a0303e48966b40008d58137
😎 Deploy Preview https://deploy-preview-5330--teslamate.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 10, 2026

CLA assistant check
All committers have signed the CLA.

@finikwashere finikwashere marked this pull request as ready for review May 10, 2026 09:42
@JakobLichterfeld JakobLichterfeld changed the title fix: classify offline charges using geofence charge_type hint feat: classify offline charges using newly geofence charge_type hint May 10, 2026
@JakobLichterfeld
Copy link
Copy Markdown
Member

Thanks for your contribution.

immediately corrects all past charging sessions at that location.

This is not the best practice for changing historical charging types. It is okay to change them if they were offline, but otherwise, the correct data may become corrupted.

@finikwashere
Copy link
Copy Markdown
Author

This is not the best practice for changing historical charging types. It is okay to change them if they were offline, but otherwise, the correct data may become corrupted.

Should it be optional, triggered by the user after upgrade?

@brianmay
Copy link
Copy Markdown
Collaborator

immediately corrects all past charging sessions at that location.

Not sure, there might be some confusion here in how this was documented.

The wording implies that we change (correct) the database at query time. "correct" would mean we write new values to the database.

That would be bad, queries have to remain read only. My grafana only has read only access to the database.

But my reading of the queries seems to indicate that they are read only and do not change any data.

Or am I totally confused here?

@brianmay
Copy link
Copy Markdown
Collaborator

I strongly suspect the tests will need fixing...

- Fix geofence_live_test.exs: two pattern-matches on .field.is-horizontal
  now account for the new charge_type row (5 → 6 fields)
- Add missing gettext strings to default.pot and all 17 active locale
  po files (ca, da, de, es, fi, fr, it, ja, ko, nb, nl, sv, th, tr, uk,
  zh_Hans, zh_Hant) for: Default charge type, Auto-detect, AC, DC, and
  the offline charging fallback help text
@finikwashere
Copy link
Copy Markdown
Author

Apologies for the confusion.
The tests are updated.
I've also updated the translations.

On "corrects all past charging sessions"** (@brianmay): Grafana queries are read-only, and this is what the SQL here also does. The wording in the PR description was misleading. What I meant is:

Setting charge_type = 'AC' on a geofence causes all future queries to return 'AC' for historical sessions at that location — no data is written or modified. The SQL purely reads geofences.charge_type as an additional input to the CASE expression at query time.

The new CASE expression uses the geofence hint only as step 3 of 4, after the live telemetry signals have already been checked:

CASE
  WHEN NULLIF(mode() within group (order by charger_phases), 0) IS NOT NULL THEN 'AC'  -- online AC session
  WHEN bool_or(fast_charger_present) = true THEN 'DC'                                  -- online DC session
  WHEN MAX(g.charge_type::text) IS NOT NULL THEN MAX(g.charge_type::text)              -- offline fallback
  ELSE 'DC'
END

So for any session that was online during charging (i.e. charger_phases or fast_charger_present has real data), the geofence hint is never reached. It only kicks in for sessions where the car had no connectivity and both signals are NULL — which is exactly the scenario described in #4401.

Our 26-line heex insertion (Default charge type section) shifted all
existing gettext calls below line 118 in form.html.heex by 26 lines.
Update the stale #: references in default.pot:
  :124 (Back)              → :150
  :128 (Save)              → :154
  :129 (Saving...)         → :155
  :155 (Charging Costs)    → :181
  :163 (ngettext sessions) → :189
  :174 (Continue)          → :200
  :177 (Add costs ...)     → :203

Fixes mix gettext.extract --check-up-to-date CI failure.
@JakobLichterfeld JakobLichterfeld added the note:discussion Details or approval are up for discussion label May 18, 2026
@JakobLichterfeld JakobLichterfeld marked this pull request as draft May 18, 2026 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

note:discussion Details or approval are up for discussion

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Offline Charging sessions - Missing Added Energy + Default Charge Type

4 participants