mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
Задача 8: классификация и обработчики — живой прогон, cron, archive change
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# Design: Классификация писем и подключение обработчиков
|
||||
|
||||
## Context
|
||||
|
||||
Пайплайн почты сейчас:
|
||||
```
|
||||
IMAP → himalaya → email.md (frontmatter + текст) → [конец]
|
||||
```
|
||||
Вложения теряются (баг `--dir` вместо `--downloads-dir`). Классификации нет.
|
||||
Обработчиков нет. Ollama с Qwen3:8b уже используется (contacts_extractor),
|
||||
Radicale и Vikunja развёрнуты.
|
||||
|
||||
Известные ограничения:
|
||||
- Vikunja выведена из проекта (лишняя сущность; задачи через Radicale VTODO,
|
||||
см. чейндж `remove-vikunja-use-radicale-tasks`).
|
||||
- Radicale работает, календарь «Рабочий» и «Задачи» есть (в «Рабочий» я уже
|
||||
добавил «Обход в Глории»).
|
||||
- Telegram-уведомления: Hermes gateway может слать в Telegram; нужен канал/chat_id.
|
||||
- Приватность: вся LLM-обработка локально (Ollama localhost:11434).
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Починить скачивание вложений в каталог письма.
|
||||
- Классифицировать каждое письмо локальной моделью, писать тег + обоснование.
|
||||
- Подключать обработчики по тегам: urgent→уведомление, task→задача в Vikunja,
|
||||
meeting→событие в Radicale, info→ничего.
|
||||
- Идемпотентность: письмо обрабатывается один раз.
|
||||
|
||||
**Non-Goals:**
|
||||
- Не делаем веб-интерфейс (это отдельный чейндж «Веб-интерфейс ассистента»).
|
||||
- Не делаем сложный NLP / классификацию по нескольким моделям — только Qwen3:8b.
|
||||
- Не мигрируем существующие письма (классификация только новых; историю можно
|
||||
переклассифицировать отдельно флагом `--force`).
|
||||
- Не реализуем умные дедлайны/приоритеты на основе содержания — только теги.
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: Классификатор — отдельный скрипт `email_classifier.py`
|
||||
Отдельный скрипт (а не функция в mail_archive.py), потому что:
|
||||
- mail_archive.py — no-agent cron каждые 5 мин, он должен быть быстрым и лёгким;
|
||||
LLM-вызов медленный (секунды на письмо).
|
||||
- Классификация идёт после архивации, отдельным проходом.
|
||||
- Легко запускать вручную, менять модель/промпт, добавлять теги.
|
||||
Скрипт читает email.md, отдаёт текст Qwen3:8b, получает JSON, пишет в frontmatter.
|
||||
|
||||
### D2: Формат классификации — отдельное поле в frontmatter
|
||||
В `email.md` frontmatter добавляем:
|
||||
```yaml
|
||||
classification: task,meeting # или info / urgent / task / meeting / unclassified
|
||||
classification_reason: "Просят подготовить бюджет и назначить встречу"
|
||||
```
|
||||
- `classification` — основной тег; `classification_reason` — обоснование.
|
||||
- Трекинг обработанных: письмо «обработано», если есть `classification`.
|
||||
Для надёжности дополнительно пишем в SQLite (`mail_index.db`) или state JSON
|
||||
(когда обработан) — но минимум: поле в frontmatter достаточно.
|
||||
|
||||
### D3: Обработчики — отдельный скрипт `email_handlers.py`
|
||||
Скрипт, который:
|
||||
1. Находит письма с тегом, для которых ещё не выполнен обработчик.
|
||||
2. По тегу вызывает соответствующий обработчик:
|
||||
- `urgent` → Telegram
|
||||
- `task` → Vikunja API
|
||||
- `meeting` → Radicale (создать VEVENT)
|
||||
- `info` → ничего
|
||||
3. Помечает обработанное письмо (поле `handled_urgent: true` / `handled_task: true`
|
||||
/ `handled_meeting: true`), чтобы не дублировать.
|
||||
|
||||
### D4: Куда слать встречу — Radicale (календарь Рабочий)
|
||||
Событие встречи создаётся в Radicale (cal.nixg.ru), календарь «Рабочий»
|
||||
(рабочие встречи). Формат VEVENT с SUMMARY=тема письма, DTSTART из классификации
|
||||
(если дата/время указаны) или на ближайший рабочий день 11:00 (по умолчанию).
|
||||
Это согласуется с тем, что пользователь уже использует Radicale для календаря
|
||||
(и я добавил туда «Обход в Глории»). Дата парсится LLM (в classification_reason
|
||||
модель возвращает JSON с датой/временем/продолжительностью).
|
||||
|
||||
### D5: Куда слать задачу — Radicale (VTODO, календарь «Задачи»)
|
||||
Задача создаётся в Radicale как VTODO (CalDAV) в календаре «Задачи»
|
||||
(`https://cal.nixg.ru/estorozhenko/Задачи/`), а не в Vikunja. Vikunja выведена
|
||||
из проекта (см. чейндж `remove-vikunja-use-radicale-tasks`). SUMMARY=тема письма,
|
||||
DESCRIPTION=ссылка на email.md, при наличии даты — DTSTART/DUE.
|
||||
|
||||
### D6: Уведомления — Telegram через Hermes gateway
|
||||
`urgent` шлёт уведомление в Telegram. Используем Hermes gateway (или прямое
|
||||
сообщение через API бота). Настройки (chat_id, token) в `.env` / config.
|
||||
|
||||
### D7: Идемпотентность и трекинг
|
||||
- Классификатор: письмо пропускается, если в frontmatter есть `classification`.
|
||||
- Обработчики: письмо пропускается, если для его тега уже стоит `handled_*`.
|
||||
- Это гарантирует: повторный запуск cron не создаст дубликатов.
|
||||
|
||||
### D8: Хранение секретов
|
||||
- Vikunja API token, Telegram chat_id/token — в `.env` (рядом с compose) или
|
||||
в конфиге Hermes. Не хардкодить.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Vikunja нет токена** → снято: Vikunja выведена, обработчик `task` пишет
|
||||
VTODO в Radicale. Нужен только Basic-auth Radicale (есть).
|
||||
- **Качество классификации Qwen3:8b** → возможны ложные срабатывания
|
||||
(письмо помечено `task`, хотя задачи нет). Митигирует: `classification_reason`
|
||||
виден пользователю, легко править вручную; теги — не жёсткие.
|
||||
- **Письмо с несколькими сущностями** (задача И встреча) → классификация может
|
||||
вернуть комбинацию `task,meeting`; обработчики запускаются для каждого тега.
|
||||
- **Дата встречи в свободном тексте** → LLM может ошибиться. Митигирует:
|
||||
если дата не уверенна, ставим ближайший рабочий день 11:00 + reason «дата не
|
||||
найдена точно».
|
||||
- **Производительность** → Qwen3:8b на CPU медленный; классификатор должен быть
|
||||
дозированным (`--limit N`, как contacts_extractor `--limit 15`).
|
||||
- **Обработка старых писем** → не делаем в этом чейндже; при желании отдельный
|
||||
проход `--force` по архивным.
|
||||
Reference in New Issue
Block a user