Bayan AI commited on
Commit
ce14862
·
1 Parent(s): 8f8ed51

Add debugging scripts and fix grammar/punctuation logic

Browse files
camel_test_out.json ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "word": "يسعى",
4
+ "diac": "يَسْعَى",
5
+ "lex": "سَعَى",
6
+ "pos": "verb",
7
+ "per": "3",
8
+ "num": "s",
9
+ "gen": "m",
10
+ "root": "س.ع.#",
11
+ "stem": "سْعَى"
12
+ },
13
+ {
14
+ "word": "يسعي",
15
+ "diac": "يَسْعَى",
16
+ "lex": "سَعَى",
17
+ "pos": "verb",
18
+ "per": "3",
19
+ "num": "s",
20
+ "gen": "m",
21
+ "root": "س.ع.#",
22
+ "stem": "سْعَى"
23
+ },
24
+ {
25
+ "word": "يدعو",
26
+ "diac": "يَدْعُو",
27
+ "lex": "دَعا",
28
+ "pos": "verb",
29
+ "per": "3",
30
+ "num": "s",
31
+ "gen": "m",
32
+ "root": "د.ع.#",
33
+ "stem": "دْعُو"
34
+ },
35
+ {
36
+ "word": "يمشي",
37
+ "diac": "يَمْشَى",
38
+ "lex": "مَشَى",
39
+ "pos": "verb",
40
+ "per": "3",
41
+ "num": "s",
42
+ "gen": "m",
43
+ "root": "م.ش.#",
44
+ "stem": "مْشَى"
45
+ },
46
+ {
47
+ "word": "يتقي",
48
+ "diac": "يَتَّقِي",
49
+ "lex": "ٱِتَّقَى",
50
+ "pos": "verb",
51
+ "per": "3",
52
+ "num": "s",
53
+ "gen": "m",
54
+ "root": "#.ق.#",
55
+ "stem": "تَّقِي"
56
+ },
57
+ {
58
+ "word": "يسعون",
59
+ "diac": "يَسْعَُونَ",
60
+ "lex": "سَعَى",
61
+ "pos": "verb",
62
+ "per": "3",
63
+ "num": "p",
64
+ "gen": "m",
65
+ "root": "س.ع.#",
66
+ "stem": "سْعَ"
67
+ },
68
+ {
69
+ "word": "يذهبون",
70
+ "diac": "يَذْهَبُونَ",
71
+ "lex": "ذَهَب",
72
+ "pos": "verb",
73
+ "per": "3",
74
+ "num": "p",
75
+ "gen": "m",
76
+ "root": "ذ.ه.ب",
77
+ "stem": "ذْهَب"
78
+ },
79
+ {
80
+ "word": "تذهبين",
81
+ "diac": "تَذْهَبِينَ",
82
+ "lex": "ذَهَب",
83
+ "pos": "verb",
84
+ "per": "2",
85
+ "num": "s",
86
+ "gen": "f",
87
+ "root": "ذ.ه.ب",
88
+ "stem": "ذْهَب"
89
+ },
90
+ {
91
+ "word": "تذهبان",
92
+ "diac": "تَذْهَبانِ",
93
+ "lex": "ذَهَب",
94
+ "pos": "verb",
95
+ "per": "2",
96
+ "num": "d",
97
+ "gen": "m",
98
+ "root": "ذ.ه.ب",
99
+ "stem": "ذْهَب"
100
+ },
101
+ {
102
+ "word": "متي",
103
+ "diac": "مَتَى",
104
+ "lex": "مَتَى",
105
+ "pos": "conj",
106
+ "per": "na",
107
+ "num": "na",
108
+ "gen": "na",
109
+ "root": "م.ت.#",
110
+ "stem": "مَتَى"
111
+ }
112
+ ]
debug_pipeline.py CHANGED
@@ -1,6 +1,7 @@
1
  import sys
 
 
2
  from pathlib import Path
3
-
4
  # Add src to python path
