# How TeslaMate Charge Cost Tracking Calculates Energy Expenses Per Session

> Learn how TeslaMate charge cost tracking calculates energy expenses per session using geofence pricing and consumption data for per-kWh or per-minute billing.

- Repository: [TeslaMate/teslamate](https://github.com/teslamate-org/teslamate)
- Tags: internals
- Published: 2026-06-23

---

**TeslaMate calculates energy expenses per charging session by executing a SQL UPDATE that combines geofence pricing settings (session fees and per-unit rates) with actual consumption metrics, supporting both per-kWh and per-minute billing models.**

Charge cost tracking in the teslamate-org/teslamate repository turns raw charging telemetry into precise monetary values using a server-side SQL calculation. The Elixir-based application persists pricing rules within geofence configurations and applies them to charging sessions through an efficient batch update mechanism. Understanding how TeslaMate calculates these costs per session requires examining the interplay between schema definitions, pricing configurations, and the SQL logic that unifies them.

## The Three-Component Architecture

### Geofence Pricing Schema

Located in [[`lib/teslamate/locations/geo_fence.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/locations/geo_fence.ex)](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/locations/geo_fence.ex), the `GeoFence` schema stores the monetary parameters that drive expense calculations. Each geofence record contains `session_fee` (a flat charge per session), `cost_per_unit` (the variable rate), and `billing_type` (a string of either `"per_kwh"` or `"per_minute"`).

### Charging Process Schema

The `ChargingProcess` schema in [[`lib/teslamate/log/charging_process.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/log/charging_process.ex)](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/log/charging_process.ex) defines the target field: `cost` (a `:decimal` type). This schema also tracks the input metrics `charge_energy_used`, `charge_energy_added`, and `duration_min`, which serve as the variables for the expense formula.

### SQL Calculation Logic

The actual computation occurs in [[`lib/teslamate/locations.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/locations.ex)](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/locations.ex#L31-L49) within the `calculate_charge_costs/1` function. Instead of iterating through records in Elixir, this function issues a single SQL UPDATE statement that computes costs for all unprocessed sessions belonging to a specific geofence.

## How Energy Expenses Are Computed

The SQL UPDATE employs a `CASE` expression to handle three pricing scenarios:

1. **Unconfigured pricing**: When both `session_fee` and `cost_per_unit` are NULL, the cost remains NULL.

2. **Per-kWh billing**: For `billing_type = 'per_kwh'`, the calculation uses `GREATEST(charge_energy_used, charge_energy_added)` multiplied by the per-unit rate, then adds any flat session fee.

3. **Per-minute billing**: For `billing_type = 'per_minute'`, the system multiplies `duration_min` by the per-unit rate and adds the session fee.

The underlying SQL structure resembles:

```sql
CASE
  WHEN g.session_fee IS NULL AND g.cost_per_unit IS NULL THEN NULL
  WHEN g.billing_type = 'per_kwh' THEN
    COALESCE(g.session_fee, 0) +
    COALESCE(g.cost_per_unit * GREATEST(c.charge_energy_used,
                                        c.charge_energy_added), 0)
  WHEN g.billing_type = 'per_minute' THEN
    COALESCE(g.session_fee, 0) +
    COALESCE(g.cost_per_unit * c.duration_min, 0)
END

```

This approach ensures that energy expenses per session reflect the higher of the two energy readings (useful when the vehicle's internal meter resets mid-charge) and safely handles NULL values via `COALESCE`.

## Practical Implementation Examples

To programmatically trigger the cost calculation for a specific geofence:

```elixir
alias TeslaMate.Locations

# Load a geofence with configured pricing

geofence = Locations.get_geofence!(42)

# Execute the batch cost calculation

:ok = Locations.calculate_charge_costs(geofence)

```

Inspecting the calculated result:

```elixir
alias TeslaMate.Log.ChargingProcess
alias TeslaMate.Repo

process = Repo.get!(ChargingProcess, 101)

# The calculated expense is stored as a Decimal

process.cost

# => #Decimal<12.40>

```

The SQL UPDATE executed atomically processes all pending rows:

```elixir

# Conceptual representation of the Ecto query executed:

"""
UPDATE charging_processes AS cp
SET cost = (
  SELECT CASE
    WHEN g.session_fee IS NULL AND g.cost_per_unit IS NULL THEN NULL
    WHEN g.billing_type = 'per_kwh' THEN
      COALESCE(g.session_fee, 0) +
      COALESCE(g.cost_per_unit * GREATEST(c.charge_energy_used,
                                          c.charge_energy_added), 0)
    WHEN g.billing_type = 'per_minute' THEN
      COALESCE(g.session_fee, 0) +
      COALESCE(g.cost_per_unit * c.duration_min, 0)
  END
  FROM charging_processes c
  JOIN geofences g ON g.id = c.geofence_id
  WHERE c.id = cp.id
)
WHERE cp.geofence_id = $1 AND cp.cost IS NULL;
"""

```

## Summary

- **Geofence configuration** determines pricing via `session_fee`, `cost_per_unit`, and `billing_type` fields in the `GeoFence` schema.
- **Session storage** occurs in the `ChargingProcess` schema, which defines the `cost` column along with energy and duration metrics.
- **Batch calculation** happens in `TeslaMate.Locations.calculate_charge_costs/1`, which runs a SQL UPDATE to populate the `cost` field for all sessions lacking a value.
- **Energy safety** is ensured by using `GREATEST(charge_energy_used, charge_energy_added)` to capture the maximum energy consumed.
- **Mixed pricing** is supported by adding flat fees to variable rates through `COALESCE` wrapping.

## Frequently Asked Questions

### How does TeslaMate handle energy data when the vehicle meter resets during charging?

The SQL calculation uses `GREATEST(charge_energy_used, charge_energy_added)` to compare the two metrics and select the larger value. This ensures that even if the vehicle's internal meter resets mid-session, the calculation accounts for the total energy transferred.

### Can I configure both a flat session fee and a per-kWh rate for the same location?

Yes. When both values are present in the geofence configuration, the `COALESCE(g.session_fee, 0)` expression adds the flat fee to the result of `cost_per_unit` multiplied by the energy or duration. This allows hybrid pricing models common at public charging stations.

### Why do some charging sessions show NULL for cost instead of zero?

If a geofence lacks both a `session_fee` and `cost_per_unit`, or if the session occurred at a location without an associated geofence, the `CASE` statement returns NULL. This indicates that pricing was never configured for that session rather than a free charge. You can manually enter costs through the live view defined in [[`lib/teslamate_web/live/charge_live/cost.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/live/charge_live/cost.ex)](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/live/charge_live/cost.ex).

### Does updating a geofence's pricing retroactively change historical session costs?

No. The `calculate_charge_costs/1` function filters with `WHERE cp.cost IS NULL`, meaning it only calculates expenses for sessions that haven't been priced yet. Existing cost values remain static to preserve historical accuracy. To recalculate old sessions, you would need to NULLify the `cost` column for those specific records and re-trigger the function.