// 插件用到的基本盤 template 定義(= 替代建表)。 // 鐵律:插件新類型只能建 template,不建表。這裡集中宣告 slot schema, // 任何寫入前先 client.ensureTemplate 確保存在。 import type { KbdbClient } from './kbdb-client'; import type { Triplet, Entity } from '../types'; import type { BaseRecord } from './kbdb-client'; export const TPL_TRIPLET = 'triplet'; export const TPL_ENTITY = 'entity'; export const TPL_ENTITY_PENDING = 'entity_pending'; export const TRIPLET_SLOTS = [ 'subject', 'predicate', 'object', 'source_block_id', 'confidence', 'clusters_json', 'bridge_score', 'subject_entity_type', 'object_entity_type', // 取代/快照(T3.2):status=active|deprecated;superseded_by=取代它的新 record id; // source_uri+content_hash 承載 ingest idempotency(按 source_uri 分組 deprecate)。 // source_anchor 供 get_source 精準回跳原文(T3.7)。 'status', 'superseded_by', 'source_uri', 'content_hash', 'source_anchor', ]; // gloss(T3.2b):一句話描述,供「詞+gloss」語義 normalize 的 embedding 對象。 export const ENTITY_SLOTS = ['canonical', 'aliases_json', 'entity_type', 'owner', 'gloss']; export const ENTITY_PENDING_SLOTS = [ 'raw_name', 'candidate_entity_id', 'candidate_canonical', 'similarity', ]; /** 確保插件三個 template 存在(idempotent,走 API)。 */ export async function ensurePluginTemplates(client: KbdbClient): Promise { await client.ensureTemplate(TPL_TRIPLET, TRIPLET_SLOTS, 'knowledge graph triplet (S-P-O)'); await client.ensureTemplate(TPL_ENTITY, ENTITY_SLOTS, 'normalized entity (canonical + aliases)'); await client.ensureTemplate(TPL_ENTITY_PENDING, ENTITY_PENDING_SLOTS, 'pending entity alias for review'); } /** 基本盤 record → 插件 Triplet 型別。 */ export function recordToTriplet(rec: BaseRecord): Triplet { const v = rec.values; return { id: rec.record_id, subject: v.subject ?? '', predicate: v.predicate ?? '', object: v.object ?? '', source_block_id: v.source_block_id ?? null, confidence: parseFloat(v.confidence ?? '1.0'), clusters: safeArr(v.clusters_json), bridge_score: parseInt(v.bridge_score ?? '0', 10), subject_entity_type: (v.subject_entity_type as Triplet['subject_entity_type']) || null, object_entity_type: (v.object_entity_type as Triplet['object_entity_type']) || null, // 缺省視為 active(相容尚無 status slot 的舊資料)。 status: v.status === 'deprecated' ? 'deprecated' : 'active', superseded_by: v.superseded_by || null, source_uri: v.source_uri || null, content_hash: v.content_hash || null, source_anchor: v.source_anchor || null, created_at: 0, updated_at: 0, }; } /** 基本盤 record → 插件 Entity 型別。 */ export function recordToEntity(rec: BaseRecord): Entity { return { id: rec.record_id, canonical: rec.values.canonical ?? '', aliases: safeArr(rec.values.aliases_json), }; } function safeArr(json?: string): string[] { try { const p = JSON.parse(json || '[]'); return Array.isArray(p) ? p : []; } catch { return []; } }