Bayan AI commited on
Commit ·
52e630f
1
Parent(s): d7619b9
Fix: Reorder Jaccard filter in app.py to run after evaluating grammar bypass rules
Browse files- src/app.py +60 -59
src/app.py
CHANGED
|
@@ -2006,6 +2006,64 @@ def analyze_text():
|
|
| 2006 |
)
|
| 2007 |
continue
|
| 2008 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2009 |
# ── FIX-27a: Grammar structured data protection ──
|
| 2010 |
# Block grammar diffs where the original contains digits.
|
| 2011 |
# The grammar model corrupts dates/numbers/times/percentages.
|
|
@@ -2023,7 +2081,7 @@ def analyze_text():
|
|
| 2023 |
# Block grammar diffs where the correction is too different
|
| 2024 |
# from the original (character-level Jaccard < 0.5).
|
| 2025 |
# Catches: القانون→القانين, يعزف→يعزفون, للإنسان→للإنسين
|
| 2026 |
-
if orig_text and corr_text and len(orig_text) > 2:
|
| 2027 |
import re as _re_jac
|
| 2028 |
# Strip punctuation/spaces for comparison
|
| 2029 |
_o_chars = set(_re_jac.sub(r'[\s.,،؛؟!:;?]', '', orig_text))
|
|
@@ -2042,7 +2100,7 @@ def analyze_text():
|
|
| 2042 |
# ── FIX-06: Directional block protection for grammar ──
|
| 2043 |
# Prevents meaning-changing substitutions (كان→كأن etc.)
|
| 2044 |
# especially critical when spelling is skipped (>1000 chars).
|
| 2045 |
-
if corr_text in _DIRECTIONAL_BLOCKS.get(orig_text, set()):
|
| 2046 |
logger.info(
|
| 2047 |
f"[GRAMMAR] Directional block: '{orig_text}'→'{corr_text}'"
|
| 2048 |
)
|
|
@@ -2066,63 +2124,6 @@ def analyze_text():
|
|
| 2066 |
if _gram_dir_blocked:
|
| 2067 |
continue
|
| 2068 |
|
| 2069 |
-
# ── Phase 4 (BUG-033/E10): Grammar output sanity check ──
|
| 2070 |
-
# Reject grammar corrections that produce a non-word when
|
| 2071 |
-
# the original was already a valid word. Mirrors spelling filter.
|
| 2072 |
-
# BUT: bypass for known grammar patterns (case endings, nasb, etc.)
|
| 2073 |
-
_is_grammar_pattern = False
|
| 2074 |
-
if orig_text and corr_text:
|
| 2075 |
-
# Case: ون/ان → ين (sound masculine plural case)
|
| 2076 |
-
if (orig_text.endswith('ون') and corr_text.endswith('ين') and
|
| 2077 |
-
orig_text[:-2] == corr_text[:-2]):
|
| 2078 |
-
_is_grammar_pattern = True
|
| 2079 |
-
# Nasb/Jazm: ون → وا (verb mood)
|
| 2080 |
-
elif (orig_text.endswith('ون') and corr_text.endswith('وا') and
|
| 2081 |
-
orig_text[:-2] == corr_text[:-2]):
|
| 2082 |
-
_is_grammar_pattern = True
|
| 2083 |
-
# Five nouns: وك → اك/يك (أبوك→أباك, أخوك→أخيك)
|
| 2084 |
-
elif (len(orig_text) >= 3 and len(corr_text) >= 3 and
|
| 2085 |
-
orig_text[-2:] in ('وك', 'وه') and
|
| 2086 |
-
corr_text[-2:] in ('اك', 'يك', 'اه', 'يه')):
|
| 2087 |
-
_is_grammar_pattern = True
|
| 2088 |
-
# Dual: ان → ين (dual oblique)
|
| 2089 |
-
elif (orig_text.endswith('ان') and corr_text.endswith('ين') and
|
| 2090 |
-
orig_text[:-2] == corr_text[:-2] and len(orig_text) >= 4):
|
| 2091 |
-
_is_grammar_pattern = True
|
| 2092 |
-
# Demonstrative: هذان→هاتان, هاتان→هذان
|
| 2093 |
-
elif ({orig_text, corr_text} <= {'هذان', 'هاتان'}):
|
| 2094 |
-
_is_grammar_pattern = True
|
| 2095 |
-
# ── NEW: SV agreement suffix additions ──
|
| 2096 |
-
# Past tense masc plural: verb→verb+وا (ذهب→ذهبوا, حضر→حضروا)
|
| 2097 |
-
elif (corr_text.endswith('وا') and corr_text[:-2] == orig_text
|
| 2098 |
-
and len(orig_text) >= 3):
|
| 2099 |
-
_is_grammar_pattern = True
|
| 2100 |
-
# Past tense fem plural: verb→verb+ن (ذهب→ذهبن, حضر→حضرن)
|
| 2101 |
-
elif (corr_text.endswith('ن') and corr_text[:-1] == orig_text
|
| 2102 |
-
and len(orig_text) >= 3):
|
| 2103 |
-
_is_grammar_pattern = True
|
| 2104 |
-
# Present tense fem plural: ون → ن (يلعبون → يلعبن or يلعبون → تلعبن)
|
| 2105 |
-
elif (orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…').endswith('ون') and corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…').endswith('ن') and len(orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')) >= 3):
|
| 2106 |
-
_o_cl = orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2107 |
-
_c_cl = corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2108 |
-
_o_stem = _o_cl[:-2]
|
| 2109 |
-
_c_stem = _c_cl[:-1]
|
| 2110 |
-
if _o_stem == _c_stem or (len(_o_stem) > 1 and _o_stem[1:] == _c_stem[1:] and _o_stem[0] in 'يت' and _c_stem[0] in 'يت'):
|
| 2111 |
-
_is_grammar_pattern = True
|
| 2112 |
-
# Present tense masc plural: يفعل→يفعلون (adding ون)
|
| 2113 |
-
elif (corr_text.endswith('ون') and corr_text[:-2] == orig_text
|
| 2114 |
-
and len(orig_text) >= 3):
|
| 2115 |
-
_is_grammar_pattern = True
|
| 2116 |
-
# Gender: adjective→adjective+ة (جميل→جميلة, كبير→كبيرة)
|
| 2117 |
-
elif (corr_text.endswith('ة') and corr_text[:-1] == orig_text
|
| 2118 |
-
and len(orig_text) >= 3):
|
| 2119 |
-
_is_grammar_pattern = True
|
| 2120 |
-
# Gender with ي: ذكي→ذكية
|
| 2121 |
-
elif (corr_text.endswith('ية') and corr_text[:-1] == orig_text[:-1] + 'ي'
|
| 2122 |
-
and orig_text.endswith('ي') and len(orig_text) >= 3):
|
| 2123 |
-
_is_grammar_pattern = True
|
| 2124 |
-
|
| 2125 |
-
|
| 2126 |
if not _is_grammar_pattern:
|
| 2127 |
if len(orig_text.split()) == 1 and len(corr_text.split()) == 1:
|
| 2128 |
try:
|
|
|
|
| 2006 |
)
|
| 2007 |
continue
|
| 2008 |
|
| 2009 |
+
# Evaluate grammar patterns early to bypass heuristic blocks.
|
| 2010 |
+
_is_grammar_pattern = False
|
| 2011 |
+
if orig_text and corr_text:
|
| 2012 |
+
# Case: ون/ان → ين (sound masculine plural case)
|
| 2013 |
+
if (orig_text.endswith('ون') and corr_text.endswith('ين') and
|
| 2014 |
+
orig_text[:-2] == corr_text[:-2]):
|
| 2015 |
+
_is_grammar_pattern = True
|
| 2016 |
+
# Nasb/Jazm: ون → وا (verb mood)
|
| 2017 |
+
elif (orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…').endswith('ون') and corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…').endswith('وا') and len(orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')) >= 3):
|
| 2018 |
+
_o_cl = orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2019 |
+
_c_cl = corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2020 |
+
_o_stem = _o_cl[:-2]
|
| 2021 |
+
_c_stem = _c_cl[:-2]
|
| 2022 |
+
if _o_stem == _c_stem or (len(_o_stem) > 1 and _o_stem[1:] == _c_stem[1:] and _o_stem[0] in 'يت' and _c_stem[0] in 'يت'):
|
| 2023 |
+
_is_grammar_pattern = True
|
| 2024 |
+
# Five nouns: وك → اك/يك (أبوك→أباك, أخوك→أخيك)
|
| 2025 |
+
elif (len(orig_text) >= 3 and len(corr_text) >= 3 and
|
| 2026 |
+
orig_text[-2:] in ('وك', 'وه') and
|
| 2027 |
+
corr_text[-2:] in ('اك', 'يك', 'اه', 'يه')):
|
| 2028 |
+
_is_grammar_pattern = True
|
| 2029 |
+
# Dual: ان → ين (dual oblique)
|
| 2030 |
+
elif (orig_text.endswith('ان') and corr_text.endswith('ين') and
|
| 2031 |
+
orig_text[:-2] == corr_text[:-2] and len(orig_text) >= 4):
|
| 2032 |
+
_is_grammar_pattern = True
|
| 2033 |
+
# Demonstrative: هذان→هاتان, هاتان→هذان
|
| 2034 |
+
elif ({orig_text, corr_text} <= {'هذان', 'هاتان'}):
|
| 2035 |
+
_is_grammar_pattern = True
|
| 2036 |
+
# ── NEW: SV agreement suffix additions ──
|
| 2037 |
+
# Past tense masc plural: verb→verb+وا (ذهب→ذهبوا, حضر→حضروا)
|
| 2038 |
+
elif (corr_text.endswith('وا') and corr_text[:-2] == orig_text
|
| 2039 |
+
and len(orig_text) >= 3):
|
| 2040 |
+
_is_grammar_pattern = True
|
| 2041 |
+
# Past tense fem plural: verb→verb+ن (ذهب→ذهبن, حضر→حضرن)
|
| 2042 |
+
elif (corr_text.endswith('ن') and corr_text[:-1] == orig_text
|
| 2043 |
+
and len(orig_text) >= 3):
|
| 2044 |
+
_is_grammar_pattern = True
|
| 2045 |
+
# Present tense fem plural: ون → ن (يلعبون → يلعبن or يلعبون → تلعبن)
|
| 2046 |
+
elif (orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…').endswith('ون') and corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…').endswith('ن') and len(orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')) >= 3):
|
| 2047 |
+
_o_cl = orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2048 |
+
_c_cl = corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2049 |
+
_o_stem = _o_cl[:-2]
|
| 2050 |
+
_c_stem = _c_cl[:-1]
|
| 2051 |
+
if _o_stem == _c_stem or (len(_o_stem) > 1 and _o_stem[1:] == _c_stem[1:] and _o_stem[0] in 'يت' and _c_stem[0] in 'يت'):
|
| 2052 |
+
_is_grammar_pattern = True
|
| 2053 |
+
# Present tense masc plural: يفعل→يفعلون (adding ون)
|
| 2054 |
+
elif (corr_text.endswith('ون') and corr_text[:-2] == orig_text
|
| 2055 |
+
and len(orig_text) >= 3):
|
| 2056 |
+
_is_grammar_pattern = True
|
| 2057 |
+
# Gender: adjective→adjective+ة (جميل→جميلة, كبير→كبيرة)
|
| 2058 |
+
elif (corr_text.endswith('ة') and corr_text[:-1] == orig_text
|
| 2059 |
+
and len(orig_text) >= 3):
|
| 2060 |
+
_is_grammar_pattern = True
|
| 2061 |
+
# Gender with ي: ذكي→ذكية
|
| 2062 |
+
elif (corr_text.endswith('ية') and corr_text[:-1] == orig_text[:-1] + 'ي'
|
| 2063 |
+
and orig_text.endswith('ي') and len(orig_text) >= 3):
|
| 2064 |
+
_is_grammar_pattern = True
|
| 2065 |
+
|
| 2066 |
+
|
| 2067 |
# ── FIX-27a: Grammar structured data protection ──
|
| 2068 |
# Block grammar diffs where the original contains digits.
|
| 2069 |
# The grammar model corrupts dates/numbers/times/percentages.
|
|
|
|
| 2081 |
# Block grammar diffs where the correction is too different
|
| 2082 |
# from the original (character-level Jaccard < 0.5).
|
| 2083 |
# Catches: القانون→القانين, يعزف→يعزفون, للإنسان→للإنسين
|
| 2084 |
+
if not _is_grammar_pattern and orig_text and corr_text and len(orig_text) > 2:
|
| 2085 |
import re as _re_jac
|
| 2086 |
# Strip punctuation/spaces for comparison
|
| 2087 |
_o_chars = set(_re_jac.sub(r'[\s.,،؛؟!:;?]', '', orig_text))
|
|
|
|
| 2100 |
# ── FIX-06: Directional block protection for grammar ──
|
| 2101 |
# Prevents meaning-changing substitutions (كان→كأن etc.)
|
| 2102 |
# especially critical when spelling is skipped (>1000 chars).
|
| 2103 |
+
if not _is_grammar_pattern and corr_text in _DIRECTIONAL_BLOCKS.get(orig_text, set()):
|
| 2104 |
logger.info(
|
| 2105 |
f"[GRAMMAR] Directional block: '{orig_text}'→'{corr_text}'"
|
| 2106 |
)
|
|
|
|
| 2124 |
if _gram_dir_blocked:
|
| 2125 |
continue
|
| 2126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2127 |
if not _is_grammar_pattern:
|
| 2128 |
if len(orig_text.split()) == 1 and len(corr_text.split()) == 1:
|
| 2129 |
try:
|