Устанавливаемый 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, без отдельных скриптов в корне:
команда работает одинаково из любой папки, если пакет установлен.
В дипломной работе интерфейсом было настольное окно на 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 |