Compare commits
3 Commits
4eec2ebc8c
...
129901617d
Author | SHA1 | Date |
---|---|---|
Maximilian Stiefel | 129901617d | 3 years ago |
Maximilian Stiefel | fcc2b6330f | 3 years ago |
Maximilian Stiefel | cc3c13727e | 3 years ago |
3 changed files with 338 additions and 0 deletions
@ -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 |
||||
|
|
@ -0,0 +1,29 @@ |
|||||
|
{ |
||||
|
"currency": "CHF", |
||||
|
"kwh_price_home": 0.2, |
||||
|
"kwh_price_commercial": 0.5, |
||||
|
"petrol_litre_price": 1.75, |
||||
|
"kilometers_per_year": 20000.0, |
||||
|
"years": 6, |
||||
|
"ecar": { |
||||
|
"label": "Nissan Leaf 2013", |
||||
|
"price": 7600, |
||||
|
"taxes": 0, |
||||
|
"insurance": 354, |
||||
|
"kwh_per_kilometer": 0.16, |
||||
|
"maintenance": 100, |
||||
|
"charging_behaviour": { |
||||
|
"percent_free_charges": 90, |
||||
|
"percent_home_charges": 5, |
||||
|
"percent_commercial_charges": 5 |
||||
|
} |
||||
|
}, |
||||
|
"ccar": { |
||||
|
"label": "Suzuki Grand Vitara 2008", |
||||
|
"price": 7000, |
||||
|
"taxes": 350, |
||||
|
"insurance": 362, |
||||
|
"litres_per_kilometer": 0.08, |
||||
|
"maintenance": 1000 |
||||
|
} |
||||
|
} |
@ -0,0 +1,161 @@ |
|||||
|
import matplotlib.pyplot as plt |
||||
|
import numpy as np |
||||
|
import json |
||||
|
import sys |
||||
|
import argparse |
||||
|
__author__ = 'm3x1m0m' |
||||
|
|
||||
|
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["ccar"]["maintenance"]]) |
||||
|
self.kilometers = settings["kilometers_per_year"] |
||||
|
self.currency = settings["currency"] |
||||
|
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 |
||||
|
def get_kilometers(self): |
||||
|
return self.kilometers |
||||
|
def get_currency(self): |
||||
|
return self.currency |
||||
|
|
||||
|
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 = total_costs + increment |
||||
|
months += 1 |
||||
|
kilometers = self.settings_extractor.get_kilometers() * months / months_a_year |
||||
|
return [months//months_a_year, months%months_a_year, kilometers] # years, months |
||||
|
def calculate_amortization_point(self): |
||||
|
y = 0 |
||||
|
m = 1 |
||||
|
months_a_year = 12.0 |
||||
|
costs_a_month = self.calculate_costs(y, m) |
||||
|
savings_a_month = costs_a_month[1]-costs_a_month[0] |
||||
|
months_till_amortized = self.settings_extractor.get_purchase()[0] / savings_a_month |
||||
|
kilometers = months_till_amortized * self.settings_extractor.get_kilometers() / months_a_year |
||||
|
months_till_amortized = np.ceil(months_till_amortized) |
||||
|
return months_till_amortized//months_a_year, months_till_amortized%months_a_year, round(kilometers, ndigits=2) |
||||
|
|
||||
|
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 the EV becomes cheaper).', action='store_true') |
||||
|
parser.add_argument('-c','--amortization', help='Calculate the point in time when the electric vehicle is amortized completely by savings.', action='store_true') |
||||
|
parser.add_argument('-d','--savings_per_month', help='Calculate savings per month.', action='store_true') |
||||
|
parser.add_argument('-e','--savings_per_year', help='Calculate savings per year.', action='store_true') |
||||
|
parser.add_argument('-f','--savings_per_kilometer', help='Calculate savings per 100 kilometers (only driving, no maintenance, taxes or insurance).', action='store_true') |
||||
|
parser.add_argument('-g','--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) |
||||
|
be_years = None |
||||
|
be_months = None |
||||
|
be_kilometers = None |
||||
|
if args.break_even: |
||||
|
be_years, be_months, be_kilometers = comparator.calculate_break_even() |
||||
|
print("Break even after {} years and {} months.".format(be_years, be_months)) |
||||
|
if args.savings_per_month: |
||||
|
years = 0 |
||||
|
months = 1 |
||||
|
savings = comparator.calculate_costs(years, months) |
||||
|
print("Savings per month based on yearly spending: {}.".format(round(savings[1]-savings[0], ndigits=2))) |
||||
|
if args.savings_per_year: |
||||
|
years = 1 |
||||
|
months = 0 |
||||
|
savings = comparator.calculate_costs(years, months) |
||||
|
print("Savings per year: {}.".format(round(savings[1]-savings[0], ndigits=2))) |
||||
|
if args.savings_per_kilometer: |
||||
|
hundred_km = 100.0 |
||||
|
driving = hundred_km * extractor.get_driving() / extractor.get_kilometers() |
||||
|
labels = extractor.get_labels() |
||||
|
print("Costs driving 100 km in the {}: {}. Costs driving 100 km in the {}: {}.".format(labels[0], round(driving[0], ndigits=2), labels[1], round(driving[1]), ndigits=2)) |
||||
|
am_years = None |
||||
|
am_months = None |
||||
|
am_kilometers = None |
||||
|
if args.amortization: |
||||
|
am_years, am_months, am_kilometers = comparator.calculate_amortization_point() |
||||
|
print("The electric vehicle will be amortized by savings after {} years, {} months or exactely at {} kilometres.".format(am_years, am_months, am_kilometers)) |
||||
|
if args.plot != None: |
||||
|
width = 0.3 |
||||
|
plt_colors = ["#8ecae6", "#219ebc", "#023047", "#ffb703", "#fb8500"]; |
||||
|
color_ind = 0 |
||||
|
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 = plt_colors[0]) |
||||
|
current_y = extractor.get_purchase() |
||||
|
y = 0 |
||||
|
for i in range(args.plot): |
||||
|
ax.bar(labels, taxes, width, bottom = current_y, label = "Taxes".format(y), color = plt_colors[1]) |
||||
|
current_y = current_y + taxes |
||||
|
ax.bar(labels, insurance, width, bottom = current_y, label = "Insurance".format(y), color = plt_colors[2]) |
||||
|
current_y = current_y + insurance |
||||
|
ax.bar(labels, driving, width, bottom = current_y, label = "Driving".format(y), color = plt_colors[3]) |
||||
|
current_y = current_y + driving |
||||
|
ax.bar(labels, maintenance, width, bottom = current_y, label = "Maintenance".format(y), color = plt_colors[4]) |
||||
|
current_y = current_y + maintenance |
||||
|
y += 1 |
||||
|
labels = ["Purchase", "Taxes", "Insurance", "Driving", "Maintenance"] |
||||
|
if args.break_even: |
||||
|
months_a_year = 12.0 |
||||
|
be_money = (be_years + be_months/months_a_year) * comparator.calculate_costs_a_year() |
||||
|
be_money = be_money[1] + extractor.get_purchase() |
||||
|
ax.plot(np.linspace(-0.2, 1.2, 10), [be_money[1]]*10, "--", color = plt_colors[2], label = "Break even") |
||||
|
ax.text(0.3, be_money[1] + 100, "Break even: {} years, {} months, {} kilometers".format(be_years, be_months, be_kilometers)) |
||||
|
labels = ["Break even"] + labels |
||||
|
if args.amortization: |
||||
|
months_a_year = 12.0 |
||||
|
am_money = (am_years + am_months/months_a_year) * comparator.calculate_costs_a_year() |
||||
|
am_money = am_money[1] + extractor.get_purchase() |
||||
|
ax.plot(np.linspace(-0.2, 1.2, 10), [am_money[1]]*10, "--", color = plt_colors[2], label = "Amortization") |
||||
|
ax.text(0.3, am_money[1] + 100, "Amortization: {} years, {} months, {} kilometers".format(am_years, am_months, am_kilometers)) |
||||
|
labels = ["Amortization"] + labels |
||||
|
ax.set_ylabel(extractor.get_currency()) |
||||
|
ax.set_title("Comparision of economics electric vs. combustion car") |
||||
|
ax.legend(labels) |
||||
|
ax.grid(axis = "y") |
||||
|
plt.show() |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
main() |
Loading…
Reference in new issue