PD03 commited on
Commit
62633c3
·
verified ·
1 Parent(s): af1c55a

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +204 -344
src/streamlit_app.py CHANGED
@@ -20,7 +20,7 @@ st.set_page_config(
20
  initial_sidebar_state="expanded"
21
  )
22
 
23
- # Custom CSS
24
  st.markdown("""
25
  <style>
26
  /* Main theme colors */
@@ -129,31 +129,46 @@ def get_openai_api_key():
129
 
130
  # Method 3: Try from Hugging Face Spaces environment
131
  if not api_key:
132
- api_key = os.getenv('OPENAI_API_TOKEN') # Sometimes HF uses this
133
 
134
  return api_key
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  # Data generation function
137
  @st.cache_data
138
  def generate_synthetic_procurement_data():
139
  """Generate synthetic SAP S/4HANA procurement data"""
140
  fake = Faker()
141
- np.random.seed(42) # For reproducible data
142
  random.seed(42)
143
 
144
- # Vendors data
145
  vendors = [
146
  "Siemens AG", "BASF SE", "BMW Group", "Mercedes-Benz", "Bosch GmbH",
147
  "ThyssenKrupp", "Bayer AG", "Continental AG", "Henkel AG", "SAP SE"
148
  ]
149
 
150
- # Material categories
151
  material_categories = [
152
  "Raw Materials", "Components", "Packaging", "Services",
153
  "IT Equipment", "Office Supplies", "Machinery", "Chemicals"
154
  ]
155
 
156
- # Generate purchase orders
157
  purchase_orders = []
158
  for i in range(500):
159
  order_date = fake.date_between(start_date='-2y', end_date='today')
@@ -178,7 +193,6 @@ def generate_synthetic_procurement_data():
178
  }
179
  purchase_orders.append(po)
180
 
181
- # Generate spend analytics data
182
  spend_data = []
183
  for vendor in vendors:
184
  for category in material_categories:
@@ -194,33 +208,64 @@ def generate_synthetic_procurement_data():
194
 
195
  return pd.DataFrame(purchase_orders), pd.DataFrame(spend_data)
196
 
197
- # AI Agent Classes
198
  class LLMPoweredProcurementAgent:
199
- """AI Agent powered by OpenAI GPT for intelligent procurement analysis"""
200
 
201
  def __init__(self, po_data: pd.DataFrame, spend_data: pd.DataFrame):
202
  self.po_data = po_data
203
  self.spend_data = spend_data
204
 
205
- # Safely get OpenAI API key
206
  self.api_key = get_openai_api_key()
207
- self.llm_available = bool(self.api_key)
 
208
 
209
- if self.llm_available:
210
- try:
211
- import openai
212
- self.client = openai.OpenAI(api_key=self.api_key)
213
- except ImportError:
214
- self.llm_available = False
215
- self.client = None
216
- else:
217
- self.client = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
  def generate_executive_summary(self) -> str:
220
- """Generate an executive summary using GPT or fallback"""
221
 
222
  if not self.llm_available:
223
- # Enhanced rule-based summary if no API key
224
  total_spend = self.po_data['order_value'].sum()
225
  total_orders = len(self.po_data)
226
  on_time_rate = self.po_data['on_time_delivery'].mean() * 100
@@ -228,7 +273,9 @@ class LLMPoweredProcurementAgent:
228
  top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
229
  top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
230
 
231
- return f"""**🎯 Executive Summary - Procurement Performance Dashboard**
 
 
232
 
233
  📊 **Current Portfolio Overview**
234
  • Total procurement spend: €{total_spend:,.0f} across {total_orders:,} purchase orders
@@ -251,61 +298,100 @@ class LLMPoweredProcurementAgent:
251
  • Develop performance-based contracts with high-performing suppliers
252
  • Establish automated approval workflows for orders under €10,000
253
 
254
- *🔧 Note: Connect OpenAI API for advanced AI insights and natural language analysis*"""
255
 
256
- # Prepare data summary for LLM
257
  data_summary = {
258
  "total_spend": float(self.po_data['order_value'].sum()),
259
  "total_orders": len(self.po_data),
260
  "unique_vendors": len(self.po_data['vendor'].unique()),
261
  "avg_order_value": float(self.po_data['order_value'].mean()),
262
  "on_time_delivery_rate": float(self.po_data['on_time_delivery'].mean()),
263
- "top_vendors": self.po_data.groupby('vendor')['order_value'].sum().nlargest(3).to_dict(),
264
- "top_categories": self.po_data.groupby('material_category')['order_value'].sum().nlargest(3).to_dict(),
265
  "quality_score_avg": float(self.po_data['quality_score'].mean())
266
  }
267
 
268
  prompt = f"""
269
- As a senior procurement analyst with expertise in SAP S/4HANA systems, provide an executive summary of procurement performance:
270
-
271
- Data: {json.dumps(data_summary, indent=2)}
272
 
273
- Provide:
274
  1. Executive overview (2-3 sentences)
275
- 2. Key performance highlights with specific metrics
276
- 3. Critical areas needing attention
277
  4. Strategic recommendations (3-4 actionable items)
278
 
279
- Keep it professional, metrics-focused, and actionable for C-level executives.
280
  """
281
 
282
  try:
283
  response = self.client.chat.completions.create(
284
- model="gpt-4",
285
  messages=[
286
- {"role": "system", "content": "You are a senior procurement analyst with 15+ years of SAP S/4HANA experience."},
287
  {"role": "user", "content": prompt}
288
  ],
289
  max_tokens=600,
290
  temperature=0.7
291
  )
292
- return response.choices[0].message.content
 
 
 
293
  except Exception as e:
294
- return f"🤖 AI Analysis temporarily unavailable. Using rule-based insights instead.\n\n{self.generate_executive_summary()}"
 
295
 
296
  def chat_with_data(self, user_question: str) -> str:
297
- """Natural language interface to query procurement data"""
298
 
299
  if not self.llm_available:
300
- # Enhanced rule-based responses
301
- question_lower = user_question.lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
- if any(word in question_lower for word in ["spend", "cost", "money", "budget"]):
304
- total_spend = self.po_data['order_value'].sum()
305
- top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
306
- monthly_avg = total_spend / 24 # Assuming 2 years of data
307
- return f"""💰 **Spend Analysis:**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
 
309
  • **Total procurement spend**: €{total_spend:,.0f}
310
  • **Monthly average**: €{monthly_avg:,.0f}
311
  • **Largest spend category**: {top_category}
@@ -313,114 +399,41 @@ class LLMPoweredProcurementAgent:
313
 
314
  The spending is distributed across {len(self.po_data['material_category'].unique())} categories with {top_category} representing the highest investment area.
315
 
316
- *💡 Connect OpenAI API for detailed spend optimization strategies!*"""
 
 
 
 
 
317
 
318
- elif any(word in question_lower for word in ["vendor", "supplier", "partner"]):
319
- top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
320
- vendor_count = len(self.po_data['vendor'].unique())
321
- top_vendor_performance = self.po_data[self.po_data['vendor'] == top_vendor]['on_time_delivery'].mean() * 100
322
- return f"""🤝 **Vendor Analysis:**
323
 
 
324
  • **Total active vendors**: {vendor_count}
325
  • **Top strategic partner**: {top_vendor}
326
  • **{top_vendor} performance**: {top_vendor_performance:.1f}% on-time delivery
327
  • **Vendor diversity**: Well-distributed across multiple suppliers
328
 
329
- Your vendor portfolio shows good diversification with {top_vendor} as the leading partner.
330
-
331
- *💡 Connect OpenAI API for detailed vendor relationship strategies!*"""
332
-
333
- elif any(word in question_lower for word in ["risk", "compliance", "quality"]):
334
- avg_quality = self.po_data['quality_score'].mean()
335
- on_time_rate = self.po_data['on_time_delivery'].mean() * 100
336
- return f"""⚠️ **Risk & Quality Analysis:**
337
-
338
- • **Average quality score**: {avg_quality:.1f}/10
339
- • **On-time delivery rate**: {on_time_rate:.1f}%
340
- • **Performance status**: {'Excellent' if avg_quality > 8.5 else 'Good' if avg_quality > 7.5 else 'Needs Improvement'}
341
-
342
- Overall risk profile appears {'low' if on_time_rate > 85 else 'moderate'} based on delivery performance metrics.
343
-
344
- *💡 Connect OpenAI API for comprehensive risk assessment!*"""
345
-
346
- elif any(word in question_lower for word in ["trend", "pattern", "analysis"]):
347
- return f"""📈 **Trend Analysis:**
348
-
349
- • **Data period**: {self.po_data['order_date'].min()} to {self.po_data['order_date'].max()}
350
- • **Total orders processed**: {len(self.po_data):,}
351
- • **Peak category**: {self.po_data.groupby('material_category')['order_value'].sum().idxmax()}
352
- • **Seasonal patterns**: Data shows consistent procurement activity
353
-
354
- Historical data indicates stable procurement operations with opportunities for optimization.
355
-
356
- *💡 Connect OpenAI API for advanced trend forecasting!*"""
357
-
358
- else:
359
- return f"""🤖 **Procurement Assistant Ready!**
360
-
361
- I can help you analyze:
362
- • 💰 **Spending patterns** and budget optimization
363
- • 🤝 **Vendor performance** and relationship management
364
- • ⚠️ **Risk assessment** and quality metrics
365
- • 📈 **Trends and forecasting** for strategic planning
366
-
367
- **Current data scope**: {len(self.po_data):,} orders across {len(self.po_data['vendor'].unique())} vendors
368
 
369
- Try asking: "What are my biggest spending areas?" or "How are my vendors performing?"
 
 
 
370
 
371
- *💡 Connect OpenAI API for natural language conversations and advanced insights!*"""
372
-
373
- # LLM-powered response
374
- data_context = {
375
- "procurement_summary": {
376
- "total_spend": float(self.po_data['order_value'].sum()),
377
- "order_count": len(self.po_data),
378
- "vendor_count": len(self.po_data['vendor'].unique()),
379
- "date_range": f"{self.po_data['order_date'].min()} to {self.po_data['order_date'].max()}",
380
- "categories": self.po_data['material_category'].unique().tolist(),
381
- "vendors": self.po_data['vendor'].unique().tolist()
382
- },
383
- "performance_metrics": {
384
- "avg_quality_score": float(self.po_data['quality_score'].mean()),
385
- "on_time_delivery_rate": float(self.po_data['on_time_delivery'].mean()),
386
- "avg_order_value": float(self.po_data['order_value'].mean())
387
- }
388
- }
389
-
390
- prompt = f"""
391
- User Question: {user_question}
392
-
393
- Procurement Data Context:
394
- {json.dumps(data_context, indent=2)}
395
-
396
- Answer the user's question based on the procurement data. Be conversational yet professional.
397
- Include specific metrics when relevant and relate findings to business impact.
398
- If you need additional data not available in the context, suggest what analysis would be helpful.
399
- """
400
-
401
- try:
402
- response = self.client.chat.completions.create(
403
- model="gpt-4",
404
- messages=[
405
- {"role": "system", "content": "You are an expert procurement analyst assistant. Provide helpful, professional responses about procurement data and strategy."},
406
- {"role": "user", "content": prompt}
407
- ],
408
- max_tokens=500,
409
- temperature=0.7
410
- )
411
- return response.choices[0].message.content
412
- except Exception as e:
413
- return f"I'm having trouble accessing advanced AI right now. Here's what I can tell you based on the data:\n\n{self.chat_with_data(user_question)}"
414
 
 
 
415
  def analyze_spend_patterns(self) -> Dict[str, Any]:
416
- """Analyze spending patterns and generate insights"""
417
  total_spend = self.po_data['order_value'].sum()
418
  avg_order_value = self.po_data['order_value'].mean()
419
 
420
- # Top spending categories
421
  category_spend = self.po_data.groupby('material_category')['order_value'].sum().sort_values(ascending=False)
422
-
423
- # Vendor performance analysis
424
  vendor_performance = self.po_data.groupby('vendor').agg({
425
  'order_value': 'sum',
426
  'on_time_delivery': 'mean',
@@ -440,17 +453,17 @@ if 'data_loaded' not in st.session_state:
440
  st.session_state.po_df, st.session_state.spend_df = generate_synthetic_procurement_data()
441
  st.session_state.data_loaded = True
442
 
443
- # Initialize AI agents
444
- @st.cache_resource
445
  def initialize_agents():
 
446
  analytics_agent = LLMPoweredProcurementAgent(st.session_state.po_df, st.session_state.spend_df)
447
  return analytics_agent
448
 
449
  analytics_agent = initialize_agents()
450
 
451
- # API Key status check
452
- api_key = get_openai_api_key()
453
- api_key_status = "🟢 Connected" if api_key else "🔴 Not Connected"
454
 
455
  # Main header
456
  st.markdown(f"""
@@ -461,16 +474,28 @@ st.markdown(f"""
461
  </div>
462
  """, unsafe_allow_html=True)
463
 
464
- # Sidebar navigation
465
  with st.sidebar:
466
- st.markdown("### 🤖 AI-Powered Analytics")
467
- st.markdown(f"**OpenAI Status:** {api_key_status}")
468
-
469
- if not api_key:
 
 
 
 
 
 
 
 
 
 
 
 
470
  st.markdown("""
471
  <div class="alert alert-info">
472
- <small><strong>💡 Enhanced AI Features</strong><br>
473
- Add OpenAI API key as OPENAI_API_KEY in your Hugging Face Space settings for advanced AI conversations and insights!</small>
474
  </div>
475
  """, unsafe_allow_html=True)
476
 
@@ -489,18 +514,12 @@ with st.sidebar:
489
  "nav-link-selected": {"background-color": "#0066cc"},
490
  }
