feat: конвейер ingest→SQLite, API на БД, фронт сравнения цен
- etl/pipeline.py — extract→normalize→validate→SQLite, дедуп партнёров, версии цен, отчёт качества
- api/main.py — 7 эндпоинтов на SQLite; ядро /services/{id}/partners (сравнение)
- web/index.html — одностраничный фронт (палитра Nomad): поиск, сравнение, дашборд, unmatched
- загрузчик справочника: уникальный service_id
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -64,6 +64,7 @@ def load_dictionary(path: str | Path) -> list[Service]:
|
||||
raise ValueError(f"В справочнике нет колонки Name_ru. Заголовки: {header}")
|
||||
|
||||
services: list[Service] = []
|
||||
seen_ids: dict[str, int] = {}
|
||||
for row in rows:
|
||||
raw_name = row[name_idx]
|
||||
if not raw_name or not str(raw_name).strip():
|
||||
@@ -74,8 +75,15 @@ def load_dictionary(path: str | Path) -> list[Service]:
|
||||
if tar and not TARIFICATOR_RE.match(tar):
|
||||
tar = None # отбрасываем мусорные коды, оставляем только валидный формат
|
||||
|
||||
# «<ID>-<Code>» иногда повторяется в справочнике — гарантируем уникальность.
|
||||
sid = f"{row[col['ID']]}-{row[col['Code']]}"
|
||||
repeat = seen_ids.get(sid, 0)
|
||||
seen_ids[sid] = repeat + 1
|
||||
if repeat:
|
||||
sid = f"{sid}#{repeat}"
|
||||
|
||||
service = Service(
|
||||
service_id=f"{row[col['ID']]}-{row[col['Code']]}",
|
||||
service_id=sid,
|
||||
specialty=str(row[col["Специальность"]]).strip() if row[col["Специальность"]] else "",
|
||||
name_ru=str(raw_name).strip(),
|
||||
tarificator_code=tar,
|
||||
|
||||
Reference in New Issue
Block a user