Paul720810 commited on
Commit
087e4d5
·
verified ·
1 Parent(s): 6ba8459

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +93 -53
app.py CHANGED
@@ -596,11 +596,12 @@ SELECT
596
  if m:
597
  y1, y2 = m.group(1), m.group(2)
598
  return (
599
- "SELECT strftime('%Y-%m', completed_time) AS month, "
600
- f"SUM(CASE WHEN strftime('%Y', completed_time)='{y1}' THEN 1 ELSE 0 END) AS count_{y1}, "
601
- f"SUM(CASE WHEN strftime('%Y', completed_time)='{y2}' THEN 1 ELSE 0 END) AS count_{y2} "
602
- "FROM jobtimeline "
603
- f"WHERE strftime('%Y', completed_time) IN ('{y1}','{y2}') "
 
604
  "GROUP BY month ORDER BY month;"
605
  )
606
 
@@ -609,33 +610,46 @@ SELECT
609
  if m:
610
  year = m.group(1)
611
  return (
612
- "SELECT strftime('%Y-%m', completed_time) AS month, COUNT(*) AS count "
613
- "FROM jobtimeline "
614
- f"WHERE strftime('%Y', completed_time)='{year}' "
 
615
  "GROUP BY month ORDER BY month;"
616
  )
617
 
618
  # 評級分布(Pass/Fail)
619
  if ("評級" in q) or ("pass" in q_lower) or ("fail" in q_lower):
620
- return "SELECT rating, COUNT(*) AS count FROM tsr53sampledescription GROUP BY rating;"
 
 
 
 
621
 
622
  # 金額最高 Top N(預設 10)
623
  m = re.search(r"金額.*?(?:最高|前|top)\s*(\d+)?", q_lower)
624
  if m:
625
  n = m.group(1) or "10"
626
- return f"SELECT * FROM tsr53invoice ORDER BY amount DESC LIMIT {n};"
627
 
628
  # 客戶工作單數量最多 Top N
629
  m = re.search(r"客戶.*?(?:最多|top|前)\s*(\d+)?", q_lower)
630
  if m:
631
  n = m.group(1) or "10"
632
- return f"SELECT applicant, COUNT(*) AS count FROM tsr53sampledescription GROUP BY applicant ORDER BY count DESC LIMIT {n};"
 
 
 
 
 
 
 
633
 
634
  # 昨天完成多少
635
  if "昨天" in q:
636
  return (
637
- "SELECT COUNT(*) AS count FROM jobtimeline "
638
- "WHERE date(completed_time)=date('now','-1 day');"
 
639
  )
640
 
641
  return None
@@ -651,6 +665,28 @@ SELECT
651
  self._log(f"最終整理 SQL 失敗: {e}", "ERROR")
652
  return (sql_text or ""), status
