## Алгоритм обработки расписаний общественного транспорта

Для каждой нитки хранится 2 вектора расписаний: основные и альтернативные. Каждое расписание по своему типу может быть интервальным или точным.

Интервальное расписание представляет собой структуру: `дата начала, дата окончания, дни недели (набор), время начала, время окончания, интервал (в секундах)`.

Точное расписание представляет собой структуру: `дата начала, дата окончания, дни недели (набор), вектор времён отправления`.

Альтернативное расписание по структуре совпадает с обычным, но его задача - не дополнять, а заменять основное расписание на общем периоде дат их действия.

Для обоих векторов (основных и альтернативных) поддерживается инвариант: никакие два расписания из вектора не имеют общих точек в пространстве `дата начала, дата окончания, дни недели`.

На всех стадиях обработки, включая запись в MTR-файлы, все времена хранятся в часовом поясе оператора.

1. На стадии `read_from_ymapsdf`:

    1.1. Интервальные расписания приходят из YMapsDF в формате, описанном выше. Точные времена отправления из YMapsDF приходят по отдельности: 1 объект категории `freq_dt` - 1 время отправления.

    1.2. Каждое считываемое расписание объединяется с уже накопленным вектором расписаний соответствующего типа (основные/альтернативные). Алгоритм объединения последовательно сравнивает новое расписание с каждым из имеющихся по датам и дням недели. Если есть непустое пересечение, то строятся новые расписания:

    - 1) Даты и дни недели, относящиеся только к старому расписанию : сохраняются данные (время начала/время окончания/интервал/набор времён отправления) из старого расписания.
    - 2) Даты из общей части + дни недели, относящиеся только к старому расписанию : сохраняются данные из старого расписания.
    - 3) Даты и дни недели из общей части : для точных расписаний к набору времён отправления добавляется время из нового расписания (если оно совпадает с одним из имеющихся, то фактически набор не меняется). Для интервальных расписаний такое пересечение являются недопустимым и отлавливается валидацией.
    - 4) Даты из общей части + дни недели, относящиеся только к новому расписанию : сохраняются данные из нового расписания.
    - 5) Даты и дни недели, относящиеся только к новому расписанию : сохраняются данные из нового расписания.

    Первые 4 порождённых расписания заносятся в результат, а пятое ("хвост" нового расписания) продолжает дальше в цикле сравниваться с остальными имеющимися. В конце остаток, не имеющий общих точек ни с одним из ранее накопленных расписаний, тоже кладётся в результат, после чего тот заменяет старый вектор в структуре данных.

    Реально может быть порождено и более 5 расписаний, т. к. в случае перехода интервала дат через Новый год он предварительно делится на два (по 31 декабря и с 1 января), каждый из которых участвует в объединении по отдельности.

2. На стадии `update`:

    2.1. Для интервальных расписаний происходит заполнение недостающих полей (`время начала, время окончания, интервал`) из расписания оператора. Если какие-либо из этих значений отсутствуют в обоих расписаниях, то в качестве дат принимается интервал 01.01-31.12, времени работы - 00:00-24:00, интервала - 0 секунд.

    2.2. Для каждой нитки происходит финальное объединение векторов основных и альтернативных расписаний.

    Здесь уже объединяются не вектор с отдельным расписанием, а два вектора между собой. Поскольку в процессе объединения меняются оба аргумента, то алгоритм работает так: вектора итерируются двойным вложенным циклом, при непустом пересечении порождённые расписания вставляются на место старых ("общая" часть записывается в левый вектор), после чего поиск продолжается с учётом вновь добавленных расписаний. После завершения цикла содержимое правого вектора дописывается к левому и возвращается в качестве результата.

    Порождение новых расписаний схоже с тем, что происходит на предыдущей стадии, только вместо пунктов 3) - 5) в результат кладётся альтернативное расписание целиком, без дробления. Здесь уже допускаются разные типы расписаний: точное может быть альтернативным для интервального и т. д. Расписания же из пунктов 1) - 2) (это ровно та часть основного, которое оказалось не затронуто альтернативным) формируются по тому же принципу, что выше.

3. На стадии `write_to_mtr`произодится запись расписаний в MTR-файлы `calendar`, `frequency` и `timetable`.

### Пример 1

Расписания в НК:

|    Даты     | Дни недели | Времена отправления | Альтернативное |
|:-----------:|:----------:|:-------------------:|:--------------:|
| 01.01-30.09 |  Пн,Вт,Пт  |     08:00,10:00     |       Нет      |
| 01.04-31.12 |  Пн,Пт,Вс  |     10:00,18:00     |       Нет      |

Расписания в YMapsDF:

|    Даты     | Дни недели | Времена отправления | Альтернативное |
|:-----------:|:----------:|:-------------------:|:--------------:|
| 01.01-30.09 |  Пн,Вт,Пт  |        08:00        |       Нет      |
| 01.01-30.09 |  Пн,Вт,Пт  |        10:00        |       Нет      |
| 01.04-31.12 |  Пн,Пт,Вс  |        10:00        |       Нет      |
| 01.04-31.12 |  Пн,Пт,Вс  |        18:00        |       Нет      |