5
  sys.path.insert(0, str(Path(r'c:\Users\dell\PycharmProjects\JupyterProject1\PythonProject\BAYAN\src')))
6
 
 
1
  import sys
2
+ import io
3
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
4
  from pathlib import Path
 
5
  # Add src to python path
6
  sys.path.insert(0, str(Path(r'c:\Users\dell\PycharmProjects\JupyterProject1\PythonProject\BAYAN\src')))
7
 
local_debug.txt ADDED
File without changes
out.txt ADDED
Binary file (42.2 kB). View file
 
out2.txt ADDED
Binary file (46 kB). View file
 
pc_data.txt ADDED
Binary file (3.58 kB). View file
 
src/nlp/grammar/grammar_rules.py CHANGED
@@ -53,18 +53,6 @@ class ArabicGrammarGuard:
53
  if w1_word.endswith('ون'): corrected_tokens[i] = w1_word[:-2]
54
  elif w1_word.endswith('وا'): corrected_tokens[i] = w1_word[:-2]
55
 
56
- elif w1_pos == 'noun' and w2_pos == 'verb':
57
- if w1_word.endswith('ون') and not (w2_word.endswith('ون') or w2_word.endswith('وا') or w2_word.endswith('ين')):
58
- if w2_info.analysis.get('num') == 's':
59
- corrected_tokens[i+1] = w2_word + 'ون'
60
-
61
- # Match adjectives (adj) only; skip words starting with ب or ending with alef tanween
62
- elif w1_pos == 'noun' and w2_pos == 'adj':
63
- if w1_word.endswith('ون') and not w2_word.endswith('ون'):
64
- if w2_info.analysis.get('num') == 's' and w2_info.analysis.get('gen') == 'm':
65
- if len(w2_word) > 2 and not w2_word.endswith('ا') and not w2_word.startswith('ب'):
66
- corrected_tokens[i+1] = w2_word + 'ون'
67
-
68
  return " ".join(corrected_tokens)
69
 
70
  def smart_asmaa_khamsa_fix(self, text):
@@ -369,9 +357,8 @@ class ArabicGrammarGuard:
369
  verb_num = verb_info.analysis.get('num', 's')
370
 
371
  # Skip if verb is already plural
372
- if verb_num != 's':
373
- continue
374
-
375
  # Only trigger on CONFIRMED plurals:
376
  # 1. Known broken plural nouns (hardcoded list)
377
  # 2. Sound masculine plural ending in ون/ين
@@ -429,9 +416,13 @@ class ArabicGrammarGuard:
429
  is_plural_fem = True
430
  else:
431
  is_plural_masc = True
432
-
 
433
  if not is_plural_masc and not is_plural_fem:
434
- continue
 
 
 
435
 
436
  # Fix the verb to agree with the plural subject
437
  # Detect if verb is present tense (starts with ي/ت/ن/أ)
@@ -453,12 +444,15 @@ class ArabicGrammarGuard:
453
  if verb_word.endswith('وَ'):
454
  verb_word = verb_word[:-1]
455
  corrected_tokens[i+1] = verb_word + 'ون'
 
 
 
456
  else:
457
  # Past tense: ذهب→ذهبوا (masc) / ذهبن (fem)
458
  if is_plural_fem:
459
  if verb_word.endswith('وا') or verb_word.endswith('ون'):
460
  verb_word = verb_word[:-2]
461
- elif verb_word.endswith('ت') or verb_word.endswith('تْ') or verb_word.endswith('تَ'):
462
  verb_word = verb_word[:-1]
463
  if not verb_word.endswith('ن') and not verb_word.endswith('نَ'):
464
  if verb_word.endswith('ى') or verb_word.endswith('ا'):
@@ -476,21 +470,31 @@ class ArabicGrammarGuard:
476
  elif verb_word.endswith('ى') or verb_word.endswith('ا'):
477
  verb_word = verb_word[:-1]
478
  corrected_tokens[i+1] = verb_word + 'وا'
 
 
 
 
 
