# Задание: собрать контент-план для блога про ИИ

Ты — агент на отдельной машине. Ниже самодостаточное задание: развернуть сбор
кандидатов из открытых источников, отфильтровать то, что уже опубликовано, и выдать
контент-план на заданный горизонт. Всё, что нужно, есть в этом файле и в скрипте
`collect_candidates.py` рядом с ним. Дописывать код можно и нужно, если в твоём
окружении что-то не работает.

**Главное правило: ты ничего не публикуешь.** Ни постов, ни сообщений. Результат —
файл с планом и короткий отчёт.

---

## 0. Что считается готовым результатом

1. Файл `content-plan-YYYY-MM-DD.md` — план на горизонт (по умолчанию неделя, 7–10 тем).
2. В плане у каждой темы: рабочий заголовок (≤60 символов, на языке блога), источник с
   полным URL, тезис с конкретными цифрами, угол подачи, приоритет, риски.
3. Блок «Отклонено» с причинами (дубль / мало материала / нет цифр / источник мёртв).
4. Отчёт: сколько кандидатов собрано по каждому источнику, какие источники отдали
   ошибку и почему, сколько отсеяно дедупом.

План не считается готовым, если хотя бы одна тема опирается на URL или цифру, которую
ты не открывал.

---

## 1. Развернуть окружение

```bash
python3 --version                  # нужно 3.9+
mkdir -p ~/content-planner && cd ~/content-planner
# положи сюда collect_candidates.py из этого задания
python3 collect_candidates.py --help   # проверка, что файл читается
```

Зависимости — опциональные, скрипт работает на стандартной библиотеке:

```bash
pip install pysocks     # только если ходишь через socks5-прокси
pip install yt-dlp      # только если нужен источник YouTube
```

Проверь доступность ключевых хостов (при необходимости — через прокси):

```bash
python3 - <<'PY'
import urllib.request
for url in ("https://hn.algolia.com/api/v1/search?tags=story&hitsPerPage=1",
            "http://export.arxiv.org/api/query?search_query=cat:cs.AI&max_results=1",
            "https://lobste.rs/t/ai.json"):
    try:
        with urllib.request.urlopen(url, timeout=15) as r:
            print("OK  ", r.status, url)
    except Exception as e:
        print("FAIL", type(e).__name__, url)
PY
```

Если хосты недоступны напрямую — запускай скрипт с `--proxy socks5://host:port`
(поддерживается `socks5://` и `http://`).

---

## 2. Собрать кандидатов

```bash
python3 collect_candidates.py \
  --sources rss,hn,arxiv,reddit,devto,lobsters,github,youtube \
  --blog-dir ~/blog/posts \        # каталог с уже опубликованными статьями (.md)
  --seen-file seen.json \          # список URL, уже использованных в прошлых планах
  --deep 10 \                      # сколько топ-кандидатов скачать и посчитать слова
  --limit 15 \                     # сколько тем оставить
  --out plan.json --md plan.md
```

Что делает скрипт: собирает материалы из восьми источников, считает объём текста,
оценивает свежесть и вовлечённость, отсеивает дубли против каталога постов
(`--blog-dir`) и ранее использованные URL (`--seen-file`), сортирует по оценке и
пишет `plan.json` + markdown-заготовку.

Так выглядит рабочий прогон (эталон для сверки):

```
[rss] 78 кандидатов за 13.1s (1 ошибок)
[hn] 25 кандидатов за 0.8s (0 ошибок)
[arxiv] 15 кандидатов за 1.7s (0 ошибок)
[devto] 15 кандидатов за 0.5s (0 ошибок)
[lobsters] 15 кандидатов за 0.5s (0 ошибок)
[github] 12 кандидатов за 2.2s (0 ошибок)
Итого: 160 собрано → 12 отобрано (дедуп против 424 постов, ошибок: 1)
```

Если у какого-то источника 0 кандидатов и ошибки — это нормально (сайт блокирует,
фид умер, лимит запросов). Разберись по каждой ошибке отдельно и напиши в отчёте,
что именно не сработало; не подменяй отсутствие данных догадками.

### Источники, которые скрипт умеет

