Spaces:
Sleeping
Sleeping
| """ | |
| 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() | |
| def get_available_months(): | |
| return db.get_available_months() | |
| def get_cycle_months(): | |
| return db.get_cycle_months() | |
| 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) | |
| 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.") | |