from pydantic import BaseModel, Field from typing import Optional # ── Common ───────────────────────────────────────────────────────────────── class PaginationMeta(BaseModel): total: int page: int per_page: int pages: int model_config = { "json_schema_extra": { "examples": [{"total": 6986, "page": 1, "per_page": 20, "pages": 350}] } } class PaginatedResponse(BaseModel): meta: PaginationMeta data: list # ── Hadith ───────────────────────────────────────────────────────────────── class HadithSummary(BaseModel): id: str = Field(description="Unique hadith UUID") collection: str = Field(description="Collection name in English") hadith_number: int = Field(description="Hadith number within collection") grade: Optional[str] = Field(None, description="Grading: Sahih, Hasan, Da'if, etc.") arabic_text: Optional[str] = Field(None, description="Full Arabic text (may be truncated in list views)") matn_text: Optional[str] = Field(None, description="Body text only (without isnad)") sanad_text: Optional[str] = Field(None, description="Chain of narration text only") model_config = { "json_schema_extra": { "examples": [{ "id": "dcf8df41-3185-4e20-a9af-db3696a48c79", "collection": "Sahih Bukhari", "hadith_number": 1, "grade": "Sahih", "arabic_text": "حَدَّثَنَا الْحُمَيْدِيُّ عَبْدُ اللَّهِ بْنُ الزُّبَيْرِ قَالَ حَدَّثَنَا سُفْيَانُ...", "matn_text": "إِنَّمَا الأَعْمَالُ بِالنِّيَّاتِ وَإِنَّمَا لِكُلِّ امْرِئٍ مَا نَوَى...", "sanad_text": "حَدَّثَنَا الْحُمَيْدِيُّ عَبْدُ اللَّهِ بْنُ الزُّبَيْرِ قَالَ حَدَّثَنَا سُفْيَانُ قَالَ حَدَّثَنَا يَحْيَى بْنُ سَعِيدٍ الأَنْصَارِيُّ" }] } } class TopicTag(BaseModel): topic_arabic: str = Field(description="Topic name in Arabic, e.g. الصلاة") topic_english: str = Field(description="Topic name in English, e.g. Prayer") category: str = Field(description="Broad Islamic category: عقيدة، فقه، سيرة، أخلاق، تفسير") model_config = { "json_schema_extra": { "examples": [{ "topic_arabic": "النية", "topic_english": "Intention", "category": "فقه" }] } } class NarratorInChain(BaseModel): order: int = Field(description="Position in chain: 1=closest to compiler, last=closest to Prophet ﷺ") name_arabic: str = Field(description="Narrator's Arabic name as it appears in the hadith text") name_transliterated: Optional[str] = Field(None, description="Latin transliteration of the name") entity_type: Optional[str] = Field(None, description="PERSON, KUNYA (أبو/أم), NISBA (attributional), or TITLE (رسول الله)") transmission_verb: Optional[str] = Field(None, description="Exact Arabic transmission verb: حدثنا، أخبرنا، عن، سمعت") model_config = { "json_schema_extra": { "examples": [{ "order": 1, "name_arabic": "الْحُمَيْدِيُّ", "name_transliterated": "al-Humaydi", "entity_type": "NISBA", "transmission_verb": "حَدَّثَنَا" }] } } class HadithDetail(BaseModel): id: str = Field(description="Unique hadith UUID") collection: str = Field(description="Collection English name") hadith_number: int = Field(description="Number within collection") grade: Optional[str] = Field(None, description="Hadith grade") arabic_text: Optional[str] = Field(None, description="Complete Arabic text") sanad_text: Optional[str] = Field(None, description="Isnad (chain) text only") matn_text: Optional[str] = Field(None, description="Matn (body) text only") narrator_chain: list[NarratorInChain] = Field(default_factory=list, description="Ordered narrator chain from Neo4j graph") topics: list[TopicTag] = Field(default_factory=list, description="Topic tags for searchability") model_config = { "json_schema_extra": { "examples": [{ "id": "dcf8df41-3185-4e20-a9af-db3696a48c79", "collection": "Sahih Bukhari", "hadith_number": 1, "grade": "Sahih", "arabic_text": "حَدَّثَنَا الْحُمَيْدِيُّ عَبْدُ اللَّهِ بْنُ الزُّبَيْرِ...", "sanad_text": "حَدَّثَنَا الْحُمَيْدِيُّ...", "matn_text": "إِنَّمَا الأَعْمَالُ بِالنِّيَّاتِ...", "narrator_chain": [ {"order": 1, "name_arabic": "الْحُمَيْدِيُّ", "name_transliterated": "al-Humaydi", "entity_type": "NISBA", "transmission_verb": "حَدَّثَنَا"}, {"order": 2, "name_arabic": "سُفْيَانُ", "name_transliterated": "Sufyan", "entity_type": "PERSON", "transmission_verb": "حَدَّثَنَا"}, ], "topics": [ {"topic_arabic": "النية", "topic_english": "Intention", "category": "فقه"}, ] }] } } # ── Narrator ─────────────────────────────────────────────────────────────── class NarratorSummary(BaseModel): name_arabic: str = Field(description="Primary Arabic name") name_transliterated: Optional[str] = Field(None, description="Latin transliteration") entity_type: Optional[str] = Field(None, description="PERSON, KUNYA, NISBA, TITLE") generation: Optional[str] = Field(None, description="طبقة: صحابي، تابعي، تابع التابعين") reliability_grade: Optional[str] = Field(None, description="جرح وتعديل: ثقة، صدوق، ضعيف، متروك") hadith_count: int = Field(0, description="Number of hadiths this narrator appears in") model_config = { "json_schema_extra": { "examples": [{ "name_arabic": "أَبُو هُرَيْرَةَ", "name_transliterated": "Abu Hurayrah", "entity_type": "KUNYA", "generation": "صحابي", "reliability_grade": "ثقة", "hadith_count": 5374 }] } } class NameForm(BaseModel): name: str = Field(description="Alternative name form") type: str = Field(description="Name type: PERSON, KUNYA, NISBA, TITLE") class FamilyInfo(BaseModel): father: Optional[str] = None mother: Optional[str] = None spouse: Optional[str] = None children: list[str] = Field(default_factory=list) class PlaceRelation(BaseModel): place: str = Field(description="Place name in Arabic") relation: str = Field(description="BORN_IN, LIVED_IN, DIED_IN, or TRAVELED_TO") model_config = { "json_schema_extra": { "examples": [{"place": "المدينة", "relation": "LIVED_IN"}] } } class NarratorProfile(BaseModel): name_arabic: str = Field(description="Primary Arabic name") name_transliterated: Optional[str] = Field(None, description="Latin transliteration") entity_type: Optional[str] = Field(None, description="PERSON, KUNYA, NISBA, TITLE") full_nasab: Optional[str] = Field(None, description="Full lineage: فلان بن فلان بن فلان") kunya: Optional[str] = Field(None, description="أبو/أم name (e.g. أبو هريرة)") nisba: Optional[str] = Field(None, description="Attributional name (e.g. البخاري، المدني، الزهري)") laqab: Optional[str] = Field(None, description="Title or epithet (e.g. أمير المؤمنين في الحديث)") generation: Optional[str] = Field(None, description="طبقة: صحابي، تابعي، تابع التابعين، أتباع تابع التابعين") reliability_grade: Optional[str] = Field(None, description="جرح وتعديل: ثقة، ثقة حافظ، صدوق، ضعيف، متروك") reliability_detail: Optional[str] = Field(None, description="Extended grading explanation from scholars") birth_year_hijri: Optional[int] = Field(None, description="Birth year (Hijri calendar)") death_year_hijri: Optional[int] = Field(None, description="Death year (Hijri calendar)") birth_year_ce: Optional[int] = Field(None, description="Birth year (CE)") death_year_ce: Optional[int] = Field(None, description="Death year (CE)") biography_summary_arabic: Optional[str] = Field(None, description="2-3 sentence biography in Arabic") biography_summary_english: Optional[str] = Field(None, description="2-3 sentence biography in English") total_hadiths_narrated_approx: Optional[int] = Field(None, description="Approximate total hadiths narrated across all collections") hadith_count: int = Field(0, description="Hadiths in current database") hadiths: list[HadithSummary] = Field(default_factory=list, description="Sample hadiths narrated (max 50)") teachers: list[NarratorSummary] = Field(default_factory=list, description="Known teachers / شيوخ") students: list[NarratorSummary] = Field(default_factory=list, description="Known students / تلاميذ") places: list[PlaceRelation] = Field(default_factory=list, description="Associated places (born, lived, died, traveled)") tribes: list[str] = Field(default_factory=list, description="Tribal affiliations (e.g. قريش، دوس، الأنصار)") bio_verified: bool = Field(False, description="Whether biography has been manually verified against classical sources") model_config = { "json_schema_extra": { "examples": [{ "name_arabic": "أَبُو هُرَيْرَةَ", "name_transliterated": "Abu Hurayrah", "entity_type": "KUNYA", "full_nasab": "عبد الرحمن بن صخر الدوسي", "kunya": "أبو هريرة", "nisba": "الدوسي", "laqab": None, "generation": "صحابي", "reliability_grade": "ثقة", "reliability_detail": "صحابي جليل، أكثر الصحابة رواية للحديث", "birth_year_hijri": None, "death_year_hijri": 57, "birth_year_ce": None, "death_year_ce": 676, "biography_summary_arabic": "أبو هريرة الدوسي، صحابي جليل، أكثر الصحابة رواية للحديث النبوي. أسلم عام خيبر ولازم النبي ﷺ.", "biography_summary_english": "Abu Hurayrah al-Dawsi, a prominent Companion and the most prolific narrator of hadith. He accepted Islam during Khaybar and remained close to the Prophet ﷺ.", "total_hadiths_narrated_approx": 5374, "hadith_count": 142, "hadiths": [], "teachers": [{"name_arabic": "رسول الله ﷺ", "name_transliterated": "Prophet Muhammad", "entity_type": "TITLE", "generation": None, "reliability_grade": None, "hadith_count": 0}], "students": [{"name_arabic": "الزهري", "name_transliterated": "al-Zuhri", "entity_type": "NISBA", "generation": "تابعي", "reliability_grade": "ثقة", "hadith_count": 89}], "places": [{"place": "المدينة", "relation": "LIVED_IN"}], "tribes": ["دوس"], "bio_verified": False, }] } } # ── Isnad Chain ──────────────────────────────────────────────────────────── class IsnadNode(BaseModel): name_arabic: str = Field(description="Narrator Arabic name") name_transliterated: Optional[str] = Field(None, description="Latin transliteration") entity_type: Optional[str] = Field(None, description="PERSON, KUNYA, NISBA, TITLE") generation: Optional[str] = Field(None, description="طبقة") reliability_grade: Optional[str] = Field(None, description="جرح وتعديل grade") class IsnadLink(BaseModel): source: str = Field(description="name_arabic of narrator who received the hadith") target: str = Field(description="name_arabic of narrator they received it from") transmission_verb: Optional[str] = Field(None, description="Exact verb: حدثنا، أخبرنا، عن، سمعت، أنبأنا") class IsnadChain(BaseModel): hadith_id: str = Field(description="UUID of the hadith") collection: str = Field(description="Collection name") hadith_number: int = Field(description="Hadith number") nodes: list[IsnadNode] = Field(default_factory=list, description="Narrator nodes for graph visualization") links: list[IsnadLink] = Field(default_factory=list, description="Directed edges: source heard from target") model_config = { "json_schema_extra": { "examples": [{ "hadith_id": "dcf8df41-3185-4e20-a9af-db3696a48c79", "collection": "Sahih Bukhari", "hadith_number": 1, "nodes": [ {"name_arabic": "الْحُمَيْدِيُّ", "name_transliterated": "al-Humaydi", "entity_type": "NISBA", "generation": "تابع التابعين", "reliability_grade": "ثقة"}, {"name_arabic": "سُفْيَانُ بْنُ عُيَيْنَةَ", "name_transliterated": "Sufyan ibn Uyaynah", "entity_type": "PERSON", "generation": "تابع التابعين", "reliability_grade": "ثقة"}, {"name_arabic": "يَحْيَى بْنُ سَعِيدٍ", "name_transliterated": "Yahya ibn Sa'id al-Ansari", "entity_type": "PERSON", "generation": "تابعي", "reliability_grade": "ثقة"}, {"name_arabic": "عُمَرُ بْنُ الْخَطَّابِ", "name_transliterated": "Umar ibn al-Khattab", "entity_type": "PERSON", "generation": "صحابي", "reliability_grade": "ثقة"}, ], "links": [ {"source": "الْحُمَيْدِيُّ", "target": "سُفْيَانُ بْنُ عُيَيْنَةَ", "transmission_verb": "حَدَّثَنَا"}, {"source": "سُفْيَانُ بْنُ عُيَيْنَةَ", "target": "يَحْيَى بْنُ سَعِيدٍ", "transmission_verb": "حَدَّثَنَا"}, {"source": "يَحْيَى بْنُ سَعِيدٍ", "target": "عُمَرُ بْنُ الْخَطَّابِ", "transmission_verb": "عن"}, ] }] } } # ── Relationships / Who Met Who ──────────────────────────────────────────── class NarratorInteraction(BaseModel): narrator_a: str = Field(description="First narrator Arabic name") narrator_a_transliterated: Optional[str] = Field(None, description="First narrator transliteration") narrator_b: str = Field(description="Second narrator Arabic name") narrator_b_transliterated: Optional[str] = Field(None, description="Second narrator transliteration") relationship_type: str = Field(description="NARRATED_FROM, TEACHER_OF, HEARD_BY, STUDENT_OF") shared_hadith_count: int = Field(0, description="Number of hadiths connecting them") hadith_ids: list[str] = Field(default_factory=list, description="IDs of connecting hadiths (max 20)") model_config = { "json_schema_extra": { "examples": [{ "narrator_a": "الزهري", "narrator_a_transliterated": "al-Zuhri", "narrator_b": "أنس بن مالك", "narrator_b_transliterated": "Anas ibn Malik", "relationship_type": "NARRATED_FROM", "shared_hadith_count": 23, "hadith_ids": ["abc-123", "def-456"] }] } } class NarratorConnection(BaseModel): narrator: str = Field(description="Connected narrator Arabic name") narrator_transliterated: Optional[str] = Field(None, description="Transliteration") connection_type: str = Field(description="Relationship type") direction: str = Field(description="'incoming' (they → this) or 'outgoing' (this → them)") class NarratorNetwork(BaseModel): center: NarratorSummary connections: list[NarratorConnection] = Field(default_factory=list) total_connections: int = 0 # ── Search ───────────────────────────────────────────────────────────────── class SemanticSearchResult(BaseModel): hadith: HadithSummary = Field(description="Matching hadith") score: float = Field(description="Cosine similarity score (0-1, higher = more relevant)") collection: Optional[str] = Field(None, description="Collection name") model_config = { "json_schema_extra": { "examples": [{ "hadith": { "id": "abc-123", "collection": "Sahih Bukhari", "hadith_number": 1, "grade": "Sahih", "arabic_text": "إِنَّمَا الأَعْمَالُ بِالنِّيَّاتِ..." }, "score": 0.9234, "collection": "Sahih Bukhari" }] } } class FullTextSearchResult(BaseModel): hadith: HadithSummary = Field(description="Matching hadith") score: float = Field(description="Elasticsearch relevance score (higher = more relevant)") highlights: list[str] = Field(default_factory=list, description="Text fragments with highlighted matches") model_config = { "json_schema_extra": { "examples": [{ "hadith": { "id": "abc-123", "collection": "Sahih Muslim", "hadith_number": 1599, "grade": "Sahih", "arabic_text": "..." }, "score": 12.45, "highlights": ["...عن الصلاة في المسجد الحرام..."] }] } }