WafaaFraih commited on
Commit
cae32e0
·
verified ·
1 Parent(s): 36cc6af

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +30 -44
main.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Saudi Legal AI — Production Version v8.2 (High Speed Backend + Original UI)
3
  Gemini-first · Groq fallback · FastAPI + HTML frontend
4
  """
5
  import os
@@ -25,7 +25,6 @@ from langchain_community.vectorstores import Chroma
25
  from langchain_community.embeddings import SentenceTransformerEmbeddings
26
  from datasets import load_dataset
27
  from rank_bm25 import BM25Okapi
28
- from rapidfuzz import fuzz, process as fuzz_proc
29
  import chromadb
30
 
31
  # ══════════════════════════════════════════════════════════
@@ -49,11 +48,9 @@ GEMINI_AVAILABLE: bool = False
49
  gemini_clients: dict = {}
50
  groq_client: Optional[Groq] = None
51
 
52
- request_log = deque(maxlen=500)
53
- api_stats = defaultdict(int)
54
- user_ips = defaultdict(list)
55
- _trans_cache = {}
56
- _legal_cache = {}
57
  _answer_cache = {}
58
 
59
  GEMINI_PRIORITY = [
@@ -95,7 +92,6 @@ COLLOQUIAL = {
95
  "فين":"أين","مين":"من","ليه":"لماذا","عندي":"لدي",
96
  "اشتغلت":"عملت","فصلوني":"تم فصلي","طردوني":"تم فصلي",
97
  "اتطردت":"تم فصلي","مش":"لا","كمان":"أيضاً","زي":"مثل",
98
- "اللازم":"يجب","المفروض":"يجب",
99
  "مكافاه":"مكافأة","مكافأه":"مكافأة","مكافاة":"مكافأة",
100
  "رخصه":"رخصة","مزاوله":"مزاولة","المحاماه":"المحاماة",
101
  "ليا":"لي","هل ليا":"هل يحق لي",
@@ -109,25 +105,10 @@ COLLOQUIAL = {
109
  "اتفصلت":"تم فصلي", "هستقيل":"استقالة", "استقيل":"استقالة", "هسيب الشغل":"استقالة"
110
  }
111
 
112
- ENGLISH_TO_ARABIC = {
113
- "conditions for lawyer license": "شروط مزاولة مهنة المحاماة",
114
- "what is the penalty": "ما هي عقوبة",
115
- "what are my rights": "ما هي حقوقي",
116
- "how is calculated": "كيف تحسب",
117
- "i was dismissed": "تم فصلي",
118
- "wrongful termination": "فصل تعسفي",
119
- "end of service": "مكافأة نهاية الخدمة",
120
- "money laundering": "غسل الأموال",
121
- "cybercrime": "الجرائم المعلوماتية",
122
- "working hours": "ساعات العمل",
123
- "annual leave": "الإجازة السنوية",
124
- "maternity leave": "إجازة الأمومة",
125
- "labor law": "نظام العمل",
126
- }
127
-
128
  LAW_KEYWORDS = {
129
  "توثيق":"نظام التوثيق","كاتب عدل":"نظام التوثيق","موثق":"نظام التوثيق",
130
  "محاماة":"نظام المحاماة","محامي":"نظام المحاماة","مزاولة مهنة":"نظام المحاماة",
 
131
  "إفلاس":"نظام الإفلاس","إعسار":"نظام الإفلاس",
132
  "تحكيم":"نظام التحكيم",
133
  "إثبات":"نظام الإثبات","شاهد":"نظام الإثبات",
@@ -136,6 +117,7 @@ LAW_KEYWORDS = {
136
  "توقيف":"نظام الإجراءات الجزائية","تهمة":"نظام الإجراءات الجزائية",
137
  "محتجز":"نظام الإجراءات الجزائية","جريمة":"نظام الإجراءات الجزائية",
138
  "قبض":"نظام الإجراءات الجزائية","قتل":"نظام الإجراءات الجزائية","قضية":"نظام الإجراءات الجزائية",
 
139
  "زواج":"نظام الأحوال الشخصية","طلاق":"نظام الأحوال الشخصية",
140
  "نفقة":"نظام الأحوال الشخصية","حضانة":"نظام الأحوال الشخصية","خلع":"نظام الأحوال الشخصية",
141
  "عقار":"نظام التسجيل العيني للعقار",
@@ -173,12 +155,14 @@ PRACTICAL_PATTERNS = {
173
  QUERY_EXPANSION = {
174
  "مكافأة نهاية الخدمة": "يستحق العامل مكافأة عن مدة خدمته نصف شهر سنة",
175
  "فصلوني": "إنهاء عقد العمل تعسفي تعويض فصل",
 
176
  "حقوقي": "يستحق العامل مكافأة تعويض نظام العمل",
177
  "ساعات العمل": "لا يجوز تشغيل العامل أكثر من ثماني ساعات يومياً",
 
178
  "اتهمت": "حق المتهم في محامٍ أثناء التحقيق الجزائي",
 
179
  "شروط مزاولة المحاماة":"يشترط فيمن يزاول المحاماة سعودي جدول المحامين",
180
  "أركان العقد": "أركان العقد الإيجاب والقبول والمحل",
181
- "عقوبات غسل الأموال": "يعاقب على غسل الأموال سجن غرامة",
182
  }
183
 
184
  STOP_WORDS = {"من","في","على","إلى","عن","مع","هي","هو","ما","لا","أن","إن","كل","هذا","ذلك","التي","الذي","هذه","تلك","قد","إذا","إذ"}
@@ -262,7 +246,11 @@ async def startup():
262
  print(f" ⚠️ HF load failed: {e}")
263
 
264
  try:
265
- with open("train.json", "r", encoding="utf-8") as f:
 
 
 
 
266
  train_raw = json.load(f)
267
 
268
  added_from_json = 0
@@ -281,7 +269,8 @@ async def startup():
281
  law_name = subj.split("/")[0].strip()
282
  law_name = re.sub(r"\(.*?\)", "", law_name).strip()
283
  hf_items.append({
284
- "text": text[:2000], "article_number": "نص قانوني",
 
285
  "law_name": law_name, "law_type": "نظام", "_src": "train.json",
286
  "is_exec_reg": 1 if "لائحة" in law_name else 0
287
  })
@@ -309,9 +298,10 @@ async def startup():
309
  for item in hf_items if len(item.get("text","")) > 30
310
  ]
311
 
312
- splitter = RecursiveCharacterTextSplitter(chunk_size=1200, chunk_overlap=150, separators=["\nالمادة ","\n\n","\n","."," "])
313
- chunks = [d for d in docs_lc if len(d.page_content) <= 1200]
314
- chunks += splitter.split_documents([d for d in docs_lc if len(d.page_content) > 1200])
 
315
 
316
  chroma_client = chromadb.Client()
317
  vectorstore = Chroma.from_documents(documents=chunks, embedding=embeddings, client=chroma_client, collection_name="saudi_legal_v8")
@@ -356,7 +346,6 @@ def detect_law(question):
356
  return None
357
 
358
  def fast_rewrite(question):
359
- # Fast rewrite without LLM (Saves 3 seconds!)
360
  queries = [question]
361
  for pat, exp in QUERY_EXPANSION.items():
362
  if pat in question: queries.append(exp); break
@@ -431,28 +420,24 @@ def post_process(answer, docs):
431
  law = docs[0].metadata.get("law_name","")
432
  art = docs[0].metadata.get("article_number","")
433
  if law and art: answer = f"📋 المرجع: {law} — {art}\n\n{answer}"
434
- lines = answer.split("\n")
435
- clean = [l for l in lines if
436
- sum(1 for c in l if "\u0600"<=c<="\u06ff") / max(len(l.replace(" ","")),1) > 0.25
437
- or any(s in l for s in ["📋","✅","📝","⚠️","•","-","─"])]
438
- return "\n".join(clean).strip()
439
 
440
  # ══════════════════════════════════════════════════════════
441
  # System Prompts
442
  # ══════════════════════════════════════════════════════════
443
- SYSTEM_PROMPT = """أنت مساعد قانوني سعودي محترف ومختصر.
444
 
