Tyycha commited on
Commit
e8697a4
·
1 Parent(s): 92e9082

feat: vocabulary YAML + demo DB script + auto-load vocab

Browse files
configs/example_vocabulary.yaml CHANGED
@@ -1,33 +1,60 @@
1
- # Бизнес-словарь компании — пример заполнения
2
- # Скопируй этот файл, переименуй под свою компанию и заполни своими терминами.
3
- # Путь к файлу указывается при запуске утилиты.
 
 
4
 
5
- company: "ООО Ромашка"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # Бизнес-термины и метрики
8
- # Ключ — слово/фраза как говорит аналитик
9
- # Значение — что это означает в терминах SQL / данных
10
  terms:
11
- выручка: "SUM(orders.amount) при условии orders.status = 'paid'"
 
 
12
  оборот: "SUM(orders.amount) по всем заказам включая отменённые"
13
- активный клиент: "клиент, совершивший хотя бы одну покупку за последние 90 дней"
14
- новый клиент: "клиент, зарегистрированный менее 30 дней назад"
15
- этот год: "YEAR(order_date) совпадает с текущим годом"
16
- прошлый месяц: "месяц предшествующий текущему"
17
- этот квартал: "текущий квартал календарного года (Q1=янв-март, Q2=апр-июн и т.д.)"
18
- средний чек: "AVG(orders.amount) по оплаченным заказам"
19
- конверсия: "доля оплаченных заказов от общего числа"
 
 
 
 
 
 
 
 
 
 
20
 
21
- # Стандартные условия фильтрации (применяются по умолчанию если аналитик явно не указал иное)
22
  filters:
23
  только_оплаченные: "orders.status = 'paid'"
24
- без_возвратов: "orders.is_return = 0 или orders.is_return IS NULL"
25
  только_активные_товары: "products.is_active = 1"
26
 
27
- # Дополнительные правила и особенности схемы
28
  notes:
29
- - "Таблица orders содержит все заказы. Колонка amount сумма в рублях."
30
- - "Клиенты хранятся в таблице customers, товары в products."
31
- - "Связь заказ-товар через таб��ицу order_items (order_id, product_id, quantity, price)."
32
- - "Даты хранятся в формате YYYY-MM-DD в колонке order_date."
33
- - "Менеджеры хранятся в таблице managers, связь с заказами через orders.manager_id."
 
 
 
 
 
1
+ # Бизнес-словарь для демо-базы (data/demo/sales.sqlite)
2
+ # Загружается автоматически при подключении к демо-базе.
3
+ #
4
+ # Скопируй этот файл и переименуй под свою компанию.
5
+ # Затем загрузи через боковую панель → «Применить словарь».
6
 
7
+ company: "Демо: Интернет-магазин электроники"
8
+
9
+ # ──────────────────────────────────────────────
10
+ # СХЕМА ДЕМО-БАЗЫ (справка)
11
+ # ──────────────────────────────────────────────
12
+ # customers : id, name, city, phone, email, created_at
13
+ # managers : id, name, department
14
+ # products : id, name, category, price, is_active
15
+ # orders : id, customer_id, manager_id, AMOUNT, status, order_date
16
+ # order_items: id, order_id, product_id, quantity, price
17
+ #
18
+ # orders.amount = итоговая сумма заказа (рублей)
19
+ # order_items.price = цена единицы товара
20
+ # order_items.quantity = количество штук
21
+ # Итог позиции = order_items.quantity * order_items.price
22
+ # В order_items НЕТ колонки amount!
23
 
 
 
 
24
  terms:
25
+ выручка: >
26
+ SUM(orders.amount) WHERE orders.status = 'paid'.
27
+ Колонка amount есть только в таблице orders, не в order_items.
28
  оборот: "SUM(orders.amount) по всем заказам включая отменённые"
