""" Feature 2: Testing Cycle Browser Month-year filter with pre-computed cycles from the cycle_periods DB table. """ import streamlit as st import pandas as pd from core.db_connector import get_db_connector from ui.components import page_header, metric_row from ui.plotly_charts import create_cycle_timeline page_header( "Testing Cycles", "Cycles are identified from motor activity — >0% starts a cycle and returning to 0% ends a cycle. " "To qualify, a testing cycle must show either >5% motor speed OR >10 sec duration observed." ) db = get_db_connector() @st.cache_data(ttl=3600) def get_available_months(): return db.get_available_months() @st.cache_data(ttl=3600) def get_cycle_months(): return db.get_cycle_months() @st.cache_data(ttl=600) def load_cycles_for_month(month_str: str): """Load pre-computed cycles from cycle_periods table for a given month.""" return db.get_cycle_periods(month_str) @st.cache_data(ttl=600) def load_all_cycles(): """Load all pre-computed cycles (for cross-page use).""" return db.get_cycle_periods() # Sidebar: month selector with st.sidebar: st.subheader("Select Period") sensor_months = get_available_months() cycle_months = get_cycle_months() months = sorted(set(sensor_months) | set(cycle_months)) if not months: st.error("No data months found in database.") st.stop() selected_month = st.selectbox( "Month", months, index=len(months) - 1, # Default to most recent key="cycle_month", ) # Load cycles for selected month cycles = load_cycles_for_month(selected_month) # Also cache all cycles for the comparison page all_cycles = load_all_cycles() st.session_state.all_cycles = all_cycles if not cycles: st.info(f"No testing cycles found in {selected_month}.") else: # Summary metrics total_runtime = sum(c['duration_minutes'] for c in cycles) peak_pressures = [c.get('peak_pressure', 0) for c in cycles if c.get('peak_pressure')] max_pressure = max(peak_pressures) if peak_pressures else 0 total_dispensed = sum(c.get('total_kg_dispensed', 0) for c in cycles) metrics = [ {'label': 'Cycles', 'value': len(cycles), 'unit': ''}, {'label': 'Total Runtime', 'value': f"{total_runtime:.0f}", 'unit': 'min'}, {'label': 'Peak Pressure (PT130)', 'value': f"{max_pressure:.0f}", 'unit': 'bar'}, {'label': 'Total Dispensed', 'value': f"{total_dispensed:.1f}", 'unit': 'kg'}, ] metric_row(metrics) st.divider() # Timeline chart fig = create_cycle_timeline(cycles, selected_month) st.plotly_chart(fig, use_container_width=True) st.divider() # Cycle table st.subheader("Cycle Details") table_data = [] for c in cycles: display_start = c.get('start_time_et') or c['start_time'] display_end = c.get('end_time_et') or c['end_time'] table_data.append({ 'Cycle': c['cycle_id'], 'Start (ET)': display_start.strftime('%b %d %H:%M'), 'End (ET)': display_end.strftime('%b %d %H:%M'), 'Duration (min)': f"{c['duration_minutes']:.1f}", 'Peak Speed (RPM)': f"{c.get('peak_speed', 0):.0f}", 'Peak Pressure (bar)': f"{c.get('peak_pressure', 0):.0f}" if c.get('peak_pressure') else 'N/A', 'Avg Flow (kg/min)': f"{c.get('avg_flow', 0):.2f}" if c.get('avg_flow') else 'N/A', 'Dispensed (kg)': f"{c.get('total_kg_dispensed', 0):.2f}" if c.get('total_kg_dispensed') else 'N/A', 'Pump Strokes': f"{c.get('total_pump_strokes', 0):.0f}" if c.get('total_pump_strokes') else 'N/A', }) df_table = pd.DataFrame(table_data) st.dataframe(df_table, use_container_width=True, hide_index=True) # Store cycles in session state for other pages st.session_state.detected_cycles = cycles st.info("Select a cycle above, then navigate to **Cycle Detail** page for in-depth analysis.")