Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: CI

on:
push:
pull_request:
branches: [main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
name: Ruff (lint + format)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Set up Python
run: uv python install 3.10

- name: Install dependencies
run: uv sync --locked --all-extras --dev

- name: Ruff check
run: uv run ruff check .

- name: Ruff format check
run: uv run ruff format --check .

test:
name: pytest (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --locked --all-extras --dev

- name: Run tests
run: uv run pytest -n auto

- name: Upload coverage to Codecov
if: matrix.python-version == '3.10'
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
fail_ci_if_error: false
23 changes: 20 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
data/
src/__pycache__/
# data
/data/
db.sqlite3

# python and venvs
__pycache__
.python-version
.venv
db.sqlite3


# pytest
.pytest_cache/


# Coverage
.coverage
.coverage.*
coverage.xml
htmlcov/

# Ruff
.ruff_cache/
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
# Классификация приоритета обращений

Автоматизация процесса приоритизации инцидентов для IT-сервис-деска с использованием методов машинного обучения.

<!-- Meta & Language -->
[![License](https://img.shields.io/github/license/yrmint/ml-app-arch)](#)
[![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white)](#)

<!-- Data manipulation -->
[![Pandas](https://img.shields.io/badge/Pandas-150458?logo=pandas&logoColor=white)](#)
[![NumPy](https://img.shields.io/badge/NumPy-013243?logo=numpy&logoColor=white)](#)

<!-- Machine Learning & Data Science -->
[![Scikit-learn](https://img.shields.io/badge/Scikit--learn-F7931E?logo=scikit-learn&logoColor=white)](#)
[![XGBoost](https://img.shields.io/badge/XGBoost-EB5424?logo=xgboost&logoColor=white)](#)
[![Optuna](https://img.shields.io/badge/Optuna-002C76?logo=optuna&logoColor=white)](#)
[![SHAP](https://img.shields.io/badge/SHAP-1D4ED8?logo=shap&logoColor=white)](#)

[![Git](https://img.shields.io/badge/Git-F05032?logo=git&logoColor=fff)](#)
[![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-2088FF?logo=github-actions&logoColor=white)](#)
[![Pytest](https://img.shields.io/badge/Pytest-fff?logo=pytest&logoColor=000)](#)
[![uv](https://img.shields.io/badge/uv-Lightning_Fast-261230?logo=python&logoColor=white)](#)

# Классификация приоритета обращений

Автоматизация процесса приоритизации инцидентов для IT-сервис-деска с использованием методов машинного обучения.

## Обзор проекта

Проект предназначен для автоматического назначения приоритета (Low, Medium, High) входящим тикетам на основе их характеристик
Expand All @@ -33,7 +42,7 @@

### 1. Предобработка и EDA
Проведен разведочный анализ данных: выявлены асимметрии в распределениях и скоррелированные признаки.
- Мультиколлинеарность: удален признак org_users (corr = 0.89 с company_size_cat) для повышения устойчивости моделей.
- Мультиколлинеарность: удален признак org_users (corr = 0.94 с company_size_cat) для повышения устойчивости моделей.
- Удалены неинформативные идентификаторы (ID).
- Разделение данных выполнено с использованием stratify для сохранения баланса классов в выборках (Train 70% / Val 15% / Test 15%).

Expand All @@ -43,13 +52,16 @@
- RandomForestClassifier
- HistGradientBoostingClassifier
- XGBClassifier

Оптимизация гиперпараметров проводилась с помощью Optuna (50 trials) с кросс-валидацией. Целевой метрикой для оптимизации была выбрана F1-macro, так как она объективно оценивает качество работы с миноритарным классом (High priority).

### 3. Оценка и интерпретация
- Лучшая модель - XGBoostClassifier:
- F1-Score (macro) = 0.98
- Анализ матрицы ошибок (Confusion Matrix) показал, что модель успешно справляется с минимизацией бизнес-рисков — критические инциденты (High) никогда не классифицируются как низкоприоритетные (Low).
![alt text](./assets/confusion_matrix_xgboost.png)
- Для объяснения логики модели применен метод SHAP. Анализ показал, что модель опирается в первую очередь на измеримые последствия инцидента (customers_affected, downtime_min, error_rate_pct), а фоновые характеристики выступают как уточняющие сигналы. Это полностью согласуется с реальной бизнес-логикой.
![alt text](./assets/shap.png)

## Установка и запуск

Expand All @@ -69,13 +81,27 @@ uv python install 3.10
```

```commandline
uv sync
uv sync --all-extras --dev
```

### 3. Обучение и инференс модели

[support_tickets.ipynb](src/support_tickets.ipynb)

### 4. Тестирование

Ruff (linter and formatting):
```commandline
uv run ruff check . --fix
```
```commandline
uv run ruff format .
```
Pytests:
```
uv run pytest -n auto
```

## Идея для развития проекта:

Создать искусственный поток данных на основе датасета, который бы проходил через обработку (ETL), и подавался в контейнезированную ML-систему с airflow+spark+mlflow+rabbitmq/kafka
Expand Down Expand Up @@ -136,6 +162,6 @@ uv sync
- Измерить задержки ETL и инференса.
- Оценить масштабируемость решения.

## Итоговый результат
### Итоговый результат
Контейнеризированная потоковая MLOps-платформа, демонстрирующая полный цикл:
Генерация данных → Kafka/RabbitMQ → Spark ETL → Feature Engineering → Обучение XGBoost → MLflow Registry → API инференса → Мониторинг и обучение.
Binary file added assets/confusion_matrix_xgboost.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/shap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 52 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "supporttickets-prioritization"
version = "0.1.0"
description = "Add your description here"
description = "Приоритизация тикетов службы поддержки методами ML"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
Expand All @@ -16,3 +16,54 @@ dependencies = [
"shap>=0.49.1",
"xgboost==2.1.4",
]

[dependency-groups]
dev = [
"ruff>=0.8.0",
"pytest>=9.1.1",
"pytest-cov>=7.1.0",
"pytest-xdist>=3.8.0",
"pandera>=0.32.0",
"pytest-mock>=3.15.1",
]

[tool.ruff]
line-length = 100
target-version = "py310"
extend-exclude = ["notebooks", ".ipynb_checkpoints"]

[tool.ruff.lint]
# E/W — pycodestyle, F — pyflakes, I — isort, B — bugbear,
# UP — pyupgrade, N — pep8-naming, C4 — comprehensions
select = ["E", "W", "F", "I", "B", "UP", "N", "C4", "SIM"]
ignore = ["E501","N803", "N806"] # E501: длина строки — на усмотрение fromatter'а
# N803,N806: X, X_val и гиперпарметр C допустимы не в lowercase

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["N802", "N803"] # в тестах можно CamelCase имена

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.pytest.ini_options]
minversion = "9.1.1"
testpaths = ["tests"]
pythonpath = ["."]
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"--cov=src",
"--cov-report=term-missing",
"--cov-report=xml",
"--cov-fail-under=60",
"-p no:cacheprovider",
]
markers = [
"smoke: быстрые проверки импорта и структуры",
"data: тесты схемы и распределения данных",
"model: тесты качества модели и метрик",
"invariant: инвариантные тесты (свойства, которые всегда выполняются)",
"slow: долгие тесты",
]
Loading
Loading