|
|
""" |
|
|
Integration module for scenario comparison functionality in the HVAC calculator. |
|
|
|
|
|
This module provides UI components and integration code for the scenario comparison |
|
|
functionality in both the cooling and heating calculators. |
|
|
""" |
|
|
|
|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import matplotlib.pyplot as plt |
|
|
from utils.scenario_manager import ScenarioManager |
|
|
|
|
|
def add_scenario_ui(calculator_type): |
|
|
""" |
|
|
Add scenario saving UI to the calculator. |
|
|
|
|
|
Args: |
|
|
calculator_type (str): Type of calculator ('cooling' or 'heating') |
|
|
|
|
|
Returns: |
|
|
bool: True if a scenario was saved, False otherwise |
|
|
""" |
|
|
st.subheader("Save Current Calculation as Scenario") |
|
|
|
|
|
|
|
|
results_key = f"{calculator_type}_results" |
|
|
if results_key not in st.session_state or not st.session_state[results_key]: |
|
|
st.warning("Please complete a calculation before saving a scenario.") |
|
|
return False |
|
|
|
|
|
|
|
|
form_data_key = f"{calculator_type}_form_data" |
|
|
|
|
|
|
|
|
scenario_name = st.text_input("Scenario Name", f"My {calculator_type.capitalize()} Scenario") |
|
|
scenario_description = st.text_area("Scenario Description", "Description of this scenario...") |
|
|
|
|
|
|
|
|
if st.button("Save Scenario"): |
|
|
if not scenario_name: |
|
|
st.error("Please provide a name for the scenario.") |
|
|
return False |
|
|
|
|
|
|
|
|
scenario_manager = ScenarioManager() |
|
|
|
|
|
|
|
|
try: |
|
|
path = scenario_manager.save_scenario( |
|
|
scenario_name, |
|
|
scenario_description, |
|
|
calculator_type, |
|
|
st.session_state[form_data_key], |
|
|
st.session_state[results_key] |
|
|
) |
|
|
|
|
|
st.success(f"Scenario saved successfully: {scenario_name}") |
|
|
return True |
|
|
except Exception as e: |
|
|
st.error(f"Error saving scenario: {str(e)}") |
|
|
return False |
|
|
|
|
|
return False |
|
|
|
|
|
def scenario_comparison_page(): |
|
|
""" |
|
|
Create a page for comparing saved scenarios. |
|
|
""" |
|
|
st.title("Scenario Comparison") |
|
|
|
|
|
|
|
|
scenario_manager = ScenarioManager() |
|
|
|
|
|
|
|
|
all_scenarios = scenario_manager.get_available_scenarios() |
|
|
|
|
|
if not all_scenarios: |
|
|
st.warning("No saved scenarios found. Please save some scenarios first.") |
|
|
return |
|
|
|
|
|
|
|
|
calculator_type = st.radio( |
|
|
"Calculator Type", |
|
|
["cooling", "heating", "all"], |
|
|
format_func=lambda x: x.capitalize() if x != "all" else "All" |
|
|
) |
|
|
|
|
|
filtered_scenarios = all_scenarios |
|
|
if calculator_type != "all": |
|
|
filtered_scenarios = [s for s in all_scenarios if s["calculator_type"] == calculator_type] |
|
|
|
|
|
if not filtered_scenarios: |
|
|
st.warning(f"No {calculator_type} scenarios found.") |
|
|
return |
|
|
|
|
|
|
|
|
scenarios_df = pd.DataFrame([ |
|
|
{ |
|
|
"Name": s["name"], |
|
|
"Description": s["description"], |
|
|
"Type": s["calculator_type"].capitalize(), |
|
|
"Date": s["timestamp"] |
|
|
} |
|
|
for s in filtered_scenarios |
|
|
]) |
|
|
|
|
|
st.subheader("Available Scenarios") |
|
|
st.dataframe(scenarios_df) |
|
|
|
|
|
|
|
|
st.subheader("Select Scenarios to Compare") |
|
|
st.info("Select at least two scenarios to compare. The first selected scenario will be used as the base scenario.") |
|
|
|
|
|
|
|
|
scenario_options = {f"{s['name']} ({s['calculator_type'].capitalize()})": s["path"] for s in filtered_scenarios} |
|
|
|
|
|
|
|
|
selected_scenario_names = st.multiselect( |
|
|
"Select Scenarios", |
|
|
options=list(scenario_options.keys()) |
|
|
) |
|
|
|
|
|
if len(selected_scenario_names) < 2: |
|
|
st.warning("Please select at least two scenarios to compare.") |
|
|
return |
|
|
|
|
|
|
|
|
selected_scenario_paths = [scenario_options[name] for name in selected_scenario_names] |
|
|
|
|
|
|
|
|
if st.button("Compare Selected Scenarios"): |
|
|
|
|
|
comparison = scenario_manager.compare_scenarios(selected_scenario_paths) |
|
|
|
|
|
if "error" in comparison: |
|
|
st.error(comparison["error"]) |
|
|
return |
|
|
|
|
|
|
|
|
charts = scenario_manager.generate_comparison_charts(comparison) |
|
|
|
|
|
|
|
|
scenario_manager.display_comparison_in_streamlit(comparison, charts) |
|
|
|
|
|
|
|
|
st.subheader("Export Comparison") |
|
|
|
|
|
|
|
|
comparison_data = [] |
|
|
|
|
|
|
|
|
for load in comparison["total_loads"]: |
|
|
row = { |
|
|
"Scenario": load["name"], |
|
|
"Metric": "Total Load", |
|
|
"Value": load["total_load_kw"], |
|
|
"Unit": "kW" |
|
|
} |
|
|
comparison_data.append(row) |
|
|
|
|
|
row = { |
|
|
"Scenario": load["name"], |
|
|
"Metric": "Recommended Size", |
|
|
"Value": load["recommended_size_kw"], |
|
|
"Unit": "kW" |
|
|
} |
|
|
comparison_data.append(row) |
|
|
|
|
|
|
|
|
for breakdown in comparison["breakdown"]: |
|
|
for category in breakdown: |
|
|
if category != "name": |
|
|
row = { |
|
|
"Scenario": breakdown["name"], |
|
|
"Metric": f"{category.capitalize()} Percentage", |
|
|
"Value": breakdown[category], |
|
|
"Unit": "%" |
|
|
} |
|
|
comparison_data.append(row) |
|
|
|
|
|
|
|
|
base_scenario = comparison["scenarios"][0] |
|
|
for scenario_name, diff in comparison["differences"].items(): |
|
|
row = { |
|
|
"Scenario": scenario_name, |
|
|
"Metric": f"Absolute Difference from {base_scenario}", |
|
|
"Value": diff["absolute_diff_kw"], |
|
|
"Unit": "kW" |
|
|
} |
|
|
comparison_data.append(row) |
|
|
|
|
|
row = { |
|
|
"Scenario": scenario_name, |
|
|
"Metric": f"Percentage Difference from {base_scenario}", |
|
|
"Value": diff["percentage_diff"], |
|
|
"Unit": "%" |
|
|
} |
|
|
comparison_data.append(row) |
|
|
|
|
|
|
|
|
export_df = pd.DataFrame(comparison_data) |
|
|
|
|
|
|
|
|
csv = export_df.to_csv(index=False) |
|
|
|
|
|
|
|
|
st.download_button( |
|
|
label="Download Comparison as CSV", |
|
|
data=csv, |
|
|
file_name="scenario_comparison.csv", |
|
|
mime="text/csv" |
|
|
) |
|
|
|
|
|
def add_scenario_comparison_to_app(app_module): |
|
|
""" |
|
|
Add scenario comparison functionality to the main app. |
|
|
|
|
|
Args: |
|
|
app_module: The main app module to modify |
|
|
""" |
|
|
|
|
|
app_module.pages["scenario_comparison"] = scenario_comparison_page |
|
|
|
|
|
|
|
|
original_main = app_module.main |
|
|
|
|
|
def new_main(): |
|
|
st.sidebar.title("HVAC Load Calculator") |
|
|
|
|
|
|
|
|
st.sidebar.markdown(""" |
|
|
**Created by:** Dr. Majed Abuseif |
|
|
|
|
|
This tool was created to facilitate HVAC calculation and understanding for Deakin University students, but has been enhanced to cover wider aspects to allow professionals and energy and HVAC enthusiasts to use it. |
|
|
""") |
|
|
|
|
|
|
|
|
page = st.sidebar.radio( |
|
|
"Select Calculator", |
|
|
["Cooling Load Calculator", "Heating Load Calculator", "Scenario Comparison"] |
|
|
) |
|
|
|
|
|
if page == "Cooling Load Calculator": |
|
|
app_module.cooling_calculator() |
|
|
elif page == "Heating Load Calculator": |
|
|
app_module.heating_calculator() |
|
|
elif page == "Scenario Comparison": |
|
|
scenario_comparison_page() |
|
|
|
|
|
|
|
|
app_module.main = new_main |
|
|
|
|
|
return app_module |
|
|
|
|
|
def add_scenario_ui_to_calculator(calculator_module, calculator_type): |
|
|
""" |
|
|
Add scenario saving UI to a calculator module. |
|
|
|
|
|
Args: |
|
|
calculator_module: The calculator module to modify |
|
|
calculator_type (str): Type of calculator ('cooling' or 'heating') |
|
|
""" |
|
|
|
|
|
if calculator_type == "cooling": |
|
|
original_results_form = calculator_module.results_form |
|
|
|
|
|
def new_results_form(ref_data): |
|
|
|
|
|
original_results_form(ref_data) |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
add_scenario_ui(calculator_type) |
|
|
|
|
|
|
|
|
calculator_module.results_form = new_results_form |
|
|
|
|
|
elif calculator_type == "heating": |
|
|
original_results_form = calculator_module.results_form |
|
|
|
|
|
def new_results_form(ref_data): |
|
|
|
|
|
original_results_form(ref_data) |
|
|
|
|
|
|
|
|
st.markdown("---") |
|
|
add_scenario_ui(calculator_type) |
|
|
|
|
|
|
|
|
calculator_module.results_form = new_results_form |
|
|
|
|
|
return calculator_module |
|
|
|