Этап: Аналитические запросы
This commit is contained in:
1 parent
d90f1f30b2
commit
2ec0b38d95
10 files changed
+315
-1
No files matched your search
@@ -67,3 +67,13 @@
|
|||||||
**Фактические проверки.** В существующей .venv прошли все 45 тестов. Пять новых интеграционных тестов запускали копию проекта во временном каталоге из другой рабочей папки. Переполнение суммы группы откатило изменения обеих таблиц. Испорченный CSV сохранил прежний снимок. Набор без принятых заказов очистил обе таблицы. Сбой экспорта после фиксации базы завершил CLI с ненулевым кодом и сообщением об обновлённой БД.
|
**Фактические проверки.** В существующей .venv прошли все 45 тестов. Пять новых интеграционных тестов запускали копию проекта во временном каталоге из другой рабочей папки. Переполнение суммы группы откатило изменения обеих таблиц. Испорченный CSV сохранил прежний снимок. Набор без принятых заказов очистил обе таблицы. Сбой экспорта после фиксации базы завершил CLI с ненулевым кодом и сообщением об обновлённой БД.
|
||||||
|
|
||||||
На рабочем синтетическом CSV команда python main.py создала outputs/orders.db. Вход — 1500 записей, приняты 1482, отброшены 18. Получено 438 групп по дате и категории. Суммарная выручка в обеих таблицах составила 1 687 705 899 копеек. Содержимое обеих таблиц сравнили до и после повторного запуска, заказы сверили по всем шести полям с clean_orders.csv. SHA-256 исходного CSV совпал с исходным замером. Аналитические запросы остаются следующим этапом.
|
На рабочем синтетическом CSV команда python main.py создала outputs/orders.db. Вход — 1500 записей, приняты 1482, отброшены 18. Получено 438 групп по дате и категории. Суммарная выручка в обеих таблицах составила 1 687 705 899 копеек. Содержимое обеих таблиц сравнили до и после повторного запуска, заказы сверили по всем шести полям с clean_orders.csv. SHA-256 исходного CSV совпал с исходным замером. Аналитические запросы остаются следующим этапом.
|
||||||
|
|
||||||
|
## 12 ноября 2026 — SQL-аналитика и Markdown-отчёт
|
||||||
|
|
||||||
|
**Запрос пользователя.** Подключить три запроса к main.py, сохранить фактические результаты и пояснения в outputs/query_results.md, проверить запросы на временных базах.
|
||||||
|
|
||||||
|
**Решения.** Запросы читают дневную витрину. Рейтинг ограничен пятью категориями согласно плану. Динамика включает календарные пропуски и изменение общей выручки к предыдущему дню. Для аномалий календарь общий, среднее и дисперсия отдельные для каждой категории. Перед AVG вычитается минимум категории, чтобы лучше сохранять небольшие различия при большой выручке. Метод сравнивает квадрат отклонения с четырьмя дисперсиями. Все три результата получаются в одной транзакции чтения, денежные колонки отчёта выражены в копейках.
|
||||||
|
|
||||||
|
**Фактические проверки.** В существующей .venv прошёл 51 тест. На временных базах проверены сортировка при равной выручке, пропущенный день, рост и падение по отдельным категориям, постоянный ряд, короткий период, точная граница в два стандартных отклонения и пустой снимок. Контрольная сумма рабочей базы до и после тестов совпала.
|
||||||
|
|
||||||
|
**Запуск на данных проекта.** При первом запуске Windows отказала в замене clean_orders.csv после фиксации БД. После разрешённого повторного запуска python main.py завершился успешно. Для чтения созданного отчёта при проверке также потребовался доступ вне песочницы. Все строки трёх результатов сверены с Markdown. Получены 5 категорий, 91 календарный день и 18 аномальных пар «дата и категория». SHA-256 исходного CSV совпал с ранее зафиксированным.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from src.database import reload_database
|
from src.database import reload_database, save_query_results
|
||||||
from src.extract import CSVFileError, read_records
|
from src.extract import CSVFileError, read_records
|
||||||
from src.quality import OUTPUT_DIR, build_report, save_reports
|
from src.quality import OUTPUT_DIR, build_report, save_reports
|
||||||
from src.transform import clean_orders
|
from src.transform import clean_orders
|
||||||
@@ -19,6 +19,7 @@ def main() -> int:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
save_reports(accepted, rejected, report)
|
save_reports(accepted, rejected, report)
|
||||||
|
save_query_results(OUTPUT_DIR / "orders.db", OUTPUT_DIR / "query_results.md")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
exc.add_note("База уже обновлена. Сохранение файлов отчётов не завершено.")
|
exc.add_note("База уже обновлена. Сохранение файлов отчётов не завершено.")
|
||||||
raise
|
raise
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,140 @@
|
|||||||
|
# Выручка по синтетическим заказам
|
||||||
|
|
||||||
|
Период охватывает даты от первого до последнего принятого заказа включительно. Нулевой день внутри диапазона считаем днём без заказов. Полнота исходных данных для таких дней не подтверждена.
|
||||||
|
|
||||||
|
## Топ категорий
|
||||||
|
|
||||||
|
Пять категорий с наибольшей суммарной выручкой. При равенстве порядок определяется названием.
|
||||||
|
|
||||||
|
| Категория | Заказы, шт. | Выручка, коп. |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Электроника | 310 | 391080190 |
|
||||||
|
| Товары для дома | 293 | 349440300 |
|
||||||
|
| Спорт | 302 | 338855762 |
|
||||||
|
| Одежда | 295 | 307648084 |
|
||||||
|
| Книги | 281 | 299951820 |
|
||||||
|
|
||||||
|
## Динамика общей выручки
|
||||||
|
|
||||||
|
Сумма по всем категориям за календарный день и изменение к предыдущему дню. Для первой даты изменение не определено.
|
||||||
|
|
||||||
|
| Дата | Выручка, коп. | Изменение, коп. |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 2024-01-01 | 5234207 | — |
|
||||||
|
| 2024-01-02 | 15959605 | 10725398 |
|
||||||
|
| 2024-01-03 | 13252392 | -2707213 |
|
||||||
|
| 2024-01-04 | 17152115 | 3899723 |
|
||||||
|
| 2024-01-05 | 17877952 | 725837 |
|
||||||
|
| 2024-01-06 | 15339640 | -2538312 |
|
||||||
|
| 2024-01-07 | 19892270 | 4552630 |
|
||||||
|
| 2024-01-08 | 25490951 | 5598681 |
|
||||||
|
| 2024-01-09 | 14691064 | -10799887 |
|
||||||
|
| 2024-01-10 | 9513666 | -5177398 |
|
||||||
|
| 2024-01-11 | 14198281 | 4684615 |
|
||||||
|
| 2024-01-12 | 22484473 | 8286192 |
|
||||||
|
| 2024-01-13 | 19967837 | -2516636 |
|
||||||
|
| 2024-01-14 | 19375849 | -591988 |
|
||||||
|
| 2024-01-15 | 25760629 | 6384780 |
|
||||||
|
| 2024-01-16 | 32761991 | 7001362 |
|
||||||
|
| 2024-01-17 | 17439350 | -15322641 |
|
||||||
|
| 2024-01-18 | 25226964 | 7787614 |
|
||||||
|
| 2024-01-19 | 16038444 | -9188520 |
|
||||||
|
| 2024-01-20 | 13482523 | -2555921 |
|
||||||
|
| 2024-01-21 | 25309562 | 11827039 |
|
||||||
|
| 2024-01-22 | 25405924 | 96362 |
|
||||||
|
| 2024-01-23 | 16170985 | -9234939 |
|
||||||
|
| 2024-01-24 | 19929525 | 3758540 |
|
||||||
|
| 2024-01-25 | 13812648 | -6116877 |
|
||||||
|
| 2024-01-26 | 20075426 | 6262778 |
|
||||||
|
| 2024-01-27 | 13037503 | -7037923 |
|
||||||
|
| 2024-01-28 | 22508862 | 9471359 |
|
||||||
|
| 2024-01-29 | 16052452 | -6456410 |
|
||||||
|
| 2024-01-30 | 18644889 | 2592437 |
|
||||||
|
| 2024-01-31 | 11619914 | -7024975 |
|
||||||
|
| 2024-02-01 | 20804046 | 9184132 |
|
||||||
|
| 2024-02-02 | 9864238 | -10939808 |
|
||||||
|
| 2024-02-03 | 29199916 | 19335678 |
|
||||||
|
| 2024-02-04 | 17149356 | -12050560 |
|
||||||
|
| 2024-02-05 | 15187448 | -1961908 |
|
||||||
|
| 2024-02-06 | 18078285 | 2890837 |
|
||||||
|
| 2024-02-07 | 15343818 | -2734467 |
|
||||||
|
| 2024-02-08 | 19468051 | 4124233 |
|
||||||
|
| 2024-02-09 | 16459694 | -3008357 |
|
||||||
|
| 2024-02-10 | 14071437 | -2388257 |
|
||||||
|
| 2024-02-11 | 22626032 | 8554595 |
|
||||||
|
| 2024-02-12 | 15600384 | -7025648 |
|
||||||
|
| 2024-02-13 | 20147931 | 4547547 |
|
||||||
|
| 2024-02-14 | 23307304 | 3159373 |
|
||||||
|
| 2024-02-15 | 12665127 | -10642177 |
|
||||||
|
| 2024-02-16 | 16561242 | 3896115 |
|
||||||
|
| 2024-02-17 | 15303081 | -1258161 |
|
||||||
|
| 2024-02-18 | 26452474 | 11149393 |
|
||||||
|
| 2024-02-19 | 21636025 | -4816449 |
|
||||||
|
| 2024-02-20 | 28618838 | 6982813 |
|
||||||
|
| 2024-02-21 | 17006115 | -11612723 |
|
||||||
|
| 2024-02-22 | 11692493 | -5313622 |
|
||||||
|
| 2024-02-23 | 28585480 | 16892987 |
|
||||||
|
| 2024-02-24 | 14715781 | -13869699 |
|
||||||
|
| 2024-02-25 | 18760056 | 4044275 |
|
||||||
|
| 2024-02-26 | 17345781 | -1414275 |
|
||||||
|
| 2024-02-27 | 15714988 | -1630793 |
|
||||||
|
| 2024-02-28 | 27629837 | 11914849 |
|
||||||
|
| 2024-02-29 | 23000428 | -4629409 |
|
||||||
|
| 2024-03-01 | 15387167 | -7613261 |
|
||||||
|
| 2024-03-02 | 11285080 | -4102087 |
|
||||||
|
| 2024-03-03 | 14710174 | 3425094 |
|
||||||
|
| 2024-03-04 | 10967689 | -3742485 |
|
||||||
|
| 2024-03-05 | 18435191 | 7467502 |
|
||||||
|
| 2024-03-06 | 18962554 | 527363 |
|
||||||
|
| 2024-03-07 | 11197837 | -7764717 |
|
||||||
|
| 2024-03-08 | 15393970 | 4196133 |
|
||||||
|
| 2024-03-09 | 22885538 | 7491568 |
|
||||||
|
| 2024-03-10 | 15339096 | -7546442 |
|
||||||
|
| 2024-03-11 | 28717903 | 13378807 |
|
||||||
|
| 2024-03-12 | 20028289 | -8689614 |
|
||||||
|
| 2024-03-13 | 28862996 | 8834707 |
|
||||||
|
| 2024-03-14 | 23501759 | -5361237 |
|
||||||
|
| 2024-03-15 | 7788128 | -15713631 |
|
||||||
|
| 2024-03-16 | 16635114 | 8846986 |
|
||||||
|
| 2024-03-17 | 22454061 | 5818947 |
|
||||||
|
| 2024-03-18 | 8894345 | -13559716 |
|
||||||
|
| 2024-03-19 | 25480556 | 16586211 |
|
||||||
|
| 2024-03-20 | 22016371 | -3464185 |
|
||||||
|
| 2024-03-21 | 19594771 | -2421600 |
|
||||||
|
| 2024-03-22 | 14913116 | -4681655 |
|
||||||
|
| 2024-03-23 | 20186036 | 5272920 |
|
||||||
|
| 2024-03-24 | 16282631 | -3903405 |
|
||||||
|
| 2024-03-25 | 20217864 | 3935233 |
|
||||||
|
| 2024-03-26 | 17567169 | -2650695 |
|
||||||
|
| 2024-03-27 | 28243586 | 10676417 |
|
||||||
|
| 2024-03-28 | 25251572 | -2992014 |
|
||||||
|
| 2024-03-29 | 15348968 | -9902604 |
|
||||||
|
| 2024-03-30 | 19370836 | 4021868 |
|
||||||
|
| 2024-03-31 | 15081953 | -4288883 |
|
||||||
|
|
||||||
|
## Аномалии по категориям
|
||||||
|
|
||||||
|
Для каждой категории берём все дни периода, включая нулевые. Вычисляем среднюю дневную выручку и дисперсию генеральной совокупности как средний квадрат отклонения от среднего. День попадает в таблицу, если квадрат его отклонения строго больше четырёх дисперсий. Учитываются рост и падение. При периоде короче семи дней или нулевой дисперсии категория не даёт аномалий.
|
||||||
|
|
||||||
|
| Дата | Категория | Выручка, коп. |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 2024-01-02 | Товары для дома | 9424949 |
|
||||||
|
| 2024-01-05 | Одежда | 8751658 |
|
||||||
|
| 2024-01-16 | Книги | 9093093 |
|
||||||
|
| 2024-01-16 | Товары для дома | 10344579 |
|
||||||
|
| 2024-01-18 | Электроника | 12813010 |
|
||||||
|
| 2024-01-22 | Одежда | 9872917 |
|
||||||
|
| 2024-01-28 | Одежда | 8850850 |
|
||||||
|
| 2024-02-03 | Одежда | 12467037 |
|
||||||
|
| 2024-02-11 | Электроника | 10399062 |
|
||||||
|
| 2024-02-23 | Электроника | 11543519 |
|
||||||
|
| 2024-02-25 | Товары для дома | 9834762 |
|
||||||
|
| 2024-02-28 | Товары для дома | 9481484 |
|
||||||
|
| 2024-03-12 | Не указана | 729743 |
|
||||||
|
| 2024-03-19 | Книги | 8433237 |
|
||||||
|
| 2024-03-23 | Спорт | 10383417 |
|
||||||
|
| 2024-03-25 | Одежда | 8569495 |
|
||||||
|
| 2024-03-26 | Спорт | 8244141 |
|
||||||
|
| 2024-03-27 | Электроника | 13178994 |
|
||||||
|
|
||||||
|
Аномалия служит сигналом для проверки. Само отклонение не доказывает ошибку. На короткой выборке оценка нестабильна. Выбросы влияют на среднее и дисперсию, а метод не учитывает сезонность.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
WITH RECURSIVE calendar(day, last_day) AS (
|
||||||
|
SELECT MIN(order_date), MAX(order_date) FROM daily_category_revenue
|
||||||
|
HAVING COUNT(*) > 0
|
||||||
|
UNION ALL
|
||||||
|
SELECT date(day, '+1 day'), last_day FROM calendar WHERE day < last_day
|
||||||
|
),
|
||||||
|
categories AS (
|
||||||
|
SELECT DISTINCT category FROM daily_category_revenue
|
||||||
|
),
|
||||||
|
daily AS (
|
||||||
|
SELECT day, c.category, COALESCE(r.revenue_kopecks, 0) AS revenue_kopecks
|
||||||
|
FROM calendar CROSS JOIN categories AS c
|
||||||
|
LEFT JOIN daily_category_revenue AS r ON r.order_date = day AND r.category = c.category
|
||||||
|
),
|
||||||
|
-- Вычитаем общий для категории минимум в целых копейках до перехода AVG к REAL.
|
||||||
|
-- Такой сдвиг сохраняет отклонения и различия в копейку при большом базовом уровне.
|
||||||
|
shifted AS (
|
||||||
|
SELECT *, revenue_kopecks - MIN(revenue_kopecks) OVER (PARTITION BY category) AS amount
|
||||||
|
FROM daily
|
||||||
|
),
|
||||||
|
deviations AS (
|
||||||
|
SELECT *, amount - AVG(amount) OVER (PARTITION BY category) AS deviation
|
||||||
|
FROM shifted
|
||||||
|
),
|
||||||
|
stats AS (
|
||||||
|
SELECT *, COUNT(*) OVER (PARTITION BY category) AS days,
|
||||||
|
AVG(deviation * deviation) OVER (PARTITION BY category) AS variance
|
||||||
|
FROM deviations
|
||||||
|
)
|
||||||
|
SELECT day AS order_date, category, revenue_kopecks
|
||||||
|
FROM stats
|
||||||
|
WHERE days >= 7 AND variance > 0 AND deviation * deviation > 4 * variance
|
||||||
|
ORDER BY day, category;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
WITH RECURSIVE calendar(day, last_day) AS (
|
||||||
|
SELECT MIN(order_date), MAX(order_date) FROM daily_category_revenue
|
||||||
|
HAVING COUNT(*) > 0
|
||||||
|
UNION ALL
|
||||||
|
SELECT date(day, '+1 day'), last_day FROM calendar WHERE day < last_day
|
||||||
|
),
|
||||||
|
daily AS (
|
||||||
|
SELECT day AS order_date, COALESCE(SUM(r.revenue_kopecks), 0) AS revenue_kopecks
|
||||||
|
FROM calendar
|
||||||
|
LEFT JOIN daily_category_revenue AS r ON r.order_date = day
|
||||||
|
GROUP BY day
|
||||||
|
)
|
||||||
|
SELECT order_date, revenue_kopecks,
|
||||||
|
revenue_kopecks - LAG(revenue_kopecks) OVER (ORDER BY order_date) AS change_kopecks
|
||||||
|
FROM daily
|
||||||
|
ORDER BY order_date;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
SELECT category,
|
||||||
|
SUM(order_count) AS order_count,
|
||||||
|
SUM(revenue_kopecks) AS revenue_kopecks
|
||||||
|
FROM daily_category_revenue
|
||||||
|
GROUP BY category
|
||||||
|
ORDER BY revenue_kopecks DESC, category
|
||||||
|
LIMIT 5;
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
from html import escape
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from .transform import Order
|
from .transform import Order
|
||||||
|
|
||||||
@@ -31,3 +33,49 @@ def reload_database(path: Path, orders: Iterable[Order]) -> None:
|
|||||||
FROM orders
|
FROM orders
|
||||||
GROUP BY order_date, category
|
GROUP BY order_date, category
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def save_query_results(path: Path, destination: Path) -> None:
|
||||||
|
"""Выполнить три запроса на одном снимке и заменить готовый Markdown-отчёт."""
|
||||||
|
sections = (
|
||||||
|
("top_categories", "Топ категорий",
|
||||||
|
"Пять категорий с наибольшей суммарной выручкой. При равенстве порядок определяется названием.",
|
||||||
|
("Категория", "Заказы, шт.", "Выручка, коп.")),
|
||||||
|
("daily_revenue", "Динамика общей выручки",
|
||||||
|
"Сумма по всем категориям за календарный день и изменение к предыдущему дню. "
|
||||||
|
"Для первой даты изменение не определено.",
|
||||||
|
("Дата", "Выручка, коп.", "Изменение, коп.")),
|
||||||
|
("anomalies", "Аномалии по категориям",
|
||||||
|
"Для каждой категории берём все дни периода, включая нулевые. Вычисляем среднюю дневную "
|
||||||
|
"выручку и дисперсию генеральной совокупности как средний квадрат отклонения от среднего. "
|
||||||
|
"День попадает в таблицу, если квадрат его отклонения строго больше четырёх дисперсий. "
|
||||||
|
"Учитываются рост и падение. При периоде короче семи дней или нулевой дисперсии "
|
||||||
|
"категория не даёт аномалий.",
|
||||||
|
("Дата", "Категория", "Выручка, коп.")),
|
||||||
|
)
|
||||||
|
lines = ["# Выручка по синтетическим заказам", "",
|
||||||
|
"Период охватывает даты от первого до последнего принятого заказа включительно. "
|
||||||
|
"Нулевой день внутри диапазона считаем днём без заказов. Полнота исходных данных "
|
||||||
|
"для таких дней не подтверждена."]
|
||||||
|
with closing(sqlite3.connect(path.resolve().as_uri() + "?mode=ro", uri=True)) as db:
|
||||||
|
db.execute("BEGIN")
|
||||||
|
for name, title, description, columns in sections:
|
||||||
|
sql = SCHEMA.with_name(name + ".sql").read_text(encoding="utf-8")
|
||||||
|
rows = db.execute(sql).fetchall()
|
||||||
|
lines.extend(("", f"## {title}", "", description, ""))
|
||||||
|
if not rows:
|
||||||
|
lines.append("Аномалий не обнаружено." if name == "anomalies" else "Нет принятых заказов.")
|
||||||
|
continue
|
||||||
|
lines.append("| " + " | ".join(columns) + " |")
|
||||||
|
lines.append("| " + " | ".join("---" for _ in columns) + " |")
|
||||||
|
for row in rows:
|
||||||
|
cells = ["—" if value is None else escape(str(value)).replace("|", "|")
|
||||||
|
for value in row]
|
||||||
|
lines.append("| " + " | ".join(cells) + " |")
|
||||||
|
lines.extend(("", "Аномалия служит сигналом для проверки. Само отклонение не доказывает ошибку. "
|
||||||
|
"На короткой выборке оценка нестабильна. Выбросы влияют на среднее и дисперсию, "
|
||||||
|
"а метод не учитывает сезонность."))
|
||||||
|
with TemporaryDirectory(dir=destination.parent) as staging:
|
||||||
|
report = Path(staging) / destination.name
|
||||||
|
report.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
report.replace(destination)
|
||||||
@@ -73,6 +73,10 @@ def test_full_run_and_repeat(project):
|
|||||||
assert (len(amounts), sum(amounts)) == (count, revenue)
|
assert (len(amounts), sum(amounts)) == (count, revenue)
|
||||||
|
|
||||||
outputs = project / "outputs"
|
outputs = project / "outputs"
|
||||||
|
markdown = (outputs / "query_results.md").read_text(encoding="utf-8")
|
||||||
|
assert "| Книги | 3 | 2600 |" in markdown
|
||||||
|
assert "| 2024-01-02 | 500 | -1900 |" in markdown
|
||||||
|
assert "Аномалий не обнаружено." in markdown
|
||||||
with (outputs / "clean_orders.csv").open(encoding="utf-8-sig", newline="") as source:
|
with (outputs / "clean_orders.csv").open(encoding="utf-8-sig", newline="") as source:
|
||||||
clean = list(csv.DictReader(source))
|
clean = list(csv.DictReader(source))
|
||||||
assert sorted((int(r["order_id"]), r["order_date"], r["category"], int(r["quantity"]),
|
assert sorted((int(r["order_id"]), r["order_date"], r["category"], int(r["quantity"]),
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import sqlite3
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.database import SCHEMA, reload_database, save_query_results
|
||||||
|
from src.transform import Order
|
||||||
|
|
||||||
|
|
||||||
|
def make_database(tmp_path, sales):
|
||||||
|
path = tmp_path / "orders.db"
|
||||||
|
orders = [
|
||||||
|
Order(i, f"2024-01-{day:02}", category, 1, amount, amount, i)
|
||||||
|
for i, (day, category, amount) in enumerate(sales, 1)
|
||||||
|
]
|
||||||
|
reload_database(path, orders)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def query(path, name):
|
||||||
|
with closing(sqlite3.connect(path)) as db:
|
||||||
|
return db.execute(SCHEMA.with_name(name + ".sql").read_text(encoding="utf-8")).fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def test_ranking_and_calendar_gap(tmp_path):
|
||||||
|
db = make_database(tmp_path, [(1, "B", 50), (1, "B", 50), (3, "A", 100), (3, "C", 1)])
|
||||||
|
assert query(db, "top_categories") == [("A", 1, 100), ("B", 2, 100), ("C", 1, 1)]
|
||||||
|
assert query(db, "daily_revenue") == [
|
||||||
|
("2024-01-01", 100, None), ("2024-01-02", 0, -100), ("2024-01-03", 101, 101),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_separate_categories_and_zero_days(tmp_path):
|
||||||
|
sales = [(day, "Постоянная", 50) for day in range(1, 8)]
|
||||||
|
sales += [(day, "Падение", 100) for day in range(1, 8) if day != 4]
|
||||||
|
sales += [(4, "Рост", 100)]
|
||||||
|
db = make_database(tmp_path, sales)
|
||||||
|
assert query(db, "anomalies") == [
|
||||||
|
("2024-01-04", "Падение", 0), ("2024-01-04", "Рост", 100),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("amounts", [[10] * 7, [1] * 5 + [100], [10] * 8 + [20] * 2])
|
||||||
|
def test_constant_short_or_exactly_two_standard_deviations(tmp_path, amounts):
|
||||||
|
db = make_database(tmp_path, [(i, "A", value) for i, value in enumerate(amounts, 1)])
|
||||||
|
assert query(db, "anomalies") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_queries_and_report(tmp_path):
|
||||||
|
db = make_database(tmp_path, [])
|
||||||
|
for name in ("top_categories", "daily_revenue", "anomalies"):
|
||||||
|
assert query(db, name) == []
|
||||||
|
destination = tmp_path / "query_results.md"
|
||||||
|
save_query_results(db, destination)
|
||||||
|
assert "Аномалий не обнаружено." in destination.read_text(encoding="utf-8")
|
||||||
Reference in new issue
Block a user