feat(извлечение): надёжное распознавание колонки названий + добор трудных PDF через Vision

- grid: колонки «Код …» и «№» исключены из кандидатов на колонку названий
  (ключ «услуг» ловил «Код услуги») — клиника с 5181 строкой больше не даёт 0.
- vision: авто-добор PDF при пустом или подозрительно низком выходе строк/страницу,
  замена недобора полным распознаванием, ретраи на лимиты, лимит размера документа.
- презентация статуса проекта на /day2; маршрут /day2 в Caddy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 12:39:55 +05:00
parent 7d339dce87
commit ac9f7fea15
6 changed files with 255 additions and 21 deletions
+9 -2
View File
@@ -97,16 +97,23 @@ def _assign_columns(grid: Grid, header_idx: int):
price_cols[c] = fallback.pop(0) if fallback else f"extra_{c}"
# --- колонка названия: явный заголовок, иначе самая кириллическая ---
# Колонки кода/индекса исключаем заранее: иначе ключ «услуг» из _NAME_KEYS ловит
# заголовок «Код услуги» и колонка кодов ошибочно становится колонкой названий.
def _is_code_or_index(c: int) -> bool:
return any(k in header[c].lower() for k in _CODE_HEADER_KEYS + _INDEX_HEADER_KEYS)
name_col = next(
(
c
for c in range(ncol)
if c not in price_cols and any(k in header[c].lower() for k in _NAME_KEYS)
if c not in price_cols
and not _is_code_or_index(c)
and any(k in header[c].lower() for k in _NAME_KEYS)
),
None,
)
if name_col is None:
candidates = [c for c in range(ncol) if c not in price_cols]
candidates = [c for c in range(ncol) if c not in price_cols and not _is_code_or_index(c)]
name_col = max(candidates, key=lambda c: _cyrillic_score(sample, c)) if candidates else 0
# --- колонка кода: заголовок «Код»/«тарификатор» или код-подобные значения ---