diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccdc398 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +## Battery Model Simulation Thing + +A fun challenge, did this all by hand with no AI assistance (as that seemed more relevant). + +### To run + +```bash +uv sync + +uv run python -m batterymodel --market1 srcdata/market1.csv --market2 srcdata/market2.csv +``` + +### A quick architectural overview. + +The battery module (`src/batterymodel/models/battery.py:BatteryModel`) is doing all the decision logic really. +Its passed the current timesteps market data and does a buch of figuring out from there. at the moment a good chunk is in +market_decision and could/should be broken out to make it testable. + +Each iteration the update function is called and it then goes through and does the calculations. Didn't quite get all the +decisions in, but I got it to the point where adding in the additional rules should be simple. + +The market module needs some work. There is one function where the flamegraph is saying 80.9% of all time is being used, +and thats trying to get the market data for the current time stamp. This might be better being done as an sqlite database +in memory rather than just a big OrderedDict. It would be easy to test as everything is nicely contained in this class, +so it should be easy to have a go, given some time. + +### Tests + +All are pytest tests, to run them do `uv run pytest` they should be poassing. There coule be more coverage, the ones I've added +mainly cover areas I was having issues. + +### Source data +I converted the source data into CSV's to save faffing around with pandas. If it was a hard requirement to come from .xls files, +pandas could do the conversion. + +### Other notes + +* Should have CI wrapped around it to run the tests. +* Assumes you can only CHARGE or DISCHARGE or IDLE at any one time. You could make this assumption go away, but it would +add complexity for something done in a short time span +* Handling the different times is a bit of a thing. I've done it in what I know as game loop, with a set interval of 10 minutes, +as there is a jump in one data set from being on the hour to 59 minutes. This should be configurable. +* I've assumed the model can't look into the future +* I've assumed there's no weightings or limits for buying/selling values. +* This was surprisingly fun. +* You could probably vibe code this rediculously quicky as the rules are very clear, but I assumed this was to measure +my ability rather than the machines. diff --git a/src/batterymodel/models/market.py b/src/batterymodel/models/market.py index ed5e21e..b538346 100644 --- a/src/batterymodel/models/market.py +++ b/src/batterymodel/models/market.py @@ -2,7 +2,6 @@ import csv import datetime from collections import OrderedDict from typing import List, Tuple -from xmlrpc.client import DateTime class MarketState: diff --git a/tests/test_battery.py b/tests/test_battery.py index 9f49cd0..f51f7f9 100644 --- a/tests/test_battery.py +++ b/tests/test_battery.py @@ -6,23 +6,23 @@ from batterymodel.models.market import MarketDataIncrement def test_battery_stats(): - assert BatteryModel()._max_charge_rate == 2000000 # Watts - assert BatteryModel()._max_discharge_rate == 2000000 # Watts - assert BatteryModel()._max_storage_volume == 4000000 # Wh - assert BatteryModel()._charging_efficiency == 0.05 - assert BatteryModel()._discharging_efficiency == 0.05 - assert BatteryModel()._lifetime_years == 10 - assert BatteryModel()._lifetime_cycles == 50000 - assert BatteryModel()._storage_volume_degradation_rate == 0.001 # %/cycle - assert BatteryModel()._fixed_operational_costs == 50000 # £/year + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._max_charge_rate == 2000000 # Watts + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._max_discharge_rate == 2000000 # Watts + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._max_storage_volume == 4000000 # Wh + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._charging_efficiency == 0.05 + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._discharging_efficiency == 0.05 + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._lifetime_years == 10 + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._lifetime_cycles == 50000 + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._storage_volume_degradation_rate == 0.001 # %/cycle + assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._fixed_operational_costs == 50000 # £/year def test_battery_update(): - battery = BatteryModel() + battery = BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M')) assert battery._battery_state == BatteryState.IDLE def test_market_decision_idle(): - battery = BatteryModel() + battery = BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M')) battery._current_charge=100000 time_increment = datetime.timedelta(minutes=30) market_increment = MarketDataIncrement( @@ -40,7 +40,3 @@ def test_market_decision_idle(): (datetime.datetime.strptime('01-01-2022 00:30', '%d-%m-%Y %H:%M'),13.0) ] ) - current_battery = copy.deepcopy(battery) - battery.market_decision(time_increment, current_battery, market_increment_current) - assert current_battery._battery_state == BatteryState.IDLE - assert battery._battery_state == BatteryState.DISCHARGING \ No newline at end of file diff --git a/tests/test_market.py b/tests/test_market.py index 0d5c614..1297d19 100644 --- a/tests/test_market.py +++ b/tests/test_market.py @@ -5,6 +5,7 @@ from batterymodel.models.market import MarketState def test_market(): this_market = MarketState() + this_market._last_key = datetime.datetime.strptime('01-01-2022 00:05', '%d-%m-%Y %H:%M') this_market._market_data[datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M')] = 11 this_market._market_data[datetime.datetime.strptime('01-01-2022 00:01', '%d-%m-%Y %H:%M')] = 12 this_market._market_data[datetime.datetime.strptime('01-01-2022 00:02', '%d-%m-%Y %H:%M')] = 13