feat(ingest+поиск): приём ZIP через интерфейс, регистронезависимый поиск, чистка очереди

- Загрузка архива (§4.1): POST /ingest распаковывает ZIP, дораспознаёт и
  догружает прайсы в базу (append). Виджет в админке. Кэш эмбеддингов справочника
  (dict_emb.npy) — догрузка считает только новый файл, эмбеддинги best-effort с
  откатом на fuzzy при сбое Gemini.
- Поиск по name_norm (lowercase): «УЗИ» и «узи» дают одинаковый результат —
  SQLite LIKE не сворачивает регистр для кириллицы.
- Фильтр названий: код-мнемоники («МОЗО.15») больше не попадают в очередь.
- /stats считается из базы — цифры на дашборде обновляются после загрузки.
- Деплой через Dockerfile (зависимости в образе, код монтируется томом).
- ruff: ядро и весь репозиторий проходят проверку.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 11:35:12 +05:00
parent 89928511ba
commit 69a9884db5
25 changed files with 2217 additions and 182 deletions
+8 -2
View File
@@ -4,6 +4,7 @@
многолистовых книг и многотабличных PDF. Сырой текст нужен для аудита и для
детектора битого текстового слоя.
"""
from __future__ import annotations
import re
@@ -29,14 +30,19 @@ def partner_from_name(name: str) -> str:
def detect_format(path: str) -> str:
ext = Path(path).suffix.lower()
return {".xlsx": "xlsx", ".xls": "xls", ".docx": "docx", ".pdf": "pdf"}.get(ext, ext.lstrip("."))
return {".xlsx": "xlsx", ".xls": "xls", ".docx": "docx", ".pdf": "pdf"}.get(
ext, ext.lstrip(".")
)
def docx_grids(path: str) -> tuple[list[Grid], str]:
import docx
document = docx.Document(path)
grids = [[[clean(cell.text) for cell in row.cells] for row in table.rows] for table in document.tables]
grids = [
[[clean(cell.text) for cell in row.cells] for row in table.rows]
for table in document.tables
]
text = "\n".join(p.text for p in document.paragraphs)
return grids, text