Random Python Scripts #1: EV Chargers, Memberships, and Existential Pythoning
- ikerdomingoperez
- Aug 15, 2025
- 8 min read
Updated: Sep 2, 2025
Welcome to the first post in a series we're calling Random Python Scripts, where we write code for things that probably don't need code. Why? Because we love Python and have no idea what to do with our valuable time. If you've ever stared at your terminal thinking, "I should automate something pointless", this series is for you.
Today's script? An attempt to make sense of EV charger pricing across brands, speeds, and membership schemes. Because nothing screams "data science" like trying to figure out if paying 0.4 the KWh at EasyGo is worth it. Spoiler: it's not, unless you enjoy the thrill of arriving at a charger that's either broken, mysteriously offline, or just pretending to be available.

The Problem
EV chargers have wildly inconsistent pricing. Some allow overpriced contactless payments, some offer memberships, some have peak and off-peak rates, and some (looking at you, Tesla) offer off-peak discounts if you're willing to wait in a queue that rivals Disneyland's Space Mountain.
So we built a script to:
Load EV charger pricing data
Calculate how much you'd pay for different charge amounts
Determine the "threshold" where a membership becomes financially worth it
Estimate how far you'd drive monthly to justify the membership
Declare a winner for each charge amount (based on price, not reliability)
The Script (a.k.a. Python Therapy)
Let's walk through the script, just to showcase some PEP8 standards, general good practices and a couple of refactoring tricks.
Constants and Setup
We start with some constants: file names, charge amounts (from 50 to 400 kWh), and average km per kWh for city and motorway driving. These are defined like a professional would do it. All caps, clear names, no magic numbers floating around.
EV_CHARGERS_FILE = 'ev_chargers_details.json'
CHARGE_AMOUNTS = list(range(50, 401, 50))
AVG_KM_PER_KWH_CITY = 7.5
AVG_KM_PER_KWH_MOTORWAY = 5.5
Did I just say clear names? no, that's not enough. Names must also be descriptive, as much as possible. You will write the code once, but you will read it a dozen of times (well, not this script probably, but think about code at work). Avoid stuff like DATA = 'foo', or FILE = 'result.txt'. Squeeze your brains just for 2 seconds and find a good name for your variables and files. In python name convention for global vars is caps with underscores, and lowercase for local vars. No camelcase, please.
Loading the Data
We load the JSON file with a clean little function that includes type annotations and UTF-8 encoding.
def load_json(filepath: str) -> Dict[str, Any]:
with open(filepath, 'r', encoding='utf-8') as json_file:
...
Parsing the Data (because no math teacher ever promised you'd actually use this)
This is where the magic happens. For each brand and charger speed, we do:
Calculate the cost of charging various amounts
cost of KWh multiplied by amount of KWh plus membership cost, if any
Determine the threshold where membership pays off
membership cost divided by KWh price discount
Estimate monthly driving in kilometers where membership pays off
Above threshold multipied by average KWh usage in city or motorway driving
We even add a "WINNERS!" rows at the end, which is basically a leaderboard of who are Top 3 cheapest per charge amount. Again, not who's functional. Just cheapest.
Writing the CSV
We wrap it all up by writing to a CSV file. Because nothing says "I did something today" like opening Excel and seeing your script's output. If you are no CSV fan (you should), there are many alternatives, we will explore them other day. Tables, graphs, cli outputs, pictures... not only pandas can do that, there are other python gems around.
Best Practices We Actually Followed
Despite the randomness, we did a few things right:
PEP8-compliant: no rogue indentation or 200-character lines
Type annotations: even if they don't give real value to the code here, they help reading and understanding
Modular functions: each task lives in its own clean little box. Single responsibility principle.
Docstrings: every function explains itself. Ever heard of Documentation as code? this is the first step.
Constants: no hardcoded chaos, no magic numbers in the middle of the script.
Safe defaults: .get() used to avoid key errors, initialize variables.
List comprehensions, lambdas and other python tricks
The script also showcases Python’s expressive power through features like list comprehensions, lambdas and a few python builtins.
List Comprehensions endless benefits
If you're not familiar, this is a compact way to write a loop in a single line. Well, they also have improved performance over a classic loop, and they allow filtering, and can be wrapped with other builtins and... did you know nested list comprehension exist?
Take this example:
reference_price = next(
(i['kwh'] for i in my_list if i['speed'] == speed), 0.0
) if membership_price else 0.0
This one-liner combines three Python features:
A list comprehension to filter and extract values
The next() built-in, which returns the first matching item or a default if none are found
A ternary conditional expression to decide whether to run the logic at all
It’s a replacement for a more verbose block:
# define a default value
reference_price = 0.0
if membership_price:
for item in price_list:
# filter and break the loop on first match
if item['charging_speed'] == charging_speed:
reference_price = item['kwh_price']
breakDictionary comprehension
Python’s dictionary comprehension is a nice way to construct dictionaries from iterables, combining clarity with power. It’s the dictionary counterpart to list comprehension, and just as useful.
Let’s say you want to calculate the total cost of charging different amounts of electricity (in KWh), factoring in a per-unit price and a fixed membership fee.
Here’s the one-liner:
{f'{amount} KWh': round((amount * kwh_price) + membership_price, 1) for amount in CHARGE_AMOUNTS}
Breakdown
Keys: Formatted strings like '10 KWh', '20 KWh', etc.
Values: Total cost, calculated as (amount * kwh_price) + membership_price, rounded to one decimal place.
Loop: CHARGE_AMOUNTS is a list of energy amounts (e.g. [10, 20, 30, 50]).
Output Example:
{
'10 KWh': 4.5,
'20 KWh': 7.5,
'30 KWh': 10.5,
'50 KWh': 14.5
}Benefits of Dictionary Comprehension:
Concise: One line replaces a multi-step loop.
Readable: Clear mapping between keys and values.
Efficient: Faster execution in many cases due to internal optimizations.
Flexible: Supports conditions, transformations, and nesting.
Alternate Version Without Comprehension
If you prefer a more traditional approach or want to make the logic easier to debug, here’s how you’d write it using a for loop:
charging_costs = {}
for amount in CHARGE_AMOUNTS:
key = f'{amount} KWh'
value = round((amount * kwh_price) + membership_price, 1)
charging_costs[key] = value
return charging_costs
This version is more verbose but sometimes easier to follow for beginners or when adding complex logic.
Sorting with Lambdas (and the Legacy Alternative)
The most important Python trick in the script is the use of lambda functions with sorted():
sorted_rows = sorted(rows, key=lambda x: x.get(col, float('inf')))This line sorts a list of dictionaries (rows) by the value of a given column. If a dictionary doesn't contain that key, it falls back to float('inf') (python representation of infinity), ensuring those entries are pushed to the end of the list.
Here’s what’s happening:
lambda x: x.get(column, float('inf')) defines an anonymous function that safely retrieves the column value
sorted(..., key=...) uses that function to determine sort order
float('inf') acts as a default for missing values, keeping the sort stable and predictable
To achieve the same result without lambdas or sorted(), you'd need to write something like this:
def get_sort_value(row, column):
if column in row:
return row[column]
else:
return float('inf')
def get_key_from_tuple(tup):
return tup[0]
# Step 1: Create a list of tuples: (sort_value, row)
rows_with_keys = []
for row in rows:
sort_value = get_sort_value(row, column)
rows_with_keys.append((sort_value, row))
# Step 2: Sort the list of tuples by the sort_value using a named function
rows_with_keys.sort(key=get_key_from_tuple)
# Step 3: Extract the sorted rows using a loop
sorted_rows = []
for pair in rows_with_keys:
sorted_rows.append(pair[1])
Basically, for each row, extract the value we want to use to compare, and create a tuple with it and the row. Then.. compare, sort by that value, and keep the row.
It works, but it’s clearly more verbose and harder to follow. The lambda-based one-liner is not only more elegant and efficient, but also easier to maintain and less error-prone.
Recap of Python Tricks So Far
Trick | Purpose | Example Use Case |
List Comprehension | Build lists from iterables | Filtering even numbers, squaring values |
Dict Comprehension | Build dictionaries from iterables | Mapping KWh amounts to total costs |
Lambda Functions | Create small anonymous functions | Sorting, quick transformations |
Full script
"""
EV Charger Pricing Analysis Script
This script processes random EV charger pricing data to determine:
- Cost per charge for various energy amounts
- Threshold at which a membership becomes cost-effective
- Estimated monthly driving in city and motorway conditions to pass the threshold
Note:
- Contactless options are not considered, as they are always more expensive. Just get the app!
- Prices change a lot by location, time and other variables. Adjust the ev_chargers_details.json accordingly
"""
import json
import csv
from typing import Dict, List, Any
EV_CHARGERS_FILE: str = 'ev_chargers_details.json'
RESULT_FILE: str = 'ev_chargers_breakdown.csv'
CHARGE_AMOUNTS: List[int] = list(range(50, 401, 50)) # 50, 100, ..., 400
AVG_KM_PER_KWH_CITY: float = 7.5
AVG_KM_PER_KWH_MOTORWAY: float = 5.5
COLUMNS: List[str] = (
['brand', 'charging_speed', 'membership_price', 'kwh_price', 'threshold', 'value_city', 'value_motorway'] +
[f'{amount} KWh' for amount in CHARGE_AMOUNTS]
)
def load_json(filepath: str) -> Dict[str, Any]:
"""Load EV charger data from a JSON file."""
with open(filepath, 'r', encoding='utf-8') as json_file:
return json.load(json_file)
def calculate_threshold(membership_price: float, reference_price: float, kwh_price: float) -> int:
"""Calculate the threshold at which membership becomes cost-effective."""
if reference_price <= kwh_price:
return 0
return int(membership_price / (reference_price - kwh_price))
def format_kwh_values(kwh_price: float, membership_price: float) -> Dict[str, float]:
"""Generate cost values for each charge amount."""
return {f'{amount} KWh': round((amount * kwh_price) + membership_price, 1) for amount in CHARGE_AMOUNTS}
def parse_ev_data(ev_data: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Parse EV charger data and compute cost metrics."""
rows: List[Dict[str, Any]] = []
for brand, price_list in ev_data.items():
for entry in price_list:
membership_price = entry.get('membership_price', 0.0)
kwh_price = entry.get('kwh_price', 0.0)
charging_speed = entry.get('charging_speed', '')
reference_price = next(
(item['kwh_price'] for item in price_list if item['charging_speed'] == charging_speed),
0.0
) if membership_price else 0.0
row: Dict[str, Any] = {
'brand': brand,
'charging_speed': charging_speed,
'membership_price': membership_price,
'kwh_price': kwh_price,
**format_kwh_values(kwh_price, membership_price)
}
if reference_price:
threshold_kwh = calculate_threshold(membership_price, reference_price, kwh_price)
row['threshold'] = f'{threshold_kwh} KWh'
row['value_city'] = f'{int(threshold_kwh * AVG_KM_PER_KWH_CITY)} Km'
row['value_motorway'] = f'{int(threshold_kwh * AVG_KM_PER_KWH_MOTORWAY)} Km'
rows.append(row)
winners_rows = calculate_winners(rows)
rows.extend(winners_rows)
return rows
def calculate_winners(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Calculate the top 3 cheapest options for each charge amount."""
winner_rows = []
for rank in range(1, 4):
label = f'WINNER {rank}'
row: Dict[str, Any] = {column: '' for column in COLUMNS}
row['brand'] = label
for amount in CHARGE_AMOUNTS:
column = f'{amount} KWh'
sorted_rows = sorted(rows, key=lambda x: x.get(column, float('inf')))
if len(sorted_rows) >= rank:
winner = sorted_rows[rank - 1]
membership = winner['membership_price']
row[column] = f"{winner['brand']} {winner['membership_price']}" if membership else winner['brand']
winner_rows.append(row)
return winner_rows
def write_csv(filepath: str, rows: List[Dict[str, Any]], headers: List[str]) -> None:
"""Write parsed data to a CSV file."""
with open(filepath, 'w', newline='', encoding='utf-8') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=headers)
writer.writeheader()
writer.writerows(rows)
def main() -> None:
ev_data = load_json(EV_CHARGERS_FILE)
parsed_rows = parse_ev_data(ev_data)
write_csv(RESULT_FILE, parsed_rows, COLUMNS)
if __name__ == '__main__':
main()
EV Chargers data:
Final Thoughts
This script won't fix EV infrastructure. It won't make EasyGo chargers work. It won't shorten the Tesla queue. But it will give you a CSV file that tells you which brand is cheapest, assuming you don't have a fancy Zappy at home already.
Stay tuned for Random Python Scripts #2, where we might simulate how many tupperware lids you need to take out of the tupperware box to match your tupperware, or analyze your spotify algorithm to determine your mental stability. Who knows? It's data-driven random chaos and it's coming soon.



Comments