Upload 2 files
Browse files- .gitattributes +1 -0
- quran.py +350 -0
- quran_master.db +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
quran_master.db filter=lfs diff=lfs merge=lfs -text
|
quran.py
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""Quran.py
|
| 3 |
+
|
| 4 |
+
Automatically generated by Colab.
|
| 5 |
+
|
| 6 |
+
Original file is located at
|
| 7 |
+
https://colab.research.google.com/drive/1WwaR-xsFnY5iffCndJV5metzB0RS4_GP
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import sqlite3
|
| 11 |
+
import re
|
| 12 |
+
from rapidfuzz.distance import Levenshtein
|
| 13 |
+
|
| 14 |
+
# ==========================================
|
| 15 |
+
# طبقة التطبيع الموسعة
|
| 16 |
+
# ==========================================
|
| 17 |
+
|
| 18 |
+
# مجموعات الحروف المتشابهة بصرياً أو صوتياً — كلها ترجع لشكل واحد
|
| 19 |
+
_SIMILAR_GROUPS = [
|
| 20 |
+
('اأإآٱ', 'ا'),
|
| 21 |
+
('ةه', 'ه'),
|
| 22 |
+
('يىئ', 'ي'),
|
| 23 |
+
('وؤ', 'و'),
|
| 24 |
+
('ذد', 'د'), # متقاربة بصرياً للمبتدئين
|
| 25 |
+
('زرذ', 'ر'), # أحياناً يخلط المستخدم بينها
|
| 26 |
+
('طت', 'ت'),
|
| 27 |
+
('ضظ', 'ض'),
|
| 28 |
+
('سص', 'س'),
|
| 29 |
+
('ثت', 'ت'),
|
| 30 |
+
('خح', 'ح'),
|
| 31 |
+
('غع', 'ع'),
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
def _build_similarity_table():
|
| 35 |
+
table = {}
|
| 36 |
+
for group, canonical in _SIMILAR_GROUPS:
|
| 37 |
+
for ch in group:
|
| 38 |
+
table[ch] = canonical
|
| 39 |
+
return table
|
| 40 |
+
|
| 41 |
+
_SIM_TABLE = _build_similarity_table()
|
| 42 |
+
|
| 43 |
+
def normalize_arabic(text: str, *, deep: bool = False) -> str:
|
| 44 |
+
"""
|
| 45 |
+
تطبيع النص العربي.
|
| 46 |
+
deep=False → تطبيع خفيف (للتخزين في DB أو المقارنة الدقيقة)
|
| 47 |
+
deep=True → تطبيع عميق يساوي بين الحروف المتشابهة (للبحث الضبابي)
|
| 48 |
+
"""
|
| 49 |
+
if not text:
|
| 50 |
+
return ""
|
| 51 |
+
# إزالة التشكيل والعلامات القرآنية
|
| 52 |
+
text = re.sub(
|
| 53 |
+
r'[\u064B-\u065F\u0670\u0671\u0656'
|
| 54 |
+
r'\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]',
|
| 55 |
+
'', text
|
| 56 |
+
)
|
| 57 |
+
# همزات
|
| 58 |
+
text = re.sub(r'[أإآٱ]', 'ا', text)
|
| 59 |
+
# تاء مربوطة
|
| 60 |
+
text = re.sub(r'ة', 'ه', text)
|
| 61 |
+
# ألف مقصورة
|
| 62 |
+
text = re.sub(r'ى', 'ي', text)
|
| 63 |
+
|
| 64 |
+
if deep:
|
| 65 |
+
text = ''.join(_SIM_TABLE.get(ch, ch) for ch in text)
|
| 66 |
+
|
| 67 |
+
return ' '.join(text.strip().split())
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# ==========================================
|
| 71 |
+
# مطابقة ضبابية على مستوى الكلمات
|
| 72 |
+
# ==========================================
|
| 73 |
+
|
| 74 |
+
def _word_similarity(a: str, b: str) -> float:
|
| 75 |
+
"""نسبة التشابه بين كلمتين (0.0 → 1.0)."""
|
| 76 |
+
max_len = max(len(a), len(b), 1)
|
| 77 |
+
dist = Levenshtein.distance(a, b)
|
| 78 |
+
return 1.0 - dist / max_len
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _score_window(query_words: list[str], window_words: list[str],
|
| 82 |
+
query_deep: list[str], window_deep: list[str]) -> float:
|
| 83 |
+
"""
|
| 84 |
+
احسب نسبة تطابق نافذة كلمات مع استعلام المستخدم.
|
| 85 |
+
نستخدم نسختين: خفيفة (أولوية) وعميقة (احتياط).
|
| 86 |
+
"""
|
| 87 |
+
if len(window_words) != len(query_words):
|
| 88 |
+
return 0.0
|
| 89 |
+
|
| 90 |
+
total = 0.0
|
| 91 |
+
for qw, ww, qd, wd in zip(query_words, window_words, query_deep, window_deep):
|
| 92 |
+
# نأخذ أعلى نتيجة بين المقارنة الخفيفة والعميقة
|
| 93 |
+
s_light = _word_similarity(qw, ww)
|
| 94 |
+
s_deep = _word_similarity(qd, wd)
|
| 95 |
+
total += max(s_light, s_deep)
|
| 96 |
+
|
| 97 |
+
return total / len(query_words)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
# ==========================================
|
| 101 |
+
# الدالة الرئيسية
|
| 102 |
+
# ==========================================
|
| 103 |
+
|
| 104 |
+
def search_bayan(query_text: str,
|
| 105 |
+
target_type: str = "تدقيق الايات",
|
| 106 |
+
fuzzy_threshold: float = 0.72) -> dict:
|
| 107 |
+
"""
|
| 108 |
+
البحث عن آية قرآنية مع دعم الأخطاء الإملائية.
|
| 109 |
+
|
| 110 |
+
المعاملات:
|
| 111 |
+
query_text : النص المُدخَل من المستخدم (قد يحتوي أخطاء)
|
| 112 |
+
target_type : لغة الإخراج (uthmani / english / french / ...)
|
| 113 |
+
fuzzy_threshold : الحد الأدنى لقبول التطابق (0→1)، افتراضياً 0.72
|
| 114 |
+
|
| 115 |
+
المُخرج:
|
| 116 |
+
dict يحتوي على:
|
| 117 |
+
matched_segment : النص المُصحَّح بالرسم العثماني أو الترجمة
|
| 118 |
+
full_verse : الآيات كاملة مع التوثيق
|
| 119 |
+
similarity_score : درجة التشابه
|
| 120 |
+
metadata : تفاصيل الآيات
|
| 121 |
+
أو:
|
| 122 |
+
error : رسالة الخطأ
|
| 123 |
+
"""
|
| 124 |
+
conn = sqlite3.connect('quran_master.db')
|
| 125 |
+
cursor = conn.cursor()
|
| 126 |
+
|
| 127 |
+
language_mapping = {
|
| 128 |
+
"تدقيق الايات": "uthmani",
|
| 129 |
+
"bengali": "bn", "bosnian": "bs", "english": "en", "french": "fr",
|
| 130 |
+
"german": "de", "indonesian": "id", "malay": "ms", "persian": "fa",
|
| 131 |
+
"portuguese": "pt", "russian": "ru", "spanish": "es",
|
| 132 |
+
"turkish": "tr", "uzbek": "uz"
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
clean_target = str(target_type).lower().strip()
|
| 136 |
+
lang_code = language_mapping.get(clean_target, "uthmani")
|
| 137 |
+
|
| 138 |
+
verse_column = "v.text_uthmani" if lang_code == "uthmani" else f"v.lang_{lang_code}"
|
| 139 |
+
sura_column = "s.ar" if lang_code == "uthmani" else f"s.lang_{lang_code}"
|
| 140 |
+
|
| 141 |
+
# ── تطبيع الاستعلام بنسختين ──
|
| 142 |
+
query_light = normalize_arabic(query_text, deep=False).split()
|
| 143 |
+
query_deep = normalize_arabic(query_text, deep=True).split()
|
| 144 |
+
|
| 145 |
+
if not query_light:
|
| 146 |
+
conn.close()
|
| 147 |
+
return {"error": "النص المُدخل فارغ"}
|
| 148 |
+
|
| 149 |
+
n = len(query_light)
|
| 150 |
+
|
| 151 |
+
# ==========================================
|
| 152 |
+
# البحث بالمرساة الديناميكية (من الأطول للأقصر)
|
| 153 |
+
# ==========================================
|
| 154 |
+
candidate_starts: list[tuple[int, int]] = []
|
| 155 |
+
|
| 156 |
+
for i in range(n, 0, -1):
|
| 157 |
+
anchor = ' '.join(query_light[:i])
|
| 158 |
+
# بحث LIKE عادي أولاً (سريع)
|
| 159 |
+
cursor.execute("""
|
| 160 |
+
SELECT v.sura_num, v.aya_num
|
| 161 |
+
FROM verses v
|
| 162 |
+
WHERE v.text_clean LIKE ?
|
| 163 |
+
ORDER BY v.sura_num, v.aya_num
|
| 164 |
+
""", ('%' + anchor + '%',))
|
| 165 |
+
candidate_starts = cursor.fetchall()
|
| 166 |
+
if candidate_starts:
|
| 167 |
+
break
|
| 168 |
+
|
| 169 |
+
# إذا لم تجد شيئاً بالمرساة الخفيفة → جرّب المرساة العميقة
|
| 170 |
+
# (يستخدم text_deep إذا كان موجوداً، وإلا يعود لـ text_clean)
|
| 171 |
+
if not candidate_starts:
|
| 172 |
+
# اكتشف أعمدة الجدول
|
| 173 |
+
cursor.execute("PRAGMA table_info(verses)")
|
| 174 |
+
cols = {row[1] for row in cursor.fetchall()}
|
| 175 |
+
deep_col = "v.text_deep" if "text_deep" in cols else "v.text_clean"
|
| 176 |
+
|
| 177 |
+
for i in range(n, 0, -1):
|
| 178 |
+
anchor_deep = ' '.join(query_deep[:i])
|
| 179 |
+
cursor.execute(f"""
|
| 180 |
+
SELECT v.sura_num, v.aya_num
|
| 181 |
+
FROM verses v
|
| 182 |
+
WHERE {deep_col} LIKE ?
|
| 183 |
+
ORDER BY v.sura_num, v.aya_num
|
| 184 |
+
""", ('%' + anchor_deep + '%',))
|
| 185 |
+
rows = cursor.fetchall()
|
| 186 |
+
if rows:
|
| 187 |
+
candidate_starts = rows
|
| 188 |
+
break
|
| 189 |
+
|
| 190 |
+
# الملاذ الأخير: ابحث بكل كلمة على حدة وخذ الآيات الأكثر تكراراً
|
| 191 |
+
if not candidate_starts:
|
| 192 |
+
counts: dict[tuple, int] = {}
|
| 193 |
+
for word in query_light:
|
| 194 |
+
if len(word) < 3:
|
| 195 |
+
continue
|
| 196 |
+
cursor.execute("""
|
| 197 |
+
SELECT v.sura_num, v.aya_num
|
| 198 |
+
FROM verses v
|
| 199 |
+
WHERE v.text_clean LIKE ?
|
| 200 |
+
""", ('%' + word + '%',))
|
| 201 |
+
for row in cursor.fetchall():
|
| 202 |
+
counts[row] = counts.get(row, 0) + 1
|
| 203 |
+
if counts:
|
| 204 |
+
candidate_starts = sorted(counts, key=counts.get, reverse=True)[:15]
|
| 205 |
+
|
| 206 |
+
if not candidate_starts:
|
| 207 |
+
conn.close()
|
| 208 |
+
return {
|
| 209 |
+
"matched_segment": "",
|
| 210 |
+
"full_verse": "لم يُعثر على تطابق — تحقق من النص المُدخل"
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
# ==========================================
|
| 214 |
+
# النافذة المنزلقة + التقييم الضبابي
|
| 215 |
+
# ==========================================
|
| 216 |
+
best_score = -1.0
|
| 217 |
+
best_match_idx = -1
|
| 218 |
+
best_rows = None
|
| 219 |
+
|
| 220 |
+
for start_sura, start_aya in candidate_starts:
|
| 221 |
+
cursor.execute(f"""
|
| 222 |
+
SELECT v.sura_num, v.aya_num, v.text_clean,
|
| 223 |
+
v.text_uthmani, {verse_column}, {sura_column}
|
| 224 |
+
FROM verses v
|
| 225 |
+
JOIN suras_translated s ON v.sura_num = s.sura_number
|
| 226 |
+
WHERE (v.sura_num = ? AND v.aya_num >= ?) OR (v.sura_num > ?)
|
| 227 |
+
ORDER BY v.sura_num, v.aya_num
|
| 228 |
+
LIMIT 12
|
| 229 |
+
""", (start_sura, start_aya, start_sura))
|
| 230 |
+
fetched = cursor.fetchall()
|
| 231 |
+
|
| 232 |
+
QURAN_MARKS = {
|
| 233 |
+
'ۖ', 'ۗ', 'ۘ', 'ۙ', 'ۚ', 'ۛ', 'ۜ', '',
|
| 234 |
+
'۞', '۩'
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
# بناء خريطة الكلمات
|
| 239 |
+
combined_light, combined_deep, word_map = [], [], []
|
| 240 |
+
for row in fetched:
|
| 241 |
+
s_num, a_num, t_clean, t_uthmani, t_target, s_name = row
|
| 242 |
+
clean_w = t_clean.split()
|
| 243 |
+
uthmani_w = [
|
| 244 |
+
token
|
| 245 |
+
for token in t_uthmani.split()
|
| 246 |
+
if token not in QURAN_MARKS
|
| 247 |
+
]
|
| 248 |
+
deep_w = normalize_arabic(t_clean, deep=True).split()
|
| 249 |
+
|
| 250 |
+
for j, cw in enumerate(clean_w):
|
| 251 |
+
combined_light.append(cw)
|
| 252 |
+
combined_deep.append(deep_w[j] if j < len(deep_w) else cw)
|
| 253 |
+
word_map.append({
|
| 254 |
+
"clean": cw,
|
| 255 |
+
"uthmani": uthmani_w[j] if j < len(uthmani_w) else cw,
|
| 256 |
+
"sura_num": s_num,
|
| 257 |
+
"aya_num": a_num,
|
| 258 |
+
"target_text": t_target,
|
| 259 |
+
"sura_name": s_name,
|
| 260 |
+
})
|
| 261 |
+
|
| 262 |
+
total_words = len(combined_light)
|
| 263 |
+
if total_words < n:
|
| 264 |
+
continue
|
| 265 |
+
|
| 266 |
+
# نافذة منزلقة — ابحث عن أعلى نتيجة
|
| 267 |
+
for j in range(total_words - n + 1):
|
| 268 |
+
score = _score_window(
|
| 269 |
+
query_light, combined_light[j:j+n],
|
| 270 |
+
query_deep, combined_deep[j:j+n]
|
| 271 |
+
)
|
| 272 |
+
if score > best_score:
|
| 273 |
+
best_score = score
|
| 274 |
+
best_match_idx = j
|
| 275 |
+
best_rows = word_map
|
| 276 |
+
|
| 277 |
+
# إذا وجدنا تطابقاً كاملاً، لا داعي للاستمرار
|
| 278 |
+
if best_score >= 0.999:
|
| 279 |
+
break
|
| 280 |
+
|
| 281 |
+
conn.close()
|
| 282 |
+
|
| 283 |
+
# if best_score < fuzzy_threshold or best_match_idx == -1:
|
| 284 |
+
# return {
|
| 285 |
+
# "error": (
|
| 286 |
+
# f"أقرب تطابق وجدناه بدرجة {best_score:.0%} وهي أقل من الحد المقبول "
|
| 287 |
+
# f"({fuzzy_threshold:.0%}). تحقق من النص المُدخل."
|
| 288 |
+
# )
|
| 289 |
+
# }
|
| 290 |
+
|
| 291 |
+
# ==========================================
|
| 292 |
+
# تشكيل المخرجات
|
| 293 |
+
# ==========================================
|
| 294 |
+
matched_words = best_rows[best_match_idx: best_match_idx + n]
|
| 295 |
+
|
| 296 |
+
# بناء matched_segment مع رقم كل آية
|
| 297 |
+
aya_words: dict[tuple, list] = {}
|
| 298 |
+
for w in matched_words:
|
| 299 |
+
key = (w["sura_num"], w["aya_num"])
|
| 300 |
+
if key not in aya_words:
|
| 301 |
+
aya_words[key] = []
|
| 302 |
+
aya_words[key].append(w["uthmani"] if lang_code == "uthmani" else w["target_text"])
|
| 303 |
+
|
| 304 |
+
if lang_code == "uthmani":
|
| 305 |
+
seg_parts = [
|
| 306 |
+
" ".join(words) + f" ({a_num})"
|
| 307 |
+
for (_, a_num), words in aya_words.items()
|
| 308 |
+
]
|
| 309 |
+
matched_segment = " ".join(seg_parts)
|
| 310 |
+
else:
|
| 311 |
+
# للترجمات: نص الآية كامل + رقمها (بدون تكرار)
|
| 312 |
+
seen_texts, seg_parts = set(), []
|
| 313 |
+
for (_, a_num), words in aya_words.items():
|
| 314 |
+
txt = words[0] # target_text مكرر لكل كلمة، نأخذ الأول
|
| 315 |
+
if txt not in seen_texts:
|
| 316 |
+
seen_texts.add(txt)
|
| 317 |
+
seg_parts.append(f"{txt} ({a_num})")
|
| 318 |
+
matched_segment = " ".join(seg_parts)
|
| 319 |
+
|
| 320 |
+
# الآيات المشمولة — مرتبة بالترتيب
|
| 321 |
+
involved: dict[tuple, dict] = {}
|
| 322 |
+
for w in matched_words:
|
| 323 |
+
key = (w["sura_num"], w["aya_num"])
|
| 324 |
+
if key not in involved:
|
| 325 |
+
involved[key] = {"sura_name": w["sura_name"], "target_text": w["target_text"]}
|
| 326 |
+
|
| 327 |
+
ayah_nums = [a_num for (_, a_num) in involved]
|
| 328 |
+
sura_name = next(iter(involved.values()))["sura_name"]
|
| 329 |
+
|
| 330 |
+
verse_body_parts = []
|
| 331 |
+
for (s_num, a_num), data in involved.items():
|
| 332 |
+
verse_body_parts.append(f"{data['target_text']} ({a_num})")
|
| 333 |
+
|
| 334 |
+
combined_body = " ".join(verse_body_parts)
|
| 335 |
+
|
| 336 |
+
if len(ayah_nums) == 1:
|
| 337 |
+
ref = f"{sura_name}: {ayah_nums[0]}"
|
| 338 |
+
else:
|
| 339 |
+
nums_str = "،".join(str(x) for x in ayah_nums)
|
| 340 |
+
ref = f"{sura_name}: {nums_str}"
|
| 341 |
+
|
| 342 |
+
full_verse_formatted = f"({combined_body}) [{ref}]"
|
| 343 |
+
matched_segment = f"({matched_segment}) [{ref}]"
|
| 344 |
+
|
| 345 |
+
is_exact = best_score >= 0.999
|
| 346 |
+
return {
|
| 347 |
+
"matched_segment": matched_segment,
|
| 348 |
+
"full_verse": full_verse_formatted,
|
| 349 |
+
|
| 350 |
+
}
|
quran_master.db
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8249c308936ddc26e70e46580405f7751d2c96501d1185f617ae9b0436e987a9
|
| 3 |
+
size 23064576
|