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
Files changed (1) hide show
  1. 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: