a6a6609c48
- db/migrations/001_init.sql — схема (4 сущности, prices jsonb, pgvector/pg_trgm) - contracts/models.py — общие модели (RawRow, ParsedDocument, MatchResult, *Out) - etl/dictionary.py — загрузчик справочника (1281 услуга, нормализация) - api/main.py — FastAPI: 7 эндпоинтов ТЗ + /stats (контракт) - infra/ — docker-compose (pg+pgvector, api, web), Caddyfile - scripts/proof/phase0_proof.py — проверка каскада на реальных данных (35% без эмбеддингов) - data/ в .gitignore: данные клиник в публичный репо не коммитятся Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
2.7 KiB
Python
81 lines
2.7 KiB
Python
"""Общие модели данных — контракт между агентами (Pydantic v2).
|
|
|
|
`RawRow` и `ParsedDocument` — выход любого парсера (граница агентов B → C).
|
|
`MatchResult` — выход сопоставления (агент C). `*Out`-модели — выдача API (агент D),
|
|
её же потребляет фронт (агент E). Любая правка этих типов проходит через оркестратора.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
# Карта тарифов: ключ — тип цены, значение — сумма в KZT.
|
|
# Возможные ключи: resident, nonresident, cis, far, insurance, partner.
|
|
PriceTiers = dict[str, float]
|
|
|
|
|
|
class RawRow(BaseModel):
|
|
"""Одна сырая строка прайса — единый выход любого парсера."""
|
|
|
|
service_name_raw: str
|
|
service_code_source: str | None = None
|
|
prices: PriceTiers = Field(default_factory=dict)
|
|
unit: str | None = None
|
|
section: str | None = None # заголовок секции — контекст специальности
|
|
|
|
|
|
class ParsedDocument(BaseModel):
|
|
"""Результат разбора одного файла прайса."""
|
|
|
|
file_name: str
|
|
file_format: str # pdf / scan_pdf / docx / xlsx / xls
|
|
partner_name: str | None = None
|
|
city: str | None = None
|
|
effective_date: date | None = None
|
|
rows: list[RawRow] = Field(default_factory=list)
|
|
raw_content: str = ""
|
|
parse_log: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class MatchResult(BaseModel):
|
|
"""Результат сопоставления строки со справочником."""
|
|
|
|
service_id: str | None = None
|
|
method: str | None = None # code / exact / embedding / fuzzy / manual
|
|
confidence: float = 0.0
|
|
|
|
|
|
# --- выдача API ---
|
|
class ServiceOut(BaseModel):
|
|
service_id: str
|
|
specialty: str
|
|
name_ru: str
|
|
tarificator_code: str | None = None
|
|
|
|
|
|
class PartnerOut(BaseModel):
|
|
partner_id: str
|
|
name: str
|
|
city: str | None = None
|
|
is_active: bool = True
|
|
|
|
|
|
class PriceOut(BaseModel):
|
|
partner_id: str
|
|
partner_name: str
|
|
price_resident_kzt: float | None = None
|
|
price_nonresident_kzt: float | None = None
|
|
prices: PriceTiers = Field(default_factory=dict)
|
|
effective_date: date | None = None
|
|
|
|
|
|
class Stats(BaseModel):
|
|
"""Сводка для дашборда и отчёта о качестве."""
|
|
|
|
documents_total: int = 0
|
|
documents_done: int = 0
|
|
items_total: int = 0
|
|
auto_matched_pct: float = 0.0
|
|
unmatched_total: int = 0
|