entropy25 commited on
Commit
7477afa
Β·
verified Β·
1 Parent(s): dc2fd1a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +223 -145
app.py CHANGED
@@ -6,8 +6,130 @@ import plotly.graph_objects as go
6
  from datetime import datetime, timedelta
7
  import google.generativeai as genai
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  # Page config
10
- st.set_page_config(page_title="Production Data Monitor Platform – Nilsen Service & Consulting AS", page_icon="🏭", layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  @st.cache_resource
13
  def init_ai():
@@ -52,12 +174,23 @@ def get_material_stats(df):
52
 
53
  return stats
54
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  def create_total_production_chart(df, time_period='daily'):
56
- """Create total production trend chart"""
57
  if time_period == 'daily':
58
  grouped = df.groupby('date')['weight_kg'].sum().reset_index()
59
  fig = px.line(grouped, x='date', y='weight_kg',
60
- title='πŸ“Š Total Production Trend',
61
  labels={'weight_kg': 'Weight (kg)', 'date': 'Date'})
62
  elif time_period == 'weekly':
63
  df['week'] = df['date'].dt.isocalendar().week
@@ -65,30 +198,27 @@ def create_total_production_chart(df, time_period='daily'):
65
  grouped = df.groupby(['year', 'week'])['weight_kg'].sum().reset_index()
66
  grouped['week_label'] = grouped['year'].astype(str) + '-W' + grouped['week'].astype(str)
67
  fig = px.bar(grouped, x='week_label', y='weight_kg',
68
- title='πŸ“Š Total Production Trend (Weekly)',
69
  labels={'weight_kg': 'Weight (kg)', 'week_label': 'Week'})
70
- else: # monthly
71
  df['month'] = df['date'].dt.to_period('M')
72
  grouped = df.groupby('month')['weight_kg'].sum().reset_index()
73
  grouped['month'] = grouped['month'].astype(str)
74
  fig = px.bar(grouped, x='month', y='weight_kg',
75
- title='πŸ“Š Total Production Trend (Monthly)',
76
  labels={'weight_kg': 'Weight (kg)', 'month': 'Month'})
77
 
78
- fig.update_layout(height=400, showlegend=False)
79
- if time_period == 'daily':
80
- fig.update_traces(line=dict(color='#1f77b4'))
81
  return fig
82
 
83
  def create_materials_trend_chart(df, time_period='daily', selected_materials=None):
84
- """Create individual materials trend chart"""
85
  if selected_materials:
86
  df = df[df['material_type'].isin(selected_materials)]
87
 
88
  if time_period == 'daily':
89
  grouped = df.groupby(['date', 'material_type'])['weight_kg'].sum().reset_index()
90
  fig = px.line(grouped, x='date', y='weight_kg', color='material_type',
91
- title='🏷️ Materials Production Trends',
92
  labels={'weight_kg': 'Weight (kg)', 'date': 'Date', 'material_type': 'Material'})
93
  elif time_period == 'weekly':
94
  df['week'] = df['date'].dt.isocalendar().week
@@ -96,80 +226,54 @@ def create_materials_trend_chart(df, time_period='daily', selected_materials=Non
96
  grouped = df.groupby(['year', 'week', 'material_type'])['weight_kg'].sum().reset_index()
97
  grouped['week_label'] = grouped['year'].astype(str) + '-W' + grouped['week'].astype(str)
98
  fig = px.bar(grouped, x='week_label', y='weight_kg', color='material_type',
99
- title='🏷️ Materials Production Trends (Weekly)',
100
  labels={'weight_kg': 'Weight (kg)', 'week_label': 'Week', 'material_type': 'Material'})
101
- else: # monthly
102
  df['month'] = df['date'].dt.to_period('M')
103
  grouped = df.groupby(['month', 'material_type'])['weight_kg'].sum().reset_index()
104
  grouped['month'] = grouped['month'].astype(str)
105
  fig = px.bar(grouped, x='month', y='weight_kg', color='material_type',
106
- title='🏷️ Materials Production Trends (Monthly)',
107
  labels={'weight_kg': 'Weight (kg)', 'month': 'Month', 'material_type': 'Material'})
108
 
109
- fig.update_layout(height=400)
110
  return fig
111
 
112
  def create_shift_trend_chart(df, time_period='daily'):
113
- """Create shift production trend chart over time with stacked bars"""
114
  if time_period == 'daily':
115
- # Create stacked bar chart where each bar is one day
116
  grouped = df.groupby(['date', 'shift'])['weight_kg'].sum().reset_index()
117
-
118
- # Pivot to have shifts as columns
119
  pivot_data = grouped.pivot(index='date', columns='shift', values='weight_kg').fillna(0)
120
 
121
  fig = go.Figure()
122
 
123
- # Add day shift (bottom of stack)
124
  if 'day' in pivot_data.columns:
125
  fig.add_trace(go.Bar(
126
- x=pivot_data.index,
127
- y=pivot_data['day'],
128
- name='Day Shift',
129
- marker_color='#FFA500',
130
- text=pivot_data['day'].round(0),
131
- textposition='inside'
132
  ))
133
 
134
- # Add night shift (top of stack)
135
  if 'night' in pivot_data.columns:
136
  fig.add_trace(go.Bar(
137
- x=pivot_data.index,
138
- y=pivot_data['night'],
139
- name='Night Shift',
140
- marker_color='#4169E1',
141
  base=pivot_data['day'] if 'day' in pivot_data.columns else 0,
142
- text=pivot_data['night'].round(0),
143
- textposition='inside'
144
  ))
145
 
146
  fig.update_layout(
147
- title='πŸ“… Daily Shift Production Trends (Stacked)',
148
- xaxis_title='Date',
149
- yaxis_title='Weight (kg)',
150
- barmode='stack',
151
- height=400,
152
- showlegend=True
153
  )
154
-
155
- elif time_period == 'weekly':
156
- df['week'] = df['date'].dt.isocalendar().week
157
- df['year'] = df['date'].dt.year
158
- grouped = df.groupby(['year', 'week', 'shift'])['weight_kg'].sum().reset_index()
159
- grouped['week_label'] = grouped['year'].astype(str) + '-W' + grouped['week'].astype(str)
160
- fig = px.bar(grouped, x='week_label', y='weight_kg', color='shift',
161
- title='πŸ“… Weekly Shift Production Trends',
162
- labels={'weight_kg': 'Weight (kg)', 'week_label': 'Week', 'shift': 'Shift'},
163
- barmode='stack')
164
- else: # monthly
165
- df['month'] = df['date'].dt.to_period('M')
166
- grouped = df.groupby(['month', 'shift'])['weight_kg'].sum().reset_index()
167
- grouped['month'] = grouped['month'].astype(str)
168
- fig = px.bar(grouped, x='month', y='weight_kg', color='shift',
169
- title='πŸ“… Monthly Shift Production Trends',
170
- labels={'weight_kg': 'Weight (kg)', 'month': 'Month', 'shift': 'Shift'},
171
  barmode='stack')
172
- fig.update_layout(height=400)
173
 
174
  return fig
175
 
@@ -205,29 +309,35 @@ def query_ai(model, stats, question):
205
  except:
206
  return "Error getting AI response"
207
 
208
- # Main app
209
  def main():
210
- st.title("🏭 Production Data Monitor Platform – Nilsen Service & Consulting AS")
211
- st.markdown("*Real-time production analysis dashboard*")
 
 
 
 
 
 
 
212
 
213
  model = init_ai()
214
 
215
- # Sidebar controls
216
  with st.sidebar:
217
- st.header("πŸ“Š Controls")
218
- uploaded_file = st.file_uploader("Upload Data", type=['csv'])
219
 
220
  if model:
221
- st.success("πŸ€– AI Ready")
222
  else:
223
- st.warning("⚠️ AI Unavailable")
224
 
225
  if uploaded_file:
226
  df = load_data(uploaded_file)
227
  stats = get_material_stats(df)
228
 
229
  # Material Overview
230
- st.subheader("πŸ“‹ Material Overview")
231
  materials = [k for k in stats.keys() if k != '_total_']
232
 
233
  cols = st.columns(4)
@@ -240,9 +350,7 @@ def main():
240
  delta=f"{info['percentage']:.1f}% of total"
241
  )
242
  st.caption(f"Daily avg: {info['daily_avg']:,.0f} kg")
243
- st.caption(f"Work days: {info['work_days']} days")
244
 
245
- # Total production metric
246
  total_info = stats['_total_']
247
  with cols[3]:
248
  st.metric(
@@ -251,124 +359,94 @@ def main():
251
  delta="100% of total"
252
  )
253
  st.caption(f"Daily avg: {total_info['daily_avg']:,.0f} kg")
254
- st.caption(f"Work days: {total_info['work_days']} days")
255
-
256
- # Total Production Chart Section
257
- st.subheader("πŸ“Š Total Production Trends")
258
-
259
- col_total1, col_total2 = st.columns([3, 1])
260
 
261
- with col_total2:
262
- total_time_view = st.selectbox("Total Time Period", ["daily", "weekly", "monthly"], key="total_time")
263
 
264
- with col_total1:
265
- total_chart = create_total_production_chart(df, total_time_view)
266
- st.plotly_chart(total_chart, use_container_width=True)
267
 
268
- # Materials Production Chart Section
269
- st.subheader("🏷️ Materials Production Trends")
 
 
 
 
270
 
271
- col_mat1, col_mat2 = st.columns([3, 1])
 
272
 
273
- with col_mat2:
274
- materials_time_view = st.selectbox("Materials Time Period", ["daily", "weekly", "monthly"], key="materials_time")
275
-
276
  selected_materials = st.multiselect(
277
  "Select Materials",
278
- options=materials,
279
- default=materials,
280
- key="materials_select",
281
- help="Choose which materials to display"
282
  )
283
 
284
- with col_mat1:
285
  if selected_materials:
286
- materials_chart = create_materials_trend_chart(df, materials_time_view, selected_materials)
287
- st.plotly_chart(materials_chart, use_container_width=True)
288
- else:
289
- st.info("Please select materials to display trends")
 
290
 
291
  # Shift Analysis
292
  if 'shift' in df.columns:
293
- st.subheader("πŸŒ“ Shift Analysis")
294
-
295
- # Shift time trend controls moved to right
296
- col_shift_trend1, col_shift_trend2 = st.columns([3, 1])
297
 
298
- with col_shift_trend2:
299
- shift_time_view = st.selectbox("Shift Time Period", ["daily", "weekly", "monthly"], key="shift_time")
300
-
301
- with col_shift_trend1:
302
- st.markdown("**πŸŒ“ Shift Production Trends**")
303
- shift_trend_chart = create_shift_trend_chart(df, shift_time_view)
304
- st.plotly_chart(shift_trend_chart, use_container_width=True)
305
-
306
- # Shift comparison charts
307
- st.markdown("**πŸŒ“ Shift Comparison Analysis**")
308
- shift_col1, shift_col2 = st.columns(2)
309
-
310
- with shift_col1:
311
- # Shift comparison by material
312
- shift_data = df.groupby(['shift', 'material_type'])['weight_kg'].sum().reset_index()
313
- shift_chart = px.bar(shift_data, x='shift', y='weight_kg', color='material_type',
314
- title='Production by Shift and Material',
315
- labels={'weight_kg': 'Weight (kg)', 'shift': 'Shift', 'material_type': 'Material'})
316
  st.plotly_chart(shift_chart, use_container_width=True)
317
-
318
- with shift_col2:
319
- # Total production by shift
320
- shift_total = df.groupby('shift')['weight_kg'].sum().reset_index()
321
- shift_total_chart = px.pie(shift_total, values='weight_kg', names='shift',
322
- title='Total Production Distribution by Shift')
323
- st.plotly_chart(shift_total_chart, use_container_width=True)
324
 
325
  # Quality Check
326
- st.subheader("⚠️ Quality Check")
327
  outliers = detect_outliers(df)
328
 
329
- alert_cols = st.columns(len(outliers))
330
  for i, (material, info) in enumerate(outliers.items()):
331
- with alert_cols[i]:
332
  if info['count'] > 0:
333
- st.warning(f"**{material.title()}**: {info['count']} outliers")
334
- st.caption(f"Normal range: {info['range']}")
335
  else:
336
- st.success(f"**{material.title()}**: All normal")
337
 
338
  # AI Assistant
339
  if model:
340
- st.subheader("πŸ€– AI Insights")
341
 
342
- # Quick questions
343
  quick_questions = [
344
  "What's the production trend?",
345
- "Which material is most consistent?",
346
  "Any efficiency recommendations?"
347
  ]
348
 
349
  cols = st.columns(len(quick_questions))
350
  for i, q in enumerate(quick_questions):
351
  with cols[i]:
352
- if st.button(q, key=f"quick_q_{i}"):
353
- answer = query_ai(model, stats, q)
354
- st.info(answer)
 
355
 
356
- # Custom question
357
- custom_question = st.text_input("Ask anything about your production data:")
358
- if custom_question:
359
- if st.button("Ask AI"):
360
  answer = query_ai(model, stats, custom_question)
361
- st.success(f"**Question:** {custom_question}")
362
- st.write(f"**Answer:** {answer}")
363
 
364
  else:
365
  st.info("πŸ“ Upload your production data to start analysis")
366
  st.markdown("""
367
  **Expected TSV format:**
368
- - `date`: MM/DD/YYYY format
369
- - `weight_kg`: Production weight in kilograms
370
- - `material_type`: Material category/type
371
- - `shift`: day/night shift (optional)
372
  """)
373
 
374
  if __name__ == "__main__":
 
6
  from datetime import datetime, timedelta
7
  import google.generativeai as genai
8
 
9
+ # Design System Configuration
10
+ DESIGN_SYSTEM = {
11
+ 'colors': {
12
+ 'primary': '#1E40AF', # Blue
13
+ 'secondary': '#059669', # Green
14
+ 'accent': '#DC2626', # Red
15
+ 'warning': '#D97706', # Orange
16
+ 'success': '#10B981', # Emerald
17
+ 'background': '#F8FAFC', # Light gray
18
+ 'text': '#1F2937', # Dark gray
19
+ 'border': '#E5E7EB' # Light border
20
+ },
21
+ 'fonts': {
22
+ 'title': 'font-family: "Inter", sans-serif; font-weight: 700;',
23
+ 'subtitle': 'font-family: "Inter", sans-serif; font-weight: 600;',
24
+ 'body': 'font-family: "Inter", sans-serif; font-weight: 400;'
25
+ }
26
+ }
27
+
28
  # Page config
29
+ st.set_page_config(
30
+ page_title="Production Monitor | Nilsen Service & Consulting",
31
+ page_icon="🏭",
32
+ layout="wide",
33
+ initial_sidebar_state="expanded"
34
+ )
35
+
36
+ # Custom CSS for design system
37
+ def load_css():
38
+ st.markdown(f"""
39
+ <style>
40
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
41
+
42
+ .main-header {{
43
+ background: linear-gradient(135deg, {DESIGN_SYSTEM['colors']['primary']} 0%, {DESIGN_SYSTEM['colors']['secondary']} 100%);
44
+ padding: 1.5rem 2rem;
45
+ border-radius: 12px;
46
+ margin-bottom: 2rem;
47
+ color: white;
48
+ text-align: center;
49
+ }}
50
+
51
+ .main-title {{
52
+ {DESIGN_SYSTEM['fonts']['title']}
53
+ font-size: 2.2rem;
54
+ margin: 0;
55
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
56
+ }}
57
+
58
+ .main-subtitle {{
59
+ {DESIGN_SYSTEM['fonts']['body']}
60
+ font-size: 1rem;
61
+ opacity: 0.9;
62
+ margin-top: 0.5rem;
63
+ }}
64
+
65
+ .metric-card {{
66
+ background: white;
67
+ border: 1px solid {DESIGN_SYSTEM['colors']['border']};
68
+ border-radius: 12px;
69
+ padding: 1.5rem;
70
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
71
+ transition: transform 0.2s ease;
72
+ }}
73
+
74
+ .metric-card:hover {{
75
+ transform: translateY(-2px);
76
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
77
+ }}
78
+
79
+ .section-header {{
80
+ {DESIGN_SYSTEM['fonts']['subtitle']}
81
+ color: {DESIGN_SYSTEM['colors']['text']};
82
+ font-size: 1.4rem;
83
+ margin: 2rem 0 1rem 0;
84
+ padding-bottom: 0.5rem;
85
+ border-bottom: 2px solid {DESIGN_SYSTEM['colors']['primary']};
86
+ }}
87
+
88
+ .chart-container {{
89
+ background: white;
90
+ border-radius: 12px;
91
+ padding: 1rem;
92
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
93
+ margin-bottom: 1rem;
94
+ }}
95
+
96
+ .alert-success {{
97
+ background: linear-gradient(135deg, {DESIGN_SYSTEM['colors']['success']}15, {DESIGN_SYSTEM['colors']['success']}25);
98
+ border: 1px solid {DESIGN_SYSTEM['colors']['success']};
99
+ border-radius: 8px;
100
+ padding: 1rem;
101
+ color: {DESIGN_SYSTEM['colors']['success']};
102
+ }}
103
+
104
+ .alert-warning {{
105
+ background: linear-gradient(135deg, {DESIGN_SYSTEM['colors']['warning']}15, {DESIGN_SYSTEM['colors']['warning']}25);
106
+ border: 1px solid {DESIGN_SYSTEM['colors']['warning']};
107
+ border-radius: 8px;
108
+ padding: 1rem;
109
+ color: {DESIGN_SYSTEM['colors']['warning']};
110
+ }}
111
+
112
+ .stSelectbox > div > div {{
113
+ border-radius: 8px;
114
+ border: 1px solid {DESIGN_SYSTEM['colors']['border']};
115
+ }}
116
+
117
+ .stButton > button {{
118
+ background: {DESIGN_SYSTEM['colors']['primary']};
119
+ color: white;
120
+ border: none;
121
+ border-radius: 8px;
122
+ padding: 0.5rem 1rem;
123
+ font-weight: 500;
124
+ transition: all 0.2s ease;
125
+ }}
126
+
127
+ .stButton > button:hover {{
128
+ background: {DESIGN_SYSTEM['colors']['secondary']};
129
+ transform: translateY(-1px);
130
+ }}
131
+ </style>
132
+ """, unsafe_allow_html=True)
133
 
134
  @st.cache_resource
135
  def init_ai():
 
174
 
175
  return stats
176
 
177
+ def get_chart_theme():
178
+ return {
179
+ 'layout': {
180
+ 'plot_bgcolor': 'white',
181
+ 'paper_bgcolor': 'white',
182
+ 'font': {'family': 'Inter, sans-serif', 'color': DESIGN_SYSTEM['colors']['text']},
183
+ 'colorway': [DESIGN_SYSTEM['colors']['primary'], DESIGN_SYSTEM['colors']['secondary'],
184
+ DESIGN_SYSTEM['colors']['accent'], DESIGN_SYSTEM['colors']['warning']],
185
+ 'margin': {'t': 60, 'b': 40, 'l': 40, 'r': 40}
186
+ }
187
+ }
188
+
189
  def create_total_production_chart(df, time_period='daily'):
 
190
  if time_period == 'daily':
191
  grouped = df.groupby('date')['weight_kg'].sum().reset_index()
192
  fig = px.line(grouped, x='date', y='weight_kg',
193
+ title='Total Production Trend',
194
  labels={'weight_kg': 'Weight (kg)', 'date': 'Date'})
195
  elif time_period == 'weekly':
196
  df['week'] = df['date'].dt.isocalendar().week
 
198
  grouped = df.groupby(['year', 'week'])['weight_kg'].sum().reset_index()
199
  grouped['week_label'] = grouped['year'].astype(str) + '-W' + grouped['week'].astype(str)
200
  fig = px.bar(grouped, x='week_label', y='weight_kg',
201
+ title='Total Production Trend (Weekly)',
202
  labels={'weight_kg': 'Weight (kg)', 'week_label': 'Week'})
203
+ else:
204
  df['month'] = df['date'].dt.to_period('M')
205
  grouped = df.groupby('month')['weight_kg'].sum().reset_index()
206
  grouped['month'] = grouped['month'].astype(str)
207
  fig = px.bar(grouped, x='month', y='weight_kg',
208
+ title='Total Production Trend (Monthly)',
209
  labels={'weight_kg': 'Weight (kg)', 'month': 'Month'})
210
 
211
+ fig.update_layout(**get_chart_theme()['layout'], height=400, showlegend=False)
 
 
212
  return fig
213
 
214
  def create_materials_trend_chart(df, time_period='daily', selected_materials=None):
 
215
  if selected_materials:
216
  df = df[df['material_type'].isin(selected_materials)]
217
 
218
  if time_period == 'daily':
219
  grouped = df.groupby(['date', 'material_type'])['weight_kg'].sum().reset_index()
220
  fig = px.line(grouped, x='date', y='weight_kg', color='material_type',
221
+ title='Materials Production Trends',
222
  labels={'weight_kg': 'Weight (kg)', 'date': 'Date', 'material_type': 'Material'})
223
  elif time_period == 'weekly':
224
  df['week'] = df['date'].dt.isocalendar().week
 
226
  grouped = df.groupby(['year', 'week', 'material_type'])['weight_kg'].sum().reset_index()
227
  grouped['week_label'] = grouped['year'].astype(str) + '-W' + grouped['week'].astype(str)
228
  fig = px.bar(grouped, x='week_label', y='weight_kg', color='material_type',
229
+ title='Materials Production Trends (Weekly)',
230
  labels={'weight_kg': 'Weight (kg)', 'week_label': 'Week', 'material_type': 'Material'})
231
+ else:
232
  df['month'] = df['date'].dt.to_period('M')
233
  grouped = df.groupby(['month', 'material_type'])['weight_kg'].sum().reset_index()
234
  grouped['month'] = grouped['month'].astype(str)
235
  fig = px.bar(grouped, x='month', y='weight_kg', color='material_type',
236
+ title='Materials Production Trends (Monthly)',
237
  labels={'weight_kg': 'Weight (kg)', 'month': 'Month', 'material_type': 'Material'})
238
 
239
+ fig.update_layout(**get_chart_theme()['layout'], height=400)
240
  return fig
241
 
242
  def create_shift_trend_chart(df, time_period='daily'):
 
243
  if time_period == 'daily':
 
244
  grouped = df.groupby(['date', 'shift'])['weight_kg'].sum().reset_index()
 
 
245
  pivot_data = grouped.pivot(index='date', columns='shift', values='weight_kg').fillna(0)
246
 
247
  fig = go.Figure()
248
 
 
249
  if 'day' in pivot_data.columns:
250
  fig.add_trace(go.Bar(
251
+ x=pivot_data.index, y=pivot_data['day'], name='Day Shift',
252
+ marker_color=DESIGN_SYSTEM['colors']['warning'],
253
+ text=pivot_data['day'].round(0), textposition='inside'
 
 
 
254
  ))
255
 
 
256
  if 'night' in pivot_data.columns:
257
  fig.add_trace(go.Bar(
258
+ x=pivot_data.index, y=pivot_data['night'], name='Night Shift',
259
+ marker_color=DESIGN_SYSTEM['colors']['primary'],
 
 
260
  base=pivot_data['day'] if 'day' in pivot_data.columns else 0,
261
+ text=pivot_data['night'].round(0), textposition='inside'
 
262
  ))
263
 
264
  fig.update_layout(
265
+ **get_chart_theme()['layout'],
266
+ title='Daily Shift Production Trends (Stacked)',
267
+ xaxis_title='Date', yaxis_title='Weight (kg)',
268
+ barmode='stack', height=400, showlegend=True
 
 
269
  )
270
+ else:
271
+ # Similar logic for weekly/monthly but simplified for space
272
+ grouped = df.groupby(['date' if time_period == 'daily' else 'date', 'shift'])['weight_kg'].sum().reset_index()
273
+ fig = px.bar(grouped, x='date', y='weight_kg', color='shift',
274
+ title=f'{time_period.title()} Shift Production Trends',
 
 
 
 
 
 
 
 
 
 
 
 
275
  barmode='stack')
276
+ fig.update_layout(**get_chart_theme()['layout'], height=400)
277
 
278
  return fig
279
 
 
309
  except:
310
  return "Error getting AI response"
311
 
 
312
  def main():
313
+ load_css()
314
+
315
+ # Modern header
316
+ st.markdown("""
317
+ <div class="main-header">
318
+ <div class="main-title">🏭 Production Monitor</div>
319
+ <div class="main-subtitle">Nilsen Service & Consulting AS | Real-time Production Analytics</div>
320
+ </div>
321
+ """, unsafe_allow_html=True)
322
 
323
  model = init_ai()
324
 
325
+ # Sidebar
326
  with st.sidebar:
327
+ st.markdown("### πŸ“Š Dashboard Controls")
328
+ uploaded_file = st.file_uploader("Upload Production Data", type=['csv'])
329
 
330
  if model:
331
+ st.success("πŸ€– AI Assistant Ready")
332
  else:
333
+ st.warning("⚠️ AI Assistant Unavailable")
334
 
335
  if uploaded_file:
336
  df = load_data(uploaded_file)
337
  stats = get_material_stats(df)
338
 
339
  # Material Overview
340
+ st.markdown('<div class="section-header">πŸ“‹ Material Overview</div>', unsafe_allow_html=True)
341
  materials = [k for k in stats.keys() if k != '_total_']
342
 
343
  cols = st.columns(4)
 
350
  delta=f"{info['percentage']:.1f}% of total"
351
  )