491
  )
492
-
493
- st.markdown("---")
494
- st.markdown("### 📊 Quick Stats")
495
- st.metric("Total Orders", f"{len(st.session_state.po_df):,}")
496
- st.metric("Active Vendors", f"{len(st.session_state.po_df['vendor'].unique())}")
497
- st.metric("Categories", f"{len(st.session_state.po_df['material_category'].unique())}")
498
 
 
499
  if selected == "🏠 Dashboard":
500
- # AI-generated insights at the top
501
  st.markdown("### 🧠 AI Executive Summary")
502
 
503
- with st.spinner('🤖 AI analyzing procurement data...'):
504
  executive_summary = analytics_agent.generate_executive_summary()
505
 
506
  st.markdown(f"""
@@ -559,7 +578,6 @@ if selected == "🏠 Dashboard":
559
  col1, col2 = st.columns(2)
560
 
561
  with col1:
562
- # Spend by category
563
  category_spend = st.session_state.po_df.groupby('material_category')['order_value'].sum().reset_index()
564
  fig_pie = px.pie(
565
  category_spend,
@@ -568,16 +586,10 @@ if selected == "🏠 Dashboard":
568
  title='Spend Distribution by Category',
569
  color_discrete_sequence=px.colors.qualitative.Set3
570
  )
571
- fig_pie.update_layout(
572
- title_font_size=16,
573
- title_x=0.5,
574
- showlegend=True,
575
- height=400
576
- )
577
  st.plotly_chart(fig_pie, use_container_width=True)
578
 
579
  with col2:
580
- # Top vendors
581
  vendor_spend = st.session_state.po_df.groupby('vendor')['order_value'].sum().reset_index()
582
  vendor_spend = vendor_spend.nlargest(8, 'order_value')
583
 
@@ -589,12 +601,7 @@ if selected == "🏠 Dashboard":
589
  color='order_value',
590
  color_continuous_scale='Blues'
591
  )
592
- fig_bar.update_layout(
593
- title_font_size=16,
594
- title_x=0.5,
595
- xaxis_tickangle=45,
596
- height=400
597
- )
598
  st.plotly_chart(fig_bar, use_container_width=True)
599
 
600
  elif selected == "💬 AI Chat":
@@ -603,15 +610,15 @@ elif selected == "💬 AI Chat":
603
  st.markdown(f"""
604
  <div class="ai-insight">
605
  <h4>🤖 Intelligent Procurement Assistant</h4>
606
- <p>Ask me anything about your procurement data! I can analyze trends, vendor performance, spending patterns, and provide strategic recommendations.</p>
607
- <p><small>Status: {api_key_status}</small></p>
608
  </div>
609
  """, unsafe_allow_html=True)
610
 
611
  # Chat interface
612
  if "messages" not in st.session_state:
613
  st.session_state.messages = [
614
- {"role": "assistant", "content": "Hello! I'm your AI procurement analyst. I've analyzed your procurement portfolio and I'm ready to help! What would you like to explore?"}
615
  ]
616
 
617
  # Display chat messages
@@ -621,220 +628,73 @@ elif selected == "💬 AI Chat":
621
 
622
  # Chat input
623
  if prompt := st.chat_input("Ask about your procurement data..."):
624
- # Add user message to chat history
625
  st.session_state.messages.append({"role": "user", "content": prompt})
626
  with st.chat_message("user"):
627
  st.markdown(prompt)
628
 
629
- # Generate AI response
630
  with st.chat_message("assistant"):
631
- with st.spinner("🤖 Analyzing your question..."):
632
  response = analytics_agent.chat_with_data(prompt)
633
  st.markdown(response)
634
 
635
- # Add assistant response to chat history
636
  st.session_state.messages.append({"role": "assistant", "content": response})
637
 
638
- # Suggested questions
639
- st.markdown("#### 💡 Try these sample questions:")
640
-
641
  col1, col2, col3 = st.columns(3)
642
 
643
- sample_questions = [
644
  "What are my biggest spending areas?",
645
- "How are my vendors performing?",
646
- "What risks should I be concerned about?"
647
  ]
648
 
649
- for i, (col, question) in enumerate(zip([col1, col2, col3], sample_questions)):
650
  with col:
651
  if st.button(f"💭 {question}", key=f"q_{i}"):
652
- # Add the question to chat
653
  st.session_state.messages.append({"role": "user", "content": question})
654
- with st.spinner("🤖 Analyzing..."):
655
- response = analytics_agent.chat_with_data(question)
656
  st.session_state.messages.append({"role": "assistant", "content": response})
657
  st.rerun()
658
-
659
- # Clear chat button
660
- if st.button("🗑️ Clear Chat History"):
661
- st.session_state.messages = [
662
- {"role": "assistant", "content": "Chat cleared! What would you like to know about your procurement data?"}
663
- ]
664
- st.rerun()
665
 
666
  elif selected == "📊 Analytics":
667
  st.markdown("### 📈 Advanced Analytics Dashboard")
668
 
669
- # Vendor performance analysis
670
- st.markdown("#### 🏆 Vendor Performance Scorecard")
671
-
672
  vendor_performance = st.session_state.po_df.groupby('vendor').agg({
673
  'order_value': 'sum',
674
  'on_time_delivery': 'mean',
675
  'quality_score': 'mean',
676
  'po_number': 'count'
677
  }).round(2)
678
- vendor_performance.columns = ['Total Spend (€)', 'On-Time Delivery (%)', 'Quality Score', 'Order Count']
679
- vendor_performance['On-Time Delivery (%)'] = (vendor_performance['On-Time Delivery (%)'] * 100).round(1)
680
- vendor_performance = vendor_performance.sort_values('Total Spend (€)', ascending=False)
681
-
682
- st.dataframe(
683
- vendor_performance.head(10),
684
- use_container_width=True,
685
- column_config={
686
- "Total Spend (€)": st.column_config.NumberColumn(
687
- "Total Spend (€)",
688
- help="Total procurement spend with vendor",
689
- format="€%.0f",
690
- ),
691
- "On-Time Delivery (%)": st.column_config.NumberColumn(
692
- "On-Time Delivery (%)",
693
- help="Percentage of on-time deliveries",
694
- format="%.1f%%",
695
- ),
696
- "Quality Score": st.column_config.NumberColumn(
697
- "Quality Score",
698
- help="Average quality rating (1-10)",
699
- format="%.1f/10",
700
- )
701
- }
702
- )
703
 
704
- # Performance charts
705
- col1, col2 = st.columns(2)
706
-
707
- with col1:
708
- # Performance scatter plot
709
- fig_scatter = px.scatter(
710
- st.session_state.po_df,
711
- x='on_time_delivery',
712
- y='quality_score',
713
- size='order_value',
714
- color='vendor',
715
- title='Vendor Performance Matrix',
716
- labels={'on_time_delivery': 'On-Time Delivery Rate', 'quality_score': 'Quality Score (1-10)'},
717
- hover_data=['vendor', 'order_value']
718
- )
719
- fig_scatter.update_layout(height=500, showlegend=False)
720
- st.plotly_chart(fig_scatter, use_container_width=True)
721
-
722
- with col2:
723
- # Monthly trend
724
- st.session_state.po_df['order_month'] = pd.to_datetime(st.session_state.po_df['order_date']).dt.to_period('M')
725
- monthly_trend = st.session_state.po_df.groupby('order_month')['order_value'].sum().reset_index()
726
- monthly_trend['order_month'] = monthly_trend['order_month'].astype(str)
727
-
728
- fig_trend = px.line(
729
- monthly_trend,
730
- x='order_month',
731
- y='order_value',
732
- title='Monthly Procurement Spend Trend',
733
- markers=True
734
- )
735
- fig_trend.update_layout(
736
- height=500,
737
- xaxis_tickangle=45
738
- )
739
- fig_trend.update_traces(line_color='#0066cc', line_width=3, marker_size=8)
740
- st.plotly_chart(fig_trend, use_container_width=True)
741
 
742
  elif selected == "��� Recommendations":
743
- st.markdown("### 🚀 Strategic Procurement Recommendations")
744
-
745
- st.markdown("""
746
- <div class="ai-insight">
747
- <h3>🎯 AI-Powered Strategic Optimization</h3>
748
- <p>Based on comprehensive data analysis, here are prioritized recommendations to enhance your procurement strategy and drive business value.</p>
749
- </div>
750
- """, unsafe_allow_html=True)
751
-
752
- # Calculate some metrics for recommendations
753
- vendor_count = len(st.session_state.po_df['vendor'].unique())
754
- avg_order_value = st.session_state.po_df['order_value'].mean()
755
- low_value_orders = len(st.session_state.po_df[st.session_state.po_df['order_value'] < 5000])
756
- total_orders = len(st.session_state.po_df)
757
 
758
  recommendations = [
759
- {
760
- "priority": "🔥 High Priority",
761
- "title": "Vendor Consolidation Strategy",
762
- "description": f"With {vendor_count} active vendors, consolidating to 5-7 strategic partners could reduce costs by 12-18% and improve relationship management.",
763
- "impact": "💰 Cost Reduction: €50K-75K annually",
764
- "timeline": "3-6 months"
765
- },
766
- {
767
- "priority": "⚡ Quick Win",
768
- "title": "Procurement Process Automation",
769
- "description": f"{low_value_orders}/{total_orders} orders ({low_value_orders/total_orders*100:.1f}%) are under €5,000. Implementing automated approval workflows could save 40+ hours weekly.",
770
- "impact": "⏱️ Efficiency Gain: 160 hours/month",
771
- "timeline": "4-8 weeks"
772
- },
773
- {
774
- "priority": "📈 Strategic",
775
- "title": "Performance-Based Contracts",
776
- "description": "Implement KPI-driven contracts with top 5 vendors focusing on quality scores >8.5 and delivery performance >90%.",
777
- "impact": "📊 Performance Improvement: 15-25%",
778
- "timeline": "6-9 months"
779
- },
780
- {
781
- "priority": "🛡️ Risk Management",
782
- "title": "Supplier Risk Monitoring",
783
- "description": "Deploy real-time risk assessment tools to monitor supplier financial health, compliance, and performance metrics.",
784
- "impact": "⚠️ Risk Reduction: 30-40%",
785
- "timeline": "2-4 months"
786
- },
787
- {
788
- "priority": "🎯 Innovation",
789
- "title": "Digital Procurement Platform",
790
- "description": "Upgrade to AI-powered procurement platform with predictive analytics, spend optimization, and automated sourcing capabilities.",
791
- "impact": "🚀 Digital Transformation: 25-35% efficiency",
792
- "timeline": "9-12 months"
793
- }
794
  ]
795
 
796
  for i, rec in enumerate(recommendations, 1):
797
- priority_color = {"🔥 High Priority": "#dc3545", "⚡ Quick Win": "#28a745", "📈 Strategic": "#0066cc", "🛡️ Risk Management": "#ffc107", "🎯 Innovation": "#6f42c1"}
798
-
799
  st.markdown(f"""
800
  <div class="alert alert-success">
801
- <h4 style="color: {priority_color[rec['priority']]};">{rec['priority']}</h4>
802
- <h3>{rec['title']}</h3>
803
- <p style="margin-bottom: 1rem;">{rec['description']}</p>
804
- <div style="display: flex; justify-content: space-between; font-size: 0.9rem;">
805
- <span><strong>{rec['impact']}</strong></span>
806
- <span><strong>⏱️ Timeline: {rec['timeline']}</strong></span>
807
- </div>
808
  </div>
809
  """, unsafe_allow_html=True)
810
-
811
- # Implementation roadmap
812
- st.markdown("#### 🗺️ Implementation Roadmap")
813
-
814
- roadmap_data = {
815
- "Phase": ["Phase 1 (0-3 months)", "Phase 2 (3-6 months)", "Phase 3 (6-12 months)"],
816
- "Focus Areas": [
817
- "Process Automation, Quick Wins",
818
- "Vendor Consolidation, Risk Management",
819
- "Strategic Contracts, Digital Platform"
820
- ],
821
- "Expected ROI": ["15-20%", "20-30%", "30-40%"],
822
- "Key Deliverables": [
823
- "Automated workflows, Spend visibility",
824
- "Strategic partnerships, Risk framework",
825
- "AI-powered platform, Performance management"
826
- ]
827
- }
828
-
829
- roadmap_df = pd.DataFrame(roadmap_data)
830
- st.dataframe(roadmap_df, use_container_width=True, hide_index=True)
831
 
832
  # Footer
833
  st.markdown("---")
834
  st.markdown(f"""
835
  <div style="text-align: center; padding: 1rem; color: #666;">
836
- <p>🤖 <strong>Agentic AI Procurement Analytics</strong> | Built with Streamlit & Python | SAP S/4HANA Integration Demo</p>
837
- <p><em>Synthetic data demonstration • {len(st.session_state.po_df):,} orders • {len(st.session_state.po_df['vendor'].unique())} vendors • OpenAI {api_key_status}</em></p>
838
- <p><small>💡 Add your OpenAI API key as 'OPENAI_API_KEY' in Hugging Face Space settings for enhanced AI features</small></p>
839
  </div>
840
  """, unsafe_allow_html=True)
 
20
  initial_sidebar_state="expanded"
21
  )
22
 
23
+ # Custom CSS (same as before)
24
  st.markdown("""
25
  <style>
26
  /* Main theme colors */
 
129
 
130
  # Method 3: Try from Hugging Face Spaces environment
131
  if not api_key:
132
+ api_key = os.getenv('OPENAI_API_TOKEN')
133
 
134
  return api_key
135
 
136
+ # Function to safely initialize OpenAI client
137
+ def create_openai_client(api_key):
138
+ """Safely create OpenAI client with proper error handling"""
139
+ if not api_key:
140
+ return None, "No API key available"
141
+
142
+ try:
143
+ import openai
144
+ # Try creating client with minimal parameters
145
+ client = openai.OpenAI(api_key=api_key)
146
+ # Test the client with a simple call
147
+ client.models.list()
148
+ return client, "Connected successfully"
149
+ except ImportError:
150
+ return None, "OpenAI package not installed"
151
+ except Exception as e:
152
+ return None, f"Connection failed: {str(e)}"
153
+
154
  # Data generation function
155
  @st.cache_data
156
  def generate_synthetic_procurement_data():
157
  """Generate synthetic SAP S/4HANA procurement data"""
158
  fake = Faker()
159
+ np.random.seed(42)
160
  random.seed(42)
161
 
 
162
  vendors = [
163
  "Siemens AG", "BASF SE", "BMW Group", "Mercedes-Benz", "Bosch GmbH",
164
  "ThyssenKrupp", "Bayer AG", "Continental AG", "Henkel AG", "SAP SE"
165
  ]
166
 
 
167
  material_categories = [
168
  "Raw Materials", "Components", "Packaging", "Services",
169
  "IT Equipment", "Office Supplies", "Machinery", "Chemicals"
170
  ]
171
 
 
172
  purchase_orders = []
173
  for i in range(500):
174
  order_date = fake.date_between(start_date='-2y', end_date='today')
 
193
  }
