From 62794b38575967f0f702184cf8599fa152c21682 Mon Sep 17 00:00:00 2001 From: hyungi Date: Mon, 29 Jun 2026 22:56:59 +0000 Subject: [PATCH] =?UTF-8?q?feat(search):=20ASME=20=EC=A0=88-KB=20schema=20?= =?UTF-8?q?379=20+=20doc=5Fkind=20retrieval=20=ED=95=84=ED=84=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - migration 379: documents +parent_id/doc_kind/clause_code/clause_part/clause_order + clause_links/document_tags - _license_sql 에 doc_kind=standard 필터(절-문서 read/nav 전용, 검색 제외; 전 문서 standard=동작보존) Co-Authored-By: Claude Opus 4.8 (1M context) --- app/services/search/retrieval_service.py | 4 ++- migrations/379_asme_clause_kb.sql | 37 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 migrations/379_asme_clause_kb.sql diff --git a/app/services/search/retrieval_service.py b/app/services/search/retrieval_service.py index cf93602..3db8cfa 100644 --- a/app/services/search/retrieval_service.py +++ b/app/services/search/retrieval_service.py @@ -142,7 +142,9 @@ def _license_sql(alias: str) -> str: 술어 정의 = license_filter.restricted_exclude_sql 공유(digest/briefing/study 풀이와 단일 source). """ from services.search.license_filter import restricted_exclude_sql - return " AND " + restricted_exclude_sql(alias) + _p = (alias + ".") if alias else "" + # ASME clause-KB(379): clause docs (doc_kind='clause') = read/nav/backlink only, excluded from retrieval/digest legs. + return " AND " + restricted_exclude_sql(alias) + f" AND {_p}doc_kind = 'standard'" def cloud_eligible_doc_sql(alias: str = "") -> str: diff --git a/migrations/379_asme_clause_kb.sql b/migrations/379_asme_clause_kb.sql new file mode 100644 index 0000000..b092c49 --- /dev/null +++ b/migrations/379_asme_clause_kb.sql @@ -0,0 +1,37 @@ +-- 379_asme_clause_kb.sql +-- ASME 절-지식베이스: 절 = 개별 documents 행(parent_id) + 절↔절 백링크 + 태깅 (additive, idempotent) +-- 검색 무접촉: 절 doc 은 embedding NULL(벡터 제외) + doc_kind='clause'(retrieval doc-leg 필터로 제외). + +ALTER TABLE documents + ADD COLUMN IF NOT EXISTS parent_id bigint REFERENCES documents(id), + ADD COLUMN IF NOT EXISTS doc_kind text NOT NULL DEFAULT 'standard', + ADD COLUMN IF NOT EXISTS clause_code text, + ADD COLUMN IF NOT EXISTS clause_part text, + ADD COLUMN IF NOT EXISTS clause_order int; + +CREATE INDEX IF NOT EXISTS idx_documents_parent_id ON documents(parent_id) WHERE parent_id IS NOT NULL; +CREATE INDEX IF NOT EXISTS idx_documents_doc_kind ON documents(doc_kind); +CREATE INDEX IF NOT EXISTS idx_documents_clause_code ON documents(clause_code) WHERE clause_code IS NOT NULL; + +-- 절↔절 백링크 (dangling 허용: dst_doc_id nullable) +CREATE TABLE IF NOT EXISTS clause_links ( + id bigserial PRIMARY KEY, + src_doc_id bigint NOT NULL REFERENCES documents(id) ON DELETE CASCADE, + dst_code text NOT NULL, + dst_doc_id bigint REFERENCES documents(id) ON DELETE SET NULL, + anchor text, + ctx text, + char_off int +); +CREATE INDEX IF NOT EXISTS idx_clause_links_src ON clause_links(src_doc_id); +CREATE INDEX IF NOT EXISTS idx_clause_links_dst ON clause_links(dst_doc_id) WHERE dst_doc_id IS NOT NULL; +CREATE INDEX IF NOT EXISTS idx_clause_links_dstcode ON clause_links(dst_code); + +-- 태깅 (Part 자동 + 주제) +CREATE TABLE IF NOT EXISTS document_tags ( + doc_id bigint NOT NULL REFERENCES documents(id) ON DELETE CASCADE, + tag text NOT NULL, + tag_kind text NOT NULL DEFAULT 'topic', + PRIMARY KEY (doc_id, tag) +); +CREATE INDEX IF NOT EXISTS idx_document_tags_tag ON document_tags(tag);