653
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  def _validate_and_fix_sql(self, question: str, raw_response: str) -> Tuple[Optional[str], str]:
655
  """
656
  (V29 / 穩健正則 + 智能計數) 多層次 SQL 生成:
@@ -741,7 +777,8 @@ SELECT
741
  entity_name, column_name = entity_match_data['name'], entity_match_data['column']
742
  match_op = '=' if column_name.endswith('ID') else 'LIKE'
743
  entity_val = f"'%{entity_name}%'" if match_op == 'LIKE' else f"'{entity_name}'"
744
- sql['where'].append(f"{column_name} {match_op} {entity_val}")
 
745
  sql['log_parts'].append(entity_match_data['type'] + ":" + entity_name)
746
  if intents.get('action') == 'list':
747
  sql['select'].append("sd.BuyerName")
@@ -794,6 +831,9 @@ SELECT
794
  salvage_sql = 'SELECT ' + resp
795
  parsed_sql = parse_sql_from_response(salvage_sql) or salvage_sql
796
 
 
 
 
797
  if not parsed_sql:
798
  self._log(f"❌ 未能從模型回應中解析出任何 SQL。原始回應: {raw_response}", "ERROR")
799
  return None, f"無法解析SQL。原始回應:\n{raw_response}"
@@ -842,45 +882,45 @@ SELECT
842
  return self._finalize_sql(fixed_sql, status)
843
 
844
  def _generate_fallback_sql(self, prompt: str) -> str:
845
- """當模型不可用時的備用 SQL 生成"""
846
- prompt_lower = (prompt or "").lower()
847
- # 統計類:優先使用 JobTimeline.ReportAuthorization,避免不存在的 completed_time 欄位
848
- if ("統計" in prompt) or ("數量" in prompt) or ("多少" in prompt) or ("count" in prompt_lower):
849
- if ("月" in prompt) or ("per month" in prompt_lower) or ("monthly" in prompt_lower):
850
- return (
851
- "SELECT strftime('%Y-%m', jt.ReportAuthorization) AS month, "
852
- "COUNT(DISTINCT jt.JobNo) AS count "
853
- "FROM JobTimeline AS jt "
854
- "WHERE jt.ReportAuthorization IS NOT NULL "
855
- "GROUP BY month ORDER BY month;"
856
- )
857
- elif ("客戶" in prompt) or ("buyer" in prompt_lower) or ("applicant" in prompt_lower):
858
- return (
859
- "SELECT sd.ApplicantName AS applicant, COUNT(DISTINCT jt.JobNo) AS count "
860
- "FROM JobTimeline AS jt "
861
- "JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo "
862
- "WHERE jt.ReportAuthorization IS NOT NULL "
863
- "GROUP BY sd.ApplicantName ORDER BY count DESC;"
864
- )
865
- else:
866
- return (
867
- "SELECT COUNT(DISTINCT jt.JobNo) AS total_count "
868
- "FROM JobTimeline AS jt "
869
- "WHERE jt.ReportAuthorization IS NOT NULL;"
870
- )
871
- # 金額彙總
872
- if ("金額" in prompt) or ("總額" in prompt) or ("amount" in prompt_lower) or ("sum" in prompt_lower):
873
- return "SELECT SUM(LocalAmount) AS total_amount FROM TSR53Invoice;"
874
- # 評級分布
875
- if ("評級" in prompt) or ("rating" in prompt_lower) or ("pass" in prompt_lower) or ("fail" in prompt_lower):
876
- return "SELECT OverallRating AS rating, COUNT(*) AS count FROM TSR53SampleDescription GROUP BY OverallRating;"
877
- # 通用後備:最近 10 筆報告
878
- return (
879
- "SELECT jt.JobNo, jt.ReportAuthorization "
880
- "FROM JobTimeline AS jt "
881
- "WHERE jt.ReportAuthorization IS NOT NULL "
882
- "ORDER BY jt.ReportAuthorization DESC LIMIT 10;"
883
- )
884
 
885
  def process_question(self, question: str) -> Tuple[str, str]:
886
  """處理使用者問題"""
 
596
  if m:
597
  y1, y2 = m.group(1), m.group(2)
598
  return (
599
+ "SELECT strftime('%Y-%m', jt.ReportAuthorization) AS month, "
600
+ f"COUNT(DISTINCT CASE WHEN strftime('%Y', jt.ReportAuthorization)='{y1}' THEN jt.JobNo END) AS count_{y1}, "
601
+ f"COUNT(DISTINCT CASE WHEN strftime('%Y', jt.ReportAuthorization)='{y2}' THEN jt.JobNo END) AS count_{y2} "
602
+ "FROM JobTimeline AS jt "
603
+ "WHERE jt.ReportAuthorization IS NOT NULL "
604
+ f"AND strftime('%Y', jt.ReportAuthorization) IN ('{y1}','{y2}') "
605
  "GROUP BY month ORDER BY month;"
606
  )
607
 
 
610
  if m:
611
  year = m.group(1)
612
  return (
613
+ "SELECT strftime('%Y-%m', jt.ReportAuthorization) AS month, COUNT(DISTINCT jt.JobNo) AS count "
614
+ "FROM JobTimeline AS jt "
615
+ "WHERE jt.ReportAuthorization IS NOT NULL "
616
+ f"AND strftime('%Y', jt.ReportAuthorization)='{year}' "
617
  "GROUP BY month ORDER BY month;"
618
  )
619
 
620
  # 評級分布(Pass/Fail)
621
  if ("評級" in q) or ("pass" in q_lower) or ("fail" in q_lower):
622
+ return (
623
+ "SELECT sd.OverallRating AS rating, COUNT(*) AS count "
624
+ "FROM TSR53SampleDescription AS sd "
625
+ "GROUP BY sd.OverallRating;"
626
+ )
627
 
628
  # 金額最高 Top N(預設 10)
629
  m = re.search(r"金額.*?(?:最高|前|top)\s*(\d+)?", q_lower)
630
  if m:
631
  n = m.group(1) or "10"
632
+ return f"SELECT iv.* FROM TSR53Invoice AS iv ORDER BY iv.LocalAmount DESC LIMIT {n};"
633
 
634
  # 客戶工作單數量最多 Top N
635
  m = re.search(r"客戶.*?(?:最多|top|前)\s*(\d+)?", q_lower)
636
  if m:
637
  n = m.group(1) or "10"
638
+ return (
639
+ f"SELECT sd.ApplicantName AS applicant, COUNT(DISTINCT jt.JobNo) AS count "
640
+ "FROM JobTimeline AS jt "
641
+ "JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo "
642
+ "WHERE jt.ReportAuthorization IS NOT NULL "
643
+ "GROUP BY sd.ApplicantName ORDER BY count DESC "
644
+ f"LIMIT {n};"
645
+ )
646
 
647
  # 昨天完成多少
648
  if "昨天" in q:
649
  return (
650
+ "SELECT COUNT(DISTINCT jt.JobNo) AS count FROM JobTimeline AS jt "
651
+ "WHERE jt.ReportAuthorization IS NOT NULL "
652
+ "AND date(jt.ReportAuthorization)=date('now','-1 day');"
653
  )
654
 
655
  return None
 
665
  self._log(f"最終整理 SQL 失敗: {e}", "ERROR")
666
  return (sql_text or ""), status
667
 
668
+ def _regenerate_sql_strict(self, question: str) -> Optional[str]:
669
+ """當模型輸出非 SQL 或無法解析時,使用嚴格限制的提示詞重生一次。"""
670
+ try:
671
+ rel = self._identify_relevant_tables(question)
672
+ schema_str = self._format_relevant_schema(rel)
673
+ strict_prompt = (
674
+ "You are a SQLite SQL generator.\n"
675
+ + "Given the schema below and the question, output ONE valid SQL query only.\n\n"
676
+ + "SCHEMA:\n" + schema_str + "\n\n"
677
+ + "QUESTION:\n" + (question or "").strip() + "\n\n"
678
+ + "Return only the final SQL query in a fenced code block (```sql ... ```). "
679
+ + "The SQL must start with SELECT and end with a semicolon. No explanation."
680
+ )
681
+ raw = self.huggingface_api_call(strict_prompt)
682
+ sql = parse_sql_from_response(raw)
683
+ if sql:
684
+ self._log("🔁 嚴格模式重生成功。")
685
+ return sql
686
+ except Exception as e:
687
+ self._log(f"嚴格模式重生失敗: {e}", "ERROR")
688
+ return None
689
+
690
  def _validate_and_fix_sql(self, question: str, raw_response: str) -> Tuple[Optional[str], str]:
691
  """