194
  purchase_orders.append(po)
195
 
 
196
  spend_data = []
197
  for vendor in vendors:
198
  for category in material_categories:
 
208
 
209
  return pd.DataFrame(purchase_orders), pd.DataFrame(spend_data)
210
 
211
+ # AI Agent Class with improved error handling
212
  class LLMPoweredProcurementAgent:
213
+ """AI Agent with robust OpenAI integration and fallback capabilities"""
214
 
215
  def __init__(self, po_data: pd.DataFrame, spend_data: pd.DataFrame):
216
  self.po_data = po_data
217
  self.spend_data = spend_data
218
 
219
+ # Initialize OpenAI client safely
220
  self.api_key = get_openai_api_key()
221
+ self.client, self.connection_status = create_openai_client(self.api_key)
222
+ self.llm_available = self.client is not None
223
 
224
+ # Store connection details for debugging
225
+ self.debug_info = {
226
+ "api_key_available": bool(self.api_key),
227
+ "api_key_length": len(self.api_key) if self.api_key else 0,
228
+ "connection_status": self.connection_status,
229
+ "llm_available": self.llm_available
230
+ }
231
+
232
+ def get_status_info(self) -> Dict[str, Any]:
233
+ """Get detailed status information for debugging"""
234
+ return self.debug_info
235
+
236
+ def test_llm_connection(self) -> Dict[str, Any]:
237
+ """Test LLM connection with detailed status"""
238
+ if not self.llm_available:
239
+ return {
240
+ "status": "❌ Disconnected",
241
+ "details": self.connection_status,
242
+ "recommendation": "Add OPENAI_API_KEY to your Hugging Face Space secrets"
243
+ }
244
+
245
+ try:
246
+ # Test with minimal API call
247
+ response = self.client.chat.completions.create(
248
+ model="gpt-3.5-turbo",
249
+ messages=[{"role": "user", "content": "Test"}],
250
+ max_tokens=5
251
+ )
252
+ return {
253
+ "status": "✅ Connected",
254
+ "details": "OpenAI API responding normally",
255
+ "model": "gpt-3.5-turbo"
256
+ }
257
+ except Exception as e:
258
+ return {
259
+ "status": "⚠️ Error",
260
+ "details": f"API call failed: {str(e)}",
261
+ "recommendation": "Check API key validity"
262
+ }
263
 
