From fcc2b6330f3fc970201002311c29e12f9d4252a3 Mon Sep 17 00:00:00 2001 From: Maximilian Stiefel Date: Tue, 28 Dec 2021 20:47:15 +0100 Subject: [PATCH] Refactoring --- .gitignore | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ecar.json | 14 ++--- ecar.py | 133 +++++++++++++++++++++++++++++++++-------------- 3 files changed, 251 insertions(+), 44 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0859918 --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# End of https://www.toptal.com/developers/gitignore/api/python + +*.swp + diff --git a/ecar.json b/ecar.json index bdc6d3d..aa234d7 100644 --- a/ecar.json +++ b/ecar.json @@ -3,26 +3,26 @@ "kwh_price_commercial": 0.5, "petrol_litre_price": 1.75, "kilometers_per_year": 20000.0, - "years": 3, + "years": 6, "ecar": { - "label": "Nissan Leaf", + "label": "Nissan Leaf 2013", "price": 7600, "taxes": 0, "insurance": 354, "kwh_per_kilometer": 0.16, "maintenance": 200, "charging_behaviour": { - "percent_free_charges": 80, - "percent_home_charges": 10, - "percent_commercial_charges": 10 + "percent_free_charges": 90, + "percent_home_charges": 5, + "percent_commercial_charges": 5 } }, "ccar": { - "label": "Suzuki Grand Vitara", + "label": "Suzuki Grand Vitara 2008", "price": 7000, "taxes": 350, "insurance": 362, - "litre_per_kilometer": 0.08, + "litres_per_kilometer": 0.08, "maintenance": 1000 } } diff --git a/ecar.py b/ecar.py index e557a50..06f46cd 100644 --- a/ecar.py +++ b/ecar.py @@ -1,44 +1,103 @@ import matplotlib.pyplot as plt import numpy as np import json +import sys +import argparse +__author__ = 'm3x1m0m' -with open("ecar.json", "r") as rf: - settings = json.load(rf) +class c_settings_extractor: + def __init__(self, fname): + with open(fname, "r") as rf: + settings = json.load(rf) + kilometer_price_ccar = settings["ccar"]["litres_per_kilometer"] * settings["petrol_litre_price"] + self.labels = [settings["ecar"]["label"], settings["ccar"]["label"]] + self.purchase = np.array([settings["ecar"]["price"], settings["ccar"]["price"]]) + self.taxes = np.array([settings["ecar"]["taxes"], settings["ccar"]["taxes"]]) + self.insurance = np.array([settings["ecar"]["insurance"], settings["ccar"]["insurance"]]) + kilometer_price_ecar = ( settings["ecar"]["charging_behaviour"]["percent_home_charges"] * settings["kwh_price_home"] + + settings["ecar"]["charging_behaviour"]["percent_commercial_charges"] * settings["kwh_price_commercial"]) / 100.0 + self.driving = np.array([kilometer_price_ecar * settings["kilometers_per_year"], kilometer_price_ccar * settings["kilometers_per_year"]]) + self.maintenance = np.array([settings["ecar"]["maintenance"], settings["ecar"]["maintenance"]]) + def get_labels(self): + return self.labels + def get_purchase(self): + return self.purchase + def get_taxes(self): + return self.taxes + def get_insurance(self): + return self.insurance + def get_driving(self): + return self.driving + def get_maintenance(self): + return self.maintenance + +class c_ecar_comparator: + def __init__(self, fname): + self.settings_extractor = c_settings_extractor(fname) + def calculate_costs_a_year(self): + taxes = self.settings_extractor.get_taxes() + insurance = self.settings_extractor.get_insurance() + driving = self.settings_extractor.get_driving() + maintenance = self.settings_extractor.get_maintenance() + return taxes + insurance + driving + maintenance + def calculate_costs(self, years, months): + months_a_year = 12.0 + costs_a_year = self.calculate_costs_a_year() + return costs_a_year * (years + months/months_a_year) + def calculate_break_even(self): + total_costs = self.settings_extractor.get_purchase() + months_a_year = 12.0 + increment = self.calculate_costs_a_year() / months_a_year + months = 0 + while total_costs[0] > total_costs[1]: + total_costs += increment + months += 1 + return [months/months_a_year, months%months_a_year] # years, months -kilometer_price = np.array( [settings["ecar"]["kwh_per_kilometer"] * settings["kwh_price_home"], - settings["ccar"]["litre_per_kilometer"] * settings["petrol_litre_price"]]) -labels = [settings["ecar"]["label"], settings["ccar"]["label"]] -price = np.array([settings["ecar"]["price"], settings["ccar"]["price"]]) -taxes = np.array([settings["ecar"]["taxes"], settings["ccar"]["taxes"]]) -insurance = np.array([settings["ecar"]["insurance"], settings["ccar"]["insurance"]]) -kilometer_price_ecar = (settings["ecar"]["charging_behaviour"]["percent_home_charges"] * settings["kwh_price_home"] - + settings["ecar"]["charging_behaviour"]["percent_commercial_charges"] * settings["kwh_price_commercial"]) / 100.0 -driving = np.array([kilometer_price_ecar * settings["kilometers_per_year"], kilometer_price[1] * settings["kilometers_per_year"]]) -maintenance = np.array([settings["ecar"]["maintenance"], settings["ecar"]["maintenance"]]) -width = 0.3 +def main(): + parser = argparse.ArgumentParser(description='This script allows to calculate if an electric car makes sense financially for you') + parser.add_argument('-a','--settings', help='Settings file', required=True, metavar=('FILENAME')) + parser.add_argument('-b','--break_even', help='Calculate the break even point. (When does the EV become cheaper)', action='store_true') + parser.add_argument('-c','--savings_per_year', help='Calculate savings per year', action='store_true') + parser.add_argument('-d','--savings_per_month', help='Calculate savings per month', action='store_true') + parser.add_argument('-e','--plot', help='Visualize costs over one or multiple years', type=int, metavar=('YEARS')) + args = parser.parse_args() + if not args.break_even and not args.savings_per_year and not args.savings_per_month and not args.plot: + sys.exit("Please choose one or multiple options") + comparator = c_ecar_comparator(args.settings) + extractor = c_settings_extractor(args.settings) + if args.plot: + width = 0.3 + labels = extractor.get_labels() + purchase = extractor.get_purchase() + taxes = extractor.get_taxes() + insurance = extractor.get_insurance() + driving = extractor.get_driving() + maintenance = extractor.get_maintenance() + fig, ax = plt.subplots() + ax.bar(labels, purchase, width, label = "Price", color = "gray") + current_y = extractor.get_purchase() + y = 0 -fig, ax = plt.subplots() -ax.bar(labels, price, width, label = "Price", color = "gray") -currenty = price -y = 0 + for i in range(args.plot): + ax.bar(labels, taxes, width, bottom = current_y, label = "Taxes".format(y), color = "darkgreen") + current_y = current_y + taxes + ax.bar(labels, insurance, width, bottom = current_y, label = "Insurance".format(y), color = "royalblue") + current_y = current_y + insurance + ax.bar(labels, driving, width, bottom = current_y, label = "Driving".format(y), color = "midnightblue") + current_y = current_y + driving + ax.bar(labels, maintenance, width, bottom = current_y, label = "Maintenance".format(y), color = "lavender") + current_y = current_y + maintenance + y += 1 + ecar_top = current_y[0] + #ax.plot(np.linspace(-0.2, 1.2, 10), [ecar_top]*10, "--", color = "firebrick", label = "Break even") + #ax.text(0.3, ecar_top * 0.95, "Break even: {} years, {} kilometers".format(y, y*settings["kilometers_per_year"])) + ax.set_ylabel("CHF") + ax.set_title("Comparision of economics electric vs. combustion car") + ax.legend(["Price", "Taxes", "Insurance", "Driving", "Maintenance"]) + ax.grid(axis = "y") + #print("Break even after {} years and {} kilometers. {}".format(y, y*settings["kilometers_per_year"], current_y[0]-current_y[1])) + plt.show() -for i in range(settings["years"]): -#while currenty[0] > currenty[1]: - ax.bar(labels, taxes, width, bottom = currenty, label = "Taxes".format(y), color = "darkgreen") - currenty = currenty + taxes - ax.bar(labels, insurance, width, bottom = currenty, label = "Insurance".format(y), color = "royalblue") - currenty = currenty + insurance - ax.bar(labels, driving, width, bottom = currenty, label = "Driving".format(y), color = "midnightblue") - currenty = currenty + driving - ax.bar(labels, maintenance, width, bottom = currenty, label = "Maintenance".format(y), color = "lavender") - currenty = currenty + maintenance - y += 1 -ecar_top = currenty[0] -#ax.plot(np.linspace(-0.2, 1.2, 10), [ecar_top]*10, "--", color = "firebrick", label = "Break even") -ax.text(0.3, ecar_top * 0.95, "Break even: {} years, {} kilometers".format(y, y*settings["kilometers_per_year"])) -ax.set_ylabel("CHF") -ax.set_title("Comparision of economics electric vs. combustion car") -ax.legend(["Price", "Taxes", "Insurance", "Driving", "Maintenance"]) -ax.grid(axis = "y") -print("Break even after {} years and {} kilometers. {}, {}".format(y, y*settings["kilometers_per_year"], currenty[0], currenty[1])) -plt.show() +if __name__ == "__main__": + main()