# Сервис предсказания цен

Веб-сервис предсказания цен на подержанные автомобили. Мониторинг в комплекте.

Обзор сервисов (по `compose.yaml`, см. о развёртывании ниже):

| Профили Compose |       Имя        |     Объекты      |     Описание     |
|-----------------|------------------|------------------|------------------|
| &mdash;         | `prices-predictor` | код: `ml_service/` | Веб-сервис предсказания цен, только stateless API. Об используемой предсказательной модели см. `research/README.md`. |
| &mdash;         | `prometheus`     | конфигурация: `prometheus/` | Мониторинг сервиса ([Prometheus](https://prometheus.io/)). |
| &mdash;         | `grafana`        | сохранённая конфигурация: `grafana/` | Аналитика и визуализация данных мониторига сервиса ([Grafana](https://grafana.com/)). |
| `with-testers`  | `load-tester`    | код: `load-tester/` | Генератор потока случайных запросов к `prices-predictor` для тестирования. |

Дополнительно:

* `models/` &mdash; расположение файла модели `model.pkl` для использования сервисом `prices-predictor`.
  * `fetch_model_as_pickle_from_mlflow.py` &mdash; скрипт для экспорта предиктивной модели scikit-learn из MLFlow в файл.

## API сервиса предсказания цен

**Базовый URL**: `/api`. Все указанные далее URL записаны **относительно базового URL**, если не указано иное.

* Полная интерактивная документация (Swagger UI): `/docs`.

* Предсказать цену подержанного автомобиля: `/predict` (POST).

  Пример запроса:

  * requst query: `item_id=16` (параметр `item_id` необходим!);

  * request body:

    ```json
    {
        "selling_price": 5.59,
        "driven_kms": 27000.0,
        "age": 5.0,
        "fuel_type": "petrol",
        "selling_type": "dealer",
        "transmission_type": "manual"
    }
    ```

  * response body:

    ```json
    {
        "item_id": 16,
        "price": 3.743508852258851
    }
    ```

* Тестовый эндпоинт: `/` (GET).

  Возвращает простой демонстрационный объект JSON.

  Может использоваться для проверки состояния сервиса.

## Мониторинг

### Prometheus UI

#### Примеры запросов

Гистограмма предсказанных цен `model_prediction_value_bucket` (запрос: `rate(model_prediction_value_bucket[5m]`):

![Гистограмма предсказанных цен как временные ряды](docs/screenshot-prometheus-query-model-1.png)

Гистограмма продолжительности предсказания цен моделью ML `model_prediction_seconds_bucket` (запрос: `rate(model_prediction_seconds_bucket[5m]`):

![Гистограмма продолжительности предсказания цен моделью ML как временные ряды](docs/screenshot-prometheus-query-model-2.png)

Интенсивность потока запросов к сервису предсказания цен с разными результатами (успех &mdash; коды HTTP `2xx`, ошибки со стороны клиента &mdash; коды HTTP `4xx`) `http_requests_total{handler="/predict"}` (запрос: `rate(http_requests_total{handler="/predict"}[5m]`):

![Интенсивность потока запросов к сервису предсказания цен с разными результатами](docs/screenshot-prometheus-query-http-1.png)

Интенсивность потока запросов к **веб-серверу** сервиса предсказания цен **с ошибками** `http_requests_total{handler="/predict"}` (запрос: `sum without(handler, method) (rate(http_requests_total{status=~"4..|5.."}[5m]))`):

![Интенсивность потока запросов к веб-серверу сервиса предсказания цен, заканчивающихся ошибками](docs/screenshot-prometheus-query-http-1.png)

### Дашборд в Grafana

Дашборд экспортирован в файл: `grafana/objects/dashboard-1765200932880.json`.

![Дашборд в Grafana](docs/screenshot-grafana-dashboard.png)

Элементы:

* мониторинг модели:
  * гистограмма распределения предсказанных цен за период времени (10 мин);
* прикладной уровень:
  * интенсивность потока запросов (всех запросов; запросов, заканчивающихся ошибкой);
* инфраструктурный уровень:
  * состояние сервиса (up/down);
  * выделенный процессу объём VRAM.

## Развёртывание

### Файл модели

Файл используемой предсказательной модели можно извлечь из MLFlow скриптом `models/fetch_model_as_pickle_from_mlflow.py`. Файл модели можно размещается в `models/model.pkl`.

Например, извлечь модель по имени (`<model-name>`) и версии (`<model-version>`) (например, `UsedCardPricePredictionFinal/1`) (команда запускается из корневой директории проекта &mdash; от этого зависит путь к создаваемому файлу):

    python services/models/fetch_model_as_pickle_from_mlflow.py --model "models:/<model-name>/<model-version>" services/models/model.pkl

Можно указать адрес tracking сервера MLFlow, например: `--tracking-uri "http://localhost:5000"`.

Информация о других опциях доступна:

    python services/models/fetch_model_as_pickle_from_mlflow.py --help

### Образы Docker

#### `ml_model` (для сервиса `prices-predictor`)

**Сборка образа** (замените `<version>` на номер версии) (команда запускается из корневой директории проекта &mdash; от этого зависит путь к директории):

    docker build -t ml_service:<version> services/ml_service/

**Независимый запуск** (замените `<version>` на номер версии образа, `<models-dir>` на **абсолютный** путь к директории, где размещён файл предсказательной модели `model.pkl`, `<port>` на порт для запуска веб-сервиса (например, `8000`)):

    docker run -v "<models-dir>:/models" -p <port>:8000 ml_service:<version>

Модель может быть размещена в `models/`; тогда, например, при запуске команды из корна проекта: `$(pwd)/services/models` (здесь `$(pwd)` используется потому, что необходим абсолютный путь).

#### `load-tester` (для сервиса `load-tester`)

**Сборка образа** (замените `<version>` на номер версии) (команда запускается из корневой директории проекта &mdash; от этого зависит путь к директории):

    docker build -t load_tester:<version> services/load_tester/

**Независимый запуск** (замените `<version>` на номер версии образа, `<api-base-url>` на базовый URL сервиса `prices-predictor` (например, `http://prices-predictor:8000/api`)):

    docker run -e "API_BASE_URL=<api-base-url>" ml_service:<version>

### Развёртывание сервиса посредством Compose

Конфигурация описана в файле `compose.yaml`. Имя системы: `mpei-iis-system`.

Рекомендуется (не обязательно) использовать env-файл `compose.env`. Используйте файл `compose.env.template` как шаблон.

**Директория `models/` используется сервисом `prices-predictor` как том** с файлом модели `model.pkl`. Поместите туда файл модели, см. [Файл модели](#файл-модели).

**Управление сервисом с мониторингом** (замените `<command>` и `[options...]`):

    docker compose -f services/compose.yaml --env-file services/compose.env <command> [options...]

**Для запуска вместе с генераторами тестовых запросов** используйте опцию compose `--profile=with-tester`.

Основные команды `docker compose`:

* `up`: создать и запустить контейнеры (также тома, сети и прочее); оставляет вывод логов прикреплённым к терминалу, `SIGINT` останавливает контейнеры, **но не удаляет созданные объекты**;
  * опция `-d`: то же, но открепляет процесс от терминала.
* `down`: остановить и удалить контейнеры (также сети и прочее; для удаления томов используйте опцию `-v`).
* `start`: запустить существующие контейнеры.
* `stop`: остановить контейнеры.
* `restart`: перезапустить контейнеры.

**Открытые на хосте интерфейсы**:

* `localhost:8010`: Сервис `prices-predictor`. Базовый URL: `/api`.
* `localhost:9090`: UI Prometheus.
* `localhost:3000`: Grafana.

**Доступные на хосте тома**:

* `mpei-iis-system_prometheus-storage`: БД Prometheus.
* `mpei-iis-system_grafana-storage`: БД Grafana.