264
  def generate_executive_summary(self) -> str:
265
+ """Generate executive summary with clear status indicators"""
266
 
267
  if not self.llm_available:
268
+ # Enhanced rule-based summary
269
  total_spend = self.po_data['order_value'].sum()
270
  total_orders = len(self.po_data)
271
  on_time_rate = self.po_data['on_time_delivery'].mean() * 100
 
273
  top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
274
  top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
275
 
276
+ return f"""🤖 **[Smart Analysis - Rule-Based]**
277
+
278
+ **🎯 Executive Summary - Procurement Performance Dashboard**
279
 
280
  📊 **Current Portfolio Overview**
281
  • Total procurement spend: €{total_spend:,.0f} across {total_orders:,} purchase orders
 
298
  • Develop performance-based contracts with high-performing suppliers
299
  • Establish automated approval workflows for orders under €10,000
300
 
301
+ *🔧 Status: Using intelligent rule-based analysis. {self.connection_status}*"""
302
 
303
+ # LLM-powered summary
304
  data_summary = {
305
  "total_spend": float(self.po_data['order_value'].sum()),
306
  "total_orders": len(self.po_data),
307
  "unique_vendors": len(self.po_data['vendor'].unique()),
308
  "avg_order_value": float(self.po_data['order_value'].mean()),
309
  "on_time_delivery_rate": float(self.po_data['on_time_delivery'].mean()),
 
 
310
  "quality_score_avg": float(self.po_data['quality_score'].mean())
311
  }
