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
|
||||
from collections import OrderedDict
|
||||
from typing import List, Tuple
|
||||
from xmlrpc.client import DateTime
|
||||
|
||||
|
||||
class MarketState:
|
||||
|
||||
+11
-15
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user