mirror of
https://github.com/EDeev/mobiles_dataset.git
synced 2026-10-07 20:49:44 +03:00
Compare commits
8 commits
fe5e71a248
...
7277093a13
| Author | SHA1 | Date | |
|---|---|---|---|
| 7277093a13 | |||
| 2daa52a238 | |||
| 3be7052b0a | |||
| 2c26b2fe65 | |||
| 891a0e9a3c | |||
| 20a36a7d1c | |||
| acc9e6b207 | |||
| 227b491d96 |
20 changed files with 4551 additions and 6917 deletions
5
.gitattributes
vendored
Normal file
5
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
* text=auto eol=lf
|
||||
*.png binary
|
||||
*.pdf binary
|
||||
*.docx binary
|
||||
*.pgerd text
|
||||
36
.github/workflows/ci.yml
vendored
Normal file
36
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports: ["5432:5432"]
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U postgres" --health-interval 5s --health-retries 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- run: pip install ruff==0.14.0 psycopg2-binary==2.9.9 pandas==2.1.4 numpy==1.26.2
|
||||
- name: Ruff (синтаксис и ошибки)
|
||||
run: ruff check --select E9,F .
|
||||
- name: Схема и импорт датасета
|
||||
env:
|
||||
PGPASSWORD: postgres
|
||||
DB_USER: postgres
|
||||
DB_PASSWORD: postgres
|
||||
run: |
|
||||
psql -h localhost -U postgres -f sql/create_schema.sql
|
||||
python scripts/import_data.py
|
||||
test "$(psql -h localhost -U postgres -d mobile_devices_db -tAc 'select count(*) from models')" -gt 900
|
||||
23
.github/workflows/release.yml
vendored
Normal file
23
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
|
||||
jobs:
|
||||
exe:
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- run: pip install pyinstaller psycopg2-binary==2.9.9 PyQt6==6.6.1
|
||||
- name: Сборка .exe из однофайловой версии
|
||||
run: pyinstaller --onefile --windowed --name MobileDevices scripts/exe/mobile_devices.py
|
||||
- name: Файл в релиз
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh release upload "${{ github.ref_name }}" dist/MobileDevices.exe --clobber
|
||||
File diff suppressed because it is too large
Load diff
76
README.en.md
Normal file
76
README.en.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Mobile Devices Database
|
||||
|
||||
[Русский](README.md) · **English**
|
||||
|
||||
[](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml)
|
||||
[](https://github.com/EDeev/mobiles_dataset/releases)
|
||||
|
||||
Database design coursework: the 2025 mobile devices dataset split into a normalized PostgreSQL database,
|
||||
a PyQt6 desktop app to work with it, and a measurement of index gains with `EXPLAIN ANALYZE`.
|
||||
|
||||
**Status:** coursework ("Database Design and Administration", Moscow Polytechnic University, 2025), completed
|
||||
|
||||

|
||||
|
||||
**Stack:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
|
||||
|
||||
## Features
|
||||
|
||||
- 3NF schema of five tables (companies, processors, models, regions, prices) with foreign keys and
|
||||
cascading actions
|
||||
- Import of the [Kaggle dataset](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
|
||||
(930 rows; 914 models and 4569 prices after cleaning) split into reference tables
|
||||
- App: adding companies, models with create, edit and delete, prices in five regions (Pakistan, India,
|
||||
China, UAE, USA) with currency symbols, search by name, company and RAM, a price statistics tab
|
||||
- Index experiment: `EXPLAIN ANALYZE` queries before and after, plans saved in `sql/explain_results/`
|
||||
|
||||
| Metric | No indexes | With indexes | Difference |
|
||||
|---|---|---|---|
|
||||
| Search | 0.234 ms | 0.089 ms | 62% faster |
|
||||
| Four-table JOIN | 18.6 ms | 0.95 ms | 95% faster |
|
||||
| Planner cost | 44.76 | 12.45 | 72% lower |
|
||||
| Rows scanned | 914 | 18 | 98% fewer |
|
||||
|
||||
Measured on this dataset (hundreds of rows), so absolute times are fractions of a millisecond.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
git clone https://github.com/EDeev/mobiles_dataset.git && cd mobiles_dataset
|
||||
docker compose up -d # PostgreSQL 15 with the schema from sql/create_schema.sql
|
||||
pip install -r requirements.txt
|
||||
python scripts/import_data.py # load the dataset
|
||||
python main.py # the app
|
||||
```
|
||||
|
||||
A ready-made Windows build (`.exe`) is in the [releases](https://github.com/EDeev/mobiles_dataset/releases).
|
||||
|
||||
## Without Docker
|
||||
|
||||
1. Run `sql/create_schema.sql` in psql or pgAdmin as `postgres` — the script creates the
|
||||
`mobile_devices_db` database itself.
|
||||
2. Set the connection with `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
|
||||
(defaults: `localhost:5432`, `mobile_devices_db`, `admin` / `password`).
|
||||
3. `python scripts/import_data.py`, then `python main.py`.
|
||||
|
||||
## Screenshots
|
||||
|
||||
| Companies | Analytics |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
## License
|
||||
|
||||
Coursework (Database Design and Administration, Moscow Polytechnic University, 2025). The code is open
|
||||
for study; there is no separate license. The dataset comes from Kaggle under its author's terms.
|
||||
|
||||
## Author
|
||||
|
||||
**Egor Deev** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<sub>⭐ If you find this project useful, give it a star on GitHub!</sub>
|
||||
<p><sub>Made with ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
|
||||
</div>
|
||||
331
README.md
331
README.md
|
|
@ -1,290 +1,89 @@
|
|||
# 📱 Mobile Devices Database Management System
|
||||
# Mobile Devices Database
|
||||
|
||||
**Комплексная система управления каталогом мобильных устройств с GUI-интерфейсом**
|
||||
**Русский** · [English](README.en.md)
|
||||
|
||||
[](https://python.org)
|
||||
[](https://postgresql.org)
|
||||
[](https://pypi.org/project/PyQt6/)
|
||||
[](#)
|
||||
[](https://github.com/EDeev/mobiles_dataset/actions/workflows/ci.yml)
|
||||
[](https://github.com/EDeev/mobiles_dataset/releases)
|
||||
|
||||
## 🎯 Назначение проекта
|
||||
Курсовая по проектированию баз данных: датасет мобильных устройств 2025 года, разложенный в
|
||||
нормализованную базу PostgreSQL, десктопное приложение на PyQt6 для работы с ним и измерение
|
||||
выигрыша от индексов через `EXPLAIN ANALYZE`.
|
||||
|
||||
Курсовая работа по дисциплине "Проектирование и администрирование баз данных", представляющая собой полнофункциональную информационную систему для управления данными о мобильных устройствах. Проект демонстрирует практическое применение принципов реляционного моделирования, оптимизации производительности СУБД и создания современных desktop-приложений.
|
||||
**Статус:** учебный проект (курсовая «Проектирование и администрирование баз данных», Московский
|
||||
Политех, 2025), завершён
|
||||
|
||||
## 🏗️ Архитектура системы
|
||||

|
||||
|
||||
### Компонентная структура
|
||||
```
|
||||
📦 mobile-devices-db/
|
||||
├── database.py # Слой доступа к данным (DAO)
|
||||
├── main_window.py # GUI-интерфейс на PyQt6
|
||||
├── main.py # Запускаемый файл
|
||||
├── Mobiles Dataset 2025.csv # Дата-сет данных
|
||||
├── 🗃️ sql/ # SQL-скрипты и схема БД
|
||||
│ ├── create_schema.sql # Создание нормализованной структуры
|
||||
│ └── performance_analysis.sql # Тестовые запросы для анализа
|
||||
├── 🐍 scripts/ # Python-модули системы
|
||||
│ └── import_data.py # Система импорта и нормализации
|
||||
├── 📊 docs/ # Техническая документация
|
||||
└── 📋 requirements.txt # Зависимости проекта
|
||||
**Стек:** Python 3.11 · PostgreSQL 15 · psycopg2 · PyQt6 · pandas · Docker
|
||||
|
||||
## Возможности
|
||||
|
||||
- Схема в 3НФ из пяти таблиц (компании, процессоры, модели, регионы, цены) с внешними ключами и
|
||||
каскадными операциями
|
||||
- Импорт [датасета с Kaggle](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
|
||||
(930 строк, после очистки 914 моделей и 4569 цен) с разнесением по справочникам
|
||||
- Приложение: добавление компаний, модели с добавлением, правкой и удалением, цены по пяти регионам (Пакистан,
|
||||
Индия, Китай, ОАЭ, США) с символами валют, поиск по названию, компании и RAM, вкладка статистики цен
|
||||
- Эксперимент с индексами: запросы `EXPLAIN ANALYZE` до и после, планы сохранены в `sql/explain_results/`
|
||||
|
||||
| Метрика | Без индексов | С индексами | Разница |
|
||||
|---|---|---|---|
|
||||
| Поиск | 0,234 мс | 0,089 мс | быстрее на 62 % |
|
||||
| JOIN четырёх таблиц | 18,6 мс | 0,95 мс | быстрее на 95 % |
|
||||
| Стоимость по планировщику | 44,76 | 12,45 | ниже на 72 % |
|
||||
| Просмотренные строки | 914 | 18 | меньше на 98 % |
|
||||
|
||||
Измерено на этом датасете (сотни строк), поэтому абсолютные времена — доли миллисекунды.
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```bash
|
||||
git clone https://github.com/EDeev/mobiles_dataset.git && cd mobiles_dataset
|
||||
docker compose up -d # PostgreSQL 15 со схемой из sql/create_schema.sql
|
||||
pip install -r requirements.txt
|
||||
python scripts/import_data.py # загрузка датасета
|
||||
python main.py # приложение
|
||||
```
|
||||
|
||||
### Технологический стек
|
||||
- **СУБД**: PostgreSQL 15.x с расширенными возможностями индексирования
|
||||
- **Backend**: Python 3.11+ с паттерном Singleton для управления соединениями
|
||||
- **Frontend**: PyQt6 с архитектурой Model-View-Controller
|
||||
- **Интеграция**: psycopg2 для типобезопасного взаимодействия с БД
|
||||
Готовая сборка для Windows (`.exe`) — в [релизах](https://github.com/EDeev/mobiles_dataset/releases).
|
||||
|
||||
## ⚡ Ключевые особенности
|
||||
## Без Docker
|
||||
|
||||
### 🔧 Техническая реализация
|
||||
- **Нормализация до 3НФ**: Декомпозиция исходного CSV в 5 взаимосвязанных таблиц
|
||||
- **Референциальная целостность**: Система FK с каскадными операциями
|
||||
- **Оптимизация производительности**: Стратегическое индексирование с 62-95% улучшением
|
||||
- **CRUD-функционал**: Полный спектр операций через интуитивный GUI
|
||||
1. Выполните `sql/create_schema.sql` в psql или pgAdmin под пользователем `postgres` — скрипт сам
|
||||
создаёт базу `mobile_devices_db`.
|
||||
2. Задайте подключение переменными `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
|
||||
(по умолчанию `localhost:5432`, `mobile_devices_db`, `admin` / `password`).
|
||||
3. `python scripts/import_data.py`, затем `python main.py`.
|
||||
|
||||
### 📈 Результаты оптимизации
|
||||
| Метрика | До оптимизации | После оптимизации | Улучшение |
|
||||
|---------|----------------|-------------------|-----------|
|
||||
| Время выполнения поиска | 0.234 мс | 0.089 мс | **62% быстрее** |
|
||||
| JOIN-запросы (4 таблицы) | 18.6 мс | 0.95 мс | **95% быстрее** |
|
||||
| Стоимость по планировщику | 44.76 ед. | 12.45 ед. | **72% снижение** |
|
||||
| Обработанные строки | 914 (фильтрация) | 18 (прямой доступ) | **98% сокращение** |
|
||||
## Как выглядит
|
||||
|
||||
### 🌍 Функциональные возможности
|
||||
- **Интеллектуальный поиск** по множественным атрибутам устройств
|
||||
- **Валютная локализация** ценовой информации (5 регионов)
|
||||
- **Автоматический импорт** данных с нормализацией на лету
|
||||
- **Аналитические отчеты** с региональной статистикой
|
||||
| Компании | Аналитика |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
## 📊 Источники данных
|
||||
## Структура
|
||||
|
||||
Проект использует актуальный датасет мобильных устройств 2025 года:
|
||||
- **Источник**: [Kaggle - Mobiles Dataset 2025](https://www.kaggle.com/datasets/abdulmalik1518/mobiles-dataset-2025)
|
||||
- **Размер**: 1000+ записей устройств
|
||||
- **Охват**: Основные производители (Apple, Samsung, Xiaomi, OnePlus, etc.)
|
||||
- **Регионы**: Пакистан, Индия, Китай, США, ОАЭ
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### Системные требования
|
||||
- **Python 3.11+** с поддержкой venv
|
||||
- **PostgreSQL 15.x** с административными правами
|
||||
- **4GB RAM** (рекомендуется 8GB для больших наборов данных)
|
||||
|
||||
### Установка и настройка
|
||||
|
||||
1. **Клонирование репозитория**
|
||||
```bash
|
||||
git clone https://github.com/username/mobile-devices-db.git
|
||||
cd mobile-devices-db
|
||||
```
|
||||
|
||||
2. **Создание виртуального окружения**
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/macOS
|
||||
# или
|
||||
venv\Scripts\activate # Windows
|
||||
```
|
||||
|
||||
3. **Установка зависимостей**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. **Настройка PostgreSQL**
|
||||
```sql
|
||||
-- Создание пользователя и БД
|
||||
CREATE USER admin WITH PASSWORD 'your_password';
|
||||
CREATE DATABASE mobile_devices_db OWNER admin;
|
||||
GRANT ALL PRIVILEGES ON DATABASE mobile_devices_db TO admin;
|
||||
```
|
||||
|
||||
5. **Развертывание схемы данных**
|
||||
```bash
|
||||
psql -U admin -d mobile_devices_db -f sql/create_schema.sql
|
||||
```
|
||||
|
||||
6. **Импорт тестовых данных**
|
||||
```bash
|
||||
python scripts/import_data.py
|
||||
```
|
||||
|
||||
7. **Запуск приложения**
|
||||
```bash
|
||||
python scripts/main_window.py
|
||||
```
|
||||
|
||||
## 📊 Структура базы данных
|
||||
|
||||
### Реляционная модель (3НФ)
|
||||
```mermaid
|
||||
erDiagram
|
||||
COMPANIES ||--o{ MODELS : manufactures
|
||||
PROCESSORS ||--o{ MODELS : uses
|
||||
MODELS ||--o{ PRICES : "has prices in"
|
||||
REGIONS ||--o{ PRICES : "contains prices"
|
||||
|
||||
COMPANIES {
|
||||
int company_id PK
|
||||
varchar company_name UK
|
||||
}
|
||||
|
||||
MODELS {
|
||||
int model_id PK
|
||||
varchar model_name
|
||||
int company_id FK
|
||||
int processor_id FK
|
||||
varchar ram
|
||||
varchar battery_capacity
|
||||
int launched_year
|
||||
}
|
||||
|
||||
PRICES {
|
||||
int price_id PK
|
||||
int model_id FK
|
||||
int region_id FK
|
||||
decimal price
|
||||
varchar currency
|
||||
}
|
||||
```
|
||||
database.py доступ к БД (синглтон, параметризованные запросы)
|
||||
main_window.py, main.py приложение на PyQt6
|
||||
scripts/import_data.py импорт и нормализация датасета
|
||||
scripts/exe/ однофайловая версия приложения для сборки .exe
|
||||
sql/ схема, запросы для анализа производительности, ERD, результаты EXPLAIN
|
||||
docs/report.* отчёт по курсовой (md, docx, pdf)
|
||||
```
|
||||
|
||||
### Ключевые таблицы
|
||||
- **Companies**: Справочник производителей (19 уникальных компаний)
|
||||
- **Models**: Каталог устройств (914 моделей с техническими характеристиками)
|
||||
- **Processors**: Справочник процессоров (217 уникальных чипсетов)
|
||||
- **Regions**: Географические регионы ценообразования (5 регионов)
|
||||
- **Prices**: Региональная ценовая информация (4,569 записей)
|
||||
## Лицензия
|
||||
|
||||
## 🎨 Пользовательский интерфейс
|
||||
Учебный проект (Проектирование и администрирование баз данных, Московский Политех, 2025). Код открыт
|
||||
для изучения, отдельной лицензии нет. Датасет — с Kaggle, на условиях его автора.
|
||||
|
||||
### Основные компоненты
|
||||
- **📋 Каталог компаний**: Обзор производителей с количеством моделей
|
||||
- **📱 Управление моделями**: CRUD-операции с техническими характеристиками
|
||||
- **💰 Ценовое управление**: Редактирование цен с валютной локализацией
|
||||
- **📊 Аналитическая панель**: Региональная статистика и отчеты
|
||||
## Автор
|
||||
|
||||
### Функциональные возможности
|
||||
```python
|
||||
# Пример поискового запроса
|
||||
def search_models(self, search_text: str):
|
||||
"""Интеллектуальный поиск по множественным атрибутам"""
|
||||
return self.db.search_models(search_text)
|
||||
|
||||
# Валютная локализация
|
||||
CURRENCY_MAP = {
|
||||
'Pakistan': ('PKR', '₨'),
|
||||
'India': ('INR', '₹'),
|
||||
'China': ('CNY', '¥'),
|
||||
'USA': ('USD', '$'),
|
||||
'Dubai': ('AED', 'د.إ')
|
||||
}
|
||||
```
|
||||
|
||||
## 🔍 Анализ производительности
|
||||
|
||||
### Методология тестирования
|
||||
Комплексный анализ производительности выполнен с использованием PostgreSQL EXPLAIN ANALYZE:
|
||||
|
||||
#### Тестовые сценарии
|
||||
1. **Простые селективные запросы** - поиск по названию компании
|
||||
2. **Многотабличные JOIN** - получение полной информации об устройствах
|
||||
3. **Агрегирующие операции** - статистические запросы с группировкой
|
||||
4. **Поиск по характеристикам** - фильтрация по техническим параметрам
|
||||
|
||||
#### Стратегия оптимизации
|
||||
```sql
|
||||
-- Базовые B-tree индексы
|
||||
CREATE INDEX idx_companies_name ON companies(company_name);
|
||||
CREATE INDEX idx_models_company_id ON models(company_id);
|
||||
|
||||
-- Составные индексы для комплексных запросов
|
||||
CREATE INDEX idx_models_ram_battery ON models(ram, battery_capacity);
|
||||
|
||||
-- Функциональные индексы для LIKE-операций
|
||||
CREATE INDEX idx_companies_name_pattern ON companies(company_name varchar_pattern_ops);
|
||||
```
|
||||
|
||||
## 📈 Практические результаты
|
||||
|
||||
### Количественные показатели
|
||||
- **Импорт данных**: 930 записей устройств с 100% целостностью
|
||||
- **Нормализация**: 60% сокращение избыточности данных
|
||||
- **Производительность**: 95% улучшение для сложных JOIN-запросов
|
||||
- **Индексирование**: 72% снижение стоимости по планировщику
|
||||
|
||||
### Качественные достижения
|
||||
- Полная автоматизация процесса нормализации данных
|
||||
- Интуитивный GUI с поддержкой валютной локализации
|
||||
- Масштабируемая архитектура для enterprise-развертывания
|
||||
- Комплексная система мониторинга производительности
|
||||
|
||||
## 🔧 Документация
|
||||
|
||||
- **[Техническая документация](docs/README.md)** - Подробное описание архитектуры и API
|
||||
- **[Отчет по курсовой работе](docs/report.md)** - Полный академический отчет
|
||||
- **[SQL-скрипты](sql/)** - Схемы БД и запросы для анализа производительности
|
||||
|
||||
### Конфигурация базы данных
|
||||
```python
|
||||
# Параметры подключения
|
||||
DATABASE_CONFIG = {
|
||||
'host': 'localhost',
|
||||
'port': 5432,
|
||||
'database': 'mobile_devices_db',
|
||||
'user': 'admin',
|
||||
'password': 'your_password'
|
||||
}
|
||||
```
|
||||
|
||||
### Основные зависимости
|
||||
```
|
||||
psycopg2-binary==2.9.9 # PostgreSQL adapter
|
||||
PyQt6==6.6.1 # GUI framework
|
||||
pandas==2.1.4 # Data processing
|
||||
numpy==1.26.2 # Numerical computing
|
||||
```
|
||||
|
||||
## 🚀 Перспективы развития
|
||||
|
||||
### Краткосрочные цели
|
||||
- **Веб-интерфейс**: Реализация REST API на FastAPI
|
||||
- **Автоматизация**: Интеграция с внешними API для обновления каталога
|
||||
- **Мониторинг**: Real-time дашборды производительности
|
||||
|
||||
### Долгосрочная стратегия
|
||||
- **Machine Learning**: Прогнозирование ценовых тенденций
|
||||
- **Микросервисы**: Архитектурная модернизация для cloud-deployment
|
||||
- **Аналитика**: Интеграция с Apache Kafka для stream-processing
|
||||
|
||||
## 📚 Академическая ценность
|
||||
|
||||
Проект демонстрирует практическое применение ключевых концепций:
|
||||
|
||||
### Теоретические основы
|
||||
- **Нормализация реляционных данных** до третьей нормальной формы
|
||||
- **Оптимизация производительности СУБД** через стратегическое индексирование
|
||||
- **Архитектурные паттерны** для desktop-приложений с базами данных
|
||||
|
||||
### Практические навыки
|
||||
- Проектирование и реализация нормализованных схем данных
|
||||
- Анализ и оптимизация производительности SQL-запросов
|
||||
- Создание многокомпонентных приложений с GUI-интерфейсом
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Этот проект создан в учебных целях как курсовая работа по дисциплине "Проектирование и администрирование баз данных" в Московском Политехническом Университете, 2024-2025 учебный год.
|
||||
|
||||
## 👨💻 Автор
|
||||
|
||||
**Деев Егор Викторович** - Backend Developer
|
||||
- GitHub: [@EDeev](https://github.com/EDeev)
|
||||
- Email: egor@deev.space
|
||||
- Telegram: [@Egor_Deev](https://t.me/Egor_Deev)
|
||||
**Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p>
|
||||
<sub>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
|
||||
<p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
|
||||
</div>
|
||||
|
|
|
|||
20
compose.yaml
Normal file
20
compose.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Локальный PostgreSQL 15 со схемой курсовой: docker compose up -d
|
||||
services:
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./docker/00-roles.sql:/docker-entrypoint-initdb.d/00-roles.sql:ro
|
||||
- ./sql/create_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql:ro
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d mobile_devices_db"]
|
||||
interval: 5s
|
||||
retries: 20
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
10
database.py
10
database.py
|
|
@ -1,4 +1,5 @@
|
|||
# db/database.py
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
|
@ -20,8 +21,13 @@ class Database:
|
|||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self, host='localhost', port=5432, database='mobile_devices_db',
|
||||
user='admin', password='password'):
|
||||
def __init__(self, host=None, port=None, database=None, user=None, password=None):
|
||||
# Параметры подключения: аргументы, затем переменные окружения DB_*, затем значения для локальной разработки
|
||||
host = host or os.environ.get('DB_HOST', 'localhost')
|
||||
port = port or int(os.environ.get('DB_PORT', '5432'))
|
||||
database = database or os.environ.get('DB_NAME', 'mobile_devices_db')
|
||||
user = user or os.environ.get('DB_USER', 'admin')
|
||||
password = password or os.environ.get('DB_PASSWORD', 'password')
|
||||
if not hasattr(self, 'initialized'):
|
||||
self.connection_params = {
|
||||
'host': host,
|
||||
|
|
|
|||
2
docker/00-roles.sql
Normal file
2
docker/00-roles.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
-- Роль приложения для локального стенда (значения по умолчанию из database.py)
|
||||
CREATE ROLE admin LOGIN SUPERUSER PASSWORD 'password';
|
||||
2374
docs/README.md
2374
docs/README.md
File diff suppressed because it is too large
Load diff
4746
docs/report.md
4746
docs/report.md
File diff suppressed because it is too large
Load diff
BIN
docs/screenshots/analytics.png
Normal file
BIN
docs/screenshots/analytics.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
docs/screenshots/companies.png
Normal file
BIN
docs/screenshots/companies.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/screenshots/models.png
Normal file
BIN
docs/screenshots/models.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
1
main.py
1
main.py
|
|
@ -1,7 +1,6 @@
|
|||
import sys
|
||||
import logging
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PyQt6.QtCore import Qt
|
||||
|
||||
from main_window import MainWindow
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
# ui/main_window.py
|
||||
import sys
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QTableWidget, QTableWidgetItem, QPushButton, QTabWidget,
|
||||
QLabel, QLineEdit, QComboBox, QSpinBox, QMessageBox,
|
||||
QDialog, QFormLayout, QDialogButtonBox, QHeaderView,
|
||||
QToolBar, QStatusBar, QGroupBox, QTextEdit, QInputDialog
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
||||
from PyQt6.QtGui import QAction, QIcon, QFont
|
||||
from typing import Optional, Dict, Any
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QAction, QFont
|
||||
from typing import Dict, Any
|
||||
import logging
|
||||
|
||||
# Импортируем наш модуль БД
|
||||
|
|
@ -190,6 +189,9 @@ class PriceDialog(QDialog):
|
|||
def load_prices(self):
|
||||
"""Загрузка цен из БД"""
|
||||
prices = self.db.get_model_prices(self.model_id)
|
||||
# При включённой сортировке строки переставляются прямо во время заполнения,
|
||||
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
|
||||
self.prices_table.setSortingEnabled(False)
|
||||
self.prices_table.setRowCount(len(prices))
|
||||
|
||||
for row, price_data in enumerate(prices):
|
||||
|
|
@ -214,6 +216,9 @@ class PriceDialog(QDialog):
|
|||
delete_btn = QPushButton("Удалить")
|
||||
delete_btn.clicked.connect(lambda checked, pid=price_data['price_id']: self.delete_price(pid))
|
||||
self.prices_table.setCellWidget(row, 3, delete_btn)
|
||||
# сортировку возвращаем только после заполнения (см. начало функции)
|
||||
self.prices_table.setSortingEnabled(True)
|
||||
|
||||
|
||||
def add_update_price(self):
|
||||
"""Добавление или обновление цены"""
|
||||
|
|
@ -425,6 +430,9 @@ class MainWindow(QMainWindow):
|
|||
def load_companies(self):
|
||||
"""Загрузка списка компаний"""
|
||||
companies = self.db.get_all_companies()
|
||||
# При включённой сортировке строки переставляются прямо во время заполнения,
|
||||
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
|
||||
self.companies_table.setSortingEnabled(False)
|
||||
self.companies_table.setRowCount(len(companies))
|
||||
|
||||
for row, company in enumerate(companies):
|
||||
|
|
@ -442,6 +450,9 @@ class MainWindow(QMainWindow):
|
|||
count_item = QTableWidgetItem(str(company['models_count']))
|
||||
count_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.companies_table.setItem(row, 2, count_item)
|
||||
# сортировку возвращаем только после заполнения (см. начало функции)
|
||||
self.companies_table.setSortingEnabled(True)
|
||||
|
||||
|
||||
def load_models(self, search_text=""):
|
||||
"""Загрузка списка моделей"""
|
||||
|
|
@ -450,6 +461,9 @@ class MainWindow(QMainWindow):
|
|||
else:
|
||||
models = self.db.get_all_models()
|
||||
|
||||
# При включённой сортировке строки переставляются прямо во время заполнения,
|
||||
# и кнопки строки оказываются привязаны к чужим записям — выключаем её на время
|
||||
self.models_table.setSortingEnabled(False)
|
||||
self.models_table.setRowCount(len(models))
|
||||
|
||||
for row, model in enumerate(models):
|
||||
|
|
@ -508,6 +522,9 @@ class MainWindow(QMainWindow):
|
|||
delete_btn = QPushButton("🗑️ Удалить")
|
||||
delete_btn.clicked.connect(lambda checked, mid=model['model_id']: self.delete_model(mid))
|
||||
self.models_table.setCellWidget(row, 10, delete_btn)
|
||||
# сортировку возвращаем только после заполнения (см. начало функции)
|
||||
self.models_table.setSortingEnabled(True)
|
||||
|
||||
|
||||
def add_company(self):
|
||||
"""Добавление новой компании"""
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import sys
|
||||
import pandas as pd
|
||||
import psycopg2
|
||||
from psycopg2.extras import execute_values
|
||||
import re
|
||||
from typing import Optional, Dict, Tuple
|
||||
from typing import Optional, Dict
|
||||
import logging
|
||||
|
||||
# Настройка логирования
|
||||
|
|
@ -245,12 +246,13 @@ class MobileDataImporter:
|
|||
# Использование скрипта
|
||||
if __name__ == "__main__":
|
||||
# Конфигурация подключения к БД
|
||||
# Параметры подключения — из переменных окружения DB_* (по умолчанию — локальная разработка)
|
||||
db_config = {
|
||||
'host': 'localhost',
|
||||
'port': 5432,
|
||||
'database': 'mobile_devices_db',
|
||||
'user': 'admin',
|
||||
'password': 'password' # Замените на ваш пароль
|
||||
'host': os.environ.get('DB_HOST', 'localhost'),
|
||||
'port': int(os.environ.get('DB_PORT', '5432')),
|
||||
'database': os.environ.get('DB_NAME', 'mobile_devices_db'),
|
||||
'user': os.environ.get('DB_USER', 'admin'),
|
||||
'password': os.environ.get('DB_PASSWORD', 'password'),
|
||||
}
|
||||
|
||||
# Создаем импортер и выполняем импорт
|
||||
|
|
@ -258,6 +260,9 @@ if __name__ == "__main__":
|
|||
|
||||
try:
|
||||
importer.connect()
|
||||
importer.import_data('Mobiles Dataset 2025.csv') # Укажите путь к вашему файлу
|
||||
# Датасет лежит в корне репозитория; другой файл можно передать первым аргументом
|
||||
csv_path = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'Mobiles Dataset 2025.csv')
|
||||
importer.import_data(csv_path)
|
||||
finally:
|
||||
importer.disconnect()
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
"QUERY PLAN"
|
||||
"Sort (cost=44.76..44.84 rows=31 width=245) (actual time=0.216..0.217 rows=18 loops=1)"
|
||||
" Sort Key: c.company_name, m.model_name"
|
||||
" Sort Method: quicksort Memory: 25kB"
|
||||
" -> Hash Join (cost=17.20..43.99 rows=31 width=245) (actual time=0.055..0.180 rows=18 loops=1)"
|
||||
" Hash Cond: (m.company_id = c.company_id)"
|
||||
" -> Seq Scan on models m (cost=0.00..26.71 rows=31 width=31) (actual time=0.038..0.159 rows=18 loops=1)"
|
||||
" Filter: (((ram)::text ~~ '%8GB%'::text) AND ((battery_capacity)::text ~~ '%5000%'::text))"
|
||||
" Rows Removed by Filter: 896"
|
||||
" -> Hash (cost=13.20..13.20 rows=320 width=222) (actual time=0.010..0.010 rows=19 loops=1)"
|
||||
" Buckets: 1024 Batches: 1 Memory Usage: 9kB"
|
||||
" -> Seq Scan on companies c (cost=0.00..13.20 rows=320 width=222) (actual time=0.004..0.005 rows=19 loops=1)"
|
||||
"Planning Time: 0.155 ms"
|
||||
"Execution Time: 0.234 ms"
|
||||
"QUERY PLAN"
|
||||
"Sort (cost=44.76..44.84 rows=31 width=245) (actual time=0.216..0.217 rows=18 loops=1)"
|
||||
" Sort Key: c.company_name, m.model_name"
|
||||
" Sort Method: quicksort Memory: 25kB"
|
||||
" -> Hash Join (cost=17.20..43.99 rows=31 width=245) (actual time=0.055..0.180 rows=18 loops=1)"
|
||||
" Hash Cond: (m.company_id = c.company_id)"
|
||||
" -> Seq Scan on models m (cost=0.00..26.71 rows=31 width=31) (actual time=0.038..0.159 rows=18 loops=1)"
|
||||
" Filter: (((ram)::text ~~ '%8GB%'::text) AND ((battery_capacity)::text ~~ '%5000%'::text))"
|
||||
" Rows Removed by Filter: 896"
|
||||
" -> Hash (cost=13.20..13.20 rows=320 width=222) (actual time=0.010..0.010 rows=19 loops=1)"
|
||||
" Buckets: 1024 Batches: 1 Memory Usage: 9kB"
|
||||
" -> Seq Scan on companies c (cost=0.00..13.20 rows=320 width=222) (actual time=0.004..0.005 rows=19 loops=1)"
|
||||
"Planning Time: 0.155 ms"
|
||||
"Execution Time: 0.234 ms"
|
||||
|
|
|
|||
|
|
|
@ -1,6 +1,6 @@
|
|||
"schemaname","n_tup_ins","n_tup_upd","n_tup_del","seq_scan","seq_tup_read","idx_scan","idx_tup_fetch"
|
||||
"public","19","0","0","33","589","934","915"
|
||||
"public","914","0","0","32","27420","5520","4702"
|
||||
"public","4569","0","0","26","109656","4750","565"
|
||||
"public","5","0","0","9","30","4594","4594"
|
||||
"public","217","0","0","8","1302","1141","924"
|
||||
"schemaname","n_tup_ins","n_tup_upd","n_tup_del","seq_scan","seq_tup_read","idx_scan","idx_tup_fetch"
|
||||
"public","19","0","0","33","589","934","915"
|
||||
"public","914","0","0","32","27420","5520","4702"
|
||||
"public","4569","0","0","26","109656","4750","565"
|
||||
"public","5","0","0","9","30","4594","4594"
|
||||
"public","217","0","0","8","1302","1141","924"
|
||||
|
|
|
|||
|
Loading…
Add table
Reference in a new issue