| import streamlit as st | |
| import warnings | |
| warnings.filterwarnings("ignore") | |
| from config import get_css_styles, get_settings, get_translation | |
| from data_handler import load_preset_data, load_uploaded_data, data_health_check | |
| from analytics import get_material_stats, detect_outliers, create_total_production_chart, create_materials_trend_chart, create_shift_trend_chart | |
| from ai_engine import init_ai_model | |
| from typing import Dict | |
| from datetime import datetime | |
| from utils import init_session_state | |
| st.set_page_config( | |
| page_title="Production Monitor with AI Insights | Nilsen Service & Consulting", | |
| page_icon="🏭", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| def render_welcome_screen(t: Dict): | |
| st.markdown(f'<div class="section-header">{t["welcome_title"]}</div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown(f"### {t['welcome_quick_start']}") | |
| st.markdown(t['welcome_steps']) | |
| with col2: | |
| st.markdown(f"### {t['welcome_features']}") | |
| st.markdown(t['welcome_features_list']) | |
| st.info(t['welcome_ready']) | |
| def main(): | |
| st.markdown(get_css_styles(), unsafe_allow_html=True) | |
| lang = st.sidebar.selectbox("Language", ["English", "Norsk"], index=1) | |
| t = get_translation(lang) | |
| st.markdown(f""" | |
| <div class="main-header"> | |
| <div class="main-title">{t['page_title']}</div> | |
| <div class="main-subtitle">{t['page_subtitle']}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| init_session_state() | |
| model = init_ai_model() | |
| settings = get_settings() | |
| with st.sidebar: | |
| st.markdown(f"### {t['sidebar_data_source']}") | |
| uploaded_file = st.file_uploader(t['sidebar_upload'], type=['csv']) | |
| st.markdown("---") | |
| st.markdown(f"### {t['sidebar_quick_load']}") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("2024 Data", type="primary", key="load_2024"): | |
| st.session_state.load_preset = "2024" | |
| with col2: | |
| if st.button("2025 Data", type="primary", key="load_2025"): | |
| st.session_state.load_preset = "2025" | |
| st.markdown("---") | |
| st.markdown(t['sidebar_format_hint']) | |
| if model: | |
| st.success(f"{t['ai_ready']} ({settings.environment})") | |
| else: | |
| st.warning(t['ai_unavailable']) | |
| st.info(t['ai_config_hint']) | |
| df = st.session_state.current_df | |
| stats = st.session_state.current_stats | |
| if uploaded_file: | |
| try: | |
| df = load_uploaded_data(uploaded_file) | |
| stats = get_material_stats(df) | |
| st.session_state.current_df = df | |
| st.session_state.current_stats = stats | |
| st.success(t['data_uploaded']) | |
| except Exception as e: | |
| st.error(f"Error loading uploaded file: {str(e)}") | |
| elif 'load_preset' in st.session_state: | |
| year = st.session_state.load_preset | |
| try: | |
| with st.spinner(f"Loading {year} data..."): | |
| df = load_preset_data(year) | |
| if df is not None: | |
| stats = get_material_stats(df) | |
| st.session_state.current_df = df | |
| st.session_state.current_stats = stats | |
| st.success(f"{year} {t['data_loaded']}") | |
| except Exception as e: | |
| st.error(f"Error loading {year} data: {str(e)}") | |
| finally: | |
| del st.session_state.load_preset | |
| if df is not None and stats is not None: | |
| health = data_health_check(df) | |
| with st.expander(t['data_health']): | |
| cols = st.columns(4) | |
| for i, (key, value) in enumerate(health.items()): | |
| with cols[i]: | |
| st.metric(key, value) | |
| st.markdown(f'<div class="section-header">{t["section_material_overview"]}</div>', unsafe_allow_html=True) | |
| materials = [k for k in stats.keys() if k != '_total_'] | |
| cols = st.columns(4) | |
| for i, material in enumerate(materials[:3]): | |
| info = stats[material] | |
| with cols[i]: | |
| st.metric( | |
| label=material.replace('_', ' ').title(), | |
| value=f"{info['total']:,.0f} kg", | |
| delta=f"{info['percentage']:.1f}% of total" | |
| ) | |
| st.caption(f"{t['metric_daily_avg']}: {info['daily_avg']:,.0f} kg") | |
| if len(materials) >= 3: | |
| total_info = stats['_total_'] | |
| with cols[3]: | |
| st.metric( | |
| label=t['metric_total'], | |
| value=f"{total_info['total']:,.0f} kg", | |
| delta="100% of total" | |
| ) | |
| st.caption(f"{t['metric_daily_avg']}: {total_info['daily_avg']:,.0f} kg") | |
| st.markdown(f'<div class="section-header">{t["section_production_trends"]}</div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns([3, 1]) | |
| with col2: | |
| time_view = st.selectbox(t['time_period'], ["daily", "weekly", "monthly"], key="time_view_select") | |
| with col1: | |
| with st.container(): | |
| st.markdown('<div class="chart-container">', unsafe_allow_html=True) | |
| total_chart = create_total_production_chart(df, time_view, lang) | |
| st.plotly_chart(total_chart, use_container_width=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| st.markdown(f'<div class="section-header">{t["section_materials_analysis"]}</div>', unsafe_allow_html=True) | |
| col1, col2 = st.columns([3, 1]) | |
| with col2: | |
| selected_materials = st.multiselect( | |
| t['select_materials'], | |
| options=materials, | |
| default=materials, | |
| key="materials_select" | |
| ) | |
| with col1: | |
| if selected_materials: | |
| with st.container(): | |
| st.markdown('<div class="chart-container">', unsafe_allow_html=True) | |
| materials_chart = create_materials_trend_chart(df, time_view, selected_materials, lang) | |
| st.plotly_chart(materials_chart, use_container_width=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| if 'shift' in df.columns: | |
| st.markdown(f'<div class="section-header">{t["section_shift_analysis"]}</div>', unsafe_allow_html=True) | |
| with st.container(): | |
| st.markdown('<div class="chart-container">', unsafe_allow_html=True) | |
| shift_chart = create_shift_trend_chart(df, time_view, lang) | |
| st.plotly_chart(shift_chart, use_container_width=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| outliers = detect_outliers(df) | |
| st.markdown(f'<div class="section-header">{t["section_quality_check"]}</div>', unsafe_allow_html=True) | |
| cols = st.columns(len(outliers)) | |
| for i, (material, info) in enumerate(outliers.items()): | |
| with cols[i]: | |
| if info['count'] > 0: | |
| dates_str = ", ".join(info['dates']) | |
| st.markdown(f'''<div class="alert-warning"> | |
| <strong>{material.title()}</strong><br> | |
| {info["count"]} {t['quality_outliers']}<br> | |
| {t['quality_normal_range']}: {info["range"]}<br> | |
| <div class="quality-dates">Dates: {dates_str}</div> | |
| </div>''', unsafe_allow_html=True) | |
| else: | |
| st.markdown(f'<div class="alert-success"><strong>{material.title()}</strong><br>{t["quality_normal"]}</div>', | |
| unsafe_allow_html=True) | |
| st.markdown(f'<div class="section-header">{t["section_export"]}</div>', unsafe_allow_html=True) | |
| col1, col2, col3 = st.columns(3) | |
| with col1: | |
| if st.button(t['btn_generate_pdf'], key="generate_pdf_btn", type="primary"): | |
| try: | |
| with st.spinner(t['pdf_generating']): | |
| from pdf_generator import create_enhanced_pdf_report | |
| st.session_state.pdf_buffer = create_enhanced_pdf_report(df, stats, outliers, model, lang) | |
| st.session_state.export_ready = True | |
| st.success(t['pdf_success']) | |
| except Exception as e: | |
| st.error(f"{t['pdf_failed']}: {str(e)}") | |
| st.session_state.export_ready = False | |
| if st.session_state.export_ready and st.session_state.pdf_buffer: | |
| st.download_button( | |
| label=t['btn_download_pdf'], | |
| data=st.session_state.pdf_buffer, | |
| file_name=f"production_report_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf", | |
| mime="application/pdf", | |
| key="download_pdf_btn" | |
| ) | |
| with col2: | |
| if st.button(t['btn_generate_csv'], key="generate_csv_btn", type="primary"): | |
| try: | |
| from pdf_generator import create_csv_export | |
| st.session_state.csv_data = create_csv_export(df, stats) | |
| st.success(t['csv_success']) | |
| except Exception as e: | |
| st.error(f"{t['csv_failed']}: {str(e)}") | |
| if st.session_state.csv_data is not None: | |
| csv_string = st.session_state.csv_data.to_csv(index=False) | |
| st.download_button( | |
| label=t['btn_download_csv'], | |
| data=csv_string, | |
| file_name=f"production_summary_{datetime.now().strftime('%Y%m%d_%H%M')}.csv", | |
| mime="text/csv", | |
| key="download_csv_btn" | |
| ) | |
| with col3: | |
| csv_string = df.to_csv(index=False) | |
| st.download_button( | |
| label=t['btn_download_raw'], | |
| data=csv_string, | |
| file_name=f"raw_production_data_{datetime.now().strftime('%Y%m%d_%H%M')}.csv", | |
| mime="text/csv", | |
| key="download_raw_btn" | |
| ) | |
| if model: | |
| st.markdown(f'<div class="section-header">{t["section_ai_insights"]}</div>', unsafe_allow_html=True) | |
| quick_questions = [t['ai_quick_q1'], t['ai_quick_q2'], t['ai_quick_q3']] | |
| cols = st.columns(len(quick_questions)) | |
| for i, q in enumerate(quick_questions): | |
| with cols[i]: | |
| if st.button(q, key=f"ai_q_{i}"): | |
| from ai_engine import query_ai | |
| with st.spinner(t['ai_analyzing']): | |
| answer = query_ai(model, stats, q, df, lang) | |
| st.info(answer) | |
| custom_question = st.text_input( | |
| t['ai_ask_label'], | |
| placeholder=t['ai_custom_placeholder'], | |
| key="custom_ai_question" | |
| ) | |
| if custom_question and st.button(t['ai_ask_btn'], key="ask_ai_btn"): | |
| from ai_engine import query_ai | |
| with st.spinner(t['ai_analyzing']): | |
| answer = query_ai(model, stats, custom_question, df, lang) | |
| st.success(f"**Q:** {custom_question}") | |
| st.write(f"**A:** {answer}") | |
| else: | |
| st.markdown(f'<div class="section-header">{t["section_ai_config"]}</div>', unsafe_allow_html=True) | |
| st.info(t['ai_config_info']) | |
| else: | |
| render_welcome_screen(t) | |
| if __name__ == "__main__": | |
| main() |