445
- نطاقك: وزارة العدل، نظام العمل، الإجراءات الجزائية، الجرائم المعلوماتية، حماية البيانات، المعاملات المدنية.
446
 
447
  صيغة الإجابة الإلزامية:
448
  📋 المرجع: [اسم النظام] — [رقم المادة]
449
- ✅ الإجابة: [إجابة مباشرة وقصيرة. استخدم نعم/لا فقط إذا كان السؤال بصيغة "هل". إذا كان استفسار أجب بالمعلومة فوراً]
450
- 📝 التفاصيل: [شرح دقيق من النص القانوني المرفق فقط]
451
 
452
  قواعد:
453
  ١. العربية الفصحى حصراً.
454
  ٢. اذكر النظام والمادة دائماً.
455
- ٣. لا تخترع معلومات — استند للنصوص المرفقة فقط.
456
  ٤. ابدأ بـ 📋 مباشرة."""
457
 
458
  OUT_OF_SCOPE = """أنا مساعد قانوني متخصص في الأنظمة السعودية.
@@ -542,7 +527,7 @@ def ask_legal(question: str) -> dict:
542
  elif has_anti and any(w in question for w in ["فلوس", "دين", "سلف"]):
543
  target_law = "نظام المعاملات المدنية"
544
 
545
- # Hybrid retrieval
546
  seen, result_docs = set(), []
547
  def add_docs(new_docs):