352
  st.caption(f"Daily avg: {info['daily_avg']:,.0f} kg")
 
353
 
 
354
  total_info = stats['_total_']
355
  with cols[3]:
356
  st.metric(
 
359
  delta="100% of total"
360
  )
361
  st.caption(f"Daily avg: {total_info['daily_avg']:,.0f} kg")
 
 
 
 
 
 
362
 
363
+ # Charts Section
364
+ st.markdown('<div class="section-header">πŸ“Š Production Trends</div>', unsafe_allow_html=True)
365
 
366
+ col1, col2 = st.columns([3, 1])
367
+ with col2:
368
+ time_view = st.selectbox("Time Period", ["daily", "weekly", "monthly"])
369
 
370
+ with col1:
371
+ with st.container():
372
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
373
+ total_chart = create_total_production_chart(df, time_view)
374
+ st.plotly_chart(total_chart, use_container_width=True)
375
+ st.markdown('</div>', unsafe_allow_html=True)
376
 
377
+ # Materials Analysis
378
+ st.markdown('<div class="section-header">🏷️ Materials Analysis</div>', unsafe_allow_html=True)
379
 
380
+ col1, col2 = st.columns([3, 1])
381
+ with col2:
 
382
  selected_materials = st.multiselect(
383
  "Select Materials",
384
+ options=materials, default=materials
 
 
 
385
  )
