Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Powerplants production dispatch API initiation #49

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 payload.json in the variable *file_path* in the script, 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 to payload.json

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