Источник ICS: три пути и план на случай отказа
Главный организационный риск проекта: доступа к корпоративному календарю могут не дать. Здесь — как получить ICS и почему отказ не блокирует работу.
Почему именно ICS
.ics (iCalendar, RFC 5545) — открытый формат, который понимают все:
Outlook/Exchange, Teams, Google Calendar, Яндекс, Apple. Он же выдаётся при
экспорте и при публикации календаря по ссылке.
Работать через ICS-файл, а не через API конкретного вендора — сознательный выбор: не привязываемся к Microsoft Graph, а значит не нужны регистрация приложения в Azure AD, согласие администратора и OAuth-токены. Для стажёра это разница между «сделал за вечер» и «жду решения ИБ три месяца».
Три пути получить ICS
Путь 1 · Публикация личного календаря (пробовать первым)
Outlook Web → Календарь → Параметры → Общий доступ и разрешения →
Опубликовать календарь → выбрать уровень детализации → получить ссылку .ics.
- Плюс: это ваш личный календарь, а не корпоративный ресурс. Часто разрешено.
- Минус: только ваши встречи, не все брони переговорки.
- Риск: функцию нередко отключают на уровне тенанта — тогда кнопки просто нет.
Проверяется за две минуты. Если кнопка есть — дальше всё просто.
Уровень детализации. Достаточно «Только занят/свободен» — тем и лучше: тем времени и организаторов в данных нет, а для метрики они и не нужны. Это же снимает часть вопросов о приватности (см. ниже).
Путь 2 · Календарь ресурса (правильный, но через админа)
В Exchange переговорка — это room mailbox со своим календарём, где лежат все её брони. Именно он нужен по-хорошему.
Доступ даёт администратор: делегирование прав на почтовый ящик ресурса либо публикация его календаря.
- Плюс: полные данные — все брони помещения, а не одного человека.
- Минус: для стажёра запрос почти наверняка уйдёт в ИБ.
Просить это стоит не сейчас, а после прототипа — см. ниже.
Путь 3 · Ручной экспорт (всегда доступен)
Outlook → Файл → Сохранить календарь → формат iCalendar → диапазон дат.
Это снимок, а не синхронизация. Но для демонстрации метрики этого полностью достаточно: выгрузили за месяц, посчитали, показали результат.
Если не дадут вообще ничего
Проект всё равно делается — на генераторе правдоподобного расписания.
# gen_ics.py — синтетический календарь переговорки на месяц.
# Нужен, когда корпоративного доступа нет: парсер и метрика те же.
import random, datetime as dt
TZ = "Europe/Moscow"
random.seed(42) # воспроизводимость
def workdays(start, days):
d = start
while d < start + dt.timedelta(days=days):
if d.weekday() < 5: # пн–пт
yield d
d += dt.timedelta(days=1)
lines = ["BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//xSTEM//MeetingRoom//RU"]
# еженедельный статус — повторяющееся событие (RRULE): проверяет парсер
lines += [
"BEGIN:VEVENT",
"UID:weekly-standup@example.org",
"DTSTART;TZID=%s:20260706T100000" % TZ,
"DTEND;TZID=%s:20260706T103000" % TZ,
"RRULE:FREQ=WEEKLY;BYDAY=MO;COUNT=12",
"SUMMARY:Еженедельный статус",
"END:VEVENT",
]
# разовые встречи
uid = 0
for day in workdays(dt.date(2026, 7, 6), 28):
for _ in range(random.choice([0, 1, 1, 2, 3])):
hour = random.choice([9, 11, 13, 14, 15, 16, 17])
dur = random.choice([30, 60, 60, 90])
uid += 1
s = dt.datetime.combine(day, dt.time(hour, 0))
e = s + dt.timedelta(minutes=dur)
lines += [
"BEGIN:VEVENT",
f"UID:evt-{uid}@example.org",
f"DTSTART;TZID={TZ}:{s:%Y%m%dT%H%M%S}",
f"DTEND;TZID={TZ}:{e:%Y%m%dT%H%M%S}",
"SUMMARY:Встреча",
"END:VEVENT",
]
lines.append("END:VCALENDAR")
open("meetingroom.ics", "w", encoding="utf-8").write("\r\n".join(lines))
print(f"событий: {uid + 1} (включая серию RRULE)")Генератор намеренно включает и RRULE-серию, и разовые встречи — иначе парсер не будет проверен на главной сложности.
Честность в портфолио. Если данные синтетические — так и говорить. «Пайплайн работает на реальных данных датчика и синтетическом календаре, потому что корпоративный доступ требует согласования» — это нормальный инженерный ответ, который показывает и результат, и понимание ограничений. Выдавать сгенерированное за реальное нельзя.
Абстракция источника
Ключевое проектное решение: обработка не знает, откуда взялся ICS.
# .env проекта — меняется одна строка, код не трогается
ICS_SOURCE_TYPE=url # url | file | generated
ICS_URL=https://outlook.office365.com/owa/calendar/.../reachcalendar.ics
ICS_FILE=/opt/meetingroom/calendar.ics
ICS_SYNC_INTERVAL=15m
ICS_WINDOW_DAYS=30 # окно синхронизации: см. «Синк и метрика»| Тип | Когда | Что делает |
|---|---|---|
url | дали ссылку на публикацию | скачивает по расписанию |
file | ручной экспорт | читает локальный файл |
generated | доступа нет | берёт вывод gen_ics.py |
Все три отдают одну и ту же строку ICS дальше по конвейеру. Парсер, upsert и метрика — общие.
Стратегия запроса доступа
Порядок важнее формулировок.
Неправильно: просить доступ к календарю переговорки, не имея ничего. Это запрос «дайте данные, я что-нибудь придумаю» — уходит в ИБ и застревает.
Правильно: сначала прототип на экспорте или генераторе, потом разговор:
«Я собрал систему: датчик присутствия показывает фактическую занятость переговорной, календарь — плановую. За тестовый месяц получилось, что N% времени помещение числилось забронированным, но фактически пустовало. Для регулярного обновления нужна ссылка на публикацию календаря переговорной — данные только занят/свободен, без тем и участников.»
Разница принципиальная: во втором случае вы просите под готовый результат и сразу снимаете вопрос о приватности, ограничивая запрос до занят/свободен.
Приватность — сказать до того, как спросят
Вопрос возникнет обязательно, лучше ответить заранее.
| Не нужно | Почему |
|---|---|
| темы встреч | метрике безразлично |
| организатор и участники | персональные данные |
| вложения, тело события | не используются |
Нужно ровно три поля: начало, конец, факт брони. Уровень «занят/свободен» при публикации календаря даёт именно это.
В схеме bookings поля organizer и title есть — при работе с корпоративными
данными их не заполнять. Так проект не превращается в слежку за
сотрудниками, а остаётся аналитикой помещения.
Что дальше
Синк и метрика — разворачивание RRULE, идемпотентный upsert и SQL, который отвечает на главный вопрос.