692
  (V29 / 穩健正則 + 智能計數) 多層次 SQL 生成:
 
777
  entity_name, column_name = entity_match_data['name'], entity_match_data['column']
778
  match_op = '=' if column_name.endswith('ID') else 'LIKE'
779
  entity_val = f"'%{entity_name}%'" if match_op == 'LIKE' else f"'{entity_name}'"
780
+ collate = " COLLATE NOCASE" if match_op == 'LIKE' else ""
781
+ sql['where'].append(f"{column_name} {match_op} {entity_val}{collate}")
782
  sql['log_parts'].append(entity_match_data['type'] + ":" + entity_name)
783
  if intents.get('action') == 'list':
784
  sql['select'].append("sd.BuyerName")
 
831
  salvage_sql = 'SELECT ' + resp
832
  parsed_sql = parse_sql_from_response(salvage_sql) or salvage_sql
833
 
834
+ if not parsed_sql:
835
+ self._log("模型輸出非 SQL,啟用嚴格模式重生一次…")
836
+ parsed_sql = self._regenerate_sql_strict(q)
837
  if not parsed_sql:
838
  self._log(f"❌ 未能從模型回應中解析出任何 SQL。原始回應: {raw_response}", "ERROR")
839
  return None, f"無法解析SQL。原始回應:\n{raw_response}"
 