312
 
313
  prompt = f"""
314
+ As a senior procurement analyst, provide an executive summary based on this data:
315
+ {json.dumps(data_summary, indent=2)}
 
316
 
317
+ Include:
318
  1. Executive overview (2-3 sentences)
319
+ 2. Key performance highlights
320
+ 3. Areas needing attention
321
  4. Strategic recommendations (3-4 actionable items)
322
 
323
+ Keep it professional and actionable for executives.
324
  """
325
 
326
  try:
327
  response = self.client.chat.completions.create(
328
+ model="gpt-3.5-turbo",
329
  messages=[
330
+ {"role": "system", "content": "You are a senior procurement analyst with SAP S/4HANA expertise."},
331
  {"role": "user", "content": prompt}
332
  ],
333
  max_tokens=600,
334
  temperature=0.7
335
  )
336
+
337
+ ai_response = response.choices[0].message.content
338
+ return f"🧠 **[AI-Powered Analysis - OpenAI GPT]**\n\n{ai_response}"
339
+
340
  except Exception as e:
341
+ fallback = self.generate_executive_summary() # Recursive call will use rule-based
342
+ return f"⚠️ **[AI Temporarily Unavailable - Using Smart Fallback]**\n\n{fallback}\n\n*Error: {str(e)}*"
343
 
