# Сохранённые события → CSV для BI

Версия примера: 1.1, 27.09.2026. Python 3.9+, только стандартная библиотека.
Статус: работает локально на приложенных синтетических данных. Клиент API проверен подменой HTTPS-ответов в памяти; запросы к настоящему BOX не выполнялись.

## Результат

Две JSON-страницы превращаются в CSV с четырьмя уникальными событиями. Один точный дубль пропускается. Таблицу можно импортировать в BI или Excel/LibreOffice и сгруппировать по камере и типу события. Отдельно показан подсчёт на Python.

Для перехода к своим данным приложен опциональный fetch_events.py: Swagger своей установки → одна выбранная страница API → настройка mapping.json → CSV. Для первого запуска с учебными данными этот шаг не нужен.

Формат ответа API BOX 3.x: список находится в поле `StoredViolationEvents.events`. Поля самих событий задаёт схема `StoredViolationEvent` конкретной установки. Все поля с префиксом `demo_` и статусы в этих данных придуманы для упражнения: это **не схема API BOX**. Их сопоставление хранится в `mapping.json`.

## Запуск

Распакуйте комплект и откройте терминал в его каталоге:

```sh
python3 export_events.py sample-page-1.json sample-page-2.json --config mapping.json --output events.csv
python3 summarize.py events.csv --output summary.csv
python3 check_example.py
```

Первая команда печатает `{"duplicates_skipped": 1, "rows": 4}`. Файл `events.csv` должен совпасть с `expected.csv`; `summary.csv` — с `expected-summary.csv`. CSV событий содержит UTF-8 BOM, запятую-разделитель и заголовки. В BI задайте ID/камеру/тип/статус как текст; время содержит явное смещение UTC+03:00. Скрипт время не конвертирует.

## Что посмотреть в BI

Импортируйте events.csv → строки: «Камера» → столбцы: «Тип» → значение: количество ID. Получится: Камера 1 / no_helmet = 2; Камера 2 / no_helmet = 1; Камера 2 / vehicle = 1. Это количество зарегистрированных событий, включая непросмотренные и отклонённые. Для числа подтверждённых событий добавьте фильтр «Статус = confirmed»: останется две записи. Это учебная иллюстрация, а не показатель безопасности объекта.

## Опционально: получить одну страницу через API

Сначала сверьте со Swagger своей версии BOX путь `GET /api/statistics/event-storage/events/`, Bearer-авторизацию, `page`, `page_size` и `sorting`. Числовые ограничения и поля отдельного события определяет ваш сервер. Токен получают штатным способом, описанным в руководстве API. Этот клиент не выполняет вход и не обновляет токен.

Клиент принимает origin и access_token только из `BOX_ORIGIN` и `BOX_ACCESS_TOKEN`. В Bash/Zsh задайте адрес своей установки и запустите скрытый ввод токена:

```sh
export BOX_ORIGIN='https://box.example.invalid'
read -r -s BOX_ACCESS_TOKEN
```

Замените демонстрационный адрес своим. После команды read введите access_token без префикса Bearer и нажмите Enter; ввод не отображается. Затем выполните:

```sh
export BOX_ACCESS_TOKEN
python3 fetch_events.py --page 1 --page-size 20 --sorting=-timestamp --timeout 15 --output raw-page-1.json
unset BOX_ACCESS_TOKEN
```

Запрос читает **только указанную страницу**. Фильтров периода нет; клиент не обходит остальные страницы, не повторяет запрос и не подтверждает полноту выгрузки. Знак минус для сортировки передавайте через `--sorting=-timestamp`. Другие значения сверяйте со Swagger.

Успех означает HTTP 200, корректный JSON-объект и массив `events` в нём; пустой массив допустим. Полный ответ сохраняется в новый UTF-8 JSON-файл. В терминале выводится только сводка, например для страницы из трёх событий:

```json
{"events": 3, "page_requested": 1, "page_size_requested": 20, "status": "saved"}
```

`page_requested` и `page_size_requested` повторяют параметры запроса и не подтверждают семантику серверных полей пагинации. Файл не перезаписывается: выберите другое имя. Заголовок Authorization, ответ сервера и текст сетевых исключений в диагностике не печатаются.

HTTPS, проверка цепочки сертификата и имени сервера включены; отключения TLS-проверки нет. Для собственного центра сертификации установите доверенный CA в окружении Python. Redirect запрещён, поэтому укажите конечный origin. Таймаут 15 секунд применяется к сетевым операциям, а не ко всему запуску. Ответ ограничен 10 МиБ; при превышении уменьшите размер страницы. При HTTP 401/403 проверьте токен и права, при 422 — параметры по Swagger. Клиент возвращает код 1 при ошибке.

## Преобразовать свои данные

1. Получите одну разрешённую страницу через fetch_events.py или сохраните JSON штатным клиентом своей установки.
2. Проверьте структуру каждого события в Swagger, создайте копию mapping.json с именем mapping-real.json и замените paths. Вложенные поля задаются через точку, например source.name. Ключи с буквальной точкой и массивы этим простым mapper не поддерживаются.
3. Укажите устойчивый deduplicate_by. При одинаковом ключе и разном JSON обработка останавливается: решение, какая версия события актуальна, нужно принять явно.
4. Передайте файлы в нужном порядке. Скрипт не проверяет полноту периода/страниц и не сортирует события. Данные загружаются в память; комплект рассчитан на небольшие выгрузки.

После настройки полей:

```sh
python3 export_events.py raw-page-1.json --config mapping-real.json --output events-real.csv
```

Учебный mapping.json с demo_* не подходит к реальному ответу без изменения. Оставьте имена колонок «Камера» и «Тип», если хотите применить summarize.py к своей таблице.

Значения, похожие на формулу таблицы (=, +, -, @ после пробелов), получают начальный апостроф. Это сохраняет безопасный текст в Excel, но меняет строковое представление: учитывайте его при импорте в BI. Скрипт не приводит значения к числовым типам.

## Проверки и применимость

check_example.py проверяет точное содержимое CSV, подсчёт для BI, дубль, конфликт версий события, отсутствие поля и экранирование формул. Некорректный вход обнаруживается до записи CSV.

Он также запускает check_fetch.py. HTTPS-транспорт полностью подменён через unittest.mock: проверены путь и параметры GET, Bearer-заголовок, настройки проверки TLS, один запрос, пустой events, ошибки JSON/HTTP, таймаут, запрет redirect, отсутствие перезаписи и отсутствие токена в диагностике. В проверках используются только фиктивный токен, зарезервированный домен example.invalid и синтетические ответы; сетевых обращений нет. Установка TLS-соединения с сервером, права реального пользователя, совместимость конкретной сборки BOX и импорт в BI **не проверены**.

Руководства:
- https://vizorlabs.ru/technology/docs/box3-api/#event-response
- https://vizorlabs.ru/technology/docs/box3-api/#event-query
- https://vizorlabs.ru/technology/docs/event-contracts/

Файлы: fetch_events.py, export_events.py, summarize.py, check_fetch.py, check_example.py, mapping.json, две sample-page, expected.csv и expected-summary.csv. Все приложенные данные искусственные. Доступы нужны только для опционального запроса к вашей установке.
