PlotDirector/PlotLine/Services/StoryIntelligencePipelineStateService.cs

229 lines
9.7 KiB
C#

using PlotLine.Data;
using PlotLine.Models;
namespace PlotLine.Services;
public interface IStoryIntelligencePipelineStateService
{
Task<StoryIntelligenceBookPipelineState?> GetForBookAsync(int bookId, int userId);
Task<IReadOnlyList<StoryIntelligenceBookPipelineState>> ListForUserAsync(int userId);
Task<StoryIntelligenceBookPipelineState?> EnsureForBookAsync(int bookId, int userId);
Task RecordChapterAnalysisQueuedAsync(int projectId, int bookId, int runId);
Task RecordSceneImportAsync(int projectId, int bookId, int runId);
Task RecordCharacterImportAsync(int projectId, int bookId);
Task RecordLocationImportAsync(int projectId, int bookId);
Task RecordAssetImportAsync(int projectId, int bookId);
Task RecordRelationshipImportAsync(int projectId, int bookId);
Task RecordKnowledgeImportAsync(int projectId, int bookId);
Task RecordNarrativeImportAsync(int projectId, int bookId);
Task<StoryIntelligenceSceneAnalysisCompletion?> CompleteSceneAnalysisIfReadyAsync(int bookId, int userId);
Task<StoryIntelligenceSceneAnalysisRecoveryCandidate?> FindNextReadySceneAnalysisAsync();
Task<IReadOnlyList<StoryIntelligenceCommittedRunSummary>> ListCommittedRunsByBookAsync(int bookId, int userId);
}
public sealed class StoryIntelligencePipelineStateService(
IStoryIntelligencePipelineRepository pipelines,
ILogger<StoryIntelligencePipelineStateService> logger) : IStoryIntelligencePipelineStateService
{
public async Task<StoryIntelligenceBookPipelineState?> GetForBookAsync(int bookId, int userId)
=> await pipelines.GetByBookForUserAsync(bookId, userId)
?? await EnsureForBookAsync(bookId, userId);
public Task<IReadOnlyList<StoryIntelligenceBookPipelineState>> ListForUserAsync(int userId)
=> pipelines.ListForUserAsync(userId);
public async Task<StoryIntelligenceBookPipelineState?> EnsureForBookAsync(int bookId, int userId)
{
var existing = await pipelines.GetByBookForUserAsync(bookId, userId);
if (existing is not null)
{
return existing;
}
var committedRuns = await pipelines.ListCommittedRunsByBookAsync(bookId, userId);
if (committedRuns.Count == 0)
{
return null;
}
var latestRun = committedRuns.OrderByDescending(run => run.StoryIntelligenceRunID).First();
var created = await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = latestRun.ProjectID,
BookID = latestRun.BookID,
CurrentStage = StoryIntelligencePipelineStages.CharacterReview,
LastCompletedStage = StoryIntelligencePipelineStages.SceneImport,
CurrentReviewStage = StoryIntelligencePipelineStages.CharacterReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = latestRun.StoryIntelligenceRunID
});
logger.LogInformation(
"Created Story Intelligence pipeline state for existing imported book {BookID}. ProjectID={ProjectID} CurrentStage={CurrentStage}",
bookId,
created.ProjectID,
created.CurrentStage);
return created;
}
public async Task RecordSceneImportAsync(int projectId, int bookId, int runId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.CharacterReview,
LastCompletedStage = StoryIntelligencePipelineStages.SceneImport,
CurrentReviewStage = StoryIntelligencePipelineStages.CharacterReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = runId
});
}
public async Task RecordChapterAnalysisQueuedAsync(int projectId, int bookId, int runId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.Chapters,
LastCompletedStage = null,
CurrentReviewStage = null,
Status = StoryIntelligencePipelineStatuses.InProgress,
CompletedUtc = null,
LastRunID = runId
});
}
public async Task RecordCharacterImportAsync(int projectId, int bookId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.LocationReview,
LastCompletedStage = StoryIntelligencePipelineStages.CharacterImport,
CurrentReviewStage = StoryIntelligencePipelineStages.LocationReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = null,
MarkCharacterReviewComplete = true
});
}
public async Task RecordLocationImportAsync(int projectId, int bookId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.AssetReview,
LastCompletedStage = StoryIntelligencePipelineStages.LocationImport,
CurrentReviewStage = StoryIntelligencePipelineStages.AssetReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = null,
MarkLocationReviewComplete = true
});
}
public async Task RecordAssetImportAsync(int projectId, int bookId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.RelationshipReview,
LastCompletedStage = StoryIntelligencePipelineStages.AssetImport,
CurrentReviewStage = StoryIntelligencePipelineStages.RelationshipReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = null,
MarkAssetReviewComplete = true
});
}
public async Task RecordRelationshipImportAsync(int projectId, int bookId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.KnowledgeReview,
LastCompletedStage = StoryIntelligencePipelineStages.RelationshipImport,
CurrentReviewStage = StoryIntelligencePipelineStages.KnowledgeReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = null,
MarkRelationshipReviewComplete = true
});
}
public async Task RecordKnowledgeImportAsync(int projectId, int bookId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.NarrativeReview,
LastCompletedStage = StoryIntelligencePipelineStages.KnowledgeImport,
CurrentReviewStage = StoryIntelligencePipelineStages.NarrativeReview,
Status = StoryIntelligencePipelineStatuses.NeedsReview,
CompletedUtc = null,
LastRunID = null,
MarkKnowledgeReviewComplete = true
});
}
public async Task RecordNarrativeImportAsync(int projectId, int bookId)
{
await pipelines.UpsertAsync(new StoryIntelligenceBookPipelineSaveRequest
{
ProjectID = projectId,
BookID = bookId,
CurrentStage = StoryIntelligencePipelineStages.Complete,
LastCompletedStage = StoryIntelligencePipelineStages.NarrativeImport,
CurrentReviewStage = null,
Status = StoryIntelligencePipelineStatuses.Complete,
CompletedUtc = DateTime.UtcNow,
LastRunID = null,
MarkNarrativeReviewComplete = true
});
}
public async Task<StoryIntelligenceSceneAnalysisCompletion?> CompleteSceneAnalysisIfReadyAsync(int bookId, int userId)
{
var completion = await pipelines.TryCompleteSceneAnalysisAsync(bookId, userId);
LogSceneAnalysisCompletion(completion, "completed");
return completion;
}
public Task<StoryIntelligenceSceneAnalysisRecoveryCandidate?> FindNextReadySceneAnalysisAsync()
=> pipelines.FindNextReadySceneAnalysisAsync();
public Task<IReadOnlyList<StoryIntelligenceCommittedRunSummary>> ListCommittedRunsByBookAsync(int bookId, int userId)
=> pipelines.ListCommittedRunsByBookAsync(bookId, userId);
private void LogSceneAnalysisCompletion(StoryIntelligenceSceneAnalysisCompletion? completion, string mode)
{
if (completion is not { CompletedNow: true })
{
return;
}
logger.LogInformation(
"Story Intelligence scene analysis {Mode} for book {BookID}. PipelineID={PipelineID} UserID={UserID} Scenes={CompletedScenes}/{TotalExpectedScenes} FailedScenes={FailedScenes} Runs={CompletedRunCount}/{RunCount}",
mode,
completion.BookID,
completion.StoryIntelligenceBookPipelineID,
completion.UserID,
completion.CompletedScenes,
completion.TotalExpectedScenes,
completion.FailedScenes,
completion.CompletedRunCount,
completion.RunCount);
}
}