Session 2026-09-13: unify directions (canon+LLM-fallback, 966/1019), publisher dry_run fix, git repo, archive 8 changes, candidates-mail-ui WIP, docs

This commit is contained in:
kpa39l
2026-09-13 17:48:06 +00:00
parent c3f59f7b7a
commit 71fd48aa06
40 changed files with 305 additions and 7 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-11
@@ -0,0 +1,42 @@
## Дизайн
### 1. Фильтр `dt` в web/app.py
Рядом с `tpl.filters["from_json"]` (строка 29):
```python
from datetime import datetime
def dt_filter(value) -> str:
if not value:
return ""
try:
if isinstance(value, str):
value = datetime.fromisoformat(value.replace("Z", "+00:00"))
return value.strftime("%H:%M %d.%m.%Y")
except (ValueError, TypeError):
return str(value)[:16] if value else ""
tpl.filters["dt"] = dt_filter
```
Примечание: `fromisoformat` в Python 3.11+ понимает `2026-08-15T15:53` без секунд и с
`T`-разделителем. Если строка `2026-08-15T15:53:00` — тоже ок.
### 2. Шаблоны
| Файл | Строка сейчас | Станет |
|---|---|---|
| `candidates.html` | `{{ (p.published_at or '')[:16] }}` | `{{ p.published_at \| dt }}` |
| `published.html:10` | `{{ (p.published_at or '')[:16] }}` | `{{ p.published_at \| dt }}` |
| `metrics.html:23` | `{{ (r.started_at or '')[:16] }}` | `{{ r.started_at \| dt }}` |
В `candidates.html` уточнить: сейчас строка `{{ (p.published_at or '')[:16] }}` с префиксом
`{{ source_name }} · ` — оставить `{{ p.source_name }} · {{ p.published_at | dt }}`.
### 3. Проверка
- Строка `2026-08-15T15:53` → вывод `15:53 15.08.2026`.
- `None`/пусто → пустая строка (без "None").
- В metrics таблице время запуска в том же формате.
- `pytest`/curl: страницы рендерятся без 500.
@@ -0,0 +1,26 @@
## Why
Даты в веб-UI отображаются как ISO-строка из SQLite: `2026-08-15T15:53` (срез `[:16]`
в шаблонах `candidates.html`, `published.html`, `metrics.html`). Пользователь хочет
человекочитаемый формат: `20:00 15.08.2026` (время ЧЧ:ММ, дата ДД.ММ.ГГГГ).
Места:
- `web/templates/candidates.html:…` — `{{ (p.published_at or '')[:16] }}`
- `web/templates/published.html:10` — `{{ (p.published_at or '')[:16] }}`
- `web/templates/metrics.html:23` — `{{ (r.started_at or '')[:16] }}`
## What Changes
- Добавить Jinja2-фильтр `dt` (datetime): парсит ISO-строку `2026-08-15T15:53[:00]`,
выводит `20:00 15.08.2026`.
- Во всех трёх шаблонах заменить `{{ (x or '')[:16] }}` → `{{ x | dt }}`.
- Использовать `datetime.fromisoformat` + `strftime("%H:%M %d.%m.%Y")`.
- Если строка не парсится (None/мусор) — выводить пустую строку/прочерк (не падать).
## Impact
- Файлы: `web/app.py` (фильтр `dt`), шаблоны `candidates.html`, `published.html`,
`metrics.html` (замена вызовов).
- Новых зависимостей нет (stdlib `datetime`).
- Никакого JS: форматирование на сервере.
- Rollback: вернуть `[:16]` в трёх шаблонах, убрать фильтр.
@@ -0,0 +1,17 @@
## 1. Фильтр
- [x] 1.1 Добавить `dt_filter` в `web/app.py` (datetime.fromisoformat → `%H:%M %d.%m.%Y`, безопасно к None/мусору)
- [x] 1.2 Зарегистрировать как `tpl.filters["dt"]`
## 2. Применение в шаблонах
- [x] 2.1 `candidates.html`: `{{ (p.published_at or '')[:16] }}` → `{{ p.published_at | dt }}`
- [x] 2.2 `published.html:10`: то же для `published_at`
- [x] 2.3 `metrics.html:23`: `{{ (r.started_at or '')[:16] }}` → `{{ r.started_at | dt }}`
- [x] 2.4 Проверка: `grep -rn "\[:16\]" web/templates/` — пусто
## 3. Проверка
- [x] 3.1 Дата в UI: `15:53 15.08.2026` (а не `2026-08-15T15:53`)
- [x] 3.2 None → пустая строка, страницы без ошибок
- [x] 3.3 Регресс: candidates/published/metrics рендерятся с новым форматом