29
+ средний чек: "AVG(orders.amount) WHERE orders.status = 'paid'"
30
+ топ клиентов: >
31
+ GROUP BY customers.id, customers.name ORDER BY SUM(orders.amount) DESC LIMIT N.
32
+ JOIN customers ON orders.customer_id = customers.id WHERE orders.status = 'paid'.
33
+ топ товаров: >
34
+ GROUP BY products.name ORDER BY SUM(order_items.quantity) DESC LIMIT N.
35
+ JOIN order_items ON orders.id = order_items.order_id WHERE orders.status = 'paid'.
36
+ выручка по товарам: >
37
+ SUM(order_items.quantity * order_items.price) GROUP BY products.name.
38
+ Итог позиции считается как quantity * price из order_items.
39
+ количество заказов: "COUNT(*) FROM orders WHERE status = 'paid'"
40
+ активный товар: "products.is_active = 1"
41
+ этот год: "strftime('%Y', orders.order_date) = strftime('%Y', 'now')"
42
+ прошлый год: "strftime('%Y', orders.order_date) = CAST(strftime('%Y', 'now') - 1 AS TEXT)"
43
+ этот месяц: "strftime('%Y-%m', orders.order_date) = strftime('%Y-%m', 'now')"
44
+ конверсия: >
45
+ CAST(SUM(CASE WHEN status='paid' THEN 1 ELSE 0 END) AS REAL) / COUNT(*) * 100 FROM orders.
46
 
 
47
  filters:
48
  только_оплаченные: "orders.status = 'paid'"
 
49
  только_активные_товары: "products.is_active = 1"
50
 
 
51
  notes:
