How TeslaMate Charge Cost Tracking Calculates Energy Expenses Per Session
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), 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) 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#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:
-
Unconfigured pricing: When both
session_feeandcost_per_unitare NULL, the cost remains NULL. -
Per-kWh billing: For
billing_type = 'per_kwh', the calculation usesGREATEST(charge_energy_used, charge_energy_added)multiplied by the per-unit rate, then adds any flat session fee. -
Per-minute billing: For
billing_type = 'per_minute', the system multipliesduration_minby the per-unit rate and adds the session fee.
The underlying SQL structure resembles:
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:
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:
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:
# 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, andbilling_typefields in theGeoFenceschema. - Session storage occurs in the
ChargingProcessschema, which defines thecostcolumn along with energy and duration metrics. - Batch calculation happens in
TeslaMate.Locations.calculate_charge_costs/1, which runs a SQL UPDATE to populate thecostfield 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
COALESCEwrapping.
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).
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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →