File size: 18,477 Bytes
eff511c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | from __future__ import annotations
import re
from typing import Any
from clause_retrieval import evidence_spans_matching
from sources import format_clause_sources
from utils import normalize_for_search
CASE_RULES = {
"student_status_and_remedy": {
"triggers": (
"kaydinin sil", "kaydi sil", "ilisigi kes", "kayit sil", "itiraz",
"uyaril", "teblig", "bildiril", "mazeret",
),
"evidence_selectors": (
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 44",
"label": "b",
"contains_all": ("azami süreler içinde", "kayıt yenilenmemesi", "ilişikleri kesilmez"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 44",
"label": "b",
"contains_all": ("dört yıl üst üste", "yetkili kurullarının kararı", "ilişikleri kesilebilir"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 54",
"label": "ç",
"contains_all": ("disiplin cezalarına karşı", "on beş gün", "itiraz edilebilir"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 54",
"label": "e",
"contains_all": ("itiraz hakkı kullanılmadan", "idari yargı"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 54",
"label": "11",
"contains_all": ("öğrenciye yapılacak tebligatta", "Tebligat Kanunu"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 14",
"label": "6",
"contains_all": ("enstitü", "kararlarına yapılacak itirazları", "karara bağlamak"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 15",
"label": "4",
"contains_all": ("enstitü", "yönetim kurullarının kararlarına", "kesin karara bağlamak"),
},
),
},
"student_excuse_period": {
"triggers": ("mazeret", "mazeret hakki", "mazeret izni"),
"evidence_selectors": (
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 46",
"label": "g",
"contains_all": ("mazeretleri", "yönetim kurulunca kabul edilmeyenler", "kayıt yaptıramaz"),
},
),
},
"graduation_and_registration": {
"triggers": (
"mezun", "diploma", "ders sec", "ders kay", "kayit yaptir",
"tez dersi", "uzmanlik dersi", "kredi", "yukumluluk",
),
"evidence_selectors": (
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 44",
"label": "a",
"contains_all": ("ders kredileri", "diğer yükümlülükleri", "tamamlamaları halinde"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 44",
"label": "b",
"contains_all": ("kayıt", "tez", "teorik ders"),
},
),
},
"academic_guidance": {
"triggers": ("danisman", "ogretim uyes", "rehber", "tez"),
"evidence_selectors": (
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 22",
"label": "c",
"contains_all": ("yardım etmek", "yol göstermek", "rehberlik etmek"),
},
),
},
"institute_governance": {
"triggers": ("enstitu", "mudurluk", "yonetim kurulu", "ogrenci isleri"),
"evidence_selectors": (
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 19",
"label": "b",
"contains_all": ("enstitü müdürü", "görevleri", "enstitü bakımından"),
},
{
"document_id": "TR-KANUN-2547",
"article_id": "Madde 19",
"label": "e",
"contains_all": ("enstitü kurulu", "yönetim kurulu", "görevleri"),
},
),
},
}
def is_normative_case_question(question: str) -> bool:
q = normalize_for_search(question)
if len(q) < 150:
return False
assessment_signal = any(
signal in q
for signal in (
"kim sorumlu", "kimler sorumlu", "sorumluluklarini yapmamis",
"sorumlulugunu yerine getirmemis", "burada kim", "bu durumda kim",
"hukuken degerlendir", "mevzuata gore degerlendir",
"hangi kanun maddelerinden yararlanabilir", "itiraz etmek istiyor",
"kaydinin silinecegi", "ilisiginin kesilecegi",
)
)
narrative_signal_count = sum(
signal in q
for signal in (
"ancak", "daha sonra", "son asama", "son donem", "boyunca",
"halde", "diyor", "ise", "cunku",
)
)
actor_count = sum(
actor in q
for actor in ("ogrenci", "danisman", "enstitu", "universite", "yonetim", "senato")
)
institutional_action = any(
signal in q for signal in ("bildiriliyor", "bildirildi", "kaydinin sil", "ilisigi kes", "itiraz")
)
return assessment_signal and narrative_signal_count >= 1 and (
actor_count >= 2 or (actor_count >= 1 and institutional_action)
)
def analyze_normative_case(question: str) -> dict[str, Any]:
if not is_normative_case_question(question):
return {}
normalized = normalize_for_search(question)
matched_rules = [
rule_name
for rule_name, rule in CASE_RULES.items()
if any(trigger in normalized for trigger in rule["triggers"])
]
evidence_selectors: list[dict[str, Any]] = []
for rule_name in matched_rules:
for selector in CASE_RULES[rule_name]["evidence_selectors"]:
if selector not in evidence_selectors:
evidence_selectors.append(selector)
evidence_spans = evidence_spans_matching(evidence_selectors)
if not evidence_spans:
return {}
explicit_actors = _case_actors(normalized)
inferred_actors = _inferred_case_actors(normalized, explicit_actors)
actors = explicit_actors + [actor for actor in inferred_actors if actor not in explicit_actors]
facts = _case_facts(question)
answer = _render_case_assessment(question, matched_rules, actors, facts, evidence_spans)
return {
"answer": answer,
"case_frame": {
"actors": actors,
"explicit_actors": explicit_actors,
"inferred_actors": inferred_actors,
"facts": facts,
"matched_rules": matched_rules,
"responsibility_tests": _responsibility_tests(actors),
"missing_evidence": _missing_evidence(),
"assessment_mode": "source_locked_multi_hop",
},
"evidence_spans": evidence_spans,
"evidence_ids": [item.get("evidence_id", "") for item in evidence_spans],
"clause_ids": _unique(item.get("parent_clause_id", "") for item in evidence_spans),
"document_ids": _unique(item.get("document_id", "") for item in evidence_spans),
"source_ids": _unique(item.get("article_id", "") for item in evidence_spans),
"scores": [20.0 for _ in evidence_spans],
"score_breakdowns": [{"case_rule_match": 1.0} for _ in evidence_spans],
"source_route": {"route": "normative_case_analysis"},
}
def _render_case_assessment(
question: str,
matched_rules: list[str],
actors: list[str],
facts: list[str],
evidence_spans: list[dict[str, Any]],
) -> str:
if "student_status_and_remedy" in matched_rules or "student_excuse_period" in matched_rules:
return _render_student_status_remedy_assessment(question, facts, evidence_spans)
has_graduation = "graduation_and_registration" in matched_rules
has_guidance = "academic_guidance" in matched_rules
has_institute = "institute_governance" in matched_rules
sections = [
"**Normatif vaka değerlendirmesi**",
"",
"Bu olayda tek bir kişiyi doğrudan sorumlu ilan etmek mevcut corpusla mümkün değil. "
"2547 sayılı Kanun sorumluluk çerçevesini kuruyor; fakat tez dersi ve uzmanlık alan dersi "
"kaydını kimin kontrol edeceği üniversitenin lisansüstü yönetmeliği, senato kararları ve "
"enstitü iş akışında belirlenir.",
]
if facts:
sections.extend(["", "**Sistemin ayırdığı olay unsurları**"])
sections.extend(f"- {fact}" for fact in facts[:5])
sections.extend(["", "**Kanundan kesin olarak çıkarılabilenler**"])
if has_graduation:
sections.extend([
"1. **Mezuniyet koşulu:** Madde 44/a uyarınca öğrenci, belirlenen ders kredileri ile "
"diğer yükümlülükleri başarıyla tamamlamadan diploma hakkı kazanmaz.",
"2. **Yerel düzenleme zorunluluğu:** Madde 44/b; kayıt, devam, tez ve teorik ders "
"içerikleri ile diploma alınmasına ilişkin ayrıntıları, YÖK'ün temel ilkelerine uygun "
"olarak üniversite senatosunun düzenlemesine bırakır. Bu nedenle yalnızca 2547'ye "
"bakarak 'tez dersi seçilmemişse her durumda mezuniyet imkansızdır' denemez; ilgili "
"üniversite düzenlemesi de görülmelidir.",
])
if has_guidance:
sections.append(
"3. **Akademik rehberlik:** Madde 22/c öğretim üyesine öğrenciye gerekli konularda "
"yardım etme, yol gösterme ve rehberlik etme görevi verir. Bu genel görev danışmanın "
"süreçte tamamen ilgisiz sayılamayacağını gösterir; ancak ders kaydını kontrol veya "
"onay yükümlülüğünü tek başına kanıtlamaz."
)
if has_institute:
sections.append(
"4. **Enstitü yönetimi:** Madde 19, enstitü müdürü ile enstitü kurulu ve yönetim "
"kurulunun enstitü bakımından yönetsel görev taşıdığını gösterir. Buna karşılık dönemlik "
"ders kaydını hangi personelin doğrulayacağını bu madde açıkça söylemez."
)
sections.extend([
"",
"**Aktör bazlı sorumluluk testi**",
"- **Öğrenci - koşullu sorumluluk:** Üniversite düzenlemesi tez ve uzmanlık alan dersini "
"seçmeyi açıkça öğrenciye yüklüyorsa, öğrencinin kayıt yükümlülüğünü yerine getirmediği "
"söylenebilir. Tezin fiilen tamamlanmış olması, kayıt yükümlülüğünü kendiliğinden ortadan kaldırmaz.",
"- **Danışman - olası rehberlik/süreç sorumluluğu:** Danışmanın ders seçimini onaylama veya "
"izleme görevi yerel düzenlemede ya da bilgi sistemi iş akışında varsa sorumluluğu doğabilir. "
"Mevcut corpus yalnızca genel rehberlik görevini kanıtlıyor; somut ihlal için yeterli değil.",
"- **Enstitü/anabilim dalı/idare - olası kontrol sorumluluğu:** Öğrencinin bir dönem tez "
"çalışmasını yürütmesine izin verildiği, danışmanlık ve ilerleme işlemlerinin kayda alındığı "
"halde ders eksikliğinin yalnız mezuniyet aşamasında fark edilmesi bir süreç kontrol boşluğuna "
"işaret eder. Bunun hukuki veya idari sorumluluğa dönüşmesi, yerel görev ve onay kurallarına bağlıdır.",
"",
"**Sonuç**",
"Mevcut kanun corpusuna göre en güvenilir sonuç, öğrencinin olası kayıt eksikliği yanında "
"danışman ve enstitü süreçlerinde de olası gözetim/kontrol eksikliği bulunduğudur. Ancak "
"sorumluluğun kesin dağılımı yapılamaz. Kesin karar için üniversitenin Lisansüstü Eğitim ve "
"Öğretim Yönetmeliği, enstitü usul ve esasları, senato kararları, danışman onay akışı ve "
"öğrenci bilgi sistemi kayıtları corpusa eklenmelidir.",
])
answer = "\n".join(sections)
sources = format_clause_sources(evidence_spans, question=question, answer=answer)
return f"{answer}\n\n---\n\n**Kaynaklar**\n{sources}"
def _render_student_status_remedy_assessment(
question: str,
facts: list[str],
evidence_spans: list[dict[str, Any]],
) -> str:
q = normalize_for_search(question)
asks_excuse_period = "mazeret" in q and any(term in q for term in ("kac", "donem", "sure"))
if asks_excuse_period:
sections = [
"İncelenen 2547, 2914 ve 2809 sayılı kanunlarda öğrencilere tanınmış "
"genel ve sabit bir ‘kaç dönem mazeret hakkı’ bulunmuyor.",
"",
"2547 sayılı Kanunun 46/g maddesi yalnızca katkı payı veya öğrenim ücretini süresinde "
"ödemeyen öğrencinin mazeretinin ilgili yükseköğretim kurumu yönetim kurulunca kabul "
"edilebileceğini düzenler; mazeretin kaç dönem süreceğini belirlemez. Akademik kayıt, "
"izin ve mazeret dönemlerinin sayısı üniversitenin eğitim-öğretim veya lisansüstü "
"yönetmeliğinden kontrol edilmelidir.",
]
else:
sections = [
"**Normatif vaka değerlendirmesi**",
"",
"Kayıt silme işleminin hukuki sebebi belirtilmeden tek bir itiraz maddesi seçmek doğru "
"olmaz. Ancak mevcut corpus öğrencinin dayanabileceği hükümleri üç ihtimale ayırıyor:",
"",
"1. **Kayıt yenilememe veya ücret ödememe gerekçesi:** Madde 44/c'ye göre azami süre "
"içinde yalnızca katkı payının/öğrenim ücretinin ödenmemesi veya kayıt yenilenmemesi "
"öğrencinin ilişiğinin kesilmesine yetmez. Dört yıl üst üste bu durumun yaşanması halinde "
"ise yetkili üniversite kurulu kararı ve YÖK onayıyla ilişik kesilebilir.",
"2. **Disiplin cezası gerekçesi:** İşlem bir öğrenci disiplin cezasıysa Madde 54 uyarınca "
"on beş gün içinde üniversite yönetim kuruluna itiraz edilebilir; ayrıca bu itiraz "
"kullanılmadan idari yargıya da başvurulabilir. Öğrenciye yapılacak tebligatta kural olarak "
"7201 sayılı Tebligat Kanunu uygulanır.",
"3. **Enstitü veya fakülte kurulu kararı:** Madde 14/6 ve Madde 15/4, kurulun türüne göre "
"senato veya üniversite yönetim kurulunun itirazı inceleme yetkisini düzenler.",
"",
"**Uyarılmama iddiası**",
"2547 sayılı Kanun akademik nedenle kayıt silmeden önce her durumda ayrıca bir uyarı "
"yapılmasını açık ve genel bir şart olarak koymuyor. Bu nedenle ‘uyarılmadım’ iddiası "
"tek başına işlemi geçersiz kılmaz; kararın yazılı gerekçesi, dayandığı madde, tebliğ "
"tarihi ve üniversitenin öğrenci/lisansüstü yönetmeliği birlikte incelenmelidir.",
"",
"**Pratik sonuç:** Öğrenci önce işlemin disiplin cezası mı, azami süre/kayıt yenilememe "
"işlemi mi, yoksa enstitü yönetim kurulu kararı mı olduğunu yazılı olarak istemelidir. "
"Doğru itiraz mercii ve süre bu ayrımdan sonra belirlenebilir.",
]
answer = "\n".join(sections)
source_evidence = evidence_spans
if asks_excuse_period:
source_evidence = [item for item in evidence_spans if item.get("article_id") == "Madde 46"]
sources = format_clause_sources(source_evidence, question=question, answer=answer)
return f"{answer}\n\n---\n\n**Kaynaklar**\n{sources}"
def _case_actors(normalized_question: str) -> list[str]:
actor_terms = (
("ogrenci", "öğrenci"),
("danisman", "danışman"),
("enstitu", "enstitü"),
("anabilim dali", "anabilim dalı"),
("ogrenci isleri", "öğrenci işleri"),
("senato", "senato"),
("universite", "üniversite"),
)
return [label for token, label in actor_terms if token in normalized_question]
def _inferred_case_actors(normalized_question: str, explicit_actors: list[str]) -> list[str]:
inferred = []
if "tez" in normalized_question and "öğrenci" in explicit_actors and "danışman" not in explicit_actors:
inferred.append("danışman")
return inferred
def _case_facts(question: str) -> list[str]:
cleaned = re.sub(r"\s+", " ", question).strip()
parts = re.split(r"(?<=[.!?])\s+|\s+(?:ancak|fakat|ama)\s+", cleaned, flags=re.IGNORECASE)
facts = []
for part in parts:
fact = part.strip(" .?!\"")
if len(fact) < 20 or "kimler soruml" in normalize_for_search(fact):
continue
facts.append(fact)
return facts
def _responsibility_tests(actors: list[str]) -> list[dict[str, str]]:
tests = {
"öğrenci": ("conditional", "Yerel düzenlemedeki ders kayıt yükümlülüğü doğrulanmalı."),
"danışman": ("insufficient", "Ders seçimini onaylama veya izleme görevi doğrulanmalı."),
"enstitü": ("insufficient", "Kayıt ve mezuniyet kontrol iş akışındaki görev doğrulanmalı."),
}
return [
{"actor": actor, "status": tests[actor][0], "test": tests[actor][1]}
for actor in actors
if actor in tests
]
def _missing_evidence() -> list[str]:
return [
"Üniversite Lisansüstü Eğitim ve Öğretim Yönetmeliği",
"Enstitü usul ve esasları",
"Senato kararları",
"Danışman ders onay iş akışı",
"Öğrenci bilgi sistemi işlem kayıtları",
]
def _unique(values) -> list[str]:
result = []
for value in values:
if value and value not in result:
result.append(value)
return result
|