548
  for d in new_docs:
@@ -583,8 +568,10 @@ def ask_legal(question: str) -> dict:
583
  return {"answer":"الخدمة مشغولة — يرجى المحاولة مجدداً.",
584
  "sources":[],"coverage":0,"model":"unavailable","normalized":question}
585
 
 
 
586
  result = {
587
- "answer": post_process(ans, final),
588
  "sources": [{"law":d.metadata.get("law_name",""),"article":d.metadata.get("article_number","")} for d in final[:3]],
589
  "coverage": round(cov*100) if target_law else round(cov*50),
590
  "model": model_used,
@@ -1175,5 +1162,4 @@ async def ask_endpoint(req: QuestionRequest, request: Request):
1175
 
1176
  if __name__ == "__main__":
1177
  import uvicorn
1178
- # ── استخدام Port 7860 المطلوب من Hugging Face Spaces ──
1179
  uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=False)
 
1
  """
2
+ Saudi Legal AI — Production Version v8.2
3
  Gemini-first · Groq fallback · FastAPI + HTML frontend
4
  """
5
  import os
 
25
  from langchain_community.embeddings import SentenceTransformerEmbeddings
26
  from datasets import load_dataset
27
  from rank_bm25 import BM25Okapi
 
28
  import chromadb
29
 
30
  # ══════════════════════════════════════════════════════════
 
48
  gemini_clients: dict = {}
49
  groq_client: Optional[Groq] = None
50
 
51
+ request_log = deque(maxlen=500)
52
+ api_stats = defaultdict(int)
53
+ user_ips = defaultdict(list)
 
 
54
  _answer_cache = {}
55
 
56
  GEMINI_PRIORITY = [
 
92
  "فين":"أين","مين":"من","ليه":"لماذا","عندي":"لدي",
93
  "اشتغلت":"عملت","فصلوني":"تم فصلي","طردوني":"تم فصلي",
94
  "اتطردت":"تم فصلي","مش":"لا","كمان":"أيضاً","زي":"مثل",
 
95
  "مكافاه":"مكافأة","مكافأه":"مكافأة","مكافاة":"مكافأة",
96
  "رخصه":"رخصة","مزاوله":"مزاولة","المحاماه":"المحاماة",
97
  "ليا":"لي","هل ليا":"هل يحق لي",
 
105
  "اتفصلت":"تم فصلي", "هستقيل":"استقالة", "استقيل":"استقالة", "هسيب الشغل":"استقالة"
106
  }
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  LAW_KEYWORDS = {
109
  "توثيق":"نظام التوثيق","كاتب عدل":"نظام التوثيق","موثق":"نظام التوثيق",
110
  "محاماة":"نظام المحاماة","محامي":"نظام المحاماة","مزاولة مهنة":"نظام المحاماة",
111
+ "ترخيص محاماة":"نظام المحاماة",
112
  "إفلاس":"نظام الإفلاس","إعسار":"نظام الإفلاس",
113
  "تحكيم":"نظام التحكيم",
114
  "إثبات":"نظام الإثبات","شاهد":"نظام الإثبات",
 
117
  "توقيف":"نظام الإجراءات الجزائية","تهمة":"نظام الإجراءات الجزائية",
118
  "محتجز":"نظام الإجراءات الجزائية","جريمة":"نظام الإجراءات الجزائية",
119
  "قبض":"نظام الإجراءات الجزائية","قتل":"نظام الإجراءات الجزائية","قضية":"نظام الإجراءات الجزائية",
120
+ "عليا قضية":"نظام الإجراءات الجزائية",
121
  "زواج":"نظام الأحوال الشخصية","طلاق":"نظام الأحوال الشخصية",
122
  "نفقة":"نظام الأحوال الشخصية","حضانة":"نظام الأحوال الشخصية","خلع":"نظام الأحوال الشخصية",
123
  "عقار":"نظام التسجيل العيني للعقار",
 
155
  QUERY_EXPANSION = {
156
  "مكافأة نهاية الخدمة": "يستحق العامل مكافأة عن مدة خدمته نصف شهر سنة",
157
  "فصلوني": "إنهاء عقد العمل تعسفي تعويض فصل",
158
+ "اتطردت": "تم فصلي تعويض فصل تعسفي مكافأة نهاية الخدمة",
159
  "حقوقي": "يستحق العامل مكافأة تعويض نظام العمل",
160
  "ساعات العمل": "لا يجوز تشغيل العامل أكثر من ثماني ساعات يومياً",
161
+ "شهادة خبرة": "يلتزم صاحب العمل بإعطاء شهادة خبرة عند انتهاء العقد",
162
  "اتهمت": "حق المتهم في محامٍ أثناء التحقيق الجزائي",
163
+ "حقوق الموقوف": "يحق للموقوف الاتصال بمحاميه وذويه عند التوقيف",
164
  "شروط مزاولة المحاماة":"يشترط فيمن يزاول المحاماة سعودي جدول المحامين",
165
  "أركان العقد": "أركان العقد الإيجاب والقبول والمحل",
 
166
  }
167
 
168
  STOP_WORDS = {"من","في","على","إلى","عن","مع","هي","هو","ما","لا","أن","إن","كل","هذا","ذلك","التي","الذي","هذه","تلك","قد","إذا","إذ"}
 
246
  print(f" ⚠️ HF load failed: {e}")
247
 
248
  try:
249
+ # حل مشكلة المسار المطلق لملف train.json
250
+ base_dir = os.path.dirname(os.path.abspath(__file__))
251
+ json_path = os.path.join(base_dir, "train.json")
252
+
253
+ with open(json_path, "r", encoding="utf-8") as f:
254
  train_raw = json.load(f)
255
 
256
  added_from_json = 0
 
269
  law_name = subj.split("/")[0].strip()
270
  law_name = re.sub(r"\(.*?\)", "", law_name).strip()
271
  hf_items.append({
272
+ "text": text, # أخذ المادة كاملة دون القص
273
+ "article_number": "نص قانوني",
274
  "law_name": law_name, "law_type": "نظام", "_src": "train.json",
275
  "is_exec_reg": 1 if "لائحة" in law_name else 0
276
  })
 
298
  for item in hf_items if len(item.get("text","")) > 30
299
  ]
