mayar-waleed commited on
Commit
a52abbb
·
1 Parent(s): 101a9d7
EVALUATION_README.md DELETED
@@ -1,153 +0,0 @@
1
- # RAG Evaluation with Ragas
2
-
3
- ## 📊 Evaluation Metrics
4
-
5
- This evaluation uses **Ragas** library to measure RAG system quality:
6
-
7
- | Metric | Description | Range | Goal |
8
- |--------|-------------|-------|------|
9
- | **faithfulness** | Is the answer grounded in the context? | 0-1 | Higher |
10
- | **answer_relevancy** | Does the answer relate to the question? | 0-1 | Higher |
11
- | **context_precision** | How much retrieved context was relevant? | 0-1 | Higher |
12
- | **context_recall** | Did we retrieve all needed information? | 0-1 | Higher |
13
- | **context_relevancy** | Overall relevance of context to question | 0-1 | Higher |
14
-
15
- ## 🚀 How to Run
16
-
17
- ### 1. Prerequisites
18
-
19
- Make sure you have:
20
- - ✅ Ragas installed: `pip install ragas datasets`
21
- - ✅ OpenAI API key set in `.env` file (Ragas uses it for evaluation)
22
- - ✅ Your RAG system working (test with `streamlit run app_final.py`)
23
-
24
- ### 2. Prepare Test Dataset
25
-
26
- Edit `test_dataset.json` with your test questions:
27
-
28
- ```json
29
- [
30
- {
31
- "question": "Your question in Arabic",
32
- "ground_truth": "Expected correct answer (optional but recommended)"
33
- }
34
- ]
35
- ```
36
-
37
- ### 3. Run Evaluation
38
-
39
- ```bash
40
- python evaluate_rag.py
41
- ```
42
-
43
- ### 4. Check Results
44
-
45
- The script will generate:
46
- - `evaluation_results.json` - Summary metrics
47
- - `evaluation_detailed.json` - Full Q&A pairs with contexts
48
-
49
- ## 📈 Understanding Results
50
-
51
- ### Good Scores (Target)
52
- - **faithfulness**: > 0.7 (answers stay within context)
53
- - **answer_relevancy**: > 0.8 (answers address the question)
54
- - **context_precision**: > 0.6 (low noise in retrieval)
55
- - **context_recall**: > 0.7 (retrieved all needed info)
56
- - **context_relevancy**: > 0.7 (context matches question)
57
-
58
- ### If Scores Are Low
59
-
60
- **Low Faithfulness (<0.5)**
61
- - LLM is hallucinating or adding external info
62
- - Solution: Adjust prompt to be stricter about using only context
63
-
64
- **Low Answer Relevancy (<0.6)**
65
- - Answers don't match questions
66
- - Solution: Check if retrieval is getting right documents
67
-
68
- **Low Context Precision (<0.4)**
69
- - Too much irrelevant context retrieved
70
- - Solution: Tune retrieval k parameter, adjust reranker top_n
71
-
72
- **Low Context Recall (<0.5)**
73
- - Missing important information
74
- - Solution: Increase k in retrievers, check if documents have the info
75
-
76
- **Low Context Relevancy (<0.5)**
77
- - Retrieved documents don't match question
78
- - Solution: Check embeddings, tune hybrid search weights
79
-
80
- ## 🔧 Customization
81
-
82
- ### Add More Test Questions
83
-
84
- Edit `test_dataset.json`:
85
- ```json
86
- {
87
- "question": "ما هي شروط الترشح للبرلمان؟",
88
- "ground_truth": "يجب أن يكون مصرياً، متمتعاً بحقوقه المدنية والسياسية..."
89
- }
90
- ```
91
-
92
- ### Change Metrics
93
-
94
- Edit `evaluate_rag.py` and modify the metrics list:
95
- ```python
96
- evaluation_results = evaluate(
97
- dataset,
98
- metrics=[
99
- faithfulness,
100
- answer_relevancy,
101
- # Add or remove metrics here
102
- ],
103
- )
104
- ```
105
-
106
- ### Use Different LLM for Evaluation
107
-
108
- Ragas supports different LLMs. Edit the evaluation call:
109
- ```python
110
- from ragas.llms import LangchainLLMWrapper
111
- from langchain_groq import ChatGroq
112
-
113
- # Use Groq instead of OpenAI for evaluation
114
- evaluator_llm = LangchainLLMWrapper(ChatGroq(model="llama-3.1-70b-versatile"))
115
-
116
- evaluation_results = evaluate(
117
- dataset,
118
- metrics=[...],
119
- llm=evaluator_llm
120
- )
121
- ```
122
-
123
- ## 📝 Notes
124
-
125
- 1. **OpenAI API Cost**: Ragas uses OpenAI API for evaluation (GPT-3.5/4). Each evaluation costs ~$0.01-0.10 depending on dataset size.
126
-
127
- 2. **Ground Truth**: While optional, providing ground_truth improves `context_recall` metric accuracy.
128
-
129
- 3. **Arabic Support**: Ragas works with Arabic text, but evaluation quality depends on the LLM used (GPT-4 is better for Arabic than GPT-3.5).
130
-
131
- 4. **Batch Size**: For large datasets (>50 questions), consider splitting into batches to avoid API rate limits.
132
-
133
- ## 🐛 Troubleshooting
134
-
135
- **Error: "OpenAI API key not found"**
136
- - Add `OPENAI_API_KEY=sk-...` to your `.env` file
137
-
138
- **Error: "Rate limit exceeded"**
139
- - Wait a few minutes or reduce test dataset size
140
-
141
- **Error: "Import error"**
142
- - Run: `pip install ragas datasets openai langchain-openai`
143
-
144
- **Metrics return NaN or 0**
145
- - Check if answers/contexts are empty
146
- - Ensure ground_truth is provided for context_recall
147
- - Try with a smaller dataset first (2-3 questions)
148
-
149
- ## 📚 Resources
150
-
151
- - [Ragas Documentation](https://docs.ragas.io/)
152
- - [Ragas GitHub](https://github.com/explodinggradients/ragas)
153
- - [RAG Evaluation Best Practices](https://docs.ragas.io/en/latest/concepts/metrics/index.html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
EVALUATION_SETUP.md DELETED
@@ -1,173 +0,0 @@
1
- # RAG Evaluation Setup - Summary
2
-
3
- ## 📁 Files Created
4
-
5
- 1. **evaluate_rag.py** - Full evaluation script with detailed explanations
6
- 2. **quick_eval.py** - Simple evaluation script (faster to run)
7
- 3. **test_dataset.json** - Test questions and ground truth answers
8
- 4. **EVALUATION_README.md** - Complete documentation
9
-
10
- ## 🎯 Quick Start (3 Steps)
11
-
12
- ### Step 1: Add OpenAI API Key
13
- Add to your `.env` file:
14
- ```
15
- OPENAI_API_KEY=sk-your-key-here
16
- ```
17
-
18
- ### Step 2: Edit Test Questions (Optional)
19
- Edit `test_dataset.json` to add/modify test questions
20
-
21
- ### Step 3: Run Evaluation
22
- ```bash
23
- # Option A: Quick evaluation
24
- python quick_eval.py
25
-
26
- # Option B: Full evaluation with explanations
27
- python evaluate_rag.py
28
- ```
29
-
30
- ## 📊 What Gets Evaluated
31
-
32
- ### The 6 Ragas Metrics:
33
-
34
- 1. **faithfulness** (0-1, higher better)
35
- - Is answer grounded in context?
36
- - Checks if model added external information
37
-
38
- 2. **answer_relevancy** (0-1, higher better)
39
- - Does answer match the question?
40
- - Checks if answer is on-topic
41
-
42
- 3. **context_precision** (0-1, higher better)
43
- - How much retrieved context was useful?
44
- - Measures retrieval signal-to-noise ratio
45
-
46
- 4. **context_recall** (0-1, higher better)
47
- - Did we retrieve all needed info?
48
- - Requires ground_truth to measure
49
-
50
- 5. **context_relevancy** (0-1, higher better)
51
- - Overall context relevance to question
52
- - Measures retrieval quality
53
-
54
- 6. **response_relevancy** (included in answer_relevancy)
55
- - Similar to answer_relevancy
56
-
57
- ## 📈 Expected Scores
58
-
59
- ### Good Performance:
60
- - faithfulness: > 0.7
61
- - answer_relevancy: > 0.8
62
- - context_precision: > 0.6
63
- - context_recall: > 0.7
64
- - context_relevancy: > 0.7
65
-
66
- ### If Scores Are Low:
67
-
68
- **Low Faithfulness?**
69
- → Tighten prompt to avoid hallucinations
70
-
71
- **Low Answer Relevancy?**
72
- → Check retrieval quality
73
-
74
- **Low Context Precision?**
75
- → Reduce k in retrievers or increase reranker top_n
76
-
77
- **Low Context Recall?**
78
- → Increase k in retrievers, check if info exists
79
-
80
- **Low Context Relevancy?**
81
- → Adjust hybrid search beta weights
82
-
83
- ## 🔧 Tuning Your System
84
-
85
- Based on evaluation results, you can tune:
86
-
87
- 1. **In app_final.py:**
88
- - Line 172: `base_retriever k` (semantic search)
89
- - Line 208: `bm25_retriever k` (keyword search)
90
- - Line 260: `metadata_retriever k` (metadata filter)
91
- - Line 335: `beta_semantic, beta_keyword, beta_metadata` (hybrid weights)
92
- - Line 427: `compressor top_n` (reranker final count)
93
- - Line 440: `temperature` (LLM creativity)
94
-
95
- 2. **Test Changes:**
96
- ```bash
97
- # After tuning parameters
98
- python quick_eval.py
99
- # Compare new scores with previous results
100
- ```
101
-
102
- ## 💡 Tips
103
-
104
- 1. **Start Small**: Test with 3-5 questions first
105
- 2. **Add Ground Truth**: Improves context_recall accuracy
106
- 3. **Compare Before/After**: Save results before making changes
107
- 4. **Use GPT-4**: Set `OPENAI_MODEL=gpt-4` for better Arabic evaluation
108
-
109
- ## 📝 Example Workflow
110
-
111
- ```bash
112
- # 1. Baseline evaluation
113
- python quick_eval.py
114
- # Save as: evaluation_baseline.json
115
-
116
- # 2. Tune parameter (e.g., increase reranker top_n from 5 to 7)
117
- # Edit app_final.py line 427: top_n=7
118
-
119
- # 3. Re-evaluate
120
- python quick_eval.py
121
- # Save as: evaluation_tuned.json
122
-
123
- # 4. Compare scores
124
- # If improved → keep change
125
- # If worse → revert and try different parameter
126
- ```
127
-
128
- ## 🐛 Common Issues
129
-
130
- **"OpenAI API key not found"**
131
- ```bash
132
- # Check .env file has:
133
- OPENAI_API_KEY=sk-...
134
- ```
135
-
136
- **"Rate limit exceeded"**
137
- ```bash
138
- # Wait 1 minute and retry, or reduce test dataset size
139
- ```
140
-
141
- **"Import ragas failed"**
142
- ```bash
143
- pip install ragas datasets openai langchain-openai
144
- ```
145
-
146
- **Scores all 0 or NaN**
147
- ```bash
148
- # Check:
149
- # 1. Answers are being generated (not empty)
150
- # 2. Contexts are being retrieved
151
- # 3. Ground truth is provided for context_recall
152
- ```
153
-
154
- ## 📚 Output Files
155
-
156
- After running evaluation:
157
- - `evaluation_results.json` - Summary metrics
158
- - `evaluation_detailed.json` - Full Q&A with contexts
159
- - Console output - Formatted metrics display
160
-
161
- ## 🎓 Next Steps
162
-
163
- 1. Run baseline evaluation
164
- 2. Identify lowest-scoring metric
165
- 3. Tune relevant parameters
166
- 4. Re-evaluate and compare
167
- 5. Repeat until satisfied
168
-
169
- ---
170
-
171
- **Need Help?**
172
- - Ragas Docs: https://docs.ragas.io/
173
- - Example Notebooks: https://github.com/explodinggradients/ragas/tree/main/docs/examples
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/config.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from dataclasses import dataclass
6
+
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Settings:
14
+ base_dir: str = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
15
+ data_dir: str = os.path.join(base_dir, "data")
16
+ chroma_dir: str = os.path.join(base_dir, "chroma_db")
17
+
18
+ groq_api_key: str = os.getenv("GROQ_API_KEY", "")
19
+ groq_model_name: str = os.getenv("GROQ_MODEL_NAME", "llama-3.3-70b-versatile")
20
+
21
+ reranker_model_path: str = os.getenv("RERANKER_MODEL_PATH", "")
22
+
23
+ semantic_k: int = int(os.getenv("SEMANTIC_K", "10"))
24
+ bm25_k: int = int(os.getenv("BM25_K", "10"))
25
+ meta_k: int = int(os.getenv("META_K", "10"))
26
+ hybrid_top_k: int = int(os.getenv("HYBRID_TOP_K", "12"))
27
+ rrf_k: int = int(os.getenv("RRF_K", "60"))
28
+
29
+ temperature: float = float(os.getenv("LLM_TEMPERATURE", "0.2"))
30
+ max_tokens: int = int(os.getenv("LLM_MAX_TOKENS", "2048"))
31
+ top_p: float = float(os.getenv("LLM_TOP_P", "0.85"))
32
+
33
+
34
+ settings = Settings()
app/deps.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # ============================================
3
+ # file: app/deps.py
4
+ # ============================================
5
+ from __future__ import annotations
6
+
7
+ import threading
8
+ from typing import Any, Optional
9
+
10
+ from .config import settings
11
+ from .rag_pipeline import build_qa_chain
12
+
13
+ _lock = threading.RLock()
14
+ _chain: Optional[Any] = None
15
+
16
+
17
+ def get_chain():
18
+ global _chain
19
+ with _lock:
20
+ if _chain is None:
21
+ _chain = build_qa_chain(settings)
22
+ return _chain
23
+
24
+
25
+ def reload_chain():
26
+ global _chain
27
+ with _lock:
28
+ _chain = build_qa_chain(settings)
29
+ return _chain
30
+
31
+
app/main.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # file: app/main.py
3
+ # ============================================
4
+ from __future__ import annotations
5
+
6
+ from typing import List
7
+
8
+ import anyio
9
+ from fastapi import FastAPI, HTTPException
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+
12
+ from .deps import get_chain, reload_chain
13
+ from .schemas import AskRequest, AskResponse, SourceDoc
14
+ from .utils import convert_to_eastern_arabic
15
+
16
+ app = FastAPI(title="Legal RAG API", version="1.0.0")
17
+
18
+ # Optional: allow frontend calls
19
+ app.add_middleware(
20
+ CORSMiddleware,
21
+ allow_origins=["*"], # tighten later
22
+ allow_credentials=True,
23
+ allow_methods=["*"],
24
+ allow_headers=["*"],
25
+ )
26
+
27
+
28
+ @app.on_event("startup")
29
+ def _startup():
30
+ # preload once
31
+ get_chain()
32
+
33
+
34
+ @app.get("/health")
35
+ def health():
36
+ return {"status": "ok"}
37
+
38
+
39
+ @app.post("/reload")
40
+ def reload():
41
+ reload_chain()
42
+ return {"status": "reloaded"}
43
+
44
+
45
+ def _dedupe_sources(docs) -> List[SourceDoc]:
46
+ if not docs:
47
+ return []
48
+ seen = set()
49
+ out: List[SourceDoc] = []
50
+ for doc in docs:
51
+ article_num = str(doc.metadata.get("article_number", "")).strip()
52
+ if article_num and article_num in seen:
53
+ continue
54
+ if article_num:
55
+ seen.add(article_num)
56
+ out.append(
57
+ SourceDoc(
58
+ article_id=str(doc.metadata.get("article_id", "")) or None,
59
+ article_number=article_num or None,
60
+ law_name=str(doc.metadata.get("law_name", "")) or None,
61
+ legal_nature=str(doc.metadata.get("legal_nature", "")) or None,
62
+ keywords=str(doc.metadata.get("keywords", "")) or None,
63
+ part=str(doc.metadata.get("part", "")) or None,
64
+ chapter=str(doc.metadata.get("chapter", "")) or None,
65
+ page_content=str(doc.page_content or ""),
66
+ )
67
+ )
68
+ return out
69
+
70
+
71
+ @app.post("/ask", response_model=AskResponse)
72
+ async def ask(payload: AskRequest):
73
+ chain = get_chain()
74
+
75
+ try:
76
+ # LangChain invoke is sync; run in worker thread
77
+ result = await anyio.to_thread.run_sync(chain.invoke, payload.query)
78
+ except Exception as e:
79
+ raise HTTPException(status_code=500, detail=str(e)) from e
80
+
81
+ answer = result.get("answer", "")
82
+ sources_docs = result.get("context", []) if payload.include_sources else []
83
+ sources = _dedupe_sources(sources_docs)
84
+
85
+ if payload.eastern_arabic_numerals:
86
+ answer = convert_to_eastern_arabic(answer)
87
+ if payload.include_sources:
88
+ for s in sources:
89
+ s.page_content = convert_to_eastern_arabic(s.page_content)
90
+ if s.article_number:
91
+ s.article_number = convert_to_eastern_arabic(s.article_number)
92
+
93
+ return AskResponse(answer=answer, sources=sources, raw=result)
94
+
95
+
96
+
app/rag_pipeline.py ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # file: app/rag_pipeline.py
3
+ # ============================================
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import logging
8
+ import os
9
+ import re
10
+ import warnings
11
+ from collections import defaultdict
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ from typing import Dict, List, Set
14
+
15
+ import numpy as np
16
+ from langchain_chroma import Chroma
17
+ from langchain_core.callbacks import CallbackManagerForRetrieverRun
18
+ from langchain_core.documents import Document
19
+ from langchain_core.output_parsers import StrOutputParser
20
+ from langchain_core.prompts import ChatPromptTemplate
21
+ from langchain_core.retrievers import BaseRetriever
22
+ from langchain_core.runnables import RunnableParallel, RunnablePassthrough
23
+ from langchain_groq import ChatGroq
24
+ from langchain_huggingface import HuggingFaceEmbeddings
25
+ from langchain_community.cross_encoders import HuggingFaceCrossEncoder
26
+ from langchain_classic.retrievers import ContextualCompressionRetriever
27
+ from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
28
+ from rank_bm25 import BM25Okapi
29
+
30
+ from .config import Settings
31
+ from .utils import arabic_tokenize
32
+
33
+ os.environ["TRANSFORMERS_NO_PROGRESS_BAR"] = "1"
34
+ warnings.filterwarnings("ignore")
35
+
36
+ logger = logging.getLogger(__name__)
37
+ logging.basicConfig(level=logging.INFO)
38
+
39
+
40
+ def _load_json_folder(folder_path: str) -> List[dict]:
41
+ all_items: List[dict] = []
42
+ for filename in os.listdir(folder_path):
43
+ if not filename.lower().endswith(".json"):
44
+ continue
45
+ file_path = os.path.join(folder_path, filename)
46
+ with open(file_path, "r", encoding="utf-8") as f:
47
+ obj = json.load(f)
48
+
49
+ wrapper_law_name = ""
50
+
51
+ if isinstance(obj, list):
52
+ articles: List[dict] = []
53
+ for entry in obj:
54
+ if isinstance(entry, dict) and "data" in entry and isinstance(entry["data"], list):
55
+ wrapper_law_name = entry.get("law_name", "")
56
+ for art in entry["data"]:
57
+ art.setdefault("_law_name", wrapper_law_name)
58
+ articles.extend(entry["data"])
59
+ elif isinstance(entry, dict) and "articles" in entry and isinstance(entry["articles"], list):
60
+ wrapper_law_name = entry.get("law_name", "")
61
+ for art in entry["articles"]:
62
+ art.setdefault("_law_name", wrapper_law_name)
63
+ articles.extend(entry["articles"])
64
+ elif isinstance(entry, dict):
65
+ if not entry.get("_law_name"):
66
+ aid = entry.get("article_id", "")
67
+ entry["_law_name"] = "الدستور المصري" if "CONST" in str(aid).upper() else ""
68
+ articles.append(entry)
69
+ all_items.extend(articles)
70
+ elif isinstance(obj, dict):
71
+ wrapper_law_name = obj.get("law_name", "")
72
+ if "data" in obj and isinstance(obj["data"], list):
73
+ for art in obj["data"]:
74
+ art.setdefault("_law_name", wrapper_law_name)
75
+ all_items.extend(obj["data"])
76
+ elif "articles" in obj and isinstance(obj["articles"], list):
77
+ for art in obj["articles"]:
78
+ art.setdefault("_law_name", wrapper_law_name)
79
+ all_items.extend(obj["articles"])
80
+ else:
81
+ obj.setdefault("_law_name", wrapper_law_name)
82
+ all_items.append(obj)
83
+ else:
84
+ logger.warning("Unsupported JSON format in: %s", file_path)
85
+
86
+ return all_items
87
+
88
+
89
+ def build_qa_chain(settings: Settings):
90
+ """
91
+ Builds and returns:
92
+ qa_chain: Runnable that returns {"context": [Document...], "input": str, "answer": str}
93
+ """
94
+ if not os.path.exists(settings.data_dir):
95
+ raise FileNotFoundError(f"Data folder not found: {settings.data_dir}")
96
+
97
+ data = _load_json_folder(settings.data_dir)
98
+
99
+ # de-dup
100
+ unique: Dict[str, dict] = {}
101
+ for item in data:
102
+ key = str(item.get("article_id") or item.get("article_number") or hash(json.dumps(item, ensure_ascii=False)))
103
+ unique[key] = item
104
+ data = list(unique.values())
105
+
106
+ docs: List[Document] = []
107
+ for item in data:
108
+ article_number = item.get("article_number")
109
+ original_text = item.get("original_text")
110
+ simplified_summary = item.get("simplified_summary")
111
+ if not article_number or not original_text or not simplified_summary:
112
+ continue
113
+
114
+ law_name = item.get("law_name") or item.get("_law_name", "")
115
+ part_bab = item.get("part (Bab)", "")
116
+ chapter_fasl = item.get("chapter (Fasl)", "")
117
+ section = item.get("section", "")
118
+
119
+ page_content = f"""القانون: {law_name}
120
+ رقم المادة: {article_number}
121
+ الباب: {part_bab}
122
+ الفصل: {chapter_fasl}
123
+ القسم: {section}
124
+ النص الأصلي: {original_text}
125
+ الشرح المبسط: {simplified_summary}"""
126
+
127
+ metadata = {
128
+ "article_id": item.get("article_id") or str(article_number),
129
+ "article_number": str(article_number),
130
+ "law_name": law_name,
131
+ "legal_nature": item.get("legal_nature", ""),
132
+ "keywords": ", ".join(item.get("keywords", []) or []),
133
+ "part": part_bab,
134
+ "chapter": chapter_fasl,
135
+ }
136
+ docs.append(Document(page_content=page_content, metadata=metadata))
137
+
138
+ if not docs:
139
+ raise RuntimeError("No valid documents found in data folder (missing required fields).")
140
+
141
+ embeddings = HuggingFaceEmbeddings(model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1")
142
+
143
+ # vector store reuse
144
+ db_exists = os.path.exists(settings.chroma_dir) and os.listdir(settings.chroma_dir)
145
+ if db_exists:
146
+ vectorstore = Chroma(
147
+ persist_directory=settings.chroma_dir,
148
+ embedding_function=embeddings,
149
+ )
150
+ stored_count = vectorstore._collection.count()
151
+ if stored_count == 0 or abs(stored_count - len(docs)) > 5:
152
+ import shutil
153
+
154
+ shutil.rmtree(settings.chroma_dir, ignore_errors=True)
155
+ db_exists = False
156
+
157
+ if not db_exists:
158
+ vectorstore = Chroma.from_documents(
159
+ docs,
160
+ embeddings,
161
+ persist_directory=settings.chroma_dir,
162
+ )
163
+
164
+ base_retriever = vectorstore.as_retriever(search_kwargs={"k": settings.semantic_k})
165
+
166
+ # -----------------------------
167
+ # BM25 retriever
168
+ # -----------------------------
169
+ class BM25Retriever(BaseRetriever):
170
+ corpus_docs: List[Document]
171
+ bm25: BM25Okapi = None
172
+ tokenized_corpus: list = None
173
+ k: int = 10
174
+
175
+ class Config:
176
+ arbitrary_types_allowed = True
177
+
178
+ def __init__(self, **data):
179
+ super().__init__(**data)
180
+ self.tokenized_corpus = [arabic_tokenize(doc.page_content) for doc in self.corpus_docs]
181
+ self.bm25 = BM25Okapi(self.tokenized_corpus)
182
+
183
+ def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
184
+ tokenized_query = arabic_tokenize(query)
185
+ if not tokenized_query:
186
+ return []
187
+ scores = self.bm25.get_scores(tokenized_query)
188
+ top_indices = np.argsort(scores)[::-1][: self.k]
189
+ return [self.corpus_docs[i] for i in top_indices if scores[i] > 0]
190
+
191
+ async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
192
+ return self._get_relevant_documents(query, run_manager=run_manager)
193
+
194
+ bm25_retriever = BM25Retriever(corpus_docs=docs, k=settings.bm25_k)
195
+
196
+ # -----------------------------
197
+ # Metadata filter retriever
198
+ # -----------------------------
199
+ class MetadataFilterRetriever(BaseRetriever):
200
+ corpus_docs: List[Document]
201
+ keyword_index: Dict[str, Set[int]] = None
202
+ law_name_index: Dict[str, Set[int]] = None
203
+ k: int = 10
204
+
205
+ class Config:
206
+ arbitrary_types_allowed = True
207
+
208
+ def __init__(self, **data):
209
+ super().__init__(**data)
210
+ self.keyword_index = defaultdict(set)
211
+ self.law_name_index = defaultdict(set)
212
+ for idx, doc in enumerate(self.corpus_docs):
213
+ kw_text = (
214
+ str(doc.metadata.get("keywords", ""))
215
+ + " "
216
+ + str(doc.metadata.get("legal_nature", ""))
217
+ + " "
218
+ + str(doc.metadata.get("part", ""))
219
+ + " "
220
+ + str(doc.metadata.get("chapter", ""))
221
+ )
222
+ for token in arabic_tokenize(kw_text):
223
+ self.keyword_index[token].add(idx)
224
+
225
+ for token in arabic_tokenize(str(doc.metadata.get("law_name", ""))):
226
+ self.law_name_index[token].add(idx)
227
+
228
+ def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
229
+ query_tokens = arabic_tokenize(query)
230
+ if not query_tokens:
231
+ return []
232
+
233
+ scores = defaultdict(float)
234
+ for token in query_tokens:
235
+ for idx in self.keyword_index.get(token, set()):
236
+ scores[idx] += 3.0
237
+ for idx in self.law_name_index.get(token, set()):
238
+ scores[idx] += 4.0
239
+
240
+ if not scores:
241
+ return []
242
+
243
+ top = sorted(scores.items(), key=lambda x: x[1], reverse=True)[: self.k]
244
+ return [self.corpus_docs[idx] for idx, _ in top]
245
+
246
+ async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
247
+ return self._get_relevant_documents(query, run_manager=run_manager)
248
+
249
+ metadata_retriever = MetadataFilterRetriever(corpus_docs=docs, k=settings.meta_k)
250
+
251
+ # -----------------------------
252
+ # Hybrid RRF retriever (parallel)
253
+ # -----------------------------
254
+ class HybridRRFRetriever(BaseRetriever):
255
+ semantic_retriever: BaseRetriever
256
+ bm25_retriever: BM25Retriever
257
+ metadata_retriever: MetadataFilterRetriever
258
+ beta_semantic: float = 0.5
259
+ beta_keyword: float = 0.3
260
+ beta_metadata: float = 0.2
261
+ k: int = 60
262
+ top_k: int = 12
263
+
264
+ class Config:
265
+ arbitrary_types_allowed = True
266
+
267
+ def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
268
+ with ThreadPoolExecutor(max_workers=3) as pool:
269
+ fut_sem = pool.submit(self.semantic_retriever.invoke, query)
270
+ fut_bm = pool.submit(self.bm25_retriever.invoke, query)
271
+ fut_meta = pool.submit(self.metadata_retriever.invoke, query)
272
+
273
+ semantic_docs = fut_sem.result()
274
+ bm25_docs = fut_bm.result()
275
+ metadata_docs = fut_meta.result()
276
+
277
+ rrf_scores: Dict[str, float] = {}
278
+ all_docs: Dict[str, Document] = {}
279
+
280
+ for weight, doc_list in [
281
+ (self.beta_semantic, semantic_docs),
282
+ (self.beta_keyword, bm25_docs),
283
+ (self.beta_metadata, metadata_docs),
284
+ ]:
285
+ for rank, doc in enumerate(doc_list, start=1):
286
+ doc_id = doc.metadata.get("article_id") or doc.metadata.get("article_number") or str(hash(doc.page_content))
287
+ rrf_scores[doc_id] = rrf_scores.get(doc_id, 0.0) + weight / (self.k + rank)
288
+ all_docs.setdefault(doc_id, doc)
289
+
290
+ sorted_ids = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
291
+ return [all_docs[did] for did, _ in sorted_ids[: self.top_k] if did in all_docs]
292
+
293
+ async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
294
+ return self._get_relevant_documents(query, run_manager=run_manager)
295
+
296
+ hybrid_retriever = HybridRRFRetriever(
297
+ semantic_retriever=base_retriever,
298
+ bm25_retriever=bm25_retriever,
299
+ metadata_retriever=metadata_retriever,
300
+ beta_semantic=0.5,
301
+ beta_keyword=0.3,
302
+ beta_metadata=0.2,
303
+ k=settings.rrf_k,
304
+ top_k=settings.hybrid_top_k,
305
+ )
306
+
307
+ # -----------------------------
308
+ # Reranker (CrossEncoder)
309
+ # -----------------------------
310
+ if not settings.reranker_model_path:
311
+ raise RuntimeError("RERANKER_MODEL_PATH is not set in .env (must point to your local reranker folder).")
312
+ if not os.path.exists(settings.reranker_model_path):
313
+ raise FileNotFoundError(f"Reranker path not found: {settings.reranker_model_path}")
314
+
315
+ cross_encoder = HuggingFaceCrossEncoder(model_name=settings.reranker_model_path)
316
+ compressor = CrossEncoderReranker(model=cross_encoder, top_n=5)
317
+ compression_retriever = ContextualCompressionRetriever(
318
+ base_compressor=compressor,
319
+ base_retriever=hybrid_retriever,
320
+ )
321
+
322
+ # -----------------------------
323
+ # LLM + prompt
324
+ # -----------------------------
325
+ if not settings.groq_api_key:
326
+ raise RuntimeError("GROQ_API_KEY is missing in .env")
327
+
328
+ llm = ChatGroq(
329
+ groq_api_key=settings.groq_api_key,
330
+ model_name=settings.groq_model_name,
331
+ temperature=settings.temperature,
332
+ max_tokens=settings.max_tokens,
333
+ model_kwargs={"top_p": settings.top_p},
334
+ )
335
+
336
+ system_instructions = """
337
+ <role>
338
+ أنت "المساعد القانوني الذكي"، مستشار قانوني متخصص في القوانين المصرية التالية:
339
+ - الدستور المصري
340
+ - القانون المدني المصري
341
+ - قانون العمل المصري
342
+ - قانون الأحوال الشخصية المصري
343
+ - قانون مكافحة جرائم تقنية المعلومات
344
+ - قانون الإجراءات الجنائية المصري
345
+
346
+ مهمتك الأساسية: الإجابة بدقة استناداً إلى "السياق التشريعي" المرفق أدناه.
347
+ عند وجود نص قانوني في السياق، هو مصدرك الأول والأهم.
348
+ </role>
349
+
350
+ <decision_logic>
351
+ حلّل سؤال المستخدم ثم اتبع أول حالة ينطبق شرطها:
352
+
353
+ ━━━ الحالة ١ — الإجابة موجودة في السياق (الأولوية القصوى) ━━━
354
+ الشرط: توجد مادة أو أكثر في السياق تتناول موضوع السؤال بشكل مباشر أو وثيق الصلة.
355
+ الفعل:
356
+ • أجب من السياق مباشرةً دون مقدمات.
357
+ • وثّق كل معلومة بذكر اسم القانون ورقم المادة (مثال: «وفقاً للمادة (٥٢) من قانون العمل...»).
358
+ • استخرج ما يجيب السؤال تحديداً — لا تنسخ المادة كاملة.
359
+ • لا تضف معلومات من خارج السياق في هذه الحالة.
360
+
361
+ ━━━ الحالة ٢ — السياق يغطي الموضوع جزئياً ━━━
362
+ الشرط: توجد مواد ذات صلة لكنها لا تجيب السؤال بالكامل.
363
+ الفعل:
364
+ • اذكر أولاً ما تنص عليه المواد المتاحة (مع التوثيق).
365
+ • ثم أضف توضيحاً عملياً مختصراً يساعد المستخدم، مع التنبيه بعبارة:
366
+ «ملاحظة عملية:» أو «من الناحية التطبيقية:» قبل أي إضافة.
367
+ • لا تخترع أرقام مواد أو تنسب نصوصاً لقوانين لم ترد في السياق.
368
+
369
+ ━━━ الحالة ٣ — السياق لا يحتوي الإجابة + السؤال إجرائي/عملي ━━━
370
+ الشرط: لا توجد مادة في السياق تتعلق بالموضوع، لكن السؤال عن إجراءات عملية (بلاغ، محضر، حادث، طلاق، تعامل مع الشرطة...).
371
+ الفعل:
372
+ • ابدأ بعبارة: «بناءً على الإجراءات القانونية المتعارف عليها في مصر (وليس استناداً لنص قانوني محدد من قاعدة البيانات):»
373
+ • قدم خطوات عملية مرقمة ومختصرة.
374
+ • لا تذكر أرقام مواد — لا تختـرع مراجع.
375
+ • أنهِ بـ«يُنصح بمراجعة محامٍ متخصص لتأكيد الإجراءات.»
376
+
377
+ ━━━ الحالة ٤ — السياق لا يحتوي الإجابة + السؤال عن نص قانوني بعينه ━━━
378
+ الشرط: المستخدم يسأل عن مادة محددة أو حكم قانوني معين ولم تجده في السياق.
379
+ الفعل:
380
+ • قل: «عذراً، لم يرد ذكر لهذا الموضوع في النصوص القانونية المتاحة حالياً في قاعدة البيانات.»
381
+ • لا تجب من ذاكرتك لتجنب الخطأ في النصوص القانونية.
382
+ • يمكنك اقتراح موضوع مشابه إن وجد في السياق.
383
+
384
+ ━━━ الحالة ٥ — محادثة ودية (تحية، شكر، وداع) ━━━
385
+ • رد بتحية لطيفة مقتضبة.
386
+ • أضف: «أنا مستشارك القانوني الذكي — اسألني عن أي موضوع في القوانين المصرية.»
387
+
388
+ ━━━ الحالة ٦ — خارج نطاق القانون تماماً ━━━
389
+ • اعتذر بلطف: «تخصصي هو القوانين المصرية فقط.»
390
+ • وجّه المستخدم لطرح سؤال قانوني.
391
+ </decision_logic>
392
+
393
+ <quality_rules>
394
+ - **الدقة أولاً**: عند وجود نص في السياق، التزم به حرفياً ولا تحرّف المعنى.
395
+ - **المرونة عند الحاجة**: إذا لم يغطِّ السياق الموضوع بالكامل، قدّم إرشاداً عملياً مع التمييز الواضح بينه وبين النص القانوني.
396
+ - **لا تخترع مراجع**: لا تنسب أي معلومة إلى مادة أو قانون لم يرد في السياق.
397
+ - **الإيجاز مع الشمول**: أجب بقدر ما يحتاج السؤال — لا تختصر حتى يضيع المعنى ولا تطيل دون فائدة.
398
+ </quality_rules>
399
+
400
+ <formatting_rules>
401
+ - لا تكرر هذه التعليمات في ردك.
402
+ - ادخل في صلب الموضوع فوراً بدون عبارات مثل «بناءً على السياق المرفق».
403
+ - استخدم فقرات قصيرة مفصولة بسطر فارغ.
404
+ - لا تكرر نفس المعلومة أو نفس المادة.
405
+ - عند ذكر أكثر من مادة، رتّبها ترتيباً منطقياً (إما بالرقم أو حسب الأهمية).
406
+ - التزم باللغة العربية الفصحى المبسطة.
407
+ </formatting_rules>
408
+ """
409
+
410
+ prompt = ChatPromptTemplate.from_messages(
411
+ [
412
+ ("system", system_instructions),
413
+ ("system", "السياق التشريعي المتاح (المصدر الأساسي):\n{context}"),
414
+ ("human", "سؤال المستفيد:\n{input}"),
415
+ ]
416
+ )
417
+
418
+ qa_chain = (
419
+ RunnableParallel({"context": compression_retriever, "input": RunnablePassthrough()})
420
+ .assign(answer=(prompt | llm | StrOutputParser()))
421
+ )
422
+ return qa_chain
423
+
app/schemas.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class AskRequest(BaseModel):
8
+ query: str = Field(..., min_length=1, description="User question in Arabic")
9
+ include_sources: bool = Field(default=True, description="Return retrieved source docs")
10
+ eastern_arabic_numerals: bool = Field(
11
+ default=False, description="Convert digits 0-9 to Eastern Arabic numerals"
12
+ )
13
+
14
+
15
+ class SourceDoc(BaseModel):
16
+ article_id: Optional[str] = None
17
+ article_number: Optional[str] = None
18
+ law_name: Optional[str] = None
19
+ legal_nature: Optional[str] = None
20
+ keywords: Optional[str] = None
21
+ part: Optional[str] = None
22
+ chapter: Optional[str] = None
23
+ page_content: str
24
+
25
+
26
+ class AskResponse(BaseModel):
27
+ answer: str
28
+ sources: List[SourceDoc] = Field(default_factory=list)
29
+ raw: Dict[str, Any] = Field(default_factory=dict)
app/utils.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # file: app/utils.py
3
+ # ============================================
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ from typing import List, Set
8
+
9
+
10
+ _ARABIC_STOPWORDS: Set[str] = {
11
+ "في", "من", "على", "إلى", "عن", "أن", "هذا", "هذه", "التي", "الذي",
12
+ "ما", "لا", "أو", "و", "كل", "ذلك", "بين", "كان", "قد", "هو", "هي",
13
+ "لم", "بل", "ثم", "إذا", "حتى", "لكن", "منه", "فيه", "عند", "له",
14
+ "بها", "لها", "منها", "فيها", "التى", "الذى", "ولا", "وفى", "كما",
15
+ "تلك", "هنا", "أي", "دون", "ليس", "إلا", "أما", "مع", "عليه",
16
+ }
17
+
18
+
19
+ def arabic_tokenize(text: str) -> List[str]:
20
+ """
21
+ Arabic tokenization used by BM25 + metadata index.
22
+ Removes diacritics, keeps Arabic letters/spaces, removes stopwords.
23
+ """
24
+ text = re.sub(r"[\u064B-\u065F\u0670]", "", text) # strip tashkeel
25
+ text = re.sub(r"[^\u0600-\u06FF\s]", " ", text) # keep Arabic only
26
+ tokens = text.split()
27
+ return [t for t in tokens if t not in _ARABIC_STOPWORDS and len(t) > 1]
28
+
29
+
30
+ def convert_to_eastern_arabic(text: str) -> str:
31
+ """Converts 0123456789 to ٠١٢٣٤٥٦٧٨٩"""
32
+ western = "0123456789"
33
+ eastern = "٠١٢٣٤٥٦٧٨٩"
34
+ return text.translate(str.maketrans(western, eastern))
35
+
app_final.py DELETED
@@ -1,625 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import os
3
- import sys
4
- import json
5
- from dotenv import load_dotenv
6
- import streamlit as st
7
- import logging
8
- import warnings
9
-
10
- # Suppress progress bars from transformers/tqdm
11
- os.environ['TRANSFORMERS_NO_PROGRESS_BAR'] = '1'
12
- warnings.filterwarnings('ignore')
13
-
14
- # 1. Loaders & Splitters
15
- from langchain_core.documents import Document
16
- from langchain_text_splitters import RecursiveCharacterTextSplitter
17
- from langchain_core.retrievers import BaseRetriever
18
- from langchain_core.callbacks import CallbackManagerForRetrieverRun
19
- from typing import List
20
- from rank_bm25 import BM25Okapi
21
- import numpy as np
22
-
23
- # 2. Vector Store & Embeddings
24
- from langchain_chroma import Chroma
25
- from langchain_huggingface import HuggingFaceEmbeddings
26
-
27
- # 3. Reranker Imports
28
- from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
29
- from langchain_classic.retrievers import ContextualCompressionRetriever
30
- from langchain_community.cross_encoders import HuggingFaceCrossEncoder
31
-
32
- # 4. LLM
33
- from langchain_groq import ChatGroq
34
- from langchain_core.prompts import ChatPromptTemplate
35
- from langchain_core.output_parsers import StrOutputParser
36
- from langchain_core.runnables import RunnablePassthrough, RunnableParallel
37
-
38
- # Configure logging
39
- logging.basicConfig(level=logging.INFO)
40
- logger = logging.getLogger(__name__)
41
-
42
- load_dotenv()
43
-
44
- # ==========================================
45
- # 🎨 UI SETUP (CSS FOR ARABIC & RTL)
46
- # ==========================================
47
- st.set_page_config(page_title="المساعد القانوني", page_icon="⚖️")
48
-
49
- # This CSS block fixes the "001" number issue and right alignment
50
- st.markdown("""
51
- <style>
52
- /* Force the main app container to be Right-to-Left */
53
- .stApp {
54
- direction: rtl;
55
- text-align: right;
56
- }
57
-
58
- /* Fix input fields to type from right */
59
- .stTextInput input {
60
- direction: rtl;
61
- text-align: right;
62
- }
63
-
64
- /* Fix chat messages alignment */
65
- .stChatMessage {
66
- direction: rtl;
67
- text-align: right;
68
- }
69
-
70
- /* Ensure proper paragraph spacing */
71
- .stMarkdown p {
72
- margin: 0.5em 0 !important;
73
- line-height: 1.6;
74
- word-spacing: 0.1em;
75
- }
76
-
77
- /* Ensure numbers display correctly in RTL */
78
- p, div, span, label {
79
- unicode-bidi: embed;
80
- direction: inherit;
81
- white-space: normal;
82
- word-wrap: break-word;
83
- }
84
-
85
- /* Force all content to respect RTL */
86
- * {
87
- direction: rtl !important;
88
- }
89
-
90
- /* Preserve line breaks and spacing */
91
- .stMarkdown pre {
92
- direction: rtl;
93
- text-align: right;
94
- white-space: pre-wrap;
95
- word-wrap: break-word;
96
- }
97
-
98
- /* Hide the "Deploy" button and standard menu for cleaner look */
99
- #MainMenu {visibility: hidden;}
100
- footer {visibility: hidden;}
101
-
102
- </style>
103
- """, unsafe_allow_html=True)
104
-
105
- # Put this at the top of your code
106
- def convert_to_eastern_arabic(text):
107
- """Converts 0123456789 to ٠١٢٣٤٥٦٧٨٩"""
108
- if not isinstance(text, str):
109
- return text
110
- western_numerals = '0123456789'
111
- eastern_numerals = '٠١٢٣٤٥٦٧٨٩'
112
- translation_table = str.maketrans(western_numerals, eastern_numerals)
113
- return text.translate(translation_table)
114
-
115
- st.title("⚖️ المساعد القانوني الذكي (دستور مصر)")
116
-
117
- # ==========================================
118
- # 🚀 CACHED RESOURCE LOADING (THE FIX)
119
- # ==========================================
120
- # This decorator tells Streamlit: "Run this ONCE and save the result."
121
- @st.cache_resource
122
- def initialize_rag_pipeline():
123
- print("🔄 Initializing system...")
124
- print("📥 Loading data...")
125
-
126
- # 1. Load JSON
127
- json_path = "Egyptian_Constitution_legalnature_only.json"
128
- if not os.path.exists(json_path):
129
- raise FileNotFoundError(f"File not found: {json_path}")
130
-
131
- with open(json_path, "r", encoding="utf-8") as f:
132
- data = json.load(f)
133
-
134
- # Create a mapping of article numbers for cross-reference lookup
135
- article_map = {str(item['article_number']): item for item in data}
136
-
137
- docs = []
138
- for item in data:
139
- # Build cross-reference section
140
- cross_ref_text = ""
141
- if item.get('cross_references') and len(item['cross_references']) > 0:
142
- cross_ref_text = "\nالمواد ذات الصلة (المراجع المتقاطعة): " + ", ".join(
143
- [f"المادة {ref}" for ref in item['cross_references']]
144
- )
145
-
146
- # Construct content
147
- page_content = f"""
148
- رقم المادة: {item['article_number']}
149
- النص الأصلي: {item['original_text']}
150
- الشرح المبسط: {item['simplified_summary']}{cross_ref_text}
151
- """
152
- metadata = {
153
- "article_id": item['article_id'],
154
- "article_number": str(item['article_number']),
155
- "legal_nature": item['legal_nature'],
156
- "keywords": ", ".join(item['keywords']),
157
- "part": item.get('part (Bab)', ''),
158
- "chapter": item.get('chapter (Fasl)', ''),
159
- "cross_references": ", ".join([str(ref) for ref in item.get('cross_references', [])]) # Convert list to string
160
- }
161
- docs.append(Document(page_content=page_content, metadata=metadata))
162
-
163
- print(f"✅ Loaded {len(docs)} constitutional articles")
164
-
165
- # 2. Embeddings
166
- print("Loading embeddings model...")
167
- embeddings = HuggingFaceEmbeddings(
168
- model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1"
169
- )
170
- print("✅ Embeddings model ready")
171
-
172
- # 3. No splitting - keep articles as complete units
173
- chunks = docs
174
-
175
- # 4. Vector Store
176
- print("Building vector database...")
177
- vectorstore = Chroma.from_documents(
178
- chunks,
179
- embeddings,
180
- persist_directory="chroma_db"
181
- )
182
- base_retriever = vectorstore.as_retriever(search_kwargs={"k": 15})
183
- print("✅ Vector database ready")
184
-
185
- # 5. Create BM25 Keyword Retriever
186
- class BM25Retriever(BaseRetriever):
187
- """BM25-based keyword retriever for constitutional articles"""
188
- corpus_docs: List[Document]
189
- bm25: BM25Okapi = None
190
- k: int = 15
191
-
192
- class Config:
193
- arbitrary_types_allowed = True
194
-
195
- def __init__(self, **data):
196
- super().__init__(**data)
197
- # Tokenize corpus for BM25
198
- tokenized_corpus = [doc.page_content.split() for doc in self.corpus_docs]
199
- self.bm25 = BM25Okapi(tokenized_corpus)
200
-
201
- def _get_relevant_documents(
202
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
203
- ) -> List[Document]:
204
- # Tokenize query
205
- tokenized_query = query.split()
206
- # Get BM25 scores
207
- scores = self.bm25.get_scores(tokenized_query)
208
- # Get top k indices
209
- top_indices = np.argsort(scores)[::-1][:self.k]
210
- # Return documents
211
- return [self.corpus_docs[i] for i in top_indices if scores[i] > 0]
212
-
213
- async def _aget_relevant_documents(
214
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
215
- ) -> List[Document]:
216
- return self._get_relevant_documents(query, run_manager=run_manager)
217
-
218
- bm25_retriever = BM25Retriever(corpus_docs=docs, k=15)
219
- print("✅ BM25 keyword retriever ready")
220
-
221
- # 6. Create Metadata Filter Retriever
222
- class MetadataFilterRetriever(BaseRetriever):
223
- """Metadata-based filtering retriever"""
224
- corpus_docs: List[Document]
225
- k: int = 15
226
-
227
- class Config:
228
- arbitrary_types_allowed = True
229
-
230
- def _get_relevant_documents(
231
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
232
- ) -> List[Document]:
233
- query_lower = query.lower()
234
- scored_docs = []
235
-
236
- for doc in self.corpus_docs:
237
- score = 0
238
- # Match keywords
239
- keywords = doc.metadata.get('keywords', '').lower()
240
- if any(word in keywords for word in query_lower.split()):
241
- score += 3
242
-
243
- # Match legal nature
244
- legal_nature = doc.metadata.get('legal_nature', '').lower()
245
- if any(word in legal_nature for word in query_lower.split()):
246
- score += 2
247
-
248
- # Match part/chapter
249
- part = doc.metadata.get('part', '').lower()
250
- chapter = doc.metadata.get('chapter', '').lower()
251
- if any(word in part or word in chapter for word in query_lower.split()):
252
- score += 1
253
-
254
- # Match in content
255
- if any(word in doc.page_content.lower() for word in query_lower.split()):
256
- score += 1
257
-
258
- if score > 0:
259
- scored_docs.append((doc, score))
260
-
261
- # Sort by score and return top k
262
- scored_docs.sort(key=lambda x: x[1], reverse=True)
263
- return [doc for doc, _ in scored_docs[:self.k]]
264
-
265
- async def _aget_relevant_documents(
266
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
267
- ) -> List[Document]:
268
- return self._get_relevant_documents(query, run_manager=run_manager)
269
-
270
- metadata_retriever = MetadataFilterRetriever(corpus_docs=docs, k=15)
271
- print("✅ Metadata filter retriever ready")
272
-
273
- # 7. Create Hybrid RRF Retriever
274
- class HybridRRFRetriever(BaseRetriever):
275
- """Combines semantic, BM25, and metadata retrievers using Reciprocal Rank Fusion"""
276
- semantic_retriever: BaseRetriever
277
- bm25_retriever: BM25Retriever
278
- metadata_retriever: MetadataFilterRetriever
279
- beta_semantic: float = 0.6 # Weight for semantic search
280
- beta_keyword: float = 0.2 # Weight for BM25 keyword search
281
- beta_metadata: float = 0.2 # Weight for metadata filtering
282
- k: int = 60 # RRF constant (typically 60)
283
- top_k: int = 15
284
-
285
- class Config:
286
- arbitrary_types_allowed = True
287
-
288
- def _get_relevant_documents(
289
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
290
- ) -> List[Document]:
291
- # Get results from all three retrievers
292
- semantic_docs = self.semantic_retriever.invoke(query)
293
- bm25_docs = self.bm25_retriever.invoke(query)
294
- metadata_docs = self.metadata_retriever.invoke(query)
295
-
296
- # Apply Reciprocal Rank Fusion
297
- rrf_scores = {}
298
-
299
- # Process semantic results
300
- for rank, doc in enumerate(semantic_docs, start=1):
301
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
302
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_semantic / (self.k + rank)
303
-
304
- # Process BM25 results
305
- for rank, doc in enumerate(bm25_docs, start=1):
306
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
307
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_keyword / (self.k + rank)
308
-
309
- # Process metadata results
310
- for rank, doc in enumerate(metadata_docs, start=1):
311
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
312
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_metadata / (self.k + rank)
313
-
314
- # Create document lookup
315
- all_docs = {}
316
- for doc in semantic_docs + bm25_docs + metadata_docs:
317
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
318
- if doc_id not in all_docs:
319
- all_docs[doc_id] = doc
320
-
321
- # Sort by RRF score
322
- sorted_doc_ids = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
323
-
324
- # Return top k documents
325
- result_docs = []
326
- for doc_id, score in sorted_doc_ids[:self.top_k]:
327
- if doc_id in all_docs:
328
- result_docs.append(all_docs[doc_id])
329
-
330
- return result_docs
331
-
332
- async def _aget_relevant_documents(
333
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
334
- ) -> List[Document]:
335
- return self._get_relevant_documents(query, run_manager=run_manager)
336
-
337
- # Create hybrid retriever with tuned beta weights
338
- hybrid_retriever = HybridRRFRetriever(
339
- semantic_retriever=base_retriever,
340
- bm25_retriever=bm25_retriever,
341
- metadata_retriever=metadata_retriever,
342
- beta_semantic=0.5, # Semantic search gets highest weight (most reliable)
343
- beta_keyword=0.3, # BM25 keyword search (good for exact term matches)
344
- beta_metadata=0.2, # Metadata filtering (supporting role)
345
- k=60,
346
- top_k=20
347
- )
348
- print("✅ Hybrid RRF retriever ready with β weights: semantic=0.5, keyword=0.3, metadata=0.2")
349
-
350
- # 8. Create Cross-Reference Enhanced Retriever
351
- class CrossReferenceRetriever(BaseRetriever):
352
- """Enhances retrieval by automatically fetching cross-referenced articles"""
353
- base_retriever: BaseRetriever
354
- article_map: dict
355
-
356
- class Config:
357
- arbitrary_types_allowed = True
358
-
359
- def _get_relevant_documents(
360
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
361
- ) -> List[Document]:
362
- # Get initial results
363
- initial_docs = self.base_retriever.invoke(query)
364
-
365
- # Collect all related article numbers
366
- all_article_numbers = set()
367
- for doc in initial_docs:
368
- if 'article_number' in doc.metadata:
369
- all_article_numbers.add(doc.metadata['article_number'])
370
- # Parse cross_references (now stored as comma-separated string)
371
- cross_refs_str = doc.metadata.get('cross_references', '')
372
- if cross_refs_str:
373
- cross_refs = [ref.strip() for ref in cross_refs_str.split(',')]
374
- for ref in cross_refs:
375
- if ref: # Skip empty strings
376
- all_article_numbers.add(str(ref))
377
-
378
- # Build enhanced document list
379
- enhanced_docs = []
380
- seen_numbers = set()
381
-
382
- # Add initially retrieved documents
383
- for doc in initial_docs:
384
- enhanced_docs.append(doc)
385
- seen_numbers.add(doc.metadata.get('article_number'))
386
-
387
- # Add cross-referenced articles not yet retrieved
388
- for article_num in all_article_numbers:
389
- if article_num not in seen_numbers and article_num in self.article_map:
390
- article_data = self.article_map[article_num]
391
- cross_ref_text = ""
392
- if article_data.get('cross_references'):
393
- cross_ref_text = "\nالمواد ذات الصلة: " + ", ".join(
394
- [f"المادة {ref}" for ref in article_data['cross_references']]
395
- )
396
-
397
- page_content = f"""
398
- رقم المادة: {article_data['article_number']}
399
- النص الأصلي: {article_data['original_text']}
400
- الشرح المبسط: {article_data['simplified_summary']}{cross_ref_text}
401
- """
402
-
403
- enhanced_doc = Document(
404
- page_content=page_content,
405
- metadata={
406
- "article_id": article_data['article_id'],
407
- "article_number": str(article_data['article_number']),
408
- "legal_nature": article_data['legal_nature'],
409
- "keywords": ", ".join(article_data['keywords']),
410
- "cross_references": ", ".join([str(ref) for ref in article_data.get('cross_references', [])])
411
- }
412
- )
413
- enhanced_docs.append(enhanced_doc)
414
- seen_numbers.add(article_num)
415
-
416
- return enhanced_docs
417
-
418
- async def _aget_relevant_documents(
419
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
420
- ) -> List[Document]:
421
- return self._get_relevant_documents(query, run_manager=run_manager)
422
-
423
- cross_ref_retriever = CrossReferenceRetriever(
424
- base_retriever=hybrid_retriever,
425
- article_map=article_map
426
- )
427
- print("✅ Cross-reference retriever ready (using hybrid RRF base)")
428
-
429
- # 9. Reranker
430
- print("Loading reranker model...")
431
- local_model_path = r"D:\FOE\Senior 2\Graduation Project\Chatbot_me\reranker"
432
-
433
- if not os.path.exists(local_model_path):
434
- raise FileNotFoundError(f"Reranker path not found: {local_model_path}")
435
-
436
- model = HuggingFaceCrossEncoder(model_name=local_model_path)
437
- compressor = CrossEncoderReranker(model=model, top_n=5)
438
-
439
- compression_retriever = ContextualCompressionRetriever(
440
- base_compressor=compressor,
441
- base_retriever=cross_ref_retriever
442
- )
443
- print("✅ Reranker model ready")
444
-
445
- # 7. LLM - Balanced for consistency with slight creativity
446
- # 7. LLM Configuration
447
- llm = ChatGroq(
448
- groq_api_key=os.getenv("GROQ_API_KEY"),
449
- model_name="llama-3.1-8b-instant",
450
- temperature=0.3, # Slightly increased to allow helpful general advice
451
- model_kwargs={"top_p": 0.9}
452
- )
453
-
454
- # ==================================================
455
- # 🛠️ THE FIX: SEPARATE SYSTEM INSTRUCTIONS FROM USER INPUT
456
- # ==================================================
457
-
458
- # ==================================================
459
- # 🧠 PROMPT ENGINEERING: DECISION TREE LOGIC
460
- # ==================================================
461
-
462
- system_instructions = """
463
- <role>
464
- أنت "المساعد القانوني الذكي"، خبير متخصص في الدستور المصري والقوانين الإجرائية.
465
- مهمتك: تقديم إجابات دقيقة بناءً على "السياق التشريعي" المرفق أولاً، أو تقديم نصائح إجرائية عامة عند الضرورة.
466
- </role>
467
-
468
- <decision_logic>
469
- عليك تحليل "سؤال المستخدم" و"السياق التشريعي" وتصنيف الحالة واختيار الرد المناسب بناءً على القواعد التالية بدقة:
470
-
471
- 🔴 الحالة الأولى: (الإجابة موجودة في السياق التشريعي)
472
- الشرط: إذا وجدت معلومات داخل "السياق التشريعي المتاح" تجيب على السؤال.
473
- الفعل:
474
- 1. استخرج الإجابة من السياق فقط.
475
- 2. ابدأ الإجابة مباشرة دون مقدمات.
476
- 3. يجب توثيق الإجابة برقم المادة (مثال: "نصت المادة (50) على...").
477
- 4. توقف هنا. لا تضف أي معلومات خارجية.
478
-
479
- 🟡 الحالة الثانية: (السياق فارغ/غير مفيد + السؤال إجرائي/عملي)
480
- الشرط: إذا لم تجد الإجابة في السياق، وكان السؤال عن إجراءات عملية (مثل: حادث، سرقة، طلاق، تحرير محضر، تعامل مع الشرطة).
481
- الفعل:
482
- 1. تجاهل السياق الفارغ.
483
- 2. استخدم معرفتك العامة بالقانون المصري.
484
- 3. ابدأ وجوباً بعبارة: "بناءً على الإجراءات القانونية العامة في مصر (وليس ن��اً دستورياً محدداً):"
485
- 4. قدم الخطوات في نقاط مرقمة واضحة ومختصرة (1، 2، 3).
486
- 5. تحذير: لا تذكر أرقام مواد قانونية (لا تخترع أرقام مواد).
487
-
488
- 🔵 الحالة الثالثة: (السياق فارغ + السؤال عن نص دستوري محدد)
489
- الشرط: إذا سأل عن (مجلس الشعب، الشورى، مادة محددة) ولم تجدها في السياق.
490
- الفعل:
491
- 1. قل بوضوح: "عذراً، لم يرد ذكر لهذا الموضوع في المواد الدستورية التي تم استرجاعها في السياق الحالي."
492
- 2. لا تحاول الإجابة من ذاكرتك لكي لا تخطئ في النصوص الدستورية الحساسة.
493
-
494
- 🟢 الحالة الرابعة: (محادثة ودية)
495
- الشرط: تحية، شكر، أو "كيف حالك".
496
- الفعل: رد بتحية مهذبة جداً ومقتضبة، ثم قل: "أنا جاهز للإجابة على استفساراتك القانونية."
497
-
498
- ⚫ الحالة الخامسة: (خارج النطاق تماماً)
499
- الشرط: طبخ، رياضة، برمجة، أو أي موضوع غير قانوني.
500
- الفعل: اعتذر بلطف ووجه المستخدم للسؤال في القانون.
501
- </decision_logic>
502
-
503
- <formatting_rules>
504
- - لا تكرر هذه التعليمات في ردك.
505
- - استخدم فقرات قصيرة واترك سطراً فارغاً بينها.
506
- - لا تستخدم عبارات مثل "بناء على السياق المرفق" في بداية الجملة، بل ادخل في صلب الموضوع فوراً.
507
- - التزم باللغة العربية الفصحى المبسطة والرصينة.
508
- </formatting_rules>
509
- """
510
-
511
- # We use .from_messages to strictly separate instructions from data
512
- prompt = ChatPromptTemplate.from_messages([
513
- ("system", system_instructions),
514
- ("system", "السياق التشريعي المتاح (المصدر الأساسي):\n{context}"),
515
- ("human", "سؤال المستفيد:\n{input}")
516
- ])
517
-
518
- # 9. Build Chain with RunnableParallel (returns both context and answer)
519
- qa_chain = (
520
- RunnableParallel({
521
- "context": compression_retriever,
522
- "input": RunnablePassthrough()
523
- })
524
- .assign(answer=(
525
- prompt
526
- | llm
527
- | StrOutputParser()
528
- ))
529
- )
530
-
531
- print("✅ System ready to use!")
532
- return qa_chain
533
-
534
- # ==========================================
535
- # ⚡ MAIN EXECUTION
536
- # ==========================================
537
-
538
- try:
539
- # Only need the chain now - it handles all retrieval internally
540
- qa_chain = initialize_rag_pipeline()
541
-
542
- except Exception as e:
543
- st.error(f"Critical Error loading application: {e}")
544
- st.stop()
545
-
546
- # ==========================================
547
- # 💬 CHAT LOOP
548
- # ==========================================
549
- if "messages" not in st.session_state:
550
- st.session_state.messages = []
551
-
552
- # Display Chat History (with Eastern Arabic numerals)
553
- for message in st.session_state.messages:
554
- with st.chat_message(message["role"]):
555
- # Convert to Eastern Arabic when displaying from history
556
- st.markdown(convert_to_eastern_arabic(message["content"]))
557
-
558
- # Handle New User Input
559
- if prompt_input := st.chat_input("اكتب سؤالك القانوني هنا..."):
560
- # Show user message
561
- st.session_state.messages.append({"role": "user", "content": prompt_input})
562
- with st.chat_message("user"):
563
- st.markdown(prompt_input)
564
-
565
- # Generate Response
566
- with st.chat_message("assistant"):
567
- with st.spinner("جاري التحليل القانوني..."):
568
- try:
569
- # Invoke chain ONCE - returns Dict with 'context', 'input', and 'answer'
570
- result = qa_chain.invoke(prompt_input)
571
-
572
- # Extract answer and context from result
573
- response_text = result["answer"]
574
- source_docs = result["context"] # Context is already in the result!
575
-
576
- # Display Answer
577
- response_text_arabic = convert_to_eastern_arabic(response_text)
578
- st.markdown(response_text_arabic)
579
-
580
- # Display Sources
581
- if source_docs and len(source_docs) > 0:
582
- print(f"✅ Found {len(source_docs)} documents")
583
- # Deduplicate documents by article_number
584
- seen_articles = set()
585
- unique_docs = []
586
-
587
- for doc in source_docs:
588
- article_num = str(doc.metadata.get('article_number', '')).strip()
589
- if article_num and article_num not in seen_articles:
590
- seen_articles.add(article_num)
591
- unique_docs.append(doc)
592
-
593
- st.markdown("---") # Separator before sources
594
-
595
- if unique_docs:
596
- with st.expander(f"📚 المصادر المستخدمة ({len(unique_docs)} مادة)"):
597
- st.markdown("### المواد الدستورية المستخدمة في التحليل:")
598
- st.markdown("---")
599
-
600
- for idx, doc in enumerate(unique_docs, 1):
601
- article_num = str(doc.metadata.get('article_number', '')).strip()
602
- legal_nature = doc.metadata.get('legal_nature', '')
603
-
604
- if article_num:
605
- st.markdown(f"**المادة رقم {convert_to_eastern_arabic(article_num)}**")
606
- if legal_nature:
607
- st.markdown(f"*الطبيعة القانونية: {legal_nature}*")
608
-
609
- # Display article content
610
- content_lines = doc.page_content.strip().split('\n')
611
- for line in content_lines:
612
- line = line.strip()
613
- if line:
614
- st.markdown(convert_to_eastern_arabic(line))
615
-
616
- st.markdown("---")
617
- else:
618
- st.info("📌 لم يتم العثور على مصادر")
619
- else:
620
- st.info("📌 لم يتم العثور على مصادر")
621
-
622
- # Persist the raw answer to avoid double conversion glitches on rerun
623
- st.session_state.messages.append({"role": "assistant", "content": response_text})
624
- except Exception as e:
625
- st.error(f"حدث خطأ: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_final_pheonix.py DELETED
@@ -1,838 +0,0 @@
1
- # === Phoenix Observability Setup ===
2
- import os
3
- from datetime import datetime
4
-
5
- try:
6
- # OpenTelemetry SDK + OTLP exporter (Phoenix consumes OTLP)
7
- from opentelemetry import trace
8
- from opentelemetry.sdk.resources import SERVICE_NAME, Resource
9
- from opentelemetry.sdk.trace import TracerProvider
10
- from opentelemetry.sdk.trace.export import BatchSpanProcessor
11
- from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
12
- PHOENIX_AVAILABLE = True
13
- except Exception:
14
- PHOENIX_AVAILABLE = False
15
-
16
-
17
- def setup_phoenix_tracing():
18
- """Configure OTLP tracing for Phoenix. Uses PHOENIX_OTLP_ENDPOINT env if set."""
19
- if not PHOENIX_AVAILABLE:
20
- return None
21
-
22
- service_name = os.getenv("PHOENIX_SERVICE_NAME", "constitutional-assistant")
23
- otlp_endpoint = os.getenv("PHOENIX_OTLP_ENDPOINT", "http://localhost:6006/v1/traces")
24
-
25
- resource = Resource(attributes={SERVICE_NAME: service_name})
26
- provider = TracerProvider(resource=resource)
27
- exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
28
- span_processor = BatchSpanProcessor(exporter)
29
- provider.add_span_processor(span_processor)
30
- trace.set_tracer_provider(provider)
31
- return trace.get_tracer(service_name)
32
-
33
-
34
- # Create a module-level tracer
35
- _phoenix_tracer = setup_phoenix_tracing()
36
-
37
-
38
- class PhoenixSpan:
39
- """Context manager helper to create spans with proper parent-child hierarchy."""
40
- def __init__(self, name: str, attributes: dict | None = None, kind: str = "INTERNAL"):
41
- self.name = name
42
- self.attributes = attributes or {}
43
- self.kind = kind
44
- self._span_context = None
45
- self._span = None
46
- self._start_time = None
47
-
48
- def __enter__(self):
49
- if _phoenix_tracer:
50
- from opentelemetry.trace import SpanKind
51
- import time
52
- self._start_time = time.time()
53
-
54
- # Map string kind to SpanKind enum
55
- kind_map = {
56
- "CLIENT": SpanKind.CLIENT,
57
- "SERVER": SpanKind.SERVER,
58
- "INTERNAL": SpanKind.INTERNAL,
59
- }
60
- span_kind = kind_map.get(self.kind, SpanKind.INTERNAL)
61
-
62
- # Use start_as_current_span to establish parent-child relationships
63
- self._span_context = _phoenix_tracer.start_as_current_span(
64
- self.name,
65
- kind=span_kind
66
- )
67
- self._span = self._span_context.__enter__()
68
- for k, v in self.attributes.items():
69
- try:
70
- self._span.set_attribute(k, v)
71
- except Exception:
72
- pass
73
- return self
74
-
75
- def set_attr(self, key: str, value):
76
- if self._span:
77
- try:
78
- self._span.set_attribute(key, value)
79
- except Exception:
80
- pass
81
-
82
- def __exit__(self, exc_type, exc, tb):
83
- if self._span_context:
84
- try:
85
- if exc_type:
86
- self._span.record_exception(exc)
87
- from opentelemetry.trace import Status, StatusCode
88
- self._span.set_status(Status(StatusCode.ERROR, str(exc)))
89
- else:
90
- # Add duration as attribute
91
- if self._start_time:
92
- import time
93
- duration = time.time() - self._start_time
94
- self._span.set_attribute("duration_ms", round(duration * 1000, 2))
95
- from opentelemetry.trace import Status, StatusCode
96
- self._span.set_status(Status(StatusCode.OK))
97
- self._span_context.__exit__(exc_type, exc, tb)
98
- except Exception:
99
- pass
100
-
101
- # -*- coding: utf-8 -*-
102
- import os
103
- import sys
104
- import json
105
- from dotenv import load_dotenv
106
- import streamlit as st
107
- import logging
108
- import warnings
109
-
110
- # Suppress progress bars from transformers/tqdm
111
- os.environ['TRANSFORMERS_NO_PROGRESS_BAR'] = '1'
112
- warnings.filterwarnings('ignore')
113
-
114
- # 1. Loaders & Splitters
115
- from langchain_core.documents import Document
116
- from langchain_text_splitters import RecursiveCharacterTextSplitter
117
- from langchain_core.retrievers import BaseRetriever
118
- from langchain_core.callbacks import CallbackManagerForRetrieverRun
119
- from typing import List
120
- from rank_bm25 import BM25Okapi
121
- import numpy as np
122
-
123
- # 2. Vector Store & Embeddings
124
- from langchain_chroma import Chroma
125
- from langchain_huggingface import HuggingFaceEmbeddings
126
-
127
- # 3. Reranker Imports
128
- from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
129
- from langchain_classic.retrievers import ContextualCompressionRetriever
130
- from langchain_community.cross_encoders import HuggingFaceCrossEncoder
131
-
132
- # 4. LLM
133
- from langchain_groq import ChatGroq
134
- from langchain_core.prompts import ChatPromptTemplate
135
- from langchain_core.output_parsers import StrOutputParser
136
- from langchain_core.runnables import RunnablePassthrough, RunnableParallel
137
-
138
- # Configure logging
139
- logging.basicConfig(level=logging.INFO)
140
- logger = logging.getLogger(__name__)
141
-
142
- load_dotenv()
143
-
144
- # ==========================================
145
- # 🎨 UI SETUP (CSS FOR ARABIC & RTL)
146
- # ==========================================
147
- st.set_page_config(page_title="المساعد القانوني", page_icon="⚖️")
148
-
149
- # This CSS block fixes the "001" number issue and right alignment
150
- st.markdown("""
151
- <style>
152
- /* Force the main app container to be Right-to-Left */
153
- .stApp {
154
- direction: rtl;
155
- text-align: right;
156
- }
157
-
158
- /* Fix input fields to type from right */
159
- .stTextInput input {
160
- direction: rtl;
161
- text-align: right;
162
- }
163
-
164
- /* Fix chat messages alignment */
165
- .stChatMessage {
166
- direction: rtl;
167
- text-align: right;
168
- }
169
-
170
- /* Ensure proper paragraph spacing */
171
- .stMarkdown p {
172
- margin: 0.5em 0 !important;
173
- line-height: 1.6;
174
- word-spacing: 0.1em;
175
- }
176
-
177
- /* Ensure numbers display correctly in RTL */
178
- p, div, span, label {
179
- unicode-bidi: embed;
180
- direction: inherit;
181
- white-space: normal;
182
- word-wrap: break-word;
183
- }
184
-
185
- /* Force all content to respect RTL */
186
- * {
187
- direction: rtl !important;
188
- }
189
-
190
- /* Preserve line breaks and spacing */
191
- .stMarkdown pre {
192
- direction: rtl;
193
- text-align: right;
194
- white-space: pre-wrap;
195
- word-wrap: break-word;
196
- }
197
-
198
- /* Hide the "Deploy" button and standard menu for cleaner look */
199
- #MainMenu {visibility: hidden;}
200
- footer {visibility: hidden;}
201
-
202
- </style>
203
- """, unsafe_allow_html=True)
204
-
205
- # Put this at the top of your code
206
- def convert_to_eastern_arabic(text):
207
- """Converts 0123456789 to ٠١٢٣٤٥٦٧٨٩"""
208
- if not isinstance(text, str):
209
- return text
210
- western_numerals = '0123456789'
211
- eastern_numerals = '٠١٢٣٤٥٦٧٨٩'
212
- translation_table = str.maketrans(western_numerals, eastern_numerals)
213
- return text.translate(translation_table)
214
-
215
- st.title("⚖️ المساعد القانوني الذكي (دستور مصر)")
216
-
217
- # ==========================================
218
- # 🚀 CACHED RESOURCE LOADING (THE FIX)
219
- # ==========================================
220
- # This decorator tells Streamlit: "Run this ONCE and save the result."
221
- @st.cache_resource
222
- def initialize_rag_pipeline():
223
- print("🔄 Initializing system...")
224
- print("📥 Loading data...")
225
-
226
- # 1. Load JSON
227
- json_path = "Egyptian_Constitution_legalnature_only.json"
228
- if not os.path.exists(json_path):
229
- raise FileNotFoundError(f"File not found: {json_path}")
230
-
231
- with open(json_path, "r", encoding="utf-8") as f:
232
- data = json.load(f)
233
-
234
- # Create a mapping of article numbers for cross-reference lookup
235
- article_map = {str(item['article_number']): item for item in data}
236
-
237
- docs = []
238
- for item in data:
239
- # Build cross-reference section
240
- cross_ref_text = ""
241
- if item.get('cross_references') and len(item['cross_references']) > 0:
242
- cross_ref_text = "\nالمواد ذات الصلة (المراجع المتقاطعة): " + ", ".join(
243
- [f"المادة {ref}" for ref in item['cross_references']]
244
- )
245
-
246
- # Construct content
247
- page_content = f"""
248
- رقم المادة: {item['article_number']}
249
- النص الأصلي: {item['original_text']}
250
- الشرح المبسط: {item['simplified_summary']}{cross_ref_text}
251
- """
252
- metadata = {
253
- "article_id": item['article_id'],
254
- "article_number": str(item['article_number']),
255
- "legal_nature": item['legal_nature'],
256
- "keywords": ", ".join(item['keywords']),
257
- "part": item.get('part (Bab)', ''),
258
- "chapter": item.get('chapter (Fasl)', ''),
259
- "cross_references": ", ".join([str(ref) for ref in item.get('cross_references', [])]) # Convert list to string
260
- }
261
- docs.append(Document(page_content=page_content, metadata=metadata))
262
-
263
- print(f"✅ Loaded {len(docs)} constitutional articles")
264
-
265
- # 2. Embeddings
266
- print("Loading embeddings model...")
267
- embeddings = HuggingFaceEmbeddings(
268
- model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1"
269
- )
270
- print("✅ Embeddings model ready")
271
-
272
- # 3. No splitting - keep articles as complete units
273
- chunks = docs
274
-
275
- # 4. Vector Store
276
- print("Building vector database...")
277
- vectorstore = Chroma.from_documents(
278
- chunks,
279
- embeddings,
280
- persist_directory="chroma_db"
281
- )
282
- base_retriever = vectorstore.as_retriever(search_kwargs={"k": 15})
283
- print("✅ Vector database ready")
284
-
285
- # 5. Create BM25 Keyword Retriever
286
- class BM25Retriever(BaseRetriever):
287
- """BM25-based keyword retriever for constitutional articles"""
288
- corpus_docs: List[Document]
289
- bm25: BM25Okapi = None
290
- k: int = 15
291
-
292
- class Config:
293
- arbitrary_types_allowed = True
294
-
295
- def __init__(self, **data):
296
- super().__init__(**data)
297
- # Tokenize corpus for BM25
298
- tokenized_corpus = [doc.page_content.split() for doc in self.corpus_docs]
299
- self.bm25 = BM25Okapi(tokenized_corpus)
300
-
301
- def _get_relevant_documents(
302
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
303
- ) -> List[Document]:
304
- # Tokenize query
305
- tokenized_query = query.split()
306
- # Get BM25 scores
307
- scores = self.bm25.get_scores(tokenized_query)
308
- # Get top k indices
309
- top_indices = np.argsort(scores)[::-1][:self.k]
310
- # Return documents
311
- return [self.corpus_docs[i] for i in top_indices if scores[i] > 0]
312
-
313
- async def _aget_relevant_documents(
314
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
315
- ) -> List[Document]:
316
- return self._get_relevant_documents(query, run_manager=run_manager)
317
-
318
- bm25_retriever = BM25Retriever(corpus_docs=docs, k=15)
319
- print("✅ BM25 keyword retriever ready")
320
-
321
- # 6. Create Metadata Filter Retriever
322
- class MetadataFilterRetriever(BaseRetriever):
323
- """Metadata-based filtering retriever"""
324
- corpus_docs: List[Document]
325
- k: int = 15
326
-
327
- class Config:
328
- arbitrary_types_allowed = True
329
-
330
- def _get_relevant_documents(
331
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
332
- ) -> List[Document]:
333
- query_lower = query.lower()
334
- scored_docs = []
335
-
336
- for doc in self.corpus_docs:
337
- score = 0
338
- # Match keywords (boosted)
339
- keywords = doc.metadata.get('keywords', '').lower()
340
- if any(word in keywords for word in query_lower.split()):
341
- score += 4
342
-
343
- # Match legal nature (boosted)
344
- legal_nature = doc.metadata.get('legal_nature', '').lower()
345
- if any(word in legal_nature for word in query_lower.split()):
346
- score += 3
347
-
348
- # Match part/chapter
349
- part = doc.metadata.get('part', '').lower()
350
- chapter = doc.metadata.get('chapter', '').lower()
351
- if any(word in part or word in chapter for word in query_lower.split()):
352
- score += 1
353
-
354
- # Match in content
355
- if any(word in doc.page_content.lower() for word in query_lower.split()):
356
- score += 1
357
-
358
- if score > 0:
359
- scored_docs.append((doc, score))
360
-
361
- # Sort by score and return top k
362
- scored_docs.sort(key=lambda x: x[1], reverse=True)
363
- return [doc for doc, _ in scored_docs[:self.k]]
364
-
365
- async def _aget_relevant_documents(
366
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
367
- ) -> List[Document]:
368
- return self._get_relevant_documents(query, run_manager=run_manager)
369
-
370
- metadata_retriever = MetadataFilterRetriever(corpus_docs=docs, k=15)
371
- print("✅ Metadata filter retriever ready")
372
-
373
- # 7. Create Hybrid RRF Retriever
374
- class HybridRRFRetriever(BaseRetriever):
375
- """Combines semantic, BM25, and metadata retrievers using Reciprocal Rank Fusion"""
376
- semantic_retriever: BaseRetriever
377
- bm25_retriever: BM25Retriever
378
- metadata_retriever: MetadataFilterRetriever
379
- beta_semantic: float = 0.6 # Weight for semantic search
380
- beta_keyword: float = 0.25 # Weight for BM25 keyword search
381
- beta_metadata: float = 0.15 # Weight for metadata filtering
382
- k: int = 60 # RRF constant (typically 60)
383
- top_k: int = 25
384
-
385
- class Config:
386
- arbitrary_types_allowed = True
387
-
388
- def _get_relevant_documents(
389
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
390
- ) -> List[Document]:
391
- # Get results from all three retrievers (no separate spans - details logged in hybrid_retrieval span)
392
- semantic_docs = self.semantic_retriever.invoke(query)
393
- bm25_docs = self.bm25_retriever.invoke(query)
394
- metadata_docs = self.metadata_retriever.invoke(query)
395
-
396
- # Apply Reciprocal Rank Fusion
397
- rrf_scores = {}
398
-
399
- # Process semantic results
400
- for rank, doc in enumerate(semantic_docs, start=1):
401
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
402
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_semantic / (self.k + rank)
403
-
404
- # Process BM25 results
405
- for rank, doc in enumerate(bm25_docs, start=1):
406
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
407
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_keyword / (self.k + rank)
408
-
409
- # Process metadata results
410
- for rank, doc in enumerate(metadata_docs, start=1):
411
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
412
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_metadata / (self.k + rank)
413
-
414
- # Create document lookup
415
- all_docs = {}
416
- for doc in semantic_docs + bm25_docs + metadata_docs:
417
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
418
- if doc_id not in all_docs:
419
- all_docs[doc_id] = doc
420
-
421
- # Sort by RRF score
422
- sorted_doc_ids = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
423
-
424
- # Return top k documents
425
- result_docs = []
426
- for doc_id, score in sorted_doc_ids[:self.top_k]:
427
- if doc_id in all_docs:
428
- result_docs.append(all_docs[doc_id])
429
-
430
- # Log all retrieval details in one place (no nested spans to avoid hierarchy issues)
431
- try:
432
- with PhoenixSpan("hybrid_retrieval", {
433
- "query": query[:200],
434
- "beta_semantic": self.beta_semantic,
435
- "beta_keyword": self.beta_keyword,
436
- "beta_metadata": self.beta_metadata,
437
- "rrf_k_constant": self.k,
438
- "top_k_limit": self.top_k
439
- }, kind="INTERNAL") as fusion_span:
440
- # Semantic retrieval details
441
- fusion_span.set_attr("semantic_input_count", len(semantic_docs))
442
- if semantic_docs:
443
- fusion_span.set_attr("semantic_top_5", ", ".join([d.metadata.get('article_number', 'N/A') for d in semantic_docs[:5]]))
444
-
445
- # BM25 retrieval details
446
- fusion_span.set_attr("bm25_input_count", len(bm25_docs))
447
- if bm25_docs:
448
- fusion_span.set_attr("bm25_top_5", ", ".join([d.metadata.get('article_number', 'N/A') for d in bm25_docs[:5]]))
449
-
450
- # Metadata retrieval details
451
- fusion_span.set_attr("metadata_input_count", len(metadata_docs))
452
- if metadata_docs:
453
- fusion_span.set_attr("metadata_top_5", ", ".join([d.metadata.get('article_number', 'N/A') for d in metadata_docs[:5]]))
454
-
455
- # Fusion results
456
- fusion_span.set_attr("unique_docs_before_fusion", len(all_docs))
457
- fusion_span.set_attr("final_doc_count", len(result_docs))
458
- if result_docs:
459
- top_article_nums = [d.metadata.get('article_number', 'N/A') for d in result_docs[:10]]
460
- fusion_span.set_attr("fused_top_10_articles", ", ".join(map(str, top_article_nums)))
461
- # Show top 5 RRF scores
462
- top_scores = [(doc_id, f"{score:.4f}") for doc_id, score in sorted_doc_ids[:5]]
463
- fusion_span.set_attr("top_5_rrf_scores", str(top_scores))
464
- fusion_span.set_attr("top_doc_preview", result_docs[0].page_content[:300])
465
- except Exception:
466
- pass
467
-
468
- return result_docs
469
-
470
- async def _aget_relevant_documents(
471
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
472
- ) -> List[Document]:
473
- return self._get_relevant_documents(query, run_manager=run_manager)
474
-
475
- # Create hybrid retriever with tuned beta weights
476
- hybrid_retriever = HybridRRFRetriever(
477
- semantic_retriever=base_retriever,
478
- bm25_retriever=bm25_retriever,
479
- metadata_retriever=metadata_retriever,
480
- beta_semantic=0.6, # Semantic search gets highest weight (most reliable)
481
- beta_keyword=0.25, # BM25 keyword search (good for exact term matches)
482
- beta_metadata=0.15, # Metadata filtering (supporting role)
483
- k=60,
484
- top_k=25
485
- )
486
- print("✅ Hybrid RRF retriever ready with β weights: semantic=0.6, keyword=0.25, metadata=0.15, top_k=25")
487
-
488
- # 8. Create Cross-Reference Enhanced Retriever
489
- class CrossReferenceRetriever(BaseRetriever):
490
- """Enhances retrieval by automatically fetching cross-referenced articles"""
491
- base_retriever: BaseRetriever
492
- article_map: dict
493
-
494
- class Config:
495
- arbitrary_types_allowed = True
496
-
497
- def _get_relevant_documents(
498
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
499
- ) -> List[Document]:
500
- with PhoenixSpan("cross_reference_expansion", {"query": query[:200]}, kind="INTERNAL") as xref_span:
501
- # Get initial results
502
- initial_docs = self.base_retriever.invoke(query)
503
- xref_span.set_attr("initial_doc_count", len(initial_docs))
504
-
505
- # Collect all related article numbers
506
- all_article_numbers = set()
507
- for doc in initial_docs:
508
- if 'article_number' in doc.metadata:
509
- all_article_numbers.add(doc.metadata['article_number'])
510
- # Parse cross_references (now stored as comma-separated string)
511
- cross_refs_str = doc.metadata.get('cross_references', '')
512
- if cross_refs_str:
513
- cross_refs = [ref.strip() for ref in cross_refs_str.split(',')]
514
- for ref in cross_refs:
515
- if ref: # Skip empty strings
516
- all_article_numbers.add(str(ref))
517
-
518
- # Build enhanced document list
519
- enhanced_docs = []
520
- seen_numbers = set()
521
-
522
- # Add initially retrieved documents
523
- for doc in initial_docs:
524
- enhanced_docs.append(doc)
525
- seen_numbers.add(doc.metadata.get('article_number'))
526
-
527
- # Add cross-referenced articles not yet retrieved
528
- for article_num in all_article_numbers:
529
- if article_num not in seen_numbers and article_num in self.article_map:
530
- article_data = self.article_map[article_num]
531
- cross_ref_text = ""
532
- if article_data.get('cross_references'):
533
- cross_ref_text = "\nالمواد ذات الصلة: " + ", ".join(
534
- [f"المادة {ref}" for ref in article_data['cross_references']]
535
- )
536
-
537
- page_content = f"""
538
- رقم المادة: {article_data['article_number']}
539
- النص الأصلي: {article_data['original_text']}
540
- الشرح المبسط: {article_data['simplified_summary']}{cross_ref_text}
541
- """
542
-
543
- enhanced_doc = Document(
544
- page_content=page_content,
545
- metadata={
546
- "article_id": article_data['article_id'],
547
- "article_number": str(article_data['article_number']),
548
- "legal_nature": article_data['legal_nature'],
549
- "keywords": ", ".join(article_data['keywords']),
550
- "cross_references": ", ".join([str(ref) for ref in article_data.get('cross_references', [])])
551
- }
552
- )
553
- enhanced_docs.append(enhanced_doc)
554
- seen_numbers.add(article_num)
555
-
556
- # Record expansion stats (OUTSIDE the loop, at the end)
557
- expanded_articles = [doc.metadata.get('article_number') for doc in enhanced_docs if doc not in initial_docs]
558
- xref_span.set_attr("cross_refs_added", len(expanded_articles))
559
- xref_span.set_attr("final_doc_count", len(enhanced_docs))
560
- if expanded_articles:
561
- xref_span.set_attr("expanded_article_numbers", ", ".join(map(str, expanded_articles[:15])))
562
-
563
- return enhanced_docs
564
-
565
- async def _aget_relevant_documents(
566
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
567
- ) -> List[Document]:
568
- return self._get_relevant_documents(query, run_manager=run_manager)
569
-
570
- cross_ref_retriever = CrossReferenceRetriever(
571
- base_retriever=hybrid_retriever,
572
- article_map=article_map
573
- )
574
- print("✅ Cross-reference retriever ready (using hybrid RRF base)")
575
-
576
- # 9. Reranker
577
- print("Loading reranker model...")
578
- local_model_path = r"D:\FOE\Senior 2\Graduation Project\Chatbot_me\reranker"
579
-
580
- if not os.path.exists(local_model_path):
581
- raise FileNotFoundError(f"Reranker path not found: {local_model_path}")
582
-
583
- model = HuggingFaceCrossEncoder(model_name=local_model_path)
584
- compressor = CrossEncoderReranker(model=model, top_n=10)
585
-
586
- # Wrap compression retriever to add Phoenix spans
587
- class InstrumentedCompressionRetriever(BaseRetriever):
588
- base_retriever: ContextualCompressionRetriever
589
-
590
- class Config:
591
- arbitrary_types_allowed = True
592
-
593
- def _get_relevant_documents(
594
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
595
- ) -> List[Document]:
596
- with PhoenixSpan("reranker_compression", {
597
- "query": query[:200],
598
- "model": "HuggingFaceCrossEncoder",
599
- "top_n": 10
600
- }, kind="INTERNAL") as rerank_span:
601
- # Apply reranking (this will call cross_ref_retriever internally)
602
- reranked_docs = self.base_retriever.invoke(query)
603
-
604
- rerank_span.set_attr("output_doc_count", len(reranked_docs))
605
- if reranked_docs:
606
- output_articles = [d.metadata.get('article_number', 'N/A') for d in reranked_docs]
607
- rerank_span.set_attr("reranked_articles", ", ".join(map(str, output_articles)))
608
- rerank_span.set_attr("top_doc_preview", reranked_docs[0].page_content[:400] if reranked_docs else "")
609
-
610
- return reranked_docs
611
-
612
- async def _aget_relevant_documents(
613
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
614
- ) -> List[Document]:
615
- return self._get_relevant_documents(query, run_manager=run_manager)
616
-
617
- base_compression_retriever = ContextualCompressionRetriever(
618
- base_compressor=compressor,
619
- base_retriever=cross_ref_retriever
620
- )
621
- compression_retriever = InstrumentedCompressionRetriever(base_retriever=base_compression_retriever)
622
- print("✅ Reranker model ready")
623
-
624
- # 7. LLM - More deterministic for relevance
625
- # 7. LLM Configuration
626
- llm = ChatGroq(
627
- groq_api_key=os.getenv("GROQ_API_KEY"),
628
- model_name="llama-3.1-8b-instant",
629
- temperature=0.1,
630
- model_kwargs={"top_p": 0.9}
631
- )
632
-
633
- # ==================================================
634
- # 🛠️ THE FIX: SEPARATE SYSTEM INSTRUCTIONS FROM USER INPUT
635
- # ==================================================
636
-
637
- # ==================================================
638
- # 🧠 PROMPT ENGINEERING: DECISION TREE LOGIC
639
- # ==================================================
640
-
641
- system_instructions = """
642
- <role>
643
- أنت "المساعد القانوني الذكي"، خبير متخصص في الدستور المصري والقوانين الإجرائية.
644
- مهمتك: تقديم إجابات دقيقة بناءً على "السياق التشريعي" المرفق أولاً، أو تقديم نصائح إجرائية عامة عند الضرورة.
645
- </role>
646
-
647
- <decision_logic>
648
- عليك تحليل "سؤال المستخدم" و"السياق التشريعي" وتصنيف الحالة واختيار الرد المناسب بناءً على القواعد التالية بدقة:
649
-
650
- 🔴 الحالة الأولى: (الإجابة موجودة في السياق التشريعي)
651
- الشرط: إذا وجدت معلومات داخل "السياق التشريعي المتاح" تجيب على السؤال.
652
- الفعل:
653
- 1. استخرج الإجابة من السياق فقط.
654
- 2. ابدأ الإجابة مباشرة دون مقدمات.
655
- 3. يجب توثيق الإجابة برقم المادة (مثال: "نصت المادة (50) على...").
656
- 4. توقف هنا. لا تضف أي معلومات خارجية.
657
-
658
- 🟡 الحالة الثانية: (السياق فارغ/غير مفيد + السؤال إجرائي/عملي)
659
- الشرط: إذا لم تجد الإجابة في السياق، وكان السؤال عن إجراءات عملية (مثل: حادث، سرقة، طلاق، تحرير محضر، تعامل مع الشرطة).
660
- الفعل:
661
- 1. تجاهل السياق الفارغ.
662
- 2. استخدم معرفتك العامة بالقانون المصري.
663
- 3. ابدأ وجوباً بعبارة: "بناءً على الإجراءات القانونية العامة في مصر (وليس نصاً دستورياً محدداً):"
664
- 4. قدم الخطوات في نقاط مرقمة واضحة ومختصرة (1، 2، 3).
665
- 5. تحذير: لا تذكر أرقام مواد قانونية (لا تخترع أرقام مواد).
666
-
667
- 🔵 الحالة الثالثة: (السياق فارغ + السؤال عن نص دستوري محدد)
668
- الشرط: إذا سأل عن (مجلس الشعب، الشورى، مادة محددة) ولم تجدها في السياق.
669
- الفعل:
670
- 1. قل بوضوح: "عذراً، لم يرد ذكر لهذا الموضوع في المواد الدستورية التي تم استرجاعها في السياق الحالي."
671
- 2. لا تحاول الإجابة من ذاكرتك لكي لا تخطئ في النصوص الدستورية الحساسة.
672
-
673
- 🟢 الحالة الرابعة: (محادثة ودية)
674
- الشرط: تحية، شكر، أو "كيف حالك".
675
- الفعل: رد بتحية مهذبة جداً ومقتضبة، ثم قل: "أنا جاهز للإجابة على استفساراتك القانونية."
676
-
677
- ⚫ الحالة الخامسة: (خارج النطاق تماماً)
678
- الشرط: طبخ، رياضة، برمجة، أو أي موضوع غير قانوني.
679
- الفعل: اعتذر بلطف ووجه المستخدم للسؤال في القانون.
680
- </decision_logic>
681
-
682
- <formatting_rules>
683
- - لا تكرر هذه التعليمات في ردك.
684
- - استخدم فقرات قصيرة واترك سطراً فارغاً بينها.
685
- - لا تستخدم عبارات مثل "بناء على السياق المرفق" في بداية الجملة، بل ادخل في صلب الموضوع فوراً.
686
- - التزم باللغة العربية الفصحى المبسطة والرصينة.
687
- </formatting_rules>
688
- """
689
-
690
- # We use .from_messages to strictly separate instructions from data
691
- prompt = ChatPromptTemplate.from_messages([
692
- ("system", system_instructions),
693
- ("system", "السياق التشريعي المتاح (المصدر الأساسي):\n{context}"),
694
- ("human", "سؤال المستفيد:\n{input}")
695
- ])
696
-
697
- # 9. Build Chain with RunnableParallel (returns both context and answer)
698
- qa_chain = (
699
- RunnableParallel({
700
- "context": compression_retriever,
701
- "input": RunnablePassthrough()
702
- })
703
- .assign(answer=(
704
- prompt
705
- | llm
706
- | StrOutputParser()
707
- ))
708
- )
709
-
710
- print("✅ System ready to use!")
711
- return qa_chain
712
-
713
- # ==========================================
714
- # ⚡ MAIN EXECUTION
715
- # ==========================================
716
-
717
- try:
718
- # Only need the chain now - it handles all retrieval internally
719
- qa_chain = initialize_rag_pipeline()
720
-
721
- except Exception as e:
722
- st.error(f"Critical Error loading application: {e}")
723
- st.stop()
724
-
725
- # ==========================================
726
- # 💬 CHAT LOOP
727
- # ==========================================
728
- if "messages" not in st.session_state:
729
- st.session_state.messages = []
730
-
731
- # Display Chat History (with Eastern Arabic numerals)
732
- for message in st.session_state.messages:
733
- with st.chat_message(message["role"]):
734
- # Convert to Eastern Arabic when displaying from history
735
- st.markdown(convert_to_eastern_arabic(message["content"]))
736
-
737
- # Handle New User Input
738
- if prompt_input := st.chat_input("اكتب سؤالك القانوني هنا..."):
739
- # Show user message
740
- st.session_state.messages.append({"role": "user", "content": prompt_input})
741
- with st.chat_message("user"):
742
- st.markdown(prompt_input)
743
-
744
- # Generate Response
745
- with st.chat_message("assistant"):
746
- with st.spinner("جاري التحليل القانوني..."):
747
- try:
748
- # Invoke chain ONCE - returns Dict with 'context', 'input', and 'answer'
749
- with PhoenixSpan("chat_request", {
750
- "question": prompt_input,
751
- "question_len": len(prompt_input or ""),
752
- "timestamp": datetime.utcnow().isoformat(),
753
- }, kind="SERVER") as span:
754
- result = qa_chain.invoke(prompt_input)
755
-
756
- # Extract answer and context from result
757
- response_text = result["answer"]
758
- source_docs = result["context"]
759
-
760
- # Attach detailed context attributes
761
- try:
762
- ctx_list = result.get("context", []) or []
763
- ctx_count = len(ctx_list)
764
- span.set_attr("context_count", ctx_count)
765
- if ctx_count:
766
- # Record all article numbers
767
- article_nums = [doc.metadata.get("article_number", "N/A") for doc in ctx_list]
768
- span.set_attr("context_articles", ", ".join(map(str, article_nums)))
769
- # Record legal natures
770
- legal_natures = [doc.metadata.get("legal_nature", "N/A") for doc in ctx_list]
771
- span.set_attr("legal_natures", ", ".join(legal_natures[:5]))
772
- # Add context preview (first doc)
773
- span.set_attr("context_preview", ctx_list[0].page_content[:500])
774
- except Exception:
775
- pass
776
-
777
- # Log LLM generation as a nested span (properly nested under chat_request)
778
- with PhoenixSpan("llm_generation", {
779
- "model": "llama-3.1-8b-instant",
780
- "temperature": 0.1,
781
- "top_p": 0.9,
782
- "prompt_preview": prompt_input[:300]
783
- }, kind="CLIENT") as llm_span:
784
- llm_span.set_attr("response", response_text)
785
- llm_span.set_attr("response_len", len(response_text))
786
- llm_span.set_attr("response_preview", response_text[:500])
787
- llm_span.set_attr("context_docs_used", len(source_docs))
788
-
789
- # Display Answer
790
- response_text_arabic = convert_to_eastern_arabic(response_text)
791
- st.markdown(response_text_arabic)
792
-
793
- # Display Sources
794
- if source_docs and len(source_docs) > 0:
795
- print(f"✅ Found {len(source_docs)} documents")
796
- # Deduplicate documents by article_number
797
- seen_articles = set()
798
- unique_docs = []
799
-
800
- for doc in source_docs:
801
- article_num = str(doc.metadata.get('article_number', '')).strip()
802
- if article_num and article_num not in seen_articles:
803
- seen_articles.add(article_num)
804
- unique_docs.append(doc)
805
-
806
- st.markdown("---") # Separator before sources
807
-
808
- if unique_docs:
809
- with st.expander(f"📚 المصادر المستخدمة ({len(unique_docs)} مادة)"):
810
- st.markdown("### المواد الدستورية المستخدمة في التحليل:")
811
- st.markdown("---")
812
-
813
- for idx, doc in enumerate(unique_docs, 1):
814
- article_num = str(doc.metadata.get('article_number', '')).strip()
815
- legal_nature = doc.metadata.get('legal_nature', '')
816
-
817
- if article_num:
818
- st.markdown(f"**المادة رقم {convert_to_eastern_arabic(article_num)}**")
819
- if legal_nature:
820
- st.markdown(f"*الطبيعة القانونية: {legal_nature}*")
821
-
822
- # Display article content
823
- content_lines = doc.page_content.strip().split('\n')
824
- for line in content_lines:
825
- line = line.strip()
826
- if line:
827
- st.markdown(convert_to_eastern_arabic(line))
828
-
829
- st.markdown("---")
830
- else:
831
- st.info("📌 لم يتم العثور على مصادر")
832
- else:
833
- st.info("📌 لم يتم العثور على مصادر")
834
-
835
- # Persist the raw answer to avoid double conversion glitches on rerun
836
- st.session_state.messages.append({"role": "assistant", "content": response_text})
837
- except Exception as e:
838
- st.error(f"حدث خطأ: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app_final_updated.py DELETED
@@ -1,704 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import os
3
- import sys
4
- import json
5
- from dotenv import load_dotenv
6
- import logging
7
- import warnings
8
-
9
- # Suppress progress bars from transformers/tqdm
10
- os.environ['TRANSFORMERS_NO_PROGRESS_BAR'] = '1'
11
- warnings.filterwarnings('ignore')
12
-
13
- # 1. Loaders & Splitters
14
- from langchain_core.documents import Document
15
- #from langchain_text_splitters import RecursiveCharacterTextSplitter
16
- from langchain_core.retrievers import BaseRetriever
17
- from langchain_core.callbacks import CallbackManagerForRetrieverRun
18
- from typing import List
19
- from rank_bm25 import BM25Okapi
20
- import numpy as np
21
-
22
- # 2. Vector Store & Embeddings
23
- from langchain_chroma import Chroma
24
- from langchain_huggingface import HuggingFaceEmbeddings
25
-
26
- # 3. Reranker Imports
27
- from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
28
- from langchain_classic.retrievers import ContextualCompressionRetriever
29
- from langchain_community.cross_encoders import HuggingFaceCrossEncoder
30
-
31
- # 4. LLM
32
- from langchain_groq import ChatGroq
33
- from langchain_core.prompts import ChatPromptTemplate
34
- from langchain_core.output_parsers import StrOutputParser
35
- from langchain_core.runnables import RunnablePassthrough, RunnableParallel
36
-
37
- # Configure logging
38
- logging.basicConfig(level=logging.INFO)
39
- logger = logging.getLogger(__name__)
40
-
41
- load_dotenv()
42
-
43
- # ==========================================
44
- # 🧭 RUNTIME MODE (UI vs CLI)
45
- # ==========================================
46
- RUN_MODE = os.getenv("RUN_MODE", "").strip().lower()
47
- IS_CLI = RUN_MODE in {"cli", "terminal", "eval", "evaluation"}
48
-
49
- if IS_CLI:
50
- class _DummyStreamlit:
51
- @staticmethod
52
- def cache_resource(func=None, **_kwargs):
53
- if func is None:
54
- def decorator(f):
55
- return f
56
- return decorator
57
- return func
58
-
59
- st = _DummyStreamlit()
60
- else:
61
- import streamlit as st
62
-
63
- # ==========================================
64
- # 📁 PATHS (use project-relative folders)
65
- # ==========================================
66
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
67
- DATA_DIR = os.path.join(BASE_DIR, "data")
68
- CHROMA_DIR = os.path.join(BASE_DIR, "chroma_db")
69
-
70
- if not IS_CLI:
71
- # ==========================================
72
- # 🎨 UI SETUP (CSS FOR ARABIC & RTL)
73
- # ==========================================
74
- st.set_page_config(page_title="المساعد القانوني", page_icon="⚖️")
75
-
76
- # This CSS block fixes the "001" number issue and right alignment
77
- st.markdown("""
78
- <style>
79
- /* Force the main app container to be Right-to-Left */
80
- .stApp {
81
- direction: rtl;
82
- text-align: right;
83
- }
84
-
85
- /* Fix input fields to type from right */
86
- .stTextInput input {
87
- direction: rtl;
88
- text-align: right;
89
- }
90
-
91
- /* Fix chat messages alignment */
92
- .stChatMessage {
93
- direction: rtl;
94
- text-align: right;
95
- }
96
-
97
- /* Ensure proper paragraph spacing */
98
- .stMarkdown p {
99
- margin: 0.5em 0 !important;
100
- line-height: 1.6;
101
- word-spacing: 0.1em;
102
- }
103
-
104
- /* Ensure numbers display correctly in RTL */
105
- p, div, span, label {
106
- unicode-bidi: embed;
107
- direction: inherit;
108
- white-space: normal;
109
- word-wrap: break-word;
110
- }
111
-
112
- /* Force all content to respect RTL */
113
- * {
114
- direction: rtl !important;
115
- }
116
-
117
- /* Preserve line breaks and spacing */
118
- .stMarkdown pre {
119
- direction: rtl;
120
- text-align: right;
121
- white-space: pre-wrap;
122
- word-wrap: break-word;
123
- }
124
-
125
- /* Hide the "Deploy" button and standard menu for cleaner look */
126
- #MainMenu {visibility: hidden;}
127
- footer {visibility: hidden;}
128
-
129
- </style>
130
- """, unsafe_allow_html=True)
131
-
132
- # Put this at the top of your code
133
- def convert_to_eastern_arabic(text):
134
- """Converts 0123456789 to ٠١٢٣٤٥٦٧٨٩"""
135
- if not isinstance(text, str):
136
- return text
137
- western_numerals = '0123456789'
138
- eastern_numerals = '٠١٢٣٤٥٦٧٨٩'
139
- translation_table = str.maketrans(western_numerals, eastern_numerals)
140
- return text.translate(translation_table)
141
-
142
- if not IS_CLI:
143
- st.title("⚖️ المساعد القانوني الذكي (دستور مصر)")
144
-
145
- # ==========================================
146
- # 🚀 CACHED RESOURCE LOADING (THE FIX)
147
- # ==========================================
148
- # This decorator tells Streamlit: "Run this ONCE and save the result."
149
- @st.cache_resource
150
- def initialize_rag_pipeline():
151
- print("🔄 Initializing system...")
152
- print("📥 Loading data...")
153
- # 1. Load JSONs from ./data (supports multiple files)
154
- def load_json_folder(folder_path: str):
155
- all_items = []
156
- for filename in os.listdir(folder_path):
157
- if not filename.lower().endswith(".json"):
158
- continue
159
- file_path = os.path.join(folder_path, filename)
160
- with open(file_path, "r", encoding="utf-8") as f:
161
- obj = json.load(f)
162
-
163
- # Support: list of articles, or dict with 'data'/'articles', or single dict article
164
- if isinstance(obj, list):
165
- all_items.extend(obj)
166
- elif isinstance(obj, dict):
167
- if "data" in obj and isinstance(obj["data"], list):
168
- all_items.extend(obj["data"])
169
- elif "articles" in obj and isinstance(obj["articles"], list):
170
- all_items.extend(obj["articles"])
171
- else:
172
- all_items.append(obj)
173
- else:
174
- logger.warning(f"Unsupported JSON format in: {file_path}")
175
- return all_items
176
-
177
- if not os.path.exists(DATA_DIR):
178
- raise FileNotFoundError(f"Data folder not found: {DATA_DIR}")
179
-
180
- data = load_json_folder(DATA_DIR)
181
-
182
- # Optional: de-duplicate (article_id preferred, fallback to article_number)
183
- unique = {}
184
- for item in data:
185
- key = str(item.get("article_id") or item.get("article_number") or hash(json.dumps(item, ensure_ascii=False)))
186
- unique[key] = item
187
- data = list(unique.values())
188
-
189
- # Create a mapping of article numbers for cross-reference lookup
190
- article_map = {str(item['article_number']): item for item in data if 'article_number' in item}
191
-
192
- docs = []
193
- for item in data:
194
- article_number = item.get("article_number")
195
- original_text = item.get("original_text")
196
- simplified_summary = item.get("simplified_summary")
197
-
198
- if not article_number or not original_text or not simplified_summary:
199
- logger.warning("Skipping item with missing fields (article_number/original_text/simplified_summary)")
200
- continue
201
-
202
- cross_refs = item.get("cross_references")
203
- if not isinstance(cross_refs, list):
204
- cross_refs = []
205
-
206
- # Build cross-reference section
207
- cross_ref_text = ""
208
- if cross_refs:
209
- cross_ref_text = "\nالمواد ذات الصلة (المراجع المتقاطعة): " + ", ".join(
210
- [f"المادة {ref}" for ref in cross_refs]
211
- )
212
-
213
- # Construct content
214
- page_content = f"""
215
- رقم المادة: {article_number}
216
- النص الأصلي: {original_text}
217
- الشرح المبسط: {simplified_summary}{cross_ref_text}
218
- """
219
-
220
- metadata = {
221
- "article_id": item.get("article_id") or str(article_number),
222
- "article_number": str(article_number),
223
- "legal_nature": item.get("legal_nature", ""),
224
- "keywords": ", ".join(item.get("keywords", []) or []),
225
- "part": item.get("part (Bab)", ""),
226
- "chapter": item.get("chapter (Fasl)", ""),
227
- "cross_references": ", ".join([str(ref) for ref in cross_refs])
228
- }
229
- docs.append(Document(page_content=page_content, metadata=metadata))
230
-
231
- print(f"✅ Loaded {len(docs)} constitutional articles")
232
-
233
- # 2. Embeddings
234
- print("Loading embeddings model...")
235
- embeddings = HuggingFaceEmbeddings(
236
- model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1"
237
- )
238
- print("✅ Embeddings model ready")
239
-
240
- # 3. No splitting - keep articles as complete units
241
- chunks = docs
242
- # 4. Vector Store (persist once, load on next runs)
243
- if os.path.exists(CHROMA_DIR) and os.listdir(CHROMA_DIR):
244
- print("📦 Loading existing vector database...")
245
- vectorstore = Chroma(
246
- persist_directory=CHROMA_DIR,
247
- embedding_function=embeddings
248
- )
249
- print("✅ Loaded existing Chroma DB (no re-embedding)")
250
- else:
251
- print("🧱 Building vector database for the first time (this will create embeddings)...")
252
- vectorstore = Chroma.from_documents(
253
- chunks,
254
- embeddings,
255
- persist_directory=CHROMA_DIR
256
- )
257
- print("✅ Built Chroma DB and persisted to disk")
258
-
259
- base_retriever = vectorstore.as_retriever(search_kwargs={"k": 15})
260
- # 5. Create BM25 Keyword Retriever
261
- class BM25Retriever(BaseRetriever):
262
- """BM25-based keyword retriever for constitutional articles"""
263
- corpus_docs: List[Document]
264
- bm25: BM25Okapi = None
265
- k: int = 15
266
-
267
- class Config:
268
- arbitrary_types_allowed = True
269
-
270
- def __init__(self, **data):
271
- super().__init__(**data)
272
- # Tokenize corpus for BM25
273
- tokenized_corpus = [doc.page_content.split() for doc in self.corpus_docs]
274
- self.bm25 = BM25Okapi(tokenized_corpus)
275
-
276
- def _get_relevant_documents(
277
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
278
- ) -> List[Document]:
279
- # Tokenize query
280
- tokenized_query = query.split()
281
- # Get BM25 scores
282
- scores = self.bm25.get_scores(tokenized_query)
283
- # Get top k indices
284
- top_indices = np.argsort(scores)[::-1][:self.k]
285
- # Return documents
286
- return [self.corpus_docs[i] for i in top_indices if scores[i] > 0]
287
-
288
- async def _aget_relevant_documents(
289
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
290
- ) -> List[Document]:
291
- return self._get_relevant_documents(query, run_manager=run_manager)
292
-
293
- bm25_retriever = BM25Retriever(corpus_docs=docs, k=15)
294
- print("✅ BM25 keyword retriever ready")
295
-
296
- # 6. Create Metadata Filter Retriever
297
- class MetadataFilterRetriever(BaseRetriever):
298
- """Metadata-based filtering retriever"""
299
- corpus_docs: List[Document]
300
- k: int = 15
301
-
302
- class Config:
303
- arbitrary_types_allowed = True
304
-
305
- def _get_relevant_documents(
306
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
307
- ) -> List[Document]:
308
- query_lower = query.lower()
309
- scored_docs = []
310
-
311
- for doc in self.corpus_docs:
312
- score = 0
313
- # Match keywords
314
- keywords = doc.metadata.get('keywords', '').lower()
315
- if any(word in keywords for word in query_lower.split()):
316
- score += 3
317
-
318
- # Match legal nature
319
- legal_nature = doc.metadata.get('legal_nature', '').lower()
320
- if any(word in legal_nature for word in query_lower.split()):
321
- score += 2
322
-
323
- # Match part/chapter
324
- part = doc.metadata.get('part', '').lower()
325
- chapter = doc.metadata.get('chapter', '').lower()
326
- if any(word in part or word in chapter for word in query_lower.split()):
327
- score += 1
328
-
329
- # Match in content
330
- if any(word in doc.page_content.lower() for word in query_lower.split()):
331
- score += 1
332
-
333
- if score > 0:
334
- scored_docs.append((doc, score))
335
-
336
- # Sort by score and return top k
337
- scored_docs.sort(key=lambda x: x[1], reverse=True)
338
- return [doc for doc, _ in scored_docs[:self.k]]
339
-
340
- async def _aget_relevant_documents(
341
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
342
- ) -> List[Document]:
343
- return self._get_relevant_documents(query, run_manager=run_manager)
344
-
345
- metadata_retriever = MetadataFilterRetriever(corpus_docs=docs, k=15)
346
- print("✅ Metadata filter retriever ready")
347
-
348
- # 7. Create Hybrid RRF Retriever
349
- class HybridRRFRetriever(BaseRetriever):
350
- """Combines semantic, BM25, and metadata retrievers using Reciprocal Rank Fusion"""
351
- semantic_retriever: BaseRetriever
352
- bm25_retriever: BM25Retriever
353
- metadata_retriever: MetadataFilterRetriever
354
- beta_semantic: float = 0.6 # Weight for semantic search
355
- beta_keyword: float = 0.2 # Weight for BM25 keyword search
356
- beta_metadata: float = 0.2 # Weight for metadata filtering
357
- k: int = 60 # RRF constant (typically 60)
358
- top_k: int = 15
359
-
360
- class Config:
361
- arbitrary_types_allowed = True
362
-
363
- def _get_relevant_documents(
364
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
365
- ) -> List[Document]:
366
- # Get results from all three retrievers
367
- semantic_docs = self.semantic_retriever.invoke(query)
368
- bm25_docs = self.bm25_retriever.invoke(query)
369
- metadata_docs = self.metadata_retriever.invoke(query)
370
-
371
- # Apply Reciprocal Rank Fusion
372
- rrf_scores = {}
373
-
374
- # Process semantic results
375
- for rank, doc in enumerate(semantic_docs, start=1):
376
- doc_id = (doc.metadata.get('article_id') or doc.metadata.get('article_number') or str(hash(doc.page_content)))
377
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_semantic / (self.k + rank)
378
-
379
- # Process BM25 results
380
- for rank, doc in enumerate(bm25_docs, start=1):
381
- doc_id = (doc.metadata.get('article_id') or doc.metadata.get('article_number') or str(hash(doc.page_content)))
382
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_keyword / (self.k + rank)
383
-
384
- # Process metadata results
385
- for rank, doc in enumerate(metadata_docs, start=1):
386
- doc_id = (doc.metadata.get('article_id') or doc.metadata.get('article_number') or str(hash(doc.page_content)))
387
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_metadata / (self.k + rank)
388
-
389
- # Create document lookup
390
- all_docs = {}
391
- for doc in semantic_docs + bm25_docs + metadata_docs:
392
- doc_id = (doc.metadata.get('article_id') or doc.metadata.get('article_number') or str(hash(doc.page_content)))
393
- if doc_id not in all_docs:
394
- all_docs[doc_id] = doc
395
-
396
- # Sort by RRF score
397
- sorted_doc_ids = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
398
-
399
- # Return top k documents
400
- result_docs = []
401
- for doc_id, score in sorted_doc_ids[:self.top_k]:
402
- if doc_id in all_docs:
403
- result_docs.append(all_docs[doc_id])
404
-
405
- return result_docs
406
-
407
- async def _aget_relevant_documents(
408
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
409
- ) -> List[Document]:
410
- return self._get_relevant_documents(query, run_manager=run_manager)
411
-
412
- # Create hybrid retriever with tuned beta weights
413
- hybrid_retriever = HybridRRFRetriever(
414
- semantic_retriever=base_retriever,
415
- bm25_retriever=bm25_retriever,
416
- metadata_retriever=metadata_retriever,
417
- beta_semantic=0.5, # Semantic search gets highest weight (most reliable)
418
- beta_keyword=0.3, # BM25 keyword search (good for exact term matches)
419
- beta_metadata=0.2, # Metadata filtering (supporting role)
420
- k=60,
421
- top_k=20
422
- )
423
- print("✅ Hybrid RRF retriever ready with β weights: semantic=0.5, keyword=0.3, metadata=0.2")
424
-
425
- # 8. Create Cross-Reference Enhanced Retriever
426
- class CrossReferenceRetriever(BaseRetriever):
427
- """Enhances retrieval by automatically fetching cross-referenced articles"""
428
- base_retriever: BaseRetriever
429
- article_map: dict
430
-
431
- class Config:
432
- arbitrary_types_allowed = True
433
-
434
- def _get_relevant_documents(
435
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
436
- ) -> List[Document]:
437
- # Get initial results
438
- initial_docs = self.base_retriever.invoke(query)
439
-
440
- # Collect all related article numbers
441
- all_article_numbers = set()
442
- for doc in initial_docs:
443
- if 'article_number' in doc.metadata:
444
- all_article_numbers.add(doc.metadata['article_number'])
445
- # Parse cross_references (now stored as comma-separated string)
446
- cross_refs_str = doc.metadata.get('cross_references', '')
447
- if cross_refs_str:
448
- cross_refs = [ref.strip() for ref in cross_refs_str.split(',')]
449
- for ref in cross_refs:
450
- if ref: # Skip empty strings
451
- all_article_numbers.add(str(ref))
452
-
453
- # Build enhanced document list
454
- enhanced_docs = []
455
- seen_numbers = set()
456
-
457
- # Add initially retrieved documents
458
- for doc in initial_docs:
459
- enhanced_docs.append(doc)
460
- seen_numbers.add(doc.metadata.get('article_number'))
461
-
462
- # Add cross-referenced articles not yet retrieved
463
- for article_num in all_article_numbers:
464
- if article_num not in seen_numbers and article_num in self.article_map:
465
- article_data = self.article_map[article_num]
466
- cross_ref_text = ""
467
- cross_refs = article_data.get("cross_references")
468
- if not isinstance(cross_refs, list):
469
- cross_refs = []
470
- if cross_refs:
471
- cross_ref_text = "\nالمواد ذات الصلة: " + ", ".join(
472
- [f"المادة {ref}" for ref in cross_refs]
473
- )
474
-
475
- page_content = f"""
476
- رقم المادة: {article_data.get('article_number', '')}
477
- النص الأصلي: {article_data.get('original_text', '')}
478
- الشرح المبسط: {article_data.get('simplified_summary', '')}{cross_ref_text}
479
- """
480
-
481
- enhanced_doc = Document(
482
- page_content=page_content,
483
- metadata={
484
- "article_id": article_data.get("article_id") or str(article_data.get("article_number", "")),
485
- "article_number": str(article_data.get("article_number", "")),
486
- "legal_nature": article_data.get("legal_nature", ""),
487
- "keywords": ", ".join(article_data.get("keywords", []) or []),
488
- "cross_references": ", ".join([str(ref) for ref in cross_refs])
489
- }
490
- )
491
- enhanced_docs.append(enhanced_doc)
492
- seen_numbers.add(article_num)
493
-
494
- return enhanced_docs
495
-
496
- async def _aget_relevant_documents(
497
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
498
- ) -> List[Document]:
499
- return self._get_relevant_documents(query, run_manager=run_manager)
500
-
501
- cross_ref_retriever = CrossReferenceRetriever(
502
- base_retriever=hybrid_retriever,
503
- article_map=article_map
504
- )
505
- print("✅ Cross-reference retriever ready (using hybrid RRF base)")
506
-
507
- # 9. Reranker
508
- print("Loading reranker model...")
509
- local_model_path = r"D:\FOE\Senior 2\Graduation Project\Chatbot_me\reranker"
510
-
511
- if not os.path.exists(local_model_path):
512
- raise FileNotFoundError(f"Reranker path not found: {local_model_path}")
513
-
514
- model = HuggingFaceCrossEncoder(model_name=local_model_path)
515
- compressor = CrossEncoderReranker(model=model, top_n=5)
516
-
517
- compression_retriever = ContextualCompressionRetriever(
518
- base_compressor=compressor,
519
- base_retriever=cross_ref_retriever
520
- )
521
- print("✅ Reranker model ready")
522
-
523
- # 7. LLM - Balanced for consistency with slight creativity
524
- # 7. LLM Configuration
525
- llm = ChatGroq(
526
- groq_api_key=os.getenv("GROQ_API_KEY"),
527
- model_name="llama-3.1-8b-instant",
528
- temperature=0.3, # Slightly increased to allow helpful general advice
529
- model_kwargs={"top_p": 0.9}
530
- )
531
-
532
- # ==================================================
533
- # 🛠️ THE FIX: SEPARATE SYSTEM INSTRUCTIONS FROM USER INPUT
534
- # ==================================================
535
-
536
- # ==================================================
537
- # 🧠 PROMPT ENGINEERING: DECISION TREE LOGIC
538
- # ==================================================
539
-
540
- system_instructions = """
541
- <role>
542
- أنت "المساعد القانوني الذكي"، خبير متخصص في الدستور المصري والقوانين الإجرائية.
543
- مهمتك: تقديم إجابات دقيقة بناءً على "السياق التشريعي" المرفق أولاً، أو تقديم نصائح إجرائية عامة عند الضرورة.
544
- </role>
545
-
546
- <decision_logic>
547
- عليك تحليل "سؤال المستخدم" و"السياق التشريعي" وتصنيف الحالة واختيار الرد المناسب بناءً على القواعد التالية بدقة:
548
-
549
- 🔴 الحالة الأولى: (الإجابة موجودة في السياق التشريعي)
550
- الشرط: إذا وجدت معلومات داخل "السياق التشريعي المتاح" تجيب على السؤال.
551
- الفعل:
552
- 1. استخرج الإجابة من السياق فقط.
553
- 2. ابدأ الإجابة مباشرة دون مقدمات.
554
- 3. يجب توثيق الإجابة برقم المادة (مثال: "نصت المادة (50) على...").
555
- 4. توقف هنا. لا تضف أي معلومات خارجية.
556
-
557
- 🟡 الحالة الثانية: (السياق فارغ/غير مفيد + السؤال إجرائي/عملي)
558
- الشرط: إذا لم تجد الإجابة في السياق، وكان السؤال عن إجراءات عملية (مثل: حادث، سرقة، طلاق، تحرير محضر، تعامل مع الشرطة).
559
- الفعل:
560
- 1. تجاهل السياق الفارغ.
561
- 2. استخدم معرفتك العامة بالقانون المصري.
562
- 3. ابدأ وجوباً بعبارة: "بناءً على الإجراءات القانونية العامة في مصر (وليس نصاً دستورياً محدداً):"
563
- 4. قدم الخطوات في نقاط مرقمة واضحة ومختصرة (1، 2، 3).
564
- 5. تحذير: لا تذكر أرقام مواد قانونية (لا تخترع أرقام مواد).
565
-
566
- 🔵 الحالة الثالثة: (السياق فارغ + السؤال عن نص دستوري محدد)
567
- الشرط: إذا سأل عن (مجلس الشعب، الشورى، مادة محددة) ولم تجدها في السياق.
568
- الفعل:
569
- 1. قل بوضوح: "عذراً، لم يرد ذكر لهذا الموضوع في المواد الدستورية التي تم استرجاعها في السياق الحالي."
570
- 2. لا تحاول الإجابة من ذاكرتك لكي لا تخطئ في النصوص الدستورية الحساسة.
571
-
572
- 🟢 الحالة الرابعة: (محادثة ودية)
573
- الشرط: تحية، شكر، أو "كيف حالك".
574
- الفعل: رد بتحية مهذبة جداً ومقتضبة، ثم قل: "أنا جاهز للإجابة على استفساراتك القانونية."
575
-
576
- ⚫ الحالة الخامسة: (خارج النطاق تماماً)
577
- الشرط: طبخ، رياضة، برمجة، أو أي موضوع غير قانوني.
578
- الفعل: اعتذر بلطف ووجه المستخدم للسؤال في القانون.
579
- </decision_logic>
580
-
581
- <formatting_rules>
582
- - لا تكرر هذه التعليمات في ردك.
583
- - استخدم فقرات قصيرة واترك سطراً فارغاً بينها.
584
- - لا تستخدم عبارات مثل "بناء على السياق المرفق" في بداية الجملة، بل ادخل في صلب الموضوع فوراً.
585
- - التزم باللغة العربية الفصحى المبسطة والرصينة.
586
- </formatting_rules>
587
- """
588
-
589
- # We use .from_messages to strictly separate instructions from data
590
- prompt = ChatPromptTemplate.from_messages([
591
- ("system", system_instructions),
592
- ("system", "السياق التشريعي المتاح (المصدر الأساسي):\n{context}"),
593
- ("human", "سؤال المستفيد:\n{input}")
594
- ])
595
-
596
- # 9. Build Chain with RunnableParallel (returns both context and answer)
597
- qa_chain = (
598
- RunnableParallel({
599
- "context": compression_retriever,
600
- "input": RunnablePassthrough()
601
- })
602
- .assign(answer=(
603
- prompt
604
- | llm
605
- | StrOutputParser()
606
- ))
607
- )
608
-
609
- print("✅ System ready to use!")
610
- return qa_chain
611
-
612
- if not IS_CLI:
613
- # ==========================================
614
- # ⚡ MAIN EXECUTION
615
- # ==========================================
616
-
617
- try:
618
- # Only need the chain now - it handles all retrieval internally
619
- qa_chain = initialize_rag_pipeline()
620
-
621
- except Exception as e:
622
- st.error(f"Critical Error loading application: {e}")
623
- st.stop()
624
-
625
- # ==========================================
626
- # 💬 CHAT LOOP
627
- # ==========================================
628
- if "messages" not in st.session_state:
629
- st.session_state.messages = []
630
-
631
- # Display Chat History (with Eastern Arabic numerals)
632
- for message in st.session_state.messages:
633
- with st.chat_message(message["role"]):
634
- # Convert to Eastern Arabic when displaying from history
635
- st.markdown(convert_to_eastern_arabic(message["content"]))
636
-
637
- # Handle New User Input
638
- if prompt_input := st.chat_input("اكتب سؤالك القانوني هنا..."):
639
- # Show user message
640
- st.session_state.messages.append({"role": "user", "content": prompt_input})
641
- with st.chat_message("user"):
642
- st.markdown(prompt_input)
643
-
644
- # Generate Response
645
- with st.chat_message("assistant"):
646
- with st.spinner("جاري التحليل القانوني..."):
647
- try:
648
- # Invoke chain ONCE - returns Dict with 'context', 'input', and 'answer'
649
- result = qa_chain.invoke(prompt_input)
650
-
651
- # Extract answer and context from result
652
- response_text = result["answer"]
653
- source_docs = result["context"] # Context is already in the result!
654
-
655
- # Display Answer
656
- response_text_arabic = convert_to_eastern_arabic(response_text)
657
- st.markdown(response_text_arabic)
658
-
659
- # Display Sources
660
- if source_docs and len(source_docs) > 0:
661
- print(f"✅ Found {len(source_docs)} documents")
662
- # Deduplicate documents by article_number
663
- seen_articles = set()
664
- unique_docs = []
665
-
666
- for doc in source_docs:
667
- article_num = str(doc.metadata.get('article_number', '')).strip()
668
- if article_num and article_num not in seen_articles:
669
- seen_articles.add(article_num)
670
- unique_docs.append(doc)
671
-
672
- st.markdown("---") # Separator before sources
673
-
674
- if unique_docs:
675
- with st.expander(f"📚 المصادر المستخدمة ({len(unique_docs)} مادة)"):
676
- st.markdown("### المواد الدستورية المستخدمة في التحليل:")
677
- st.markdown("---")
678
-
679
- for idx, doc in enumerate(unique_docs, 1):
680
- article_num = str(doc.metadata.get('article_number', '')).strip()
681
- legal_nature = doc.metadata.get('legal_nature', '')
682
-
683
- if article_num:
684
- st.markdown(f"**المادة رقم {convert_to_eastern_arabic(article_num)}**")
685
- if legal_nature:
686
- st.markdown(f"*الطبيعة القانونية: {legal_nature}*")
687
-
688
- # Display article content
689
- content_lines = doc.page_content.strip().split('\n')
690
- for line in content_lines:
691
- line = line.strip()
692
- if line:
693
- st.markdown(convert_to_eastern_arabic(line))
694
-
695
- st.markdown("---")
696
- else:
697
- st.info("📌 لم يتم العثور على مصادر")
698
- else:
699
- st.info("📌 لم يتم العثور على مصادر")
700
-
701
- # Persist the raw answer to avoid double conversion glitches on rerun
702
- st.session_state.messages.append({"role": "assistant", "content": response_text})
703
- except Exception as e:
704
- st.error(f"حدث خطأ: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
data/Egyptian_Civil.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/Egyptian_Constitution_legalnature_only.json CHANGED
@@ -1,8 +1,4 @@
1
  [
2
- {
3
- "law_key": "egyptian_constitution",
4
- "law_name": "الدستور المصري",
5
- "data": [
6
  {
7
  "article_id": "EG-CONST-ART-001",
8
  "article_number": "1",
@@ -16,7 +12,6 @@
16
  "الديمقراطية",
17
  "جمهورية مصر العربية"
18
  ],
19
- "cross_references": [],
20
  "legal_nature": "مبدأ دستوري"
21
  },
22
  {
@@ -32,7 +27,6 @@
32
  "الشريعة الإسلامية",
33
  "التشريع"
34
  ],
35
- "cross_references": [],
36
  "legal_nature": "مبدأ دستوري"
37
  },
38
  {
@@ -48,7 +42,6 @@
48
  "اليهود",
49
  "الشئون الدينية"
50
  ],
51
- "cross_references": [],
52
  "legal_nature": "مبدأ دستوري"
53
  },
54
  {
@@ -65,7 +58,6 @@
65
  "المساواة",
66
  "تكافؤ الفرص"
67
  ],
68
- "cross_references": [],
69
  "legal_nature": "مبدأ دستوري"
70
  },
71
  {
@@ -81,7 +73,6 @@
81
  "الفصل بين السلطات",
82
  "حقوق الإنسان"
83
  ],
84
- "cross_references": [],
85
  "legal_nature": "مبدأ دستوري"
86
  },
87
  {
@@ -97,7 +88,6 @@
97
  "القانون",
98
  "اكتساب الجنسية"
99
  ],
100
- "cross_references": [],
101
  "legal_nature": "حق أساسي/حرية"
102
  },
103
  {
@@ -113,7 +103,6 @@
113
  "اللغة العربية",
114
  "شيخ الأزهر"
115
  ],
116
- "cross_references": [],
117
  "legal_nature": "مبدأ دستوري"
118
  },
119
  {
@@ -129,7 +118,6 @@
129
  "التكافل الاجتماعي",
130
  "الحياة الكريمة"
131
  ],
132
- "cross_references": [],
133
  "legal_nature": "مبدأ دستوري"
134
  },
135
  {
@@ -144,7 +132,6 @@
144
  "عدم التمييز",
145
  "المواطنة"
146
  ],
147
- "cross_references": [],
148
  "legal_nature": "مبدأ دستوري"
149
  },
150
  {
@@ -161,7 +148,6 @@
161
  "الأخلاق",
162
  "الوطنية"
163
  ],
164
- "cross_references": [],
165
  "legal_nature": "مبدأ دستوري"
166
  },
167
  {
@@ -178,7 +164,6 @@
178
  "المجالس النيابية",
179
  "حماية الأمومة والطفولة"
180
  ],
181
- "cross_references": [],
182
  "legal_nature": "حق أساسي/حرية"
183
  },
184
  {
@@ -194,7 +179,6 @@
194
  "العمل الجبري",
195
  "الخدمة العامة"
196
  ],
197
- "cross_references": [],
198
  "legal_nature": "حق أساسي/حرية"
199
  },
200
  {
@@ -211,7 +195,6 @@
211
  "السلامة المهنية",
212
  "الفصل التعسفي"
213
  ],
214
- "cross_references": [],
215
  "legal_nature": "حق أساسي/حرية"
216
  },
217
  {
@@ -227,7 +210,6 @@
227
  "حقوق الموظفين",
228
  "التأديب"
229
  ],
230
- "cross_references": [],
231
  "legal_nature": "حق أساسي/حرية"
232
  },
233
  {
@@ -241,7 +223,6 @@
241
  "الإضراب السلمي",
242
  "القانون"
243
  ],
244
- "cross_references": [],
245
  "legal_nature": "حق أساسي/حرية"
246
  },
247
  {
@@ -258,7 +239,6 @@
258
  "رعاية الأسر",
259
  "المجتمع المدني"
260
  ],
261
- "cross_references": [],
262
  "legal_nature": "التزام/واجب على الدولة"
263
  },
264
  {
@@ -275,7 +255,6 @@
275
  "العمالة غير المنتظمة",
276
  "أموال التأمينات"
277
  ],
278
- "cross_references": [],
279
  "legal_nature": "حق أساسي/حرية"
280
  },
281
  {
@@ -292,7 +271,6 @@
292
  "الطوارئ",
293
  "القطاع الصحي"
294
  ],
295
- "cross_references": [],
296
  "legal_nature": "حق أساسي/حرية"
297
  },
298
  {
@@ -309,7 +287,6 @@
309
  "الهوية الوطنية",
310
  "جودة التعليم"
311
  ],
312
- "cross_references": [],
313
  "legal_nature": "حق أساسي/حرية"
314
  },
315
  {
@@ -325,7 +302,6 @@
325
  "سوق العمل",
326
  "الجودة العالمية"
327
  ],
328
- "cross_references": [],
329
  "legal_nature": "التزام/واجب على الدولة"
330
  },
331
  {
@@ -342,7 +318,6 @@
342
  "الجامعات الأهلية",
343
  "الجامعات الخاصة"
344
  ],
345
- "cross_references": [],
346
  "legal_nature": "حق أساسي/حرية"
347
  },
348
  {
@@ -358,7 +333,6 @@
358
  "حقوق المعلمين",
359
  "جودة التعليم"
360
  ],
361
- "cross_references": [],
362
  "legal_nature": "التزام/واجب على الدولة"
363
  },
364
  {
@@ -375,7 +349,6 @@
375
  "اقتصاد المعرفة",
376
  "المخترعين"
377
  ],
378
- "cross_references": [],
379
  "legal_nature": "حق أساسي/حرية"
380
  },
381
  {
@@ -392,7 +365,6 @@
392
  "حقوق الإنسان",
393
  "الأخلاق المهنية"
394
  ],
395
- "cross_references": [],
396
  "legal_nature": "مبدأ دستوري"
397
  },
398
  {
@@ -408,7 +380,6 @@
408
  "المجتمع المدني",
409
  "خطة شاملة"
410
  ],
411
- "cross_references": [],
412
  "legal_nature": "التزام/واجب على الدولة"
413
  },
414
  {
@@ -422,7 +393,6 @@
422
  "الرتب المدنية",
423
  "حظر"
424
  ],
425
- "cross_references": [],
426
  "legal_nature": "حظر دستوري"
427
  },
428
  {
@@ -439,7 +409,6 @@
439
  "الشفافية",
440
  "منع الاحتكار"
441
  ],
442
- "cross_references": [],
443
  "legal_nature": "حق أساسي/حرية"
444
  },
445
  {
@@ -456,7 +425,6 @@
456
  "المشروعات الصغيرة",
457
  "القطاع غير الرسمي"
458
  ],
459
- "cross_references": [],
460
  "legal_nature": "التزام/واجب على الدولة"
461
  },
462
  {
@@ -473,7 +441,6 @@
473
  "دعم الفلاح",
474
  "الإنتاج الزراعي"
475
  ],
476
- "cross_references": [],
477
  "legal_nature": "التزام/واجب على الدولة"
478
  },
479
  {
@@ -488,7 +455,6 @@
488
  "حماية الصيادين",
489
  "البيئة"
490
  ],
491
- "cross_references": [],
492
  "legal_nature": "التزام/واجب على الدولة"
493
  },
494
  {
@@ -503,7 +469,6 @@
503
  "الأمن القومي",
504
  "الاقتصاد الرقمي"
505
  ],
506
- "cross_references": [],
507
  "legal_nature": "التزام/واجب على الدولة"
508
  },
509
  {
@@ -520,7 +485,6 @@
520
  "الاستثمار",
521
  "حقوق الأجيال القادمة"
522
  ],
523
- "cross_references": [],
524
  "legal_nature": "مبدأ دستوري"
525
  },
526
  {
@@ -536,7 +500,6 @@
536
  "الملكية التعاونية",
537
  "حماية الملكية"
538
  ],
539
- "cross_references": [],
540
  "legal_nature": "مبدأ دستوري"
541
  },
542
  {
@@ -550,7 +513,6 @@
550
  "الملكية العامة",
551
  "حرمة المال العام"
552
  ],
553
- "cross_references": [],
554
  "legal_nature": "التزام/واجب على الدولة"
555
  },
556
  {
@@ -566,7 +528,6 @@
566
  "نزع الملكية",
567
  "التعويض العادل"
568
  ],
569
- "cross_references": [],
570
  "legal_nature": "حق أساسي/حرية"
571
  },
572
  {
@@ -580,7 +541,6 @@
580
  "القطاع الخاص",
581
  "المسؤولية الاجتماعية"
582
  ],
583
- "cross_references": [],
584
  "legal_nature": "التزام/واجب على الدولة"
585
  },
586
  {
@@ -595,7 +555,6 @@
595
  "التعاونيات",
596
  "استقلال التعاونيات"
597
  ],
598
- "cross_references": [],
599
  "legal_nature": "حق أساسي/حرية"
600
  },
601
  {
@@ -611,7 +570,6 @@
611
  "العدالة الاجتماعية",
612
  "التهرب الضريبي"
613
  ],
614
- "cross_references": [],
615
  "legal_nature": "التزام/واجب على الدولة"
616
  },
617
  {
@@ -625,7 +583,6 @@
625
  "الادخار",
626
  "حماية المدخرات"
627
  ],
628
- "cross_references": [],
629
  "legal_nature": "التزام/واجب على الدولة"
630
  },
631
  {
@@ -640,7 +597,6 @@
640
  "المصادرة الخاصة",
641
  "حماية الأموال"
642
  ],
643
- "cross_references": [],
644
  "legal_nature": "حظر دستوري"
645
  },
646
  {
@@ -656,7 +612,6 @@
656
  "التنمية المستدامة",
657
  "الموارد البشرية"
658
  ],
659
- "cross_references": [],
660
  "legal_nature": "التزام/واجب على الدولة"
661
  },
662
  {
@@ -673,7 +628,6 @@
673
  "القطاع العام",
674
  "التعاونيات"
675
  ],
676
- "cross_references": [],
677
  "legal_nature": "حقوق وواجبات"
678
  },
679
  {
@@ -688,7 +642,6 @@
688
  "الممر المائي",
689
  "التنمية الاقتصادية"
690
  ],
691
- "cross_references": [],
692
  "legal_nature": "التزام/واجب على الدولة"
693
  },
694
  {
@@ -704,7 +657,6 @@
704
  "المياه الجوفية",
705
  "حماية البيئة النهرية"
706
  ],
707
- "cross_references": [],
708
  "legal_nature": "حقوق وواجبات"
709
  },
710
  {
@@ -720,7 +672,6 @@
720
  "الثروة السمكية والحيوانية",
721
  "الرفق بالحيوان"
722
  ],
723
- "cross_references": [],
724
  "legal_nature": "التزام/واجب على الدولة"
725
  },
726
  {
@@ -736,7 +687,6 @@
736
  "حقوق الأجيال القادمة",
737
  "الموارد الطبيعية"
738
  ],
739
- "cross_references": [],
740
  "legal_nature": "حقوق وواجبات"
741
  },
742
  {
@@ -751,7 +701,6 @@
751
  "الحضارة المصرية",
752
  "التنوع الثقافي"
753
  ],
754
- "cross_references": [],
755
  "legal_nature": "التزام/واجب على الدولة"
756
  },
757
  {
@@ -767,7 +716,6 @@
767
  "المناطق النائية",
768
  "الترجمة"
769
  ],
770
- "cross_references": [],
771
  "legal_nature": "حق أساسي/حرية"
772
  },
773
  {
@@ -783,7 +731,6 @@
783
  "حظر الاتجار",
784
  "جريمة لا تسقط بالتقادم"
785
  ],
786
- "cross_references": [],
787
  "legal_nature": "التزام/واجب على الدولة"
788
  },
789
  {
@@ -800,7 +747,6 @@
800
  "التعددية الثقافية",
801
  "الحضارة المصرية القديمة"
802
  ],
803
- "cross_references": [],
804
  "legal_nature": "التزام/واجب على الدولة"
805
  },
806
  {
@@ -815,7 +761,6 @@
815
  "حقوق الإنسان",
816
  "احترام الدولة"
817
  ],
818
- "cross_references": [],
819
  "legal_nature": "حق أساسي/حرية"
820
  },
821
  {
@@ -830,7 +775,6 @@
830
  "جريمة",
831
  "التقادم"
832
  ],
833
- "cross_references": [],
834
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
835
  },
836
  {
@@ -846,7 +790,6 @@
846
  "مفوضية منع التمييز",
847
  "الحض على الكراهية"
848
  ],
849
- "cross_references": [],
850
  "legal_nature": "حق أساسي/حرية"
851
  },
852
  {
@@ -863,7 +806,6 @@
863
  "حق الدفاع",
864
  "التعويض"
865
  ],
866
- "cross_references": [],
867
  "legal_nature": "حق أساسي/حرية"
868
  },
869
  {
@@ -879,7 +821,6 @@
879
  "بطلان الاعتراف",
880
  "حظر التعذيب"
881
  ],
882
- "cross_references": [],
883
  "legal_nature": "حق أساسي/حرية"
884
  },
885
  {
@@ -895,7 +836,6 @@
895
  "الإشراف القضائي",
896
  "الرعاية اللاحقة"
897
  ],
898
- "cross_references": [],
899
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
900
  },
901
  {
@@ -911,7 +851,6 @@
911
  "المراقبة",
912
  "وسائل الاتصال"
913
  ],
914
- "cross_references": [],
915
  "legal_nature": "حق أساسي/حرية"
916
  },
917
  {
@@ -926,7 +865,6 @@
926
  "تفتيش المنازل",
927
  "إذن قضائي"
928
  ],
929
- "cross_references": [],
930
  "legal_nature": "حق أساسي/حرية"
931
  },
932
  {
@@ -941,7 +879,6 @@
941
  "الأمن",
942
  "الطمأنينة"
943
  ],
944
- "cross_references": [],
945
  "legal_nature": "حق أساسي/حرية"
946
  },
947
  {
@@ -956,7 +893,6 @@
956
  "الاتجار بالأعضاء",
957
  "التجارب الطبية"
958
  ],
959
- "cross_references": [],
960
  "legal_nature": "قاعدة إجرائية"
961
  },
962
  {
@@ -971,7 +907,6 @@
971
  "زراعة الأعضاء",
972
  "الوصية"
973
  ],
974
- "cross_references": [],
975
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
976
  },
977
  {
@@ -987,7 +922,6 @@
987
  "المنع من السفر",
988
  "الإبعاد"
989
  ],
990
- "cross_references": [],
991
  "legal_nature": "حق أساسي/حرية"
992
  },
993
  {
@@ -1002,7 +936,6 @@
1002
  "جريمة",
1003
  "التقادم"
1004
  ],
1005
- "cross_references": [],
1006
  "legal_nature": "حظر وتجريم"
1007
  },
1008
  {
@@ -1018,7 +951,6 @@
1018
  "دور العبادة",
1019
  "الأديان السماوية"
1020
  ],
1021
- "cross_references": [],
1022
  "legal_nature": "حق أساسي/حرية"
1023
  },
1024
  {
@@ -1034,7 +966,6 @@
1034
  "حرية التعبير",
1035
  "النشر"
1036
  ],
1037
- "cross_references": [],
1038
  "legal_nature": "حق أساسي/حرية"
1039
  },
1040
  {
@@ -1049,7 +980,6 @@
1049
  "الباحثين",
1050
  "الابتكار"
1051
  ],
1052
- "cross_references": [],
1053
  "legal_nature": "حق أساسي/حرية"
1054
  },
1055
  {
@@ -1065,7 +995,6 @@
1065
  "حظر الحبس في جرائم النشر",
1066
  "النيابة العامة"
1067
  ],
1068
- "cross_references": [],
1069
  "legal_nature": "حق أساسي/حرية"
1070
  },
1071
  {
@@ -1081,7 +1010,6 @@
1081
  "الوثائق القومية",
1082
  "البيانات"
1083
  ],
1084
- "cross_references": [],
1085
  "legal_nature": "حق أساسي/حرية"
1086
  },
1087
  {
@@ -1095,7 +1023,6 @@
1095
  "الملكية الفكرية",
1096
  "حماية الحقوق"
1097
  ],
1098
- "cross_references": [],
1099
  "legal_nature": "التزام/واجب على الدولة"
1100
  },
1101
  {
@@ -1111,7 +1038,6 @@
1111
  "إصدار الصحف",
1112
  "البث الإذاعي"
1113
  ],
1114
- "cross_references": [],
1115
  "legal_nature": "حق أساسي/حرية"
1116
  },
1117
  {
@@ -1127,7 +1053,6 @@
1127
  "جرائم النشر",
1128
  "وقت الحرب"
1129
  ],
1130
- "cross_references": [],
1131
  "legal_nature": "حظر دستوري"
1132
  },
1133
  {
@@ -1143,7 +1068,6 @@
1143
  "الحياد",
1144
  "تعدد الآراء"
1145
  ],
1146
- "cross_references": [],
1147
  "legal_nature": "التزام/واجب على الدولة"
1148
  },
1149
  {
@@ -1159,7 +1083,6 @@
1159
  "الاحتجاج السلمي",
1160
  "الاجتماع الخاص"
1161
  ],
1162
- "cross_references": [],
1163
  "legal_nature": "حق أساسي/حرية"
1164
  },
1165
  {
@@ -1174,7 +1097,6 @@
1174
  "حظر الأحزاب الدينية",
1175
  "حل الأحزاب"
1176
  ],
1177
- "cross_references": [],
1178
  "legal_nature": "حق أساسي/حرية"
1179
  },
1180
  {
@@ -1190,7 +1112,6 @@
1190
  "حرية النشاط",
1191
  "حظر الجمعيات السرية"
1192
  ],
1193
- "cross_references": [],
1194
  "legal_nature": "حق أساسي/حرية"
1195
  },
1196
  {
@@ -1206,7 +1127,6 @@
1206
  "استقلال النقابات",
1207
  "الهيئات النظامية"
1208
  ],
1209
- "cross_references": [],
1210
  "legal_nature": "حق أساسي/حرية"
1211
  },
1212
  {
@@ -1221,7 +1141,6 @@
1221
  "فرض الحراسة",
1222
  "استقلال مهني"
1223
  ],
1224
- "cross_references": [],
1225
  "legal_nature": "إحالة للتنظيم التشريعي"
1226
  },
1227
  {
@@ -1237,7 +1156,6 @@
1237
  "التخطيط العمراني",
1238
  "العدالة الاجتماعية"
1239
  ],
1240
- "cross_references": [],
1241
  "legal_nature": "حقوق وواجبات"
1242
  },
1243
  {
@@ -1253,7 +1171,6 @@
1253
  "السيادة الغذائية",
1254
  "التنوع البيولوجي"
1255
  ],
1256
- "cross_references": [],
1257
  "legal_nature": "حق أساسي/حرية"
1258
  },
1259
  {
@@ -1270,7 +1187,6 @@
1270
  "عمالة الأطفال",
1271
  "قضاء الأحداث"
1272
  ],
1273
- "cross_references": [],
1274
  "legal_nature": "حق أساسي/حرية"
1275
  },
1276
  {
@@ -1286,7 +1202,6 @@
1286
  "الدمج",
1287
  "تخصيص فرص عمل"
1288
  ],
1289
- "cross_references": [],
1290
  "legal_nature": "حق أساسي/حرية"
1291
  },
1292
  {
@@ -1302,7 +1217,6 @@
1302
  "التمكين",
1303
  "العمل التطوعي"
1304
  ],
1305
- "cross_references": [],
1306
  "legal_nature": "التزام/واجب على الدولة"
1307
  },
1308
  {
@@ -1317,7 +1231,6 @@
1317
  "المعاشات",
1318
  "رعاية المسنين"
1319
  ],
1320
- "cross_references": [],
1321
  "legal_nature": "حق أساسي/حرية"
1322
  },
1323
  {
@@ -1332,7 +1245,6 @@
1332
  "الموهوبين رياضياً",
1333
  "الهيئات الرياضية"
1334
  ],
1335
- "cross_references": [],
1336
  "legal_nature": "حق أساسي/حرية"
1337
  },
1338
  {
@@ -1346,7 +1258,6 @@
1346
  "مخاطبة السلطات",
1347
  "الأشخاص الاعتبارية"
1348
  ],
1349
- "cross_references": [],
1350
  "legal_nature": "حق أساسي/حرية"
1351
  },
1352
  {
@@ -1361,7 +1272,6 @@
1361
  "الدفاع عن الوطن",
1362
  "التجنيد الإجباري"
1363
  ],
1364
- "cross_references": [],
1365
  "legal_nature": "التزام/واجب على الدولة"
1366
  },
1367
  {
@@ -1377,7 +1287,6 @@
1377
  "نزاهة الانتخابات",
1378
  "قاعدة بيانات الناخبين"
1379
  ],
1380
- "cross_references": [],
1381
  "legal_nature": "حقوق وواجبات"
1382
  },
1383
  {
@@ -1392,7 +1301,6 @@
1392
  "تصويت المغتربين",
1393
  "رعاية المصالح"
1394
  ],
1395
- "cross_references": [],
1396
  "legal_nature": "التزام/واجب على الدولة"
1397
  },
1398
  {
@@ -1407,7 +1315,6 @@
1407
  "الاتجار بالبشر",
1408
  "الاستغلال القسري"
1409
  ],
1410
- "cross_references": [],
1411
  "legal_nature": "حظر دستوري"
1412
  },
1413
  {
@@ -1422,7 +1329,6 @@
1422
  "استقلال الوقف",
1423
  "شروط الواقف"
1424
  ],
1425
- "cross_references": [],
1426
  "legal_nature": "التزام/واجب على الدولة"
1427
  },
1428
  {
@@ -1437,7 +1343,6 @@
1437
  "حظر تسليم اللاجئين",
1438
  "حقوق الإنسان"
1439
  ],
1440
- "cross_references": [],
1441
  "legal_nature": "حق أساسي/حرية"
1442
  },
1443
  {
@@ -1452,7 +1357,6 @@
1452
  "عدم الانتقاص",
1453
  "جوهر الحق"
1454
  ],
1455
- "cross_references": [],
1456
  "legal_nature": "إحالة للتنظيم التشريعي"
1457
  },
1458
  {
@@ -1467,7 +1371,6 @@
1467
  "حقوق الإنسان",
1468
  "قوة القانون"
1469
  ],
1470
- "cross_references": [],
1471
  "legal_nature": "التزام/واجب على الدولة"
1472
  },
1473
  {
@@ -1483,7 +1386,6 @@
1483
  "حصانة القضاء",
1484
  "حماية الحريات"
1485
  ],
1486
- "cross_references": [],
1487
  "legal_nature": "مبدأ دستوري"
1488
  },
1489
  {
@@ -1499,7 +1401,6 @@
1499
  "عدم الرجعية",
1500
  "حكم قضائي"
1501
  ],
1502
- "cross_references": [],
1503
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
1504
  },
1505
  {
@@ -1516,7 +1417,6 @@
1516
  "استئناف الجنايات",
1517
  "حماية الشهود"
1518
  ],
1519
- "cross_references": [],
1520
  "legal_nature": "حق أساسي/حرية"
1521
  },
1522
  {
@@ -1532,7 +1432,6 @@
1532
  "حظر المحاكم الاستثنائية",
1533
  "الرقابة القضائية"
1534
  ],
1535
- "cross_references": [],
1536
  "legal_nature": "حق أساسي/حرية"
1537
  },
1538
  {
@@ -1547,7 +1446,6 @@
1547
  "استقلال المحاماة",
1548
  "المساعدة القانونية"
1549
  ],
1550
- "cross_references": [],
1551
  "legal_nature": "حق أساسي/حرية"
1552
  },
1553
  {
@@ -1564,7 +1462,6 @@
1564
  "التعويض العادل",
1565
  "المجلس القومي لحقوق الإنسان"
1566
  ],
1567
- "cross_references": [],
1568
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
1569
  },
1570
  {
@@ -1581,7 +1478,6 @@
1581
  "الموظف العام",
1582
  "الدعوى الجنائية"
1583
  ],
1584
- "cross_references": [],
1585
  "legal_nature": "حق أساسي/حرية"
1586
  },
1587
  {
@@ -1597,7 +1493,6 @@
1597
  "الموازنة العامة",
1598
  "الرقابة"
1599
  ],
1600
- "cross_references": [],
1601
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1602
  },
1603
  {
@@ -1614,7 +1509,6 @@
1614
  "النظام الانتخابي",
1615
  "التعيين الرئاسي"
1616
  ],
1617
- "cross_references": [],
1618
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1619
  },
1620
  {
@@ -1628,7 +1522,6 @@
1628
  "تفرغ العضو",
1629
  "الاحتفاظ بالوظيفة"
1630
  ],
1631
- "cross_references": [],
1632
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1633
  },
1634
  {
@@ -1642,7 +1535,6 @@
1642
  "اليمين الدستورية",
1643
  "واجبات العضو"
1644
  ],
1645
- "cross_references": [],
1646
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1647
  },
1648
  {
@@ -1656,7 +1548,6 @@
1656
  "مكافأة العضوية",
1657
  "عدم سريان الزيادة"
1658
  ],
1659
- "cross_references": [],
1660
  "legal_nature": "مبدأ اقتصادي/مالي"
1661
  },
1662
  {
@@ -1670,7 +1561,6 @@
1670
  "مدة العضوية",
1671
  "موعد الانتخابات"
1672
  ],
1673
- "cross_references": [],
1674
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1675
  },
1676
  {
@@ -1685,7 +1575,6 @@
1685
  "محكمة النقض",
1686
  "الطعون الانتخابية"
1687
  ],
1688
- "cross_references": [],
1689
  "legal_nature": "اختصاص قضائي/محكمة"
1690
  },
1691
  {
@@ -1699,7 +1588,6 @@
1699
  "خلو المقعد",
1700
  "الانتخابات التكميلية"
1701
  ],
1702
- "cross_references": [],
1703
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1704
  },
1705
  {
@@ -1714,7 +1602,6 @@
1714
  "إقرار الذمة المالية",
1715
  "الهدايا"
1716
  ],
1717
- "cross_references": [],
1718
  "legal_nature": "قاعدة إجرائية"
1719
  },
1720
  {
@@ -1729,7 +1616,6 @@
1729
  "أغلبية الثلثين",
1730
  "فقد الثقة"
1731
  ],
1732
- "cross_references": [],
1733
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1734
  },
1735
  {
@@ -1743,7 +1629,6 @@
1743
  "الاستقالة",
1744
  "قبول الاستقالة"
1745
  ],
1746
- "cross_references": [],
1747
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1748
  },
1749
  {
@@ -1757,7 +1642,6 @@
1757
  "الحصانة البرلمانية",
1758
  "حرية الرأي"
1759
  ],
1760
- "cross_references": [],
1761
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1762
  },
1763
  {
@@ -1772,7 +1656,6 @@
1772
  "إذن المجلس",
1773
  "رفع الحصانة"
1774
  ],
1775
- "cross_references": [],
1776
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1777
  },
1778
  {
@@ -1787,7 +1670,6 @@
1787
  "انعقاد الجلسات",
1788
  "بطلان الاجتماع"
1789
  ],
1790
- "cross_references": [],
1791
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1792
  },
1793
  {
@@ -1802,7 +1684,6 @@
1802
  "دعوة الرئيس",
1803
  "اعتماد الموازنة"
1804
  ],
1805
- "cross_references": [],
1806
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1807
  },
1808
  {
@@ -1816,7 +1697,6 @@
1816
  "اجتماع غير عادي",
1817
  "دعوة طارئة"
1818
  ],
1819
- "cross_references": [],
1820
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1821
  },
1822
  {
@@ -1832,7 +1712,6 @@
1832
  "مدة الرئاسة",
1833
  "إعفاء الرئيس"
1834
  ],
1835
- "cross_references": [],
1836
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1837
  },
1838
  {
@@ -1846,7 +1725,6 @@
1846
  "اللائحة الداخلية",
1847
  "تنظيم العمل"
1848
  ],
1849
- "cross_references": [],
1850
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1851
  },
1852
  {
@@ -1860,7 +1738,6 @@
1860
  "حفظ النظام",
1861
  "رئيس المجلس"
1862
  ],
1863
- "cross_references": [],
1864
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1865
  },
1866
  {
@@ -1874,7 +1751,6 @@
1874
  "علنية الجلسات",
1875
  "جلسة سرية"
1876
  ],
1877
- "cross_references": [],
1878
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1879
  },
1880
  {
@@ -1890,7 +1766,6 @@
1890
  "القوانين المكملة للدستور",
1891
  "أغلبية الثلثين"
1892
  ],
1893
- "cross_references": [],
1894
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
1895
  },
1896
  {
@@ -1905,7 +1780,6 @@
1905
  "اللجان النوعية",
1906
  "لجنة المقترحات"
1907
  ],
1908
- "cross_references": [],
1909
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1910
  },
1911
  {
@@ -1920,7 +1794,6 @@
1920
  "إصدار القوانين",
1921
  "أغلبية الثلثين"
1922
  ],
1923
- "cross_references": [],
1924
  "legal_nature": "حق أساسي/حرية"
1925
  },
1926
  {
@@ -1936,7 +1809,6 @@
1936
  "تعديل النفقات",
1937
  "السنة المالية"
1938
  ],
1939
- "cross_references": [],
1940
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1941
  },
1942
  {
@@ -1951,7 +1823,6 @@
1951
  "الجهاز المركزي للمحاسبات",
1952
  "الرقابة المالية"
1953
  ],
1954
- "cross_references": [],
1955
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1956
  },
1957
  {
@@ -1965,7 +1836,6 @@
1965
  "الأموال العامة",
1966
  "التحصيل والصرف"
1967
  ],
1968
- "cross_references": [],
1969
  "legal_nature": "إحالة للتنظيم التشريعي"
1970
  },
1971
  {
@@ -1980,7 +1850,6 @@
1980
  "الديون العامة",
1981
  "موافقة البرلمان"
1982
  ],
1983
- "cross_references": [],
1984
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1985
  },
1986
  {
@@ -1995,7 +1864,6 @@
1995
  "المعاشات",
1996
  "الخزانة العامة"
1997
  ],
1998
- "cross_references": [],
1999
  "legal_nature": "إحالة للتنظيم التشريعي"
2000
  },
2001
  {
@@ -2009,7 +1877,6 @@
2009
  "توجيه الأسئلة",
2010
  "الرقابة البرلمانية"
2011
  ],
2012
- "cross_references": [],
2013
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2014
  },
2015
  {
@@ -2023,7 +1890,6 @@
2023
  "الاستجواب",
2024
  "المحاسبة السياسية"
2025
  ],
2026
- "cross_references": [],
2027
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2028
  },
2029
  {
@@ -2038,7 +1904,6 @@
2038
  "استقالة الحكومة",
2039
  "المسؤولية التضامنية"
2040
  ],
2041
- "cross_references": [],
2042
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2043
  },
2044
  {
@@ -2052,7 +1917,6 @@
2052
  "طلب مناقشة عامة",
2053
  "استيضاح السياسة"
2054
  ],
2055
- "cross_references": [],
2056
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2057
  },
2058
  {
@@ -2065,7 +1929,6 @@
2065
  "keywords": [
2066
  "اقتراح برغبة"
2067
  ],
2068
- "cross_references": [],
2069
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2070
  },
2071
  {
@@ -2079,7 +1942,6 @@
2079
  "طلب إحاطة",
2080
  "بيان عاجل"
2081
  ],
2082
- "cross_references": [],
2083
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2084
  },
2085
  {
@@ -2094,7 +1956,6 @@
2094
  "جمع الأدلة",
2095
  "حق المعلومات"
2096
  ],
2097
- "cross_references": [],
2098
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2099
  },
2100
  {
@@ -2108,7 +1969,6 @@
2108
  "حضور الحكومة",
2109
  "الاستماع للوزراء"
2110
  ],
2111
- "cross_references": [],
2112
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2113
  },
2114
  {
@@ -2123,7 +1983,6 @@
2123
  "الاستفتاء الشعبي",
2124
  "انتخابات مبكرة"
2125
  ],
2126
- "cross_references": [],
2127
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2128
  },
2129
  {
@@ -2138,7 +1997,6 @@
2138
  "الشكاوى",
2139
  "تواصل البرلمان"
2140
  ],
2141
- "cross_references": [],
2142
  "legal_nature": "حق أساسي/حرية"
2143
  },
2144
  {
@@ -2154,7 +2012,6 @@
2154
  "استقلال الوطن",
2155
  "احترام الدستور"
2156
  ],
2157
- "cross_references": [],
2158
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2159
  },
2160
  {
@@ -2170,7 +2027,6 @@
2170
  "إجراءات الانتخاب",
2171
  "حظر المناصب الحزبية"
2172
  ],
2173
- "cross_references": [],
2174
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2175
  },
2176
  {
@@ -2186,7 +2042,6 @@
2186
  "السن القانوني",
2187
  "الخدمة العسكرية"
2188
  ],
2189
- "cross_references": [],
2190
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2191
  },
2192
  {
@@ -2201,7 +2056,6 @@
2201
  "التوكيلات الشعبية",
2202
  "شروط الترشح"
2203
  ],
2204
- "cross_references": [],
2205
  "legal_nature": "حق أساسي/حرية"
2206
  },
2207
  {
@@ -2215,7 +2069,6 @@
2215
  "الاقتراع المباشر",
2216
  "الأغلبية المطلقة"
2217
  ],
2218
- "cross_references": [],
2219
  "legal_nature": "قاعدة إجرائية"
2220
  },
2221
  {
@@ -2229,7 +2082,6 @@
2229
  "اليمين الدستورية",
2230
  "تولي المهام"
2231
  ],
2232
- "cross_references": [],
2233
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2234
  },
2235
  {
@@ -2245,7 +2097,6 @@
2245
  "إقرار الذمة المالية",
2246
  "الهدايا"
2247
  ],
2248
- "cross_references": [],
2249
  "legal_nature": "إحالة للتنظيم التشريعي"
2250
  },
2251
  {
@@ -2261,7 +2112,6 @@
2261
  "حل المجلس",
2262
  "الوزارات السيادية"
2263
  ],
2264
- "cross_references": [],
2265
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2266
  },
2267
  {
@@ -2276,7 +2126,6 @@
2276
  "التعديل الوزاري",
2277
  "موافقة البرلمان"
2278
  ],
2279
- "cross_references": [],
2280
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2281
  },
2282
  {
@@ -2290,7 +2139,6 @@
2290
  "تفويض السلطات",
2291
  "التفويض الإداري"
2292
  ],
2293
- "cross_references": [],
2294
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2295
  },
2296
  {
@@ -2304,7 +2152,6 @@
2304
  "اجتماع الحكومة",
2305
  "رئاسة الاجتماع"
2306
  ],
2307
- "cross_references": [],
2308
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2309
  },
2310
  {
@@ -2318,9 +2165,6 @@
2318
  "السياسة العامة",
2319
  "بيان الرئيس"
2320
  ],
2321
- "cross_references": [
2322
- "الدستور"
2323
- ],
2324
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2325
  },
2326
  {
@@ -2335,12 +2179,6 @@
2335
  "تعيين النائب",
2336
  "اختصاصات النائب"
2337
  ],
2338
- "cross_references": [
2339
- "144",
2340
- "141",
2341
- "145",
2342
- "173"
2343
- ],
2344
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2345
  },
2346
  {
@@ -2356,9 +2194,6 @@
2356
  "الاستفتاء الشعبي",
2357
  "حظر التنازل عن الأرض"
2358
  ],
2359
- "cross_references": [
2360
- "الدستور"
2361
- ],
2362
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2363
  },
2364
  {
@@ -2374,7 +2209,6 @@
2374
  "إرسال القوات",
2375
  "مجلس الدفاع الوطني"
2376
  ],
2377
- "cross_references": [],
2378
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2379
  },
2380
  {
@@ -2389,7 +2223,6 @@
2389
  "الموظفين العموميين",
2390
  "الدبلوماسيين"
2391
  ],
2392
- "cross_references": [],
2393
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2394
  },
2395
  {
@@ -2404,7 +2237,6 @@
2404
  "موافقة البرلمان",
2405
  "تمديد الطوارئ"
2406
  ],
2407
- "cross_references": [],
2408
  "legal_nature": "تنظيم م��سسي/هيكلي"
2409
  },
2410
  {
@@ -2419,7 +2251,6 @@
2419
  "تخفيف العقوبة",
2420
  "العفو الشامل"
2421
  ],
2422
- "cross_references": [],
2423
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2424
  },
2425
  {
@@ -2434,7 +2265,6 @@
2434
  "الضرورة",
2435
  "مراجعة البرلمان"
2436
  ],
2437
- "cross_references": [],
2438
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2439
  },
2440
  {
@@ -2448,9 +2278,6 @@
2448
  "الاستفتاء الشعبي",
2449
  "المصالح العليا"
2450
  ],
2451
- "cross_references": [
2452
- "الدستور"
2453
- ],
2454
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2455
  },
2456
  {
@@ -2463,7 +2290,6 @@
2463
  "keywords": [
2464
  "استقالة الرئيس"
2465
  ],
2466
- "cross_references": [],
2467
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2468
  },
2469
  {
@@ -2479,7 +2305,6 @@
2479
  "المحكمة الخاصة",
2480
  "العزل"
2481
  ],
2482
- "cross_references": [],
2483
  "legal_nature": "اختصاص قضائي/محكمة"
2484
  },
2485
  {
@@ -2495,7 +2320,6 @@
2495
  "انتخابات رئاسية مبكرة",
2496
  "تقييد الصلاحيات"
2497
  ],
2498
- "cross_references": [],
2499
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2500
  },
2501
  {
@@ -2511,7 +2335,6 @@
2511
  "حل البرلمان",
2512
  "الاستفتاء"
2513
  ],
2514
- "cross_references": [],
2515
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2516
  },
2517
  {
@@ -2525,7 +2348,6 @@
2525
  "أسبقية الانتخابات",
2526
  "استمرار المجلس"
2527
  ],
2528
- "cross_references": [],
2529
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2530
  },
2531
  {
@@ -2540,7 +2362,6 @@
2540
  "رئيس مجلس الوزراء",
2541
  "الهيئة التنفيذية"
2542
  ],
2543
- "cross_references": [],
2544
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2545
  },
2546
  {
@@ -2556,7 +2377,6 @@
2556
  "الجنسية",
2557
  "حظر الجمع بين المناصب"
2558
  ],
2559
- "cross_references": [],
2560
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2561
  },
2562
  {
@@ -2570,7 +2390,6 @@
2570
  "اليمين الدستورية",
2571
  "تولي المهام"
2572
  ],
2573
- "cross_references": [],
2574
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2575
  },
2576
  {
@@ -2586,7 +2405,6 @@
2586
  "تعارض المصالح",
2587
  "حظر الأعمال التجارية"
2588
  ],
2589
- "cross_references": [],
2590
  "legal_nature": "إحالة للتنظيم التشريعي"
2591
  },
2592
  {
@@ -2603,7 +2421,6 @@
2603
  "الأمن القومي",
2604
  "إعداد القوانين"
2605
  ],
2606
- "cross_references": [],
2607
  "legal_nature": "حق أساسي/حرية"
2608
  },
2609
  {
@@ -2618,7 +2435,6 @@
2618
  "الوكيل الدائم",
2619
  "الاستقرار المؤسسي"
2620
  ],
2621
- "cross_references": [],
2622
  "legal_nature": "ضمان/حماية"
2623
  },
2624
  {
@@ -2633,7 +2449,6 @@
2633
  "مجلس النواب",
2634
  "المساءلة"
2635
  ],
2636
- "cross_references": [],
2637
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2638
  },
2639
  {
@@ -2648,7 +2463,6 @@
2648
  "التفويض",
2649
  "رئيس الوزراء"
2650
  ],
2651
- "cross_references": [],
2652
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2653
  },
2654
  {
@@ -2663,7 +2477,6 @@
2663
  "المصالح العامة",
2664
  "قرارات تنظيمية"
2665
  ],
2666
- "cross_references": [],
2667
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2668
  },
2669
  {
@@ -2677,7 +2490,6 @@
2677
  "لوائح الضبط",
2678
  "النظام العام"
2679
  ],
2680
- "cross_references": [],
2681
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2682
  },
2683
  {
@@ -2692,9 +2504,6 @@
2692
  "الجرائم الوظيفية",
2693
  "الخيانة العظمى"
2694
  ],
2695
- "cross_references": [
2696
- "159"
2697
- ],
2698
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2699
  },
2700
  {
@@ -2707,7 +2516,6 @@
2707
  "keywords": [
2708
  "الاستقالة"
2709
  ],
2710
- "cross_references": [],
2711
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2712
  },
2713
  {
@@ -2723,7 +2531,6 @@
2723
  "المحافظات",
2724
  "الوحدات المحلية"
2725
  ],
2726
- "cross_references": [],
2727
  "legal_nature": "قاعدة إجرائية"
2728
  },
2729
  {
@@ -2738,7 +2545,6 @@
2738
  "نقل السلطات",
2739
  "المرافق المحلية"
2740
  ],
2741
- "cross_references": [],
2742
  "legal_nature": "التزام/واجب على الدولة"
2743
  },
2744
  {
@@ -2753,7 +2559,6 @@
2753
  "توزيع الموارد",
2754
  "التنمية المحلية"
2755
  ],
2756
- "cross_references": [],
2757
  "legal_nature": "التزام/واجب على الدولة"
2758
  },
2759
  {
@@ -2768,7 +2573,6 @@
2768
  "الضرائب المحلية",
2769
  "الموارد المالية"
2770
  ],
2771
- "cross_references": [],
2772
  "legal_nature": "مبدأ اقتصادي/مالي"
2773
  },
2774
  {
@@ -2783,7 +2587,6 @@
2783
  "رؤساء الوحدات المحلية",
2784
  "التعيين والانتخاب"
2785
  ],
2786
- "cross_references": [],
2787
  "legal_nature": "إحالة للتنظيم التشريعي"
2788
  },
2789
  {
@@ -2800,7 +2603,6 @@
2800
  "الرقابة المحلية",
2801
  "سحب الثقة"
2802
  ],
2803
- "cross_references": [],
2804
  "legal_nature": "مبدأ اقتصادي/مالي"
2805
  },
2806
  {
@@ -2816,7 +2618,6 @@
2816
  "تنازع الاختصاص",
2817
  "مجلس الدولة"
2818
  ],
2819
- "cross_references": [],
2820
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2821
  },
2822
  {
@@ -2830,7 +2631,6 @@
2830
  "الموازنة المحلية",
2831
  "الحساب الختامي"
2832
  ],
2833
- "cross_references": [],
2834
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2835
  },
2836
  {
@@ -2844,7 +2644,6 @@
2844
  "حل المجالس المحلية",
2845
  "الحظر الإداري"
2846
  ],
2847
- "cross_references": [],
2848
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2849
  },
2850
  {
@@ -2860,7 +2659,6 @@
2860
  "التدخل في العدالة",
2861
  "عدم التقادم"
2862
  ],
2863
- "cross_references": [],
2864
  "legal_nature": "مبدأ دستوري"
2865
  },
2866
  {
@@ -2876,7 +2674,6 @@
2876
  "تعيين الرؤساء",
2877
  "المجلس الأعلى للجهات القضائية"
2878
  ],
2879
- "cross_references": [],
2880
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2881
  },
2882
  {
@@ -2892,7 +2689,6 @@
2892
  "المساءلة التأديبية",
2893
  "الندب"
2894
  ],
2895
- "cross_references": [],
2896
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2897
  },
2898
  {
@@ -2907,7 +2703,6 @@
2907
  "النطق بالحكم",
2908
  "النظام العام"
2909
  ],
2910
- "cross_references": [],
2911
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2912
  },
2913
  {
@@ -2922,7 +2717,6 @@
2922
  "مجلس القضاء الأعلى",
2923
  "الفصل في المنازعات"
2924
  ],
2925
- "cross_references": [],
2926
  "legal_nature": "إحالة للتنظيم التشريعي"
2927
  },
2928
  {
@@ -2938,7 +2732,6 @@
2938
  "الدعوى الجنائية",
2939
  "تعيين النائب العام"
2940
  ],
2941
- "cross_references": [],
2942
  "legal_nature": "اختصاص قضائي/محكمة"
2943
  },
2944
  {
@@ -2954,7 +2747,6 @@
2954
  "الإفتاء القانوني",
2955
  "مراجعة التشريعات"
2956
  ],
2957
- "cross_references": [],
2958
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2959
  },
2960
  {
@@ -2970,7 +2762,6 @@
2970
  "الموازنة المستقلة",
2971
  "الجمعية العامة"
2972
  ],
2973
- "cross_references": [],
2974
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2975
  },
2976
  {
@@ -2986,7 +2777,6 @@
2986
  "تنازع الاختصاص",
2987
  "الأحكام المتناقضة"
2988
  ],
2989
- "cross_references": [],
2990
  "legal_nature": "اختصاص قضائي/محكمة"
2991
  },
2992
  {
@@ -3002,7 +2792,6 @@
3002
  "تعيين القضاة",
3003
  "رئيس الجمهورية"
3004
  ],
3005
- "cross_references": [],
3006
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3007
  },
3008
  {
@@ -3017,7 +2806,6 @@
3017
  "استقلال القضاة",
3018
  "المساءلة التأديبية"
3019
  ],
3020
- "cross_references": [],
3021
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3022
  },
3023
  {
@@ -3033,7 +2821,6 @@
3033
  "إلزامية الأحكام",
3034
  "عدم الدستورية"
3035
  ],
3036
- "cross_references": [],
3037
  "legal_nature": "قيمة تعليمية/تثقيفية/ثقافية"
3038
  },
3039
  {
@@ -3050,7 +2837,6 @@
3050
  "صياغة العقود",
3051
  "التسوية الودية"
3052
  ],
3053
- "cross_references": [],
3054
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3055
  },
3056
  {
@@ -3067,7 +2853,6 @@
3067
  "الجزاءات التأديبية",
3068
  "مجلس الدولة"
3069
  ],
3070
- "cross_references": [],
3071
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3072
  },
3073
  {
@@ -3084,7 +2869,6 @@
3084
  "استقلال المحاماة",
3085
  "جهات التحقيق"
3086
  ],
3087
- "cross_references": [],
3088
  "legal_nature": "حق أساسي/حرية"
3089
  },
3090
  {
@@ -3101,7 +2885,6 @@
3101
  "استقلال الخبراء",
3102
  "الحماية القانونية"
3103
  ],
3104
- "cross_references": [],
3105
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3106
  },
3107
  {
@@ -3118,7 +2901,6 @@
3118
  "المجلس الأعلى للقوات المسلحة",
3119
  "صون الدستور"
3120
  ],
3121
- "cross_references": [],
3122
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3123
  },
3124
  {
@@ -3133,7 +2915,6 @@
3133
  "القائد العام",
3134
  "التعيين"
3135
  ],
3136
- "cross_references": [],
3137
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3138
  },
3139
  {
@@ -3149,7 +2930,6 @@
3149
  "اللجان القضائية العسكرية",
3150
  "المنازعات الإدارية"
3151
  ],
3152
- "cross_references": [],
3153
  "legal_nature": "إحالة للتنظيم التشريعي"
3154
  },
3155
  {
@@ -3165,7 +2945,6 @@
3165
  "تأمين البلاد",
3166
  "رقم واحد"
3167
  ],
3168
- "cross_references": [],
3169
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3170
  },
3171
  {
@@ -3181,7 +2960,6 @@
3181
  "الجرائم العسكرية",
3182
  "استقلال القضاء"
3183
  ],
3184
- "cross_references": [],
3185
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3186
  },
3187
  {
@@ -3197,7 +2975,6 @@
3197
  "إدارة الأزمات",
3198
  "الكوارث"
3199
  ],
3200
- "cross_references": [],
3201
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3202
  },
3203
  {
@@ -3212,7 +2989,6 @@
3212
  "النظام العام",
3213
  "الأمن"
3214
  ],
3215
- "cross_references": [],
3216
  "legal_nature": "حق أساسي/حرية"
3217
  },
3218
  {
@@ -3228,7 +3004,6 @@
3228
  "المجلس الأعلى",
3229
  "الأمن"
3230
  ],
3231
- "cross_references": [],
3232
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3233
  },
3234
  {
@@ -3244,7 +3019,6 @@
3244
  "المجلس الأعلى لتنظيم الإعلام",
3245
  "الصحافة"
3246
  ],
3247
- "cross_references": [],
3248
  "legal_nature": "إحالة للتنظيم التشريعي"
3249
  },
3250
  {
@@ -3257,7 +3031,6 @@
3257
  "keywords": [
3258
  "الصحافة"
3259
  ],
3260
- "cross_references": [],
3261
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3262
  },
3263
  {
@@ -3272,7 +3045,6 @@
3272
  "الإعلام",
3273
  "المجلس الأعلى لتنظيم الإعلام"
3274
  ],
3275
- "cross_references": [],
3276
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3277
  },
3278
  {
@@ -3289,7 +3061,6 @@
3289
  "ذوي الإعاقة",
3290
  "الاستقلال المالي والإداري"
3291
  ],
3292
- "cross_references": [],
3293
  "legal_nature": "إحالة للتنظيم التشريعي"
3294
  },
3295
  {
@@ -3306,7 +3077,6 @@
3306
  "الرقابة الإدارية",
3307
  "الجهاز المركزي للمحاسبات"
3308
  ],
3309
- "cross_references": [],
3310
  "legal_nature": "إحالة للتنظيم التشريعي"
3311
  },
3312
  {
@@ -3322,7 +3092,6 @@
3322
  "الاستقلال والحياد",
3323
  "موافقة البرلمان"
3324
  ],
3325
- "cross_references": [],
3326
  "legal_nature": "حظر دستوري"
3327
  },
3328
  {
@@ -3338,7 +3107,6 @@
3338
  "الرقابة البرلمانية",
3339
  "إبلاغ النيابة"
3340
  ],
3341
- "cross_references": [],
3342
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3343
  },
3344
  {
@@ -3354,7 +3122,6 @@
3354
  "حماية المال العام",
3355
  "الاستراتيجية الوطنية"
3356
  ],
3357
- "cross_references": [],
3358
  "legal_nature": "التزام/واجب على الدولة"
3359
  },
3360
  {
@@ -3369,7 +3136,6 @@
3369
  "الرقابة المالية",
3370
  "الموازنة العامة"
3371
  ],
3372
- "cross_references": [],
3373
  "legal_nature": "مبدأ اقتصادي/مالي"
3374
  },
3375
  {
@@ -3385,7 +3151,6 @@
3385
  "إصدار النقد",
3386
  "استقرار الأسعار"
3387
  ],
3388
- "cross_references": [],
3389
  "legal_nature": "حق أساسي/حرية"
3390
  },
3391
  {
@@ -3401,7 +3166,6 @@
3401
  "سوق المال",
3402
  "التأمين"
3403
  ],
3404
- "cross_references": [],
3405
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3406
  },
3407
  {
@@ -3415,7 +3179,6 @@
3415
  "العاصمة",
3416
  "القاهرة"
3417
  ],
3418
- "cross_references": [],
3419
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3420
  },
3421
  {
@@ -3431,7 +3194,6 @@
3431
  "شعار الجمهورية",
3432
  "إهانة العلم"
3433
  ],
3434
- "cross_references": [],
3435
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3436
  },
3437
  {
@@ -3446,9 +3208,6 @@
3446
  "القوانين السابقة",
3447
  "إصدار القوانين"
3448
  ],
3449
- "cross_references": [
3450
- "الدستور"
3451
- ],
3452
  "legal_nature": "حكم انتقالي"
3453
  },
3454
  {
@@ -3464,7 +3223,6 @@
3464
  "الأثر الرجعي",
3465
  "تاريخ العمل بالقانون"
3466
  ],
3467
- "cross_references": [],
3468
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3469
  },
3470
  {
@@ -3480,7 +3238,6 @@
3480
  "حظر التعديل",
3481
  "إجراءات التعديل"
3482
  ],
3483
- "cross_references": [],
3484
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3485
  },
3486
  {
@@ -3494,7 +3251,6 @@
3494
  "وحدة الدستور",
3495
  "التكامل"
3496
  ],
3497
- "cross_references": [],
3498
  "legal_nature": "مبدأ تفسيري"
3499
  },
3500
  {
@@ -3509,7 +3265,6 @@
3509
  "الهيئة الوطنية للانتخابات",
3510
  "إشراف قضائي"
3511
  ],
3512
- "cross_references": [],
3513
  "legal_nature": "حكم انتقالي"
3514
  },
3515
  {
@@ -3522,9 +3277,6 @@
3522
  "keywords": [
3523
  "انتخابات مجلس النواب"
3524
  ],
3525
- "cross_references": [
3526
- "102"
3527
- ],
3528
  "legal_nature": "حكم انتقالي"
3529
  },
3530
  {
@@ -3538,7 +3290,6 @@
3538
  "مواعيد الانتخابات",
3539
  "إجراءات انتقالية"
3540
  ],
3541
- "cross_references": [],
3542
  "legal_nature": "حكم انتقالي"
3543
  },
3544
  {
@@ -3552,7 +3303,6 @@
3552
  "مدة الرئاسة",
3553
  "إعلان النتيجة"
3554
  ],
3555
- "cross_references": [],
3556
  "legal_nature": "حكم انتقالي"
3557
  },
3558
  {
@@ -3566,7 +3316,6 @@
3566
  "الرئيس المؤقت",
3567
  "تسليم السلطة"
3568
  ],
3569
- "cross_references": [],
3570
  "legal_nature": "حكم انتقالي"
3571
  },
3572
  {
@@ -3580,7 +3329,6 @@
3580
  "خلو المنصب",
3581
  "الرئيس المؤقت"
3582
  ],
3583
- "cross_references": [],
3584
  "legal_nature": "حكم انتقالي"
3585
  },
3586
  {
@@ -3595,7 +3343,6 @@
3595
  "المجلس الأعلى للقوات المسلحة",
3596
  "التعيين"
3597
  ],
3598
- "cross_references": [],
3599
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3600
  },
3601
  {
@@ -3610,7 +3357,6 @@
3610
  "حرية العقيدة",
3611
  "المسيحيين"
3612
  ],
3613
- "cross_references": [],
3614
  "legal_nature": "التزام تشريعي"
3615
  },
3616
  {
@@ -3626,7 +3372,6 @@
3626
  "المناطق الحدودية",
3627
  "العدالة الاجتماعية"
3628
  ],
3629
- "cross_references": [],
3630
  "legal_nature": "حق أساسي/حرية"
3631
  },
3632
  {
@@ -3641,7 +3386,6 @@
3641
  "تمويل الإرهاب",
3642
  "تعويض الأضرار"
3643
  ],
3644
- "cross_references": [],
3645
  "legal_nature": "التزام/واجب على الدولة"
3646
  },
3647
  {
@@ -3657,7 +3401,6 @@
3657
  "الصحة",
3658
  "التعليم الإلزامي"
3659
  ],
3660
- "cross_references": [],
3661
  "legal_nature": "حكم انتقالي"
3662
  },
3663
  {
@@ -3671,7 +3414,6 @@
3671
  "ندب القضاة",
3672
  "استقلال القضاء"
3673
  ],
3674
- "cross_references": [],
3675
  "legal_nature": "حكم انتقالي"
3676
  },
3677
  {
@@ -3685,7 +3427,6 @@
3685
  "استئناف الجنايات",
3686
  "درجات التقاضي"
3687
  ],
3688
- "cross_references": [],
3689
  "legal_nature": "حكم انتقالي"
3690
  },
3691
  {
@@ -3700,7 +3441,6 @@
3700
  "المصالحة الوطنية",
3701
  "تعويض الضحايا"
3702
  ],
3703
- "cross_references": [],
3704
  "legal_nature": "التزام تشريعي"
3705
  },
3706
  {
@@ -3714,7 +3454,6 @@
3714
  "مدة الرئاسة",
3715
  "تعديلات 2019"
3716
  ],
3717
- "cross_references": [],
3718
  "legal_nature": "قاعدة إجرائية"
3719
  },
3720
  {
@@ -3728,9 +3467,6 @@
3728
  "الإدارة المحلية",
3729
  "تطبيق تدريجي"
3730
  ],
3731
- "cross_references": [
3732
- "180"
3733
- ],
3734
  "legal_nature": "حكم انتقالي"
3735
  },
3736
  {
@@ -3744,7 +3480,6 @@
3744
  "تمثيل العمال والفلاحين",
3745
  "مجلس النواب"
3746
  ],
3747
- "cross_references": [],
3748
  "legal_nature": "التزام/واجب على الدولة"
3749
  },
3750
  {
@@ -3761,7 +3496,6 @@
3761
  "ذوي الإعاقة",
3762
  "المصريين بالخارج"
3763
  ],
3764
- "cross_references": [],
3765
  "legal_nature": "التزام/واجب على الدولة"
3766
  },
3767
  {
@@ -3775,9 +3509,6 @@
3775
  "كوتا المرأة",
3776
  "سريان التعديلات"
3777
  ],
3778
- "cross_references": [
3779
- "102"
3780
- ],
3781
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3782
  },
3783
  {
@@ -3792,7 +3523,6 @@
3792
  "مجلس النواب",
3793
  "الموظفين"
3794
  ],
3795
- "cross_references": [],
3796
  "legal_nature": "حكم انتقالي"
3797
  },
3798
  {
@@ -3806,7 +3536,6 @@
3806
  "إلغاء دستوري",
3807
  "الإعلان الدستوري"
3808
  ],
3809
- "cross_references": [],
3810
  "legal_nature": "قيمة تعليمية/تثقيفية/ثقافية"
3811
  },
3812
  {
@@ -3820,7 +3549,6 @@
3820
  "نفاذ الدستور",
3821
  "الاستفتاء الشعبي"
3822
  ],
3823
- "cross_references": [],
3824
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3825
  },
3826
  {
@@ -3836,7 +3564,6 @@
3836
  "السلام الاجتماعي",
3837
  "الحقوق والحريات"
3838
  ],
3839
- "cross_references": [],
3840
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3841
  },
3842
  {
@@ -3853,7 +3580,6 @@
3853
  "المعاهدات الدولية",
3854
  "السياسة العامة"
3855
  ],
3856
- "cross_references": [],
3857
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3858
  },
3859
  {
@@ -3869,7 +3595,6 @@
3869
  "التعيين",
3870
  "مدة العضوية"
3871
  ],
3872
- "cross_references": [],
3873
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3874
  },
3875
  {
@@ -3885,7 +3610,6 @@
3885
  "السن القانوني",
3886
  "النظام الانتخابي"
3887
  ],
3888
- "cross_references": [],
3889
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3890
  },
3891
  {
@@ -3899,7 +3623,6 @@
3899
  "حظر الجمع",
3900
  "مجلس النواب"
3901
  ],
3902
- "cross_references": [],
3903
  "legal_nature": "حظر دستوري"
3904
  },
3905
  {
@@ -3914,7 +3637,6 @@
3914
  "الحكومة",
3915
  "علاقة السلطات"
3916
  ],
3917
- "cross_references": [],
3918
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3919
  },
3920
  {
@@ -3930,32 +3652,6 @@
3930
  "الواجبات",
3931
  "الإجراءات"
3932
  ],
3933
- "cross_references": [
3934
- "103",
3935
- "104",
3936
- "105",
3937
- "107",
3938
- "108",
3939
- "109",
3940
- "110",
3941
- "111",
3942
- "112",
3943
- "113",
3944
- "114",
3945
- "115",
3946
- "116",
3947
- "117",
3948
- "118",
3949
- "119",
3950
- "120",
3951
- "121",
3952
- "132",
3953
- "133",
3954
- "136",
3955
- "137"
3956
- ],
3957
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3958
  }
3959
- ]
3960
- }
3961
  ]
 
1
  [
 
 
 
 
2
  {
3
  "article_id": "EG-CONST-ART-001",
4
  "article_number": "1",
 
12
  "الديمقراطية",
13
  "جمهورية مصر العربية"
14
  ],
 
15
  "legal_nature": "مبدأ دستوري"
16
  },
17
  {
 
27
  "الشريعة الإسلامية",
28
  "التشريع"
29
  ],
 
30
  "legal_nature": "مبدأ دستوري"
31
  },
32
  {
 
42
  "اليهود",
43
  "الشئون الدينية"
44
  ],
 
45
  "legal_nature": "مبدأ دستوري"
46
  },
47
  {
 
58
  "المساواة",
59
  "تكافؤ الفرص"
60
  ],
 
61
  "legal_nature": "مبدأ دستوري"
62
  },
63
  {
 
73
  "الفصل بين السلطات",
74
  "حقوق الإنسان"
75
  ],
 
76
  "legal_nature": "مبدأ دستوري"
77
  },
78
  {
 
88
  "القانون",
89
  "اكتساب الجنسية"
90
  ],
 
91
  "legal_nature": "حق أساسي/حرية"
92
  },
93
  {
 
103
  "اللغة العربية",
104
  "شيخ الأزهر"
105
  ],
 
106
  "legal_nature": "مبدأ دستوري"
107
  },
108
  {
 
118
  "التكافل الاجتماعي",
119
  "الحياة الكريمة"
120
  ],
 
121
  "legal_nature": "مبدأ دستوري"
122
  },
123
  {
 
132
  "عدم التمييز",
133
  "المواطنة"
134
  ],
 
135
  "legal_nature": "مبدأ دستوري"
136
  },
137
  {
 
148
  "الأخلاق",
149
  "الوطنية"
150
  ],
 
151
  "legal_nature": "مبدأ دستوري"
152
  },
153
  {
 
164
  "المجالس النيابية",
165
  "حماية الأمومة والطفولة"
166
  ],
 
167
  "legal_nature": "حق أساسي/حرية"
168
  },
169
  {
 
179
  "العمل الجبري",
180
  "الخدمة العامة"
181
  ],
 
182
  "legal_nature": "حق أساسي/حرية"
183
  },
184
  {
 
195
  "السلامة المهنية",
196
  "الفصل التعسفي"
197
  ],
 
198
  "legal_nature": "حق أساسي/حرية"
199
  },
200
  {
 
210
  "حقوق الموظفين",
211
  "التأديب"
212
  ],
 
213
  "legal_nature": "حق أساسي/حرية"
214
  },
215
  {
 
223
  "الإضراب السلمي",
224
  "القانون"
225
  ],
 
226
  "legal_nature": "حق أساسي/حرية"
227
  },
228
  {
 
239
  "رعاية الأسر",
240
  "المجتمع المدني"
241
  ],
 
242
  "legal_nature": "التزام/واجب على الدولة"
243
  },
244
  {
 
255
  "العمالة غير المنتظمة",
256
  "أموال التأمينات"
257
  ],
 
258
  "legal_nature": "حق أساسي/حرية"
259
  },
260
  {
 
271
  "الطوارئ",
272
  "القطاع الصحي"
273
  ],
 
274
  "legal_nature": "حق أساسي/حرية"
275
  },
276
  {
 
287
  "الهوية الوطنية",
288
  "جودة التعليم"
289
  ],
 
290
  "legal_nature": "حق أساسي/حرية"
291
  },
292
  {
 
302
  "سوق العمل",
303
  "الجودة العالمية"
304
  ],
 
305
  "legal_nature": "التزام/واجب على الدولة"
306
  },
307
  {
 
318
  "الجامعات الأهلية",
319
  "الجامعات الخاصة"
320
  ],
 
321
  "legal_nature": "حق أساسي/حرية"
322
  },
323
  {
 
333
  "حقوق المعلمين",
334
  "جودة التعليم"
335
  ],
 
336
  "legal_nature": "التزام/واجب على الدولة"
337
  },
338
  {
 
349
  "اقتصاد المعرفة",
350
  "المخترعين"
351
  ],
 
352
  "legal_nature": "حق أساسي/حرية"
353
  },
354
  {
 
365
  "حقوق الإنسان",
366
  "الأخلاق المهنية"
367
  ],
 
368
  "legal_nature": "مبدأ دستوري"
369
  },
370
  {
 
380
  "المجتمع المدني",
381
  "خطة شاملة"
382
  ],
 
383
  "legal_nature": "التزام/واجب على الدولة"
384
  },
385
  {
 
393
  "الرتب المدنية",
394
  "حظر"
395
  ],
 
396
  "legal_nature": "حظر دستوري"
397
  },
398
  {
 
409
  "الشفافية",
410
  "منع الاحتكار"
411
  ],
 
412
  "legal_nature": "حق أساسي/حرية"
413
  },
414
  {
 
425
  "المشروعات الصغيرة",
426
  "القطاع غير الرسمي"
427
  ],
 
428
  "legal_nature": "التزام/واجب على الدولة"
429
  },
430
  {
 
441
  "دعم الفلاح",
442
  "الإنتاج الزراعي"
443
  ],
 
444
  "legal_nature": "التزام/واجب على الدولة"
445
  },
446
  {
 
455
  "حماية الصيادين",
456
  "البيئة"
457
  ],
 
458
  "legal_nature": "التزام/واجب على الدولة"
459
  },
460
  {
 
469
  "الأمن القومي",
470
  "الاقتصاد الرقمي"
471
  ],
 
472
  "legal_nature": "التزام/واجب على الدولة"
473
  },
474
  {
 
485
  "الاستثمار",
486
  "حقوق الأجيال القادمة"
487
  ],
 
488
  "legal_nature": "مبدأ دستوري"
489
  },
490
  {
 
500
  "الملكية التعاونية",
501
  "حماية الملكية"
502
  ],
 
503
  "legal_nature": "مبدأ دستوري"
504
  },
505
  {
 
513
  "الملكية العامة",
514
  "حرمة المال العام"
515
  ],
 
516
  "legal_nature": "التزام/واجب على الدولة"
517
  },
518
  {
 
528
  "نزع الملكية",
529
  "التعويض العادل"
530
  ],
 
531
  "legal_nature": "حق أساسي/حرية"
532
  },
533
  {
 
541
  "القطاع الخاص",
542
  "المسؤولية الاجتماعية"
543
  ],
 
544
  "legal_nature": "التزام/واجب على الدولة"
545
  },
546
  {
 
555
  "التعاونيات",
556
  "استقلال التعاونيات"
557
  ],
 
558
  "legal_nature": "حق أساسي/حرية"
559
  },
560
  {
 
570
  "العدالة الاجتماعية",
571
  "التهرب الضريبي"
572
  ],
 
573
  "legal_nature": "التزام/واجب على الدولة"
574
  },
575
  {
 
583
  "الادخار",
584
  "حماية المدخرات"
585
  ],
 
586
  "legal_nature": "التزام/واجب على الدولة"
587
  },
588
  {
 
597
  "المصادرة الخاصة",
598
  "حماية الأموال"
599
  ],
 
600
  "legal_nature": "حظر دستوري"
601
  },
602
  {
 
612
  "التنمية المستدامة",
613
  "الموارد البشرية"
614
  ],
 
615
  "legal_nature": "التزام/واجب على الدولة"
616
  },
617
  {
 
628
  "القطاع العام",
629
  "التعاونيات"
630
  ],
 
631
  "legal_nature": "حقوق وواجبات"
632
  },
633
  {
 
642
  "الممر المائي",
643
  "التنمية الاقتصادية"
644
  ],
 
645
  "legal_nature": "التزام/واجب على الدولة"
646
  },
647
  {
 
657
  "المياه الجوفية",
658
  "حماية البيئة النهرية"
659
  ],
 
660
  "legal_nature": "حقوق وواجبات"
661
  },
662
  {
 
672
  "الثروة السمكية والحيوانية",
673
  "الرفق بالحيوان"
674
  ],
 
675
  "legal_nature": "التزام/واجب على الدولة"
676
  },
677
  {
 
687
  "حقوق الأجيال القادمة",
688
  "الموارد الطبيعية"
689
  ],
 
690
  "legal_nature": "حقوق وواجبات"
691
  },
692
  {
 
701
  "الحضارة المصرية",
702
  "التنوع الثقافي"
703
  ],
 
704
  "legal_nature": "التزام/واجب على الدولة"
705
  },
706
  {
 
716
  "المناطق النائية",
717
  "الترجمة"
718
  ],
 
719
  "legal_nature": "حق أساسي/حرية"
720
  },
721
  {
 
731
  "حظر الاتجار",
732
  "جريمة لا تسقط بالتقادم"
733
  ],
 
734
  "legal_nature": "التزام/واجب على الدولة"
735
  },
736
  {
 
747
  "التعددية الثقافية",
748
  "الحضارة المصرية القديمة"
749
  ],
 
750
  "legal_nature": "التزام/واجب على الدولة"
751
  },
752
  {
 
761
  "حقوق الإنسان",
762
  "احترام الدولة"
763
  ],
 
764
  "legal_nature": "حق أساسي/حرية"
765
  },
766
  {
 
775
  "جريمة",
776
  "التقادم"
777
  ],
 
778
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
779
  },
780
  {
 
790
  "مفوضية منع التمييز",
791
  "الحض على الكراهية"
792
  ],
 
793
  "legal_nature": "حق أساسي/حرية"
794
  },
795
  {
 
806
  "حق الدفاع",
807
  "التعويض"
808
  ],
 
809
  "legal_nature": "حق أساسي/حرية"
810
  },
811
  {
 
821
  "بطلان الاعتراف",
822
  "حظر التعذيب"
823
  ],
 
824
  "legal_nature": "حق أساسي/حرية"
825
  },
826
  {
 
836
  "الإشراف القضائي",
837
  "الرعاية اللاحقة"
838
  ],
 
839
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
840
  },
841
  {
 
851
  "المراقبة",
852
  "وسائل الاتصال"
853
  ],
 
854
  "legal_nature": "حق أساسي/حرية"
855
  },
856
  {
 
865
  "تفتيش المنازل",
866
  "إذن قضائي"
867
  ],
 
868
  "legal_nature": "حق أساسي/حرية"
869
  },
870
  {
 
879
  "الأمن",
880
  "الطمأنينة"
881
  ],
 
882
  "legal_nature": "حق أساسي/حرية"
883
  },
884
  {
 
893
  "الاتجار بالأعضاء",
894
  "التجارب الطبية"
895
  ],
 
896
  "legal_nature": "قاعدة إجرائية"
897
  },
898
  {
 
907
  "زراعة الأعضاء",
908
  "الوصية"
909
  ],
 
910
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
911
  },
912
  {
 
922
  "المنع من السفر",
923
  "الإبعاد"
924
  ],
 
925
  "legal_nature": "حق أساسي/حرية"
926
  },
927
  {
 
936
  "جريمة",
937
  "التقادم"
938
  ],
 
939
  "legal_nature": "حظر وتجريم"
940
  },
941
  {
 
951
  "دور العبادة",
952
  "الأديان السماوية"
953
  ],
 
954
  "legal_nature": "حق أساسي/حرية"
955
  },
956
  {
 
966
  "حرية التعبير",
967
  "النشر"
968
  ],
 
969
  "legal_nature": "حق أساسي/حرية"
970
  },
971
  {
 
980
  "الباحثين",
981
  "الابتكار"
982
  ],
 
983
  "legal_nature": "حق أساسي/حرية"
984
  },
985
  {
 
995
  "حظر الحبس في جرائم النشر",
996
  "النيابة العامة"
997
  ],
 
998
  "legal_nature": "حق أساسي/حرية"
999
  },
1000
  {
 
1010
  "الوثائق القومية",
1011
  "البيانات"
1012
  ],
 
1013
  "legal_nature": "حق أساسي/حرية"
1014
  },
1015
  {
 
1023
  "الملكية الفكرية",
1024
  "حماية الحقوق"
1025
  ],
 
1026
  "legal_nature": "التزام/واجب على الدولة"
1027
  },
1028
  {
 
1038
  "إصدار الصحف",
1039
  "البث الإذاعي"
1040
  ],
 
1041
  "legal_nature": "حق أساسي/حرية"
1042
  },
1043
  {
 
1053
  "جرائم النشر",
1054
  "وقت الحرب"
1055
  ],
 
1056
  "legal_nature": "حظر دستوري"
1057
  },
1058
  {
 
1068
  "الحياد",
1069
  "تعدد الآراء"
1070
  ],
 
1071
  "legal_nature": "التزام/واجب على الدولة"
1072
  },
1073
  {
 
1083
  "الاحتجاج السلمي",
1084
  "الاجتماع الخاص"
1085
  ],
 
1086
  "legal_nature": "حق أساسي/حرية"
1087
  },
1088
  {
 
1097
  "حظر الأحزاب الدينية",
1098
  "حل الأحزاب"
1099
  ],
 
1100
  "legal_nature": "حق أساسي/حرية"
1101
  },
1102
  {
 
1112
  "حرية النشاط",
1113
  "حظر الجمعيات السرية"
1114
  ],
 
1115
  "legal_nature": "حق أساسي/حرية"
1116
  },
1117
  {
 
1127
  "استقلال النقابات",
1128
  "الهيئات النظامية"
1129
  ],
 
1130
  "legal_nature": "حق أساسي/حرية"
1131
  },
1132
  {
 
1141
  "فرض الحراسة",
1142
  "استقلال مهني"
1143
  ],
 
1144
  "legal_nature": "إحالة للتنظيم التشريعي"
1145
  },
1146
  {
 
1156
  "التخطيط العمراني",
1157
  "العدالة الاجتماعية"
1158
  ],
 
1159
  "legal_nature": "حقوق وواجبات"
1160
  },
1161
  {
 
1171
  "السيادة الغذائية",
1172
  "التنوع البيولوجي"
1173
  ],
 
1174
  "legal_nature": "حق أساسي/حرية"
1175
  },
1176
  {
 
1187
  "عمالة الأطفال",
1188
  "قضاء الأحداث"
1189
  ],
 
1190
  "legal_nature": "حق أساسي/حرية"
1191
  },
1192
  {
 
1202
  "الدمج",
1203
  "تخصيص فرص عمل"
1204
  ],
 
1205
  "legal_nature": "حق أساسي/حرية"
1206
  },
1207
  {
 
1217
  "التمكين",
1218
  "العمل التطوعي"
1219
  ],
 
1220
  "legal_nature": "التزام/واجب على الدولة"
1221
  },
1222
  {
 
1231
  "المعاشات",
1232
  "رعاية المسنين"
1233
  ],
 
1234
  "legal_nature": "حق أساسي/حرية"
1235
  },
1236
  {
 
1245
  "الموهوبين رياضياً",
1246
  "الهيئات الرياضية"
1247
  ],
 
1248
  "legal_nature": "حق أساسي/حرية"
1249
  },
1250
  {
 
1258
  "مخاطبة السلطات",
1259
  "الأشخاص الاعتبارية"
1260
  ],
 
1261
  "legal_nature": "حق أساسي/حرية"
1262
  },
1263
  {
 
1272
  "الدفاع عن الوطن",
1273
  "التجنيد الإجباري"
1274
  ],
 
1275
  "legal_nature": "التزام/واجب على الدولة"
1276
  },
1277
  {
 
1287
  "نزاهة الانتخابات",
1288
  "قاعدة بيانات الناخبين"
1289
  ],
 
1290
  "legal_nature": "حقوق وواجبات"
1291
  },
1292
  {
 
1301
  "تصويت المغتربين",
1302
  "رعاية المصالح"
1303
  ],
 
1304
  "legal_nature": "التزام/واجب على الدولة"
1305
  },
1306
  {
 
1315
  "الاتجار بالبشر",
1316
  "الاستغلال القسري"
1317
  ],
 
1318
  "legal_nature": "حظر دستوري"
1319
  },
1320
  {
 
1329
  "استقلال الوقف",
1330
  "شروط الواقف"
1331
  ],
 
1332
  "legal_nature": "التزام/واجب على الدولة"
1333
  },
1334
  {
 
1343
  "حظر تسليم اللاجئين",
1344
  "حقوق الإنسان"
1345
  ],
 
1346
  "legal_nature": "حق أساسي/حرية"
1347
  },
1348
  {
 
1357
  "عدم الانتقاص",
1358
  "جوهر الحق"
1359
  ],
 
1360
  "legal_nature": "إحالة للتنظيم التشريعي"
1361
  },
1362
  {
 
1371
  "حقوق الإنسان",
1372
  "قوة القانون"
1373
  ],
 
1374
  "legal_nature": "التزام/واجب على الدولة"
1375
  },
1376
  {
 
1386
  "حصانة القضاء",
1387
  "حماية الحريات"
1388
  ],
 
1389
  "legal_nature": "مبدأ دستوري"
1390
  },
1391
  {
 
1401
  "عدم الرجعية",
1402
  "حكم قضائي"
1403
  ],
 
1404
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
1405
  },
1406
  {
 
1417
  "استئناف الجنايات",
1418
  "حماية الشهود"
1419
  ],
 
1420
  "legal_nature": "حق أساسي/حرية"
1421
  },
1422
  {
 
1432
  "حظر المحاكم الاستثنائية",
1433
  "الرقابة القضائية"
1434
  ],
 
1435
  "legal_nature": "حق أساسي/حرية"
1436
  },
1437
  {
 
1446
  "استقلال المحاماة",
1447
  "المساعدة القانونية"
1448
  ],
 
1449
  "legal_nature": "حق أساسي/حرية"
1450
  },
1451
  {
 
1462
  "التعويض العادل",
1463
  "المجلس القومي لحقوق الإنسان"
1464
  ],
 
1465
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
1466
  },
1467
  {
 
1478
  "الموظف العام",
1479
  "الدعوى الجنائية"
1480
  ],
 
1481
  "legal_nature": "حق أساسي/حرية"
1482
  },
1483
  {
 
1493
  "الموازنة العامة",
1494
  "الرقابة"
1495
  ],
 
1496
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1497
  },
1498
  {
 
1509
  "النظام الانتخابي",
1510
  "التعيين الرئاسي"
1511
  ],
 
1512
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1513
  },
1514
  {
 
1522
  "تفرغ العضو",
1523
  "الاحتفاظ بالوظيفة"
1524
  ],
 
1525
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1526
  },
1527
  {
 
1535
  "اليمين الدستورية",
1536
  "واجبات العضو"
1537
  ],
 
1538
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1539
  },
1540
  {
 
1548
  "مكافأة العضوية",
1549
  "عدم سريان الزيادة"
1550
  ],
 
1551
  "legal_nature": "مبدأ اقتصادي/مالي"
1552
  },
1553
  {
 
1561
  "مدة العضوية",
1562
  "موعد الانتخابات"
1563
  ],
 
1564
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1565
  },
1566
  {
 
1575
  "محكمة النقض",
1576
  "الطعون الانتخابية"
1577
  ],
 
1578
  "legal_nature": "اختصاص قضائي/محكمة"
1579
  },
1580
  {
 
1588
  "خلو المقعد",
1589
  "الانتخابات التكميلية"
1590
  ],
 
1591
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1592
  },
1593
  {
 
1602
  "إقرار الذمة المالية",
1603
  "الهدايا"
1604
  ],
 
1605
  "legal_nature": "قاعدة إجرائية"
1606
  },
1607
  {
 
1616
  "أغلبية الثلثين",
1617
  "فقد الثقة"
1618
  ],
 
1619
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1620
  },
1621
  {
 
1629
  "الاستقالة",
1630
  "قبول الاستقالة"
1631
  ],
 
1632
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1633
  },
1634
  {
 
1642
  "الحصانة البرلمانية",
1643
  "حرية الرأي"
1644
  ],
 
1645
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1646
  },
1647
  {
 
1656
  "إذن المجلس",
1657
  "رفع الحصانة"
1658
  ],
 
1659
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1660
  },
1661
  {
 
1670
  "انعقاد الجلسات",
1671
  "بطلان الاجتماع"
1672
  ],
 
1673
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1674
  },
1675
  {
 
1684
  "دعوة الرئيس",
1685
  "اعتماد الموازنة"
1686
  ],
 
1687
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1688
  },
1689
  {
 
1697
  "اجتماع غير عادي",
1698
  "دعوة طارئة"
1699
  ],
 
1700
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1701
  },
1702
  {
 
1712
  "مدة الرئاسة",
1713
  "إعفاء الرئيس"
1714
  ],
 
1715
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1716
  },
1717
  {
 
1725
  "اللائحة الداخلية",
1726
  "تنظيم العمل"
1727
  ],
 
1728
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1729
  },
1730
  {
 
1738
  "حفظ النظام",
1739
  "رئيس المجلس"
1740
  ],
 
1741
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1742
  },
1743
  {
 
1751
  "علنية الجلسات",
1752
  "جلسة سرية"
1753
  ],
 
1754
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1755
  },
1756
  {
 
1766
  "القوانين المكملة للدستور",
1767
  "أغلبية الثلثين"
1768
  ],
 
1769
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
1770
  },
1771
  {
 
1780
  "اللجان النوعية",
1781
  "لجنة المقترحات"
1782
  ],
 
1783
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1784
  },
1785
  {
 
1794
  "إصدار القوانين",
1795
  "أغلبية الثلثين"
1796
  ],
 
1797
  "legal_nature": "حق أساسي/حرية"
1798
  },
1799
  {
 
1809
  "تعديل النفقات",
1810
  "السنة المالية"
1811
  ],
 
1812
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1813
  },
1814
  {
 
1823
  "الجهاز المركزي للمحاسبات",
1824
  "الرقابة المالية"
1825
  ],
 
1826
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1827
  },
1828
  {
 
1836
  "الأموال العامة",
1837
  "التحصيل والصرف"
1838
  ],
 
1839
  "legal_nature": "إحالة للتنظيم التشريعي"
1840
  },
1841
  {
 
1850
  "الديون العامة",
1851
  "موافقة البرلمان"
1852
  ],
 
1853
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1854
  },
1855
  {
 
1864
  "المعاشات",
1865
  "الخزانة العامة"
1866
  ],
 
1867
  "legal_nature": "إحالة للتنظيم التشريعي"
1868
  },
1869
  {
 
1877
  "توجيه الأسئلة",
1878
  "الرقابة البرلمانية"
1879
  ],
 
1880
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1881
  },
1882
  {
 
1890
  "الاستجواب",
1891
  "المحاسبة السياسية"
1892
  ],
 
1893
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1894
  },
1895
  {
 
1904
  "استقالة الحكومة",
1905
  "المسؤولية التضامنية"
1906
  ],
 
1907
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1908
  },
1909
  {
 
1917
  "طلب مناقشة عامة",
1918
  "استيضاح السياسة"
1919
  ],
 
1920
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1921
  },
1922
  {
 
1929
  "keywords": [
1930
  "اقتراح برغبة"
1931
  ],
 
1932
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1933
  },
1934
  {
 
1942
  "طلب إحاطة",
1943
  "بيان عاجل"
1944
  ],
 
1945
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1946
  },
1947
  {
 
1956
  "جمع الأدلة",
1957
  "حق المعلومات"
1958
  ],
 
1959
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1960
  },
1961
  {
 
1969
  "حضور الحكومة",
1970
  "الاستماع للوزراء"
1971
  ],
 
1972
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1973
  },
1974
  {
 
1983
  "الاستفتاء الشعبي",
1984
  "انتخابات مبكرة"
1985
  ],
 
1986
  "legal_nature": "تنظيم مؤسسي/هيكلي"
1987
  },
1988
  {
 
1997
  "الشكاوى",
1998
  "تواصل البرلمان"
1999
  ],
 
2000
  "legal_nature": "حق أساسي/حرية"
2001
  },
2002
  {
 
2012
  "استقلال الوطن",
2013
  "احترام الدستور"
2014
  ],
 
2015
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2016
  },
2017
  {
 
2027
  "إجراءات الانتخاب",
2028
  "حظر المناصب الحزبية"
2029
  ],
 
2030
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2031
  },
2032
  {
 
2042
  "السن القانوني",
2043
  "الخدمة العسكرية"
2044
  ],
 
2045
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2046
  },
2047
  {
 
2056
  "التوكيلات الشعبية",
2057
  "شروط الترشح"
2058
  ],
 
2059
  "legal_nature": "حق أساسي/حرية"
2060
  },
2061
  {
 
2069
  "الاقتراع المباشر",
2070
  "الأغلبية المطلقة"
2071
  ],
 
2072
  "legal_nature": "قاعدة إجرائية"
2073
  },
2074
  {
 
2082
  "اليمين الدستورية",
2083
  "تولي المهام"
2084
  ],
 
2085
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2086
  },
2087
  {
 
2097
  "إقرار الذمة المالية",
2098
  "الهدايا"
2099
  ],
 
2100
  "legal_nature": "إحالة للتنظيم التشريعي"
2101
  },
2102
  {
 
2112
  "حل المجلس",
2113
  "الوزارات السيادية"
2114
  ],
 
2115
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2116
  },
2117
  {
 
2126
  "التعديل الوزاري",
2127
  "موافقة البرلمان"
2128
  ],
 
2129
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2130
  },
2131
  {
 
2139
  "تفويض السلطات",
2140
  "التفويض الإداري"
2141
  ],
 
2142
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2143
  },
2144
  {
 
2152
  "اجتماع الحكومة",
2153
  "رئاسة الاجتماع"
2154
  ],
 
2155
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2156
  },
2157
  {
 
2165
  "السياسة العامة",
2166
  "بيان الرئيس"
2167
  ],
 
 
 
2168
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2169
  },
2170
  {
 
2179
  "تعيين النائب",
2180
  "اختصاصات النائب"
2181
  ],
 
 
 
 
 
 
2182
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2183
  },
2184
  {
 
2194
  "الاستفتاء الشعبي",
2195
  "حظر التنازل عن الأرض"
2196
  ],
 
 
 
2197
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2198
  },
2199
  {
 
2209
  "إرسال القوات",
2210
  "مجلس الدفاع الوطني"
2211
  ],
 
2212
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2213
  },
2214
  {
 
2223
  "الموظفين العموميين",
2224
  "الدبلوماسيين"
2225
  ],
 
2226
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2227
  },
2228
  {
 
2237
  "موافقة البرلمان",
2238
  "تمديد الطوارئ"
2239
  ],
 
2240
  "legal_nature": "تنظيم م��سسي/هيكلي"
2241
  },
2242
  {
 
2251
  "تخفيف العقوبة",
2252
  "العفو الشامل"
2253
  ],
 
2254
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2255
  },
2256
  {
 
2265
  "الضرورة",
2266
  "مراجعة البرلمان"
2267
  ],
 
2268
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2269
  },
2270
  {
 
2278
  "الاستفتاء الشعبي",
2279
  "المصالح العليا"
2280
  ],
 
 
 
2281
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2282
  },
2283
  {
 
2290
  "keywords": [
2291
  "استقالة الرئيس"
2292
  ],
 
2293
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2294
  },
2295
  {
 
2305
  "المحكمة الخاصة",
2306
  "العزل"
2307
  ],
 
2308
  "legal_nature": "اختصاص قضائي/محكمة"
2309
  },
2310
  {
 
2320
  "انتخابات رئاسية مبكرة",
2321
  "تقييد الصلاحيات"
2322
  ],
 
2323
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2324
  },
2325
  {
 
2335
  "حل البرلمان",
2336
  "الاستفتاء"
2337
  ],
 
2338
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2339
  },
2340
  {
 
2348
  "أسبقية الانتخابات",
2349
  "استمرار المجلس"
2350
  ],
 
2351
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2352
  },
2353
  {
 
2362
  "رئيس مجلس الوزراء",
2363
  "الهيئة التنفيذية"
2364
  ],
 
2365
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2366
  },
2367
  {
 
2377
  "الجنسية",
2378
  "حظر الجمع بين المناصب"
2379
  ],
 
2380
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2381
  },
2382
  {
 
2390
  "اليمين الدستورية",
2391
  "تولي المهام"
2392
  ],
 
2393
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2394
  },
2395
  {
 
2405
  "تعارض المصالح",
2406
  "حظر الأعمال التجارية"
2407
  ],
 
2408
  "legal_nature": "إحالة للتنظيم التشريعي"
2409
  },
2410
  {
 
2421
  "الأمن القومي",
2422
  "إعداد القوانين"
2423
  ],
 
2424
  "legal_nature": "حق أساسي/حرية"
2425
  },
2426
  {
 
2435
  "الوكيل الدائم",
2436
  "الاستقرار المؤسسي"
2437
  ],
 
2438
  "legal_nature": "ضمان/حماية"
2439
  },
2440
  {
 
2449
  "مجلس النواب",
2450
  "المساءلة"
2451
  ],
 
2452
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2453
  },
2454
  {
 
2463
  "التفويض",
2464
  "رئيس الوزراء"
2465
  ],
 
2466
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2467
  },
2468
  {
 
2477
  "المصالح العامة",
2478
  "قرارات تنظيمية"
2479
  ],
 
2480
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2481
  },
2482
  {
 
2490
  "لوائح الضبط",
2491
  "النظام العام"
2492
  ],
 
2493
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2494
  },
2495
  {
 
2504
  "الجرائم الوظيفية",
2505
  "الخيانة العظمى"
2506
  ],
 
 
 
2507
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2508
  },
2509
  {
 
2516
  "keywords": [
2517
  "الاستقالة"
2518
  ],
 
2519
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2520
  },
2521
  {
 
2531
  "المحافظات",
2532
  "الوحدات المحلية"
2533
  ],
 
2534
  "legal_nature": "قاعدة إجرائية"
2535
  },
2536
  {
 
2545
  "نقل السلطات",
2546
  "المرافق المحلية"
2547
  ],
 
2548
  "legal_nature": "التزام/واجب على الدولة"
2549
  },
2550
  {
 
2559
  "توزيع الموارد",
2560
  "التنمية المحلية"
2561
  ],
 
2562
  "legal_nature": "التزام/واجب على الدولة"
2563
  },
2564
  {
 
2573
  "الضرائب المحلية",
2574
  "الموارد المالية"
2575
  ],
 
2576
  "legal_nature": "مبدأ اقتصادي/مالي"
2577
  },
2578
  {
 
2587
  "رؤساء الوحدات المحلية",
2588
  "التعيين والانتخاب"
2589
  ],
 
2590
  "legal_nature": "إحالة للتنظيم التشريعي"
2591
  },
2592
  {
 
2603
  "الرقابة المحلية",
2604
  "سحب الثقة"
2605
  ],
 
2606
  "legal_nature": "مبدأ اقتصادي/مالي"
2607
  },
2608
  {
 
2618
  "تنازع الاختصاص",
2619
  "مجلس الدولة"
2620
  ],
 
2621
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2622
  },
2623
  {
 
2631
  "الموازنة المحلية",
2632
  "الحساب الختامي"
2633
  ],
 
2634
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2635
  },
2636
  {
 
2644
  "حل المجالس المحلية",
2645
  "الحظر الإداري"
2646
  ],
 
2647
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2648
  },
2649
  {
 
2659
  "التدخل في العدالة",
2660
  "عدم التقادم"
2661
  ],
 
2662
  "legal_nature": "مبدأ دستوري"
2663
  },
2664
  {
 
2674
  "تعيين الرؤساء",
2675
  "المجلس الأعلى للجهات القضائية"
2676
  ],
 
2677
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2678
  },
2679
  {
 
2689
  "المساءلة التأديبية",
2690
  "الندب"
2691
  ],
 
2692
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2693
  },
2694
  {
 
2703
  "النطق بالحكم",
2704
  "النظام العام"
2705
  ],
 
2706
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2707
  },
2708
  {
 
2717
  "مجلس القضاء الأعلى",
2718
  "الفصل في المنازعات"
2719
  ],
 
2720
  "legal_nature": "إحالة للتنظيم التشريعي"
2721
  },
2722
  {
 
2732
  "الدعوى الجنائية",
2733
  "تعيين النائب العام"
2734
  ],
 
2735
  "legal_nature": "اختصاص قضائي/محكمة"
2736
  },
2737
  {
 
2747
  "الإفتاء القانوني",
2748
  "مراجعة التشريعات"
2749
  ],
 
2750
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2751
  },
2752
  {
 
2762
  "الموازنة المستقلة",
2763
  "الجمعية العامة"
2764
  ],
 
2765
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2766
  },
2767
  {
 
2777
  "تنازع الاختصاص",
2778
  "الأحكام المتناقضة"
2779
  ],
 
2780
  "legal_nature": "اختصاص قضائي/محكمة"
2781
  },
2782
  {
 
2792
  "تعيين القضاة",
2793
  "رئيس الجمهورية"
2794
  ],
 
2795
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2796
  },
2797
  {
 
2806
  "استقلال القضاة",
2807
  "المساءلة التأديبية"
2808
  ],
 
2809
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2810
  },
2811
  {
 
2821
  "إلزامية الأحكام",
2822
  "عدم الدستورية"
2823
  ],
 
2824
  "legal_nature": "قيمة تعليمية/تثقيفية/ثقافية"
2825
  },
2826
  {
 
2837
  "صياغة العقود",
2838
  "التسوية الودية"
2839
  ],
 
2840
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2841
  },
2842
  {
 
2853
  "الجزاءات التأديبية",
2854
  "مجلس الدولة"
2855
  ],
 
2856
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2857
  },
2858
  {
 
2869
  "استقلال المحاماة",
2870
  "جهات التحقيق"
2871
  ],
 
2872
  "legal_nature": "حق أساسي/حرية"
2873
  },
2874
  {
 
2885
  "استقلال الخبراء",
2886
  "الحماية القانونية"
2887
  ],
 
2888
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2889
  },
2890
  {
 
2901
  "المجلس الأعلى للقوات المسلحة",
2902
  "صون الدستور"
2903
  ],
 
2904
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2905
  },
2906
  {
 
2915
  "القائد العام",
2916
  "التعيين"
2917
  ],
 
2918
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2919
  },
2920
  {
 
2930
  "اللجان القضائية العسكرية",
2931
  "المنازعات الإدارية"
2932
  ],
 
2933
  "legal_nature": "إحالة للتنظيم التشريعي"
2934
  },
2935
  {
 
2945
  "تأمين البلاد",
2946
  "رقم واحد"
2947
  ],
 
2948
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2949
  },
2950
  {
 
2960
  "الجرائم العسكرية",
2961
  "استقلال القضاء"
2962
  ],
 
2963
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
2964
  },
2965
  {
 
2975
  "إدارة الأزمات",
2976
  "الكوارث"
2977
  ],
 
2978
  "legal_nature": "تنظيم مؤسسي/هيكلي"
2979
  },
2980
  {
 
2989
  "النظام العام",
2990
  "الأمن"
2991
  ],
 
2992
  "legal_nature": "حق أساسي/حرية"
2993
  },
2994
  {
 
3004
  "المجلس الأعلى",
3005
  "الأمن"
3006
  ],
 
3007
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3008
  },
3009
  {
 
3019
  "المجلس الأعلى لتنظيم الإعلام",
3020
  "الصحافة"
3021
  ],
 
3022
  "legal_nature": "إحالة للتنظيم التشريعي"
3023
  },
3024
  {
 
3031
  "keywords": [
3032
  "الصحافة"
3033
  ],
 
3034
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3035
  },
3036
  {
 
3045
  "الإعلام",
3046
  "المجلس الأعلى لتنظيم الإعلام"
3047
  ],
 
3048
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3049
  },
3050
  {
 
3061
  "ذوي الإعاقة",
3062
  "الاستقلال المالي والإداري"
3063
  ],
 
3064
  "legal_nature": "إحالة للتنظيم التشريعي"
3065
  },
3066
  {
 
3077
  "الرقابة الإدارية",
3078
  "الجهاز المركزي للمحاسبات"
3079
  ],
 
3080
  "legal_nature": "إحالة للتنظيم التشريعي"
3081
  },
3082
  {
 
3092
  "الاستقلال والحياد",
3093
  "موافقة البرلمان"
3094
  ],
 
3095
  "legal_nature": "حظر دستوري"
3096
  },
3097
  {
 
3107
  "الرقابة البرلمانية",
3108
  "إبلاغ النيابة"
3109
  ],
 
3110
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3111
  },
3112
  {
 
3122
  "حماية المال العام",
3123
  "الاستراتيجية الوطنية"
3124
  ],
 
3125
  "legal_nature": "التزام/واجب على الدولة"
3126
  },
3127
  {
 
3136
  "الرقابة المالية",
3137
  "الموازنة العامة"
3138
  ],
 
3139
  "legal_nature": "مبدأ اقتصادي/مالي"
3140
  },
3141
  {
 
3151
  "إصدار النقد",
3152
  "استقرار الأسعار"
3153
  ],
 
3154
  "legal_nature": "حق أساسي/حرية"
3155
  },
3156
  {
 
3166
  "سوق المال",
3167
  "التأمين"
3168
  ],
 
3169
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3170
  },
3171
  {
 
3179
  "العاصمة",
3180
  "القاهرة"
3181
  ],
 
3182
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3183
  },
3184
  {
 
3194
  "شعار الجمهورية",
3195
  "إهانة العلم"
3196
  ],
 
3197
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3198
  },
3199
  {
 
3208
  "القوانين السابقة",
3209
  "إصدار القوانين"
3210
  ],
 
 
 
3211
  "legal_nature": "حكم انتقالي"
3212
  },
3213
  {
 
3223
  "الأثر الرجعي",
3224
  "تاريخ العمل بالقانون"
3225
  ],
 
3226
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3227
  },
3228
  {
 
3238
  "حظر التعديل",
3239
  "إجراءات التعديل"
3240
  ],
 
3241
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3242
  },
3243
  {
 
3251
  "وحدة الدستور",
3252
  "التكامل"
3253
  ],
 
3254
  "legal_nature": "مبدأ تفسيري"
3255
  },
3256
  {
 
3265
  "الهيئة الوطنية للانتخابات",
3266
  "إشراف قضائي"
3267
  ],
 
3268
  "legal_nature": "حكم انتقالي"
3269
  },
3270
  {
 
3277
  "keywords": [
3278
  "انتخابات مجلس النواب"
3279
  ],
 
 
 
3280
  "legal_nature": "حكم انتقالي"
3281
  },
3282
  {
 
3290
  "مواعيد الانتخابات",
3291
  "إجراءات انتقالية"
3292
  ],
 
3293
  "legal_nature": "حكم انتقالي"
3294
  },
3295
  {
 
3303
  "مدة الرئاسة",
3304
  "إعلان النتيجة"
3305
  ],
 
3306
  "legal_nature": "حكم انتقالي"
3307
  },
3308
  {
 
3316
  "الرئيس المؤقت",
3317
  "تسليم السلطة"
3318
  ],
 
3319
  "legal_nature": "حكم انتقالي"
3320
  },
3321
  {
 
3329
  "خلو المنصب",
3330
  "الرئيس المؤقت"
3331
  ],
 
3332
  "legal_nature": "حكم انتقالي"
3333
  },
3334
  {
 
3343
  "المجلس الأعلى للقوات المسلحة",
3344
  "التعيين"
3345
  ],
 
3346
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3347
  },
3348
  {
 
3357
  "حرية العقيدة",
3358
  "المسيحيين"
3359
  ],
 
3360
  "legal_nature": "التزام تشريعي"
3361
  },
3362
  {
 
3372
  "المناطق الحدودية",
3373
  "العدالة الاجتماعية"
3374
  ],
 
3375
  "legal_nature": "حق أساسي/حرية"
3376
  },
3377
  {
 
3386
  "تمويل الإرهاب",
3387
  "تعويض الأضرار"
3388
  ],
 
3389
  "legal_nature": "التزام/واجب على الدولة"
3390
  },
3391
  {
 
3401
  "الصحة",
3402
  "التعليم الإلزامي"
3403
  ],
 
3404
  "legal_nature": "حكم انتقالي"
3405
  },
3406
  {
 
3414
  "ندب القضاة",
3415
  "استقلال القضاء"
3416
  ],
 
3417
  "legal_nature": "حكم انتقالي"
3418
  },
3419
  {
 
3427
  "استئناف الجنايات",
3428
  "درجات التقاضي"
3429
  ],
 
3430
  "legal_nature": "حكم انتقالي"
3431
  },
3432
  {
 
3441
  "المصالحة الوطنية",
3442
  "تعويض الضحايا"
3443
  ],
 
3444
  "legal_nature": "التزام تشريعي"
3445
  },
3446
  {
 
3454
  "مدة الرئاسة",
3455
  "تعديلات 2019"
3456
  ],
 
3457
  "legal_nature": "قاعدة إجرائية"
3458
  },
3459
  {
 
3467
  "الإدارة المحلية",
3468
  "تطبيق تدريجي"
3469
  ],
 
 
 
3470
  "legal_nature": "حكم انتقالي"
3471
  },
3472
  {
 
3480
  "تمثيل العمال والفلاحين",
3481
  "مجلس النواب"
3482
  ],
 
3483
  "legal_nature": "التزام/واجب على الدولة"
3484
  },
3485
  {
 
3496
  "ذوي الإعاقة",
3497
  "المصريين بالخارج"
3498
  ],
 
3499
  "legal_nature": "التزام/واجب على الدولة"
3500
  },
3501
  {
 
3509
  "كوتا المرأة",
3510
  "سريان التعديلات"
3511
  ],
 
 
 
3512
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3513
  },
3514
  {
 
3523
  "مجلس النواب",
3524
  "الموظفين"
3525
  ],
 
3526
  "legal_nature": "حكم انتقالي"
3527
  },
3528
  {
 
3536
  "إلغاء دستوري",
3537
  "الإعلان الدستوري"
3538
  ],
 
3539
  "legal_nature": "قيمة تعليمية/تثقيفية/ثقافية"
3540
  },
3541
  {
 
3549
  "نفاذ الدستور",
3550
  "الاستفتاء الشعبي"
3551
  ],
 
3552
  "legal_nature": "غير محدد / يحتاج مراجعة بشرية"
3553
  },
3554
  {
 
3564
  "السلام الاجتماعي",
3565
  "الحقوق والحريات"
3566
  ],
 
3567
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3568
  },
3569
  {
 
3580
  "المعاهدات الدولية",
3581
  "السياسة العامة"
3582
  ],
 
3583
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3584
  },
3585
  {
 
3595
  "التعيين",
3596
  "مدة العضوية"
3597
  ],
 
3598
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3599
  },
3600
  {
 
3610
  "السن القانوني",
3611
  "النظام الانتخابي"
3612
  ],
 
3613
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3614
  },
3615
  {
 
3623
  "حظر الجمع",
3624
  "مجلس النواب"
3625
  ],
 
3626
  "legal_nature": "حظر دستوري"
3627
  },
3628
  {
 
3637
  "الحكومة",
3638
  "علاقة السلطات"
3639
  ],
 
3640
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3641
  },
3642
  {
 
3652
  "الواجبات",
3653
  "الإجراءات"
3654
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3655
  "legal_nature": "تنظيم مؤسسي/هيكلي"
3656
  }
 
 
3657
  ]
data/Egyptian_Labour_Law.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/Egyptian_Personal Status Laws.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/Technology Crimes Law.json CHANGED
The diff for this file is too large to render. See raw diff
 
data/قانون_الإجراءات_الجنائية.json CHANGED
The diff for this file is too large to render. See raw diff
 
evaluate.py DELETED
@@ -1,620 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- RAGAS Evaluation Script for Constitutional Legal Assistant
4
- Evaluates: faithfulness, answer_relevancy, context_precision, context_recall
5
- """
6
-
7
- import os
8
- import json
9
- from dotenv import load_dotenv
10
- import logging
11
- import warnings
12
-
13
- # Suppress progress bars
14
- os.environ['TRANSFORMERS_NO_PROGRESS_BAR'] = '1'
15
- warnings.filterwarnings('ignore')
16
-
17
- # Core imports
18
- from langchain_core.documents import Document
19
- from langchain_core.retrievers import BaseRetriever
20
- from langchain_core.callbacks import CallbackManagerForRetrieverRun
21
- from typing import List
22
- from rank_bm25 import BM25Okapi
23
- import numpy as np
24
-
25
- # Vector Store & Embeddings
26
- from langchain_chroma import Chroma
27
- from langchain_huggingface import HuggingFaceEmbeddings
28
-
29
- # Reranker
30
- from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
31
- from langchain_classic.retrievers import ContextualCompressionRetriever
32
- from langchain_community.cross_encoders import HuggingFaceCrossEncoder
33
-
34
- # LLM
35
- from langchain_groq import ChatGroq
36
- from langchain_core.prompts import ChatPromptTemplate
37
- from langchain_core.output_parsers import StrOutputParser
38
- from langchain_core.runnables import RunnablePassthrough, RunnableParallel
39
-
40
- # Evaluation
41
- from datasets import Dataset
42
- from ragas import evaluate
43
- from ragas.metrics import (
44
- faithfulness,
45
- answer_relevancy,
46
- context_precision,
47
- context_recall,
48
- )
49
- from ragas.llms import LangchainLLMWrapper
50
- from ragas.embeddings import LangchainEmbeddingsWrapper
51
-
52
- # Configure logging
53
- logging.basicConfig(level=logging.INFO)
54
- logger = logging.getLogger(__name__)
55
-
56
- load_dotenv()
57
-
58
- # ==========================================
59
- # 🚀 RAG PIPELINE INITIALIZATION
60
- # ==========================================
61
-
62
- def initialize_rag_pipeline():
63
- """Initialize the RAG pipeline for constitutional legal questions"""
64
- print("🔄 Initializing RAG pipeline...")
65
- print("📥 Loading data...")
66
-
67
- # 1. Load JSON
68
- json_path = "Egyptian_Constitution_legalnature_only.json"
69
- if not os.path.exists(json_path):
70
- raise FileNotFoundError(f"File not found: {json_path}")
71
-
72
- with open(json_path, "r", encoding="utf-8") as f:
73
- data = json.load(f)
74
-
75
- # Create article mapping for cross-references
76
- article_map = {str(item['article_number']): item for item in data}
77
-
78
- docs = []
79
- for item in data:
80
- # Build cross-reference section
81
- cross_ref_text = ""
82
- if item.get('cross_references') and len(item['cross_references']) > 0:
83
- cross_ref_text = "\nالمواد ذات الصلة (المراجع المتقاطعة): " + ", ".join(
84
- [f"المادة {ref}" for ref in item['cross_references']]
85
- )
86
-
87
- # Construct document content
88
- page_content = f"""
89
- رقم المادة: {item['article_number']}
90
- النص الأصلي: {item['original_text']}
91
- الشرح المبسط: {item['simplified_summary']}{cross_ref_text}
92
- """
93
-
94
- metadata = {
95
- "article_id": item['article_id'],
96
- "article_number": str(item['article_number']),
97
- "legal_nature": item['legal_nature'],
98
- "keywords": ", ".join(item['keywords']),
99
- "part": item.get('part (Bab)', ''),
100
- "chapter": item.get('chapter (Fasl)', ''),
101
- "cross_references": ", ".join([str(ref) for ref in item.get('cross_references', [])])
102
- }
103
- docs.append(Document(page_content=page_content, metadata=metadata))
104
-
105
- print(f"✅ Loaded {len(docs)} constitutional articles")
106
-
107
- # 2. Embeddings
108
- print("Loading embeddings model...")
109
- embeddings = HuggingFaceEmbeddings(
110
- model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1"
111
- )
112
- print("✅ Embeddings ready")
113
-
114
- # 3. Vector Store
115
- print("Building vector database...")
116
- vectorstore = Chroma.from_documents(
117
- docs,
118
- embeddings,
119
- persist_directory="chroma_db"
120
- )
121
- base_retriever = vectorstore.as_retriever(search_kwargs={"k": 15})
122
- print("✅ Vector database ready")
123
-
124
- # 4. BM25 Keyword Retriever
125
- class BM25Retriever(BaseRetriever):
126
- """BM25-based keyword retriever"""
127
- corpus_docs: List[Document]
128
- bm25: BM25Okapi = None
129
- k: int = 15
130
-
131
- class Config:
132
- arbitrary_types_allowed = True
133
-
134
- def __init__(self, **data):
135
- super().__init__(**data)
136
- tokenized_corpus = [doc.page_content.split() for doc in self.corpus_docs]
137
- self.bm25 = BM25Okapi(tokenized_corpus)
138
-
139
- def _get_relevant_documents(
140
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
141
- ) -> List[Document]:
142
- tokenized_query = query.split()
143
- scores = self.bm25.get_scores(tokenized_query)
144
- top_indices = np.argsort(scores)[::-1][:self.k]
145
- return [self.corpus_docs[i] for i in top_indices if scores[i] > 0]
146
-
147
- async def _aget_relevant_documents(self, query: str, **kwargs) -> List[Document]:
148
- return self._get_relevant_documents(query)
149
-
150
- bm25_retriever = BM25Retriever(corpus_docs=docs, k=15)
151
- print("✅ BM25 retriever ready")
152
-
153
- # 5. Metadata Filter Retriever
154
- class MetadataFilterRetriever(BaseRetriever):
155
- """Metadata-based filtering retriever"""
156
- corpus_docs: List[Document]
157
- k: int = 15
158
-
159
- class Config:
160
- arbitrary_types_allowed = True
161
-
162
- def _get_relevant_documents(
163
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
164
- ) -> List[Document]:
165
- query_lower = query.lower()
166
- scored_docs = []
167
-
168
- for doc in self.corpus_docs:
169
- score = 0
170
- keywords = doc.metadata.get('keywords', '').lower()
171
- if any(word in keywords for word in query_lower.split()):
172
- score += 3
173
-
174
- legal_nature = doc.metadata.get('legal_nature', '').lower()
175
- if any(word in legal_nature for word in query_lower.split()):
176
- score += 2
177
-
178
- part = doc.metadata.get('part', '').lower()
179
- chapter = doc.metadata.get('chapter', '').lower()
180
- if any(word in part or word in chapter for word in query_lower.split()):
181
- score += 1
182
-
183
- if any(word in doc.page_content.lower() for word in query_lower.split()):
184
- score += 1
185
-
186
- if score > 0:
187
- scored_docs.append((doc, score))
188
-
189
- scored_docs.sort(key=lambda x: x[1], reverse=True)
190
- return [doc for doc, _ in scored_docs[:self.k]]
191
-
192
- async def _aget_relevant_documents(self, query: str, **kwargs) -> List[Document]:
193
- return self._get_relevant_documents(query)
194
-
195
- metadata_retriever = MetadataFilterRetriever(corpus_docs=docs, k=15)
196
- print("✅ Metadata retriever ready")
197
-
198
- # 6. Hybrid RRF Retriever
199
- class HybridRRFRetriever(BaseRetriever):
200
- """Combines semantic, BM25, and metadata using Reciprocal Rank Fusion"""
201
- semantic_retriever: BaseRetriever
202
- bm25_retriever: BM25Retriever
203
- metadata_retriever: MetadataFilterRetriever
204
- beta_semantic: float = 0.5
205
- beta_keyword: float = 0.3
206
- beta_metadata: float = 0.2
207
- k: int = 60
208
- top_k: int = 15
209
-
210
- class Config:
211
- arbitrary_types_allowed = True
212
-
213
- def _get_relevant_documents(
214
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
215
- ) -> List[Document]:
216
- semantic_docs = self.semantic_retriever.invoke(query)
217
- bm25_docs = self.bm25_retriever.invoke(query)
218
- metadata_docs = self.metadata_retriever.invoke(query)
219
-
220
- rrf_scores = {}
221
-
222
- for rank, doc in enumerate(semantic_docs, start=1):
223
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
224
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_semantic / (self.k + rank)
225
-
226
- for rank, doc in enumerate(bm25_docs, start=1):
227
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
228
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_keyword / (self.k + rank)
229
-
230
- for rank, doc in enumerate(metadata_docs, start=1):
231
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
232
- rrf_scores[doc_id] = rrf_scores.get(doc_id, 0) + self.beta_metadata / (self.k + rank)
233
-
234
- all_docs = {}
235
- for doc in semantic_docs + bm25_docs + metadata_docs:
236
- doc_id = doc.metadata.get('article_number', str(hash(doc.page_content)))
237
- if doc_id not in all_docs:
238
- all_docs[doc_id] = doc
239
-
240
- sorted_doc_ids = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
241
- result_docs = []
242
- for doc_id, score in sorted_doc_ids[:self.top_k]:
243
- if doc_id in all_docs:
244
- result_docs.append(all_docs[doc_id])
245
-
246
- return result_docs
247
-
248
- async def _aget_relevant_documents(self, query: str, **kwargs) -> List[Document]:
249
- return self._get_relevant_documents(query)
250
-
251
- hybrid_retriever = HybridRRFRetriever(
252
- semantic_retriever=base_retriever,
253
- bm25_retriever=bm25_retriever,
254
- metadata_retriever=metadata_retriever,
255
- beta_semantic=0.5,
256
- beta_keyword=0.3,
257
- beta_metadata=0.2,
258
- k=60,
259
- top_k=20
260
- )
261
- print("✅ Hybrid RRF retriever ready (β: semantic=0.5, keyword=0.3, metadata=0.2)")
262
-
263
- # 7. Cross-Reference Retriever
264
- class CrossReferenceRetriever(BaseRetriever):
265
- """Enhances retrieval by fetching cross-referenced articles"""
266
- base_retriever: BaseRetriever
267
- article_map: dict
268
-
269
- class Config:
270
- arbitrary_types_allowed = True
271
-
272
- def _get_relevant_documents(
273
- self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None
274
- ) -> List[Document]:
275
- initial_docs = self.base_retriever.invoke(query)
276
-
277
- all_article_numbers = set()
278
- for doc in initial_docs:
279
- if 'article_number' in doc.metadata:
280
- all_article_numbers.add(doc.metadata['article_number'])
281
- cross_refs_str = doc.metadata.get('cross_references', '')
282
- if cross_refs_str:
283
- cross_refs = [ref.strip() for ref in cross_refs_str.split(',')]
284
- for ref in cross_refs:
285
- if ref:
286
- all_article_numbers.add(str(ref))
287
-
288
- enhanced_docs = []
289
- seen_numbers = set()
290
-
291
- for doc in initial_docs:
292
- enhanced_docs.append(doc)
293
- seen_numbers.add(doc.metadata.get('article_number'))
294
-
295
- for article_num in all_article_numbers:
296
- if article_num not in seen_numbers and article_num in self.article_map:
297
- article_data = self.article_map[article_num]
298
- cross_ref_text = ""
299
- if article_data.get('cross_references'):
300
- cross_ref_text = "\nالمواد ذات الصلة: " + ", ".join(
301
- [f"المادة {ref}" for ref in article_data['cross_references']]
302
- )
303
-
304
- page_content = f"""
305
- رقم المادة: {article_data['article_number']}
306
- النص الأصلي: {article_data['original_text']}
307
- الشرح المبسط: {article_data['simplified_summary']}{cross_ref_text}
308
- """
309
-
310
- enhanced_doc = Document(
311
- page_content=page_content,
312
- metadata={
313
- "article_id": article_data['article_id'],
314
- "article_number": str(article_data['article_number']),
315
- "legal_nature": article_data['legal_nature'],
316
- "keywords": ", ".join(article_data['keywords']),
317
- "cross_references": ", ".join([str(ref) for ref in article_data.get('cross_references', [])])
318
- }
319
- )
320
- enhanced_docs.append(enhanced_doc)
321
- seen_numbers.add(article_num)
322
-
323
- return enhanced_docs
324
-
325
- async def _aget_relevant_documents(self, query: str, **kwargs) -> List[Document]:
326
- return self._get_relevant_documents(query)
327
-
328
- cross_ref_retriever = CrossReferenceRetriever(
329
- base_retriever=hybrid_retriever,
330
- article_map=article_map
331
- )
332
- print("✅ Cross-reference retriever ready")
333
-
334
- # 8. Reranker
335
- print("Loading reranker model...")
336
- local_model_path = r"D:\FOE\Senior 2\Graduation Project\Chatbot_me\reranker"
337
-
338
- if not os.path.exists(local_model_path):
339
- raise FileNotFoundError(f"Reranker path not found: {local_model_path}")
340
-
341
- model = HuggingFaceCrossEncoder(model_name=local_model_path)
342
- compressor = CrossEncoderReranker(model=model, top_n=5)
343
-
344
- compression_retriever = ContextualCompressionRetriever(
345
- base_compressor=compressor,
346
- base_retriever=cross_ref_retriever
347
- )
348
- print("✅ Reranker ready (top_n=5)")
349
-
350
- # 9. LLM Configuration
351
- llm = ChatGroq(
352
- groq_api_key=os.getenv("GROQ_API_KEY"),
353
- model_name="llama-3.1-8b-instant",
354
- temperature=0.3,
355
- model_kwargs={"top_p": 0.9}
356
- )
357
-
358
- # 10. Prompt Template
359
- system_instructions = """
360
- <role>
361
- أنت "المساعد القانوني الذكي"، خبير متخصص في الدستور المصري والقوانين الإجرائية.
362
- مهمتك: تقديم إجابات دقيقة بناءً على "السياق التشريعي" المرفق أولاً، أو تقديم نصائح إجرائية عامة عند الضرورة.
363
- </role>
364
-
365
- <decision_logic>
366
- عليك تحليل "سؤال المستخدم" و"السياق التشريعي" وتصنيف الحالة واختيار الرد المناسب:
367
-
368
- 🔴 الحالة الأولى: (الإجابة موجودة في السياق التشريعي)
369
- - استخرج الإجابة من السياق فقط
370
- - ابدأ الإجابة مباشرة دون مقدمات
371
- - وثق الإجابة برقم المادة
372
- - توقف، لا تضف معلومات خارجية
373
-
374
- 🟡 الحالة ��لثانية: (السياق فارغ + السؤال إجرائي/عملي)
375
- - استخدم معرفتك العامة بالقانون المصري
376
- - ابدأ بـ: "بناءً على الإجراءات القانونية العامة في مصر:"
377
- - قدم الخطوات في نقاط مرقمة
378
-
379
- 🔵 الحالة الثالثة: (السياق فارغ + سؤال دستوري)
380
- - قل: "عذراً، لم يرد ذكر لهذا في المواد المسترجاعة"
381
- - لا تخترع نصوصاً دستورية
382
-
383
- 🟢 الحالة الرابعة: (تحية/شكر)
384
- - رد بتحية مهذبة مختصرة
385
-
386
- ⚫ الحالة الخامسة: (خارج النطاق)
387
- - اعتذر بلطف ووجه للقانون
388
- </decision_logic>
389
-
390
- <formatting_rules>
391
- - استخدم فقرات قصيرة واترك سطراً فارغاً بينها
392
- - التزم باللغة العربية الفصحى المبسطة
393
- </formatting_rules>
394
- """
395
-
396
- prompt = ChatPromptTemplate.from_messages([
397
- ("system", system_instructions),
398
- ("system", "السياق التشريعي المتاح:\n{context}"),
399
- ("human", "السؤال:\n{input}")
400
- ])
401
-
402
- # 11. Build QA Chain
403
- qa_chain = (
404
- RunnableParallel({
405
- "context": compression_retriever,
406
- "input": RunnablePassthrough()
407
- })
408
- .assign(answer=(
409
- prompt
410
- | llm
411
- | StrOutputParser()
412
- ))
413
- )
414
-
415
- print("✅ RAG pipeline initialized!\n")
416
- return qa_chain
417
-
418
- # ==========================================
419
- # 📊 RAGAS EVALUATION
420
- # ==========================================
421
-
422
- def run_evaluation(test_file="test_dataset.json", output_file="evaluation_results.json"):
423
- """Run RAGAS evaluation on test dataset"""
424
-
425
- print("\n" + "="*60)
426
- print("📊 RAGAS EVALUATION")
427
- print("="*60)
428
-
429
- # Load test dataset
430
- print(f"\n📂 Loading test dataset: {test_file}")
431
- with open(test_file, "r", encoding="utf-8") as f:
432
- test_questions = json.load(f)
433
- print(f"✅ Loaded {len(test_questions)} test questions")
434
-
435
- # Initialize RAG pipeline
436
- print("\n📥 Initializing RAG pipeline...")
437
- qa_chain = initialize_rag_pipeline()
438
-
439
- # Generate answers
440
- print("\n🤖 Generating answers for evaluation...")
441
- results = {
442
- "question": [],
443
- "answer": [],
444
- "contexts": [],
445
- "ground_truth": []
446
- }
447
-
448
- for idx, item in enumerate(test_questions, 1):
449
- question = item["question"]
450
- ground_truth = item.get("ground_truth", "")
451
-
452
- print(f" [{idx}/{len(test_questions)}] Processing question {idx}...")
453
-
454
- try:
455
- result = qa_chain.invoke(question)
456
- answer = result["answer"]
457
- contexts = [doc.page_content for doc in result["context"]]
458
-
459
- results["question"].append(question)
460
- results["answer"].append(answer)
461
- results["contexts"].append(contexts)
462
- results["ground_truth"].append(ground_truth)
463
-
464
- except Exception as e:
465
- print(f" ❌ Error: {str(e)[:100]}")
466
- results["question"].append(question)
467
- results["answer"].append("Error generating answer")
468
- results["contexts"].append([])
469
- results["ground_truth"].append(ground_truth)
470
-
471
- # Run Ragas evaluation
472
- print("\n⚙️ Running RAGAS metrics...")
473
- dataset = Dataset.from_dict(results)
474
-
475
- # Configure evaluation LLM (same as main app)
476
- print(" 📌 Using Groq (llama-3.1-8b-instant, temp=0.3, top_p=0.9)")
477
- evaluator_llm = LangchainLLMWrapper(ChatGroq(
478
- groq_api_key=os.getenv("GROQ_API_KEY"),
479
- model_name="llama-3.1-8b-instant",
480
- temperature=0.3,
481
- model_kwargs={"top_p": 0.9},
482
- max_retries=2
483
- ))
484
-
485
- # Configure evaluation embeddings (same as main app)
486
- print(" 📌 Using HuggingFace (Omartificial-Intelligence-Space/GATE-AraBert-v1)")
487
- evaluator_embeddings = LangchainEmbeddingsWrapper(HuggingFaceEmbeddings(
488
- model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1"
489
- ))
490
-
491
- try:
492
- import time
493
- print("\n ⏳ Evaluating each question separately with all metrics...")
494
- print(" ⚠️ This will take ~10-15 minutes (120 sec delay between questions)\n")
495
-
496
- # Evaluate each question separately to see results immediately
497
- all_scores = {
498
- "faithfulness": [],
499
- "answer_relevancy": [],
500
- "context_precision": [],
501
- "context_recall": []
502
- }
503
-
504
- for q_idx in range(len(results["question"])):
505
- print(f"\n 📋 Question {q_idx + 1}/{len(results['question'])}: {results['question'][q_idx][:60]}...")
506
-
507
- # Create single-question dataset
508
- single_q_data = {
509
- "question": [results["question"][q_idx]],
510
- "answer": [results["answer"][q_idx]],
511
- "contexts": [results["contexts"][q_idx]],
512
- "ground_truth": [results["ground_truth"][q_idx]]
513
- }
514
- single_dataset = Dataset.from_dict(single_q_data)
515
-
516
- # Evaluate all metrics for this question
517
- try:
518
- q_result = evaluate(
519
- single_dataset,
520
- metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
521
- llm=evaluator_llm,
522
- embeddings=evaluator_embeddings,
523
- raise_exceptions=False
524
- )
525
-
526
- # Extract scores (handle if they're lists or single values)
527
- def get_score(value):
528
- if isinstance(value, list):
529
- return value[0] if len(value) > 0 else 0.0
530
- return float(value) if value is not None else 0.0
531
-
532
- f_score = get_score(q_result['faithfulness'])
533
- a_score = get_score(q_result['answer_relevancy'])
534
- cp_score = get_score(q_result['context_precision'])
535
- cr_score = get_score(q_result['context_recall'])
536
-
537
- # Display scores for this question
538
- print(f" Faithfulness : {f_score:.4f}")
539
- print(f" Answer Relevancy : {a_score:.4f}")
540
- print(f" Context Precision : {cp_score:.4f}")
541
- print(f" Context Recall : {cr_score:.4f}")
542
-
543
- all_scores["faithfulness"].append(f_score)
544
- all_scores["answer_relevancy"].append(a_score)
545
- all_scores["context_precision"].append(cp_score)
546
- all_scores["context_recall"].append(cr_score)
547
-
548
- except Exception as e:
549
- print(f" ❌ Error evaluating this question: {str(e)[:80]}")
550
- all_scores["faithfulness"].append(0.0)
551
- all_scores["answer_relevancy"].append(0.0)
552
- all_scores["context_precision"].append(0.0)
553
- all_scores["context_recall"].append(0.0)
554
-
555
- # Wait between questions to avoid rate limits
556
- if q_idx < len(results["question"]) - 1:
557
- print(f"\n ⏳ Waiting 120 seconds (2 min) before next question...")
558
- time.sleep(120)
559
-
560
- # Calculate average scores
561
- eval_results = {
562
- "faithfulness": sum(all_scores["faithfulness"]) / len(all_scores["faithfulness"]) if all_scores["faithfulness"] else 0.0,
563
- "answer_relevancy": sum(all_scores["answer_relevancy"]) / len(all_scores["answer_relevancy"]) if all_scores["answer_relevancy"] else 0.0,
564
- "context_precision": sum(all_scores["context_precision"]) / len(all_scores["context_precision"]) if all_scores["context_precision"] else 0.0,
565
- "context_recall": sum(all_scores["context_recall"]) / len(all_scores["context_recall"]) if all_scores["context_recall"] else 0.0
566
- }
567
-
568
- # Display results
569
- print("\n" + "="*60)
570
- print("📈 EVALUATION RESULTS")
571
- print("="*60)
572
-
573
- for metric, score in eval_results.items():
574
- if isinstance(score, (int, float)):
575
- print(f" {metric:28s}: {score:.4f}")
576
-
577
- # Save results to JSON
578
- with open(output_file, "w", encoding="utf-8") as f:
579
- results_dict = {
580
- "metrics": {k: float(v) if isinstance(v, (int, float)) else str(v)
581
- for k, v in eval_results.items()},
582
- "test_samples": len(dataset),
583
- "test_file": test_file
584
- }
585
- json.dump(results_dict, f, ensure_ascii=False, indent=2)
586
-
587
- print(f"\n💾 Results saved to: {output_file}")
588
- print("="*60 + "\n")
589
-
590
- return eval_results
591
-
592
- except Exception as e:
593
- print(f"\n❌ Evaluation failed: {e}")
594
- print("\n⚠️ Make sure:")
595
- print(" 1. GROQ_API_KEY is set in .env")
596
- print(" 2. You have valid Groq API credits")
597
- print(" 3. Internet connection is available")
598
- return None
599
-
600
- # ==========================================
601
- # 🎯 MAIN EXECUTION
602
- # ==========================================
603
-
604
- if __name__ == "__main__":
605
- import sys
606
-
607
- test_file = "test_dataset.json"
608
- output_file = "evaluation_results.json"
609
-
610
- # Check for command line arguments
611
- if len(sys.argv) > 1:
612
- test_file = sys.argv[1]
613
- if len(sys.argv) > 2:
614
- output_file = sys.argv[2]
615
-
616
- print("\n" + "="*60)
617
- print("🚀 Constitutional Legal Assistant - RAGAS Evaluation")
618
- print("="*60)
619
-
620
- run_evaluation(test_file, output_file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
evaluate_rag.py CHANGED
@@ -46,8 +46,8 @@ from langchain_groq import ChatGroq
46
  from langchain_huggingface import HuggingFaceEmbeddings
47
  import logging
48
 
49
- # Import the RAG pipeline initialization
50
- from app_final_updated import initialize_rag_pipeline
51
 
52
  # Suppress verbose API logging
53
  logging.getLogger("httpx").setLevel(logging.WARNING)
@@ -200,21 +200,18 @@ def run_evaluation():
200
  print(f"{'-'*60}")
201
 
202
  try:
203
- # Invoke the chain
204
- result = qa_chain.invoke(question)
205
-
206
- answer = result["answer"]
207
- context_docs = result["context"]
208
-
209
- # Extract context text from documents
210
- contexts = [doc.page_content for doc in context_docs]
211
-
212
  # Store results
213
  results["question"].append(question)
214
  results["answer"].append(answer)
215
  results["contexts"].append(contexts)
216
  results["ground_truth"].append(ground_truth)
217
-
218
  print(f"✅ Generated answer ({len(answer)} chars)")
219
  print(f"✅ Retrieved {len(contexts)} context documents")
220
 
 
46
  from langchain_huggingface import HuggingFaceEmbeddings
47
  import logging
48
 
49
+ # Import the RAG pipeline initialization from the main RAG module
50
+ from rag import initialize_rag_pipeline, ask
51
 
52
  # Suppress verbose API logging
53
  logging.getLogger("httpx").setLevel(logging.WARNING)
 
200
  print(f"{'-'*60}")
201
 
202
  try:
203
+ # Use rag.ask which returns (answer, sources)
204
+ answer, sources = ask(question)
205
+
206
+ # sources is a list of dicts produced by rag._docs_to_sources
207
+ contexts = [s.get("content", "") for s in sources]
208
+
 
 
 
209
  # Store results
210
  results["question"].append(question)
211
  results["answer"].append(answer)
212
  results["contexts"].append(contexts)
213
  results["ground_truth"].append(ground_truth)
214
+
215
  print(f"✅ Generated answer ({len(answer)} chars)")
216
  print(f"✅ Retrieved {len(contexts)} context documents")
217
 
evaluation_results.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "metrics": {
3
- "faithfulness": 0.8375,
4
- "answer_relevancy": 0.4021723436510297,
5
- "context_precision": 0.9999999999583333,
6
- "context_recall": NaN },
7
- "test_samples": 2,
8
- "test_file": "test_dataset.json"
9
- }
 
 
 
 
 
 
 
 
 
 
models.py DELETED
@@ -1,27 +0,0 @@
1
- from pydantic import BaseModel, Field
2
- from typing import List, Optional, Dict, Any
3
-
4
- class AskRequest(BaseModel):
5
- question: str
6
-
7
- class SourceItem(BaseModel):
8
- law_key: Optional[str] = None
9
- law_name: Optional[str] = None
10
- article_number: Optional[str] = None
11
- legal_nature: Optional[str] = None
12
- keywords: Optional[str] = None
13
- content: Optional[str] = None
14
- metadata: Optional[Dict[str, Any]] = None
15
-
16
- class ArticleItem(BaseModel):
17
- law_key: Optional[str] = None
18
- law_name: Optional[str] = None
19
- article_number: Optional[str] = None
20
- original_text: Optional[str] = None
21
- simplified_summary: Optional[str] = None
22
- legal_nature: Optional[str] = None
23
-
24
- class AskResponse(BaseModel):
25
- answer: Optional[str] = None
26
- articles: Optional[List[ArticleItem]] = None
27
- sources: List[SourceItem] = Field(default_factory=list)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag.py DELETED
@@ -1,840 +0,0 @@
1
- # rag.py
2
- import os
3
- import json
4
- import logging
5
- import warnings
6
- from typing import Any, Dict, List, Tuple, Optional, Set
7
-
8
- from dotenv import load_dotenv
9
-
10
- from langchain_core.documents import Document
11
- from langchain_chroma import Chroma
12
- from langchain_huggingface import HuggingFaceEmbeddings
13
-
14
- from langchain_core.retrievers import BaseRetriever
15
- from langchain_core.callbacks import CallbackManagerForRetrieverRun
16
-
17
- from langchain_classic.retrievers.document_compressors import CrossEncoderReranker
18
- from langchain_classic.retrievers import ContextualCompressionRetriever
19
- from langchain_community.cross_encoders import HuggingFaceCrossEncoder
20
-
21
- from langchain_groq import ChatGroq
22
- from langchain_core.prompts import ChatPromptTemplate
23
- from langchain_core.output_parsers import StrOutputParser
24
-
25
- # Optional deps (BM25)
26
- try:
27
- from rank_bm25 import BM25Okapi
28
- import numpy as np
29
- _HAS_BM25 = True
30
- except Exception:
31
- BM25Okapi = None
32
- np = None
33
- _HAS_BM25 = False
34
-
35
-
36
- # ----------------------------
37
- # Global config
38
- # ----------------------------
39
- os.environ["TRANSFORMERS_NO_PROGRESS_BAR"] = "1"
40
- warnings.filterwarnings("ignore")
41
-
42
- logger = logging.getLogger(__name__)
43
- logging.basicConfig(level=logging.INFO)
44
-
45
- load_dotenv()
46
-
47
- THIS_FILE = os.path.abspath(__file__)
48
- BASE_DIR = os.path.dirname(THIS_FILE)
49
-
50
- DATA_DIR = os.path.join(BASE_DIR, "data")
51
- CHROMA_DIR = os.path.join(BASE_DIR, "chroma_db")
52
- RERANKER_DIR = os.path.join(BASE_DIR, "reranker")
53
-
54
- _qa_chain = None
55
-
56
- # Two retrievers One general retriever + many per-law retrievers
57
- _retriever_general: Optional[BaseRetriever] = None
58
- _retrievers_by_law: Dict[str, BaseRetriever] = {}
59
-
60
-
61
- # Composite key map to avoid article_number collisions across laws
62
- _article_map_all: Optional[Dict[str, dict]] = None
63
-
64
- # Canonical keys (keep stable across datasets)
65
- CANON_CONSTITUTION = "egyptian_constitution"
66
-
67
-
68
- # ----------------------------
69
- # Helpers (data loading)
70
- # ----------------------------
71
- def _load_json_folder(folder_path: str) -> List[dict]:
72
- """
73
- Loads all json files and attaches _source_file so we can infer missing law_key/law_name.
74
- Supports:
75
- - list[dict] (either direct articles OR a wrapper dict that contains data/articles)
76
- - dict with "data": list[dict]
77
- - dict with "articles": list[dict]
78
- - single dict
79
- """
80
- all_items: List[dict] = []
81
-
82
- for filename in os.listdir(folder_path):
83
- if not filename.lower().endswith(".json"):
84
- continue
85
-
86
- file_path = os.path.join(folder_path, filename)
87
- with open(file_path, "r", encoding="utf-8") as f:
88
- obj = json.load(f)
89
-
90
- def attach_source(x):
91
- if isinstance(x, dict):
92
- x["_source_file"] = filename
93
- return x
94
-
95
- def flatten_wrapper(wrapper: dict):
96
- """If wrapper has data/articles list, flatten it and propagate top-level law meta."""
97
- if not isinstance(wrapper, dict):
98
- return
99
- top_lk = wrapper.get("law_key")
100
- top_ln = wrapper.get("law_name")
101
-
102
- if "data" in wrapper and isinstance(wrapper["data"], list):
103
- for x in wrapper["data"]:
104
- if isinstance(x, dict):
105
- x.setdefault("law_key", top_lk)
106
- x.setdefault("law_name", top_ln)
107
- all_items.append(attach_source(x))
108
- return
109
-
110
- if "articles" in wrapper and isinstance(wrapper["articles"], list):
111
- for x in wrapper["articles"]:
112
- if isinstance(x, dict):
113
- x.setdefault("law_key", top_lk)
114
- x.setdefault("law_name", top_ln)
115
- all_items.append(attach_source(x))
116
- return
117
-
118
- # Not a wrapper; treat as single article dict
119
- all_items.append(attach_source(wrapper))
120
-
121
- # Case 1: list
122
- if isinstance(obj, list):
123
- for elem in obj:
124
- # elem might be a wrapper dict (law_key/law_name/data) OR an article dict
125
- if isinstance(elem, dict):
126
- flatten_wrapper(elem)
127
- else:
128
- # unexpected, but keep it
129
- all_items.append(elem)
130
- continue
131
-
132
- # Case 2: dict
133
- if isinstance(obj, dict):
134
- flatten_wrapper(obj)
135
- continue
136
-
137
- logger.warning("Unsupported JSON format in: %s", file_path)
138
-
139
- return all_items
140
-
141
-
142
- def _canonicalize_law_key(law_key: Optional[str]) -> Optional[str]:
143
- if not law_key:
144
- return None
145
- lk = str(law_key).strip()
146
- if not lk:
147
- return None
148
- lk_low = lk.lower()
149
-
150
- # Normalize common variants
151
- if lk_low in {"egyptian_constitution", "egyptian-constitution", "constitution", "egypt_constitution"}:
152
- return CANON_CONSTITUTION
153
-
154
- return lk_low
155
-
156
-
157
- def _infer_law_meta(item: dict) -> Tuple[Optional[str], Optional[str]]:
158
- """
159
- Some datasets include law_key/law_name at the top-level only (or are missing entirely).
160
- We infer robustly from:
161
- - item["law_key"]/item["law_name"]
162
- - article_id prefix
163
- - source filename
164
- """
165
- lk = item.get("law_key")
166
- ln = item.get("law_name")
167
-
168
- # If present, canonicalize law_key
169
- lk_canon = _canonicalize_law_key(lk)
170
- if lk_canon:
171
- return lk_canon, (str(ln).strip() if ln else None)
172
-
173
- src = str(item.get("_source_file") or "").lower()
174
- aid = str(item.get("article_id") or "").upper()
175
-
176
- if "constitution" in src or aid.startswith("EG-CONST"):
177
- return CANON_CONSTITUTION, "الدستور المصري"
178
-
179
- if "labour" in src or "labor" in src or aid.startswith("EG-LABOR"):
180
- return "egyptian_labour_law", "قانون العمل"
181
-
182
- if "civil" in src or aid.startswith("EG-CIVIL"):
183
- return "civil_law", "القانون المدني المصري"
184
-
185
- if "personal status" in src or "الأحوال" in src or aid.startswith("EG-PSL"):
186
- return "personal_status", "قانون الأحوال الشخصية"
187
-
188
- if "technology crimes" in src or "tech" in src or aid.startswith("EG-TECH"):
189
- return "tech_crimes", "قانون مكافحة جرائم تقنية المعلومات"
190
-
191
- if "الإجراءات" in src or "الاجراءات" in src or aid.startswith("EG-CRIM-PROC"):
192
- return "criminal_law", "قانون الإجراءات الجنائية"
193
-
194
- return None, (str(ln).strip() if ln else None)
195
-
196
-
197
- def _dedupe_items(items: List[dict]) -> List[dict]:
198
- unique: Dict[str, dict] = {}
199
- for item in items:
200
- law_key, _ = _infer_law_meta(item)
201
- num = str(item.get("article_number", "")).strip()
202
- key = str(item.get("article_id") or (f"{law_key}::{num}" if law_key and num else "") or hash(json.dumps(item, ensure_ascii=False)))
203
- unique[key] = item
204
- return list(unique.values())
205
-
206
-
207
- def _build_documents(items: List[dict]) -> Tuple[List[Document], Dict[str, dict]]:
208
- """
209
- Returns:
210
- - docs: LangChain Documents
211
- - article_map: key = "{law_key}::{article_number}" -> raw dict (for cross-ref fetching)
212
- """
213
- article_map: Dict[str, dict] = {}
214
- docs: List[Document] = []
215
-
216
- for item in items:
217
- law_key, law_name = _infer_law_meta(item)
218
- num = str(item.get("article_number", "")).strip()
219
-
220
- # composite key to avoid collisions
221
- if law_key and num:
222
- article_map[f"{law_key}::{num}"] = item
223
-
224
- cross_refs = item.get("cross_references") or []
225
- cross_ref_text = ""
226
- if isinstance(cross_refs, list) and len(cross_refs) > 0:
227
- cross_ref_text = "\nالمواد ذات الصلة: " + ", ".join([f"المادة {ref}" for ref in cross_refs])
228
-
229
- page_content = f"""
230
- رقم المادة: {item.get('article_number')}
231
- النص الأصلي: {item.get('original_text')}
232
- الشرح المبسط: {item.get('simplified_summary')}{cross_ref_text}
233
- """.strip()
234
-
235
- keywords_list = item.get("keywords", [])
236
- keywords_str = ", ".join(keywords_list) if isinstance(keywords_list, list) else str(keywords_list or "")
237
-
238
- metadata = {
239
- "article_id": item.get("article_id"),
240
- "article_number": num,
241
- "legal_nature": item.get("legal_nature", ""),
242
- "keywords": keywords_str,
243
- "part": item.get("part (Bab)", item.get("part", "")),
244
- "chapter": item.get("chapter (Fasl)", item.get("chapter", "")),
245
- "cross_references": ", ".join([str(ref) for ref in (cross_refs or [])]),
246
- "law_key": law_key,
247
- "law_name": law_name,
248
- }
249
-
250
- docs.append(Document(page_content=page_content, metadata=metadata))
251
-
252
- logger.info("Loaded %d documents", len(docs))
253
- return docs, article_map
254
-
255
-
256
- def _docs_to_context(docs: List[Document]) -> str:
257
- parts = []
258
- for d in docs:
259
- num = str(d.metadata.get("article_number", "")).strip()
260
- law_name = (d.metadata.get("law_name") or "").strip()
261
- content = (d.page_content or "").strip()
262
- header = f"[{law_name} - المادة {num}]" if (law_name and num) else (f"[المادة {num}]" if num else "")
263
- parts.append(f"{header}\n{content}".strip())
264
- return "\n\n---\n\n".join(parts)
265
-
266
-
267
- def _is_constitutional_question(q: str) -> bool:
268
- q = (q or "").strip()
269
- keywords = ["الدستور", "دستوري", "دستورية", "في الدستور", "مواد الدستور", "نص المادة"]
270
- return any(k in q for k in keywords)
271
-
272
-
273
-
274
- def _preferred_law_keys(question: str) -> Optional[Set[str]]:
275
- """
276
- Lightweight topic routing to reduce cross-law contamination.
277
- Returns a set of preferred law_keys (canonical) or None (no preference).
278
- """
279
- q = (question or "").strip().lower()
280
-
281
- # Constitution
282
- if _is_constitutional_question(question):
283
- return {CANON_CONSTITUTION}
284
-
285
- # Labour / workplace
286
- labour_terms = [
287
- "قانون العمل", "العام��", "العمال", "صاحب العمل", "منشأة", "المنشأة",
288
- "الأجر", "الأجور", "إضراب", "الإضراب", "فصل تعسفي", "سخرة", "جبراً", "تحرش", "تنمر", "مكان العمل"
289
- ]
290
- if any(term in q for term in labour_terms):
291
- return {"egyptian_labour_law"}
292
-
293
- # Tech crimes
294
- tech_terms = [
295
- "جرائم تقنية", "تقنية المعلومات", "اختراق", "هاكر", "حساب", "بريد إلكتروني", "ابتزاز", "انتحال", "فيسبوك",
296
- "واتساب", "تلغرام", "نشر", "صور", "بيانات شخصية", "خصوصية", "الشبكة", "موقع", "منصة"
297
- ]
298
- if any(term in q for term in tech_terms):
299
- return {"tech_crimes"}
300
-
301
- # Criminal procedure
302
- proc_terms = [
303
- "إجراءات جنائية", "الإجراءات الجنائية", "محضر", "بلاغ", "قسم", "شرطة", "نيابة", "تحقيق", "حبس احتياطي",
304
- "ضبط", "تفتيش", "قبض", "تظلم"
305
- ]
306
- if any(term in q for term in proc_terms):
307
- return {"criminal_law"}
308
-
309
- # Personal status
310
- ps_terms = ["أحوال شخصية", "نفقة", "حضانة", "طلاق", "خلع", "رؤية", "استضافة", "مؤخر", "قائمة المنقولات"]
311
- if any(term in q for term in ps_terms):
312
- return {"personal_status"}
313
-
314
- # Civil law
315
- civil_terms = ["القانون المدني", "عقد", "التزام", "تعويض", "مسؤولية تقصيرية", "بطلان", "إبطال", "فسخ"]
316
- if any(term in q for term in civil_terms):
317
- return {"civil_law"}
318
-
319
- return None
320
-
321
-
322
-
323
- def _wants_penalty(question: str) -> bool:
324
- """
325
- Returns True if the user explicitly asks about punishment/sanctions (criminal/administrative),
326
- so we are allowed to include "العقوبات" articles. Otherwise, prefer substantive (non-penalty) rules.
327
- """
328
- q = (question or "").strip()
329
- penalty_terms = [
330
- "عقوبة", "العقوبات", "يُعاقب", "يعاقب", "غرامة", "حبس", "سجن", "إغلاق", "غلق",
331
- "جزاء", "جزاءات", "تجريم", "ما عقوبة", "ما هي عقوبة", "كم غرامة"
332
- ]
333
- return any(term in q for term in penalty_terms)
334
-
335
- def _is_procedural_question(q: str) -> bool:
336
- q = (q or "").strip()
337
- proc = ["محضر", "بلاغ", "قسم", "شرطة", "نيابة", "دعوى", "قضية", "طلاق", "نفقة", "حضانة", "إيصال", "شيك"]
338
- return any(k in q for k in proc)
339
-
340
-
341
- # ----------------------------
342
- # Custom retrievers (BM25 / Metadata / Hybrid RRF / Cross-refs)
343
- # ----------------------------
344
- class BM25Retriever(BaseRetriever):
345
- """BM25-based keyword retriever"""
346
- corpus_docs: List[Document]
347
- k: int = 15
348
-
349
- bm25: Any = None
350
-
351
- class Config:
352
- arbitrary_types_allowed = True
353
-
354
- def __init__(self, **data):
355
- super().__init__(**data)
356
- if not _HAS_BM25:
357
- raise RuntimeError("rank_bm25/numpy not installed. Install: pip install rank-bm25 numpy")
358
- tokenized = [doc.page_content.split() for doc in self.corpus_docs]
359
- self.bm25 = BM25Okapi(tokenized)
360
-
361
- def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
362
- tokenized_query = query.split()
363
- scores = self.bm25.get_scores(tokenized_query)
364
- top_idx = np.argsort(scores)[::-1][: self.k]
365
- return [self.corpus_docs[i] for i in top_idx if scores[i] > 0]
366
-
367
- async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
368
- return self._get_relevant_documents(query, run_manager=run_manager)
369
-
370
-
371
- class MetadataFilterRetriever(BaseRetriever):
372
- """Metadata + light content scoring retriever"""
373
- corpus_docs: List[Document]
374
- k: int = 15
375
-
376
- class Config:
377
- arbitrary_types_allowed = True
378
-
379
- def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
380
- q = (query or "").lower()
381
- q_words = [w for w in q.split() if w.strip()]
382
- scored = []
383
-
384
- for doc in self.corpus_docs:
385
- score = 0
386
- keywords = (doc.metadata.get("keywords") or "").lower()
387
- legal_nature = (doc.metadata.get("legal_nature") or "").lower()
388
- part = (doc.metadata.get("part") or "").lower()
389
- chapter = (doc.metadata.get("chapter") or "").lower()
390
- content = (doc.page_content or "").lower()
391
- law_name = (doc.metadata.get("law_name") or "").lower()
392
-
393
- if any(w in keywords for w in q_words):
394
- score += 3
395
- if any(w in legal_nature for w in q_words):
396
- score += 2
397
- if any((w in part) or (w in chapter) for w in q_words):
398
- score += 1
399
- if any(w in content for w in q_words):
400
- score += 1
401
- if any(w in law_name for w in q_words):
402
- score += 1
403
-
404
- if score > 0:
405
- scored.append((doc, score))
406
-
407
- scored.sort(key=lambda x: x[1], reverse=True)
408
- return [doc for doc, _ in scored[: self.k]]
409
-
410
- async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
411
- return self._get_relevant_documents(query, run_manager=run_manager)
412
-
413
-
414
- class HybridRRFRetriever(BaseRetriever):
415
- """
416
- Reciprocal Rank Fusion across:
417
- - semantic retriever (Chroma)
418
- - BM25 retriever
419
- - metadata retriever
420
- """
421
- semantic_retriever: BaseRetriever
422
- bm25_retriever: Optional[BaseRetriever] = None
423
- metadata_retriever: Optional[BaseRetriever] = None
424
-
425
- beta_semantic: float = 0.5
426
- beta_keyword: float = 0.3
427
- beta_metadata: float = 0.2
428
-
429
- rrf_k: int = 60
430
- top_k: int = 20
431
-
432
- class Config:
433
- arbitrary_types_allowed = True
434
-
435
- def _doc_id(self, doc: Document) -> str:
436
- aid = str(doc.metadata.get("article_id") or "").strip()
437
- if aid:
438
- return aid
439
- lk = str(doc.metadata.get("law_key") or "").strip()
440
- num = str(doc.metadata.get("article_number") or "").strip()
441
- if lk and num:
442
- return f"{lk}::{num}"
443
- return str(hash(doc.page_content or ""))
444
-
445
- def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
446
- sem_docs = self.semantic_retriever.invoke(query) or []
447
-
448
- bm_docs = []
449
- if self.bm25_retriever is not None:
450
- try:
451
- bm_docs = self.bm25_retriever.invoke(query) or []
452
- except Exception:
453
- bm_docs = []
454
-
455
- md_docs = []
456
- if self.metadata_retriever is not None:
457
- md_docs = self.metadata_retriever.invoke(query) or []
458
-
459
- rrf_scores: Dict[str, float] = {}
460
- doc_lookup: Dict[str, Document] = {}
461
-
462
- def add_ranked(docs: List[Document], beta: float):
463
- for rank, d in enumerate(docs, start=1):
464
- did = self._doc_id(d)
465
- rrf_scores[did] = rrf_scores.get(did, 0.0) + beta / (self.rrf_k + rank)
466
- if did not in doc_lookup:
467
- doc_lookup[did] = d
468
-
469
- add_ranked(sem_docs, self.beta_semantic)
470
- add_ranked(bm_docs, self.beta_keyword)
471
- add_ranked(md_docs, self.beta_metadata)
472
-
473
- sorted_ids = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
474
- out = []
475
- for did, _ in sorted_ids[: self.top_k]:
476
- if did in doc_lookup:
477
- out.append(doc_lookup[did])
478
- return out
479
-
480
- async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
481
- return self._get_relevant_documents(query, run_manager=run_manager)
482
-
483
-
484
- class CrossReferenceRetriever(BaseRetriever):
485
- """Fetches cross-referenced articles automatically (within the same law)."""
486
- base_retriever: BaseRetriever
487
- article_map: Dict[str, dict]
488
-
489
- class Config:
490
- arbitrary_types_allowed = True
491
-
492
- def _build_doc_from_item(self, item: dict) -> Document:
493
- law_key, law_name = _infer_law_meta(item)
494
-
495
- cross_refs = item.get("cross_references") or []
496
- cross_ref_text = ""
497
- if isinstance(cross_refs, list) and len(cross_refs) > 0:
498
- cross_ref_text = "\nالمواد ذات الصلة: " + ", ".join([f"المادة {ref}" for ref in cross_refs])
499
-
500
- page_content = f"""
501
- رقم المادة: {item.get('article_number')}
502
- النص الأصلي: {item.get('original_text')}
503
- الشرح المبسط: {item.get('simplified_summary')}{cross_ref_text}
504
- """.strip()
505
-
506
- keywords_list = item.get("keywords", [])
507
- keywords_str = ", ".join(keywords_list) if isinstance(keywords_list, list) else str(keywords_list or "")
508
-
509
- metadata = {
510
- "article_id": item.get("article_id"),
511
- "article_number": str(item.get("article_number", "")).strip(),
512
- "legal_nature": item.get("legal_nature", ""),
513
- "keywords": keywords_str,
514
- "cross_references": ", ".join([str(ref) for ref in (cross_refs or [])]),
515
- "law_key": law_key,
516
- "law_name": law_name,
517
- }
518
- return Document(page_content=page_content, metadata=metadata)
519
-
520
- def _doc_uid(self, d: Document) -> str:
521
- aid = str(d.metadata.get("article_id") or "").strip()
522
- if aid:
523
- return aid
524
- lk = str(d.metadata.get("law_key") or "").strip()
525
- num = str(d.metadata.get("article_number") or "").strip()
526
- if lk and num:
527
- return f"{lk}::{num}"
528
- return str(hash(d.page_content or ""))
529
-
530
- def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
531
- initial_docs = self.base_retriever.invoke(query) or []
532
-
533
- enhanced: List[Document] = []
534
- seen: Set[str] = set()
535
-
536
- # add initial docs
537
- for d in initial_docs:
538
- uid = self._doc_uid(d)
539
- if uid in seen:
540
- continue
541
- seen.add(uid)
542
- enhanced.append(d)
543
-
544
- # add cross refs within same law
545
- for d in initial_docs:
546
- lk = str(d.metadata.get("law_key") or "").strip()
547
- if not lk:
548
- continue
549
-
550
- cross_str = (d.metadata.get("cross_references") or "").strip()
551
- if not cross_str:
552
- continue
553
-
554
- for ref in [x.strip() for x in cross_str.split(",")]:
555
- if not ref:
556
- continue
557
- key = f"{lk}::{ref}"
558
- if key in self.article_map:
559
- new_doc = self._build_doc_from_item(self.article_map[key])
560
- uid = self._doc_uid(new_doc)
561
- if uid not in seen:
562
- seen.add(uid)
563
- enhanced.append(new_doc)
564
-
565
- return enhanced
566
-
567
- async def _aget_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun = None) -> List[Document]:
568
- return self._get_relevant_documents(query, run_manager=run_manager)
569
-
570
-
571
- # ----------------------------
572
- # Retriever builder
573
- # ----------------------------
574
- def _build_retriever(
575
- docs_subset: List[Document],
576
- article_map_all: Dict[str, dict],
577
- semantic_retriever: BaseRetriever,
578
- ) -> BaseRetriever:
579
- bm25_retriever = None
580
- if _HAS_BM25:
581
- try:
582
- bm25_retriever = BM25Retriever(corpus_docs=docs_subset, k=15)
583
- logger.info("BM25 enabled for subset=%d", len(docs_subset))
584
- except Exception as e:
585
- logger.warning("BM25 disabled: %s", e)
586
-
587
- metadata_retriever = MetadataFilterRetriever(corpus_docs=docs_subset, k=15)
588
-
589
- hybrid = HybridRRFRetriever(
590
- semantic_retriever=semantic_retriever,
591
- bm25_retriever=bm25_retriever,
592
- metadata_retriever=metadata_retriever,
593
- beta_semantic=0.5,
594
- beta_keyword=0.3,
595
- beta_metadata=0.2,
596
- rrf_k=60,
597
- top_k=20,
598
- )
599
-
600
- cross_ref = CrossReferenceRetriever(base_retriever=hybrid, article_map=article_map_all)
601
-
602
- if not os.path.exists(RERANKER_DIR):
603
- raise FileNotFoundError(f"Reranker folder not found: {RERANKER_DIR}")
604
-
605
- cross_encoder = HuggingFaceCrossEncoder(model_name=RERANKER_DIR)
606
- compressor = CrossEncoderReranker(model=cross_encoder, top_n=5)
607
-
608
- return ContextualCompressionRetriever(base_compressor=compressor, base_retriever=cross_ref)
609
-
610
-
611
- # ----------------------------
612
- # Main initializer
613
- # ----------------------------
614
- def initialize_rag_pipeline():
615
- global _retriever_general, _retrievers_by_law, _article_map_all
616
-
617
- if not os.path.exists(DATA_DIR):
618
- raise FileNotFoundError(f"Data folder not found: {DATA_DIR}")
619
-
620
- data = _load_json_folder(DATA_DIR)
621
- data = _dedupe_items(data)
622
-
623
- docs_all, article_map_all = _build_documents(data)
624
- _article_map_all = article_map_all
625
-
626
- embeddings = HuggingFaceEmbeddings(model_name="Omartificial-Intelligence-Space/GATE-AraBert-v1")
627
-
628
- # Build / load ONE Chroma DB for ALL docs
629
- if os.path.exists(CHROMA_DIR) and os.listdir(CHROMA_DIR):
630
- logger.info("Loading existing Chroma DB: %s", CHROMA_DIR)
631
- vectorstore = Chroma(persist_directory=CHROMA_DIR, embedding_function=embeddings)
632
- else:
633
- logger.info("Building Chroma DB (first time) into: %s", CHROMA_DIR)
634
- vectorstore = Chroma.from_documents(docs_all, embeddings, persist_directory=CHROMA_DIR)
635
-
636
- # General semantic retriever
637
- semantic_general = vectorstore.as_retriever(search_kwargs={"k": 15})
638
-
639
- # ✅ Build general retriever over ALL docs
640
- _retriever_general = _build_retriever(docs_all, article_map_all, semantic_general)
641
-
642
- # ✅ Build a retriever for EACH law_key automatically
643
- _retrievers_by_law = {}
644
- law_keys = sorted({(d.metadata.get("law_key") or "").strip() for d in docs_all if (d.metadata.get("law_key") or "").strip()})
645
- logger.info("Detected law_keys=%s", law_keys)
646
-
647
- for lk in law_keys:
648
- docs_subset = [d for d in docs_all if d.metadata.get("law_key") == lk]
649
- if not docs_subset:
650
- continue
651
-
652
- # Use Chroma metadata filter to keep semantic search inside this law only
653
- semantic_law = vectorstore.as_retriever(search_kwargs={"k": 15, "filter": {"law_key": lk}})
654
- _retrievers_by_law[lk] = _build_retriever(docs_subset, article_map_all, semantic_law)
655
- logger.info("Built retriever for law_key=%s docs=%d", lk, len(docs_subset))
656
-
657
- # --- LLM setup (same as your existing code) ---
658
- groq_key = os.getenv("GROQ_API_KEY")
659
- if not groq_key:
660
- raise ValueError("GROQ_API_KEY is missing. Add it to .env")
661
-
662
- llm = ChatGroq(
663
- groq_api_key=groq_key,
664
- model_name="llama-3.1-8b-instant",
665
- temperature=0.3,
666
- model_kwargs={"top_p": 0.9},
667
- )
668
- system_instructions = """
669
- <role>
670
- أنت "المساعد القانوني الذكي"، خبير متخصص في الدستور المصري والقوانين الإجرائية.
671
- مهمتك: تقديم إجابات دقيقة بناءً على "السياق التشريعي" المرفق أولاً، أو تقديم نصائح إجرائية عامة عند الضرورة.
672
- </role>
673
-
674
- <decision_logic>
675
- 🔴 الحالة الأولى: إذا وجدت معلومات داخل السياق تجيب على السؤال:
676
- - استخرج الإجابة من السياق فقط.
677
- - ابدأ الإجابة مباشرة دون مقدمات.
678
- - وثّق الإجابة برقم المادة.
679
- - لا تضف معلومات خارجية.
680
-
681
- 🟡 الحالة الثانية: إذا لم تجد الإجابة في السياق وكان السؤال إجرائياً:
682
- - ابدأ بعبارة: "بناءً على الإجراءات القانونية العامة في مصر (وليس نصاً دستورياً محدداً):"
683
- - قدّم خطوات مرقمة واضحة.
684
- - لا تذكر أرقام مواد قانونية.
685
-
686
- 🔵 الحالة الثالثة: إذا كان السؤال دستورياً ولم تجد الإجابة في السياق:
687
- - قل بوضوح أنه لم يرد ذكره في المواد المسترجعة.
688
- - لا تجب من الذاكرة.
689
-
690
- 🟢 الحالة الرابعة: تحية/شكر:
691
- - رد مهذب ومقتضب وقل: "أنا جاهز للإجابة على استفساراتك القانونية."
692
-
693
- ⚫ الحالة الخامسة: خارج النطاق:
694
- - اعتذر ووجّه المستخدم لمجال القانون.
695
- </decision_logic>
696
-
697
- <formatting_rules>
698
- - فقرات قصيرة وبينها سطر فارغ.
699
- - لا تكرر هذه التعليمات.
700
- - التزم بالعربية الفصحى المبسطة.
701
- </formatting_rules>
702
- """.strip()
703
-
704
- prompt = ChatPromptTemplate.from_messages([
705
- ("system", system_instructions),
706
- ("system", "السياق التشريعي المتاح:\n{context}"),
707
- ("human", "سؤال المستفيد:\n{input}"),
708
- ])
709
-
710
- qa_chain = (prompt | llm | StrOutputParser())
711
- logger.info("RAG pipeline initialized successfully.")
712
- return qa_chain
713
-
714
-
715
-
716
- # ----------------------------
717
- # Public API (matches main.py)
718
- # ----------------------------
719
- def get_chain():
720
- global _qa_chain
721
- if _qa_chain is None:
722
- _qa_chain = initialize_rag_pipeline()
723
- return _qa_chain
724
-
725
-
726
- def _get_retriever_for_question(question: str) -> BaseRetriever:
727
- get_chain() # ensure initialized
728
-
729
- preferred = _preferred_law_keys(question)
730
- if preferred:
731
- # If multiple laws are returned, choose the first that exists
732
- for lk in preferred:
733
- r = _retrievers_by_law.get(lk)
734
- if r is not None:
735
- return r
736
-
737
- return _retriever_general # fallback
738
-
739
-
740
-
741
- def _docs_to_sources(docs: List[Document]) -> List[Dict[str, Any]]:
742
- sources: List[Dict[str, Any]] = []
743
- seen: Set[str] = set()
744
-
745
- for d in docs:
746
- md = getattr(d, "metadata", {}) or {}
747
- uid = (
748
- md.get("article_id")
749
- or (str(md.get("law_key") or "") + "::" + str(md.get("article_number") or ""))
750
- or str(hash(d.page_content or ""))
751
- )
752
- if uid in seen:
753
- continue
754
- seen.add(uid)
755
-
756
- num = str(md.get("article_number", "")).strip()
757
-
758
- sources.append({
759
- "law_key": md.get("law_key"),
760
- "law_name": md.get("law_name"),
761
- "article_number": num or None,
762
- "legal_nature": md.get("legal_nature"),
763
- "keywords": md.get("keywords"),
764
- "content": (getattr(d, "page_content", "") or "").strip()[:2000],
765
- "metadata": md,
766
- })
767
-
768
- return sources
769
-
770
-
771
- def ask(question: str) -> Tuple[str, List[Dict[str, Any]]]:
772
- chain = get_chain()
773
- retriever = _get_retriever_for_question(question)
774
-
775
- docs = retriever.invoke(question) or []
776
-
777
- # ✅ Topic routing filter: keep only documents from the most relevant law when possible.
778
- preferred = _preferred_law_keys(question)
779
- if preferred:
780
- filtered = [d for d in docs if (d.metadata.get("law_key") in preferred)]
781
- if filtered:
782
- docs = filtered
783
-
784
- # ✅ If top doc has a law_key, prefer staying within that law to avoid mixing.
785
- if docs:
786
- top_law = docs[0].metadata.get("law_key")
787
- if top_law:
788
- same_law = [d for d in docs if d.metadata.get("law_key") == top_law]
789
- if same_law:
790
- docs = same_law
791
-
792
- # ✅ Penalty filter:
793
- # If the user did NOT ask about "عقوبة/غرامة/حبس..." then avoid pulling "العقوبات" articles
794
- # because they often contaminate substantive Q&A (like workplace harassment).
795
- if docs and not _wants_penalty(question):
796
- non_penalty = []
797
- for d in docs:
798
- md = d.metadata or {}
799
- chapter = str(md.get("chapter") or "")
800
- part = str(md.get("part") or "")
801
- legal_nature = str(md.get("legal_nature") or "")
802
- text = (chapter + " " + part + " " + legal_nature)
803
- if "عقوب" in text or "قاعدة عقابية" in text or "تجريم" in text:
804
- continue
805
- non_penalty.append(d)
806
- if non_penalty:
807
- docs = non_penalty
808
-
809
- context_text = _docs_to_context(docs)
810
-
811
- # Hard guard: constitutional questions must be answered from constitution docs only.
812
- preferred = _preferred_law_keys(question)
813
- if preferred:
814
- has_pref = any((d.metadata.get("law_key") in preferred) for d in docs)
815
- if not has_pref:
816
- msg = "عذراً، لم يرد ذكر لهذا الموضوع في المواد المسترجعة من القانون الأكثر صلة بالسؤال ضمن السياق الحالي."
817
- return msg, _docs_to_sources(docs)
818
-
819
-
820
- answer = chain.invoke({"context": context_text, "input": question})
821
-
822
- # ✅ Post-guard 1: if we have ANY useful context, forbid procedural prefix.
823
- if docs:
824
- bad_prefix = "بناءً على الإجراءات القانونية العامة في مصر (وليس نصاً دستورياً محدداً):"
825
- answer = (answer or "").replace(bad_prefix, "").strip()
826
- answer = "\n".join([line for line in answer.splitlines() if line.strip()]).strip()
827
-
828
- # Also remove common hallucination like: "لا توجد مواد قانونية محددة في السياق..."
829
- halluc_lines = [
830
- "لا توجد مواد قانونية محددة في السياق المرفق",
831
- "لا توجد مواد قانونية محددة في السياق",
832
- "لا توجد معلومات داخل السياق",
833
- "لا توجد مواد داخل السياق",
834
- ]
835
- for hl in halluc_lines:
836
- answer = answer.replace(hl, "").strip()
837
- answer = "\n".join([line for line in answer.splitlines() if line.strip()]).strip()
838
-
839
- return answer, _docs_to_sources(docs)
840
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag_test_questions_100.json DELETED
@@ -1,1380 +0,0 @@
1
- {
2
- "schema_version": "1.0",
3
- "dataset_version": "2026-02-03",
4
- "created_at": "2026-02-03T19:15:30.097990+00:00",
5
- "description": "Benchmark set for Egyptian legal RAG assistant: constitutional Qs + multi-law retrieval + procedural gating.",
6
- "laws_in_scope": [
7
- {
8
- "key": "constitution",
9
- "path": "/mnt/data/Egyptian_Constitution_legalnature_only.json"
10
- },
11
- {
12
- "key": "criminal_procedure",
13
- "path": "/mnt/data/قانون_الإجراءات_الجنائية.json"
14
- },
15
- {
16
- "key": "tech_crimes",
17
- "path": "/mnt/data/Technology Crimes Law.json"
18
- },
19
- {
20
- "key": "labor",
21
- "path": "/mnt/data/Egyptian_Labour_Law.json"
22
- },
23
- {
24
- "key": "personal_status",
25
- "path": "/mnt/data/Egyptian_Personal Status Laws.json"
26
- },
27
- {
28
- "key": "civil",
29
- "path": "/mnt/data/Egyptian_Civil.json"
30
- }
31
- ],
32
- "defaults": {
33
- "k": 15,
34
- "rerank_top_n": 5,
35
- "recall_k": 5,
36
- "pass_rule": "pass if recall@k>=1 for questions with expected articles; else pass if answer startswith expected_answer_prefix (if provided)."
37
- },
38
- "cases": [
39
- {
40
- "id": "Q001",
41
- "category": "constitutional",
42
- "question": "ما مضمون المادة 1 من الدستور المصري؟",
43
- "expected": {
44
- "law": "constitution",
45
- "expected_article_numbers": [
46
- "1"
47
- ],
48
- "must_cite_articles": true,
49
- "answer_should_not_add_external_info": true
50
- },
51
- "notes": "جمهورية مصر العربية دولة ذات سيادة، موحدة لا تقبل التجزئة، نظامها جمهوري ديمقراطي يقوم على المواطنة وسيادة القانون. الشعب المصري جزء من الأمة العربية والعالم الإسلامي والقارة الأفريقية."
52
- },
53
- {
54
- "id": "Q002",
55
- "category": "constitutional",
56
- "question": "ما مضمون المادة 2 من الدستور المصري؟",
57
- "expected": {
58
- "law": "constitution",
59
- "expected_article_numbers": [
60
- "2"
61
- ],
62
- "must_cite_articles": true,
63
- "answer_should_not_add_external_info": true
64
- },
65
- "notes": "الإسلام دين الدولة، واللغة العربية لغتها الرسمية، ومبادئ الشريعة الإسلامية هي المصدر الرئيسي للتشريع."
66
- },
67
- {
68
- "id": "Q003",
69
- "category": "constitutional",
70
- "question": "ما مضمون المادة 3 من الدستور المصري؟",
71
- "expected": {
72
- "law": "constitution",
73
- "expected_article_numbers": [
74
- "3"
75
- ],
76
- "must_cite_articles": true,
77
- "answer_should_not_add_external_info": true
78
- },
79
- "notes": "مبادئ شرائع المسيحيين واليهود هي المصدر الرئيسي لتشريعات أحوالهم الشخصية وشئونهم الدينية واختيار قياداتهم."
80
- },
81
- {
82
- "id": "Q004",
83
- "category": "constitutional",
84
- "question": "ما مضمون المادة 4 من الدستور المصري؟",
85
- "expected": {
86
- "law": "constitution",
87
- "expected_article_numbers": [
88
- "4"
89
- ],
90
- "must_cite_articles": true,
91
- "answer_should_not_add_external_info": true
92
- },
93
- "notes": "السيادة للشعب وحده وهو مصدر السلطات، ويصون الوحدة الوطنية القائمة على المساواة والعدل وتكافؤ الفرص."
94
- },
95
- {
96
- "id": "Q005",
97
- "category": "constitutional",
98
- "question": "ما مضمون المادة 5 من الدستور المصري؟",
99
- "expected": {
100
- "law": "constitution",
101
- "expected_article_numbers": [
102
- "5"
103
- ],
104
- "must_cite_articles": true,
105
- "answer_should_not_add_external_info": true
106
- },
107
- "notes": "يقوم النظام السياسي على التعددية السياسية والحزبية، والتداول السلمي للسلطة، والفصل بين السلطات، واحترام حقوق الإنسان."
108
- },
109
- {
110
- "id": "Q006",
111
- "category": "constitutional",
112
- "question": "ما مضمون المادة 6 من الدستور المصري؟",
113
- "expected": {
114
- "law": "constitution",
115
- "expected_article_numbers": [
116
- "6"
117
- ],
118
- "must_cite_articles": true,
119
- "answer_should_not_add_external_info": true
120
- },
121
- "notes": "الجنسية حق لكل من يولد لأب مصري أو أم مصرية، ويكفل القانون الاعتراف القانوني به ومنحه أوراقاً رسمية، ويحدد شروط اكتساب الجنسية."
122
- },
123
- {
124
- "id": "Q007",
125
- "category": "constitutional",
126
- "question": "ما مضمون المادة 7 من الدستور المصري؟",
127
- "expected": {
128
- "law": "constitution",
129
- "expected_article_numbers": [
130
- "7"
131
- ],
132
- "must_cite_articles": true,
133
- "answer_should_not_add_external_info": true
134
- },
135
- "notes": "الأزهر الشريف هيئة مستقلة وهو المرجع الأساسي في العلوم الدينية. تلتزم الدولة بتمويله، وشيخ الأزهر مستقل ولا يمكن عزله."
136
- },
137
- {
138
- "id": "Q008",
139
- "category": "constitutional",
140
- "question": "ما مضمون المادة 8 من الدستور المصري؟",
141
- "expected": {
142
- "law": "constitution",
143
- "expected_article_numbers": [
144
- "8"
145
- ],
146
- "must_cite_articles": true,
147
- "answer_should_not_add_external_info": true
148
- },
149
- "notes": "يقوم المجتمع على التضامن الاجتماعي، وتلتزم الدولة بتحقيق العدالة الاجتماعية والتكافل لضمان حياة كريمة للمواطنين."
150
- },
151
- {
152
- "id": "Q009",
153
- "category": "constitutional",
154
- "question": "ما مضمون المادة 9 من الدستور المصري؟",
155
- "expected": {
156
- "law": "constitution",
157
- "expected_article_numbers": [
158
- "9"
159
- ],
160
- "must_cite_articles": true,
161
- "answer_should_not_add_external_info": true
162
- },
163
- "notes": "تلتزم الدولة بتحقيق تكافؤ الفرص بين جميع المواطنين بلا تمييز."
164
- },
165
- {
166
- "id": "Q010",
167
- "category": "constitutional",
168
- "question": "ما مضمون المادة 10 من الدستور المصري؟",
169
- "expected": {
170
- "law": "constitution",
171
- "expected_article_numbers": [
172
- "10"
173
- ],
174
- "must_cite_articles": true,
175
- "answer_should_not_add_external_info": true
176
- },
177
- "notes": "الأسرة هي أساس المجتمع وتقوم على الدين والأخلاق والوطنية، وتعمل الدولة على حمايتها واستقرارها."
178
- },
179
- {
180
- "id": "Q011",
181
- "category": "constitutional",
182
- "question": "ما مضمون المادة 11 من الدستور المصري؟",
183
- "expected": {
184
- "law": "constitution",
185
- "expected_article_numbers": [
186
- "11"
187
- ],
188
- "must_cite_articles": true,
189
- "answer_should_not_add_external_info": true
190
- },
191
- "notes": "تكفل الدولة المساواة بين الرجل والمرأة في كافة الحقوق، وتضمن تمثيلها في المجالس النيابية وتوليها الوظائف العامة والقضائية، وتحميها من العنف وتوفر الرعاية للفئات الأكثر احتياجاً."
192
- },
193
- {
194
- "id": "Q012",
195
- "category": "constitutional",
196
- "question": "ما مضمون المادة 12 من الدستور المصري؟",
197
- "expected": {
198
- "law": "constitution",
199
- "expected_article_numbers": [
200
- "12"
201
- ],
202
- "must_cite_articles": true,
203
- "answer_should_not_add_external_info": true
204
- },
205
- "notes": "العمل حق وواجب تكفله الدولة. يُمنع العمل الجبري إلا بقانون ولخدمة عامة وبمقابل عادل."
206
- },
207
- {
208
- "id": "Q013",
209
- "category": "constitutional",
210
- "question": "ما مضمون المادة 13 من الدستور المصري؟",
211
- "expected": {
212
- "law": "constitution",
213
- "expected_article_numbers": [
214
- "13"
215
- ],
216
- "must_cite_articles": true,
217
- "answer_should_not_add_external_info": true
218
- },
219
- "notes": "تحافظ الدولة على حقوق العمال وتضمن علاقات عمل متوازنة والتفاوض الجماعي، وتحميهم من المخاطر والفصل التعسفي."
220
- },
221
- {
222
- "id": "Q014",
223
- "category": "constitutional",
224
- "question": "ما مضمون المادة 14 من الدستور المصري؟",
225
- "expected": {
226
- "law": "constitution",
227
- "expected_article_numbers": [
228
- "14"
229
- ],
230
- "must_cite_articles": true,
231
- "answer_should_not_add_external_info": true
232
- },
233
- "notes": "الوظائف العامة حق للمواطنين بناءً على الكفاءة، وهي تكليف لخدمة الشعب. تحمي الدولة الموظفين ولا يجوز فصلهم إلا تأديبياً أو بالقانون."
234
- },
235
- {
236
- "id": "Q015",
237
- "category": "constitutional",
238
- "question": "ما مضمون المادة 15 من الدستور المصري؟",
239
- "expected": {
240
- "law": "constitution",
241
- "expected_article_numbers": [
242
- "15"
243
- ],
244
- "must_cite_articles": true,
245
- "answer_should_not_add_external_info": true
246
- },
247
- "notes": "الإضراب السلمي هو حق ينظمه القانون."
248
- },
249
- {
250
- "id": "Q016",
251
- "category": "constitutional",
252
- "question": "ما مضمون المادة 16 من الدستور المصري؟",
253
- "expected": {
254
- "law": "constitution",
255
- "expected_article_numbers": [
256
- "16"
257
- ],
258
- "must_cite_articles": true,
259
- "answer_should_not_add_external_info": true
260
- },
261
- "notes": "تلتزم الدولة بتكريم ورعاية شهداء الوطن ومصابي الثورة والعمليات الأمنية وأسرهم، وتوفير فرص عمل لهم، وتشجع المجتمع المدني على المساهمة."
262
- },
263
- {
264
- "id": "Q017",
265
- "category": "constitutional",
266
- "question": "ما مضمون المادة 17 من الدستور المصري؟",
267
- "expected": {
268
- "law": "constitution",
269
- "expected_article_numbers": [
270
- "17"
271
- ],
272
- "must_cite_articles": true,
273
- "answer_should_not_add_external_info": true
274
- },
275
- "notes": "تكفل الدولة التأمين والضمان الاجتماعي للمواطنين لضمان حياة كريمة، خاصة للفئات الضعيفة. أموال التأمينات خاصة ومحمية وتدار بواسطة هيئة مستقلة."
276
- },
277
- {
278
- "id": "Q018",
279
- "category": "constitutional",
280
- "question": "ما مضمون المادة 18 من الدستور المصري؟",
281
- "expected": {
282
- "law": "constitution",
283
- "expected_article_numbers": [
284
- "18"
285
- ],
286
- "must_cite_articles": true,
287
- "answer_should_not_add_external_info": true
288
- },
289
- "notes": "لكل مواطن الحق في الصحة والرعاية المتكاملة. تلتزم الدولة بتخصيص نسبة من الإنفاق للصحة وإقامة تأمين صحي شامل، وتجرم الامتناع عن علاج الطوارئ."
290
- },
291
- {
292
- "id": "Q019",
293
- "category": "constitutional",
294
- "question": "ما مضمون المادة 19 من الدستور المصري؟",
295
- "expected": {
296
- "law": "constitution",
297
- "expected_article_numbers": [
298
- "19"
299
- ],
300
- "must_cite_articles": true,
301
- "answer_should_not_add_external_info": true
302
- },
303
- "notes": "التعليم حق لكل مواطن وإلزامي حتى الثانوية ومجاني في مؤسسات الدولة. تلتزم الدولة بتخصيص نسبة من الإنفاق للتعليم ومراقبة جودته."
304
- },
305
- {
306
- "id": "Q020",
307
- "category": "constitutional",
308
- "question": "ما مضمون المادة 20 من الدستور المصري؟",
309
- "expected": {
310
- "law": "constitution",
311
- "expected_article_numbers": [
312
- "20"
313
- ],
314
- "must_cite_articles": true,
315
- "answer_should_not_add_external_info": true
316
- },
317
- "notes": "تلتزم الدولة بتشجيع وتطوير التعليم الفني والتقني والتدريب المهني وفق معايير الجودة واحتياجات السوق."
318
- },
319
- {
320
- "id": "Q021",
321
- "category": "constitutional",
322
- "question": "ما مضمون المادة 21 من الدستور المصري؟",
323
- "expected": {
324
- "law": "constitution",
325
- "expected_article_numbers": [
326
- "21"
327
- ],
328
- "must_cite_articles": true,
329
- "answer_should_not_add_external_info": true
330
- },
331
- "notes": "تكفل الدولة استقلال الجامعات ومجانية التعليم الجامعي الحكومي وتخصيص نسبة من الإنفاق له. تشجع الجامعات الأهلية وتراقب جودة التعليم الخاص والأهلي."
332
- },
333
- {
334
- "id": "Q022",
335
- "category": "constitutional",
336
- "question": "ما مضمون المادة 22 من الدستور المصري؟",
337
- "expected": {
338
- "law": "constitution",
339
- "expected_article_numbers": [
340
- "22"
341
- ],
342
- "must_cite_articles": true,
343
- "answer_should_not_add_external_info": true
344
- },
345
- "notes": "المعلمون هم ركيزة التعليم، وتكفل الدولة تنمية مهاراتهم ورعاية حقوقهم لضمان جودة التعليم."
346
- },
347
- {
348
- "id": "Q023",
349
- "category": "constitutional",
350
- "question": "ما مضمون المادة 23 من الدستور المصري؟",
351
- "expected": {
352
- "law": "constitution",
353
- "expected_article_numbers": [
354
- "23"
355
- ],
356
- "must_cite_articles": true,
357
- "answer_should_not_add_external_info": true
358
- },
359
- "notes": "تكفل الدولة حرية البحث العلمي وتدعم الباحثين، وتخصص له نسبة من الإنفاق الحكومي، وتشجع مساهمة القطاع الخاص والمصريين بالخارج."
360
- },
361
- {
362
- "id": "Q024",
363
- "category": "constitutional",
364
- "question": "ما مضمون ا��مادة 24 من الدستور المصري؟",
365
- "expected": {
366
- "law": "constitution",
367
- "expected_article_numbers": [
368
- "24"
369
- ],
370
- "must_cite_articles": true,
371
- "answer_should_not_add_external_info": true
372
- },
373
- "notes": "اللغة العربية والدين والتاريخ مواد أساسية في التعليم قبل الجامعي. تدرس الجامعات حقوق الإنسان والأخلاق المهنية."
374
- },
375
- {
376
- "id": "Q025",
377
- "category": "constitutional",
378
- "question": "ما مضمون المادة 25 من الدستور المصري؟",
379
- "expected": {
380
- "law": "constitution",
381
- "expected_article_numbers": [
382
- "25"
383
- ],
384
- "must_cite_articles": true,
385
- "answer_should_not_add_external_info": true
386
- },
387
- "notes": "تلتزم الدولة بالقضاء على الأمية الهجائية والرقمية وفق خطة زمنية وبمشاركة المجتمع المدني."
388
- },
389
- {
390
- "id": "Q026",
391
- "category": "constitutional",
392
- "question": "ما مضمون المادة 26 من الدستور المصري؟",
393
- "expected": {
394
- "law": "constitution",
395
- "expected_article_numbers": [
396
- "26"
397
- ],
398
- "must_cite_articles": true,
399
- "answer_should_not_add_external_info": true
400
- },
401
- "notes": "يحظر إنشاء الرتب المدنية."
402
- },
403
- {
404
- "id": "Q027",
405
- "category": "constitutional",
406
- "question": "ما مضمون المادة 27 من الدستور المصري؟",
407
- "expected": {
408
- "law": "constitution",
409
- "expected_article_numbers": [
410
- "27"
411
- ],
412
- "must_cite_articles": true,
413
- "answer_should_not_add_external_info": true
414
- },
415
- "notes": "يهدف النظام الاقتصادي لتحقيق الرخاء والتنمية المستدامة والعدالة الاجتماعية. يلتزم بالشفافية والتنافسية ومنع الاحتكار وحماية المستهلك والعمال، وضمان حد أدنى للأجور."
416
- },
417
- {
418
- "id": "Q028",
419
- "category": "constitutional",
420
- "question": "ما مضمون المادة 28 من الدستور المصري؟",
421
- "expected": {
422
- "law": "constitution",
423
- "expected_article_numbers": [
424
- "28"
425
- ],
426
- "must_cite_articles": true,
427
- "answer_should_not_add_external_info": true
428
- },
429
- "notes": "تلتزم الدولة بحماية الأنشطة الاقتصادية وزيادة تنافسيتها وتشجيع الاستثمار والإنتاج والتصدير. تهتم بالمشروعات الصغيرة وتنظيم القطاع غير الرسمي."
430
- },
431
- {
432
- "id": "Q029",
433
- "category": "constitutional",
434
- "question": "ما مضمون المادة 29 من الدستور المصري؟",
435
- "expected": {
436
- "law": "constitution",
437
- "expected_article_numbers": [
438
- "29"
439
- ],
440
- "must_cite_articles": true,
441
- "answer_should_not_add_external_info": true
442
- },
443
- "notes": "الزراعة أساس الاقتصاد. تلتزم الدولة بحماية الأراضي الزراعية وتنمية الريف والإنتاج الزراعي والحيواني، وشراء المحاصيل بأسعار عادلة، ودعم صغار الفلاحين."
444
- },
445
- {
446
- "id": "Q030",
447
- "category": "constitutional",
448
- "question": "ما مضمون المادة 30 من الدستور المصري؟",
449
- "expected": {
450
- "law": "constitution",
451
- "expected_article_numbers": [
452
- "30"
453
- ],
454
- "must_cite_articles": true,
455
- "answer_should_not_add_external_info": true
456
- },
457
- "notes": "تلتزم الدولة بحماية الثروة السمكية ودعم الصيادين مع الحفاظ على البيئة."
458
- },
459
- {
460
- "id": "Q031",
461
- "category": "constitutional",
462
- "question": "ما مضمون المادة 31 من الدستور المصري؟",
463
- "expected": {
464
- "law": "constitution",
465
- "expected_article_numbers": [
466
- "31"
467
- ],
468
- "must_cite_articles": true,
469
- "answer_should_not_add_external_info": true
470
- },
471
- "notes": "أمن الفضاء المعلوماتي جزء من الأمن القومي والاقتصادي، وتلتزم الدولة بحمايته."
472
- },
473
- {
474
- "id": "Q032",
475
- "category": "constitutional",
476
- "question": "ما مضمون المادة 32 من الدستور المصري؟",
477
- "expected": {
478
- "law": "constitution",
479
- "expected_article_numbers": [
480
- "32"
481
- ],
482
- "must_cite_articles": true,
483
- "answer_should_not_add_external_info": true
484
- },
485
- "notes": "الموارد الطبيعية ملك للشعب ويجب الحفاظ عليها واستغلالها بحكمة. تشجع الدولة الطاقة المتجددة وتصنيع المواد الأولية. يحدد القانون شروط استغلال الموارد والمرافق العامة."
486
- },
487
- {
488
- "id": "Q033",
489
- "category": "constitutional",
490
- "question": "ما مضمون المادة 33 من الدستور المصري؟",
491
- "expected": {
492
- "law": "constitution",
493
- "expected_article_numbers": [
494
- "33"
495
- ],
496
- "must_cite_articles": true,
497
- "answer_should_not_add_external_info": true
498
- },
499
- "notes": "تحمي الدولة جميع أنواع الملكية: العامة والخاصة والتعاونية."
500
- },
501
- {
502
- "id": "Q034",
503
- "category": "constitutional",
504
- "question": "ما مضمون المادة 34 من الدستور المصري؟",
505
- "expected": {
506
- "law": "constitution",
507
- "expected_article_numbers": [
508
- "34"
509
- ],
510
- "must_cite_articles": true,
511
- "answer_should_not_add_external_info": true
512
- },
513
- "notes": "للملكية العامة حرمة ويجب حمايتها ولا يجوز المساس بها."
514
- },
515
- {
516
- "id": "Q035",
517
- "category": "constitutional",
518
- "question": "ما مضمون المادة 35 من الدستور المصري؟",
519
- "expected": {
520
- "law": "constitution",
521
- "expected_article_numbers": [
522
- "35"
523
- ],
524
- "must_cite_articles": true,
525
- "answer_should_not_add_external_info": true
526
- },
527
- "notes": "الملكية الخاصة والإرث مصونان. لا تُفرض الحراسة أو تُنزع الملكية إلا بالقانون وحكم قضائي وللمنفعة العامة مقابل تعويض عادل."
528
- },
529
- {
530
- "id": "Q036",
531
- "category": "constitutional",
532
- "question": "ما مضمون المادة 36 من الدستور المصري؟",
533
- "expected": {
534
- "law": "constitution",
535
- "expected_article_numbers": [
536
- "36"
537
- ],
538
- "must_cite_articles": true,
539
- "answer_should_not_add_external_info": true
540
- },
541
- "notes": "تحفز الدولة القطاع الخاص للمشاركة في المسؤولية الاجتماعية."
542
- },
543
- {
544
- "id": "Q037",
545
- "category": "constitutional",
546
- "question": "ما مضمون المادة 37 من الدستور المصري؟",
547
- "expected": {
548
- "law": "constitution",
549
- "expected_article_numbers": [
550
- "37"
551
- ],
552
- "must_cite_articles": true,
553
- "answer_should_not_add_external_info": true
554
- },
555
- "notes": "الملكية التعاونية مصونة وتدعم الدولة التعاونيات وتضمن استقلالها، ولا تحل إلا بحكم قضائي."
556
- },
557
- {
558
- "id": "Q038",
559
- "category": "constitutional",
560
- "question": "ما مضمون المادة 38 من الدستور المصري؟",
561
- "expected": {
562
- "law": "constitution",
563
- "expected_article_numbers": [
564
- "38"
565
- ],
566
- "must_cite_articles": true,
567
- "answer_should_not_add_external_info": true
568
- },
569
- "notes": "النظام الضريبي يهدف لتنمية الموارد والعدالة. الضرائب لا تُفرض إلا بقانون وتكون تصاعدية. الدولة تلتزم بتطوير النظام الضريبي، والتهرب الضريبي جريمة."
570
- },
571
- {
572
- "id": "Q039",
573
- "category": "constitutional",
574
- "question": "ما مضمون المادة 39 من الدستور المصري؟",
575
- "expected": {
576
- "law": "constitution",
577
- "expected_article_numbers": [
578
- "39"
579
- ],
580
- "must_cite_articles": true,
581
- "answer_should_not_add_external_info": true
582
- },
583
- "notes": "الادخار واجب وطني تحميه وتشجعه الدولة."
584
- },
585
- {
586
- "id": "Q040",
587
- "category": "constitutional",
588
- "question": "ما مضمون المادة 40 من الدستور المصري؟",
589
- "expected": {
590
- "law": "constitution",
591
- "expected_article_numbers": [
592
- "40"
593
- ],
594
- "must_cite_articles": true,
595
- "answer_should_not_add_external_info": true
596
- },
597
- "notes": "المصادرة العامة للأموال ممنوعة، والخاصة لا تجوز إلا بحكم قضائي."
598
- },
599
- {
600
- "id": "Q041",
601
- "category": "criminal_procedure",
602
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 1؟",
603
- "expected": {
604
- "law": "criminal_procedure",
605
- "expected_article_numbers": [
606
- "1"
607
- ],
608
- "must_cite_articles": true
609
- },
610
- "notes": "النيابة العامة هي الجهة الوحيدة المختصة برفع الدعوى الجنائية ومباشرتها، ولا يجوز تركها أو وقفها إلا وفقاً للقانون."
611
- },
612
- {
613
- "id": "Q042",
614
- "category": "criminal_procedure",
615
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 2؟",
616
- "expected": {
617
- "law": "criminal_procedure",
618
- "expected_article_numbers": [
619
- "2"
620
- ],
621
- "must_cite_articles": true
622
- },
623
- "notes": "النائب العام أو أعضاء النيابة العامة يباشرون الدعوى الجنائية، ويجوز تعيين آخرين لأداء هذه الوظيفة وفقاً للقانون."
624
- },
625
- {
626
- "id": "Q043",
627
- "category": "criminal_procedure",
628
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 3؟",
629
- "expected": {
630
- "law": "criminal_procedure",
631
- "expected_article_numbers": [
632
- "3"
633
- ],
634
- "must_cite_articles": true
635
- },
636
- "notes": "في جرائم معينة (مثل السب والقذف والزنا) لا ترفع الدعوى الجنائية إلا بشكوى من المجني عليه أو وكيله خلال ثلاثة أشهر من علمه بالجريمة."
637
- },
638
- {
639
- "id": "Q044",
640
- "category": "criminal_procedure",
641
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 4؟",
642
- "expected": {
643
- "law": "criminal_procedure",
644
- "expected_article_numbers": [
645
- "4"
646
- ],
647
- "must_cite_articles": true
648
- },
649
- "notes": "شكوى أحد المجني عليهم تكفي لتحريك الدعوى، والشكوى ضد متهم واحد تعتبر شكوى ضد جميع المتهمين."
650
- },
651
- {
652
- "id": "Q045",
653
- "category": "criminal_procedure",
654
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 5؟",
655
- "expected": {
656
- "law": "criminal_procedure",
657
- "expected_article_numbers": [
658
- "5"
659
- ],
660
- "must_cite_articles": true
661
- },
662
- "notes": "إذا كان المجني عليه قاصراً (أقل من 15 سنة) أو مصاباً بعاهة عقلية، يقدم الشكوى من له الولاية عليه."
663
- },
664
- {
665
- "id": "Q046",
666
- "category": "criminal_procedure",
667
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 6؟",
668
- "expected": {
669
- "law": "criminal_procedure",
670
- "expected_article_numbers": [
671
- "6"
672
- ],
673
- "must_cite_articles": true
674
- },
675
- "notes": "إذا تعارضت مصلحة المجني عليه مع مصلحة ممثله أو لم يكن له ممثل، تنوب عنه النيابة العامة في تقديم الشكوى."
676
- },
677
- {
678
- "id": "Q047",
679
- "category": "criminal_procedure",
680
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 7؟",
681
- "expected": {
682
- "law": "criminal_procedure",
683
- "expected_article_numbers": [
684
- "7"
685
- ],
686
- "must_cite_articles": true
687
- },
688
- "notes": "وفاة المجني عليه تنهي حقه في الشكوى، لكن إذا قدمت الشكوى قبل الوفاة تستمر الدعوى."
689
- },
690
- {
691
- "id": "Q048",
692
- "category": "criminal_procedure",
693
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 8؟",
694
- "expected": {
695
- "law": "criminal_procedure",
696
- "expected_article_numbers": [
697
- "8"
698
- ],
699
- "must_cite_articles": true
700
- },
701
- "notes": "في بعض الجرائم (المواد 181 و182 عقوبات) يشترط طلب كتابي من وزير العدل لرفع الدعوى الجنائية."
702
- },
703
- {
704
- "id": "Q049",
705
- "category": "criminal_procedure",
706
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 8 مكرر؟",
707
- "expected": {
708
- "law": "criminal_procedure",
709
- "expected_article_numbers": [
710
- "8 مكرر"
711
- ],
712
- "must_cite_articles": true
713
- },
714
- "notes": "جرائم المادة 116 مكرراً (أ) من قانون العقوبات لا ترفع إلا من النائب العام أو المحامي العام."
715
- },
716
- {
717
- "id": "Q050",
718
- "category": "criminal_procedure",
719
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 9؟",
720
- "expected": {
721
- "law": "criminal_procedure",
722
- "expected_article_numbers": [
723
- "9"
724
- ],
725
- "must_cite_articles": true
726
- },
727
- "notes": "جرائم الإهانة (المادة 184 عقوبات) تتطلب طلباً من الهيئة المجني عليها، وبشكل عام لا يجوز ��لتحقيق في جرائم الشكوى إلا بعد تقديمها."
728
- },
729
- {
730
- "id": "Q051",
731
- "category": "criminal_procedure",
732
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 10؟",
733
- "expected": {
734
- "law": "criminal_procedure",
735
- "expected_article_numbers": [
736
- "10"
737
- ],
738
- "must_cite_articles": true
739
- },
740
- "notes": "يجوز للشاكي التنازل عن الشكوى قبل صدور حكم نهائي وتنقضي الدعوى بالتنازل. التنازل لأحد المتهمين يمتد للباقين. لا ينتقل حق التنازل للورثة إلا في دعوى الزنا."
741
- },
742
- {
743
- "id": "Q052",
744
- "category": "criminal_procedure",
745
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 11؟",
746
- "expected": {
747
- "law": "criminal_procedure",
748
- "expected_article_numbers": [
749
- "11"
750
- ],
751
- "must_cite_articles": true
752
- },
753
- "notes": "لمحكمة الجنايات إقامة الدعوى على متهمين آخرين أو وقائع مرتبطة وإحالتها للنيابة للتحقيق، ويجوز ندب أحد أعضائها للتحقيق مع تطبيق أحكام قاضي التحقيق عليه."
754
- },
755
- {
756
- "id": "Q053",
757
- "category": "criminal_procedure",
758
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 12؟",
759
- "expected": {
760
- "law": "criminal_procedure",
761
- "expected_article_numbers": [
762
- "12"
763
- ],
764
- "must_cite_articles": true
765
- },
766
- "notes": "لمحكمة النقض عند نظر الموضوع للمرة الثانية حق إقامة الدعوى كمحكمة الجنايات، ولا يشترك من قرر الإقامة في نظر الطعن الثاني."
767
- },
768
- {
769
- "id": "Q054",
770
- "category": "criminal_procedure",
771
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 13؟",
772
- "expected": {
773
- "law": "criminal_procedure",
774
- "expected_article_numbers": [
775
- "13"
776
- ],
777
- "must_cite_articles": true
778
- },
779
- "notes": "لمحكمة الجنايات أو النقض إقامة الدعوى على من يخل بأوامرها أو يؤثر في قضائها أو الشهود أثناء نظر الدعوى."
780
- },
781
- {
782
- "id": "Q055",
783
- "category": "criminal_procedure",
784
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 14؟",
785
- "expected": {
786
- "law": "criminal_procedure",
787
- "expected_article_numbers": [
788
- "14"
789
- ],
790
- "must_cite_articles": true
791
- },
792
- "notes": "وفاة المتهم تنهي الدعوى الجنائية، لكن يجوز الحكم بالمصادرة إذا حدثت الوفاة أثناء نظر الدعوى."
793
- },
794
- {
795
- "id": "Q056",
796
- "category": "criminal_procedure",
797
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 15؟",
798
- "expected": {
799
- "law": "criminal_procedure",
800
- "expected_article_numbers": [
801
- "15"
802
- ],
803
- "must_cite_articles": true
804
- },
805
- "notes": "تنقضي الدعوى الجنائية بالتقادم: 10 سنوات للجنايات، 3 سنوات للجنح، سنة للمخالفات. بعض الجرائم الخطيرة (التعذيب، الاعتداء على الحرية) لا تسقط بالتقادم."
806
- },
807
- {
808
- "id": "Q057",
809
- "category": "criminal_procedure",
810
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 16؟",
811
- "expected": {
812
- "law": "criminal_procedure",
813
- "expected_article_numbers": [
814
- "16"
815
- ],
816
- "must_cite_articles": true
817
- },
818
- "notes": "مدة التقادم في الدعوى الجنائية لا تتوقف لأي سبب."
819
- },
820
- {
821
- "id": "Q058",
822
- "category": "criminal_procedure",
823
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 17؟",
824
- "expected": {
825
- "law": "criminal_procedure",
826
- "expected_article_numbers": [
827
- "17"
828
- ],
829
- "must_cite_articles": true
830
- },
831
- "notes": "تنقطع مدة التقادم بإجراءات التحقيق أو الاتهام أو المحاكمة أو الأمر الجنائي، وتبدأ من جديد من يوم الانقطاع."
832
- },
833
- {
834
- "id": "Q059",
835
- "category": "criminal_procedure",
836
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 18؟",
837
- "expected": {
838
- "law": "criminal_procedure",
839
- "expected_article_numbers": [
840
- "18"
841
- ],
842
- "must_cite_articles": true
843
- },
844
- "notes": "انقطاع مدة التقادم لأحد المتهمين يمتد أثره للباقين ما لم تتخذ ضدهم إجراءات مستقلة."
845
- },
846
- {
847
- "id": "Q060",
848
- "category": "criminal_procedure",
849
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 18 مكرر؟",
850
- "expected": {
851
- "law": "criminal_procedure",
852
- "expected_article_numbers": [
853
- "18 مكرر"
854
- ],
855
- "must_cite_articles": true
856
- },
857
- "notes": "يجوز التصالح في المخالفات والجنح البسيطة بدفع ثلث الغرامة قبل رفع الدعوى أو ثلثيها بعد رفعها، وتنقضي الدعوى الجنائية بالتصالح."
858
- },
859
- {
860
- "id": "Q061",
861
- "category": "criminal_procedure",
862
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 18 مكرر (أ)؟",
863
- "expected": {
864
- "law": "criminal_procedure",
865
- "expected_article_numbers": [
866
- "18 مكرر (أ)"
867
- ],
868
- "must_cite_articles": true
869
- },
870
- "notes": "يجوز للمجني عليه أو ورثته الصلح مع المتهم في جرائم معينة (الضرب، السرقة البسيطة، إتلاف المال) وتنقضي الدعوى بالصلح حتى بعد الحكم البات."
871
- },
872
- {
873
- "id": "Q062",
874
- "category": "criminal_procedure",
875
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 19؟",
876
- "expected": {
877
- "law": "criminal_procedure",
878
- "expected_article_numbers": [
879
- "19"
880
- ],
881
- "must_cite_articles": true
882
- },
883
- "notes": "مادة ملغاة."
884
- },
885
- {
886
- "id": "Q063",
887
- "category": "criminal_procedure",
888
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 20؟",
889
- "expected": {
890
- "law": "criminal_procedure",
891
- "expected_article_numbers": [
892
- "20"
893
- ],
894
- "must_cite_articles": true
895
- },
896
- "notes": "مادة ملغاة."
897
- },
898
- {
899
- "id": "Q064",
900
- "category": "criminal_procedure",
901
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 21؟",
902
- "expected": {
903
- "law": "criminal_procedure",
904
- "expected_article_numbers": [
905
- "21"
906
- ],
907
- "must_cite_articles": true
908
- },
909
- "notes": "مأمورو الضبط القضائي يبحثون عن الجرائم ومرتكبيها ويجمعون الاستدلالات اللازمة للتحقيق."
910
- },
911
- {
912
- "id": "Q065",
913
- "category": "criminal_procedure",
914
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 22؟",
915
- "expected": {
916
- "law": "criminal_procedure",
917
- "expected_article_numbers": [
918
- "22"
919
- ],
920
- "must_cite_articles": true
921
- },
922
- "notes": "مأمورو الضبط القضائي يتبعون النائب العام ويخضعون لإشرافه، وله طلب محاسبتهم تأديبياً وجنائياً."
923
- },
924
- {
925
- "id": "Q066",
926
- "category": "tech_crimes",
927
- "question": "ما الذي تنظمه المادة 1 في قانون مكافحة جرائم تقنية المعلومات؟",
928
- "expected": {
929
- "law": "tech_crimes",
930
- "expected_article_numbers": [
931
- "1"
932
- ],
933
- "must_cite_articles": true
934
- },
935
- "notes": "تحدد المادة التعريفات الأساسية في القانون مثل الجهاز والوزير المختص والبيانات الإلكترونية والبيانات الشخصية والحكومية، والمعالجة الإلكترونية وتقنية المعلومات، ومقدم الخدمة والمستخدم، والبرنامج والنظام"
936
- },
937
- {
938
- "id": "Q067",
939
- "category": "tech_crimes",
940
- "question": "ما الذي تنظمه المادة 2 في قانون مكافحة جرائم تقنية المعلومات؟",
941
- "expected": {
942
- "law": "tech_crimes",
943
- "expected_article_numbers": [
944
- "2"
945
- ],
946
- "must_cite_articles": true
947
- },
948
- "notes": "تُلزم المادة مقدمي الخدمة بحفظ سجلات وبيانات محددة لمدة 180 يومًا، والحفاظ على سريتها وتأمينها وعدم إفشائها إلا بأمر قضائي. كما تُلزمهم بتوفير بيانات تعريفية وترخيصية للمستخدمين والجهات المختصة، وتمكي"
949
- },
950
- {
951
- "id": "Q068",
952
- "category": "tech_crimes",
953
- "question": "ما ا��ذي تنظمه المادة 3 في قانون مكافحة جرائم تقنية المعلومات؟",
954
- "expected": {
955
- "law": "tech_crimes",
956
- "expected_article_numbers": [
957
- "3"
958
- ],
959
- "must_cite_articles": true
960
- },
961
- "notes": "تُطبق أحكام القانون على جرائم تقنية المعلومات المرتكبة خارج مصر من غير المصريين متى كان الفعل مجرمًا بالدولة التي وقع فيها، وذلك في حالات محددة مثل ارتباط الجريمة بوسيلة نقل مصرية، أو وجود مجني عليه م"
962
- },
963
- {
964
- "id": "Q069",
965
- "category": "tech_crimes",
966
- "question": "ما الذي تنظمه المادة 4 في قانون مكافحة جرائم تقنية المعلومات؟",
967
- "expected": {
968
- "law": "tech_crimes",
969
- "expected_article_numbers": [
970
- "4"
971
- ],
972
- "must_cite_articles": true
973
- },
974
- "notes": "تلتزم السلطات المصرية بتيسير التعاون الدولي وتبادل المعلومات وفق الاتفاقيات أو المعاملة بالمثل لمنع جرائم تقنية المعلومات والمساعدة في التحقيق وتتبع الجناة، ويكون مركز الاستعداد لطوارئ الحاسب والشبكات"
975
- },
976
- {
977
- "id": "Q070",
978
- "category": "tech_crimes",
979
- "question": "ما الذي تنظمه المادة 5 في قانون مكافحة جرائم تقنية المعلومات؟",
980
- "expected": {
981
- "law": "tech_crimes",
982
- "expected_article_numbers": [
983
- "5"
984
- ],
985
- "must_cite_articles": true
986
- },
987
- "notes": "يجوز لوزير العدل بالاتفاق مع الوزير المختص منح صفة الضبطية القضائية للعاملين بالجهاز أو لغيرهم ممن تحددهم جهات الأمن القومي، فيما يخص الجرائم المرتبطة بأعمال وظائفهم وفق هذا القانون."
988
- },
989
- {
990
- "id": "Q071",
991
- "category": "tech_crimes",
992
- "question": "ما الذي تنظمه المادة 6 في قانون مكافحة جرائم تقنية المعلومات؟",
993
- "expected": {
994
- "law": "tech_crimes",
995
- "expected_article_numbers": [
996
- "6"
997
- ],
998
- "must_cite_articles": true
999
- },
1000
- "notes": "تجيز المادة لجهة التحقيق إصدار أوامر مؤقتة مسببـة لمدة تصل إلى 30 يومًا قابلة للتجديد مرة واحدة لضبط البيانات والأنظمة وتتبعها، والبحث والتفتيش والدخول إلى البرامج وقواعد البيانات، وإلزام مقدم الخدمة "
1001
- },
1002
- {
1003
- "id": "Q072",
1004
- "category": "tech_crimes",
1005
- "question": "ما الذي تنظمه المادة 7 في قانون مكافحة جرائم تقنية المعلومات؟",
1006
- "expected": {
1007
- "law": "tech_crimes",
1008
- "expected_article_numbers": [
1009
- "7"
1010
- ],
1011
- "must_cite_articles": true
1012
- },
1013
- "notes": "تنظم المادة إجراءات حجب المواقع المرتبطة بجرائم تقنية المعلومات التي تهدد الأمن القومي، بما يشمل أمر الحجب القضائي وعرضه على المحكمة خلال مدد محددة، والحجب المؤقت في حالات الاستعجال مع تحرير محضر وعرض"
1014
- },
1015
- {
1016
- "id": "Q073",
1017
- "category": "tech_crimes",
1018
- "question": "ما الذي تنظمه المادة 8 في قانون مكافحة جرائم تقنية المعلومات؟",
1019
- "expected": {
1020
- "law": "tech_crimes",
1021
- "expected_article_numbers": [
1022
- "8"
1023
- ],
1024
- "must_cite_articles": true
1025
- },
1026
- "notes": "تحدد المادة حق التظلم من أوامر الحجب أمام محكمة الجنايات المختصة بعد مرور سبعة أيام، مع إمكانية تجديد التظلم كل ثلاثة أشهر إذا رُفض، وإجراءات إيداع التظلم وتحديد جلسة والفصل فيه خلال سبعة أيام."
1027
- },
1028
- {
1029
- "id": "Q074",
1030
- "category": "tech_crimes",
1031
- "question": "ما الذي تنظمه المادة 9 في قانون مكافحة جرائم تقنية المعلومات؟",
1032
- "expected": {
1033
- "law": "tech_crimes",
1034
- "expected_article_numbers": [
1035
- "9"
1036
- ],
1037
- "must_cite_articles": true
1038
- },
1039
- "notes": "تجيز المادة إصدار أمر مسبب بمنع السفر أو إدراج الاسم على قوائم ترقب الوصول عند الضرورة أو وجود أدلة كافية، مع حق التظلم خلال 15 يومًا وتجديده كل ثلاثة أشهر، والفصل القضائي خلال 15 يومًا، وإمكانية العد"
1040
- },
1041
- {
1042
- "id": "Q075",
1043
- "category": "tech_crimes",
1044
- "question": "ما الذي تنظمه المادة 10 في قانون مكافحة جرائم تقنية المعلومات؟",
1045
- "expected": {
1046
- "law": "tech_crimes",
1047
- "expected_article_numbers": [
1048
- "10"
1049
- ],
1050
- "must_cite_articles": true
1051
- },
1052
- "notes": "تنشئ المادة سجلين للخبراء بالجهاز (للعاملين وغير العاملين)، وتحدد خضوعهم لقواعد تنظيم الخبرة أمام القضاء، مع تطبيق قواعد المساءلة الإدارية والتأديبية للخبراء غير العاملين وفق قانونهم، وتُحال تفاصيل ال"
1053
- },
1054
- {
1055
- "id": "Q076",
1056
- "category": "labor",
1057
- "question": "ما مضمون المادة 1 في قانون العمل المصري (قانون العمل)؟",
1058
- "expected": {
1059
- "law": "labor",
1060
- "expected_article_numbers": [
1061
- "1"
1062
- ],
1063
- "must_cite_articles": true
1064
- },
1065
- "notes": "تعريفات شاملة لـ 38 مصطلحاً أساسياً في قانون العمل تشمل: تعريف العامل وصاحب العمل والمتدرج، تفصيل شامل لمكونات الأجر (الأساسي والمتغير بما في ذلك العمولة والعلاوات والمنح والمكافآت والبدلات ونصيب الأر"
1066
- },
1067
- {
1068
- "id": "Q077",
1069
- "category": "labor",
1070
- "question": "ما مضمون المادة 2 في قانون العمل المصري (قانون العمل)؟",
1071
- "expected": {
1072
- "law": "labor",
1073
- "expected_article_numbers": [
1074
- "2"
1075
- ],
1076
- "must_cite_articles": true
1077
- },
1078
- "notes": "تحديد السنة بـ365 يوماً والشهر بـ30 يوماً لأغراض تطبيق القانون، ما لم يتفق الطرفان على خلاف ذلك."
1079
- },
1080
- {
1081
- "id": "Q078",
1082
- "category": "labor",
1083
- "question": "ما مضمون المادة 3 في قانون العمل المصري (قانون العمل)؟",
1084
- "expected": {
1085
- "law": "labor",
1086
- "expected_article_numbers": [
1087
- "3"
1088
- ],
1089
- "must_cite_articles": true
1090
- },
1091
- "notes": "قانون العمل رقم 14 لسنة 2025 هو القانون العام المنظم لجميع علاقات العمل في مصر."
1092
- },
1093
- {
1094
- "id": "Q079",
1095
- "category": "labor",
1096
- "question": "ما مضمون المادة 4 في قانون العمل المصري (قانون العمل)؟",
1097
- "expected": {
1098
- "law": "labor",
1099
- "expected_article_numbers": [
1100
- "4"
1101
- ],
1102
- "must_cite_articles": true
1103
- },
1104
- "notes": "حظر السخرة والعمل الجبري والتحرش والتنمر والعنف بكافة أشكاله (اللفظي والجسدي والنفسي) ضد العمال، مع تحديد جزاءات تأديبية في لوائح المنشأة."
1105
- },
1106
- {
1107
- "id": "Q080",
1108
- "category": "labor",
1109
- "question": "ما مضمون المادة 5 في قانون العمل المصري (قانون العمل)؟",
1110
- "expected": {
1111
- "law": "labor",
1112
- "expected_article_numbers": [
1113
- "5"
1114
- ],
1115
- "must_cite_articles": true
1116
- },
1117
- "notes": "حظر التمييز في العمل لأي سبب، مع استثناء المزايا المقررة قانوناً للمرأة والطفل وذوي الإعاقة والأقزام بالقدر اللازم لحمايتهم ودمجهم."
1118
- },
1119
- {
1120
- "id": "Q081",
1121
- "category": "labor",
1122
- "question": "ما مضمون المادة 6 في قانون العمل المصري (قانون العمل)؟",
1123
- "expected": {
1124
- "law": "labor",
1125
- "expected_article_numbers": [
1126
- "6"
1127
- ],
1128
- "must_cite_articles": true
1129
- },
1130
- "notes": "بطلان أي شرط ينتقص من حقوق العامل، مع استمرار الشروط الأفضل حتى في حالة تغيير ملكية المنشأة."
1131
- },
1132
- {
1133
- "id": "Q082",
1134
- "category": "labor",
1135
- "question": "ما مضمون المادة 7 في قانون العمل المصري (قانون العمل)؟",
1136
- "expected": {
1137
- "law": "labor",
1138
- "expected_article_numbers": [
1139
- "7"
1140
- ],
1141
- "must_cite_articles": true
1142
- },
1143
- "notes": "إعفاء العمال من الرسوم القضائية والدمغة مع النفاذ المعجل وعدم اشتراط محامٍ."
1144
- },
1145
- {
1146
- "id": "Q083",
1147
- "category": "labor",
1148
- "question": "ما مضمون المادة 8 في قانون العمل المصري (قانون العمل)؟",
1149
- "expected": {
1150
- "law": "labor",
1151
- "expected_article_numbers": [
1152
- "8"
1153
- ],
1154
- "must_cite_articles": true
1155
- },
1156
- "notes": "امتياز حقوق العمال على جميع أموال صاحب العمل قبل أي ديون أخرى بما فيها ديون الخزانة العامة."
1157
- },
1158
- {
1159
- "id": "Q084",
1160
- "category": "labor",
1161
- "question": "ما مضمون المادة 9 في قانون العمل المصري (قانون العمل)؟",
1162
- "expected": {
1163
- "law": "labor",
1164
- "expected_article_numbers": [
1165
- "9"
1166
- ],
1167
- "must_cite_articles": true
1168
- },
1169
- "notes": "حماية حقوق العمال في حالة التصفية أو الإفلاس مع إشراف الجهة الإدارية المختصة."
1170
- },
1171
- {
1172
- "id": "Q085",
1173
- "category": "labor",
1174
- "question": "ما مضمون المادة 10 في قانون العمل المصري (قانون العمل)؟",
1175
- "expected": {
1176
- "law": "labor",
1177
- "expected_article_numbers": [
1178
- "10"
1179
- ],
1180
- "must_cite_articles": true
1181
- },
1182
- "notes": "المسؤولية التضامنية لأصحاب العمل المتعددين والوكلاء المفوضين."
1183
- },
1184
- {
1185
- "id": "Q086",
1186
- "category": "labor",
1187
- "question": "ما مضمون المادة 11 في قانون العمل المصري (قانون العمل)؟",
1188
- "expected": {
1189
- "law": "labor",
1190
- "expected_article_numbers": [
1191
- "11"
1192
- ],
1193
- "must_cite_articles": true
1194
- },
1195
- "notes": "استمرار عقود العمل رغم انتقال ملكية المنشأة بأي طريق."
1196
- },
1197
- {
1198
- "id": "Q087",
1199
- "category": "labor",
1200
- "question": "ما مضمون المادة 12 في قانون العمل المصري (قانون العمل)؟",
1201
- "expected": {
1202
- "law": "labor",
1203
- "expected_article_numbers": [
1204
- "12"
1205
- ],
1206
- "must_cite_articles": true
1207
- },
1208
- "notes": "علاوة سنوية لا تقل عن 3% من الأجر التأميني مع إمكانية التخفيض بموافقة المجلس القومي للأجور."
1209
- },
1210
- {
1211
- "id": "Q088",
1212
- "category": "labor",
1213
- "question": "ما مضمون المادة 13 في قانون العمل المصري (قانون العمل)؟",
1214
- "expected": {
1215
- "law": "labor",
1216
- "expected_article_numbers": [
1217
- "13"
1218
- ],
1219
- "must_cite_articles": true
1220
- },
1221
- "notes": "تحديد اختصاصات الجهة الإدارية المختصة بقرار من الوزير."
1222
- },
1223
- {
1224
- "id": "Q089",
1225
- "category": "labor",
1226
- "question": "ما مضمون المادة 14 في قانون العمل المصري (قانون العمل)؟",
1227
- "expected": {
1228
- "law": "labor",
1229
- "expected_article_numbers": [
1230
- "14"
1231
- ],
1232
- "must_cite_articles": true
1233
- },
1234
- "notes": "توزيع حصيلة الغرامات: ثلث للوزارة للخدمات والتدريب، والباقي للخزانة العامة."
1235
- },
1236
- {
1237
- "id": "Q090",
1238
- "category": "labor",
1239
- "question": "ما مضمون المادة 15 في قانون العمل المصري (قانون العمل)؟",
1240
- "expected": {
1241
- "law": "labor",
1242
- "expected_article_numbers": [
1243
- "15"
1244
- ],
1245
- "must_cite_articles": true
1246
- },
1247
- "notes": "تحصيل الرسوم والخدمات وفقاً لقانون وسائل الدفع غير النقدي."
1248
- },
1249
- {
1250
- "id": "Q091",
1251
- "category": "personal_status",
1252
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 1؟",
1253
- "expected": {
1254
- "law": "personal_status",
1255
- "expected_article_numbers": [
1256
- "1"
1257
- ],
1258
- "must_cite_articles": true
1259
- },
1260
- "notes": "تجب النفقة للزوجة من تاريخ العقد الصحيح وتشمل الغذاء والكسوة والمسكن والعلاج. لا تجب النفقة إذا ارتدت أو امتنعت عن تسليم نفسها أو خرجت بدون إذن. نفقة الزوجة دين على الزوج ولها امتياز على أمواله."
1261
- },
1262
- {
1263
- "id": "Q092",
1264
- "category": "personal_status",
1265
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 2؟",
1266
- "expected": {
1267
- "law": "personal_status",
1268
- "expected_article_numbers": [
1269
- "2"
1270
- ],
1271
- "must_cite_articles": true
1272
- },
1273
- "notes": "نفقة المطلقة التي تستحقها تعتبر ديناً من تاريخ الطلاق."
1274
- },
1275
- {
1276
- "id": "Q093",
1277
- "category": "personal_status",
1278
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 3؟",
1279
- "expected": {
1280
- "law": "personal_status",
1281
- "expected_article_numbers": [
1282
- "3"
1283
- ],
1284
- "must_cite_articles": true
1285
- },
1286
- "notes": "مادة ملغاة."
1287
- },
1288
- {
1289
- "id": "Q094",
1290
- "category": "personal_status",
1291
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 4؟",
1292
- "expected": {
1293
- "law": "personal_status",
1294
- "expected_article_numbers": [
1295
- "4"
1296
- ],
1297
- "must_cite_articles": true
1298
- },
1299
- "notes": "إذا امتنع الزوج عن الإنفاق وله مال ظاهر نفذ الحكم في ماله. وإن لم يكن له مال وأصر على عدم الإنفاق طلق عليه القاضي. وإن ادعى العجز وأثبته أمهله شهراً."
1300
- },
1301
- {
1302
- "id": "Q095",
1303
- "category": "personal_status",
1304
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 5؟",
1305
- "expected": {
1306
- "law": "personal_status",
1307
- "expected_article_numbers": [
1308
- "5"
1309
- ],
1310
- "must_cite_articles": true
1311
- },
1312
- "notes": "إذا كان الزوج غائباً غيبة قريبة وله مال نفذ الحكم في ماله، وإن لم يكن له مال أعذر إليه القاضي. وإن كان بعيد الغيبة أو مفقوداً ولا مال له طلق عليه القاضي. وتسري الأحكام على المحبوس المعسر."
1313
- },
1314
- {
1315
- "id": "Q096",
1316
- "category": "personal_status",
1317
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 6؟",
1318
- "expected": {
1319
- "law": "personal_status",
1320
- "expected_article_numbers": [
1321
- "6"
1322
- ],
1323
- "must_cite_articles": true
1324
- },
1325
- "notes": "التطليق لعدم الإنفاق يقع رجعياً، وللزوج مراجعة زوجته إذا ثبت يساره واستعد للإنفاق أثناء العدة."
1326
- },
1327
- {
1328
- "id": "Q097",
1329
- "category": "personal_status",
1330
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 7؟",
1331
- "expected": {
1332
- "law": "personal_status",
1333
- "expected_article_numbers": [
1334
- "7"
1335
- ],
1336
- "must_cite_articles": true
1337
- },
1338
- "notes": "مادة ملغاة."
1339
- },
1340
- {
1341
- "id": "Q098",
1342
- "category": "personal_status",
1343
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 8؟",
1344
- "expected": {
1345
- "law": "personal_status",
1346
- "expected_article_numbers": [
1347
- "8"
1348
- ],
1349
- "must_cite_articles": true
1350
- },
1351
- "notes": "إذا ظهر المفقود حياً فزوجته له، إلا إذا تمتع بها الثاني دون علم بحياة الأول فتكون للثاني ما لم يكن عقده في عدة وفاة الأول."
1352
- },
1353
- {
1354
- "id": "Q099",
1355
- "category": "personal_status",
1356
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 9؟",
1357
- "expected": {
1358
- "law": "personal_status",
1359
- "expected_article_numbers": [
1360
- "9"
1361
- ],
1362
- "must_cite_articles": true
1363
- },
1364
- "notes": "للزوجة طلب التفريق إذا وجدت بزوجها عيباً مستحكماً كالجنون أو الجذام أو البرص، سواء كان قبل العقد أو بعده، ما لم تكن عالمة به أو رضيت به."
1365
- },
1366
- {
1367
- "id": "Q100",
1368
- "category": "personal_status",
1369
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 10؟",
1370
- "expected": {
1371
- "law": "personal_status",
1372
- "expected_article_numbers": [
1373
- "10"
1374
- ],
1375
- "must_cite_articles": true
1376
- },
1377
- "notes": "الفرقة بسبب العيب تعتبر طلاقاً بائناً."
1378
- }
1379
- ]
1380
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag_test_questions_10_mixed.json DELETED
@@ -1,172 +0,0 @@
1
- {
2
- "schema_version": "1.0",
3
- "dataset_version": "2026-02-03-mixed10",
4
- "created_at": "2026-02-03T19:15:30.097990+00:00",
5
- "description": "Benchmark set for Egyptian legal RAG assistant: constitutional Qs + multi-law retrieval + procedural gating. (Mixed 10-case smoke test across 5 categories)",
6
- "laws_in_scope": [
7
- {
8
- "key": "constitution",
9
- "path": "/mnt/data/Egyptian_Constitution_legalnature_only.json"
10
- },
11
- {
12
- "key": "criminal_procedure",
13
- "path": "/mnt/data/قانون_الإجراءات_الجنائية.json"
14
- },
15
- {
16
- "key": "tech_crimes",
17
- "path": "/mnt/data/Technology Crimes Law.json"
18
- },
19
- {
20
- "key": "labor",
21
- "path": "/mnt/data/Egyptian_Labour_Law.json"
22
- },
23
- {
24
- "key": "personal_status",
25
- "path": "/mnt/data/Egyptian_Personal Status Laws.json"
26
- },
27
- {
28
- "key": "civil",
29
- "path": "/mnt/data/Egyptian_Civil.json"
30
- }
31
- ],
32
- "defaults": {
33
- "k": 15,
34
- "rerank_top_n": 5,
35
- "recall_k": 5,
36
- "pass_rule": "pass if recall@k>=1 for questions with expected articles; else pass if answer startswith expected_answer_prefix (if provided)."
37
- },
38
- "cases": [
39
- {
40
- "id": "Q001",
41
- "category": "constitutional",
42
- "question": "ما مضمون المادة 1 من الدستور المصري؟",
43
- "expected": {
44
- "law": "constitution",
45
- "expected_article_numbers": [
46
- "1"
47
- ],
48
- "must_cite_articles": true,
49
- "answer_should_not_add_external_info": true
50
- },
51
- "notes": "جمهورية مصر العربية دولة ذات سيادة، موحدة لا تقبل التجزئة، نظامها جمهوري ديمقراطي يقوم على المواطنة وسيادة القانون. الشعب المصري جزء من الأمة العربية والعالم الإسلامي والقارة الأفريقية."
52
- },
53
- {
54
- "id": "Q002",
55
- "category": "constitutional",
56
- "question": "ما مضمون المادة 2 من الدستور المصري؟",
57
- "expected": {
58
- "law": "constitution",
59
- "expected_article_numbers": [
60
- "2"
61
- ],
62
- "must_cite_articles": true,
63
- "answer_should_not_add_external_info": true
64
- },
65
- "notes": "الإسلام دين الدولة، واللغة العربية لغتها الرسمية، ومبادئ الشريعة الإسلامية هي المصدر الرئيسي للتشريع."
66
- },
67
- {
68
- "id": "Q041",
69
- "category": "criminal_procedure",
70
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 1؟",
71
- "expected": {
72
- "law": "criminal_procedure",
73
- "expected_article_numbers": [
74
- "1"
75
- ],
76
- "must_cite_articles": true
77
- },
78
- "notes": "النيابة العامة هي الجهة الوحيدة المختصة برفع الدعوى الجنائية ومباشرتها، ولا يجوز تركها أو وقفها إلا وفقاً للقانون."
79
- },
80
- {
81
- "id": "Q042",
82
- "category": "criminal_procedure",
83
- "question": "وفقاً لقانون الإجراءات الجنائية، ماذا تقرر المادة 2؟",
84
- "expected": {
85
- "law": "criminal_procedure",
86
- "expected_article_numbers": [
87
- "2"
88
- ],
89
- "must_cite_articles": true
90
- },
91
- "notes": "النائب العام أو أعضاء النيابة العامة يباشرون الدعوى الجنائية، ويجوز تعيين آخرين لأداء هذه الوظيفة وفقاً للقانون."
92
- },
93
- {
94
- "id": "Q066",
95
- "category": "tech_crimes",
96
- "question": "ما الذي تنظمه المادة 1 في قانون مكافحة جرائم تقنية المعلومات؟",
97
- "expected": {
98
- "law": "tech_crimes",
99
- "expected_article_numbers": [
100
- "1"
101
- ],
102
- "must_cite_articles": true
103
- },
104
- "notes": "تحدد المادة التعريفات الأساسية في القانون مثل الجهاز والوزير المختص والبيانات الإلكترونية والبيانات الشخصية والحكومية، والمعالجة الإلكترونية وتقنية المعلومات، ومقدم الخدمة والمستخدم، والبرنامج والنظام"
105
- },
106
- {
107
- "id": "Q067",
108
- "category": "tech_crimes",
109
- "question": "ما الذي تنظمه المادة 2 في قانون مكافحة جرائم تقنية المعلومات؟",
110
- "expected": {
111
- "law": "tech_crimes",
112
- "expected_article_numbers": [
113
- "2"
114
- ],
115
- "must_cite_articles": true
116
- },
117
- "notes": "تُلزم المادة مقدمي الخدمة بحفظ سجلات وبيانات محددة لمدة 180 يومًا، والحفاظ على سريتها وتأمينها وعدم إفشائها إلا بأمر قضائي. كما تُلزم��م بتوفير بيانات تعريفية وترخيصية للمستخدمين والجهات المختصة، وتمكي"
118
- },
119
- {
120
- "id": "Q076",
121
- "category": "labor",
122
- "question": "ما مضمون المادة 1 في قانون العمل المصري (قانون العمل)؟",
123
- "expected": {
124
- "law": "labor",
125
- "expected_article_numbers": [
126
- "1"
127
- ],
128
- "must_cite_articles": true
129
- },
130
- "notes": "تعريفات شاملة لـ 38 مصطلحاً أساسياً في قانون العمل تشمل: تعريف العامل وصاحب العمل والمتدرج، تفصيل شامل لمكونات الأجر (الأساسي والمتغير بما في ذلك العمولة والعلاوات والمنح والمكافآت والبدلات ونصيب الأر"
131
- },
132
- {
133
- "id": "Q077",
134
- "category": "labor",
135
- "question": "ما مضمون المادة 2 في قانون العمل المصري (قانون العمل)؟",
136
- "expected": {
137
- "law": "labor",
138
- "expected_article_numbers": [
139
- "2"
140
- ],
141
- "must_cite_articles": true
142
- },
143
- "notes": "تحديد السنة بـ365 يوماً والشهر بـ30 يوماً لأغراض تطبيق القانون، ما لم يتفق الطرفان على خلاف ذلك."
144
- },
145
- {
146
- "id": "Q091",
147
- "category": "personal_status",
148
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 1؟",
149
- "expected": {
150
- "law": "personal_status",
151
- "expected_article_numbers": [
152
- "1"
153
- ],
154
- "must_cite_articles": true
155
- },
156
- "notes": "تجب النفقة للزوجة من تاريخ العقد الصحيح وتشمل الغذاء والكسوة والمسكن والعلاج. لا تجب النفقة إذا ارتدت أو امتنعت عن تسليم نفسها أو خرجت بدون إذن. نفقة الزوجة دين على الزوج ولها امتياز على أمواله."
157
- },
158
- {
159
- "id": "Q092",
160
- "category": "personal_status",
161
- "question": "في القانون رقم 25 لسنة 1920، ماذا تقرر المادة 2؟",
162
- "expected": {
163
- "law": "personal_status",
164
- "expected_article_numbers": [
165
- "2"
166
- ],
167
- "must_cite_articles": true
168
- },
169
- "notes": "نفقة المطلقة التي تستحقها تعتبر ديناً من تاريخ الطلاق."
170
- }
171
- ]
172
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
rag_test_runner.py DELETED
@@ -1,270 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- RAG Test Runner (metrics + CSV)
5
- --------------------------------
6
- Runs a JSON test set against either:
7
- 1) a local Python module exposing `ask(question) -> (answer: str, sources: list[dict])`, OR
8
- 2) an HTTP endpoint that returns JSON {"answer": "...", "sources": [...]}
9
-
10
- Outputs:
11
- - summary metrics to stdout
12
- - a CSV report with per-case results (pass/fail + recall@k + rank)
13
- """
14
- from __future__ import annotations
15
-
16
- import argparse
17
- import csv
18
- import importlib
19
- import json
20
- import re
21
- from dataclasses import dataclass
22
- from datetime import datetime
23
- from typing import Any, Dict, List, Optional, Tuple
24
-
25
- try:
26
- import requests # optional for --mode http
27
- except Exception: # pragma: no cover
28
- requests = None # type: ignore
29
-
30
-
31
- @dataclass
32
- class CaseResult:
33
- case_id: str
34
- category: str
35
- question: str
36
- expected_articles: List[str]
37
- got_articles: List[str]
38
- recall_at_k: float
39
- rank: Optional[int]
40
- passed: bool
41
- fail_reason: Optional[str]
42
- answer_snippet: str
43
-
44
-
45
- def normalize_article_number(x: Any) -> Optional[str]:
46
- if x is None:
47
- return None
48
- s = str(x).strip()
49
- s = re.sub(r"\s+", " ", s)
50
- return s or None
51
-
52
-
53
- def extract_articles(sources: Any, key: str) -> List[str]:
54
- out: List[str] = []
55
- if not sources:
56
- return out
57
- if isinstance(sources, dict):
58
- sources = [sources]
59
- if not isinstance(sources, list):
60
- return out
61
-
62
- for s in sources:
63
- if not isinstance(s, dict):
64
- continue
65
- val = s.get(key)
66
- num = normalize_article_number(val)
67
- if num:
68
- out.append(num)
69
- return out
70
-
71
-
72
- def recall_and_rank(expected: List[str], retrieved: List[str], k: int) -> Tuple[float, Optional[int]]:
73
- if not expected:
74
- return 0.0, None
75
- topk = retrieved[:k]
76
- exp_set = set(expected)
77
- for i, a in enumerate(topk, start=1):
78
- if a in exp_set:
79
- return 1.0, i
80
- return 0.0, None
81
-
82
-
83
- def run_local(module_name: str, question: str) -> Tuple[str, Any]:
84
- mod = importlib.import_module(module_name)
85
- if not hasattr(mod, "ask"):
86
- raise AttributeError(f"Module '{module_name}' must expose ask(question) -> (answer, sources)")
87
- answer, sources = mod.ask(question)
88
- return str(answer), sources
89
-
90
-
91
- def run_http(url: str, question: str, timeout: float = 60.0) -> Tuple[str, Any]:
92
- if requests is None:
93
- raise RuntimeError("requests is not installed. Install it or use --mode local.")
94
- resp = requests.post(url, json={"question": question}, timeout=timeout)
95
- resp.raise_for_status()
96
- data = resp.json()
97
- return str(data.get("answer", "")), data.get("sources", [])
98
-
99
-
100
- def evaluate_case(
101
- case: Dict[str, Any],
102
- mode: str,
103
- module_name: str,
104
- url: str,
105
- k: int,
106
- source_article_key: str,
107
- ) -> CaseResult:
108
- case_id = str(case.get("id", ""))
109
- category = str(case.get("category", ""))
110
- question = str(case.get("question", "")).strip()
111
-
112
- expected = case.get("expected", {}) or {}
113
- expected_articles = [normalize_article_number(x) for x in (expected.get("expected_article_numbers") or [])]
114
- expected_articles = [x for x in expected_articles if x]
115
- expected_prefix = expected.get("expected_answer_prefix")
116
-
117
- # Run model
118
- if mode == "local":
119
- answer, sources = run_local(module_name, question)
120
- else:
121
- answer, sources = run_http(url, question)
122
-
123
- got_articles = extract_articles(sources, source_article_key)
124
-
125
- r_at_k, rank = recall_and_rank(expected_articles, got_articles, k)
126
-
127
- # Pass/fail logic
128
- passed = True
129
- fail_reason = None
130
-
131
- if expected_articles:
132
- if r_at_k < 1.0:
133
- passed = False
134
- fail_reason = f"missed expected articles in top-{k}"
135
- else:
136
- # No expected articles => check prefix if provided (procedural general gating)
137
- if expected_prefix:
138
- if not answer.strip().startswith(str(expected_prefix).strip()):
139
- passed = False
140
- fail_reason = "answer did not follow expected procedural prefix"
141
-
142
- snippet = answer.strip().replace("\n", " ")
143
- if len(snippet) > 220:
144
- snippet = snippet[:220] + "…"
145
-
146
- return CaseResult(
147
- case_id=case_id,
148
- category=category,
149
- question=question,
150
- expected_articles=expected_articles,
151
- got_articles=got_articles[:k],
152
- recall_at_k=r_at_k,
153
- rank=rank,
154
- passed=passed,
155
- fail_reason=fail_reason,
156
- answer_snippet=snippet,
157
- )
158
-
159
-
160
- def main() -> int:
161
- ap = argparse.ArgumentParser()
162
- ap.add_argument("--suite", required=True, help="Path to JSON test suite")
163
- ap.add_argument("--mode", choices=["local", "http"], default="local")
164
- ap.add_argument("--module", default="rag", help="Python module name for local mode (default: rag)")
165
- ap.add_argument("--url", default="http://127.0.0.1:8000/ask", help="HTTP endpoint for http mode")
166
- ap.add_argument("--k", type=int, default=5, help="k for recall@k (default: 5)")
167
- ap.add_argument("--out", default="rag_test_report.csv", help="Output CSV file path")
168
- ap.add_argument(
169
- "--source-article-key",
170
- default="article_number",
171
- help="Key used inside each source dict for the article number (default: article_number)",
172
- )
173
- args = ap.parse_args()
174
-
175
- with open(args.suite, "r", encoding="utf-8") as f:
176
- suite = json.load(f)
177
-
178
- cases = suite.get("cases") if isinstance(suite, dict) else None
179
- if not isinstance(cases, list):
180
- raise ValueError("Invalid suite format: expected {'cases': [...]}")
181
-
182
- results: List[CaseResult] = []
183
-
184
- for case in cases:
185
- if not isinstance(case, dict):
186
- continue
187
-
188
- try:
189
- res = evaluate_case(case, args.mode, args.module, args.url, args.k, args.source_article_key)
190
- results.append(res) # ✅ FIX: append success results
191
- except Exception as e:
192
- cid = str(case.get("id", ""))
193
- results.append(
194
- CaseResult(
195
- case_id=cid,
196
- category=str(case.get("category", "")),
197
- question=str(case.get("question", "")),
198
- expected_articles=[
199
- str(x)
200
- for x in (case.get("expected", {}) or {}).get("expected_article_numbers", [])
201
- ],
202
- got_articles=[],
203
- recall_at_k=0.0,
204
- rank=None,
205
- passed=False,
206
- fail_reason=f"runner error: {e}",
207
- answer_snippet="",
208
- )
209
- )
210
-
211
- # Aggregate metrics
212
- total = len(results)
213
- passed_cnt = sum(1 for r in results if r.passed)
214
- pass_rate = (passed_cnt / total) if total else 0.0
215
-
216
- with_expected = [r for r in results if r.expected_articles]
217
- avg_recall = (sum(r.recall_at_k for r in with_expected) / len(with_expected)) if with_expected else 0.0
218
-
219
- # MRR on cases with expected
220
- rr_vals = [(1.0 / r.rank) for r in with_expected if r.rank]
221
- mrr = (sum(rr_vals) / len(with_expected)) if with_expected else 0.0
222
-
223
- # Write CSV
224
- fieldnames = [
225
- "id",
226
- "category",
227
- "question",
228
- "expected_articles",
229
- "retrieved_topk_articles",
230
- "recall_at_k",
231
- "rank",
232
- "passed",
233
- "fail_reason",
234
- "answer_snippet",
235
- ]
236
- with open(args.out, "w", encoding="utf-8-sig", newline="") as f:
237
- w = csv.DictWriter(f, fieldnames=fieldnames)
238
- w.writeheader()
239
- for r in results:
240
- w.writerow(
241
- {
242
- "id": r.case_id,
243
- "category": r.category,
244
- "question": r.question,
245
- "expected_articles": "|".join(r.expected_articles),
246
- "retrieved_topk_articles": "|".join(r.got_articles),
247
- "recall_at_k": f"{r.recall_at_k:.3f}",
248
- "rank": "" if r.rank is None else r.rank,
249
- "passed": "PASS" if r.passed else "FAIL",
250
- "fail_reason": r.fail_reason or "",
251
- "answer_snippet": r.answer_snippet,
252
- }
253
- )
254
-
255
- print("=== RAG Test Summary ===")
256
- print(f"Timestamp: {datetime.utcnow().isoformat()}Z")
257
- print(f"Suite: {args.suite}")
258
- print(f"Mode: {args.mode}")
259
- print(f"k: {args.k}")
260
- print(f"Total cases: {total}")
261
- print(f"Passed: {passed_cnt} ({pass_rate*100:.1f}%)")
262
- print(f"Avg recall@{args.k} (cases with expected): {avg_recall:.3f}")
263
- print(f"MRR (cases with expected): {mrr:.3f}")
264
- print(f"CSV report: {args.out}")
265
-
266
- return 0 if passed_cnt == total else 1
267
-
268
-
269
- if __name__ == "__main__":
270
- raise SystemExit(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ragas_dataset_100.csv DELETED
@@ -1,101 +0,0 @@
1
- category,question,ground_truth,source_law_key,source_law_name,source_article_id,source_article_number,source_legal_nature
2
- الدستور,ما الطبيعة القانونية لحق العمل في الدستور المصري؟,حق أساسي/حرية: العمل حق وواجب تكفله الدولة. يُمنع العمل الجبري إلا بقانون ولخدمة عامة وبمقابل عادل.,egyptian_constitution,الدستور المصري,EG-CONST-ART-012,12,حق أساسي/حرية
3
- الدستور,ما الطبيعة القانونية لحرية الفكر والرأي والتعبير في الدستور المصري؟,حق أساسي/حرية: حرية الفكر والرأي والتعبير مكفولة بكافة الوسائل.,egyptian_constitution,الدستور المصري,EG-CONST-ART-065,65,حق أساسي/حرية
4
- الدستور,ما الطبيعة القانونية لحق تنظيم الاجتماعات العامة والمواكب والتظاهرات في الدستور المصري؟,حق أساسي/حرية: حق التظاهر والاجتماع السلمي مكفول بالإخطار. الاجتماعات الخاصة لا تتطلب إخطاراً ولا يجوز مراقبتها.,egyptian_constitution,الدستور المصري,EG-CONST-ART-073,73,حق أساسي/حرية
5
- الدستور,ما مضمون المادة الخاصة بحظر العمل الجبري في الدستور المصري؟,العمل حق، وواجب، وشرف تكفله الدولة. ولا يجوز إلزام أى مواطن بالعمل جبراً، إلا بمقتضى قانون، ولأداء خدمة عامة، لمدة محددة، وبمقابل عادل، ودون إخلال بالحقوق الأساسية للمكلفين بالعمل.,egyptian_constitution,الدستور المصري,EG-CONST-ART-012,12,حق أساسي/حرية
6
- الدستور,هل يكفل الدستور حرية تكوين الجمعيات والمؤسسات الأهلية؟ وما شروط ذلك بإيجاز؟,تكوين الجمعيات الأهلية حق بالإخطار. تعمل بحرية ولا تُحل إلا بحكم قضائي. تحظر الجمعيات السرية أو العسكرية.,egyptian_constitution,الدستور المصري,EG-CONST-ART-075,75,حق أساسي/حرية
7
- الدستور,ما الطبيعة القانونية لمبدأ سيادة القانون في الدستور المصري؟,مبدأ دستوري: سيادة القانون هي أساس الحكم، والدولة تخضع للقانون. استقلال القضاء وحصانته ضمانات أساسية للحقوق.,egyptian_constitution,الدستور المصري,EG-CONST-ART-094,94,مبدأ دستوري
8
- الدستور,ما حدود تحريك الدعاوى لوقف أو مصادرة الأعمال الفنية والأدبية وفق الدستور؟,حرية الإبداع مكفولة. لا يجوز الحبس في جرائم النشر الفني (إلا في حالات محددة كالتحريض على العنف). الدعاوى ضد الأعمال الفنية تكون عبر النيابة العامة.,egyptian_constitution,الدستور المصري,EG-CONST-ART-067,67,حق أساسي/حرية
9
- الدستور,هل يجوز الحبس في الجرائم المرتكبة بسبب علانية المنتج الفني أو الأدبي وفق الدستور؟,حرية الإبداع مكفولة. لا يجوز الحبس في جرائم النشر الفني (إلا في حالات محددة كالتحريض على العنف). الدعاوى ضد الأعمال الفنية تكون عبر النيابة العامة.,egyptian_constitution,الدستور المصري,EG-CONST-ART-067,67,حق أساسي/حرية
10
- الدستور,ما مضمون المادة المتعلقة بالحق في التقاضي وعدم سقوط الدعوى بالتقادم في الاعتداء على الحقوق والحريات؟,كل اعتداء على الحرية الشخصية أو حرمة الحياة الخاصة للمواطنين، وغيرها من الحقوق والحريات العامة التى يكفلها الدستور والقانون، جريمة لا تسقط الدعوى الجنائية ولا المدنية الناشئة عنها بالتقادم، وللمضرور إقامة الدعوى الجنائية بالطريق المباشر. وتكفل الدولة تعويضا عادلا لمن وقع عليه الاعتداء، وللمجلس القومى لحقوق الإنسان إبلاغ النيابة العامة عن أى انتهاك لهذه الحقوق، وله أن يتدخل فى الدعوى المدنية منضماً إلى المضرور بناء على طلبه، وذلك كله على الوجه المبين بالقانون.,egyptian_constitution,الدستور المصري,EG-CONST-ART-099,99,غير محدد / يحتاج مراجعة بشرية
11
- الدستور,ما الفرق بين حق الاجتماع الخاص وحق التظاهر وفق الدستور؟,حق ال��ظاهر والاجتماع السلمي مكفول بالإخطار. الاجتماعات الخاصة لا تتطلب إخطاراً ولا يجوز مراقبتها.,egyptian_constitution,الدستور المصري,EG-CONST-ART-073,73,حق أساسي/حرية
12
- الدستور,اذكر بإيجاز مضمون المادة التي تقرر أن الوظائف العامة حق للمواطنين على أساس الكفاءة.,الوظائف العامة حق للمواطنين على أساس الكفاءة، ودون محاباة أو وساطة، وتكليف للقائمين بها لخدمة الشعب، وتكفل الدولة حقوقهم وحمايتهم، وقيامهم بأداء واجباتهم فى رعاية مصالح الشعب، ولا يجوز فصلهم بغير الطريق التأديبى، إلا فى الأحوال التى يحددها القانون.,egyptian_constitution,الدستور المصري,EG-CONST-ART-014,14,حق أساسي/حرية
13
- الدستور,ما التزام الدولة تجاه حقوق العمال وعلاقات العمل وفق الدستور؟,تحافظ الدولة على حقوق العمال وتضمن علاقات عمل متوازنة والتفاوض الجماعي، وتحميهم من المخاطر والفصل التعسفي.,egyptian_constitution,الدستور المصري,EG-CONST-ART-013,13,حق أساسي/حرية
14
- الدستور,هل يقر الدستور حق الإخطار لتكوين الجمعيات أم يتطلب ترخيصاً؟,تكوين الجمعيات الأهلية حق بالإخطار. تعمل بحرية ولا تُحل إلا بحكم قضائي. تحظر الجمعيات السرية أو العسكرية.,egyptian_constitution,الدستور المصري,EG-CONST-ART-075,75,حق أساسي/حرية
15
- الدستور,ما القاعدة الدستورية بشأن تدخل الجهة الإدارية في شؤون الجمعيات الأهلية؟,تكوين الجمعيات الأهلية حق بالإخطار. تعمل بحرية ولا تُحل إلا بحكم قضائي. تحظر الجمعيات السرية أو العسكرية.,egyptian_constitution,الدستور المصري,EG-CONST-ART-075,75,حق أساسي/حرية
16
- الدستور,ما مضمون المادة الخاصة بحرية الإبداع الفني والأدبي؟,حرية الإبداع الفنى والأدبى مكفولة، وتلتزم الدولة بالنهوض بالفنون والآداب، ورعاية المبدعين وحماية إبداعاتهم، وتوفير وسائل التشجيع اللازمة لذلك. ولا يجوز رفع أو تحريك الدعاوى لوقف أو مصادرة الأعمال الفنية والأدبية والفكرية أو ضد مبدعيها إلا عن طريق النيابة العامة، ولا توقع عقوبة سالبة للحرية فى الجرائم التى ترتكب بسبب علانية المنتج الفنى أو الأدبى أو الفكرى، أما الجرائم المتعلقة بالتحريض على العنف أو التمييز بين المواطنين أو الطعن فى أعراض الأفراد، فيحدد القانون عقوباتها. وللمحكمة فى هذه الأحوال إلزام المحكوم عليه بتعويض جزائى للمضرور من الجريمة، إضافة إلى التعويضات الأصلية المستحقة له عما لحقه من أضرار منها، وذلك كله وفقاً للقانون.,egyptian_constitution,الدستور المصري,EG-CONST-ART-067,67,حق أساسي/حرية
17
- الدستور,في أي حالات يجيز الدستور تنظيم/تقييد بعض الحقوق بمقتضى القانون؟ اذكر الفكرة العامة فقط.,الحقوق والحريات اللصيقة بالمواطن لا تقبل التعطيل أو الانتقاص، ولا يجوز للقانون تقييد جوهرها.,egyptian_constitution,الدستور المصري,EG-CONST-ART-092,92,إحالة للتنظيم التشريعي
18
- الدستور,ما الطبيعة القانونية لحرمة الحياة الخاصة في الدستور (وفق المادة المتعلقة بالاعتداء عليها)؟,غير محدد / يحتاج مراجعة بشرية: الاعتداء على الحريات جريمة لا تسقط بالتقادم، وللمتضرر حق التعويض وإقامة الدعوى المباشرة. للمجلس القومي لحقوق الإنسان حق التدخل والإبلاغ.,egyptian_constitution,الدستور المصري,EG-CONST-ART-099,99,غير محدد / يحتاج مراجعة بشرية
19
- الدستور,ما مضمون المادة التي تنص على أن العمل حق وواجب وشرف؟,العمل حق، وواجب، وشرف تكفله الدولة. ولا يجوز إلزام أى مواطن بالعمل جبراً، إلا بمقتضى قانون، ولأداء خدمة عامة، لمدة محددة، وبمقابل عادل، ودون إخلال بالحقوق الأساسية ��لمكلفين بالعمل.,egyptian_constitution,الدستور المصري,EG-CONST-ART-012,12,حق أساسي/حرية
20
- الدستور,هل الدستور يحظر التمييز/التحريض في سياق جرائم النشر الفني؟ اذكر القيد العام.,حرية الإبداع مكفولة. لا يجوز الحبس في جرائم النشر الفني (إلا في حالات محددة كالتحريض على العنف). الدعاوى ضد الأعمال الفنية تكون عبر النيابة العامة.,egyptian_constitution,الدستور المصري,EG-CONST-ART-067,67,حق أساسي/حرية
21
- الدستور,ما علاقة مبدأ المواطنة بالنظام الجمهوري الديمقراطي وفق المادة الأولى؟,جمهورية مصر العربية دولة ذات سيادة، موحدة لا تقبل التجزئة، نظامها جمهوري ديمقراطي يقوم على المواطنة وسيادة القانون. الشعب المصري جزء من الأمة العربية والعالم الإسلامي والقارة الأفريقية.,egyptian_constitution,الدستور المصري,EG-CONST-ART-001,1,مبدأ دستوري
22
- قانون العمل,ما حكم التحرش أو التنمر أو العنف ضد العامل في مكان العمل وفق قانون العمل؟,حظر السخرة والعمل الجبري والتحرش والتنمر والعنف بكافة أشكاله (اللفظي والجسدي والنفسي) ضد العمال، مع تحديد جزاءات تأديبية في لوائح المنشأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-004,4,قاعدة قانونية
23
- قانون العمل,ما حكم تشغيل العامل سخرة أو جبراً وفق قانون العمل؟,حظر السخرة والعمل الجبري والتحرش والتنمر والعنف بكافة أشكاله (اللفظي والجسدي والنفسي) ضد العمال، مع تحديد جزاءات تأديبية في لوائح المنشأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-004,4,قاعدة قانونية
24
- قانون العمل,ما أثر أي شرط أو اتفاق ينتقص من حقوق العامل وفق قانون العمل؟,بطلان أي شرط ينتقص من حقوق العامل، مع استمرار الشروط الأفضل حتى في حالة تغيير ملكية المنشأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-006,6,قاعدة قانونية
25
- قانون العمل,ما القاعدة التي تقرر استمرار المزايا أو الشروط الأفضل للعامل حتى مع انتقال ملكية المنشأة؟,بطلان أي شرط ينتقص من حقوق العامل، مع استمرار الشروط الأفضل حتى في حالة تغيير ملكية المنشأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-006,6,قاعدة قانونية
26
- قانون العمل,كيف ينظم قانون العمل حق الإضراب بشكل عام؟,الإضراب يوقف التزامات عقد العمل طوال مدة الإضراب.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-235,235,قاعدة قانونية
27
- قانون العمل,ما شرط استنفاد التسوية الودية قبل إعلان الإضراب وفق قانون العمل؟,حق الإضراب مكفول بعد استنفاد التسوية الودية، ويُنظم عبر المنظمة النقابية أو المفوض العمالي وفق ضوابط القانون.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-231,231,قاعدة قانونية
28
- قانون العمل,من الجهة التي تعلن وتنظم الإضراب وفق قانون العمل؟,الإضراب يوقف التزامات عقد العمل طوال مدة الإضراب.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-235,235,قاعدة قانونية
29
- قانون العمل,متى يُحظر الإضراب في المنشآت الحيوية وفق قانون العمل؟,يُحظر الإضراب في المنشآت الحيوية والخدمات الأساسية أو الظروف الاستثنائية، ورئيس الوزراء يحددها بقرار.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-234,234,قاعدة قانونية
30
- قانون العمل,من يصدر قرار تحديد المنشآت الحيوية والخدمات الأساسية المحظور الإضراب فيها؟,يُحظر الإضراب في المنشآت الحيوية والخدمات الأساسية أو الظروف الاستثنائية، ورئيس الوزراء يحددها بقرار.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-234,234,قاعدة قانونية
31
- قانون العمل,اذكر بإيجاز تعريف العمل المؤقت في قانون العمل.,تعريف أنماط العمل الجديدة وذكر صورها الرئيسية مثل العمل عن بُعد، الجزئي، المرن، وتقاسم العمل.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-096,96,قاعدة قانونية
32
- قانون العمل,اذكر بإيجاز تعريف العمل العرضي في قانون العمل.,تعريف أنماط العمل الجديدة وذكر صورها الرئيسية مثل العمل عن بُعد، الجزئي، المرن، وتقاسم العمل.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-096,96,قاعدة قانونية
33
- قانون العمل,اذكر بإيجاز تعريف العمل الموسمي في قانون العمل.,تعريف أنماط العمل الجديدة وذكر صورها الرئيسية مثل العمل عن بُعد، الجزئي، المرن، وتقاسم العمل.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-096,96,قاعدة قانونية
34
- قانون العمل,اذكر بإيجاز مفهوم الأجر التأميني كما ورد في تعريفات قانون العمل.,علاوة سنوية لا تقل عن 3% من الأجر التأميني مع إمكانية التخفيض بموافقة المجلس القومي للأجور.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-012,12,قاعدة قانونية
35
- قانون العمل,ما المقصود بـ(موقع العمل) وفق تعريفات قانون العمل؟,تعريف أنماط العمل الجديدة وذكر صورها الرئيسية مثل العمل عن بُعد، الجزئي، المرن، وتقاسم العمل.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-096,96,قاعدة قانونية
36
- قانون العمل,ما المقصود بـ(المنشأة) وفق تعريفات قانون العمل؟,تعريف المنشأة: أي مشروع/مرفق يملكه أو يديره أشخاص القانون العام أو الخاص.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-243,243,قاعدة قانونية
37
- قانون العمل,ما المقصود بـ(المهنة أو الحرفة) وفق تعريفات قانون العمل؟,ينظم شكل ومحتوى اتفاق التدرج ومتطلبات المكافأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-029,29,قاعدة قانونية تنظيمية
38
- قانون العمل,ما المقصود بـ(الوزارة المختصة) و(الجهة الإدارية المختصة) وفق قانون العمل؟,تحديد اختصاصات الجهة الإدارية المختصة بقرار من الوزير.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-013,13,قاعدة قانونية
39
- قانون العمل,هل يجيز قانون العمل إبراء العامل من حقوقه أثناء سريان العقد؟,يلتزم صاحب العمل بتمكين العامل من الاطلاع على درجته الوظيفية وعناصر أجره، وإعطائه شهادة خبرة وكفاءة مجاناً عند الطلب أثناء سريان العقد أو بعد انتهائه.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-175,175,قاعدة قانونية
40
- قانون العمل,كيف يتعامل قانون العمل مع الشروط الأفضل المقررة بالعقد أو اللائحة أو العرف؟,بطلان أي شرط ينتقص من حقوق العامل، مع استمرار الشروط الأفضل حتى في حالة تغيير ملكية المنشأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-006,6,قاعدة قانونية
41
- قانون العمل,ما القاعدة العامة بشأن حظر العنف اللفظي/الجسدي/النفسي على العامل؟,حظر السخرة والعمل الجبري والتحرش والتنمر والعنف بكافة أشكاله (اللفظي والجسدي والنفسي) ضد العمال، مع تحديد جزاءات تأديبية في لوائح المنشأة.,Egyptian_Labour_Law,قانون العمل,EG-LABOR-LAW-14-2025-ART-004,4,قاعدة قانونية
42
- الإجراءات الجنائية,ما الخطوات العامة لتحرير محضر في قسم الشرطة في مصر؟,يحدد القانون مأموري الضبط القضائي وهم: أعضاء النيابة، ضباط الشرطة وأمناؤها، العمد، نظار المحطات، وغيرهم. ويجوز بقرار وزاري منح صفة الضبط القضائي لموظفين آخرين.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-023,23,تحديد اختصاص
43
- الإجراءات الجنائية,ما الخطوات العامة لتقديم بلاغ للنيابة العامة؟,سلطة النيابة العامة في الإفراج عن المتهم في أي وقت.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-204,204,سلطة النيابة العامة
44
- الإج��اءات الجنائية,ما الفرق العام بين محضر إثبات حالة ومحضر جنحة؟,سلطة قاضي التحقيق في الانتقال لإثبات حالة مسرح الجريمة والأدلة المادية.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-090,90,سلطة قاضي التحقيق
45
- الإجراءات الجنائية,ما الإجراءات العامة للتظلم من الحبس الاحتياطي؟,حق النيابة العامة في طلب الحبس الاحتياطي في أي وقت.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-137,137,سلطة النيابة العامة
46
- الإجراءات الجنائية,ما المقصود بالتلبس وما أثره الإجرائي بشكل عام؟,لمأمور الضبط القضائي في التلبس منع الحاضرين من المغادرة حتى تحرير المحضر واستدعاء من يفيد في التحقيق.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-032,32,سلطة الضبط القضائي
47
- الإجراءات الجنائية,متى يجوز القبض بدون إذن في القواعد العامة للإجراءات الجنائية؟,في جرائم الشكوى المتلبس بها لا يجوز القبض إلا إذا قدمت الشكوى ممن يملكها، ويجوز تقديمها لرجل السلطة العامة الحاضر.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-039,39,قيد على القبض
48
- الإجراءات الجنائية,ما هي الحقوق الإجرائية الأساسية للمتهم أثناء التحقيق؟,للمتضرر من الجريمة الادعاء بحقوق مدنية أثناء التحقيق، ويفصل قاضي التحقيق نهائياً في قبوله.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-076,76,حق قانوني
49
- الإجراءات الجنائية,ما هي القواعد العامة لسماع الشهود في التحقيقات؟,جواز الطعن في الأحكام الصادرة على الشهود وفقاً للقواعد المقررة.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-120,120,حق قانوني
50
- الإجراءات الجنائية,كيف يتم طلب ندب خبير في التحقيقات وفق الإجراءات العامة؟,لوزير العدل طلب ندب مستشار من محكمة الاستئناف للتحقيق في جرائم معينة بقرار من الجمعية العامة.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-065,65,قاعدة إجرائية
51
- الإجراءات الجنائية,ما الفرق بين الاستدعاء والضبط والإحضار بشكل عام؟,سلطة قاضي التحقيق في إصدار أمر الحضور أو الضبط والإحضار.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-126,126,سلطة قاضي التحقيق
52
- الإجراءات الجنائية,ما الإجراءات العامة لعمل توكيل لمحامٍ للحضور أمام النيابة؟,تطبيق أحكام الشهود أمام قاضي التحقيق على التحقيق أمام النيابة العامة.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-208,208,إحالة قانونية
53
- الإجراءات الجنائية,ما الخطوات العامة للإبلاغ عن جريمة إلكترونية رسمياً؟,لكل شخص علم بوقوع جريمة عامة (لا تتطلب شكوى) أن يبلغ النيابة أو الضبط القضائي عنها.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-025,25,حق قانوني
54
- الإجراءات الجنائية,ما الإجراءات العامة لطلب صلح/تصالح في جنحة (إن أمكن)؟,وجوب حضور المتهم أو نائبه عند التفتيش إن أمكن، وكذلك صاحب المنزل.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-092,92,ضمانة إجرائية
55
- الإجراءات الجنائية,ما القواعد العامة لإعلان الخصوم في الدعاوى الجنائية؟,حق الخصوم في الاطلاع على أوراق الدعوى بعد إعلانهم.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-236,236,ضمانة إجرائية
56
- الإجراءات الجنائية,ما المقصود بأمر الحفظ وأثره بشكل عام؟,يجب على النيابة إعلان أمر الحفظ للمجني عليه والمدعي بالحقوق المدنية أو ورثتهما.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-062,62,واجب إجرائي
57
- الإجراءات الجنائية,ما الفرق بين قرار الإحالة وأمر ألا وجه لإقامة الدعوى بشكل عام؟,صدور الأمر بلا وجه لإقامة الدعوى من النيابة العامة والإفراج عن المتهم.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-209,209,سلطة النيابة العامة
58
- الإجراءات الجنائية,ما الخطوات العامة للتظلم من قرار حفظ محضر؟,للنيابة العامة حفظ الأوراق إذا رأت عدم وجود محل للسير في الدعوى.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-061,61,سلطة النيابة العامة
59
- الإجراءات الجنائية,كيف يتم إثبات الإصابات في محضر (الإجراءات العامة)؟,صدور الحكم علناً وإجراءات ضمان حضور المتهم.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-303,303,قاعدة إجرائية
60
- الإجراءات الجنائية,ما إجراءات طلب استخراج شهادة تحركات/تحريات في قضية (بشكل عام)؟,حق الخصوم في المعارضة في سماع شهادة الشهود غير المعلنين.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-379,379,ضمانة إجرائية
61
- الإجراءات الجنائية,ما الإجراءات العامة للتعامل مع بلاغ سرقة (من وقت البلاغ حتى التحقيق)؟,للنيابة العامة الاطلاع على أوراق التحقيق في أي وقت دون تأخير سير التحقيق.,criminal_law,قانون الإجراءات الجنائية,EG-CRIM-PROC-ART-080,80,سلطة النيابة العامة
62
- جرائم تقنية المعلومات,ما حكم اختراق بريد إلكتروني أو حساب خاص وفق قانون مكافحة جرائم تقنية المعلومات؟,تجرم المادة إخفاء أو العبث بالأدلة الرقمية من قبل مدير موقع أو حساب أو بريد إلكتروني بقصد إعاقة عمل الجهات الرسمية.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-028,28,تجريم وعقوبة
63
- جرائم تقنية المعلومات,ما العقوبة العامة لاختراق حساب خاص لشخص طبيعي وفق قانون جرائم تقنية المعلومات؟,تلتزم السلطات المصرية بتيسير التعاون الدولي وتبادل المعلومات وفق الاتفاقيات أو المعاملة بالمثل لمنع جرائم تقنية المعلومات والمساعدة في التحقيق وتتبع الجناة، ويكون مركز الاستعداد لطوارئ الحاسب والشبكات بالجهاز نقطة الاتصال الفنية.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-004,4,تعاون دولي
64
- جرائم تقنية المعلومات,كيف تختلف العقوبة إذا كان الحساب يخص شخصاً اعتبارياً خاصاً وفق قانون جرائم تقنية المعلومات؟,تلتزم السلطات المصرية بتيسير التعاون الدولي وتبادل المعلومات وفق الاتفاقيات أو المعاملة بالمثل لمنع جرائم تقنية المعلومات والمساعدة في التحقيق وتتبع الجناة، ويكون مركز الاستعداد لطوارئ الحاسب والشبكات بالجهاز نقطة الاتصال الفنية.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-004,4,تعاون دولي
65
- جرائم تقنية المعلومات,ما حكم نشر صور أو معلومات تنتهك خصوصية شخص دون رضاه عبر الإنترنت؟,تجرم المادة الاعتداء على القيم الأسرية أو الخصوصية عبر الرسائل الكثيفة دون موافقة، أو تسليم بيانات للترويج دون موافقة، أو نشر محتوى ينتهك الخصوصية سواء كان صحيحًا أو غير صحيح.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-025,25,تجريم وعقوبة
66
- جرائم تقنية المعلومات,ما حكم إرسال رسائل إلكترونية بكثافة لشخص دون موافقته؟,تجرم المادة الاعتداء على القيم الأسرية أو الخصوصية عبر الرسائل الكثيفة دون موافقة، أو تسليم بيانات للترويج دون موافقة، أو نشر محتوى ينتهك الخصوصية سواء كان صحيحًا أو غير صحيح.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-025,25,تجريم وعقوبة
67
- جرائم تقنية المعلومات,ما حكم منح بيانات شخصية لموقع للترويج دون موافقة صاحبها؟,تجرم المادة الاعتداء على القيم الأسرية أو الخصوصية عبر الر��ائل الكثيفة دون موافقة، أو تسليم بيانات للترويج دون موافقة، أو نشر محتوى ينتهك الخصوصية سواء كان صحيحًا أو غير صحيح.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-025,25,تجريم وعقوبة
68
- جرائم تقنية المعلومات,هل تُعاقب جريمة انتهاك الخصوصية حتى لو كانت المعلومات المنشورة صحيحة؟,تجرم المادة الاعتداء على القيم الأسرية أو الخصوصية عبر الرسائل الكثيفة دون موافقة، أو تسليم بيانات للترويج دون موافقة، أو نشر محتوى ينتهك الخصوصية سواء كان صحيحًا أو غير صحيح.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-025,25,تجريم وعقوبة
69
- جرائم تقنية المعلومات,ما نطاق الأفعال التي تجرمها المادة المتعلقة بحرمة الحياة الخاصة في قانون جرائم تقنية المعلومات؟,تلتزم السلطات المصرية بتيسير التعاون الدولي وتبادل المعلومات وفق الاتفاقيات أو المعاملة بالمثل لمنع جرائم تقنية المعلومات والمساعدة في التحقيق وتتبع الجناة، ويكون مركز الاستعداد لطوارئ الحاسب والشبكات بالجهاز نقطة الاتصال الفنية.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-004,4,تعاون دولي
70
- جرائم تقنية المعلومات,هل يعتبر نشر محتوى ينتهك الحياة الخاصة عبر الشبكة المعلوماتية جريمة؟,تجرم المادة الاعتداء على القيم الأسرية أو الخصوصية عبر الرسائل الكثيفة دون موافقة، أو تسليم بيانات للترويج دون موافقة، أو نشر محتوى ينتهك الخصوصية سواء كان صحيحًا أو غير صحيح.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-025,25,تجريم وعقوبة
71
- جرائم تقنية المعلومات,ما الفرق بين اختراق الحساب وإتلاف/تعطيل البريد الإلكتروني وفق قانون جرائم تقنية المعلومات؟,تجرم المادة الاعتداء على البريد الإلكتروني أو المواقع أو الحسابات الخاصة بالإتلاف أو التعطيل أو الاختراق، وتشدد العقوبة إذا تعلق الأمر بشخص اعتباري خاص.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-018,18,تجريم وعقوبة
72
- جرائم تقنية المعلومات,في حالة تعرض شخص لابتزاز إلكتروني، ما الخطوات القانونية العامة للإبلاغ وجمع الأدلة الرقمية؟,تمنح المادة للأدلة الرقمية المستخرجة من الأنظمة والأجهزة والوسائط الإلكترونية حجية الأدلة المادية في الإثبات الجنائي متى توافرت الشروط الفنية المحددة باللائحة التنفيذية.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-011,11,قاعدة إثبات
73
- جرائم تقنية المعلومات,ما الدليل الرقمي الذي يُفضّل حفظه قبل الإبلاغ عن اختراق حساب؟,عند تعدد المستأجرين يفضل من سبق لوضع اليد أو سجل عقده بحسن نية، وإلا فطلب التعويض.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0573,573,حكم تقريري
74
- جرائم تقنية المعلومات,ما حكم انتحال شخصية على الإنترنت والإجراءات العامة للتبليغ؟,العقوبة شخصية ولا تفرض إلا بقانون وحكم قضائي، ولا تسري القوانين العقابية بأثر رجعي.,egyptian_constitution,الدستور المصري,EG-CONST-ART-095,95,غير محدد / يحتاج مراجعة بشرية
75
- جرائم تقنية المعلومات,إذا تم نشر بياناتك الشخصية بدون إذن، ما الإجراءات العامة لطلب إزالتها والتبليغ؟,لا يغير المستأجر العين بدون إذن إلا إذا لم يضر، وإلا ألزم بإعادة الحال والتعويض.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0580,580,التزام / جزاء
76
- جرائم تقنية المعلومات,ما النص الذي يجرم الاعتداء على القيم الأسرية/حرمة الحياة الخاصة عبر الإنترنت (وما فكرته العامة)؟,يعاقب بالحبس مدة لا تقل عن ستة أشهر وبغ��امة لا تقل عن خمسين ألف جنيه ولا تجاوز مائة ألف جنيه، أو بإحدى هاتين العقوبتين، كل من اعتدى على أى من المبادئ أو القيم الأسرية فى المجتمع المصرى أو انتهك حرمة الحياة الخاصة، أو أرسل بكثافة العديد من الرسائل الإلكترونية لشخص معين دون موافقته، أو منح بيانات شخصية إلى نظام أو موقع إلكترونى للترويج للسلع أو الخدمات دون موافقته، أو نشر عن طريق الشبكة المعلوماتية أو إحدى وسائل تقنية المعلومات معلومات أو أخبارًا أو صورًا وما فى حكمها تنتهك خصوصية أى شخص دون رضاه سواء كانت المعلومات المنشورة صحيحة أو غير صحيحة.,tech_crimes,قانون مكافحة جرائم تقنية المعلومات,EG-TECH-ART-025,25,تجريم وعقوبة
77
- الأحوال الشخصية,ما الشروط العامة لاستحقاق الزوجة النفقة وفق قانون الأحوال الشخصية؟,تجب النفقة للزوجة من تاريخ العقد الصحيح وتشمل الغذاء والكسوة والمسكن والعلاج. لا تجب النفقة إذا ارتدت أو امتنعت عن تسليم نفسها أو خرجت بدون إذن. نفقة الزوجة دين على الزوج ولها امتياز على أمواله.,personal_status,قانون الأحوال الشخصية,EG-PSL-1920-ART-001,1,حكم تنظيمي
78
- الأحوال الشخصية,متى تسقط نفقة الزوجة؟ (بصياغة عامة),تجب النفقة للزوجة من تاريخ العقد الصحيح وتشمل الغذاء والكسوة والمسكن والعلاج. لا تجب النفقة إذا ارتدت أو امتنعت عن تسليم نفسها أو خرجت بدون إذن. نفقة الزوجة دين على الزوج ولها امتياز على أمواله.,personal_status,قانون الأحوال الشخصية,EG-PSL-1920-ART-001,1,حكم تنظيمي
79
- الأحوال الشخصية,ما القواعد العامة لحضانة الصغير وترتيبها في قانون الأحوال الشخصية؟,تنفذ أحكام تسليم الصغير أو ضمه أو رؤيته أو سكناه وفقاً للمادتين 67 و69 من القانون 1/2000 بالإجراءات المبينة في هذا القرار.,personal_status,قانون الأحوال الشخصية,EG-PSL-2000-MD1087-ART-001,1,حكم إجرائي
80
- الأحوال الشخصية,ما الحالات العامة التي تتيح للزوجة طلب الطلاق للضرر؟,كنايات الطلاق لا يقع بها الطلاق إلا إذا نوى الزوج الطلاق.,personal_status,قانون الأحوال الشخصية,EG-PSL-1929-ART-004,4,حكم تنظيمي
81
- الأحوال الشخصية,ما الفرق العام بين الطلاق والخلع من حيث الإجراءات والآثار؟,كنايات الطلاق لا يقع بها الطلاق إلا إذا نوى الزوج الطلاق.,personal_status,قانون الأحوال الشخصية,EG-PSL-1929-ART-004,4,حكم تنظيمي
82
- الأحوال الشخصية,ما القواعد العامة لإثبات الزواج أو الطلاق عند النزاع؟,كنايات الطلاق لا يقع بها الطلاق إلا إذا نوى الزوج الطلاق.,personal_status,قانون الأحوال الشخصية,EG-PSL-1929-ART-004,4,حكم تنظيمي
83
- الأحوال الشخصية,ما المبدأ العام في رؤية الصغير وتنظيمها؟,تنفذ أحكام تسليم الصغير أو ضمه أو رؤيته أو سكناه وفقاً للمادتين 67 و69 من القانون 1/2000 بالإجراءات المبينة في هذا القرار.,personal_status,قانون الأحوال الشخصية,EG-PSL-2000-MD1087-ART-001,1,حكم إجرائي
84
- الأحوال الشخصية,ما هي المستندات العامة المطلوبة لرفع دعوى نفقة؟,لا تسمع دعوى نفقة العدة لأكثر من سنة من تاريخ الطلاق، ولا دعوى الإرث للمطلقة بعد سنة من الطلاق.,personal_status,قانون الأحوال الشخصية,EG-PSL-1929-ART-017,17,حكم تنظيمي
85
- الأحوال الشخصية,ما القواعد العامة لإثبات دخل الزوج في دعوى نفقة؟,تجب النفقة للزوجة من تاريخ العقد الصحيح وتشمل الغذاء والكسوة والمسكن والعلاج. لا تجب النفقة إذا ارتدت أو امتنعت عن تسليم نفسها أو خرجت بدون إذن. نفقة الزوجة دين على الزوج ولها امتياز على أمواله.,personal_status,قانون الأحوال الشخصية,EG-PSL-1920-ART-001,1,حكم تنظيمي
86
- الأحوال الشخصية,ما هي الآثار العامة لثبوت النسب؟,لا تسمع دعوى النسب إذا ثبت عدم التلاقي بين الزوجين، أو أتت الزوجة بالولد بعد سنة من غيبة الزوج أو طلاقها أو وفاته.,personal_status,قانون الأحوال الشخصية,EG-PSL-1929-ART-015,15,حكم تنظيمي
87
- الأحوال الشخصية,ما الإجراءات العامة لعمل إعلام وراثة؟,لا يلزم اتباع إجراءات الحصر والتحفظ إذا كان المال لا يتجاوز 3000 جنيه.,personal_status,قانون الأحوال الشخصية,EG-PSL-2000-ART-035,35,حكم إجرائي
88
- الأحوال الشخصية,كيف تُقدَّر نفقة الصغير بشكل عام؟,تنفذ أحكام تسليم الصغير أو ضمه أو رؤيته أو سكناه وفقاً للمادتين 67 و69 من القانون 1/2000 بالإجراءات المبينة في هذا القرار.,personal_status,قانون الأحوال الشخصية,EG-PSL-2000-MD1087-ART-001,1,حكم إجرائي
89
- الأحوال الشخصية,ما الفرق بين نفقة الزوجة ونفقة الصغير؟,تجب النفقة للزوجة من تاريخ العقد الصحيح وتشمل الغذاء والكسوة والمسكن والعلاج. لا تجب النفقة إذا ارتدت أو امتنعت عن تسليم نفسها أو خرجت بدون إذن. نفقة الزوجة دين على الزوج ولها امتياز على أمواله.,personal_status,قانون الأحوال الشخصية,EG-PSL-1920-ART-001,1,حكم تنظيمي
90
- الأحوال الشخصية,ما القواعد العامة للمتعة والمؤخر بعد الطلاق؟,كنايات الطلاق لا يقع بها الطلاق إلا إذا نوى الزوج الطلاق.,personal_status,قانون الأحوال الشخصية,EG-PSL-1929-ART-004,4,حكم تنظيمي
91
- الأحوال الشخصية,متى تُمنع الحاضنة من الحضانة بشكل عام؟,يجوز للنيابة العامة إصدار قرار مسبب بتسليم الصغير مؤقتاً لمن تتحقق مصلحته معها عند وجود نزاع على الحضانة.,personal_status,قانون الأحوال الشخصية,EG-PSL-2000-ART-070,70,حكم إجرائي
92
- القانون المدني,ما المقصود بالالتزام في القانون المدني؟,في الالتزام ببذل عناية يوفي المدين بالتزامه ببذل عناية الشخص العادي، ويبقى مسؤولاً عن الغش والخطأ الجسيم.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0211,211,التزام / حكم تقريري
93
- القانون المدني,ما الفرق بين البطلان المطلق والبطلان النسبي في القانون المدني؟,العقد الباطل يجوز لكل ذي مصلحة التمسك ببطلانه وللمحكمة القضاء به تلقائياً، ولا يزول بالإجازة. وتسقط دعوى البطلان بـ15 سنة.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0141,141,تعريف / حكم تقريري
94
- القانون المدني,متى يكون العقد قابلاً للإبطال؟,إذا بطل شق من العقد بطل وحده، إلا إذا تبين أن العقد ما كان ليتم بدونه فيبطل كله (انتقاص العقد).,civil_law,القانون المدني المصري,EG-CIVIL-ART-0143,143,تعريف / حكم تقريري
95
- القانون المدني,ما هي أركان العقد في القانون المدني بإيجاز؟,إذا بطل العقد وتوافرت فيه أركان عقد آخر، يصح باعتباره ذلك العقد إذا كانت نية المتعاقدين تنصرف إليه (تحول العقد).,civil_law,القانون المدني المصري,EG-CIVIL-ART-0144,144,تعريف / حكم تقريري
96
- القانون المدني,ما المقصود بالمسؤولية التقصيرية؟,كل خطأ سبب ضرراً للغير يُلزم مرتكبه بالتعويض (القاعدة العامة في المسؤولية التقصيرية).,civil_law,القانون المدني المصري,EG-CIVIL-ART-0163,163,التزام
97
- القانون المدني,ما شروط استحقاق التعويض عن الفعل الضار؟,الالتزامات غير التعاقدية تخضع لقانون بلد وقوع الفعل المنشئ. لكن الأفعال الضارة في الخارج المشروعة في مصر لا تُعد غير مشروعة.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0021,21,تعريف / حكم تقريري
98
- القانون المدني,ما الفرق بين الفسخ والبطلان في العقود؟,في العقود الملزمة للجانبين، عند عدم الوفاء يجوز للدائن بعد الإعذار طلب التنفيذ أو الفسخ مع التعويض. ويجوز للقاضي منح أجل أو رفض الفسخ إذا ك��ن الإخلال قليل الأهمية.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0157,157,حق / رخصة
99
- القانون المدني,ما المقصود بالإثراء بلا سبب وما أثره؟,تسقط دعوى الإثراء بلا سبب بـ3 سنوات من العلم أو 15 سنة من نشوء الحق.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0180,180,إجراءات / تنظيم
100
- القانون المدني,ما المقصود بالعيب في الرضا (غلط/تدليس/إكراه) بشكل عام؟,إذا أخطر المشتري البائع بالعيب في الوقت الملائم كان له الرجوع بالضمان وفق أحكام المادة 444.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0450,450,حق / إحالة تشريعية
101
- القانون المدني,ما القاعدة العامة في تنفيذ العقد بحسن نية؟,للمستحق طلب تنفيذ العقد أو فسخه مع التعويض إذا كان بعوض ولم يقم المدين بالتزامه.,civil_law,القانون المدني المصري,EG-CIVIL-ART-0746,746,جزاء
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ragas_eval.py DELETED
@@ -1,106 +0,0 @@
1
- # ragas_eval.py
2
- # تقييم نظام RAG باستخدام Ragas
3
- # الاستخدام:
4
- # 1) ضعي الملف ragas_dataset_100.csv بجانب هذا السكربت (أو عدّلي DATASET_PATH)
5
- # 2) تأكدي أن دالة ask() تعمل وتُرجع (answer, sources)
6
- # 3) شغّلي: python ragas_eval.py
7
- #
8
- # ملاحظات:
9
- # - لو ما عندك Ground Truth اتركي العمود ground_truth فاضيًا وسيتم تجاهل context_recall تلقائيًا.
10
- # - Ragas يعتمد على LLM للتقييم؛ يمكنك ضبطه على نفس مزودك (Groq) أو أي LLM آخر.
11
-
12
- import os
13
- import pandas as pd
14
- from datasets import Dataset
15
-
16
- from rag import ask # <-- تأكدي أن اسم ملفك rag.py وبه ask()
17
-
18
- from ragas import evaluate
19
- from ragas.metrics import (
20
- faithfulness,
21
- answer_relevancy,
22
- context_precision,
23
- context_recall,
24
- )
25
-
26
- # (اختياري) استخدمي نفس LLM كمُقيّم
27
- try:
28
- from langchain_groq import ChatGroq
29
- EVAL_LLM = ChatGroq(
30
- groq_api_key=os.getenv("GROQ_API_KEY"),
31
- model_name="llama-3.1-8b-instant",
32
- temperature=0
33
- )
34
- except Exception:
35
- EVAL_LLM = None
36
-
37
- DATASET_PATH = "ragas_dataset_100.csv"
38
- OUT_PATH = "ragas_results.csv"
39
-
40
- def build_eval_rows(df: pd.DataFrame):
41
- rows = []
42
- for _, r in df.iterrows():
43
- q = str(r["question"]).strip()
44
- gt = str(r.get("ground_truth", "")).strip()
45
-
46
- ans, sources = ask(q)
47
-
48
- # contexts: النصوص المسترجعة (chunks)
49
- contexts = []
50
- for s in (sources or []):
51
- c = (s.get("content") or "").strip()
52
- if c:
53
- contexts.append(c)
54
-
55
- row = {
56
- "question": q,
57
- "answer": ans,
58
- "contexts": contexts,
59
- }
60
- if gt:
61
- row["ground_truths"] = [gt] # ragas expects list[str]
62
- rows.append(row)
63
- return rows
64
-
65
- def main():
66
- df = pd.read_csv(DATASET_PATH)
67
-
68
- rows = build_eval_rows(df)
69
- dataset = Dataset.from_list(rows)
70
-
71
- # لو في أي Ground Truth فعلاً، نفعل context_recall، وإلا نشيله
72
- has_gt = any(("ground_truths" in x) for x in rows)
73
- metrics = [faithfulness, answer_relevancy, context_precision]
74
- if has_gt:
75
- metrics.append(context_recall)
76
-
77
- kwargs = {}
78
- if EVAL_LLM is not None:
79
- kwargs["llm"] = EVAL_LLM
80
-
81
- result = evaluate(dataset, metrics=metrics, **kwargs)
82
-
83
- # تلخيص
84
- print("\n=== RAGAS SUMMARY ===")
85
- try:
86
- print(result)
87
- except Exception:
88
- pass
89
-
90
- # تفاصيل لكل سؤال
91
- out_df = result.to_pandas()
92
- out_df.to_csv(OUT_PATH, index=False, encoding="utf-8-sig")
93
- print(f"\nSaved per-question results to: {OUT_PATH}")
94
-
95
- # متوسطات
96
- print("\n=== MEANS ===")
97
- for col in out_df.columns:
98
- if col.startswith("faithfulness") or col.startswith("answer_relevancy") or col.startswith("context_precision") or col.startswith("context_recall"):
99
- try:
100
- mean = out_df[col].mean()
101
- print(f"{col}: {mean:.4f}")
102
- except Exception:
103
- pass
104
-
105
- if __name__ == "__main__":
106
- main()