""" Feature 4: LLM-Driven Variation Analysis Claude explains variations and stabilizations in a testing cycle """ import streamlit as st from core.db_connector import get_db_connector from analysis.llm_analyzer import LLMCycleAnalyzer from ui.components import page_header, analysis_card, follow_up_section page_header( "Variation Analysis", "AI-powered interpretation of pressure/temperature variations and stabilizations in a testing cycle." ) db = get_db_connector() llm = LLMCycleAnalyzer() # Check prerequisites cycle_stats = st.session_state.get('current_cycle_stats') current_cycle = st.session_state.get('current_cycle') plateaus = st.session_state.get('current_plateaus', {}) if not cycle_stats or not current_cycle: st.warning( "No cycle selected. Please go to **Testing Cycles** to detect cycles, " "then **Cycle Detail** to select and analyze a specific cycle." ) st.stop() # Header info st.subheader(f"Cycle {current_cycle['cycle_id']} - {current_cycle['start_time'].strftime('%b %d %H:%M')}") col1, col2, col3 = st.columns(3) col1.metric("Duration", f"{cycle_stats['time_range']['duration_minutes']:.0f} min") if 'discharge_pressure' in cycle_stats: col2.metric("Peak PT130", f"{cycle_stats['discharge_pressure']['peak']:.0f} bar") plateau_count = sum(len(p) for p in plateaus.values()) col3.metric("Plateaus", plateau_count) st.divider() # LLM availability check if not llm.api_available: st.error("Claude API key not configured. Add ANTHROPIC_API_KEY to your .env file to enable AI analysis.") st.stop() # Check for cached result cache_key = f"variation_{current_cycle['cycle_id']}_{current_cycle['start_time']}" prompt_key = f"{cache_key}_prompt" followup_key = f"{cache_key}_followups" cached_result = st.session_state.get(cache_key) if cached_result: analysis_card("AI Analysis", cached_result) # Follow-up section original_prompt = st.session_state.get(prompt_key, "") follow_up_section( session_key=followup_key, llm_analyzer=llm, original_prompt=original_prompt, original_analysis=cached_result, ) if st.button("Re-analyze", key="reanalyze_variation"): del st.session_state[cache_key] st.session_state.pop(prompt_key, None) st.session_state.pop(followup_key, None) st.rerun() else: st.info("Click below to generate an AI-powered analysis of this cycle's variations and stabilizations.") if st.button("Analyze Cycle", type="primary", use_container_width=True): with st.spinner("Claude is analyzing the cycle data..."): result = llm.analyze_cycle_variations( cycle_stats=cycle_stats, plateaus=plateaus, ) # Store the prompt that was used (for follow-up context) st.session_state[prompt_key] = llm._build_variation_prompt( cycle_stats, plateaus, None, llm.retriever.format_for_prompt( llm.retriever.get_context_for_cycle(cycle_stats, plateaus) ) if llm.retriever else "", ) st.session_state[cache_key] = result st.rerun() # Show input data summary with st.expander("Data sent to AI", expanded=False): st.json({ 'time_range': { 'start': str(cycle_stats['time_range']['start']), 'end': str(cycle_stats['time_range']['end']), 'duration_minutes': cycle_stats['time_range']['duration_minutes'], }, 'discharge_pressure': cycle_stats.get('discharge_pressure'), 'compression_ratio': cycle_stats.get('compression_ratio'), 'flow': cycle_stats.get('flow'), 'ramp_rate': cycle_stats.get('ramp_rate'), 'motor': cycle_stats.get('motor'), 'plateaus': { tag: [ {'value': p['value'], 'duration_min': p['duration_minutes']} for p in periods ] for tag, periods in plateaus.items() }, 'performance_vs_targets': cycle_stats.get('performance_vs_targets'), })