archivartaunik commited on
Commit
b9afb16
Β·
verified Β·
1 Parent(s): ef83ec8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -45
app.py CHANGED
@@ -1,24 +1,9 @@
1
- # HF Spaces / Gradio app: Vochi CRM call logs + AI analysis
2
- # ─────────────────────────────────────────────────────────────────────────────
3
- # How to deploy (short):
4
- # 1) Create a new Space (Python + Gradio).
5
- # 2) Add a file named `app.py` with THIS code.
6
- # 3) Add a file named `requirements.txt` with the lines below.
7
- # 4) In the Space β†’ Settings β†’ Repository secrets, add:
8
- # - VOCHI_BASE_URL (e.g. https://crm.vochi.by/api)
9
- # - VOCHI_CLIENT_ID (client id string)
10
- # - GOOGLE_API_KEY (API key)
11
- # - VOCHI_UI_PASSWORD (password to unlock the UI)
12
-
13
- #
14
- # UI language: English.
15
-
16
  from __future__ import annotations
17
  import os
18
  import json
19
  import datetime as _dt
20
  from typing import List, Tuple, Optional
21
- import time # ← ДАДАНА
22
 
23
  import requests
24
  import pandas as pd
@@ -181,12 +166,85 @@ def _system_instruction(lang_code: str) -> str:
181
  return "Reply in English."
182
  return "Reply in the caller's language; if unclear, use concise professional English."
183
 
184
- # ← ДАДАНА: нСвялікі Ρ…Π΅Π»ΠΏΠ΅Ρ€ для Π²Ρ‹Π±Π°Ρ€Ρƒ ΠΏΡ€Π°ΠΌΠΏΡ‚Ρƒ
185
  def _prepare_prompt(template_key: str, custom_prompt: str) -> str:
186
  if template_key == "custom":
187
  return (custom_prompt or "").strip() or PROMPT_TEMPLATES["simple"]
188
  return PROMPT_TEMPLATES.get(template_key, PROMPT_TEMPLATES["simple"])
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  # ─────────────────────────────────────────────────────────────────────────────
191
  # Gradio handlers
192
  # ─────────────────────────────────────────────────────────────────────────────
@@ -290,9 +348,7 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
290
 
291
  # Call model
292
  try:
293
- merged = f"""[SYSTEM INSTRUCTION: {sys_inst}]
294
-
295
- {prompt}"""
296
  resp = client.models.generate_content(model=model_name, contents=[uploaded_file, merged])
297
  text = getattr(resp, "text", None)
298
  if not text:
