# LR3 Создание сервиса предсказаний

## Цель
Создать микросервис предсказаний моделью ML, создать его docker-образ и запустить сервис в контейнере.
## Задание 

1. Активировать виртуальное окружение. Установить библиотеки `fastapi`, `uvicorn`, не забыв обновить `requirements.txt`.
2. Запустить mlflow, убедиться, что доступны экспериметы, прогоны и модели, сделанные в ЛР2.
3. Создать директорию `./services`, в ней еще одну директорию `ml_service`, и в ней основной файл `main.py`.
```
my_proj
 |_____ .venv_my_proj
 |_____ .git
 |_____ data
 |       |___ ...
 |
 |_____ eda
 |       |___ ...
 |
 |_____ research
 |       |___ ...
 |
 |_____ services
 |       |___ ml_service
 |              |___main.py
 |              
 |_____ .gitignore
 |_____ README.md
 |_____ requirements.txt
```

4. В созданном файле создать экземпляр `FastAPI-приложения`, сделать обработку GET-запросов к корню приложения `/`, выдавая в ответ на запрос словарь `{"Hello": "World"}`
> В ходе работы с файлами `.py` будут создаваться директории `__pycache__`, в которых хранятся скомпилированные байт-код модули `.pyc`. Эти папки коммитить не нужно. Добавьте правило в `.gitignore`.
5. Запустить сервер `uvicorn` и убедиться, что сервис обрабатывает запросы к корню, пройдя на страницу `http://localhost:8000/docs` и выполнив тестовый запрос.
6. Создать endpoint `/api/prediction` выдачи предсказания для объекта. Endpoint будет обрабатывает POST-запросы, принимая в качестве URL-параметра идентификатор объекта `item_id`, а в теле запроса (body) все признаки объекта, необходимые для подачи на вход модели и выдачи предсказания. Возвращать будет словарь из двух объектов - `item_id` - тот же идентификатор объекта, и `predict` - предсказанное значение.
7. Запустить сервер `uvicorn` и убедиться, что сервис обрабатывает запросы к  `/api/prediction`, пройдя на страницу `http://localhost:8000/docs` и выполнив тестовый запрос к этому endpoint-у. Если сервис работает правильно, то вы должны увидеть ответ такой структуры:
```
Response body
{
  "item_id": 123,
  "price": 0.7637315626006276
}
```

8. В директории `./services` создать директорию `models`, в которой будет храниться обученная модель, а также скрипт по ее получению из mlflow. Создать скрипт `get_model.py` 
```
my_proj
 |_____ .venv_my_proj
 |_____ .git
 |_____ data
 |       |___ ...
 |
 |_____ eda
 |       |___ ...
 |
 |_____ research
 |       |___ ...
 |
 |_____ services
 |       |___ ml_service
 |       |      |___main.py
 |       |___models
 |              |___get_model.py
 |              |___model.pkl    # Появится после выполнения следующего пункта 
 |              
 |_____ .gitignore
 |_____ README.md
 |_____ requirements.txt
```


9. Сформировать скрипт `get_model.py`, который должен подключаться к mlflow, выгружать модель по её run_id и сохранять ее в файл `model.pkl`.

10. В GUI MLFlow скопировать run_id production-модели, указать его в скрипте. Запустить скрипт и убедиться, что в директории `./services/models` появился файл `model.pkl`.

11. В директории `ml_service` создать файл `api_handler.py` и в нем описать класс-обработчик запросов к API `FastAPIHandler`. 

Класс должен иметь метод `__init__` в котором загружается модель из файла `/model.pkl`, и метод `predict`, возвращающий предсказание.

12. Доработать основной модуль сервиса и класс-обработчик таким образом, чтобы сервис предсказывал значения для любого произвольного объекта, параметры которого передаются в теле запроса. Убедиться в корректности работы. Остановить сервис.