300
 
301
+ # تكبير الحجم لعدم قص الكلمات في منتصفها
302
+ splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=300, separators=["\nالمادة ","\n\n","\n","."," "])
303
+ chunks = [d for d in docs_lc if len(d.page_content) <= 2000]
304
+ chunks += splitter.split_documents([d for d in docs_lc if len(d.page_content) > 2000])
305
 
306
  chroma_client = chromadb.Client()
307
  vectorstore = Chroma.from_documents(documents=chunks, embedding=embeddings, client=chroma_client, collection_name="saudi_legal_v8")
 
346
  return None
347
 
348
  def fast_rewrite(question):
 
349
  queries = [question]
350
  for pat, exp in QUERY_EXPANSION.items():
351
  if pat in question: queries.append(exp); break
 
420
  law = docs[0].metadata.get("law_name","")
421
  art = docs[0].metadata.get("article_number","")
422
  if law and art: answer = f"📋 المرجع: {law} — {art}\n\n{answer}"
423
+ return answer # بدون أي حذف للسطور
 
 
 
 
424
 
425
  # ══════════════════════════════════════════════════════════
426
  # System Prompts
427
  # ══════════════════════════════════════════════════════════
428
+ SYSTEM_PROMPT = """أنت مستشار قانوني سعودي محترف وشامل في إجاباتك.
429
 
430
+ نطاقك: وزارة العدل، نظام العمل، الإجراءات الجزائية، المعاملات المدنية، الجرائم المعلوماتية.
431
 
432
  صيغة الإجابة الإلزامية:
433
  📋 المرجع: [اسم النظام] — [رقم المادة]
434
+ ✅ الإجابة: [إجابة واضحة وشاملة للسؤال. استخدم نعم/لا فقط إذا كان السؤال يبدأ بـ "هل"]
435
+ 📝 التفاصيل: [اشرح هنا بالتفصيل الممل والواضح بناءً على النصوص المرفقة. اكتب فقرة كاملة مترابطة ولا تقم أبداً ببتر الجمل]
436
 
437
  قواعد:
438
  ١. العربية الفصحى حصراً.
439
  ٢. اذكر النظام والمادة دائماً.
440
+ ٣. اشرح براحتك وبشكل مفصل جداً لمساعدة السائل.
441
  ٤. ابدأ بـ 📋 مباشرة."""
442
 
443
  OUT_OF_SCOPE = """أنا مساعد قانوني متخصص في الأنظمة السعودية.
 
527
  elif has_anti and any(w in question for w in ["فلوس", "دين", "سلف"]):
528
  target_law = "نظام المعاملات المدنية"
529
 
530
+ # Retrieval
531
  seen, result_docs = set(), []
532
  def add_docs(new_docs):
533
  for d in new_docs:
 
568
  return {"answer":"الخدمة مشغولة — يرجى المحاولة مجدداً.",
569
  "sources":[],"coverage":0,"model":"unavailable","normalized":question}
570
 
571
+ ans = post_process(ans, final)
572
+
573
  result = {
574
+ "answer": ans,
575
  "sources": [{"law":d.metadata.get("law_name",""),"article":d.metadata.get("article_number","")} for d in final[:3]],
576
  "coverage": round(cov*100) if target_law else round(cov*50),
577
  "model": model_used,
 
1162
 
1163
  if __name__ == "__main__":
1164
  import uvicorn
 
1165
  uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=False)