mirror of
https://gitverse.ru/kpa39l/email-local-archive.git
synced 2026-09-29 09:15:11 +00:00
125 lines
5.7 KiB
Markdown
125 lines
5.7 KiB
Markdown
# SQLite-индекс архива писем
|
|
|
|
## Назначение
|
|
|
|
Быстрый поиск и трекинг по архиву email.md без grep-а по всем папкам.
|
|
Основа для: contacts_extractor (знает какие письма обработаны), sqlite_search (FTS5), digest (выборка по дате).
|
|
|
|
## Компоненты
|
|
|
|
| Компонент | Путь | Назначение |
|
|
|-----------|------|------------|
|
|
| Индексатор | `/opt/hermes/email-assistant/scripts/mail_index.py` | Сканирует email.md → SQLite |
|
|
| Поиск | `/opt/hermes/email-assistant/scripts/sqlite_search.py` | FTS5-поиск по индексу |
|
|
| База | `/opt/hermes/email/mail_index.db` | SQLite (WAL mode) |
|
|
|
|
## Схема БД
|
|
|
|
### emails — основная таблица
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS emails (
|
|
path TEXT PRIMARY KEY, -- относительный путь от EMAIL_ROOT
|
|
uid INTEGER, -- числовой UID из пути
|
|
folder TEXT, -- INBOX, INBOX/!Scan, Sent...
|
|
date TEXT, -- дата из frontmatter (ISO)
|
|
from_addr TEXT, -- отправитель
|
|
to_addrs TEXT, -- получатели
|
|
subject TEXT, -- тема
|
|
body_preview TEXT, -- первые 500 символов тела (без HTML)
|
|
contacts_extracted INTEGER DEFAULT 0, -- 0/1 — обработано contacts_extractor
|
|
contacts_skipped INTEGER DEFAULT 0, -- 0/1 — нет подписи / LLM error
|
|
first_seen TEXT, -- когда проиндексировано
|
|
last_scanned TEXT, -- последняя проверка contacts
|
|
file_mtime REAL -- mtime файла для инкрементальной проверки
|
|
);
|
|
```
|
|
|
|
### email_fts — FTS5 virtual table
|
|
|
|
```sql
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS email_fts USING fts5(
|
|
subject, from_addr, to_addrs, body_preview,
|
|
content='emails',
|
|
content_rowid='rowid',
|
|
tokenize='unicode61'
|
|
);
|
|
```
|
|
|
|
Синхронизация через триггеры INSERT/UPDATE/DELETE + FTS5 rebuild.
|
|
|
|
### Индексы
|
|
|
|
- `idx_emails_folder` — быстрая фильтрация по папке
|
|
- `idx_emails_uid` — lookup по UID
|
|
- `idx_emails_contacts` — необработанные письма (contacts_extracted=0)
|
|
- `idx_emails_date` — сортировка по дате
|
|
|
|
## Контракт между mail_index.py и contacts_extractor.py
|
|
|
|
**mail_index.py** владеет схемой и создаёт таблицы. **contacts_extractor.py** — только читает/пишет поля `contacts_extracted`, `contacts_skipped`, `last_scanned`.
|
|
|
|
```sql
|
|
-- contacts_extractor берёт необработанные письма:
|
|
SELECT rowid, path, uid, folder, from_addr, subject
|
|
FROM emails
|
|
WHERE contacts_extracted = 0 AND contacts_skipped = 0
|
|
ORDER BY folder, uid
|
|
LIMIT ?
|
|
|
|
-- После обработки:
|
|
UPDATE emails SET contacts_extracted=1, last_scanned=? WHERE rowid=?
|
|
UPDATE emails SET contacts_skipped=1, last_scanned=? WHERE rowid=?
|
|
```
|
|
|
|
## Быстродействие
|
|
|
|
- 2073 письма → полная индексация ~9 секунд
|
|
- FTS5-поиск — мгновенно (<100ms)
|
|
- WAL mode — конкурентные чтения не блокируют запись
|
|
- Инкрементальная индексация по mtime — доли секунды
|
|
|
|
## Использование sqlite_search.py
|
|
|
|
```bash
|
|
# Простой поиск (AND по умолчанию)
|
|
python3 sqlite_search.py 'Стороженко'
|
|
python3 sqlite_search.py 'битрикс OR контрагент'
|
|
python3 sqlite_search.py '"точечная фраза"'
|
|
|
|
# Фильтры
|
|
python3 sqlite_search.py --folder 'INBOX/!Отчеты'
|
|
python3 sqlite_search.py --limit 20
|
|
|
|
# Специальные префиксы (точно в поле from_addr/subject через LIKE)
|
|
python3 sqlite_search.py 'from:example@mail'
|
|
python3 sqlite_search.py 'subject:отчёт'
|
|
|
|
# Вкл. тело письма в FTS5 (медленнее, но находит больше)
|
|
python3 sqlite_search.py --body
|
|
```
|
|
|
|
## Операторы FTS5
|
|
|
|
- `AND` — по умолчанию между словами
|
|
- `OR` — `'битрикс OR контрагент'`
|
|
- `"точная фраза"` — кавычки для точного совпадения
|
|
- `-исключить` — минус перед словом
|
|
- `prefix*` — wildcard (звёздочка на конце)
|
|
|
|
## mail_index.py — ключевые параметры
|
|
|
|
```bash
|
|
python3 mail_index.py # полная переиндексация
|
|
python3 mail_index.py --incremental # только новые (по mtime)
|
|
python3 mail_index.py --search "..." # поиск (встроенный, без FTS5)
|
|
python3 mail_index.py --stats # статистика
|
|
```
|
|
|
|
## Важные детали
|
|
|
|
- **body_preview** — только первые 500 символов. Для full-text search с телом используй `sqlite_search.py --body` (FTS5 на preview).
|
|
- **contacts_extracted/contacts_skipped** — взаимоисключающие флаги. Если ни один не 1 — письмо не обработано.
|
|
- **file_mtime** — для инкрементальной индексации. Если mtime файла > last_mtime в БД — переиндексировать.
|
|
- **FTS5 rebuild** — вызывается после каждой полной индексации для согласованности.
|
|
- Исключает папки `contacts/` и `state/` из сканирования. |