@@ -315,21 +371,27 @@ def ui_analyze(selected_idx: Optional[int], df: pd.DataFrame,
315
  except Exception:
316
  pass
317
 
318
- # ← ДАДАНА: ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹ Π°Π½Π°Π»Ρ–Π· званкоў Π·Π° дзСнь/час
 
 
 
319
  def ui_analyze_calls_by_date(
320
  authed: bool,
321
  date_str: str,
 
 
 
322
  template_key: str,
323
  custom_prompt: str,
324
  lang_code: str,
325
  model_pref: str,
326
  ):
327
  """
328
- Fetch calls for the given date and analyze them one-by-one with the same
329
- settings/fields as the single-call AI Analysis tab. Save a .txt file and
330
- show results on screen.
331
  """
332
- # Π“Π΅ΠΉΡ‚ ΠΏΠ° ΠΏΠ°Ρ€ΠΎΠ»Ρ–
333
  if not authed:
334
  return (
335
  "πŸ”’ УвядзіцС ΠΏΠ°Ρ€ΠΎΠ»ΡŒ, ΠΊΠ°Π± Π·Π°ΠΏΡƒΡΡ†Ρ–Ρ†ΡŒ Π°Π½Π°Π»Ρ–Π·.", # results_md
@@ -338,11 +400,12 @@ def ui_analyze_calls_by_date(
338
  gr.update(visible=True), # show pwd group
339
  )
340
 
341
- date_str = (date_str or "").strip()
342
- if not date_str:
343
- date_str = _today_str()
 
344
 
345
- # 1) ΠΡ‚Ρ€Ρ‹ΠΌΠ°Ρ†ΡŒ спіс званкоў Π·Π° Π΄Π°Ρ‚Ρƒ
346
  try:
347
  calls = fetch_calllogs(date_str)
348
  df = pd.DataFrame(calls)
@@ -352,7 +415,15 @@ def ui_analyze_calls_by_date(
352
  if df.empty:
353
  return ("Π—Π° гэты дзСнь званкоў Π½Π΅ Π·Π½ΠΎΠΉΠ΄Π·Π΅Π½Π°.", None, "", gr.update())
354
 
355
- # 2) ΠŸΠ°Π΄Ρ€Ρ‹Ρ…Ρ‚ΠΎΡžΠΊΠ° ΠΊΠ»Ρ–Π΅Π½Ρ‚Π° мадэлі Ρ– ΠΏΡ€Π°ΠΌΠΏΡ‚Ρƒ
 
 
 
 
 
 
 
 
356
  if not _HAS_GENAI:
357
  return ("❌ google-genai library not found.", None, "", gr.update())
358
 
@@ -369,16 +440,18 @@ def ui_analyze_calls_by_date(
369
  sys_inst = _system_instruction(lang_code)
370
  prompt = _prepare_prompt(template_key, custom_prompt)
371
 
372
- # 3) ΠŸΠ°ΠΊΠ΅Ρ‚Π½Ρ‹ ΠΏΡ€Π°Ρ…ΠΎΠ΄: ΠΏΠ° Π°Π΄Π½Ρ‹ΠΌ Π²Ρ‹ΠΊΠ»Ρ–ΠΊΡƒ
373
- results_blocks: List[str] = []
 
 
374
  analyzed = 0
375
 
376
- for i, row in df.iterrows():
377
  unique_id = str(row.get("UniqueId", ""))
378
  if not unique_id:
379
  continue
380
 
381
- # Π‘ΠΊΠ°Ρ‡Π²Π°Π΅ΠΌ/ΠΊΡΡˆΡ‹Π½Π³ΡƒΠ΅ΠΌ mp3
382
  try:
383
  mp3_path = f"/tmp/call_{unique_id}.mp3"
384
  if not os.path.exists(mp3_path) or os.path.getsize(mp3_path) == 0:
@@ -414,7 +487,7 @@ def ui_analyze_calls_by_date(
414
  except Exception:
415
  pass
416
 
417
- # Π‘Ρ„Π°Ρ€ΠΌΠ°Π²Π°Ρ†ΡŒ Π±Π»ΠΎΠΊ Π²Ρ‹Π½Ρ–ΠΊΡƒ для гэтага Π·Π²Π°Π½ΠΊΠ°
418
  block = (
419
  f"### Call {i+1}\n"
420
  f"- UniqueId: {unique_id}\n"
@@ -429,15 +502,16 @@ def ui_analyze_calls_by_date(
429
  analyzed += 1
430
 
431
  if analyzed == 0:
432
- return ("НС атрымалася ΠΏΡ€Π°Π°Π½Π°Π»Ρ–Π·Π°Π²Π°Ρ†ΡŒ Π½Ρ–Π²ΠΎΠ΄Π½Π°Π³Π° Π·Π²Π°Π½ΠΊΠ°.", None, "", gr.update())
 
433
 
434
- # 4) Π—Π°Ρ…Π°Π²Π°Ρ†ΡŒ Ρƒ .txt Ρ– Π²ΡΡ€Π½ΡƒΡ†ΡŒ
435
  results_text = "\n\n".join(results_blocks)
436
- fname = f"/tmp/batch_analysis_{date_str}_{int(time.time())}.txt"
437
  with open(fname, "w", encoding="utf-8") as f:
438
  f.write(results_text)
439
 
440
- status = f"Π“Π°Ρ‚ΠΎΠ²Π° βœ…. ΠŸΡ€Π°Π°Π½Π°Π»Ρ–Π·Π°Π²Π°Π½Π° званкоў: {analyzed}."
441
  return (results_text, fname, status, gr.update(visible=False))
442
 
443
  # ───────────────────────────────────────────────────────��─────────────────────
@@ -478,9 +552,6 @@ def ui_fetch_or_auth(date_str: str, authed: bool):
478
  # Build Gradio UI
479
  # ─────────────────────────────────────────────────────────────────────────────
480
 
481
- def _today_str():
482
- return _dt.date.today().strftime("%Y-%m-%d")
483
-
484
  with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
485
  gr.Markdown(
486
  """
@@ -522,10 +593,15 @@ with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
522
  analyze_btn = gr.Button("🧠 Analyze", variant="primary")
523
  analysis_md = gr.Markdown()
524
 
525
- # ← ДАДАНА: новая ўкладка ΠΏΠ°ΠΊΠ΅Ρ‚Π½Π°Π³Π° Π°Π½Π°Π»Ρ–Π·Ρƒ
526
  with gr.Tab("Аналіз званкоў Π·Π° дзСнь/час"):
527
  batch_date_inp = gr.Textbox(label="Date", value=_today_str(), scale=1)
528
 
 
 
 
 
 
529
  with gr.Row():
530
  tpl_batch_dd = gr.Dropdown(choices=TPL_OPTIONS, value="simple", label="Template")
531
  lang_batch_dd = gr.Dropdown(choices=LANG_OPTIONS, value="default", label="Language")
@@ -563,11 +639,11 @@ with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
563
  outputs=[analysis_md],
564
  )
565
 
566
- # ← ДАДАНА: ΠΏΠ°Π΄Π·Π΅Ρ– для Π½ΠΎΠ²Π°ΠΉ ΡƒΠΊΠ»Π°Π΄ΠΊΡ–
567
  tpl_batch_dd.change(ui_toggle_custom_prompt, inputs=[tpl_batch_dd], outputs=[custom_batch_tb])
568
  batch_btn.click(
569
  ui_analyze_calls_by_date,
570
- inputs=[authed, batch_date_inp, tpl_batch_dd, custom_batch_tb, lang_batch_dd, model_batch_dd],
571
  outputs=[batch_results_md, batch_file_out, batch_status_md, pwd_group],
572
  )
573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
  import os
3
  import json
4
  import datetime as _dt
5
  from typing import List, Tuple, Optional
6
+ import time
7
 
8
  import requests
9
  import pandas as pd
 
166
  return "Reply in English."
167
  return "Reply in the caller's language; if unclear, use concise professional English."
168
 
169
+
170
  def _prepare_prompt(template_key: str, custom_prompt: str) -> str:
171
  if template_key == "custom":
172
  return (custom_prompt or "").strip() or PROMPT_TEMPLATES["simple"]
173
  return PROMPT_TEMPLATES.get(template_key, PROMPT_TEMPLATES["simple"])
174
 
175
+
176
+ def _today_str():
177
+ return _dt.date.today().strftime("%Y-%m-%d")
178
+
179
+ # Time helpers (HH:MM β†’ seconds since midnight)
180
+ _DEF_T_FROM = "00:00"
181
+ _DEF_T_TO = "23:59"
182
+
183
+
184
+ def _parse_hhmm(s: Optional[str]) -> Optional[int]:
185
+ s = (s or "").strip()
186
+ if not s:
187
+ return None
188
+ try:
189
+ hh, mm = s.split(":", 1)
190
+ hh = int(hh)
191
+ mm = int(mm)
192
+ if not (0 <= hh <= 23 and 0 <= mm <= 59):
193
+ return None
194
+ return hh * 3600 + mm * 60
195
+ except Exception:
196
+ return None
197
+
198
+
199
+ def _infer_calltype_col(df: pd.DataFrame) -> Optional[str]:
200
+ for c in df.columns:
201
+ low = str(c).lower()
202
+ if low in {"calltype", "call_type", "type"}:
203
+ return c
204
+ return None
205
+
206
+
207
+ def _normalize_start_to_time(df: pd.DataFrame) -> pd.DataFrame:
208
+ if "Start" not in df.columns:
209
+ return df
210
+ out = df.copy()
211
+ try:
212
+ out["_Start_dt"] = pd.to_datetime(out["Start"], errors="coerce")
213
+ out["_Start_time_sec"] = out["_Start_dt"].dt.hour.fillna(0).astype(int) * 3600 + \
214
+ out["_Start_dt"].dt.minute.fillna(0).astype(int) * 60 + \
215
+ out["_Start_dt"].dt.second.fillna(0).astype(int)
216
+ except Exception:
217
+ out["_Start_time_sec"] = np.nan
218
+ return out
219
+
220
+
221
+ def _filter_calls(df: pd.DataFrame, t_from: Optional[str], t_to: Optional[str], calltype: Optional[str]) -> pd.DataFrame:
222
+ if df is None or df.empty:
223
+ return df
224
+
225
+ tf = _parse_hhmm(t_from) or _parse_hhmm(_DEF_T_FROM) or 0
226
+ tt = _parse_hhmm(t_to) or _parse_hhmm(_DEF_T_TO) or (24*3600-1)
227
+ if tt < tf:
228
+ # Swap if user inverted
229
+ tf, tt = tt, tf
230
+
231
+ work = _normalize_start_to_time(df)
232
+ mask_time = (work["_Start_time_sec"].fillna(-1).astype(int) >= tf) & (work["_Start_time_sec"].fillna(-1).astype(int) <= tt)
233
+
234
+ mask = mask_time
235
+
236
+ ct = (calltype or "").strip()
237
+ if ct:
238
+ col = _infer_calltype_col(work)
239
+ if col:
240
+ # exact match (case-insensitive)
241
+ mask = mask & (work[col].astype(str).str.lower() == ct.lower())
242
+ else:
243
+ # if there is no calltype column, keep only time filter
244
+ pass
245
+
246
+ return work.loc[mask].drop(columns=[c for c in ["_Start_dt", "_Start_time_sec"] if c in work.columns])
247
+
248
  # ─────────────────────────────────────────────────────────────────────────────
249
  # Gradio handlers
250
  # ─────────────────────────────────────────────────────────────────────────────
 
348
 
349
  # Call model
350
  try:
351
+ merged = f"""[SYSTEM INSTRUCTION: {sys_inst}]\n\n{prompt}"""
 
 
352
  resp = client.models.generate_content(model=model_name, contents=[uploaded_file, merged])
353
  text = getattr(resp, "text", None)
354
  if not text:
 
371
  except Exception:
372
  pass
373
 
374
+ # ────────────────────────────────────��────────────────────────────────────────
375
+ # NEW: Batch analysis with time interval + calltype filter
376
+ # ─────────────────────────────────────────────────────────────────────────────
377
+
378
  def ui_analyze_calls_by_date(
379
  authed: bool,
380
  date_str: str,
381
+ t_from: str,
382
+ t_to: str,
383
+ calltype: str,
384
  template_key: str,
385
  custom_prompt: str,
386
  lang_code: str,
387
  model_pref: str,
388
  ):
389
  """
390
+ Fetch calls for the given date, filter them by time window and calltype,
391
+ and analyze one-by-one with the same settings as the single-call tab.
392
+ Save a .txt file and show results on screen. Also show how many calls matched the filter.
393
  """
394
+ # Password gate
395
  if not authed:
396
  return (
397
  "πŸ”’ УвядзіцС ΠΏΠ°Ρ€ΠΎΠ»ΡŒ, ΠΊΠ°Π± Π·Π°ΠΏΡƒΡΡ†Ρ–Ρ†ΡŒ Π°Π½Π°Π»Ρ–Π·.", # results_md
 
400
  gr.update(visible=True), # show pwd group
401
  )
402
 
403
+ date_str = (date_str or "").strip() or _today_str()
404
+ t_from = (t_from or _DEF_T_FROM).strip()
405
+ t_to = (t_to or _DEF_T_TO).strip()
406
+ calltype = (calltype or "").strip()
407
 
408
+ # 1) Fetch list for the date
409
  try:
410
  calls = fetch_calllogs(date_str)
411
  df = pd.DataFrame(calls)
 
415
  if df.empty:
416
  return ("Π—Π° гэты дзСнь званкоў Π½Π΅ Π·Π½ΠΎΠΉΠ΄Π·Π΅Π½Π°.", None, "", gr.update())
417
 
418
+ # 2) Filter by time + calltype
419
+ df_f = _filter_calls(df, t_from, t_to, calltype)
420
+ total = len(df_f)
421
+
422
+ if total == 0:
423
+ info = f"Па Ρ„Ρ–Π»ΡŒΡ‚Ρ€Ρƒ ({t_from}–{t_to}{' | ' + calltype if calltype else ''}) β€” 0 званкоў."
424
+ return (info, None, info, gr.update())
425
+
426
+ # 3) Prepare model
427
  if not _HAS_GENAI:
428
  return ("❌ google-genai library not found.", None, "", gr.update())
429
 
 
440
  sys_inst = _system_instruction(lang_code)
441
  prompt = _prepare_prompt(template_key, custom_prompt)
442
 
443
+ # 4) Iterate filtered calls
444
+ results_blocks: List[str] = [
445
+ f"## Π’Ρ‹Π½Ρ–ΠΊΡ–\nΠ”Π°Ρ‚Π°: {date_str}\n\nΠ€Ρ–Π»ΡŒΡ‚Ρ€: {t_from}–{t_to}{' | calltype=' + calltype if calltype else ''}\n\nΠ—Π½ΠΎΠΉΠ΄Π·Π΅Π½Π° званкоў: {total}\n\n---"
446
+ ]
447
  analyzed = 0
448
 
449
+ for i, row in df_f.reset_index(drop=True).iterrows():
450
  unique_id = str(row.get("UniqueId", ""))
451
  if not unique_id:
452
  continue
453
 
454
+ # Download/cache mp3
455
  try:
456
  mp3_path = f"/tmp/call_{unique_id}.mp3"
457
  if not os.path.exists(mp3_path) or os.path.getsize(mp3_path) == 0:
 
487
  except Exception:
488
  pass
489
 
490
+ # Result block
491
  block = (
492
  f"### Call {i+1}\n"
493
  f"- UniqueId: {unique_id}\n"
 
502
  analyzed += 1
503
 
504
  if analyzed == 0:
505
+ info = "НС атрымалася ΠΏΡ€Π°Π°Π½Π°Π»Ρ–Π·Π°Π²Π°Ρ†ΡŒ Π½Ρ–Π²ΠΎΠ΄Π½Π°Π³Π° Π·Π²Π°Π½ΠΊΠ°."
506
+ return (info, None, info, gr.update())
507
 
508
+ # 5) Save to .txt and return
509
  results_text = "\n\n".join(results_blocks)
510
+ fname = f"/tmp/batch_analysis_{date_str}_{t_from.replace(':','')}-{t_to.replace(':','')}_{int(time.time())}.txt"
511
  with open(fname, "w", encoding="utf-8") as f:
512
  f.write(results_text)
513
 
514
+ status = f"Π“Π°Ρ‚ΠΎΠ²Π° βœ…. Па Ρ„Ρ–Π»ΡŒΡ‚Ρ€Ρƒ β€” {total} званкоў. ΠŸΡ€Π°Π°Π½Π°Π»Ρ–Π·Π°Π²Π°Π½Π°: {analyzed}."
515
  return (results_text, fname, status, gr.update(visible=False))
516
 
517
  # ───────────────────────────────────────────────────────��─────────────────────
 
552
  # Build Gradio UI
553
  # ─────────────────────────────────────────────────────────────────────────────
554
 
 
 
 
555
  with gr.Blocks(title="Vochi CRM Call Logs (Gradio)") as demo:
556
  gr.Markdown(
557
  """
 
593
  analyze_btn = gr.Button("🧠 Analyze", variant="primary")
594
  analysis_md = gr.Markdown()
595
 
596
+ # NEW: ΠΏΠ°ΠΊΠ΅Ρ‚Π½Ρ‹ Π°Π½Π°Π»Ρ–Π· Π· Ρ„Ρ–Π»ΡŒΡ‚Ρ€Π°ΠΌΡ– ΠΏΠ° часС Ρ– calltype
597
  with gr.Tab("Аналіз званкоў Π·Π° дзСнь/час"):
598
  batch_date_inp = gr.Textbox(label="Date", value=_today_str(), scale=1)
599
 
600
+ with gr.Row():
601
+ t_from_inp = gr.Textbox(label="Time from (HH:MM)", value=_DEF_T_FROM)
602
+ t_to_inp = gr.Textbox(label="Time to (HH:MM)", value=_DEF_T_TO)
603
+ calltype_inp = gr.Textbox(label="calltype (exact)", placeholder="e.g. inbound / outbound")
604
+
605
  with gr.Row():
606
  tpl_batch_dd = gr.Dropdown(choices=TPL_OPTIONS, value="simple", label="Template")
607
  lang_batch_dd = gr.Dropdown(choices=LANG_OPTIONS, value="default", label="Language")
 
639
  outputs=[analysis_md],
640
  )
641
 
642
+ # NEW: events for the new batch tab with filters
643
  tpl_batch_dd.change(ui_toggle_custom_prompt, inputs=[tpl_batch_dd], outputs=[custom_batch_tb])
644
  batch_btn.click(
645
  ui_analyze_calls_by_date,
646
+ inputs=[authed, batch_date_inp, t_from_inp, t_to_inp, calltype_inp, tpl_batch_dd, custom_batch_tb, lang_batch_dd, model_batch_dd],
647
  outputs=[batch_results_md, batch_file_out, batch_status_md, pwd_group],
648
  )
649