entropy25 commited on
Commit
3bb8d22
Β·
verified Β·
1 Parent(s): 7fe86c5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -354
app.py CHANGED
@@ -16,6 +16,8 @@ import plotly.io as pio
16
  import tempfile
17
  import os
18
  import requests
 
 
19
 
20
  DESIGN_SYSTEM = {
21
  'colors': {
@@ -89,117 +91,20 @@ def load_css():
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;
@@ -223,10 +128,21 @@ def load_css():
223
 
224
  @st.cache_resource
225
  def init_ai():
226
- api_key = st.secrets.get("GOOGLE_API_KEY", "")
 
 
 
 
 
 
 
227
  if api_key:
228
- genai.configure(api_key=api_key)
229
- return genai.GenerativeModel('gemini-1.5-flash')
 
 
 
 
230
  return None
231
 
232
  @st.cache_data
@@ -256,7 +172,7 @@ def generate_sample_data(year):
256
  dates = pd.date_range(start=start_date, end=end_date, freq='D')
257
  weekdays = dates[dates.weekday < 5]
258
  data = []
259
- materials = ['steel', 'aluminum', 'polymer', 'copper']
260
  shifts = ['day', 'night']
261
  for date in weekdays:
262
  for material in materials:
@@ -264,7 +180,7 @@ def generate_sample_data(year):
264
  base_weight = {
265
  'steel': 1500,
266
  'aluminum': 800,
267
- 'polymer': 600,
268
  'copper': 400
269
  }[material]
270
  weight = base_weight + np.random.normal(0, base_weight * 0.2)
@@ -426,145 +342,82 @@ def detect_outliers(df):
426
  return outliers
427
 
428
  def generate_ai_summary(model, df, stats, outliers):
429
- """Generate professional data analysis with actionable insights"""
430
  if not model:
431
- return "AI analysis unavailable - API key not configured"
432
-
433
  try:
434
- total_production = stats['_total_']['total']
435
- work_days = stats['_total_']['work_days']
436
- daily_avg = stats['_total_']['daily_avg']
437
  materials = [k for k in stats.keys() if k != '_total_']
438
- total_outliers = sum(info['count'] for info in outliers.values())
439
-
440
- # Get top material and its percentage
441
- top_material = max(materials, key=lambda x: stats[x]['total'])
442
- top_percentage = stats[top_material]['percentage']
443
-
444
- # Calculate shift info if available
445
- shift_info = ""
446
- if 'shift' in df.columns:
447
- day_total = df[df['shift'] == 'day']['weight_kg'].sum()
448
- night_total = df[df['shift'] == 'night']['weight_kg'].sum()
449
- day_pct = (day_total / total_production) * 100
450
- night_pct = (night_total / total_production) * 100
451
- shift_info = f"Day shift: {day_pct:.1f}%, Night shift: {night_pct:.1f}%"
452
-
453
- context = f"""
454
- Production Analysis Data:
455
- - Period: {df['date'].min().strftime('%B %d, %Y')} to {df['date'].max().strftime('%B %d, %Y')}
456
- - Total production: {total_production:,.0f} kg over {work_days} work days
457
- - Daily average: {daily_avg:,.0f} kg
458
- - Materials tracked: {', '.join([m.title() for m in materials])}
459
- - Top material: {top_material.title()} ({top_percentage:.1f}% of total)
460
- - Quality issues: {total_outliers} outliers detected across all materials
461
- - {shift_info}
462
-
463
- Material breakdown:
464
- """
465
  for material in materials:
466
  info = stats[material]
467
- outlier_count = outliers[material]['count']
468
- context += f"- {material.title()}: {info['total']:,.0f} kg ({info['percentage']:.1f}%), {outlier_count} outliers\n"
469
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  prompt = f"""
471
- {context}
472
 
473
- Provide a professional production analysis with these sections:
474
 
475
- **EXECUTIVE ASSESSMENT**
476
- One paragraph overview emphasizing production strengths and improvement opportunities (not problems).
477
 
478
- **KEY DATA INSIGHTS**
479
- 2-3 specific insights focusing on optimization potential. Frame outliers as "improvement opportunities" and imbalances as "enhancement areas." Use positive language.
480
 
481
- **ACTIONABLE RECOMMENDATIONS**
482
- 2-3 collaborative recommendations starting with "We recommend" and mentioning how Nilsen Service & Consulting can assist in implementation.
483
 
484
- **PARTNERSHIP OPPORTUNITIES**
485
- How Nilsen Service & Consulting can collaborate to unlock further potential.
486
 
487
- Write as a solution-oriented consultant. Frame challenges as opportunities. Use collaborative language like "enhance," "optimize," "unlock potential." Maximum 250 words total.
488
- """
 
 
 
489
 
 
 
490
  response = model.generate_content(prompt)
491
  return response.text
492
-
493
  except Exception as e:
494
  return f"AI analysis error: {str(e)}"
495
 
496
- def render_ai_section(df, stats, model):
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 = [
514
- "How does our monitoring system ensure production quality?",
515
- "What operational advantages does our platform provide?",
516
- "How does our AI deliver cost savings and efficiency gains?"
517
- ]
518
-
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:
532
- custom_question = st.text_input("Ask our AI system about production management:",
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)
541
- st.success(f"**Your Question:** {custom_question}")
542
- st.info(f"**Our AI Solution:** {answer}")
543
- else:
544
- st.warning("Please enter a question to see our AI capabilities")
545
-
546
- # Value proposition
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:
567
- return "AI assistant not available"
568
  context_parts = [
569
  "Production Data Summary:",
570
  *[f"- {mat.title()}: {info['total']:,.0f}kg ({info['percentage']:.1f}%)"
@@ -584,8 +437,8 @@ def query_ai(model, stats, question, df=None):
584
  try:
585
  response = model.generate_content(context)
586
  return response.text
587
- except:
588
- return "Error getting AI response"
589
 
590
  def save_plotly_as_image(fig, filename):
591
  try:
@@ -815,7 +668,7 @@ def create_enhanced_pdf_report(df, stats, outliers, model=None):
815
  else:
816
  elements.append(PageBreak())
817
  elements.append(Paragraph("AI Analysis", subtitle_style))
818
- elements.append(Paragraph("AI analysis unavailable - API key not configured. Please configure Google AI API key to enable intelligent insights.", styles['Normal']))
819
  elements.append(Spacer(1, 30))
820
  footer_text = f"""
821
  <para alignment="center">
@@ -844,7 +697,7 @@ def create_csv_export(df, stats):
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,13 +711,13 @@ def add_export_section(df, stats, outliers, model):
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,13 +727,13 @@ def add_export_section(df, stats, outliers, model):
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,29 +753,26 @@ def main():
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)
907
-
908
  model = init_ai()
909
-
910
  if 'current_df' not in st.session_state:
911
  st.session_state.current_df = None
912
  if 'current_stats' not in st.session_state:
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,22 +783,21 @@ def main():
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
942
-
943
  if uploaded_file:
944
  try:
945
  df = load_data(uploaded_file)
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,42 +807,34 @@ def main():
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
-
972
- # Display metrics in rows of 4
973
- for i in range(0, len(all_metrics), 4):
974
- row_metrics = all_metrics[i:i+4]
975
- cols = st.columns(len(row_metrics))
976
-
977
- for j, item in enumerate(row_metrics):
978
- with cols[j]:
979
- if item == '_total_':
980
- info = stats['_total_']
981
- st.metric(
982
- label="Total Production",
983
- value=f"{info['total']:,.0f} kg",
984
- delta="100% of total"
985
- )
986
- st.caption(f"Daily avg: {info['daily_avg']:,.0f} kg")
987
- else:
988
- info = stats[item]
989
- st.metric(
990
- label=item.replace('_', ' ').title(),
991
- value=f"{info['total']:,.0f} kg",
992
- delta=f"{info['percentage']:.1f}% of total"
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")
@@ -1003,8 +844,7 @@ def main():
1003
  total_chart = create_total_production_chart(df, time_view)
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(
@@ -1020,77 +860,29 @@ def main():
1020
  materials_chart = create_materials_trend_chart(df, time_view, selected_materials)
1021
  st.plotly_chart(materials_chart, use_container_width=True)
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):
1044
- row_materials = materials_list[i:i+cols_per_row]
1045
- cols = st.columns(len(row_materials))
1046
-
1047
- for j, (material, info) in enumerate(row_materials):
1048
- with cols[j]:
1049
- material_title = material.replace("_", " ").title()
1050
-
1051
- if info['count'] > 0:
1052
- # Show all dates
1053
- dates_display = ', '.join(info['dates'])
1054
-
1055
- card_html = f'''
1056
- <div class="quality-card card-warning">
1057
- <div class="card-title">{material_title}</div>
1058
- <div class="status-badge status-warning">Warning</div>
1059
- <div class="outliers-info">
1060
- <span class="outliers-count warning">{info["count"]}</span>
1061
- <span class="outliers-text">Outliers</span>
1062
- </div>
1063
- <div class="range-info">
1064
- <div class="range-label">Normal Range</div>
1065
- <div>{info["range"]}</div>
1066
- </div>
1067
- <div class="dates-section">
1068
- <div class="dates-label">Dates</div>
1069
- <div class="dates-list">{dates_display}</div>
1070
- </div>
1071
- </div>
1072
- '''
1073
  else:
1074
- card_html = f'''
1075
- <div class="quality-card card-success">
1076
- <div class="card-title">{material_title}</div>
1077
- <div class="status-badge status-success">Normal</div>
1078
- <div class="outliers-info">
1079
- <span class="outliers-count success">0</span>
1080
- <span class="outliers-text">Outliers</span>
1081
- </div>
1082
- <div class="success-center">
1083
- <div class="success-message">All values within normal range</div>
1084
- </div>
1085
- </div>
1086
- '''
1087
-
1088
- st.markdown(card_html, unsafe_allow_html=True)
1089
-
1090
  add_export_section(df, stats, outliers, model)
1091
-
1092
  if model:
1093
- st.markdown('<div class="section-header">AI Insights</div>', unsafe_allow_html=True)
1094
  quick_questions = [
1095
  "How does production distribution on weekdays compare to weekends?",
1096
  "Which material exhibits the most volatility in our dataset?",
@@ -1103,7 +895,6 @@ def main():
1103
  with st.spinner("Analyzing..."):
1104
  answer = query_ai(model, stats, q, df)
1105
  st.info(answer)
1106
-
1107
  custom_question = st.text_input("Ask about your production data:",
1108
  placeholder="e.g., 'Compare steel vs aluminum last month'",
1109
  key="custom_ai_question")
@@ -1112,12 +903,33 @@ def main():
1112
  answer = query_ai(model, stats, custom_question, df)
1113
  st.success(f"**Q:** {custom_question}")
1114
  st.write(f"**A:** {answer}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
  else:
1116
- st.markdown('<div class="section-header">How to Use This Platform</div>', unsafe_allow_html=True)
1117
  col1, col2 = st.columns(2)
1118
  with col1:
1119
  st.markdown("""
1120
- ### Quick Start
1121
  1. Upload your TSV data in the sidebar
1122
  2. Or click Quick Load buttons for preset data
1123
  3. View production by material type
@@ -1128,7 +940,7 @@ def main():
1128
  """)
1129
  with col2:
1130
  st.markdown("""
1131
- ### Key Features
1132
  - Real-time interactive charts
1133
  - One-click preset data loading
1134
  - Time-period comparisons
@@ -1137,7 +949,7 @@ def main():
1137
  - AI-powered PDF reports
1138
  - Intelligent recommendations
1139
  """)
1140
- st.info("Ready to start? Upload your production data or use Quick Load buttons to begin analysis!")
1141
 
1142
  if __name__ == "__main__":
1143
  main()
 
16
  import tempfile
17
  import os
18
  import requests
19
+ import warnings
20
+ warnings.filterwarnings("ignore", message=".*secrets.*")
21
 
22
  DESIGN_SYSTEM = {
23
  'colors': {
 
91
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
92
  margin-bottom: 1rem;
93
  }}
94
+ .alert-success {{
95
+ background: linear-gradient(135deg, {DESIGN_SYSTEM['colors']['success']}15, {DESIGN_SYSTEM['colors']['success']}25);
96
+ border: 1px solid {DESIGN_SYSTEM['colors']['success']};
97
+ border-radius: 8px;
98
+ padding: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  color: {DESIGN_SYSTEM['colors']['success']};
100
  }}
101
+ .alert-warning {{
102
+ background: linear-gradient(135deg, {DESIGN_SYSTEM['colors']['warning']}15, {DESIGN_SYSTEM['colors']['warning']}25);
103
+ border: 1px solid {DESIGN_SYSTEM['colors']['warning']};
 
 
 
 
104
  border-radius: 8px;
105
+ padding: 1rem;
106
+ color: {DESIGN_SYSTEM['colors']['warning']};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }}
 
108
  .stButton > button {{
109
  background: {DESIGN_SYSTEM['colors']['primary']};
110
  color: white;
 
128
 
129
  @st.cache_resource
130
  def init_ai():
131
+ """Initialize AI model with proper error handling for secrets"""
132
+ try:
133
+ # Try to get API key from Streamlit secrets
134
+ api_key = st.secrets.get("GOOGLE_API_KEY", "")
135
+ except (FileNotFoundError, KeyError, AttributeError):
136
+ # If secrets file doesn't exist or key not found, try environment variable
137
+ api_key = os.environ.get("GOOGLE_API_KEY", "")
138
+
139
  if api_key:
140
+ try:
141
+ genai.configure(api_key=api_key)
142
+ return genai.GenerativeModel('gemini-1.5-flash')
143
+ except Exception as e:
144
+ st.error(f"AI configuration failed: {str(e)}")
145
+ return None
146
  return None
147
 
148
  @st.cache_data
 
172
  dates = pd.date_range(start=start_date, end=end_date, freq='D')
173
  weekdays = dates[dates.weekday < 5]
174
  data = []
175
+ materials = ['steel', 'aluminum', 'plastic', 'copper']
176
  shifts = ['day', 'night']
177
  for date in weekdays:
178
  for material in materials:
 
180
  base_weight = {
181
  'steel': 1500,
182
  'aluminum': 800,
183
+ 'plastic': 600,
184
  'copper': 400
185
  }[material]
186
  weight = base_weight + np.random.normal(0, base_weight * 0.2)
 
342
  return outliers
343
 
344
  def generate_ai_summary(model, df, stats, outliers):
 
345
  if not model:
346
+ return "AI analysis unavailable - Google API key not configured. Please set the GOOGLE_API_KEY environment variable or in Streamlit secrets to enable AI insights."
 
347
  try:
 
 
 
348
  materials = [k for k in stats.keys() if k != '_total_']
349
+ context_parts = [
350
+ "# Production Data Analysis Context",
351
+ f"## Overview",
352
+ f"- Total Production: {stats['_total_']['total']:,.0f} kg",
353
+ f"- Production Period: {stats['_total_']['work_days']} working days",
354
+ f"- Daily Average: {stats['_total_']['daily_avg']:,.0f} kg",
355
+ f"- Materials Tracked: {len(materials)}",
356
+ "",
357
+ "## Material Breakdown:"
358
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  for material in materials:
360
  info = stats[material]
361
+ context_parts.append(f"- {material.title()}: {info['total']:,.0f} kg ({info['percentage']:.1f}%), avg {info['daily_avg']:,.0f} kg/day")
362
+ daily_data = df.groupby('date')['weight_kg'].sum()
363
+ trend_direction = "increasing" if daily_data.iloc[-1] > daily_data.iloc[0] else "decreasing"
364
+ volatility = daily_data.std() / daily_data.mean() * 100
365
+ context_parts.extend([
366
+ "",
367
+ "## Trend Analysis:",
368
+ f"- Overall trend: {trend_direction}",
369
+ f"- Production volatility: {volatility:.1f}% coefficient of variation",
370
+ f"- Peak production: {daily_data.max():,.0f} kg",
371
+ f"- Lowest production: {daily_data.min():,.0f} kg"
372
+ ])
373
+ total_outliers = sum(info['count'] for info in outliers.values())
374
+ context_parts.extend([
375
+ "",
376
+ "## Quality Control:",
377
+ f"- Total outliers detected: {total_outliers}",
378
+ f"- Materials with quality issues: {sum(1 for info in outliers.values() if info['count'] > 0)}"
379
+ ])
380
+ if 'shift' in df.columns:
381
+ shift_stats = df.groupby('shift')['weight_kg'].sum()
382
+ context_parts.extend([
383
+ "",
384
+ "## Shift Performance:",
385
+ f"- Day shift: {shift_stats.get('day', 0):,.0f} kg",
386
+ f"- Night shift: {shift_stats.get('night', 0):,.0f} kg"
387
+ ])
388
+ context_text = "\n".join(context_parts)
389
  prompt = f"""
390
+ {context_text}
391
 
392
+ As an expert AI analyst embedded within the "Production Monitor with AI Insights" platform, provide a comprehensive analysis based on the data provided. Your tone should be professional and data-driven. Your primary goal is to highlight how the platform's features reveal critical insights.
393
 
394
+ Structure your response in the following format:
 
395
 
396
+ **PRODUCTION ASSESSMENT**
397
+ Evaluate the overall production status (Excellent/Good/Needs Attention). Briefly justify your assessment using key metrics from the data summary.
398
 
399
+ **KEY FINDINGS**
400
+ Identify 3-4 of the most important insights. For each finding, explicitly mention the platform feature that made the discovery possible. Use formats like "(revealed by the 'Quality Check' module)" or "(visualized in the 'Production Trend' chart)".
401
 
402
+ Example Finding format:
403
+ β€’ Finding X: [Your insight, e.g., "Liquid-Ctu production shows high volatility..."] (as identified by the 'Materials Analysis' view).
404
 
405
+ **RECOMMENDATIONS**
406
+ Provide 2-3 actionable recommendations. Frame these as steps the management can take, encouraging them to use the platform for further investigation.
407
+
408
+ Example Recommendation format:
409
+ β€’ Recommendation Y: [Your recommendation, e.g., "Investigate the root causes of the 11 outliers..."] We recommend using the platform's interactive charts to drill down into the specific dates identified by the 'Quality Check' module.
410
 
411
+ Keep the entire analysis concise and under 300 words.
412
+ """
413
  response = model.generate_content(prompt)
414
  return response.text
 
415
  except Exception as e:
416
  return f"AI analysis error: {str(e)}"
417
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  def query_ai(model, stats, question, df=None):
419
  if not model:
420
+ return "AI assistant not available - Please configure Google API key"
421
  context_parts = [
422
  "Production Data Summary:",
423
  *[f"- {mat.title()}: {info['total']:,.0f}kg ({info['percentage']:.1f}%)"
 
437
  try:
438
  response = model.generate_content(context)
439
  return response.text
440
+ except Exception as e:
441
+ return f"Error getting AI response: {str(e)}"
442
 
443
  def save_plotly_as_image(fig, filename):
444
  try:
 
668
  else:
669
  elements.append(PageBreak())
670
  elements.append(Paragraph("AI Analysis", subtitle_style))
671
+ elements.append(Paragraph("AI analysis unavailable - Google API key not configured. Please set the GOOGLE_API_KEY environment variable or configure it in Streamlit secrets to enable intelligent insights.", styles['Normal']))
672
  elements.append(Spacer(1, 30))
673
  footer_text = f"""
674
  <para alignment="center">
 
697
  return summary_df
698
 
699
  def add_export_section(df, stats, outliers, model):
700
+ st.markdown('<div class="section-header">πŸ“„ Export Reports</div>', unsafe_allow_html=True)
701
  if 'export_ready' not in st.session_state:
702
  st.session_state.export_ready = False
703
  if 'pdf_buffer' not in st.session_state:
 
711
  with st.spinner("Generating PDF with AI analysis..."):
712
  st.session_state.pdf_buffer = create_enhanced_pdf_report(df, stats, outliers, model)
713
  st.session_state.export_ready = True
714
+ st.success("βœ… PDF report with AI analysis generated successfully!")
715
  except Exception as e:
716
+ st.error(f"❌ PDF generation failed: {str(e)}")
717
  st.session_state.export_ready = False
718
  if st.session_state.export_ready and st.session_state.pdf_buffer:
719
  st.download_button(
720
+ label="πŸ’Ύ Download PDF Report",
721
  data=st.session_state.pdf_buffer,
722
  file_name=f"production_report_ai_{datetime.now().strftime('%Y%m%d_%H%M')}.pdf",
723
  mime="application/pdf",
 
727
  if st.button("Generate CSV Summary", key="generate_csv_btn", type="primary"):
728
  try:
729
  st.session_state.csv_data = create_csv_export(df, stats)
730
+ st.success("βœ… CSV summary generated successfully!")
731
  except Exception as e:
732
+ st.error(f"❌ CSV generation failed: {str(e)}")
733
  if st.session_state.csv_data is not None:
734
  csv_string = st.session_state.csv_data.to_csv(index=False)
735
  st.download_button(
736
+ label="πŸ’Ύ Download CSV Summary",
737
  data=csv_string,
738
  file_name=f"production_summary_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
739
  mime="text/csv",
 
753
  load_css()
754
  st.markdown("""
755
  <div class="main-header">
756
+ <div class="main-title">🏭 Production Monitor with AI Insights</div>
757
  <div class="main-subtitle">Nilsen Service & Consulting AS | Real-time Production Analytics & Recommendations</div>
758
  </div>
759
  """, unsafe_allow_html=True)
 
760
  model = init_ai()
 
761
  if 'current_df' not in st.session_state:
762
  st.session_state.current_df = None
763
  if 'current_stats' not in st.session_state:
764
  st.session_state.current_stats = None
 
765
  with st.sidebar:
766
+ st.markdown("### πŸ“Š Data Source")
767
  uploaded_file = st.file_uploader("Upload Production Data", type=['csv'])
768
  st.markdown("---")
769
+ st.markdown("### πŸ“Š Quick Load")
770
  col1, col2 = st.columns(2)
771
  with col1:
772
+ if st.button("πŸ“Š 2024 Data", type="primary", key="load_2024"):
773
  st.session_state.load_preset = "2024"
774
  with col2:
775
+ if st.button("πŸ“Š 2025 Data", type="primary", key="load_2025"):
776
  st.session_state.load_preset = "2025"
777
  st.markdown("---")
778
  st.markdown("""
 
783
  - `shift`: day/night (optional)
784
  """)
785
  if model:
786
+ st.success("πŸ€– AI Assistant Ready")
787
  else:
788
+ st.warning("⚠️ AI Assistant Unavailable")
789
+ st.info("To enable AI features, set GOOGLE_API_KEY as environment variable or in Streamlit secrets")
790
  df = st.session_state.current_df
791
  stats = st.session_state.current_stats
 
792
  if uploaded_file:
793
  try:
794
  df = load_data(uploaded_file)
795
  stats = get_material_stats(df)
796
  st.session_state.current_df = df
797
  st.session_state.current_stats = stats
798
+ st.success("βœ… Data uploaded successfully!")
799
  except Exception as e:
800
+ st.error(f"❌ Error loading uploaded file: {str(e)}")
801
  elif 'load_preset' in st.session_state:
802
  year = st.session_state.load_preset
803
  try:
 
807
  stats = get_material_stats(df)
808
  st.session_state.current_df = df
809
  st.session_state.current_stats = stats
810
+ st.success(f"βœ… {year} data loaded successfully!")
811
  except Exception as e:
812
+ st.error(f"❌ Error loading {year} data: {str(e)}")
813
  finally:
814
  del st.session_state.load_preset
 
815
  if df is not None and stats is not None:
816
+ st.markdown('<div class="section-header">πŸ“‹ Material Overview</div>', unsafe_allow_html=True)
817
  materials = [k for k in stats.keys() if k != '_total_']
818
+ cols = st.columns(4)
819
+ for i, material in enumerate(materials[:3]):
820
+ info = stats[material]
821
+ with cols[i]:
822
+ st.metric(
823
+ label=material.replace('_', ' ').title(),
824
+ value=f"{info['total']:,.0f} kg",
825
+ delta=f"{info['percentage']:.1f}% of total"
826
+ )
827
+ st.caption(f"Daily avg: {info['daily_avg']:,.0f} kg")
828
+ if len(materials) >= 3:
829
+ total_info = stats['_total_']
830
+ with cols[3]:
831
+ st.metric(
832
+ label="Total Production",
833
+ value=f"{total_info['total']:,.0f} kg",
834
+ delta="100% of total"
835
+ )
836
+ st.caption(f"Daily avg: {total_info['daily_avg']:,.0f} kg")
837
+ st.markdown('<div class="section-header">πŸ“Š Production Trends</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
838
  col1, col2 = st.columns([3, 1])
839
  with col2:
840
  time_view = st.selectbox("Time Period", ["daily", "weekly", "monthly"], key="time_view_select")
 
844
  total_chart = create_total_production_chart(df, time_view)
845
  st.plotly_chart(total_chart, use_container_width=True)
846
  st.markdown('</div>', unsafe_allow_html=True)
847
+ st.markdown('<div class="section-header">🏷️ Materials Analysis</div>', unsafe_allow_html=True)
 
848
  col1, col2 = st.columns([3, 1])
849
  with col2:
850
  selected_materials = st.multiselect(
 
860
  materials_chart = create_materials_trend_chart(df, time_view, selected_materials)
861
  st.plotly_chart(materials_chart, use_container_width=True)
862
  st.markdown('</div>', unsafe_allow_html=True)
 
863
  if 'shift' in df.columns:
864
+ st.markdown('<div class="section-header">πŸŒ“ Shift Analysis</div>', unsafe_allow_html=True)
865
  with st.container():
866
  st.markdown('<div class="chart-container">', unsafe_allow_html=True)
867
  shift_chart = create_shift_trend_chart(df, time_view)
868
  st.plotly_chart(shift_chart, use_container_width=True)
869
  st.markdown('</div>', unsafe_allow_html=True)
870
+ st.markdown('<div class="section-header">⚠️ Quality Check</div>', unsafe_allow_html=True)
 
 
871
  outliers = detect_outliers(df)
872
+ cols = st.columns(len(outliers))
873
+ for i, (material, info) in enumerate(outliers.items()):
874
+ with cols[i]:
875
+ if info['count'] > 0:
876
+ if len(info['dates']) <= 5:
877
+ dates_str = ", ".join(info['dates'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
  else:
879
+ dates_str = f"{', '.join(info['dates'][:3])}, +{len(info['dates'])-3} more"
880
+ st.markdown(f'<div class="alert-warning"><strong>{material.title()}</strong><br>{info["count"]} outliers detected<br>Normal range: {info["range"]}<br><small>Dates: {dates_str}</small></div>', unsafe_allow_html=True)
881
+ else:
882
+ st.markdown(f'<div class="alert-success"><strong>{material.title()}</strong><br>All values normal</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
883
  add_export_section(df, stats, outliers, model)
 
884
  if model:
885
+ st.markdown('<div class="section-header">πŸ€– AI Insights</div>', unsafe_allow_html=True)
886
  quick_questions = [
887
  "How does production distribution on weekdays compare to weekends?",
888
  "Which material exhibits the most volatility in our dataset?",
 
895
  with st.spinner("Analyzing..."):
896
  answer = query_ai(model, stats, q, df)
897
  st.info(answer)
 
898
  custom_question = st.text_input("Ask about your production data:",
899
  placeholder="e.g., 'Compare steel vs aluminum last month'",
900
  key="custom_ai_question")
 
903
  answer = query_ai(model, stats, custom_question, df)
904
  st.success(f"**Q:** {custom_question}")
905
  st.write(f"**A:** {answer}")
906
+ else:
907
+ st.markdown('<div class="section-header">πŸ€– AI Configuration</div>', unsafe_allow_html=True)
908
+ st.info("""
909
+ **AI Assistant is currently unavailable.**
910
+
911
+ To enable AI features, you need to configure your Google AI API key:
912
+
913
+ **Option 1: Environment Variable**
914
+ ```bash
915
+ export GOOGLE_API_KEY="your_api_key_here"
916
+ ```
917
+
918
+ **Option 2: Streamlit Secrets**
919
+ Create `.streamlit/secrets.toml`:
920
+ ```toml
921
+ GOOGLE_API_KEY = "your_api_key_here"
922
+ ```
923
+
924
+ **Option 3: Azure App Service**
925
+ Set environment variable in Azure portal under Configuration > Application settings.
926
+ """)
927
  else:
928
+ st.markdown('<div class="section-header">πŸ“– How to Use This Platform</div>', unsafe_allow_html=True)
929
  col1, col2 = st.columns(2)
930
  with col1:
931
  st.markdown("""
932
+ ### πŸš€ Quick Start
933
  1. Upload your TSV data in the sidebar
934
  2. Or click Quick Load buttons for preset data
935
  3. View production by material type
 
940
  """)
941
  with col2:
942
  st.markdown("""
943
+ ### πŸ“Š Key Features
944
  - Real-time interactive charts
945
  - One-click preset data loading
946
  - Time-period comparisons
 
949
  - AI-powered PDF reports
950
  - Intelligent recommendations
951
  """)
952
+ st.info("πŸ“ Ready to start? Upload your production data or use Quick Load buttons to begin analysis!")
953
 
954
  if __name__ == "__main__":
955
  main()