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/ План реализации, журнал работы с ИИ и промпты из истории чата с отмеченными пропусками команд терминала.

Обработка одного снимка

  1. Прочитать CSV в UTF-8 с поддержкой BOM. Заголовок должен содержать ровно пять полей из таблицы ниже, каждое по одному разу. Колонки сопоставляются по имени. Номер записи начинается с 1 после заголовка. Перевод строки внутри поля в кавычках сохраняется внутри той же записи. Исходный CSV
  2. Сравнить исходные значения полей до очистки. Для каждого точного повтора оставить первое вхождение, остальным присвоить причину «Точный дубль». Это правило действует и для повторов ошибочной записи.
  3. Проверить число полей и значения. Пустая запись и неверное число полей дают причины отбраковки. При правильной ширине собрать ошибки всех независимых полей. Нечитаемый файл, неверный заголовок, ошибка декодирования или разбора структуры CSV прерывают запуск до загрузки БД.
  4. Сгруппировать валидные заказы по нормализованному ID. Если все значения совпадают, принять первую запись, остальные отметить как нормализованные дубли. Если варианты различаются, отклонить всю валидную группу как конфликт. Например, ошибочная цена у повтора сама по себе не создаёт конфликт с корректным заказом.
  5. Проверить, что каждый исходный номер получил ровно один итоговый статус. Загрузить принятые заказы в 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, база уже обновлена, а часть отчётов может относиться к предыдущему запуску. После устранения причины повторите запуск с тем же входом.
S
Description
Выполненное тестовое задание Перкова Владислава
Readme
255 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 100%