Skip to main content
Glama
ibezgachev

sales-analytics

by ibezgachev
README.md
# sales-analytics-mcp

Прототип аналитической системы на базе LLM: MCP-сервер и набор скиллов,
через которые модель загружает табличные данные (CSV/Excel/JSON),
очищает их, строит графики и пишет отчёт с выводами.

Собственного чат-интерфейса здесь нет и не предполагается — сервер
подключается к готовому клиенту (Claude Desktop), и всю цепочку вызовов
ведёт сама модель.

Ключевое архитектурное решение: **датафрейм не пересекает границу LLM**.
`load_data` кладёт данные в session store и возвращает короткий
`dataset_id`; все остальные инструменты принимают этот id, а не сами
данные. Обоснование и замеры — в [ARCHITECTURE.md](ARCHITECTURE.md).

- Пример сгенерированного отчёта: [reports/sample_report.md](reports/sample_report.md)
- Графики: [charts/](charts/)
- Описание архитектуры: [ARCHITECTURE.md](ARCHITECTURE.md)
- Спецификация инструментов для REST-интеграций: [openapi.json](openapi.json)

## Стек

Python 3.11+, [FastMCP](https://gofastmcp.com) (транспорты stdio и
streamable-http), pandas, matplotlib + seaborn (статичные PNG),
openpyxl, ruff, pytest.

## Установка

```bash
git clone https://github.com/ibezgachev/sales-analytics-mcp.git
cd sales-analytics-mcp

python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate

pip install -e ".[dev]"
```

Проверка, что всё встало:

```bash
pytest
ruff check .
```

## Запуск

Обычно сервер запускать руками не нужно — MCP-клиент делает это сам (см.
следующий раздел). Ручной запуск полезен, чтобы убедиться, что сервер
стартует без ошибок.

```bash
# транспорт stdio — для локальных клиентов вроде Claude Desktop
python server_stdio.py

# транспорт streamable-http — http://127.0.0.1:8000/mcp
python server_http.py
```

Оба файла собирают один и тот же набор инструментов через
`core.mcp_app.build_mcp_server()`; различается только транспорт.

## Подключение к Claude Desktop

> ### ⚠️ Где на самом деле лежит `claude_desktop_config.json`
>
> Стандартный путь `%APPDATA%\Claude\claude_desktop_config.json` верен
> **не для всех установок**. Если Claude Desktop поставлен как
> приложение из Microsoft Store (MSIX-пакет), этой папки не существует
> вовсе, а конфиг лежит в песочнице пакета:
>
> ```
> %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json
> ```
>
> Симптом: правишь файл по «правильному» пути (или создаёшь его) — и
> сервер не появляется в клиенте, сколько ни перезапускай. На поиск этого
> легко потерять полчаса и решить, что проект не работает.
>
> Надёжный способ определить свой вариант — найти файл по имени:
>
> ```powershell
> Get-ChildItem -Path $env:LOCALAPPDATA,$env:APPDATA -Recurse -Filter claude_desktop_config.json -ErrorAction SilentlyContinue
> ```

Добавьте в конфиг блок `mcpServers` (если файл уже есть — допишите ключ
`sales-analytics` внутрь существующего `mcpServers`, не затирая
остальное):

```json
{
  "mcpServers": {
    "sales-analytics": {
      "command": "C:\\путь\\к\\проекту\\.venv\\Scripts\\python.exe",
      "args": ["C:\\путь\\к\\проекту\\server_stdio.py"]
    }
  }
}
```

Пути — абсолютные, обратные слэши экранированы. На Linux/macOS —
`/путь/к/проекту/.venv/bin/python` без экранирования.

После правки **полностью завершите приложение** (через трей или
диспетчер задач — закрытия окна недостаточно) и запустите заново.
Проверить: в списке инструментов клиента должен появиться
`sales-analytics` с 13 инструментами.

## Инструменты