479
 
480
  return " ".join(corrected_tokens)
481
 
482
  def regex_rules_fallback(self, text):
 
 
 
 
 
483
  # إن وأخواتها
484
  text = re.sub(r'\b(إن|أن|كأن|لكن|لعل|ليت|ان|كان)\s+(أبوك|ابوك|أخوك|اخوك|ذو|فوك)\b',
485
- lambda m: f"{m.group(1)} {m.group(2).replace('و', 'ا')}", text)
486
 
487
  # الأفعال المتعدية (Object position)
488
  text = re.sub(r'\b(رأيت|شاهدت|قابلت|زرت|سمعت|عرفت|وجدت|أحب|أكرمت|صادفت)\s+(أبوك|ابوك|أخوك|اخوك|ذو|فوك)\b',
489
- lambda m: f"{m.group(1)} {m.group(2).replace('و', 'ا')}", text)
490
 
491
  # حروف الجر المنفصلة بمسافة (في أخوك -> في أخيك)
492
  text = re.sub(r'\b([وف]?(?:في|من|إلى|الي|على|علي|عن))\s+(أبوك|ابوك|أباك|اباك|أخوك|اخوك|أخاك|اخاك|ذو|ذا)\b',
493
- lambda m: f"{m.group(1)} {m.group(2).replace('و', 'ي').replace('ا', 'ي')}", text)
494
 
495
  # حروف الجر المتصلة بدون مسافة (بأخوك، لأبوك -> بأخيك، لأبيك)
