Skip to content

Latest commit

 

History

History
210 lines (172 loc) · 16.4 KB

application.md

File metadata and controls

210 lines (172 loc) · 16.4 KB

Приложение

Устанавливаемый Python-пакет sales_analytics (src-layout, сборка hatchling, Python 3.13). Как запустить — в README; здесь — как приложение устроено и почему.

Архитектура

             ┌────────────────────────── точки входа ──────────────────────────┐
             │  api/  REST API (FastAPI)    forecasting/__main__   storage/__main__ │
             └───────────────┬─────────────────────┬──────────────────┬────────┘
                             │                     │                  │
                     ┌───────▼─────────────────────▼───────┐          │
                     │ forecasting/                        │          │
                     │   preprocessing.py  панель, признаки│          │
                     │   model.py          обучение,       │          │
                     │                     метрики, прогноз│          │
                     │   report.py         графики         │          │
                     └───────┬─────────────────────────────┘          │
                             │ DemandData                             │
                     ┌───────▼────────────────────────────────────────▼┐
                     │ storage/                                         │
                     │   repository.py  load_demand_data()  — PostgreSQL│
                     │                  load_demand_data_csv() — data/  │
                     │   entities.py    Branch, Product, DemandData     │
                     │   postgres.py, schema.py, export.py, sql/        │
                     └──────────────────────────────────────────────────┘
       config.py — настройки из .env          exceptions.py — ошибки пакета

Три слоя, зависимости направлены только вниз.

Слой Отвечает за Не знает о
storage SQL, подключение, схему, чтение и выгрузку данных модели и API
forecasting предобработку, обучение, метрики, прогноз, графики SQL и HTTP
api HTTP: запросы, ответы, фоновые задачи, коды ошибок SQL и устройстве модели

Файлы

src/sales_analytics/              устанавливаемый пакет (импорт: sales_analytics.*)
├── config.py                     настройки из .env: DB_*, DATASET_DIR, OUTPUT_DIR, API_* (pydantic-settings)
├── exceptions.py                 SalesAnalyticsError и производные
├── storage/                      учётная система в PostgreSQL
│   ├── sql/tables.sql            таблицы, индексы и представления
│   ├── schema.py                 create_tables()
│   ├── postgres.py               connect() — подключение с параметрами из .env, create_database()
│   ├── entities.py               датаклассы: Branch, Product, DemandData
│   ├── repository.py             load_demand_data() из базы, load_demand_data_csv() из датасета
│   ├── export.py                 export_dataset() — выгрузка таблиц в CSV
│   └── __main__.py               командная строка: роль и база, таблицы, выгрузка датасета
├── forecasting/                  прогноз спроса
│   ├── preprocessing.py          недельная панель и признаки: DemandData -> TrainingSet
│   ├── model.py                  обучение, метрики, сохранение, прогноз
│   ├── report.py                 графики
│   └── __main__.py               командная строка: train, predict, report
└── api/                          REST API на FastAPI
    ├── app.py                    приложение, ошибки пакета -> HTTP-коды
    ├── routes.py                 эндпоинты
    ├── schemas.py                модели запросов и ответов
    ├── jobs.py                   фоновые задачи: обучение, выгрузка датасета
    └── __main__.py               запуск: python -m sales_analytics.api

Почему так

  • Весь SQL — в storage. Предобработка получает датакласс DemandData и не знает, откуда он взялся. Это паттерн «репозиторий» [14]: источник данных подменяется без изменения остального кода. Благодаря ему модель обучается и на PostgreSQL, и на CSV-датасете, а тесты — на искусственных данных без базы.
  • DemandData проверяет себя при создании. Если в таблице не хватает колонки, ошибка DataError возникает сразу при загрузке с понятным текстом, а не в середине обучения с KeyError.
  • Справочники — датаклассы, история продаж — таблицы pandas. Баз шесть, позиций около тысячи — им удобно быть объектами с типами. Строк продаж сотни тысяч, и список объектов был бы в десятки раз медленнее и тяжелее таблицы.
  • Логика модели не зависит от интерфейса. CLI и API вызывают одни и те же функции train(), save(), forecast(), save_forecast(), поэтому результат не зависит от того, через что запущен расчёт.

Точки входа

Команда Назначение
python -m sales_analytics.api REST API — основная точка входа: обучение, прогноз, выгрузка датасета
python -m sales_analytics.forecasting train / predict / report то же из командной строки, плюс графики для README
python -m sales_analytics.storage init / create / export администрирование: роль и база, таблицы, выгрузка в CSV

Каждый подпакет запускается через __main__.py, без отдельных скриптов в корне: команда работает одинаково из любой папки, если пакет установлен.

Почему REST API, а не настольный интерфейс

В дипломной работе интерфейсом было настольное окно на customtkinter. Для прогноза спроса этого мало:

  • Прогнозом пользуется не человек, а другая система. Его потребитель — учётная система или модуль закупок, который раз в неделю забирает прогноз по всем позициям. HTTP и JSON — общий язык таких интеграций [13].
  • Документация и проверка запросов бесплатно. FastAPI строит по аннотациям типов описание OpenAPI и страницу /docs, где любой запрос можно выполнить из браузера, а pydantic проверяет тело запроса: горизонт вне 1–52 или неизвестный источник данных возвращают 422 до начала расчёта.
  • Обучение идёт минутами. Окно настольного приложения на это время зависает, а API возвращает номер задачи сразу.

