first commit
This commit is contained in:
0
streamlit_dashboard/utility_classes/__init__.py
Normal file
0
streamlit_dashboard/utility_classes/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
153
streamlit_dashboard/utility_classes/base_report_page.py
Normal file
153
streamlit_dashboard/utility_classes/base_report_page.py
Normal file
@@ -0,0 +1,153 @@
|
||||
import logging
|
||||
from typing import List, Any, Dict
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import streamlit as st
|
||||
from streamlit.delta_generator import DeltaGenerator
|
||||
|
||||
from utility_classes.dashboard_config_parser import DashboardConfig
|
||||
from streamlit_constants import DASHBOARD_CONFIG_OBJECT_KEY
|
||||
|
||||
class Renderable(ABC):
|
||||
"""
|
||||
Abstract base class for UI components drawn to a Streamlit container.
|
||||
|
||||
Provides a strict contract for rendering and manages unique identifier generation.
|
||||
This structure is critical in Streamlit to prevent widget state collisions
|
||||
when multiple instances of the same UI component are rendered simultaneously.
|
||||
|
||||
:param instance_id: A unique identifier for this specific rendered instance.
|
||||
:type instance_id: str
|
||||
"""
|
||||
def __init__(self, instance_id: str):
|
||||
self.instance_id = instance_id
|
||||
|
||||
@abstractmethod
|
||||
def render(self, container: DeltaGenerator):
|
||||
"""
|
||||
Renders the component's UI elements onto the specified Streamlit container.
|
||||
|
||||
:param container: The Streamlit layout element where this component will be drawn.
|
||||
:type container: DeltaGenerator
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_widget_key(self, widget_unique_id: str) -> str:
|
||||
"""
|
||||
Generates a globally unique Streamlit widget key for this specific instance.
|
||||
|
||||
:param widget_unique_id: The local identifier for the specific widget.
|
||||
:type widget_unique_id: str
|
||||
:return: A concatenated string combining the instance ID and widget ID.
|
||||
:rtype: str
|
||||
"""
|
||||
return f'{self.instance_id}_{widget_unique_id}'
|
||||
|
||||
class BaseReportPage(Renderable):
|
||||
"""
|
||||
Standardizes the rendering pipeline for all analytical report pages.
|
||||
|
||||
This class breaks down UI generation into a predictable, synchronous lifecycle:
|
||||
1. Render controls (capture inputs).
|
||||
2. Generate figures (process data).
|
||||
3. Render figures (display outputs).
|
||||
This strict pipeline allows higher-level orchestrators to intercept the process
|
||||
mid-cycle (e.g., to mutate generated figures before they are drawn).
|
||||
|
||||
:param title: The title of the page, acting as its base instance ID.
|
||||
:type title: str
|
||||
"""
|
||||
def __init__(self, title:str):
|
||||
# set the instance id to the title of the page
|
||||
super().__init__(title)
|
||||
self.title = title
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.app_config:DashboardConfig = st.session_state[DASHBOARD_CONFIG_OBJECT_KEY]
|
||||
|
||||
@staticmethod
|
||||
def get_page_name():
|
||||
"""
|
||||
Retrieves the human-readable name of the report for UI selection menus.
|
||||
|
||||
:return: The display name of the report.
|
||||
:rtype: str
|
||||
"""
|
||||
return ""
|
||||
|
||||
def render_controls(self, container: DeltaGenerator) -> Dict[str, Any]:
|
||||
"""
|
||||
Renders the input widgets required to parameterize the report.
|
||||
|
||||
:param container: The Streamlit container to draw the widgets onto.
|
||||
:type container: DeltaGenerator
|
||||
:return: A dictionary of user-selected parameters, or None if inputs are invalid.
|
||||
:rtype: Dict[str, Any]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def generate_figures(self, parameters: Dict[str, Any]):
|
||||
"""
|
||||
Processes parameters and generates the underlying data/figures for the report.
|
||||
|
||||
:param parameters: The dictionary of user inputs captured from render_controls.
|
||||
:type parameters: Dict[str, Any]
|
||||
:return: A dictionary of computed outputs or Plotly figures.
|
||||
:rtype: Dict[str, Any]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def render_figures(self, container: DeltaGenerator, output_data: Dict[str, Any]):
|
||||
"""
|
||||
Draws the previously generated figures and data onto the screen.
|
||||
|
||||
:param container: The Streamlit container to draw the visuals onto.
|
||||
:type container: DeltaGenerator
|
||||
:param output_data: The computed results returned by generate_figures.
|
||||
:type output_data: Dict[str, Any]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_syncable_figure_keys(self) -> List[str]:
|
||||
"""
|
||||
Identifies which generated figures support external axis synchronization for when
|
||||
two of the same report are displayed side by side.
|
||||
|
||||
This should really only be used for graphs for which axis synchronization makes sense
|
||||
(like bar charts that show quantities not percentages)
|
||||
|
||||
:return: A list of dictionary keys corresponding to figures in output_data
|
||||
that can have their Y-axes scaled dynamically by a parent orchestrator.
|
||||
:rtype: List[str]
|
||||
"""
|
||||
# Default to no synchronization
|
||||
return []
|
||||
|
||||
def render(self, container: DeltaGenerator):
|
||||
"""
|
||||
Executes the standardized report lifecycle.
|
||||
|
||||
Sequentially chains control rendering, data generation, and figure drawing,
|
||||
passing state safely between each phase and halting if user parameters are missing.
|
||||
|
||||
:param container: The Streamlit layout container for the entire report.
|
||||
:type container: DeltaGenerator
|
||||
"""
|
||||
parameters = self.render_controls(container)
|
||||
|
||||
# Only proceed if the user has provided valid inputs
|
||||
if parameters is not None:
|
||||
print("Got parameters")
|
||||
output_data = self.generate_figures(parameters)
|
||||
if output_data is not None:
|
||||
print("got output data")
|
||||
self.render_figures(container, output_data)
|
||||
else:
|
||||
self.logger.error(
|
||||
"No output figures or objects were provided to render this page, if no renderable figures are desired, an empty dictionary should be returned.")
|
||||
st.error(
|
||||
f"No objects to render this page were provided. A detailed error has been added to the logs. {self.app_config.get_errors_contact_string()}")
|
||||
st.stop()
|
||||
else:
|
||||
self.logger.error("No parameters were provided to render this page, if no parameters are desired an empty dictionary should be returned.")
|
||||
st.error(f"No parameters to render this page were provided. A detailed error has been added to the logs. {self.app_config.get_errors_contact_string()}")
|
||||
st.stop()
|
||||
374
streamlit_dashboard/utility_classes/dashboard_config_parser.py
Normal file
374
streamlit_dashboard/utility_classes/dashboard_config_parser.py
Normal file
@@ -0,0 +1,374 @@
|
||||
import yaml
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict
|
||||
import copy
|
||||
import logging
|
||||
|
||||
@dataclass
|
||||
class ExportModulePair:
|
||||
"""
|
||||
Data structure pairing data export endpoints across adjacent fiscal periods.
|
||||
|
||||
This class groups the URLs for the current and previous fiscal years, allowing
|
||||
the application to easily pass paired historical and active data sources to
|
||||
downstream report pipelines.
|
||||
"""
|
||||
current_fy:str | None = None
|
||||
prev_fy:str | None = None
|
||||
|
||||
# Export module configuration file constants
|
||||
EXPORT_MODULE_URLS_KEY = "export_module_urls"
|
||||
EXPORT_MODULE_CLIENT_LIST_KEY = "clients_list"
|
||||
EXPORT_MODULE_NBS_KEY = "new_business_starts"
|
||||
EXPORT_MODULE_FUNDING_KEY = "capital_funding"
|
||||
EXPORT_MODULE_TRAININGS_KEY = "trainings"
|
||||
CURRENT_FY_KEY = "current_fy"
|
||||
PREV_FY_KEY = "prev_fy"
|
||||
|
||||
LOGGING_CONFIG_SECTION_KEY = "logging"
|
||||
LOGGING_LEVEL_KEY = "level"
|
||||
LOGGING_FILE_KEY = "file"
|
||||
|
||||
ERRORS_SECTION_KEY = "errors"
|
||||
CONTACT_NAME_KEY = "contact_name"
|
||||
CONTACT_METHOD_KEY = "contact_method"
|
||||
|
||||
USDA_API_SECTION_KEY = "usda_api"
|
||||
USDA_API_KEY_KEY = "key"
|
||||
|
||||
CENSUS_SECTION_KEY = "census"
|
||||
CENSUS_YEAR_KEY = "year"
|
||||
|
||||
TEMPLATE_DICT = {
|
||||
EXPORT_MODULE_URLS_KEY: {
|
||||
EXPORT_MODULE_CLIENT_LIST_KEY: {
|
||||
CURRENT_FY_KEY: "",
|
||||
PREV_FY_KEY: ""
|
||||
},
|
||||
EXPORT_MODULE_NBS_KEY: {
|
||||
CURRENT_FY_KEY: "",
|
||||
PREV_FY_KEY: ""
|
||||
},
|
||||
EXPORT_MODULE_FUNDING_KEY: {
|
||||
CURRENT_FY_KEY: "",
|
||||
PREV_FY_KEY: ""
|
||||
},
|
||||
EXPORT_MODULE_TRAININGS_KEY: {
|
||||
CURRENT_FY_KEY: "",
|
||||
PREV_FY_KEY: ""
|
||||
}
|
||||
},
|
||||
LOGGING_CONFIG_SECTION_KEY: {
|
||||
LOGGING_LEVEL_KEY: "WARNING",
|
||||
LOGGING_FILE_KEY: "dashboard_log.log"
|
||||
},
|
||||
ERRORS_SECTION_KEY: {
|
||||
CONTACT_NAME_KEY: "",
|
||||
CONTACT_METHOD_KEY: "",
|
||||
},
|
||||
USDA_API_SECTION_KEY: {
|
||||
USDA_API_KEY_KEY: "",
|
||||
},
|
||||
CENSUS_SECTION_KEY: {
|
||||
CENSUS_YEAR_KEY: "",
|
||||
}
|
||||
}
|
||||
|
||||
class DashboardConfig:
|
||||
"""
|
||||
Central manager for application configuration state and schema validation.
|
||||
|
||||
This class handles the serialization, parsing, and strict validation of the YAML
|
||||
configuration file. It acts as the single source of truth for all external Neoserra
|
||||
data endpoints and logging behaviors, abstracting the dictionary traversal away from
|
||||
the reporting classes.
|
||||
|
||||
:param filename: The filepath to the target YAML configuration file.
|
||||
:type filename: str
|
||||
"""
|
||||
|
||||
def __init__(self, filename:str):
|
||||
"""
|
||||
Initializes the configuration manager with a default structural template.
|
||||
|
||||
Establishes the base dictionary structure in memory before attempting any file I/O
|
||||
operations, ensuring internal accessors do not fail on missing root keys.
|
||||
|
||||
:param filename: The path to the configuration file on disk.
|
||||
:type filename: str
|
||||
"""
|
||||
self.config_dictionary:Dict = copy.deepcopy(TEMPLATE_DICT)
|
||||
self.filename = filename
|
||||
|
||||
def set_clients_list_current_fy_url(self, current_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the current fiscal period's client dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_CLIENT_LIST_KEY][CURRENT_FY_KEY] = current_fy_url
|
||||
|
||||
def set_clients_list_prev_fy_url(self, prev_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the previous fiscal period's client dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_CLIENT_LIST_KEY][PREV_FY_KEY] = prev_fy_url
|
||||
|
||||
def get_clients_list_urls(self) -> ExportModulePair:
|
||||
"""
|
||||
Retrieves the paired endpoints for the client list dataset.
|
||||
|
||||
:return: An object containing the current and previous fiscal period URLs.
|
||||
:rtype: ExportModulePair
|
||||
"""
|
||||
return ExportModulePair(
|
||||
current_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_CLIENT_LIST_KEY][CURRENT_FY_KEY],
|
||||
prev_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_CLIENT_LIST_KEY][PREV_FY_KEY]
|
||||
)
|
||||
|
||||
def set_nbs_milestones_current_fy_url(self, current_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the current fiscal period's New Business Starts dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_NBS_KEY][CURRENT_FY_KEY] = current_fy_url
|
||||
|
||||
def set_nbs_milestones_prev_fy_url(self, prev_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the previous fiscal period's New Business Starts dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_NBS_KEY][PREV_FY_KEY] = prev_fy_url
|
||||
|
||||
def get_nbs_milestones_urls(self) -> ExportModulePair:
|
||||
"""
|
||||
Retrieves the paired endpoints for the New Business Starts dataset.
|
||||
|
||||
:return: An object containing the current and previous fiscal period URLs.
|
||||
:rtype: ExportModulePair
|
||||
"""
|
||||
return ExportModulePair(
|
||||
current_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_NBS_KEY][CURRENT_FY_KEY],
|
||||
prev_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_NBS_KEY][PREV_FY_KEY]
|
||||
)
|
||||
|
||||
def set_funding_milestones_current_fy_url(self, current_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the current fiscal period's funding milestones dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_FUNDING_KEY][CURRENT_FY_KEY] = current_fy_url
|
||||
|
||||
def set_funding_milestones_prev_fy_url(self, prev_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the previous fiscal period's funding milestones dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_FUNDING_KEY][PREV_FY_KEY] = prev_fy_url
|
||||
|
||||
def get_funding_milestones_urls(self) -> ExportModulePair:
|
||||
"""
|
||||
Retrieves the paired endpoints for the funding milestones dataset.
|
||||
|
||||
:return: An object containing the current and previous fiscal period URLs.
|
||||
:rtype: ExportModulePair
|
||||
"""
|
||||
return ExportModulePair(
|
||||
current_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_FUNDING_KEY][CURRENT_FY_KEY],
|
||||
prev_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_FUNDING_KEY][PREV_FY_KEY]
|
||||
)
|
||||
|
||||
def set_trainings_current_fy_url(self, current_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the current fiscal period's training events dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_TRAININGS_KEY][CURRENT_FY_KEY] = current_fy_url
|
||||
|
||||
def set_trainings_prev_fy_url(self, prev_fy_url:str):
|
||||
"""
|
||||
Updates the in-memory endpoint URL for the previous fiscal period's training events dataset.
|
||||
"""
|
||||
self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_TRAININGS_KEY][PREV_FY_KEY] = prev_fy_url
|
||||
|
||||
def get_trainings_urls(self) -> ExportModulePair:
|
||||
"""
|
||||
Retrieves the paired endpoints for the training events dataset.
|
||||
|
||||
:return: An object containing the current and previous fiscal period URLs.
|
||||
:rtype: ExportModulePair
|
||||
"""
|
||||
return ExportModulePair(
|
||||
current_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_TRAININGS_KEY][CURRENT_FY_KEY],
|
||||
prev_fy=self.config_dictionary[EXPORT_MODULE_URLS_KEY][EXPORT_MODULE_TRAININGS_KEY][PREV_FY_KEY]
|
||||
)
|
||||
|
||||
def set_usda_api_key(self, key:str):
|
||||
self.config_dictionary[USDA_API_SECTION_KEY][USDA_API_KEY_KEY] = key
|
||||
|
||||
def get_usda_api_key(self):
|
||||
return self.config_dictionary[USDA_API_SECTION_KEY][USDA_API_KEY_KEY]
|
||||
|
||||
def set_census_year(self, year:str):
|
||||
self.config_dictionary[CENSUS_SECTION_KEY][CENSUS_YEAR_KEY] = year
|
||||
|
||||
def get_census_year(self):
|
||||
return self.config_dictionary[CENSUS_SECTION_KEY][CENSUS_YEAR_KEY]
|
||||
|
||||
def get_log_path(self) -> str | None:
|
||||
"""
|
||||
Retrieves the configured file path for the application's rotating file logger.
|
||||
|
||||
:return: The target filepath, or None if the configuration is missing.
|
||||
:rtype: str | None
|
||||
"""
|
||||
try:
|
||||
return self.config_dictionary[LOGGING_CONFIG_SECTION_KEY][LOGGING_FILE_KEY]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def get_log_level(self) -> int:
|
||||
"""
|
||||
Translates the human-readable logging level from the configuration file into system constants.
|
||||
|
||||
Maps string values (e.g., "DEBUG", "WARNING") to their corresponding integer values
|
||||
required by the native Python `logging` module.
|
||||
|
||||
:return: The integer constant representing the logging severity level.
|
||||
:rtype: int
|
||||
"""
|
||||
|
||||
try:
|
||||
log_level = self.config_dictionary[LOGGING_CONFIG_SECTION_KEY][LOGGING_LEVEL_KEY]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
if log_level == "DEBUG":
|
||||
return logging.DEBUG
|
||||
elif log_level == "INFO":
|
||||
return logging.INFO
|
||||
elif log_level == "WARNING" or log_level == "WARN":
|
||||
return logging.WARNING
|
||||
elif log_level == "ERROR":
|
||||
return logging.ERROR
|
||||
elif log_level == "CRITICAL":
|
||||
return logging.CRITICAL
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_errors_contact_name(self, name:str):
|
||||
self.config_dictionary[ERRORS_SECTION_KEY][CONTACT_NAME_KEY] = name
|
||||
|
||||
def get_errors_contact_name(self) -> str:
|
||||
return self.config_dictionary[ERRORS_SECTION_KEY][CONTACT_NAME_KEY]
|
||||
|
||||
def set_errors_contact_method(self, method:str):
|
||||
self.config_dictionary[ERRORS_SECTION_KEY][CONTACT_METHOD_KEY] = method
|
||||
|
||||
def get_errors_contact_method(self):
|
||||
return self.config_dictionary[ERRORS_SECTION_KEY][CONTACT_METHOD_KEY]
|
||||
|
||||
def get_errors_contact_string(self) -> str:
|
||||
return f"Contact {self.get_errors_contact_name()} ({self.get_errors_contact_method()})"
|
||||
|
||||
@staticmethod
|
||||
def write_template(filename:str):
|
||||
"""
|
||||
Bootstraps a new environment by generating a default configuration file on disk.
|
||||
|
||||
Writes the hardcoded `TEMPLATE_DICT` schema to the specified path to ensure administrators
|
||||
have the correct structural format when deploying the dashboard for the first time.
|
||||
|
||||
:param filename: The target filepath for the generated YAML file.
|
||||
:type filename: str
|
||||
"""
|
||||
|
||||
with open(filename, "w") as stream:
|
||||
try:
|
||||
yaml.dump(TEMPLATE_DICT, stream, default_flow_style=False, indent=4)
|
||||
except Exception as e:
|
||||
raise IOError("Could not dump configuration template") from e
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Serializes the current in-memory configuration state to the YAML file on disk.
|
||||
"""
|
||||
|
||||
with open(self.filename, "w") as stream:
|
||||
try:
|
||||
yaml.dump(self.config_dictionary, stream, default_flow_style=False, indent=4)
|
||||
except Exception as e:
|
||||
raise IOError("Could not save configuration file!") from e
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
Reads the configuration file from disk and enforces strict schema validation.
|
||||
|
||||
Acts as a fail-fast gateway for the application environment. It manually verifies the
|
||||
existence of every required organizational key (export modules, fiscal periods, and logging
|
||||
parameters). If the YAML file is structurally malformed or missing endpoints, it immediately
|
||||
raises explicit KeyErrors to halt application boot, preventing cascading fetch failures later.
|
||||
"""
|
||||
try:
|
||||
with open(self.filename, "r") as stream:
|
||||
config = yaml.safe_load(stream)
|
||||
self.config_dictionary = config
|
||||
|
||||
export_module_urls = config.get(EXPORT_MODULE_URLS_KEY, None)
|
||||
|
||||
# Parse all of the export module urls form the config file
|
||||
if export_module_urls is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have config section {EXPORT_MODULE_URLS_KEY}.")
|
||||
|
||||
# Handle the clients list section
|
||||
clients_section = export_module_urls.get(EXPORT_MODULE_CLIENT_LIST_KEY, None)
|
||||
if clients_section is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have config section {EXPORT_MODULE_CLIENT_LIST_KEY} under section {EXPORT_MODULE_URLS_KEY}.")
|
||||
clients_section_current = clients_section.get(CURRENT_FY_KEY, None)
|
||||
clients_section_prev = clients_section.get(PREV_FY_KEY, None)
|
||||
if clients_section_current is None or clients_section_prev is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have either a current fiscal year or a previous fiscal year section underneath section {EXPORT_MODULE_URLS_KEY}/{EXPORT_MODULE_CLIENT_LIST_KEY}. Add '{CURRENT_FY_KEY}:' and '{PREV_FY_KEY}:' under this config section.")
|
||||
|
||||
nbs_section = export_module_urls.get(EXPORT_MODULE_NBS_KEY, None)
|
||||
if nbs_section is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have config section {EXPORT_MODULE_NBS_KEY} under section {EXPORT_MODULE_URLS_KEY}.")
|
||||
nbs_section_current = nbs_section.get(CURRENT_FY_KEY, None)
|
||||
nbs_section_prev = nbs_section.get(PREV_FY_KEY, None)
|
||||
if nbs_section_current is None or nbs_section_prev is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have either a current fiscal year or a previous fiscal year section underneath section {EXPORT_MODULE_URLS_KEY}/{EXPORT_MODULE_NBS_KEY}. Add '{CURRENT_FY_KEY}:' and '{PREV_FY_KEY}:' under this config section.")
|
||||
|
||||
funding_section = export_module_urls.get(EXPORT_MODULE_FUNDING_KEY, None)
|
||||
if funding_section is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have config section {EXPORT_MODULE_FUNDING_KEY} under section {EXPORT_MODULE_URLS_KEY}.")
|
||||
funding_section_current = funding_section.get(CURRENT_FY_KEY, None)
|
||||
funding_section_prev = funding_section.get(PREV_FY_KEY, None)
|
||||
if funding_section_current is None or funding_section_prev is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have either a current fiscal year or a previous fiscal year section underneath section {EXPORT_MODULE_URLS_KEY}/{EXPORT_MODULE_FUNDING_KEY}. Add '{CURRENT_FY_KEY}:' and '{PREV_FY_KEY}:' under this config section.")
|
||||
|
||||
trainings_section = export_module_urls.get(EXPORT_MODULE_TRAININGS_KEY, None)
|
||||
if trainings_section is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have config section {EXPORT_MODULE_TRAININGS_KEY} under section {EXPORT_MODULE_URLS_KEY}.")
|
||||
trainings_section_current = trainings_section.get(CURRENT_FY_KEY, None)
|
||||
trainings_section_prev = trainings_section.get(PREV_FY_KEY, None)
|
||||
if trainings_section_current is None or trainings_section_prev is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have either a current fiscal year or a previous fiscal year section underneath section {EXPORT_MODULE_URLS_KEY}/{EXPORT_MODULE_TRAININGS_KEY}. Add '{CURRENT_FY_KEY}:' and '{PREV_FY_KEY}:' under this config section.")
|
||||
|
||||
logging_section = self.config_dictionary.get(LOGGING_CONFIG_SECTION_KEY, None)
|
||||
if logging_section is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have a config section {LOGGING_CONFIG_SECTION_KEY}.")
|
||||
logging_section_level = logging_section.get(LOGGING_LEVEL_KEY, None)
|
||||
if logging_section_level is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have a config section {LOGGING_LEVEL_KEY} under section {LOGGING_CONFIG_SECTION_KEY}.")
|
||||
logging_section_file = logging_section.get(LOGGING_FILE_KEY, None)
|
||||
if logging_section_file is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have a config section {LOGGING_FILE_KEY} under section {LOGGING_CONFIG_SECTION_KEY}.")
|
||||
|
||||
errors_section = self.config_dictionary.get(ERRORS_SECTION_KEY, None)
|
||||
if errors_section is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have a config section {ERRORS_SECTION_KEY}.")
|
||||
|
||||
contact_name = errors_section.get(CONTACT_NAME_KEY, None)
|
||||
if contact_name is None:
|
||||
raise KeyError(
|
||||
f"The dashboard configuration file {self.filename} did not have a config section {CONTACT_NAME_KEY} under {ERRORS_SECTION_KEY}.")
|
||||
|
||||
contact_method = errors_section.get(CONTACT_METHOD_KEY, None)
|
||||
if contact_method is None:
|
||||
raise KeyError(f"The dashboard configuration file {self.filename} did not have a config section {CONTACT_METHOD_KEY} under {ERRORS_SECTION_KEY}.")
|
||||
|
||||
except FileNotFoundError:
|
||||
self.config_dictionary = {}
|
||||
44
streamlit_dashboard/utility_classes/figure_with_max_y.py
Normal file
44
streamlit_dashboard/utility_classes/figure_with_max_y.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from typing import TypedDict
|
||||
|
||||
import pandas as pd
|
||||
from plotly.graph_objects import Figure
|
||||
|
||||
class FigureWithMaxY(TypedDict):
|
||||
"""
|
||||
A helper container class that gets used by report pages to encapsulate
|
||||
Plotly figures along with the maximum y value represented on the figures.
|
||||
|
||||
This aids in axis synchronization when two of the same report are shown side by side
|
||||
"""
|
||||
figure:Figure
|
||||
max_y:float
|
||||
|
||||
def find_fig_max_y_and_generate_wrapper(fig: Figure) -> 'FigureWithMaxY':
|
||||
"""
|
||||
Helper to automatically extract the max Y value from a Plotly figure
|
||||
and wrap it in the FigureWithMaxY utility class.
|
||||
"""
|
||||
max_y = 0.0
|
||||
# Iterate through all traces (bars, lines, etc.) in the figure
|
||||
for trace in fig.data:
|
||||
if hasattr(trace, 'y') and trace.y is not None:
|
||||
# Get the max of this trace, ignoring NaNs
|
||||
current_max = max(trace.y) if len(trace.y) > 0 else 0
|
||||
max_y = max(max_y, float(current_max))
|
||||
|
||||
return FigureWithMaxY(figure=fig, max_y=max_y)
|
||||
|
||||
def extract_figure_data(figure:Figure) -> pd.DataFrame:
|
||||
data_list = []
|
||||
for trace in figure.data:
|
||||
df_trace = pd.DataFrame({
|
||||
'x': trace.x,
|
||||
'y': trace.y
|
||||
})
|
||||
data_list.append(df_trace)
|
||||
|
||||
if len(data_list) == 0:
|
||||
return pd.DataFrame()
|
||||
|
||||
full_df = pd.concat(data_list)
|
||||
return full_df
|
||||
Reference in New Issue
Block a user