Bayan AI commited on
Commit ·
ebd15e7
1
Parent(s): 76c4caf
refactor: simplify architecture and replace whitelists with CamelTools
Browse files- src/app.py +20 -88
- src/nlp/grammar/grammar_rules.py +0 -3
- src/nlp/punctuation/spelling/araspell_rules.py +7 -35
- src/nlp/spelling/araspell_rules.py +7 -35
src/app.py
CHANGED
|
@@ -81,6 +81,8 @@ logging.basicConfig(
|
|
| 81 |
)
|
| 82 |
logger = logging.getLogger(__name__)
|
| 83 |
|
|
|
|
|
|
|
| 84 |
# Initialize Flask app
|
| 85 |
app = Flask(__name__, static_folder='.', static_url_path='')
|
| 86 |
CORS(app, resources={r"/api/*": {"origins": "*"}}) # CORS for API routes only
|
|
@@ -1975,31 +1977,6 @@ def analyze_text():
|
|
| 1975 |
except Exception:
|
| 1976 |
pass # Bidirectional check is optional
|
| 1977 |
|
| 1978 |
-
# ── Phase 12 (A6): Safety Net — Raw Model Fallback ──
|
| 1979 |
-
# If raw model output has fewer OOV words, prefer it.
|
| 1980 |
-
try:
|
| 1981 |
-
_raw_oov = spell_checker.vocab_manager.count_oov_words(raw_corrected)
|
| 1982 |
-
_our_oov = spell_checker.vocab_manager.count_oov_words(safe_text)
|
| 1983 |
-
if _raw_oov == 0 and _our_oov > 0:
|
| 1984 |
-
logger.info(
|
| 1985 |
-
f"[SPELLING] Safety net: raw=0 OOV, ours={_our_oov} OOV "
|
| 1986 |
-
f"— using raw model output"
|
| 1987 |
-
)
|
| 1988 |
-
safe_text = raw_corrected
|
| 1989 |
-
elif _raw_oov == 0 and _our_oov == 0:
|
| 1990 |
-
# Both all-IV but raw is closer to input → prefer raw
|
| 1991 |
-
_raw_dist = _levenshtein(current_text, raw_corrected)
|
| 1992 |
-
_our_dist = _levenshtein(current_text, safe_text)
|
| 1993 |
-
_rvr_dist = _levenshtein(safe_text, raw_corrected)
|
| 1994 |
-
if _raw_dist < _our_dist and _rvr_dist <= 3:
|
| 1995 |
-
logger.info(
|
| 1996 |
-
f"[SPELLING] Safety net: raw closer to input "
|
| 1997 |
-
f"(raw_dist={_raw_dist}, our_dist={_our_dist})"
|
| 1998 |
-
)
|
| 1999 |
-
safe_text = raw_corrected
|
| 2000 |
-
except Exception:
|
| 2001 |
-
pass # Safety net is optional
|
| 2002 |
-
|
| 2003 |
ctx.mutate_text(safe_text, OffsetMapper)
|
| 2004 |
current_text = ctx.current_text
|
| 2005 |
except Exception as e:
|
|
@@ -2323,6 +2300,12 @@ def analyze_text():
|
|
| 2323 |
_o_cl = orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2324 |
_c_cl = corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2326 |
# Case: ون/ان → ين (sound masculine plural / dual case change)
|
| 2327 |
if (_o_cl.endswith('ون') and _c_cl.endswith('ين') and _o_cl[:-2] == _c_cl[:-2]):
|
| 2328 |
_is_grammar_pattern = True
|
|
@@ -2412,9 +2395,13 @@ def analyze_text():
|
|
| 2412 |
# Catches: القانون→القانين, يعزف→يعزفون, للإنسان→للإنسين
|
| 2413 |
if not _is_grammar_pattern and orig_text and corr_text and len(orig_text) > 2:
|
| 2414 |
import re as _re_jac
|
| 2415 |
-
# Strip punctuation/spaces for comparison
|
| 2416 |
-
|
| 2417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2418 |
if _o_chars and _c_chars:
|
| 2419 |
_jac = len(_o_chars & _c_chars) / len(_o_chars | _c_chars)
|
| 2420 |
if _jac < 0.5:
|
|
@@ -2451,8 +2438,11 @@ def analyze_text():
|
|
| 2451 |
_gram_dir_blocked = True
|
| 2452 |
break
|
| 2453 |
if _gram_dir_blocked:
|
| 2454 |
-
|
| 2455 |
-
|
|
|
|
|
|
|
|
|
|
| 2456 |
|
| 2457 |
# FIX-22: Protect tanween (preserve ً ٌ ٍ from original)
|
| 2458 |
_TANWEEN_CHARS = set('ًٌٍ')
|
|
@@ -2508,64 +2498,6 @@ def analyze_text():
|
|
| 2508 |
logger.error(traceback.format_exc())
|
| 2509 |
timing_ms['grammar_error'] = f"{type(e).__name__}: {str(e)[:200]}"
|
| 2510 |
|
| 2511 |
-
# ── FIX-48v3: ه→ة pass AFTER grammar (whitelist-based) ──
|
| 2512 |
-
# Must run AFTER grammar so grammar model can use ه for gender decisions.
|
| 2513 |
-
# Uses a whitelist of common words that are frequently written with ه instead of ة.
|
| 2514 |
-
if not _is_religious_text:
|
| 2515 |
-
try:
|
| 2516 |
-
_HATA_WHITELIST = {
|
| 2517 |
-
# Common nouns — definite form (with ال)
|
| 2518 |
-
'الحكومه': 'الحكومة', 'المدرسه': 'المدرسة', 'الشركه': 'الشركة',
|
| 2519 |
-
'الجامعه': 'الجامعة', 'المدينه': 'المدينة', 'القصه': 'القصة',
|
| 2520 |
-
'المكتبه': 'المكتبة', 'الطائره': 'الطائرة', 'الوزاره': 'الوزارة',
|
| 2521 |
-
'المديره': 'المديرة', 'المعلمه': 'المعلمة', 'الطالبه': 'الطالبة',
|
| 2522 |
-
'القريه': 'القرية', 'الحديقه': 'الحديقة', 'المحكمه': 'المحكمة',
|
| 2523 |
-
'الكنيسه': 'الكنيسة', 'المنطقه': 'المنطقة', 'الدوله': 'الدولة',
|
| 2524 |
-
'السياره': 'السيارة', 'الطاوله': 'الطاولة', 'الغرفه': 'الغرفة',
|
| 2525 |
-
'المحطه': 'المحطة', 'السفاره': 'السفارة', 'الوظيفه': 'الوظيفة',
|
| 2526 |
-
'الصحيفه': 'الصحيفة', 'العائله': 'العائلة', 'الحياه': 'الحياة',
|
| 2527 |
-
'الصلاه': 'الصلاة', 'الزكاه': 'الزكاة',
|
| 2528 |
-
# Common nouns — indefinite form
|
| 2529 |
-
'حكومه': 'حكومة', 'مدرسه': 'مدرسة', 'شركه': 'شركة',
|
| 2530 |
-
'جامعه': 'جامعة', 'مدينه': 'مدينة', 'قصه': 'قصة',
|
| 2531 |
-
'مكتبه': 'مكتبة', 'طائره': 'طائرة', 'وزاره': 'وزارة',
|
| 2532 |
-
'مديره': 'مديرة', 'معلمه': 'معلمة', 'طالبه': 'طالبة',
|
| 2533 |
-
'قريه': 'قرية', 'حديقه': 'حديقة', 'محكمه': 'محكمة',
|
| 2534 |
-
'منطقه': 'منطقة', 'دوله': 'دولة', 'سياره': 'سيارة',
|
| 2535 |
-
'غرفه': 'غرفة', 'محطه': 'محطة', 'وظيفه': 'وظيفة',
|
| 2536 |
-
'عائله': 'عائلة', 'حياه': 'حياة', 'صلاه': 'صلاة',
|
| 2537 |
-
# Common adjectives — feminine
|
| 2538 |
-
'كبيره': 'كبيرة', 'صغيره': 'صغيرة', 'جميله': 'جميلة',
|
| 2539 |
-
'طويله': 'طويلة', 'قصيره': 'قصيرة', 'جديده': 'جديدة',
|
| 2540 |
-
'قديمه': 'قديمة', 'سريعه': 'سريعة', 'بطيئه': 'بطيئة',
|
| 2541 |
-
}
|
| 2542 |
-
_hata_text = ctx.current_text
|
| 2543 |
-
_hata_words = _hata_text.split()
|
| 2544 |
-
_hata_changed = False
|
| 2545 |
-
_hata_result = []
|
| 2546 |
-
_hata_pos = 0 # track position in text for patch offsets
|
| 2547 |
-
for _hw in _hata_words:
|
| 2548 |
-
_hw_clean = _hw.rstrip('.،؛؟!?!')
|
| 2549 |
-
if _hw_clean in _HATA_WHITELIST:
|
| 2550 |
-
_punct_suffix = _hw[len(_hw_clean):]
|
| 2551 |
-
_fixed = _HATA_WHITELIST[_hw_clean]
|
| 2552 |
-
logger.info(f"[HA-TA] Post-grammar ه→ة: '{_hw}'→'{_fixed}{_punct_suffix}'")
|
| 2553 |
-
_hata_result.append(_fixed + _punct_suffix)
|
| 2554 |
-
_hata_changed = True
|
| 2555 |
-
# Create a patch so the final output includes this fix
|
| 2556 |
-
ctx.add_patch(
|
| 2557 |
-
'spelling', _hata_pos, _hata_pos + len(_hw),
|
| 2558 |
-
_fixed + _punct_suffix, confidence=0.85,
|
| 2559 |
-
)
|
| 2560 |
-
else:
|
| 2561 |
-
_hata_result.append(_hw)
|
| 2562 |
-
_hata_pos += len(_hw) + 1 # +1 for space
|
| 2563 |
-
if _hata_changed:
|
| 2564 |
-
_hata_new = ' '.join(_hata_result)
|
| 2565 |
-
ctx.mutate_text(_hata_new, OffsetMapper)
|
| 2566 |
-
current_text = ctx.current_text
|
| 2567 |
-
except Exception as e:
|
| 2568 |
-
logger.warning(f"[HA-TA] Failed: {type(e).__name__}: {e}")
|
| 2569 |
|
| 2570 |
# 3. Punctuation (runs on grammar-corrected text — PuncAra-v1 local model)
|
| 2571 |
# FIX-07: Skip punctuation for religious text
|
|
|
|
| 81 |
)
|
| 82 |
logger = logging.getLogger(__name__)
|
| 83 |
|
| 84 |
+
DEBUG_TRACE = True # Toggleable trace logging
|
| 85 |
+
|
| 86 |
# Initialize Flask app
|
| 87 |
app = Flask(__name__, static_folder='.', static_url_path='')
|
| 88 |
CORS(app, resources={r"/api/*": {"origins": "*"}}) # CORS for API routes only
|
|
|
|
| 1977 |
except Exception:
|
| 1978 |
pass # Bidirectional check is optional
|
| 1979 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1980 |
ctx.mutate_text(safe_text, OffsetMapper)
|
| 1981 |
current_text = ctx.current_text
|
| 1982 |
except Exception as e:
|
|
|
|
| 2300 |
_o_cl = orig_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2301 |
_c_cl = corr_text.rstrip('.,،؛;:!؟?()[]{}«»"\'…')
|
| 2302 |
|
| 2303 |
+
# Priority 4: Diacritic-Normalized Grammar Validation
|
| 2304 |
+
import re as _re_diac
|
| 2305 |
+
_o_cl = _re_diac.sub(r'[\u064B-\u065F\u0670]', '', _o_cl)
|
| 2306 |
+
_c_cl = _re_diac.sub(r'[\u064B-\u065F\u0670]', '', _c_cl)
|
| 2307 |
+
|
| 2308 |
+
|
| 2309 |
# Case: ون/ان → ين (sound masculine plural / dual case change)
|
| 2310 |
if (_o_cl.endswith('ون') and _c_cl.endswith('ين') and _o_cl[:-2] == _c_cl[:-2]):
|
| 2311 |
_is_grammar_pattern = True
|
|
|
|
| 2395 |
# Catches: القانون→القانين, يعزف→يعزفون, للإنسان→للإنسين
|
| 2396 |
if not _is_grammar_pattern and orig_text and corr_text and len(orig_text) > 2:
|
| 2397 |
import re as _re_jac
|
| 2398 |
+
# Strip punctuation/spaces and normalize Alif/Hamza for comparison
|
| 2399 |
+
_o_norm = _re_jac.sub(r'[\s.,،؛؟!:;?]', '', orig_text)
|
| 2400 |
+
_c_norm = _re_jac.sub(r'[\s.,،؛؟!:;?]', '', corr_text)
|
| 2401 |
+
_o_norm = _re_jac.sub(r'[أإآ]', 'ا', _o_norm)
|
| 2402 |
+
_c_norm = _re_jac.sub(r'[أإآ]', 'ا', _c_norm)
|
| 2403 |
+
_o_chars = set(_o_norm)
|
| 2404 |
+
_c_chars = set(_c_norm)
|
| 2405 |
if _o_chars and _c_chars:
|
| 2406 |
_jac = len(_o_chars & _c_chars) / len(_o_chars | _c_chars)
|
| 2407 |
if _jac < 0.5:
|
|
|
|
| 2438 |
_gram_dir_blocked = True
|
| 2439 |
break
|
| 2440 |
if _gram_dir_blocked:
|
| 2441 |
+
logger.error(traceback.format_exc())
|
| 2442 |
+
continue
|
| 2443 |
+
# DEBUG_TRACE
|
| 2444 |
+
if _is_grammar_pattern:
|
| 2445 |
+
logger.debug(f"[DEBUG_TRACE] Pattern match found for: '{orig_text}'→'{corr_text}'")
|
| 2446 |
|
| 2447 |
# FIX-22: Protect tanween (preserve ً ٌ ٍ from original)
|
| 2448 |
_TANWEEN_CHARS = set('ًٌٍ')
|
|
|
|
| 2498 |
logger.error(traceback.format_exc())
|
| 2499 |
timing_ms['grammar_error'] = f"{type(e).__name__}: {str(e)[:200]}"
|
| 2500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2501 |
|
| 2502 |
# 3. Punctuation (runs on grammar-corrected text — PuncAra-v1 local model)
|
| 2503 |
# FIX-07: Skip punctuation for religious text
|
src/nlp/grammar/grammar_rules.py
CHANGED
|
@@ -203,9 +203,6 @@ class ArabicGrammarGuard:
|
|
| 203 |
return " ".join(corrected_tokens)
|
| 204 |
|
| 205 |
def fix_gender_agreement(self, text):
|
| 206 |
-
text = re.sub(r'\bهذان\s+(ال[أ-ي]+تان)\b', r'هاتان \1', text)
|
| 207 |
-
text = re.sub(r'\bهاتان\s+(ال[أ-ي]+[^ت]ان)\b', r'هذان \1', text)
|
| 208 |
-
text = re.sub(r'\bهذهن\b', 'هاتان', text)
|
| 209 |
|
| 210 |
text = re.sub(r'\bأحد عشر\s+([أ-ي]+ة)\b', r'إحدى عشرة \1', text)
|
| 211 |
text = re.sub(r'\bأحد عشرة\s+([أ-ي]+ة)\b', r'إحدى عشرة \1', text)
|
|
|
|
| 203 |
return " ".join(corrected_tokens)
|
| 204 |
|
| 205 |
def fix_gender_agreement(self, text):
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
text = re.sub(r'\bأحد عشر\s+([أ-ي]+ة)\b', r'إحدى عشرة \1', text)
|
| 208 |
text = re.sub(r'\bأحد عشرة\s+([أ-ي]+ة)\b', r'إحدى عشرة \1', text)
|
src/nlp/punctuation/spelling/araspell_rules.py
CHANGED
|
@@ -660,49 +660,21 @@ class OutputValidator:
|
|
| 660 |
# ═══════════════════════════════════════════════════════════════════════════════
|
| 661 |
|
| 662 |
class VocabularyManager:
|
| 663 |
-
"""Centralized vocabulary management for OOV/IV detection."""
|
| 664 |
-
|
| 665 |
-
HAMZA_VARIANTS = {'أ', 'إ', 'آ', 'ء', 'ؤ', 'ئ', 'ا'}
|
| 666 |
-
ALEF_NORMALIZED = 'ا'
|
| 667 |
-
TA_MARBUTA = 'ة'
|
| 668 |
-
HA = 'ه'
|
| 669 |
-
YA_VARIANTS = {'ي', 'ى'}
|
| 670 |
-
YA_NORMALIZED = 'ي'
|
| 671 |
|
| 672 |
def __init__(self, tokenizer):
|
| 673 |
self.tokenizer = tokenizer
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
self.normalized_vocab = {self.normalize_for_comparison(w): w for w in self.vocab}
|
| 680 |
-
logger.info(f"VocabularyManager initialized: {len(self.vocab)} words")
|
| 681 |
-
|
| 682 |
-
@classmethod
|
| 683 |
-
def normalize_for_comparison(cls, word: str) -> str:
|
| 684 |
-
result = []
|
| 685 |
-
for i, char in enumerate(word):
|
| 686 |
-
if char in cls.HAMZA_VARIANTS:
|
| 687 |
-
result.append(cls.ALEF_NORMALIZED)
|
| 688 |
-
elif char == cls.TA_MARBUTA and i == len(word) - 1:
|
| 689 |
-
result.append(cls.HA)
|
| 690 |
-
elif char in cls.YA_VARIANTS:
|
| 691 |
-
result.append(cls.YA_NORMALIZED)
|
| 692 |
-
else:
|
| 693 |
-
result.append(char)
|
| 694 |
-
return ''.join(result)
|
| 695 |
|
| 696 |
def is_iv(self, word: str) -> bool:
|
| 697 |
clean = re.sub(r'[^\w]', '', word)
|
| 698 |
if not clean:
|
| 699 |
return True
|
| 700 |
-
|
| 701 |
-
return True
|
| 702 |
-
normalized = self.normalize_for_comparison(clean)
|
| 703 |
-
if normalized in self.normalized_vocab:
|
| 704 |
-
return True
|
| 705 |
-
return False
|
| 706 |
|
| 707 |
def is_oov(self, word: str) -> bool:
|
| 708 |
return not self.is_iv(word)
|
|
|
|
| 660 |
# ═══════════════════════════════════════════════════════════════════════════════
|
| 661 |
|
| 662 |
class VocabularyManager:
|
| 663 |
+
"""Centralized vocabulary management for OOV/IV detection using CamelTools."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
|
| 665 |
def __init__(self, tokenizer):
|
| 666 |
self.tokenizer = tokenizer
|
| 667 |
+
from camel_tools.morphology.database import MorphologyDB
|
| 668 |
+
from camel_tools.morphology.analyzer import Analyzer
|
| 669 |
+
self._db = MorphologyDB.builtin_db()
|
| 670 |
+
self.analyzer = Analyzer(self._db)
|
| 671 |
+
logger.info("VocabularyManager initialized with CamelTools Analyzer")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
|
| 673 |
def is_iv(self, word: str) -> bool:
|
| 674 |
clean = re.sub(r'[^\w]', '', word)
|
| 675 |
if not clean:
|
| 676 |
return True
|
| 677 |
+
return len(self.analyzer.analyze(clean)) > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
def is_oov(self, word: str) -> bool:
|
| 680 |
return not self.is_iv(word)
|
src/nlp/spelling/araspell_rules.py
CHANGED
|
@@ -674,49 +674,21 @@ class OutputValidator:
|
|
| 674 |
# ═══════════════════════════════════════════════════════════════════════════════
|
| 675 |
|
| 676 |
class VocabularyManager:
|
| 677 |
-
"""Centralized vocabulary management for OOV/IV detection."""
|
| 678 |
-
|
| 679 |
-
HAMZA_VARIANTS = {'أ', 'إ', 'آ', 'ء', 'ؤ', 'ئ', 'ا'}
|
| 680 |
-
ALEF_NORMALIZED = 'ا'
|
| 681 |
-
TA_MARBUTA = 'ة'
|
| 682 |
-
HA = 'ه'
|
| 683 |
-
YA_VARIANTS = {'ي', 'ى'}
|
| 684 |
-
YA_NORMALIZED = 'ي'
|
| 685 |
|
| 686 |
def __init__(self, tokenizer):
|
| 687 |
self.tokenizer = tokenizer
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
self.normalized_vocab = {self.normalize_for_comparison(w): w for w in self.vocab}
|
| 694 |
-
logger.info(f"VocabularyManager initialized: {len(self.vocab)} words")
|
| 695 |
-
|
| 696 |
-
@classmethod
|
| 697 |
-
def normalize_for_comparison(cls, word: str) -> str:
|
| 698 |
-
result = []
|
| 699 |
-
for i, char in enumerate(word):
|
| 700 |
-
if char in cls.HAMZA_VARIANTS:
|
| 701 |
-
result.append(cls.ALEF_NORMALIZED)
|
| 702 |
-
elif char == cls.TA_MARBUTA and i == len(word) - 1:
|
| 703 |
-
result.append(cls.HA)
|
| 704 |
-
elif char in cls.YA_VARIANTS:
|
| 705 |
-
result.append(cls.YA_NORMALIZED)
|
| 706 |
-
else:
|
| 707 |
-
result.append(char)
|
| 708 |
-
return ''.join(result)
|
| 709 |
|
| 710 |
def is_iv(self, word: str) -> bool:
|
| 711 |
clean = re.sub(r'[^\w]', '', word)
|
| 712 |
if not clean:
|
| 713 |
return True
|
| 714 |
-
|
| 715 |
-
return True
|
| 716 |
-
normalized = self.normalize_for_comparison(clean)
|
| 717 |
-
if normalized in self.normalized_vocab:
|
| 718 |
-
return True
|
| 719 |
-
return False
|
| 720 |
|
| 721 |
def is_oov(self, word: str) -> bool:
|
| 722 |
return not self.is_iv(word)
|
|
|
|
| 674 |
# ═══════════════════════════════════════════════════════════════════════════════
|
| 675 |
|
| 676 |
class VocabularyManager:
|
| 677 |
+
"""Centralized vocabulary management for OOV/IV detection using CamelTools."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
def __init__(self, tokenizer):
|
| 680 |
self.tokenizer = tokenizer
|
| 681 |
+
from camel_tools.morphology.database import MorphologyDB
|
| 682 |
+
from camel_tools.morphology.analyzer import Analyzer
|
| 683 |
+
self._db = MorphologyDB.builtin_db()
|
| 684 |
+
self.analyzer = Analyzer(self._db)
|
| 685 |
+
logger.info("VocabularyManager initialized with CamelTools Analyzer")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
|
| 687 |
def is_iv(self, word: str) -> bool:
|
| 688 |
clean = re.sub(r'[^\w]', '', word)
|
| 689 |
if not clean:
|
| 690 |
return True
|
| 691 |
+
return len(self.analyzer.analyze(clean)) > 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
|
| 693 |
def is_oov(self, word: str) -> bool:
|
| 694 |
return not self.is_iv(word)
|