mfirat007 commited on
Commit
663f74a
·
verified ·
1 Parent(s): 70d278e

Upload 11 files

Browse files
.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.0 Proof-First Normative Intelligence"
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.0",
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.0"
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.0"
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
- "full_rule_engine",
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.0 Proof-First</strong></div>
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")