""" schema.py — Схема базы данных (DDL). Создаёт все таблицы при старте приложения. Вызывается ОДИН раз из app.py: schema.ensure_schema(). Таблицы: documents — исходные файлы + распарсенный текст contracts — договоры (клиент, номер, дата) supplements — допники (привязка к договору) spec_rows — строки спецификаций (после LLM-извлечения) spec_history — история изменений строк между допниками """ import db # ── Таблица documents ────────────────────────────────────────── # Хранит исходный файл и результат парсинга (текст). # status отслеживает прогресс: uploaded → parsed → extracted → error DDL_DOCUMENTS = """ CREATE TABLE IF NOT EXISTS documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), filename TEXT NOT NULL, -- оригинальное имя файла mime_type TEXT NOT NULL, -- MIME-тип (docx/pdf/zip) original_bytes BYTEA NOT NULL, -- исходный файл как есть parsed_text TEXT, -- текст после parser.py + textify.py status TEXT DEFAULT 'uploaded', -- uploaded | parsed | extracted | error error_message TEXT, -- текст ошибки, если status=error created_at TIMESTAMPTZ DEFAULT now() -- когда загружен ); """ # ── Таблица contracts ────────────────────────────────────────── # Один договор = одна запись. Может иметь цепочку допников. DDL_CONTRACTS = """ CREATE TABLE IF NOT EXISTS contracts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), number TEXT NOT NULL, -- номер договора (например "03700_1") client TEXT, -- клиент (ЗАО "XXX001") date_signed DATE, -- дата подписания status TEXT DEFAULT 'active', -- active | closed created_at TIMESTAMPTZ DEFAULT now() ); """ # ── Таблица supplements ──────────────────────────────────────── # Каждый допник (включая изначальный договор) — отдельная запись. # type: initial (начальная спецификация) | amendment (изменение) | termination DDL_SUPPLEMENTS = """ CREATE TABLE IF NOT EXISTS supplements ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), contract_id UUID REFERENCES contracts(id), -- к какому договору number TEXT, -- номер допника date_signed DATE, -- дата подписания type TEXT DEFAULT 'initial', -- initial | amendment | termination document_id UUID REFERENCES documents(id), -- исходный файл created_at TIMESTAMPTZ DEFAULT now() ); """ # ── Таблица spec_rows ────────────────────────────────────────── # Строки спецификации после LLM-извлечения. # Одна строка = одна услуга в спецификации. DDL_SPEC_ROWS = """ CREATE TABLE IF NOT EXISTS spec_rows ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), supplement_id UUID REFERENCES supplements(id), -- из какого допника row_num INTEGER, -- № п/п (порядковый номер в таблице) name TEXT, -- наименование услуги price NUMERIC, -- цена за единицу qty NUMERIC, -- объём (количество) sum NUMERIC, -- сумма (price * qty) date_start DATE, -- дата начала оказания date_end DATE, -- дата окончания (может быть NULL) created_at TIMESTAMPTZ DEFAULT now() ); """ # ── Таблица spec_history ─────────────────────────────────────── # История изменений: какая строка когда и как поменялась. # change_type: added (новая) | changed (изменена) | deleted (удалена) | unchanged DDL_SPEC_HISTORY = """ CREATE TABLE IF NOT EXISTS spec_history ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), contract_id UUID REFERENCES contracts(id), -- для быстрых запросов supplement_id UUID REFERENCES supplements(id), -- в каком допнике изменение row_num INTEGER, -- № п/п строки change_type TEXT NOT NULL, -- added | changed | deleted | unchanged old_values JSONB, -- старые значения {price, qty, sum, ...} new_values JSONB, -- новые значения {price, qty, sum, ...} created_at TIMESTAMPTZ DEFAULT now() ); """ # ── Список всех DDL ──────────────────────────────────────────── ALL_DDL = [ ("documents", DDL_DOCUMENTS), ("contracts", DDL_CONTRACTS), ("supplements", DDL_SUPPLEMENTS), ("spec_rows", DDL_SPEC_ROWS), ("spec_history", DDL_SPEC_HISTORY), ] def ensure_schema(): """ Создаёт все таблицы, если их ещё нет. Вызывается при старте приложения (app.py). Безопасна для многократного вызова (IF NOT EXISTS). Использует db.execute() — DDL не возвращает строк. """ for name, ddl in ALL_DDL: rowcount, err = db.execute(ddl) if err: print(f"[schema] ⚠️ {name}: {err}") else: print(f"[schema] ✅ {name}")