forked from Deen-Bridge/dnb-ai
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverifier.py
More file actions
153 lines (126 loc) · 5 KB
/
Copy pathverifier.py
File metadata and controls
153 lines (126 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import difflib
import re
from enum import Enum
from typing import Any
from corpus import corpus
class VerificationStatus(str, Enum):
VERIFIED = "verified"
MISMATCH = "mismatch"
UNVERIFIED = "unverified"
NOT_QUOTED = "not_quoted"
# Arabic Tashkeel / Diacritical Marks
TASHKEEL_REGEX = re.compile(r"[\u0617-\u061A\u064B-\u0652\u0670]")
# Extraction Regex: Matches "Quran 2:255", "Surah 2:255", "[2:255]", "(2:255)", etc.
QURAN_REF_REGEX = re.compile(
r"(?:Surah|Quran|Qur\'an)?\s*\[?\b([1-9]|[1-9]\d|1[0-0]\d|11[0-4])\s*:\s*([1-9]\d*)\b\]?"
r"(?:\s*[\"\'«”](.*?)[\"\'»“])?",
re.IGNORECASE | re.DOTALL,
)
HADITH_REF_REGEX = re.compile(
r"\b(Bukhari|Muslim|Abu Dawud|Tirmidhi|Nasa\'i|Ibn Majah|Muwatta|Ahmad)\b"
r"\s*(?:hadith|no\.|number|#)?\s*(\d+)?"
r"(?:\s*[\"\'«”](.*?)[\"\'»“])?",
re.IGNORECASE,
)
def normalize_arabic(text: str) -> str:
"""Strip tashkeel/diacritics and normalize Alef variants."""
if not text:
return ""
text = TASHKEEL_REGEX.sub("", text)
# Unify Alef forms (أ, إ, آ -> ا)
text = re.sub(r"[\u0622\u0623\u0625]", "\u0627", text)
return text.strip()
def normalize_english(text: str) -> str:
"""Casefold, strip punctuation, and normalize whitespace."""
if not text:
return ""
text = text.lower()
text = re.sub(r"[^\w\s]", "", text)
return " ".join(text.split())
def calculate_similarity(generated_quote: str, corpus_text: str) -> float:
"""Calculate similarity ratio between generated quote and corpus text using stdlib difflib."""
norm_gen = normalize_english(generated_quote)
norm_corp = normalize_english(corpus_text)
if not norm_gen or not norm_corp:
return 0.0
return difflib.SequenceMatcher(None, norm_gen, norm_corp).ratio()
def verify_quran_citation(surah: int, ayah: int, quote: str | None = None) -> dict[str, Any]:
"""Verify a single Quran reference against the corpus."""
max_ayahs = corpus.get_ayah_count(surah)
# 1. Check existence
if max_ayahs is None or ayah < 1 or ayah > max_ayahs:
return {
"source": "quran",
"surah": surah,
"ayah": ayah,
"status": VerificationStatus.MISMATCH,
"reason": f"Surah {surah} only has {max_ayahs or 0} ayahs; ayah {ayah} does not exist.",
}
ayah_data = corpus.get_ayah(surah, ayah)
# If no quote is given with the reference
if not quote or not quote.strip():
return {
"source": "quran",
"surah": surah,
"ayah": ayah,
"status": VerificationStatus.NOT_QUOTED,
"reason": "Reference exists; no quote provided for verification.",
}
# 2. Check quote similarity (English translation)
corpus_english = ayah_data.get("english", "") if ayah_data else ""
similarity = calculate_similarity(quote, corpus_english)
# Threshold of 0.70 accounts for variations across translation editions
if similarity >= 0.70:
return {
"source": "quran",
"surah": surah,
"ayah": ayah,
"status": VerificationStatus.VERIFIED,
"similarity": round(similarity, 2),
}
else:
return {
"source": "quran",
"surah": surah,
"ayah": ayah,
"status": VerificationStatus.MISMATCH,
"similarity": round(similarity, 2),
"correct_text": corpus_english,
"reason": f"Quote does not match Surah {surah}:{ayah} text in corpus.",
}
def verify_hadith_citation(collection: str, number: str | None = None, quote: str | None = None) -> dict[str, Any]:
"""Verification for Hadith citations (defaults to honest unverified label when corpus is unavailable)."""
if not corpus.has_hadith_corpus():
return {
"source": "hadith",
"collection": collection,
"number": number,
"status": VerificationStatus.UNVERIFIED,
"reason": "Hadith corpus not available for verification.",
}
# Future expansion for #24 when Hadith corpus lands
return {
"source": "hadith",
"collection": collection,
"number": number,
"status": VerificationStatus.UNVERIFIED,
"reason": "Hadith verification not implemented.",
}
def extract_and_verify_all(text: str) -> list[dict[str, Any]]:
"""Extract all citations from text and return their verification statuses."""
results = []
# Extract & Verify Quran References
for match in QURAN_REF_REGEX.finditer(text):
surah = int(match.group(1))
ayah = int(match.group(2))
quote = match.group(3)
res = verify_quran_citation(surah, ayah, quote)
results.append(res)
# Extract & Verify Hadith References
for match in HADITH_REF_REGEX.finditer(text):
collection = match.group(1)
number = match.group(2)
quote = match.group(3)
res = verify_hadith_citation(collection, number, quote)
results.append(res)
return results