Files
vesti/openspec/changes/candidates-only-external-new/design.md
T
kpa39l acb49cc702 fix(candidates): только внешние new-посты в списке кандидатов
- _fetch_candidates: в WHERE всегда is_own=0 AND status='new' → свои посты
  канала @dedinit (is_own=1, backfill) и published/rejected больше не
  показываются как кандидаты
- counts табов: считаются по внешним постам (не по жёсткому where), чтобы
  табы статусов показывали реальные счётчики
- change: candidates-only-external-new (skip_specs)
2026-09-14 05:11:54 +00:00

3.2 KiB

Design

Файл: /opt/vesti/web/app.py, функция _fetch_candidates.

Текущее (строки ~151-165):

    where = []
    params = []
    if direction:
        where.append("p.direction=?")
        params.append(direction)
    if status:
        where.append("p.status=?")
        params.append(status)
    if own in ("1", "0"):
        where.append("p.is_own=?")
        params.append(int(own))
    if q:
        where.append("(p.text LIKE ? OR p.summary LIKE ?)")
        params += [f"%{q}%", f"%{q}%"]
    w = ("WHERE " + " AND ".join(where)) if where else ""

Проблемы:

  1. При status='' (по умолчанию) в кандидаты попадают published и rejected.
  2. own='' (по умолчанию) не фильтрует is_own → в кандидаты попадают 846 постов собственного канала (is_own=1), которые не являются кандидатами.

Правка — два условия добавляются в WHERE всегда:

    where = []
    params = []
    # Кандидаты = только внешние (is_own=0) посты в статусе new.
    # Свои посты канала (is_own=1) не являются кандидатами — это контент
    # собственного канала, управляется отдельно (fan-out); published/rejected —
    # уже решённые посты, им не место в очереди кандидатов.
    where.append("p.is_own=0")
    where.append("p.status='new'")
    if direction:
        where.append("p.direction=?")
        params.append(direction)
    if status and status != "new":
        # Явный фильтр статуса (rejected/published) — просмотр решённых;
        # для 'new' условие уже добавлено выше.
        where.append("p.status=?")
        params.append(status)
    if own in ("1", "0"):
        where.append("p.is_own=?")
        params.append(int(own))
    if q:
        where.append("(p.text LIKE ? OR p.summary LIKE ?)")
        params += [f"%{q}%", f"%{q}%"]
    w = ("WHERE " + " AND ".join(where)) if where else ""

Примечание: own='1' (⭐ Свои) вернёт пустую выборку — это корректно: свои посты не кандидаты. Фильтр own оставлен для обратной совместимости (внешние = own='0' = все кандидаты).

Верификация

  • openspec validate candidates-only-external-new — чисто.
  • Юнит: python -c "import sys; sys.path.insert(0,'/opt/vesti'); import web.app as A; c=A._db(); g,co,st=A._fetch_candidates(c,'','','','','date'); print([(x['key'],len(x['posts'])) for x in g]); print('new всего постов:', sum(len(x['posts']) for x in g)); c.close()" → все группы: только внешние посты; is_own=1 посты отсутствуют, published/rejected отсутствуют.
  • Живой: GET /candidates → в списке нет ⭐, нет «✅ Опубликованные», нет published/rejected.
  • Рестарт sudo systemctl restart vesti-web.