ai4ce-helpers 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ai4ce-helpers
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Functions to help interact with the AI4CE backend
|
|
5
|
+
Author: Your Name
|
|
6
|
+
Author-email: you@example.com
|
|
7
|
+
Requires-Python: >=3.12,<3.14
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Requires-Dist: httpx (>=0.28,<0.29)
|
|
11
|
+
Requires-Dist: streamlit (>=1.47,<2.0)
|
|
12
|
+
Requires-Dist: toml (>=0.10,<0.11)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
empty
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
empty
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "ai4ce-helpers"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Functions to help interact with the AI4CE backend"
|
|
5
|
+
authors = ["Your Name <you@example.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "ai4ce_helpers", from = "src"}]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = ">=3.12,<3.14"
|
|
11
|
+
toml = "^0.10"
|
|
12
|
+
httpx = "^0.28"
|
|
13
|
+
streamlit = "^1.47"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["poetry-core"]
|
|
19
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from _backend_calls import _backend_POST, _backend_PUT, _backend_GET, check_backend_availability
|
|
2
|
+
from functions import (load_file,
|
|
3
|
+
create_new_project,
|
|
4
|
+
get_list_of_all_project_infos,
|
|
5
|
+
get_project_info,
|
|
6
|
+
set_project_name,
|
|
7
|
+
get_recent_project,
|
|
8
|
+
get_mission_info,
|
|
9
|
+
set_mission_orbit,
|
|
10
|
+
get_enabled_components,
|
|
11
|
+
traverse_and_modify,
|
|
12
|
+
enable_component,
|
|
13
|
+
set_sys_arch,
|
|
14
|
+
get_sys_arch,
|
|
15
|
+
set_sys_generator,
|
|
16
|
+
update_sys_generator,
|
|
17
|
+
set_trained_model,
|
|
18
|
+
get_prepared_system_generator_info,
|
|
19
|
+
get_tags_from_string,
|
|
20
|
+
translate_tomlsystem_to_backend,
|
|
21
|
+
comp_create,
|
|
22
|
+
get_all_decisions,
|
|
23
|
+
get_comp_statistics
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = (
|
|
27
|
+
"_backend_POST",
|
|
28
|
+
"_backend_PUT",
|
|
29
|
+
"_backend_GET",
|
|
30
|
+
"load_file",
|
|
31
|
+
"create_new_project",
|
|
32
|
+
"get_list_of_all_project_infos",
|
|
33
|
+
"get_project_info",
|
|
34
|
+
"set_project_name",
|
|
35
|
+
"get_recent_project",
|
|
36
|
+
"get_mission_info",
|
|
37
|
+
"set_mission_orbit",
|
|
38
|
+
"get_enabled_components",
|
|
39
|
+
"traverse_and_modify",
|
|
40
|
+
"enable_component",
|
|
41
|
+
"set_sys_arch",
|
|
42
|
+
"get_sys_arch",
|
|
43
|
+
"set_sys_generator",
|
|
44
|
+
"update_sys_generator",
|
|
45
|
+
"set_trained_model",
|
|
46
|
+
"get_prepared_system_generator_info",
|
|
47
|
+
"get_tags_from_string",
|
|
48
|
+
"translate_tomlsystem_to_backend",
|
|
49
|
+
"comp_create",
|
|
50
|
+
"get_all_decisions",
|
|
51
|
+
"get_comp_statistics"
|
|
52
|
+
)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import time
|
|
4
|
+
import httpx
|
|
5
|
+
import toml
|
|
6
|
+
import streamlit as st
|
|
7
|
+
|
|
8
|
+
# Set backend base url, depending on whether the app is running in
|
|
9
|
+
# a docker container (which is most likely the unified interface.)
|
|
10
|
+
def check_if_backend_in_docker(PORT: int = 8000) -> str:
|
|
11
|
+
"""Check if the backend is running in a Docker container.
|
|
12
|
+
If streamlit is started as part of docker, it will most likely have an environment variable set for the backend URL.
|
|
13
|
+
If not, the backend is assumed to be running on localhost.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
PORT (int): The port on which the backend is running.
|
|
17
|
+
Returns:
|
|
18
|
+
str: The URL of the backend.
|
|
19
|
+
"""
|
|
20
|
+
if os.environ.get("BACKEND_URL"):
|
|
21
|
+
return f"{os.environ.get("BACKEND_URL")}/api"
|
|
22
|
+
if Path("/.dockerenv").exists():
|
|
23
|
+
return f"http://backend:{PORT}/api"
|
|
24
|
+
return f"http://localhost:{PORT}/api"
|
|
25
|
+
|
|
26
|
+
BACKEND_URL = check_if_backend_in_docker()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def check_backend_availability():
|
|
30
|
+
"""Check check every 5 seconds if the backend is available.
|
|
31
|
+
Wait a max of 30 seconds before giving up. Display loading message
|
|
32
|
+
during check, remove loading message once backend is available. Display
|
|
33
|
+
error message if backend is not available.
|
|
34
|
+
"""
|
|
35
|
+
backend_available = False
|
|
36
|
+
timeout = 30
|
|
37
|
+
|
|
38
|
+
backend_url = check_if_backend_in_docker().removesuffix("/api")
|
|
39
|
+
|
|
40
|
+
# display loading message
|
|
41
|
+
checking = st.warning("Waiting for backend...")
|
|
42
|
+
|
|
43
|
+
while not backend_available and timeout > 0:
|
|
44
|
+
try:
|
|
45
|
+
response = httpx.get(backend_url, timeout=1)
|
|
46
|
+
if response.status_code == 200:
|
|
47
|
+
backend_available = True
|
|
48
|
+
except httpx.TimeoutException:
|
|
49
|
+
time.sleep(5)
|
|
50
|
+
|
|
51
|
+
timeout -= 5
|
|
52
|
+
if not backend_available:
|
|
53
|
+
checking.empty()
|
|
54
|
+
st.error(f"Backend is not available at {backend_url}")
|
|
55
|
+
else:
|
|
56
|
+
# remove loading message
|
|
57
|
+
checking.empty()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _backend_GET(
|
|
61
|
+
endpoint: str,
|
|
62
|
+
headers: dict = {"accept": "application/json, application/toml"}
|
|
63
|
+
) -> tuple[int, dict | str]:
|
|
64
|
+
"""An internal function to make the development of get functions easier.
|
|
65
|
+
|
|
66
|
+
Params:
|
|
67
|
+
endpoint(str): the URL of the backend endpoint to post to
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
dict: json response from the backend
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
response = httpx.get(url=f"{BACKEND_URL}{endpoint}", headers=headers, follow_redirects=True)
|
|
76
|
+
# print(response.url) # For Debugging
|
|
77
|
+
# print(response.json()) # For Debugging
|
|
78
|
+
# print(response.status_code) # For Debugging
|
|
79
|
+
|
|
80
|
+
# Raises an HTTPError if the response status code is 4xx or 5xx
|
|
81
|
+
response.raise_for_status()
|
|
82
|
+
if response.status_code == 200:
|
|
83
|
+
return (response.status_code, toml.loads(response.text) if response.headers['content-type'] == 'application/toml' else response.json())
|
|
84
|
+
except httpx.HTTPStatusError as e:
|
|
85
|
+
if e.response.status_code == 500:
|
|
86
|
+
print(f"Server Error (500):{e.response.text}")
|
|
87
|
+
return (e.response.status_code, "Server Error: 500")
|
|
88
|
+
elif e.response.status_code == 422:
|
|
89
|
+
print(f"Unprocessable Content (422): {e.response.text}")
|
|
90
|
+
return (e.response.status_code, "A problem with the payload itself.")
|
|
91
|
+
else:
|
|
92
|
+
print(f"Error ({e.response.status_code}): {e.response.text}")
|
|
93
|
+
return (e.response.status_code, e.response.json())
|
|
94
|
+
except httpx.RequestError as e:
|
|
95
|
+
print(f"An error occurred while requesting {e.request.url!r}.")
|
|
96
|
+
return (500, f"Request Error: {e}")
|
|
97
|
+
return (500, "An unknown error occurred.")
|
|
98
|
+
|
|
99
|
+
def _backend_POST(
|
|
100
|
+
endpoint: str,
|
|
101
|
+
data: dict,
|
|
102
|
+
headers: dict = {"Content-Type": "application/json", "accept": "application/json"}
|
|
103
|
+
) -> tuple[int, dict | str]:
|
|
104
|
+
"""An internal function to make the development of posting functions easier.
|
|
105
|
+
|
|
106
|
+
Params:
|
|
107
|
+
endpoint(str): the URL of the backend endpoint to post to
|
|
108
|
+
data(dict): the concent to put into the backend
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
dict: json response from the backend
|
|
112
|
+
|
|
113
|
+
Catches:
|
|
114
|
+
httpx.HTTPStatusError: if the response status code is 4xx or 5xx
|
|
115
|
+
httpx.RequestError: if there is a problem with the request
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
# Make the POST request
|
|
119
|
+
try:
|
|
120
|
+
response = httpx.post(url=f"{BACKEND_URL}{endpoint}",
|
|
121
|
+
json=data,
|
|
122
|
+
headers=headers,
|
|
123
|
+
follow_redirects=True
|
|
124
|
+
)
|
|
125
|
+
response.raise_for_status()
|
|
126
|
+
if response.status_code == 201:
|
|
127
|
+
return (response.status_code, response.json())
|
|
128
|
+
except httpx.HTTPStatusError as e:
|
|
129
|
+
if e.response.status_code == 500:
|
|
130
|
+
print(f"Server Error (500):{e.response.text}")
|
|
131
|
+
return (e.response.status_code, "Server Error: 500")
|
|
132
|
+
elif e.response.status_code == 422:
|
|
133
|
+
print(f"Unprocessable Content (422): {e.response.text}")
|
|
134
|
+
return (e.response.status_code, "A problem with the payload itself.")
|
|
135
|
+
else:
|
|
136
|
+
print(f"Error ({e.response.status_code}): {e.response.text}")
|
|
137
|
+
return (e.response.status_code, e.response.json())
|
|
138
|
+
except httpx.RequestError as e:
|
|
139
|
+
print(f"An error occurred while requesting {e.request.url!r}.")
|
|
140
|
+
return (500, f"Request Error: {e}")
|
|
141
|
+
return (500, "An unknown error occurred.")
|
|
142
|
+
|
|
143
|
+
def _backend_PUT(
|
|
144
|
+
endpoint: str,
|
|
145
|
+
data: dict,
|
|
146
|
+
headers: dict = {"Content-Type": "application/json", "accept": "application/json"}
|
|
147
|
+
) -> tuple[int, dict | str]:
|
|
148
|
+
"""An internal function to make the development of PUT functions/update easier.
|
|
149
|
+
|
|
150
|
+
Params:
|
|
151
|
+
endpoint(str): the URL of the backend endpoint to post to
|
|
152
|
+
data(dict): the concent to put into the backend
|
|
153
|
+
headers(dict | None): headers to include in the request
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
dict: html response from the backend
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Make the PUT request
|
|
161
|
+
try:
|
|
162
|
+
response = httpx.put(url=f"{BACKEND_URL}{endpoint}",
|
|
163
|
+
json=data,
|
|
164
|
+
headers=headers
|
|
165
|
+
)
|
|
166
|
+
response.raise_for_status()
|
|
167
|
+
if response.status_code == 200:
|
|
168
|
+
return (response.status_code, toml.loads(response.text) if response.headers['content-type'] == 'application/toml' else response.json())
|
|
169
|
+
except httpx.HTTPStatusError as e:
|
|
170
|
+
if e.response.status_code == 500:
|
|
171
|
+
print(f"Server Error (500):{e.response.text}")
|
|
172
|
+
return (e.response.status_code, "Server Error: 500")
|
|
173
|
+
elif e.response.status_code == 422:
|
|
174
|
+
print(f"Unprocessable Content (422): {e.response.text}")
|
|
175
|
+
return (e.response.status_code, "A problem with the payload itself.")
|
|
176
|
+
else:
|
|
177
|
+
print(f"Error ({e.response.status_code}): {e.response.text}")
|
|
178
|
+
return (e.response.status_code, e.response.json())
|
|
179
|
+
except httpx.RequestError as e:
|
|
180
|
+
print(f"An error occurred while requesting {e.request.url!r}.")
|
|
181
|
+
return (500, f"Request Error: {e}")
|
|
182
|
+
return (500, "An unknown error occurred.")
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# Here is the place to but general helper functions
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from pprint import pprint
|
|
7
|
+
from typing import List, Tuple
|
|
8
|
+
import toml
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from _backend_calls import _backend_POST, _backend_PUT, _backend_GET
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_file(filename: Path) -> dict | pd.DataFrame:
|
|
15
|
+
"""
|
|
16
|
+
Load data from a file. The function currently supports JSON files.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
filename (Path): The path to the file.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
dict: The data loaded from the file if it's a JSON.
|
|
23
|
+
"""
|
|
24
|
+
if filename.suffix == '.csv':
|
|
25
|
+
df = pd.read_csv(filename, sep=',', header=0,
|
|
26
|
+
index_col=None, na_values=['NA', '?'])
|
|
27
|
+
return df
|
|
28
|
+
|
|
29
|
+
elif filename.suffix == '.json':
|
|
30
|
+
with open(filename, 'r') as file:
|
|
31
|
+
return json.load(file)
|
|
32
|
+
|
|
33
|
+
elif filename.suffix == '.toml':
|
|
34
|
+
with open(filename, 'r') as file:
|
|
35
|
+
return toml.load(file)
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"You need to add the file format to the load_file function.")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_new_project(project_info: dict):
|
|
44
|
+
"""Create a new project in the backend.
|
|
45
|
+
Params:
|
|
46
|
+
project_info(dict):
|
|
47
|
+
Returns:
|
|
48
|
+
dict:
|
|
49
|
+
"""
|
|
50
|
+
status_code, message = _backend_POST(
|
|
51
|
+
endpoint=f"/v2/projects/", data=project_info)
|
|
52
|
+
if status_code == 200:
|
|
53
|
+
return message
|
|
54
|
+
else:
|
|
55
|
+
print(f"Error creating project: {message}")
|
|
56
|
+
return {"error": message}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_list_of_all_project_infos() -> list[tuple[int, str, str]]:
|
|
60
|
+
"""A function to connect to the backend and get a list of all projects
|
|
61
|
+
in the current project data base.
|
|
62
|
+
|
|
63
|
+
Params:
|
|
64
|
+
None
|
|
65
|
+
|
|
66
|
+
Retruns:
|
|
67
|
+
all_project (list): List of tuples of id and name for all projects in the projectDB
|
|
68
|
+
project_id (int): The project Identification Number
|
|
69
|
+
project_name (str): The name of the project/mission
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
status_code, projects = _backend_GET(endpoint=f"/v2/projects/")
|
|
73
|
+
project_ids: list[tuple[int, str, str]] = []
|
|
74
|
+
if status_code == 200:
|
|
75
|
+
for project in projects:
|
|
76
|
+
project_ids.append((project["id"], project["project_name"], project["modified"]))
|
|
77
|
+
return project_ids
|
|
78
|
+
else:
|
|
79
|
+
print(f"Error fetching projects: {projects}")
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
def get_project_info(project_id: int) -> dict:
|
|
83
|
+
status_code, project_info = _backend_GET(endpoint=f"/v2/projects/{project_id}/")
|
|
84
|
+
return project_info
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def set_project_name(project_id: int, project_name: str) -> dict:
|
|
88
|
+
"""A function to set the project name of a specific project, identified by its ID.
|
|
89
|
+
|
|
90
|
+
Params:
|
|
91
|
+
project_id (int): The project Identification Number
|
|
92
|
+
project_name(str): Name of the project/mission, eg OPS-Sat
|
|
93
|
+
Returns:
|
|
94
|
+
dict: html response from the backend to indicate success (200) or problems
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
data = {
|
|
98
|
+
"project_id": project_id,
|
|
99
|
+
"project_name": project_name,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# status, msg = _backend_put(endpoint=f"/v2/projects/{project_id}", data=data)
|
|
103
|
+
# return (status, msg)
|
|
104
|
+
status_code, msg = _backend_PUT(endpoint=f"/v2/projects/{project_id}/", data=data)
|
|
105
|
+
if status_code == 200:
|
|
106
|
+
return msg
|
|
107
|
+
else:
|
|
108
|
+
print(f"Error setting project name: {msg}")
|
|
109
|
+
return {"error": msg}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_recent_project() -> tuple[int, str, str]:
|
|
113
|
+
status_code, msg = _backend_GET(endpoint=f"/v2/projects/recent/")
|
|
114
|
+
if status_code != 200:
|
|
115
|
+
print(f"Error fetching recent project: {msg}")
|
|
116
|
+
return (None, "No recent project found.")
|
|
117
|
+
return (msg["id"], msg["project_name"], msg["modified"])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_mission_info(project_id: int) -> dict:
|
|
121
|
+
"""A function to get the mission/orbit info for a specific project based on its ID.
|
|
122
|
+
Params:
|
|
123
|
+
project_id (int): The project Identification Number
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
dict: Keplerian elements to define the orbit
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
response = _backend_GET(
|
|
130
|
+
endpoint=f"/v2/projects/{project_id}/mission/")
|
|
131
|
+
return response["orbit"]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def set_mission_orbit(project_id: int, orbit_info: dict) -> dict:
|
|
135
|
+
"""A function to set the orbit in the project database.
|
|
136
|
+
Params:
|
|
137
|
+
project_id (int): The project Identification Number
|
|
138
|
+
orbit_info(dict): Information about the satelllite's orbit
|
|
139
|
+
Returns:
|
|
140
|
+
dict: html response from the backend to indicate success (200) or problems
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
data = {
|
|
144
|
+
"mission_name": "string", # TODO remove?
|
|
145
|
+
"description": "string", # TODO remove?
|
|
146
|
+
"launch_date": "string",
|
|
147
|
+
"purpose": "string", # TODO remove?
|
|
148
|
+
"website": "string", # TODO remove?
|
|
149
|
+
"picture_web": "string", # TODO remove?
|
|
150
|
+
"project_id": 0,
|
|
151
|
+
"orbit": orbit_info,
|
|
152
|
+
# "orbit": {
|
|
153
|
+
# "celestial_object": "string",
|
|
154
|
+
# "orbit_shell": "string",
|
|
155
|
+
# "altitude_km": 0,
|
|
156
|
+
# "epoch_utc": "string",
|
|
157
|
+
# "semi_major_axis_km": 0,
|
|
158
|
+
# "eccentricity": 0,
|
|
159
|
+
# "inclination_degrees": 0,
|
|
160
|
+
# "perigee_height_km": 0,
|
|
161
|
+
# "apogee_height_km": 0,
|
|
162
|
+
# "raan_degrees": 0,
|
|
163
|
+
# "argument_of_perigee_degrees": 0,
|
|
164
|
+
# "mean_anomaly_at_epoch_degrees": 0,
|
|
165
|
+
# "revolutions_per_day": 0,
|
|
166
|
+
# "orbit_number_at_epoch": 0,
|
|
167
|
+
# "time_eclipse_min": 0,
|
|
168
|
+
# "mission_id": 0 # TODO remove?
|
|
169
|
+
# }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# {
|
|
173
|
+
# "Celestical_Object": "Earth", # Earth, Luna, Sun, Mars
|
|
174
|
+
# "Orbit_shell" : "LEO", # LEO, MEO, GEO
|
|
175
|
+
# "Altitude_km" : 515,
|
|
176
|
+
# "Epoch_UTC" : datetime.strptime("2024-05-27T07:32:00", "%Y-%m-%dT%H:%M:%S"),
|
|
177
|
+
# "Semi_major_Axis_km" : 6_786,
|
|
178
|
+
# "Eccentricity" : 0.0005551,
|
|
179
|
+
# "Inclination_degrees" : 97.4540,
|
|
180
|
+
# "Perigee_Height_km" : 385,
|
|
181
|
+
# "Apogee_Height_km" : 392,
|
|
182
|
+
# "RAAN_degrees" : 267.4498,
|
|
183
|
+
# "Argument_of_Perigee_degrees" : 256.8657,
|
|
184
|
+
# "Mean_Anomaly_at_Epoch_degrees" : 103.1980,
|
|
185
|
+
# "Revolutions_per_Day" : 15.59560910,
|
|
186
|
+
# "Orbit_Number_at_Epoch" : 23_446,
|
|
187
|
+
# "Time_Eclipse_min" : 38.512, # calculated by AI4CE software
|
|
188
|
+
# }
|
|
189
|
+
|
|
190
|
+
status, msg = _backend_POST(endpoint=f"/v2/projects/{project_id}/mission/", data=data)
|
|
191
|
+
if status == 201:
|
|
192
|
+
return msg
|
|
193
|
+
else:
|
|
194
|
+
print(f"Error setting mission/orbit info: {msg}")
|
|
195
|
+
return {"error": msg}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def get_enabled_components(nested_dict, enabled_components=None) -> list:
|
|
199
|
+
"""
|
|
200
|
+
Recursively traverses a nested dictionary to find and return a list of components that are enabled.
|
|
201
|
+
|
|
202
|
+
Parameters:
|
|
203
|
+
nested_dict (dict): The nested dictionary to traverse.
|
|
204
|
+
enabled_components (list, optional): A list to store the names of the enabled components.
|
|
205
|
+
Defaults to None, in which case a new list is created.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
list: A list of the names of the enabled components.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
if enabled_components is None:
|
|
212
|
+
enabled_components = []
|
|
213
|
+
|
|
214
|
+
for key, value in nested_dict.items():
|
|
215
|
+
if isinstance(value, dict):
|
|
216
|
+
if value.get('Enabled') == True:
|
|
217
|
+
enabled_components.append(key)
|
|
218
|
+
pass
|
|
219
|
+
get_enabled_components(value, enabled_components)
|
|
220
|
+
|
|
221
|
+
return enabled_components
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def traverse_and_modify(d: dict, sys_config_enabled: dict):
|
|
225
|
+
for key, value in d.items():
|
|
226
|
+
if isinstance(value, dict):
|
|
227
|
+
component = key
|
|
228
|
+
traverse_and_modify(d=value, sys_config_enabled=sys_config_enabled)
|
|
229
|
+
else:
|
|
230
|
+
# This block executes only if `value` is not a dict, i.e., at the deepest level
|
|
231
|
+
# if d["Enabled"] is True or d["Enable"] is True or :
|
|
232
|
+
try:
|
|
233
|
+
if d["Enabled"] is True:
|
|
234
|
+
sys_config_enabled.update(enable_component(
|
|
235
|
+
component_name=key, data=load_file(Path("ai4ce/backend_sys_default.json"))))
|
|
236
|
+
except KeyError as e:
|
|
237
|
+
print("Error: ", e)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def enable_component(component_name: str, data: dict) -> dict:
|
|
241
|
+
"""Recursively search for a component in the nested dictionary from the backend
|
|
242
|
+
and set 'enabled' to True if the feature name matches any key or value (case-insensitive).
|
|
243
|
+
|
|
244
|
+
Parameters:
|
|
245
|
+
data (dict): The nested dictionary to search within.
|
|
246
|
+
feature_name (str): The feature name to search for, case-insensitively.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
dict: edited dict
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
for key, value in data.items():
|
|
253
|
+
# print(key, value)
|
|
254
|
+
if isinstance(value, dict):
|
|
255
|
+
enable_component(component_name, value)
|
|
256
|
+
elif isinstance(value, list):
|
|
257
|
+
for item in value:
|
|
258
|
+
if isinstance(item, dict):
|
|
259
|
+
enable_component(component_name, item)
|
|
260
|
+
if key.lower() == component_name.lower() or str(value).lower() == component_name.lower():
|
|
261
|
+
data['enabled'] = True
|
|
262
|
+
return data
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def set_sys_arch(project_id: int, sys_arch: dict) -> dict:
|
|
266
|
+
"""A function to set the system architecture in the project database.
|
|
267
|
+
Params:
|
|
268
|
+
project_id (int): The project Identification Number
|
|
269
|
+
sys_arch(dict): Information about the satelllite's modules, which form the system architecture
|
|
270
|
+
Returns:
|
|
271
|
+
dict: html response from the backend to indicate success (200) or problems
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
sys_arch_example = {
|
|
275
|
+
"system": {
|
|
276
|
+
"eps": {
|
|
277
|
+
"enabled": True,
|
|
278
|
+
"battery": {
|
|
279
|
+
"enabled": True,
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"payload": {
|
|
283
|
+
"camera": {
|
|
284
|
+
"enabled": True,
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
response: dict = _backend_POST(
|
|
291
|
+
endpoint=f"/v2/projects/{project_id}/system/", data=sys_arch)
|
|
292
|
+
return response
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_sys_arch(project_id: int) -> dict:
|
|
296
|
+
"""A function to get the system configuration info for a specific project based on its ID.
|
|
297
|
+
Params:
|
|
298
|
+
project_id (int): The project Identification Number
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
dict: System information
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
response = _backend_GET(
|
|
305
|
+
endpoint=f"/v2/projects/{project_id}/system/")
|
|
306
|
+
return response
|
|
307
|
+
|
|
308
|
+
# set_comp_list(project: int, not_yet_defined: dict)
|
|
309
|
+
# For each selected system generator, we need a place to store the corresponding found comp lists
|
|
310
|
+
# Every generator can produce n sets of components
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def set_sys_generator(project_id: int, sys_gen_info: dict) -> dict:
|
|
314
|
+
response = _backend_POST(
|
|
315
|
+
endpoint=f"/v2/projects/{project_id}/sysgen/", data=sys_gen_info)
|
|
316
|
+
return response
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def update_sys_generator(project_id: int, sys_gen_info: dict) -> dict:
|
|
320
|
+
response = _backend_PUT(
|
|
321
|
+
endpoint=f"/v2/projects/{project_id}/sysgen/", data=sys_gen_info)
|
|
322
|
+
return response
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def set_trained_model(project_id: int, model_info: dict) -> dict:
|
|
326
|
+
"""A function to upload a trained AI model.
|
|
327
|
+
Params:
|
|
328
|
+
project_id (int): The project Identification Number
|
|
329
|
+
model (ASKYOUNES): info about model
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
dict: Upload server response
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
sta, msg = _backend_POST(endpoint=f"/v2/projects/{project_id}/sysgen/",
|
|
336
|
+
data=model_info)
|
|
337
|
+
|
|
338
|
+
return msg
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def get_prepared_system_generator_info(project_id: int) -> list:
|
|
342
|
+
"""A function to get all prepared system generators.
|
|
343
|
+
Params:
|
|
344
|
+
project_id (int): The project Identification Number
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
list: list of dictionaries with infos for the prepared system generators
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
response = _backend_GET(
|
|
351
|
+
endpoint=f"/v2/projects/{project_id}/sysgen/")
|
|
352
|
+
return response
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# def get_trained_model(project_id: int) -> mode: ASKYOUNES
|
|
356
|
+
# pass
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# def set_train_log(project_id: int, logs: ASKYOUNES) -> DB_response: ASKALEX
|
|
360
|
+
# pass
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# def get_train_logs(project_id: int) -> logs: ASKYOUNE pass
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def get_tags_from_string(str_of_tags: str) -> List[str]:
|
|
367
|
+
"""
|
|
368
|
+
example_tags_str = "['satellite', 'power', 'solar-panels']"
|
|
369
|
+
"""
|
|
370
|
+
# The string representation of a list of tags
|
|
371
|
+
|
|
372
|
+
# Convert the string to an actual list using ast.literal_eval
|
|
373
|
+
tags: list = ast.literal_eval(str_of_tags)
|
|
374
|
+
return tags
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def translate_tomlsystem_to_backend(system: dict, project_id: int) -> dict:
|
|
378
|
+
"""Translate a system configuration from a TOML file to a format that can be uploaded to the backend.
|
|
379
|
+
|
|
380
|
+
Parameters:
|
|
381
|
+
system (dict): The system configuration in TOML format.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
dict: The system configuration in a format that can be uploaded to the backend.
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
sys_config_enabled = {}
|
|
388
|
+
# Loading System Configuration
|
|
389
|
+
for key, value in system.items():
|
|
390
|
+
if isinstance(value, dict):
|
|
391
|
+
traverse_and_modify(d=value, sys_config_enabled=sys_config_enabled)
|
|
392
|
+
|
|
393
|
+
resp = set_sys_arch(project_id=project_id, sys_arch=sys_config_enabled)
|
|
394
|
+
pass
|
|
395
|
+
return resp
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def comp_create(comp_info: dict) -> dict:
|
|
399
|
+
"""Create a new component in the backend.
|
|
400
|
+
Params:
|
|
401
|
+
comp_info(dict): Information about the component
|
|
402
|
+
Returns:
|
|
403
|
+
dict:
|
|
404
|
+
"""
|
|
405
|
+
status_code, msg = _backend_POST(
|
|
406
|
+
endpoint=f"/v2/components/", data=comp_info)
|
|
407
|
+
return status_code, msg
|
|
408
|
+
|
|
409
|
+
def get_all_decisions() -> dict:
|
|
410
|
+
"""Fetches all decisions from the DecisionDB and returns them as a dictionary.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
dict: A dictionary containing all decisions fetched from the DecisionDB.
|
|
414
|
+
"""
|
|
415
|
+
response = _backend_GET(endpoint="/v2/components/export/")
|
|
416
|
+
return response
|
|
417
|
+
|
|
418
|
+
def get_comp_statistics() -> dict:
|
|
419
|
+
|
|
420
|
+
response =_backend_GET(endpoint="/v2/streamlit/db/stats/components")
|
|
421
|
+
return response
|
|
422
|
+
|