Files
schedule/tests/test_parsing.py
Vladislav Kopnin 2cbb19312e Аудитории: обозначения в нижнем регистре, аббревиатуры — заглавными
Тип, оснащение и пометки приводятся к одному виду: слова пишутся со строчной,
а аббревиатуры («ПК», «МО», «СТ») остаются полностью заглавными; в подписях
фильтров то же самое. В карточках теперь «комп.класс · 12 мест · ПК, маркер»,
«МО и СТ; без проектора». Тесты: 162.
2026-10-10 13:26:15 +03:00

302 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тесты разбора листов: группы, дни/пары/чётность, индексы, вспомогательные листы."""
from datetime import date
import pytest
from fixtures import fixture_sheets, make_sheets
from schedule import parsing
from schedule.errors import ScheduleError
@pytest.fixture()
def sheets():
return make_sheets(fixture_sheets())
@pytest.fixture()
def data(sheets):
return parsing.parse_schedule(sheets)
def test_hidden_group_excluded(data):
assert set(data) == {"ИВТ-11БО", "ИТ-12БО"}
def test_group_names_with_top_and_sheet_order():
"""ТОП-группы распознаются, а порядок групп — как столбцы в таблице."""
sheets = fixture_sheets()
header = sheets[parsing.MAIN_SHEET].rows[0]
header[6] = "ИТ-ТОП-11БО\nсеместр до 11.01.27"
header[7] = "АА-11БО\nсеместр до 29.12.26"
data = parsing.parse_schedule(make_sheets(sheets))
assert list(data) == ["ИТ-ТОП-11БО", "АА-11БО"] # не по алфавиту, а по столбцам
assert data["ИТ-ТОП-11БО"]["days"]["понедельник"][1]["ч"]
def test_not_a_group_headers_ignored():
"""Похожие, но не групповые заголовки столбцами групп не становятся."""
sheets = fixture_sheets()
header = sheets[parsing.MAIN_SHEET].rows[0]
header[9] = "ИВТ 11БО" # пробел вместо дефиса — это не имя группы
data = parsing.parse_schedule(make_sheets(sheets))
assert set(data) == {"ИВТ-11БО", "ИТ-12БО"}
def test_semester(data):
assert data["ИВТ-11БО"]["semester_until"] == "11.01.27"
assert data["ИТ-12БО"]["semester_until"] == "29.12.26"
def test_day_period_parity(data):
mon = data["ИВТ-11БО"]["days"]["понедельник"]
p1 = mon[1]
assert p1["time"] == "9:00-10:35"
assert p1["ч"] is not None and p1["з"] is not None
assert "Математика" in p1["ч"]
assert "216" in p1["з"]
def test_empty_parity_slot_kept(data):
mon = data["ИТ-12БО"]["days"]["понедельник"]
assert mon[1]["з"] is None
# время на пустой слот подставлено
assert mon[1]["time"] == "9:00-10:35"
def test_lessons_preparsed(data):
mon = data["ИВТ-11БО"]["days"]["понедельник"]
lesson = mon[1]["lessons"]["ч"][0]
assert lesson["subject"] == "Математика"
assert lesson["teacher"] == "Смирнов А.В."
assert lesson["room"] == "ауд. 215"
assert lesson["note"] == "пара 14.09 не будет"
# пустое parity -> None
assert data["ИТ-12БО"]["days"]["понедельник"][1]["lessons"]["з"] is None
def test_marker_parsed(data):
mon_g1 = data["ИВТ-11БО"]["days"]["понедельник"]
mon_g2 = data["ИТ-12БО"]["days"]["понедельник"]
assert mon_g1[2]["lessons"]["ч"][0]["marker"] == "к.в. - 2"
assert mon_g2[2]["lessons"]["з"][0]["marker"] == "к.в."
def test_tuesday_only_ch(data):
tue = data["ИВТ-11БО"]["days"]["вторник"]
assert tue[1]["ч"] and "Биология" in tue[1]["ч"]
assert tue[1]["з"] is None
def test_missing_main_sheet(sheets):
del sheets[parsing.MAIN_SHEET]
with pytest.raises(ScheduleError):
parsing.parse_schedule(sheets)
def test_odd_day_block_warns(caplog):
sheets = make_sheets(fixture_sheets())
# уберём вторую строку понедельника (пара 1, з) -> нечётное число строк
rows = sheets[parsing.MAIN_SHEET].rows
# строки: 0 header, 1 с/по, 2..5 понедельник, 6..7 вторник
del rows[3]
with caplog.at_level("WARNING"):
result = parsing.parse_schedule(sheets)
assert any("сдвинуться" in r.message for r in caplog.records)
# разбор всё равно завершается
assert "ИВТ-11БО" in result
def test_week_ranges(sheets):
weeks = parsing.extract_week_ranges(sheets)
assert len(weeks) == 8
assert weeks[0] == {"parity": "ч", "start": date(2026, 8, 31), "end": date(2026, 9, 6)}
assert weeks[1]["parity"] == "з"
assert weeks[-2]["parity"] == "ч"
def test_current_parity():
sheets = make_sheets(fixture_sheets())
weeks = parsing.extract_week_ranges(sheets)
assert parsing.current_parity(weeks, date(2026, 9, 1)) == "ч"
assert parsing.current_parity(weeks, date(2026, 9, 9)) == "з"
assert parsing.current_parity(weeks, date(2030, 1, 1)) is None
def test_teacher_room_index(data):
teachers, rooms = parsing.build_teacher_room_index(data)
assert set(teachers) == {"Смирнов А.В.", "Иванов И.И.", "Петров П.П.", "Сидорова С.С."}
smirnov = teachers["Смирнов А.В."]["понедельник"]["1"]
assert len(smirnov["ч"]) == 1 and len(smirnov["з"]) == 1
assert smirnov["ч"][0]["subject"] == "Математика"
assert smirnov["ч"][0]["room"] == "ауд. 215"
assert smirnov["ч"][0]["groups"] == ["ИВТ-11БО"]
petrov_zn = teachers["Петров П.П."]["понедельник"]["2"]["з"]
assert petrov_zn[0]["marker"] == "к.в."
assert set(rooms) == {"ауд. 215", "ауд. 216", "ауд. 301", "ауд. 220", "ауд. 201"}
room_301 = rooms["ауд. 301"]["понедельник"]
assert room_301["1"]["ч"][0]["subject"] == "Физика"
assert room_301["1"]["ч"][0]["teacher"] == "Иванов И.И."
assert room_301["2"]["ч"][0]["groups"] == ["ИВТ-11БО"]
def test_room_index_detached_room():
"""Аудитория, оторванная пустой строкой, попадает в индекс с предметом и преподавателем."""
sheets = fixture_sheets()
sheets[parsing.MAIN_SHEET].rows[6][7] = "Химия\nПетров П.П.\n\n ауд. 201"
data = parsing.parse_schedule(make_sheets(sheets))
_, rooms = parsing.build_teacher_room_index(data)
entry = rooms["ауд. 201"]["вторник"]["1"]["ч"][0]
assert entry["subject"] == "Химия"
assert entry["teacher"] == "Петров П.П."
assert entry["groups"] == ["ИТ-12БО"]
def test_day_times(data):
times = parsing.build_day_times(data)
assert times["понедельник"] == {"1": "9:00-10:35", "2": "10:45-12:20"}
assert times["вторник"] == {"1": "9:00-10:35"}
def test_group_schedule_shape(data):
out = parsing.build_group_schedule(data, "ИВТ-11БО")
assert out["group"] == "ИВТ-11БО"
assert out["semester_until"] == "11.01.27"
p1 = out["days"]["понедельник"]["1"]
assert p1["time"] == "9:00-10:35"
assert "raw" in p1["ч"] and "lessons" in p1["ч"]
assert p1["з"]["lessons"][0]["room"] == "ауд. 216"
assert parsing.build_group_schedule(data, "НЕТ-11БО") is None
def test_rooms_info(sheets):
info = parsing.parse_rooms_info(sheets)
assert set(info) == {"ауд. 215", "ауд. 301", "ауд. 220"}
assert info["ауд. 215"]["info"] == "комп.класс, 12 мест"
assert info["ауд. 215"]["type"] == "комп.класс"
assert info["ауд. 215"]["capacity"] == 12
assert info["ауд. 215"]["equipment"] == ["ПК"]
assert info["ауд. 301"]["info"] == "интерактивная панель, 24 места"
assert info["ауд. 301"]["capacity"] == 24
assert info["ауд. 301"]["equipment"] == ["интерактивная панель"]
assert info["ауд. 220"]["info"] == "20 мест"
assert info["ауд. 220"]["type"] == ""
assert info["ауд. 220"]["capacity"] == 20
assert info["ауд. 220"]["equipment"] == []
def test_teachers_full(sheets):
full = parsing.parse_teachers_full(sheets)
assert full == {
"Смирнов А.В.": "Смирнов Александр Викторович",
"Иванов И.И.": "Иванов Иван Иванович",
}
def test_sort_rooms():
assert parsing.sort_rooms(["ауд. 10", "ауд. 9", "ауд. 100", "зал"]) == [
"ауд. 9", "ауд. 10", "ауд. 100", "зал",
]
def test_build_room_schedule(data):
_, rooms = parsing.build_teacher_room_index(data)
times = parsing.build_day_times(data)
out = parsing.build_room_schedule(rooms, "ауд. 301", times)
# дни с расписанием плюс дни, где есть время пар (даже без занятий)
assert set(out["days"]) == {"понедельник", "вторник"}
assert out["days"]["понедельник"]["1"]["ч"][0]["subject"] == "Физика"
assert parsing.build_room_schedule(rooms, "ауд. 999", times) is None
def test_room_attributes_from_descriptions():
"""Атрибуты аудиторий разбираются из описаний вида «тип; оснащение; N мест»."""
from xlsx_builder import SheetSpec
descriptions = {
"201": "комп.класс МО и СТ; без проектора; маркер; 12 мест",
"203": "аудитория для практических; без проектора; меловая доска; 44 места",
"210": "комп.класс; проектор; маркер; 28 мест",
"305": "кабинет английского; телевизор; маркер; 24 места",
"402": "402 четверг пятница",
"405": "пк",
"410": "",
}
spec = SheetSpec(rows=[
[None] * 6 + list(descriptions.keys()),
[None] * 6 + list(descriptions.values()),
])
info = parsing.parse_rooms_info(make_sheets({parsing.ROOMS_SHEET: spec}))
assert info["ауд. 201"]["type"] == "комп.класс"
assert info["ауд. 201"]["capacity"] == 12
# «без проектора» — проектора нет, хотя слово в тексте есть
assert info["ауд. 201"]["equipment"] == ["ПК", "маркер"]
assert info["ауд. 201"]["available_days"] is None
assert info["ауд. 203"]["type"] == "аудитория для практических"
assert info["ауд. 203"]["equipment"] == ["меловая доска"]
assert info["ауд. 203"]["capacity"] == 44
assert info["ауд. 210"]["equipment"] == ["ПК", "проектор", "маркер"]
assert info["ауд. 305"]["type"] == "кабинет английского"
assert info["ауд. 305"]["equipment"] == ["телевизор", "маркер"]
assert info["ауд. 402"]["available_days"] == ["четверг", "пятница"]
assert info["ауд. 402"]["capacity"] is None
assert info["ауд. 402"]["type"] == ""
assert info["ауд. 405"]["equipment"] == ["ПК"]
assert info["ауд. 410"]["info"] == ""
assert info["ауд. 410"]["type"] == ""
def test_room_payload_shape():
payload = parsing.room_payload("ауд. 215", {
"info": "лекционная; проектор; 96 мест",
"type": "лекционная",
"type_group": "лекционная",
"capacity": 96,
"equipment": ["проектор"],
"available_days": None,
})
assert payload == {
"room": "ауд. 215",
"info": "лекционная; проектор; 96 мест",
"type": "лекционная",
"type_group": "лекционная",
"capacity": 96,
"equipment": ["проектор"],
"available_days": None,
}
def test_room_type_group_merges_similar():
"""Близкие формулировки типов сводятся к общему типу для фильтра."""
assert parsing.room_type_group("комп.класс") == "комп.класс"
assert parsing.room_type_group("комп.класс Linux") == "комп.класс"
assert parsing.room_type_group("компьютерный класс") == "комп.класс"
assert parsing.room_type_group("английский") == "кабинет английского"
assert parsing.room_type_group("кабинет английского") == "кабинет английского"
assert parsing.room_type_group("ауд. каф. ДА") == "аудитория кафедры"
assert parsing.room_type_group("лекционная") == "лекционная"
assert parsing.room_type_group("лекционно-практическая") == "лекционно-практическая"
assert parsing.room_type_group("аудитория для практических") == "аудитория для практических"
assert parsing.room_type_group("") == ""
def test_lowercase_except_abbreviations():
"""Обозначения — в нижнем регистре, аббревиатуры остаются заглавными."""
assert parsing.room_type("комп.класс Linux; 10 мест") == "комп.класс linux"
assert parsing.room_type("Кабинет Английского") == "кабинет английского"
assert parsing.room_equipment("ПК, Проектор, интернет МО") == ["ПК", "проектор"]
assert parsing.lower_except_abbr("МО и СТ; без проектора") == "МО и СТ; без проектора"