openspec: архив 14 завершённых change-ов (веб-фиксы, crawler-queue, own-content-hub, publisher-service); спеки влиты в openspec/specs

This commit is contained in:
kpa39l
2026-09-16 17:01:00 +00:00
parent 584582a48c
commit 771f6a8276
88 changed files with 1632 additions and 3 deletions
@@ -0,0 +1,40 @@
# Tasks: crawler-queue
## 1. Воркер (фаза 3)
- [x] 1.1 crawler/worker.py: claim_post (атомарный claim: classified=0 → -1, BEGIN IMMEDIATE)
Проверка: in-memory тест — два вызова claim_post дают разные id (атомарно)
- [x] 1.2 process_post: classify_text + трафилатура для summary-only; UPDATE posts (direction/relevance/interest/summary/classified=1)
Проверка: in-memory — пост «linux» → tech, classified=1; «игры» → games
- [x] 1.3 Исключение → classified=0 (вернуть в очередь)
Проверка: try/except в work_loop, UPDATE classified=0; реализовано
- [x] 1.4 run(workers=8, limit=200): ThreadPoolExecutor, as_completed, счётчики (processed/classified/llm_ok)
Проверка: `python -m crawler.worker --workers 4 --limit 2` → 8 обработано; без ошибок
## 2. Сбор (фаза 1)
- [x] 2.1 crawler/crawl_sources.py: запуск telegram_crawler + rss_crawler (сбор новых кандидатов в очередь)
Проверка: `python -m crawler.crawl_sources --all` → запускает оба; `--crawler rss` — только RSS
## 3. Страница /crawlers
- [x] 3.1 web/app.py: GET /crawlers (за auth) — таблица источников (slug, name, crawler, status, last_fetch, last_error, error_count, priority) + карточки очереди (pending/processing/done)
Проверка: GET /crawlers с auth → 200, таблица с lwn (проверено httpx)
- [x] 3.2 web/templates/crawlers.html — шаблон (таблица + карточки + кнопки)
- [x] 3.3 POST /crawlers/run → запуск worker.run (фаза 3); POST /crawlers/reset → sources.status='alive', error_count=0
Проверка: POST /crawlers/run → воркер реально стартует (logs/worker.log), 200/302; reset — UPDATE
## 4. Интеграция и доки
- [x] 4.1 AGENT.MD — команды worker/crawl_sources (добавлены)
- [x] 4.2 STATUS.md / TODO.md — страница /crawlers, параллельная фаза 3 (обновлены)
- [x] 4.3 `openspec validate crawler-queue` → 0 ошибок
- [x] 4.4 TODO.md: задача «crawler-очередь» закрыта ✅
## Примечания (найденные при реализации)
- SQLite: соединение привязано к потоку — создаётся ВНУТРИ work_loop (нельзя делить между потоками).
- Локальная Ollama (qwen3:8b) держит 1 слот — 8 параллельных LLM-запросов → таймауты (посты возвращаются в очередь, не теряются). Добавлен `LLM_SEM` (threading.BoundedSemaphore, MAX_CONCURRENT_LLM=2).
- Очередь = posts (classified IS NULL OR 0) — отдельная таблица НЕ нужна (дублирование).
- error_count/etag для RSS — из rss_state (в sources их нет); запрос /crawlers использует COALESCE.
- **SOURCE_RULES (classifier/keywords.py)**: правило источника с приоритетом над словарём и LLM. `lwn → tech` — все посты LWN (технологическое СМИ) получают направление tech детерминированно, без LLM (method='source-rule'). Работает в worker.py и старом CLI classifier.classify (оба JOIN sources → source_slug).