Dhom1 commited on
Commit
85a616e
·
verified ·
1 Parent(s): 2cbb20d

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +118 -27
src/streamlit_app.py CHANGED
@@ -17,7 +17,7 @@ os.environ["TRANSFORMERS_CACHE"] = "/tmp"
17
  openai.api_key = os.getenv("OPENAI_API_KEY")
18
 
19
  # إعداد صفحة Streamlit
20
- st.set_page_config(page_title="Unleash the AI Agent", layout="wide")
21
 
22
  # تحميل ملف CSS من نفس مجلد هذا الملف
23
  current_dir = pathlib.Path(__file__).resolve().parent
@@ -41,7 +41,7 @@ st.markdown(
41
  unsafe_allow_html=True,
42
  )
43
 
44
- # عرض شعار Health Matrix في الزاوية اليسرى العليا باستخدام الصورة المحلية
45
  logo_path = current_dir / "logo.jpg"
46
  if logo_path.exists():
47
  logo_url = str(logo_path.as_posix())
@@ -54,23 +54,48 @@ if logo_path.exists():
54
  unsafe_allow_html=True,
55
  )
56
 
57
- # بيانات الشفتات (يمكن استبدالها بقراءة من API في المستقبل)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  shift_data = """
59
  ShiftID,Department,RequiredSkill,RequiredCert,ShiftTime
60
  S101,ICU,ICU,ACLS,2025-06-04 07:00
61
  """
62
- df_shifts = pd.read_csv(StringIO(shift_data))
63
-
64
- # جلب بيانات الموظفين عبر الدالة من schedule.py
65
- employee_ids = [850] # يمكنك تغيير الأرقام حسب الحاجة
66
- df_employees = fetch_employees(employee_ids)
67
 
68
- # دالة التحقق من الأهلية بناءً على الـ JobRole فقط
69
  def is_eligible(row, shift):
 
70
  return row.get("JobRole", "").strip().lower() == shift["RequiredSkill"].strip().lower()
71
 
72
- # دالة GPT لتحديد القرار المناسب (تعيين، إشعار، تجاهل)
73
  def gpt_decide(shift, eligible_df):
 
 
 
 
 
 
 
 
 
74
  emp_list = (
75
  eligible_df[["fullName", "phoneNumber", "organizationPath"]].to_dict(orient="records")
76
  if not eligible_df.empty
@@ -104,40 +129,95 @@ def gpt_decide(shift, eligible_df):
104
  temperature=0.4,
105
  max_tokens=200,
106
  )
107
- # نتوقع أن يرجع ChatGPT JSON صالحاً، لذلك نستخدم literal_eval بشكل آمن
108
  import ast
109
-
110
  return ast.literal_eval(response["choices"][0]["message"]["content"])
111
  except Exception as e:
112
  st.error(f"❌ GPT Error: {e}")
113
  return {"action": "skip"}
114
 
115
- # زر واحد لتنفيذ الوكيل الذكي وعرض النتائج
116
- if st.button("🤖 Unleash the AI Agent"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  shift_assignment_results = []
118
  reasoning_rows = []
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  for _, shift in df_shifts.iterrows():
121
- # حدد الموظفين المؤهلين بناءً على JobRole
122
  eligible = df_employees[
123
  df_employees.apply(lambda r: is_eligible(r, shift), axis=1)
124
  ] if not df_employees.empty else pd.DataFrame()
125
-
126
- # استخدام GPT لاتخاذ القرار
 
127
  decision = gpt_decide(shift, eligible)
128
-
129
  if decision.get("action") == "assign":
130
  emp = decision.get("employee")
131
- st.success(f"✅ {emp} assigned to {shift['Department']} at {shift['ShiftTime']}")
 
 
 
 
132
  shift_assignment_results.append((emp, shift["ShiftID"], "✅ Auto-Filled"))
133
  elif decision.get("action") == "notify":
134
  emp = decision.get("employee")
135
- st.info(f"📬 Notify {emp} to take the shift")
 
 
 
 
136
  shift_assignment_results.append((emp, shift["ShiftID"], "📨 Notify"))
137
  else:
 
 
 
 
 
138
  shift_assignment_results.append(("❌ No eligible", shift["ShiftID"], "⚠️ Skipped"))
139
-
140
- # أسباب الأهلية/الرفض لكل موظف (مؤقتاً نعتبر الشهادات والتوفر وساعات الإضافي متاحة)
141
  for _, emp_row in df_employees.iterrows():
142
  role_match = shift["RequiredSkill"].strip().lower() == emp_row.get("JobRole", "").strip().lower()
143
  cert_ok = True
@@ -159,14 +239,25 @@ if st.button("🤖 Unleash the AI Agent"):
159
  ),
160
  }
161
  )
 
 
 
162
 
163
- # عرض ملخص توزيع المناوبات
 
 
 
 
164
  st.subheader("📊 Shift Fulfillment Summary")
165
- st.dataframe(pd.DataFrame(shift_assignment_results, columns=["Employee", "ShiftID", "Status"]))
166
-
167
- # عرض جدول لشرح أسباب الاختيار أو الرفض
 
168
  st.subheader("📋 Reasoning Behind Selections")
169
- st.dataframe(pd.DataFrame(reasoning_rows))
 
 
 
170
 
171
  # تذييل الصفحة
172
  st.markdown(
 
17
  openai.api_key = os.getenv("OPENAI_API_KEY")
18
 
19
  # إعداد صفحة Streamlit
20
+ st.set_page_config(page_title="Health Matrix AI Command Center", layout="wide")
21
 
22
  # تحميل ملف CSS من نفس مجلد هذا الملف
23
  current_dir = pathlib.Path(__file__).resolve().parent
 
41
  unsafe_allow_html=True,
42
  )
43
 
44
+ # عرض شعار Health Matrix في الزاوية العلوية اليسرى باستخدام الصورة المحلية
45
  logo_path = current_dir / "logo.jpg"
46
  if logo_path.exists():
47
  logo_url = str(logo_path.as_posix())
 
54
  unsafe_allow_html=True,
55
  )
56
 
57
+ # --------------------
58
+ # محتوى الصفحة الرئيسية (Landing Page)
59
+
60
+ # عنوان وتعريف قصير
61
+ st.markdown(
62
+ """
63
+ <div style="margin-top:5rem; text-align:center;">
64
+ <h1 style="font-size:3rem; font-weight:600; margin-bottom:0.5rem; color:#36ba01;">Welcome to the AI‑Powered Command Center</h1>
65
+ <h2 style="font-size:1.8rem; font-weight:500; margin-top:0; color:#004c97;">by Health Matrix</h2>
66
+ <p style="max-width:700px; margin:0 auto; font-size:1.1rem; line-height:1.5; color:#cad8e5;">
67
+ This smart assistant leverages AI to automate decisions, schedule actions, and provide real‑time updates – all while you relax.
68
+ </p>
69
+ </div>
70
+ """,
71
+ unsafe_allow_html=True,
72
+ )
73
+
74
+ # تحضير مكان لعرض الخط الزمني للمهام
75
+ timeline_container = st.container()
76
+
77
+ # تخصيص الأسماء الافتراضية للموظفين والشفتات
78
+ employee_ids_default = [850]
79
  shift_data = """