| Инструмент | Назначение |
|---|---|
| `load_data` | Загрузка CSV/Excel/JSON, автоопределение кодировки, разделителя и формата дат. Возвращает `dataset_id` и сводку |
| `describe_data` | Статистика по типу колонки: числа, категории, даты |
| `clean_data` | Дубли, пропуски, нормализация текста, выбросы по IQR. Возвращает новый `dataset_id` и лог операций |
| `aggregate` | Сводная таблица числами, без построения графика |
| `plot_trend` | Динамика числовой колонки по месяцам |
| `plot_distribution` | Гистограмма распределения |
| `correlation_analysis` | Тепловая карта корреляций |
| `plot_top_n` | Горизонтальная столбчатая диаграмма топ-N категорий |
| `auto_analyze` | Сам выбирает тип графика по типу колонки |
| `analyze_seasonality` | Распределение показателя по календарным месяцам и кварталам |
| `list_datasets` | Какие `dataset_id` доступны в этой сессии |
| `prepare_insights_context` | Собирает статистику, лог очистки и описания графиков в материал для отчёта |
| `export_report` | Сохраняет готовый текст отчёта в `reports/` |

Первые пять имён из ТЗ (`load_data`, `describe_data`, `plot_trend`,
`plot_distribution`, `correlation_analysis`) сохранены дословно.

Каждый график возвращает **путь к PNG и текстовое описание того, что на
нём видно** — модель не видит изображение, и без описания не смогла бы
сослаться на график в отчёте. Почему это оказалось критично и что
выяснилось при проверке — в [ARCHITECTURE.md](ARCHITECTURE.md).

## Пример диалога

Системный промпт с последовательностью шагов лежит в
[prompts/system_prompt.md](prompts/system_prompt.md) и дублируется как
MCP-примитив `prompt` с именем `sales_analysis_workflow` — клиент может
подтянуть его сам.

Первое сообщение может быть таким:

```
Проанализируй данные о продажах из файла
C:\путь\к\проекту\data\sales_data.csv

Загрузи их, посмотри структуру, почисти от дефектов, построй графики
и дай развёрнутый отчёт с выводами и практическими рекомендациями.
```

Дальше модель ведёт цепочку сама:
`load_data` → `describe_data` → `clean_data` → графики →
`prepare_insights_context` → `export_report`.

Что получилось на выходе — [reports/sample_report.md](reports/sample_report.md).

Скриншоты диалога: [docs/screenshots/](docs/screenshots/) — прогон
проведён в чистом чате, без подгрузки системного промпта, только по
описаниям инструментов.

## Тестовые данные

`data/sales_data.csv` — синтетический датасет (180 строк, 2023–2024), в
который **намеренно заложены дефекты**: пропуски, дубли, выбросы,
разнобой в форматах дат и в написании регионов. Без них очистке нечего
было бы чистить.

Точный состав дефектов с количествами — [data/README.md](data/README.md);
этот файл служит эталоном при проверке очистки.

Пересоздать (воспроизводимо, `random_state` зафиксирован):

```bash
python scripts/generate_data.py
```

## Интеграция через OpenAPI

[openapi.json](openapi.json) представляет каждый MCP-инструмент как
`POST /tools/{name}` с той же JSON-схемой параметров, которую видит
модель. Это не спецификация HTTP-маршрутов `server_http.py` (тот говорит
на протоколе MCP, а не на обычном REST), а совместимое представление для
интеграций, которым нужен именно OpenAPI — например, Custom GPT Action.

Живой публичный HTTPS-эндпоинт в рамках задания не разворачивался, это
осознанное ограничение — см. ARCHITECTURE.md.

Перегенерировать после добавления скилла:

```bash
python scripts/generate_openapi.py
```

## Разработка

```bash
ruff check .          # линтер
ruff format .         # форматтер
pytest                # тесты
```

Добавление нового скилла — это один новый файл в `skills/`; правок в
`core/` и в серверных точках входа не требуется. Как именно — в разделе
«Как добавить новый скилл» в [ARCHITECTURE.md](ARCHITECTURE.md).

## Лицензия

[MIT](LICENSE).

### Расширяемость подтверждается диффом, а не декларацией

Последний скилл — `analyze_seasonality` — добавлен намеренно отдельно
от остальных, уже после того как система была написана и
задокументирована, именно чтобы это можно было проверить.

```bash
git show --stat "$(git log --format=%H --grep='скилл анализа сезонности' -1)"
```

В этом коммите — ровно два файла: `skills/seasonality.py` и правка
таблицы инструментов в `README.md`. Ни строки в `core/`, ни строки в
`server_stdio.py` и `server_http.py`. При этом после перезапуска клиента
инструмент появляется в списке тринадцатым, со схемой параметров,
построенной из сигнатуры и докстринга.

(Тесты на скилл добавлены следующим коммитом — отдельно, чтобы дифф
коммита-доказательства оставался минимальным и его можно было
прочитать целиком за полминуты.)