Skip to content

Commit c67d445

Browse files
author
Weber
committed
[TSK] Init powerplant production plan API
1 parent 82c6412 commit c67d445

11 files changed

Lines changed: 197 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*env/
2+
*__pycache__/

USING_APP_README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
# About the API
3+
4+
## Prerequisite
5+
6+
Current API was built throught Flask framework. In order to launch Flask server, some dependencies
7+
are needed, and should be installed (see requirements.txt)
8+
9+
## API using
10+
11+
Of course, you need to launch the flask server. On root folder, launch app.py.
12+
13+
### First way : throught script
14+
15+
Always on the root, a script called *launch_dummy_payload.py* init a request attempt to the server. To use it,
16+
just set the path to your file on the variable file_path, then launch it on CLI.
17+
18+
### Second way : curl command
19+
20+
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.
21+
22+
## Interesting upgrade
23+
24+
### CO2 cost inclusion
25+
26+
Could be interesting to add in merit order this cost
27+
28+
### Production quantity order
29+
30+
Include production qty in using order, after cost;
31+
By instance, producing load of 400 with two powerplants (efficienty and cost equals) pmin and pmax respectively at 100/300 and 200/450.
32+
It makes sens to use second before, to avoid two powerplants activation (first cannot take the load completely, second is required,
33+
which oversize the asked load: 300 + 200 > 400).

__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from . import controllers
2+
from . import models
3+
import app
4+
import blue_print

app.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from flask import Flask
2+
3+
from blue_print import production_plan_blue_print
4+
5+
app = Flask(__name__)
6+
app.register_blueprint(production_plan_blue_print)
7+
8+
if __name__ == "__main__":
9+
app.run(host="127.0.0.0", port=8888)

blue_print.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from flask import Blueprint
2+
from controllers.production_plan_controller import get_production_plan
3+
4+
production_plan_blue_print = Blueprint("production_plan_blue_print", __name__)
5+
production_plan_blue_print.route("/production_plan", methods=["POST"])(
6+
get_production_plan
7+
)

controllers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import production_plan_controller
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from flask import request, jsonify
2+
from models.powerplants import (
3+
init_all_powerplant_units,
4+
get_merit_order_production_plan,
5+
)
6+
7+
8+
def get_production_plan():
9+
request_values = request.json
10+
required_load = request_values.get("load", 0)
11+
powerplant_values = request_values.get("powerplants", {})
12+
productivity_settings = request_values.get("fuels")
13+
try:
14+
assert (
15+
isinstance(required_load, float) or isinstance(required_load, int)
16+
) and required_load > 0
17+
powerplants = init_all_powerplant_units(
18+
powerplant_values, productivity_settings
19+
)
20+
assert len(powerplants) != 0
21+
load_production = get_merit_order_production_plan(required_load, powerplants)
22+
except AssertionError:
23+
return jsonify(
24+
{"Error": "No valid load or powerplants missing in the given values."}
25+
)
26+
except Exception as e:
27+
return jsonify(
28+
{
29+
"Error": f"Some troubles append during processing. '{e}'. Please check the values"
30+
}
31+
)
32+
return jsonify(load_production)

launch_dummy_payload.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
import requests
3+
import json
4+
from json import JSONDecodeError
5+
6+
file_path = '' # Your filepath
7+
8+
data = {}
9+
10+
try:
11+
with open(file_path, 'r') as file:
12+
data = json.load(file)
13+
headers = {'Content-Type': 'application/json'}
14+
15+
res = requests.post(
16+
'http://127.0.0.0:8888/production_plan',
17+
json.dumps(data), headers=headers)
18+
if res.ok:
19+
print(res.json())
20+
21+
except JSONDecodeError:
22+
print("Given Json file is not valid.")
23+

models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import powerplants

models/powerplants.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
TYPE_COST_MAPPING = {"gasfired": "gas(euro/MWh)", "turbojet": "kerosine(euro/MWh)"}
2+
3+
4+
class PowerPlant:
5+
6+
def __init__(self, values):
7+
for key, value in values.items():
8+
setattr(self, key, value)
9+
10+
def get_producted_load(self, required_load):
11+
# To extend method, depending powerplant type
12+
pass
13+
14+
15+
class WindPowerPlant(PowerPlant):
16+
17+
def __init__(self, values):
18+
super().__init__(values)
19+
self.wind_rate = values.get("wind")
20+
21+
def get_producted_load(self, required_load):
22+
if required_load <= 0:
23+
return 0
24+
load_to_product = required_load if self.pmax > required_load else self.pmax
25+
return round(load_to_product * self.wind_rate, 1)
26+
27+
28+
class FossilePowerPlant(PowerPlant):
29+
30+
def __init__(self, values):
31+
super().__init__(values)
32+
theorical_cost = values.get("cost")
33+
self.cost = round(theorical_cost * (1 / (self.efficiency)), 1)
34+
35+
def get_producted_load(self, required_load):
36+
if required_load <= 0:
37+
return 0
38+
elif self.pmin < required_load < self.pmax:
39+
return required_load
40+
return self.pmin if required_load < self.pmin else self.pmax
41+
42+
43+
def init_all_powerplant_units(powerplant_values, productivity_settings):
44+
powerplants = []
45+
breakpoint()
46+
for values in powerplant_values:
47+
type = values.get("type")
48+
cost_type = TYPE_COST_MAPPING.get(type, "wind")
49+
values.update(
50+
{
51+
"cost": productivity_settings.get(cost_type, 0.0),
52+
"wind": productivity_settings.get("wind(%)", 0.0) / 100,
53+
}
54+
)
55+
ToInstanceClass = WindPowerPlant if type == "windturbine" else FossilePowerPlant
56+
powerplant = ToInstanceClass(values)
57+
powerplants.append(powerplant)
58+
return powerplants
59+
60+
61+
def get_merit_order_production_plan(required_load, powerplants):
62+
production_plan = []
63+
ordered_powerplants = sorted(powerplants, key=lambda powerplant: powerplant.cost)
64+
for powerplant in ordered_powerplants:
65+
producted_load = powerplant.get_producted_load(required_load)
66+
production_plan.append({"name": powerplant.name, "p": producted_load})
67+
required_load = round(required_load - producted_load, 1)
68+
return production_plan

0 commit comments

Comments
 (0)