PD03 commited on
Commit
bd6f07d
Β·
verified Β·
1 Parent(s): 2b63de9

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +241 -235
src/streamlit_app.py CHANGED
@@ -23,7 +23,6 @@ st.set_page_config(
23
  # Custom CSS (same as before)
24
  st.markdown("""
25
  <style>
26
- /* Main theme colors */
27
  :root {
28
  --primary-color: #0066cc;
29
  --secondary-color: #f0f8ff;
@@ -33,12 +32,10 @@ st.markdown("""
33
  --danger-color: #dc3545;
34
  }
35
 
36
- /* Hide Streamlit branding */
37
  #MainMenu {visibility: hidden;}
38
  footer {visibility: hidden;}
39
  header {visibility: hidden;}
40
 
41
- /* Custom header styling */
42
  .main-header {
43
  background: linear-gradient(90deg, #0066cc, #004c99);
44
  padding: 1rem;
@@ -48,7 +45,6 @@ st.markdown("""
48
  text-align: center;
49
  }
50
 
51
- /* Metric cards styling */
52
  .metric-card {
53
  background: white;
54
  padding: 1.5rem;
@@ -58,7 +54,6 @@ st.markdown("""
58
  margin-bottom: 1rem;
59
  }
60
 
61
- /* AI insights styling */
62
  .ai-insight {
63
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
64
  color: white;
@@ -67,7 +62,6 @@ st.markdown("""
67
  margin: 1rem 0;
68
  }
69
 
70
- /* Alert styling */
71
  .alert {
72
  padding: 1rem;
73
  border-radius: 8px;
@@ -93,7 +87,6 @@ st.markdown("""
93
  color: #0c5460;
94
  }
95
 
96
- /* Button styling */
97
  .stButton > button {
98
  background: linear-gradient(90deg, #0066cc, #004c99);
99
  color: white;
@@ -111,50 +104,58 @@ st.markdown("""
111
  </style>
112
  """, unsafe_allow_html=True)
113
 
114
- # Function to safely get OpenAI API key
115
- # Function to safely get OpenAI API key (UPDATED)
116
  def get_openai_api_key():
117
- """Safely retrieve OpenAI API key from various sources"""
118
- api_key = None
119
-
120
- # Method 1: Try from environment variables first (for HF Spaces)
121
- api_key = os.getenv('OPENAI_API_KEY')
122
-
123
- # Method 2: Try alternative environment variable names
124
- if not api_key:
125
- api_key = os.getenv('OPENAI_API_TOKEN')
126
 
127
- # Method 3: Try from Streamlit secrets (only if secrets exist)
128
- if not api_key:
129
- try:
130
- # Check if secrets file exists before accessing
131
- import streamlit as st
132
- if hasattr(st, 'secrets') and st.secrets:
133
- if 'OPENAI_API_KEY' in st.secrets:
134
- api_key = st.secrets["OPENAI_API_KEY"]
135
- except (FileNotFoundError, Exception):
136
- # Silently continue if secrets file doesn't exist
137
- pass
138
-
139
- return api_key
140
-
141
- # Function to safely initialize OpenAI client
142
- def create_openai_client(api_key):
143
- """Safely create OpenAI client with proper error handling"""
144
  if not api_key:
145
  return None, "No API key available"
146
 
147
  try:
148
- import openai
149
- # Try creating client with minimal parameters
150
- client = openai.OpenAI(api_key=api_key)
151
- # Test the client with a simple call
152
- client.models.list()
153
- return client, "Connected successfully"
154
- except ImportError:
155
- return None, "OpenAI package not installed"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  except Exception as e:
157
- return None, f"Connection failed: {str(e)}"
158
 
159
  # Data generation function
160
  @st.cache_data
@@ -213,180 +214,189 @@ def generate_synthetic_procurement_data():
213
 
214
  return pd.DataFrame(purchase_orders), pd.DataFrame(spend_data)
215
 
216
- # AI Agent Class with improved error handling
217
- class LLMPoweredProcurementAgent:
218
- """AI Agent with robust OpenAI integration and fallback capabilities"""
219
 
220
  def __init__(self, po_data: pd.DataFrame, spend_data: pd.DataFrame):
221
  self.po_data = po_data
222
  self.spend_data = spend_data
223
-
224
- # Initialize OpenAI client safely
225
  self.api_key = get_openai_api_key()
226
- self.client, self.connection_status = create_openai_client(self.api_key)
227
- self.llm_available = self.client is not None
228
 
229
- # Store connection details for debugging
230
- self.debug_info = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  "api_key_available": bool(self.api_key),
232
  "api_key_length": len(self.api_key) if self.api_key else 0,
233
- "connection_status": self.connection_status,
234
- "llm_available": self.llm_available
 
235
  }
236
 
237
- def get_status_info(self) -> Dict[str, Any]:
238
- """Get detailed status information for debugging"""
239
- return self.debug_info
240
-
241
  def test_llm_connection(self) -> Dict[str, Any]:
242
  """Test LLM connection with detailed status"""
243
- if not self.llm_available:
244
  return {
245
- "status": "❌ Disconnected",
246
- "details": self.connection_status,
247
- "recommendation": "Add OPENAI_API_KEY to your Hugging Face Space secrets"
248
  }
249
 
250
- try:
251
- # Test with minimal API call
252
- response = self.client.chat.completions.create(
253
- model="gpt-3.5-turbo",
254
- messages=[{"role": "user", "content": "Test"}],
255
- max_tokens=5
256
- )
257
  return {
258
  "status": "βœ… Connected",
259
  "details": "OpenAI API responding normally",
260
- "model": "gpt-3.5-turbo"
261
  }
262
- except Exception as e:
263
  return {
264
- "status": "⚠️ Error",
265
- "details": f"API call failed: {str(e)}",
266
- "recommendation": "Check API key validity"
267
  }
268
 
269
  def generate_executive_summary(self) -> str:
270
- """Generate executive summary with clear status indicators"""
271
 
272
  if not self.llm_available:
273
- # Enhanced rule-based summary
274
- total_spend = self.po_data['order_value'].sum()
275
- total_orders = len(self.po_data)
276
- on_time_rate = self.po_data['on_time_delivery'].mean() * 100
277
- quality_avg = self.po_data['quality_score'].mean()
278
- top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
279
- top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
280
-
281
- return f"""πŸ€– **[Smart Analysis - Rule-Based]**
282
-
283
- **🎯 Executive Summary - Procurement Performance Dashboard**
284
-
285
- πŸ“Š **Current Portfolio Overview**
286
- β€’ Total procurement spend: €{total_spend:,.0f} across {total_orders:,} purchase orders
287
- β€’ Active vendor network: {len(self.po_data['vendor'].unique())} strategic suppliers
288
- β€’ Average order value: €{self.po_data['order_value'].mean():,.0f}
289
-
290
- πŸ† **Performance Highlights**
291
- β€’ On-time delivery performance: {on_time_rate:.1f}% (Industry benchmark: 85%)
292
- β€’ Average supplier quality score: {quality_avg:.1f}/10
293
- β€’ Leading spend category: {top_category}
294
- β€’ Top strategic partner: {top_vendor}
295
-
296
- ⚑ **Strategic Opportunities**
297
- β€’ Vendor consolidation potential identified in {len(self.po_data['vendor'].unique())} supplier base
298
- β€’ Contract optimization opportunities with top-tier vendors
299
- β€’ Digital procurement automation possibilities for routine purchases
300
-
301
- πŸ’‘ **AI-Powered Recommendations**
302
- β€’ Implement strategic sourcing for {top_category} category
303
- β€’ Develop performance-based contracts with high-performing suppliers
304
- β€’ Establish automated approval workflows for orders under €10,000
305
-
306
- *πŸ”§ Status: Using intelligent rule-based analysis. {self.connection_status}*"""
307
 
308
- # LLM-powered summary
309
  data_summary = {
310
  "total_spend": float(self.po_data['order_value'].sum()),
311
  "total_orders": len(self.po_data),
312
- "unique_vendors": len(self.po_data['vendor'].unique()),
313
  "avg_order_value": float(self.po_data['order_value'].mean()),
314
- "on_time_delivery_rate": float(self.po_data['on_time_delivery'].mean()),
315
- "quality_score_avg": float(self.po_data['quality_score'].mean())
316
  }
317
 
318
- prompt = f"""
319
- As a senior procurement analyst, provide an executive summary based on this data:
320
- {json.dumps(data_summary, indent=2)}
321
-
322
- Include:
323
- 1. Executive overview (2-3 sentences)
324
- 2. Key performance highlights
325
- 3. Areas needing attention
326
- 4. Strategic recommendations (3-4 actionable items)
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- Keep it professional and actionable for executives.
329
- """
330
 
331
- try:
332
- response = self.client.chat.completions.create(
333
- model="gpt-3.5-turbo",
334
- messages=[
335
- {"role": "system", "content": "You are a senior procurement analyst with SAP S/4HANA expertise."},
336
- {"role": "user", "content": prompt}
337
- ],
338
- max_tokens=600,
339
- temperature=0.7
340
- )
341
-
342
- ai_response = response.choices[0].message.content
343
  return f"🧠 **[AI-Powered Analysis - OpenAI GPT]**\n\n{ai_response}"
344
-
345
- except Exception as e:
346
- fallback = self.generate_executive_summary() # Recursive call will use rule-based
347
- return f"⚠️ **[AI Temporarily Unavailable - Using Smart Fallback]**\n\n{fallback}\n\n*Error: {str(e)}*"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
  def chat_with_data(self, user_question: str) -> str:
350
- """Enhanced chat with clear response type indicators"""
351
 
352
  if not self.llm_available:
353
  return self._get_rule_based_response(user_question)
354
 
355
- try:
356
- data_context = {
357
- "total_spend": float(self.po_data['order_value'].sum()),
358
- "order_count": len(self.po_data),
359
- "vendor_count": len(self.po_data['vendor'].unique()),
360
- "avg_quality": float(self.po_data['quality_score'].mean()),
361
- "on_time_rate": float(self.po_data['on_time_delivery'].mean())
 
 
 
 
 
 
 
 
 
 
362
  }
363
-
364
- prompt = f"""
365
- User Question: {user_question}
366
- Procurement Data: {json.dumps(data_context, indent=2)}
367
-
368
- Answer professionally with specific metrics where relevant.
369
- """
370
-
371
- response = self.client.chat.completions.create(
372
- model="gpt-3.5-turbo",
373
- messages=[
374
- {"role": "system", "content": "You are an expert procurement analyst assistant."},
375
- {"role": "user", "content": prompt}
376
- ],
377
- max_tokens=400,
378
- temperature=0.7
379
- )
380
-
381
- ai_response = response.choices[0].message.content
382
- return f"🧠 **[AI Response]**\n\n{ai_response}"
383
-
384
- except Exception as e:
385
- fallback_response = self._get_rule_based_response(user_question)
386
- return f"⚠️ **[Smart Fallback]** AI temporarily unavailable\n\n{fallback_response}\n\n*Error details: {str(e)}*"
387
 
388
  def _get_rule_based_response(self, question: str) -> str:
389
- """Enhanced rule-based responses with detailed analysis"""
390
  question_lower = question.lower()
391
 
392
  if any(word in question_lower for word in ["spend", "cost", "money", "budget"]):
@@ -394,44 +404,47 @@ class LLMPoweredProcurementAgent:
394
  top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
395
  monthly_avg = total_spend / 24
396
 
397
- return f"""πŸ€– **[Rule-Based Analysis]**
398
 
399
- πŸ’° **Spend Analysis:**
400
- β€’ **Total procurement spend**: €{total_spend:,.0f}
401
  β€’ **Monthly average**: €{monthly_avg:,.0f}
402
- β€’ **Largest spend category**: {top_category}
403
- β€’ **Average order size**: €{self.po_data['order_value'].mean():,.0f}
404
 
405
- The spending is distributed across {len(self.po_data['material_category'].unique())} categories with {top_category} representing the highest investment area.
406
 
407
- *πŸ’‘ Connect OpenAI for advanced AI analysis!*"""
408
 
409
  elif any(word in question_lower for word in ["vendor", "supplier", "partner"]):
410
  top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
411
  vendor_count = len(self.po_data['vendor'].unique())
412
- top_vendor_performance = self.po_data[self.po_data['vendor'] == top_vendor]['on_time_delivery'].mean() * 100
413
 
414
- return f"""πŸ€– **[Rule-Based Analysis]**
 
 
 
 
 
 
415
 
416
- 🀝 **Vendor Analysis:**
417
- β€’ **Total active vendors**: {vendor_count}
418
- β€’ **Top strategic partner**: {top_vendor}
419
- β€’ **{top_vendor} performance**: {top_vendor_performance:.1f}% on-time delivery
420
- β€’ **Vendor diversity**: Well-distributed across multiple suppliers
421
 
422
- *πŸ’‘ Connect OpenAI for detailed vendor insights!*"""
423
 
424
  else:
425
- return f"""πŸ€– **[Rule-Based Analysis]**
426
 
427
- I can analyze your procurement data! Try asking about:
428
- β€’ πŸ’° **Spending patterns**: "What are my biggest costs?"
429
  β€’ 🀝 **Vendor performance**: "How are my suppliers doing?"
430
- β€’ ⚠️ **Risk factors**: "What should I worry about?"
 
431
 
432
- **Current data**: {len(self.po_data):,} orders across {len(self.po_data['vendor'].unique())} vendors
433
 
434
- *πŸ’‘ Connect OpenAI API for natural language conversations!*"""
435
 
436
  def analyze_spend_patterns(self) -> Dict[str, Any]:
437
  """Analyze spending patterns"""
@@ -458,15 +471,10 @@ if 'data_loaded' not in st.session_state:
458
  st.session_state.po_df, st.session_state.spend_df = generate_synthetic_procurement_data()
459
  st.session_state.data_loaded = True
460
 
461
- # Initialize AI agents with safe caching
462
- def initialize_agents():
463
- """Initialize agents without caching to avoid errors"""
464
- analytics_agent = LLMPoweredProcurementAgent(st.session_state.po_df, st.session_state.spend_df)
465
- return analytics_agent
466
 
467
- analytics_agent = initialize_agents()
468
-
469
- # Get connection status
470
  status_info = analytics_agent.get_status_info()
471
  api_key_status = "🟒 Connected" if status_info['llm_available'] else "πŸ”΄ Not Connected"
472
 
@@ -479,16 +487,17 @@ st.markdown(f"""
479
  </div>
480
  """, unsafe_allow_html=True)
481
 
482
- # Sidebar with enhanced status
483
  with st.sidebar:
484
  st.markdown("### πŸ€– AI System Status")
485
  st.markdown(f"**Connection:** {api_key_status}")
 
486
 
487
- # Debug information
488
- with st.expander("πŸ” Debug Information"):
489
  st.json(status_info)
490
 
491
- # Connection test button
492
  if st.button("πŸ”„ Test AI Connection"):
493
  test_result = analytics_agent.test_llm_connection()
494
  st.markdown(f"**Status:** {test_result['status']}")
@@ -500,7 +509,7 @@ with st.sidebar:
500
  st.markdown("""
501
  <div class="alert alert-info">
502
  <small><strong>πŸ’‘ Enable AI Features</strong><br>
503
- Add OPENAI_API_KEY to your Hugging Face Space settings for enhanced AI capabilities!</small>
504
  </div>
505
  """, unsafe_allow_html=True)
506
 
@@ -520,7 +529,7 @@ with st.sidebar:
520
  }
521
  )
522
 
523
- # Dashboard section
524
  if selected == "🏠 Dashboard":
525
  st.markdown("### 🧠 AI Executive Summary")
526
 
@@ -540,42 +549,42 @@ if selected == "🏠 Dashboard":
540
  col1, col2, col3, col4 = st.columns(4)
541
 
542
  with col1:
543
- st.markdown("""
544
  <div class="metric-card">
545
  <h3 style="color: var(--primary-color); margin: 0;">Total Spend</h3>
546
- <h2 style="margin: 0.5rem 0;">€{:,.0f}</h2>
547
  <p style="color: #28a745; margin: 0;">πŸ“ˆ Active Portfolio</p>
548
  </div>
549
- """.format(insights['total_spend']), unsafe_allow_html=True)
550
 
551
  with col2:
552
- st.markdown("""
553
  <div class="metric-card">
554
  <h3 style="color: var(--primary-color); margin: 0;">Avg Order Value</h3>
555
- <h2 style="margin: 0.5rem 0;">€{:,.0f}</h2>
556
  <p style="color: #17a2b8; margin: 0;">πŸ“Š Order Efficiency</p>
557
  </div>
558
- """.format(insights['avg_order_value']), unsafe_allow_html=True)
559
 
560
  with col3:
561
  active_vendors = len(st.session_state.po_df['vendor'].unique())
562
- st.markdown("""
563
  <div class="metric-card">
564
  <h3 style="color: var(--primary-color); margin: 0;">Active Vendors</h3>
565
- <h2 style="margin: 0.5rem 0;">{}</h2>
566
  <p style="color: #6f42c1; margin: 0;">🀝 Strategic Partners</p>
567
  </div>
568
- """.format(active_vendors), unsafe_allow_html=True)
569
 
570
  with col4:
571
  on_time_delivery = st.session_state.po_df['on_time_delivery'].mean() * 100
572
- st.markdown("""
573
  <div class="metric-card">
574
  <h3 style="color: var(--primary-color); margin: 0;">On-Time Delivery</h3>
575
- <h2 style="margin: 0.5rem 0;">{:.1f}%</h2>
576
  <p style="color: #28a745; margin: 0;">⏰ Performance</p>
577
  </div>
578
- """.format(on_time_delivery), unsafe_allow_html=True)
579
 
580
  # Charts
581
  st.markdown("### πŸ“Š Executive Dashboard")
@@ -588,8 +597,7 @@ if selected == "🏠 Dashboard":
588
  category_spend,
589
  values='order_value',
590
  names='material_category',
591
- title='Spend Distribution by Category',
592
- color_discrete_sequence=px.colors.qualitative.Set3
593
  )
594
  fig_pie.update_layout(title_font_size=16, title_x=0.5, height=400)
595
  st.plotly_chart(fig_pie, use_container_width=True)
@@ -602,9 +610,7 @@ if selected == "🏠 Dashboard":
602
  vendor_spend,
603
  x='vendor',
604
  y='order_value',
605
- title='Top Vendors by Spend',
606
- color='order_value',
607
- color_continuous_scale='Blues'
608
  )
609
  fig_bar.update_layout(title_font_size=16, title_x=0.5, xaxis_tickangle=45, height=400)
610
  st.plotly_chart(fig_bar, use_container_width=True)
@@ -614,16 +620,16 @@ elif selected == "πŸ’¬ AI Chat":
614
 
615
  st.markdown(f"""
616
  <div class="ai-insight">
617
- <h4>πŸ€– Intelligent Procurement Assistant</h4>
618
- <p>Ask me anything about your procurement data! I can analyze trends, vendor performance, and provide strategic recommendations.</p>
619
- <p><small>Status: {api_key_status} | Response Mode: {'AI-Powered' if status_info['llm_available'] else 'Rule-Based Analysis'}</small></p>
620
  </div>
621
  """, unsafe_allow_html=True)
622
 
623
  # Chat interface
624
  if "messages" not in st.session_state:
625
  st.session_state.messages = [
626
- {"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?"}
627
  ]
628
 
629
  # Display chat messages
@@ -680,10 +686,10 @@ elif selected == "🎯 Recommendations":
680
  st.markdown("### πŸš€ Strategic Recommendations")
681
 
682
  recommendations = [
683
- "🎯 **Vendor Consolidation**: Reduce supplier base from 10 to 6-7 strategic partners for 12-18% cost reduction",
684
- "⚑ **Process Automation**: Implement automated approval for orders under €5,000 to save 40+ hours weekly",
685
- "πŸ“Š **Performance Contracts**: Establish KPI-driven agreements with top vendors",
686
- "πŸ›‘οΈ **Risk Monitoring**: Deploy real-time supplier risk assessment tools",
687
  "πŸš€ **Digital Platform**: Upgrade to AI-powered procurement system"
688
  ]
689
 
@@ -699,7 +705,7 @@ elif selected == "🎯 Recommendations":
699
  st.markdown("---")
700
  st.markdown(f"""
701
  <div style="text-align: center; padding: 1rem; color: #666;">
702
- <p>πŸ€– <strong>Agentic AI Procurement Analytics</strong> | Built with Streamlit & Python</p>
703
  <p><em>Demo with synthetic data β€’ {len(st.session_state.po_df):,} orders β€’ OpenAI {api_key_status}</em></p>
704
  </div>
705
  """, unsafe_allow_html=True)
 
23
  # Custom CSS (same as before)
24
  st.markdown("""
25
  <style>
 
26
  :root {
27
  --primary-color: #0066cc;
28
  --secondary-color: #f0f8ff;
 
32
  --danger-color: #dc3545;
33
  }
34
 
 
35
  #MainMenu {visibility: hidden;}
36
  footer {visibility: hidden;}
37
  header {visibility: hidden;}
38
 
 
39
  .main-header {
40
  background: linear-gradient(90deg, #0066cc, #004c99);
41
  padding: 1rem;
 
45
  text-align: center;
46
  }
47
 
 
48
  .metric-card {
49
  background: white;
50
  padding: 1.5rem;
 
54
  margin-bottom: 1rem;
55
  }
56
 
 
57
  .ai-insight {
58
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
59
  color: white;
 
62
  margin: 1rem 0;
63
  }
64
 
 
65
  .alert {
66
  padding: 1rem;
67
  border-radius: 8px;
 
87
  color: #0c5460;
88
  }
89
 
 
90
  .stButton > button {
91
  background: linear-gradient(90deg, #0066cc, #004c99);
92
  color: white;
 
104
  </style>
105
  """, unsafe_allow_html=True)
106
 
107
+ # Safe OpenAI integration that works with ANY version
 
108
  def get_openai_api_key():
109
+ """Get API key from environment variables (Hugging Face Spaces compatible)"""
110
+ return (
111
+ os.getenv('OPENAI_API_KEY') or
112
+ os.getenv('OPENAI_API_TOKEN') or
113
+ os.getenv('OPENAI_KEY')
114
+ )
115
+
116
+ def safe_openai_chat(api_key, messages, model="gpt-3.5-turbo", max_tokens=400):
117
+ """Universal OpenAI chat function that works with any OpenAI version"""
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  if not api_key:
120
  return None, "No API key available"
121
 
122
  try:
123
+ # Try using requests directly (most reliable method)
124
+ import requests
125
+
126
+ headers = {
127
+ "Authorization": f"Bearer {api_key}",
128
+ "Content-Type": "application/json"
129
+ }
130
+
131
+ data = {
132
+ "model": model,
133
+ "messages": messages,
134
+ "max_tokens": max_tokens,
135
+ "temperature": 0.7
136
+ }
137
+
138
+ response = requests.post(
139
+ "https://api.openai.com/v1/chat/completions",
140
+ headers=headers,
141
+ json=data,
142
+ timeout=30
143
+ )
144
+
145
+ if response.status_code == 200:
146
+ result = response.json()
147
+ return result['choices'][0]['message']['content'], "Success"
148
+ elif response.status_code == 401:
149
+ return None, "Invalid API key"
150
+ elif response.status_code == 429:
151
+ return None, "Rate limit exceeded"
152
+ else:
153
+ return None, f"API error: {response.status_code}"
154
+
155
+ except requests.exceptions.RequestException as e:
156
+ return None, f"Network error: {str(e)}"
157
  except Exception as e:
158
+ return None, f"Request failed: {str(e)}"
159
 
160
  # Data generation function
161
  @st.cache_data
 
214
 
215
  return pd.DataFrame(purchase_orders), pd.DataFrame(spend_data)
216
 
217
+ # Updated AI Agent with bulletproof OpenAI integration
218
+ class UniversalProcurementAgent:
219
+ """AI Agent with universal OpenAI compatibility"""
220
 
221
  def __init__(self, po_data: pd.DataFrame, spend_data: pd.DataFrame):
222
  self.po_data = po_data
223
  self.spend_data = spend_data
 
 
224
  self.api_key = get_openai_api_key()
225
+ self.llm_available = bool(self.api_key)
 
226
 
227
+ # Test connection if API key exists
228
+ if self.llm_available:
229
+ self._test_connection()
230
+
231
+ def _test_connection(self):
232
+ """Test OpenAI connection using direct API call"""
233
+ test_response, status = safe_openai_chat(
234
+ self.api_key,
235
+ [{"role": "user", "content": "Hello"}],
236
+ max_tokens=5
237
+ )
238
+
239
+ if test_response is None:
240
+ self.llm_available = False
241
+ self.connection_error = status
242
+ else:
243
+ self.connection_error = "Connected successfully"
244
+
245
+ def get_status_info(self) -> Dict[str, Any]:
246
+ """Get connection status information"""
247
+ return {
248
  "api_key_available": bool(self.api_key),
249
  "api_key_length": len(self.api_key) if self.api_key else 0,
250
+ "llm_available": self.llm_available,
251
+ "connection_status": getattr(self, 'connection_error', 'Not tested'),
252
+ "method": "Direct API calls (version-agnostic)"
253
  }
254
 
 
 
 
 
255
  def test_llm_connection(self) -> Dict[str, Any]:
256
  """Test LLM connection with detailed status"""
257
+ if not self.api_key:
258
  return {
259
+ "status": "❌ No API Key",
260
+ "details": "Add OPENAI_API_KEY to your Hugging Face Space secrets",
261
+ "recommendation": "Go to Settings β†’ Variables & Secrets β†’ Add OPENAI_API_KEY"
262
  }
263
 
264
+ response, status = safe_openai_chat(
265
+ self.api_key,
266
+ [{"role": "user", "content": "Test connection"}],
267
+ max_tokens=10
268
+ )
269
+
270
+ if response:
271
  return {
272
  "status": "βœ… Connected",
273
  "details": "OpenAI API responding normally",
274
+ "method": "Direct API calls (universal compatibility)"
275
  }
276
+ else:
277
  return {
278
+ "status": "⚠️ Connection Failed",
279
+ "details": status,
280
+ "recommendation": "Check API key validity or try regenerating it"
281
  }
282
 
283
  def generate_executive_summary(self) -> str:
284
+ """Generate executive summary with AI or fallback"""
285
 
286
  if not self.llm_available:
287
+ return self._generate_rule_based_summary()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
+ # Prepare data for AI analysis
290
  data_summary = {
291
  "total_spend": float(self.po_data['order_value'].sum()),
292
  "total_orders": len(self.po_data),
293
+ "vendor_count": len(self.po_data['vendor'].unique()),
294
  "avg_order_value": float(self.po_data['order_value'].mean()),
295
+ "on_time_delivery": float(self.po_data['on_time_delivery'].mean()),
296
+ "avg_quality": float(self.po_data['quality_score'].mean())
297
  }
298
 
299
+ messages = [
300
+ {
301
+ "role": "system",
302
+ "content": "You are a senior procurement analyst with expertise in SAP S/4HANA systems. Provide professional, actionable insights."
303
+ },
304
+ {
305
+ "role": "user",
306
+ "content": f"""Analyze this procurement data and provide an executive summary:
307
+
308
+ {json.dumps(data_summary, indent=2)}
309
+
310
+ Include:
311
+ 1. Executive overview (2-3 sentences)
312
+ 2. Key performance highlights with metrics
313
+ 3. Areas needing attention
314
+ 4. Strategic recommendations (3-4 actionable items)
315
+
316
+ Keep it professional and actionable for C-level executives."""
317
+ }
318
+ ]
319
 
320
+ ai_response, status = safe_openai_chat(self.api_key, messages, max_tokens=600)
 
321
 
322
+ if ai_response:
 
 
 
 
 
 
 
 
 
 
 
323
  return f"🧠 **[AI-Powered Analysis - OpenAI GPT]**\n\n{ai_response}"
324
+ else:
325
+ fallback = self._generate_rule_based_summary()
326
+ return f"⚠️ **[AI Temporarily Unavailable]**\n\n{fallback}\n\n*Error: {status}*"
327
+
328
+ def _generate_rule_based_summary(self) -> str:
329
+ """Enhanced rule-based summary"""
330
+ total_spend = self.po_data['order_value'].sum()
331
+ total_orders = len(self.po_data)
332
+ on_time_rate = self.po_data['on_time_delivery'].mean() * 100
333
+ quality_avg = self.po_data['quality_score'].mean()
334
+ top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
335
+ top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
336
+
337
+ return f"""πŸ€– **[Smart Analysis - Rule-Based Engine]**
338
+
339
+ **🎯 Executive Summary - Procurement Performance Dashboard**
340
+
341
+ πŸ“Š **Portfolio Overview**
342
+ β€’ Total spend: €{total_spend:,.0f} across {total_orders:,} purchase orders
343
+ β€’ Active suppliers: {len(self.po_data['vendor'].unique())} strategic partners
344
+ β€’ Average order value: €{self.po_data['order_value'].mean():,.0f}
345
+
346
+ πŸ† **Performance Metrics**
347
+ β€’ On-time delivery: {on_time_rate:.1f}% (Industry benchmark: 85%)
348
+ β€’ Quality score: {quality_avg:.1f}/10 (Excellent: >8.5)
349
+ β€’ Top category: {top_category}
350
+ β€’ Leading partner: {top_vendor}
351
+
352
+ ⚑ **Strategic Opportunities**
353
+ β€’ Vendor consolidation from {len(self.po_data['vendor'].unique())} to 6-7 strategic partners
354
+ β€’ Contract optimization with high-performing suppliers
355
+ β€’ Process automation for routine purchases
356
+
357
+ πŸ’‘ **Recommended Actions**
358
+ β€’ Implement strategic sourcing for {top_category}
359
+ β€’ Develop KPI-driven vendor agreements
360
+ β€’ Deploy automated approval workflows
361
+
362
+ *πŸ”§ Advanced AI analysis available with OpenAI connection*"""
363
 
364
  def chat_with_data(self, user_question: str) -> str:
365
+ """Chat interface with universal compatibility"""
366
 
367
  if not self.llm_available:
368
  return self._get_rule_based_response(user_question)
369
 
370
+ # Prepare context for AI
371
+ context = {
372
+ "total_spend": float(self.po_data['order_value'].sum()),
373
+ "order_count": len(self.po_data),
374
+ "vendor_count": len(self.po_data['vendor'].unique()),
375
+ "avg_quality": float(self.po_data['quality_score'].mean()),
376
+ "on_time_rate": float(self.po_data['on_time_delivery'].mean())
377
+ }
378
+
379
+ messages = [
380
+ {
381
+ "role": "system",
382
+ "content": "You are an expert procurement analyst. Answer questions about procurement data professionally with specific metrics when relevant."
383
+ },
384
+ {
385
+ "role": "user",
386
+ "content": f"User Question: {user_question}\n\nProcurement Data Context: {json.dumps(context, indent=2)}\n\nProvide a helpful, professional response."
387
  }
388
+ ]
389
+
390
+ ai_response, status = safe_openai_chat(self.api_key, messages, max_tokens=400)
391
+
392
+ if ai_response:
393
+ return f"🧠 **[AI Response - OpenAI GPT]**\n\n{ai_response}"
394
+ else:
395
+ fallback = self._get_rule_based_response(user_question)
396
+ return f"⚠️ **[Smart Fallback]** AI temporarily unavailable\n\n{fallback}\n\n*Error: {status}*"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
 
398
  def _get_rule_based_response(self, question: str) -> str:
399
+ """Enhanced rule-based responses"""
400
  question_lower = question.lower()
401
 
402
  if any(word in question_lower for word in ["spend", "cost", "money", "budget"]):
 
404
  top_category = self.po_data.groupby('material_category')['order_value'].sum().idxmax()
405
  monthly_avg = total_spend / 24
406
 
407
+ return f"""πŸ€– **[Smart Analysis Engine]**
408
 
409
+ πŸ’° **Spend Analysis**
410
+ β€’ **Total spend**: €{total_spend:,.0f}
411
  β€’ **Monthly average**: €{monthly_avg:,.0f}
412
+ β€’ **Top category**: {top_category}
413
+ β€’ **Order average**: €{self.po_data['order_value'].mean():,.0f}
414
 
415
+ **Distribution**: Spend spans {len(self.po_data['material_category'].unique())} categories with {top_category} leading investment.
416
 
417
+ *πŸš€ Connect OpenAI for advanced spend optimization strategies*"""
418
 
419
  elif any(word in question_lower for word in ["vendor", "supplier", "partner"]):
420
  top_vendor = self.po_data.groupby('vendor')['order_value'].sum().idxmax()
421
  vendor_count = len(self.po_data['vendor'].unique())
422
+ performance = self.po_data[self.po_data['vendor'] == top_vendor]['on_time_delivery'].mean() * 100
423
 
424
+ return f"""πŸ€– **[Smart Analysis Engine]**
425
+
426
+ 🀝 **Vendor Portfolio**
427
+ β€’ **Active suppliers**: {vendor_count}
428
+ β€’ **Top partner**: {top_vendor}
429
+ β€’ **Performance**: {performance:.1f}% on-time delivery
430
+ β€’ **Portfolio health**: Well-diversified supply base
431
 
432
+ **Strategic insight**: {top_vendor} represents your strongest partnership with excellent delivery performance.
 
 
 
 
433
 
434
+ *πŸš€ Connect OpenAI for detailed vendor relationship strategies*"""
435
 
436
  else:
437
+ return f"""πŸ€– **[Smart Analysis Engine]**
438
 
439
+ **Available Analysis:**
440
+ β€’ πŸ’° **Spending insights**: "What are my biggest costs?"
441
  β€’ 🀝 **Vendor performance**: "How are my suppliers doing?"
442
+ β€’ ⚠️ **Risk assessment**: "What risks should I monitor?"
443
+ β€’ πŸ“ˆ **Trend analysis**: "Show me spending patterns"
444
 
445
+ **Current scope**: {len(self.po_data):,} orders β€’ {len(self.po_data['vendor'].unique())} vendors β€’ €{self.po_data['order_value'].sum():,.0f} total spend
446
 
447
+ *πŸš€ Connect OpenAI for natural language conversations and advanced insights*"""
448
 
449
  def analyze_spend_patterns(self) -> Dict[str, Any]:
450
  """Analyze spending patterns"""
 
471
  st.session_state.po_df, st.session_state.spend_df = generate_synthetic_procurement_data()
472
  st.session_state.data_loaded = True
473
 
474
+ # Initialize AI agent
475
+ analytics_agent = UniversalProcurementAgent(st.session_state.po_df, st.session_state.spend_df)
 
 
 
476
 
477
+ # Get status
 
 
478
  status_info = analytics_agent.get_status_info()
479
  api_key_status = "🟒 Connected" if status_info['llm_available'] else "πŸ”΄ Not Connected"
480
 
 
487
  </div>
488
  """, unsafe_allow_html=True)
489
 
490
+ # Sidebar with status and navigation
491
  with st.sidebar:
492
  st.markdown("### πŸ€– AI System Status")
493
  st.markdown(f"**Connection:** {api_key_status}")
494
+ st.markdown(f"**Method:** {status_info.get('method', 'N/A')}")
495
 
496
+ # Enhanced debug info
497
+ with st.expander("πŸ” System Information"):
498
  st.json(status_info)
499
 
500
+ # Connection test
501
  if st.button("πŸ”„ Test AI Connection"):
502
  test_result = analytics_agent.test_llm_connection()
503
  st.markdown(f"**Status:** {test_result['status']}")
 
509
  st.markdown("""
510
  <div class="alert alert-info">
511
  <small><strong>πŸ’‘ Enable AI Features</strong><br>
512
+ Add your OpenAI API key as OPENAI_API_KEY in Hugging Face Space secrets for advanced AI capabilities!</small>
513
  </div>
514
  """, unsafe_allow_html=True)
515
 
 
529
  }
530
  )
531
 
532
+ # Main content sections
533
  if selected == "🏠 Dashboard":
534
  st.markdown("### 🧠 AI Executive Summary")
535
 
 
549
  col1, col2, col3, col4 = st.columns(4)
550
 
551
  with col1:
552
+ st.markdown(f"""
553
  <div class="metric-card">
554
  <h3 style="color: var(--primary-color); margin: 0;">Total Spend</h3>
555
+ <h2 style="margin: 0.5rem 0;">€{insights['total_spend']:,.0f}</h2>
556
  <p style="color: #28a745; margin: 0;">πŸ“ˆ Active Portfolio</p>
557
  </div>
558
+ """, unsafe_allow_html=True)
559
 
560
  with col2:
561
+ st.markdown(f"""
562
  <div class="metric-card">
563
  <h3 style="color: var(--primary-color); margin: 0;">Avg Order Value</h3>
564
+ <h2 style="margin: 0.5rem 0;">€{insights['avg_order_value']:,.0f}</h2>
565
  <p style="color: #17a2b8; margin: 0;">πŸ“Š Order Efficiency</p>
566
  </div>
567
+ """, unsafe_allow_html=True)
568
 
569
  with col3:
570
  active_vendors = len(st.session_state.po_df['vendor'].unique())
571
+ st.markdown(f"""
572
  <div class="metric-card">
573
  <h3 style="color: var(--primary-color); margin: 0;">Active Vendors</h3>
574
+ <h2 style="margin: 0.5rem 0;">{active_vendors}</h2>
575
  <p style="color: #6f42c1; margin: 0;">🀝 Strategic Partners</p>
576
  </div>
577
+ """, unsafe_allow_html=True)
578
 
579
  with col4:
580
  on_time_delivery = st.session_state.po_df['on_time_delivery'].mean() * 100
581
+ st.markdown(f"""
582
  <div class="metric-card">
583
  <h3 style="color: var(--primary-color); margin: 0;">On-Time Delivery</h3>
584
+ <h2 style="margin: 0.5rem 0;">{on_time_delivery:.1f}%</h2>
585
  <p style="color: #28a745; margin: 0;">⏰ Performance</p>
586
  </div>
587
+ """, unsafe_allow_html=True)
588
 
589
  # Charts
590
  st.markdown("### πŸ“Š Executive Dashboard")
 
597
  category_spend,
598
  values='order_value',
599
  names='material_category',
600
+ title='Spend Distribution by Category'
 
601
  )
602
  fig_pie.update_layout(title_font_size=16, title_x=0.5, height=400)
603
  st.plotly_chart(fig_pie, use_container_width=True)
 
610
  vendor_spend,
611
  x='vendor',
612
  y='order_value',
613
+ title='Top Vendors by Spend'
 
 
614
  )
615
  fig_bar.update_layout(title_font_size=16, title_x=0.5, xaxis_tickangle=45, height=400)
616
  st.plotly_chart(fig_bar, use_container_width=True)
 
620
 
621
  st.markdown(f"""
622
  <div class="ai-insight">
623
+ <h4>πŸ€– Universal AI Assistant</h4>
624
+ <p>Ask me anything about your procurement data! I use direct API calls for maximum compatibility.</p>
625
+ <p><small>Status: {api_key_status} | Method: Universal Compatibility</small></p>
626
  </div>
627
  """, unsafe_allow_html=True)
628
 
629
  # Chat interface
630
  if "messages" not in st.session_state:
631
  st.session_state.messages = [
632
+ {"role": "assistant", "content": "Hello! I'm your universal procurement analyst. I've loaded your data and I'm ready to help with any questions!"}
633
  ]
634
 
635
  # Display chat messages
 
686
  st.markdown("### πŸš€ Strategic Recommendations")
687
 
688
  recommendations = [
689
+ "🎯 **Vendor Consolidation**: Reduce supplier base for 12-18% cost reduction",
690
+ "⚑ **Process Automation**: Implement automated approval workflows",
691
+ "πŸ“Š **Performance Contracts**: Establish KPI-driven vendor agreements",
692
+ "πŸ›‘οΈ **Risk Monitoring**: Deploy real-time supplier assessment tools",
693
  "πŸš€ **Digital Platform**: Upgrade to AI-powered procurement system"
694
  ]
695
 
 
705
  st.markdown("---")
706
  st.markdown(f"""
707
  <div style="text-align: center; padding: 1rem; color: #666;">
708
+ <p>πŸ€– <strong>Universal AI Procurement Analytics</strong> | Built with Direct API Integration</p>
709
  <p><em>Demo with synthetic data β€’ {len(st.session_state.po_df):,} orders β€’ OpenAI {api_key_status}</em></p>
710
  </div>
711
  """, unsafe_allow_html=True)