496
  text = re.sub(r'\b([وف]?[بل])(أبوك|ابوك|أباك|اباك|أخوك|اخوك|أخاك|اخاك|ذو|ذا)\b',
@@ -500,6 +504,11 @@ class ArabicGrammarGuard:
500
  # were REMOVED because they caused massive overcorrection on correct text.
501
  # These patterns are handled by CamelTools-based rules (fix_prepositions_advanced,
502
  # fix_verbs_nasb_and_jazm) which have POS-tag awareness.
 
 
 
 
 
503
 
504
  return text
505
 
 
53
  if w1_word.endswith('ون'): corrected_tokens[i] = w1_word[:-2]
54
  elif w1_word.endswith('وا'): corrected_tokens[i] = w1_word[:-2]
55
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  return " ".join(corrected_tokens)
57
 
58
  def smart_asmaa_khamsa_fix(self, text):
 
357
  verb_num = verb_info.analysis.get('num', 's')
358
 
359
  # Skip if verb is already plural
360
+ # Removed singular verb check to allow fixing gender mismatch on already plural verbs (e.g. البنات يذهبون -> يذهبن)
361
+
 
362
  # Only trigger on CONFIRMED plurals:
363
  # 1. Known broken plural nouns (hardcoded list)
364
  # 2. Sound masculine plural ending in ون/ين
 
416
  is_plural_fem = True
417
  else:
418
  is_plural_masc = True
419
+
420
+ is_singular_fem = False
421
  if not is_plural_masc and not is_plural_fem:
422
+ if noun_gen == 'f' or noun_word.endswith('ة') or noun_word in KNOWN_FEMININE_NOUNS:
423
+ is_singular_fem = True
424
+ else:
425
+ continue
426
 
427
  # Fix the verb to agree with the plural subject
428
  # Detect if verb is present tense (starts with ي/ت/ن/أ)
 
444
  if verb_word.endswith('وَ'):
445
  verb_word = verb_word[:-1]
446
  corrected_tokens[i+1] = verb_word + 'ون'
447
+ elif is_singular_fem:
448
+ if verb_word.startswith('ي'):
449
+ corrected_tokens[i+1] = 'ت' + verb_word[1:]
450
  else:
451
  # Past tense: ذهب→ذهبوا (masc) / ذهبن (fem)
452
  if is_plural_fem:
453
  if verb_word.endswith('وا') or verb_word.endswith('ون'):
454
  verb_word = verb_word[:-2]
455
+ elif verb_word.endswith('ت') or verb_word.endswith('تْ') or verb_word.endswith('تَ') or verb_word.endswith('و'):
456
  verb_word = verb_word[:-1]
457
  if not verb_word.endswith('ن') and not verb_word.endswith('نَ'):
458
  if verb_word.endswith('ى') or verb_word.endswith('ا'):
 
470
  elif verb_word.endswith('ى') or verb_word.endswith('ا'):
471
  verb_word = verb_word[:-1]
472
  corrected_tokens[i+1] = verb_word + 'وا'
473
+ elif is_singular_fem:
474
+ if not verb_word.endswith('ت') and not verb_word.endswith('تْ') and not verb_word.endswith('تَ'):
475
+ if verb_word.endswith('ى'):
476
+ verb_word = verb_word[:-1] + 'ا'
477
+ corrected_tokens[i+1] = verb_word + 'ت'
478
 
479
  return " ".join(corrected_tokens)
480
 
481
  def regex_rules_fallback(self, text):
482
+ def _add_hamza(word):
483
+ if word.startswith('ا') and not word.startswith('ال'):
484
+ return 'أ' + word[1:]
485
+ return word
486
+
487
  # إن وأخواتها
488
  text = re.sub(r'\b(إن|أن|كأن|لكن|لعل|ليت|ان|كان)\s+(أبوك|ابوك|أخوك|اخوك|ذو|فوك)\b',
489
+ lambda m: f"{m.group(1)} {_add_hamza(m.group(2)).replace('و', 'ا')}", text)
490
 
491
  # الأفعال المتعدية (Object position)
492
  text = re.sub(r'\b(رأيت|شاهدت|قابلت|زرت|سمعت|عرفت|وجدت|أحب|أكرمت|صادفت)\s+(أبوك|ابوك|أخوك|اخوك|ذو|فوك)\b',
493
+ lambda m: f"{m.group(1)} {_add_hamza(m.group(2)).replace('و', 'ا')}", text)
494
 
495
  # حروف الجر المنفصلة بمسافة (في أخوك -> في أخيك)
496
  text = re.sub(r'\b([وف]?(?:في|من|إلى|الي|على|علي|عن))\s+(أبوك|ابوك|أباك|اباك|أخوك|اخوك|أخاك|اخاك|ذو|ذا)\b',
497
+ lambda m: f"{m.group(1)} {_add_hamza(m.group(2)).replace('و', 'ي').replace('ا', 'ي')}", text)
498
 
499
  # حروف الجر المتصلة بدون مسافة (بأخوك، لأبوك -> بأخيك، لأبيك)
500
  text = re.sub(r'\b([وف]?[بل])(أبوك|ابوك|أباك|اباك|أخوك|اخوك|أخاك|اخاك|ذو|ذا)\b',
 
504
  # were REMOVED because they caused massive overcorrection on correct text.
505
  # These patterns are handled by CamelTools-based rules (fix_prepositions_advanced,
506
  # fix_verbs_nasb_and_jazm) which have POS-tag awareness.
507
+
508
+ # FIX-PC010: Add targeted safe regex for Nasb/Jazm particles + verb
509
+ # Only match clear present tense verbs starting with ي/ت/ن/أ and ending in ون
510
+ text = re.sub(r'\b(أن|ان|لن|كي|حتى|لم|لما)\s+([يتا][\u0600-\u06FF]{2,})ون\b',
511
+ r'\1 \2وا', text)
512
 
513
  return text
514
 
test_failures.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import io
3
+ import sys
4
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
5
+ with open('tests/phase10/reports/collision_benchmark_results.json', 'r', encoding='utf-8') as f:
6
+ data = json.load(f)
7
+ for r in data['results']:
8
+ if r.get('pipeline_verdict') != 'PASS':
9
+ print(f"{r['id']} | {r['category']} | Expected: {r['expected']} | Actual: {r.get('pipeline_output', '')}")
test_grammar_logic.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import codecs
3
+ from pathlib import Path
4
+
5
+ sys.stdout.reconfigure(encoding='utf-8')
6
+
7
+ # Add src to python path
8
+ sys.path.insert(0, str(Path(r'c:\Users\dell\PycharmProjects\JupyterProject1\PythonProject\BAYAN\src')))
9
+
10
+ from nlp.grammar.grammar_rules import ArabicGrammarGuard
11
+
12
+ guard = ArabicGrammarGuard()
13
+
14
+ tests = [
15
+ "المهندسون صممت المشروع", # PC001 spelling output
16
+ "البنات يذهبون إلى المدرسة", # PC003 spelling output
17
+ "أن ابوك رجل طيب", # PC007 spelling output
18
+ "العمال بنى المبنى الجديد", # PC020 spelling output
19
+ "الامهات طبخ الطعام والاطفال لعب", # PC043 spelling output
20
+ "المديره وافق علي المشروع والموظفات وافق أيضا" # PC046
21
+ ]
22
+
23
+ for t in tests:
24
+ print(f"\n[IN] {t}")
25
+ out = guard.apply_rules(t)
26
+ print(f"[OUT] {out}")
27
+
test_pc.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import sys
3
+ import unittest
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(r'c:\Users\dell\PycharmProjects\JupyterProject1\PythonProject\BAYAN\src')))
6
+
7
+ import logging
8
+ from app import app as flask_app
9
+
10
+ class TestPC004(unittest.TestCase):
11
+ def test_pc004(self):
12
+ text = "الرجال يعملون في المصنعو"
13
+ client = flask_app.test_client()
14
+ response = client.post('/api/analyze', json={'text': text})
15
+ res = response.get_json()
16
+ print("Final:", res.get('corrected_text', res))
17
+
18
+ if __name__ == '__main__':
19
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
20
+ unittest.main()
test_sv.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import sys
3
+ import unittest
4
+ from pathlib import Path
5
+ sys.path.insert(0, str(Path(r'c:\Users\dell\PycharmProjects\JupyterProject1\PythonProject\BAYAN\src')))
6
+
7
+ from app import apply_patches
8
+
9
+ class TestPC003(unittest.TestCase):
10
+ def test_pc003(self):
11
+ orig_text = "البنات يذهبون الي المدرسه"
12
+ spelling_corr = "البنات يذهبون إلى المدرسة"
13
+ grammar_corr = "البنات يذهبن إلى المدرسة"
14
+
15
+ from src.nlp.stage_locker import StageLocker
16
+ class MockCtx:
17
+ def __init__(self):
18
+ self.stage_locker = StageLocker()
19
+ self.current_text = orig_text
20
+ from nlp.offset_mapper import OffsetMapper
21
+ self._mapper = OffsetMapper()
22
+ self._mapper.init(orig_text)
23
+
24
+ def add_patch(self, stage, start, end, corr, **kwargs):
25
+ self.stage_locker.add_patch(stage, start, end)
26
+ orig_start, orig_end = self._mapper.map_range(start, end)
27
+ print(f"Adding patch: {self.current_text[start:end]} -> {corr} (mapped to {orig_start}:{orig_end})")
28
+
29
+ def mutate_text(self, new_text, mapper_cls):
30
+ self.current_text = new_text
31
+ self._mapper.init(new_text)
32
+
33
+ ctx = MockCtx()
34
+
35
+ # Apply spelling
36
+ apply_patches(orig_text, orig_text, spelling_corr, "spelling", ctx)
37
+ ctx.mutate_text(spelling_corr, None)
38
+
39
+ # Apply grammar
40
+ final = apply_patches(orig_text, spelling_corr, grammar_corr, "grammar", ctx)
41
+ print("Final:", final)
42
+
43
+ if __name__ == '__main__':
44
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
45
+ unittest.main()
tests/phase10/reports/collision_benchmark_results.json CHANGED
The diff for this file is too large to render. See raw diff