386
 
387
+ with col1:
388
  if selected_materials:
389
+ with st.container():
390
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
391
+ materials_chart = create_materials_trend_chart(df, time_view, selected_materials)
392
+ st.plotly_chart(materials_chart, use_container_width=True)
393
+ st.markdown('</div>', unsafe_allow_html=True)
394
 
395
  # Shift Analysis
396
  if 'shift' in df.columns:
397
+ st.markdown('<div class="section-header">πŸŒ“ Shift Analysis</div>', unsafe_allow_html=True)
 
 
 
398
 
399
+ with st.container():
400
+ st.markdown('<div class="chart-container">', unsafe_allow_html=True)
401
+ shift_chart = create_shift_trend_chart(df, time_view)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  st.plotly_chart(shift_chart, use_container_width=True)
403
+ st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
404
 
405
  # Quality Check
406
+ st.markdown('<div class="section-header">⚠️ Quality Check</div>', unsafe_allow_html=True)
407
  outliers = detect_outliers(df)
408
 
409
+ cols = st.columns(len(outliers))
410
  for i, (material, info) in enumerate(outliers.items()):
411
+ with cols[i]:
412
  if info['count'] > 0:
413
+ st.markdown(f'<div class="alert-warning"><strong>{material.title()}</strong><br>{info["count"]} outliers detected<br>Normal range: {info["range"]}</div>', unsafe_allow_html=True)
 
