Skip to content

Commit

Permalink
Powerplants production dispatch API initiation
Browse files Browse the repository at this point in the history
  • Loading branch information
Weber committed Sep 15, 2024
1 parent 82c6412 commit 67f7a52
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*env/
*__pycache__/
33 changes: 33 additions & 0 deletions USING_APP_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

# About the API

## Prerequisite

Current API was built throught Flask framework. In order to launch Flask server, some dependencies
are needed, and should be installed (see requirements.txt)

## API using

Of course, you need to launch the flask server. On root folder, launch app.py.

### First way : throught script

Always on the root, a script called *launch_dummy_payload.py* init a request attempt to the server. To use it,
just set the path to your file on the variable file_path, then launch it on CLI.

### Second way : curl command

Less convenient, curl command could be use, like this : *curl -X POST http://127.0.0.0:8888/production_plan -H "Content-Type: application/json" -d your_json_values*. Keep in mind that is not the most convenient way for large set of data. In this case, use the first option.

## Interesting upgrade

### CO2 cost inclusion

Could be interesting to add in merit order this cost

### Production quantity order

Include production qty in using order, after cost;
By instance, producing load of 400 with two powerplants (efficienty and cost equals) pmin and pmax respectively at 100/300 and 200/450.
It makes sens to use second before, to avoid two powerplants activation (first cannot take the load completely, second is required,
which oversize the asked load: 300 + 200 > 400).
4 changes: 4 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import controllers
from . import models
import app
import blue_print
9 changes: 9 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from flask import Flask

from blue_print import production_plan_blue_print

app = Flask(__name__)
app.register_blueprint(production_plan_blue_print)

if __name__ == "__main__":
app.run(host="127.0.0.0", port=8888)
7 changes: 7 additions & 0 deletions blue_print.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask import Blueprint
from controllers.production_plan_controller import get_production_plan

production_plan_blue_print = Blueprint("production_plan_blue_print", __name__)
production_plan_blue_print.route("/production_plan", methods=["POST"])(
get_production_plan
)
1 change: 1 addition & 0 deletions controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import production_plan_controller
32 changes: 32 additions & 0 deletions controllers/production_plan_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from flask import request, jsonify
from models.powerplants import (
init_all_powerplant_units,
get_merit_order_production_plan,
)


def get_production_plan():
request_values = request.json
required_load = request_values.get("load", 0)
powerplant_values = request_values.get("powerplants", {})
productivity_settings = request_values.get("fuels")
try:
assert (
isinstance(required_load, float) or isinstance(required_load, int)
) and required_load > 0
powerplants = init_all_powerplant_units(
powerplant_values, productivity_settings
)
assert len(powerplants) != 0
load_production = get_merit_order_production_plan(required_load, powerplants)
except AssertionError:
return jsonify(
{"Error": "No valid load or powerplants missing in the given values."}
)
except Exception as e:
return jsonify(
{
"Error": f"Some troubles append during processing. '{e}'. Please check the values"
}
)
return jsonify(load_production)
23 changes: 23 additions & 0 deletions launch_dummy_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

import requests
import json
from json import JSONDecodeError

file_path = '' # Your filepath

data = {}

try:
with open(file_path, 'r') as file:
data = json.load(file)
headers = {'Content-Type': 'application/json'}

res = requests.post(
'http://127.0.0.0:8888/production_plan',
json.dumps(data), headers=headers)
if res.ok:
print(res.json())

except JSONDecodeError:
print("Given Json file is not valid.")

1 change: 1 addition & 0 deletions models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import powerplants
68 changes: 68 additions & 0 deletions models/powerplants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
TYPE_COST_MAPPING = {"gasfired": "gas(euro/MWh)", "turbojet": "kerosine(euro/MWh)"}


class PowerPlant:

def __init__(self, values):
for key, value in values.items():
setattr(self, key, value)

def get_producted_load(self, required_load):
# To extend method, depending powerplant type
pass


class WindPowerPlant(PowerPlant):

def __init__(self, values):
super().__init__(values)
self.wind_rate = values.get("wind")

def get_producted_load(self, required_load):
if required_load <= 0:
return 0
load_to_product = required_load if self.pmax > required_load else self.pmax
return round(load_to_product * self.wind_rate, 1)


class FossilePowerPlant(PowerPlant):

def __init__(self, values):
super().__init__(values)
theorical_cost = values.get("cost")
self.cost = round(theorical_cost * (1 / (self.efficiency)), 1)

def get_producted_load(self, required_load):
if required_load <= 0:
return 0
elif self.pmin < required_load < self.pmax:
return required_load
return self.pmin if required_load < self.pmin else self.pmax


def init_all_powerplant_units(powerplant_values, productivity_settings):
powerplants = []
breakpoint()
for values in powerplant_values:
type = values.get("type")
cost_type = TYPE_COST_MAPPING.get(type, "wind")
values.update(
{
"cost": productivity_settings.get(cost_type, 0.0),
"wind": productivity_settings.get("wind(%)", 0.0) / 100,
}
)
ToInstanceClass = WindPowerPlant if type == "windturbine" else FossilePowerPlant
powerplant = ToInstanceClass(values)
powerplants.append(powerplant)
return powerplants


def get_merit_order_production_plan(required_load, powerplants):
production_plan = []
ordered_powerplants = sorted(powerplants, key=lambda powerplant: powerplant.cost)
for powerplant in ordered_powerplants:
producted_load = powerplant.get_producted_load(required_load)
production_plan.append({"name": powerplant.name, "p": producted_load})
required_load = round(required_load - producted_load, 1)
return production_plan
17 changes: 17 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
black==24.8.0
blinker==1.8.2
certifi==2024.8.30
charset-normalizer==3.3.2
click==8.1.7
Flask==3.0.3
idna==3.9
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
mypy-extensions==1.0.0
packaging==24.1
pathspec==0.12.1
platformdirs==4.3.3
requests==2.32.3
urllib3==2.2.3
Werkzeug==3.0.4

0 comments on commit 67f7a52

Please sign in to comment.