mirror of
https://gitverse.ru/kpa39l/email-assistant.git
synced 2026-09-29 09:15:09 +00:00
feat: классификация писем Qwen3:8b + обработчики (urgent→TG, task→VTODO, meeting→VEVENT)
- mail_archive.py: фикс вложений himalaya --dir → --downloads-dir; attachments/ только при has_attachment; идемпотентно - email_classifier.py: Qwen3:8b (Ollama) → теги info/urgent/task/meeting в frontmatter email.md - email_handlers.py: urgent→Telegram (Bot API+SOCKS5), task→Radicale VTODO, meeting→Radicale VEVENT; handled_* идемпотентность; --dry-run - .gitignore: игнор *.env.bak* - openspec: чейндж email-classification-handlers (в работе) - STATUS/TODO/WALKTHROUGH: прогресс сессии, подводные камни Radicale (http.client, Depth:1, формат дат)
This commit is contained in:
+157
-1
@@ -91,4 +91,160 @@ postgres), Caddy (cal.nixg.ru, tasks.nixg.ru), Android.
|
||||
### Известные открытые хвосты (репозиторий)
|
||||
- `PLAN_WEBUI.md` — untracked (не закоммичен).
|
||||
- Cron mail-index-incremental и digest-weekly — не настроены.
|
||||
- Push mirror gitea → gitverse — не настроен (нужен токен [REDACTED]).
|
||||
- Push mirror gitea → gitverse — не настроен (нужен токен [REDACTED]).
|
||||
|
||||
## 2026-09-13
|
||||
|
||||
### Radicale: пароль сменён (пользователь не помнил старый)
|
||||
|
||||
**Проблема:** старый пароль Radicale (md5/$apr1$-хэш в `data/users`) не подходил;
|
||||
пользователь не помнил пароль.
|
||||
|
||||
**Решение:** `cp -a users users.bak.<ts>` → сгенерировали хэш `$apr1$`
|
||||
(`openssl passwd -apr1 '<пароль>'`, значение — `RADICALE_PASS` в `radicale/.env`) →
|
||||
перезаписали `data/users`. Проверка:
|
||||
`PROPFIND https://cal.nixg.ru/` → **207** (доступ подтверждён).
|
||||
|
||||
**Урок:** Radicale хранит пароль как **Apache `$apr1$` (MD5-crypt)**, не как
|
||||
простой md5. Проверять пароль — `crypt.crypt(cand, hash) == hash`.
|
||||
|
||||
### «Обход в Глории» — повторяющееся событие (Рабочий)
|
||||
|
||||
Добавлено через CalDAV PUT:
|
||||
`PUT /estorozhenko/<urlencoded 'Рабочий'>/obhod-v-glorii-2026.ics` → **201**.
|
||||
VTIMEZONE Europe/Moscow + RRULE:FREQ=WEEKLY;BYDAY=TU, DTSTART 11:00.
|
||||
Пользователь подтвердил: GMT+3 отображается корректно.
|
||||
|
||||
**Урок:** сервер UTC, а у пользователя GMT+3 — обязательно указывать VTIMEZONE
|
||||
(Europe/Moscow), иначе время «поедет» в приложении.
|
||||
|
||||
### Чейндж: классификация писем + обработчики (email-classification-handlers)
|
||||
|
||||
Пользователь попросил после скачивания письма классифицировать его локальной
|
||||
моделью (Qwen3:8b) и подключать обработчики по тегам.
|
||||
|
||||
**Диагностика пайплайна (важно):**
|
||||
- Сейчас скачивается **только текст** + метаданные. Вложения — **НЕ качаются**.
|
||||
- **Баг:** `get_attachments()` в `mail_archive.py` вызывает
|
||||
`himalaya attachment download --dir <dest>`, но правильный флаг —
|
||||
**`--downloads-dir`** (не `--dir`). Команда падает (exit 2), ошибка молча
|
||||
глотается `except: pass`, папка `attachments/` всегда пустая.
|
||||
- Проверено на живом письме с `has_attachment: true`: папка пустая.
|
||||
- Классификатора/обработчиков нет; тэгов `tags:` нет ни в одном email.md.
|
||||
|
||||
**Создан чейндж** `email-classification-handlers` (proposal/specs/design/tasks):
|
||||
- `email-attachments`: фикс вложений (`--downloads-dir`, в каталог письма
|
||||
`<msg_dir>/attachments/`, идемпотентно)
|
||||
- `email-classification`: Qwen3:8b (Ollama localhost:11434) → теги
|
||||
info/urgent/task/meeting (+unclassified при ошибке), поле `classification` +
|
||||
`classification_reason` в frontmatter, идемпотентно, приватно
|
||||
- `email-handlers`: urgent→Telegram, task→Radicale VTODO, meeting→Radicale VEVENT,
|
||||
info→ничего; `handled_*` в frontmatter
|
||||
|
||||
### Vikunja — ЛИШНЯЯ СУЩНОСТЬ, удалена (remove-vikunja-use-radicale-tasks)
|
||||
|
||||
**Решение пользователя (2026-09-13):** Vikunja не нужна — Radicale умеет задачи
|
||||
как VTODO (календарь «Задачи»), jtx board читает их по CalDAV.
|
||||
|
||||
**Создан и применён чейндж** `remove-vikunja-use-radicale-tasks` (14 задач, все
|
||||
выполнены):
|
||||
- Обработчик `task` в classification → **Radicale CalDAV PUT VTODO**
|
||||
(SUMMARY=тема, DESCRIPTION=ссылка на email.md, DTSTART/DUE при наличии)
|
||||
- `docker compose down -v` в `/opt/hermes/email-assistant/vikunja/` → контейнеры
|
||||
`vikunja` + `vikunja-db` удалены, порт 3456 свободен
|
||||
- Каталог `vikunja/` удалён (db от root — `sudo rm -rf`)
|
||||
- Caddy vps02: блок `tasks.nixg.ru` закомментирован (строки 114-120),
|
||||
`caddy validate` → Valid, `caddy reload`. Бэкап Caddyfile:
|
||||
`/opt/caddy/Caddyfile.bak-vikunja-removed`
|
||||
- Проверка: `cal.nixg.ru` → 207 (работает), `tasks.nixg.ru` → 502 (не проксируется)
|
||||
- Тестовый VTODO `test-vikunja-removal-2026` создан в «Задачи» (PUT 201, GET 200)
|
||||
- TODO.md / STATUS.md / local-calendar-tasks (SUPERSEDED) обновлены
|
||||
- Бэкап Vikunja пропущен по явному решению пользователя
|
||||
|
||||
**Урок:** Radicale нативно хранит VTODO (задачи) — отдельный трекер задач
|
||||
(Vikunja) был избыточен. При выборе сервисов календаря Radicale закрывает и
|
||||
календари, и задачи (VTODO), и контакты (CardDAV).
|
||||
|
||||
### Открытые хвосты (2026-09-13)
|
||||
- Чейндж `email-classification-handlers` — 0/23 задач (фикс вложений, скрипты
|
||||
классификатора/обработчиков, cron, Telegram-секреты).
|
||||
- Чейндж `contacts-caldav-server` — 16/30 (двусторонний sync контактов работает,
|
||||
есть ещё задачи).
|
||||
- Android-синхронизация: DAVx5 → Radicale (контакты синхронизировались), задачи
|
||||
VTODO → jtx board — не настроено.
|
||||
- Тестовый VTODO `test-vikunja-removal-2026` остался в «Задачи» (проверка).
|
||||
|
||||
## 2026-09-13 (вечер) — чейндж email-classification-handlers, автономный заход
|
||||
|
||||
Сессия велась автономно (пользователь дал разрешение «работай без подтверждения»).
|
||||
Цель — закрыть чейндж `email-classification-handlers` (0/23 → прогресс).
|
||||
|
||||
### 1. Вложения (email-attachments) — сделано, проверено
|
||||
|
||||
`scripts/mail_archive.py`:
|
||||
- `get_attachments()`: флаг `--dir` → **`--downloads-dir`** (подтверждено
|
||||
`himalaya attachment download --help`).
|
||||
- Папка `attachments/` создаётся **только** при `has_attachment: true`
|
||||
(раньше — безусловно, плодила 1695 пустых папок).
|
||||
- Идемпотентность: если в `dest_dir` уже есть файлы — повторно не качает.
|
||||
- Удалена мёртвая строка `attachments_dir = msg_dir / "attachments"` из цикла.
|
||||
- Проверено: `.xlsx` скачался на живом письме `Archive/2025/11/28`,
|
||||
повторный прогон не дублировал (контроль hashlib).
|
||||
|
||||
### 2. Классификатор (email-classification) — создан
|
||||
|
||||
`scripts/email_classifier.py` (327 строк):
|
||||
- Читает `email.md` из архива, для писем **без** поля `classification` в frontmatter
|
||||
вызывает **Qwen3:8b через Ollama (localhost:11434)**.
|
||||
- Пишет `classification` (теги info/urgent/task/meeting) + `classification_reason`
|
||||
в frontmatter. Идемпотентно (повторный прогон пропускает уже классифицированные).
|
||||
- Ручной прогон: письмо `2026/422` получило теги `task,meeting` ✓.
|
||||
- Переиспользованы паттерны из `contacts_extractor.py` (clean_body, call_llm,
|
||||
константы MAX_BODY_CHARS/LLM_TIMEOUT).
|
||||
|
||||
### 3. Обработчики (email-handlers) — созданы, Radicale проверен живьём
|
||||
|
||||
`scripts/email_handlers.py` (~470 строк):
|
||||
- `urgent` → **Telegram** (Bot API через SOCKS5 `socks5://127.0.0.1:1080`,
|
||||
токен `VESTI_BOT_TOKEN` из /opt/vesti/.env, канал `TELEGRAM_CHAT_ID`,
|
||||
дефолт `@dedinit_vesti`).
|
||||
- `task` → **Radicale CalDAV PUT VTODO** в календарь «Задачи».
|
||||
- `meeting` → **Radicale CalDAV PUT VEVENT** в календарь «Рабочий».
|
||||
- `info` → ничего.
|
||||
- Идемпотентность: после успеха пишет `handled_urgent/handled_task/handled_meeting: true`
|
||||
в frontmatter; повторный прогон пропускает.
|
||||
- Режимы: `--limit N`, `--folder`, `--dry-run`.
|
||||
|
||||
**Подводные камни Radicale (важно для повторения!):**
|
||||
1. **Пароль в `radicale/.env` НЕ совпадал с `radicale/data/users`** — пароль менялся
|
||||
в 13.09 17:55 (users.bak), но `.env` остался от 11.09. Доступ 401. Синхронизировал
|
||||
`.env` с актуальным паролем (бэкап `.env.bak.<ts>`). **Правило: после смены пароля
|
||||
Radicale обновлять и `.env` скриптов.**
|
||||
2. **`urllib.request` НЕ работает с percent-encoded кириллицей в URL** CalDAV
|
||||
(`/estorozhenko/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B8/...`) — падает
|
||||
`Errno -2 Name or service not known`. Решение: использовать **`http.client`**
|
||||
напрямую (HTTPConnection + request с готовым path). Проверено: PUT 201.
|
||||
3. **PROPFIND без заголовка `Depth: 1`** возвращает только сам ресурс, без
|
||||
дочерних календарей → коллекции «не находились». Обязательно `headers["Depth"] = "1"`.
|
||||
4. **Формат дат iCalendar единый `YYYYMMDDTHHMMSS`** — не смешивать
|
||||
`2026-09-14T11:00:00` (с дефисами) и `20260914T110000` (Radicale отклоняет
|
||||
первое, 400 Bad Request).
|
||||
5. **Коллекции Radicale кэшируются** через `@lru_cache` (PROPFIND один раз за проход).
|
||||
|
||||
**Проверено:** dry-run → `task` VTODO 204, `meeting` VEVENT 201, тестовые объекты
|
||||
удалены (DELETE 200).
|
||||
|
||||
### Статус чейнджа на конец сессии
|
||||
- 1. Вложения: готово (проверено).
|
||||
- 2. Классификатор: скрипт готов, прогон прошёл (письмо 422 → task,meeting).
|
||||
- 3. Обработчики: скрипт готов, dry-run чист (VTODO/VEVENT создаются).
|
||||
Осталось: живой прогон на реальном письме (без --dry-run), проверка urgent→Telegram.
|
||||
- 4. Секреты: Radicale-пароль в .env синхронизирован. Осталось: TELEGRAM_CHAT_ID
|
||||
в .env (токен есть в /opt/vesti/.env, VESTI_BOT_TOKEN).
|
||||
- 5. Cron: не настроен (после пунктов 3-4).
|
||||
- 6. Документация: этот WALKTHROUGH, STATUS/TODO — следующий заход.
|
||||
|
||||
**Открыто на следующий заход:** живой прогон обработчиков (--limit 1 на каком-то
|
||||
письме с task/meeting), проверка доставки urgent в Telegram, cron
|
||||
(классификатор → обработчики после mail-archive), обновление STATUS.md/TODO.md,
|
||||
вычитка openspec-файлов чейнджа.
|
||||
Reference in New Issue
Block a user