Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions grading.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from github import Github
from pdfminer.high_level import extract_text
from PyPDF2 import PdfReader
from pdf2image import convert_from_bytes
import pytesseract
import io
import re


def check_pdf_content(name_file, name_repository, name_item, name_lab, num_group, name_student, main_sections, name_branch=None, sha_commit=None, token=None):
# проверяем, что все обязательные параметры указаны
if not all([name_file, name_repository, name_item, name_lab, num_group, name_student]):
print("Все обязательные параметры должны быть указаны")
return {"first_page": False, "missing_sections": main_sections}

# скачивание файла
try:
if token: # если токен указан
g = Github(token) # создаем экземпляр объект с аутентификацией
else:
Github() # иначе анонимный доступ

# получаем доступ к указанному репозиторию
repo = g.get_repo(name_repository)

# если имя ветки не указано
if not name_branch:
# запрашиваем файл из ветки по умолчанию
file_content = repo.get_contents(name_file)
else:
if not sha_commit: # если не указан коммит
# запрашиваем файл из последнего коммита указанной ветки
file_content = repo.get_contents(name_file, ref=name_branch)
else: # иначе запрашиваем из указанного коммита
file_content = repo.get_contents(name_file, ref=sha_commit)
# декодируем содержимое файла в бинарные данные (PDF)
content = file_content.decoded_content
except Exception as e:
print(f"Ошибка при скачивании файла {name_file} из {name_repository}: {e}")
return {"first_page": False, "missing_sections": main_sections}

# проверка количества страниц
try:
# создаем объект из бинарных данных
pdf = PdfReader(io.BytesIO(content))
# получаем количество страниц
num_pages = len(pdf.pages)
if num_pages < 2:
print("PDF содержит только одну страницу, проверка разделов невозможна")
return {"first_page": False, "missing_sections": main_sections}
except Exception as e:
print(f"Ошибка при чтении PDF: {e}")
return {"first_page": False, "missing_sections": main_sections}

# пытаемся извлечь текст с первой и с остальных страниц
# с помощью pdfminer.six
# и переводим текст в нижний регистр
first_page_text = extract_text(io.BytesIO(content), page_numbers=[0]).lower()
all_pages_text = extract_text(io.BytesIO(content), page_numbers=range(1, num_pages)).lower()

# если не удалось извлечь, пробуем OCR
if not first_page_text or not all_pages_text:
print("Текст не извлечен с помощью pdfminer, пробуем OCR")
try:
images = convert_from_bytes(content)
first_page_text = pytesseract.image_to_string(images[0], lang='rus+eng').lower() if images else ""
all_pages_text = " ".join(pytesseract.image_to_string(img, lang='rus+eng').lower() for img in images[1:]) if len(images) > 1 else ""
except Exception as e:
print(f"Ошибка при использовании OCR: {e}")
return {"first_page": False, "missing_sections": main_sections}
# удаляем лишние пробелы, знаки препинания
# для нормализации текста
first_page_text = re.sub(r'[^\w\s]', '', re.sub(r'\s+', ' ', first_page_text.strip()))
all_pages_text = re.sub(r'[^\w\s]', '', re.sub(r'\s+', ' ', all_pages_text.strip()))

# выводим извлеченный текста
print(f"Текст первой страницы: '{first_page_text}'")
print(f"Текст остальных страниц: '{all_pages_text[:1000]}...'") # Ограничение для читаемости

# проверяем, получилось ли извлечь текст
if not first_page_text:
print("Не удалось извлечь текст с первой страницы")
return {"first_page": False, "missing_sections": main_sections}
if not all_pages_text and num_pages > 1:
print("Не удалось извлечь текст с остальных страниц")
return {"first_page": False, "missing_sections": main_sections}

formatted_name = format_name(name_student)

# удаляем знаки препинания и
# переводим в нижний регистр входные параметры
substrings = [re.sub(r'[^\w\s]', '', s.lower()) for s in [name_item, name_lab, num_group, formatted_name] if s]


# вызываем функцию для проверки присутствия
# всех подстрок на одной странице
first_page_valid = check_substring_exist(first_page_text, substrings)

