algomancy-gui 0.3.16__py3-none-any.whl

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.
Files changed (46) hide show
  1. algomancy_gui/__init__.py +0 -0
  2. algomancy_gui/admin_page/__init__.py +1 -0
  3. algomancy_gui/admin_page/admin.py +362 -0
  4. algomancy_gui/admin_page/sessions.py +57 -0
  5. algomancy_gui/appconfiguration.py +291 -0
  6. algomancy_gui/compare_page/__init__.py +1 -0
  7. algomancy_gui/compare_page/compare.py +360 -0
  8. algomancy_gui/compare_page/kpicard.py +236 -0
  9. algomancy_gui/compare_page/scenarioselector.py +99 -0
  10. algomancy_gui/componentids.py +177 -0
  11. algomancy_gui/contentregistry.py +167 -0
  12. algomancy_gui/cqmloader.py +58 -0
  13. algomancy_gui/data_page/__init__.py +1 -0
  14. algomancy_gui/data_page/data.py +77 -0
  15. algomancy_gui/data_page/datamanagementdeletemodal.py +260 -0
  16. algomancy_gui/data_page/datamanagementderivemodal.py +201 -0
  17. algomancy_gui/data_page/datamanagementdownloadmodal.py +193 -0
  18. algomancy_gui/data_page/datamanagementimportmodal.py +438 -0
  19. algomancy_gui/data_page/datamanagementsavemodal.py +191 -0
  20. algomancy_gui/data_page/datamanagementtopbar.py +123 -0
  21. algomancy_gui/data_page/datamanagementuploadmodal.py +366 -0
  22. algomancy_gui/data_page/dialogcallbacks.py +51 -0
  23. algomancy_gui/data_page/filenamematcher.py +109 -0
  24. algomancy_gui/defaultloader.py +36 -0
  25. algomancy_gui/gui_launcher.py +183 -0
  26. algomancy_gui/home_page/__init__.py +1 -0
  27. algomancy_gui/home_page/home.py +16 -0
  28. algomancy_gui/layout.py +199 -0
  29. algomancy_gui/layouthelpers.py +30 -0
  30. algomancy_gui/managergetters.py +28 -0
  31. algomancy_gui/overview_page/__init__.py +1 -0
  32. algomancy_gui/overview_page/overview.py +20 -0
  33. algomancy_gui/py.typed +0 -0
  34. algomancy_gui/scenario_page/__init__.py +0 -0
  35. algomancy_gui/scenario_page/delete_confirmation.py +29 -0
  36. algomancy_gui/scenario_page/new_scenario_creator.py +104 -0
  37. algomancy_gui/scenario_page/new_scenario_parameters_window.py +154 -0
  38. algomancy_gui/scenario_page/scenario_badge.py +36 -0
  39. algomancy_gui/scenario_page/scenario_cards.py +119 -0
  40. algomancy_gui/scenario_page/scenarios.py +596 -0
  41. algomancy_gui/sessionmanager.py +168 -0
  42. algomancy_gui/settingsmanager.py +43 -0
  43. algomancy_gui/stylingconfigurator.py +740 -0
  44. algomancy_gui-0.3.16.dist-info/METADATA +71 -0
  45. algomancy_gui-0.3.16.dist-info/RECORD +46 -0
  46. algomancy_gui-0.3.16.dist-info/WHEEL +4 -0
