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

30 KiB
Raw Permalink Blame History

SMS → n8n → LLM → БД/Telegram: принятые решения и контракты данных

Контекст: автоматизация "Семейный бюджет". Источник данных — банковские SMS, которые пересылаются в n8n вебхуком, далее парсятся и обогащаются (LLM), затем пишутся в БД и/или отправляются уведомления в Telegram.


СТАТУС ПРОЕКТА

ВЫПОЛНЕНО (100% основного функционала)

Инфраструктура

  • PostgreSQL 16 развернут в Docker на Synology NAS (порт 5433)
  • pgAdmin настроен (порт 5050)
  • Таблица transactions создана и расширена полями для LLM и human-in-the-loop
  • n8n запущен в Docker-контейнере
  • Telegram-бот создан через @BotFather
  • Telegram credentials настроены в n8n (Base URL: https://api.telegram.org)

n8n Workflow - Основная ветка обработки SMS

  • Webhook (POST) настроен для приёма SMS
  • Auth check (проверка x-api-key заголовка)
  • Code node (parse_sms) для парсинга SMS → NormalizedTransaction JSON
  • PostgreSQL INSERT (create_from_sms) для записи базовых данных транзакции
    • Always Output Data = ON для получения id
  • Edit Fields (Set) для data minimization перед LLM
  • HTTP Request (post_to_llm) для обращения к LM Studio API
  • Code node (parse_from_llm) для парсинга JSON-ответа от LLM
    • Очистка markdown обёртки (```json)
    • Merge с originalData из create_from_sms
    • Исправлена ошибка: choices.message → choices[0].message
  • PostgreSQL UPDATE (update_llm_fields) для записи обогащённых данных
    • counterparty, category, subcategory
    • llm_processed_at
    • human_verified = FALSE (пока не подтверждено)
  • Telegram Send Message (edit_or_confirm) для отправки карточки транзакции с кнопками
    • Inline Keyboard: Подтвердить / Изменить категорию / Изменить контрагента

LLM - Локальная модель

  • LM Studio установлен и настроен
  • Модель Qwen2.5-7B-Instruct (Q6_K, 6 ГБ) загружена
  • API-сервер запущен (порт 1234, Serve on Local Network включен)
  • Параметры модели оптимизированы (Context: 8192, Temperature: 0.2, Max Tokens: 512)
  • Промпт улучшен для корректной работы с неизвестными контрагентами
  • Обработка markdown-обёртки в ответах (```json)

Human-in-the-loop через Telegram - ПОЛНОСТЬЮ ЗАВЕРШЕНО

Единый Telegram Trigger (решена проблема конфликта webhook):

  • Один answer_trigger для ВСЕХ событий (callback_query + message)
  • Решена проблема 403 Forbidden при регистрации webhook
  • Telegram Credential настроен с Base URL = https://api.telegram.org
  • IF node (split_event_type) для разделения типов событий

TRUE ветка (подтверждение) - ЗАВЕРШЕНА:

  • Code node (parse_from_callback) для парсинга callback_data
    • Извлечение: action, field, transactionId, chatId, messageId, messageText
    • Определение isEditedCard (проверка текста "не сохранено")
  • IF node (confirm_or_edit) для разделения подтверждения/редактирования
  • IF node (check_if_edited) для определения источника подтверждения
    • TRUE: подтверждение после редактирования → parse_card_text
    • FALSE: первое подтверждение без изменений → row_select_by_id
  • Code node (parse_card_text) для извлечения данных из текста карточки
  • Postgres UPDATE (update_edited_transaction) для сохранения отредактированных данных
    • counterparty, category, human_verified = TRUE
  • Postgres SELECT (row_select_by_id) для получения данных транзакции
  • Postgres UPDATE (update_after_confirm) для первого подтверждения
    • human_verified = TRUE
    • human_verified_at = timestamp
  • Merge node (merge_confirm_paths) для объединения двух путей подтверждения
  • Telegram Answer Callback Query (popup_confirm) для всплывающего уведомления
  • Telegram Edit Message Text (confirmation_message) для обновления сообщения

FALSE ветка (редактирование) - ЗАВЕРШЕНА:

  • IF node (which_field_to_edit) для определения редактируемого поля
  • Telegram Send Message (category_edit) для запроса новой категории
    • С Reply To Message ID для связи с транзакцией
    • Напоминание о лимите 12 символов
  • Telegram Send Message (counterparty_edit) для запроса нового контрагента
    • С Reply To Message ID для связи с транзакцией
    • Напоминание о лимите 12 символов
  • IF node (split_event_type) после единого триггера
    • isEmpty(callback_query) = TRUE → текстовое сообщение → parse_text_input
    • isEmpty(callback_query) = FALSE → callback от кнопки → parse_from_callback
  • Code node (parse_text_input) для парсинга ответа пользователя
    • Извлечение transactionId из reply_to_message (#123)
    • Определение changedField по ключевым словам
    • Игнорирование команд (/)
    • Output: newValue, chatId, transactionId, changedField
  • Postgres SELECT (get_current_transaction) для получения текущих данных
  • Code node (merge_changes) для применения изменений
    • Merge новых и существующих данных
    • Не пишет в БД (временное состояние)
  • 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 (ЗАВЕРШЕНА)

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 (КРИТИЧНО)

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: Подтверждение транзакции (ЗАВЕРШЕНА)

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: Редактирование транзакции с циклом (ЗАВЕРШЕНА)

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:

-- Базовые поля
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:

Ты эксперт по категоризации расходов семейного бюджета в России. Анализируй банковские SMS и извлекай данные.

ВАЖНО:
- Если контрагент НЕИЗВЕСТЕН или непонятен - укажи confidence < 0.5
- Уверенность 1.0 ставь ТОЛЬКО для известных российских брендов
- Для неочевидных названий снижай confidence до 0.3-0.6

Известные сети: PYATEROCHKA, MAGNIT, ЗОЛОТОЕ ЯБЛОКО, WILDBERRIES, YANDEX TAXI

Для неизвестных: category=Неизвестно, confidence < 0.5

Отвечай ТОЛЬКО валидным JSON без markdown обёртки.

User Message:

Проанализируй 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)

// Исправлено: 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)

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 (для обработки текстовых ответов)

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 (применение изменений без записи в БД)

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 (извлечение данных из текста карточки)

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 (подготовка данных для финальных нод)

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:
Требуется подтверждение транзакции #{{ $json.id }}

Сумма: {{ $json.signed_amount }} {{ $json.currency }}
Контрагент: {{ $json.counterparty }}
Категория: {{ $json.category }}
Дата: {{ $json.received_at }}
  • Reply Markup:
{
  "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:
Данные обновлены (не сохранено)

Сумма: {{ $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.messagechoices[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)