Files
medtech-hackathon/db/migrations/001_init.sql
T
admins a6a6609c48 feat: Фаза 0 — каркас, контракты, загрузчик справочника
- 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>
2026-06-26 15:23:06 +05:00

79 lines
5.2 KiB
SQL

-- Схема MedArchive. Четыре сущности из ТЗ §3, адаптированные под реальные данные:
-- многотарифные цены (резидент/нерезидент/СНГ/…) хранятся в prices (jsonb),
-- а два главных тарифа продублированы отдельными колонками под ТЗ и API.
CREATE EXTENSION IF NOT EXISTS vector; -- pgvector для семантического сопоставления
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- триграммы для нечёткого поиска
-- Эталонный справочник услуг (1281 позиция, загружается из xlsx).
CREATE TABLE service (
service_id text PRIMARY KEY, -- «<ID>-<Code>» из справочника
specialty text NOT NULL DEFAULT '', -- специальность = категория + сужение поиска
name_ru text NOT NULL,
name_norm text NOT NULL, -- нормализованная форма для точного совпадения
tarificator_code text, -- код тарификатора, если задан
synonyms jsonb NOT NULL DEFAULT '[]'::jsonb, -- наращиваем при ручной верификации
embedding vector(384), -- эмбеддинг name_ru (multilingual)
is_active boolean NOT NULL DEFAULT true
);
CREATE INDEX service_tarificator_idx ON service (tarificator_code) WHERE tarificator_code IS NOT NULL;
CREATE INDEX service_name_trgm_idx ON service USING gin (name_ru gin_trgm_ops);
CREATE INDEX service_embedding_idx ON service USING hnsw (embedding vector_cosine_ops);
-- Клиника-партнёр.
CREATE TABLE partner (
partner_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
city text,
address text,
bin varchar(12), -- БИН организации, для дедупликации
contact_email text,
contact_phone text,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX partner_bin_idx ON partner (bin) WHERE bin IS NOT NULL;
-- Исходный прайс-документ (один файл = прайс одной клиники на дату).
CREATE TABLE price_document (
doc_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
partner_id uuid NOT NULL REFERENCES partner(partner_id),
file_name text NOT NULL,
file_format text NOT NULL, -- pdf / scan_pdf / docx / xlsx / xls
effective_date date, -- дата вступления в силу (год из имени файла)
parsed_at timestamptz,
parse_status text NOT NULL DEFAULT 'pending', -- pending/processing/done/error/needs_review
parse_log text,
raw_content text -- сырой извлечённый текст для аудита
);
-- Позиция прайса (услуга + цены). Это рабочая лошадка системы.
CREATE TABLE price_item (
item_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
doc_id uuid NOT NULL REFERENCES price_document(doc_id),
partner_id uuid NOT NULL REFERENCES partner(partner_id), -- денормализация ради скорости
service_name_raw text NOT NULL, -- название как в документе
service_code_source text, -- код из источника (внутренний или тарификатора)
service_id text REFERENCES service(service_id), -- нормализованная услуга (nullable)
prices jsonb NOT NULL DEFAULT '{}'::jsonb, -- {resident, nonresident, cis, far, insurance, partner}
price_resident_kzt numeric, -- главный тариф, продублирован под ТЗ/API
price_nonresident_kzt numeric,
currency_original text NOT NULL DEFAULT 'KZT',
unit text,
effective_date date,
map_method text, -- code/exact/embedding/fuzzy/manual
map_confidence numeric,
is_verified boolean NOT NULL DEFAULT false,
verification_note text,
is_active boolean NOT NULL DEFAULT true,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX price_item_service_idx ON price_item (service_id);
CREATE INDEX price_item_partner_idx ON price_item (partner_id);
CREATE INDEX price_item_unmatched_idx ON price_item (service_id) WHERE service_id IS NULL;
CREATE INDEX price_item_raw_trgm_idx ON price_item USING gin (service_name_raw gin_trgm_ops);
-- Активная позиция уникальна по клинике, названию и дате прайса (версионирование — через is_active).
CREATE UNIQUE INDEX price_item_active_idx
ON price_item (partner_id, service_name_raw, effective_date) WHERE is_active;