414
  else:
415
+ st.markdown(f'<div class="alert-success"><strong>{material.title()}</strong><br>All values normal</div>', unsafe_allow_html=True)
416
 
417
  # AI Assistant
418
  if model:
419
+ st.markdown('<div class="section-header">πŸ€– AI Insights</div>', unsafe_allow_html=True)
420
 
 
421
  quick_questions = [
422
  "What's the production trend?",
423
+ "Which material performs best?",
424
  "Any efficiency recommendations?"
425
  ]
426
 
427
  cols = st.columns(len(quick_questions))
428
  for i, q in enumerate(quick_questions):
429
  with cols[i]:
430
+ if st.button(q, key=f"q_{i}"):
431
+ with st.spinner("Analyzing..."):
432
+ answer = query_ai(model, stats, q)
433
+ st.info(answer)
434
 
435
+ custom_question = st.text_input("Ask about your production data:")
436
+ if custom_question and st.button("Ask AI"):
437
+ with st.spinner("Analyzing..."):
 
438
  answer = query_ai(model, stats, custom_question)
439
+ st.success(f"**Q:** {custom_question}")
440
+ st.write(f"**A:** {answer}")
441
 
442
  else:
443
  st.info("πŸ“ Upload your production data to start analysis")
444
  st.markdown("""
445
  **Expected TSV format:**
446
+ - `date`: MM/DD/YYYY
447
+ - `weight_kg`: Production weight in kg
448
+ - `material_type`: Material category
449
+ - `shift`: day/night (optional)
450
  """)
451
 
452
  if __name__ == "__main__":