From f52820fff62e4594410357cb82021a10adf701f9 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Mon, 5 Oct 2026 15:06:30 +0300 Subject: [PATCH] docs(todo): add task 3 for word deduplication and duplicate prevention --- TODO.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 8dfa874..a844257 100644 --- a/TODO.md +++ b/TODO.md @@ -47,7 +47,22 @@ --- -### 3. 🔌 Пул соединений HTTP (`httpx.AsyncClient` с Keep-Alive) +### 3. 🛡️ Дедупликация слов и защита от повторного добавления (Reader + API) +- [ ] **Проверка дубликатов на бэкенде (`POST /api/cards`)**: + - Проверять наличие слова для пользователя без учёта регистра: `lower(trim(term))`. + - При попытке повторного добавления: не создавать дубликат-клон, а дополнять контекстное предложение (если в новой цитате другой контекст) и объединять теги книги (`tags`), возвращая существующую карточку со статусом `already_exists`. +- [ ] **Индикация в читалке (Reader Popup)**: + - При клике на слово проверять, есть ли уже `cleanTerm` в словаре пользователя. + - Если слово уже сохранено: показывать зелёный бейдж `✓ В словаре` вместо активной кнопки `+ В словарь` (предотвращая случайные повторные клики). + - Предоставить кнопку «Добавить контекст» для привязки новой цитаты к существующей карточке. +- [ ] **Индекс целостности в SQLite**: + - Добавить `CREATE INDEX IF NOT EXISTS idx_vocab_user_term_nocase ON vocabulary_cards(user_id, term COLLATE NOCASE);` для мгновенного поиска за $O(\log N)$. +- [ ] **Слияние существующих дубликатов в базе**: + - Написать безопасную миграцию/скрипт, объединяющий накопившиеся дубликаты (например, `admired`, `suddenly`/`Suddenly` у пользователя `kopri`), объединяя контексты предложений и сохраняя историю повторений SRS. + +--- + +### 4. 🔌 Пул соединений HTTP (`httpx.AsyncClient` с Keep-Alive) - [ ] В [`app/dictionary.py`](file:///home/ai-projects/services/study.qpid.pro/app/dictionary.py) создать синглтон-клиент `get_http_client()` с пулом соединений: ```python limits = httpx.Limits(max_keepalive_connections=10, max_connections=20) @@ -57,7 +72,7 @@ --- -### 4. 🗄️ Тюнинг SQLite PRAGMA и составных индексов +### 5. 🗄️ Тюнинг SQLite PRAGMA и составных индексов - [ ] В [`app/database.py`](file:///home/ai-projects/services/study.qpid.pro/app/database.py): - Вынести `PRAGMA journal_mode=WAL;` в `init_db()` (убрать избыточный вызов при каждом запросе). - Включить параметры производительности памяти Linux: @@ -66,22 +81,17 @@ conn.execute("PRAGMA mmap_size = 268435456;") # 256MB mmap conn.execute("PRAGMA cache_size = -32000;") # 32MB cache ``` - - Добавить составной индекс: - ```sql - CREATE INDEX IF NOT EXISTS idx_vocab_user_term_nocase - ON vocabulary_cards(user_id, term COLLATE NOCASE); - ``` -- [ ] Обеспечить Zero-copy чтение страниц БД ядром Linux и мгновенный поиск дубликатов по пользователю за $O(\log N)$. +- [ ] Обеспечить Zero-copy чтение страниц БД ядром Linux. --- -### 5. ✂️ Де-блоатинг внешних парсеров (YAGNI) +### 6. ✂️ Де-блоатинг внешних парсеров (YAGNI) - [ ] Удалить лишний сетевой опрос Datamuse (`fetch_datamuse`) — он дублирует определения и перегружает ответ нерелевантными рифмами. - [ ] Упростить хрупкий regex-парсинг разметки Wikitext, опираясь на стабильные REST JSON эндпоинты. --- -### 6. 📚 Локальный оффлайн-словарь (99%+ покрытия со словоформами, SQLite 10–20 МБ) +### 7. 📚 Локальный оффлайн-словарь (99%+ покрытия со словоформами, SQLite 10–20 МБ) - [ ] Создать в SQLite таблицу: ```sql CREATE TABLE IF NOT EXISTS offline_dictionary ( @@ -103,7 +113,7 @@ --- -### 7. 🧩 Модульность фронтенда на нативных ES-модулях (без Webpack/Vite) +### 8. 🧩 Модульность фронтенда на нативных ES-модулях (без Webpack/Vite) - [ ] Перевести [`app/static/js/app.js`](file:///home/ai-projects/services/study.qpid.pro/app/static/js/app.js) на нативный браузерный синтаксис `