-- Схема 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, -- «-» из справочника 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;