top of page
Search

Random Python Scripts #1: EV Chargers, Memberships, and Existential Pythoning

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.


ev charger price rank
EV Chargers price ranking... with python!

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']
            break

Dictionary 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


bottom of page