-
Notifications
You must be signed in to change notification settings - Fork 3
Repair Draft #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Repair Draft #31
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,13 @@ | ||
| *.egg-info/ | ||
| __pycache__/ | ||
| .pytest_cache/ | ||
| .coverage | ||
| .DS_Store | ||
| .mypy_cache/ | ||
| .pytest_cache/ | ||
| .venv/ | ||
| .vscode/ | ||
| dist/ | ||
| *.py[cod] | ||
| .DS_Store | ||
| *.egg | ||
| .coverage | ||
| __pycache__/ | ||
| *.bak | ||
| *.egg | ||
| *.egg-info/ | ||
| *.py[cod] | ||
| build/ | ||
| dist/ |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,10 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import hashlib | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import importlib.resources | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import streamlit as st | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pydantic import BaseModel, ValidationError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from epicc.config import CONFIG | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from epicc.formats import VALID_PARAMETER_SUFFIXES | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,6 +17,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from epicc.utils.model_loader import get_built_in_models | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from epicc.utils.parameter_loader import load_model_params | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from epicc.utils.parameter_ui import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| item_level, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| render_parameters_with_indent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reset_parameters_to_defaults, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -50,11 +54,17 @@ def _render_excel_parameter_inputs( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| st.sidebar.info("Upload an Excel model file to edit parameters.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return params, label_overrides | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploaded_excel_name = uploaded_excel_model.name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if st.session_state.get("excel_active_name") != uploaded_excel_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| st.session_state.excel_active_name = uploaded_excel_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| upload_bytes = uploaded_excel_model.getvalue() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| upload_hash = hashlib.sha1(upload_bytes).hexdigest() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| excel_identity = (uploaded_excel_model.name, len(upload_bytes), upload_hash) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| should_refresh_params = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if st.session_state.get("excel_active_identity") != excel_identity: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| st.session_state.excel_active_identity = excel_identity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| st.session_state.params = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params = st.session_state.params | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| should_refresh_params = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploaded_excel_name = uploaded_excel_model.name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| editable_defaults, _ = load_excel_params_defaults_with_computed( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploaded_excel_model, sheet_name=None, start_row=3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -66,6 +76,9 @@ def handle_reset_excel() -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for col_letter, default_text in current_headers.items(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| st.session_state[f"label_override_{col_letter}"] = default_text | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if should_refresh_params: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handle_reset_excel() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| st.sidebar.button("Reset Parameters", on_click=handle_reset_excel) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if current_headers: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -96,34 +109,53 @@ def _render_python_parameter_inputs( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model: BaseSimulationModel, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| model_key: str, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params: dict[str, Any], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> tuple[dict[str, Any], dict[str, str]]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> tuple[dict[str, Any], dict[str, str], dict[str, Any], bool]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label_overrides: dict[str, str] = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sorted_suffixes = sorted(VALID_PARAMETER_SUFFIXES) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploaded_params = st.sidebar.file_uploader( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Optional parameter override file", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Optional parameter file", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type=sorted_suffixes, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| help="If omitted, model defaults are used.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| accept_multiple_files=False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param_identity = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "upload" if uploaded_params else "default", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploaded_params.name if uploaded_params else None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if uploaded_params: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| upload_bytes = uploaded_params.getvalue() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| upload_hash = hashlib.sha1(upload_bytes).hexdigest() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| param_identity = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "upload", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uploaded_params.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| len(upload_bytes), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| upload_hash, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+124
to
+132
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| upload_bytes = uploaded_params.getvalue() | |
| upload_hash = hashlib.sha1(upload_bytes).hexdigest() | |
| param_identity = ( | |
| "upload", | |
| uploaded_params.name, | |
| len(upload_bytes), | |
| upload_hash, | |
| ) | |
| else: | |
| upload_size = getattr(uploaded_params, "size", None) | |
| upload_cache_identity = ("upload", uploaded_params.name, upload_size) | |
| cached_upload_identity = st.session_state.get("uploaded_param_hash_identity") | |
| cached_upload_hash = st.session_state.get("uploaded_param_hash") | |
| if cached_upload_identity != upload_cache_identity or cached_upload_hash is None: | |
| upload_bytes = uploaded_params.getvalue() | |
| upload_size = len(upload_bytes) | |
| upload_cache_identity = ("upload", uploaded_params.name, upload_size) | |
| cached_upload_hash = hashlib.sha1(upload_bytes).hexdigest() | |
| st.session_state.uploaded_param_hash_identity = upload_cache_identity | |
| st.session_state.uploaded_param_hash = cached_upload_hash | |
| param_identity = ( | |
| "upload", | |
| uploaded_params.name, | |
| upload_size, | |
| cached_upload_hash, | |
| ) | |
| else: | |
| st.session_state.pop("uploaded_param_hash_identity", None) | |
| st.session_state.pop("uploaded_param_hash", None) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ | |
| from abc import ABC, abstractmethod | ||
| from typing import Any | ||
|
|
||
| from pydantic import BaseModel | ||
|
|
||
|
|
||
| class BaseSimulationModel(ABC): | ||
| """Abstract contract for Python-defined simulation models.""" | ||
|
|
@@ -29,7 +31,7 @@ def scenario_labels(self) -> dict[str, str]: | |
| @abstractmethod | ||
| def run( | ||
| self, | ||
| params: dict[str, Any], | ||
| params: BaseModel, | ||
| label_overrides: dict[str, str] | None = None, | ||
| ) -> dict[str, Any]: | ||
| """Run the model and return result payload for rendering.""" | ||
|
|
@@ -38,6 +40,10 @@ def run( | |
| def default_params(self) -> dict[str, Any]: | ||
| """Return the model's default parameters as a raw (unflattened) dict.""" | ||
|
|
||
| @abstractmethod | ||
| def parameter_model(self) -> type[BaseModel]: | ||
| """Return a Pydantic model used to validate uploaded parameter files.""" | ||
|
Comment on lines
31
to
+45
|
||
|
|
||
| @abstractmethod | ||
| def build_sections(self, results: dict[str, Any]) -> list[dict[str, Any]]: | ||
| """Transform run results into section payloads for UI rendering.""" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uploaded_excel_model.getvalue()reads the entire XLSX into memory on every Streamlit rerun (even when the upload hasn’t changed) just to computeexcel_identity. For large spreadsheets this can noticeably slow the UI. Consider using cheaper identity signals first (e.g., filename + size/last_modified if available) and only hashing when those change, or caching the computed hash/bytes inst.session_stateso it’s computed once per upload.