882
  return self._finalize_sql(fixed_sql, status)
883
 
884
  def _generate_fallback_sql(self, prompt: str) -> str:
885
+ """當模型不可用時的備用 SQL 生成"""
886
+ prompt_lower = (prompt or "").lower()
887
+ # 統計類:優先使用 JobTimeline.ReportAuthorization,避免不存在的 completed_time 欄位
888
+ if ("統計" in prompt) or ("數量" in prompt) or ("多少" in prompt) or ("count" in prompt_lower):
889
+ if ("月" in prompt) or ("per month" in prompt_lower) or ("monthly" in prompt_lower):
890
+ return (
891
+ "SELECT strftime('%Y-%m', jt.ReportAuthorization) AS month, "
892
+ "COUNT(DISTINCT jt.JobNo) AS count "
893
+ "FROM JobTimeline AS jt "
894
+ "WHERE jt.ReportAuthorization IS NOT NULL "
895
+ "GROUP BY month ORDER BY month;"
896
+ )
897
+ elif ("客戶" in prompt) or ("buyer" in prompt_lower) or ("applicant" in prompt_lower):
898
+ return (
899
+ "SELECT sd.ApplicantName AS applicant, COUNT(DISTINCT jt.JobNo) AS count "
900
+ "FROM JobTimeline AS jt "
901
+ "JOIN TSR53SampleDescription AS sd ON jt.JobNo = sd.JobNo "
902
+ "WHERE jt.ReportAuthorization IS NOT NULL "
903
+ "GROUP BY sd.ApplicantName ORDER BY count DESC;"
904
+ )
905
+ else:
906
+ return (
907
+ "SELECT COUNT(DISTINCT jt.JobNo) AS total_count "
908
+ "FROM JobTimeline AS jt "
909
+ "WHERE jt.ReportAuthorization IS NOT NULL;"
910
+ )
911
+ # 金額彙總
912
+ if ("金額" in prompt) or ("總額" in prompt) or ("amount" in prompt_lower) or ("sum" in prompt_lower):
913
+ return "SELECT SUM(LocalAmount) AS total_amount FROM TSR53Invoice;"
914
+ # 評級分布
915
+ if ("評級" in prompt) or ("rating" in prompt_lower) or ("pass" in prompt_lower) or ("fail" in prompt_lower):
916
+ return "SELECT OverallRating AS rating, COUNT(*) AS count FROM TSR53SampleDescription GROUP BY OverallRating;"
917
+ # 通用後備:最近 10 筆報告
918
+ return (
919
+ "SELECT jt.JobNo, jt.ReportAuthorization "
920
+ "FROM JobTimeline AS jt "
921
+ "WHERE jt.ReportAuthorization IS NOT NULL "
922
+ "ORDER BY jt.ReportAuthorization DESC LIMIT 10;"
923
+ )
924
 
925
  def process_question(self, question: str) -> Tuple[str, str]:
926
  """處理使用者問題"""