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
+21 -8
View File
@@ -70,8 +70,14 @@ def _parse_response(text: str) -> list[RawRow]:
return rows
def extract_with_vision(path: str, max_pages: int = 12, dpi: int = 140) -> list[RawRow]:
"""Извлечь позиции из PDF через Gemini Vision. Возвращает список RawRow."""
def extract_with_vision(path: str, max_pages: int = 40, dpi: int = 140) -> list[RawRow]:
"""Извлечь позиции из PDF через Gemini Vision. Возвращает список RawRow.
Страницы распознаются по одной. При превышении квоты (429) делается пауза и
повтор — большой документ не должен срываться из-за лимита запросов в минуту.
"""
import time
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise RuntimeError("нет GEMINI_API_KEY в окружении")
@@ -85,10 +91,17 @@ def extract_with_vision(path: str, max_pages: int = 12, dpi: int = 140) -> list[
rows: list[RawRow] = []
for png in _render_pages(path, max_pages, dpi):
response = client.models.generate_content(
model=model,
contents=[types.Part.from_bytes(data=png, mime_type="image/png"), _PROMPT],
config=config,
)
rows.extend(_parse_response(response.text))
part = types.Part.from_bytes(data=png, mime_type="image/png")
for attempt in range(6):
try:
response = client.models.generate_content(
model=model, contents=[part, _PROMPT], config=config
)
rows.extend(_parse_response(response.text))
break
except Exception as exc: # пауза и повтор при превышении квоты (429)
if ("RESOURCE_EXHAUSTED" in str(exc) or "429" in str(exc)) and attempt < 5:
time.sleep(15)
continue
raise
return rows