@@ -0,0 +1,236 @@
1
+ """
2
+ kpicard.py - KPI Card Component
3
+
4
+ This module defines functions for creating and formatting KPI cards that display
5
+ performance metrics and comparisons between scenarios.
6
+ """
7
+
8
+ from dash import html
9
+ import dash_bootstrap_components as dbc
10
+
11
+ from algomancy_scenario import ImprovementDirection, BASE_KPI
12
+ from algomancy_utils import Measurement
13
+
14
+
15
+ def is_improvement_good(better_when, left, right):
16
+ """
17
+ Determine if the change between left and right values is positive according to the measurement direction.
18
+
19
+ Args:
20
+ better_when: Direction in which improvement is measured (higher or lower)
21
+ left: Left value to compare
22
+ right: Right value to compare
23
+
24
+ Returns:
25
+ bool or None: True if the change is positive, False if negative, None if can't determine
26
+ """
27
+ if left is None or right is None:
28
+ return None
29
+ if better_when == ImprovementDirection.HIGHER:
30
+ return right > left
31
+ if better_when == ImprovementDirection.LOWER:
32
+ return right < left
33
+ return None
34
+
35
+
36
+ def get_delta_binary(left_kpi: BASE_KPI, right_kpi: BASE_KPI):
37
+ left_value = 1 if left_kpi.success else 0
38
+ right_value = 1 if right_kpi.success else 0
39
+
40
+ delta = right_value - left_value
41
+ is_good = delta > 0
42
+
43
+ if abs(delta) < 1e-10: # Handle floating point precision
44
+ return "No change", "-", "text-muted"
45
+
46
+ arrow = "🡅" if is_good else "🡇"
47
+
48
+ # Create delta measurement with same unit as scaled measurements
49
+ delta_str = f"{left_kpi.pretty()} {arrow} {right_kpi.pretty()}"
50
+
51
+ if is_good:
52
+ details = "Right passes but left does not"
53
+ else:
54
+ details = "Left passes but right does not"
55
+
56
+ color_class = "text-success" if is_good else "text-danger"
57
+ return delta_str, details, color_class
58
+
59
+
60
+ def get_delta_default(left_kpi: BASE_KPI, right_kpi: BASE_KPI):
61
+ """
62
+ Determine difference, percentage, and color between two measurements.
63
+ Scales left measurement and matches right to the same unit.
64
+
65
+ Args:
66
+ left_kpi: The left measurement to compare
67
+ right_kpi: The right measurement to compare
68
+
69
+ Returns:
70
+ tuple: A tuple containing (delta string, percentage string, color class)
71
+ """
72
+ left_measurement: Measurement = left_kpi.measurement
73
+ right_measurement: Measurement = right_kpi.measurement
74
+ better_when: ImprovementDirection = left_kpi.better_when
75
+
76
+ # Handle None or uninitialized measurements
77
+ if (
78
+ left_measurement is None
79
+ or right_measurement is None
80
+ or left_measurement.value == Measurement.INITIAL_VALUE
81
+ or right_measurement.value == Measurement.INITIAL_VALUE
82
+ ):
83
+ return "No data", "-", "text-muted"
84
+
85
+ # Scale left measurement first
86
+ left_scaled = left_measurement.scale()
87
+
88
+ # Match right measurement to left's scaled unit
89
+ try:
90
+ right_scaled = right_measurement.scale_to_unit(left_scaled.unit)
91
+ except ValueError:
92
+ # Units are incompatible
93
+ return "Incompatible units", "-", "text-warning"
94
+
95
+ # Get the actual values for comparison
96
+ left_value = left_scaled.value
97
+ right_value = right_scaled.value
98
+
99
+ delta = right_value - left_value
100
+ is_good = is_improvement_good(better_when, left_value, right_value)
101
+
102
+ if abs(delta) < 1e-10: # Handle floating point precision
103
+ return "No change", "-", "text-muted"
104
+
105
+ arrow = "🡅" if delta > 0 else "🡇"
106
+
107
+ # Create delta measurement with same unit as scaled measurements
108
+ delta_measurement = Measurement(left_scaled.base_measurement, abs(delta))
109
+ delta_str = f"{arrow} {delta_measurement.pretty()}"
110
+
111
+ try:
112
+ delta_perc = (delta / left_value * 100) if abs(left_value) > 1e-10 else 0
113
+ except ZeroDivisionError:
114
+ delta_perc = 0
115
+
116
+ verdict = "better" if is_good else "worse"
117
+ delta_perc_str = f"Right is relatively {abs(delta_perc):.1f}% {verdict} than left"
118
+
119
+ color_class = "text-success" if is_good else "text-danger"
120
+ return delta_str, delta_perc_str, color_class
121
+
122
+
123
+ def get_delta_infos(left_kpi: BASE_KPI, right_kpi: BASE_KPI):
124
+ if left_kpi.is_binary_kpi:
125
+ return get_delta_binary(left_kpi, right_kpi)
126
+ else:
127
+ return get_delta_default(left_kpi, right_kpi)
128
+
129
+
130
+ def kpi_card(
131
+ left_kpi: BASE_KPI,
132
+ right_kpi: BASE_KPI,
133
+ ):
134
+ """
135
+ Create a compact KPI comparison card without excessive height.
136
+ Automatically scales the left measurement and matches the right to the same unit.
137
+
138
+ Args:
139
+ left_kpi
140
+ right_kpi
141
+
142
+ Returns:
143
+ dbc.Card: A Dash Bootstrap card component displaying the KPI comparison
144
+ """
145
+
146
+ # Extract unit symbol for display
147
+ unit = left_kpi.get_pretty_unit()
148
+
149
+ kpi_type = str(left_kpi.better_when).capitalize().replace("_", " ") + (
150
+ f" {left_kpi.get_threshold_str(unit)}"
151
+ if left_kpi.is_binary_kpi
152
+ else " is better"
153
+ )
154
+
155
+ header = html.Div(
156
+ [
157
+ # Left group: name + optional unit
158
+ html.Div(
159
+ [
160
+ html.Span(str(left_kpi.name), className="fw-bold"),
161
+ # html.Span(f" ({unit_symbol})", className="text-secondary ms-1") if unit_symbol else None,
162
+ ],
163
+ style={"display": "flex", "alignItems": "center", "gap": "0.25rem"},
164
+ ),
165
+ # Right-aligned kpi_type (same muted style as unit)
166
+ html.Div(
167
+ html.Span(kpi_type, className="text-secondary"),
168
+ style={
169
+ "display": "flex",
170
+ "alignItems": "center",
171
+ "justifyContent": "flex-end",
172
+ "fontSize": "0.7rem",
173
+ },
174
+ ),
175
+ ],
176
+ # Make the header take full width and push the right group to the far right
177
+ style={
178
+ "fontSize": "1rem",
179
+ "display": "flex",
180
+ "alignItems": "center",
181
+ "justifyContent": "space-between",
182
+ "width": "100%",
183
+ "marginBottom": "10px",
184
+ },
185
+ )
186
+
187
+ values = html.Div(
188
+ [
189
+ html.Small(
190
+ f"Left: {left_kpi.details(unit)}",
191
+ style={"flex": "1", "textAlign": "left"},
192
+ ),
193
+ html.Small(
194
+ f"Right: {right_kpi.details(unit)}",
195
+ style={"flex": "1", "textAlign": "right"},
196
+ ),
197
+ ],
198
+ className="text-muted",
199
+ style={
200
+ "fontSize": "0.85rem",
201
+ "lineHeight": "1",
202
+ "marginBottom": "2px",
203
+ "display": "flex",
204
+ "width": "100%",
205
+ },
206
+ )
207
+
208
+ delta_str, delta_perc_str, color_class = get_delta_infos(left_kpi, right_kpi)
209
+ delta = html.Div(
210
+ [
211
+ # Second row: Change, centered
212
+ html.Div(
213
+ html.H2(f"{delta_str}", className=color_class),
214
+ style={"width": "100%", "textAlign": "center", "marginTop": "0.3em"},
215
+ ),
216
+ # Third row: Change percent, centered
217
+ html.Div(
218
+ html.H6(f"{delta_perc_str}", className=color_class),
219
+ style={"width": "100%", "textAlign": "center", "marginTop": "0.0em"},
220
+ ),
221
+ ]
222
+ )
223
+
224
+ return dbc.Card(
225
+ dbc.CardBody(
226
+ [header, values, delta], className="p-2", style={"display": "block"}
227
+ ),
228
+ className="shadow-sm bg-light",
229
+ style={
230
+ "margin": "2px",
231
+ "borderRadius": "0.5rem",
232
+ "boxShadow": "0 1px 2px rgba(0,0,0,0.07)",
233
+ "height": "auto",
234
+ "display": "block",
235
+ },
236
+ )
@@ -0,0 +1,99 @@
1
+ """
2
+ scenarioselector.py - Scenario Selection Component
3
+
4
+ This module defines the scenario selector component for the compare dashboard page.
5
+ It allows users to select and compare two different scenarios side by side.
6
+ """
7
+
8
+ import dash_bootstrap_components as dbc
9
+ from dash import html, dcc, get_app
10
+
11
+ from algomancy_scenario import ScenarioManager
12
+
13
+ from ..layouthelpers import create_wrapped_content_div
14
+ from ..componentids import (
15
+ LEFT_SCENARIO_DROPDOWN,
16
+ RIGHT_SCENARIO_DROPDOWN,
17
+ LEFT_SCENARIO_OVERVIEW,
18
+ RIGHT_SCENARIO_OVERVIEW,
19
+ PERF_SBS_LEFT_COLLAPSE,
20
+ PERF_SBS_RIGHT_COLLAPSE,
21
+ )
22
+ from ..settingsmanager import SettingsManager
23
+
24
+
25
+ # === Helper ===
26
+ def get_completed_scenarios(scenario_manager: ScenarioManager):
27
+ return [
28
+ {"label": s.tag, "value": s.id}
29
+ for s in scenario_manager.list_scenarios()
30
+ if s.is_completed()
31
+ ]
32
+
33
+
34
+ def create_side_by_side_selector(scenario_manager: ScenarioManager):
35
+ selector = dbc.Row(
36
+ [
37
+ dbc.Col(
38
+ [
39
+ html.Label("Left Scenario"),
40
+ dcc.Dropdown(
41
+ id=LEFT_SCENARIO_DROPDOWN,
42
+ options=get_completed_scenarios(scenario_manager),
43
+ placeholder="Select completed scenario",
44
+ ),
45
+ ],
46
+ width=6,
47
+ ),
48
+ dbc.Col(
49
+ [
50
+ html.Label("Right Scenario"),
51
+ dcc.Dropdown(
52
+ id=RIGHT_SCENARIO_DROPDOWN,
53
+ options=get_completed_scenarios(scenario_manager),
54
+ placeholder="Select completed scenario",
55
+ ),
56
+ ],
57
+ width=6,
58
+ ),
59
+ ],
60
+ className="mb-4",
61
+ )
62
+
63
+ return selector
64
+
65
+
66
+ def create_side_by_side_viewer():
67
+ settings: SettingsManager = get_app().server.settings
68
+ left_overview = create_wrapped_content_div(
69
+ html.Div(id=LEFT_SCENARIO_OVERVIEW, className="mt-3 compare-sbs-content"),
70
+ show_loading=settings.show_loading_on_comparepage,
71
+ cqm=settings.use_cqm_loader,
72
+ spinner_scale=1.5,
73
+ )
74
+ right_overview = create_wrapped_content_div(
75
+ html.Div(id=RIGHT_SCENARIO_OVERVIEW, className="mt-3"),
76
+ show_loading=settings.show_loading_on_comparepage,
77
+ cqm=settings.use_cqm_loader,
78
+ spinner_scale=1.5,
79
+ )
80
+
81
+ viewer = dbc.Row(
82
+ [
83
+ dbc.Col(
84
+ [
85
+ dbc.Collapse(left_overview, id=PERF_SBS_LEFT_COLLAPSE),
86
+ ],
87
+ width=6,
88
+ ),
89
+ dbc.Col(
90
+ [
91
+ dbc.Collapse(right_overview, id=PERF_SBS_RIGHT_COLLAPSE),
92
+ ],
93
+ width=6,
94
+ ),
95
+ ],
96
+ className="side-by-side-viewer",
97
+ )
98
+
99
+ return viewer
@@ -0,0 +1,177 @@
1
+ """
2
+ componentids.py - Component ID Constants
3
+
4
+ This module defines constants for all component IDs used in the dashboard application.
5
+ These IDs are used for callbacks and to reference components in the Dash application.
6
+ """
7
+
8
+ # === Session IDs ===
9
+ ACTIVE_SESSION = "active-session-id"
10
+
11
+ # === Styling ===
12
+ GLOBAL_STYLING_STORE = "global-styling"
13
+ PAGE_CONTENT = "page-content"
14
+
15
+ # === Home IDs ===
16
+ HOME_PAGE_CONTENT = "home-page-content"
17
+
18
+ # === Data IDs ===
19
+ DATA_PAGE = "data-page"
20
+ DM_LIST_UPDATER_STORE = "dm-list-updater"
21
+
22
+ DATA_PAGE_CONTENT = "data-page-content"
23
+ DATA_SELECTOR_DROPDOWN = "data-selector-dropdown"
24
+
25
+ DATA_MAN_SUCCESS_ALERT = "data-man-success-alert"
26
+ DATA_MAN_ERROR_ALERT = "data-man-error-alert"
27
+
28
+ DM_DERIVE_OPEN_BTN = "dm-derive-open-btn"
29
+ DM_DERIVE_MODAL = "dm-derive-modal"
30
+ DM_DERIVE_MODAL_CLOSE_BTN = "dm-derive-modal-close-btn"
31
+ DM_DERIVE_MODAL_SUBMIT_BTN = "dm-derive-modal-submit-btn"
32
+ DM_DERIVE_SET_SELECTOR = "dm-derive-set-selector"
33
+ DM_DERIVE_SET_NAME_INPUT = "dm-derive-set-name-input"
34
+
35
+ DM_DELETE_OPEN_BUTTON = "dm-delete-open-btn"
36
+ DM_DELETE_SUBMIT_BUTTON = "DATA_MAN_DELETE_BTN"
37
+ DM_DELETE_SET_SELECTOR = "data-man-delete-selector"
38
+ DM_DELETE_CLOSE_BUTTON = "data-man-delete-close-btn"
39
+ DM_DELETE_MODAL = "data-man-delete-modal"
40
+ DM_DELETE_COLLAPSE = "data-man-delete-collapse"
41
+ DM_DELETE_CONFIRM_INPUT = "data-man-delete-confirm-input"
42
+
43
+ DM_SAVE_MODAL = "dm-save-modal"
44
+ DM_SAVE_OPEN_BUTTON = "dm-save-open-btn"
45
+ DM_SAVE_SUBMIT_BUTTON = "dm-save-submit-btn"
46
+ DM_SAVE_MODAL_CLOSE_BTN = "dm-save-modal-close-btn"
47
+ DM_SAVE_SET_SELECTOR = "dm-save-set-selector"
48
+
49
+ DM_IMPORT_MODAL = "dm-import-modal"
50
+ DM_IMPORT_OPEN_BUTTON = "dm-load-open-btn"
51
+ DM_IMPORT_UPLOADER = "dm-load-uploader"
52
+ DM_IMPORT_SUBMIT_BUTTON = "dm-load-submit-btn"
53
+ DM_IMPORT_MODAL_CLOSE_BTN = "dm-load-modal-close-btn"
54
+ DM_IMPORT_MODAL_FILEVIEWER_COLLAPSE = "dm-load-modal-fileviewer-collapse"
55
+ DM_IMPORT_MODAL_FILEVIEWER_CARD = "dm-load-modal-fileviewer-card"
56
+ DM_IMPORT_MODAL_NAME_INPUT = "dm-load-modal-name-input"
57
+ DM_IMPORT_MODAL_FILEVIEWER_ALERT = "dm-load-modal-fileviewer-alert"
58
+
59
+ DM_UPLOAD_OPEN_BUTTON = "dm-upload-open-btn"
60
+ DM_UPLOAD_MODAL = "dm-upload-modal"
61
+ DM_UPLOAD_UPLOADER = "dm-upload-uploader"
62
+ DM_UPLOAD_SUBMIT_BUTTON = "dm-upload-submit-btn"
63
+ DM_UPLOAD_MODAL_CLOSE_BTN = "dm-upload-modal-close-btn"
64
+ DM_UPLOAD_MODAL_FILEVIEWER_CARD = "dm-upload-modal-fileviewer-card"
65
+ DM_UPLOAD_MODAL_FILEVIEWER_COLLAPSE = "dm-upload-modal-fileviewer-collapse"
66
+ DM_UPLOAD_MODAL_FILEVIEWER_ALERT = "dm-upload-modal-fileviewer-alert"
67
+ DM_UPLOAD_DATA_STORE = "dm-upload-data-store"
68
+ DM_UPLOAD_SUCCESS_ALERT = "dm-upload-success-alert"
69
+
70
+ DM_DOWNLOAD_OPEN_BUTTON = "dm-download-open-btn"
71
+ DM_DOWNLOAD_MODAL = "dm-download-modal"
72
+ DM_DOWNLOAD_CHECKLIST = "dm-download-checklist"
73
+ DM_DOWNLOAD_SUBMIT_BUTTON = "dm-download-submit-btn"
74
+ DM_DOWNLOAD_MODAL_CLOSE_BTN = "dm-download-modal-close-btn"
75
+
76
+ # === Scenario IDs ===
77
+ SCENARIO_PAGE = "scenario-page"
78
+
79
+ SCENARIO_LIST = "scenario-list"
80
+ SCENARIO_DELETE_MODAL = "delete-modal"
81
+ SCENARIO_CREATE_STATUS = "create-status"
82
+ SCENARIO_TO_DELETE = "scenario-to-delete"
83
+ SCENARIO_SELECTED = "selected-scenario"
84
+ SCENARIO_SELECTED_ID_STORE = "selected-scenario-id"
85
+
86
+ SCENARIO_CREATOR_MODAL = "scenario-creator-modal"
87
+ SCENARIO_CREATOR_OPEN_BUTTON = "scenario-creator-open-btn"
88
+
89
+ SCENARIO_NEW_BUTTON = "create-scenario-button"
90
+ SCENARIO_PROCESS_BUTTON = "scenario-process-button"
91
+ SCENARIO_DELETE_BUTTON = "scenario-delete-button"
92
+ SCENARIO_CONFIRM_DELETE_BUTTON = "scenario-confirm-delete-button"
93
+ SCENARIO_CANCEL_DELETE_BUTTON = "scenario-cancel-delete-button"
94
+
95
+ SCENARIO_STATUS_UPDATE_EVENT = "scenario-status-update-event"
96
+ SCENARIO_STATUS_BADGE = "scenario-status-badge"
97
+
98
+ SCENARIO_CARD = "scenario-card"
99
+ SCENARIO_TAG_INPUT = "scenario-tag-input"
100
+ SCENARIO_DATA_INPUT = "scenario-data-input"
101
+ SCENARIO_ALGO_INPUT = "scenario-algo-input"
102
+ SCENARIO_PARAMS_INPUT = "scenario-params-input"
103
+
104
+ SCENARIO_STATUS_SNAPSHOT = "scenario-status-snapshot"
105
+ SCENARIO_STATUS_UPDATE = "scenario-status-update"
106
+
107
+ SCENARIO_ALERT = "scenario-alert"
108
+
109
+ ALGO_PARAMS_WINDOW_ID = "algo-params-window"
110
+ ALGO_PARAMS_ENTRY_TAB = "algo-params-entry"
111
+ ALGO_PARAMS_UPLOAD_TAB = "algo-params-upload"
112
+ ALGO_PARAMS_ENTRY_CARD = "algo-params-entry-card"
113
+ ALGO_PARAM_INPUT = "algo-param-input"
114
+ ALGO_PARAM_DATE_INPUT = "algo-param-date-input"
115
+ ALGO_PARAM_INTERVAL_INPUT = "algo-param-interval-input"
116
+
117
+ SCENARIO_PROG_INTERVAL = "scenario-progress-polling-interval"
118
+ SCENARIO_PROG_BAR = "scenario-progress-bar"
119
+ SCENARIO_PROG_TEXT = "scenario-progress-text"
120
+ SCENARIO_PROG_COLLAPSE = "scenario-progress-collapse"
121
+ SCENARIO_CURRENTLY_RUNNING_STORE = "scenario-currently-running"
122
+
123
+ SCENARIO_LIST_UPDATE_STORE = "scenario-list-update-store"
124
+
125
+ # === Component IDs ===
126
+ LEFT_SCENARIO_DROPDOWN = "left-scenario-dropdown"
127
+ RIGHT_SCENARIO_DROPDOWN = "right-scenario-dropdown"
128
+
129
+ LEFT_SCENARIO_OVERVIEW = "left-scenario-overview"
130
+ RIGHT_SCENARIO_OVERVIEW = "right-scenario-overview"
131
+
132
+ KPI_IMPROVEMENT_SECTION = "kpi-improvement-section"
133
+ PERF_PRIMARY_RESULTS = "perf-primary-results"
134
+ PERF_SECONDARY_RESULTS = "perf-secondary-results"
135
+
136
+ PERF_DETAILS_COLLAPSE = "secondary-results-collapse"
137
+ LEFT_SECONDARY_RESULTS = "left-secondary-results"
138
+ RIGHT_SECONDARY_RESULTS = "right-secondary-results"
139
+
140
+ PERF_TOGGLE_CHECKLIST_LEFT = "toggle-checklist-left"
141
+ PERF_TOGGLE_CHECKLIST_RIGHT = "toggle-checklist-right"
142
+ PERF_SBS_LEFT_COLLAPSE = "perf-sbs-left-collapse"
143
+ PERF_SBS_RIGHT_COLLAPSE = "perf-sbs-right-collapse"
144
+ PERF_KPI_COLLAPSE = "perf-kpi-collapse"
145
+ PERF_COMPARE_COLLAPSE = "perf-compare-collapse"
146
+ HOW_TO_CREATE_NEW_SESSION = "how-to-create-new-session"
147
+
148
+
149
+ # === Compare Page IDs ===
150
+ COMPARE_PAGE = "compare-page"
151
+ COMPARE_DETAIL_VIEW = "compare-detail-view"
152
+
153
+ # === Overview Page IDs ===
154
+ # OVERVIEW_TABLE = "overview-table"
155
+ # OVERVIEW_UPDATE_INTERVAL = "overview-update-interval"
156
+ OVERVIEW_PAGE_CONTENT = "overview-page-content"
157
+
158
+ # === Admin Page IDs ===
159
+ ADMIN_PAGE = "admin-page"
160
+ ADMIN_NEW_SESSION = "admin-new-session"
161
+ ADMIN_COPY_SESSION = "admin-copy-session"
162
+ ADMIN_SELECT_SESSION = "admin-select-session"
163
+ ADMIN_LOG_WINDOW = "admin-log-window"
164
+ ADMIN_LOG_INTERVAL = "admin-log-interval"
165
+ ADMIN_LOG_FILTER = "admin-log-filter"
166
+ NEW_SESSION_BUTTON = "new-session-button"
167
+ SESSION_CREATOR_MODAL = "session-creator-modal"
168
+ NEW_SESSION_NAME = "new-session-name"
169
+
170
+ # === Layout IDs ===
171
+ SIDEBAR = "sidebar"
172
+ SIDEBAR_TOGGLE = "sidebar-toggle"
173
+ SIDEBAR_COLLAPSED = "sidebar-collapsed"
174
+
175
+ # === Notification related ===
176
+ SCENARIO_NOTIFICATION = "scenario-notification"
177
+ SCENARIO_NOTIFICATION_STORE = "scenario-notification-store"
@@ -0,0 +1,167 @@
1
+ from typing import Callable, List
2
+
3
+ from dash import html
4
+
5
+ from algomancy_data import BASE_DATA_BOUND
6
+ from algomancy_content.pages.page import (
7
+ HomePage,
8
+ DataPage,
9
+ ScenarioPage,
10
+ ComparePage,
11
+ OverviewPage,
12
+ )
13
+ from algomancy_scenario import Scenario
14
+
15
+
16
+ class ContentRegistry:
17
+ def __init__(self):
18
+ self._home_content: Callable[[], html.Div] | None = None
19
+ self._data_content: Callable[[BASE_DATA_BOUND], html.Div] | None = None
20
+ self._scenario_content: Callable[[Scenario], html.Div] | None = None
21
+ self._compare_side_by_side: Callable[[Scenario, str], html.Div] | None = None
22
+ self._compare_compare: Callable[[Scenario, Scenario], html.Div] | None = None
23
+ self._compare_details: Callable[[Scenario, Scenario], html.Div] | None = None
24
+ self._overview_content: Callable[[List[Scenario]], html.Div] | None = None
25
+
26
+ def register_pages(
27
+ self,
28
+ home_page: HomePage,
29
+ data_page: DataPage,
30
+ scenario_page: ScenarioPage,
31
+ compare_page: ComparePage,
32
+ overview_page: OverviewPage,
33
+ ) -> None:
34
+ self._register_home_page(home_page)
35
+ self._register_data_page(data_page)
36
+ self._register_scenario_page(scenario_page)
37
+ self._register_compare_page(compare_page)
38
+ self._register_overview_page(overview_page)
39
+
40
+ def _register_home_page(self, page: HomePage) -> None:
41
+ self._home_content = page.create_content
42
+ page.register_callbacks()
43
+
44
+ def _register_data_page(self, page: DataPage) -> None:
45
+ self._data_content = page.create_content
46
+ page.register_callbacks()
47
+
48
+ def _register_scenario_page(self, page: ScenarioPage) -> None:
49
+ self._scenario_content = page.create_content
50
+ page.register_callbacks()
51
+
52
+ def _register_compare_page(self, page: ComparePage) -> None:
53
+ self._compare_side_by_side = page.create_side_by_side_content
54
+ self._compare_compare = page.create_compare_section
55
+ self._compare_details = page.create_details_section
56
+ page.register_callbacks()
57
+
58
+ def _register_overview_page(self, page: OverviewPage) -> None:
59
+ self._overview_content = page.create_content
60
+ page.register_callbacks()
61
+
62
+ @property
63
+ def home_content(self) -> Callable[[], html.Div]:
64
+ if self._home_content:
65
+ return self._home_content
66
+ else:
67
+
68
+ def default_content():
69
+ return html.Div(
70
+ [
71
+ html.H1("Home content was not filled."),
72
+ ]
73
+ )
74
+
75
+ return default_content
76
+
77
+ @property
78
+ def data_content(self) -> Callable[[BASE_DATA_BOUND], html.Div]:
79
+ if self._data_content:
80
+ return self._data_content
81
+ else:
82
+
83
+ def default_content(data: BASE_DATA_BOUND):
84
+ return html.Div(
85
+ [
86
+ html.H1("Data content was not filled."),
87
+ html.H2(f"Data source: {data.name}"),
88
+ ]
89
+ )
90
+
91
+ return default_content
92
+
93
+ @property
94
+ def scenario_content(self) -> Callable[[Scenario], html.Div]:
95
+ if self._scenario_content:
96
+ return self._scenario_content
97
+ else:
98
+
99
+ def default_content(scenario: Scenario):
100
+ return html.Div(
101
+ [
102
+ html.H1("Scenario content was not filled."),
103
+ html.H2(f"Scenario: {scenario.tag}"),
104
+ ]
105
+ )
106
+
107
+ return default_content
108
+
109
+ @property
110
+ def compare_side_by_side(self) -> Callable[[Scenario, str], html.Div]:
111
+ if self._compare_side_by_side:
112
+ return self._compare_side_by_side
113
+ else:
114
+
115
+ def default_content(scenario: Scenario, side: str):
116
+ return html.Div(
117
+ [
118
+ html.H1("Compare side by side content was not filled."),
119
+ ]
120
+ )
121
+
122
+ return default_content
123
+
124
+ @property
125
+ def compare_compare(self) -> Callable[[Scenario, Scenario], html.Div]:
126
+ if self._compare_compare:
127
+ return self._compare_compare
128
+ else:
129
+
130
+ def default_content(scenario1: Scenario, scenario2: Scenario):
131
+ return html.Div(
132
+ [
133
+ html.H1("Compare content was not filled."),
134
+ ]
135
+ )
136
+
137
+ return default_content
138
+
139
+ @property
140
+ def compare_details(self) -> Callable[[Scenario, Scenario], html.Div]:
141
+ if self._compare_details:
142
+ return self._compare_details
143
+ else:
144
+
145
+ def default_content(scenario1: Scenario, scenario2: Scenario):
146
+ return html.Div(
147
+ [
148
+ html.H1("Compare details content was not filled."),
149
+ ]
150
+ )
151
+
152
+ return default_content
153
+
154
+ @property
155
+ def overview_content(self) -> Callable[[List[Scenario]], html.Div]:
156
+ if self._overview_content:
157
+ return self._overview_content
158
+ else:
159
+
160
+ def default_content(scenarios: List[Scenario]):
161
+ return html.Div(
162
+ [
163
+ html.H1("Overview content was not filled."),
164
+ ]
165
+ )
166
+
167
+ return default_content