"""Academic joins: roles, record dates and outcomes must belong to one work.""" import pytest from tools.knowledge.sources import profile_units,advisor_name from tools.knowledge.academic import answer_academic,completion_event,academic_intent from tools.knowledge.query import answer_from_index,intent def index(*profiles): rows=[] for number,(name,text) in enumerate(profiles): rows.extend(profile_units({'title':name,'path':f'pages/students/2022/student_{number}/README.md', 'content':text},include_records=True)) return {'version':'fixture','index_version':{'version':'fixture'},'academic_schema':1,'units':rows} @pytest.fixture(params=['rag','graphrag']) def engine(request):return request.param def test_supervisor_and_consultant_are_distinct_and_missing_role_is_not_invented(engine): data=index(('Alena Nová','# Bakalárska práca 2026\nkonzultant Matúš Čavojský\n' 'Predbežný názov:\nOrchestrácia modelov pomocou MCP\n')) r=answer_from_index('Kto je vedúci a konzultant bakalárskej práce Aleny Novej?',data,engine=engine) assert r['status']=='ok' assert 'Konzultant: Matúš Čavojský' in r['answer'] assert 'Vedúci práce: v dostupných podkladoch nie je uvedený' in r['answer'] assert 'Orchestrácia modelov' in r['answer'] and r['source_urls'] def test_profile_level_supervisor_is_not_assigned_to_every_work(engine): data=index(('Alena Nová','Vedúci: Daniel Hládek\n# Diplomová práca 2024\nNázov: Overené grafy\n')) r=answer_academic('Kto je vedúci diplomovej práce Aleny Novej z roku 2024?',data,engine=engine) assert 'Vedúci práce: Daniel' not in r['answer'] assert 'nie je uvedený' in r['answer'] def test_latest_meeting_is_selected_by_date_even_when_profile_order_is_reversed(engine): data=index(('Alena Nová','# Diplomová práca 2026\nStretnutie 5.5.2026\nStav:\n' '- Implementovaný systém RAG.\nÚlohy:\n- Vyhodnoťte model.\n' 'Stretnutie 1.2.2026\nStav:\n- Vytvorený prvý návrh systému.\n')) r=answer_academic('Kedy mala Alena Nová posledné stretnutie a o čom bolo?',data,engine=engine) assert r['status']=='ok' and '2026-05-05' in r['answer'] assert 'Implementovaný' in r['answer'] and 'Plán:' in r['answer'] assert 'prvý návrh' not in r['answer'] assert all(e.get('date')=='2026-05-05' for e in r['evidence']) def test_empty_latest_meeting_does_not_borrow_older_meeting_contents(engine): data=index(('Alena Nová','# Diplomová práca 2026\nStretnutie 1.2.2026\n' 'Stav:\n- Implementovaný systém RAG.\nStretnutie 5.5.2026\n')) r=answer_academic('Kedy mala Alena Nová posledné stretnutie?',data,engine=engine) assert '2026-05-05' in r['answer'] and 'Obsah tohto stretnutia nie je' in r['answer'] assert 'Implementovaný' not in r['answer'] def test_partial_date_does_not_inherit_year_or_establish_absolute_latest_meeting(engine): data=index(('Alena Nová','# Diplomová práca 2027\nStretnutie 5.5.2026\nStav:\n' '- Implementovaný systém RAG.\nStretnutie 7.11.\nStav:\n- Urobená nová verzia systému.\n')) r=answer_academic('Kedy mala Alena Nová posledné stretnutie?',data,engine=engine) assert '2026-05-05' in r['answer'] and 'či sú novšie' in r['answer'] assert '2027-11-07' not in r['answer'] and '2026-11-07' not in r['answer'] def test_premature_work_note_does_not_prove_failed_defense_or_ended_studies(engine): data=index(('Samuel Vasiľ','# Bakalárska práca 2026\nUkončil predčasne\n')) r=answer_academic('Ukončil Samuel Vasiľ štúdium úspešne alebo predčasne?',data,engine=engine) assert 'Ukončil predčasne' in r['answer'] assert r['reason']=='completion_not_documented' assert 'rozsahom' in r['answer'] and 'Nemožno z toho usúdiť, že stále prebieha' in r['answer'] @pytest.mark.parametrize('sentence',[ 'Plán ukončiť štúdium v roku 2024.', 'Mal by úspešne ukončiť diplomovú prácu v roku 2024.', 'Dokončil text práce v roku 2024.', 'Dokončil prácu na experimente v roku 2024.', 'Implementovaný systém RAG.', 'Nie je potvrdené, že úspešne obhájil diplomovú prácu.', 'Kolega úspešne obhájil diplomovú prácu v roku 2024.', 'Úspešne obhájil diplomovú prácu, ale neukončil štúdium.', ]) def test_subtasks_plans_negations_and_other_people_do_not_prove_graduation(sentence): row={'text':sentence,'phase':'progress','work_type':'diplomova','work_year':2024} assert completion_event(row) is None def test_work_heading_year_is_not_a_completion_year(engine): data=index(('Alena Nová','# Diplomová práca 2024\nVedúci: Daniel Hládek\n' 'Stretnutie 1.6.2024\nStav:\n- Úspešne obhájila diplomovú prácu.\n')) r=answer_academic('Ktorí študenti v roku 2024 úspešne ukončili práce pod vedením Daniela Hladeka?',data,engine=engine) assert r['status']=='no_answer' and r['reason']=='academic_conditions_not_documented' def test_completion_supervisor_and_year_join_to_same_person_and_work(engine): data=index( ('Alena Nová','# Diplomová práca 2024\nVedúci: Daniel Hládek\nNázov: Presné grafy\n' 'Stav:\n- Úspešne obhájila diplomovú prácu v roku 2024.\n'), ('Boris Starý','# Diplomová práca 2024\nVedúci: Ján Staš\nStav:\n' '- Úspešne obhájil diplomovú prácu v roku 2024.\n' '# Bakalárska práca 2022\nVedúci: Daniel Hládek\n'), ('Cyril Malý','# Diplomová práca 2024\nVedúci: Daniel Hládek\n' 'Stav:\n- Úspešne obhájil diplomovú prácu v roku 2025.\n')) r=answer_academic('Ktorí študenti v roku 2024 úspešne ukončili práce pod vedením Daniela Hladeka?',data,engine=engine) assert r['status']=='ok' and 'Alena Nová' in r['answer'] and 'Presné grafy' in r['answer'] assert 'Boris Starý' not in r['answer'] and 'Cyril Malý' not in r['answer'] assert {e['person'] for e in r['evidence']}=={'Alena Nová'} assert {e['work_label'] for e in r['evidence']}=={'Diplomová práca 2024'} def test_submission_is_not_summarized_as_success(engine): data=index(('Alena Nová','# Diplomová práca 2024\nVedúci: Daniel Hládek\n' 'Stav:\n- Odovzdala diplomovú prácu v roku 2024.\n')) r=answer_academic('Ktorí študenti v roku 2024 úspešne ukončili práce pod vedením Daniela Hladeka?',data,engine=engine) assert r['status']=='no_answer' def test_actual_conflicting_outcomes_are_reported_instead_of_cherry_picking_success(engine): data=index(('Alena Nová','# Diplomová práca 2024\nVedúci: Daniel Hládek\nStav:\n' '- Úspešne obhájila diplomovú prácu v roku 2024.\n' '- Neobhájila diplomovú prácu v roku 2024.\n')) r=answer_academic('Ktorí študenti v roku 2024 úspešne ukončili práce pod vedením Daniela Hladeka?',data,engine=engine) assert r['status']=='no_answer' and r['reason']=='conflicting_completion_evidence' assert 'rozporné záznamy' in r['answer'] def test_exact_completion_date_needs_an_explicit_event_date_not_only_event_year(engine): data=index(('Alena Nová','# Diplomová práca 2024\nStav:\n' '- Úspešne obhájila diplomovú prácu v roku 2024.\n')) r=answer_academic('Obhájila Alena Nová diplomovú prácu 5.6.2024?',data,engine=engine) assert r['status']=='no_answer' data=index(('Alena Nová','# Diplomová práca 2024\nStav:\n' '- Úspešne obhájila diplomovú prácu dňa 5.6.2024.\n')) r=answer_academic('Obhájila Alena Nová diplomovú prácu 5.6.2024?',data,engine=engine) assert r['status']=='ok' and '5.6.2024' in r['answer'] def test_limit_is_reported_without_claiming_an_exhaustive_list(engine): data=index(*[(name,'# Diplomová práca 2024\nVedúci: Daniel Hládek\n' 'Stav:\n- Úspešne obhájil diplomovú prácu v roku 2024.\n') for name in ['Alena Nová','Boris Starý']]) r=answer_academic('Ktorí študenti v roku 2024 úspešne ukončili práce pod vedením Daniela Hladeka?',data,engine=engine,limit=1) assert r['truncated'] and 'obmedzený výber' in r['answer'] assert len({e['person'] for e in r['evidence']})==1 def test_old_checkpoint_does_not_silently_answer_new_intents(engine): data=index(('Alena Nová','# Diplomová práca 2024\nVedúci: Daniel Hládek\n')) data.pop('academic_schema') r=answer_academic('Kto je vedúci práce Aleny Novej?',data,engine=engine) assert r['status']=='error' and r['reason']=='content_index_unavailable' @pytest.mark.parametrize('question,mode',[ ('Kto je vedúci práce Jána Holpa?','advisors'), ('Kto vedie diplomovú prácu Jána Holpa?','advisors'), ('Kedy mal Ján Holp naposledy stretnutie?','meeting'), ('Ukončil Samuel Vasiľ štúdium úspešne?','completion'), ]) def test_academic_intents_use_the_shared_backend(question,mode): assert intent(question)==academic_intent(question)==mode def test_completed_subtasks_are_not_routed_to_degree_completion(): assert academic_intent('Aké experimenty dokončil Ján Holp?') is None assert academic_intent('Ktoré úlohy dokončil Ján Holp?') is None