Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -89,77 +89,117 @@ def load_css():
|
|
| 89 |
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
| 90 |
margin-bottom: 1rem;
|
| 91 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
.quality-card {{
|
| 93 |
background: white;
|
| 94 |
border: 1px solid {DESIGN_SYSTEM['colors']['border']};
|
| 95 |
-
border-radius:
|
| 96 |
-
padding:
|
| 97 |
-
min-height:
|
| 98 |
-
box-shadow: 0
|
| 99 |
-
|
|
|
|
|
|
|
| 100 |
}}
|
| 101 |
.quality-card:hover {{
|
| 102 |
transform: translateY(-2px);
|
| 103 |
-
box-shadow: 0
|
| 104 |
}}
|
| 105 |
.card-warning {{
|
| 106 |
-
border-left:
|
| 107 |
}}
|
| 108 |
.card-success {{
|
| 109 |
-
border-left:
|
| 110 |
-
}}
|
| 111 |
-
.card-header {{
|
| 112 |
-
margin-bottom: 12px;
|
| 113 |
}}
|
| 114 |
-
.
|
| 115 |
-
font-size:
|
| 116 |
font-weight: 600;
|
| 117 |
color: {DESIGN_SYSTEM['colors']['text']};
|
|
|
|
| 118 |
}}
|
| 119 |
.status-badge {{
|
| 120 |
display: inline-block;
|
| 121 |
-
padding:
|
| 122 |
-
border-radius:
|
| 123 |
font-size: 12px;
|
| 124 |
-
font-weight:
|
| 125 |
-
|
|
|
|
|
|
|
| 126 |
}}
|
| 127 |
-
.
|
| 128 |
-
background:
|
| 129 |
color: {DESIGN_SYSTEM['colors']['warning']};
|
| 130 |
}}
|
| 131 |
-
.
|
| 132 |
-
background:
|
| 133 |
color: {DESIGN_SYSTEM['colors']['success']};
|
| 134 |
}}
|
| 135 |
-
.
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
color: #6B7280;
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
}}
|
| 140 |
.dates-section {{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
font-size: 11px;
|
| 142 |
color: #9CA3AF;
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
font-weight: 600;
|
| 147 |
-
|
|
|
|
|
|
|
| 148 |
}}
|
| 149 |
.success-center {{
|
| 150 |
text-align: center;
|
| 151 |
-
margin-top:
|
| 152 |
-
}}
|
| 153 |
-
.cards-grid {{
|
| 154 |
-
display: flex;
|
| 155 |
-
flex-wrap: wrap;
|
| 156 |
-
gap: 16px;
|
| 157 |
-
margin-top: 20px;
|
| 158 |
}}
|
| 159 |
-
.
|
| 160 |
-
|
| 161 |
-
|
|
|
|
| 162 |
}}
|
|
|
|
| 163 |
.stButton > button {{
|
| 164 |
background: {DESIGN_SYSTEM['colors']['primary']};
|
| 165 |
color: white;
|
|
@@ -457,17 +497,17 @@ def render_ai_section(df, stats, model):
|
|
| 457 |
"""Showcase our AI capabilities and technical achievements"""
|
| 458 |
|
| 459 |
if model:
|
| 460 |
-
st.markdown('<div class="section-header">
|
| 461 |
|
| 462 |
# Demonstrate our capabilities
|
| 463 |
-
st.markdown("###
|
| 464 |
outliers = detect_outliers(df)
|
| 465 |
with st.spinner("Demonstrating our AI capabilities..."):
|
| 466 |
ai_summary = generate_ai_summary(model, df, stats, outliers)
|
| 467 |
st.success(ai_summary)
|
| 468 |
|
| 469 |
# Show our system's intelligence
|
| 470 |
-
st.markdown("###
|
| 471 |
st.markdown("*See how our AI system provides professional insights for your operations:*")
|
| 472 |
|
| 473 |
demo_questions = [
|
|
@@ -479,13 +519,13 @@ def render_ai_section(df, stats, model):
|
|
| 479 |
cols = st.columns(len(demo_questions))
|
| 480 |
for i, question in enumerate(demo_questions):
|
| 481 |
with cols[i]:
|
| 482 |
-
if st.button(f"
|
| 483 |
with st.spinner("Our AI analyzing..."):
|
| 484 |
answer = query_ai(model, stats, question, df)
|
| 485 |
st.info(f"**Nilsen AI Response:** {answer}")
|
| 486 |
|
| 487 |
# Interactive demonstration
|
| 488 |
-
st.markdown("###
|
| 489 |
col1, col2 = st.columns([3, 1])
|
| 490 |
|
| 491 |
with col1:
|
|
@@ -493,8 +533,8 @@ def render_ai_section(df, stats, model):
|
|
| 493 |
placeholder="e.g., 'How can your system improve our operational efficiency?'",
|
| 494 |
key="demo_question")
|
| 495 |
with col2:
|
| 496 |
-
st.markdown("<br>", unsafe_allow_html=True)
|
| 497 |
-
if st.button("
|
| 498 |
if custom_question:
|
| 499 |
with st.spinner("Nilsen AI processing..."):
|
| 500 |
answer = query_ai(model, stats, custom_question, df)
|
|
@@ -507,21 +547,20 @@ def render_ai_section(df, stats, model):
|
|
| 507 |
st.markdown("---")
|
| 508 |
st.markdown("""
|
| 509 |
<div style="background: linear-gradient(135deg, #1E40AF15, #05966925); padding: 1rem; border-radius: 8px; border: 1px solid #1E40AF;">
|
| 510 |
-
<h4>
|
| 511 |
<ul>
|
| 512 |
-
<li>
|
| 513 |
-
<li>
|
| 514 |
-
<li>
|
| 515 |
-
<li>
|
| 516 |
</ul>
|
| 517 |
</div>
|
| 518 |
""", unsafe_allow_html=True)
|
| 519 |
|
| 520 |
else:
|
| 521 |
-
st.markdown('<div class="section-header">
|
| 522 |
-
st.error("
|
| 523 |
-
st.info("
|
| 524 |
-
|
| 525 |
|
| 526 |
def query_ai(model, stats, question, df=None):
|
| 527 |
if not model:
|
|
@@ -805,7 +844,7 @@ def create_csv_export(df, stats):
|
|
| 805 |
return summary_df
|
| 806 |
|
| 807 |
def add_export_section(df, stats, outliers, model):
|
| 808 |
-
st.markdown('<div class="section-header">
|
| 809 |
if 'export_ready' not in st.session_state:
|
| 810 |
st.session_state.export_ready = False
|
| 811 |
if 'pdf_buffer' not in st.session_state:
|
|
@@ -819,13 +858,13 @@ def add_export_section(df, stats, outliers, model):
|
|
| 819 |
with st.spinner("Generating PDF with AI analysis..."):
|
| 820 |
st.session_state.pdf_buffer = create_enhanced_pdf_report(df, stats, outliers, model)
|
| 821 |
st.session_state.export_ready = True
|
| 822 |
-
st.success("
|
| 823 |
except Exception as e:
|
| 824 |
-
st.error(f"
|
| 825 |
st.session_state.export_ready = False
|
| 826 |
if st.session_state.export_ready and st.session_state.pdf_buffer:
|
| 827 |
st.download_button(
|
| 828 |
-
label="
|
| 829 |
data=st.session_state.pdf_buffer,
|
| 830 |
file_name=f"production_report_ai_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
|
| 831 |
mime="application/pdf",
|
|
@@ -835,13 +874,13 @@ def add_export_section(df, stats, outliers, model):
|
|
| 835 |
if st.button("Generate CSV Summary", key="generate_csv_btn", type="primary"):
|
| 836 |
try:
|
| 837 |
st.session_state.csv_data = create_csv_export(df, stats)
|
| 838 |
-
st.success("
|
| 839 |
except Exception as e:
|
| 840 |
-
st.error(f"
|
| 841 |
if st.session_state.csv_data is not None:
|
| 842 |
csv_string = st.session_state.csv_data.to_csv(index=False)
|
| 843 |
st.download_button(
|
| 844 |
-
label="
|
| 845 |
data=csv_string,
|
| 846 |
file_name=f"production_summary_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
|
| 847 |
mime="text/csv",
|
|
@@ -861,7 +900,7 @@ def main():
|
|
| 861 |
load_css()
|
| 862 |
st.markdown("""
|
| 863 |
<div class="main-header">
|
| 864 |
-
<div class="main-title">
|
| 865 |
<div class="main-subtitle">Nilsen Service & Consulting AS | Real-time Production Analytics & Recommendations</div>
|
| 866 |
</div>
|
| 867 |
""", unsafe_allow_html=True)
|
|
@@ -874,16 +913,16 @@ def main():
|
|
| 874 |
st.session_state.current_stats = None
|
| 875 |
|
| 876 |
with st.sidebar:
|
| 877 |
-
st.markdown("###
|
| 878 |
uploaded_file = st.file_uploader("Upload Production Data", type=['csv'])
|
| 879 |
st.markdown("---")
|
| 880 |
-
st.markdown("###
|
| 881 |
col1, col2 = st.columns(2)
|
| 882 |
with col1:
|
| 883 |
-
if st.button("
|
| 884 |
st.session_state.load_preset = "2024"
|
| 885 |
with col2:
|
| 886 |
-
if st.button("
|
| 887 |
st.session_state.load_preset = "2025"
|
| 888 |
st.markdown("---")
|
| 889 |
st.markdown("""
|
|
@@ -894,9 +933,9 @@ def main():
|
|
| 894 |
- `shift`: day/night (optional)
|
| 895 |
""")
|
| 896 |
if model:
|
| 897 |
-
st.success("
|
| 898 |
else:
|
| 899 |
-
st.warning("
|
| 900 |
|
| 901 |
df = st.session_state.current_df
|
| 902 |
stats = st.session_state.current_stats
|
|
@@ -907,9 +946,9 @@ def main():
|
|
| 907 |
stats = get_material_stats(df)
|
| 908 |
st.session_state.current_df = df
|
| 909 |
st.session_state.current_stats = stats
|
| 910 |
-
st.success("
|
| 911 |
except Exception as e:
|
| 912 |
-
st.error(f"
|
| 913 |
elif 'load_preset' in st.session_state:
|
| 914 |
year = st.session_state.load_preset
|
| 915 |
try:
|
|
@@ -919,14 +958,14 @@ def main():
|
|
| 919 |
stats = get_material_stats(df)
|
| 920 |
st.session_state.current_df = df
|
| 921 |
st.session_state.current_stats = stats
|
| 922 |
-
st.success(f"
|
| 923 |
except Exception as e:
|
| 924 |
-
st.error(f"
|
| 925 |
finally:
|
| 926 |
del st.session_state.load_preset
|
| 927 |
|
| 928 |
if df is not None and stats is not None:
|
| 929 |
-
st.markdown('<div class="section-header">
|
| 930 |
materials = [k for k in stats.keys() if k != '_total_']
|
| 931 |
all_metrics = materials + ['_total_']
|
| 932 |
|
|
@@ -954,7 +993,7 @@ def main():
|
|
| 954 |
)
|
| 955 |
st.caption(f"Daily avg: {info['daily_avg']:,.0f} kg")
|
| 956 |
|
| 957 |
-
st.markdown('<div class="section-header">
|
| 958 |
col1, col2 = st.columns([3, 1])
|
| 959 |
with col2:
|
| 960 |
time_view = st.selectbox("Time Period", ["daily", "weekly", "monthly"], key="time_view_select")
|
|
@@ -965,7 +1004,7 @@ def main():
|
|
| 965 |
st.plotly_chart(total_chart, use_container_width=True)
|
| 966 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 967 |
|
| 968 |
-
st.markdown('<div class="section-header">
|
| 969 |
col1, col2 = st.columns([3, 1])
|
| 970 |
with col2:
|
| 971 |
selected_materials = st.multiselect(
|
|
@@ -983,21 +1022,22 @@ def main():
|
|
| 983 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 984 |
|
| 985 |
if 'shift' in df.columns:
|
| 986 |
-
st.markdown('<div class="section-header">
|
| 987 |
with st.container():
|
| 988 |
st.markdown('<div class="chart-container">', unsafe_allow_html=True)
|
| 989 |
shift_chart = create_shift_trend_chart(df, time_view)
|
| 990 |
st.plotly_chart(shift_chart, use_container_width=True)
|
| 991 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 992 |
|
| 993 |
-
|
|
|
|
| 994 |
outliers = detect_outliers(df)
|
| 995 |
|
| 996 |
-
# Create
|
| 997 |
materials_list = list(outliers.items())
|
| 998 |
num_materials = len(materials_list)
|
| 999 |
|
| 1000 |
-
# Create
|
| 1001 |
cols_per_row = min(3, num_materials) if num_materials <= 6 else 4
|
| 1002 |
|
| 1003 |
for i in range(0, num_materials, cols_per_row):
|
|
@@ -1009,34 +1049,41 @@ def main():
|
|
| 1009 |
material_title = material.replace("_", " ").title()
|
| 1010 |
|
| 1011 |
if info['count'] > 0:
|
| 1012 |
-
#
|
| 1013 |
-
dates_list = info['dates']
|
| 1014 |
-
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
dates_display = '<br/>'.join(dates_list[:5]) + f'<br/>(+{len(dates_list)-5} more)'
|
| 1018 |
|
| 1019 |
card_html = f'''
|
| 1020 |
<div class="quality-card card-warning">
|
| 1021 |
-
<div class="card-
|
| 1022 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1023 |
</div>
|
| 1024 |
-
<div class="status-badge warning-badge">{info["count"]} Outlier{"s" if info["count"] > 1 else ""}</div>
|
| 1025 |
-
<div class="range-text">Normal range: <strong>{info["range"]}</strong></div>
|
| 1026 |
<div class="dates-section">
|
| 1027 |
-
<div class="dates-
|
| 1028 |
-
{dates_display}
|
| 1029 |
</div>
|
| 1030 |
</div>
|
| 1031 |
'''
|
| 1032 |
else:
|
| 1033 |
card_html = f'''
|
| 1034 |
<div class="quality-card card-success">
|
| 1035 |
-
<div class="card-
|
| 1036 |
-
|
|
|
|
|
|
|
|
|
|
| 1037 |
</div>
|
| 1038 |
<div class="success-center">
|
| 1039 |
-
<
|
| 1040 |
</div>
|
| 1041 |
</div>
|
| 1042 |
'''
|
|
@@ -1046,7 +1093,7 @@ def main():
|
|
| 1046 |
add_export_section(df, stats, outliers, model)
|
| 1047 |
|
| 1048 |
if model:
|
| 1049 |
-
st.markdown('<div class="section-header">
|
| 1050 |
quick_questions = [
|
| 1051 |
"How does production distribution on weekdays compare to weekends?",
|
| 1052 |
"Which material exhibits the most volatility in our dataset?",
|
|
|
|
| 89 |
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
| 90 |
margin-bottom: 1rem;
|
| 91 |
}}
|
| 92 |
+
|
| 93 |
+
/* Updated Quality Check Styles */
|
| 94 |
+
.quality-grid {{
|
| 95 |
+
display: grid;
|
| 96 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 97 |
+
gap: 16px;
|
| 98 |
+
margin-top: 20px;
|
| 99 |
+
}}
|
| 100 |
.quality-card {{
|
| 101 |
background: white;
|
| 102 |
border: 1px solid {DESIGN_SYSTEM['colors']['border']};
|
| 103 |
+
border-radius: 12px;
|
| 104 |
+
padding: 24px;
|
| 105 |
+
min-height: 140px;
|
| 106 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
| 107 |
+
border-left: 4px solid transparent;
|
| 108 |
+
transition: all 0.3s ease;
|
| 109 |
+
position: relative;
|
| 110 |
}}
|
| 111 |
.quality-card:hover {{
|
| 112 |
transform: translateY(-2px);
|
| 113 |
+
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
| 114 |
}}
|
| 115 |
.card-warning {{
|
| 116 |
+
border-left-color: {DESIGN_SYSTEM['colors']['warning']};
|
| 117 |
}}
|
| 118 |
.card-success {{
|
| 119 |
+
border-left-color: {DESIGN_SYSTEM['colors']['success']};
|
|
|
|
|
|
|
|
|
|
| 120 |
}}
|
| 121 |
+
.card-title {{
|
| 122 |
+
font-size: 16px;
|
| 123 |
font-weight: 600;
|
| 124 |
color: {DESIGN_SYSTEM['colors']['text']};
|
| 125 |
+
margin-bottom: 12px;
|
| 126 |
}}
|
| 127 |
.status-badge {{
|
| 128 |
display: inline-block;
|
| 129 |
+
padding: 6px 12px;
|
| 130 |
+
border-radius: 16px;
|
| 131 |
font-size: 12px;
|
| 132 |
+
font-weight: 600;
|
| 133 |
+
text-transform: uppercase;
|
| 134 |
+
letter-spacing: 0.5px;
|
| 135 |
+
margin-bottom: 12px;
|
| 136 |
}}
|
| 137 |
+
.status-warning {{
|
| 138 |
+
background: rgba(217, 119, 6, 0.15);
|
| 139 |
color: {DESIGN_SYSTEM['colors']['warning']};
|
| 140 |
}}
|
| 141 |
+
.status-success {{
|
| 142 |
+
background: rgba(16, 185, 129, 0.15);
|
| 143 |
color: {DESIGN_SYSTEM['colors']['success']};
|
| 144 |
}}
|
| 145 |
+
.outliers-info {{
|
| 146 |
+
display: flex;
|
| 147 |
+
align-items: baseline;
|
| 148 |
+
margin-bottom: 12px;
|
| 149 |
+
}}
|
| 150 |
+
.outliers-count {{
|
| 151 |
+
font-size: 24px;
|
| 152 |
+
font-weight: 700;
|
| 153 |
+
margin-right: 8px;
|
| 154 |
+
}}
|
| 155 |
+
.outliers-count.warning {{
|
| 156 |
+
color: {DESIGN_SYSTEM['colors']['warning']};
|
| 157 |
+
}}
|
| 158 |
+
.outliers-count.success {{
|
| 159 |
+
color: {DESIGN_SYSTEM['colors']['success']};
|
| 160 |
+
}}
|
| 161 |
+
.outliers-text {{
|
| 162 |
color: #6B7280;
|
| 163 |
+
font-size: 14px;
|
| 164 |
+
}}
|
| 165 |
+
.range-info {{
|
| 166 |
+
background: #F8F9FA;
|
| 167 |
+
padding: 12px;
|
| 168 |
+
border-radius: 8px;
|
| 169 |
+
margin-bottom: 12px;
|
| 170 |
+
font-size: 13px;
|
| 171 |
+
color: #495057;
|
| 172 |
+
}}
|
| 173 |
+
.range-label {{
|
| 174 |
+
font-weight: 600;
|
| 175 |
+
color: {DESIGN_SYSTEM['colors']['text']};
|
| 176 |
+
margin-bottom: 4px;
|
| 177 |
}}
|
| 178 |
.dates-section {{
|
| 179 |
+
font-size: 12px;
|
| 180 |
+
color: #6B7280;
|
| 181 |
+
}}
|
| 182 |
+
.dates-label {{
|
| 183 |
font-size: 11px;
|
| 184 |
color: #9CA3AF;
|
| 185 |
+
text-transform: uppercase;
|
| 186 |
+
letter-spacing: 0.5px;
|
| 187 |
+
margin-bottom: 8px;
|
| 188 |
font-weight: 600;
|
| 189 |
+
}}
|
| 190 |
+
.dates-list {{
|
| 191 |
+
line-height: 1.6;
|
| 192 |
}}
|
| 193 |
.success-center {{
|
| 194 |
text-align: center;
|
| 195 |
+
margin-top: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
}}
|
| 197 |
+
.success-message {{
|
| 198 |
+
font-size: 14px;
|
| 199 |
+
color: {DESIGN_SYSTEM['colors']['success']};
|
| 200 |
+
font-weight: 500;
|
| 201 |
}}
|
| 202 |
+
|
| 203 |
.stButton > button {{
|
| 204 |
background: {DESIGN_SYSTEM['colors']['primary']};
|
| 205 |
color: white;
|
|
|
|
| 497 |
"""Showcase our AI capabilities and technical achievements"""
|
| 498 |
|
| 499 |
if model:
|
| 500 |
+
st.markdown('<div class="section-header">AI-Powered Solution</div>', unsafe_allow_html=True)
|
| 501 |
|
| 502 |
# Demonstrate our capabilities
|
| 503 |
+
st.markdown("### **Nilsen Service & Consulting - Technical Achievements**")
|
| 504 |
outliers = detect_outliers(df)
|
| 505 |
with st.spinner("Demonstrating our AI capabilities..."):
|
| 506 |
ai_summary = generate_ai_summary(model, df, stats, outliers)
|
| 507 |
st.success(ai_summary)
|
| 508 |
|
| 509 |
# Show our system's intelligence
|
| 510 |
+
st.markdown("### **Experience Our Advanced Analytics**")
|
| 511 |
st.markdown("*See how our AI system provides professional insights for your operations:*")
|
| 512 |
|
| 513 |
demo_questions = [
|
|
|
|
| 519 |
cols = st.columns(len(demo_questions))
|
| 520 |
for i, question in enumerate(demo_questions):
|
| 521 |
with cols[i]:
|
| 522 |
+
if st.button(f"{question}", key=f"demo_q_{i}"):
|
| 523 |
with st.spinner("Our AI analyzing..."):
|
| 524 |
answer = query_ai(model, stats, question, df)
|
| 525 |
st.info(f"**Nilsen AI Response:** {answer}")
|
| 526 |
|
| 527 |
# Interactive demonstration
|
| 528 |
+
st.markdown("### **Test Our AI Intelligence**")
|
| 529 |
col1, col2 = st.columns([3, 1])
|
| 530 |
|
| 531 |
with col1:
|
|
|
|
| 533 |
placeholder="e.g., 'How can your system improve our operational efficiency?'",
|
| 534 |
key="demo_question")
|
| 535 |
with col2:
|
| 536 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 537 |
+
if st.button("See Our AI in Action", key="demo_btn", type="primary"):
|
| 538 |
if custom_question:
|
| 539 |
with st.spinner("Nilsen AI processing..."):
|
| 540 |
answer = query_ai(model, stats, custom_question, df)
|
|
|
|
| 547 |
st.markdown("---")
|
| 548 |
st.markdown("""
|
| 549 |
<div style="background: linear-gradient(135deg, #1E40AF15, #05966925); padding: 1rem; border-radius: 8px; border: 1px solid #1E40AF;">
|
| 550 |
+
<h4>Why Choose Nilsen Service & Consulting?</h4>
|
| 551 |
<ul>
|
| 552 |
+
<li><strong>Advanced AI Integration:</strong> Cutting-edge analytics for your operations</li>
|
| 553 |
+
<li><strong>Professional Reliability:</strong> Enterprise-grade monitoring systems</li>
|
| 554 |
+
<li><strong>Proven Results:</strong> Data-driven insights that deliver ROI</li>
|
| 555 |
+
<li><strong>Complete Solution:</strong> From data processing to actionable recommendations</li>
|
| 556 |
</ul>
|
| 557 |
</div>
|
| 558 |
""", unsafe_allow_html=True)
|
| 559 |
|
| 560 |
else:
|
| 561 |
+
st.markdown('<div class="section-header">AI-Powered Solution</div>', unsafe_allow_html=True)
|
| 562 |
+
st.error("AI demonstration requires API configuration")
|
| 563 |
+
st.info("**Our AI system provides:** Advanced analytics, predictive insights, automated reporting, and intelligent recommendations for production optimization.")
|
|
|
|
| 564 |
|
| 565 |
def query_ai(model, stats, question, df=None):
|
| 566 |
if not model:
|
|
|
|
| 844 |
return summary_df
|
| 845 |
|
| 846 |
def add_export_section(df, stats, outliers, model):
|
| 847 |
+
st.markdown('<div class="section-header">Export Reports</div>', unsafe_allow_html=True)
|
| 848 |
if 'export_ready' not in st.session_state:
|
| 849 |
st.session_state.export_ready = False
|
| 850 |
if 'pdf_buffer' not in st.session_state:
|
|
|
|
| 858 |
with st.spinner("Generating PDF with AI analysis..."):
|
| 859 |
st.session_state.pdf_buffer = create_enhanced_pdf_report(df, stats, outliers, model)
|
| 860 |
st.session_state.export_ready = True
|
| 861 |
+
st.success("PDF report with AI analysis generated successfully!")
|
| 862 |
except Exception as e:
|
| 863 |
+
st.error(f"PDF generation failed: {str(e)}")
|
| 864 |
st.session_state.export_ready = False
|
| 865 |
if st.session_state.export_ready and st.session_state.pdf_buffer:
|
| 866 |
st.download_button(
|
| 867 |
+
label="Download PDF Report",
|
| 868 |
data=st.session_state.pdf_buffer,
|
| 869 |
file_name=f"production_report_ai_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
|
| 870 |
mime="application/pdf",
|
|
|
|
| 874 |
if st.button("Generate CSV Summary", key="generate_csv_btn", type="primary"):
|
| 875 |
try:
|
| 876 |
st.session_state.csv_data = create_csv_export(df, stats)
|
| 877 |
+
st.success("CSV summary generated successfully!")
|
| 878 |
except Exception as e:
|
| 879 |
+
st.error(f"CSV generation failed: {str(e)}")
|
| 880 |
if st.session_state.csv_data is not None:
|
| 881 |
csv_string = st.session_state.csv_data.to_csv(index=False)
|
| 882 |
st.download_button(
|
| 883 |
+
label="Download CSV Summary",
|
| 884 |
data=csv_string,
|
| 885 |
file_name=f"production_summary_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
|
| 886 |
mime="text/csv",
|
|
|
|
| 900 |
load_css()
|
| 901 |
st.markdown("""
|
| 902 |
<div class="main-header">
|
| 903 |
+
<div class="main-title">Production Monitor with AI Insights</div>
|
| 904 |
<div class="main-subtitle">Nilsen Service & Consulting AS | Real-time Production Analytics & Recommendations</div>
|
| 905 |
</div>
|
| 906 |
""", unsafe_allow_html=True)
|
|
|
|
| 913 |
st.session_state.current_stats = None
|
| 914 |
|
| 915 |
with st.sidebar:
|
| 916 |
+
st.markdown("### Data Source")
|
| 917 |
uploaded_file = st.file_uploader("Upload Production Data", type=['csv'])
|
| 918 |
st.markdown("---")
|
| 919 |
+
st.markdown("### Quick Load")
|
| 920 |
col1, col2 = st.columns(2)
|
| 921 |
with col1:
|
| 922 |
+
if st.button("2024 Data", type="primary", key="load_2024"):
|
| 923 |
st.session_state.load_preset = "2024"
|
| 924 |
with col2:
|
| 925 |
+
if st.button("2025 Data", type="primary", key="load_2025"):
|
| 926 |
st.session_state.load_preset = "2025"
|
| 927 |
st.markdown("---")
|
| 928 |
st.markdown("""
|
|
|
|
| 933 |
- `shift`: day/night (optional)
|
| 934 |
""")
|
| 935 |
if model:
|
| 936 |
+
st.success("AI Assistant Ready")
|
| 937 |
else:
|
| 938 |
+
st.warning("AI Assistant Unavailable")
|
| 939 |
|
| 940 |
df = st.session_state.current_df
|
| 941 |
stats = st.session_state.current_stats
|
|
|
|
| 946 |
stats = get_material_stats(df)
|
| 947 |
st.session_state.current_df = df
|
| 948 |
st.session_state.current_stats = stats
|
| 949 |
+
st.success("Data uploaded successfully!")
|
| 950 |
except Exception as e:
|
| 951 |
+
st.error(f"Error loading uploaded file: {str(e)}")
|
| 952 |
elif 'load_preset' in st.session_state:
|
| 953 |
year = st.session_state.load_preset
|
| 954 |
try:
|
|
|
|
| 958 |
stats = get_material_stats(df)
|
| 959 |
st.session_state.current_df = df
|
| 960 |
st.session_state.current_stats = stats
|
| 961 |
+
st.success(f"{year} data loaded successfully!")
|
| 962 |
except Exception as e:
|
| 963 |
+
st.error(f"Error loading {year} data: {str(e)}")
|
| 964 |
finally:
|
| 965 |
del st.session_state.load_preset
|
| 966 |
|
| 967 |
if df is not None and stats is not None:
|
| 968 |
+
st.markdown('<div class="section-header">Material Overview</div>', unsafe_allow_html=True)
|
| 969 |
materials = [k for k in stats.keys() if k != '_total_']
|
| 970 |
all_metrics = materials + ['_total_']
|
| 971 |
|
|
|
|
| 993 |
)
|
| 994 |
st.caption(f"Daily avg: {info['daily_avg']:,.0f} kg")
|
| 995 |
|
| 996 |
+
st.markdown('<div class="section-header">Production Trends</div>', unsafe_allow_html=True)
|
| 997 |
col1, col2 = st.columns([3, 1])
|
| 998 |
with col2:
|
| 999 |
time_view = st.selectbox("Time Period", ["daily", "weekly", "monthly"], key="time_view_select")
|
|
|
|
| 1004 |
st.plotly_chart(total_chart, use_container_width=True)
|
| 1005 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1006 |
|
| 1007 |
+
st.markdown('<div class="section-header">Materials Analysis</div>', unsafe_allow_html=True)
|
| 1008 |
col1, col2 = st.columns([3, 1])
|
| 1009 |
with col2:
|
| 1010 |
selected_materials = st.multiselect(
|
|
|
|
| 1022 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1023 |
|
| 1024 |
if 'shift' in df.columns:
|
| 1025 |
+
st.markdown('<div class="section-header">Shift Analysis</div>', unsafe_allow_html=True)
|
| 1026 |
with st.container():
|
| 1027 |
st.markdown('<div class="chart-container">', unsafe_allow_html=True)
|
| 1028 |
shift_chart = create_shift_trend_chart(df, time_view)
|
| 1029 |
st.plotly_chart(shift_chart, use_container_width=True)
|
| 1030 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1031 |
|
| 1032 |
+
# Updated Quality Check Section
|
| 1033 |
+
st.markdown('<div class="section-header">Quality Check</div>', unsafe_allow_html=True)
|
| 1034 |
outliers = detect_outliers(df)
|
| 1035 |
|
| 1036 |
+
# Create quality cards using new modern design
|
| 1037 |
materials_list = list(outliers.items())
|
| 1038 |
num_materials = len(materials_list)
|
| 1039 |
|
| 1040 |
+
# Create responsive grid
|
| 1041 |
cols_per_row = min(3, num_materials) if num_materials <= 6 else 4
|
| 1042 |
|
| 1043 |
for i in range(0, num_materials, cols_per_row):
|
|
|
|
| 1049 |
material_title = material.replace("_", " ").title()
|
| 1050 |
|
| 1051 |
if info['count'] > 0:
|
| 1052 |
+
# Show dates in compact format
|
| 1053 |
+
dates_list = info['dates'][:5] # Show max 5 dates
|
| 1054 |
+
dates_display = ', '.join(dates_list)
|
| 1055 |
+
if len(info['dates']) > 5:
|
| 1056 |
+
dates_display += f' (+{len(info["dates"])-5} more)'
|
|
|
|
| 1057 |
|
| 1058 |
card_html = f'''
|
| 1059 |
<div class="quality-card card-warning">
|
| 1060 |
+
<div class="card-title">{material_title}</div>
|
| 1061 |
+
<div class="status-badge status-warning">Warning</div>
|
| 1062 |
+
<div class="outliers-info">
|
| 1063 |
+
<span class="outliers-count warning">{info["count"]}</span>
|
| 1064 |
+
<span class="outliers-text">Outliers</span>
|
| 1065 |
+
</div>
|
| 1066 |
+
<div class="range-info">
|
| 1067 |
+
<div class="range-label">Normal Range</div>
|
| 1068 |
+
<div>{info["range"]}</div>
|
| 1069 |
</div>
|
|
|
|
|
|
|
| 1070 |
<div class="dates-section">
|
| 1071 |
+
<div class="dates-label">Dates</div>
|
| 1072 |
+
<div class="dates-list">{dates_display}</div>
|
| 1073 |
</div>
|
| 1074 |
</div>
|
| 1075 |
'''
|
| 1076 |
else:
|
| 1077 |
card_html = f'''
|
| 1078 |
<div class="quality-card card-success">
|
| 1079 |
+
<div class="card-title">{material_title}</div>
|
| 1080 |
+
<div class="status-badge status-success">Normal</div>
|
| 1081 |
+
<div class="outliers-info">
|
| 1082 |
+
<span class="outliers-count success">0</span>
|
| 1083 |
+
<span class="outliers-text">Outliers</span>
|
| 1084 |
</div>
|
| 1085 |
<div class="success-center">
|
| 1086 |
+
<div class="success-message">All values within normal range</div>
|
| 1087 |
</div>
|
| 1088 |
</div>
|
| 1089 |
'''
|
|
|
|
| 1093 |
add_export_section(df, stats, outliers, model)
|
| 1094 |
|
| 1095 |
if model:
|
| 1096 |
+
st.markdown('<div class="section-header">AI Insights</div>', unsafe_allow_html=True)
|
| 1097 |
quick_questions = [
|
| 1098 |
"How does production distribution on weekdays compare to weekends?",
|
| 1099 |
"Which material exhibits the most volatility in our dataset?",
|