125125
126126# Performance monitoring decorators
127127
128+ # Cached Data Loading Functions
129+ @st .cache_data
130+ def load_sample_data_cached () -> pd .DataFrame :
131+ """Cached wrapper for loading sample data to prevent regeneration on every UI interaction"""
132+ from core import DataSourceManager
133+ manager = DataSourceManager ()
134+ return manager .get_sample_data ()
135+
136+ @st .cache_data
137+ def load_file_data_cached (file_name : str , file_size : int , file_type : str , file_content : bytes ) -> pd .DataFrame :
138+ """Cached wrapper for loading file data based on file properties to avoid re-processing same files"""
139+ import os
140+ import tempfile
141+
142+ from core import DataSourceManager
143+
144+ manager = DataSourceManager ()
145+
146+ # Create a temporary file from the uploaded content
147+ with tempfile .NamedTemporaryFile (delete = False , suffix = f".{ file_type } " ) as tmp_file :
148+ tmp_file .write (file_content )
149+ tmp_file .flush ()
150+ temp_path = tmp_file .name
151+
152+ try :
153+ # Create a mock uploaded file object for the manager
154+ class MockUploadedFile :
155+ def __init__ (self , name , content ):
156+ self .name = name
157+ self ._content = content
158+
159+ def getvalue (self ):
160+ return self ._content
161+
162+ mock_file = MockUploadedFile (file_name , file_content )
163+ return manager .load_from_file (mock_file )
164+ finally :
165+ # Clean up temporary file
166+ try :
167+ os .unlink (temp_path )
168+ except OSError :
169+ pass
170+
171+ @st .cache_data
172+ def load_clickhouse_data_cached (query : str , connection_hash : str ) -> pd .DataFrame :
173+ """Cached wrapper for ClickHouse data loading based on query and connection"""
174+ # Note: This assumes the connection is already established in session state
175+ if hasattr (st .session_state , "data_source_manager" ) and st .session_state .data_source_manager .clickhouse_client :
176+ return st .session_state .data_source_manager .load_from_clickhouse (query )
177+ return pd .DataFrame ()
178+
179+ @st .cache_data
180+ def get_segmentation_properties_cached (events_data : pd .DataFrame ) -> dict [str , list [str ]]:
181+ """Cached wrapper for getting segmentation properties to avoid repeated JSON parsing"""
182+ from core import DataSourceManager
183+ manager = DataSourceManager ()
184+ return manager .get_segmentation_properties (events_data )
185+
186+ @st .cache_data
187+ def get_property_values_cached (events_data : pd .DataFrame , prop_name : str , prop_type : str ) -> list [str ]:
188+ """Cached wrapper for getting property values to avoid repeated filtering"""
189+ from core import DataSourceManager
190+ manager = DataSourceManager ()
191+ return manager .get_property_values (events_data , prop_name , prop_type )
192+
193+ @st .cache_data
194+ def get_sorted_event_names_cached (events_data : pd .DataFrame ) -> list [str ]:
195+ """Cached wrapper for getting sorted event names to avoid repeated sorting"""
196+ return sorted (events_data ["event_name" ].unique ())
197+
198+ @st .cache_data
199+ def calculate_timeseries_metrics_cached (
200+ events_data : pd .DataFrame ,
201+ funnel_steps : tuple [str , ...],
202+ polars_period : str ,
203+ config_dict : dict [str , Any ],
204+ use_polars : bool = True
205+ ) -> pd .DataFrame :
206+ """
207+ Cached wrapper for time series calculation to prevent recalculation during tab switching.
208+ Uses tuple for funnel_steps and config dict for proper hashing.
209+ """
210+ from core import FunnelCalculator
211+
212+ # Reconstruct config from dict
213+ config = FunnelConfig .from_dict (config_dict )
214+ calculator = FunnelCalculator (config , use_polars = use_polars )
215+
216+ # Convert tuple back to list for the calculator
217+ steps_list = list (funnel_steps )
218+
219+ return calculator .calculate_timeseries_metrics (events_data , steps_list , polars_period )
128220
129221# Data Source Management
130222def initialize_session_state ():
@@ -201,6 +293,7 @@ def create_enhanced_event_selector_DISABLED():
201293 """Create enhanced event selector with search, filters, and categorized display - DISABLED in simplified version"""
202294
203295
296+ @st .cache_data
204297def get_comprehensive_performance_analysis () -> dict [str , Any ]:
205298 """
206299 Get comprehensive performance analysis from all monitored components
@@ -254,13 +347,14 @@ def get_comprehensive_performance_analysis() -> dict[str, Any]:
254347 return analysis
255348
256349
350+ @st .cache_data
257351def get_event_statistics (events_data : pd .DataFrame ) -> dict [str , dict [str , Any ]]:
258352 """Get comprehensive statistics for each event in the dataset"""
259353 if events_data is None or events_data .empty :
260354 return {}
261355
262356 event_stats = {}
263- event_counts = events_data ["event_name" ].value_counts ()
357+ events_data ["event_name" ].value_counts ()
264358 total_events = len (events_data )
265359 unique_users = events_data ["user_id" ].nunique ()
266360
@@ -368,10 +462,10 @@ def clear_all_steps():
368462 if "event_statistics" not in st .session_state :
369463 st .session_state .event_statistics = get_event_statistics (st .session_state .events_data )
370464
371- # Cache the sorted list of unique event names for better UI performance
465+ # Use cached sorted event names for better UI performance
372466 if "sorted_event_names" not in st .session_state :
373- st .session_state .sorted_event_names = sorted (
374- st .session_state .events_data [ "event_name" ]. unique ()
467+ st .session_state .sorted_event_names = get_sorted_event_names_cached (
468+ st .session_state .events_data
375469 )
376470
377471 available_events = st .session_state .sorted_event_names
@@ -401,7 +495,7 @@ def clear_all_steps():
401495
402496 with event_col1 :
403497 # Checkbox с улучшенным дизайном
404- selected = st .checkbox (
498+ st .checkbox (
405499 f"**{ event } **" ,
406500 value = is_selected ,
407501 key = f"event_cb_{ event .replace (' ' , '_' ).replace ('-' , '_' )} " ,
@@ -541,9 +635,9 @@ def main():
541635 if data_source == "Sample Data" :
542636 if st .button ("Load Sample Data" , key = "load_sample_data_button" ):
543637 with st .spinner ("Loading sample data..." ):
544- st . session_state . events_data = (
545- st .session_state .data_source_manager . get_sample_data ()
546- )
638+ # Use cached sample data loading
639+ st .session_state .events_data = load_sample_data_cached ()
640+
547641 # Clear cached event names when new data is loaded
548642 if "sorted_event_names" in st .session_state :
549643 del st .session_state .sorted_event_names
@@ -562,9 +656,17 @@ def main():
562656
563657 if uploaded_file is not None :
564658 with st .spinner ("Processing file..." ):
565- st .session_state .events_data = (
566- st .session_state .data_source_manager .load_from_file (uploaded_file )
659+ # Use cached file loading based on file properties
660+ file_content = uploaded_file .getvalue ()
661+ file_type = uploaded_file .name .split ('.' )[- 1 ].lower ()
662+
663+ st .session_state .events_data = load_file_data_cached (
664+ uploaded_file .name ,
665+ uploaded_file .size ,
666+ file_type ,
667+ file_content
567668 )
669+
568670 if not st .session_state .events_data .empty :
569671 # Clear cached event names when new data is loaded
570672 if "sorted_event_names" in st .session_state :
@@ -612,9 +714,14 @@ def main():
612714
613715 if st .button ("Execute Query" ):
614716 with st .spinner ("Executing query..." ):
615- st .session_state .events_data = (
616- st .session_state .data_source_manager .load_from_clickhouse (ch_query )
717+ # Create a connection hash for caching
718+ connection_hash = f"{ ch_host } :{ ch_port } :{ ch_database } :{ ch_username } "
719+
720+ # Use cached ClickHouse loading
721+ st .session_state .events_data = load_clickhouse_data_cached (
722+ ch_query , connection_hash
617723 )
724+
618725 if not st .session_state .events_data .empty :
619726 # Clear cached event names when new data is loaded
620727 if "sorted_event_names" in st .session_state :
@@ -640,7 +747,7 @@ def main():
640747 st .markdown ("### ⚡ Performance" )
641748 if "performance_history" in st .session_state and st .session_state .performance_history :
642749 latest_performance = st .session_state .performance_history [- 1 ]
643- performance_analysis = get_comprehensive_performance_analysis ()
750+ get_comprehensive_performance_analysis ()
644751
645752 # Display current performance
646753 st .markdown ("**Latest Analysis:**" )
@@ -773,11 +880,9 @@ def main():
773880 selected_values = []
774881
775882 if st .session_state .events_data is not None and not st .session_state .events_data .empty :
776- # Update available properties
777- st .session_state .available_properties = (
778- st .session_state .data_source_manager .get_segmentation_properties (
779- st .session_state .events_data
780- )
883+ # Update available properties using cached function
884+ st .session_state .available_properties = get_segmentation_properties_cached (
885+ st .session_state .events_data
781886 )
782887
783888 if st .session_state .available_properties :
@@ -801,11 +906,9 @@ def main():
801906 if selected_property != "None" :
802907 prop_type , prop_name = selected_property .split ("_" , 1 )
803908
804- # Get available values for this property
805- prop_values = (
806- st .session_state .data_source_manager .get_property_values (
807- st .session_state .events_data , prop_name , prop_type
808- )
909+ # Get available values for this property using cached function
910+ prop_values = get_property_values_cached (
911+ st .session_state .events_data , prop_name , prop_type
809912 )
810913
811914 if prop_values :
@@ -1451,8 +1554,16 @@ def format_time(minutes):
14511554 if hasattr (st .session_state .data_source_manager , "_last_lazy_df" ):
14521555 lazy_df = st .session_state .data_source_manager .get_lazy_frame ()
14531556
1454- timeseries_data = calculator .calculate_timeseries_metrics (
1455- st .session_state .events_data , results .steps , polars_period , lazy_df
1557+ # Use cached time series calculation to prevent recalculation during tab switching
1558+ config_dict = st .session_state .funnel_config .to_dict ()
1559+ funnel_steps_tuple = tuple (results .steps )
1560+
1561+ timeseries_data = calculate_timeseries_metrics_cached (
1562+ st .session_state .events_data ,
1563+ funnel_steps_tuple ,
1564+ polars_period ,
1565+ config_dict ,
1566+ use_polars
14561567 )
14571568
14581569 if not timeseries_data .empty :
0 commit comments