Upload 11 files
Browse files- .gitignore +2 -1
- MITRANLIL_NORMATIVE_RUNTIME.md +28 -0
- admin_panel.py +70 -4
- config.py +3 -2
- decision_runtime.py +327 -0
- engine.py +39 -7
- institution_onboarding.py +3 -0
- normative_contract.py +85 -10
- ontology.py +1 -1
- rdfox_pilot.py +42 -9
- ui_gradio.py +41 -4
.gitignore
CHANGED
|
@@ -2,6 +2,7 @@ __pycache__/
|
|
| 2 |
*.py[cod]
|
| 3 |
.cache/
|
| 4 |
.env
|
| 5 |
-
.venv/
|
|
|
|
| 6 |
*.log
|
| 7 |
data/feedback/regression_candidates.jsonl
|
|
|
|
| 2 |
*.py[cod]
|
| 3 |
.cache/
|
| 4 |
.env
|
| 5 |
+
.venv/
|
| 6 |
+
.runtime_deps/
|
| 7 |
*.log
|
| 8 |
data/feedback/regression_candidates.jsonl
|
MITRANLIL_NORMATIVE_RUNTIME.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MitrAnlil Normative Runtime
|
| 2 |
+
|
| 3 |
+
MitrAnlil iki ayrı çalışma katmanına sahiptir:
|
| 4 |
+
|
| 5 |
+
- **Knowledge Assistant:** Doğal dil sorusunu yorumlar, ilgili hükümleri bulur, kaynak gösterir ve soru belirsizse açıklama ister. Cevabı hukuki karar olarak çalıştırmaz.
|
| 6 |
+
- **Decision Runtime:** Yalnızca yayımlanmış ve uzman onaylı MCKF karar sözleşmelerini yapılandırılmış olay verileri üzerinde deterministik olarak yürütür. LLM, embedding ve serbest metin bu çekirdeğe girmez.
|
| 7 |
+
|
| 8 |
+
## Güvenli sonuç sözleşmesi
|
| 9 |
+
|
| 10 |
+
- `DECIDED`: Bir veya daha fazla kural eşleşmiş ve tek sonuç üretmiştir.
|
| 11 |
+
- `UNKNOWN`: Karar için gerekli olay verisi eksiktir.
|
| 12 |
+
- `CONFLICT`: Aynı öncelikteki veya aynı anda yürürlükteki kurallar farklı sonuç üretmiştir.
|
| 13 |
+
- `REQUIRES_JUDGMENT`: Kanundaki açık normatif kavram insanın gerekçeli değerlendirmesini gerektirir.
|
| 14 |
+
- `OUT_OF_SCOPE`: Yayımlanmış paket karar türünü, tarihi veya olay kolunu kapsamıyordur.
|
| 15 |
+
|
| 16 |
+
## Yayın kapısı
|
| 17 |
+
|
| 18 |
+
Karar sözleşmeleri `data/registry/normative_decision_contracts.json` içinde sürümlenir. MCKF build sırasında Python sözleşme doğrulaması ve SHACL birlikte çalışır. Kaynağı, girdi şeması, kuralı veya sonucu eksik paket yayımlanmaz. Hukuktan doğan açık değerlendirme alanları build'i engellemez; yönetici panelinde yönetişim uyarısı olarak gösterilir.
|
| 19 |
+
|
| 20 |
+
## Karar sözleşmesi asgari yapısı
|
| 21 |
+
|
| 22 |
+
Her sözleşme; benzersiz kimlik ve sürüm, karar türü, yürürlük aralığı, kaynak madde referansı, tipli olay veri şeması, öncelikli kurallar, sonuç nesnesi, insan değerlendirmesi gereksinimleri ve uzman inceleme durumu taşır.
|
| 23 |
+
|
| 24 |
+
İşlem hattı şöyledir:
|
| 25 |
+
|
| 26 |
+
`Kullanıcı dili → Knowledge Assistant → kanonik olay toplama → şema doğrulama → Decision Runtime → sonuç + kaynak + ispat izi`
|
| 27 |
+
|
| 28 |
+
Knowledge Assistant eksik olguyu kullanıcıdan istemeli; Decision Runtime eksik bilgiyi tahmin etmemeli ve `UNKNOWN` dönmelidir.
|
admin_panel.py
CHANGED
|
@@ -12,17 +12,23 @@ from typing import Any
|
|
| 12 |
from analytics import read_clarification_events, usage_summary
|
| 13 |
from answering import evaluate_query
|
| 14 |
from clause_retrieval import evaluate_clause_query, init_clause_retrieval, load_ontology
|
| 15 |
-
from config import (
|
| 16 |
ADMIN_EXAMPLES_PATH,
|
| 17 |
CORPUS_MCKF_ONTOLOGY_PATH,
|
| 18 |
FEEDBACK_LOG_PATH,
|
| 19 |
LEGAL_DOCUMENT_REGISTRY_PATH,
|
| 20 |
SOURCE_DIR,
|
| 21 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
from utils import normalize_for_search
|
| 23 |
|
| 24 |
|
| 25 |
-
DEFAULT_EXAMPLES = [
|
| 26 |
{
|
| 27 |
"question": "2547, 2914 ve 2809 sayılı kanunlar birlikte dikkate alındığında, bir üniversitede öğretim elemanlarının akademik görevleri ile üniversitenin teşkilat yapısı arasında nasıl bir ilişki kurulabilir?",
|
| 28 |
"normativity_level": 2,
|
|
@@ -35,7 +41,67 @@ DEFAULT_EXAMPLES = [
|
|
| 35 |
"question": "2547 sayılı Kanuna göre rektörün görev, yetki ve sorumlulukları nelerdir?",
|
| 36 |
"normativity_level": 2,
|
| 37 |
},
|
| 38 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
DEMO_USERS = 202
|
| 41 |
DEMO_QUERIES = 532
|
|
|
|
| 12 |
from analytics import read_clarification_events, usage_summary
|
| 13 |
from answering import evaluate_query
|
| 14 |
from clause_retrieval import evaluate_clause_query, init_clause_retrieval, load_ontology
|
| 15 |
+
from config import (
|
| 16 |
ADMIN_EXAMPLES_PATH,
|
| 17 |
CORPUS_MCKF_ONTOLOGY_PATH,
|
| 18 |
FEEDBACK_LOG_PATH,
|
| 19 |
LEGAL_DOCUMENT_REGISTRY_PATH,
|
| 20 |
SOURCE_DIR,
|
| 21 |
+
)
|
| 22 |
+
from engine import (
|
| 23 |
+
decision_contract_catalog,
|
| 24 |
+
decision_governance_report,
|
| 25 |
+
evaluate_normative_decision,
|
| 26 |
+
runtime_build_info,
|
| 27 |
+
)
|
| 28 |
from utils import normalize_for_search
|
| 29 |
|
| 30 |
|
| 31 |
+
DEFAULT_EXAMPLES = [
|
| 32 |
{
|
| 33 |
"question": "2547, 2914 ve 2809 sayılı kanunlar birlikte dikkate alındığında, bir üniversitede öğretim elemanlarının akademik görevleri ile üniversitenin teşkilat yapısı arasında nasıl bir ilişki kurulabilir?",
|
| 34 |
"normativity_level": 2,
|
|
|
|
| 41 |
"question": "2547 sayılı Kanuna göre rektörün görev, yetki ve sorumlulukları nelerdir?",
|
| 42 |
"normativity_level": 2,
|
| 43 |
},
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def decision_type_choices() -> list[str]:
|
| 48 |
+
return sorted({str(item.get("decision_type", "")) for item in decision_contract_catalog() if item.get("decision_type")})
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def render_decision_runtime_dashboard() -> str:
|
| 52 |
+
build = runtime_build_info()
|
| 53 |
+
contracts = decision_contract_catalog()
|
| 54 |
+
governance = decision_governance_report()
|
| 55 |
+
warnings = governance.get("warnings", []) or []
|
| 56 |
+
lines = [
|
| 57 |
+
"### MitrAnlil Decision Runtime",
|
| 58 |
+
"",
|
| 59 |
+
"Bu katman doğal dil üretmez. Yalnızca yayımlanmış, uzman onaylı karar sözleşmelerini yapılandırılmış olay verileri üzerinde deterministik olarak çalıştırır.",
|
| 60 |
+
"",
|
| 61 |
+
f"- **Aktif build:** `{build.get('build_id', '')}`",
|
| 62 |
+
f"- **Karar sözleşmesi:** {len(contracts)}",
|
| 63 |
+
f"- **Yönetim uyarısı:** {len(warnings)}",
|
| 64 |
+
"- **Güvenli sonuçlar:** `UNKNOWN` · `CONFLICT` · `REQUIRES_JUDGMENT` · `OUT_OF_SCOPE`",
|
| 65 |
+
]
|
| 66 |
+
if warnings:
|
| 67 |
+
lines.extend(["", "#### Hukuki belirsizlik ve yönetişim uyarıları", "", "| Durum | Sözleşme | Olgu | Açıklama | Kaynak |", "|---|---|---|---|---|"])
|
| 68 |
+
for warning in warnings:
|
| 69 |
+
lines.append(
|
| 70 |
+
f"| `{_md(warning.get('status', ''))}` | `{_md(warning.get('contract_id', ''))}` | "
|
| 71 |
+
f"`{_md(warning.get('fact', '—') or '—')}` | {_md(warning.get('message', ''))} | "
|
| 72 |
+
f"{_md(', '.join(warning.get('source_refs', []) or []))} |"
|
| 73 |
+
)
|
| 74 |
+
else:
|
| 75 |
+
lines.extend(["", "> Aktif pakette hukuk kaynaklı açık değerlendirme alanı veya statik kural çatışması saptanmadı."])
|
| 76 |
+
if contracts:
|
| 77 |
+
lines.extend(["", "#### Yayımlanmış karar kapsamı", "", "| Karar türü | Sürüm | İnceleme | Kaynak |", "|---|---|---|---|"])
|
| 78 |
+
for contract in contracts:
|
| 79 |
+
refs = [f"{ref.get('document_id', '')}::{ref.get('article_id', '')}" for ref in contract.get("source_refs", []) or []]
|
| 80 |
+
lines.append(
|
| 81 |
+
f"| `{_md(contract.get('decision_type', ''))}` | `{_md(contract.get('version', ''))}` | "
|
| 82 |
+
f"`{_md(contract.get('review_status', ''))}` | {_md(', '.join(refs))} |"
|
| 83 |
+
)
|
| 84 |
+
return "\n".join(lines)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def run_decision_runtime_ui(decision_type: str, facts_json: str, as_of_date: str = "") -> dict[str, Any]:
|
| 88 |
+
try:
|
| 89 |
+
facts = json.loads(facts_json or "{}")
|
| 90 |
+
except json.JSONDecodeError as exc:
|
| 91 |
+
return {
|
| 92 |
+
"status": "UNKNOWN",
|
| 93 |
+
"decision_type": decision_type or "",
|
| 94 |
+
"reason": f"Olay verisi geçerli JSON değil: {exc.msg}",
|
| 95 |
+
"missing_facts": [],
|
| 96 |
+
}
|
| 97 |
+
if not isinstance(facts, dict):
|
| 98 |
+
return {
|
| 99 |
+
"status": "UNKNOWN",
|
| 100 |
+
"decision_type": decision_type or "",
|
| 101 |
+
"reason": "Olay verisi bir JSON nesnesi olmalıdır.",
|
| 102 |
+
"missing_facts": [],
|
| 103 |
+
}
|
| 104 |
+
return evaluate_normative_decision(decision_type, facts, (as_of_date or "").strip())
|
| 105 |
|
| 106 |
DEMO_USERS = 202
|
| 107 |
DEMO_QUERIES = 532
|
config.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
| 2 |
from pathlib import Path
|
| 3 |
|
| 4 |
APP_TITLE = "MitrAnlil Yükseköğretim Mevzuatı AI Asistanı"
|
| 5 |
-
APP_VERSION = "MCKF v1.
|
| 6 |
OFFICIAL_SOURCE_URL = (
|
| 7 |
"https://www.mevzuat.gov.tr/"
|
| 8 |
)
|
|
@@ -25,7 +25,8 @@ CANDIDATE_BUILDS_DIR = ONBOARDING_DIR / "builds"
|
|
| 25 |
PUBLISH_BACKUPS_DIR = ONBOARDING_DIR / "backups"
|
| 26 |
|
| 27 |
SOURCE_TEXT_PATH = SOURCE_DIR / "2547_clean.txt"
|
| 28 |
-
LEGAL_DOCUMENT_REGISTRY_PATH = REGISTRY_DIR / "legal_documents.json"
|
|
|
|
| 29 |
MCKF_ONTOLOGY_PATH = MCKF_DIR / "2547_mckf_ontology.json"
|
| 30 |
MCKF_SEMANTIC_FRAMES_PATH = MCKF_DIR / "2547_semantic_frames.jsonl"
|
| 31 |
MCKF_RETRIEVAL_INDEX_PATH = MCKF_DIR / "2547_retrieval_index.json"
|
|
|
|
| 2 |
from pathlib import Path
|
| 3 |
|
| 4 |
APP_TITLE = "MitrAnlil Yükseköğretim Mevzuatı AI Asistanı"
|
| 5 |
+
APP_VERSION = "MCKF v1.1 Knowledge Assistant + Decision Runtime"
|
| 6 |
OFFICIAL_SOURCE_URL = (
|
| 7 |
"https://www.mevzuat.gov.tr/"
|
| 8 |
)
|
|
|
|
| 25 |
PUBLISH_BACKUPS_DIR = ONBOARDING_DIR / "backups"
|
| 26 |
|
| 27 |
SOURCE_TEXT_PATH = SOURCE_DIR / "2547_clean.txt"
|
| 28 |
+
LEGAL_DOCUMENT_REGISTRY_PATH = REGISTRY_DIR / "legal_documents.json"
|
| 29 |
+
DECISION_CONTRACT_REGISTRY_PATH = REGISTRY_DIR / "normative_decision_contracts.json"
|
| 30 |
MCKF_ONTOLOGY_PATH = MCKF_DIR / "2547_mckf_ontology.json"
|
| 31 |
MCKF_SEMANTIC_FRAMES_PATH = MCKF_DIR / "2547_semantic_frames.jsonl"
|
| 32 |
MCKF_RETRIEVAL_INDEX_PATH = MCKF_DIR / "2547_retrieval_index.json"
|
decision_runtime.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import asdict, dataclass
|
| 4 |
+
from datetime import date
|
| 5 |
+
from enum import Enum
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class DecisionStatus(str, Enum):
|
| 10 |
+
DECIDED = "DECIDED"
|
| 11 |
+
UNKNOWN = "UNKNOWN"
|
| 12 |
+
CONFLICT = "CONFLICT"
|
| 13 |
+
REQUIRES_JUDGMENT = "REQUIRES_JUDGMENT"
|
| 14 |
+
OUT_OF_SCOPE = "OUT_OF_SCOPE"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass(frozen=True)
|
| 18 |
+
class DecisionResult:
|
| 19 |
+
status: str
|
| 20 |
+
decision_type: str
|
| 21 |
+
outcome: Any = None
|
| 22 |
+
reason: str = ""
|
| 23 |
+
contract_ids: tuple[str, ...] = ()
|
| 24 |
+
matched_rule_ids: tuple[str, ...] = ()
|
| 25 |
+
source_refs: tuple[str, ...] = ()
|
| 26 |
+
missing_facts: tuple[str, ...] = ()
|
| 27 |
+
judgment_requirements: tuple[str, ...] = ()
|
| 28 |
+
trace: tuple[dict[str, Any], ...] = ()
|
| 29 |
+
|
| 30 |
+
def to_dict(self) -> dict[str, Any]:
|
| 31 |
+
return asdict(self)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class NormativeDecisionRuntime:
|
| 35 |
+
"""Deterministic evaluator for expert-approved MCKF decision contracts.
|
| 36 |
+
|
| 37 |
+
Natural language, embeddings and LLM output never enter this evaluator.
|
| 38 |
+
The same package version and structured facts always produce the same
|
| 39 |
+
result and proof trace.
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(self, contracts: list[dict[str, Any]] | None = None, build_id: str = "") -> None:
|
| 43 |
+
self.contracts = [dict(item) for item in (contracts or [])]
|
| 44 |
+
self.build_id = build_id
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def from_corpus(cls, corpus: dict[str, Any] | None) -> "NormativeDecisionRuntime":
|
| 48 |
+
corpus = corpus or {}
|
| 49 |
+
return cls(corpus.get("decision_contracts", []) or [], str(corpus.get("build_id", "") or ""))
|
| 50 |
+
|
| 51 |
+
def evaluate(
|
| 52 |
+
self,
|
| 53 |
+
decision_type: str,
|
| 54 |
+
facts: dict[str, Any] | None,
|
| 55 |
+
as_of_date: str = "",
|
| 56 |
+
) -> dict[str, Any]:
|
| 57 |
+
decision_type = str(decision_type or "").strip()
|
| 58 |
+
facts = facts if isinstance(facts, dict) else {}
|
| 59 |
+
candidates = [
|
| 60 |
+
item for item in self.contracts
|
| 61 |
+
if str(item.get("decision_type", "") or "") == decision_type
|
| 62 |
+
and _is_effective(item, as_of_date)
|
| 63 |
+
]
|
| 64 |
+
if not candidates:
|
| 65 |
+
return DecisionResult(
|
| 66 |
+
status=DecisionStatus.OUT_OF_SCOPE.value,
|
| 67 |
+
decision_type=decision_type,
|
| 68 |
+
reason="Yayınlanmış MCKF paketinde bu karar türü ve tarih için çalıştırılabilir sözleşme yok.",
|
| 69 |
+
).to_dict()
|
| 70 |
+
|
| 71 |
+
evaluations = [_evaluate_contract(contract, facts, decision_type) for contract in candidates]
|
| 72 |
+
decided = [item for item in evaluations if item.status == DecisionStatus.DECIDED.value]
|
| 73 |
+
distinct_outcomes = {_stable_value(item.outcome) for item in decided}
|
| 74 |
+
if len(distinct_outcomes) > 1:
|
| 75 |
+
return DecisionResult(
|
| 76 |
+
status=DecisionStatus.CONFLICT.value,
|
| 77 |
+
decision_type=decision_type,
|
| 78 |
+
reason="Aynı olay verileri yürürlükteki karar sözleşmelerinde farklı sonuçlar üretti.",
|
| 79 |
+
contract_ids=tuple(item.contract_ids[0] for item in decided),
|
| 80 |
+
matched_rule_ids=tuple(rule for item in decided for rule in item.matched_rule_ids),
|
| 81 |
+
source_refs=tuple(dict.fromkeys(ref for item in decided for ref in item.source_refs)),
|
| 82 |
+
trace=tuple(step for item in evaluations for step in item.trace),
|
| 83 |
+
).to_dict()
|
| 84 |
+
if decided:
|
| 85 |
+
winner = decided[0]
|
| 86 |
+
return winner.to_dict()
|
| 87 |
+
|
| 88 |
+
for status in (
|
| 89 |
+
DecisionStatus.REQUIRES_JUDGMENT.value,
|
| 90 |
+
DecisionStatus.UNKNOWN.value,
|
| 91 |
+
DecisionStatus.CONFLICT.value,
|
| 92 |
+
):
|
| 93 |
+
matching = [item for item in evaluations if item.status == status]
|
| 94 |
+
if matching:
|
| 95 |
+
merged = matching[0]
|
| 96 |
+
return DecisionResult(
|
| 97 |
+
status=status,
|
| 98 |
+
decision_type=decision_type,
|
| 99 |
+
reason=merged.reason,
|
| 100 |
+
contract_ids=tuple(item.contract_ids[0] for item in matching),
|
| 101 |
+
source_refs=tuple(dict.fromkeys(ref for item in matching for ref in item.source_refs)),
|
| 102 |
+
missing_facts=tuple(dict.fromkeys(fact for item in matching for fact in item.missing_facts)),
|
| 103 |
+
judgment_requirements=tuple(
|
| 104 |
+
dict.fromkeys(req for item in matching for req in item.judgment_requirements)
|
| 105 |
+
),
|
| 106 |
+
trace=tuple(step for item in evaluations for step in item.trace),
|
| 107 |
+
).to_dict()
|
| 108 |
+
return DecisionResult(
|
| 109 |
+
status=DecisionStatus.OUT_OF_SCOPE.value,
|
| 110 |
+
decision_type=decision_type,
|
| 111 |
+
reason="Sözleşme kapsamı bulundu ancak verilen olay için uygulanabilir karar kolu yok.",
|
| 112 |
+
contract_ids=tuple(str(item.get("contract_id", "")) for item in candidates),
|
| 113 |
+
).to_dict()
|
| 114 |
+
|
| 115 |
+
def governance_report(self) -> dict[str, Any]:
|
| 116 |
+
warnings = []
|
| 117 |
+
for contract in self.contracts:
|
| 118 |
+
contract_id = str(contract.get("contract_id", "") or "")
|
| 119 |
+
for requirement in contract.get("judgment_requirements", []) or []:
|
| 120 |
+
warnings.append({
|
| 121 |
+
"status": DecisionStatus.REQUIRES_JUDGMENT.value,
|
| 122 |
+
"contract_id": contract_id,
|
| 123 |
+
"fact": requirement.get("fact", ""),
|
| 124 |
+
"message": requirement.get("reason", "İnsan değerlendirmesi gereken açık normatif kavram."),
|
| 125 |
+
"source_refs": _source_refs(contract),
|
| 126 |
+
})
|
| 127 |
+
conflicts = _static_conflicts(contract)
|
| 128 |
+
warnings.extend(conflicts)
|
| 129 |
+
if not contract.get("rules"):
|
| 130 |
+
warnings.append({
|
| 131 |
+
"status": DecisionStatus.OUT_OF_SCOPE.value,
|
| 132 |
+
"contract_id": contract_id,
|
| 133 |
+
"message": "Karar sözleşmesinde çalıştırılabilir kural bulunmuyor.",
|
| 134 |
+
"source_refs": _source_refs(contract),
|
| 135 |
+
})
|
| 136 |
+
return {
|
| 137 |
+
"schema": "MCKF-DecisionGovernanceReport-v1.0",
|
| 138 |
+
"build_id": self.build_id,
|
| 139 |
+
"contract_count": len(self.contracts),
|
| 140 |
+
"warning_count": len(warnings),
|
| 141 |
+
"warnings": warnings,
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def _evaluate_contract(contract: dict[str, Any], facts: dict[str, Any], decision_type: str) -> DecisionResult:
|
| 146 |
+
contract_id = str(contract.get("contract_id", "") or "")
|
| 147 |
+
source_refs = tuple(_source_refs(contract))
|
| 148 |
+
schema_fields = (contract.get("input_schema", {}) or {}).get("fields", {}) or {}
|
| 149 |
+
required = [name for name, spec in schema_fields.items() if (spec or {}).get("required")]
|
| 150 |
+
judgment_requirements = contract.get("judgment_requirements", []) or []
|
| 151 |
+
unresolved_judgment = [
|
| 152 |
+
item for item in judgment_requirements
|
| 153 |
+
if _fact_value(facts, str(item.get("fact", "") or ""), _MISSING) is _MISSING
|
| 154 |
+
]
|
| 155 |
+
if unresolved_judgment:
|
| 156 |
+
return DecisionResult(
|
| 157 |
+
status=DecisionStatus.REQUIRES_JUDGMENT.value,
|
| 158 |
+
decision_type=decision_type,
|
| 159 |
+
reason="Hukukun açık bıraktığı değerlendirme alanı insan kararı gerektiriyor.",
|
| 160 |
+
contract_ids=(contract_id,),
|
| 161 |
+
source_refs=source_refs,
|
| 162 |
+
judgment_requirements=tuple(
|
| 163 |
+
str(item.get("reason", "") or item.get("fact", "")) for item in unresolved_judgment
|
| 164 |
+
),
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
missing = [name for name in required if _fact_value(facts, name, _MISSING) is _MISSING]
|
| 168 |
+
if missing:
|
| 169 |
+
return DecisionResult(
|
| 170 |
+
status=DecisionStatus.UNKNOWN.value,
|
| 171 |
+
decision_type=decision_type,
|
| 172 |
+
reason="Deterministik karar için zorunlu olay verileri eksik.",
|
| 173 |
+
contract_ids=(contract_id,),
|
| 174 |
+
source_refs=source_refs,
|
| 175 |
+
missing_facts=tuple(missing),
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
matched = []
|
| 179 |
+
trace = []
|
| 180 |
+
for rule in contract.get("rules", []) or []:
|
| 181 |
+
conditions = rule.get("when", {}) or {}
|
| 182 |
+
applies, condition_trace = _conditions_match(conditions, facts)
|
| 183 |
+
trace.append({
|
| 184 |
+
"contract_id": contract_id,
|
| 185 |
+
"rule_id": rule.get("rule_id", ""),
|
| 186 |
+
"applies": applies,
|
| 187 |
+
"conditions": condition_trace,
|
| 188 |
+
})
|
| 189 |
+
if applies:
|
| 190 |
+
matched.append(rule)
|
| 191 |
+
if not matched and "default_outcome" in contract:
|
| 192 |
+
return DecisionResult(
|
| 193 |
+
status=DecisionStatus.DECIDED.value,
|
| 194 |
+
decision_type=decision_type,
|
| 195 |
+
outcome=contract.get("default_outcome"),
|
| 196 |
+
reason="Hiçbir özel kural eşleşmedi; uzman onaylı varsayılan sonuç uygulandı.",
|
| 197 |
+
contract_ids=(contract_id,),
|
| 198 |
+
source_refs=source_refs,
|
| 199 |
+
trace=tuple(trace),
|
| 200 |
+
)
|
| 201 |
+
if not matched:
|
| 202 |
+
return DecisionResult(
|
| 203 |
+
status=DecisionStatus.OUT_OF_SCOPE.value,
|
| 204 |
+
decision_type=decision_type,
|
| 205 |
+
reason="Girdiler tam olsa da bu olay için sözleşmede uygulanabilir kural bulunmuyor.",
|
| 206 |
+
contract_ids=(contract_id,),
|
| 207 |
+
source_refs=source_refs,
|
| 208 |
+
trace=tuple(trace),
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
highest = max(int(item.get("priority", 0) or 0) for item in matched)
|
| 212 |
+
winners = [item for item in matched if int(item.get("priority", 0) or 0) == highest]
|
| 213 |
+
outcomes = {_stable_value(item.get("outcome")) for item in winners}
|
| 214 |
+
if len(outcomes) > 1:
|
| 215 |
+
return DecisionResult(
|
| 216 |
+
status=DecisionStatus.CONFLICT.value,
|
| 217 |
+
decision_type=decision_type,
|
| 218 |
+
reason="Aynı öncelikte birden fazla kural farklı sonuç üretti.",
|
| 219 |
+
contract_ids=(contract_id,),
|
| 220 |
+
matched_rule_ids=tuple(str(item.get("rule_id", "")) for item in winners),
|
| 221 |
+
source_refs=source_refs,
|
| 222 |
+
trace=tuple(trace),
|
| 223 |
+
)
|
| 224 |
+
winner = sorted(winners, key=lambda item: str(item.get("rule_id", "")))[0]
|
| 225 |
+
return DecisionResult(
|
| 226 |
+
status=DecisionStatus.DECIDED.value,
|
| 227 |
+
decision_type=decision_type,
|
| 228 |
+
outcome=winner.get("outcome"),
|
| 229 |
+
reason=str(winner.get("explanation", "") or "Uzman onaylı normatif kural uygulandı."),
|
| 230 |
+
contract_ids=(contract_id,),
|
| 231 |
+
matched_rule_ids=(str(winner.get("rule_id", "")),),
|
| 232 |
+
source_refs=source_refs,
|
| 233 |
+
trace=tuple(trace),
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def _conditions_match(conditions: dict[str, Any], facts: dict[str, Any]) -> tuple[bool, list[dict[str, Any]]]:
|
| 238 |
+
mode = "any" if "any" in conditions else "all"
|
| 239 |
+
rows = conditions.get(mode, []) or []
|
| 240 |
+
if not rows:
|
| 241 |
+
return True, []
|
| 242 |
+
trace = []
|
| 243 |
+
results = []
|
| 244 |
+
for condition in rows:
|
| 245 |
+
fact = str(condition.get("fact", "") or "")
|
| 246 |
+
actual = _fact_value(facts, fact, _MISSING)
|
| 247 |
+
operator = str(condition.get("operator", "eq") or "eq")
|
| 248 |
+
expected = condition.get("value")
|
| 249 |
+
result = False if actual is _MISSING else _compare(actual, operator, expected)
|
| 250 |
+
results.append(result)
|
| 251 |
+
trace.append({"fact": fact, "operator": operator, "expected": expected, "actual": None if actual is _MISSING else actual, "result": result})
|
| 252 |
+
return (any(results) if mode == "any" else all(results)), trace
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _compare(actual: Any, operator: str, expected: Any) -> bool:
|
| 256 |
+
operations = {
|
| 257 |
+
"eq": lambda: actual == expected,
|
| 258 |
+
"ne": lambda: actual != expected,
|
| 259 |
+
"gt": lambda: actual > expected,
|
| 260 |
+
"gte": lambda: actual >= expected,
|
| 261 |
+
"lt": lambda: actual < expected,
|
| 262 |
+
"lte": lambda: actual <= expected,
|
| 263 |
+
"in": lambda: actual in expected,
|
| 264 |
+
"not_in": lambda: actual not in expected,
|
| 265 |
+
"truthy": lambda: bool(actual),
|
| 266 |
+
"falsy": lambda: not bool(actual),
|
| 267 |
+
}
|
| 268 |
+
try:
|
| 269 |
+
return bool(operations[operator]())
|
| 270 |
+
except (KeyError, TypeError, ValueError):
|
| 271 |
+
return False
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _fact_value(facts: dict[str, Any], path: str, default: Any) -> Any:
|
| 275 |
+
value: Any = facts
|
| 276 |
+
for part in path.split("."):
|
| 277 |
+
if not isinstance(value, dict) or part not in value:
|
| 278 |
+
return default
|
| 279 |
+
value = value[part]
|
| 280 |
+
return value
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _source_refs(contract: dict[str, Any]) -> list[str]:
|
| 284 |
+
return [
|
| 285 |
+
f"{item.get('document_id', '')}::{item.get('article_id', '')}"
|
| 286 |
+
for item in contract.get("source_refs", []) or []
|
| 287 |
+
if item.get("document_id") and item.get("article_id")
|
| 288 |
+
]
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _is_effective(contract: dict[str, Any], value: str) -> bool:
|
| 292 |
+
if not value:
|
| 293 |
+
return True
|
| 294 |
+
try:
|
| 295 |
+
target = date.fromisoformat(value)
|
| 296 |
+
start = date.fromisoformat(contract["effective_from"]) if contract.get("effective_from") else None
|
| 297 |
+
end = date.fromisoformat(contract["effective_to"]) if contract.get("effective_to") else None
|
| 298 |
+
except (TypeError, ValueError):
|
| 299 |
+
return False
|
| 300 |
+
return (not start or target >= start) and (not end or target <= end)
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def _static_conflicts(contract: dict[str, Any]) -> list[dict[str, Any]]:
|
| 304 |
+
signatures: dict[str, dict[str, Any]] = {}
|
| 305 |
+
warnings = []
|
| 306 |
+
for rule in contract.get("rules", []) or []:
|
| 307 |
+
signature = _stable_value({"priority": rule.get("priority", 0), "when": rule.get("when", {})})
|
| 308 |
+
previous = signatures.get(signature)
|
| 309 |
+
if previous and _stable_value(previous.get("outcome")) != _stable_value(rule.get("outcome")):
|
| 310 |
+
warnings.append({
|
| 311 |
+
"status": DecisionStatus.CONFLICT.value,
|
| 312 |
+
"contract_id": contract.get("contract_id", ""),
|
| 313 |
+
"rule_ids": [previous.get("rule_id", ""), rule.get("rule_id", "")],
|
| 314 |
+
"message": "Aynı koşul ve öncelik için farklı sonuçlar tanımlanmış.",
|
| 315 |
+
"source_refs": _source_refs(contract),
|
| 316 |
+
})
|
| 317 |
+
signatures[signature] = rule
|
| 318 |
+
return warnings
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def _stable_value(value: Any) -> str:
|
| 322 |
+
import json
|
| 323 |
+
|
| 324 |
+
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
_MISSING = object()
|
engine.py
CHANGED
|
@@ -9,15 +9,17 @@ from config import (
|
|
| 9 |
MCKF_VALIDATION_REPORT_PATH,
|
| 10 |
RUNTIME_BUILD_MANIFEST_PATH,
|
| 11 |
)
|
| 12 |
-
from normative_contract import file_sha256
|
|
|
|
| 13 |
|
| 14 |
BOOTSTRAPPED = False
|
| 15 |
-
RUNTIME_BUILD_INFO: dict = {}
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
def bootstrap_once(force: bool = False) -> None:
|
| 19 |
"""Load one validated, immutable runtime build as the knowledge source."""
|
| 20 |
-
global BOOTSTRAPPED, RUNTIME_BUILD_INFO
|
| 21 |
if BOOTSTRAPPED and not force:
|
| 22 |
return
|
| 23 |
ontology_path = CORPUS_MCKF_ONTOLOGY_PATH if CORPUS_MCKF_ONTOLOGY_PATH.exists() else MCKF_ONTOLOGY_PATH
|
|
@@ -36,12 +38,15 @@ def bootstrap_once(force: bool = False) -> None:
|
|
| 36 |
raise RuntimeError("MCKF corpus dosya özeti manifest ile uyuşmuyor.")
|
| 37 |
if validation and validation.get("conforms") is not True:
|
| 38 |
raise RuntimeError("MCKF corpus doğrulama kapısından geçmedi; runtime başlatılmadı.")
|
| 39 |
-
init_clause_retrieval(ontology)
|
|
|
|
| 40 |
RUNTIME_BUILD_INFO = {
|
| 41 |
"build_id": ontology.get("build_id", "legacy-unversioned"),
|
| 42 |
"schema": ontology.get("schema", ""),
|
| 43 |
"manifest": bool(manifest),
|
| 44 |
-
"validation_conforms": validation.get("conforms") if validation else None,
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
BOOTSTRAPPED = True
|
| 47 |
|
|
@@ -51,8 +56,35 @@ def reload_runtime() -> dict:
|
|
| 51 |
return dict(RUNTIME_BUILD_INFO)
|
| 52 |
|
| 53 |
|
| 54 |
-
def runtime_build_info() -> dict:
|
| 55 |
-
return dict(RUNTIME_BUILD_INFO)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
def _load_json(path) -> dict:
|
|
|
|
| 9 |
MCKF_VALIDATION_REPORT_PATH,
|
| 10 |
RUNTIME_BUILD_MANIFEST_PATH,
|
| 11 |
)
|
| 12 |
+
from normative_contract import file_sha256
|
| 13 |
+
from decision_runtime import NormativeDecisionRuntime
|
| 14 |
|
| 15 |
BOOTSTRAPPED = False
|
| 16 |
+
RUNTIME_BUILD_INFO: dict = {}
|
| 17 |
+
DECISION_RUNTIME = NormativeDecisionRuntime()
|
| 18 |
|
| 19 |
|
| 20 |
def bootstrap_once(force: bool = False) -> None:
|
| 21 |
"""Load one validated, immutable runtime build as the knowledge source."""
|
| 22 |
+
global BOOTSTRAPPED, RUNTIME_BUILD_INFO, DECISION_RUNTIME
|
| 23 |
if BOOTSTRAPPED and not force:
|
| 24 |
return
|
| 25 |
ontology_path = CORPUS_MCKF_ONTOLOGY_PATH if CORPUS_MCKF_ONTOLOGY_PATH.exists() else MCKF_ONTOLOGY_PATH
|
|
|
|
| 38 |
raise RuntimeError("MCKF corpus dosya özeti manifest ile uyuşmuyor.")
|
| 39 |
if validation and validation.get("conforms") is not True:
|
| 40 |
raise RuntimeError("MCKF corpus doğrulama kapısından geçmedi; runtime başlatılmadı.")
|
| 41 |
+
init_clause_retrieval(ontology)
|
| 42 |
+
DECISION_RUNTIME = NormativeDecisionRuntime.from_corpus(ontology)
|
| 43 |
RUNTIME_BUILD_INFO = {
|
| 44 |
"build_id": ontology.get("build_id", "legacy-unversioned"),
|
| 45 |
"schema": ontology.get("schema", ""),
|
| 46 |
"manifest": bool(manifest),
|
| 47 |
+
"validation_conforms": validation.get("conforms") if validation else None,
|
| 48 |
+
"decision_contract_count": len(DECISION_RUNTIME.contracts),
|
| 49 |
+
"runtime_layers": ["knowledge_assistant", "decision_runtime"],
|
| 50 |
}
|
| 51 |
BOOTSTRAPPED = True
|
| 52 |
|
|
|
|
| 56 |
return dict(RUNTIME_BUILD_INFO)
|
| 57 |
|
| 58 |
|
| 59 |
+
def runtime_build_info() -> dict:
|
| 60 |
+
return dict(RUNTIME_BUILD_INFO)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def evaluate_normative_decision(decision_type: str, facts: dict, as_of_date: str = "") -> dict:
|
| 64 |
+
bootstrap_once()
|
| 65 |
+
return DECISION_RUNTIME.evaluate(decision_type, facts, as_of_date)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def decision_governance_report() -> dict:
|
| 69 |
+
bootstrap_once()
|
| 70 |
+
return DECISION_RUNTIME.governance_report()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def decision_contract_catalog() -> list[dict]:
|
| 74 |
+
bootstrap_once()
|
| 75 |
+
return [
|
| 76 |
+
{
|
| 77 |
+
"contract_id": item.get("contract_id", ""),
|
| 78 |
+
"decision_type": item.get("decision_type", ""),
|
| 79 |
+
"title": item.get("title", ""),
|
| 80 |
+
"version": item.get("version", ""),
|
| 81 |
+
"source_refs": item.get("source_refs", []) or [],
|
| 82 |
+
"input_schema": item.get("input_schema", {}) or {},
|
| 83 |
+
"judgment_requirements": item.get("judgment_requirements", []) or [],
|
| 84 |
+
"review_status": item.get("review_status", ""),
|
| 85 |
+
}
|
| 86 |
+
for item in DECISION_RUNTIME.contracts
|
| 87 |
+
]
|
| 88 |
|
| 89 |
|
| 90 |
def _load_json(path) -> dict:
|
institution_onboarding.py
CHANGED
|
@@ -253,6 +253,7 @@ def render_submission_summary(submission_choice: str) -> str:
|
|
| 253 |
[
|
| 254 |
f"- **Aday build:** `{submission.get('candidate_build_id')}`",
|
| 255 |
f"- **MCKF doğrulaması:** `{validation.get('status', 'unknown')}`",
|
|
|
|
| 256 |
]
|
| 257 |
)
|
| 258 |
if gate.get("required_pending", 0):
|
|
@@ -416,6 +417,8 @@ def build_candidate(submission_choice: str) -> tuple[str, str]:
|
|
| 416 |
"status": "passed",
|
| 417 |
"contract_conforms": bool(validation.get("conforms")),
|
| 418 |
"shacl_status": (validation.get("shacl", {}) or {}).get("status"),
|
|
|
|
|
|
|
| 419 |
}
|
| 420 |
submission["status"] = "candidate_built"
|
| 421 |
submission["updated_at"] = _now()
|
|
|
|
| 253 |
[
|
| 254 |
f"- **Aday build:** `{submission.get('candidate_build_id')}`",
|
| 255 |
f"- **MCKF doğrulaması:** `{validation.get('status', 'unknown')}`",
|
| 256 |
+
f"- **Normatif yönetişim uyarısı:** {validation.get('decision_warning_count', 0)}",
|
| 257 |
]
|
| 258 |
)
|
| 259 |
if gate.get("required_pending", 0):
|
|
|
|
| 417 |
"status": "passed",
|
| 418 |
"contract_conforms": bool(validation.get("conforms")),
|
| 419 |
"shacl_status": (validation.get("shacl", {}) or {}).get("status"),
|
| 420 |
+
"decision_warning_count": (validation.get("decision_governance", {}) or {}).get("warning_count", 0),
|
| 421 |
+
"decision_warnings": (validation.get("decision_governance", {}) or {}).get("warnings", []),
|
| 422 |
}
|
| 423 |
submission["status"] = "candidate_built"
|
| 424 |
submission["updated_at"] = _now()
|
normative_contract.py
CHANGED
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
| 7 |
from typing import Any
|
| 8 |
|
| 9 |
|
| 10 |
-
CORE_FRAME_FIELDS = {
|
| 11 |
"actor",
|
| 12 |
"action",
|
| 13 |
"object",
|
|
@@ -17,7 +17,9 @@ CORE_FRAME_FIELDS = {
|
|
| 17 |
"modality",
|
| 18 |
"norm_category",
|
| 19 |
"confidence",
|
| 20 |
-
}
|
|
|
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
@dataclass(frozen=True)
|
|
@@ -34,12 +36,14 @@ def validate_corpus(corpus: dict[str, Any]) -> dict[str, Any]:
|
|
| 34 |
documents = corpus.get("documents", []) or []
|
| 35 |
concepts = corpus.get("concepts", []) or []
|
| 36 |
clauses = corpus.get("clauses", []) or []
|
| 37 |
-
evidence_spans = corpus.get("evidence_spans", []) or []
|
|
|
|
| 38 |
|
| 39 |
document_ids = _unique_ids(documents, "document_id", "document", issues)
|
| 40 |
concept_ids = _unique_ids(concepts, "concept_id", "concept", issues)
|
| 41 |
clause_ids = _unique_ids(clauses, "clause_id", "clause", issues)
|
| 42 |
-
evidence_ids = _unique_ids(evidence_spans, "evidence_id", "evidence", issues)
|
|
|
|
| 43 |
|
| 44 |
if not corpus.get("build_id"):
|
| 45 |
_add(issues, "error", "missing_build_id", "corpus", "Corpus build_id alanı zorunludur.")
|
|
@@ -101,26 +105,35 @@ def validate_corpus(corpus: dict[str, Any]) -> dict[str, Any]:
|
|
| 101 |
if float(frame.get("confidence", 0.0) or 0.0) < 0.45:
|
| 102 |
low_confidence += 1
|
| 103 |
|
| 104 |
-
if evidence_spans and low_confidence / len(evidence_spans) > 0.45:
|
| 105 |
_add(
|
| 106 |
issues,
|
| 107 |
"warning",
|
| 108 |
"high_low_confidence_ratio",
|
| 109 |
"corpus",
|
| 110 |
f"Evidence kayıtlarının {low_confidence}/{len(evidence_spans)} kadarı düşük çıkarım güvenine sahip.",
|
| 111 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
errors = [issue for issue in issues if issue.severity == "error"]
|
| 114 |
warnings = [issue for issue in issues if issue.severity == "warning"]
|
| 115 |
return {
|
| 116 |
-
"schema": "MCKF-ValidationReport-v1.
|
| 117 |
"build_id": corpus.get("build_id", ""),
|
| 118 |
"conforms": not errors,
|
| 119 |
"summary": {
|
| 120 |
"documents": len(documents),
|
| 121 |
"concepts": len(concepts),
|
| 122 |
"clauses": len(clauses),
|
| 123 |
-
"evidence_spans": len(evidence_spans),
|
|
|
|
| 124 |
"errors": len(errors),
|
| 125 |
"warnings": len(warnings),
|
| 126 |
"low_confidence_evidence": low_confidence,
|
|
@@ -198,10 +211,72 @@ def _unique_ids(rows: list[dict[str, Any]], field: str, kind: str, issues: list[
|
|
| 198 |
return seen
|
| 199 |
|
| 200 |
|
| 201 |
-
def _id_prefix(document_id: str) -> str:
|
| 202 |
import re
|
| 203 |
|
| 204 |
-
return re.sub(r"\W+", "_", document_id.lower(), flags=re.UNICODE).strip("_") + "__"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
|
| 207 |
def _add(issues: list[ContractIssue], severity: str, code: str, entity_id: str, message: str) -> None:
|
|
|
|
| 7 |
from typing import Any
|
| 8 |
|
| 9 |
|
| 10 |
+
CORE_FRAME_FIELDS = {
|
| 11 |
"actor",
|
| 12 |
"action",
|
| 13 |
"object",
|
|
|
|
| 17 |
"modality",
|
| 18 |
"norm_category",
|
| 19 |
"confidence",
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
DECISION_OPERATORS = {"eq", "ne", "gt", "gte", "lt", "lte", "in", "not_in", "truthy", "falsy"}
|
| 23 |
|
| 24 |
|
| 25 |
@dataclass(frozen=True)
|
|
|
|
| 36 |
documents = corpus.get("documents", []) or []
|
| 37 |
concepts = corpus.get("concepts", []) or []
|
| 38 |
clauses = corpus.get("clauses", []) or []
|
| 39 |
+
evidence_spans = corpus.get("evidence_spans", []) or []
|
| 40 |
+
decision_contracts = corpus.get("decision_contracts", []) or []
|
| 41 |
|
| 42 |
document_ids = _unique_ids(documents, "document_id", "document", issues)
|
| 43 |
concept_ids = _unique_ids(concepts, "concept_id", "concept", issues)
|
| 44 |
clause_ids = _unique_ids(clauses, "clause_id", "clause", issues)
|
| 45 |
+
evidence_ids = _unique_ids(evidence_spans, "evidence_id", "evidence", issues)
|
| 46 |
+
_unique_ids(decision_contracts, "contract_id", "decision_contract", issues)
|
| 47 |
|
| 48 |
if not corpus.get("build_id"):
|
| 49 |
_add(issues, "error", "missing_build_id", "corpus", "Corpus build_id alanı zorunludur.")
|
|
|
|
| 105 |
if float(frame.get("confidence", 0.0) or 0.0) < 0.45:
|
| 106 |
low_confidence += 1
|
| 107 |
|
| 108 |
+
if evidence_spans and low_confidence / len(evidence_spans) > 0.45:
|
| 109 |
_add(
|
| 110 |
issues,
|
| 111 |
"warning",
|
| 112 |
"high_low_confidence_ratio",
|
| 113 |
"corpus",
|
| 114 |
f"Evidence kayıtlarının {low_confidence}/{len(evidence_spans)} kadarı düşük çıkarım güvenine sahip.",
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
article_refs = {
|
| 118 |
+
(str(item.get("document_id", "")), str(item.get("article_id", "")))
|
| 119 |
+
for item in [*concepts, *clauses]
|
| 120 |
+
if item.get("document_id") and item.get("article_id")
|
| 121 |
+
}
|
| 122 |
+
for contract in decision_contracts:
|
| 123 |
+
_validate_decision_contract(contract, document_ids, article_refs, issues)
|
| 124 |
|
| 125 |
errors = [issue for issue in issues if issue.severity == "error"]
|
| 126 |
warnings = [issue for issue in issues if issue.severity == "warning"]
|
| 127 |
return {
|
| 128 |
+
"schema": "MCKF-ValidationReport-v1.1",
|
| 129 |
"build_id": corpus.get("build_id", ""),
|
| 130 |
"conforms": not errors,
|
| 131 |
"summary": {
|
| 132 |
"documents": len(documents),
|
| 133 |
"concepts": len(concepts),
|
| 134 |
"clauses": len(clauses),
|
| 135 |
+
"evidence_spans": len(evidence_spans),
|
| 136 |
+
"decision_contracts": len(decision_contracts),
|
| 137 |
"errors": len(errors),
|
| 138 |
"warnings": len(warnings),
|
| 139 |
"low_confidence_evidence": low_confidence,
|
|
|
|
| 211 |
return seen
|
| 212 |
|
| 213 |
|
| 214 |
+
def _id_prefix(document_id: str) -> str:
|
| 215 |
import re
|
| 216 |
|
| 217 |
+
return re.sub(r"\W+", "_", document_id.lower(), flags=re.UNICODE).strip("_") + "__"
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def _validate_decision_contract(
|
| 221 |
+
contract: dict[str, Any],
|
| 222 |
+
document_ids: set[str],
|
| 223 |
+
article_refs: set[tuple[str, str]],
|
| 224 |
+
issues: list[ContractIssue],
|
| 225 |
+
) -> None:
|
| 226 |
+
contract_id = str(contract.get("contract_id", "") or "")
|
| 227 |
+
for field in ("version", "title", "decision_type", "input_schema", "rules", "review_status"):
|
| 228 |
+
if not contract.get(field):
|
| 229 |
+
_add(issues, "error", f"missing_decision_{field}", contract_id, f"Karar sözleşmesinde {field} alanı yok.")
|
| 230 |
+
|
| 231 |
+
source_refs = contract.get("source_refs", []) or []
|
| 232 |
+
if not source_refs:
|
| 233 |
+
_add(issues, "error", "missing_decision_source_refs", contract_id, "Karar sözleşmesi en az bir kaynak hükme bağlanmalıdır.")
|
| 234 |
+
for source in source_refs:
|
| 235 |
+
document_id = str(source.get("document_id", "") or "")
|
| 236 |
+
article_id = str(source.get("article_id", "") or "")
|
| 237 |
+
if document_id not in document_ids:
|
| 238 |
+
_add(issues, "error", "unknown_decision_document", contract_id, f"Bilinmeyen karar kaynağı: {document_id}.")
|
| 239 |
+
if article_id and (document_id, article_id) not in article_refs:
|
| 240 |
+
_add(issues, "error", "unknown_decision_article", contract_id, f"Kaynak hüküm corpus içinde bulunamadı: {document_id}::{article_id}.")
|
| 241 |
+
|
| 242 |
+
fields = ((contract.get("input_schema", {}) or {}).get("fields", {}) or {})
|
| 243 |
+
if not isinstance(fields, dict) or not fields:
|
| 244 |
+
_add(issues, "error", "empty_decision_input_schema", contract_id, "Karar sözleşmesinin olay veri şeması boş olamaz.")
|
| 245 |
+
fields = {}
|
| 246 |
+
|
| 247 |
+
rules = contract.get("rules", []) or []
|
| 248 |
+
rule_ids: set[str] = set()
|
| 249 |
+
for rule in rules:
|
| 250 |
+
rule_id = str(rule.get("rule_id", "") or "")
|
| 251 |
+
if not rule_id:
|
| 252 |
+
_add(issues, "error", "missing_decision_rule_id", contract_id, "Karar kuralı kimliği boş.")
|
| 253 |
+
elif rule_id in rule_ids:
|
| 254 |
+
_add(issues, "error", "duplicate_decision_rule_id", contract_id, f"Kural kimliği tekrar ediyor: {rule_id}.")
|
| 255 |
+
rule_ids.add(rule_id)
|
| 256 |
+
if "outcome" not in rule:
|
| 257 |
+
_add(issues, "error", "missing_decision_outcome", rule_id or contract_id, "Karar kuralında sonuç yok.")
|
| 258 |
+
if not isinstance(rule.get("priority", 0), int):
|
| 259 |
+
_add(issues, "error", "invalid_decision_priority", rule_id or contract_id, "Kural önceliği tam sayı olmalıdır.")
|
| 260 |
+
conditions = rule.get("when", {}) or {}
|
| 261 |
+
if not isinstance(conditions, dict) or not ({"all", "any"} & set(conditions)):
|
| 262 |
+
_add(issues, "error", "invalid_decision_conditions", rule_id or contract_id, "Kural koşulları all veya any listesi taşımalıdır.")
|
| 263 |
+
continue
|
| 264 |
+
for mode in ("all", "any"):
|
| 265 |
+
for condition in conditions.get(mode, []) or []:
|
| 266 |
+
fact = str(condition.get("fact", "") or "")
|
| 267 |
+
operator = str(condition.get("operator", "eq") or "eq")
|
| 268 |
+
if fact not in fields:
|
| 269 |
+
_add(issues, "error", "unknown_decision_fact", rule_id or contract_id, f"Koşul şemada olmayan olguyu kullanıyor: {fact}.")
|
| 270 |
+
if operator not in DECISION_OPERATORS:
|
| 271 |
+
_add(issues, "error", "unknown_decision_operator", rule_id or contract_id, f"Desteklenmeyen operatör: {operator}.")
|
| 272 |
+
|
| 273 |
+
for requirement in contract.get("judgment_requirements", []) or []:
|
| 274 |
+
fact = str(requirement.get("fact", "") or "")
|
| 275 |
+
if fact not in fields:
|
| 276 |
+
_add(issues, "error", "unknown_judgment_fact", contract_id, f"Değerlendirme alanı şemada yok: {fact}.")
|
| 277 |
+
if not requirement.get("reason"):
|
| 278 |
+
_add(issues, "error", "missing_judgment_reason", contract_id, "İnsan değerlendirmesi gereksinimi gerekçe taşımalıdır.")
|
| 279 |
+
_add(issues, "warning", "requires_legal_judgment", contract_id, str(requirement.get("reason", "")))
|
| 280 |
|
| 281 |
|
| 282 |
def _add(issues: list[ContractIssue], severity: str, code: str, entity_id: str, message: str) -> None:
|
ontology.py
CHANGED
|
@@ -12,7 +12,7 @@ from evidence import build_evidence_spans
|
|
| 12 |
|
| 13 |
|
| 14 |
DOCUMENT_ONTOLOGY_SCHEMA = "MCKF-NormativeDocument-v1.0"
|
| 15 |
-
CORPUS_ONTOLOGY_SCHEMA = "MCKF-NormativeCorpus-v1.
|
| 16 |
RETRIEVAL_INDEX_SCHEMA = "MCKF-HybridRetrievalIndex-v1.0"
|
| 17 |
ONTOLOGY_SCHEMA = DOCUMENT_ONTOLOGY_SCHEMA
|
| 18 |
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
DOCUMENT_ONTOLOGY_SCHEMA = "MCKF-NormativeDocument-v1.0"
|
| 15 |
+
CORPUS_ONTOLOGY_SCHEMA = "MCKF-NormativeCorpus-v1.1"
|
| 16 |
RETRIEVAL_INDEX_SCHEMA = "MCKF-HybridRetrievalIndex-v1.0"
|
| 17 |
ONTOLOGY_SCHEMA = DOCUMENT_ONTOLOGY_SCHEMA
|
| 18 |
|
rdfox_pilot.py
CHANGED
|
@@ -12,7 +12,7 @@ from normative_roles import extract_question_frame
|
|
| 12 |
from utils import normalize_for_search, query_terms
|
| 13 |
|
| 14 |
|
| 15 |
-
PILOT_SCHEMA = "MCKF-SemanticGraphRuntime-v1.
|
| 16 |
BASE_URI = "https://mitranlil.ai/mckf/"
|
| 17 |
RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
| 18 |
|
|
@@ -23,11 +23,12 @@ MVP_CORE = [
|
|
| 23 |
"hybrid_retrieval",
|
| 24 |
"evidence_sufficiency_gate",
|
| 25 |
"source_locked_generation",
|
| 26 |
-
"citation_renderer",
|
|
|
|
| 27 |
]
|
| 28 |
|
| 29 |
POST_PILOT = [
|
| 30 |
-
"
|
| 31 |
"wide_graph_traversal",
|
| 32 |
"exception_precedence_inference",
|
| 33 |
"strict_rdf_compliance",
|
|
@@ -269,7 +270,7 @@ def build_pilot_manifest(ontology: dict[str, Any]) -> dict[str, Any]:
|
|
| 269 |
"source_policy",
|
| 270 |
],
|
| 271 |
},
|
| 272 |
-
"MVPSemanticNormativeUnit": {
|
| 273 |
"source": "data/mckf/corpus_mckf_ontology.json::evidence_spans",
|
| 274 |
"fields": [
|
| 275 |
"unit_id",
|
|
@@ -286,8 +287,12 @@ def build_pilot_manifest(ontology: dict[str, Any]) -> dict[str, Any]:
|
|
| 286 |
"temporal_constraints",
|
| 287 |
"cross_references",
|
| 288 |
"extraction_confidence",
|
| 289 |
-
],
|
| 290 |
-
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
},
|
| 292 |
"retrieval_channels": ["bm25", "lsa_dense", "semantic_frame", "sparql_graph", "exact_reference"],
|
| 293 |
"fusion": "weighted_reciprocal_rank_fusion",
|
|
@@ -319,7 +324,8 @@ def build_pilot_manifest(ontology: dict[str, Any]) -> dict[str, Any]:
|
|
| 319 |
"semantic_normative_unit_count": len(evidence_spans),
|
| 320 |
"unit_type_counts": dict(sorted(unit_type_counts.items())),
|
| 321 |
"concept_type_counts": dict(sorted(concept_type_counts.items())),
|
| 322 |
-
"cross_document_edge_count": len(ontology.get("cross_document_edges", []) or []),
|
|
|
|
| 323 |
},
|
| 324 |
}
|
| 325 |
|
|
@@ -467,14 +473,41 @@ def _iter_triples(ontology: dict[str, Any]) -> Iterable[str]:
|
|
| 467 |
for reference in _cross_references(evidence.get("source_text", "")):
|
| 468 |
yield _triple(unit_uri, _uri_pred("crossReferences"), _literal(reference["ref_text"]))
|
| 469 |
|
| 470 |
-
for edge in ontology.get("cross_document_edges", []) or []:
|
| 471 |
edge_uri = _uri("edge", edge.get("edge_id", ""))
|
| 472 |
yield _triple(edge_uri, RDF_TYPE, _uri_class("CrossDocumentEdge"))
|
| 473 |
yield _triple(edge_uri, _uri_pred("relationType"), _literal(edge.get("relation_type", "")))
|
| 474 |
yield _triple(edge_uri, _uri_pred("sourceDocument"), _uri("document", edge.get("source_document_id", "")))
|
| 475 |
yield _triple(edge_uri, _uri_pred("sourceArticle"), _literal(edge.get("source_article_id", "")))
|
| 476 |
yield _triple(edge_uri, _uri_pred("targetDocument"), _uri("document", edge.get("target_document_id", "")))
|
| 477 |
-
yield _triple(edge_uri, _uri_pred("targetArticle"), _literal(edge.get("target_article_id", "")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
|
| 479 |
|
| 480 |
def _intent_from_query(normalized: str, frame: dict[str, Any]) -> str:
|
|
|
|
| 12 |
from utils import normalize_for_search, query_terms
|
| 13 |
|
| 14 |
|
| 15 |
+
PILOT_SCHEMA = "MCKF-SemanticGraphRuntime-v1.1"
|
| 16 |
BASE_URI = "https://mitranlil.ai/mckf/"
|
| 17 |
RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
| 18 |
|
|
|
|
| 23 |
"hybrid_retrieval",
|
| 24 |
"evidence_sufficiency_gate",
|
| 25 |
"source_locked_generation",
|
| 26 |
+
"citation_renderer",
|
| 27 |
+
"deterministic_decision_runtime",
|
| 28 |
]
|
| 29 |
|
| 30 |
POST_PILOT = [
|
| 31 |
+
"advanced_rule_authoring",
|
| 32 |
"wide_graph_traversal",
|
| 33 |
"exception_precedence_inference",
|
| 34 |
"strict_rdf_compliance",
|
|
|
|
| 270 |
"source_policy",
|
| 271 |
],
|
| 272 |
},
|
| 273 |
+
"MVPSemanticNormativeUnit": {
|
| 274 |
"source": "data/mckf/corpus_mckf_ontology.json::evidence_spans",
|
| 275 |
"fields": [
|
| 276 |
"unit_id",
|
|
|
|
| 287 |
"temporal_constraints",
|
| 288 |
"cross_references",
|
| 289 |
"extraction_confidence",
|
| 290 |
+
],
|
| 291 |
+
},
|
| 292 |
+
"MCKFDecisionContract": {
|
| 293 |
+
"source": "data/mckf/corpus_mckf_ontology.json::decision_contracts",
|
| 294 |
+
"fields": ["contract_id", "version", "decision_type", "source_refs", "input_schema", "rules", "judgment_requirements", "review_status"],
|
| 295 |
+
},
|
| 296 |
},
|
| 297 |
"retrieval_channels": ["bm25", "lsa_dense", "semantic_frame", "sparql_graph", "exact_reference"],
|
| 298 |
"fusion": "weighted_reciprocal_rank_fusion",
|
|
|
|
| 324 |
"semantic_normative_unit_count": len(evidence_spans),
|
| 325 |
"unit_type_counts": dict(sorted(unit_type_counts.items())),
|
| 326 |
"concept_type_counts": dict(sorted(concept_type_counts.items())),
|
| 327 |
+
"cross_document_edge_count": len(ontology.get("cross_document_edges", []) or []),
|
| 328 |
+
"decision_contract_count": len(ontology.get("decision_contracts", []) or []),
|
| 329 |
},
|
| 330 |
}
|
| 331 |
|
|
|
|
| 473 |
for reference in _cross_references(evidence.get("source_text", "")):
|
| 474 |
yield _triple(unit_uri, _uri_pred("crossReferences"), _literal(reference["ref_text"]))
|
| 475 |
|
| 476 |
+
for edge in ontology.get("cross_document_edges", []) or []:
|
| 477 |
edge_uri = _uri("edge", edge.get("edge_id", ""))
|
| 478 |
yield _triple(edge_uri, RDF_TYPE, _uri_class("CrossDocumentEdge"))
|
| 479 |
yield _triple(edge_uri, _uri_pred("relationType"), _literal(edge.get("relation_type", "")))
|
| 480 |
yield _triple(edge_uri, _uri_pred("sourceDocument"), _uri("document", edge.get("source_document_id", "")))
|
| 481 |
yield _triple(edge_uri, _uri_pred("sourceArticle"), _literal(edge.get("source_article_id", "")))
|
| 482 |
yield _triple(edge_uri, _uri_pred("targetDocument"), _uri("document", edge.get("target_document_id", "")))
|
| 483 |
+
yield _triple(edge_uri, _uri_pred("targetArticle"), _literal(edge.get("target_article_id", "")))
|
| 484 |
+
|
| 485 |
+
for contract in ontology.get("decision_contracts", []) or []:
|
| 486 |
+
contract_id = str(contract.get("contract_id", "") or "")
|
| 487 |
+
contract_uri = _uri("decision-contract", contract_id)
|
| 488 |
+
yield _triple(contract_uri, RDF_TYPE, _uri_class("DecisionContract"))
|
| 489 |
+
yield _triple(contract_uri, _uri_pred("contractId"), _literal(contract_id))
|
| 490 |
+
yield _triple(contract_uri, _uri_pred("decisionType"), _literal(contract.get("decision_type", "")))
|
| 491 |
+
yield _triple(contract_uri, _uri_pred("version"), _literal(contract.get("version", "")))
|
| 492 |
+
yield _triple(contract_uri, _uri_pred("reviewStatus"), _literal(contract.get("review_status", "")))
|
| 493 |
+
for source in contract.get("source_refs", []) or []:
|
| 494 |
+
yield _triple(contract_uri, _uri_pred("sourceDocument"), _uri("document", source.get("document_id", "")))
|
| 495 |
+
yield _triple(contract_uri, _uri_pred("sourceArticle"), _literal(source.get("article_id", "")))
|
| 496 |
+
for requirement in contract.get("judgment_requirements", []) or []:
|
| 497 |
+
requirement_uri = _uri("judgment-requirement", f"{contract_id}__{requirement.get('fact', '')}")
|
| 498 |
+
yield _triple(requirement_uri, RDF_TYPE, _uri_class("JudgmentRequirement"))
|
| 499 |
+
yield _triple(requirement_uri, _uri_pred("belongsToDecisionContract"), contract_uri)
|
| 500 |
+
yield _triple(requirement_uri, _uri_pred("judgmentFact"), _literal(requirement.get("fact", "")))
|
| 501 |
+
yield _triple(requirement_uri, _uri_pred("judgmentReason"), _literal(requirement.get("reason", "")))
|
| 502 |
+
yield _triple(contract_uri, _uri_pred("requiresJudgment"), requirement_uri)
|
| 503 |
+
for rule in contract.get("rules", []) or []:
|
| 504 |
+
rule_uri = _uri("decision-rule", f"{contract_id}__{rule.get('rule_id', '')}")
|
| 505 |
+
yield _triple(rule_uri, RDF_TYPE, _uri_class("DecisionRule"))
|
| 506 |
+
yield _triple(rule_uri, _uri_pred("ruleId"), _literal(rule.get("rule_id", "")))
|
| 507 |
+
yield _triple(rule_uri, _uri_pred("belongsToDecisionContract"), contract_uri)
|
| 508 |
+
yield _triple(rule_uri, _uri_pred("decisionOutcome"), _literal(json.dumps(rule.get("outcome"), ensure_ascii=False, sort_keys=True)))
|
| 509 |
+
yield _triple(rule_uri, _uri_pred("priority"), _literal(str(rule.get("priority", 0))))
|
| 510 |
+
yield _triple(contract_uri, _uri_pred("decisionRule"), rule_uri)
|
| 511 |
|
| 512 |
|
| 513 |
def _intent_from_query(normalized: str, frame: dict[str, Any]) -> str:
|
ui_gradio.py
CHANGED
|
@@ -3,13 +3,16 @@ from __future__ import annotations
|
|
| 3 |
import gradio as gr
|
| 4 |
|
| 5 |
from admin_panel import (
|
| 6 |
-
add_source_document,
|
|
|
|
| 7 |
remove_source_document,
|
| 8 |
-
render_admin_overview,
|
|
|
|
| 9 |
render_metrics_dashboard,
|
| 10 |
render_recommendations,
|
| 11 |
render_sources_table,
|
| 12 |
-
render_user_analytics,
|
|
|
|
| 13 |
)
|
| 14 |
from answering import answer_question, evaluate_query
|
| 15 |
from clarification import clarification_display_text, clarification_payload
|
|
@@ -475,7 +478,7 @@ def _runtime_band() -> str:
|
|
| 475 |
graph = retrieval.get("graph", {}) or {}
|
| 476 |
return f"""
|
| 477 |
<div class="mitranlil-runtime-band">
|
| 478 |
-
<div class="mitranlil-runtime-cell">MCKF çekirdeği<strong>v1.
|
| 479 |
<div class="mitranlil-runtime-cell">Kaynak kilidi<strong class="mitranlil-runtime-ok">Aktif</strong></div>
|
| 480 |
<div class="mitranlil-runtime-cell">Retrieval<strong>BM25 · LSA · MCKF · SPARQL</strong></div>
|
| 481 |
<div class="mitranlil-runtime-cell">Doğrulanmış build<strong>{build.get('build_id', '')} · {graph.get('triples', 0)} üçlü</strong></div>
|
|
@@ -600,6 +603,40 @@ def create_demo() -> gr.Blocks:
|
|
| 600 |
refresh_admin_button = gr.Button("Paneli yenile")
|
| 601 |
|
| 602 |
with gr.Tabs():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
with gr.Tab("Kurumsal Onboarding"):
|
| 604 |
onboarding_overview = gr.HTML(render_onboarding_overview())
|
| 605 |
onboarding_refresh = gr.Button("Onboarding durumunu yenile")
|
|
|
|
| 3 |
import gradio as gr
|
| 4 |
|
| 5 |
from admin_panel import (
|
| 6 |
+
add_source_document,
|
| 7 |
+
decision_type_choices,
|
| 8 |
remove_source_document,
|
| 9 |
+
render_admin_overview,
|
| 10 |
+
render_decision_runtime_dashboard,
|
| 11 |
render_metrics_dashboard,
|
| 12 |
render_recommendations,
|
| 13 |
render_sources_table,
|
| 14 |
+
render_user_analytics,
|
| 15 |
+
run_decision_runtime_ui,
|
| 16 |
)
|
| 17 |
from answering import answer_question, evaluate_query
|
| 18 |
from clarification import clarification_display_text, clarification_payload
|
|
|
|
| 478 |
graph = retrieval.get("graph", {}) or {}
|
| 479 |
return f"""
|
| 480 |
<div class="mitranlil-runtime-band">
|
| 481 |
+
<div class="mitranlil-runtime-cell">MCKF çekirdeği<strong>v1.1 Knowledge + Decision</strong></div>
|
| 482 |
<div class="mitranlil-runtime-cell">Kaynak kilidi<strong class="mitranlil-runtime-ok">Aktif</strong></div>
|
| 483 |
<div class="mitranlil-runtime-cell">Retrieval<strong>BM25 · LSA · MCKF · SPARQL</strong></div>
|
| 484 |
<div class="mitranlil-runtime-cell">Doğrulanmış build<strong>{build.get('build_id', '')} · {graph.get('triples', 0)} üçlü</strong></div>
|
|
|
|
| 603 |
refresh_admin_button = gr.Button("Paneli yenile")
|
| 604 |
|
| 605 |
with gr.Tabs():
|
| 606 |
+
with gr.Tab("Decision Runtime"):
|
| 607 |
+
decision_dashboard = gr.Markdown(
|
| 608 |
+
render_decision_runtime_dashboard(),
|
| 609 |
+
elem_classes=["mitranlil-panel"],
|
| 610 |
+
)
|
| 611 |
+
refresh_decision_button = gr.Button("Karar kapsamını ve uyarıları yenile")
|
| 612 |
+
gr.Markdown(
|
| 613 |
+
"Knowledge Assistant kullanıcı sorusunu ve kaynakları açıklar; bu ekran ise yalnızca "
|
| 614 |
+
"kanonik JSON olgularını yayımlanmış karar sözleşmesinde çalıştırır. LLM karar vermez."
|
| 615 |
+
)
|
| 616 |
+
decision_type = gr.Dropdown(
|
| 617 |
+
choices=decision_type_choices(),
|
| 618 |
+
label="Karar türü",
|
| 619 |
+
value=(decision_type_choices()[0] if decision_type_choices() else None),
|
| 620 |
+
)
|
| 621 |
+
decision_facts = gr.Textbox(
|
| 622 |
+
label="Olay verileri (JSON)",
|
| 623 |
+
value='{"education_mode":"OPEN_EDUCATION","consecutive_terms_conditions_not_met":4}',
|
| 624 |
+
lines=7,
|
| 625 |
+
)
|
| 626 |
+
decision_as_of = gr.Textbox(label="Karar tarihi (isteğe bağlı)", placeholder="2026-08-06")
|
| 627 |
+
run_decision_button = gr.Button("Deterministik kararı çalıştır", variant="primary")
|
| 628 |
+
decision_output = gr.JSON(label="Karar sonucu ve ispat izi")
|
| 629 |
+
run_decision_button.click(
|
| 630 |
+
fn=run_decision_runtime_ui,
|
| 631 |
+
inputs=[decision_type, decision_facts, decision_as_of],
|
| 632 |
+
outputs=decision_output,
|
| 633 |
+
)
|
| 634 |
+
refresh_decision_button.click(
|
| 635 |
+
fn=render_decision_runtime_dashboard,
|
| 636 |
+
inputs=[],
|
| 637 |
+
outputs=decision_dashboard,
|
| 638 |
+
)
|
| 639 |
+
|
| 640 |
with gr.Tab("Kurumsal Onboarding"):
|
| 641 |
onboarding_overview = gr.HTML(render_onboarding_overview())
|
| 642 |
onboarding_refresh = gr.Button("Onboarding durumunu yenile")
|