- Created new snapshot files for ISF credits and daily schedules for the 26-АРХ and 26-ПГС programs. - Added summer 2026 and winter 2024 exam schedules for the 23-ИТзс program, detailing lectures, labs, and consultations.
47 KiB
Расписание ПГУ
Первый рабочий PWA-клиент для расписаний Полоцкого государственного университета. При первом запуске предлагает найти свою группу; исходный сценарий проверки — ФЭФ, 23-БА из общего документа 23-ЛГ_23-БА.docx за 2026–2027 учебный год.
Запуск
Нужны Node.js 22.12+ (проверено на 24.14.1), npm и curl.
npm ci
npm run build
npm start
Откройте http://127.0.0.1:3001. Этот режим включает service worker, установку PWA и офлайн-кэш. Для разработки с обновлением интерфейса:
npm run dev
Vite работает на http://127.0.0.1:5173 и проксирует /api на порт 3001 (другой порт — через API_PORT). В режиме разработки service worker не регистрируется. PORT и HOST настраивают сервер; при размещении нужен HTTPS. Для доступа с телефона используйте HTTPS-хостинг либо локальный HTTPS-туннель.
Возможности
- Живой каталог публичного Nextcloud университета: факультет, форма, курс и поиск по названию группы/файла. Проверенный каталог содержит 113 документов Word.
- Расписание по дням или на всю неделю, поиск предмета, преподавателя и аудитории.
- Экран «Группы»: поиск группы по номеру в любом написании («23-БА», «23 ба», «23ба») по всем документам каталога, без учёта фильтров; одно нажатие открывает нужный документ и группу. Ниже — текущая группа с подгруппой и соседними группами документа, недавние группы (открываются и без сети) и каталог чипами: факультет → курс → форма обучения. Первый запуск начинается с этого экрана, а не с чьей-то группы (
/api/groups). Если у группы есть основное расписание, в выборе не показываются её расписание зачётов и экзаменационной сессии (они идут в обратный отсчёт сессии) и уже прошедшие документы с датами, например неделя «Введение в высшее образование». Группе, у которой есть только такой документ, он остаётся доступен. - Общие ячейки попадают в каждую соответствующую группу, чужие занятия отсекаются.
- Отдельные карточки для альтернативных занятий и условий по неделям. Сохраняются исходный текст, цвет ячейки, подгруппы, даты и ограничения.
- Учебные недели по таблице университета: переключатель недель с датами, показ только занятий выбранной недели, статус занятия («ещё 4 занятия · до 27 нояб.», «начнётся 20 нояб.») и сетка недель в деталях. Режим «Все строки Word» показывает документ как есть.
- Номера пар и текущая пара («Идёт · ещё 25 мин») по расписанию звонков.
- «Что изменилось»: когда университет перепубликует документ, приложение сравнивает новую версию с сохранённой и показывает для вашей группы изменённые поля (аудитория, время, преподаватель, недели), добавленные и удалённые занятия. Последний список изменений открывается из меню.
- Моя подгруппа и скрытые занятия: в «Группах» можно выбрать подгруппу — занятия другой подгруппы пропадут; любое занятие можно скрыть из его карточки и вернуть кнопкой «Скрыто · показать». Настройки хранятся только в этом браузере.
- Поиск по университету (вкладка «Поиск»): преподаватель или аудитория — занятия на выбранный день по всем расписаниям каталога; свободные аудитории на дату и пару. Сервер разбирает все документы каталога в фоне (первый раз около 20–30 секунд, дальше из кэша) и считает занятость с учётом недель, цвета строк, семестра и разовых переносов аудиторий вида «432 (07.10-ауд.272)». Дистанционные занятия («200 (ДО)») аудиторию не занимают. Каждый ответ сообщает, сколько документов учтено; «свободна» означает «свободна по расписаниям из Word», мероприятия и закрытые аудитории расписание не знает. Работает только онлайн.
- Сессия: экзамены, зачёты и консультации группы из всех документов каталога (распознаются по «(экз.)», «(зач.)», «(конс.)» и словам «Экзамен», «Зачёт», «Консультация»). На главном экране — карточка с обратным отсчётом до ближайшего экзамена или зачёта, полный список — в листе «Сессия». Список сохраняется для просмотра без сети. В текущем каталоге документов экзаменационной сессии ещё нет; формат проверен на образцах, а не на реальных файлах университета.
- Ссылка и QR-код на расписание группы (
/?doc=<id>&group=<группа>) из меню «Поделиться расписанием». Ссылка открывает документ из каталога и нужную группу; подгруппа и скрытые занятия не передаются. Устаревшая ссылка (документ убран из каталога) открывает поиск группы. Для импортированных файлов ссылка недоступна. - Фильтр белых/зелёных строк. Объединённое на обе строки занятие показывается в обоих вариантах без дублирования.
- Импорт
.docxи.docдо 10 МБ с предварительным просмотром перед сохранением. - Сохранение последних 12 документов и выбранной группы в этом браузере. После первой успешной загрузки собранной PWA интерфейс, шрифты и расписание доступны без сервера/сети.
- Манифест PWA, PNG-иконки, установка на главный экран и ссылка на исходный Word.
Источник и обновления
Каталог университета читается через публичный DAV. Сервер обращается только к этой общей папке. Другие ссылки не принимаются.
Учебный год выбирается сам. Сервер читает корень каталога, находит папки вида 2026-2027 и с 1 сентября (Europe/Minsk) берёт папку нового года. Если её ещё нет, используется самая новая из прошлых, а /api/health предупреждает об этом. Таблица белых и зелёных недель и расписание звонков ищутся по смыслу имени («бел» + «зел» + «недел», «звонк»), сначала в папке года, затем в корне. Можно выложить несколько таблиц недель: они объединяются, при совпадении дат побеждает более новый файл. В список расписаний эти справочные файлы не попадают.
Семестры. Таблица делится на семестры там, где нумерация недель начинается заново или между неделями больше трёх недель. Расписание связывается со своим семестром по тексту Word («осенний семестр 2026/2027»), расписание сессии — по своим датам, документ без указания семестра — по семестру, в котором сегодняшняя дата. Если семестр не найден (например, весенней таблицы ещё нет), даты не рассчитываются и показываются все строки Word.
Обновления у студента. При запуске и при возвращении в приложение, если прошло больше часа, клиент сверяет сохранённое расписание с каталогом. Тот же файл с новой датой тихо перечитывается. Если для группы появился более новый документ (следующий семестр или год), показывается предложение его открыть; отказ запоминается. Если документ исчез из каталога, приложение предлагает найти группу заново.
Каталог и папка года перечитываются раз в 15 минут, таблица недель и звонки — раз в 6 часов, распознанный документ — при изменении даты файла или версии парсера. Устаревшая копия отвечает сразу, а перечитывание идёт в фоне. Ждать приходится только при первом запуске, в начале нового учебного года и при «Обновить». «Обновить» (?refresh=1) принудительно перечитывает всё, но обращается к университету не чаще раза в 2 минуты (таблицу недель — раз в 10 минут), иначе отвечает только что прочитанной копией. После сбоя источника фоновые попытки тоже ждут 2 минуты. Если источник недоступен, используются сохранённые копии с явной пометкой. Фоновая синхронизация при закрытом приложении не реализована.
Ответы API сжимаются (gzip или brotli; семестр группы — около 200 КБ JSON, в gzip — около 5 КБ) и отдаются с Cache-Control: no-cache и ETag. Клиент хранит ответ, но каждый раз спрашивает заново. Если ничего не изменилось, приходит пустой 304. Android-клиент делает это сам через дисковый кэш OkHttp. Импорт Word ограничен: два разбора одновременно, ещё четыре ждут, остальным — 429; с одного адреса — до 10 файлов за 10 минут. За прокси (Caddy, туннель) адрес клиента берётся из X-Forwarded-For, если прокси находится в локальной или частной сети. Для /api/timetable ETag считается до любой работы — из версии документа, парсера и сервера, таблицы недель, параметров запроса и даты, — поэтому неизменённое расписание отвечает 304 примерно за миллисекунду, не открывая документ.
Ошибки приходят как { "error": "текст для показа", "code": "…" }. Клиенты опираются на code, а не на текст: bad_request (400), unknown_endpoint, document_not_found, group_not_found (404), file_too_large (413), unreadable_file (422), too_many_requests и busy (429), source_unavailable и source_invalid (502, каталог университета не ответил или не распознан), source_timeout (504), internal (500). Полный список — в server/errors.js. Android и iOS при source_unavailable, source_timeout и busy оставляют на экране сохранённую копию и показывают «Повторить».
GET /api/health без обращения к сети показывает выбранный учебный год, возраст каталога, найденные файлы недель и звонков, семестры и предупреждения: папка нового года не найдена, сегодняшняя дата вне таблицы недель, источник не обновляется.
npm run sync
Серверный кэш находится в .data/ (исключён из Git). Загрузки пользователей обрабатываются в памяти; временные файлы конвертации .doc удаляются. Данные из университетского каталога могут сохраняться в серверном кэше. Для офлайн-просмотра расписания хранятся в localStorage; очистка данных браузера удалит сохранённые документы. Аналитики и учётных записей нет.
Границы парсера
Основной парсер читает XML и сетку таблиц .docx, включая горизонтальные и вертикальные объединения ячеек. Поддерживаются проверенные дневные шаблоны ФЭФ, ФИТ, ФКНЭ, ИСФ и ГФ, заочные установочные сессии с отдельными колонками предмета, аудитории и преподавателя, а также заочные лабораторно-экзаменационные сессии.
Лабораторно-экзаменационные сессии заочников (проверено на зимней сессии 23-ИТзс 2024 года и летней 2026 года, test/fixtures/itzs-*.docx) устроены иначе:
- Группа. Указана только в заголовке («группа 23-ИТзс»).
- Недели. В таблице две–три недели стоят рядом блоками столбцов «Д.н. — Время — Дисциплина», у каждого дня своя дата. Сессия поэтому не зависит от таблицы недель и показывается по своим датам даже в прошлом учебном году. API отдаёт её границы в поле
semesterс названием «Лабораторно-экзаменационная сессия». - Подгруппы. Если под одним заголовком дисциплины столбец разделён, левая часть — подгруппа 1, правая — подгруппа 2, даже при неровном делении.
- Типы и предмет. Распознаются установочные лекции и занятия (
уст.л.,уст.пз.), экзамены, зачёты, консультации и защита курсовой работы. Предмет берётся из кавычек («…по учебной дисциплине «Базы данных»»). - Время. Если у экзамена указано только начало («8.30»), конец берётся по расписанию звонков.
Расписания зачётов (папка «Расписание зачетов», проверено на 23-, 24- и 26-АРХ ИСФ, test/fixtures/isf-credits-26-arh.docx) — таблица «Дата — Время — группа»:
- Дата и время. В ячейке дня дата стоит перед днём недели («27.10.2026 вторник»), пустая ячейка продолжает день выше. Указано одно время — начало, конец берётся по расписанию звонков. Такое одиночное время теперь принимается и в других таблицах: прежде в документах «Введение в высшее образование» и в установочных сессиях пропадали строки вроде «Торжественное собрание 10.30».
- Тип записи. Строка без пометки считается зачётом, «Консультация по учебной дисциплине…» — консультацией. Все они попадают в обратный отсчёт сессии.
Это не универсальный парсер любых документов Word: без заголовков групп, дней и времени импорт возвращает понятную ошибку.
Даты занятий рассчитываются по университетской таблице «Расписание белых и зеленых недель» из корня каталога: неделя 1 начинается 01.09.2026 (вторник), нечётные недели белые, чётные зелёные. Пометка в белой или зелёной строке относится только к неделям этого цвета: 1-13н в белой строке — недели 1, 3, …, 13, а в объединённой строке — каждая неделя. с 3н. означает «до конца таблицы», а части пометки через ; или , (1-4н.;6-12н., 2-5 н., 10-17 н.) складываются. Текстовые даты вроде «15 октября» и списки дат числами («10.09;15.10;19.11;17.12», не меньше двух дат, чтобы время пары не путалось с датой) привязываются к своему дню. Нераспознанные пометки (2,6-12-16н) не скрываются, а помечаются для сверки с Word; исходная пометка всегда видна. Разбор пометок общий для сервера и браузера (shared/weeks.js), поэтому старые сохранённые расписания тоже получают даты. Вне периода таблицы приложение показывает все строки Word. Номера подгрупп соответствуют столбцам внутри группы; именованных подгрупп в этих шаблонах нет.
Поля предмета, преподавателя и аудитории извлекаются по типовым обозначениям; сложные многострочные записи могут содержать несколько преподавателей/аудиторий. Поэтому полный текст всегда доступен в деталях. Если в ячейке явно указано особое время, например 16.50 для кураторского часа, 9.00, 18.35., интервал 11:10-11:30 или несколько времён (17.00 и 18.35, 18.35+20.10 — тогда это отдельные занятия), оно имеет приоритет; окончание не выдумывается. Дата в начале ячейки (13.10, 3.12, 24.11., 12.09.26г.) делает запись разовой. Запись вида 20.10 может быть и датой, и началом восьмой пары: если такая дата приходится на другой день недели, чем строка, или после неё идёт пометка недель, это время.
Для старых .doc на сервере нужен LibreOffice Writer (soffice в PATH или LIBREOFFICE_BIN). Конвертация может изменить сложное форматирование, результат нужно сверить. Реальные документы выбранного источника — .docx; поддержка .doc проверяется отдельно на образце, преобразованном LibreOffice.
PDF, Excel, изображения и сканы пока не импортируются. Они не выдаются за распознанное расписание.
Подозрительные ячейки. После разбора каждый документ каталога проверяется (server/diagnostics.js), и GET /api/diagnostics показывает ячейки, которые прочитаны неуверенно, с исходным текстом. Фильтры: ?group=23-АРХ, ?doc=<id>, ?code=weeks_partly. Число таких ячеек есть и в /api/health (directory.problems). Коды:
weeks_partly— пометка недель прочитана не целиком (1-4н.;6-12н.);weeks_unreadable— пометка недель не прочитана (2,6-12-16н);weeks_like_date— «недели» похожи на дату с опечаткой (19,11, 03.12);weeks_out_of_range— номер недели больше, чем недель в семестре;date_in_subject,time_in_subject— дата или время в начале ячейки попали в название (24.11.,9.00,11:10-11:30);dates_unread— в ячейке есть даты, но ни одна не распознана;date_weekday— дата приходится на другой день недели, чем строка;date_out_of_year— дата вне учебного года;subject_missing— название предмета не выделено;time_invalid— время пары неправдоподобно;group_empty— у группы из заголовка нет ни одного занятия.
Такие ячейки — повод сверить документ с Word и, если это новый формат, научить парсер.
Проверки
npm test
npm run build
Тесты используют реальные Word-документы источника в test/fixtures/ с URL и метаданными рядом. Результат разбора каждого из них вместе с найденными диагностикой ячейками сравнивается построчно со снимком в test/snapshots/. Если разбор изменился намеренно, снимки обновляются командой UPDATE_SNAPSHOTS=1 npm test; разницу в test/snapshots/ стоит прочитать перед коммитом. Проверяются изоляция 23-БА/23-ЛГ, общие события, объединения ячеек, варианты недель, отдельное время кураторского часа, даты сессии и отклонение повреждённых файлов, а также таблица недель и звонков, разбор пометок с учётом цвета строки и согласие всех пометок 23-БА с таблицей.
Перед изменением парсера стоит снять снимок всего каталога и после правки сравнить с ним:
npm run snapshot -- fetch # скачать все документы каталога в .data/originals/
npm run snapshot -- save # разобрать их текущим парсером и сохранить эталон
npm run snapshot # после правки: что изменилось по каждому документу
Проверка выводит по каждому документу пропавшие и появившиеся занятия (с --all — все, иначе первые десять) и как изменилось число подозрительных ячеек по кодам. Если что-то изменилось, команда завершается с кодом 1. Документы и эталон лежат в .data/ и в git не попадают.
Docker
docker compose up -d --build
Приложение откроется на http://localhost:3001. Кэш каталога, таблицы недель и распознанных документов хранится в томе psu-data и переживает пересборку. Первый запуск разбирает все документы каталога (около полуминуты), поэтому проверка здоровья (/api/health) даёт контейнеру минуту на старт. Порт и адрес меняются переменными APP_PORT и APP_BIND.
Для установки PWA на телефон нужен HTTPS. Профиль https поднимает Caddy, который сам получает сертификат Let's Encrypt (домен должен указывать на сервер, порты 80 и 443 открыты):
DOMAIN=schedule.example.by docker compose --profile https up -d --build
Если HTTPS уже даёт ваш обратный прокси, профиль не нужен; приложение можно закрыть от внешней сети через APP_BIND=127.0.0.1. Контейнер включает curl и LibreOffice для .doc. Файл docker-compose.yml проверен docker compose config; сборка образа в этой проверке не запускалась.
Android-приложение
В папке android/ — нативный клиент на Kotlin и Jetpack Compose с компонентами Material 3 Expressive (MaterialExpressiveTheme, ModalWideNavigationRail и WideNavigationRail, соединённые ToggleButton, LoadingIndicator). Базовая тема синяя (тональная схема M3); с «Цветами обоев» — динамическая, а цвета предметов и зелёной недели гармонизируются с ней. Разделы открываются из бокового меню: на телефоне оно выезжает, на планшете в портрете и на телефоне в альбомной ориентации — узкая боковая панель, на планшете в альбомной ориентации меню открыто постоянно. На широком экране разделы делятся на две панели, как в School Planner: «Обзор» — карточки и недельная сетка, «Планы» и «Календарь» — список и выбранный день, «Поиск» — результаты и день преподавателя или аудитории; «Предметы» — сеткой карточек, «Расписание» открывается неделей; карточки лежат на подложке с оттенком, системные панели следуют теме приложения.
- Первый запуск — короткий онбординг: приветствие с тремя возможностями приложения, затем шаги с волнистым индикатором прогресса: группа, подгруппа (только если группа делится на подгруппы) и напоминания. Разрешение на уведомления спрашивается на шаге напоминаний, его можно отложить; «Назад» (с предиктивным жестом) возвращает на предыдущий шаг. После онбординга открывается «Обзор» выбранной группы.
- Обзор: отчёт за неделю (сколько пар прошло), следующие занятия сегодня с обратным отсчётом и ближайший учебный день.
- Расписание группы по дням с листанием или недельной сеткой по часам; неделя и её цвет, номера пар, текущая пара, детали занятия с исходной записью Word. Если семестр не найден в таблице недель — строки Word по дням недели.
- Предметы: пары по дням текущей недели; у каждого предмета — сокращение как в недельной сетке, следующее занятие с аудиторией, преподаватели и сколько пар на этой неделе. У каждого предмета свой постоянный цвет во всех разделах.
- Планы: задания и заметки по дням — к занятию или общие, без привязки к паре, на выбранный день, просроченные сверху, «+» прикрепляет задание к ближайшему занятию. Три макета, как в School Planner: «Повестка дня» (все дни с липкими заголовками и полосой недели, которая следует за списком), «Список» (только дни с делами) и «Доска» (колонки дней). В настройках вида — пары дня и выполненные задания.
- Календарь: месяц с отметками дней с парами и с планами, зелёные недели подсвечены; под ним лист с планами и парами выбранного дня. Лист можно поднять — месяц сворачивается до выбранной недели.
- Обратный отсчёт до сессии в «Обзоре»: дни до ближайшего экзамена или зачёта из
/api/exams(консультации — только в списке), по нажатию — вся сессия. Пока университет не опубликовал сессию, отсчёт идёт до конца семестра. Экзамены кэшируются на устройстве. - Задания и заметки к паре: из карточки занятия — к этой паре или к следующей паре того же предмета. Задания отмечаются выполненными, просроченные подсвечиваются; список — в «Обзоре», по предмету — в «Предметах», точка на блоке в недельной сетке. Хранятся только на устройстве (
notes.json) и привязаны к группе. - Посещаемость: «был» или «пропустил» в карточке занятия и одним касанием в «Обзоре» для прошедших сегодня пар; статистика по каждому предмету в «Предметах». Хранится на устройстве (
attendance.json). - Скрытие занятий: «Скрыть такие занятия» в карточке занятия убирает предмет с этим типом занятия из расписания, обзора, виджетов и напоминаний; вернуть можно в настройках.
- Второй виджет «Следующая пара» (4×1) и плитка в шторке быстрых настроек с текущей или следующей парой и аудиторией.
- Настройки отображения: цвета предметов, преподаватели, аудитории, номера пар, метки занятий и пометки из Word можно скрыть; полные сведения остаются в карточке занятия.
- Группы (кнопка группы в верхней панели «Обзора»; если у группы есть подгруппы, после выбора приложение предложит свою): поиск по номеру во всём каталоге, недавние, каталог по факультету и курсу, выбор подгруппы.
- Поиск по университету: занятия преподавателя или аудитории на выбранный день.
- Виджет «Сегодня» (Glance): текущая и следующая пара, обновляется каждые 15 минут без сети.
- Напоминания перед парой за 5–30 минут — локальные будильники из сохранённого расписания, без push-сервера. С разрешением «Будильники и напоминания» приходят точно в срок, без него — в окне ±5 минут.
- Офлайн: последнее расписание хранится на устройстве; каждые 6 часов WorkManager обновляет его, виджет и напоминания.
Экзамены, зачёты, консультации и защиты курсовых попадают в «Расписание», «Календарь» и «Планы», даже если университет публикует их отдельным документом («Расписание зачетов»): /api/timetable (API 6) добавляет к дням семестра события сессии группы из всех документов каталога и продлевает период до последнего из них (не дальше 60 дней после семестра).
- Как выделены. Янтарный цвет (красный читался бы как ошибка), значок вида события и рамка вокруг карточки.
- В «Планах» они видны всегда, даже с выключенными «Парами дня».
- В календаре день с экзаменом или зачётом отмечен янтарным квадратом, а точка планов стала фиолетовой.
Даты считает сервер: GET /api/timetable?doc=<id>&group=<группа>&from=ГГГГ-ММ-ДД&days=N (до 62 дней) или с &range=semester — весь текущий семестр от первого до последнего дня; приложение хранит его целиком и работает с ним без сети. Ответ содержит дни с неделями, номерами пар и пометкой uncertain для нераспознанных пометок, плюс строки Word (weekly) и подгруппы. Поэтому правила белых и зелёных недель в Kotlin не дублируются.
Сборка (JDK 17, Android SDK 37):
cd android
./gradlew assembleDebug
Отладочная сборка ходит на http://10.0.2.2:3010/ — это сервер из docker compose на компьютере, видимый из эмулятора (APP_PORT=3010). Другой адрес: -PPSU_API_URL=…. Релизная сборка принимает только HTTPS-адрес. Идентификатор приложения ritm.schedule — рабочий, перед публикацией его стоит заменить своим.
iOS-приложение
В папке ios/ — нативный клиент на SwiftUI для iOS 26 и новее с Liquid Glass. Функции те же, что у Android-приложения, данные — из того же API. Проект ios/PSUSchedule.xcodeproj открывается в Xcode 27; папки подключены синхронизированными группами, поэтому новые файлы попадают в сборку сами.
- Онбординг: приветствие (три стеклянные «капли» сливаются в одну), затем группа, подгруппа (только если группа делится на подгруппы) и напоминания. Разрешение на уведомления спрашивается на последнем шаге.
- Стеклянная панель вкладок: три раздела — «Обзор», «Расписание», «Планы» — и поиск отдельной круглой кнопкой справа (роль
.search). Нажатие превращает кнопку в поле ввода над клавиатурой, как в приложениях Apple (tabViewSearchActivation(.searchTabSelection)). «Календарь» открывается кнопкой в «Планах», «Предметы» — карточкой в «Обзоре». На iPad панель превращается в боковое меню. Группа и настройки открываются из панели «Обзора»; тема меняется сразу во всех окнах, включая открытые настройки. - Обзор: неделя (сколько пар осталось, чип на каждый день), обратный отсчёт до сессии или конца семестра, следующие занятия с отсчётом «Через 20 мин», посещаемость одним касанием, задания и ближайший учебный день. На iPad рядом показывается недельная сетка.
- Расписание: дни листаются в сторону под плавающей стеклянной лентой недели. На зелёных неделях стекло окрашено зелёным. Есть недельная сетка по часам и строки Word, если семестр не найден. В подробностях пары: время по звонкам, посещаемость, задания, исходная запись Word и скрытие таких занятий.
- Планы: три макета — повестка дня с лентой недели, список и доска. Можно показывать пары дня и выполненные задания. Кнопка «+» — стеклянная.
- Календарь: месяц с отметками пар и планов, зелёные недели подсвечены; ниже — план выбранного дня.
- Предметы: нагрузка недели на диаграмме Swift Charts; у каждого предмета — следующее занятие, прогресс недели, посещаемость и задания.
- Поиск «Кто где»: занятия преподавателя или аудитории на день по всем документам каталога.
- Виджеты WidgetKit: «Сегодня» (малый, средний, большой) и «Следующая пара» (малый и на экране блокировки). Таймлайн обновляется в начале и конце каждой пары; нажатие открывает день в приложении (
psuschedule://day/ГГГГ-ММ-ДД). - Офлайн: расписание, экзамены, задания, посещаемость и настройки хранятся в общей папке App Group
group.org.xtimms.psu.schedule, её читают и виджеты.- Напоминания — локальные уведомления на неделю вперёд (до 48).
- Раз в несколько часов
BGAppRefreshTaskобновляет расписание. - Ответы сервера кэшируются
URLCache: неизменённый семестр приходит пустым304.
Адрес сервера задаётся ключом PSUAPIURL в ios/Config/App-Info.plist (по умолчанию https://psu.rumblur.by/). В Debug-сборке его можно заменить переменной окружения PSU_API_URL, например для локального сервера на этом Mac:
SIMCTL_CHILD_PSU_API_URL=http://localhost:3010/ xcrun simctl launch booted org.xtimms.psu.schedule
Для установки на устройство в Xcode нужно выбрать команду разработчика и зарегистрировать App Group.
Структура
src/App.tsx— интерфейс, каталог и импорт с предпросмотром.src/storage.ts— сохранённые расписания, предпочтения и календарь недель.src/calendar.ts,src/icons.tsx— подписи дат и пар, иконки Material Symbols.shared/weeks.js— разбор пометок недель и расчёт дат, общий для сервера и браузера.shared/lessons.js— устойчивый ключ занятия, сравнение версий расписания, фильтр подгруппы и скрытых занятий.shared/updates.js— правила проверки обновлений сохранённого расписания.server/directory.js— индекс всех документов: преподаватели и аудитории из текста ячеек, занятость на дату, свободные аудитории, экзамены группы (/api/directory/search,/api/directory/day,/api/directory/free,/api/exams).server/schedules.js— разбор документа каталога с дисковым кэшем.src/Search.tsx— экран «Поиск».server/source.js— DAV-каталог, фиксированный источник и кэш; таблица недель и звонков из корня каталога (/api/calendar, кэш 6 часов).server/calendar.js— разбор таблицы недель и расписания звонков.server/parser.js— таблицы.docx, условия и преобразование.doc.server/index.js— API и выдача собранной PWA.server/limits.js— ограничения импорта: число одновременных разборов и запросов с одного адреса.server/errors.js— коды ошибок API и их HTTP-статусы.ios/Shared/— модели, даты в часовом поясе Минска, расчёт пар и общее хранилище; общие для приложения и виджетов.ios/PSUSchedule/— приложение iOS:AppModel.swift(состояние и загрузка),API.swift,Reminders.swift, экраны вViews/.ios/Widgets/— виджеты WidgetKit.scripts/serviceworker.js— генерация полного офлайн-кэша при сборке.
Это независимый клиент; университет остаётся источником расписания. Шрифт Roboto Flex поставляется локально под SIL Open Font License (public/fonts/OFL-RobotoFlex.txt).
Тема выбирается в меню: «Авто» (как в системе), «Светлая» или «Тёмная». Выбор хранится в браузере и применяется до первой отрисовки страницы, вместе с цветом строки состояния. QR-коды строятся в браузере библиотекой qrcode-generator (MIT). Иконки — Material Symbols Rounded (@material-symbols/svg-400, Apache 2.0), встроены в сборку и работают без сети. Интерфейс использует веб-адаптацию Material 3 Expressive: токены цвета и типографики, тональные поверхности, выразительные формы, навигационную панель и rail. Главный экран содержит расписание; импорт, установка и сведения о Word доступны в меню.