| `--sources` | Что берёт | Замечания |
|---|---|---|
| `rss` | 15 фидов: OpenAI, Hugging Face, DeepMind, Import AI, The Gradient, Simon Willison, Last Week in AI, One Useful Thing, BAIR, Distill, Lilian Weng, Google AI, MS Research, PyTorch, NVIDIA | Google DeepMind сейчас отдаёт битый XML — фид отбрасывается, это не поломка скрипта |
| `hn` | Hacker News через Algolia (порог 80 очков, 7 дней) | работает без прокси |
| `arxiv` | свежие cs.AI/cs.LG/cs.CL | доступен напрямую, абстракты короткие — порог слов ниже |
| `reddit` | r/LocalLLaMA, r/MachineLearning, r/ArtificialIntelligence | JSON-эндпоинты дают **403**, скрипт читает RSS; при частых запусках — **429**, делай паузу |
| `devto` | Dev.to, тег ai | публичный API без токена |
| `lobsters` | lobste.rs, тег ai | публичный JSON |
| `github` | GitHub trending | парсится HTML — единственный хрупкий источник, при 0 кандидатов смотри разметку |
| `youtube` | поиск видео по 4 запросам | нужен `yt-dlp`, иначе источник пропускается |

### Настройка под свой блог

В начале скрипта есть блок `КОНФИГ` (правится руками, ничего больше трогать не нужно):

- `RSS_FEEDS` — добавь свои фиды, формат `("Имя", "URL")`;
- `REDDIT_SUBS`, `YOUTUBE_QUERIES` — списки сообществ и поисковых запросов;
- `MIN_WORDS` — порог «хватает ли материала на статью» по типам источников;
- `FRESH_DAYS` — насколько свежими должны быть новости;
- `SOURCE_WEIGHT` — вес источников при сортировке.

---

## 3. Обработать вывод

Скрипт даёт заготовку, финальные решения принимаешь ты:

1. **Проверь объём руками.** У топовых кандидатов смотри `full_words` (это результат
   скачивания страницы, а не оценка по сниппету). Если `full_words` пусто и в
   `extra.deep_error` ошибка — источник за пейволлом или отдаёт 403: такую тему либо
   бери из другого источника, либо выкидывай.
2. **Проверь дедуп глазами.** Выведи заголовки последних 15–20 постов блога и убедись,
   что темы не повторяют уже написанное. Скрипт сравнивает заголовки и slug'и, но
   пересказ одной и той же новости другими словами он не поймает.
3. **Добавь то, чего скрипт не знает:**
   - **спрос** — поищи тему в поисковике: есть ли устойчивые формулировки запросов;
   - **GEO-угол** — под какой вопрос «что такое X», «X против Y», «как работает X»
     статья может попасть в ответ ИИ-ассистента;
   - **почему сейчас** — одна фраза, привязывающая тему ко времени.
4. **Отсекай темы-двойники.** Три кандидата про один релиз — это одна тема.
5. **Не бери тему, у которой нет цифр** (бенчмарки, размеры, стоимость, latency).
   «ИИ меняет индустрию» статьёй не становится.

---

## 4. Оформить план

```markdown
### 1. <рабочий заголовок, ≤60 символов>
- **Источник:** <название> — <полный URL>
- **Дата:** YYYY-MM-DD
- **Тезис:** 2–3 предложения с цифрами: что произошло и в чём новизна
- **Угол:** под каким вопросом читателя писать (сравнение / разбор архитектуры / практика)
- **Сущности:** модели, методы, компании, бенчмарки — что упомянуть (для поиска и ИИ-выдачи)
- **Приоритет:** высокий | средний (+ одна фраза почему)
- **Риски:** пейволл / нет транскрипта / тема отцветёт за сутки
```

В конце файла — блок «Отклонено» по одной строке на тему: причина отказа.

Обнови `seen.json`: допиши URL всех тем, которые попали в план, чтобы в следующий
раз они не всплывали заново.

---

## 5. Проверка перед сдачей

- [ ] Каждый URL из плана открывается тобой (или скриптом в `--deep`) и отдаёт материал
- [ ] Ни одна тема не совпадает с уже опубликованными постами
- [ ] У каждой темы есть цифры из источника, а не по памяти
- [ ] Заголовки ≤60 символов, без AI-клише («революционный», «меняет всё», «стоит отметить»)
- [ ] Нет тем-двойников
- [ ] В отчёте перечислены источники с ошибками и их причины
- [ ] `seen.json` обновлён

---

## Чем это можно улучшить (по желанию)

- Кэш кандидатов в SQLite со статусами (`новый` / `в плане` / `опубликован`) вместо
  `seen.json` — тогда видно историю.
- Проверка спроса через API поисковика (например, подсказки запросов) с записью
  формулировок в план.
- Source-specific сборщики под твою тематику: arXiv по другим категориям, свои
  сабреддиты, Telegram-каналы через их RSS-мосты.
- Отправка готового плана в мессенджер или ноушен-страницу.

## Границы работы

- Ничего не публиковать, не коммитить в блог и не отправлять в каналы.
- Не обходить пейволлы и авторизацию; не выдумывать URL, ID, даты и числа.
- Уважать лимиты: при 429/403 делать паузу и писать об этом в отчёте, а не ретраить в цикле.
- Картинки не трогать вообще: план — это только темы и источники.