Расписания в MTR:

|    Даты     | Дни недели | Времена отправления |
|:-----------:|:----------:|:-------------------:|
| 01.01-31.03 |  Пн,Вт,Пт  |     08:00,10:00     |
| 01.04-30.09 |     Вт     |     08:00,10:00     |
| 01.04-30.09 |   Пн,Пт    |  08:00,10:00,18:00  |
| 01.04-30.09 |     Вс     |     10:00,18:00     |
| 01.10-31.12 |  Пн,Пт,Вс  |     10:00,18:00     |

### Пример 2

Расписания в НК:

|    Даты     | Дни недели | Времена отправления | Альтернативное |
|:-----------:|:----------:|:-------------------:|:--------------:|
| 01.01-30.12 |   Пн-Вс    |        08:00        |       Нет      |
| 01.06-31.08 |   Пн-Вс    |        07:00        |       Нет      |
| 01.05-10.05 |   Пн-Вс    |        09:00        |       Да       |
| 01.05-10.05 |   Пн-Вс    |        09:15        |       Да       |

Расписания в YMapsDF для этого примера совпадают с таковыми в НК.

Расписания в MTR:

|    Даты     | Дни недели | Времена отправления |
|:-----------:|:----------:|:-------------------:|
| 01.01-30.04 |   Пн-Вс    |        08:00        |
| 01.05-10.05 |   Пн-Вс    |     09:00,09:15     |
| 11.05-31.05 |   Пн-Вс    |        08:00        |
| 01.06-31.08 |   Пн-Вс    |     07:00,08:00     |
| 01.09-30.12 |   Пн-Вс    |        08:00        |

### Пример 3

Расписания в НК:

|    Даты     | Дни недели | Время работы | Интервал |
|:-----------:|:----------:|:------------:|:--------:|
| 01.12-31.05 |  Пн,Вт,Ср  | 01:00-12:00  |  7 мин.  |

Расписания в YMapsDF для этого примера совпадают с таковыми в НК.

Расписания в MTR:

|    Даты     | Дни недели | Время работы | Интервал |
|:-----------:|:----------:|:------------:|:--------:|
| 01.12-31.12 |  Пн,Вт,Ср  | 01:00-12:00  |  7 мин.  |
| 01.01-31.01 |  Пн,Вт,Ср  | 01:00-12:00  |  7 мин.  |


## Предпочтительные вагоны для пересадки или выхода
Файл **boardings** со следующими полями:

**previous_stop_id** - Идентификатор остановки, предшествующей остановке высадки/пересадки. Соответствует значению из первого столбца файла stops
**src_stop_id** - Идентификатор остановки на этой нитке, на которой будет происходить высадка/пересадка. Соответствует значению из первого столбца файла stops
**dst_stop_id** - Идентификатор остановки, на которую происходит пересадка, ЛИБО идентификатор выхода, через который происходит высадка.
**next_stop_id** - Идентификатор остановки, следующей после остановки пересадки, в направлении движения, в котором движется пассажир после пересадки.
Задаётся для случаев со сложными станциями, типа Третьяковской, с несколькими платформами, где направление дальнейшего движения влияет на то,
в какой вагон садиться, чтобы приехать на станцию **next_stop_id**. Задаёт направление движения на нитке пересадки.
Рекомендуется не заполнять, если режим посадки (**boarding_options**) не зависит от направления движения со станции **dst_stop_id**.
**boarding_options** - Перечисление через запятую нуля или нескольких идентификаторов рекомендованных зон посадки.

Идентификатор зоны посадки обозначается перечислением через двоеточие двух значений:
**boarding_scheme:scheme_specific_area_id** ,
где **boarding_scheme** обозначает используемую схему обозначения посадки,
**scheme_specific_area_id** -- идентификатор зоны в используемой схеме.
На данный момент используется только схема **train_car** с возможными значениями зон:
 + **head** (голова состава
 + **middle_head** (ближе к голове состава)
 + **middle** (середина состава)
 + **middle_tail** (ближе к хвосту состава)
 + **tail** (хвост состава)

### Виртуальные пересадки и выходы
В некоторых случаях вагоны для удобства указаны для не существующих физически объектов пересадки/выхода.
Такие объекты имеют атрибут **is_vitual** и не пишутся в **transitions**, при этом вагоны экспортируются.

### Пример
#### Пересадка
```
(Марксистская -> Третьяковская (Калининская) -> Третьяковская (Калужско-Рижская) -> в сторону Октябрьская (Калужско-Рижская))
```

#### boardings
```
station__9858847    station__9858924    station__9858923    station__9858865    train_car:middle_head,train_car:middle,train_car:middle_tail
```
