dakheel commited on
Commit
d8545af
·
verified ·
1 Parent(s): 8bd0469

fix: add contextual sense integrity and central ruling frames

Browse files

Implemented HUDA-Net v38.0.6 contextual sense and central ruling architecture.

Changes:
- Added one-to-one contextual sense alignment for ambiguous surface terms.
- Prevented concepts distributed across unrelated fields from forming an exact match.
- Added domain-neutral OCR and text-integrity validation.
- Added evidence scope centrality to separate core rulings from side cases.
- Added central ruling frames for obligation, permission, validity, sufficiency, remedy, and exceptions.
- Added grounded multi-source ruling synthesis with professional formatting.
- Added contribution-based exact, related, and distant evidence tiers.
- Prevented broken or wrong-sense text from entering the final answer.
- Added lightweight startup regressions before the full runtime download.
- Preserved fully generic behavior with no topic-specific questions, answers, or entity dictionaries.

DEPLOY_HUDANET_V38_0_6.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploy HUDA-Net v38.0.6
2
+
3
+ 1. Extract `hudanet_v38_0_6_contextual_sense_ruling_frame.zip`.
4
+ 2. Upload every extracted file and preserve the `hudanet_core/` directory structure.
5
+ 3. Replace the existing files in the Space root.
6
+ 4. Commit the changes.
7
+ 5. Run a **Factory reboot**.
8
+
9
+ The lightweight preflight must print this before the large runtime download:
10
+
11
+ ```text
12
+ ✅ HUDA-Net v38.0.6 lightweight proposition, contextual-sense, text-integrity, central-ruling-frame, hierarchical-intent, relation-graph, issue-clustering, semantic-alignment, and compare-mode preflight passed before runtime download
13
+ ```
14
+
15
+ After the datasets load, the generic self-test must also pass.
16
+
17
+ Suggested commit title:
18
+
19
+ ```text
20
+ fix: add contextual sense integrity and central ruling frames
21
+ ```
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- HUDA-Net Query-Conditioned Proposition Graph v38.0.5 Academic Integrated — Gradio Stable
4
  =============================
5
  Deploy this file as app.py in a Hugging Face Space and add HF_TOKEN as a
6
  read-only Space secret.
@@ -257,7 +257,7 @@ def _generic_proposition_preflight() -> None:
257
  )
258
  if not unrelated_rejected or not required_roles.issubset(roles):