80
  ShiftID,Department,RequiredSkill,RequiredCert,ShiftTime
81
  S101,ICU,ICU,ACLS,2025-06-04 07:00
82
  """
83
+ df_shifts_default = pd.read_csv(StringIO(shift_data))
 
 
 
 
84
 
 
85
  def is_eligible(row, shift):
86
+ """Check if employee's job role matches shift requirement."""
87
  return row.get("JobRole", "").strip().lower() == shift["RequiredSkill"].strip().lower()
88
 
 
89
  def gpt_decide(shift, eligible_df):
90
+ """Call GPT model to decide assignment or notification.
91
+
92
+ The function returns a dict with keys action and employee, defaulting to skip if any error occurs
93
+ or the API key isn't configured.
94
+ """
95
+ # If API key is not set, skip decision automatically
96
+ if not openai.api_key:
97
+ return {"action": "skip"}
98
+ # Build prompt listing eligible employees
99
  emp_list = (
100
  eligible_df[["fullName", "phoneNumber", "organizationPath"]].to_dict(orient="records")
101
  if not eligible_df.empty
 
129
  temperature=0.4,
130
  max_tokens=200,
131
  )
 
132
  import ast
 
133
  return ast.literal_eval(response["choices"][0]["message"]["content"])
134
  except Exception as e:
135
  st.error(f"❌ GPT Error: {e}")
136
  return {"action": "skip"}
137
 
138
+ def render_timeline(events):
139
+ """Return HTML string rendering a list of timeline events."""
140
+ html = '<div class="timeline">'
141
+ for event in events:
142
+ icon = event.get("icon", "")
143
+ title = event.get("title", "")
144
+ desc = event.get("desc", "")
145
+ html += f"""
146
+ <div class="timeline-card">
147
+ <span class="icon">{icon}</span>
148
+ <div>
149
+ <h4>{title}</h4>
150
+ <p>{desc}</p>
151
+ </div>
152
+ </div>
153
+ """
154
+ html += "</div>"
155
+ return html
156
+
157
+ def run_agent(employee_ids, df_shifts):
158
+ """Execute the AI agent workflow and return events, summary, reasoning."""
159
+ events = []
160
  shift_assignment_results = []
161
  reasoning_rows = []
162
 
163
+ # Step 1: Fetch employees
164
+ events.append({"icon": "🔍", "title": "Fetching employee data", "desc": "Loading employee information from UKG API..."})
165
+ timeline_container.markdown(render_timeline(events), unsafe_allow_html=True)
166
+ # Attempt to fetch employee data
167
+ df_employees = fetch_employees(employee_ids)
168
+ if df_employees.empty:
169
+ events.append({
170
+ "icon": "⚠️",
171
+ "title": "No Employee Data",
172
+ "desc": "No employees were returned or credentials are invalid."
173
+ })
174
+ # even if no employees, proceed to next steps with empty DataFrame
175
+ else:
176
+ events.append({
177
+ "icon": "✅",
178
+ "title": "Employee Data Loaded",
179
+ "desc": f"{len(df_employees)} employee(s) loaded successfully."
180
+ })
181
+ timeline_container.markdown(render_timeline(events), unsafe_allow_html=True)
182
+
183
+ # Step 2: Evaluate each shift
184
+ events.append({"icon": "📋", "title": "Evaluating Shifts", "desc": "Matching employees to shift requirements..."})
185
+ timeline_container.markdown(render_timeline(events), unsafe_allow_html=True)
186
+ # Loop through shifts
187
  for _, shift in df_shifts.iterrows():
188
+ # Determine eligible employees based on JobRole
189
  eligible = df_employees[
190
  df_employees.apply(lambda r: is_eligible(r, shift), axis=1)
191
  ] if not df_employees.empty else pd.DataFrame()
192
+ # Step 3: Ask GPT or fallback
193
+ events.append({"icon": "🤖", "title": "Running AI Decision", "desc": "Determining best action for the shift..."})
194
+ timeline_container.markdown(render_timeline(events), unsafe_allow_html=True)
195
  decision = gpt_decide(shift, eligible)
 
196
  if decision.get("action") == "assign":
197
  emp = decision.get("employee")
198
+ events.append({
199
+ "icon": "✅",
200
+ "title": f"Assigned {emp}",
201
+ "desc": f"{emp} assigned to {shift['Department']} at {shift['ShiftTime']}"
202
+ })
203
  shift_assignment_results.append((emp, shift["ShiftID"], "✅ Auto-Filled"))
204
  elif decision.get("action") == "notify":
205
  emp = decision.get("employee")
206
+ events.append({
207
+ "icon": "📬",
208
+ "title": f"Notify {emp}",
209
+ "desc": f"Send notification to {emp} to take the shift"
210
+ })
211
  shift_assignment_results.append((emp, shift["ShiftID"], "📨 Notify"))
212
  else:
213
+ events.append({
214
+ "icon": "⚠️",
215
+ "title": "Skipped",
216
+ "desc": "No eligible employees available or decision skipped."
217
+ })
218
  shift_assignment_results.append(("❌ No eligible", shift["ShiftID"], "⚠️ Skipped"))
219
+ timeline_container.markdown(render_timeline(events), unsafe_allow_html=True)
220
+ # Add reasoning for each employee
221
  for _, emp_row in df_employees.iterrows():
222
  role_match = shift["RequiredSkill"].strip().lower() == emp_row.get("JobRole", "").strip().lower()
223
  cert_ok = True
 
239
  ),
240
  }
241
  )
242
+ events.append({"icon": "📊", "title": "Summary Ready", "desc": "The AI has finished processing the shifts."})
243
+ timeline_container.markdown(render_timeline(events), unsafe_allow_html=True)
244
+ return events, shift_assignment_results, reasoning_rows
245
 
246
+ # زر رئيسي لتشغيل الوكيل الذكي
247
+ if st.button("▶️ Start AI Agent", key="start_agent", help="Click to unleash the AI"):
248
+ # تنفيذ الوكيل وعرض النتائج على شكل خط زمني
249
+ events, shift_assignment_results, reasoning_rows = run_agent(employee_ids_default, df_shifts_default)
250
+ # عرض الملخص والشرح بعد الانتهاء
251
  st.subheader("📊 Shift Fulfillment Summary")
252
+ if shift_assignment_results:
253
+ st.dataframe(pd.DataFrame(shift_assignment_results, columns=["Employee", "ShiftID", "Status"]))
254
+ else:
255
+ st.write("No assignments to display.")
256
  st.subheader("📋 Reasoning Behind Selections")
257
+ if reasoning_rows:
258
+ st.dataframe(pd.DataFrame(reasoning_rows))
259
+ else:
260
+ st.write("No reasoning data available.")
261
 
262
  # تذييل الصفحة
263
  st.markdown(