Files

162 lines
8.3 KiB
Python

"""
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, -- исходный файл (может быть NULL, если есть original_b64)
parsed_text TEXT, -- текст после parser.py + textify.py
elements_json JSONB, -- полная структура: параграфы,таблицы,стили,страницы
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()
);
"""
# ── Таблица chunks ─────────────────────────────────────────────
# Чанковая загрузка: части файла до сборки.
# upload_id связывает чанки одного файла.
DDL_CHUNKS = """
CREATE TABLE IF NOT EXISTS chunks (
upload_id TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_data BYTEA NOT NULL,
filename TEXT,
mime TEXT,
total_chunks INTEGER,
cid TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (upload_id, chunk_index)
);
"""
# ── Таблица debug_log ─────────────────────────────────────────
DDL_DEBUG_LOG = """
CREATE TABLE IF NOT EXISTS debug_log (
id SERIAL PRIMARY KEY,
step TEXT NOT NULL,
detail TEXT,
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),
("chunks", DDL_CHUNKS),
("debug_log", DDL_DEBUG_LOG),
]
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}")
# Миграция: добавить elements_json если колонки ещё нет
db.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS elements_json JSONB")
db.execute("ALTER TABLE documents ADD COLUMN IF NOT EXISTS original_b64 TEXT")
db.execute("ALTER TABLE documents ALTER COLUMN original_bytes DROP NOT NULL")