# тоже самое, но с остальными страницами для
# проверки соответствующих основных разделов
main_sections = [re.sub(r'[^\w\s]', '', s.lower()) for s in main_sections if s]
missing_sections = check_sections_in_text(all_pages_text, main_sections)

# выводим результаты
if first_page_valid:
print("\nПервая страница соответствует требованиям")
else:
print("\nНе все данные присутствуют на первой странице")

if missing_sections:
print(f"Отсутствуют следующие разделы: {missing_sections}")
else:
print("Все основные разделы присутствуют.")

return {"first_page": first_page_valid, "missing_sections": missing_sections}

# функция, которая проверяет присутствуют ли
# основные разделы в тексте
def check_sections_in_text(text, sections):
missing_sections = []
for section in sections:
if section not in text:
missing_sections.append(section)
return missing_sections

# проверяет, присутствуют ли
# все подстроки в тексте
def check_substring_exist(text, substrings):
for substring in substrings:
if substring not in text:
return False
return True

def format_name(full_name):
# разделяем полное имя на части
parts = full_name.strip().split()

# проверяем, что имя состоит из трех частей
if len(parts) != 3:
return full_name # Возвращаем исходное имя, если формат неверный

surname, first_name, patronymic = parts

# Формируем имя в формате И. О. Фамилия
return f"{first_name[0]}. {patronymic[0]}. {surname}"
62 changes: 53 additions & 9 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from dotenv import load_dotenv
from itsdangerous import TimestampSigner, BadSignature
import re
from grading import check_pdf_content

load_dotenv()
app = FastAPI()
Expand Down Expand Up @@ -431,9 +432,9 @@ def grade_lab(course_id: str, group_id: str, lab_id: str, request: GradeRequest)

total_checks = len(check_runs)
result_string = f"{passed_count}/{total_checks} тестов пройдено"

final_result = "✓" if passed_count == total_checks else "✗"

# изменено
ci_result = "✓" if passed_count == total_checks else "✗"
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
client = gspread.authorize(creds)
Expand All @@ -456,19 +457,62 @@ 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

pdf_results = {}
if lab_config.get("report"):
report_file = "report.pdf"
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

pdf_results = check_pdf_content(
name_file=report_file,
name_repository=f"{org}/{repo_name}",
name_item=course_info.get("name", ""),
name_lab=lab_config.get("short-name", ""),
num_group=group_id,
name_student=student_name,
main_sections=lab_config.get("report", []),
name_branch="main",
sha_commit=latest_sha,
token=GITHUB_TOKEN
)

# Проверяем результаты
title_valid = pdf_results["first_page"]
sections_valid = not pdf_results["missing_sections"]

if not title_valid or not sections_valid:
final_result = "✗"
summary.append("❌ Отчет не соответствует требованиям")
else:
summary.append("✅ Отчет соответствует требованиям")

except Exception as e:
final_result = "✗"
summary.append(f"❌ Ошибка при проверке PDF: {str(e)}")
else:
final_result = "✗"
summary.append(f"❌ Файл {report_file} не найден в репозитории")
else:
final_result = ci_result

# Обновление Google Таблицы
sheet.update_cell(row_idx, lab_col, final_result)

return {
"status": "updated",
"result": final_result,
"message": f"Результат CI: {'✅ Все проверки пройдены' if final_result == '✓' else '❌ Обнаружены ошибки'}",
"message": f"Результат: {'✅ Все проверки пройдены' if final_result == '✓' else '❌ Обнаружены ошибки'}",
"passed": result_string,
"checks": summary
"checks": summary,
"pdf_results": pdf_results if pdf_results else {}
}




@app.post("/courses/upload")
async def upload_course(file: UploadFile = File(...)):
if not file.filename.endswith(".yaml") and not file.filename.endswith(".yml"):
Expand All @@ -487,4 +531,4 @@ async def upload_course(file: UploadFile = File(...)):
with open(file_location, "wb") as f:
f.write(content)

return {"detail": "Курс успешно загружен"}
return {"detail": "Курс успешно загружен"}
8 changes: 7 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ pyyaml
requests
python-multipart
python-dotenv
itsdangerous
itsdangerous
pdf2image==1.17.0
pytesseract==0.3.13
fuzzywuzzy==0.18.0
python-Levenshtein==0.26.0
pytest==8.3.2
httpx==0.27.0