52
+ - "КРИТИЧНО: колонка 'amount' существует ТОЛЬКО в таблице 'orders'. В 'order_items' её НЕТ."
53
+ - "order_items содержит колонки: id, order_id, product_id, quantity, price. Никакой колонки amount."
54
+ - "Стоимость позиции заказа = order_items.quantity * order_items.price (отдельной колонки нет)."
55
+ - "Даты хранятся как TEXT 'YYYY-MM-DD'. Для фильтрации используй BETWEEN или strftime()."
56
+ - "Статусы заказов: 'paid' (оплачен), 'pending' (ожидает), 'cancelled' (отменён)."
57
+ - "Связи: orders.customer_id → customers.id, orders.manager_id → managers.id."
58
+ - "Связи: order_items.order_id → orders.id, order_items.product_id → products.id."
59
+ - "База данных: SQLite. Функции дат: strftime('%Y', date_col), date('now', '-N days')."
60
+ - "База содержит данные за 2025-01-01 — 2026-04-30, 8 клиентов, 4 менеджера, 10 товаров."
configs/sales_vocabulary.yaml ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Бизнес-словарь для демо-базы sales.sqlite
2
+ # Загрузи этот файл в приложении через боковую панель → «Применить словарь»
3
+
4
+ company: "Демо: Интернет-магазин электроники"
5
+
6
+ # ──────────────────────────────────────────────
7
+ # СХЕМА (справочная часть — влияет на промпт)
8
+ # ──────────────────────────────────────────────
9
+ #
10
+ # customers : id, name, city, phone, email, created_at
11
+ # managers : id, name, department
12
+ # products : id, name, category, price, is_active
13
+ # orders : id, customer_id, manager_id, AMOUNT, status, order_date
14
+ # order_items: id, order_id, product_id, quantity, price
15
+ #
16
+ # ВАЖНО:
17
+ # orders.amount — итоговая сумма заказа в рублях (считается как сумма позиций)
18
+ # order_items.price — цена единицы товара (НЕ итог позиции)
19
+ # order_items.quantity — количество штук
20
+ # Итог позиции = order_items.quantity * order_items.price
21
+ # В order_items НЕТ колонки amount — только в orders!
22
+
23
+ # ──────────────────────────────────────────────
24
+ # БИЗНЕС-ТЕРМИНЫ
25
+ # ──────────────────────────────────────────────
26
+ terms:
27
+ выручка: >
28
+ SUM(orders.amount) WHERE orders.status = 'paid'.
29
+ Используй таблицу orders, колонку amount. НЕ используй order_items для расчёта выручки если не нужна детализация по товарам.
30
+ оборот: >
31
+ SUM(orders.amount) по всем заказам независимо от статуса.
32
+ средний чек: >
33
+ AVG(orders.amount) WHERE orders.status = 'paid'.
34
+ количество заказов: >
35
+ COUNT(*) FROM orders. Если нужны только оплаченные — добавь WHERE status = 'paid'.
36
+ топ клиентов: >
37
+ GROUP BY customers.id, customers.name ORDER BY SUM(orders.amount) DESC LIMIT N.
38
+ JOIN orders ON orders.customer_id = customers.id WHERE orders.status = 'paid'.
39
+ топ товаров: >
40
+ GROUP BY products.id, products.name ORDER BY SUM(order_items.quantity) DESC LIMIT N.
41
+ JOIN order_items ON order_items.product_id = products.id
42
+ JOIN orders ON orders.id = order_items.order_id WHERE orders.status = 'paid'.
43
+ выручка по товару: >
44
+ SUM(order_items.quantity * order_items.price) GROUP BY products.name.
45
+ JOIN orders ON orders.id = order_items.order_id WHERE orders.status = 'paid'.
46
+ активный товар: "products.is_active = 1"
47
+ новый клиент: >
48
+ customers.created_at >= date('now', '-30 days').
49
+ этот год: "strftime('%Y', orders.order_date) = strftime('%Y', 'now')"
50
+ прошлый год: "strftime('%Y', orders.order_date) = CAST(strftime('%Y', 'now') - 1 AS TEXT)"
51
+ этот месяц: >
52
+ strftime('%Y-%m', orders.order_date) = strftime('%Y-%m', 'now').
53
+ январь 2025: "orders.order_date BETWEEN '2025-01-01' AND '2025-01-31'"
54
+ за период: "orders.order_date BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'"
55
+ конверсия: >
56
+ CAST(SUM(CASE WHEN status='paid' THEN 1 ELSE 0 END) AS REAL) / COUNT(*) * 100 FROM orders.
57
+
58
+ # ──────────────────────────────────────────────
59
+ # СТАНДАРТНЫЕ ФИЛЬТРЫ
60
+ # ──────────────────────────────────────────────
61
+ filters:
62
+ только_оплаченные: "orders.status = 'paid'"
63
+ только_активные_товары: "products.is_active = 1"
64
+
65
+ # ──────────────────────────────────────────────
66
+ # КРИТИЧЕСКИЕ ПРАВИЛА СХЕМЫ
67
+ # ──────────────────────────────────────────────
68
+ notes:
69
+ - "КРИТИЧНО: колонка 'amount' существует ТОЛЬКО в таблице 'orders'. В 'order_items' её НЕТ."
70
+ - "order_items содержит: id, order_id, product_id, quantity, price. Никаких других колонок."
71
+ - "Стоимость позиции заказа = order_items.quantity * order_items.price (не отдельная колонка)."
72
+ - "orders.amount — это уже посчитанная итоговая сумма всего заказа в рублях."
73
+ - "Даты хранятся как TEXT в формате 'YYYY-MM-DD'. Используй strftime() или BETWEEN для фильтрации."
74
+ - "Статусы заказов: 'paid' (оплачен), 'pending' (ожидает оплаты), 'cancelled' (отменён)."
75
+ - "Для выручки по умолчанию фильтруй orders.status = 'paid'."
76
+ - "Связи: orders.customer_id → customers.id, orders.manager_id → managers.id."
77
+ - "Связи: order_items.order_id → orders.id, order_items.product_id → products.id."
78
+ - "is_active: 1 = товар продаётся, 0 = снят с продажи (например, Принтер HP LaserJet)."
79
+ - "База содержит данные за период 2025-01-01 — 2026-04-30."
data/demo/create_demo_db.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Скрипт создания демо-базы данных для Ru2SQL.
3
+
4
+ Запуск из корня проекта:
5
+ python data/demo/create_demo_db.py
6
+ """
7
+
8
+ import random
9
+ import sqlite3
10
+ from datetime import date, timedelta
11
+ from pathlib import Path
12
+
13
+ DB_PATH = Path(__file__).parent / "sales.sqlite"
14
+
15
+
16
+ def create_db():
17
+ if DB_PATH.exists():
18
+ DB_PATH.unlink()
19
+
20
+ conn = sqlite3.connect(DB_PATH)
21
+ cur = conn.cursor()
22
+
23
+ cur.executescript("""
24
+ CREATE TABLE customers (
25
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26
+ name TEXT NOT NULL,
27
+ city TEXT,
28
+ phone TEXT,
29
+ email TEXT,
30
+ created_at TEXT DEFAULT (date('now'))
31
+ );
32
+
33
+ CREATE TABLE managers (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ name TEXT NOT NULL,
36
+ department TEXT
37
+ );
38
+
39
+ CREATE TABLE products (
40
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41
+ name TEXT NOT NULL,
42
+ category TEXT,
43
+ price REAL NOT NULL,
44
+ is_active INTEGER DEFAULT 1
45
+ );
46
+
47
+ -- amount — итоговая сумма заказа в рублях (не путать с order_items.price)
48
+ CREATE TABLE orders (
49
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
50
+ customer_id INTEGER REFERENCES customers(id),
51
+ manager_id INTEGER REFERENCES managers(id),
52
+ amount REAL NOT NULL,
53
+ status TEXT DEFAULT 'paid',
54
+ order_date TEXT NOT NULL
55
+ );
56
+
57
+ -- price — цена за единицу товара; quantity — количество штук
58
+ CREATE TABLE order_items (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ order_id INTEGER REFERENCES orders(id),
61
+ product_id INTEGER REFERENCES products(id),
62
+ quantity INTEGER NOT NULL,
63
+ price REAL NOT NULL
64
+ );
65
+ """)
66
+
67
+ customers = [
68
+ ("ООО Ромашка", "Москва", "+7 495 111-22-33", "romashka@example.com"),
69
+ ("ИП Петров", "Санкт-Петербург", "+7 812 333-44-55", "petrov@example.com"),
70
+ ("Сеть магазинов Радуга","Казань", "+7 843 555-66-77", "raduga@example.com"),
71
+ ("АО Стройснаб", "Екатеринбург", "+7 343 777-88-99", "stroysnab@example.com"),
72
+ ("ООО Технолайн", "Новосибирск", "+7 383 999-00-11", "technoline@example.com"),
73
+ ("ИП Сидорова", "Москва", "+7 495 222-33-44", "sidorova@example.com"),
74
+ ("ООО Прогресс", "Краснодар", "+7 861 444-55-66", "progress@example.com"),
75
+ ("ЗАО Мегатрейд", "Самара", "+7 846 666-77-88", "megatrade@example.com"),
76
+ ]
77
+ cur.executemany(
78
+ "INSERT INTO customers (name, city, phone, email) VALUES (?,?,?,?)",
79
+ customers,
80
+ )
81
+
82
+ managers = [
83
+ ("Иванов Алексей", "Продажи"),
84
+ ("Смирнова Мария", "Продажи"),
85
+ ("Козлов Дмитрий", "Корпоративные клиенты"),
86
+ ("Новикова Анна", "Корпоративные клиенты"),
87
+ ]
88
+ cur.executemany("INSERT INTO managers (name, department) VALUES (?,?)", managers)
89
+
90
+ products_data = [
91
+ ("Ноутбук Dell XPS 15", "Электроника", 89990.0, 1),
92
+ ("Монитор LG 27inch", "Электроника", 24990.0, 1),
93
+ ("Клавиатура Logitech MX", "Периферия", 8990.0, 1),
94
+ ("Мышь Logitech G502", "Периферия", 5490.0, 1),
95
+ ("Наушники Sony WH-1000XM5", "Аудио", 29990.0, 1),
96
+ ("Веб-камера Logitech C920", "Периферия", 7990.0, 1),
97
+ ("SSD Samsung 1TB", "Комплектующие", 6990.0, 1),
98
+ ("Принтер HP LaserJet", "Оргтехника", 18990.0, 0),
99
+ ("Роутер TP-Link AX3000", "Сетевое оборудование", 4990.0, 1),
100
+ ("ИБП APC 1500VA", "Сетевое оборудование",14990.0, 1),
101
+ ]
102
+ cur.executemany(
103
+ "INSERT INTO products (name, category, price, is_active) VALUES (?,?,?,?)",
104
+ products_data,
105
+ )
106
+
107
+ random.seed(42)
108
+ start = date(2025, 1, 1)
109
+ end = date(2026, 4, 30)
110
+ delta = (end - start).days
111
+ statuses = ["paid", "paid", "paid", "pending", "cancelled"]
112
+
113
+ order_id = 1
114
+ items_rows = []
115
+ for _ in range(120):
116
+ d = start + timedelta(days=random.randint(0, delta))
117
+ customer_id = random.randint(1, 8)
118
+ manager_id = random.randint(1, 4)
119
+ status = random.choice(statuses)
120
+ n_items = random.randint(1, 4)
121
+ picked = random.sample(range(1, 11), n_items)
122
+ total = 0.0
123
+ for prod_id in picked:
124
+ qty = random.randint(1, 5)
125
+ price = products_data[prod_id - 1][2]
126
+ total += qty * price
127
+ items_rows.append((order_id, prod_id, qty, price))
128
+ cur.execute(
129
+ "INSERT INTO orders (customer_id, manager_id, amount, status, order_date) "
130
+ "VALUES (?,?,?,?,?)",
131
+ (customer_id, manager_id, round(total, 2), status, d.isoformat()),
132
+ )
133
+ order_id += 1
134
+
135
+ cur.executemany(
136
+ "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?,?,?,?)",
137
+ items_rows,
138
+ )
139
+
140
+ conn.commit()
141
+ conn.close()
142
+
143
+ print(f"База создана: {DB_PATH}")
144
+ conn2 = sqlite3.connect(DB_PATH)
145
+ for tbl in ["customers", "managers", "products", "orders", "order_items"]:
146
+ cnt = conn2.execute(f"SELECT COUNT(*) FROM {tbl}").fetchone()[0]
147
+ print(f" {tbl}: {cnt} строк")
148
+ conn2.close()
149
+
150
+
151
+ if __name__ == "__main__":
152
+ create_db()
data/demo/sales.sqlite CHANGED
Binary files a/data/demo/sales.sqlite and b/data/demo/sales.sqlite differ
 
streamlit_app.py CHANGED
@@ -188,6 +188,16 @@ with st.sidebar:
188
  st.session_state.db_connector = connector
189
  st.session_state.db_executor = executor
190
  st.session_state.db_connection_string = cs
 
 
 
 
 
 
 
 
 
 
191
  st.success(f"Подключено! Таблиц: {len(tables)}")
192
  except Exception as e:
193
  st.error(f"Ошибка подключения: {e}")
 
188
  st.session_state.db_connector = connector
189
  st.session_state.db_executor = executor
190
  st.session_state.db_connection_string = cs
191
+ # Автоматически применяем словарь для демо-базы
192
+ if "sales" in cs and st.session_state.vocabulary is None:
193
+ try:
194
+ demo_vocab_path = ROOT / "configs" / "example_vocabulary.yaml"
195
+ if demo_vocab_path.exists():
196
+ st.session_state.vocabulary = _load_vocab_from_yaml(
197
+ demo_vocab_path.read_text(encoding="utf-8")
198
+ )
199
+ except Exception:
200
+ pass
201
  st.success(f"Подключено! Таблиц: {len(tables)}")
202
  except Exception as e:
203
  st.error(f"Ошибка подключения: {e}")