344
  def chat_with_data(self, user_question: str) -> str:
345
+ """Enhanced chat with clear response type indicators"""
346
 
347
  if not self.llm_available:
348
+ return self._get_rule_based_response(user_question)
349
+
350
+ try:
351
+ data_context = {
352
+ "total_spend": float(self.po_data['order_value'].sum()),
353
+ "order_count": len(self.po_data),
354
+ "vendor_count": len(self.po_data['vendor'].unique()),
355
+ "avg_quality": float(self.po_data['quality_score'].mean()),
356
+ "on_time_rate": float(self.po_data['on_time_delivery'].mean())
357
+ }
358
+
359
+ prompt = f"""
360
+ User Question: {user_question}
361
+ Procurement Data: {json.dumps(data_context, indent=2)}
362
+
363
+ Answer professionally with specific metrics where relevant.
364
+ """
365
 
366
+ response = self.client.chat.completions.create(
367
+ model="gpt-3.5-turbo",
368
+ messages=[
369
+ {"role": "system", "content": "You are an expert procurement analyst assistant."},
370
+ {"role": "user", "content": prompt}
371
+ ],
372
+ max_tokens=400,
373
+ temperature=0.7
374
+ )
375
+
376
+ ai_response = response.choices[0].message.content
377
+ return f"🧠 **[AI Response]**\n\n{ai_response}"
378
+
379
+ except Exception as e:
380
+ fallback_response = self._get_rule_based_response(user_question)
381
+ return f"⚠️ **[Smart Fallback]** AI temporarily unavailable\n\n{fallback_response}\n\n*Error details: {str(e)}*"
382
+
383
+ def _get_rule_based_response(self, question: str) -> str:
384
+ """Enhanced rule-based responses with detailed analysis"""
385
+ question_lower = question.lower()
386
+
387
+ if any(word in question_lower for word in ["spend", "cost", "money", "budget"]):
388
+ total_spend = self.po_data['order_value'].sum()
389
+ top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
390
+ monthly_avg = total_spend / 24
391
+
392
+ return f"""🤖 **[Rule-Based Analysis]**
393
 
394
+ 💰 **Spend Analysis:**
395
  • **Total procurement spend**: €{total_spend:,.0f}
396
  • **Monthly average**: €{monthly_avg:,.0f}
397
  • **Largest spend category**: {top_category}
 
399
 
400
  The spending is distributed across {len(self.po_data['material_category'].unique())} categories with {top_category} representing the highest investment area.
401
 
402
+ *💡 Connect OpenAI for advanced AI analysis!*"""
403
+
404
+ elif any(word in question_lower for word in ["vendor", "supplier", "partner"]):
405
+ top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
406
+ vendor_count = len(self.po_data['vendor'].unique())
407
+ top_vendor_performance = self.po_data[self.po_data['vendor'] == top_vendor]['on_time_delivery'].mean() * 100
408
 