13. В папке `ml_service` создать файл `requirements.txt`, в который записать те зависимости, которые необходимы для работы сервиса. 
> Данный файл - не то же самое, что `requirements.txt` в корневой директории проекта. Данный файл будет использоваться для сборки образа, поэтому должен содержать только необходимые зависимости и их версии. Виртуальное окружение на хостовой машине по-прежнему нужно устанавливать из файла в корневой директории.

> Проще всего заполнить этот `requirements.txt`, пройдясь по файлам в папке и выписав все импорты. Абсолютно точно на этом этапе в зависимости нужно добавить `fastapi`, `uvicorn`, `pandas`, `pickle4`, `scikit-learn`.
```
my_proj
 |_____ .venv_my_proj
 ...
 |
 |_____ services
 |       |___ ml_service
 |       |      |___main.py
 |       |      |___api_handler.py
 |       |      |___Dockerfile
 |       |      |___requirements.txt  # новый файл, созданный на этом шаге
 |       |___models
 |              |___get_model.py
 |              |___model.pkl   
 |              
 |_____ .gitignore
 |_____ README.md
 |_____ requirements.txt   # старый файл, созданный в ЛР1
```


14.  В папке `ml_service` cформировать `Dockerfile`
Образ должен собираться Из базового образа `python:3.11-slim` по следующим шагам: 
* Скопировать содержимое текущей директории в директорию  `/ml_service` в контейнере
* Сделать директорию `/models` доступной снаружи контейнера 
* Сделать `/ml_service` рабочей директорией
* Выполнить установку всех зависимостей из файла `requirements.txt`
* Указать, что порт 8000 должен быть доступен снаружи контейнера
* Финальная команда - запуск сервера. Необходимо явно указать адрес хоста `"0.0.0.0"` и порта `"8000"`, на которых будет запускаться сервер.

Состав директорий проекта на данном шаге должен выглядеть следуюшим образом:
```
my_proj
 |_____ .venv_my_proj
 |_____ .git
 |_____ data
 |       |___ ...
 |
 |_____ eda
 |       |___ ...
 |
 |_____ research
 |       |___ ...
 |
 |_____ services
 |       |___ ml_service
 |       |      |___main.py
 |       |      |___api_handler.py
 |       |      |___Dockerfile
 |       |      |___requirements.txt
 |       |___models
 |              |___get_model.py
 |              |___model.pkl   
 |              
 |_____ .gitignore
 |_____ README.md
 |_____ requirements.txt
```

15. Собрать образ, дать ему понятное название и указав что это первая версия образа. 

Команду по сборке образа записать в конец `Dockerfile`, добавив значок комментария `#`.
> В рамках это ЛР мы будем создавать первую версию нашего сервиса. Даже если вы несколько раз пересобираете образ, что-то меняя или добавляя в него, в этой ЛР все равно указывайте первую версию.

> Версия указывается через двоеточие после названия образа. Например, для указания 3й версии образа: `my_super_image:3`

16. Запустить контейнер из образа. Команду по запуску контейнера записать в конец `Dockerfile`, добавив значок комментария `#`.

> При запуске не забыть указать, какой хостовый порт должен быть связанным с портом 8000 внутри контейнера.

> Указать, что хостовая директория ../models должна быть связана с одноименной директорией в контейнере. Поскольку мы собираем образ из директории `ml_service`, а интересующая директория с моделью `models` находится на уровень выше, то путь до нее можно указать так: `$(pwd)/../models`. Здесь `$(pwd)` (print working directory) - утилита командной строки, которая выводит полный путь до текущей директории. Через двоеточие от хостовой директории нужно указать соответствующий volume внутри контейнера. Полный агрумент будет выглядеть так: `$(pwd)/../models:/models`

17. Убедиться, что контейнер работает, отправив запрос в сервис. Добиться работоспособности сервиса.

18. Актуализировать файл README: 
* добавить описание разработанного сервиса: краткое описание файлов в папке ml_service, models.
* Указать команды для создания образа и запуска контейнера. 
* Указать, как можно проверить работоспособность сервиса, включив пример тела запроса.








