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 { 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 { 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; } }