共用場景資料庫(Scene/ScenePresence/SceneLine/SceneFact/SceneInteraction, 角色中立客觀記錄)與投影(ProjectionService 寫入 EpisodicMemory/ SemanticMemory/PersonalityTraitSnapshot/CharacterRelationshipEventLog, 逐一可追溯到來源場景)完全分層。歸屬信心分數(明示標記決定性、語言指紋 從已歸屬語料統計、場景名冊硬約束、對話輪替)與隔離區/指紋冷啟動(低信心 或樣本不足一律隔離,指紋精煉後可重新嘗試釋放)。多角色投影與修正(改一處 場景資料庫、reprojectScene 同步更新所有受影響角色,使用者互動記憶因為 從不帶 sourceSceneId 而結構性地不受影響,發現得晚的歸屬錯誤額外記一筆 CORRECTION 事件)。作品名冊建置門檻(達標自動建置,門檻檢查排在投影完成 之後才做,避免用到投影前的舊統計)。錨定點知識邊界(雙向:前進載入、 後退遺忘)與矛盾偵測(同優先序來源待人工確認,跨優先序可依小說原文> 官方設定集>動畫改編自動裁決)。插圖索引與衍生服裝目錄/姿勢語彙表。 刻意簡化:章節→場景切分與互動正負權重皆採結構化輸入(不做自動 NLP 場景邊界偵測與情感分析),比照 F-7 MockProvider 的做法——機制先做對, 之後有真實 NLP/LLM 能力再替換輸入來源。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
52 lines
2.2 KiB
TypeScript
52 lines
2.2 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { PrismaService } from "../prisma/prisma.service.js";
|
|
import { BUILD_THRESHOLD_SCENES, BUILD_THRESHOLD_NAMED_LINES, BUILD_THRESHOLD_INTERACTIONS } from "./constants.js";
|
|
|
|
export interface RosterProgress {
|
|
characterId: string;
|
|
sceneCount: number;
|
|
namedLineCount: number;
|
|
interactionCount: number;
|
|
meetsThreshold: boolean;
|
|
}
|
|
|
|
// M-2 作品名冊與建置門檻:新角色先進候補狀態,資料照常在場景資料庫累積,達門檻才正式建置。
|
|
// 累積量一律即時查詢(場景/台詞/互動紀錄本身就是唯一真相),不維護額外的計數快取,避免同步漂移。
|
|
@Injectable()
|
|
export class WorkRosterService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async getProgress(characterId: string): Promise<RosterProgress> {
|
|
const [sceneCount, namedLineCount, interactionCount] = await Promise.all([
|
|
this.prisma.client.scenePresence.count({ where: { characterId } }),
|
|
this.prisma.client.sceneLine.count({ where: { speakerCharacterId: characterId, status: "CONFIRMED" } }),
|
|
this.prisma.client.characterRelationshipEventLog.count({ where: { characterId } }),
|
|
]);
|
|
return {
|
|
characterId,
|
|
sceneCount,
|
|
namedLineCount,
|
|
interactionCount,
|
|
meetsThreshold:
|
|
sceneCount >= BUILD_THRESHOLD_SCENES &&
|
|
namedLineCount >= BUILD_THRESHOLD_NAMED_LINES &&
|
|
interactionCount >= BUILD_THRESHOLD_INTERACTIONS,
|
|
};
|
|
}
|
|
|
|
// 匯入場景後對每個候補角色呼叫:達門檻就自動升級為正式建置,之後的匯入走「強化路徑」(不需要特別的程式碼分支
|
|
// ——強化與候補累積本來就是同一組寫入邏輯,差別只在 buildStatus 這個查詢結果)。
|
|
async checkAndPromote(characterId: string): Promise<boolean> {
|
|
const character = await this.prisma.client.character.findUnique({ where: { id: characterId } });
|
|
if (!character || character.buildStatus === "BUILT") {
|
|
return false;
|
|
}
|
|
const progress = await this.getProgress(characterId);
|
|
if (!progress.meetsThreshold) {
|
|
return false;
|
|
}
|
|
await this.prisma.client.character.update({ where: { id: characterId }, data: { buildStatus: "BUILT" } });
|
|
return true;
|
|
}
|
|
}
|