Add README, improve test coverage, and refactor battery model initialization with timestamp
This commit is contained in:
@@ -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.
|
||||||
@@ -2,7 +2,6 @@ import csv
|
|||||||
import datetime
|
import datetime
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
from xmlrpc.client import DateTime
|
|
||||||
|
|
||||||
|
|
||||||
class MarketState:
|
class MarketState:
|
||||||
|
|||||||
+11
-15
@@ -6,23 +6,23 @@ from batterymodel.models.market import MarketDataIncrement
|
|||||||
|
|
||||||
|
|
||||||
def test_battery_stats():
|
def test_battery_stats():
|
||||||
assert BatteryModel()._max_charge_rate == 2000000 # Watts
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._max_charge_rate == 2000000 # Watts
|
||||||
assert BatteryModel()._max_discharge_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()._max_storage_volume == 4000000 # Wh
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._max_storage_volume == 4000000 # Wh
|
||||||
assert BatteryModel()._charging_efficiency == 0.05
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._charging_efficiency == 0.05
|
||||||
assert BatteryModel()._discharging_efficiency == 0.05
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._discharging_efficiency == 0.05
|
||||||
assert BatteryModel()._lifetime_years == 10
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._lifetime_years == 10
|
||||||
assert BatteryModel()._lifetime_cycles == 50000
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._lifetime_cycles == 50000
|
||||||
assert BatteryModel()._storage_volume_degradation_rate == 0.001 # %/cycle
|
assert BatteryModel(datetime.datetime.strptime('01-01-2022 00:00', '%d-%m-%Y %H:%M'))._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'))._fixed_operational_costs == 50000 # £/year
|
||||||
|
|
||||||
def test_battery_update():
|
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
|
assert battery._battery_state == BatteryState.IDLE
|
||||||
|
|
||||||
|
|
||||||
def test_market_decision_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
|
battery._current_charge=100000
|
||||||
time_increment = datetime.timedelta(minutes=30)
|
time_increment = datetime.timedelta(minutes=30)
|
||||||
market_increment = MarketDataIncrement(
|
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)
|
(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
|
|
||||||
@@ -5,6 +5,7 @@ from batterymodel.models.market import MarketState
|
|||||||
|
|
||||||
def test_market():
|
def test_market():
|
||||||
this_market = MarketState()
|
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: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: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
|
this_market._market_data[datetime.datetime.strptime('01-01-2022 00:02', '%d-%m-%Y %H:%M')] = 13
|
||||||
|
|||||||
Reference in New Issue
Block a user