409
+ return f"""🤖 **[Rule-Based Analysis]**
 
 
 
 
410
 
411
+ 🤝 **Vendor Analysis:**
412
  • **Total active vendors**: {vendor_count}
413
  • **Top strategic partner**: {top_vendor}
414
  • **{top_vendor} performance**: {top_vendor_performance:.1f}% on-time delivery
415
  • **Vendor diversity**: Well-distributed across multiple suppliers
416
 
417
+ *💡 Connect OpenAI for detailed vendor insights!*"""
418
+
419
+ else:
420
+ return f"""🤖 **[Rule-Based Analysis]**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
+ I can analyze your procurement data! Try asking about:
423
+ • 💰 **Spending patterns**: "What are my biggest costs?"
424
+ • 🤝 **Vendor performance**: "How are my suppliers doing?"
425
+ • ⚠️ **Risk factors**: "What should I worry about?"
426
 
427
+ **Current data**: {len(self.po_data):,} orders across {len(self.po_data['vendor'].unique())} vendors
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
 
429
+ *💡 Connect OpenAI API for natural language conversations!*"""
430
+
431
  def analyze_spend_patterns(self) -> Dict[str, Any]:
432
+ """Analyze spending patterns"""
433
  total_spend = self.po_data['order_value'].sum()
434
  avg_order_value = self.po_data['order_value'].mean()
435
 
 
436
  category_spend = self.po_data.groupby('material_category')['order_value'].sum().sort_values(ascending=False)
 
 
437
  vendor_performance = self.po_data.groupby('vendor').agg({
438
  'order_value': 'sum',
439
  'on_time_delivery': 'mean',
 
453
  st.session_state.po_df, st.session_state.spend_df = generate_synthetic_procurement_data()
454
  st.session_state.data_loaded = True
455
 
456
+ # Initialize AI agents with safe caching
 
457
  def initialize_agents():
458
+ """Initialize agents without caching to avoid errors"""
459
  analytics_agent = LLMPoweredProcurementAgent(st.session_state.po_df, st.session_state.spend_df)
460
  return analytics_agent
461
 
462
  analytics_agent = initialize_agents()
463
 
464
+ # Get connection status
465
+ status_info = analytics_agent.get_status_info()
466
+ api_key_status = "🟢 Connected" if status_info['llm_available'] else "🔴 Not Connected"
467
 
468
  # Main header
469
  st.markdown(f"""
 
474
  </div>
475
  """, unsafe_allow_html=True)
476
 
477
+ # Sidebar with enhanced status
478
  with st.sidebar:
479
+ st.markdown("### 🤖 AI System Status")
480
+ st.markdown(f"**Connection:** {api_key_status}")
481
+
482
+ # Debug information
483
+ with st.expander("🔍 Debug Information"):
484
+ st.json(status_info)
485
+
486
+ # Connection test button
487
+ if st.button("🔄 Test AI Connection"):
488
+ test_result = analytics_agent.test_llm_connection()
489
+ st.markdown(f"**Status:** {test_result['status']}")
490
+ st.markdown(f"**Details:** {test_result['details']}")
491
+ if 'recommendation' in test_result:
492
+ st.info(test_result['recommendation'])
493
+
494
+ if not status_info['llm_available']:
495
  st.markdown("""
496
  <div class="alert alert-info">
497
+ <small><strong>💡 Enable AI Features</strong><br>
498
+ Add OPENAI_API_KEY to your Hugging Face Space settings for enhanced AI capabilities!</small>
499
  </div>
500
  """, unsafe_allow_html=True)
501
 
 
514
  "nav-link-selected": {"background-color": "#0066cc"},
515
  }
516
  )
 
 
 
 
 
 
517
 
518
+ # Dashboard section
519
  if selected == "🏠 Dashboard":
 
