Files
Kokorone/prisma/schema.prisma
T
Jeffery 767b17e257 feat: 完成 E 群組 — 關係子系統
- RelationshipService:E-1 未知對象自動建檔(禮貌+防備=intimacy/trust=0)、
  E-3 親密度五階分層+跨階事件(@nestjs/event-emitter,StageChangeLogService 獨立訂閱)、
  E-4 久未互動時親密度半衰期衰減並標記「久未見」
- SentimentLedgerService:E-2 關係帳本,負向事件套用放大係數,驗證同等級正負事件淨值為負
- IntentInterpreterService:E-6 依親密度將同一句模稜兩可的話解讀為玩笑或冒犯
- KeywordMemoryRetriever(C-6)擴充 E-5:新增 relatedUserId 加權,retrieve() 簽名改為接受 options 物件
- schema 新增 EpisodicMemory.relatedUserId,並補上所有子表對父表的 onDelete: Cascade/SetNull
  (E-2 測試時發現先前 B 群組遺漏這些規則會導致刪除關係時外鍵違反)
- apps/api 新增 /relationships/* 端點作為引擎驗證介面
- scripts/smoke/E.mjs:涵蓋 E-1~E-6 全部驗收情境

npm run restart && npm run smoke -- E 皆通過(E-V),A/B/C/D 群組冒煙測試無回歸。

本次另排除一個本機環境限定的 Prisma CLI 網路 checkpoint 卡住問題,並從一次部分套用的
migration 中手動恢復資料庫一致性(過程記錄於 todo.md 實作記錄,供後續群組參考)。
2026-08-13 10:43:33 +08:00

209 lines
6.2 KiB
Plaintext

// Kokorone 資料模型
// R-2 會將 provider 由 sqlite 切換為 postgresql,並為 MemoryRetriever 加上 pgvector 語意檢索。
generator client {
provider = "prisma-client"
output = "../packages/db/generated"
}
datasource db {
provider = "sqlite"
}
enum CharacterSource {
ORIGINAL // 原創
EXISTING_WORK // 既有作品
}
enum CharacterBuildStatus {
CANDIDATE // 候補
BUILT // 已建置
}
enum MemorySource {
INTERACTION // 互動
OFFLINE_GENERATED // 離線生成
SOURCE_EXTRACTION // 原作萃取
}
// 情緒標籤類型:對應情緒子系統六種狀態(見 D 群組)
enum EmotionTag {
CALM // 平靜
JOY // 愉悅
SAD // 低落
ALERT // 警戒
SHY // 害羞
GRUMPY // 彆扭
}
enum RelationshipStage {
STRANGER // 陌生 0-19
ACQUAINTANCE // 認識 20-39
FRIEND // 朋友 40-59
CLOSE_AMBIGUOUS // 摯友-曖昧 60-79
BONDED // 羈絆 80-100
}
model User {
id String @id @default(cuid())
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
relationships Relationship[]
relatedMemories EpisodicMemory[]
@@map("users")
}
model Work {
id String @id @default(cuid())
title String
volumeProgress String? // 卷數進度
worldview String? // 世界觀
progressAnchor String? // 作品進度錨定點(M-9/N 群組使用;描述作品實際描寫過的時期)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
characters Character[]
@@map("works")
}
model Character {
id String @id @default(cuid())
workId String?
work Work? @relation(fields: [workId], references: [id], onDelete: SetNull)
source CharacterSource
buildStatus CharacterBuildStatus @default(CANDIDATE)
// 角色設定表七欄位
basicInfo String // 基本資料:名字、年齡、外型、出身
backgroundStory String // 背景故事:成長經歷、重大事件
personalityArchetype String // 性格原型:傲嬌/冷淡/天然呆/元氣/大小姐/三無
likesDislikes String // 喜好/厭惡
goalsObsessions String // 目標/執念
speechStyle String // 說話方式:口癖、語尾詞、第一/第二人稱
initialRelationValue String? // 人際初始值:對誰親近、對誰有心結
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
aliases CharacterAlias[]
episodicMemories EpisodicMemory[]
semanticMemories SemanticMemory[]
proceduralRules ProceduralRule[]
emotionState EmotionState?
relationships Relationship[]
@@map("characters")
}
model CharacterAlias {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
formalName String // 正式名
nickname String? // 暱稱
calledByOthers String? // 他人稱呼(作品中其他角色如何稱呼)
createdAt DateTime @default(now())
@@map("character_aliases")
}
model EpisodicMemory {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
content String
occurredAt DateTime // 發生時間
emotionTag EmotionTag
emotionIntensity Float // 情緒強度
retrievalCount Int @default(0) // 提取次數
lastRetrievedAt DateTime?
source MemorySource
weight Float @default(1) // 權重(供遺忘/檢索排序使用)
relatedUserId String? // 這段記憶與哪位使用者相關(E-5 關係加權檢索用)
relatedUser User? @relation(fields: [relatedUserId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@map("episodic_memories")
}
model SemanticMemory {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
fact String // 去情境化事實
about String // 對象(關於誰/什麼)
createdAt DateTime @default(now())
@@map("semantic_memories")
}
model ProceduralRule {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
situation String // 情境
responsePattern String // 回應模式
weight Float @default(1)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("procedural_rules")
}
model EmotionState {
id String @id @default(cuid())
characterId String @unique
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
calm Float @default(100) // 平靜
joy Float @default(0) // 愉悅
sad Float @default(0) // 低落
alert Float @default(0) // 警戒
shy Float @default(0) // 害羞
grumpy Float @default(0) // 彆扭
updatedAt DateTime @updatedAt
@@map("emotion_states")
}
model Relationship {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
intimacy Int @default(0) // 親密度 0~100
trust Int @default(0) // 信任度 0~100
firstMet DateTime?
lastInteraction DateTime?
interactionCount Int @default(0)
stage RelationshipStage @default(STRANGER)
ledgerEntries SentimentLedgerEntry[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([characterId, userId])
@@map("relationships")
}
model SentimentLedgerEntry {
id String @id @default(cuid())
relationshipId String
relationship Relationship @relation(fields: [relationshipId], references: [id], onDelete: Cascade)
date DateTime
event String
weight Float // 允許正負:正向事件為正值,負向事件為負值
createdAt DateTime @default(now())
@@map("sentiment_ledger_entries")
}