259
  raise RuntimeError(
260
- "HUDA-Net v38.0.5 preflight failed before runtime download: "
261
  + json.dumps(
262
  {
263
  "unrelated_rejected": unrelated_rejected,
@@ -310,7 +310,7 @@ def _generic_proposition_preflight() -> None:
310
  )
311
  if not relevant_passed or not distractor_rejected or not residue_pruned:
312
  raise RuntimeError(
313
- "HUDA-Net v38.0.5 semantic-alignment preflight failed before runtime download: "
314
  + json.dumps(
315
  {
316
  "relevant_passed": relevant_passed,
@@ -384,7 +384,7 @@ def _generic_proposition_preflight() -> None:
384
  )
385
  if not principle_ok:
386
  raise RuntimeError(
387
- "HUDA-Net v38.0.5 hierarchical principle preflight failed before runtime download: "
388
  + json.dumps(
389
  {
390
  "request_type": principle_result.query.primary_request_type,
@@ -397,6 +397,89 @@ def _generic_proposition_preflight() -> None:
397
  )
398
  )
399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  # Validate the compare-sources contract with coherent generic evidence before
401
  # downloading the multi-gigabyte runtime. This catches audit-fixture regressions
402
  # immediately and verifies real synthesis rather than bypassing the gate.
@@ -431,7 +514,7 @@ def _generic_proposition_preflight() -> None:
431
  primary_ok = ("Book 1" in primary_only.answer) ^ ("Book 2" in primary_only.answer)
432
  if not compare_ok or not primary_ok or compared.answer == primary_only.answer:
433
  raise RuntimeError(
434
- "HUDA-Net v38.0.5 compare-mode preflight failed before runtime download: "
435
  + json.dumps(
436
  {
437
  "compare_ok": compare_ok,
@@ -443,7 +526,7 @@ def _generic_proposition_preflight() -> None:
443
  )
444
  )
445
  print(
446
- "✅ HUDA-Net v38.0.5 lightweight proposition, hierarchical-intent, relation-graph, issue-clustering, semantic-alignment, and compare-mode preflight "
447
  "passed before runtime download"
448
  )
449
 
@@ -460,7 +543,7 @@ def _zerogpu_registration_only():
460
 
461
  _download_hudanet_private_datasets()
462
 
463
- VERSION = "38.0.5"
464
  CONFIG = {
465
  "INPUT_ROOT": "/kaggle/input",
466
  "WORK_ROOT": "/tmp/hudanet_v27",
@@ -3250,7 +3333,7 @@ import pandas as pd
3250
  from scipy import sparse
3251
  import joblib
3252
 
3253
- UI_VERSION = "38.0.5"
3254
  UI_CONFIG = {
3255
  "INPUT_ROOT": "/kaggle/input",
3256
  "RUNTIME_DATASET_SLUG": "hudanet-bilingual-certified-runtime",
@@ -5756,10 +5839,25 @@ def validate_answer_quality_v36_4() -> dict:
5756
  add("principle_and_consequence_rendered","**القاعدة:**" in principle.answer and "**وعند مخالفة القاعدة:**" in principle.answer,principle.answer)
5757
  add("answerability_used_sources",len(principle.details.get("used_record_ids",[]))>=2,principle.details)
5758
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5759
  failed=[item for item in checks if not item["passed"]]
5760
  if failed:
5761
- raise RuntimeError("HUDA-Net v38.0.5 generic proposition self-test failed: "+json.dumps(failed,ensure_ascii=False))
5762
- print(f"✅ HUDA-Net v38.0.5 generic proposition self-test passed: {len(checks)} checks")
5763
  return {"passed":True,"tested":len(checks),"checks":checks}
5764
 
5765
  def validate_specificity_guard_v33(engine: ProfessionalEvidenceEngine) -> dict:
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ HUDA-Net Query-Conditioned Proposition Graph v38.0.6 Academic Integrated — Gradio Stable
4
  =============================
5
  Deploy this file as app.py in a Hugging Face Space and add HF_TOKEN as a
6
  read-only Space secret.
 
257
  )
258
  if not unrelated_rejected or not required_roles.issubset(roles):
259
  raise RuntimeError(
260
+ "HUDA-Net v38.0.6 preflight failed before runtime download: "
261
  + json.dumps(
262
  {
263
  "unrelated_rejected": unrelated_rejected,
 
310
  )
311
  if not relevant_passed or not distractor_rejected or not residue_pruned:
312
  raise RuntimeError(
313
+ "HUDA-Net v38.0.6 semantic-alignment preflight failed before runtime download: "
314
  + json.dumps(
315
  {
316
  "relevant_passed": relevant_passed,
 
384
  )
385
  if not principle_ok:
386
  raise RuntimeError(
387
+ "HUDA-Net v38.0.6 hierarchical principle preflight failed before runtime download: "
388
  + json.dumps(
389
  {
390
  "request_type": principle_result.query.primary_request_type,
 
397
  )
398
  )
399
 
400
+ # Validate contextual sense separation, OCR integrity, and central ruling
401
+ # frames with domain-neutral synthetic evidence before the runtime download.
402
+ sense_query = "ما هو حكم تنفيذ العامل للعملية؟"
403
+ sense_sources = [
404
+ {
405
+ "record_id":"preflight-sense-main", "book_id":"sense-main",
406
+ "book":"كتاب الحكم المركزي", "title":"حكم تنفيذ العامل للعملية",
407
+ "question":sense_query, "ruling":"لا يجب / يصح",
408
+ "answer":"لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله.",
409
+ "source_kind":"clean certified source", "direct_probability":0.94,
410
+ "score":0.94, "dense_score":0.93, "cross_encoder_score":0.82,
411
+ "bm25_score":0.91, "retriever_agreement":5,
412
+ },
413
+ {
414
+ "record_id":"preflight-sense-supplement", "book_id":"sense-supplement",
415
+ "book":"كتاب الحكم المكمل", "title":"تنفيذ العامل والأهلية",
416
+ "question":"هل يجزئ تنفيذ العامل قبل اكتمال الأهلية؟",
417
+ "ruling":"صحيح غير مجزئ",
418
+ "answer":"يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي. فإذا اكتملت الأهلية وجب التنفيذ.",
419
+ "source_kind":"clean certified source", "direct_probability":0.90,
420
+ "score":0.90, "dense_score":0.92, "cross_encoder_score":0.80,
421
+ "bm25_score":0.88, "retriever_agreement":5,
422
+ },
423
+ {
424
+ "record_id":"preflight-sense-homograph", "book_id":"sense-homograph",
425
+ "book":"مصدر اللفظ المشترك", "title":"قبول العملية وآثارها",
426
+ "question":"ما علامة الانتفاع بالعملية بعد الرجوع؟", "ruling":"مقصد عام",
427
+ "answer":"أن يرجع العامل أصلح حالا وأكثر التزاما.",
428
+ "source_kind":"clean certified source", "direct_probability":0.97,
429
+ "score":0.97, "dense_score":0.95, "cross_encoder_score":0.79,
430
+ "bm25_score":0.76, "retriever_agreement":4,
431
+ },
432
+ {
433
+ "record_id":"preflight-sense-ocr", "book_id":"sense-ocr",
434
+ "book":"مصدر النص المشوه", "title":"حكم تنفيذ العامل للعملية",
435
+ "question":sense_query, "ruling":"تفصيل",
436
+ "answer":"خلاصة السجل: لاء إلا أن يفيق مساكل أفصئ دأؤود.",
437
+ "source_kind":"raw OCR source", "direct_probability":0.91,
438
+ "score":0.91, "dense_score":0.92, "cross_encoder_score":0.74,
439
+ "bm25_score":0.80, "retriever_agreement":4,
440
+ },
441
+ ]
442
+ sense_result = pipeline.resolve(sense_query, sense_sources, "ar")
443
+ sense_main_ok = any(
444
+ item.evidence.record_id == "preflight-sense-main" and item.accepted
445
+ for item in sense_result.ranked
446
+ )
447
+ homograph_rejected = any(
448
+ item.evidence.record_id == "preflight-sense-homograph"
449
+ and not item.accepted
450
+ and "contextual_sense_mismatch" in item.hard_rejections
451
+ for item in sense_result.ranked
452
+ )
453
+ ocr_rejected = any(
454
+ item.evidence.record_id == "preflight-sense-ocr"
455
+ and not item.accepted
456
+ and "evidence_text_integrity_failed" in item.hard_rejections
457
+ for item in sense_result.ranked
458
+ )
459
+ ruling_frame_ok = (
460
+ sense_result.query.primary_request_type == "ruling"
461
+ and "**الحكم المختصر:**" in sense_result.answer
462
+ and "لا يجب تنفيذ العامل للعملية" in sense_result.answer
463
+ and "لا يجزئ عن الالتزام الأصلي" in sense_result.answer
464
+ and "أصلح حالا" not in sense_result.answer
465
+ and "أفصئ دأؤود" not in sense_result.answer
466
+ )
467
+ if not sense_main_ok or not homograph_rejected or not ocr_rejected or not ruling_frame_ok:
468
+ raise RuntimeError(
469
+ "HUDA-Net v38.0.6 contextual-sense, integrity, and ruling-frame preflight failed before runtime download: "
470
+ + json.dumps(
471
+ {
472
+ "sense_main_ok": sense_main_ok,
473
+ "homograph_rejected": homograph_rejected,
474
+ "ocr_rejected": ocr_rejected,
475
+ "ruling_frame_ok": ruling_frame_ok,
476
+ "answer": sense_result.answer,
477
+ "details": sense_result.details,
478
+ },
479
+ ensure_ascii=False,
480
+ )
481
+ )
482
+
483
  # Validate the compare-sources contract with coherent generic evidence before
484
  # downloading the multi-gigabyte runtime. This catches audit-fixture regressions
485
  # immediately and verifies real synthesis rather than bypassing the gate.
 
514
  primary_ok = ("Book 1" in primary_only.answer) ^ ("Book 2" in primary_only.answer)
515
  if not compare_ok or not primary_ok or compared.answer == primary_only.answer:
516
  raise RuntimeError(
517
+ "HUDA-Net v38.0.6 compare-mode preflight failed before runtime download: "
518
  + json.dumps(
519
  {
520
  "compare_ok": compare_ok,
 
526
  )
527
  )
528
  print(
529
+ "✅ HUDA-Net v38.0.6 lightweight proposition, contextual-sense, text-integrity, central-ruling-frame, hierarchical-intent, relation-graph, issue-clustering, semantic-alignment, and compare-mode preflight "
530
  "passed before runtime download"
531
  )
532
 
 
543
 
544
  _download_hudanet_private_datasets()
545
 
546
+ VERSION = "38.0.6"
547
  CONFIG = {
548
  "INPUT_ROOT": "/kaggle/input",
549
  "WORK_ROOT": "/tmp/hudanet_v27",
 
3333
  from scipy import sparse
3334
  import joblib
3335
 
3336
+ UI_VERSION = "38.0.6"
3337
  UI_CONFIG = {
3338
  "INPUT_ROOT": "/kaggle/input",
3339
  "RUNTIME_DATASET_SLUG": "hudanet-bilingual-certified-runtime",
 
5839
  add("principle_and_consequence_rendered","**القاعدة:**" in principle.answer and "**وعند مخالفة القاعدة:**" in principle.answer,principle.answer)
5840
  add("answerability_used_sources",len(principle.details.get("used_record_ids",[]))>=2,principle.details)
5841
 
5842
+ sense_query="ما هو حكم تنفيذ العامل للعملية؟"
5843
+ sense_sources=[
5844
+ {"record_id":"sense-main","book_id":"sm","book":"كتاب الحكم المركزي","title":"حكم تنفيذ العامل للعملية","question":sense_query,"ruling":"لا يجب / يصح","answer":"لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله.","source_kind":"clean certified source","direct_probability":0.94,"score":0.94,"dense_score":0.93,"cross_encoder_score":0.82,"bm25_score":0.91,"retriever_agreement":5},
5845
+ {"record_id":"sense-supplement","book_id":"ss","book":"كتاب الحكم المكمل","title":"تنفيذ العامل والأهلية","question":"هل يجزئ تنفيذ العامل قبل اكتمال الأهلية؟","ruling":"صحيح غير مجزئ","answer":"يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي. فإذا اكتملت الأهلية وجب التنفيذ.","source_kind":"clean certified source","direct_probability":0.90,"score":0.90,"dense_score":0.92,"cross_encoder_score":0.80,"bm25_score":0.88,"retriever_agreement":5},
5846
+ {"record_id":"sense-homograph","book_id":"sh","book":"مصدر اللفظ المشترك","title":"قبول العملية وآثارها","question":"ما علامة الانتفاع بالعملية بعد الرجوع؟","ruling":"مقصد عام","answer":"أن يرجع العامل أصلح حالا وأكثر التزاما.","source_kind":"clean certified source","direct_probability":0.97,"score":0.97,"dense_score":0.95,"cross_encoder_score":0.79,"bm25_score":0.76,"retriever_agreement":4},
5847
+ {"record_id":"sense-ocr","book_id":"so","book":"مصدر النص المشوه","title":"حكم تنفيذ العامل للعملية","question":sense_query,"ruling":"تفصيل","answer":"خلاصة السجل: لاء إلا أن يفيق مساكل أفصئ دأؤود.","source_kind":"raw OCR source","direct_probability":0.91,"score":0.91,"dense_score":0.92,"cross_encoder_score":0.74,"bm25_score":0.80,"retriever_agreement":4},
5848
+ ]
5849
+ sense=pipeline.resolve(sense_query,sense_sources,"ar")
5850
+ add("specific_ruling_intent",sense.query.primary_request_type=="ruling",sense.details)
5851
+ add("contextual_homograph_rejected",any(x.evidence.record_id=="sense-homograph" and not x.accepted and "contextual_sense_mismatch" in x.hard_rejections for x in sense.ranked),sense.details)
5852
+ add("ocr_integrity_rejected",any(x.evidence.record_id=="sense-ocr" and not x.accepted and "evidence_text_integrity_failed" in x.hard_rejections for x in sense.ranked),sense.details)
5853
+ add("central_ruling_frame_rendered","**الحكم المختصر:**" in sense.answer and "لا يجب تنفيذ العامل للعملية" in sense.answer,sense.answer)
5854
+ add("complementary_ruling_facet_retained","لا يجزئ عن الالتزام الأصلي" in sense.answer,sense.answer)
5855
+ add("broken_or_wrong_sense_text_not_rendered","أصلح حالا" not in sense.answer and "أفصئ دأؤود" not in sense.answer,sense.answer)
5856
+
5857
  failed=[item for item in checks if not item["passed"]]
5858
  if failed:
5859
+ raise RuntimeError("HUDA-Net v38.0.6 generic proposition self-test failed: "+json.dumps(failed,ensure_ascii=False))
5860
+ print(f"✅ HUDA-Net v38.0.6 generic proposition self-test passed: {len(checks)} checks")
5861
  return {"passed":True,"tested":len(checks),"checks":checks}
5862
 
5863
  def validate_specificity_guard_v33(engine: ProfessionalEvidenceEngine) -> dict:
hudanet_core/README.md CHANGED
@@ -1,45 +1,27 @@
1
- # HUDA-Net Hierarchical Intent and Relation-Aware Issue Graph v38.0.5
2
 
3
- This library builds answers from the current user question and grounded source records. It contains no topic-specific entity dictionary, named question scenario, or stored fiqh answer.
4
 
5
- ## Pipeline
6
 
7
- 1. `query.py` creates an initial multi-label interpretation of the current question.
8
- 2. `relation_graph.py` extracts directed semantic relations such as acting on behalf of another party, receiving something from another party, sequence, responsibility, and prerequisite order.
9
- 3. `evidence.py` converts each retrieved record into a normalized evidence frame, including answer type, outcome, relation graph, directness, and source quality.
10
- 4. `compatibility.py` performs first-pass logical scoring using request type, semantic-mass coverage, relation direction, polarity, source quality, answerability, and retrieval support.
11
- 5. `intent_repair.py` lets strong, mutually consistent evidence repair a weak initial request-type decision without using named scenarios.
12
- 6. The evidence is re-ranked against the repaired query interpretation.
13
- 7. `issue_clustering.py` separates nearby sub-issues by their directed relations, residual concepts, outcomes, and answer structure.
14
- 8. `answerability.py` checks whether each candidate can contribute a grounded proposition to the requested answer.
15
- 9. `consensus.py` selects the dominant issue cluster, combines compatible dimensions, and separates genuine conflicts.
16
- 10. `propositions.py` extracts atomic propositions, preserves source order, validates grounding, and clusters paraphrases.
17
- 11. `grounding.py` verifies that every displayed statement is traceable to accepted source fields.
18
- 12. `synthesis.py` plans the final answer from the selected issue cluster and its supported propositions.
19
- 13. `rendering.py` formats definitions, principles, conditions, rulings, consequences, comparisons, and conflicts professionally with source attribution.
20
- 14. `pipeline.py` assigns final exact, related, and distant tiers only after answer contribution and answerability are known.
21
 
22
- ## Design guarantees
23
 
24
- - Conversation history is display-only and never enters retrieval.
25
- - No domain-specific topic aliases or question-specific answers are stored in the core library.
26
- - Neural and lexical retrievers nominate candidates; logical and relational compatibility decides whether they may build the answer.
27
- - Directed relations distinguish, for example, acting for another party from receiving funding from another party even when surface words overlap.
28
- - Weak initial intent classifications can be repaired by coherent evidence, but only through generic multi-signal voting.
29
- - The final answer is built from one dominant issue cluster rather than mixing every nearby sub-issue.
30
- - A record cannot appear in the exact tier unless it is answerable and contributed to the grounded answer.
31
- - A high confidence value cannot accompany an abstention or an ungrounded answer.
32
- - Every displayed proposition records its source, page, and contributing record IDs.
33
- - If clean compatible evidence is insufficient, the system abstains instead of dumping fragments.
34
-
35
- ## v38.0.5
36
-
37
- - Added hierarchical multi-label request-type interpretation.
38
- - Added evidence-conditioned intent repair.
39
- - Added directed semantic relation graphs.
40
- - Added relation-aware compatibility and hard rejection for reversed or unrelated roles.
41
- - Added sub-issue clustering and dominant-cluster selection.
42
- - Added principle/rule answer planning with rule, consequence, support, and sources.
43
- - Added answerability-aware exact/related/distant tier assignment.
44
- - Added confidence consistency checks for abstentions.
45
- - Added regressions for generic principle questions, side-issue separation, and relation-direction errors.
 
1
+ # HUDA-Net Generic Evidence Core v38.0.6
2
 
3
+ The core is domain-neutral. It does not contain stored jurisprudential questions, answers, entity aliases, or topic dictionaries.
4
 
5
+ ## Resolution pipeline
6
 
7
+ 1. Current-turn query normalization and multi-label request interpretation.
8
+ 2. Evidence-conditioned query focus induction.
9
+ 3. Directed relation-graph extraction.
10
+ 4. Contextual-sense alignment using one-to-one local concept co-occurrence.
11
+ 5. Text-integrity scoring for OCR and fragmented source text.
12
+ 6. Logic-first compatibility gates and multi-signal semantic rescue.
13
+ 7. Evidence-conditioned intent repair.
14
+ 8. Relation-aware sub-issue clustering.
15
+ 9. Atomic proposition extraction with grounding verification.
16
+ 10. Central ruling-frame planning across obligation, permission, validity, sufficiency, remedy, and recommendation dimensions.
17
+ 11. Consensus/conflict analysis and professional source attribution.
18
+ 12. Contribution-based exact, related, and distant evidence tiers.
 
 
19
 
20
+ ## Safety properties
21
 
22
+ - A single token cannot satisfy two different query concepts in the contextual-sense gate.
23
+ - Concepts dispersed across unrelated source fields cannot create an exact match.
24
+ - Broken OCR text cannot enter the final answer merely because its source ranked highly.
25
+ - Exact evidence must contribute a grounded proposition to the rendered answer.
26
+ - Conversation history is display-only and is excluded from retrieval.
27
+ - Dense similarity alone cannot override type, relation, sense, integrity, or grounding gates.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
hudanet_core/centrality.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Mapping
5
+
6
+ from .contextual_sense import SenseAlignment
7
+ from .text import TextProcessor
8
+ from .types import EvidenceFrame, QueryFrame
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class CentralityResult:
13
+ score: float
14
+ question_alignment: float
15
+ title_alignment: float
16
+ extra_scope_penalty: float
17
+
18
+
19
+ class ScopeCentralityEvaluator:
20
+ """Estimate whether evidence answers the central question or a side case."""
21
+
22
+ def __init__(self, text: TextProcessor):
23
+ self.text = text
24
+
25
+ def evaluate(
26
+ self,
27
+ query: QueryFrame,
28
+ evidence: EvidenceFrame,
29
+ source: Mapping[str, object],
30
+ sense: SenseAlignment,
31
+ *,
32
+ type_match: float,
33
+ directness: float,
34
+ ) -> CentralityResult:
35
+ linked_question = str(source.get("question", "") or "")
36
+ title = str(source.get("title", "") or "")
37
+ question_alignment = self.text.sentence_similarity(query.raw, linked_question, query.language) if linked_question else 0.0
38
+ title_alignment = self.text.sentence_similarity(query.raw, title, query.language) if title else 0.0
39
+
40
+ scope_terms = self.text.content_terms(f"{title} {linked_question}", query.language)
41
+ query_terms = set(query.subject_terms) | set(query.operator_terms)
42
+ residual = [term for term in scope_terms if term not in query_terms]
43
+ residual_density = len(set(residual)) / max(1, len(set(scope_terms)))
44
+ relation_narrowing = min(1.0, len(evidence.relation_edges) / 4.0)
45
+ extra_scope_penalty = min(1.0, 0.72 * residual_density + 0.28 * relation_narrowing)
46
+
47
+ score = (
48
+ 0.28 * max(question_alignment, title_alignment)
49
+ + 0.22 * sense.score
50
+ + 0.18 * sense.joint_scope_coverage
51
+ + 0.17 * type_match
52
+ + 0.15 * directness
53
+ - 0.18 * extra_scope_penalty
54
+ )
55
+ return CentralityResult(
56
+ score=max(0.0, min(1.0, score)),
57
+ question_alignment=max(0.0, min(1.0, question_alignment)),
58
+ title_alignment=max(0.0, min(1.0, title_alignment)),
59
+ extra_scope_penalty=max(0.0, min(1.0, extra_scope_penalty)),
60
+ )
hudanet_core/compatibility.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
  from typing import Dict, List, Mapping
4
 
5
  from .relation_graph import RelationGraphAnalyzer
 
 
6
  from .text import TextProcessor
7
  from .types import EvidenceFrame, QueryFrame, ScoredEvidence
8
 
@@ -18,6 +20,8 @@ class CompatibilityScorer:
18
  self.weights = {key: float(value) for key, value in ranking.get("weights", {}).items()}
19
  self.thresholds = {key: float(value) for key, value in ranking.get("thresholds", {}).items()}
20
  self.compatibility = ranking.get("type_compatibility", {})
 
 
21
 
22
  @staticmethod
23
  def _bounded_score(value: object) -> float:
@@ -150,6 +154,10 @@ class CompatibilityScorer:
150
  evidence_relation_edges = tuple(edge for edge in evidence.relation_edges if edge[1] in strict_relations)
151
  relation_alignment = self.relations.alignment(query_relation_edges, evidence_relation_edges) if query_relation_edges else 0.75
152
  principle_strength = evidence.principle_strength if query.primary_request_type == "principle" else 0.65
 
 
 
 
153
 
154
  source_quality = 0.68
155
  source_kind = str(source.get("source_kind", "")).casefold()
@@ -159,12 +167,15 @@ class CompatibilityScorer:
159
  source_quality = min(source_quality, 0.58)
160
 
161
  answerability = min(1.0,
162
- 0.20 * type_match
163
- + 0.23 * topic_metrics["semantic_mass_coverage"]
164
- + 0.18 * directness
165
- + 0.15 * evidence.answer_quality
166
- + 0.14 * relation_alignment
167
- + 0.10 * principle_strength
 
 
 
168
  )
169
  metrics = {
170
  "request_type_match": type_match,
@@ -179,6 +190,17 @@ class CompatibilityScorer:
179
  "relation_alignment": relation_alignment,
180
  "principle_strength": principle_strength,
181
  "answerability": answerability,
 
 
 
 
 
 
 
 
 
 
 
182
  }
183
  score = sum(self.weights.get(name, 0.0) * value for name, value in metrics.items())
184
 
@@ -199,6 +221,9 @@ class CompatibilityScorer:
199
  and matched_ratio >= self.thresholds.get("rescue_matched_concept_ratio", 0.60)
200
  and directness >= self.thresholds.get("rescue_directness", 0.58)
201
  and evidence.answer_quality >= self.thresholds.get("minimum_answer_quality", 0.48)
 
 
 
202
  and (
203
  neural >= self.thresholds.get("rescue_neural_prior", 0.72)
204
  or agreement >= self.thresholds.get("rescue_retriever_agreement", 0.40)
@@ -259,6 +284,21 @@ class CompatibilityScorer:
259
  and not semantic_rescue
260
  ):
261
  hard_rejections.append("directed_relation_mismatch")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  if answerability < self.thresholds.get("minimum_answerability", 0.48) and not semantic_rescue:
263
  hard_rejections.append("insufficient_answerability")
264
  if not evidence.answer_text.strip():
@@ -278,6 +318,12 @@ class CompatibilityScorer:
278
  reasons.append("قيود الواقعة مغطاة بوضوح" if query.language == "ar" else "Case constraints are clearly covered")
279
  if relation_alignment >= 0.70 and query_relation_edges:
280
  reasons.append("اتجاه العلاقة في السؤال مغطى داخل الشاهد" if query.language == "ar" else "The directed relation in the question is covered")
 
 
 
 
 
 
281
  if directness >= 0.72:
282
  reasons.append("النتيجة مباشرة وقابلة للبناء عليها" if query.language == "ar" else "The outcome is direct and usable")
283
  if evidence.answer_quality >= 0.75:
 
3
  from typing import Dict, List, Mapping
4
 
5
  from .relation_graph import RelationGraphAnalyzer
6
+ from .contextual_sense import ContextualSenseAligner
7
+ from .centrality import ScopeCentralityEvaluator
8
  from .text import TextProcessor
9
  from .types import EvidenceFrame, QueryFrame, ScoredEvidence
10
 
 
20
  self.weights = {key: float(value) for key, value in ranking.get("weights", {}).items()}
21
  self.thresholds = {key: float(value) for key, value in ranking.get("thresholds", {}).items()}
22
  self.compatibility = ranking.get("type_compatibility", {})
23
+ self.sense = ContextualSenseAligner(text)
24
+ self.centrality = ScopeCentralityEvaluator(text)
25
 
26
  @staticmethod
27
  def _bounded_score(value: object) -> float:
 
154
  evidence_relation_edges = tuple(edge for edge in evidence.relation_edges if edge[1] in strict_relations)
155
  relation_alignment = self.relations.alignment(query_relation_edges, evidence_relation_edges) if query_relation_edges else 0.75
156
  principle_strength = evidence.principle_strength if query.primary_request_type == "principle" else 0.65
157
+ sense = self.sense.evaluate(query, evidence, source)
158
+ centrality = self.centrality.evaluate(
159
+ query, evidence, source, sense, type_match=type_match, directness=directness
160
+ )
161
 
162
  source_quality = 0.68
163
  source_kind = str(source.get("source_kind", "")).casefold()
 
167
  source_quality = min(source_quality, 0.58)
168
 
169
  answerability = min(1.0,
170
+ 0.15 * type_match
171
+ + 0.17 * topic_metrics["semantic_mass_coverage"]
172
+ + 0.14 * directness
173
+ + 0.13 * evidence.answer_quality
174
+ + 0.10 * relation_alignment
175
+ + 0.07 * principle_strength
176
+ + 0.10 * sense.score
177
+ + 0.07 * sense.joint_scope_coverage
178
+ + 0.07 * evidence.integrity_score
179
  )
180
  metrics = {
181
  "request_type_match": type_match,
 
190
  "relation_alignment": relation_alignment,
191
  "principle_strength": principle_strength,
192
  "answerability": answerability,
193
+ "sense_alignment": sense.score,
194
+ "joint_scope_coverage": sense.joint_scope_coverage,
195
+ "metadata_joint_coverage": sense.metadata_joint_coverage,
196
+ "answer_joint_coverage": sense.answer_joint_coverage,
197
+ "sense_dispersion_risk": sense.dispersion_risk,
198
+ "local_context_alignment": sense.local_context_alignment,
199
+ "evidence_integrity": evidence.integrity_score,
200
+ "scope_centrality": centrality.score,
201
+ "linked_question_alignment": centrality.question_alignment,
202
+ "title_alignment": centrality.title_alignment,
203
+ "extra_scope_penalty": centrality.extra_scope_penalty,
204
  }
205
  score = sum(self.weights.get(name, 0.0) * value for name, value in metrics.items())
206
 
 
221
  and matched_ratio >= self.thresholds.get("rescue_matched_concept_ratio", 0.60)
222
  and directness >= self.thresholds.get("rescue_directness", 0.58)
223
  and evidence.answer_quality >= self.thresholds.get("minimum_answer_quality", 0.48)
224
+ and evidence.integrity_score >= self.thresholds.get("rescue_evidence_integrity", 0.54)
225
+ and sense.score >= self.thresholds.get("rescue_sense_alignment", 0.60)
226
+ and sense.joint_scope_coverage >= self.thresholds.get("rescue_joint_scope_coverage", 0.62)
227
  and (
228
  neural >= self.thresholds.get("rescue_neural_prior", 0.72)
229
  or agreement >= self.thresholds.get("rescue_retriever_agreement", 0.40)
 
284
  and not semantic_rescue
285
  ):
286
  hard_rejections.append("directed_relation_mismatch")
287
+ if (
288
+ len(query.subject_terms) > 1
289
+ and sense.score < self.thresholds.get("minimum_sense_alignment", 0.52)
290
+ and sense.joint_scope_coverage < self.thresholds.get("minimum_joint_scope_coverage", 0.62)
291
+ and not semantic_rescue
292
+ ):
293
+ hard_rejections.append("contextual_sense_mismatch")
294
+ if (
295
+ sense.dispersion_risk > self.thresholds.get("maximum_sense_dispersion_risk", 0.46)
296
+ and sense.joint_scope_coverage < self.thresholds.get("minimum_joint_scope_coverage", 0.62)
297
+ and not semantic_rescue
298
+ ):
299
+ hard_rejections.append("query_concepts_only_distributed_across_unrelated_fields")
300
+ if evidence.integrity_score < self.thresholds.get("minimum_evidence_integrity", 0.44):
301
+ hard_rejections.append("evidence_text_integrity_failed")
302
  if answerability < self.thresholds.get("minimum_answerability", 0.48) and not semantic_rescue:
303
  hard_rejections.append("insufficient_answerability")
304
  if not evidence.answer_text.strip():
 
318
  reasons.append("قيود الواقعة مغطاة بوضوح" if query.language == "ar" else "Case constraints are clearly covered")
319
  if relation_alignment >= 0.70 and query_relation_edges:
320
  reasons.append("اتجاه العلاقة في السؤال مغطى داخل الشاهد" if query.language == "ar" else "The directed relation in the question is covered")
321
+ if sense.score >= 0.72 and sense.joint_scope_coverage >= 0.72:
322
+ reasons.append("مفاهيم السؤال تجتمع في سياق واحد بالمعنى نفسه" if query.language == "ar" else "Query concepts co-occur in one coherent sense context")
323
+ if evidence.integrity_score >= 0.70:
324
+ reasons.append("سلامة النص تسمح ببناء جواب موثق" if query.language == "ar" else "Text integrity supports grounded synthesis")
325
+ if centrality.score >= 0.68:
326
+ reasons.append("الشاهد يجيب عن مركز السؤال لا عن مسألة جانبية" if query.language == "ar" else "The evidence addresses the central question rather than a side case")
327
  if directness >= 0.72:
328
  reasons.append("النتيجة مباشرة وقابلة للبناء عليها" if query.language == "ar" else "The outcome is direct and usable")
329
  if evidence.answer_quality >= 0.75:
hudanet_core/contextual_sense.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Mapping, Sequence, Tuple
5
+
6
+ from .text import TextProcessor
7
+ from .types import EvidenceFrame, QueryFrame
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class SenseAlignment:
12
+ score: float
13
+ joint_scope_coverage: float
14
+ metadata_joint_coverage: float
15
+ answer_joint_coverage: float
16
+ dispersion_risk: float
17
+ local_context_alignment: float
18
+
19
+
20
+ class ContextualSenseAligner:
21
+ """Disambiguate shared surface terms through local co-occurrence structure.
22
+
23
+ A term may occur in two records with different meanings. The aligner therefore
24
+ asks whether the query concepts occur together inside one coherent local unit,
25
+ rather than accepting a record where each concept appears in an unrelated field.
26
+ No entity dictionary or domain-specific sense inventory is used.
27
+ """
28
+
29
+ def __init__(self, text: TextProcessor):
30
+ self.text = text
31
+
32
+ def _units(self, values: Sequence[object], lang: str) -> Tuple[Tuple[str, ...], ...]:
33
+ units = []
34
+ for value in values:
35
+ raw = str(value or "").strip()
36
+ if not raw:
37
+ continue
38
+ clauses = self.text.clauses(raw, lang) or (raw,)
39
+ for clause in clauses:
40
+ terms = self.text.content_terms(clause, lang)
41
+ if terms:
42
+ units.append(tuple(terms))
43
+ return tuple(units)
44
+
45
+ def _strict_unit_coverage(self, query: QueryFrame, unit: Sequence[str]) -> float:
46
+ """One-to-one concept coverage inside a local unit.
47
+
48
+ A single surface token may not satisfy two distinct query concepts. This
49
+ prevents near-homographs or a shared stem from faking local co-occurrence.
50
+ """
51
+ if not query.subject_terms or not unit:
52
+ return 0.0
53
+ candidates = []
54
+ for q_index, query_term in enumerate(query.subject_terms):
55
+ for u_index, unit_term in enumerate(unit):
56
+ similarity = self.text.term_similarity(query_term, unit_term)
57
+ if similarity >= 0.90:
58
+ candidates.append((similarity, q_index, u_index))
59
+ candidates.sort(reverse=True)
60
+ used_query = set()
61
+ used_unit = set()
62
+ matched_weight = 0.0
63
+ total_weight = sum(float(query.term_weights.get(term, 1.0)) for term in query.subject_terms)
64
+ for similarity, q_index, u_index in candidates:
65
+ if q_index in used_query or u_index in used_unit:
66
+ continue
67
+ used_query.add(q_index)
68
+ used_unit.add(u_index)
69
+ term = query.subject_terms[q_index]
70
+ matched_weight += float(query.term_weights.get(term, 1.0)) * similarity
71
+ return min(1.0, matched_weight / max(1e-9, total_weight))
72
+
73
+ def _max_coverage(self, query: QueryFrame, units: Sequence[Sequence[str]]) -> float:
74
+ if not query.subject_terms:
75
+ return 0.0
76
+ return max((self._strict_unit_coverage(query, unit) for unit in units), default=0.0)
77
+
78
+ def evaluate(self, query: QueryFrame, evidence: EvidenceFrame, source: Mapping[str, object]) -> SenseAlignment:
79
+ if len(query.subject_terms) <= 1:
80
+ return SenseAlignment(0.82, 1.0, 1.0, 1.0, 0.0, 0.82)
81
+
82
+ metadata_values = [
83
+ source.get("title", ""), source.get("question", ""), source.get("chapter", ""),
84
+ source.get("category", ""), source.get("ruling", ""),
85
+ ]
86
+ answer_values = list(evidence.answer_segments) or [evidence.answer_text]
87
+ metadata_units = self._units(metadata_values, query.language)
88
+ answer_units = self._units(answer_values, query.language)
89
+ all_units = (*metadata_units, *answer_units)
90
+
91
+ metadata_joint = self._max_coverage(query, metadata_units)
92
+ answer_joint = self._max_coverage(query, answer_units)
93
+ joint = max(metadata_joint, answer_joint)
94
+
95
+ global_terms = tuple(dict.fromkeys((*evidence.metadata_terms, *evidence.answer_terms)))
96
+ global_coverage = self.text.weighted_fuzzy_overlap(
97
+ query.subject_terms, global_terms, query.term_weights
98
+ )
99
+ dispersion_risk = max(0.0, global_coverage - joint)
100
+
101
+ # The query itself supplies the local context for each concept: the other
102
+ # supported query concepts. Evidence receives credit when they co-occur in
103
+ # one field or clause. This distinguishes homographs without a lexicon.
104
+ local_context = 0.0
105
+ if all_units:
106
+ matched_pairs = 0.0
107
+ total_pairs = 0
108
+ for index, term in enumerate(query.subject_terms):
109
+ companions = [value for pos, value in enumerate(query.subject_terms) if pos != index]
110
+ if not companions:
111
+ continue
112
+ total_pairs += 1
113
+ best = 0.0
114
+ for unit in all_units:
115
+ term_matches = sorted(
116
+ ((self.text.term_similarity(term, value), index) for index, value in enumerate(unit)),
117
+ reverse=True,
118
+ )
119
+ companion_matches = sorted(
120
+ (
121
+ (self.text.term_similarity(companion, value), index)
122
+ for companion in companions for index, value in enumerate(unit)
123
+ ),
124
+ reverse=True,
125
+ )
126
+ for term_hit, term_index in term_matches:
127
+ if term_hit < 0.90:
128
+ break
129
+ for companion_hit, companion_index in companion_matches:
130
+ if companion_hit < 0.90:
131
+ break
132
+ if companion_index != term_index:
133
+ best = max(best, min(term_hit, companion_hit))
134
+ break
135
+ matched_pairs += best
136
+ local_context = matched_pairs / max(1, total_pairs)
137
+
138
+ score = (
139
+ 0.48 * joint
140
+ + 0.22 * metadata_joint
141
+ + 0.12 * answer_joint
142
+ + 0.18 * local_context
143
+ - 0.28 * dispersion_risk
144
+ )
145
+ return SenseAlignment(
146
+ score=max(0.0, min(1.0, score)),
147
+ joint_scope_coverage=max(0.0, min(1.0, joint)),
148
+ metadata_joint_coverage=max(0.0, min(1.0, metadata_joint)),
149
+ answer_joint_coverage=max(0.0, min(1.0, answer_joint)),
150
+ dispersion_risk=max(0.0, min(1.0, dispersion_risk)),
151
+ local_context_alignment=max(0.0, min(1.0, local_context)),
152
+ )
hudanet_core/evidence.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any, Dict, List, Mapping, Tuple
5
 
6
  from .relation_graph import RelationGraphAnalyzer
7
  from .text import TextProcessor
 
8
  from .types import EvidenceFrame
9
 
10
 
@@ -14,6 +15,7 @@ class EvidenceAnalyzer:
14
  self.semantic = semantic
15
  self.schema = schema
16
  self.relations = relations
 
17
 
18
  @staticmethod
19
  def _clean(value: Any) -> str:
@@ -153,9 +155,23 @@ class EvidenceAnalyzer:
153
  if "disputed" in outcomes and len(outcomes) == 1:
154
  decisive = min(decisive, 0.38)
155
 
156
- qualities = [self._segment_quality(value, lang) for value in answer_segments]
157
- answer_quality = max(qualities or [0.0])
158
- noise_ratio = 1.0 - (sum(qualities) / max(1, len(qualities)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  completeness = min(
160
  1.0,
161
  0.15
@@ -201,4 +217,6 @@ class EvidenceAnalyzer:
201
  raw=source,
202
  relation_edges=tuple(relation_edges),
203
  principle_strength=float(principle_strength),
 
 
204
  )
 
5
 
6
  from .relation_graph import RelationGraphAnalyzer
7
  from .text import TextProcessor
8
+ from .text_integrity import TextIntegrityEvaluator
9
  from .types import EvidenceFrame
10
 
11
 
 
15
  self.semantic = semantic
16
  self.schema = schema
17
  self.relations = relations
18
+ self.integrity = TextIntegrityEvaluator(text, semantic)
19
 
20
  @staticmethod
21
  def _clean(value: Any) -> str:
 
155
  if "disputed" in outcomes and len(outcomes) == 1:
156
  decisive = min(decisive, 0.38)
157
 
158
+ legacy_qualities = [self._segment_quality(value, lang) for value in answer_segments]
159
+ integrity_results = [
160
+ self.integrity.assess(value, lang, metadata_text) for value in answer_segments
161
+ ]
162
+ integrity_scores = [result.score for result in integrity_results]
163
+ combined_qualities = [
164
+ 0.38 * legacy + 0.62 * integrity
165
+ for legacy, integrity in zip(legacy_qualities, integrity_scores)
166
+ ]
167
+ answer_quality = max(combined_qualities or [0.0])
168
+ integrity_score = max(integrity_scores or [0.0])
169
+ noise_ratio = 1.0 - (sum(combined_qualities) / max(1, len(combined_qualities)))
170
+ integrity_details = {
171
+ "best_integrity": integrity_score,
172
+ "mean_integrity": sum(integrity_scores) / max(1, len(integrity_scores)),
173
+ "clean_segment_ratio": sum(1 for value in integrity_scores if value >= 0.58) / max(1, len(integrity_scores)),
174
+ }
175
  completeness = min(
176
  1.0,
177
  0.15
 
217
  raw=source,
218
  relation_edges=tuple(relation_edges),
219
  principle_strength=float(principle_strength),
220
+ integrity_score=float(integrity_score),
221
+ integrity_details={key: float(value) for key, value in integrity_details.items()},
222
  )
hudanet_core/pipeline.py CHANGED
@@ -182,6 +182,10 @@ class GenericEvidencePipeline:
182
  and float(item.metrics.get("answer_directness", 0.0)) >= 0.58
183
  and float(item.metrics.get("answerability", 0.0)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_answerability", 0.64))
184
  and float(item.metrics.get("relation_alignment", 0.75)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_relation_alignment", 0.58))
 
 
 
 
185
  )
186
  if is_exact:
187
  source["tier"] = "exact"
 
182
  and float(item.metrics.get("answer_directness", 0.0)) >= 0.58
183
  and float(item.metrics.get("answerability", 0.0)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_answerability", 0.64))
184
  and float(item.metrics.get("relation_alignment", 0.75)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_relation_alignment", 0.58))
185
+ and float(item.metrics.get("sense_alignment", 0.0)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_sense_alignment", 0.70))
186
+ and float(item.metrics.get("joint_scope_coverage", 0.0)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_joint_scope_coverage", 0.72))
187
+ and float(item.metrics.get("evidence_integrity", 0.0)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_evidence_integrity", 0.58))
188
+ and float(item.metrics.get("scope_centrality", 0.0)) >= float(self.resources.ranking.get("thresholds", {}).get("exact_scope_centrality", 0.58))
189
  )
190
  if is_exact:
191
  source["tier"] = "exact"
hudanet_core/propositions.py CHANGED
@@ -6,6 +6,7 @@ from typing import List, Sequence, Set, Tuple
6
  from .grounding import GroundingVerifier
7
  from .roles import PropositionRoleClassifier
8
  from .text import TextProcessor
 
9
  from .types import Proposition, PropositionCluster, QueryFrame, ScoredEvidence
10
 
11
 
@@ -24,6 +25,7 @@ class PropositionExtractor:
24
  self.ranking = ranking
25
  self.grounding = GroundingVerifier(text, templates.get("preferred_answer_fields", []))
26
  self.roles = PropositionRoleClassifier(text, semantic)
 
27
 
28
  @staticmethod
29
  def _clean(value: str) -> str:
@@ -192,6 +194,7 @@ class PropositionExtractor:
192
  min_quality = float(thresholds.get("minimum_proposition_quality", 0.54))
193
  min_score = float(thresholds.get("minimum_proposition_score", 0.52))
194
  min_focus = float(thresholds.get("minimum_proposition_focus", 0.44))
 
195
 
196
  for item in selected:
197
  source_terms = tuple(dict.fromkeys((*item.evidence.metadata_terms, *item.evidence.answer_terms)))
@@ -216,8 +219,10 @@ class PropositionExtractor:
216
  continue
217
  if explicit_item and query.language == "ar":
218
  candidate = re.sub(r"^و(?=[\u0600-\u06FF])", "", candidate).strip()
219
- quality = self._quality(candidate, query.language, explicit_item=explicit_item)
220
- if quality < min_quality:
 
 
221
  rejected.append(candidate)
222
  continue
223
 
@@ -225,6 +230,12 @@ class PropositionExtractor:
225
  candidate_focus = self.text.fuzzy_term_overlap(query.qualifier_terms, terms)
226
  candidate_anchor = self.text.fuzzy_term_overlap(query.anchor_terms, terms)
227
  relation = self._relation_score(candidate, query, item, explicit_item=explicit_item)
 
 
 
 
 
 
228
  policy = self.ranking.get("proposition_policy", {}) or {}
229
  contextual_roles = set(
230
  (policy.get("contextual_roles_by_request", {}) or {}).get(
@@ -242,7 +253,7 @@ class PropositionExtractor:
242
  # query inherits that source scope. This handles pronouns and omitted
243
  # subjects without any entity-specific dictionary.
244
  inheritance = 0.0
245
- if source_type_match or explicit_item or relation >= 0.65:
246
  inheritance = 0.88 * source_focus
247
 
248
  # Evidence-conditioned proposition scope inheritance.
@@ -269,6 +280,8 @@ class PropositionExtractor:
269
  and type_alignment >= float(inheritance_cfg.get("minimum_request_type_match", 0.62))
270
  and candidate_anchor >= float(inheritance_cfg.get("minimum_candidate_anchor", 0.42))
271
  and relation >= float(inheritance_cfg.get("minimum_candidate_relation", 0.66))
 
 
272
  ):
273
  scope_trust = (
274
  0.30 * semantic_mass
@@ -295,7 +308,6 @@ class PropositionExtractor:
295
  rejected.append(candidate)
296
  continue
297
 
298
- local_outcomes = self._local_outcomes(candidate, query.language)
299
  if (
300
  query.primary_request_type == "principle"
301
  and field == "ruling"
 
6
  from .grounding import GroundingVerifier
7
  from .roles import PropositionRoleClassifier
8
  from .text import TextProcessor
9
+ from .text_integrity import TextIntegrityEvaluator
10
  from .types import Proposition, PropositionCluster, QueryFrame, ScoredEvidence
11
 
12
 
 
25
  self.ranking = ranking
26
  self.grounding = GroundingVerifier(text, templates.get("preferred_answer_fields", []))
27
  self.roles = PropositionRoleClassifier(text, semantic)
28
+ self.integrity = TextIntegrityEvaluator(text, semantic)
29
 
30
  @staticmethod
31
  def _clean(value: str) -> str:
 
194
  min_quality = float(thresholds.get("minimum_proposition_quality", 0.54))
195
  min_score = float(thresholds.get("minimum_proposition_score", 0.52))
196
  min_focus = float(thresholds.get("minimum_proposition_focus", 0.44))
197
+ min_clause_integrity = float(thresholds.get("minimum_clause_integrity", 0.48))
198
 
199
  for item in selected:
200
  source_terms = tuple(dict.fromkeys((*item.evidence.metadata_terms, *item.evidence.answer_terms)))
 
219
  continue
220
  if explicit_item and query.language == "ar":
221
  candidate = re.sub(r"^و(?=[\u0600-\u06FF])", "", candidate).strip()
222
+ legacy_quality = self._quality(candidate, query.language, explicit_item=explicit_item)
223
+ integrity = self.integrity.assess(candidate, query.language, item.evidence.metadata_text)
224
+ quality = 0.38 * legacy_quality + 0.62 * integrity.score
225
+ if quality < min_quality or integrity.score < min_clause_integrity:
226
  rejected.append(candidate)
227
  continue
228
 
 
230
  candidate_focus = self.text.fuzzy_term_overlap(query.qualifier_terms, terms)
231
  candidate_anchor = self.text.fuzzy_term_overlap(query.anchor_terms, terms)
232
  relation = self._relation_score(candidate, query, item, explicit_item=explicit_item)
233
+ local_outcomes = self._local_outcomes(candidate, query.language)
234
+ structured_clause = (
235
+ preliminary_role != "statement"
236
+ or any(value != "unspecified" for value in local_outcomes)
237
+ or explicit_item
238
+ )
239
  policy = self.ranking.get("proposition_policy", {}) or {}
240
  contextual_roles = set(
241
  (policy.get("contextual_roles_by_request", {}) or {}).get(
 
253
  # query inherits that source scope. This handles pronouns and omitted
254
  # subjects without any entity-specific dictionary.
255
  inheritance = 0.0
256
+ if structured_clause and (source_type_match or explicit_item or relation >= 0.65):
257
  inheritance = 0.88 * source_focus
258
 
259
  # Evidence-conditioned proposition scope inheritance.
 
280
  and type_alignment >= float(inheritance_cfg.get("minimum_request_type_match", 0.62))
281
  and candidate_anchor >= float(inheritance_cfg.get("minimum_candidate_anchor", 0.42))
282
  and relation >= float(inheritance_cfg.get("minimum_candidate_relation", 0.66))
283
+ and integrity.score >= float(inheritance_cfg.get("minimum_clause_integrity", 0.55))
284
+ and (structured_clause or candidate_focus >= 0.24)
285
  ):
286
  scope_trust = (
287
  0.30 * semantic_mass
 
308
  rejected.append(candidate)
309
  continue
310
 
 
311
  if (
312
  query.primary_request_type == "principle"
313
  and field == "ruling"
hudanet_core/query.py CHANGED
@@ -123,14 +123,21 @@ class QueryAnalyzer:
123
  matched_intent_tokens = set()
124
  for type_id, rule in self.semantic.get("request_types", {}).items():
125
  patterns = (rule.get("patterns", {}) or {}).get(lang, []) or []
 
126
  hits = sum(1 for pattern in patterns if self.text.phrase_hit(normalized, pattern))
127
- if hits:
 
128
  priority = float(rule.get("priority", 1.0) or 1.0)
129
- score = min(1.0, min(1.0, 0.58 + 0.18 * hits) * priority)
 
 
 
 
 
130
  request_scores.append((type_id, score))
131
  request_type_scores[type_id] = max(request_type_scores.get(type_id, 0.0), score)
132
  matched_intent_tokens.update(
133
- self.text.content_terms(" ".join(rule.get("keywords", {}).get(lang, [])), lang)
134
  )
135
  request_scores.sort(key=lambda item: item[1], reverse=True)
136
  request_types = tuple(item[0] for item in request_scores)
 
123
  matched_intent_tokens = set()
124
  for type_id, rule in self.semantic.get("request_types", {}).items():
125
  patterns = (rule.get("patterns", {}) or {}).get(lang, []) or []
126
+ keywords = (rule.get("keywords", {}) or {}).get(lang, []) or []
127
  hits = sum(1 for pattern in patterns if self.text.phrase_hit(normalized, pattern))
128
+ keyword_hits = sum(1 for keyword in keywords if self.text.phrase_hit(normalized, keyword))
129
+ if hits or keyword_hits:
130
  priority = float(rule.get("priority", 1.0) or 1.0)
131
+ # Generic shells such as "what is" / "ما هو" must not outrank a
132
+ # more specific request word that appears in the same question.
133
+ lexical_specificity = min(0.30, 0.12 * keyword_hits)
134
+ score = min(1.0, (0.42 + 0.18 * hits + lexical_specificity) * priority)
135
+ if hits and not keyword_hits and type_id == "description":
136
+ score *= 0.68
137
  request_scores.append((type_id, score))
138
  request_type_scores[type_id] = max(request_type_scores.get(type_id, 0.0), score)
139
  matched_intent_tokens.update(
140
+ self.text.content_terms(" ".join(keywords), lang)
141
  )
142
  request_scores.sort(key=lambda item: item[1], reverse=True)
143
  request_types = tuple(item[0] for item in request_scores)
hudanet_core/resources/answer_templates.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "version": "2.1.0",
3
  "answer_prefix": {
4
  "ar": "**الإجابة:** ",
5
  "en": "**Answer:** "
@@ -136,5 +136,13 @@
136
  "support_heading": {
137
  "ar": "التفصيل أو الدليل",
138
  "en": "Supporting detail or evidence"
 
 
 
 
 
 
 
 
139
  }
140
  }
 
1
  {
2
+ "version": "1.4.0",
3
  "answer_prefix": {
4
  "ar": "**الإجابة:** ",
5
  "en": "**Answer:** "
 
136
  "support_heading": {
137
  "ar": "التفصيل أو الدليل",
138
  "en": "Supporting detail or evidence"
139
+ },
140
+ "ruling_summary_heading": {
141
+ "ar": "الحكم المختصر",
142
+ "en": "Ruling summary"
143
+ },
144
+ "ruling_detail_heading": {
145
+ "ar": "التفصيل والاستثناءات",
146
+ "en": "Details and exceptions"
147
  }
148
  }
hudanet_core/resources/ranking_config.json CHANGED
@@ -1,22 +1,26 @@
1
  {
2
- "version": "2.3.0",
3
- "description": "Domain-neutral hierarchical intent repair, directed relation alignment, issue clustering, answerability-aware tiers, proposition ranking, and grounded synthesis.",
4
  "weights": {
5
- "request_type_match": 0.112,
6
- "topic_match": 0.064,
7
- "anchor_coverage": 0.072,
8
- "query_focus_coverage": 0.096,
9
- "semantic_mass_coverage": 0.16,
10
- "critical_constraint_coverage": 0.08,
11
- "polarity_alignment": 0.064,
12
- "answer_directness": 0.064,
13
- "evidence_completeness": 0.024,
14
- "answer_quality": 0.032,
15
- "source_quality": 0.016,
16
- "neural_prior": 0.016,
17
- "relation_alignment": 0.1,
18
- "principle_strength": 0.04,
19
- "answerability": 0.06
 
 
 
 
20
  },
21
  "thresholds": {
22
  "accept": 0.53,
@@ -46,7 +50,19 @@
46
  "hard_relation_mismatch": 0.23,
47
  "minimum_answerability": 0.48,
48
  "exact_answerability": 0.64,
49
- "exact_relation_alignment": 0.58
 
 
 
 
 
 
 
 
 
 
 
 
50
  },
51
  "type_compatibility": {
52
  "definition": {
@@ -291,7 +307,8 @@
291
  "inheritance_weight": 0.9,
292
  "maximum_inherited_scope": 0.92,
293
  "requires_accepted_evidence": true,
294
- "description": "Generic multi-signal scope inheritance for grounded atomic clauses from already accepted evidence. Dense or lexical similarity alone is insufficient."
 
295
  }
296
  },
297
  "intent_repair": {
@@ -305,5 +322,44 @@
305
  "issue_clustering": {
306
  "pair_threshold": 0.33,
307
  "minimum_cluster_books_for_broad_principle": 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  }
309
- }
 
1
  {
2
+ "version": "2.4.0",
3
+ "description": "Domain-neutral contextual-sense alignment, text-integrity gating, central ruling frames, hierarchical intent repair, directed relations, issue clustering, answerability-aware tiers, and grounded synthesis.",
4
  "weights": {
5
+ "request_type_match": 0.085,
6
+ "topic_match": 0.04,
7
+ "anchor_coverage": 0.05,
8
+ "query_focus_coverage": 0.06,
9
+ "semantic_mass_coverage": 0.11,
10
+ "critical_constraint_coverage": 0.055,
11
+ "polarity_alignment": 0.045,
12
+ "answer_directness": 0.05,
13
+ "evidence_completeness": 0.015,
14
+ "answer_quality": 0.03,
15
+ "source_quality": 0.015,
16
+ "neural_prior": 0.015,
17
+ "relation_alignment": 0.07,
18
+ "principle_strength": 0.02,
19
+ "answerability": 0.05,
20
+ "sense_alignment": 0.085,
21
+ "joint_scope_coverage": 0.05,
22
+ "evidence_integrity": 0.055,
23
+ "scope_centrality": 0.1
24
  },
25
  "thresholds": {
26
  "accept": 0.53,
 
50
  "hard_relation_mismatch": 0.23,
51
  "minimum_answerability": 0.48,
52
  "exact_answerability": 0.64,
53
+ "exact_relation_alignment": 0.58,
54
+ "minimum_sense_alignment": 0.52,
55
+ "minimum_joint_scope_coverage": 0.62,
56
+ "maximum_sense_dispersion_risk": 0.46,
57
+ "minimum_evidence_integrity": 0.44,
58
+ "rescue_sense_alignment": 0.6,
59
+ "rescue_joint_scope_coverage": 0.62,
60
+ "rescue_evidence_integrity": 0.54,
61
+ "exact_sense_alignment": 0.7,
62
+ "exact_joint_scope_coverage": 0.72,
63
+ "exact_evidence_integrity": 0.58,
64
+ "exact_scope_centrality": 0.58,
65
+ "minimum_clause_integrity": 0.48
66
  },
67
  "type_compatibility": {
68
  "definition": {
 
307
  "inheritance_weight": 0.9,
308
  "maximum_inherited_scope": 0.92,
309
  "requires_accepted_evidence": true,
310
+ "description": "Generic multi-signal scope inheritance for grounded atomic clauses from already accepted evidence. Dense or lexical similarity alone is insufficient.",
311
+ "minimum_clause_integrity": 0.55
312
  }
313
  },
314
  "intent_repair": {
 
322
  "issue_clustering": {
323
  "pair_threshold": 0.33,
324
  "minimum_cluster_books_for_broad_principle": 2
325
+ },
326
+ "ruling_frame": {
327
+ "central_cluster_floor": 0.56,
328
+ "family_order": [
329
+ "obligation",
330
+ "permission",
331
+ "validity",
332
+ "sufficiency",
333
+ "remedy",
334
+ "recommendation",
335
+ "other"
336
+ ],
337
+ "outcome_families": {
338
+ "obligation": [
339
+ "obligatory",
340
+ "not_obligatory",
341
+ "obligation_dropped"
342
+ ],
343
+ "permission": [
344
+ "permissible",
345
+ "prohibited"
346
+ ],
347
+ "validity": [
348
+ "valid",
349
+ "invalid"
350
+ ],
351
+ "sufficiency": [
352
+ "sufficient",
353
+ "not_sufficient"
354
+ ],
355
+ "remedy": [
356
+ "remedy_required",
357
+ "no_remedy"
358
+ ],
359
+ "recommendation": [
360
+ "recommended",
361
+ "disliked"
362
+ ]
363
+ }
364
  }
365
+ }
hudanet_core/resources/semantic_rules.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
- "version": "2.3.0",
3
- "description": "Domain-neutral hierarchical request understanding, directed relation graphs, evidence-conditioned intent repair, outcomes, and proposition roles.",
4
  "decisive_request_types": [
5
  "ruling",
6
  "validity",
@@ -986,6 +986,7 @@
986
  },
987
  "patterns": {
988
  "ar": [
 
989
  "ما\\s+(?:هو\\s+)?الحكم",
990
  "ما\\s+حكم",
991
  "هل\\s+(?:يجوز|يجب|يحرم|يلزم)",
@@ -1163,6 +1164,28 @@
1163
  "\\b(?:pillar|essential pillar)\\b"
1164
  ]
1165
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1166
  }
1167
  },
1168
  "incompatible_outcomes": [
@@ -1185,6 +1208,10 @@
1185
  [
1186
  "remedy_required",
1187
  "no_remedy"
 
 
 
 
1188
  ]
1189
  ],
1190
  "proposition_relations": {
@@ -1309,7 +1336,7 @@
1309
  "priority": 90,
1310
  "patterns": {
1311
  "ar": [
1312
- "^(?:اذا|إذا|إن|ان|لو|عند\\s+عدم|في\\s+حال)\\b.+(?:،|\\s)(?:ف?يجب|ف?يجوز|ف?لا|ف?يسقط|ف?يلزم|ف?يفعل|ف?تستعمل|ف?يستعمل|ف?تنوب|ف?ينوب|ف?تستنيب|ف?يستنيب|ف?ينصرف|ف?يقع|ف?يبطل|ف?يصح|ف?يجزئ|ف?يجزي|ف?لا\\s+يجزئ|ف?لا\\s+يجزي)",
1313
  "^(?:تنصرف|ينصرف|يقع|تؤول|يؤول|تعود|يعود|يبطل|يصح)\\b"
1314
  ],
1315
  "en": [
 
1
  {
2
+ "version": "2.4.0",
3
+ "description": "Domain-neutral semantic rules for request types, outcomes, proposition roles, relation markers, contextual sense, and grounded ruling-frame synthesis.",
4
  "decisive_request_types": [
5
  "ruling",
6
  "validity",
 
986
  },
987
  "patterns": {
988
  "ar": [
989
+ "ما\\s+(?:هو\\s+)?(?:ال)?حكم",
990
  "ما\\s+(?:هو\\s+)?الحكم",
991
  "ما\\s+حكم",
992
  "هل\\s+(?:يجوز|يجب|يحرم|يلزم)",
 
1164
  "\\b(?:pillar|essential pillar)\\b"
1165
  ]
1166
  }
1167
+ },
1168
+ "sufficient": {
1169
+ "decisiveness": 0.95,
1170
+ "patterns": {
1171
+ "ar": [
1172
+ "(?:يجزئ|يجزي|مجزئ|يسقط\\s+الفرض|يكفي\\s+عن)"
1173
+ ],
1174
+ "en": [
1175
+ "\\b(?:sufficient|counts for|discharges the obligation|fulfills the duty)\\b"
1176
+ ]
1177
+ }
1178
+ },
1179
+ "not_sufficient": {
1180
+ "decisiveness": 0.98,
1181
+ "patterns": {
1182
+ "ar": [
1183
+ "(?:لا\\s+يجزئ|لا\\s+يجزي|غير\\s+مجزئ|لا\\s+يسقط\\s+الفرض|لا\\s+يكفي\\s+عن)"
1184
+ ],
1185
+ "en": [
1186
+ "\\b(?:not sufficient|does not count for|does not discharge the obligation|does not fulfill the duty)\\b"
1187
+ ]
1188
+ }
1189
  }
1190
  },
1191
  "incompatible_outcomes": [
 
1208
  [
1209
  "remedy_required",
1210
  "no_remedy"
1211
+ ],
1212
+ [
1213
+ "sufficient",
1214
+ "not_sufficient"
1215
  ]
1216
  ],
1217
  "proposition_relations": {
 
1336
  "priority": 90,
1337
  "patterns": {
1338
  "ar": [
1339
+ "^(?:ف?اذا|ف?إذا|ف?إن|ف?ان|ف?لو|فعند\\s+عدم|ففي\\s+حال)\\b.+(?:،|\\s)(?:ف?يجب|ف?وجب|ف?يجوز|ف?لا|ف?يسقط|ف?يلزم|ف?يفعل|ف?تستعمل|ف?يستعمل|ف?تنوب|ف?ينوب|ف?تستنيب|ف?يستنيب|ف?ينصرف|ف?يقع|ف?يبطل|ف?يصح|ف?يجزئ|ف?يجزي|ف?لا\\s+يجزئ|ف?لا\\s+يجزي)",
1340
  "^(?:تنصرف|ينصرف|يقع|تؤول|يؤول|تعود|يعود|يبطل|يصح)\\b"
1341
  ],
1342
  "en": [
hudanet_core/ruling_frame.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from typing import Dict, List, Mapping, Sequence, Set, Tuple
5
+
6
+ from .propositions import PropositionExtractor
7
+ from .rendering import AnswerRenderer
8
+ from .text import TextProcessor
9
+ from .types import PropositionCluster, QueryFrame, ScoredEvidence, SynthesisResult
10
+
11
+
12
+ class RulingFramePlanner:
13
+ """Build a central ruling from grounded propositions and complementary facets.
14
+
15
+ Outcome families are generic legal/decision dimensions. The planner does not
16
+ know any domain entities and never stores a question or answer.
17
+ """
18
+
19
+ DEFAULT_FAMILIES: Mapping[str, Set[str]] = {
20
+ "obligation": {"obligatory", "not_obligatory", "obligation_dropped"},
21
+ "permission": {"permissible", "prohibited"},
22
+ "validity": {"valid", "invalid"},
23
+ "sufficiency": {"sufficient", "not_sufficient"},
24
+ "remedy": {"remedy_required", "no_remedy"},
25
+ "recommendation": {"recommended", "disliked"},
26
+ }
27
+
28
+ def __init__(self, text: TextProcessor, semantic: dict, templates: dict, ranking: dict):
29
+ self.text = text
30
+ self.semantic = semantic
31
+ self.templates = templates
32
+ self.ranking = ranking
33
+ self.extractor = PropositionExtractor(text, semantic, templates, ranking)
34
+ self.renderer = AnswerRenderer(templates, ranking)
35
+ configured = (ranking.get("ruling_frame", {}) or {}).get("outcome_families", {}) or {}
36
+ self.families = {
37
+ key: set(values) for key, values in (configured or self.DEFAULT_FAMILIES).items()
38
+ }
39
+
40
+ def _family(self, outcomes: Sequence[str]) -> str:
41
+ outcome_set = set(outcomes)
42
+ for family, values in self.families.items():
43
+ if outcome_set & values:
44
+ return family
45
+ return "other"
46
+
47
+ def _source_labels(self, cluster: PropositionCluster, lang: str) -> List[str]:
48
+ labels = []
49
+ seen = set()
50
+ for proposition in cluster.propositions:
51
+ book = proposition.source_book
52
+ page = proposition.source_page
53
+ label = f"{book} ({'ص' if lang == 'ar' else 'p.'} {page})" if page else book
54
+ if label and label not in seen:
55
+ seen.add(label)
56
+ labels.append(label)
57
+ return labels
58
+
59
+ def _centrality_for_cluster(self, cluster: PropositionCluster, selected: Sequence[ScoredEvidence]) -> float:
60
+ by_id = {item.evidence.record_id or str(id(item.source)): item for item in selected}
61
+ values = []
62
+ for record_id in cluster.source_record_ids:
63
+ item = by_id.get(record_id)
64
+ if item:
65
+ values.append(float(item.metrics.get("scope_centrality", 0.0)))
66
+ return max(values or [0.0])
67
+
68
+ def _cluster_outcomes(self, cluster: PropositionCluster) -> Set[str]:
69
+ return {
70
+ value
71
+ for proposition in cluster.propositions
72
+ for value in proposition.outcomes
73
+ if value not in {"unspecified", "disputed", "condition", "pillar"}
74
+ }
75
+
76
+ def _cluster_families(self, cluster: PropositionCluster) -> Set[str]:
77
+ families = set()
78
+ for outcome in self._cluster_outcomes(cluster):
79
+ families.add(self._family((outcome,)))
80
+ return families or {"other"}
81
+
82
+ def _supporting_items(
83
+ self,
84
+ families: Set[str],
85
+ selected: Sequence[ScoredEvidence],
86
+ centrality_floor: float,
87
+ ) -> List[ScoredEvidence]:
88
+ result = []
89
+ for item in selected:
90
+ if float(item.metrics.get("scope_centrality", 0.0)) < centrality_floor:
91
+ continue
92
+ item_families = {
93
+ self._family((value,))
94
+ for value in item.evidence.outcomes
95
+ if value not in {"unspecified", "disputed", "condition", "pillar"}
96
+ }
97
+ if families & item_families:
98
+ result.append(item)
99
+ return result
100
+
101
+ def _item_label(self, item: ScoredEvidence, lang: str) -> str:
102
+ book = item.evidence.book or item.evidence.book_id or item.evidence.record_id
103
+ page = item.evidence.page
104
+ return f"{book} ({'ص' if lang == 'ar' else 'p.'} {page})" if page else book
105
+
106
+ def plan(self, query: QueryFrame, selected: Sequence[ScoredEvidence]) -> SynthesisResult | None:
107
+ propositions, rejected = self.extractor.extract(query, selected)
108
+ clusters = self.extractor.cluster(query, propositions)
109
+ if not clusters:
110
+ return None
111
+
112
+ centrality_floor = float((self.ranking.get("ruling_frame", {}) or {}).get("central_cluster_floor", 0.56))
113
+ central_clusters = [
114
+ cluster for cluster in clusters
115
+ if self._centrality_for_cluster(cluster, selected) >= centrality_floor
116
+ and cluster.role != "consequence"
117
+ ]
118
+ if not central_clusters:
119
+ central_clusters = [cluster for cluster in clusters if cluster.role != "consequence"] or clusters[:1]
120
+
121
+ # Prefer one broad, direct proposition that already combines several ruling
122
+ # dimensions. This prevents a compact ruling from being exploded into four
123
+ # repetitive bullets when one source states it cleanly.
124
+ def summary_key(cluster: PropositionCluster):
125
+ families = self._cluster_families(cluster) - {"other"}
126
+ word_count = len(self.text.tokens(cluster.text, query.language))
127
+ compactness = max(0.0, 1.0 - max(0, word_count - 24) / 30.0)
128
+ return (
129
+ self._centrality_for_cluster(cluster, selected),
130
+ len(families),
131
+ len(cluster.source_books),
132
+ cluster.score,
133
+ compactness,
134
+ )
135
+
136
+ summary = max(central_clusters, key=summary_key)
137
+ covered_families = self._cluster_families(summary)
138
+
139
+ family_groups: Dict[str, List[PropositionCluster]] = defaultdict(list)
140
+ details: List[PropositionCluster] = []
141
+ for cluster in clusters:
142
+ if cluster is summary:
143
+ continue
144
+ centrality = self._centrality_for_cluster(cluster, selected)
145
+ if cluster.role == "consequence" or centrality < centrality_floor:
146
+ details.append(cluster)
147
+ continue
148
+ for family in self._cluster_families(cluster):
149
+ if family not in covered_families:
150
+ family_groups[family].append(cluster)
151
+
152
+ additions: List[PropositionCluster] = []
153
+ family_order = list((self.ranking.get("ruling_frame", {}) or {}).get(
154
+ "family_order", ["obligation", "permission", "validity", "sufficiency", "remedy", "recommendation", "other"]
155
+ ))
156
+ for family in family_order:
157
+ candidates = family_groups.get(family, [])
158
+ if not candidates:
159
+ continue
160
+ candidates.sort(
161
+ key=lambda cluster: (
162
+ 1.0 / max(1, len(self._cluster_families(cluster))),
163
+ self._centrality_for_cluster(cluster, selected),
164
+ len(cluster.source_books),
165
+ cluster.score,
166
+ ),
167
+ reverse=True,
168
+ )
169
+ additions.append(candidates[0])
170
+ covered_families.add(family)
171
+
172
+ question_label = self.templates.get("question_heading", {}).get(query.language, "Question")
173
+ answer_label = self.templates.get("answer_heading", {}).get(query.language, "Answer")
174
+ summary_label = self.templates.get("ruling_summary_heading", {}).get(query.language, "Ruling")
175
+ detail_label = self.templates.get("ruling_detail_heading", {}).get(query.language, "Details")
176
+ lines = [f"**{question_label}:** {query.raw}", "", f"**{answer_label}:**", ""]
177
+
178
+ used_records: List[str] = []
179
+ used_books: List[str] = []
180
+
181
+ summary_support_floor = max(
182
+ centrality_floor, self._centrality_for_cluster(summary, selected) - 0.10
183
+ )
184
+ summary_support = self._supporting_items(
185
+ self._cluster_families(summary), selected, summary_support_floor
186
+ )
187
+ summary_labels = []
188
+ seen_labels = set()
189
+ for item in summary_support:
190
+ label = self._item_label(item, query.language)
191
+ if label and label not in seen_labels:
192
+ seen_labels.add(label)
193
+ summary_labels.append(label)
194
+ used_records.append(item.evidence.record_id or str(id(item.source)))
195
+ used_books.append(item.evidence.book_id or item.evidence.book)
196
+ if not summary_labels:
197
+ summary_labels = self._source_labels(summary, query.language)
198
+ used_records.extend(summary.source_record_ids)
199
+ used_books.extend(
200
+ proposition.source_book_id for proposition in summary.propositions
201
+ )
202
+ summary_line = f"**{summary_label}:** {summary.text.strip()}"
203
+ if summary_labels:
204
+ summary_line += f" _[{ '، '.join(summary_labels) }]_"
205
+ lines.append(summary_line)
206
+
207
+ if additions:
208
+ lines.extend(["", f"**{detail_label}:**"])
209
+ for cluster in additions:
210
+ families = self._cluster_families(cluster)
211
+ support = self._supporting_items(families, selected, centrality_floor)
212
+ labels = []
213
+ seen = set()
214
+ for item in support:
215
+ label = self._item_label(item, query.language)
216
+ if label and label not in seen:
217
+ seen.add(label)
218
+ labels.append(label)
219
+ used_records.append(item.evidence.record_id or str(id(item.source)))
220
+ used_books.append(item.evidence.book_id or item.evidence.book)
221
+ line = f"- {cluster.text.strip()}"
222
+ if labels:
223
+ line += f" _[{ '، '.join(labels) }]_"
224
+ lines.append(line)
225
+
226
+ chosen_texts = [summary.text, *(cluster.text for cluster in additions)]
227
+ useful_details = []
228
+ for cluster in details:
229
+ if any(self.text.sentence_similarity(cluster.text, text, query.language) >= 0.80 for text in chosen_texts):
230
+ continue
231
+ useful_details.append(cluster)
232
+ if useful_details:
233
+ if not additions:
234
+ lines.extend(["", f"**{detail_label}:**"])
235
+ for cluster in useful_details[:3]:
236
+ labels = self._source_labels(cluster, query.language)
237
+ line = f"- {cluster.text.strip()}"
238
+ if labels:
239
+ line += f" _[{ '، '.join(labels) }]_"
240
+ lines.append(line)
241
+ used_records.extend(cluster.source_record_ids)
242
+ used_books.extend(
243
+ proposition.source_book_id for proposition in cluster.propositions
244
+ )
245
+
246
+ return SynthesisResult(
247
+ answer="\n".join(lines).strip(),
248
+ used_record_ids=tuple(dict.fromkeys(used_records)),
249
+ used_book_ids=tuple(dict.fromkeys(used_books)),
250
+ propositions=tuple([summary, *additions, *useful_details[:3]]),
251
+ rejected_fragments=tuple(dict.fromkeys(rejected)),
252
+ )
253
+
hudanet_core/synthesis.py CHANGED
@@ -5,6 +5,7 @@ from typing import Dict, Iterable, List, Sequence, Set, Tuple
5
 
6
  from .propositions import PropositionExtractor
7
  from .rendering import AnswerRenderer
 
8
  from .text import TextProcessor
9
  from .types import ConsensusResult, PropositionCluster, QueryFrame, ScoredEvidence, SynthesisResult
10
 
@@ -19,6 +20,7 @@ class AnswerSynthesizer:
19
  self.ranking = ranking
20
  self.extractor = PropositionExtractor(text, semantic, templates, ranking)
21
  self.renderer = AnswerRenderer(templates, ranking)
 
22
 
23
  @staticmethod
24
  def _clean(value: str) -> str:
@@ -292,7 +294,19 @@ class AnswerSynthesizer:
292
  body = self.renderer.prose_answer(query, "\n".join(parts))
293
  used_record_ids = tuple(dict.fromkeys(used_record_ids_list))
294
  used_book_ids = tuple(dict.fromkeys(used_book_ids_list))
295
- elif compare_sources and query.primary_request_type in {"ruling", "validity", "remedy", "exception"}:
 
 
 
 
 
 
 
 
 
 
 
 
296
  facet_result = self._ruling_facet_answer(query, selected, style)
297
  if facet_result is not None:
298
  return facet_result
 
5
 
6
  from .propositions import PropositionExtractor
7
  from .rendering import AnswerRenderer
8
+ from .ruling_frame import RulingFramePlanner
9
  from .text import TextProcessor
10
  from .types import ConsensusResult, PropositionCluster, QueryFrame, ScoredEvidence, SynthesisResult
11
 
 
20
  self.ranking = ranking
21
  self.extractor = PropositionExtractor(text, semantic, templates, ranking)
22
  self.renderer = AnswerRenderer(templates, ranking)
23
+ self.ruling_planner = RulingFramePlanner(text, semantic, templates, ranking)
24
 
25
  @staticmethod
26
  def _clean(value: str) -> str:
 
294
  body = self.renderer.prose_answer(query, "\n".join(parts))
295
  used_record_ids = tuple(dict.fromkeys(used_record_ids_list))
296
  used_book_ids = tuple(dict.fromkeys(used_book_ids_list))
297
+ elif query.primary_request_type in {"ruling", "validity", "remedy", "exception"}:
298
+ frame_result = self.ruling_planner.plan(query, selected)
299
+ if frame_result is not None:
300
+ answer = self.renderer.append_sources(
301
+ frame_result.answer, query, selected, frame_result.used_record_ids
302
+ )
303
+ return SynthesisResult(
304
+ answer=answer,
305
+ used_record_ids=frame_result.used_record_ids,
306
+ used_book_ids=frame_result.used_book_ids,
307
+ propositions=frame_result.propositions,
308
+ rejected_fragments=frame_result.rejected_fragments,
309
+ )
310
  facet_result = self._ruling_facet_answer(query, selected, style)
311
  if facet_result is not None:
312
  return facet_result
hudanet_core/tests/test_generic_pipeline.py CHANGED
@@ -259,6 +259,35 @@ def run():
259
  check("exact tier requires answer contribution", exact_ids.issubset(used_ids) and bool(exact_ids), {"exact": sorted(exact_ids), "used": sorted(used_ids)})
260
  check("insufficient answer cannot retain extreme confidence", pipeline.resolve("ما القاعدة؟", [], "ar").confidence <= .35)
261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  semantic = pipeline.resources.semantic
263
  check("no domain anchor table", "domain_anchor_terms" not in semantic)
264
  check("no entity alias table", "concept_aliases" not in semantic)
 
259
  check("exact tier requires answer contribution", exact_ids.issubset(used_ids) and bool(exact_ids), {"exact": sorted(exact_ids), "used": sorted(used_ids)})
260
  check("insufficient answer cannot retain extreme confidence", pipeline.resolve("ما القاعدة؟", [], "ar").confidence <= .35)
261
 
262
+ # Contextual-sense separation, OCR integrity, and central ruling-frame
263
+ # synthesis. The synthetic vocabulary is domain-neutral and intentionally
264
+ # places the same surface terms in unrelated fields.
265
+ sense_query = "ما هو حكم تنفيذ العامل للعملية؟"
266
+ sense_sources = [
267
+ src("sense-main", "كتاب الحكم المركزي", "حكم تنفيذ العامل للعملية", sense_query,
268
+ "لا يجب / يصح", "لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله.",
269
+ .94, .93, .82, direct_probability=.94, bm25_score=.91, retriever_agreement=5),
270
+ src("sense-supplement", "كتاب الحكم المكمل", "تنفيذ العامل والأهلية",
271
+ "هل يجزئ تنفيذ العامل قبل اكتمال الأهلية؟", "صحيح غير مجزئ",
272
+ "يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي. فإذا اكتملت الأهلية وجب التنفيذ.",
273
+ .90, .92, .80, direct_probability=.90, bm25_score=.88, retriever_agreement=5),
274
+ src("sense-homograph", "مصدر اللفظ المشترك", "قبول العملية وآثارها",
275
+ "ما علامة الانتفاع بالعملية بعد الرجوع؟", "مقصد عام",
276
+ "أن يرجع العامل أصلح حالا وأكثر التزاما.",
277
+ .97, .95, .79, direct_probability=.97, bm25_score=.76, retriever_agreement=4),
278
+ src("sense-ocr", "مصدر النص المشوه", "حكم تنفيذ العامل للعملية", sense_query,
279
+ "تفصيل", "خلاصة السجل: لاء إلا أن يفيق مساكل أفصئ دأؤود.",
280
+ .91, .92, .74, direct_probability=.91, bm25_score=.80, retriever_agreement=4,
281
+ source_kind="raw OCR source"),
282
+ ]
283
+ sense_result = pipeline.resolve(sense_query, sense_sources, "ar")
284
+ check("specific ruling outranks generic what-is shell", sense_result.query.primary_request_type == "ruling", sense_result.details)
285
+ check("contextual homograph rejected", any(item.evidence.record_id == "sense-homograph" and not item.accepted and "contextual_sense_mismatch" in item.hard_rejections for item in sense_result.ranked), sense_result.details)
286
+ check("broken OCR rejected", any(item.evidence.record_id == "sense-ocr" and not item.accepted and "evidence_text_integrity_failed" in item.hard_rejections for item in sense_result.ranked), sense_result.details)
287
+ check("central ruling frame rendered", "**الحكم المختصر:**" in sense_result.answer and "لا يجب تنفيذ العامل للعملية" in sense_result.answer, sense_result.answer)
288
+ check("complementary sufficiency facet retained", "لا يجزئ عن الالتزام الأصلي" in sense_result.answer, sense_result.answer)
289
+ check("homograph and OCR excluded from answer", "أصلح حالا" not in sense_result.answer and "أفصئ دأؤود" not in sense_result.answer, sense_result.answer)
290
+
291
  semantic = pipeline.resources.semantic
292
  check("no domain anchor table", "domain_anchor_terms" not in semantic)
293
  check("no entity alias table", "concept_aliases" not in semantic)
hudanet_core/text_integrity.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import Mapping, Sequence
6
+
7
+ from .text import TextProcessor
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class IntegrityResult:
12
+ score: float
13
+ script_ratio: float
14
+ punctuation_ratio: float
15
+ short_token_ratio: float
16
+ metadata_coherence: float
17
+ semantic_structure: float
18
+ repetition_penalty: float
19
+
20
+
21
+ class TextIntegrityEvaluator:
22
+ """Domain-neutral integrity gate for extracted source text.
23
+
24
+ The evaluator does not use a vocabulary of valid words. It combines writing-
25
+ system consistency, punctuation/fragmentation, cross-field coherence, and
26
+ generic legal/answer structure. This catches OCR fragments while preserving
27
+ rare terminology that is supported by the source metadata or a clear ruling.
28
+ """
29
+
30
+ def __init__(self, text: TextProcessor, semantic: Mapping[str, object]):
31
+ self.text = text
32
+ self.semantic = semantic
33
+
34
+ def _semantic_structure(self, value: str, lang: str) -> float:
35
+ normalized = self.text.normalize(value, lang)
36
+ hits = 0
37
+ for rule in (self.semantic.get("outcomes", {}) or {}).values():
38
+ patterns = (rule.get("patterns", {}) or {}).get(lang, []) or []
39
+ if any(self.text.phrase_hit(normalized, pattern) for pattern in patterns):
40
+ hits += 1
41
+ for rule in (self.semantic.get("proposition_roles", {}) or {}).values():
42
+ patterns = (rule.get("patterns", {}) or {}).get(lang, []) or []
43
+ if any(self.text.phrase_hit(normalized, pattern) for pattern in patterns):
44
+ hits += 1
45
+ return min(1.0, 0.34 * hits)
46
+
47
+ def assess(self, value: object, lang: str, metadata_text: object = "") -> IntegrityResult:
48
+ raw = re.sub(r"\s+", " ", str(value or "")).strip()
49
+ normalized = self.text.normalize(raw, lang)
50
+ words = normalized.split()
51
+ if not raw or not words:
52
+ return IntegrityResult(0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0)
53
+
54
+ visible = re.findall(r"\w", raw, flags=re.UNICODE)
55
+ if lang == "ar":
56
+ letters = re.findall(r"[\u0600-\u06FF]", raw)
57
+ else:
58
+ letters = re.findall(r"[A-Za-z]", raw)
59
+ script_ratio = len(letters) / max(1, len(visible))
60
+
61
+ punctuation = re.findall(r"[^\w\s\u0600-\u06FF]", raw, flags=re.UNICODE)
62
+ punctuation_ratio = len(punctuation) / max(1, len(raw))
63
+ short_token_ratio = sum(1 for word in words if len(word) <= 2) / max(1, len(words))
64
+ long_token_ratio = sum(1 for word in words if len(word) >= 18) / max(1, len(words))
65
+
66
+ repeated_char_tokens = 0
67
+ low_diversity_tokens = 0
68
+ for word in words:
69
+ if re.search(r"(.)\1{3,}", word):
70
+ repeated_char_tokens += 1
71
+ if len(word) >= 6 and len(set(word)) / len(word) < 0.34:
72
+ low_diversity_tokens += 1
73
+ repetition_penalty = min(
74
+ 1.0,
75
+ (repeated_char_tokens + low_diversity_tokens) / max(1, len(words)) * 2.4,
76
+ )
77
+
78
+ candidate_terms = self.text.content_terms(raw, lang)
79
+ metadata_terms = self.text.content_terms(metadata_text, lang)
80
+ metadata_coherence = (
81
+ self.text.fuzzy_term_overlap(candidate_terms, metadata_terms)
82
+ if candidate_terms and metadata_terms else 0.0
83
+ )
84
+ semantic_structure = self._semantic_structure(raw, lang)
85
+
86
+ length_score = 1.0
87
+ if len(words) < 3:
88
+ length_score = 0.56
89
+ elif len(words) > 140:
90
+ length_score = 0.78
91
+
92
+ shape_score = (
93
+ 0.48 * min(1.0, script_ratio / 0.78)
94
+ + 0.18 * max(0.0, 1.0 - min(1.0, punctuation_ratio / 0.18))
95
+ + 0.14 * max(0.0, 1.0 - min(1.0, short_token_ratio / 0.62))
96
+ + 0.08 * max(0.0, 1.0 - min(1.0, long_token_ratio / 0.18))
97
+ + 0.12 * length_score
98
+ )
99
+ support_score = max(metadata_coherence, semantic_structure)
100
+ score = 0.66 * shape_score + 0.34 * support_score
101
+ score -= 0.24 * repetition_penalty
102
+
103
+ # Clauses that are both unsupported by their own metadata and contain no
104
+ # recognizable answer structure should not inherit source scope merely
105
+ # because the source record ranked well.
106
+ if metadata_coherence < 0.12 and semantic_structure < 0.18 and len(words) >= 5:
107
+ score -= 0.18
108
+ if script_ratio < 0.46:
109
+ score -= 0.22
110
+
111
+ return IntegrityResult(
112
+ score=max(0.0, min(1.0, score)),
113
+ script_ratio=max(0.0, min(1.0, script_ratio)),
114
+ punctuation_ratio=max(0.0, min(1.0, punctuation_ratio)),
115
+ short_token_ratio=max(0.0, min(1.0, short_token_ratio)),
116
+ metadata_coherence=max(0.0, min(1.0, metadata_coherence)),
117
+ semantic_structure=max(0.0, min(1.0, semantic_structure)),
118
+ repetition_penalty=max(0.0, min(1.0, repetition_penalty)),
119
+ )
hudanet_core/types.py CHANGED
@@ -58,6 +58,8 @@ class EvidenceFrame:
58
  raw: Mapping[str, Any]
59
  relation_edges: Tuple[Tuple[str, str, str], ...] = ()
60
  principle_strength: float = 0.0
 
 
61
 
62
 
63
  @dataclass
 
58
  raw: Mapping[str, Any]
59
  relation_edges: Tuple[Tuple[str, str, str], ...] = ()
60
  principle_strength: float = 0.0
61
+ integrity_score: float = 0.0
62
+ integrity_details: Mapping[str, float] = field(default_factory=dict)
63
 
64
 
65
  @dataclass
hudanet_v38_0_6_contextual_ruling_regression.json ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "38.0.6",
3
+ "query": "ما هو حكم حج العبد؟",
4
+ "answer": "**السؤال:** ما هو حكم حج العبد؟\n\n**الإجابة:**\n\n**الحكم المختصر:** لا يجب الحج على العبد، ويصح منه إذا فعله. _[الهداية على مذهب الإمام أحمد (ص 169)، الفروع (ص 207)، مختصر الخرقي (ص 71)، الكافي في فقه الإمام أحمد (ص 191)]_\n\n**التفصيل والاستثناءات:**\n- لكنه لا يجزئ عن حجة الإسلام. _[الهداية على مذهب الإمام أحمد (ص 169)، مختصر الخرقي (ص 71)]_\n- فإذا بلغ الصبي أو عتق العبد وجب عليه الحج. _[مختصر الخرقي (ص 71)]_\n\n**المصادر التي بُني عليها الجواب:**\n- الهداية على مذهب الإمام أحمد (ص 169)\n- الفروع (ص 207)\n- مختصر الخرقي (ص 71)\n- الكافي في فقه الإمام أحمد (ص 191)",
5
+ "checks": {
6
+ "request_type_is_ruling": true,
7
+ "main_answer_present": true,
8
+ "non_sufficiency_present": true,
9
+ "status_change_detail_present": true,
10
+ "homograph_rejected": true,
11
+ "ocr_rejected": true,
12
+ "bad_text_absent": true,
13
+ "exact_sources_contributed": true,
14
+ "bad_sources_distant": true
15
+ },
16
+ "passed": true,
17
+ "details": {
18
+ "request_type": "ruling",
19
+ "initial_request_type": "ruling",
20
+ "request_type_scores": {
21
+ "exception": 0.198796,
22
+ "pillars": 0.0,
23
+ "cause": 0.107044,
24
+ "ruling": 0.826066,
25
+ "description": 0.418356,
26
+ "timing": 0.140176,
27
+ "definition": 0.0,
28
+ "validity": 0.20899,
29
+ "evidence": 0.158017,
30
+ "principle": 0.234477,
31
+ "amount": 0.381888,
32
+ "location": 0.101637,
33
+ "comparison": 0.0,
34
+ "procedure": 0.110464,
35
+ "list": 0.125807,
36
+ "remedy": 0.198796,
37
+ "conditions": 0.090541,
38
+ "duties": 0.153423,
39
+ "components": 0.0
40
+ },
41
+ "intent_repaired": false,
42
+ "intent_repair_reason": "",
43
+ "relation_edges": [],
44
+ "relation_signature": "",
45
+ "subject_terms": [
46
+ "حج",
47
+ "عبد"
48
+ ],
49
+ "anchor_terms": [
50
+ "عبد"
51
+ ],
52
+ "qualifier_terms": [
53
+ "حج"
54
+ ],
55
+ "critical_terms": [
56
+ "حج"
57
+ ],
58
+ "term_document_frequency": {
59
+ "حج": 1.0,
60
+ "عبد": 1.0
61
+ },
62
+ "term_evidence_support": {
63
+ "حج": 1.0,
64
+ "عبد": 1.0
65
+ },
66
+ "term_weights": {
67
+ "حج": 0.65,
68
+ "عبد": 0.65
69
+ },
70
+ "operator_terms": [],
71
+ "polarity": "affirmative",
72
+ "accepted": 5,
73
+ "rejected": 2,
74
+ "consensus_state": "mixed",
75
+ "selected_cluster": "not_sufficient+valid",
76
+ "issue_clusters": {
77
+ "issue-1": {
78
+ "books": 4,
79
+ "weight": 4.5057,
80
+ "principle_strength": 0.514,
81
+ "relation_alignment": 0.75,
82
+ "answerability": 0.9253
83
+ },
84
+ "issue-2": {
85
+ "books": 1,
86
+ "weight": 0.9799,
87
+ "principle_strength": 0.746,
88
+ "relation_alignment": 0.75,
89
+ "answerability": 0.9432
90
+ }
91
+ },
92
+ "used_record_ids": [
93
+ "hidayah",
94
+ "furu",
95
+ "khiraqi",
96
+ "kafi"
97
+ ],
98
+ "used_book_ids": [
99
+ "الهداية على مذهب الإمام أحمد",
100
+ "الفروع",
101
+ "مختصر الخرقي",
102
+ "الكافي في فقه الإمام أحمد"
103
+ ],
104
+ "propositions": [
105
+ {
106
+ "text": "لا يجب الحج على العبد، ويصح منه إذا فعله.",
107
+ "score": 0.9543,
108
+ "source_record_ids": [
109
+ "furu"
110
+ ],
111
+ "source_books": [
112
+ "الفروع"
113
+ ],
114
+ "source_pages": [
115
+ "207"
116
+ ],
117
+ "role": "statement"
118
+ },
119
+ {
120
+ "text": "لكنه لا يجزئ عن حجة الإسلام.",
121
+ "score": 0.889,
122
+ "source_record_ids": [
123
+ "khiraqi"
124
+ ],
125
+ "source_books": [
126
+ "مختصر الخرقي"
127
+ ],
128
+ "source_pages": [
129
+ "71"
130
+ ],
131
+ "role": "statement"
132
+ },
133
+ {
134
+ "text": "فإذا بلغ الصبي أو عتق العبد وجب عليه الحج.",
135
+ "score": 0.937,
136
+ "source_record_ids": [
137
+ "khiraqi"
138
+ ],
139
+ "source_books": [
140
+ "مختصر الخرقي"
141
+ ],
142
+ "source_pages": [
143
+ "71"
144
+ ],
145
+ "role": "consequence"
146
+ }
147
+ ],
148
+ "rejected_fragments": [
149
+ "العبد غير مستطيع لأنه لا مال له ومنافعه مستحقة",
150
+ "مجزئ",
151
+ "صحيح غير مجزئ عن الفرض",
152
+ "لا يجب / يصح",
153
+ "لا يجزئ عن حجة الإسلام"
154
+ ],
155
+ "answerability_state": "grounded"
156
+ },
157
+ "ranking": [
158
+ {
159
+ "record_id": "furu",
160
+ "accepted": true,
161
+ "score": 0.991,
162
+ "rejections": [],
163
+ "metrics": {
164
+ "sense_alignment": 1.0,
165
+ "joint_scope_coverage": 1.0,
166
+ "evidence_integrity": 0.9673,
167
+ "scope_centrality": 0.8482,
168
+ "answerability": 0.9431
169
+ }
170
+ },
171
+ {
172
+ "record_id": "khiraqi",
173
+ "accepted": true,
174
+ "score": 0.99,
175
+ "rejections": [],
176
+ "metrics": {
177
+ "sense_alignment": 1.0,
178
+ "joint_scope_coverage": 1.0,
179
+ "evidence_integrity": 0.9599,
180
+ "scope_centrality": 0.7937,
181
+ "answerability": 0.9432
182
+ }
183
+ },
184
+ {
185
+ "record_id": "hidayah",
186
+ "accepted": true,
187
+ "score": 0.9803,
188
+ "rejections": [],
189
+ "metrics": {
190
+ "sense_alignment": 0.94,
191
+ "joint_scope_coverage": 1.0,
192
+ "evidence_integrity": 0.964,
193
+ "scope_centrality": 0.7895,
194
+ "answerability": 0.9378
195
+ }
196
+ },
197
+ {
198
+ "record_id": "kafi",
199
+ "accepted": true,
200
+ "score": 0.962,
201
+ "rejections": [],
202
+ "metrics": {
203
+ "sense_alignment": 0.94,
204
+ "joint_scope_coverage": 1.0,
205
+ "evidence_integrity": 0.7586,
206
+ "scope_centrality": 0.7879,
207
+ "answerability": 0.9057
208
+ }
209
+ },
210
+ {
211
+ "record_id": "arafah",
212
+ "accepted": true,
213
+ "score": 0.9371,
214
+ "rejections": [],
215
+ "metrics": {
216
+ "sense_alignment": 0.89,
217
+ "joint_scope_coverage": 1.0,
218
+ "evidence_integrity": 0.8622,
219
+ "scope_centrality": 0.6496,
220
+ "answerability": 0.9144
221
+ }
222
+ },
223
+ {
224
+ "record_id": "ocr",
225
+ "accepted": false,
226
+ "score": 0.8478,
227
+ "rejections": [
228
+ "evidence_text_integrity_failed"
229
+ ],
230
+ "metrics": {
231
+ "sense_alignment": 0.88,
232
+ "joint_scope_coverage": 1.0,
233
+ "evidence_integrity": 0.4347,
234
+ "scope_centrality": 0.7876,
235
+ "answerability": 0.8072
236
+ }
237
+ },
238
+ {
239
+ "record_id": "homograph",
240
+ "accepted": false,
241
+ "score": 0.7149,
242
+ "rejections": [
243
+ "contextual_sense_mismatch",
244
+ "query_concepts_only_distributed_across_unrelated_fields"
245
+ ],
246
+ "metrics": {
247
+ "sense_alignment": 0.27,
248
+ "joint_scope_coverage": 0.5,
249
+ "evidence_integrity": 0.4514,
250
+ "scope_centrality": 0.3872,
251
+ "answerability": 0.7137
252
+ }
253
+ }
254
+ ],
255
+ "tiers": {
256
+ "exact": [
257
+ "furu",
258
+ "khiraqi",
259
+ "hidayah",
260
+ "kafi"
261
+ ],
262
+ "related": [
263
+ "arafah"
264
+ ],
265
+ "distant": [
266
+ "ocr",
267
+ "homograph"
268
+ ]
269
+ }
270
+ }
hudanet_v38_0_6_generic_tests.json ADDED
@@ -0,0 +1,1853 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "passed": true,
3
+ "tested": 37,
4
+ "checks": [
5
+ {
6
+ "name": "request type mismatch rejected",
7
+ "passed": true,
8
+ "value": null
9
+ },
10
+ {
11
+ "name": "focused source selected",
12
+ "passed": true,
13
+ "value": "**Question:** What are the requirements of the process for the group?\n\n**Answer:**\n1. The companion must meet the permanent eligibility rule. _[Source B]_\n2. conditions. _[Source B]_\n3. The required expense is borne by the group. _[Source B]_\n4. If the companion is unavailable, the approved substitute is appointed. _[Source B]_\n\n**Sources used for the answer:**\n- Source B"
14
+ },
15
+ {
16
+ "name": "atomic propositions extracted",
17
+ "passed": true,
18
+ "value": {
19
+ "request_type": "conditions",
20
+ "initial_request_type": "conditions",
21
+ "request_type_scores": {
22
+ "cause": 0.089408,
23
+ "ruling": 0.212875,
24
+ "comparison": 0.208,
25
+ "evidence": 0.131983,
26
+ "definition": 0.144,
27
+ "exception": 0.166043,
28
+ "timing": 0.117082,
29
+ "description": 0.32,
30
+ "principle": 0.195845,
31
+ "duties": 0.212875,
32
+ "pillars": 0.095794,
33
+ "list": 0.523398,
34
+ "components": 0.149013,
35
+ "amount": 0.0,
36
+ "procedure": 0.15327,
37
+ "validity": 0.174558,
38
+ "conditions": 0.751435,
39
+ "location": 0.32,
40
+ "remedy": 0.166043
41
+ },
42
+ "intent_repaired": false,
43
+ "intent_repair_reason": "",
44
+ "relation_edges": [
45
+ [
46
+ "process",
47
+ "recipient",
48
+ "group"
49
+ ]
50
+ ],
51
+ "relation_signature": "process>recipient>group",
52
+ "subject_terms": [
53
+ "process",
54
+ "group"
55
+ ],
56
+ "anchor_terms": [
57
+ "process"
58
+ ],
59
+ "qualifier_terms": [
60
+ "group"
61
+ ],
62
+ "critical_terms": [
63
+ "group"
64
+ ],
65
+ "term_document_frequency": {
66
+ "process": 1.0,
67
+ "group": 0.5
68
+ },
69
+ "term_evidence_support": {
70
+ "process": 1.0,
71
+ "group": 0.4227470265645059
72
+ },
73
+ "term_weights": {
74
+ "process": 0.65,
75
+ "group": 0.651824
76
+ },
77
+ "operator_terms": [],
78
+ "polarity": "affirmative",
79
+ "accepted": 1,
80
+ "rejected": 1,
81
+ "consensus_state": "single_source",
82
+ "selected_cluster": "conditions",
83
+ "issue_clusters": {
84
+ "issue-1": {
85
+ "books": 1,
86
+ "weight": 0.988,
87
+ "principle_strength": 0.368,
88
+ "relation_alignment": 0.75,
89
+ "answerability": 0.9335
90
+ }
91
+ },
92
+ "used_record_ids": [
93
+ "focused"
94
+ ],
95
+ "used_book_ids": [
96
+ "Source B"
97
+ ],
98
+ "propositions": [
99
+ {
100
+ "text": "The companion must meet the permanent eligibility rule.",
101
+ "score": 0.9052,
102
+ "source_record_ids": [
103
+ "focused"
104
+ ],
105
+ "source_books": [
106
+ "Source B"
107
+ ],
108
+ "source_pages": [],
109
+ "role": "requirement"
110
+ },
111
+ {
112
+ "text": "conditions.",
113
+ "score": 0.8463,
114
+ "source_record_ids": [
115
+ "focused"
116
+ ],
117
+ "source_books": [
118
+ "Source B"
119
+ ],
120
+ "source_pages": [],
121
+ "role": "requirement"
122
+ },
123
+ {
124
+ "text": "The required expense is borne by the group.",
125
+ "score": 0.9293,
126
+ "source_record_ids": [
127
+ "focused"
128
+ ],
129
+ "source_books": [
130
+ "Source B"
131
+ ],
132
+ "source_pages": [],
133
+ "role": "assignment"
134
+ },
135
+ {
136
+ "text": "If the companion is unavailable, the approved substitute is appointed.",
137
+ "score": 0.8871,
138
+ "source_record_ids": [
139
+ "focused"
140
+ ],
141
+ "source_books": [
142
+ "Source B"
143
+ ],
144
+ "source_pages": [],
145
+ "role": "consequence"
146
+ }
147
+ ],
148
+ "rejected_fragments": [],
149
+ "answerability_state": "grounded"
150
+ }
151
+ },
152
+ {
153
+ "name": "question preserved verbatim",
154
+ "passed": true,
155
+ "value": null
156
+ },
157
+ {
158
+ "name": "source named",
159
+ "passed": true,
160
+ "value": null
161
+ },
162
+ {
163
+ "name": "paraphrases clustered",
164
+ "passed": true,
165
+ "value": [
166
+ {
167
+ "text": "The companion must meet the permanent eligibility rule.",
168
+ "score": 0.9052,
169
+ "source_record_ids": [
170
+ "focused"
171
+ ],
172
+ "source_books": [
173
+ "Source B"
174
+ ],
175
+ "source_pages": [],
176
+ "role": "requirement"
177
+ },
178
+ {
179
+ "text": "conditions.",
180
+ "score": 0.8463,
181
+ "source_record_ids": [
182
+ "focused"
183
+ ],
184
+ "source_books": [
185
+ "Source B"
186
+ ],
187
+ "source_pages": [],
188
+ "role": "requirement"
189
+ },
190
+ {
191
+ "text": "The required expense is borne by the group.",
192
+ "score": 0.9317,
193
+ "source_record_ids": [
194
+ "focused"
195
+ ],
196
+ "source_books": [
197
+ "Source B"
198
+ ],
199
+ "source_pages": [],
200
+ "role": "assignment"
201
+ },
202
+ {
203
+ "text": "If the companion is unavailable, the approved substitute is appointed.",
204
+ "score": 0.8871,
205
+ "source_record_ids": [
206
+ "focused"
207
+ ],
208
+ "source_books": [
209
+ "Source B"
210
+ ],
211
+ "source_pages": [],
212
+ "role": "consequence"
213
+ }
214
+ ]
215
+ },
216
+ {
217
+ "name": "boilerplate not displayed",
218
+ "passed": true,
219
+ "value": "**Question:** What are the requirements of the process for the group?\n\n**Answer:**\n1. The companion must meet the permanent eligibility rule. _[Source B]_\n2. A verified permit is required. _[Source D]_\n3. conditions. _[Source B، Source D]_\n4. The required expense is borne by the group. _[Source B]_\n5. If the companion is unavailable, the approved substitute is appointed. _[Source B]_\n\n**Sources used for the answer:**\n- Source B\n- Source D"
220
+ },
221
+ {
222
+ "name": "incomplete conditional removed",
223
+ "passed": true,
224
+ "value": "**Question:** What are the requirements of the process for the group?\n\n**Answer:**\n1. The companion must meet the permanent eligibility rule. _[Source B]_\n2. conditions. _[Source B]_\n3. The required expense is borne by the group. _[Source B]_\n4. If the companion is unavailable, the approved substitute is appointed. _[Source B]_\n\n**Sources used for the answer:**\n- Source B"
225
+ },
226
+ {
227
+ "name": "rationale excluded from condition list",
228
+ "passed": true,
229
+ "value": "**Question:** What are the requirements of the process for the group?\n\n**Answer:**\n1. The companion must meet the permanent eligibility rule. _[Source B]_\n2. A verified permit is required. _[Source R]_\n3. conditions. _[Source B، Source R]_\n4. The required expense is borne by the group. _[Source B]_\n5. If the companion is unavailable, the approved substitute is appointed. _[Source B]_\n\n**Sources used for the answer:**\n- Source B\n- Source R"
230
+ },
231
+ {
232
+ "name": "explicit source order preserved",
233
+ "passed": true,
234
+ "value": "**Question:** What are the requirements of the process?\n\n**Answer:**\n1. Identity verification. _[Source O]_\n2. financial capacity. _[Source O]_\n3. operational readiness. _[Source O]_\n4. conditions. _[Source O]_\n\n**Sources used for the answer:**\n- Source O"
235
+ },
236
+ {
237
+ "name": "strong conflict detected",
238
+ "passed": true,
239
+ "value": "**Question:** Is the action allowed?\n\n**Answer:** **Result:** The uploaded sources contain strong conflicting outcomes, so they should not be merged into one ruling:\n- The action is prohibited _[Source G]_\n- The action is permissible _[Source F]_"
240
+ },
241
+ {
242
+ "name": "conflict sources separated",
243
+ "passed": true,
244
+ "value": "**Question:** Is the action allowed?\n\n**Answer:** **Result:** The uploaded sources contain strong conflicting outcomes, so they should not be merged into one ruling:\n- The action is prohibited _[Source G]_\n- The action is permissible _[Source F]_"
245
+ },
246
+ {
247
+ "name": "neural score cannot override type gate",
248
+ "passed": true,
249
+ "value": {
250
+ "request_type": "timing",
251
+ "initial_request_type": "timing",
252
+ "request_type_scores": {
253
+ "cause": 0.0,
254
+ "ruling": 0.0,
255
+ "comparison": 0.208,
256
+ "evidence": 0.0,
257
+ "definition": 0.144,
258
+ "exception": 0.0,
259
+ "timing": 0.68,
260
+ "description": 0.32,
261
+ "principle": 0.1344,
262
+ "duties": 0.0,
263
+ "pillars": 0.0,
264
+ "list": 0.0,
265
+ "components": 0.0,
266
+ "amount": 0.0,
267
+ "procedure": 0.0,
268
+ "validity": 0.0,
269
+ "conditions": 0.0,
270
+ "location": 0.32,
271
+ "remedy": 0.0
272
+ },
273
+ "intent_repaired": false,
274
+ "intent_repair_reason": "",
275
+ "relation_edges": [],
276
+ "relation_signature": "",
277
+ "subject_terms": [
278
+ "process"
279
+ ],
280
+ "anchor_terms": [
281
+ "process"
282
+ ],
283
+ "qualifier_terms": [],
284
+ "critical_terms": [
285
+ "process"
286
+ ],
287
+ "term_document_frequency": {
288
+ "process": 0.0
289
+ },
290
+ "term_evidence_support": {
291
+ "process": 0.0
292
+ },
293
+ "term_weights": {
294
+ "process": 0.7
295
+ },
296
+ "operator_terms": [],
297
+ "polarity": "affirmative",
298
+ "accepted": 0,
299
+ "rejected": 1,
300
+ "consensus_state": "insufficient",
301
+ "selected_cluster": "",
302
+ "issue_clusters": {},
303
+ "used_record_ids": [],
304
+ "used_book_ids": [],
305
+ "propositions": [],
306
+ "rejected_fragments": [],
307
+ "answerability_state": "insufficient"
308
+ }
309
+ },
310
+ {
311
+ "name": "compare mode includes multiple sources",
312
+ "passed": true,
313
+ "value": "**Question:** Is the action allowed?\n\n**Answer:**\n\n**Ruling summary:** The action is permissible. _[Source I، Source J]_\n\n**Sources used for the answer:**\n- Source I\n- Source J"
314
+ },
315
+ {
316
+ "name": "primary mode uses one source",
317
+ "passed": true,
318
+ "value": "**Question:** Is the action allowed?\n\n**Answer:**\n\n**Ruling summary:** The action is permissible. _[Source I]_\n\n**Sources used for the answer:**\n- Source I"
319
+ },
320
+ {
321
+ "name": "arabic contextual roles retained",
322
+ "passed": true,
323
+ "value": {
324
+ "request_type": "conditions",
325
+ "initial_request_type": "conditions",
326
+ "request_type_scores": {
327
+ "cause": 0.0,
328
+ "ruling": 0.0,
329
+ "comparison": 0.0,
330
+ "evidence": 0.0,
331
+ "definition": 0.0,
332
+ "exception": 0.176,
333
+ "timing": 0.0,
334
+ "description": 0.0,
335
+ "principle": 0.32,
336
+ "duties": 0.0,
337
+ "pillars": 0.144,
338
+ "list": 0.2624,
339
+ "components": 0.224,
340
+ "amount": 0.0,
341
+ "procedure": 0.0,
342
+ "validity": 0.1984,
343
+ "conditions": 0.85856,
344
+ "location": 0.144,
345
+ "remedy": 0.0
346
+ },
347
+ "intent_repaired": false,
348
+ "intent_repair_reason": "",
349
+ "relation_edges": [],
350
+ "relation_signature": "",
351
+ "subject_terms": [
352
+ "موضوع",
353
+ "فية"
354
+ ],
355
+ "anchor_terms": [
356
+ "فية"
357
+ ],
358
+ "qualifier_terms": [
359
+ "موضوع"
360
+ ],
361
+ "critical_terms": [
362
+ "موضوع"
363
+ ],
364
+ "term_document_frequency": {
365
+ "موضوع": 1.0,
366
+ "فية": 1.0
367
+ },
368
+ "term_evidence_support": {
369
+ "موضوع": 1.0,
370
+ "فية": 1.0
371
+ },
372
+ "term_weights": {
373
+ "موضوع": 0.65,
374
+ "فية": 0.65
375
+ },
376
+ "operator_terms": [],
377
+ "polarity": "affirmative",
378
+ "accepted": 1,
379
+ "rejected": 0,
380
+ "consensus_state": "single_source",
381
+ "selected_cluster": "conditions",
382
+ "issue_clusters": {
383
+ "issue-1": {
384
+ "books": 1,
385
+ "weight": 0.984,
386
+ "principle_strength": 0.336,
387
+ "relation_alignment": 0.75,
388
+ "answerability": 0.9113
389
+ }
390
+ },
391
+ "used_record_ids": [
392
+ "ar-atomic"
393
+ ],
394
+ "used_book_ids": [
395
+ "المصدر العربي"
396
+ ],
397
+ "propositions": [
398
+ {
399
+ "text": "وجود المتطلب الأول.",
400
+ "score": 0.8836,
401
+ "source_record_ids": [
402
+ "ar-atomic"
403
+ ],
404
+ "source_books": [
405
+ "المصدر العربي"
406
+ ],
407
+ "source_pages": [],
408
+ "role": "requirement"
409
+ },
410
+ {
411
+ "text": "يكون المتطلب الثاني متحققًا.",
412
+ "score": 0.8649,
413
+ "source_record_ids": [
414
+ "ar-atomic"
415
+ ],
416
+ "source_books": [
417
+ "المصدر العربي"
418
+ ],
419
+ "source_pages": [],
420
+ "role": "definition"
421
+ },
422
+ {
423
+ "text": "تتحمل الفئة النفقة اللازمة.",
424
+ "score": 0.8969,
425
+ "source_record_ids": [
426
+ "ar-atomic"
427
+ ],
428
+ "source_books": [
429
+ "المصدر العربي"
430
+ ],
431
+ "source_pages": [],
432
+ "role": "assignment"
433
+ },
434
+ {
435
+ "text": "إذا تعذر الشرط تستعمل الفئة البديل المقرر.",
436
+ "score": 0.9416,
437
+ "source_record_ids": [
438
+ "ar-atomic"
439
+ ],
440
+ "source_books": [
441
+ "المصدر العربي"
442
+ ],
443
+ "source_pages": [],
444
+ "role": "consequence"
445
+ }
446
+ ],
447
+ "rejected_fragments": [
448
+ "شروط"
449
+ ],
450
+ "answerability_state": "grounded"
451
+ }
452
+ },
453
+ {
454
+ "name": "complete Arabic conditional retained",
455
+ "passed": true,
456
+ "value": "**السؤال:** ما شروط الموضوع للفئة؟\n\n**الإجابة:**\n1. وجود المتطلب الأول. _[المصدر العربي]_\n2. يكون المتطلب الثاني متحققًا. _[المصدر العربي]_\n3. تتحمل الفئة النفقة اللازمة. _[المصدر العربي]_\n4. إذا تعذر الشرط تستعمل الفئة البديل المقرر. _[المصدر العربي]_\n\n**المصادر التي بُني عليها الجواب:**\n- المصدر العربي"
457
+ },
458
+ {
459
+ "name": "evidence conditioned operator pruning",
460
+ "passed": true,
461
+ "value": {
462
+ "request_type": "ruling",
463
+ "initial_request_type": "ruling",
464
+ "request_type_scores": {
465
+ "cause": 0.098503,
466
+ "ruling": 0.91453,
467
+ "comparison": 0.0,
468
+ "evidence": 0.145409,
469
+ "definition": 0.0,
470
+ "exception": 0.182934,
471
+ "timing": 0.128992,
472
+ "description": 0.047008,
473
+ "principle": 0.32,
474
+ "duties": 0.136027,
475
+ "pillars": 0.0,
476
+ "list": 0.0,
477
+ "components": 0.0,
478
+ "amount": 0.381888,
479
+ "procedure": 0.089121,
480
+ "validity": 0.192315,
481
+ "conditions": 0.082086,
482
+ "location": 0.08547,
483
+ "remedy": 0.182934
484
+ },
485
+ "intent_repaired": false,
486
+ "intent_repair_reason": "",
487
+ "relation_edges": [],
488
+ "relation_signature": "",
489
+ "subject_terms": [
490
+ "عمل",
491
+ "فية",
492
+ "تصريح"
493
+ ],
494
+ "anchor_terms": [
495
+ "فية",
496
+ "تصريح"
497
+ ],
498
+ "qualifier_terms": [
499
+ "عمل"
500
+ ],
501
+ "critical_terms": [
502
+ "تصريح",
503
+ "عمل"
504
+ ],
505
+ "term_document_frequency": {
506
+ "عمل": 0.5,
507
+ "فية": 0.5,
508
+ "يكن": 0.5,
509
+ "لدي": 0.0,
510
+ "تصريح": 0.5
511
+ },
512
+ "term_evidence_support": {
513
+ "عمل": 0.7585279299937636,
514
+ "فية": 0.7585279299937636,
515
+ "يكن": 0.2414720700062364,
516
+ "لدي": 0.0,
517
+ "تصريح": 0.7585279299937636
518
+ },
519
+ "term_weights": {
520
+ "عمل": 0.752558,
521
+ "فية": 0.752558,
522
+ "تصريح": 0.752558
523
+ },
524
+ "operator_terms": [
525
+ "يكن",
526
+ "لدي"
527
+ ],
528
+ "polarity": "negative",
529
+ "accepted": 1,
530
+ "rejected": 1,
531
+ "consensus_state": "single_source",
532
+ "selected_cluster": "prohibited",
533
+ "issue_clusters": {
534
+ "issue-1": {
535
+ "books": 1,
536
+ "weight": 0.9901,
537
+ "principle_strength": 0.562,
538
+ "relation_alignment": 0.75,
539
+ "answerability": 0.9448
540
+ }
541
+ },
542
+ "used_record_ids": [
543
+ "operator-relevant"
544
+ ],
545
+ "used_book_ids": [
546
+ "المصدر هـ"
547
+ ],
548
+ "propositions": [
549
+ {
550
+ "text": "لا يجوز تنفيذها بدونه.",
551
+ "score": 0.8999,
552
+ "source_record_ids": [
553
+ "operator-relevant"
554
+ ],
555
+ "source_books": [
556
+ "المصدر هـ"
557
+ ],
558
+ "source_pages": [],
559
+ "role": "prohibition"
560
+ },
561
+ {
562
+ "text": "إذا لم يوجد التصريح للفئة فلا تلزمها العملية.",
563
+ "score": 0.9279,
564
+ "source_record_ids": [
565
+ "operator-relevant"
566
+ ],
567
+ "source_books": [
568
+ "المصدر هـ"
569
+ ],
570
+ "source_pages": [],
571
+ "role": "consequence"
572
+ }
573
+ ],
574
+ "rejected_fragments": [
575
+ "إن نفذتها مستوفية بقية الشروط صح التنفيذ",
576
+ "حكم"
577
+ ],
578
+ "answerability_state": "grounded"
579
+ }
580
+ },
581
+ {
582
+ "name": "multi signal semantic rescue accepts focused source",
583
+ "passed": true,
584
+ "value": {
585
+ "request_type": "ruling",
586
+ "initial_request_type": "ruling",
587
+ "request_type_scores": {
588
+ "cause": 0.098503,
589
+ "ruling": 0.91453,
590
+ "comparison": 0.0,
591
+ "evidence": 0.145409,
592
+ "definition": 0.0,
593
+ "exception": 0.182934,
594
+ "timing": 0.128992,
595
+ "description": 0.047008,
596
+ "principle": 0.32,
597
+ "duties": 0.136027,
598
+ "pillars": 0.0,
599
+ "list": 0.0,
600
+ "components": 0.0,
601
+ "amount": 0.381888,
602
+ "procedure": 0.089121,
603
+ "validity": 0.192315,
604
+ "conditions": 0.082086,
605
+ "location": 0.08547,
606
+ "remedy": 0.182934
607
+ },
608
+ "intent_repaired": false,
609
+ "intent_repair_reason": "",
610
+ "relation_edges": [],
611
+ "relation_signature": "",
612
+ "subject_terms": [
613
+ "عمل",
614
+ "فية",
615
+ "تصريح"
616
+ ],
617
+ "anchor_terms": [
618
+ "فية",
619
+ "تصريح"
620
+ ],
621
+ "qualifier_terms": [
622
+ "عمل"
623
+ ],
624
+ "critical_terms": [
625
+ "تصريح",
626
+ "عمل"
627
+ ],
628
+ "term_document_frequency": {
629
+ "عمل": 0.5,
630
+ "فية": 0.5,
631
+ "يكن": 0.5,
632
+ "لدي": 0.0,
633
+ "تصريح": 0.5
634
+ },
635
+ "term_evidence_support": {
636
+ "عمل": 0.7585279299937636,
637
+ "فية": 0.7585279299937636,
638
+ "يكن": 0.2414720700062364,
639
+ "لدي": 0.0,
640
+ "تصريح": 0.7585279299937636
641
+ },
642
+ "term_weights": {
643
+ "عمل": 0.752558,
644
+ "فية": 0.752558,
645
+ "تصريح": 0.752558
646
+ },
647
+ "operator_terms": [
648
+ "يكن",
649
+ "لدي"
650
+ ],
651
+ "polarity": "negative",
652
+ "accepted": 1,
653
+ "rejected": 1,
654
+ "consensus_state": "single_source",
655
+ "selected_cluster": "prohibited",
656
+ "issue_clusters": {
657
+ "issue-1": {
658
+ "books": 1,
659
+ "weight": 0.9901,
660
+ "principle_strength": 0.562,
661
+ "relation_alignment": 0.75,
662
+ "answerability": 0.9448
663
+ }
664
+ },
665
+ "used_record_ids": [
666
+ "operator-relevant"
667
+ ],
668
+ "used_book_ids": [
669
+ "المصدر هـ"
670
+ ],
671
+ "propositions": [
672
+ {
673
+ "text": "لا يجوز تنفيذها بدونه.",
674
+ "score": 0.8999,
675
+ "source_record_ids": [
676
+ "operator-relevant"
677
+ ],
678
+ "source_books": [
679
+ "المصدر هـ"
680
+ ],
681
+ "source_pages": [],
682
+ "role": "prohibition"
683
+ },
684
+ {
685
+ "text": "إذا لم يوجد التصريح للفئة فلا تلزمها العملية.",
686
+ "score": 0.9279,
687
+ "source_record_ids": [
688
+ "operator-relevant"
689
+ ],
690
+ "source_books": [
691
+ "المصدر هـ"
692
+ ],
693
+ "source_pages": [],
694
+ "role": "consequence"
695
+ }
696
+ ],
697
+ "rejected_fragments": [
698
+ "إن نفذتها مستوفية بقية الشروط صح التنفيذ",
699
+ "حكم"
700
+ ],
701
+ "answerability_state": "grounded"
702
+ }
703
+ },
704
+ {
705
+ "name": "high dense distractor remains rejected",
706
+ "passed": true,
707
+ "value": {
708
+ "request_type": "ruling",
709
+ "initial_request_type": "ruling",
710
+ "request_type_scores": {
711
+ "cause": 0.098503,
712
+ "ruling": 0.91453,
713
+ "comparison": 0.0,
714
+ "evidence": 0.145409,
715
+ "definition": 0.0,
716
+ "exception": 0.182934,
717
+ "timing": 0.128992,
718
+ "description": 0.047008,
719
+ "principle": 0.32,
720
+ "duties": 0.136027,
721
+ "pillars": 0.0,
722
+ "list": 0.0,
723
+ "components": 0.0,
724
+ "amount": 0.381888,
725
+ "procedure": 0.089121,
726
+ "validity": 0.192315,
727
+ "conditions": 0.082086,
728
+ "location": 0.08547,
729
+ "remedy": 0.182934
730
+ },
731
+ "intent_repaired": false,
732
+ "intent_repair_reason": "",
733
+ "relation_edges": [],
734
+ "relation_signature": "",
735
+ "subject_terms": [
736
+ "عمل",
737
+ "فية",
738
+ "تصريح"
739
+ ],
740
+ "anchor_terms": [
741
+ "فية",
742
+ "تصريح"
743
+ ],
744
+ "qualifier_terms": [
745
+ "عمل"
746
+ ],
747
+ "critical_terms": [
748
+ "تصريح",
749
+ "عمل"
750
+ ],
751
+ "term_document_frequency": {
752
+ "عمل": 0.5,
753
+ "فية": 0.5,
754
+ "يكن": 0.5,
755
+ "لدي": 0.0,
756
+ "تصريح": 0.5
757
+ },
758
+ "term_evidence_support": {
759
+ "عمل": 0.7585279299937636,
760
+ "فية": 0.7585279299937636,
761
+ "يكن": 0.2414720700062364,
762
+ "لدي": 0.0,
763
+ "تصريح": 0.7585279299937636
764
+ },
765
+ "term_weights": {
766
+ "عمل": 0.752558,
767
+ "فية": 0.752558,
768
+ "تصريح": 0.752558
769
+ },
770
+ "operator_terms": [
771
+ "يكن",
772
+ "لدي"
773
+ ],
774
+ "polarity": "negative",
775
+ "accepted": 1,
776
+ "rejected": 1,
777
+ "consensus_state": "single_source",
778
+ "selected_cluster": "prohibited",
779
+ "issue_clusters": {
780
+ "issue-1": {
781
+ "books": 1,
782
+ "weight": 0.9901,
783
+ "principle_strength": 0.562,
784
+ "relation_alignment": 0.75,
785
+ "answerability": 0.9448
786
+ }
787
+ },
788
+ "used_record_ids": [
789
+ "operator-relevant"
790
+ ],
791
+ "used_book_ids": [
792
+ "المصدر هـ"
793
+ ],
794
+ "propositions": [
795
+ {
796
+ "text": "لا يجوز تنفيذها بدونه.",
797
+ "score": 0.8999,
798
+ "source_record_ids": [
799
+ "operator-relevant"
800
+ ],
801
+ "source_books": [
802
+ "المصدر هـ"
803
+ ],
804
+ "source_pages": [],
805
+ "role": "prohibition"
806
+ },
807
+ {
808
+ "text": "إذا لم يوجد التصريح للفئة فلا تلزمها العملية.",
809
+ "score": 0.9279,
810
+ "source_record_ids": [
811
+ "operator-relevant"
812
+ ],
813
+ "source_books": [
814
+ "المصدر هـ"
815
+ ],
816
+ "source_pages": [],
817
+ "role": "consequence"
818
+ }
819
+ ],
820
+ "rejected_fragments": [
821
+ "إن نفذتها مستوفية بقية الشروط صح التنفيذ",
822
+ "حكم"
823
+ ],
824
+ "answerability_state": "grounded"
825
+ }
826
+ },
827
+ {
828
+ "name": "rare concept survives dynamic pruning",
829
+ "passed": true,
830
+ "value": {
831
+ "request_type": "ruling",
832
+ "initial_request_type": "ruling",
833
+ "request_type_scores": {
834
+ "cause": 0.1344,
835
+ "ruling": 1.0,
836
+ "comparison": 0.174252,
837
+ "evidence": 0.1984,
838
+ "definition": 0.120636,
839
+ "exception": 0.2496,
840
+ "timing": 0.176,
841
+ "description": 0.26808,
842
+ "principle": 0.2944,
843
+ "duties": 0.1856,
844
+ "pillars": 0.0,
845
+ "list": 0.0,
846
+ "components": 0.0,
847
+ "amount": 0.649968,
848
+ "procedure": 0.1216,
849
+ "validity": 0.2624,
850
+ "conditions": 0.112,
851
+ "location": 0.0,
852
+ "remedy": 0.2496
853
+ },
854
+ "intent_repaired": false,
855
+ "intent_repair_reason": "",
856
+ "relation_edges": [],
857
+ "relation_signature": "",
858
+ "subject_terms": [
859
+ "عمل",
860
+ "ذات",
861
+ "رمز",
862
+ "كيوزر"
863
+ ],
864
+ "anchor_terms": [
865
+ "عمل"
866
+ ],
867
+ "qualifier_terms": [
868
+ "ذات",
869
+ "رمز",
870
+ "كيوزر"
871
+ ],
872
+ "critical_terms": [
873
+ "ذات",
874
+ "رمز",
875
+ "كيوزر"
876
+ ],
877
+ "term_document_frequency": {
878
+ "عمل": 1.0,
879
+ "ذات": 0.1111111111111111,
880
+ "رمز": 0.1111111111111111,
881
+ "كيوزر": 0.1111111111111111
882
+ },
883
+ "term_evidence_support": {
884
+ "عمل": 1.0,
885
+ "ذات": 0.56,
886
+ "رمز": 0.56,
887
+ "كيوزر": 0.56
888
+ },
889
+ "term_weights": {
890
+ "عمل": 0.65,
891
+ "ذات": 0.829111,
892
+ "رمز": 0.829111,
893
+ "كيوزر": 0.829111
894
+ },
895
+ "operator_terms": [],
896
+ "polarity": "affirmative",
897
+ "accepted": 1,
898
+ "rejected": 8,
899
+ "consensus_state": "single_source",
900
+ "selected_cluster": "text:عمل:ذات:رمز:كيوزر:جايز",
901
+ "issue_clusters": {
902
+ "issue-1": {
903
+ "books": 1,
904
+ "weight": 0.9651,
905
+ "principle_strength": 0.23,
906
+ "relation_alignment": 0.75,
907
+ "answerability": 0.9012
908
+ }
909
+ },
910
+ "used_record_ids": [
911
+ "rare-relevant"
912
+ ],
913
+ "used_book_ids": [
914
+ "المصدر ز"
915
+ ],
916
+ "propositions": [
917
+ {
918
+ "text": "العملية ذات الرمز كيوزرون جائزة.",
919
+ "score": 0.9083,
920
+ "source_record_ids": [
921
+ "rare-relevant"
922
+ ],
923
+ "source_books": [
924
+ "المصدر ز"
925
+ ],
926
+ "source_pages": [],
927
+ "role": "statement"
928
+ }
929
+ ],
930
+ "rejected_fragments": [
931
+ "جائز"
932
+ ],
933
+ "answerability_state": "grounded"
934
+ }
935
+ },
936
+ {
937
+ "name": "principle request not misclassified as list",
938
+ "passed": true,
939
+ "value": {
940
+ "request_type": "principle",
941
+ "initial_request_type": "principle",
942
+ "request_type_scores": {
943
+ "cause": 0.092415,
944
+ "ruling": 0.220036,
945
+ "comparison": 0.0,
946
+ "evidence": 0.136423,
947
+ "definition": 0.0,
948
+ "exception": 0.171628,
949
+ "timing": 0.12102,
950
+ "description": 0.05498,
951
+ "principle": 0.867374,
952
+ "duties": 0.127621,
953
+ "pillars": 0.0,
954
+ "list": 0.090612,
955
+ "components": 0.0,
956
+ "amount": 0.0,
957
+ "procedure": 0.099964,
958
+ "validity": 0.18043,
959
+ "conditions": 0.077013,
960
+ "location": 0.263735,
961
+ "remedy": 0.171628
962
+ },
963
+ "intent_repaired": false,
964
+ "intent_repair_reason": "",
965
+ "relation_edges": [
966
+ [
967
+ "قاعد",
968
+ "concerning",
969
+ "تنفيذ"
970
+ ],
971
+ [
972
+ "عمل",
973
+ "on_behalf_of",
974
+ "غير"
975
+ ]
976
+ ],
977
+ "relation_signature": "قاعد>concerning>تنفيذ|عمل>on_behalf_of>غير",
978
+ "subject_terms": [
979
+ "تنفيذ",
980
+ "عمل",
981
+ "غير"
982
+ ],
983
+ "anchor_terms": [
984
+ "تنفيذ",
985
+ "غير"
986
+ ],
987
+ "qualifier_terms": [
988
+ "عمل"
989
+ ],
990
+ "critical_terms": [
991
+ "عمل"
992
+ ],
993
+ "term_document_frequency": {
994
+ "تنفيذ": 0.8333333333333334,
995
+ "عمل": 0.6666666666666666,
996
+ "غير": 1.0
997
+ },
998
+ "term_evidence_support": {
999
+ "تنفيذ": 0.7923824720559979,
1000
+ "عمل": 0.6975998006619147,
1001
+ "غير": 1.0
1002
+ },
1003
+ "term_weights": {
1004
+ "تنفيذ": 0.646048,
1005
+ "عمل": 0.675947,
1006
+ "غير": 0.65
1007
+ },
1008
+ "operator_terms": [],
1009
+ "polarity": "affirmative",
1010
+ "accepted": 4,
1011
+ "rejected": 2,
1012
+ "consensus_state": "agreement",
1013
+ "selected_cluster": "issue-1",
1014
+ "issue_clusters": {
1015
+ "issue-1": {
1016
+ "books": 3,
1017
+ "weight": 2.336,
1018
+ "principle_strength": 0.5633,
1019
+ "relation_alignment": 0.8233,
1020
+ "answerability": 0.8293
1021
+ },
1022
+ "issue-2": {
1023
+ "books": 1,
1024
+ "weight": 0.655,
1025
+ "principle_strength": 0.738,
1026
+ "relation_alignment": 0.735,
1027
+ "answerability": 0.8049
1028
+ }
1029
+ },
1030
+ "used_record_ids": [
1031
+ "principle-rule-1",
1032
+ "principle-rule-2"
1033
+ ],
1034
+ "used_book_ids": [
1035
+ "كتاب القاعدة الأول",
1036
+ "كتاب القاعدة الثاني"
1037
+ ],
1038
+ "propositions": [
1039
+ {
1040
+ "text": "الأصل أن يبدأ بنفسه أولا.",
1041
+ "score": 0.8887,
1042
+ "source_record_ids": [
1043
+ "principle-rule-1"
1044
+ ],
1045
+ "source_books": [
1046
+ "كتاب القاعدة الأول"
1047
+ ],
1048
+ "source_pages": [],
1049
+ "role": "principle"
1050
+ },
1051
+ {
1052
+ "text": "من لم ينفذ عن نفسه لا ينفذ عن غيره.",
1053
+ "score": 0.8846,
1054
+ "source_record_ids": [
1055
+ "principle-rule-1"
1056
+ ],
1057
+ "source_books": [
1058
+ "كتاب القاعدة الأول"
1059
+ ],
1060
+ "source_pages": [],
1061
+ "role": "principle"
1062
+ },
1063
+ {
1064
+ "text": "لا يصح أن يقدم عمل غيره على عمل نفسه الواجب.",
1065
+ "score": 0.8371,
1066
+ "source_record_ids": [
1067
+ "principle-rule-2"
1068
+ ],
1069
+ "source_books": [
1070
+ "كتاب القاعدة الثاني"
1071
+ ],
1072
+ "source_pages": [],
1073
+ "role": "principle"
1074
+ },
1075
+ {
1076
+ "text": "إذا فعل ذلك انصرف العمل إلى نفسه ولم يجزئ عن الغير.",
1077
+ "score": 0.824,
1078
+ "source_record_ids": [
1079
+ "principle-rule-2"
1080
+ ],
1081
+ "source_books": [
1082
+ "كتاب القاعدة الثاني"
1083
+ ],
1084
+ "source_pages": [],
1085
+ "role": "consequence"
1086
+ }
1087
+ ],
1088
+ "rejected_fragments": [
1089
+ "شرط النيابة",
1090
+ "لا ينفذ عن أحد حتى ينفذ عن نفسه",
1091
+ "ينفذ عن صاحبه",
1092
+ "يبدأ بنفسه",
1093
+ "لا يصح عن الغير"
1094
+ ],
1095
+ "answerability_state": "grounded"
1096
+ }
1097
+ },
1098
+ {
1099
+ "name": "directed relation rejects shared-noun funding issue",
1100
+ "passed": true,
1101
+ "value": {
1102
+ "request_type": "principle",
1103
+ "initial_request_type": "principle",
1104
+ "request_type_scores": {
1105
+ "cause": 0.092415,
1106
+ "ruling": 0.220036,
1107
+ "comparison": 0.0,
1108
+ "evidence": 0.136423,
1109
+ "definition": 0.0,
1110
+ "exception": 0.171628,
1111
+ "timing": 0.12102,
1112
+ "description": 0.05498,
1113
+ "principle": 0.867374,
1114
+ "duties": 0.127621,
1115
+ "pillars": 0.0,
1116
+ "list": 0.090612,
1117
+ "components": 0.0,
1118
+ "amount": 0.0,
1119
+ "procedure": 0.099964,
1120
+ "validity": 0.18043,
1121
+ "conditions": 0.077013,
1122
+ "location": 0.263735,
1123
+ "remedy": 0.171628
1124
+ },
1125
+ "intent_repaired": false,
1126
+ "intent_repair_reason": "",
1127
+ "relation_edges": [
1128
+ [
1129
+ "قاعد",
1130
+ "concerning",
1131
+ "تنفيذ"
1132
+ ],
1133
+ [
1134
+ "عمل",
1135
+ "on_behalf_of",
1136
+ "غير"
1137
+ ]
1138
+ ],
1139
+ "relation_signature": "قاعد>concerning>تنفيذ|عمل>on_behalf_of>غير",
1140
+ "subject_terms": [
1141
+ "تنفيذ",
1142
+ "عمل",
1143
+ "غير"
1144
+ ],
1145
+ "anchor_terms": [
1146
+ "تنفيذ",
1147
+ "غير"
1148
+ ],
1149
+ "qualifier_terms": [
1150
+ "عمل"
1151
+ ],
1152
+ "critical_terms": [
1153
+ "عمل"
1154
+ ],
1155
+ "term_document_frequency": {
1156
+ "تنفيذ": 0.8333333333333334,
1157
+ "عمل": 0.6666666666666666,
1158
+ "غير": 1.0
1159
+ },
1160
+ "term_evidence_support": {
1161
+ "تنفيذ": 0.7923824720559979,
1162
+ "عمل": 0.6975998006619147,
1163
+ "غير": 1.0
1164
+ },
1165
+ "term_weights": {
1166
+ "تنفيذ": 0.646048,
1167
+ "عمل": 0.675947,
1168
+ "غير": 0.65
1169
+ },
1170
+ "operator_terms": [],
1171
+ "polarity": "affirmative",
1172
+ "accepted": 4,
1173
+ "rejected": 2,
1174
+ "consensus_state": "agreement",
1175
+ "selected_cluster": "issue-1",
1176
+ "issue_clusters": {
1177
+ "issue-1": {
1178
+ "books": 3,
1179
+ "weight": 2.336,
1180
+ "principle_strength": 0.5633,
1181
+ "relation_alignment": 0.8233,
1182
+ "answerability": 0.8293
1183
+ },
1184
+ "issue-2": {
1185
+ "books": 1,
1186
+ "weight": 0.655,
1187
+ "principle_strength": 0.738,
1188
+ "relation_alignment": 0.735,
1189
+ "answerability": 0.8049
1190
+ }
1191
+ },
1192
+ "used_record_ids": [
1193
+ "principle-rule-1",
1194
+ "principle-rule-2"
1195
+ ],
1196
+ "used_book_ids": [
1197
+ "كتاب القاعدة الأول",
1198
+ "كتاب القاعدة الثاني"
1199
+ ],
1200
+ "propositions": [
1201
+ {
1202
+ "text": "الأصل أن يبدأ بنفسه أولا.",
1203
+ "score": 0.8887,
1204
+ "source_record_ids": [
1205
+ "principle-rule-1"
1206
+ ],
1207
+ "source_books": [
1208
+ "كتاب القاعدة الأول"
1209
+ ],
1210
+ "source_pages": [],
1211
+ "role": "principle"
1212
+ },
1213
+ {
1214
+ "text": "من لم ينفذ عن نفسه لا ينفذ عن غيره.",
1215
+ "score": 0.8846,
1216
+ "source_record_ids": [
1217
+ "principle-rule-1"
1218
+ ],
1219
+ "source_books": [
1220
+ "كتاب القاعدة الأول"
1221
+ ],
1222
+ "source_pages": [],
1223
+ "role": "principle"
1224
+ },
1225
+ {
1226
+ "text": "لا يصح أن يقدم عمل غيره على عمل نفسه الواجب.",
1227
+ "score": 0.8371,
1228
+ "source_record_ids": [
1229
+ "principle-rule-2"
1230
+ ],
1231
+ "source_books": [
1232
+ "كتاب القاعدة الثاني"
1233
+ ],
1234
+ "source_pages": [],
1235
+ "role": "principle"
1236
+ },
1237
+ {
1238
+ "text": "إذا فعل ذلك انصرف العمل إلى نفسه ولم يجزئ عن الغير.",
1239
+ "score": 0.824,
1240
+ "source_record_ids": [
1241
+ "principle-rule-2"
1242
+ ],
1243
+ "source_books": [
1244
+ "كتاب القاعدة الثاني"
1245
+ ],
1246
+ "source_pages": [],
1247
+ "role": "consequence"
1248
+ }
1249
+ ],
1250
+ "rejected_fragments": [
1251
+ "شرط النيابة",
1252
+ "لا ينفذ عن أحد حتى ينفذ عن نفسه",
1253
+ "ينفذ عن صاحبه",
1254
+ "يبدأ بنفسه",
1255
+ "لا يصح عن الغير"
1256
+ ],
1257
+ "answerability_state": "grounded"
1258
+ }
1259
+ },
1260
+ {
1261
+ "name": "dominant rule sub-issue selected",
1262
+ "passed": true,
1263
+ "value": {
1264
+ "request_type": "principle",
1265
+ "initial_request_type": "principle",
1266
+ "request_type_scores": {
1267
+ "cause": 0.092415,
1268
+ "ruling": 0.220036,
1269
+ "comparison": 0.0,
1270
+ "evidence": 0.136423,
1271
+ "definition": 0.0,
1272
+ "exception": 0.171628,
1273
+ "timing": 0.12102,
1274
+ "description": 0.05498,
1275
+ "principle": 0.867374,
1276
+ "duties": 0.127621,
1277
+ "pillars": 0.0,
1278
+ "list": 0.090612,
1279
+ "components": 0.0,
1280
+ "amount": 0.0,
1281
+ "procedure": 0.099964,
1282
+ "validity": 0.18043,
1283
+ "conditions": 0.077013,
1284
+ "location": 0.263735,
1285
+ "remedy": 0.171628
1286
+ },
1287
+ "intent_repaired": false,
1288
+ "intent_repair_reason": "",
1289
+ "relation_edges": [
1290
+ [
1291
+ "قاعد",
1292
+ "concerning",
1293
+ "تنفيذ"
1294
+ ],
1295
+ [
1296
+ "عمل",
1297
+ "on_behalf_of",
1298
+ "غير"
1299
+ ]
1300
+ ],
1301
+ "relation_signature": "قاعد>concerning>تنفيذ|عمل>on_behalf_of>غير",
1302
+ "subject_terms": [
1303
+ "تنفيذ",
1304
+ "عمل",
1305
+ "غير"
1306
+ ],
1307
+ "anchor_terms": [
1308
+ "تنفيذ",
1309
+ "غير"
1310
+ ],
1311
+ "qualifier_terms": [
1312
+ "عمل"
1313
+ ],
1314
+ "critical_terms": [
1315
+ "عمل"
1316
+ ],
1317
+ "term_document_frequency": {
1318
+ "تنفيذ": 0.8333333333333334,
1319
+ "عمل": 0.6666666666666666,
1320
+ "غير": 1.0
1321
+ },
1322
+ "term_evidence_support": {
1323
+ "تنفيذ": 0.7923824720559979,
1324
+ "عمل": 0.6975998006619147,
1325
+ "غير": 1.0
1326
+ },
1327
+ "term_weights": {
1328
+ "تنفيذ": 0.646048,
1329
+ "عمل": 0.675947,
1330
+ "غير": 0.65
1331
+ },
1332
+ "operator_terms": [],
1333
+ "polarity": "affirmative",
1334
+ "accepted": 4,
1335
+ "rejected": 2,
1336
+ "consensus_state": "agreement",
1337
+ "selected_cluster": "issue-1",
1338
+ "issue_clusters": {
1339
+ "issue-1": {
1340
+ "books": 3,
1341
+ "weight": 2.336,
1342
+ "principle_strength": 0.5633,
1343
+ "relation_alignment": 0.8233,
1344
+ "answerability": 0.8293
1345
+ },
1346
+ "issue-2": {
1347
+ "books": 1,
1348
+ "weight": 0.655,
1349
+ "principle_strength": 0.738,
1350
+ "relation_alignment": 0.735,
1351
+ "answerability": 0.8049
1352
+ }
1353
+ },
1354
+ "used_record_ids": [
1355
+ "principle-rule-1",
1356
+ "principle-rule-2"
1357
+ ],
1358
+ "used_book_ids": [
1359
+ "كتاب القاعدة الأول",
1360
+ "كتاب القاعدة الثاني"
1361
+ ],
1362
+ "propositions": [
1363
+ {
1364
+ "text": "الأصل أن يبدأ بنفسه أولا.",
1365
+ "score": 0.8887,
1366
+ "source_record_ids": [
1367
+ "principle-rule-1"
1368
+ ],
1369
+ "source_books": [
1370
+ "كتاب القاعدة الأول"
1371
+ ],
1372
+ "source_pages": [],
1373
+ "role": "principle"
1374
+ },
1375
+ {
1376
+ "text": "من لم ينفذ عن نفسه لا ينفذ عن غيره.",
1377
+ "score": 0.8846,
1378
+ "source_record_ids": [
1379
+ "principle-rule-1"
1380
+ ],
1381
+ "source_books": [
1382
+ "كتاب القاعدة الأول"
1383
+ ],
1384
+ "source_pages": [],
1385
+ "role": "principle"
1386
+ },
1387
+ {
1388
+ "text": "لا يصح أن يقدم عمل غيره على عمل نفسه الواجب.",
1389
+ "score": 0.8371,
1390
+ "source_record_ids": [
1391
+ "principle-rule-2"
1392
+ ],
1393
+ "source_books": [
1394
+ "كتاب القاعدة الثاني"
1395
+ ],
1396
+ "source_pages": [],
1397
+ "role": "principle"
1398
+ },
1399
+ {
1400
+ "text": "إذا فعل ذلك انصرف العمل إلى نفسه ولم يجزئ عن الغير.",
1401
+ "score": 0.824,
1402
+ "source_record_ids": [
1403
+ "principle-rule-2"
1404
+ ],
1405
+ "source_books": [
1406
+ "كتاب القاعدة الثاني"
1407
+ ],
1408
+ "source_pages": [],
1409
+ "role": "consequence"
1410
+ }
1411
+ ],
1412
+ "rejected_fragments": [
1413
+ "شرط النيابة",
1414
+ "لا ينفذ عن أحد حتى ينفذ عن نفسه",
1415
+ "ينفذ عن صاحبه",
1416
+ "يبدأ بنفسه",
1417
+ "لا يصح عن الغير"
1418
+ ],
1419
+ "answerability_state": "grounded"
1420
+ }
1421
+ },
1422
+ {
1423
+ "name": "principle answer uses complementary grounded sources",
1424
+ "passed": true,
1425
+ "value": "**السؤال:** ما هي القاعدة في تنفيذ العملية عن الغير؟\n\n**الإجابة:**\n\n**القاعدة:** من لم ينفذ عن نفسه لا ينفذ عن غيره. _[كتاب القاعدة الأول]_\n\n**وعند مخالفة القاعدة:**\n- إذا فعل ذلك انصرف العمل إلى نفسه ولم يجزئ عن الغير. _[كتاب القاعدة الثاني]_\n\n**التفصيل أو الدليل:**\n- الأصل أن يبدأ بنفسه أولا. _[كتاب القاعدة الأول]_\n- لا يصح أن يقدم عمل غيره على عمل نفسه الواجب. _[كتاب القاعدة الثاني]_\n\n**المصادر التي بُني عليها الجواب:**\n- كتاب القاعدة الثاني\n- كتاب القاعدة الأول"
1426
+ },
1427
+ {
1428
+ "name": "principle answer renders violation consequence",
1429
+ "passed": true,
1430
+ "value": "**السؤال:** ما هي القاعدة في تنفيذ العملية عن الغير؟\n\n**الإجابة:**\n\n**القاعدة:** من لم ينفذ عن نفسه لا ينفذ عن غيره. _[كتاب القاعدة الأول]_\n\n**وعند مخالفة القاعدة:**\n- إذا فعل ذلك انصرف العمل إلى نفسه ولم يجزئ عن الغير. _[كتاب القاعدة الثاني]_\n\n**التفصيل أو الدليل:**\n- الأصل أن يبدأ بنفسه أولا. _[كتاب القاعدة الأول]_\n- لا يصح أن يقدم عمل غيره على عمل نفسه الواجب. _[كتاب القاعدة الثاني]_\n\n**المصادر التي بُني عليها الجواب:**\n- كتاب القاعدة الثاني\n- كتاب القاعدة الأول"
1431
+ },
1432
+ {
1433
+ "name": "exact tier requires answer contribution",
1434
+ "passed": true,
1435
+ "value": {
1436
+ "exact": [
1437
+ "principle-rule-1"
1438
+ ],
1439
+ "used": [
1440
+ "principle-rule-1",
1441
+ "principle-rule-2"
1442
+ ]
1443
+ }
1444
+ },
1445
+ {
1446
+ "name": "insufficient answer cannot retain extreme confidence",
1447
+ "passed": true,
1448
+ "value": null
1449
+ },
1450
+ {
1451
+ "name": "specific ruling outranks generic what-is shell",
1452
+ "passed": true,
1453
+ "value": {
1454
+ "request_type": "ruling",
1455
+ "initial_request_type": "ruling",
1456
+ "request_type_scores": {
1457
+ "cause": 0.113185,
1458
+ "ruling": 0.840689,
1459
+ "comparison": 0.113811,
1460
+ "evidence": 0.167083,
1461
+ "definition": 0.078792,
1462
+ "exception": 0.210201,
1463
+ "timing": 0.148219,
1464
+ "description": 0.576566,
1465
+ "principle": 0.24793,
1466
+ "duties": 0.156304,
1467
+ "pillars": 0.0,
1468
+ "list": 0.084789,
1469
+ "components": 0.0,
1470
+ "amount": 0.381888,
1471
+ "procedure": 0.102406,
1472
+ "validity": 0.220981,
1473
+ "conditions": 0.114722,
1474
+ "location": 0.027781,
1475
+ "remedy": 0.210201
1476
+ },
1477
+ "intent_repaired": false,
1478
+ "intent_repair_reason": "",
1479
+ "relation_edges": [],
1480
+ "relation_signature": "",
1481
+ "subject_terms": [
1482
+ "تنفيذ",
1483
+ "عامل",
1484
+ "عمل"
1485
+ ],
1486
+ "anchor_terms": [
1487
+ "عامل",
1488
+ "عمل"
1489
+ ],
1490
+ "qualifier_terms": [
1491
+ "تنفيذ"
1492
+ ],
1493
+ "critical_terms": [
1494
+ "تنفيذ"
1495
+ ],
1496
+ "term_document_frequency": {
1497
+ "تنفيذ": 0.75,
1498
+ "عامل": 1.0,
1499
+ "عمل": 0.75
1500
+ },
1501
+ "term_evidence_support": {
1502
+ "تنفيذ": 0.7575716740265707,
1503
+ "عامل": 1.0,
1504
+ "عمل": 0.7485815138420883
1505
+ },
1506
+ "term_weights": {
1507
+ "تنفيذ": 0.664772,
1508
+ "عامل": 0.65,
1509
+ "عمل": 0.662074
1510
+ },
1511
+ "operator_terms": [],
1512
+ "polarity": "affirmative",
1513
+ "accepted": 2,
1514
+ "rejected": 2,
1515
+ "consensus_state": "mixed",
1516
+ "selected_cluster": "not_obligatory+valid",
1517
+ "issue_clusters": {
1518
+ "issue-1": {
1519
+ "books": 1,
1520
+ "weight": 0.9899,
1521
+ "principle_strength": 0.562,
1522
+ "relation_alignment": 0.75,
1523
+ "answerability": 0.9438
1524
+ },
1525
+ "issue-2": {
1526
+ "books": 1,
1527
+ "weight": 0.8133,
1528
+ "principle_strength": 0.566,
1529
+ "relation_alignment": 0.75,
1530
+ "answerability": 0.8255
1531
+ }
1532
+ },
1533
+ "used_record_ids": [
1534
+ "sense-main",
1535
+ "sense-supplement"
1536
+ ],
1537
+ "used_book_ids": [
1538
+ "كتاب الحكم المركزي",
1539
+ "كتاب الحكم المكمل"
1540
+ ],
1541
+ "propositions": [
1542
+ {
1543
+ "text": "لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله.",
1544
+ "score": 0.9631,
1545
+ "source_record_ids": [
1546
+ "sense-main"
1547
+ ],
1548
+ "source_books": [
1549
+ "كتاب الحكم المركزي"
1550
+ ],
1551
+ "source_pages": [],
1552
+ "role": "statement"
1553
+ },
1554
+ {
1555
+ "text": "يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي.",
1556
+ "score": 0.8179,
1557
+ "source_record_ids": [
1558
+ "sense-supplement"
1559
+ ],
1560
+ "source_books": [
1561
+ "كتاب الحكم المكمل"
1562
+ ],
1563
+ "source_pages": [],
1564
+ "role": "consequence"
1565
+ }
1566
+ ],
1567
+ "rejected_fragments": [
1568
+ "لا يجب / يصح",
1569
+ "صحيح غير مجزئ"
1570
+ ],
1571
+ "answerability_state": "grounded"
1572
+ }
1573
+ },
1574
+ {
1575
+ "name": "contextual homograph rejected",
1576
+ "passed": true,
1577
+ "value": {
1578
+ "request_type": "ruling",
1579
+ "initial_request_type": "ruling",
1580
+ "request_type_scores": {
1581
+ "cause": 0.113185,
1582
+ "ruling": 0.840689,
1583
+ "comparison": 0.113811,
1584
+ "evidence": 0.167083,
1585
+ "definition": 0.078792,
1586
+ "exception": 0.210201,
1587
+ "timing": 0.148219,
1588
+ "description": 0.576566,
1589
+ "principle": 0.24793,
1590
+ "duties": 0.156304,
1591
+ "pillars": 0.0,
1592
+ "list": 0.084789,
1593
+ "components": 0.0,
1594
+ "amount": 0.381888,
1595
+ "procedure": 0.102406,
1596
+ "validity": 0.220981,
1597
+ "conditions": 0.114722,
1598
+ "location": 0.027781,
1599
+ "remedy": 0.210201
1600
+ },
1601
+ "intent_repaired": false,
1602
+ "intent_repair_reason": "",
1603
+ "relation_edges": [],
1604
+ "relation_signature": "",
1605
+ "subject_terms": [
1606
+ "تنفيذ",
1607
+ "عامل",
1608
+ "عمل"
1609
+ ],
1610
+ "anchor_terms": [
1611
+ "عامل",
1612
+ "عمل"
1613
+ ],
1614
+ "qualifier_terms": [
1615
+ "تنفيذ"
1616
+ ],
1617
+ "critical_terms": [
1618
+ "تنفيذ"
1619
+ ],
1620
+ "term_document_frequency": {
1621
+ "تنفيذ": 0.75,
1622
+ "عامل": 1.0,
1623
+ "عمل": 0.75
1624
+ },
1625
+ "term_evidence_support": {
1626
+ "تنفيذ": 0.7575716740265707,
1627
+ "عامل": 1.0,
1628
+ "عمل": 0.7485815138420883
1629
+ },
1630
+ "term_weights": {
1631
+ "تنفيذ": 0.664772,
1632
+ "عامل": 0.65,
1633
+ "عمل": 0.662074
1634
+ },
1635
+ "operator_terms": [],
1636
+ "polarity": "affirmative",
1637
+ "accepted": 2,
1638
+ "rejected": 2,
1639
+ "consensus_state": "mixed",
1640
+ "selected_cluster": "not_obligatory+valid",
1641
+ "issue_clusters": {
1642
+ "issue-1": {
1643
+ "books": 1,
1644
+ "weight": 0.9899,
1645
+ "principle_strength": 0.562,
1646
+ "relation_alignment": 0.75,
1647
+ "answerability": 0.9438
1648
+ },
1649
+ "issue-2": {
1650
+ "books": 1,
1651
+ "weight": 0.8133,
1652
+ "principle_strength": 0.566,
1653
+ "relation_alignment": 0.75,
1654
+ "answerability": 0.8255
1655
+ }
1656
+ },
1657
+ "used_record_ids": [
1658
+ "sense-main",
1659
+ "sense-supplement"
1660
+ ],
1661
+ "used_book_ids": [
1662
+ "كتاب الحكم المركزي",
1663
+ "كتاب الحكم المكمل"
1664
+ ],
1665
+ "propositions": [
1666
+ {
1667
+ "text": "لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله.",
1668
+ "score": 0.9631,
1669
+ "source_record_ids": [
1670
+ "sense-main"
1671
+ ],
1672
+ "source_books": [
1673
+ "كتاب الحكم المركزي"
1674
+ ],
1675
+ "source_pages": [],
1676
+ "role": "statement"
1677
+ },
1678
+ {
1679
+ "text": "يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي.",
1680
+ "score": 0.8179,
1681
+ "source_record_ids": [
1682
+ "sense-supplement"
1683
+ ],
1684
+ "source_books": [
1685
+ "كتاب الحكم المكمل"
1686
+ ],
1687
+ "source_pages": [],
1688
+ "role": "consequence"
1689
+ }
1690
+ ],
1691
+ "rejected_fragments": [
1692
+ "لا يجب / يصح",
1693
+ "صحيح غير مجزئ"
1694
+ ],
1695
+ "answerability_state": "grounded"
1696
+ }
1697
+ },
1698
+ {
1699
+ "name": "broken OCR rejected",
1700
+ "passed": true,
1701
+ "value": {
1702
+ "request_type": "ruling",
1703
+ "initial_request_type": "ruling",
1704
+ "request_type_scores": {
1705
+ "cause": 0.113185,
1706
+ "ruling": 0.840689,
1707
+ "comparison": 0.113811,
1708
+ "evidence": 0.167083,
1709
+ "definition": 0.078792,
1710
+ "exception": 0.210201,
1711
+ "timing": 0.148219,
1712
+ "description": 0.576566,
1713
+ "principle": 0.24793,
1714
+ "duties": 0.156304,
1715
+ "pillars": 0.0,
1716
+ "list": 0.084789,
1717
+ "components": 0.0,
1718
+ "amount": 0.381888,
1719
+ "procedure": 0.102406,
1720
+ "validity": 0.220981,
1721
+ "conditions": 0.114722,
1722
+ "location": 0.027781,
1723
+ "remedy": 0.210201
1724
+ },
1725
+ "intent_repaired": false,
1726
+ "intent_repair_reason": "",
1727
+ "relation_edges": [],
1728
+ "relation_signature": "",
1729
+ "subject_terms": [
1730
+ "تنفيذ",
1731
+ "عامل",
1732
+ "عمل"
1733
+ ],
1734
+ "anchor_terms": [
1735
+ "عامل",
1736
+ "عمل"
1737
+ ],
1738
+ "qualifier_terms": [
1739
+ "تنفيذ"
1740
+ ],
1741
+ "critical_terms": [
1742
+ "تنفيذ"
1743
+ ],
1744
+ "term_document_frequency": {
1745
+ "تنفيذ": 0.75,
1746
+ "عامل": 1.0,
1747
+ "عمل": 0.75
1748
+ },
1749
+ "term_evidence_support": {
1750
+ "تنفيذ": 0.7575716740265707,
1751
+ "عامل": 1.0,
1752
+ "عمل": 0.7485815138420883
1753
+ },
1754
+ "term_weights": {
1755
+ "تنفيذ": 0.664772,
1756
+ "عامل": 0.65,
1757
+ "عمل": 0.662074
1758
+ },
1759
+ "operator_terms": [],
1760
+ "polarity": "affirmative",
1761
+ "accepted": 2,
1762
+ "rejected": 2,
1763
+ "consensus_state": "mixed",
1764
+ "selected_cluster": "not_obligatory+valid",
1765
+ "issue_clusters": {
1766
+ "issue-1": {
1767
+ "books": 1,
1768
+ "weight": 0.9899,
1769
+ "principle_strength": 0.562,
1770
+ "relation_alignment": 0.75,
1771
+ "answerability": 0.9438
1772
+ },
1773
+ "issue-2": {
1774
+ "books": 1,
1775
+ "weight": 0.8133,
1776
+ "principle_strength": 0.566,
1777
+ "relation_alignment": 0.75,
1778
+ "answerability": 0.8255
1779
+ }
1780
+ },
1781
+ "used_record_ids": [
1782
+ "sense-main",
1783
+ "sense-supplement"
1784
+ ],
1785
+ "used_book_ids": [
1786
+ "كتاب الحكم المركزي",
1787
+ "كتاب الحكم المكمل"
1788
+ ],
1789
+ "propositions": [
1790
+ {
1791
+ "text": "لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله.",
1792
+ "score": 0.9631,
1793
+ "source_record_ids": [
1794
+ "sense-main"
1795
+ ],
1796
+ "source_books": [
1797
+ "كتاب الحكم المركزي"
1798
+ ],
1799
+ "source_pages": [],
1800
+ "role": "statement"
1801
+ },
1802
+ {
1803
+ "text": "يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي.",
1804
+ "score": 0.8179,
1805
+ "source_record_ids": [
1806
+ "sense-supplement"
1807
+ ],
1808
+ "source_books": [
1809
+ "كتاب الحكم المكمل"
1810
+ ],
1811
+ "source_pages": [],
1812
+ "role": "consequence"
1813
+ }
1814
+ ],
1815
+ "rejected_fragments": [
1816
+ "لا يجب / يصح",
1817
+ "صحيح غير مجزئ"
1818
+ ],
1819
+ "answerability_state": "grounded"
1820
+ }
1821
+ },
1822
+ {
1823
+ "name": "central ruling frame rendered",
1824
+ "passed": true,
1825
+ "value": "**السؤال:** ما هو حكم تنفيذ العامل للعملية؟\n\n**الإجابة:**\n\n**الحكم المختصر:** لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله. _[كتاب الحكم المركزي]_\n\n**التفصيل والاستثناءات:**\n- يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي. _[كتاب الحكم المكمل]_\n\n**المصادر التي بُني عليها الجواب:**\n- كتاب الحكم المركزي\n- كتاب الحكم المكمل"
1826
+ },
1827
+ {
1828
+ "name": "complementary sufficiency facet retained",
1829
+ "passed": true,
1830
+ "value": "**السؤال:** ما هو حكم تنفيذ العامل للعملية؟\n\n**الإجابة:**\n\n**الحكم المختصر:** لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله. _[كتاب الحكم المركزي]_\n\n**التفصيل والاستثناءات:**\n- يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي. _[كتاب الحكم المكمل]_\n\n**المصادر التي بُني عليها الجواب:**\n- كتاب الحكم المركزي\n- كتاب الحكم المكمل"
1831
+ },
1832
+ {
1833
+ "name": "homograph and OCR excluded from answer",
1834
+ "passed": true,
1835
+ "value": "**السؤال:** ما هو حكم تنفيذ العامل للعملية؟\n\n**الإجابة:**\n\n**الحكم المختصر:** لا يجب تنفيذ العامل للعملية، لكنه يصح منه إذا فعله. _[كتاب الحكم المركزي]_\n\n**التفصيل والاستثناءات:**\n- يصح التنفيذ، لكنه لا يجزئ عن الالتزام الأصلي. _[كتاب الحكم المكمل]_\n\n**المصادر التي بُني عليها الجواب:**\n- كتاب الحكم المركزي\n- كتاب الحكم المكمل"
1836
+ },
1837
+ {
1838
+ "name": "no domain anchor table",
1839
+ "passed": true,
1840
+ "value": null
1841
+ },
1842
+ {
1843
+ "name": "no entity alias table",
1844
+ "passed": true,
1845
+ "value": null
1846
+ },
1847
+ {
1848
+ "name": "no named intent table",
1849
+ "passed": true,
1850
+ "value": null
1851
+ }
1852
+ ]
1853
+ }
hudanet_v38_0_6_manifest.json ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "38.0.6",
3
+ "description": "Contextual-sense alignment, OCR/text-integrity gating, central ruling frames, and contribution-based evidence tiers.",
4
+ "file_count_excluding_manifest": 35,
5
+ "files": [
6
+ {
7
+ "path": "DEPLOY_HUDANET_V38_0_6.md",
8
+ "size": 767,
9
+ "sha256": "53bc354b50ba36ca0b8dfc32ef497210c185c7657e6c6ffd82203ae35d46359e"
10
+ },
11
+ {
12
+ "path": "app.py",
13
+ "size": 550304,
14
+ "sha256": "c37184ff62f32e5475f6cbb0946b981b82c79d6b59a257c6e4c580fd1d3256c4"
15
+ },
16
+ {
17
+ "path": "hudanet_core/README.md",
18
+ "size": 1501,
19
+ "sha256": "0d3b25bc2e326218ac95690dc3a1b29627f8fc93097758f04ac17e3804c0a61e"
20
+ },
21
+ {
22
+ "path": "hudanet_core/__init__.py",
23
+ "size": 175,
24
+ "sha256": "c417d4feb14e04c42769554348a0fe5f9cd2764ae29fcce6bca48fb29ce1acb1"
25
+ },
26
+ {
27
+ "path": "hudanet_core/answerability.py",
28
+ "size": 1023,
29
+ "sha256": "3af690623128621b6be5e9eddf03672172b9cb6fa70ea107ceed60f40a74209c"
30
+ },
31
+ {
32
+ "path": "hudanet_core/centrality.py",
33
+ "size": 2230,
34
+ "sha256": "e68fc0bdb9f787996930b927861affb645e95ac040a8857605cbfee9930984cf"
35
+ },
36
+ {
37
+ "path": "hudanet_core/compatibility.py",
38
+ "size": 19912,
39
+ "sha256": "a7ac324863e48e2b06ad1e12f4501eab14f9cbcd23a69e51d93dcd39d6ec3c40"
40
+ },
41
+ {
42
+ "path": "hudanet_core/consensus.py",
43
+ "size": 7950,
44
+ "sha256": "c03a2c3cd290adfb0b0a92729c39616f2c5e1b8cb2c265104f66fcf94a8d5db9"
45
+ },
46
+ {
47
+ "path": "hudanet_core/contextual_sense.py",
48
+ "size": 6633,
49
+ "sha256": "1d2a44e785288d9df8c66811ff0e48b728887d1c3bdfeb5869e08a63fdafa38d"
50
+ },
51
+ {
52
+ "path": "hudanet_core/evidence.py",
53
+ "size": 10861,
54
+ "sha256": "659c3cc5a2580f8634bc40fa627f11d4df3a5edf82c9ae9cf785408e3fb2f86f"
55
+ },
56
+ {
57
+ "path": "hudanet_core/grounding.py",
58
+ "size": 1108,
59
+ "sha256": "7abe6dd19b685ae687ee1242dd9301306efbe2ecffc160aa26011ea5f206f74a"
60
+ },
61
+ {
62
+ "path": "hudanet_core/intent_repair.py",
63
+ "size": 4724,
64
+ "sha256": "c2bb3e96e4a85b941fdbb02535789eb6e041ddb454c1ce52a5219761f1633281"
65
+ },
66
+ {
67
+ "path": "hudanet_core/issue_clustering.py",
68
+ "size": 5548,
69
+ "sha256": "b76e3ebd47cc4360d854d32960884912e759ead1d477b58558ee252dd9f19348"
70
+ },
71
+ {
72
+ "path": "hudanet_core/pipeline.py",
73
+ "size": 13070,
74
+ "sha256": "bbb59414372ac48ecff5b55c93dfb0074903565043a131fc1166660b1b0f5dd2"
75
+ },
76
+ {
77
+ "path": "hudanet_core/propositions.py",
78
+ "size": 29258,
79
+ "sha256": "a3feeb76459c56a847c1d71562a740ff94cd956e3d8d30b7567e1ed829c6385c"
80
+ },
81
+ {
82
+ "path": "hudanet_core/query.py",
83
+ "size": 13522,
84
+ "sha256": "d82b0b3abd32bfd58c0e6afaee9ba49f42b99c7e23dc21973f51ebaf8cd1a706"
85
+ },
86
+ {
87
+ "path": "hudanet_core/relation_graph.py",
88
+ "size": 4902,
89
+ "sha256": "b1713307bd5a66d97fe4521512bb7ab92db22b724f05e6e3ca286d562fbbf420"
90
+ },
91
+ {
92
+ "path": "hudanet_core/rendering.py",
93
+ "size": 6922,
94
+ "sha256": "7303e3e1156ab9cbb1a3129e48e3c64535d5fd775d9b5e978ea0236ac383f6fd"
95
+ },
96
+ {
97
+ "path": "hudanet_core/resources/answer_templates.json",
98
+ "size": 5149,
99
+ "sha256": "d4efe1f130a8179f957b5150c1befc75a5bb859cc7f5d2e2db965a7b4f4a5a1b"
100
+ },
101
+ {
102
+ "path": "hudanet_core/resources/evidence_schema.json",
103
+ "size": 732,
104
+ "sha256": "522d171dd2b6371a3d9b1d01e9395ab12dcbccbbd96e546dfa965a05c77888a4"
105
+ },
106
+ {
107
+ "path": "hudanet_core/resources/ranking_config.json",
108
+ "size": 8772,
109
+ "sha256": "075173c284bd8f0598d2201ad2cd32790be1d75e368d8ced59d8ae7d2ab415cf"
110
+ },
111
+ {
112
+ "path": "hudanet_core/resources/semantic_rules.json",
113
+ "size": 34193,
114
+ "sha256": "fa27eb9d2f1b5006ef670494ef222a39925ed2520db2d0c107fc0859d69cad13"
115
+ },
116
+ {
117
+ "path": "hudanet_core/resources.py",
118
+ "size": 2268,
119
+ "sha256": "d043d3df9da9ec9c43b9a269f395ba2754f08945a80f98ff6b6516fad3087e1d"
120
+ },
121
+ {
122
+ "path": "hudanet_core/roles.py",
123
+ "size": 959,
124
+ "sha256": "d0e0a4b5fb7bb58f44cbbc62f9c0ffcb22628f2bc8811117919c4be7f1a38936"
125
+ },
126
+ {
127
+ "path": "hudanet_core/ruling_frame.py",
128
+ "size": 11249,
129
+ "sha256": "de6c700b7cce1c4507ccf9eb13f5716a9dbc7ffcf7f83b4ca9df3768672de18f"
130
+ },
131
+ {
132
+ "path": "hudanet_core/synthesis.py",
133
+ "size": 17584,
134
+ "sha256": "93c21e01743ebcaf584392f829ab44c1d2e04fb4a6c8e6f95516788acc3a672b"
135
+ },
136
+ {
137
+ "path": "hudanet_core/tests/test_generic_pipeline.py",
138
+ "size": 19626,
139
+ "sha256": "cb1ae0fc4eb44189bbee3ddb8172ee225ca55db7ac5bcc777d23c44a8dbc708e"
140
+ },
141
+ {
142
+ "path": "hudanet_core/text.py",
143
+ "size": 11211,
144
+ "sha256": "65836ed79f35694b0b0956812a6acf8f5bfb4b3a610a5eabf72222c222434e33"
145
+ },
146
+ {
147
+ "path": "hudanet_core/text_integrity.py",
148
+ "size": 4942,
149
+ "sha256": "1faa64d233350750389479d19c46bafd63f475f66431554126ba888e8456844b"
150
+ },
151
+ {
152
+ "path": "hudanet_core/types.py",
153
+ "size": 3864,
154
+ "sha256": "bae5f915556a0189700af0ec35617b536c6bcf1827915ac11f5aece14020eaf6"
155
+ },
156
+ {
157
+ "path": "hudanet_dialects.json",
158
+ "size": 61047,
159
+ "sha256": "9d73324aa9a53cef401b1b155691c5c594504ef5a89618709856d8ff86032413"
160
+ },
161
+ {
162
+ "path": "hudanet_retrieval_rules.json",
163
+ "size": 185,
164
+ "sha256": "cec40429fc82d5234e289a167c9ce8794b36bef7772e2bedb441d1569844e051"
165
+ },
166
+ {
167
+ "path": "hudanet_v38_0_6_contextual_ruling_regression.json",
168
+ "size": 7325,
169
+ "sha256": "90d523d26595d384d35637ae9e1bdd592c5d4829d9df4d106eae457f3a7ebfb5"
170
+ },
171
+ {
172
+ "path": "hudanet_v38_0_6_generic_tests.json",
173
+ "size": 57499,
174
+ "sha256": "e058f1c55c5a0f10afd129316c5d27da1205b44d41036ddb9e3cafc53737eca7"
175
+ },
176
+ {
177
+ "path": "hudanet_v38_0_6_validation_report.json",
178
+ "size": 1308,
179
+ "sha256": "69ffe3aba33310bbb73c82fac5cddc4687988b7e30177d85de81d5e30a59f019"
180
+ }
181
+ ]
182
+ }
hudanet_v38_0_6_validation_report.json ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "38.0.6",
3
+ "passed": true,
4
+ "python_files": 24,
5
+ "python_compile_passed": 24,
6
+ "python_compile_failures": [],
7
+ "regex_patterns_checked": 508,
8
+ "regex_errors": [],
9
+ "generic_tests_passed": true,
10
+ "generic_tests_count": 37,
11
+ "target_regression_passed": true,
12
+ "target_regression_checks": {
13
+ "request_type_is_ruling": true,
14
+ "main_answer_present": true,
15
+ "non_sufficiency_present": true,
16
+ "status_change_detail_present": true,
17
+ "homograph_rejected": true,
18
+ "ocr_rejected": true,
19
+ "bad_text_absent": true,
20
+ "exact_sources_contributed": true,
21
+ "bad_sources_distant": true
22
+ },
23
+ "production_domain_term_hits": [],
24
+ "preflight_before_runtime_download": true,
25
+ "version_strings_correct": true,
26
+ "preflight_includes_new_layers": true,
27
+ "new_modules": [
28
+ "hudanet_core/contextual_sense.py",
29
+ "hudanet_core/text_integrity.py",
30
+ "hudanet_core/centrality.py",
31
+ "hudanet_core/ruling_frame.py"
32
+ ],
33
+ "notes": [
34
+ "Full Hugging Face runtime download was not executed in this offline validation environment.",
35
+ "The lightweight preflight logic and the standalone generic pipeline were executed locally."
36
+ ],
37
+ "package_file_count": 36,
38
+ "package_manifest_verified_after_extraction": true,
39
+ "package_generic_tests_verified_after_extraction": true
40
+ }