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 實作記錄,供後續群組參考)。
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_episodic_memories" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"occurredAt" DATETIME NOT NULL,
|
||||
"emotionTag" TEXT NOT NULL,
|
||||
"emotionIntensity" REAL NOT NULL,
|
||||
"retrievalCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastRetrievedAt" DATETIME,
|
||||
"source" TEXT NOT NULL,
|
||||
"weight" REAL NOT NULL DEFAULT 1,
|
||||
"relatedUserId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "episodic_memories_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
CONSTRAINT "episodic_memories_relatedUserId_fkey" FOREIGN KEY ("relatedUserId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_episodic_memories" ("characterId", "content", "createdAt", "emotionIntensity", "emotionTag", "id", "lastRetrievedAt", "occurredAt", "retrievalCount", "source", "weight") SELECT "characterId", "content", "createdAt", "emotionIntensity", "emotionTag", "id", "lastRetrievedAt", "occurredAt", "retrievalCount", "source", "weight" FROM "episodic_memories";
|
||||
DROP TABLE "episodic_memories";
|
||||
ALTER TABLE "new_episodic_memories" RENAME TO "episodic_memories";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -0,0 +1,107 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_character_aliases" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"formalName" TEXT NOT NULL,
|
||||
"nickname" TEXT,
|
||||
"calledByOthers" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "character_aliases_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_character_aliases" ("calledByOthers", "characterId", "createdAt", "formalName", "id", "nickname") SELECT "calledByOthers", "characterId", "createdAt", "formalName", "id", "nickname" FROM "character_aliases";
|
||||
DROP TABLE "character_aliases";
|
||||
ALTER TABLE "new_character_aliases" RENAME TO "character_aliases";
|
||||
CREATE TABLE "new_emotion_states" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"calm" REAL NOT NULL DEFAULT 100,
|
||||
"joy" REAL NOT NULL DEFAULT 0,
|
||||
"sad" REAL NOT NULL DEFAULT 0,
|
||||
"alert" REAL NOT NULL DEFAULT 0,
|
||||
"shy" REAL NOT NULL DEFAULT 0,
|
||||
"grumpy" REAL NOT NULL DEFAULT 0,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "emotion_states_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_emotion_states" ("alert", "calm", "characterId", "grumpy", "id", "joy", "sad", "shy", "updatedAt") SELECT "alert", "calm", "characterId", "grumpy", "id", "joy", "sad", "shy", "updatedAt" FROM "emotion_states";
|
||||
DROP TABLE "emotion_states";
|
||||
ALTER TABLE "new_emotion_states" RENAME TO "emotion_states";
|
||||
CREATE UNIQUE INDEX "emotion_states_characterId_key" ON "emotion_states"("characterId");
|
||||
CREATE TABLE "new_episodic_memories" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"occurredAt" DATETIME NOT NULL,
|
||||
"emotionTag" TEXT NOT NULL,
|
||||
"emotionIntensity" REAL NOT NULL,
|
||||
"retrievalCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"lastRetrievedAt" DATETIME,
|
||||
"source" TEXT NOT NULL,
|
||||
"weight" REAL NOT NULL DEFAULT 1,
|
||||
"relatedUserId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "episodic_memories_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "episodic_memories_relatedUserId_fkey" FOREIGN KEY ("relatedUserId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_episodic_memories" ("characterId", "content", "createdAt", "emotionIntensity", "emotionTag", "id", "lastRetrievedAt", "occurredAt", "relatedUserId", "retrievalCount", "source", "weight") SELECT "characterId", "content", "createdAt", "emotionIntensity", "emotionTag", "id", "lastRetrievedAt", "occurredAt", "relatedUserId", "retrievalCount", "source", "weight" FROM "episodic_memories";
|
||||
DROP TABLE "episodic_memories";
|
||||
ALTER TABLE "new_episodic_memories" RENAME TO "episodic_memories";
|
||||
CREATE TABLE "new_procedural_rules" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"situation" TEXT NOT NULL,
|
||||
"responsePattern" TEXT NOT NULL,
|
||||
"weight" REAL NOT NULL DEFAULT 1,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "procedural_rules_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_procedural_rules" ("characterId", "createdAt", "id", "responsePattern", "situation", "updatedAt", "weight") SELECT "characterId", "createdAt", "id", "responsePattern", "situation", "updatedAt", "weight" FROM "procedural_rules";
|
||||
DROP TABLE "procedural_rules";
|
||||
ALTER TABLE "new_procedural_rules" RENAME TO "procedural_rules";
|
||||
CREATE TABLE "new_relationships" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"intimacy" INTEGER NOT NULL DEFAULT 0,
|
||||
"trust" INTEGER NOT NULL DEFAULT 0,
|
||||
"firstMet" DATETIME,
|
||||
"lastInteraction" DATETIME,
|
||||
"interactionCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"stage" TEXT NOT NULL DEFAULT 'STRANGER',
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "relationships_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "relationships_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_relationships" ("characterId", "createdAt", "firstMet", "id", "interactionCount", "intimacy", "lastInteraction", "stage", "trust", "updatedAt", "userId") SELECT "characterId", "createdAt", "firstMet", "id", "interactionCount", "intimacy", "lastInteraction", "stage", "trust", "updatedAt", "userId" FROM "relationships";
|
||||
DROP TABLE "relationships";
|
||||
ALTER TABLE "new_relationships" RENAME TO "relationships";
|
||||
CREATE UNIQUE INDEX "relationships_characterId_userId_key" ON "relationships"("characterId", "userId");
|
||||
CREATE TABLE "new_semantic_memories" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"characterId" TEXT NOT NULL,
|
||||
"fact" TEXT NOT NULL,
|
||||
"about" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "semantic_memories_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_semantic_memories" ("about", "characterId", "createdAt", "fact", "id") SELECT "about", "characterId", "createdAt", "fact", "id" FROM "semantic_memories";
|
||||
DROP TABLE "semantic_memories";
|
||||
ALTER TABLE "new_semantic_memories" RENAME TO "semantic_memories";
|
||||
CREATE TABLE "new_sentiment_ledger_entries" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"relationshipId" TEXT NOT NULL,
|
||||
"date" DATETIME NOT NULL,
|
||||
"event" TEXT NOT NULL,
|
||||
"weight" REAL NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT "sentiment_ledger_entries_relationshipId_fkey" FOREIGN KEY ("relationshipId") REFERENCES "relationships" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_sentiment_ledger_entries" ("createdAt", "date", "event", "id", "relationshipId", "weight") SELECT "createdAt", "date", "event", "id", "relationshipId", "weight" FROM "sentiment_ledger_entries";
|
||||
DROP TABLE "sentiment_ledger_entries";
|
||||
ALTER TABLE "new_sentiment_ledger_entries" RENAME TO "sentiment_ledger_entries";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
Reference in New Issue
Block a user