Skip to content

Commit dd19bc4

Browse files
committed
Initial commit
0 parents  commit dd19bc4

18 files changed

+1331
-0
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
test/__pycache__/*
2+
tmh_server/__pycache__/*
3+
4+
anlagenstammdaten/*
5+
devops/*
6+
configs/*
7+
main.py
8+
*.ipynb

README.md

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# The Mobility House Coding Challenge
2+
This README explains all steps required to collect, process, and query information about curtailment of power generation in the balance zone of the transmission grid operator (TSO) TenneT GmbH in Germany. Data ranges from 1st of January 2020 to 30th of December 2020.
3+
4+
- [Setting Up The Environment](#environment)
5+
- [Deploy Virtual Machine](#vm_deploy)
6+
- [Initialize Virtual Machine](#vm_init)
7+
- [Configure Database](#database_init)
8+
- [Example Code](#example_code)
9+
- [Assumptions](#assumptions)
10+
11+
## Setting Up The Environment <a name="environment></a>
12+
Operating system: Ubuntu 22.04 LTS
13+
14+
### Deploy Virtual Machine <a name="vm_deploy"></a>
15+
Check if you have Terraform installed by running `terraform --help` in the terminal. If Terraform is not installed on your machine follow the install guide from [Hashicorp](https://developer.hashicorp.com/terraform/install). We provide an example Terraform configuration files to deply a virtual machine running on OpenNebula. Adjust the following parts to match your environment:
16+
17+
config.tfvars:
18+
- ON_USERNAME
19+
- ON_PASSWD
20+
- ON_GROUP
21+
22+
on_vms.tf:
23+
- endpoint
24+
- flowpoint
25+
26+
1. `terraform init`
27+
2. `terraform plan -var-file=config.tfvars -out=infra.out`
28+
3. `terraform apply infra.out`
29+
4. `tearraform show` to get IP address of deployed virtual machine, which is requried for the next step
30+
5. `terraform destroy -var-file=config.tfvars` (optional)
31+
32+
### Initialize Virtual Machine <a name="vm_init"></a>
33+
Check if you have Ansible installed by running `ansible --help` in the terminal. If Ansible is not installed on your machine follow the install guide from [Ansible Documentation](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html). We install all required programs and packages on the previously deployed virtual machine via Ansible playbooks.
34+
35+
1. Define IP address(es) by using a host group (e.g., in /etc/ansible/hosts) and refering to it in the playbooks
36+
2. Adjust host group in all following YAML files
37+
3. Update virtual machine: `ansible-playbook udpate.yml`
38+
4. Install PostgreSQL: `ansible-playbook postgres_install.yml`
39+
5. Copy files to virtual machine: `ansible-playbook copy_files.yml --extra-vars '{"path_to_files": "path/to/files/with/trailing/backslash/"}'`
40+
6. Install Python virtual environments for type= server or client: `ansible-playbook prepare_virtualenv.yml --extra-vars '{"type": "server"}'`
41+
42+
43+
### Configure Database <a name="database_init"></a>
44+
Set up a PostgreSQL database with two users. First, connect to PostgreSQL `sudo -u postgres psql` and list all available users with their respective priviliges `\du` / `SELECT * FROM information_schema.role_table_grants WHERE grantee='user_name'`
45+
;` or without their priviliges `SELECT usename from pg_catalog.pg_user;`. Create new users if no suitable users already exist for the server and client `CREATE USER tmh_type WITH ENCRYPTED PASSWORD 'tmh_type';`:
46+
47+
1. type=server: User to read and write data into an existing database
48+
2. type=client: User with read-only access
49+
50+
Preparing database and tables:
51+
1. Create database `SELECT 'CREATE DATABASE curtailment_tennet' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'curtailment_tennet')\gexec`
52+
2. Connect to new database `\c curtailment_tennet`
53+
3. Create table: `CREATE TABLE IF NOT EXISTS curtailments (start_curtailment TIMESTAMP, end_curtailment TIMESTAMP, duration SMALLINT, level SMALLINT, cause VARCHAR, plant_id VARCHAR, operator VARCHAR, nominal_power smallint, energy numeric);`
54+
4. Change user priviliges <br>
55+
4.1 Give server and client read access `GRANT SELECT ON TABLE curtailments TO tmh_<type>;` <br>
56+
4.2 Give server write access `GRANT INSERT ON TABLE curtailments TO tmh_server;`
57+
4.3 Give server update access `GRANT UPDATE ON TABLE curtailments TO tmh_server;`
58+
59+
## Example Code <a name="example_code"></a>
60+
```
61+
# stdlib
62+
import os
63+
import logging
64+
65+
# third party
66+
import pandas as pd
67+
68+
# relative
69+
from tmh_server.mapping import Mapping
70+
from tmh_server.avacon_api import AvaconAPI
71+
from tmh_server.postgresql import PostgreSQL
72+
from tmh_server.process_data import ProcessData
73+
from tmh_server.mastr_scrapper import MastrScrapper
74+
75+
76+
SCRAP_MASTR: bool = False
77+
78+
# Initialize logger
79+
logger = logging.getLogger(__name__)
80+
logging.basicConfig(filename=os.path.join(os.getcwd(), "tmh.log"),
81+
format="%(asctime)s,%(levelname)s,%(module)s:%(funcName)s:%(lineno)s,%(message)s",
82+
datefmt="%Y-%m-%d %H:%M:%S",
83+
level=logging.DEBUG)
84+
85+
86+
def main():
87+
"""
88+
Combines data extraction from Avacon API, cleaning the output,
89+
and storing it in a PostgreSQL database. Then scrap
90+
Marktstammdatenregister based on network operator ID to obtain
91+
mapping of EEG Anlagenschlüssel to nominal power (might be optional).
92+
Lastly, update values for nominal power and curtailed energy based
93+
on power plant ID.
94+
"""
95+
# Extract information from Avacon API based on avacon_api.json config
96+
config_path: str = os.path.join(os.path.abspath(os.path.dirname(__file__)),
97+
"configs")
98+
avacon_api: AvaconAPI = AvaconAPI(config_path=config_path,
99+
config_name="avacon_api.json")
100+
df: pd.DataFrame = avacon_api.call_api()
101+
102+
# Clean extracted data
103+
process_data: ProcessData = ProcessData(df)
104+
process_data.clean()
105+
df: pd.DataFrame = process_data.get_data()
106+
107+
# Store cleaned data in PostgreSQL database based on query.json config
108+
psql: PostgreSQL = PostgreSQL(config_path=config_path,
109+
config_name="query.json",
110+
data=df)
111+
psql.connect_and_insert()
112+
113+
# Scrap Marktstammdatenregister (might be optional)
114+
if SCRAP_MASTR:
115+
path_anlagenstammdaten: str = os.path.join(os.path.abspath(os.path.dirname(__file__)),
116+
"anlagenstammdaten")
117+
mapper: Mapping = Mapping(path_anlagenstammdaten,
118+
"TenneT TSO GmbH EEG-Zahlungen Bewegungsdaten 2022.csv")
119+
120+
scrapper: MastrScrapper = MastrScrapper()
121+
for nb_mastr_nr in mapper.get_nb_mastr_nrs():
122+
scrapper.set_nb_mastr_nr(nb_mastr_nr)
123+
scrapper.download_via_link()
124+
scrapper.move_downloaded_file()
125+
scrapper.merge_snbs_into_one_csv(path_anlagenstammdaten)
126+
127+
# Update PostgreSQL database with values for nominal power and curtailed energy
128+
mapper.get_merged_snbs()
129+
mapper.create_mapping()
130+
mapper.set_df_db(psql.get_rows("curtailments"))
131+
mapper.map_power_to_plant_id()
132+
mapper.calculate_curtailed_power()
133+
mapper.calculate_curtailed_energy()
134+
135+
psql.close_connection()
136+
psql.set_df(mapper.df_db)
137+
psql.update_rows(col_to_update="nominal_power",
138+
col_condition="plant_id")
139+
140+
141+
if __name__ == "__main__":
142+
main()
143+
```
144+
145+
146+
## Assumptions <a name="assumptions"></a>
147+
Additional information, e.g.,
148+
- AVACON does not exclusively operate in TenneT area
149+
- Add code lintering

requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pandas
2+
requests
3+
selenium
4+
psycopg2-binary

setup.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# stdlib
2+
import os
3+
from setuptools import find_packages, setup
4+
5+
6+
def read(file_name: str):
7+
"""
8+
Utility function to read the README file.
9+
Used for the long_description.
10+
"""
11+
return open(os.path.join(os.path.dirname(__file__), file_name),
12+
encoding="utf-8").read()
13+
14+
15+
with open("requirements.txt", encoding="utf-8") as f:
16+
all_reqs = f.read().split("\n")
17+
install_requires = [x.strip() for x in all_reqs if "git+" not in x]
18+
dependency_links = [x.strip().replace("git+", "")
19+
for x in all_reqs if x.startswith("git+")]
20+
21+
setup(name="tmh_server",
22+
version="1.0.0",
23+
author="René Schwermer",
24+
author_email="[email protected]",
25+
description="Package to extract data of curtailed power and energy from different sources.",
26+
license="proprietary",
27+
keywords="API, data processing, power crutailments, database",
28+
url="https://github.com/Rene36/tmh_server",
29+
packages=find_packages(),
30+
install_requires=install_requires,
31+
dependency_links=dependency_links,
32+
long_description=read("README.md"),
33+
classifiers=["Development Status :: 3 Alpha",
34+
"License :: Other/Proprietary License",
35+
"Operating System :: Linux",
36+
"Programming Language :: Python",
37+
"Programming Language :: Python 3",
38+
"Topic :: Scientific/Engineering"]
39+
)

test/__init__.py

Whitespace-only changes.

test/test_config.json

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"fl_params":
3+
{
4+
"server_address": "172.24.32.3",
5+
"fl": {
6+
"rounds": 20,
7+
"fraction_fit": 1.0,
8+
"min_fit_clients": 2,
9+
"min_available_clients": 2,
10+
"DP": "False"
11+
}
12+
},
13+
"training_params":
14+
{
15+
"python_exec": "/home/ubuntu/flwr_systems/venv/flwr/bin/python3",
16+
"data": {
17+
"csv": {"use": "True",
18+
"label": "MotorPosition[12] (A12)",
19+
"features": ["MotorPosition[23] (A23)", "Velo_RPM[23] (A23)"],
20+
"to_dataloader": "False",
21+
"batch_size": 64,
22+
"separator": "tab"},
23+
"dataloader": {"use": "False",
24+
"dataset": "c2",
25+
"batch_size": 32}
26+
},
27+
"model": {
28+
"CIFAR10CNN": {"run": "False",
29+
"learning_rate": 0.01,
30+
"momentum": 0.9},
31+
"C2CNNLSTM": {"run": "True",
32+
"input_size": 1,
33+
"output_size": 1,
34+
"hidden_size": 3,
35+
"num_layers": 200,
36+
"learning_rate": 0.01},
37+
"Lasso": {"run": "False",
38+
"warm_start": "False",
39+
"max_iter": 1},
40+
"LogisticRegression": {"run": "False",
41+
"warm_start": "True",
42+
"max_iter": 1,
43+
"penlaty": "l2"},
44+
"LinearRegression": {"run": "False"}
45+
}
46+
}
47+
}

test/test_data.csv

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
Humidity,VOC,NO2
2+
82.46,155,59.17
3+
82.42,169,54.83
4+
82.4,191,54.72
5+
82.4,188,53.84
6+
82.41,190,56.85
7+
82.39,186,53.87
8+
82.4,192,52.67
9+
82.39,189,53.87
10+
82.39,193,52.52
11+
82.4,162,53.12
12+
82.38,163,55.35
13+
82.38,191,55.66
14+
82.38,195,52.18
15+
82.39,202,53.9
16+
82.42,210,53.75
17+
82.45,219,53.55
18+
82.44,224,52.81
19+
82.43,236,54.21
20+
82.42,182,58.32
21+
82.45,173,56.83
22+
82.43,196,54.92
23+
82.48,195,53.61
24+
82.44,187,51.36
25+
82.52,171,55.35
26+
82.48,194,59.1
27+
82.45,192,56.66
28+
82.44,187,57.39
29+
82.44,190,57.71
30+
82.41,172,54.28
31+
82.35,158,58.03
32+
82.36,187,56.19
33+
82.32,193,56.53
34+
82.33,191,55.99
35+
82.31,195,54.49
36+
82.33,199,56.82
37+
82.32,206,55.72
38+
82.29,197,57.99
39+
82.3,198,56.78
40+
82.24,213,56.38
41+
82.19,203,57.36
42+
82.15,184,55.94
43+
82.14,181,55.52
44+
82.16,182,55.76
45+
82.16,184,58.12
46+
82.12,181,58.57
47+
82.13,179,55.01
48+
82.08,183,53.77
49+
82.04,184,58.54
50+
82.04,185,54.19
51+
82.03,181,56.75
52+
82.04,179,57.06
53+
81.99,180,53.78
54+
81.97,179,54.3
55+
81.98,180,54.25
56+
81.94,178,57.08
57+
81.86,182,54.39
58+
81.84,180,58.39
59+
81.84,181,55.64
60+
81.81,176,53.38
61+
81.77,180,56.97
62+
81.78,180,55.74
63+
81.75,182,58.61
64+
81.76,190,55.24
65+
81.72,188,56.62
66+
81.73,191,59.45
67+
81.73,186,54.59
68+
81.72,190,55.39
69+
81.7,185,54.37
70+
81.65,192,55.78
71+
81.61,168,59.17
72+
81.56,155,57.25
73+
81.54,183,59.05
74+
81.53,186,61.25
75+
81.49,179,59.6
76+
81.48,151,60.17
77+
81.49,149,58.21
78+
81.46,150,60
79+
81.43,153,60.7
80+
81.34,159,55.24
81+
81.33,158,56.87
82+
81.32,162,59.26
83+
81.3,163,60.52
84+
81.3,169,59.43
85+
81.25,166,65.28
86+
81.21,165,60.68
87+
81.18,171,59.72
88+
81.16,169,58.72
89+
81.14,167,55.81
90+
81.11,172,58.72
91+
81.11,170,58.09
92+
81.05,208,60.44
93+
81.03,178,57.36
94+
81.04,186,60.42
95+
81.06,186,56.84
96+
81.05,188,58.97
97+
81.04,201,59.61
98+
80.99,206,60.04
99+
80.95,190,60.44
100+
80.98,203,61.35
101+
80.98,205,58.26
102+
80.97,201,60.53
103+
80.94,200,63.87
104+
80.93,202,59.25
105+
80.88,201,62.73
106+
80.87,199,64.09
107+
80.88,203,62.62
108+
80.86,206,63.82
109+
80.88,212,62.96
110+
80.82,205,63.05
111+
80.8,207,64.82
112+
80.78,207,63.2
113+
80.77,206,66.51
114+
80.75,211,60.01
115+
80.77,222,61.69
116+
80.73,231,64.42
117+
80.74,243,66.38
118+
80.69,246,63.32
119+
80.65,213,63.69
120+
80.62,210,66.28
121+
80.59,211,66.46
122+
80.51,208,66.39
123+
80.5,203,64.22
124+
80.53,201,64.26
125+
80.47,204,63.14
126+
80.43,202,66.3
127+
80.38,202,67.12
128+
80.35,200,62.94
129+
80.35,204,60.58
130+
80.33,198,63.96
131+
80.28,196,65.03

0 commit comments

Comments
 (0)