Files
test/backlog_buget_old.md
2026-01-16 18:37:32 +03:00

736 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SMS → n8n → LLM → БД/Telegram: принятые решения и контракты данных
Контекст: автоматизация "Семейный бюджет". Источник данных — банковские SMS, которые пересылаются в n8n вебхуком, далее парсятся и обогащаются (LLM), затем пишутся в БД и/или отправляются уведомления в Telegram.
---
## СТАТУС ПРОЕКТА
### ✅ ВЫПОЛНЕНО (100% основного функционала)
#### Инфраструктура
- [x] PostgreSQL 16 развернут в Docker на Synology NAS (порт 5433)
- [x] pgAdmin настроен (порт 5050)
- [x] Таблица `transactions` создана и расширена полями для LLM и human-in-the-loop
- [x] n8n запущен в Docker-контейнере
- [x] Telegram-бот создан через @BotFather
- [x] Telegram credentials настроены в n8n (Base URL: <https://api.telegram.org>)
#### n8n Workflow - Основная ветка обработки SMS
- [x] Webhook (POST) настроен для приёма SMS
- [x] Auth check (проверка x-api-key заголовка)
- [x] Code node (parse_sms) для парсинга SMS → NormalizedTransaction JSON
- [x] PostgreSQL INSERT (create_from_sms) для записи базовых данных транзакции
- Always Output Data = ON для получения id
- [x] Edit Fields (Set) для data minimization перед LLM
- [x] HTTP Request (post_to_llm) для обращения к LM Studio API
- [x] Code node (parse_from_llm) для парсинга JSON-ответа от LLM
- Очистка markdown обёртки (```json)
- Merge с originalData из create_from_sms
- Исправлена ошибка: choices.message → choices[0].message
- [x] PostgreSQL UPDATE (update_llm_fields) для записи обогащённых данных
- counterparty, category, subcategory
- llm_processed_at
- human_verified = FALSE (пока не подтверждено)
- [x] Telegram Send Message (edit_or_confirm) для отправки карточки транзакции с кнопками
- Inline Keyboard: Подтвердить / Изменить категорию / Изменить контрагента
#### LLM - Локальная модель
- [x] LM Studio установлен и настроен
- [x] Модель Qwen2.5-7B-Instruct (Q6_K, 6 ГБ) загружена
- [x] API-сервер запущен (порт 1234, Serve on Local Network включен)
- [x] Параметры модели оптимизированы (Context: 8192, Temperature: 0.2, Max Tokens: 512)
- [x] Промпт улучшен для корректной работы с неизвестными контрагентами
- [x] Обработка markdown-обёртки в ответах (```json)
#### Human-in-the-loop через Telegram - ПОЛНОСТЬЮ ЗАВЕРШЕНО ✅
**Единый Telegram Trigger (решена проблема конфликта webhook):**
- [x] Один answer_trigger для ВСЕХ событий (callback_query + message)
- [x] Решена проблема 403 Forbidden при регистрации webhook
- [x] Telegram Credential настроен с Base URL = <https://api.telegram.org>
- [x] IF node (split_event_type) для разделения типов событий
**TRUE ветка (подтверждение) - ЗАВЕРШЕНА:**
- [x] Code node (parse_from_callback) для парсинга callback_data
- Извлечение: action, field, transactionId, chatId, messageId, messageText
- Определение isEditedCard (проверка текста "не сохранено")
- [x] IF node (confirm_or_edit) для разделения подтверждения/редактирования
- [x] IF node (check_if_edited) для определения источника подтверждения
- TRUE: подтверждение после редактирования → parse_card_text
- FALSE: первое подтверждение без изменений → row_select_by_id
- [x] Code node (parse_card_text) для извлечения данных из текста карточки
- [x] Postgres UPDATE (update_edited_transaction) для сохранения отредактированных данных
- counterparty, category, human_verified = TRUE
- [x] Postgres SELECT (row_select_by_id) для получения данных транзакции
- [x] Postgres UPDATE (update_after_confirm) для первого подтверждения
- human_verified = TRUE
- human_verified_at = timestamp
- [x] Merge node (merge_confirm_paths) для объединения двух путей подтверждения
- [x] Telegram Answer Callback Query (popup_confirm) для всплывающего уведомления
- [x] Telegram Edit Message Text (confirmation_message) для обновления сообщения
**FALSE ветка (редактирование) - ЗАВЕРШЕНА:**
- [x] IF node (which_field_to_edit) для определения редактируемого поля
- [x] Telegram Send Message (category_edit) для запроса новой категории
- С Reply To Message ID для связи с транзакцией
- Напоминание о лимите 12 символов
- [x] Telegram Send Message (counterparty_edit) для запроса нового контрагента
- С Reply To Message ID для связи с транзакцией
- Напоминание о лимите 12 символов
- [x] IF node (split_event_type) после единого триггера
- isEmpty(callback_query) = TRUE → текстовое сообщение → parse_text_input
- isEmpty(callback_query) = FALSE → callback от кнопки → parse_from_callback
- [x] Code node (parse_text_input) для парсинга ответа пользователя
- Извлечение transactionId из reply_to_message (#123)
- Определение changedField по ключевым словам
- Игнорирование команд (/)
- Output: newValue, chatId, transactionId, changedField
- [x] Postgres SELECT (get_current_transaction) для получения текущих данных
- [x] Code node (merge_changes) для применения изменений
- Merge новых и существующих данных
- Не пишет в БД (временное состояние)
- [x] Telegram Send Message (show_updated_card) для отображения обновлённой карточки
- Маркер "не сохранено" для определения источника
- Те же три кнопки для продолжения редактирования
- ЦИКЛ ЗАМЫКАЕТСЯ: можно редактировать оба поля многократно
### 🔄 ОСТАЛОСЬ СДЕЛАТЬ
#### 1. Интеграция с Google Sheets (следующий приоритет)
- [ ] Создать Google Sheets таблицу для семейного бюджета
- [ ] Настроить Google Sheets API в n8n (Service Account)
- [ ] Добавить Google Sheets node ПОСЛЕ merge_confirm_paths
- Записывать только подтверждённые транзакции
- [ ] Настроить запись строки: Дата | Контрагент | Сумма | Категория
- [ ] Форматирование: цветовое кодирование расходов/доходов
#### 2. Автоматизация запуска
- [ ] Автозапуск LM Studio при загрузке системы
- [ ] Автозапуск Docker-контейнеров (restart policy = always)
- [ ] Health-check для мониторинга LM Studio API
#### 3. Улучшения (опционально)
- [ ] Кнопка "❌ Отклонить" для удаления ошибочных транзакций
- [ ] Редактирование подкатегории
- [ ] Валидация пользовательского ввода (длина, спецсимволы)
- [ ] Обработка исторических данных 2025 года для создания базы знаний
- [ ] Fine-tuning модели на собственных размеченных данных
- [ ] Аналитика и отчёты по категориям
---
## ФИНАЛЬНАЯ АРХИТЕКТУРА WORKFLOW (05.01.2026)
### Ветка 1: Обработка входящих SMS (ЗАВЕРШЕНА)
```text
Webhook (POST)
→ Auth check (x-api-key)
→ Code (parse_sms)
→ Postgres INSERT (create_from_sms) [Always Output Data = ON]
→ Edit Fields (data minimization)
→ HTTP Request (post_to_llm)
→ Code (parse_from_llm) [choices[0].message.content, очистка markdown, merge]
→ Postgres UPDATE (update_llm_fields) [human_verified=FALSE]
→ Telegram Send Message (edit_or_confirm) [карточка с Inline Keyboard]
```
### Ветка 2: Единый Telegram Trigger (КРИТИЧНО)
```text
answer_trigger (Telegram Trigger)
Updates: callback_query + message
Base URL: https://api.telegram.org
split_event_type (IF: isEmpty(callback_query))
↓ TRUE (текстовое сообщение)
parse_text_input → get_current_transaction → merge_changes → show_updated_card
↓ FALSE (callback от кнопки)
parse_from_callback → confirm_or_edit
```
**ВАЖНО**: Только ОДИН Telegram Trigger на весь workflow! Два триггера конфликтуют за один webhook URL.
### Ветка 3: Подтверждение транзакции (ЗАВЕРШЕНА)
```text
parse_from_callback [isEditedCard = messageText.includes("не сохранено")]
confirm_or_edit (IF: action == "confirm")
↓ TRUE (подтверждение)
check_if_edited (IF: isEditedCard == true)
↓ TRUE (после редактирования)
parse_card_text → update_edited_transaction
↓ FALSE (первое подтверждение)
row_select_by_id → update_after_confirm
merge_confirm_paths (объединение двух путей)
popup_confirm (Answer Callback Query)
confirmation_message (Edit Message Text)
```
### Ветка 4: Редактирование транзакции с циклом (ЗАВЕРШЕНА)
```text
parse_from_callback
confirm_or_edit (IF: action == "confirm")
↓ FALSE (редактирование)
which_field_to_edit (IF: field == "category")
↓ TRUE
category_edit (Send Message: "Введите категорию #ID, лимит 12 символов")
↓ FALSE
counterparty_edit (Send Message: "Введите контрагента #ID, лимит 12 символов")
[Пользователь вводит текст]
answer_trigger → split_event_type → parse_text_input
get_current_transaction (SELECT текущих данных)
merge_changes (применение изменений БЕЗ записи в БД)
show_updated_card (карточка с маркером "не сохранено")
[Те же три кнопки: Подтвердить / Изменить категорию / Изменить контрагента]
[ЦИКЛ ЗАМЫКАЕТСЯ: нажатие любой кнопки → answer_trigger]
```
**Ключевая особенность**: Изменения НЕ сохраняются в БД до финального подтверждения. Данные передаются через текст карточки (лимит 12 символов на поле для влезания в callback_data при необходимости).
---
## ТЕХНИЧЕСКИЕ ДЕТАЛИ
### База данных PostgreSQL
**Connection**:
- Host: Synology NAS IP
- Port: 5433
- Database: budget
- User: postgres
- Credential в n8n: "postgres account"
**Таблица transactions:**
```sql
-- Базовые поля
id BIGSERIAL PRIMARY KEY
raw_sms TEXT NOT NULL
sms_text TEXT NOT NULL
sms_sender VARCHAR(50)
action VARCHAR(20) NOT NULL
amount NUMERIC(15, 2)
signed_amount NUMERIC(15, 2)
currency VARCHAR(3) DEFAULT 'RUB'
balance NUMERIC(15, 2)
received_at TIMESTAMP WITH TIME ZONE NOT NULL
processed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
raw_json JSONB NOT NULL
-- LLM и Human-in-the-loop поля
counterparty VARCHAR(255)
category VARCHAR(100)
subcategory VARCHAR(100)
llm_processed_at TIMESTAMP WITH TIME ZONE
human_verified BOOLEAN DEFAULT FALSE
human_verified_at TIMESTAMP WITH TIME ZONE
-- Индексы
CREATE INDEX idx_transactions_received_at ON transactions(received_at DESC);
CREATE INDEX idx_transactions_action ON transactions(action);
CREATE INDEX idx_transactions_counterparty ON transactions(counterparty);
CREATE INDEX idx_transactions_category ON transactions(category);
CREATE INDEX idx_transactions_human_verified ON transactions(human_verified) WHERE human_verified = TRUE;
```
### Telegram Configuration
**Telegram Credential (в n8n)**:
- Access Token: токен от @BotFather
- Base URL: `https://api.telegram.org` (ВАЖНО!)
**Почему именно этот Base URL**:
- НЕ пустой (не polling mode)
- НЕ адрес n8n (не webhook mode через собственный сервер)
- Стандартный API Telegram - n8n использует гибридный режим получения событий
**Проблема с двумя триггерами**:
- Telegram позволяет только ОДИН webhook на бота
- Два Telegram Trigger в одном workflow конфликтуют
- Решение: единый триггер + IF для разделения типов событий
### LLM Промпт (финальная версия)
**System Message:**
```text
Ты эксперт по категоризации расходов семейного бюджета в России. Анализируй банковские SMS и извлекай данные.
ВАЖНО:
- Если контрагент НЕИЗВЕСТЕН или непонятен - укажи confidence < 0.5
- Уверенность 1.0 ставь ТОЛЬКО для известных российских брендов
- Для неочевидных названий снижай confidence до 0.3-0.6
Известные сети: PYATEROCHKA, MAGNIT, ЗОЛОТОЕ ЯБЛОКО, WILDBERRIES, YANDEX TAXI
Для неизвестных: category=Неизвестно, confidence < 0.5
Отвечай ТОЛЬКО валидным JSON без markdown обёртки.
```
**User Message:**
```text
Проанализируй SMS: {{ $json.sms_text }}
Тип: {{ $json.action }}
Сумма: {{ $json.amount }} {{ $json.currency }}
Если контрагент неизвестен - confidence < 0.5!
Верни JSON: {"counterparty": "", "category": "", "subcategory": "", "confidence": 0.0}
```
---
## ФИНАЛЬНЫЕ CODE NODES (готовые к использованию)
### 1. parse_from_llm (ИСПРАВЛЕНА ошибка choices.message)
```javascript
// Исправлено: choices.message → choices[0].message
let content = $input.first().json.choices[0].message.content;
// Очистка markdown обёртки
content = content.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
const llmResponse = JSON.parse(content);
const originalData = $('create_from_sms').first().json;
return {
...originalData,
counterparty: llmResponse.counterparty || 'Не определён',
category: llmResponse.category || 'Неизвестно',
subcategory: llmResponse.subcategory || 'Требует уточнения',
confidence: llmResponse.confidence !== undefined ? llmResponse.confidence : 0.0
};
```
### 2. parse_from_callback (ОБНОВЛЕНА: добавлено isEditedCard)
```javascript
const callbackData = $input.first().json.callback_query.data;
const chatId = $input.first().json.callback_query.message.chat.id;
const messageId = $input.first().json.callback_query.message.message_id;
const messageText = $input.first().json.callback_query.message.text;
const parts = callbackData.split('_');
const action = parts[0]; // confirm, edit
const field = parts.length === 3 ? parts[1] : null; // category, counterparty
const transactionId = parseInt(parts[parts.length - 1]);
// Проверка: это подтверждение после редактирования?
const isEditedCard = messageText && messageText.includes('не сохранено');
return {
action: action,
field: field,
transactionId: transactionId,
chatId: chatId,
messageId: messageId,
messageText: messageText,
isEditedCard: isEditedCard,
originalCallbackData: callbackData
};
```
### 3. parse_text_input (для обработки текстовых ответов)
```javascript
const userText = $input.first().json.message.text;
const chatId = $input.first().json.message.chat.id;
const messageData = $input.first().json.message;
// Игнорируем команды
if (userText.startsWith('/')) {
throw new Error('Команда проигнорирована');
}
// Проверяем что это ответ на наше сообщение
if (!messageData.reply_to_message) {
throw new Error('Сообщение не является ответом на запрос');
}
// Извлекаем ID транзакции из текста запроса
const originalText = messageData.reply_to_message.text;
const match = originalText.match(/#(\d+)/);
if (!match) {
throw new Error('Не удалось найти ID транзакции');
}
const transactionId = parseInt(match[1]);
// Определяем какое поле пользователь меняет
let changedField = null;
if (originalText.includes('категорию')) {
changedField = 'category';
} else if (originalText.includes('контрагента')) {
changedField = 'counterparty';
}
return {
newValue: userText.trim(),
chatId: chatId,
transactionId: transactionId,
changedField: changedField
};
```
### 4. merge_changes (применение изменений без записи в БД)
```javascript
const textInput = $('parse_text_input').first().json;
const currentData = $('get_current_transaction').first().json;
// Применяем изменение
const updatedCategory = textInput.changedField === 'category'
? textInput.newValue
: currentData.category;
const updatedCounterparty = textInput.changedField === 'counterparty'
? textInput.newValue
: currentData.counterparty;
return {
...currentData,
category: updatedCategory,
counterparty: updatedCounterparty,
chatId: textInput.chatId
};
```
### 5. parse_card_text (извлечение данных из текста карточки)
```javascript
const messageText = $input.first().json.messageText;
const transactionId = $input.first().json.transactionId;
// Извлекаем контрагента (между "Контрагент: " и "\n")
const counterpartyMatch = messageText.match(/Контрагент:\s*(.+?)(?:\n|$)/);
const counterparty = counterpartyMatch ? counterpartyMatch[1].trim() : null;
// Извлекаем категорию (между "Категория: " и "\n")
const categoryMatch = messageText.match(/Категория:\s*(.+?)(?:\n|$)/);
const category = categoryMatch ? categoryMatch[1].trim() : null;
if (!counterparty || !category) {
throw new Error('Не удалось извлечь данные из карточки');
}
return {
transactionId: transactionId,
counterparty: counterparty,
category: category,
chatId: $input.first().json.chatId,
messageId: $input.first().json.messageId
};
```
### 6. prepare_confirmation_data (подготовка данных для финальных нод)
```javascript
const parseData = $('parse_card_text').first().json;
return {
transactionId: parseData.transactionId,
chatId: parseData.chatId,
messageId: parseData.messageId,
counterparty: parseData.counterparty,
category: parseData.category
};
```
---
## НАСТРОЙКИ ВСЕХ НОД
### Telegram Nodes
**answer_trigger (Telegram Trigger)**:
- Credential: Telegram account
- Trigger On: callback_query
- Updates: `callback_query`, `message` (оба!)
**edit_or_confirm (Telegram Send Message)**:
- Chat ID: `{{ $json.chatId }}`
- Text:
```text
Требуется подтверждение транзакции #{{ $json.id }}
Сумма: {{ $json.signed_amount }} {{ $json.currency }}
Контрагент: {{ $json.counterparty }}
Категория: {{ $json.category }}
Дата: {{ $json.received_at }}
```
- Reply Markup:
```json
{
"inline_keyboard": [
[{"text": "✅ Подтвердить", "callback_data": "confirm_{{ $json.id }}"}],
[{"text": "✏️ Изменить категорию", "callback_data": "edit_category_{{ $json.id }}"}],
[{"text": "✏️ Изменить контрагента", "callback_data": "edit_counterparty_{{ $json.id }}"}]
]
}
```
**category_edit (Telegram Send Message)**:
- Chat ID: `{{ $json.chatId }}`
- Text: `Введите новую категорию для транзакции #{{ $json.transactionId }}\n\nВАЖНО: Лимит 12 символов`
- Reply To Message ID: `{{ $json.messageId }}`
**counterparty_edit (Telegram Send Message)**:
- Chat ID: `{{ $json.chatId }}`
- Text: `Введите нового контрагента для транзакции #{{ $json.transactionId }}\n\nВАЖНО: Лимит 12 символов`
- Reply To Message ID: `{{ $json.messageId }}`
**show_updated_card (Telegram Send Message)**:
- Chat ID: `{{ $json.chatId }}`
- Text:
```text
Данные обновлены (не сохранено)
Сумма: {{ $json.signed_amount }} {{ $json.currency }}
Контрагент: {{ $json.counterparty }}
Категория: {{ $json.category }}
Дата: {{ $json.received_at }}
ВАЖНО: Лимит 12 символов на поле
```
- Reply Markup: (те же три кнопки что и в edit_or_confirm)
**popup_confirm (Telegram Answer Callback Query)**:
- Query ID: `{{ $json.callback_query.id }}`
- Text: `✅ Транзакция подтверждена и сохранена`
**confirmation_message (Telegram Edit Message Text)**:
- Chat ID: `{{ $json.chatId }}`
- Message ID: `{{ $json.messageId }}`
- Text: `✅ Транзакция #{{ $json.transactionId }} подтверждена\n\nКонтрагент: {{ $json.counterparty }}\nКатегория: {{ $json.category }}`
### PostgreSQL Nodes
**create_from_sms (Postgres INSERT)**:
- Operation: Insert
- Table: transactions
- Columns: все базовые поля из parse_sms
- Options: Always Output Data = ON
**update_llm_fields (Postgres UPDATE)**:
- Operation: Update
- Table: transactions
- Columns: counterparty, category, subcategory, llm_processed_at, human_verified=FALSE
- Where: id = {{ $json.id }}
- Options: Always Output Data = ON
**get_current_transaction (Postgres SELECT)**:
- Operation: Select
- Table: transactions
- Return All: OFF
- Where: id = {{ $json.transactionId }}
- Options: Always Output Data = ON
**update_edited_transaction (Postgres UPDATE)**:
- Operation: Update
- Table: transactions
- Columns:
- counterparty = {{ $json.counterparty }}
- category = {{ $json.category }}
- human_verified = true (toggle ON)
- Where: id = {{ $json.transactionId }}
**row_select_by_id (Postgres SELECT)**:
- Operation: Select
- Table: transactions
- Return All: OFF
- Where: id = {{ $json.transactionId }}
**update_after_confirm (Postgres UPDATE)**:
- Operation: Update
- Table: transactions
- Columns:
- human_verified = true
- human_verified_at = {{ $now }}
- Where: id = {{ $json.transactionId }}
### IF Nodes
**split_event_type (разделение типов событий)**:
- Condition: `{{ $json.callback_query }}` Is Empty
- TRUE → текстовое сообщение → parse_text_input
- FALSE → callback от кнопки → parse_from_callback
**confirm_or_edit (разделение подтверждения/редактирования)**:
- Condition: `{{ $json.action }}` Equal `confirm`
- TRUE → подтверждение
- FALSE → редактирование
**check_if_edited (определение источника подтверждения)**:
- Condition: `{{ $json.isEditedCard }}` Equal `true`
- TRUE → после редактирования → parse_card_text
- FALSE → первое подтверждение → row_select_by_id
**which_field_to_edit (определение редактируемого поля)**:
- Condition: `{{ $json.field }}` Equal `category`
- TRUE → category_edit
- FALSE → counterparty_edit
### Merge Node
**merge_confirm_paths (объединение путей подтверждения)**:
- Type: Merge
- Mode: Append
- Input 1: update_after_confirm (первое подтверждение)
- Input 2: prepare_confirmation_data (после редактирования)
- Output: popup_confirm
---
## CHANGELOG
### 2026-01-05 - Human-in-the-loop ПОЛНОСТЬЮ ЗАВЕРШЁН ✅
**Критические исправления:**
- Исправлена ошибка в parse_from_llm: `choices.message``choices[0].message.content`
- Решена проблема конфликта двух Telegram триггеров за один webhook
- Удалён edit_trigger, всё через единый answer_trigger
- Добавлен split_event_type IF для разделения callback_query и message
- Исправлена ошибка 403 Forbidden при регистрации webhook
- Telegram Credential Base URL установлен в <https://api.telegram.org>
**Реализован полный цикл редактирования:**
- parse_text_input: парсинг текстовых ответов пользователя
- get_current_transaction: SELECT текущих данных
- merge_changes: применение изменений БЕЗ записи в БД
- show_updated_card: обновлённая карточка с маркером "не сохранено"
- ЦИКЛ: можно редактировать category и counterparty многократно
**Реализовано двойное подтверждение:**
- check_if_edited IF: определение источника подтверждения
- parse_card_text: извлечение данных из текста карточки
- update_edited_transaction: UPDATE после редактирования
- prepare_confirmation_data: подготовка данных
- merge_confirm_paths: объединение двух путей подтверждения
**Технические решения:**
- Лимит 12 символов на поле (для влезания в callback_data)
- Маркер "не сохранено" в тексте карточки для определения типа
- Данные передаются через текст сообщения, не через БД
- Единый триггер + IF вместо двух конфликтующих триггеров
### 2026-01-02 - Human-in-the-loop через Telegram (90% завершено)
- Изменена архитектура: все транзакции через человека
- LLM-данные сохраняются сразу (human_verified=false)
- Создан Telegram-бот, настроены credentials
- TRUE ветка (подтверждение) завершена
- FALSE ветка (редактирование) на 90%
- Парсинг markdown обёртки LLM
- Reply To Message для связи с транзакцией
### 2026-01-01 - LM Studio интегрирован
- Установлен LM Studio, загружена Qwen2.5-7B-Instruct
- Настроены параметры модели
- Решена проблема подключения n8n Docker к LM Studio
- Создан HTTP Request узел для LLM API
- Разработан промпт для категоризации
### 2025-12-XX - Базовая инфраструктура
- Развернут PostgreSQL 16 в Docker
- Создана таблица transactions
- Настроен n8n workflow: Webhook → Auth → Code → PostgreSQL
- Реализован парсинг SMS
---
## СЛЕДУЮЩИЕ ШАГИ
### Приоритет 1: Интеграция с Google Sheets
1. Создать Google Sheets таблицу для семейного бюджета
2. Настроить Google Sheets API в n8n (Service Account)
3. Добавить Google Sheets node после merge_confirm_paths
4. Записывать только подтверждённые транзакции
5. Настроить колонки: Дата | Контрагент | Сумма | Категория | Подкатегория
6. Форматирование: цветовое кодирование расходов/доходов
### Приоритет 2: Автоматизация запуска
1. Автозапуск LM Studio при загрузке системы (systemd service)
2. Docker restart policy = always для всех контейнеров
3. Health-check для мониторинга LM Studio API
4. Уведомления в Telegram при падении сервисов
### Приоритет 3: Улучшения (опционально)
1. Кнопка "❌ Отклонить" для удаления ошибочных транзакций
2. Редактирование подкатегории
3. Валидация пользовательского ввода (длина, спецсимволы)
4. Обработка исторических данных 2025 года
5. Fine-tuning модели на собственных данных
6. Аналитика и отчёты по категориям расходов
7. Экспорт данных в различные форматы
---
**Версия документа**: 3.0
**Дата последнего обновления**: 2026-01-05
**Статус**: Основной функционал ЗАВЕРШЁН (100%), готов к продакшн-использованию
**Следующий шаг**: Интеграция с Google Sheets
**Автор обновления**: AI Assistant (Perplexity)