Paul720810 commited on
Commit
f452661
·
verified ·
1 Parent(s): 4fde305

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +26 -48
app.py CHANGED
@@ -498,8 +498,11 @@ class TextToSQLSystem:
498
 
499
  def _validate_and_fix_sql(self, question: str, raw_response: str) -> Tuple[Optional[str], str]:
500
  """
501
- (V28 / 實驗組別名映射版)
502
- 增加了實驗組別名到資料庫真實值的映射,以處理 "A組" -> "TA" 這類轉換。
 
 
 
503
  """
504
  q_lower = question.lower()
505
 
@@ -507,21 +510,21 @@ class TextToSQLSystem:
507
  # 第零層:統一實體識別引擎 (Unified Entity Recognition Engine)
508
  # ==============================================================================
509
  entity_match_data = None
510
- # (您的 entity_patterns 邏輯保持不變,此處省略以保持簡潔)
511
  entity_patterns = [
512
- # 模式1: 匹配 "类型 + ID" (e.g., "买家ID C0761N") - 最高优先级
513
  {'pattern': r"(買家|买家|buyer)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.BuyerID', 'type': '買家ID'},
514
  {'pattern': r"(申請方|申请方|申請廠商|申请厂商|applicant)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.ApplicantID', 'type': '申請方ID'},
515
  {'pattern': r"(付款方|付款厂商|invoiceto)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.InvoiceToID', 'type': '付款方ID'},
516
  {'pattern': r"(代理商|agent)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.AgentID', 'type': '代理商ID'},
517
 
518
- # 模式2: 匹配 "類型 + 名稱" (e.g., "買家 Gap")
519
- {'pattern': r"(買家|买家|buyer|客戶)\s*'\"?([a-zA-Z0-9&.\s-]+?)(?:\s*的|\s+|$|有)", 'column': 'sd.BuyerName', 'type': '買家'},
520
- {'pattern': r"(申請方|申请方|申請廠商|申请厂商|applicant)\s*'\"?([a-zA-Z0-9&.\s-]+?)(?:\s*的|\s+|$|有)", 'column': 'sd.ApplicantName', 'type': '申請方'},
521
- {'pattern': r"(付款方|付款厂商|invoiceto)\s*'\"?([a-zA-Z0-9&.\s-]+?)(?:\s*的|\s+|$|有)", 'column': 'sd.InvoiceToName', 'type': '付款方'},
522
- {'pattern': r"(代理商|agent)\s*'\"?([a-zA-Z0-9&.\s-]+?)(?:\s*的|\s+|$|有)", 'column': 'sd.AgentName', 'type': '代理商'},
523
 
524
- # 模式3: 单独匹配一个 ID (e.g., "c0761n") - 较低优先级
525
  {'pattern': r"\b([A-Z]\d{4}[A-Z])\b", 'column': 'sd.ApplicantID', 'type': 'ID'}
526
  ]
527
 
@@ -542,23 +545,22 @@ class TextToSQLSystem:
542
 
543
  intents = {}
544
  sql_components = {
545
- 'select': [],
546
- 'from': "",
547
- 'joins': [],
548
- 'where': [],
549
- 'group_by': [],
550
- 'order_by': [],
551
- 'log_parts': []
552
  }
553
 
554
  # --- 運行一系列獨立的意圖偵測器 ---
555
 
556
  # 偵測器 2.1: 核心動作意圖
557
- # (此處邏輯不變)
558
  if any(kw in q_lower for kw in ['幾份', '多少', '數量', '總數', 'how many', 'count']):
559
  intents['action'] = 'count'
560
- sql_components['select'].append("COUNT(DISTINCT jt.JobNo) AS report_count")
561
- sql_components['log_parts'].append("報告總數")
 
 
 
 
 
562
  elif any(kw in q_lower for kw in ['報告號碼', '報告清��', '列出報告', 'report number', 'list of reports']):
563
  intents['action'] = 'list'
564
  sql_components['select'].append("jt.JobNo, jt.ReportAuthorization")
@@ -566,7 +568,6 @@ class TextToSQLSystem:
566
  sql_components['log_parts'].append("報告列表")
567
 
568
  # 偵測器 2.2: 時間意圖
569
- # (此處邏輯不變)
570
  year_match = re.search(r'(\d{4})\s*年?', question)
571
  month_match = re.search(r'(\d{1,2})\s*月', question)
572
  if year_match:
@@ -577,9 +578,8 @@ class TextToSQLSystem:
577
  month = month_match.group(1).zfill(2)
578
  sql_components['where'].append(f"strftime('%m', jt.ReportAuthorization) = '{month}'")
579
  sql_components['log_parts'].append(f"{month}月")
580
-
581
  # 偵測器 2.3: 實體意圖
582
- # (此處邏輯不變)
583
  if entity_match_data:
584
  if "TSR53SampleDescription" not in " ".join(sql_components['joins']):
585
  sql_components['joins'].append("JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo")
@@ -592,7 +592,6 @@ class TextToSQLSystem:
592
  sql_components['select'].append("sd.BuyerName")
593
 
594
  # 偵測器 2.4: 評級意圖
595
- # (此處邏輯不變)
596
  if 'fail' in q_lower or '失敗' in q_lower:
597
  if "TSR53SampleDescription" not in " ".join(sql_components['joins']):
598
  sql_components['joins'].append("JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo")
@@ -604,43 +603,23 @@ class TextToSQLSystem:
604
  sql_components['where'].append("sd.OverallRating = 'Pass'")
605
  sql_components['log_parts'].append("Pass")
606
 
607
- # *** V28 修正 ***
608
  # 偵測器 2.5: 實驗組 (LabGroup) 意圖 (帶有別名映射)
609
- lab_group_mapping = {
610
- 'A': 'TA',
611
- 'B': 'TB',
612
- 'C': 'TC',
613
- 'D': 'TD',
614
- 'E': 'TE',
615
- 'Y': 'TY',
616
- 'GCI': 'GCI',
617
- 'GCO': 'GCO'
618
- # ...可在此處繼續添加其他映射...
619
- }
620
  lab_group_match = re.search(r'([A-Z]{1,2})組', question, re.IGNORECASE)
621
  if lab_group_match:
622
  user_input_group = lab_group_match.group(1).upper()
623
- # 從映射表中查找真實值,如果找不到,則使用用戶的原始輸入作為備用
624
  db_lab_group = lab_group_mapping.get(user_input_group, user_input_group)
625
-
626
- # 測試項目數量應從 JobItemsInProgress 計算
627
  sql_components['joins'].append("JOIN JobItemsInProgress AS jip ON jt.JobNo = jip.JobNo")
628
  sql_components['where'].append(f"jip.LabGroup = '{db_lab_group}'")
629
  sql_components['log_parts'].append(f"{user_input_group}組(->{db_lab_group})")
630
-
631
- # 如果是計數,目標應該是測試項目而不是報告
632
- if intents.get('action') == 'count' and "測試項目" in question:
633
- sql_components['select'] = ["COUNT(jip.ItemCode) AS item_count"] # 覆蓋掉原有的 SELECT
634
- sql_components['log_parts'][0] = "測試項目總數" # 更新日誌
635
 
636
  # --- 3. 判斷是否觸發了模板,並動態組合 SQL ---
637
  if 'action' in intents:
638
- # 動態決定主表和必要的預設條件
639
  sql_components['from'] = "FROM JobTimeline AS jt"
640
- if year_match or month_match or entity_match_data or ('fail' in q_lower or '失敗' in q_lower) or ('pass' in q_lower or '通過' in q_lower):
 
641
  sql_components['where'].insert(0, "jt.ReportAuthorization IS NOT NULL")
642
 
643
- # 組合所有SQL組件
644
  select_clause = "SELECT " + ", ".join(sorted(list(set(sql_components['select']))))
645
  from_clause = sql_components['from']
646
  joins_clause = " ".join(sql_components['joins'])
@@ -658,7 +637,6 @@ class TextToSQLSystem:
658
  # ==============================================================================
659
  self._log("未觸發任何模板,嘗試解析並修正 AI 輸出...", "INFO")
660
 
661
- # (此處的 Fallback 邏輯保持不變)
662
  parsed_sql = parse_sql_from_response(raw_response)
663
  if not parsed_sql:
664
  self._log(f"❌ 未能從模型回應中解析出任何 SQL。原始回應: {raw_response}", "ERROR")
 
498
 
499
  def _validate_and_fix_sql(self, question: str, raw_response: str) -> Tuple[Optional[str], str]:
500
  """
501
+ (V29 / 穩健正則 + 智能計數 最終版)
502
+ 一個多層次的SQL生成引擎。它優先使用基於規則的動態模板生成器,
503
+ 如果無法匹配,則回退到解析和修正AI模型的輸出。
504
+ - 使用更簡潔、穩健的正則表達式來捕獲實體名稱。
505
+ - 根據問題是關於「報告」還是「測試項目」來智能地決定計數目標。
506
  """
507
  q_lower = question.lower()
508
 
 
510
  # 第零層:統一實體識別引擎 (Unified Entity Recognition Engine)
511
  # ==============================================================================
512
  entity_match_data = None
513
+ # 包含了繁簡體兼容和更穩健的模式
514
  entity_patterns = [
515
+ # 模式1: 匹配 "类型 + ID" - (保持不變)
516
  {'pattern': r"(買家|买家|buyer)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.BuyerID', 'type': '買家ID'},
517
  {'pattern': r"(申請方|申请方|申請廠商|申请厂商|applicant)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.ApplicantID', 'type': '申請方ID'},
518
  {'pattern': r"(付款方|付款厂商|invoiceto)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.InvoiceToID', 'type': '付款方ID'},
519
  {'pattern': r"(代理商|agent)\s*(?:id|代號|代碼|代号|代码)\s*'\"?\b([A-Z]\d{4}[A-Z])\b'\"?", 'column': 'sd.AgentID', 'type': '代理商ID'},
520
 
521
+ # 模式2: 匹配 "類型 + 名稱" - (簡化了模式,使其更穩健)
522
+ {'pattern': r"(買家|买家|buyer|客戶)\s+([a-zA-Z0-9&.-]+)", 'column': 'sd.BuyerName', 'type': '買家'},
523
+ {'pattern': r"(申請方|申请方|申請廠商|申请厂商|applicant)\s+([a-zA-Z0-9&.-]+)", 'column': 'sd.ApplicantName', 'type': '申請方'},
524
+ {'pattern': r"(付款方|付款厂商|invoiceto)\s+([a-zA-Z0-9&.-]+)", 'column': 'sd.InvoiceToName', 'type': '付款方'},
525
+ {'pattern': r"(代理商|agent)\s+([a-zA-Z0-9&.-]+)", 'column': 'sd.AgentName', 'type': '代理商'},
526
 
527
+ # 模式3: 单独匹配一个 ID - (保持不變)
528
  {'pattern': r"\b([A-Z]\d{4}[A-Z])\b", 'column': 'sd.ApplicantID', 'type': 'ID'}
529
  ]
530
 
 
545
 
546
  intents = {}
547
  sql_components = {
548
+ 'select': [], 'from': "", 'joins': [], 'where': [],
549
+ 'group_by': [], 'order_by': [], 'log_parts': []
 
 
 
 
 
550
  }
551
 
552
  # --- 運行一系列獨立的意圖偵測器 ---
553
 
554
  # 偵測器 2.1: 核心動作意圖
 
555
  if any(kw in q_lower for kw in ['幾份', '多少', '數量', '總數', 'how many', 'count']):
556
  intents['action'] = 'count'
557
+ # 智能決定計數目標
558
+ if "測試項目" in question or "test item" in q_lower:
559
+ sql_components['select'].append("COUNT(jip.ItemCode) AS item_count")
560
+ sql_components['log_parts'].append("測試項目總數")
561
+ else: # 預設是計數報告
562
+ sql_components['select'].append("COUNT(DISTINCT jt.JobNo) AS report_count")
563
+ sql_components['log_parts'].append("報告總數")
564
  elif any(kw in q_lower for kw in ['報告號碼', '報告清��', '列出報告', 'report number', 'list of reports']):
565
  intents['action'] = 'list'
566
  sql_components['select'].append("jt.JobNo, jt.ReportAuthorization")
 
568
  sql_components['log_parts'].append("報告列表")
569
 
570
  # 偵測器 2.2: 時間意圖
 
571
  year_match = re.search(r'(\d{4})\s*年?', question)
572
  month_match = re.search(r'(\d{1,2})\s*月', question)
573
  if year_match:
 
578
  month = month_match.group(1).zfill(2)
579
  sql_components['where'].append(f"strftime('%m', jt.ReportAuthorization) = '{month}'")
580
  sql_components['log_parts'].append(f"{month}月")
581
+
582
  # 偵測器 2.3: 實體意圖
 
583
  if entity_match_data:
584
  if "TSR53SampleDescription" not in " ".join(sql_components['joins']):
585
  sql_components['joins'].append("JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo")
 
592
  sql_components['select'].append("sd.BuyerName")
593
 
594
  # 偵測器 2.4: 評級意圖
 
595
  if 'fail' in q_lower or '失敗' in q_lower:
596
  if "TSR53SampleDescription" not in " ".join(sql_components['joins']):
597
  sql_components['joins'].append("JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo")
 
603
  sql_components['where'].append("sd.OverallRating = 'Pass'")
604
  sql_components['log_parts'].append("Pass")
605
 
 
606
  # 偵測器 2.5: 實驗組 (LabGroup) 意圖 (帶有別名映射)
607
+ lab_group_mapping = {'A': 'TA', 'B': 'TB', 'C': 'TC', 'D': 'TD', 'E': 'TE', 'Y': 'TY'}
 
 
 
 
 
 
 
 
 
 
608
  lab_group_match = re.search(r'([A-Z]{1,2})組', question, re.IGNORECASE)
609
  if lab_group_match:
610
  user_input_group = lab_group_match.group(1).upper()
 
611
  db_lab_group = lab_group_mapping.get(user_input_group, user_input_group)
 
 
612
  sql_components['joins'].append("JOIN JobItemsInProgress AS jip ON jt.JobNo = jip.JobNo")
613
  sql_components['where'].append(f"jip.LabGroup = '{db_lab_group}'")
614
  sql_components['log_parts'].append(f"{user_input_group}組(->{db_lab_group})")
 
 
 
 
 
615
 
616
  # --- 3. 判斷是否觸發了模板,並動態組合 SQL ---
617
  if 'action' in intents:
 
618
  sql_components['from'] = "FROM JobTimeline AS jt"
619
+ # 只要有任何篩選條件,就加上報告已授權的基礎限制
620
+ if sql_components['where']:
621
  sql_components['where'].insert(0, "jt.ReportAuthorization IS NOT NULL")
622
 
 
623
  select_clause = "SELECT " + ", ".join(sorted(list(set(sql_components['select']))))
624
  from_clause = sql_components['from']
625
  joins_clause = " ".join(sql_components['joins'])
 
637
  # ==============================================================================
638
  self._log("未觸發任何模板,嘗試解析並修正 AI 輸出...", "INFO")
639
 
 
640
  parsed_sql = parse_sql_from_response(raw_response)
641
  if not parsed_sql:
642
  self._log(f"❌ 未能從模型回應中解析出任何 SQL。原始回應: {raw_response}", "ERROR")