Завершить ETL-проект и документацию
This commit is contained in:
1 parent
2ec0b38d95
commit
93b1478226
6 files changed
+669
-41
No files matched your search
@@ -0,0 +1,107 @@
|
||||
# ETL заказов и дневной выручки
|
||||
|
||||
Пайплайн читает CSV с заказами, очищает записи, объясняет отбраковку и сохраняет в SQLite заказы и выручку по дням и категориям. Все данные синтетические. Генератор использует локальный `Random(42)` и фиксированный диапазон с 1 января по 31 марта 2024 года. Персональных данных в наборе нет.
|
||||
|
||||
Готовый снимок содержит **1482 принятых заказа из 1500**. Общая выручка — **16 877 058,99 ₽**. Базу и отчёты можно открыть до первого запуска. [База SQLite](outputs/orders.db) · [Результаты SQL](outputs/query_results.md)
|
||||
|
||||
## Быстрый запуск в Windows PowerShell
|
||||
|
||||
Откройте PowerShell в корне проекта. Нужен Python 3.11 или новее с модулем `sqlite3`. Пайплайн использует стандартную библиотеку Python. Для тестов нужен pytest. Команды вызывают интерпретатор окружения напрямую.
|
||||
|
||||
```powershell
|
||||
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 копеек.
|
||||
|
||||
```powershell
|
||||
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/` | [План реализации](docs/implementation_plan.md), [журнал работы с ИИ](docs/ai_work_log.md) и [промпты из истории чата](docs/prompts.md) с отмеченными пропусками команд терминала. |
|
||||
|
||||
## Обработка одного снимка
|
||||
|
||||
1. Прочитать CSV в UTF-8 с поддержкой BOM. Заголовок должен содержать ровно пять полей из таблицы ниже, каждое по одному разу. Колонки сопоставляются по имени. Номер записи начинается с 1 после заголовка. Перевод строки внутри поля в кавычках сохраняется внутри той же записи. [Исходный CSV](data/raw_orders.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`. |
|
||||
|
||||
Обе таблицы хранят деньги целым числом копеек. Ограничения проверяют обязательность полей, положительные количества и суммы, а в заказе — равенство выручки произведению цены и количества. [Схема таблиц](sql/schema.sql)
|
||||
|
||||
Каждый запуск заменяет весь снимок. В одной транзакции `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. [Очищенные заказы](outputs/clean_orders.csv) · [Отброшенные записи](outputs/rejected_rows.csv)
|
||||
|
||||
Итоговая причина — первая в порядке проверок, поэтому итоговые счётчики в сумме дают 18. Отдельный счётчик проблем учитывает каждую обнаруженную у записи проблему и допускает пересечение. Доля принятых вычисляется от числа исходных записей, для пустого входа она равна `null`. [Отчёт качества](outputs/quality_report.json)
|
||||
|
||||
## Аналитика
|
||||
|
||||
- **Топ категорий.** Первые пять по суммарной выручке с числом заказов. При равенстве сумм сортировка идёт по названию. [Запрос](sql/top_categories.sql)
|
||||
- **Динамика.** Общая дневная выручка всех категорий и изменение к предыдущему календарному дню. Диапазон идёт от первой до последней даты принятых заказов включительно. [Запрос](sql/daily_revenue.sql)
|
||||
- **Аномалии.** Для каждой категории за весь период рассчитываются среднее и дисперсия генеральной совокупности. День попадает в отчёт, когда квадрат отклонения дневной выручки от среднего строго превышает четыре дисперсии. Для периода короче семи дней и при нулевой дисперсии результат пуст. [Запрос](sql/anomalies.sql)
|
||||
|
||||
На синтетическом снимке запросы вернули пять категорий, 91 день и 18 пар «дата и категория» для проверки. Денежные колонки отчёта приведены в копейках. [Полные результаты](outputs/query_results.md)
|
||||
|
||||
## Ограничения и допущения
|
||||
|
||||
- Одна запись описывает один завершённый заказ с одним товаром в рублях. Возвраты, скидки, бесплатные товары и валютная конвертация вне модели.
|
||||
- Дни внутри календарного диапазона без записей о продажах получают нулевую выручку. Это допущение о входных данных. Полнота источника для таких дат отдельно не проверяется.
|
||||
- Аномалия служит сигналом для проверки. Короткая выборка и выбросы влияют на оценку среднего и дисперсии, сезонность метод не учитывает.
|
||||
- Пайплайн рассчитан на снимок, который помещается в память, и последовательные запуски. База и файлы отчётов обновляются раздельно. Если экспорт завершается ошибкой после фиксации SQLite, база уже обновлена, а часть отчётов может относиться к предыдущему запуску. После устранения причины повторите запуск с тем же входом.
|
||||
Reference in new issue
Block a user