CLI оставлен: он удобнее для разового обучения и для воспроизведения результатов из README.

Эндпоинты

Документация со всеми схемами запросов и ответов — на http://127.0.0.1:8000/docs, там же любой запрос можно выполнить из браузера. Данные для обучения и прогноза берутся из датасета ("source": "dataset", по умолчанию) или из базы ("source": "db"); путь к данным в запросе не принимается — только папка из .env.

Запрос Что делает
GET /health доступна ли база, есть ли датасет, какие модели обучены
POST /models/train обучение в фоне, ответ 202 и задача; тело {"horizons": [1, 2, 3, 4], "source": "dataset", "save_data": false}
GET /jobs/{id}, GET /jobs статус фоновых задач: running, done или failed с текстом ошибки
GET /models, GET /models/{h} обученные модели: периоды, признаки, метрики на тесте, WAPE по единицам
POST /forecasts прогноз на недели после конца данных; тело {"horizons": [...], "source": ...}; сохраняется в output/forecasts
GET /forecasts/latest строки последнего прогноза: фильтры branch, product_id, horizon, страницы offset, limit (до 1000)
GET /forecasts/latest/summary последний прогноз по всей сети, тонн в неделю
POST /dataset/export выгрузка таблиц из базы в data/ в фоне

Пример — обучить модель на неделю вперёд, построить прогноз и посмотреть его по базе YUN:

curl -X POST localhost:8000/models/train -H "Content-Type: application/json" -d '{"horizons": [1]}'
curl localhost:8000/jobs/<id>
curl -X POST localhost:8000/forecasts -H "Content-Type: application/json" -d '{"horizons": [1]}'
curl "localhost:8000/forecasts/latest?branch=YUN&limit=20"

Фоновые задачи

Обучение и выгрузка датасета занимают до нескольких минут и выполняются в отдельном потоке, статус хранится в памяти процесса. Одновременно идёт не больше одной задачи каждого вида: два параллельных обучения перезаписывали бы одни и те же файлы моделей.

Почему не очередь задач (Celery, RQ): ей нужен брокер — Redis или RabbitMQ — и отдельный процесс-исполнитель. Для локального приложения с одним пользователем и двумя видами задач это лишняя инфраструктура. Цена простоты — статусы теряются при перезапуске API, что задокументировано.

Ошибки

Пакет выбрасывает свои исключения, API переводит их в HTTP-коды в одном месте (app.py):

Исключение Код Пример
StorageError 503 база недоступна
DataError, ModelError 409 нет датасета или модель не обучена
ошибка валидации pydantic 422 горизонт 0
HTTPException 404 нет такой задачи или прогноза

Почему. Код модели не должен знать про HTTP, а API не должен разбирать текст ошибок. Одна иерархия исключений (SalesAnalyticsError и наследники) позволяет CLI печатать сообщение и завершаться, а API — возвращать правильный код.

Конфигурация

Настройки — в .env в корне проекта (шаблон — .env.example), читаются pydantic-settings в config.py:

Переменные Назначение
DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME подключение к PostgreSQL
DATASET_DIR, OUTPUT_DIR папка датасета и папка результатов
API_HOST, API_PORT адрес REST API
  • Пароль не попадает в код и в git. .env в .gitignore, пароль хранится как SecretStr: при печати настроек он показывается звёздочками, а не текстом.
  • Переменные окружения важнее файла — удобно для Docker и CI.
  • Типы проверяются при запуске: порт вне 1–65535 — ошибка сразу, а не при подключении.
  • Относительные пути считаются от корня проекта, а не от текущей папки, поэтому команды работают из любого места.
  • API по умолчанию слушает только 127.0.0.1: авторизации нет, и открывать его в сеть без неё нельзя.

Результаты

Всё, что создаёт приложение, складывается в output/ и не хранится в git:

Файл Содержание
models/demand_h{h}.joblib модель на горизонт h
models/demand_h{h}.json описание: признаки, категории, периоды, параметры, метрики
models/demand_h{h}_test.csv.gz прогнозы модели и ориентиров на тесте — для графиков и разбора ошибок
forecasts/forecast_{неделя}.csv прогноз в единицах товара и в кг
training/training_h{h}.csv.gz обучающая выборка — с флагом --save-data

Описание модели в JSON сохраняется рядом с моделью, чтобы по файлу можно было понять, на каком периоде и с какими признаками она обучена и какое качество показала на тесте, — не загружая саму модель.

Технологии

2024 Сейчас Почему
Язык Python 3.7 Python 3.13 3.7 давно не поддерживается
СУБД MySQL PostgreSQL 17 см. database.md
Модель случайный лес градиентный бустинг см. model.md
Данные pandas pandas, NumPy —
Графики matplotlib matplotlib —
Интерфейс customtkinter FastAPI, uvicorn см. выше
Конфигурация в коде .env + pydantic-settings пароль вне кода
Сборка — hatchling, pyproject.toml стандарт PEP 517/621, пакет ставится через pip install -e .
Тесты — pytest см. testing.md