Files
testing123/streamlit_dashboard/page_classes/nbs_milestones_page_class.py
2026-05-21 08:40:24 -04:00

256 lines
12 KiB
Python

from typing import Dict, Any, List
import logging
import pandas as pd
from streamlit.delta_generator import DeltaGenerator
from fiscalyear import *
from plotly.graph_objects import Figure
import streamlit as st
from constants_module import OUT_COLUMNS, NEOSERRA_COLUMNS, TRAINING_COUNT_COLUMNS
from streamlit_constants import DASHBOARD_CONFIG_OBJECT_KEY
from utility_classes.base_report_page import BaseReportPage
from utility_classes.figure_with_max_y import FigureWithMaxY
from cached_function_wrappers.shared import get_df_centers
from cached_function_wrappers.nbs_cached_functions import cached_get_nbs_data
from section_1_graph_library_module import ( # pyright:ignore
make_nbs_attribution_network_wide,
make_attribution_rate_chart,
make_theoretical_attribution_rate_chart,
)
from utility_classes.dashboard_config_parser import DashboardConfig, ExportModulePair
class NetworkNbsMilestonesReportPage(BaseReportPage):
"""
Concrete implementation of a report page analyzing New Business Start (NBS) milestones.
This class manages the data retrieval and rendering lifecycle for NBS metrics. It tracks
attribution sources and documentation compliance rates across centers, utilizing temporal
state (fiscal years) to route requests to the correct data endpoints.
:param kwargs: Arbitrary keyword arguments
"""
def __init__(self, **kwargs):
"""
Initializes the temporal boundaries and configuration state for the NBS report.
Captures the current and previous fiscal years to set up report filtering logic and
extracts the global dashboard configuration from the session state to resolve data export URLs.
:param kwargs: Arbitrary keyword arguments.
"""
super().__init__("Network New Business Start Milestones Report")
self.fiscal_year = FiscalYear.current()
self.prev_fiscal_year = self.fiscal_year.prev_fiscal_year
self.fiscal_year_text = f'FY{str(self.fiscal_year.fiscal_year)[2:]}'
self.prev_fiscal_year_text = f'FY{str(self.prev_fiscal_year.fiscal_year)[2:]}'
def get_fiscal_year_export_url(self, selected_fiscal_year) -> str:
"""
Resolves the external dataset endpoint based on the selected temporal state.
Maps the user's fiscal year selection to the appropriate configured export URL,
ensuring downstream data fetches hit the correct historical or current dataset.
:param selected_fiscal_year: The string representation of the chosen fiscal year.
:type selected_fiscal_year: Any
:return: The URL for the corresponding dataset export.
:rtype: str
"""
export_urls:ExportModulePair = self.app_config.get_nbs_milestones_urls()
if selected_fiscal_year == self.fiscal_year_text:
return export_urls.current_fy
else:
return export_urls.prev_fy
@staticmethod
def get_page_name():
"""
Provides the static display identifier for the report.
This value is consumed by dashboard orchestrators to populate navigation menus
and UI selectors.
:return: The human-readable name of the report.
:rtype: str
"""
return "NBS Funding Milestones"
def render_controls(self, container: DeltaGenerator) -> Dict[str, Any]:
"""
Defines the input UI and captures the parameter state required for report generation.
Renders selection widgets for fiscal year and target centers. Enforces a strict halt
on Streamlit execution if the base dataset fails to load, preventing cascading errors
in downstream processing steps.
:param container: The layout element to attach the input widgets to.
:type container: DeltaGenerator
:return: A dictionary containing the user-selected fiscal year and centers.
:rtype: Dict[str, Any]
"""
report_settings_expander = container.expander(
label="Report Options",
expanded=True,
key=self.get_widget_key("report_settings_expander")
)
report_settings_expander.markdown("## Dataset Options")
report_settings_expander.markdown("These settings will modify the input dataset used to generate the graphs.")
selected_fiscal_year = report_settings_expander.selectbox(
label="Fiscal Year",
options=[self.prev_fiscal_year_text, self.fiscal_year_text],
index=1,
key=self.get_widget_key("selected_fiscal_year_selectbox")
)
reportable_only = report_settings_expander.checkbox(label="Reportable only?", value=True, key=self.get_widget_key("reportable_only_checkbox"))
export_url:str = self.get_fiscal_year_export_url(selected_fiscal_year)
try:
all_centers = get_df_centers(export_url)
except Exception as e:
self.logger.exception(f"Failed to fetch the dataset for this page: {e}")
container.error(
f"Failed to get the list of all centers for the dataset for this page. A detailed error message has been added to the logs. {self.app_config.get_errors_contact_string()}")
st.stop()
selected_centers = report_settings_expander.multiselect(label="Centers", options=all_centers,
default=all_centers,
key=self.get_widget_key("selected_centers_multiselect"))
return {
"selected_fiscal_year":selected_fiscal_year,
"selected_centers":selected_centers,
"reportable_only":reportable_only
}
def generate_figures(self, parameters: Dict[str, Any]):
"""
Executes the analytical data pipeline and constructs visualization objects.
Fetches the NBS dataset, applies structural transformations, and generates Plotly charts
for attribution and documentation rates. Computes the maximum Y-axis value for quantity-based
charts to enable cross-report axis synchronization.
:param parameters: The parameter dictionary captured from the render_controls phase.
:type parameters: Dict[str, Any]
:return: A dictionary mapping identifiers to FigureWithMaxY objects and the raw dataframe.
:rtype: Dict[str, Any]
"""
selected_fiscal_year = parameters['selected_fiscal_year']
selected_centers = parameters['selected_centers']
reportable_only = parameters['reportable_only']
export_url = self.get_fiscal_year_export_url(selected_fiscal_year)
nbs_df = cached_get_nbs_data(export_url, reportable_only=reportable_only, allowed_centers=selected_centers)
try:
network_wide_funding_fig = make_nbs_attribution_network_wide(
nbs_df,
# Zero out the graph note, we'll render it manually later
graph_note="",
title=f"New Business Start Attributions Per Center {selected_fiscal_year}",
network_label="PASBDC*",
col_neo_center=NEOSERRA_COLUMNS.center,
col_documentation_level=OUT_COLUMNS.milestone_documentation_level
)
except Exception as e:
self.logger.exception(f"Failed to generate the network wide funding stacked bars chart: {e}")
st.error(f"Failed to generate the network wide stacked funding bar chart for this page. A detailed error has been placed in the logs. {self.app_config.get_errors_contact_string()}")
st.stop()
try:
documented_only_fig = make_attribution_rate_chart(
nbs_df,
fiscalyear=selected_fiscal_year,
documented_tag=OUT_COLUMNS.val_documented,
col_neo_center=NEOSERRA_COLUMNS.center,
col_documentation_level=OUT_COLUMNS.milestone_documentation_level
)
except Exception as e:
self.logger.exception(f"Failed to generate the network wide documented only bar chart: {e}")
st.error(
f"Failed to generate the network wide documented only bar chart for this page. A detailed error has been placed in the logs. {self.app_config.get_errors_contact_string()}")
st.stop()
try:
theoretical_fig = make_theoretical_attribution_rate_chart(
nbs_df,
title=f"Documented Percentage if All Funding Milestones With an Attribution Source had an Affirmation {selected_fiscal_year}",
documented_tag=OUT_COLUMNS.val_documented,
affirmation_missing_tag=OUT_COLUMNS.val_affirmation_missing,
col_neo_center=NEOSERRA_COLUMNS.center,
col_documentation_level=OUT_COLUMNS.milestone_documentation_level
)
# Obtain the max y value for the chart we care about synchronizing the axis with if this report is in
# a page comparer
max_bar_y = nbs_df[NEOSERRA_COLUMNS.center].value_counts().max()
except Exception as e:
self.logger.exception(f"Failed to generate the network wide theoretical documentation bar chart: {e}")
st.error(
f"Failed to generate the network wide theoretical documentation bar chart for this page. A detailed error has been placed in the logs. {self.app_config.get_errors_contact_string()}")
st.stop()
return {
"network_wide_nbs_fig":FigureWithMaxY(figure=network_wide_funding_fig, max_y=max_bar_y),
"documented_only_fig":FigureWithMaxY(figure=documented_only_fig, max_y=0.0),
"theoretical_fig":FigureWithMaxY(figure=theoretical_fig, max_y=0.0),
"nbs_df":nbs_df
}
def render_figures(self, container: DeltaGenerator, output_data: Dict[str, Any]):
"""
Binds the computed visualization artifacts and raw data to the Streamlit UI.
Draws the Plotly figures to the screen and injects static HTML definitions for
documentation compliance levels to ensure users can accurately interpret the charts.
Exposes the raw dataframe via an expander for data auditing.
:param container: The layout element to draw the report visuals onto.
:type container: DeltaGenerator
:param output_data: The computed figures and dataframes from generate_figures.
:type output_data: Dict[str, Any]
"""
network_wide_nbs_fig:Figure= output_data['network_wide_nbs_fig']['figure']
documented_only_fig:Figure = output_data['documented_only_fig']['figure']
theoretical_fig:Figure = output_data['theoretical_fig']['figure']
nbs_df:pd.DataFrame = output_data['nbs_df']
container.plotly_chart(network_wide_nbs_fig, key=self.get_widget_key("network_wide_funding_fig"))
container.markdown(
"<b>NOTE:</b>Documentation levels were determined as follows.<br><br>"
"<b>Documented: Will be submitted to Nexus as long as 'Director Verified' is checked</b></br> There is a non-blank, non-'Requested on eCenter' attribution source AND Affirmation Statement was non-blank<br>\tNOTE: If the attribution source is eCenter, no affirmation is required.<br>"
"<b>Affirmation Statement Missing: Will NOT be submitted to Nexus</b></br> Attribution source is non-blank, non-'Requested on eCenter' BUT affirmation statement was blank.</br>"
"<b>Not Documented:Will NOT be submitted to Nexus</b></br> The attribution source is blank or 'Requested on eCenter'",
unsafe_allow_html=True
)
container.plotly_chart(documented_only_fig, key=self.get_widget_key("documented_only_fig"))
container.plotly_chart(theoretical_fig, key=self.get_widget_key("theoretical_fig"))
dataset_expander = container.expander(
label="Source Datasets",
expanded=True,
key=self.get_widget_key("dataset_expander")
)
dataset_expander.markdown("## Source Data")
dataset_expander.markdown("### Neoserra Trainings Dataset")
dataset_expander.write(nbs_df)
def get_syncable_figure_keys(self) -> List[str]:
"""
Declares the specific figures that permit dynamic external Y-axis scaling.
Restricts synchronization to the network-wide funding figure, as scaling axes on
the percentage-based attribution charts would distort the visual representation.
:return: A list of dictionary keys corresponding to sync-compatible figures.
:rtype: List[str]
"""
return ["network_wide_nbs_fig"]