ETL заказов и дневной выручки
Пайплайн читает CSV с заказами, очищает записи, объясняет отбраковку и сохраняет в SQLite заказы и выручку по дням и категориям. Все данные синтетические. Генератор использует локальный Random(42) и фиксированный диапазон с 1 января по 31 марта 2024 года. Персональных данных в наборе нет.
Готовый снимок содержит 1482 принятых заказа из 1500. Общая выручка — 16 877 058,99 ₽. Базу и отчёты можно открыть до первого запуска. База SQLite · Результаты SQL
Быстрый запуск в Windows PowerShell
Откройте PowerShell в корне проекта. Нужен Python 3.11 или новее с модулем sqlite3. Пайплайн использует стандартную библиотеку Python. Для тестов нужен pytest. Команды вызывают интерпретатор окружения напрямую.
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install pytest
.\.venv\Scripts\python.exe -m src.generate_data
.\.venv\Scripts\python.exe main.py
.\.venv\Scripts\python.exe -m pytest -q
Генератор перезаписывает data/raw_orders.csv. Для работы с уже включённым CSV строку генерации можно пропустить. Сам пайплайн открывает источник только для чтения. Пути к данным, SQL и результатам вычисляются относительно корня проекта.
Проверено на Windows: Python 3.14.4, SQLite 3.50.4 (из модуля sqlite3), pytest 9.2.0. Полный набор тестов прошёл — 51 passed. При двух последовательных запусках совпали строки обеих таблиц, исходный CSV и четыре файла отчётов.
Проверка доступа в обычном PowerShell
Откройте PowerShell под своей учётной записью без запуска от имени администратора. Из корня проекта выполните команды ниже. Первая читает каждый из пяти файлов целиком и выводит его SHA-256. Вторая открывает базу SQLite только для чтения. Для приложенного снимка ожидаются 1482 заказа и 1 687 705 899 копеек.
Get-FileHash -LiteralPath .\outputs\orders.db, .\outputs\clean_orders.csv, .\outputs\rejected_rows.csv, .\outputs\quality_report.json, .\outputs\query_results.md -ErrorAction Stop
.\.venv\Scripts\python.exe -c "import sqlite3; db = sqlite3.connect('file:outputs/orders.db?mode=ro', uri=True); print(db.execute('SELECT COUNT(*), SUM(revenue_kopecks) FROM orders').fetchone()); db.close()"
Структура проекта
| Путь | Назначение |
|---|---|
main.py |
Объединяет чтение, очистку, загрузку и публикацию отчётов. |
src/generate_data.py |
Создаёт синтетический CSV с корректными заказами и заданными примерами ошибок. |
src/extract.py |
Проверяет заголовок, читает CSV и нумерует логические записи. |
src/transform.py |
Нормализует поля, проверяет значения, разрешает дубли и конфликты. |
src/quality.py |
Проверяет баланс записей, формирует статистику, сохраняет CSV и JSON. |
src/database.py |
Загружает обе таблицы, выполняет аналитические запросы и записывает Markdown. |
sql/ |
Схема SQLite и три запроса к дневной витрине. |
tests/ |
Проверки генератора, очистки, экспорта, транзакций и запросов на временных данных. |
data/, outputs/ |
Исходный CSV и готовые результаты. |
docs/ |
План реализации, журнал работы с ИИ и промпты из истории чата с отмеченными пропусками команд терминала. |
Обработка одного снимка
- Прочитать CSV в UTF-8 с поддержкой BOM. Заголовок должен содержать ровно пять полей из таблицы ниже, каждое по одному разу. Колонки сопоставляются по имени. Номер записи начинается с 1 после заголовка. Перевод строки внутри поля в кавычках сохраняется внутри той же записи. Исходный CSV
- Сравнить исходные значения полей до очистки. Для каждого точного повтора оставить первое вхождение, остальным присвоить причину «Точный дубль». Это правило действует и для повторов ошибочной записи.
- Проверить число полей и значения. Пустая запись и неверное число полей дают причины отбраковки. При правильной ширине собрать ошибки всех независимых полей. Нечитаемый файл, неверный заголовок, ошибка декодирования или разбора структуры CSV прерывают запуск до загрузки БД.
- Сгруппировать валидные заказы по нормализованному ID. Если все значения совпадают, принять первую запись, остальные отметить как нормализованные дубли. Если варианты различаются, отклонить всю валидную группу как конфликт. Например, ошибочная цена у повтора сама по себе не создаёт конфликт с корректным заказом.
- Проверить, что каждый исходный номер получил ровно один итоговый статус. Загрузить принятые заказы в SQLite и построить дневную витрину. После фиксации БД заменить файлы качества и выполнить три SQL-запроса на одном снимке чтения.
Правила полей
| Входное поле | Правило |
|---|---|
order_id |
Положительное целое. Ведущие нули допустимы. |
order_date |
Существующая дата строго в виде YYYY-MM-DD или DD.MM.YYYY. Результат хранится в ISO-формате YYYY-MM-DD. |
category |
Удалить внешние пробельные символы, внутренние последовательности заменить одним пробелом, привести строку к регистру с заглавной первой буквой. Пустую категорию заменить на «Не указана». |
quantity |
Положительное целое количество единиц товара. |
unit_price |
Положительная цена в рублях. Допустимы точка или запятая и до двух дробных знаков. Цена с третьим знаком отклоняется, даже если это ноль. |
Все поля, кроме категории, обязательны. У чисел и дат удаляются внешние пробелы. Числа записываются цифрами ASCII без разделителей тысяч, знаков или экспоненты. Неконечные значения отклоняются. Цена разбирается через Decimal и точно переводится в целые копейки. Выручка заказа равна количеству, умноженному на цену в копейках. Целые значения и произведение проверяются на диапазон SQLite INTEGER.
Таблицы и идемпотентность
| Таблица и ключ | Содержимое |
|---|---|
orders, ключ order_id |
Один принятый заказ. Поля order_id, order_date, category, quantity, unit_price_kopecks, revenue_kopecks. |
daily_category_revenue, ключ (order_date, category) |
Одна строка для дня и категории с продажами. Поля order_date, category, order_count, revenue_kopecks. |
Обе таблицы хранят деньги целым числом копеек. Ограничения проверяют обязательность полей, положительные количества и суммы, а в заказе — равенство выручки произведению цены и количества. Схема таблиц
Каждый запуск заменяет весь снимок. В одной транзакции BEGIN IMMEDIATE очищаются обе таблицы, заказы вставляются с параметрами, затем SQLite считает COUNT(*) и SUM(revenue_kopecks) по дате и категории. При ошибке загрузки, включая переполнение суммы, обе таблицы откатываются. При повторе с тем же входом строки таблиц совпадают. Корректный снимок без принятых заказов очищает обе таблицы.
Результаты качества
| Показатель | Значение |
|---|---|
| Прочитано записей | 1500 |
| Принято | 1482 (98,8%) |
| Отброшено | 18 |
| Из них ошибки значений | 14 |
| Точные / нормализованные дубли | 1 / 1 |
| Записи с конфликтующим ID | 2 |
| Группы «дата и категория» с продажами | 438 |
В очищенном CSV шесть полей заказа. В CSV отбраковки для каждого исходного номера сохранены заголовок, исходные значения и причины как JSON-массивы. Оба CSV записаны в UTF-8 с BOM. Очищенные заказы · Отброшенные записи
Итоговая причина — первая в порядке проверок, поэтому итоговые счётчики в сумме дают 18. Отдельный счётчик проблем учитывает каждую обнаруженную у записи проблему и допускает пересечение. Доля принятых вычисляется от числа исходных записей, для пустого входа она равна null. Отчёт качества
Аналитика
- Топ категорий. Первые пять по суммарной выручке с числом заказов. При равенстве сумм сортировка идёт по названию. Запрос
- Динамика. Общая дневная выручка всех категорий и изменение к предыдущему календарному дню. Диапазон идёт от первой до последней даты принятых заказов включительно. Запрос
- Аномалии. Для каждой категории за весь период рассчитываются среднее и дисперсия генеральной совокупности. День попадает в отчёт, когда квадрат отклонения дневной выручки от среднего строго превышает четыре дисперсии. Для периода короче семи дней и при нулевой дисперсии результат пуст. Запрос
На синтетическом снимке запросы вернули пять категорий, 91 день и 18 пар «дата и категория» для проверки. Денежные колонки отчёта приведены в копейках. Полные результаты
Ограничения и допущения
- Одна запись описывает один завершённый заказ с одним товаром в рублях. Возвраты, скидки, бесплатные товары и валютная конвертация вне модели.
- Дни внутри календарного диапазона без записей о продажах получают нулевую выручку. Это допущение о входных данных. Полнота источника для таких дат отдельно не проверяется.
- Аномалия служит сигналом для проверки. Короткая выборка и выбросы влияют на оценку среднего и дисперсии, сезонность метод не учитывает.
- Пайплайн рассчитан на снимок, который помещается в память, и последовательные запуски. База и файлы отчётов обновляются раздельно. Если экспорт завершается ошибкой после фиксации SQLite, база уже обновлена, а часть отчётов может относиться к предыдущему запуску. После устранения причины повторите запуск с тем же входом.