520
  st.markdown("### 🧠 AI Executive Summary")
521
 
522
+ with st.spinner('🤖 Analyzing procurement data...'):
523
  executive_summary = analytics_agent.generate_executive_summary()
524
 
525
  st.markdown(f"""
 
578
  col1, col2 = st.columns(2)
579
 
580
  with col1:
 
581
  category_spend = st.session_state.po_df.groupby('material_category')['order_value'].sum().reset_index()
582
  fig_pie = px.pie(
583
  category_spend,
 
586
  title='Spend Distribution by Category',
587
  color_discrete_sequence=px.colors.qualitative.Set3
588
  )
589
+ fig_pie.update_layout(title_font_size=16, title_x=0.5, height=400)
 
 
 
 
 
590
  st.plotly_chart(fig_pie, use_container_width=True)
591
 
592
  with col2:
 
593
  vendor_spend = st.session_state.po_df.groupby('vendor')['order_value'].sum().reset_index()
594
  vendor_spend = vendor_spend.nlargest(8, 'order_value')
595
 
 
601
  color='order_value',
602
  color_continuous_scale='Blues'
603
  )
604
+ fig_bar.update_layout(title_font_size=16, title_x=0.5, xaxis_tickangle=45, height=400)
 
 
 
 
 
605
  st.plotly_chart(fig_bar, use_container_width=True)
606
 
607
  elif selected == "💬 AI Chat":
 
610
  st.markdown(f"""
611
  <div class="ai-insight">
612
  <h4>🤖 Intelligent Procurement Assistant</h4>
613
+ <p>Ask me anything about your procurement data! I can analyze trends, vendor performance, and provide strategic recommendations.</p>
614
+ <p><small>Status: {api_key_status} | Response Mode: {'AI-Powered' if status_info['llm_available'] else 'Rule-Based Analysis'}</small></p>
615
  </div>
616
  """, unsafe_allow_html=True)
617
 
618
  # Chat interface
619
  if "messages" not in st.session_state:
620
  st.session_state.messages = [
621
+ {"role": "assistant", "content": "Hello! I'm your procurement analyst. I've loaded your data and I'm ready to help! What would you like to explore?"}
622
  ]
623
 
624
  # Display chat messages
 
628
 
629
  # Chat input
630
  if prompt := st.chat_input("Ask about your procurement data..."):
 
631
  st.session_state.messages.append({"role": "user", "content": prompt})
632
  with st.chat_message("user"):
633
  st.markdown(prompt)
634
 
 
635
  with st.chat_message("assistant"):
636
+ with st.spinner("🤖 Analyzing..."):
637
  response = analytics_agent.chat_with_data(prompt)
638
  st.markdown(response)
639
 
 
640
  st.session_state.messages.append({"role": "assistant", "content": response})
641
 
642
+ # Sample questions
643
+ st.markdown("#### 💡 Try these questions:")
 
644
  col1, col2, col3 = st.columns(3)
645
 
646
+ questions = [
647
  "What are my biggest spending areas?",
648
+ "How are my vendors performing?",
649
+ "What optimization opportunities exist?"
650
  ]
651
 
652
+ for i, (col, question) in enumerate(zip([col1, col2, col3], questions)):
653
  with col:
654
  if st.button(f"💭 {question}", key=f"q_{i}"):
 
655
  st.session_state.messages.append({"role": "user", "content": question})
656
+ response = analytics_agent.chat_with_data(question)
 
657
  st.session_state.messages.append({"role": "assistant", "content": response})
658
  st.rerun()
 
 
 
 
 
 
 
659
 
660
  elif selected == "📊 Analytics":
661
  st.markdown("### 📈 Advanced Analytics Dashboard")
662
 
 
 
 
663
  vendor_performance = st.session_state.po_df.groupby('vendor').agg({
664
  'order_value': 'sum',
665
  'on_time_delivery': 'mean',
666
  'quality_score': 'mean',
667
  'po_number': 'count'
668
  }).round(2)
669
+ vendor_performance.columns = ['Total Spend (€)', 'On-Time Delivery', 'Quality Score', 'Order Count']
670
+ vendor_performance['On-Time Delivery'] = (vendor_performance['On-Time Delivery'] * 100).round(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
671
 
672
+ st.dataframe(vendor_performance.sort_values('Total Spend (€)', ascending=False), use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
673
 
674
  elif selected == "��� Recommendations":
675
+ st.markdown("### 🚀 Strategic Recommendations")
 
 
 
 
 
 
 
 
 
 
 
 
 
676
 
677
  recommendations = [
678
+ "🎯 **Vendor Consolidation**: Reduce supplier base from 10 to 6-7 strategic partners for 12-18% cost reduction",
679
+ " **Process Automation**: Implement automated approval for orders under €5,000 to save 40+ hours weekly",
680
+ "📊 **Performance Contracts**: Establish KPI-driven agreements with top vendors",
681
+ "🛡️ **Risk Monitoring**: Deploy real-time supplier risk assessment tools",
682
+ "🚀 **Digital Platform**: Upgrade to AI-powered procurement system"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  ]
684
 
685
  for i, rec in enumerate(recommendations, 1):
 
 
686
  st.markdown(f"""
687
  <div class="alert alert-success">
688
+ <h4>Recommendation #{i}</h4>
689
+ <p>{rec}</p>
 
 
 
 
 
690
  </div>
691
  """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
  # Footer
694
  st.markdown("---")
695
  st.markdown(f"""
696
  <div style="text-align: center; padding: 1rem; color: #666;">
697
+ <p>🤖 <strong>Agentic AI Procurement Analytics</strong> | Built with Streamlit & Python</p>
698
+ <p><em>Demo with synthetic data • {len(st.session_state.po_df):,} orders • OpenAI {api_key_status}</em></p>
 
699
  </div>
700
  """, unsafe_allow_html=True)