YOKPilot3 / case_analysis.py
mfirat007's picture
Upload 98 files
eff511c
Raw
History Blame Contribute Delete
18.5 kB
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