| """ |
| Exam backend functions for AI Examiner Agent. |
| """ |
|
|
| import json |
| import random |
| import os |
| from datetime import datetime |
| from typing import TypedDict, Literal |
|
|
| TOPICS = [ |
| "Tokenization and text preprocessing", |
| "Word embeddings (Word2Vec, GloVe, FastText)", |
| "Recurrent neural networks (RNN, LSTM, GRU)", |
| "Attention mechanism and Transformers", |
| "BERT and pre-trained language models", |
| "Named Entity Recognition (NER)", |
| "Sentiment analysis", |
| "Machine translation", |
| "Text classification", |
| "Language model evaluation metrics (BLEU, ROUGE, Perplexity)", |
| ] |
|
|
| STUDENTS = [ |
| {"name": "Stanislav Androshchuk", "email": "Stanislav.Androshchuk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleksandr Babilia", "email": "Oleksandr.Babilia.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vitalii Bahrynets", "email": "Vitalii.Bahrynets.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Dmytro Betsa", "email": "Dmytro.Betsa.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Kateryna Bilyk", "email": "Kateryna.Bilyk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Iryna Boiko", "email": "Iryna.Boiko.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Ihor Boklach", "email": "Ihor.Boklach.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Bohdan Boretskyi", "email": "Bohdan.Boretskyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Yaroslav Borys", "email": "Yaroslav.Borys.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Denys Brativnyk", "email": "Denys.Brativnyk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleksandr Vlasiuk", "email": "Oleksandr.Vlasiuk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleksandr Voznyi", "email": "Oleksandr.Voznyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Khrystyna Dolynska", "email": "Khrystyna.Dolynska.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Viktor Zharkivskyi", "email": "Viktor.Zharkivskyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Olena Kalenchuk", "email": "Olena.Kalenchuk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Dmytro Kostinskyi", "email": "Dmytro.Kostinskyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Anastasiia Kudybovska", "email": "Anastasiia.Kudybovska.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vladyslav Kuchynskyi", "email": "Vladyslav.Kuchynskyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Olena Litovska", "email": "Olena.Litovska.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleh Lozovyi", "email": "Oleh.Lozovyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Roman Maior", "email": "Roman.Maior.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Yevhen Makarenko", "email": "Yevhen.Makarenko.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Serhii Matsyshyn", "email": "Serhii.Matsyshyn.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Maksym Myna", "email": "Maksym.Myna.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Artem Mikanov", "email": "Artem.Mikanov.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vitalii Mil", "email": "Vitalii.Mil.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vladyslav Miniailo", "email": "Vladyslav.Miniailo.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vladyslav Moiseienko", "email": "Vladyslav.Moiseienko.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Tymofii Nasobko", "email": "Tymofii.Nasobko.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Arsenii Ohar", "email": "Arsenii.Ohar.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Marta Oliinyk", "email": "Marta.Oliinyk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleksii Oliinyk", "email": "Oleksii.Oliinyk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Roman Omelchuk", "email": "Roman.Omelchuk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Maksym Orlianskyi", "email": "Maksym.Orlianskyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Alina Pavliv", "email": "Alina.Pavliv.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Andrii Pytel", "email": "Andrii.Pytel.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleksii Postovyi", "email": "Oleksii.Postovyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Myroslav Pronyshyn", "email": "Myroslav.Pronyshyn.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Yurii Pukhta", "email": "Yurii.Pukhta.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Denys Ratushniak", "email": "Denys.Ratushniak.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Nazar Savitskyi", "email": "Nazar.Savitskyi.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Oleksandr Siryk", "email": "Oleksandr.Siryk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Petro Slobodian", "email": "Petro.Slobodian.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Artem Somar", "email": "Artem.Somar.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vladyslav Spivakov", "email": "Vladyslav.Spivakov.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Pavlo Stetsiuk", "email": "Pavlo.Stetsiuk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vladyslav Taraban", "email": "Vladyslav.Taraban.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Andrii Tarasov", "email": "Andrii.Tarasov.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Illia Feloniuk", "email": "Illia.Feloniuk.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Sviatoslav Shainoha", "email": "Sviatoslav.Shainoha.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Sviatoslav Shylkov", "email": "Sviatoslav.Shylkov.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vitalii Yuzvyn", "email": "Vitalii.Yuzvyn.mKNSSh.2025@lpnu.ua"}, |
| {"name": "Vladyslav Yakymchuk", "email": "Vladyslav.Yakymchuk.mKNSSh.2025@lpnu.ua"}, |
| |
| {"name": "Test", "email": "test@test.com"}, |
| ] |
|
|
| DB_FILE = "exam_db.json" |
|
|
|
|
| def _load_db() -> dict: |
| if os.path.exists(DB_FILE): |
| with open(DB_FILE, "r", encoding="utf-8") as f: |
| return json.load(f) |
| return {"exams": []} |
|
|
|
|
| def _save_db(db: dict) -> None: |
| with open(DB_FILE, "w", encoding="utf-8") as f: |
| json.dump(db, f, ensure_ascii=False, indent=2) |
|
|
|
|
| def start_exam(email: str, name: str) -> list[str]: |
| student = next( |
| (s for s in STUDENTS if s["email"].lower() == email.strip().lower()), |
| None, |
| ) |
| if student is None: |
| raise ValueError( |
| f"Student with email '{email}' not found. Please check your email address." |
| ) |
|
|
| topics = random.sample(TOPICS, k=random.randint(2, 3)) |
|
|
| db = _load_db() |
| db["exams"].append({ |
| "email": email, |
| "name": name, |
| "started_at": datetime.now().isoformat(), |
| "topics": topics, |
| "score": None, |
| "finished_at": None, |
| }) |
| _save_db(db) |
| print(f"[DB] Exam started for {name} <{email}>. Topics: {topics}") |
| return topics |
|
|
|
|
| _topic_queue: list[str] = [] |
|
|
|
|
| def set_topic_queue(topics: list[str]) -> None: |
| global _topic_queue |
| _topic_queue = list(topics) |
|
|
|
|
| def get_next_topic() -> str: |
| if _topic_queue: |
| return _topic_queue.pop(0) |
| return "" |
|
|
|
|
| class Message(TypedDict): |
| role: Literal["system", "user", "tool_call"] |
| content: str |
| datetime: str |
|
|
|
|
| def end_exam(email: str, score: float, history: list[Message]) -> None: |
| db = _load_db() |
| for exam in reversed(db["exams"]): |
| if exam["email"].lower() == email.lower() and exam["score"] is None: |
| exam["score"] = score |
| exam["finished_at"] = datetime.now().isoformat() |
| exam["history"] = history |
| break |
| _save_db(db) |
| print(f"[DB] Exam finished for {email}. Score: {score}/10") |