Skip to content

feat: add madhhab-aware fiqh answers with school attribution (#39) - #48

Open
GEEKYFOCUS wants to merge 1 commit into
Deen-Bridge:devfrom
GEEKYFOCUS:feat/madhhab-aware-fiqh
Open

feat: add madhhab-aware fiqh answers with school attribution (#39)#48
GEEKYFOCUS wants to merge 1 commit into
Deen-Bridge:devfrom
GEEKYFOCUS:feat/madhhab-aware-fiqh

Conversation

@GEEKYFOCUS

@GEEKYFOCUS GEEKYFOCUS commented Jul 22, 2026

Copy link
Copy Markdown

Overview

This PR adds madhhab-aware fiqh answer capabilities to the DeenBridge AI chat endpoint. When a user asks a fiqh (Islamic jurisprudence) question, the assistant now presents the positions of the four major Sunni schools (Hanafi, Maliki, Shafi'i, Hanbali) with attribution instead of a single flattened ruling.

Changes

🔍 Fiqh Question Detection (new fiqh.py)

  • Two-stage classifier: deterministic keyword pre-filter (~0ms) with Gemini structured-output fallback for ambiguous prompts
  • Lightweight design so non-fiqh questions incur zero additional latency

🧭 Madhhab Request Field

  • ChatRequest.madhhab: Optional[str] — validated against {hanafi, maliki, shafii, hanbali}
  • Common spellings normalized (e.g. shafi'i, Shafie, shafiishafii`)
  • Unknown values degrade to None with a warning (no 422)

📜 Ikhtilaf Methodology in System Prompt

  • ISLAMIC_CONTEXT updated: replaced "Avoid controversial or divisive topics" with explicit acknowledgment of scholarly difference
  • FIQH_INSTRUCTIONS: state agreement first, attribute each school, never rank, cite evidence, consult-scholar closing for personal rulings

📡 Response Metadata

  • ChatResponse.fiqh: Optional[FiqhMetadata] with is_fiqh_question and madhhab_requested
  • Optional field — existing clients unaffected

🧪 Tests & Evaluation

  • 14 eval cases (11 fiqh, 3 non-fiqh) in tests/fixtures/fiqh_eval_cases.jsonl
  • 40 unit tests covering: madhhab normalization, keyword pre-filter, classifier with mocked Gemini, prompt assembly, FiqhMetadata model, CI config

Verification

Acceptance Criteria Status
Comparative fiqh question with no madhhab → all four schools named and attributed, no ranking
Same question with madhhab: \"shafii\" → leads with Shafi'i position
Non-fiqh questions unchanged — no four-school boilerplate
Personal-ruling phrasings → consult-a-scholar closing
Unknown madhhab degrades gracefully to auto behavior
ChatResponse carries optional fiqh metadata
All tests pass offline (no live API calls)
flake8 lint clean

Related Issue

Closes #39


Verification commands: pytest tests/test_fiqh.py -q ✅ 40 passed
python -m flake8 main.py fiqh.py tests/test_fiqh.py --max-line-length=120 --ignore=E501,W503 ✅ (no output)

Summary by CodeRabbit

  • New Features

    • Added fiqh-aware question detection for chat requests.
    • Added optional madhhab selection with support for Hanafi, Maliki, Shafi‘i, and Hanbali preferences.
    • Responses now include fiqh-related metadata when applicable.
    • Fiqh answers provide evidence, acknowledge scholarly differences, avoid ranking schools, and recommend consulting qualified scholars for personal rulings.
  • Tests

    • Added comprehensive automated coverage for fiqh detection, madhhab handling, prompt behavior, and response metadata.

…idge#39)

- Add fiqh.py module with madhhab normalization and fiqh question classifier
- Add madhhab request field to ChatRequest
- Add ikhtilaf methodology to Islamic context and FIQH_INSTRUCTIONS
- Add optional fiqh metadata to ChatResponse
- Add 14 eval cases for comparative fiqh questions
- Add unit tests (40 tests, all offline)
- Update CI config for new module
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

The chat API now accepts an optional madhhab, classifies fiqh questions, conditionally applies comparative fiqh instructions, and returns fiqh metadata. A new module provides normalization and Gemini-backed classification, with offline tests, fixtures, and CI coverage.

Fiqh-aware chat

Layer / File(s) Summary
Fiqh classification and instructions
fiqh.py
Adds madhhab normalization, deterministic pre-filtering, optional Gemini classification, and madhhab-aware fiqh answer instructions.
Chat API integration
main.py
Adds madhhab and fiqh response fields, classifies prompts, assembles conditional instructions, and returns classification metadata.
Offline validation and CI
tests/test_fiqh.py, tests/fixtures/fiqh_eval_cases.jsonl, tests/conftest.py, .github/workflows/ci.yml
Adds fixture-based unit and handler tests, test API-key setup, linting, compilation, and a dedicated fiqh test step.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChatClient
  participant ChatHandler
  participant FiqhClassifier
  participant GeminiModel
  ChatClient->>ChatHandler: Send prompt and optional madhhab
  ChatHandler->>FiqhClassifier: Classify prompt
  FiqhClassifier->>GeminiModel: Classify ambiguous prompt
  GeminiModel-->>FiqhClassifier: Return is_fiqh result
  FiqhClassifier-->>ChatHandler: Return classification
  ChatHandler->>GeminiModel: Send assembled prompt
  GeminiModel-->>ChatHandler: Return answer
  ChatHandler-->>ChatClient: Return answer and fiqh metadata
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: dayz-tech-co

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: madhhab-aware fiqh answers with school attribution.
Linked Issues check ✅ Passed The PR covers fiqh detection, madhhab normalization, prompt methodology, metadata, tests, fixtures, and CI updates required by #39.
Out of Scope Changes check ✅ Passed All reported changes align with the fiqh feature, tests, fixtures, and CI support; no unrelated scope is evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/test_fiqh.py (2)

170-196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Eval-case tests silently skip ambiguous cases, and richer fixture fields go unchecked.

test_all_eval_cases_classify_correctly only asserts when _quick_is_fiqh(...) returns non-None (Line 177). Since _quick_is_fiqh returns None whenever no fiqh keyword matches, non-fiqh fixture cases without fiqh keywords (e.g. the Musa/Pharaoh and 99-Names cases) are likely never actually checked against their is_fiqh: false ground truth — the test passes trivially for them regardless of correctness. Given the PR explicitly calls for "offline unit tests with mocked Gemini calls" to validate the eval cases, consider exercising FiqhClassifier with a mocked model (returning per-case expectations) for the cases the quick filter can't resolve, so the fixture's ground truth is actually exercised end-to-end.

Separately, test_eval_cases_have_required_fields (Line 190-195) only checks for id/question/is_fiqh, so the fixture's expects_madhhabs, expects_consult_scholar, and expects_madhhab_lead fields (tests/fixtures/fiqh_eval_cases.jsonl) are defined but never asserted anywhere — worth confirming whether these are intended for a future/manual eval harness or should be wired into these tests now.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_fiqh.py` around lines 170 - 196, Update
TestEvalCasesClassification so ambiguous cases where _quick_is_fiqh returns None
are validated through FiqhClassifier using a mocked Gemini/model response that
matches each case’s expected is_fiqh value, while preserving the quick-path
assertions. Extend test_eval_cases_have_required_fields to validate
expects_madhhabs, expects_consult_scholar, and expects_madhhab_lead when those
fixture fields are part of the contract, and assert their expected values
through the classifier result where supported.

228-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Exercise the real prompt assembly path in these tests. TestFiqhPromptAssembly rebuilds full_prompt inline, so regressions in chat()’s actual build_full_prompt() logic can slip through. Call the handler with a mocked Gemini client, or extract the prompt builder into a helper and assert on that output instead of duplicating the concatenation in the test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_fiqh.py` around lines 228 - 278, Update the tests in
TestFiqhPromptAssembly to exercise chat()’s real prompt assembly through
build_full_prompt(), using a mocked Gemini client where needed. Replace the
inline full_prompt concatenation in the fiqh and non-fiqh cases with assertions
against the handler-produced prompt, while preserving checks for
FIQH_INSTRUCTIONS, madhhab guidance, and Islamic context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fiqh.py`:
- Around line 51-105: Update FIQH_KEYWORDS and the classification logic to
remove the "nullif" entry, match literal keywords with escaped word-boundary
patterns instead of raw substring searches, and keep intentional regex
expressions such as "raises.*hands.*prayer" in a separate pattern list. Apply
the same matching behavior in the logic referenced around the additional
classification block.

In `@main.py`:
- Around line 177-180: Update the `/chat` flow around `FiqhClassifier` to
construct it with the configured Gemini model so uncertain prompts can reach
`_classify_fiqh()` and use the dedicated fiqh prompt. Because this
classification may perform synchronous Gemini I/O, run it without blocking the
event loop or switch to the classifier’s async client while preserving the
existing `is_fiqh` decision flow.

---

Nitpick comments:
In `@tests/test_fiqh.py`:
- Around line 170-196: Update TestEvalCasesClassification so ambiguous cases
where _quick_is_fiqh returns None are validated through FiqhClassifier using a
mocked Gemini/model response that matches each case’s expected is_fiqh value,
while preserving the quick-path assertions. Extend
test_eval_cases_have_required_fields to validate expects_madhhabs,
expects_consult_scholar, and expects_madhhab_lead when those fixture fields are
part of the contract, and assert their expected values through the classifier
result where supported.
- Around line 228-278: Update the tests in TestFiqhPromptAssembly to exercise
chat()’s real prompt assembly through build_full_prompt(), using a mocked Gemini
client where needed. Replace the inline full_prompt concatenation in the fiqh
and non-fiqh cases with assertions against the handler-produced prompt, while
preserving checks for FIQH_INSTRUCTIONS, madhhab guidance, and Islamic context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 122a8b00-73c6-4155-9462-85ec3c6345dc

📥 Commits

Reviewing files that changed from the base of the PR and between 28fc479 and 1201599.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • fiqh.py
  • main.py
  • tests/conftest.py
  • tests/fixtures/fiqh_eval_cases.jsonl
  • tests/test_fiqh.py

Comment thread fiqh.py
Comment on lines +51 to +105
FIQH_KEYWORDS: list[str] = [
"halal",
"haram",
"makruh",
"mustahab",
"mubah",
"wajib",
"fard",
"sunna",
"sunnah",
"bidah",
"wudu",
"wudhu",
"ghusl",
"tayammum",
"salah",
"salat",
"prayer",
"zakat",
"sawm",
"fasting",
"hajj",
"umrah",
"talaq",
"divorce",
"nikah",
"marriage",
"mahr",
"dowry",
"riba",
"interest",
"gharar",
"maysir",
"fiqh",
"ruling",
"fatwa",
"madhab",
"madhhab",
"raises.*hands.*prayer",
"wiping.*socks",
"masah",
"break.*wudu",
"nullif",
"invalidat",
"qiyam",
"ruku",
"sujood",
"tashahhud",
"dhabiha",
"slaughter",
"awrah",
"hijab",
"niqab",
"dress code",
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent non-fiqh prompts from being classified by substring matches.

re.search() matches substrings, so “interesting” matches interest; NULLIF SQL questions match nullif. These bypass the fallback and incorrectly apply fiqh-only instructions. Remove nullif, use word-boundary escaped matching for literal keywords, and keep intentional regex entries in a separate pattern list.

Also applies to: 178-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fiqh.py` around lines 51 - 105, Update FIQH_KEYWORDS and the classification
logic to remove the "nullif" entry, match literal keywords with escaped
word-boundary patterns instead of raw substring searches, and keep intentional
regex expressions such as "raises.*hands.*prayer" in a separate pattern list.
Apply the same matching behavior in the logic referenced around the additional
classification block.

Comment thread main.py
Comment on lines +177 to +180
madhhab = normalize_madhhab(request.madhhab)

classifier = FiqhClassifier()
is_fiqh = classifier.is_fiqh_question(request.prompt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## main.py outline\n'
ast-grep outline main.py --view expanded || true

printf '\n## fiqh classifier files\n'
fd -a 'fiqh' . || true
fd -a 'classifier' . || true

printf '\n## search for FiqhClassifier usages/definition\n'
rg -n "class FiqhClassifier|FiqhClassifier\\(|is_fiqh_question\\(" . || true

printf '\n## main.py around the cited lines\n'
nl -ba main.py | sed -n '150,210p' || true

Repository: Deen-Bridge/dnb-ai

Length of output: 2436


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## FiqhClassifier definition and related code\n'
rg -n -C 4 "class FiqhClassifier|def __init__|def is_fiqh_question|normalize_madhhab|async def chat" . || true

Repository: Deen-Bridge/dnb-ai

Length of output: 7647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## main.py top and /chat body\n'
sed -n '1,120p' main.py
printf '\n## main.py /chat continuation\n'
sed -n '170,240p' main.py

printf '\n## fiqh.py classifier implementation\n'
sed -n '149,260p' fiqh.py

printf '\n## search for any Gemini model wiring into FiqhClassifier\n'
rg -n "genai_model|FiqhClassifier\\(|configure\\(|google.generativeai|GenerativeModel|genai\\.GenerativeModel|api_key" main.py fiqh.py tests . || true

Repository: Deen-Bridge/dnb-ai

Length of output: 13329


Pass the Gemini model into the fiqh classifier

FiqhClassifier() is created without a model here, so uncertain prompts never reach _classify_fiqh() and default to False. That leaves the dedicated fiqh prompt unused for ambiguous questions. If this call is meant to hit Gemini from /chat, keep it off the event loop or use an async client so the endpoint stays responsive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main.py` around lines 177 - 180, Update the `/chat` flow around
`FiqhClassifier` to construct it with the configured Gemini model so uncertain
prompts can reach `_classify_fiqh()` and use the dedicated fiqh prompt. Because
this classification may perform synchronous Gemini I/O, run it without blocking
the event loop or switch to the classifier’s async client while preserving the
existing `is_fiqh` decision flow.

Source: Path instructions

@zeemscript

Copy link
Copy Markdown
Contributor

@GEEKYFOCUS fix conflict so I can merge asap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants