Bayan AI commited on
Commit ·
b3209ae
1
Parent(s): 2832a7b
Fix: Enhance Jazm and Nasb logic with camel_tools for Af'al Khamsa and defective verbs
Browse files- src/nlp/grammar/grammar_rules.py +82 -39
src/nlp/grammar/grammar_rules.py
CHANGED
|
@@ -94,11 +94,85 @@ class ArabicGrammarGuard:
|
|
| 94 |
|
| 95 |
return " ".join(corrected_tokens)
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
def fix_verbs_nasb_and_jazm(self, text):
|
| 98 |
tokens = simple_word_tokenize(text)
|
| 99 |
disambig_tokens = self.mle.disambiguate(tokens)
|
| 100 |
|
| 101 |
-
nasb_particles = ['أن', 'لن', 'كي', 'لكي', 'حتى', 'إذن']
|
| 102 |
jazm_particles = ['لم', 'لما', 'لا']
|
| 103 |
|
| 104 |
corrected_tokens = []
|
|
@@ -120,37 +194,10 @@ class ArabicGrammarGuard:
|
|
| 120 |
|
| 121 |
is_present_tense = word.startswith('ي') or word.startswith('ت') or word.startswith('ن') or word.startswith('أ')
|
| 122 |
if (pos_tag == 'verb' or is_present_tense) and (is_nasb_context or is_jazm_context):
|
| 123 |
-
if
|
| 124 |
-
word = word
|
| 125 |
-
elif word.endswith('ان'):
|
| 126 |
-
word = word[:-2] + 'ا'
|
| 127 |
-
elif word.endswith('ين'):
|
| 128 |
-
word = word[:-2] + 'ي'
|
| 129 |
-
elif is_jazm_context:
|
| 130 |
-
if word.endswith('و') and len(word) >= 3:
|
| 131 |
-
word = word[:-1] + 'ُ'
|
| 132 |
-
elif word.endswith('ي') or word.endswith('i'):
|
| 133 |
-
stem = word[:-1]
|
| 134 |
-
fatha_stems = {'يسع', 'تسع', 'أسع', 'نسع',
|
| 135 |
-
'يخش', 'تخش', 'أخش', 'نخش',
|
| 136 |
-
'يرض', 'ترض', 'أرض', 'نرض',
|
| 137 |
-
'ينس', 'تنس', 'أنس', 'ننس',
|
| 138 |
-
'يبق', 'تبق', 'أبق', 'نبق',
|
| 139 |
-
'ير', 'تر', 'أر', 'نر',
|
| 140 |
-
'يلق', 'تلق', 'ألق', 'نلق',
|
| 141 |
-
'ينه', 'تنه', 'أنه', 'ننه'}
|
| 142 |
-
if stem in fatha_stems:
|
| 143 |
-
word = stem + 'َ'
|
| 144 |
-
elif len(word) > 3:
|
| 145 |
-
word = stem + 'ِ'
|
| 146 |
-
elif (word.endswith('ى') or word.endswith('ا')) and len(word) >= 3:
|
| 147 |
-
if not word.endswith('وا'):
|
| 148 |
-
word = word[:-1] + 'َ'
|
| 149 |
elif is_nasb_context:
|
| 150 |
-
|
| 151 |
-
word = word + 'َ'
|
| 152 |
-
elif word.endswith('ي') and len(word) > 3:
|
| 153 |
-
word = word + 'َ'
|
| 154 |
|
| 155 |
corrected_tokens.append(word)
|
| 156 |
return " ".join(corrected_tokens)
|
|
@@ -445,7 +492,7 @@ class ArabicGrammarGuard:
|
|
| 445 |
return text
|
| 446 |
|
| 447 |
def fix_conditional_sentences(self, text):
|
| 448 |
-
conditional_particles = {'إن', 'من', 'ما', 'متى', 'مهما', 'أينما', 'حيثما', 'أيان', 'كيفما', 'أنى'}
|
| 449 |
tokens = simple_word_tokenize(text)
|
| 450 |
disambig_tokens = self.mle.disambiguate(tokens)
|
| 451 |
corrected_tokens = list(tokens)
|
|
@@ -474,19 +521,15 @@ class ArabicGrammarGuard:
|
|
| 474 |
continue
|
| 475 |
|
| 476 |
if in_cond and pos_tag == 'verb':
|
| 477 |
-
# Apply jazm
|
| 478 |
-
|
| 479 |
-
word = word[:-2] + 'وا'
|
| 480 |
-
elif word.endswith('ان'):
|
| 481 |
-
word = word[:-2] + 'ا'
|
| 482 |
-
elif word.endswith('ين'):
|
| 483 |
-
word = word[:-2] + 'ي'
|
| 484 |
|
| 485 |
# Fix pronoun mismatch if 2nd person context exists
|
| 486 |
if has_2nd_person_context and word.startswith('ي') and (word.endswith('وا') or word.endswith('ا') or word.endswith('ي')):
|
| 487 |
word = 'ت' + word[1:]
|
| 488 |
|
| 489 |
corrected_tokens[i] = word
|
|
|
|
| 490 |
verbs_jazmed += 1
|
| 491 |
if verbs_jazmed >= 2:
|
| 492 |
in_cond = False
|
|
|
|
| 94 |
|
| 95 |
return " ".join(corrected_tokens)
|
| 96 |
|
| 97 |
+
def _apply_jazm_to_verb(self, word, token_info):
|
| 98 |
+
# 1. Handle Af'al Khamsa using camel_tools analysis
|
| 99 |
+
if token_info and token_info.analyses:
|
| 100 |
+
analysis = token_info.analyses[0].analysis
|
| 101 |
+
num = analysis.get('num', 's')
|
| 102 |
+
per = analysis.get('per', '3')
|
| 103 |
+
gen = analysis.get('gen', 'm')
|
| 104 |
+
|
| 105 |
+
if num == 'p' and gen == 'm':
|
| 106 |
+
if word.endswith('ون'):
|
| 107 |
+
return word[:-2] + 'وا'
|
| 108 |
+
elif num == 'd':
|
| 109 |
+
if word.endswith('ان'):
|
| 110 |
+
return word[:-2] + 'ا'
|
| 111 |
+
elif num == 's' and per == '2' and gen == 'f':
|
| 112 |
+
if word.endswith('ين'):
|
| 113 |
+
return word[:-2] + 'ي'
|
| 114 |
+
|
| 115 |
+
# 2. Handle defective verbs in jazm context
|
| 116 |
+
match = re.search(r'^(.*?)([يوىاَُِْ]?)$', word)
|
| 117 |
+
if match:
|
| 118 |
+
stem = match.group(1)
|
| 119 |
+
ending = match.group(2)
|
| 120 |
+
|
| 121 |
+
fatha_bases = ['سع', 'خش', 'رض', 'نس', 'بق', 'ر', 'نه', 'حظ', 'رع', 'أب', 'تمن', 'لق', 'هو', 'سل']
|
| 122 |
+
damma_bases = ['دع', 'رج', 'شك', 'نم', 'غز', 'عف', 'سم', 'دن', 'بد', 'خل', 'عل']
|
| 123 |
+
kasra_bases = ['مش', 'جر', 'قض', 'بك', 'هد', 'رم', 'أت', 'بن', 'ق', 'وف', 'شف', 'غن', 'عط', 'تق', 'شتر', 'عتن', 'ستدع', 'نته', 'رو']
|
| 124 |
+
|
| 125 |
+
fatha_stems = {p + b for p in ['ي', 'ت', 'أ', 'ن'] for b in fatha_bases}
|
| 126 |
+
damma_stems = {p + b for p in ['ي', 'ت', 'أ', 'ن'] for b in damma_bases}
|
| 127 |
+
kasra_stems = {p + b for p in ['ي', 'ت', 'أ', 'ن'] for b in kasra_bases}
|
| 128 |
+
|
| 129 |
+
if stem in fatha_stems:
|
| 130 |
+
return stem + 'َ'
|
| 131 |
+
elif stem in damma_stems:
|
| 132 |
+
return stem + 'ُ'
|
| 133 |
+
elif stem in kasra_stems:
|
| 134 |
+
return stem + 'ِ'
|
| 135 |
+
elif ending == 'و' and len(stem) >= 2:
|
| 136 |
+
return stem + 'ُ'
|
| 137 |
+
elif ending == 'ي' and len(stem) >= 2:
|
| 138 |
+
return stem + 'ِ'
|
| 139 |
+
elif (ending == 'ى' or ending == 'ا') and len(stem) >= 2:
|
| 140 |
+
if not word.endswith('وا'):
|
| 141 |
+
return stem + 'َ'
|
| 142 |
+
|
| 143 |
+
return word
|
| 144 |
+
|
| 145 |
+
def _apply_nasb_to_verb(self, word, token_info):
|
| 146 |
+
# 1. Handle Af'al Khamsa using camel_tools analysis
|
| 147 |
+
if token_info and token_info.analyses:
|
| 148 |
+
analysis = token_info.analyses[0].analysis
|
| 149 |
+
num = analysis.get('num', 's')
|
| 150 |
+
per = analysis.get('per', '3')
|
| 151 |
+
gen = analysis.get('gen', 'm')
|
| 152 |
+
|
| 153 |
+
if num == 'p' and gen == 'm':
|
| 154 |
+
if word.endswith('ون'):
|
| 155 |
+
return word[:-2] + 'وا'
|
| 156 |
+
elif num == 'd':
|
| 157 |
+
if word.endswith('ان'):
|
| 158 |
+
return word[:-2] + 'ا'
|
| 159 |
+
elif num == 's' and per == '2' and gen == 'f':
|
| 160 |
+
if word.endswith('ين'):
|
| 161 |
+
return word[:-2] + 'ي'
|
| 162 |
+
|
| 163 |
+
# 2. Handle defective verbs in nasb
|
| 164 |
+
if word.endswith('و') and len(word) > 3:
|
| 165 |
+
return word + 'َ'
|
| 166 |
+
elif word.endswith('ي') and len(word) > 3:
|
| 167 |
+
return word + 'َ'
|
| 168 |
+
|
| 169 |
+
return word
|
| 170 |
+
|
| 171 |
def fix_verbs_nasb_and_jazm(self, text):
|
| 172 |
tokens = simple_word_tokenize(text)
|
| 173 |
disambig_tokens = self.mle.disambiguate(tokens)
|
| 174 |
|
| 175 |
+
nasb_particles = ['أن', 'ان', 'لن', 'كي', 'لكي', 'حتى', 'حتي', 'إذن', 'اذا']
|
| 176 |
jazm_particles = ['لم', 'لما', 'لا']
|
| 177 |
|
| 178 |
corrected_tokens = []
|
|
|
|
| 194 |
|
| 195 |
is_present_tense = word.startswith('ي') or word.startswith('ت') or word.startswith('ن') or word.startswith('أ')
|
| 196 |
if (pos_tag == 'verb' or is_present_tense) and (is_nasb_context or is_jazm_context):
|
| 197 |
+
if is_jazm_context:
|
| 198 |
+
word = self._apply_jazm_to_verb(word, token_info)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
elif is_nasb_context:
|
| 200 |
+
word = self._apply_nasb_to_verb(word, token_info)
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
corrected_tokens.append(word)
|
| 203 |
return " ".join(corrected_tokens)
|
|
|
|
| 492 |
return text
|
| 493 |
|
| 494 |
def fix_conditional_sentences(self, text):
|
| 495 |
+
conditional_particles = {'إن', 'ان', 'من', 'ما', 'متى', 'متي', 'مهما', 'أينما', 'حيثما', 'أيان', 'ايان', 'كيفما', 'أنى', 'اني'}
|
| 496 |
tokens = simple_word_tokenize(text)
|
| 497 |
disambig_tokens = self.mle.disambiguate(tokens)
|
| 498 |
corrected_tokens = list(tokens)
|
|
|
|
| 521 |
continue
|
| 522 |
|
| 523 |
if in_cond and pos_tag == 'verb':
|
| 524 |
+
# Apply jazm using the comprehensive camel_tools helper
|
| 525 |
+
word = self._apply_jazm_to_verb(word, token_info)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
|
| 527 |
# Fix pronoun mismatch if 2nd person context exists
|
| 528 |
if has_2nd_person_context and word.startswith('ي') and (word.endswith('وا') or word.endswith('ا') or word.endswith('ي')):
|
| 529 |
word = 'ت' + word[1:]
|
| 530 |
|
| 531 |
corrected_tokens[i] = word
|
| 532 |
+
# Increment jazmed verbs counter (handles both فعل الشرط and جواب الشرط)
|
| 533 |
verbs_jazmed += 1
|
| 534 |
if verbs_jazmed >= 2:
|
| 535 |
in_cond = False
|