diff --git a/grading.py b/grading.py new file mode 100644 index 0000000..59146c3 --- /dev/null +++ b/grading.py @@ -0,0 +1,104 @@ +import requests +import re +from io import StringIO + + +def extract_latex_content(text): + """Парсер LaTeX-контента с обработкой таблиц и спецформатирования""" + # Удаление комментариев + text = re.sub(r'%.*', '', text) + + # Извлечение содержимого между \begin{titlepage} и \end{document} + doc_match = re.search(r'\\begin{titlepage}(.*?)\\end{document}', text, re.DOTALL) + if not doc_match: + return "" + content = doc_match.group(1) + + # Обработка таблиц - извлечение текста из ячеек + content = re.sub(r'\\begin{tabular}.*?\\end{tabular}', + lambda m: ' '.join(re.findall(r'\{([^{}]*)\}', m.group())), + content, flags=re.DOTALL) + + # Нормализация пробелов + content = ' '.join(content.split()) + return content + +def extract_titlepage_content(text): + """Извлекает содержимое титульного листа""" + # Удаление комментариев + text = re.sub(r'%.*', '', text) + + # Извлечение содержимого между \begin{titlepage} и \end{titlepage} + titlepage_match = re.search(r'\\begin{titlepage}(.*?)\\end{titlepage}', text, re.DOTALL) + if not titlepage_match: + return "" + content = titlepage_match.group(1) + + # Обработка содержимого + content = re.sub(r'\\[a-zA-Z]+\*?\s*\{([^{}]*)\}', r'\1', content) + content = re.sub(r'\\[a-zA-Z]+\*?\s*', ' ', content) + content = re.sub(r'[{}]', '', content) + content = re.sub(r'\\[^\s]*', '', content) + content = ' '.join(content.split()) + + return content + +def convert_fullname_to_initials(fullname): + """Преобразует 'Фамилия Имя Отчество' в 'И.О.Фамилия'""" + parts = fullname.split() + if len(parts) == 3: + surname, name, patronymic = parts + return f"{name[0]}.{patronymic[0]}.{surname}" + return fullname + +def grade_report_latex(repo, filename, subject, lab_name, group, student_name, required_sections, branch=None, commit=None, github_token=None): + """Улучшенная функция проверки LaTeX-отчета""" + # Скачивание файла + ref = commit if commit else branch if branch else 'main' + api_url = f"https://api.github.com/repos/{repo}/contents/{filename}?ref={ref}" + + try: + # Заголовки для GitHub API + headers = { + 'Accept': 'application/vnd.github.v3+json', + } + if github_token: + headers['Authorization'] = f'token {github_token}' + + response = requests.get(api_url, headers=headers) + response.raise_for_status() + + # Декодируем содержимое файла из base64 + import base64 + content = base64.b64decode(response.json()['content']).decode('utf-8') + + except Exception as e: + raise Exception(f"Ошибка при загрузке файла через GitHub API: {str(e)}") + + # Извлечение содержимого титульного листа + titlepage_text = extract_titlepage_content(content) + + # Извлечение основного содержимого + full_text = extract_latex_content(content) + + # Преобразование 'Фамилия Имя Отчество' в 'И.О.Фамилия' + student_name = convert_fullname_to_initials(student_name) + + # Проверка титульной информации (только в titlepage) + title_checks = { + "subject": subject.lower() in titlepage_text.lower(), + "lab_name": lab_name.lower() in titlepage_text.lower(), + "group": str(group).lower() in titlepage_text.lower(), + "student_name": student_name.lower() in titlepage_text.lower().replace(" ", "") + } + + # Проверка разделов (ищем как \section{Name}, так и \section*{Name}) + sections = re.findall(r'\\section\*?\{([^{}]*)\}', full_text) + section_checks = {sec: any(sec.lower() in s.lower() for s in sections) + for sec in required_sections} + + return { + "title_page": title_checks, + "sections": section_checks + } + diff --git a/main.py b/main.py index 5d2c207..9ad9184 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ from fastapi import UploadFile, File from dotenv import load_dotenv from itsdangerous import TimestampSigner, BadSignature +from grading import grade_report_latex import re load_dotenv() @@ -456,6 +457,45 @@ def grade_lab(course_id: str, group_id: str, lab_id: str, request: GradeRequest) lab_number = parse_lab_id(lab_id) row_idx = github_values.index(username) + 3 lab_col = student_col + lab_number + lab_offset + + report_checks = {} + if lab_config.get("report"): + report_file = "report.tex" # или можно брать из конфига + report_url = f"https://api.github.com/repos/{org}/{repo_name}/contents/{report_file}" + report_resp = requests.get(report_url, headers=headers) + + if report_resp.status_code == 200: + try: + # Получаем информацию о студенте из таблицы + student_name = sheet.cell(row_idx, student_col).value + report_checks = grade_report_latex( + repo=f"{org}/{repo_name}", + filename=report_file, + subject=course_info.get("name", ""), + # Учитывать, что все названия предметов на английском в конфигах + lab_name=lab_config.get("short-name", ""), + # Полного названия лабы в конфиге нет, поэтому пока берем short-name + group=group_id, + student_name=student_name, + required_sections=lab_config.get("report", []), + branch="main", + github_token=GITHUB_TOKEN + ) + + # Проверяем результаты + title_ok = all(report_checks["title_page"].values()) + sections_ok = all(report_checks["sections"].values()) + + if not title_ok or not sections_ok: + final_result = "✗" + summary.append("❌ Отчет не соответствует требованиям") + else: + summary.append("✅ Отчет соответствует требованиям") + + except Exception as e: + final_result = "✗" + summary.append(f"❌ Ошибка при проверке отчета: {str(e)}") + sheet.update_cell(row_idx, lab_col, final_result) return {