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>
79 lines
5.2 KiB
SQL
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;
|