using System.Data; using Dapper; using PlotLine.Models; namespace PlotLine.Data; public interface IStoryIntelligenceReviewCandidateRepository { Task BeginGenerationAsync(int projectId, int bookId, string module, string source); Task GetCurrentGenerationAsync(int bookId, string module); Task SaveCandidateAsync(StoryIntelligenceReviewCandidateSaveRequest request); Task SaveEvidenceAsync(int reviewCandidateId, int? sceneId, string evidenceText, decimal? confidence, int sortOrder); Task CompleteGenerationAsync(int generationId); Task FailGenerationAsync(int generationId, string errorMessage); Task> ListPageAsync(int bookId, string module, int pageNumber, int pageSize); Task MarkReviewedAsync(StoryIntelligenceReviewCandidateDecisionRequest request); Task ResetBookAsync(int bookId); } public sealed class StoryIntelligenceReviewCandidateRepository(ISqlConnectionFactory connectionFactory) : IStoryIntelligenceReviewCandidateRepository { public async Task BeginGenerationAsync(int projectId, int bookId, string module, string source) { using var connection = connectionFactory.CreateConnection(); return await connection.QuerySingleAsync( "dbo.StoryIntelligenceReviewCandidateGeneration_Begin", new { ProjectID = projectId, BookID = bookId, Module = module, Source = source }, commandType: CommandType.StoredProcedure); } public async Task GetCurrentGenerationAsync(int bookId, string module) { using var connection = connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync( "dbo.StoryIntelligenceReviewCandidateGeneration_GetCurrent", new { BookID = bookId, Module = module }, commandType: CommandType.StoredProcedure); } public async Task SaveCandidateAsync(StoryIntelligenceReviewCandidateSaveRequest request) { using var connection = connectionFactory.CreateConnection(); return await connection.QuerySingleAsync( "dbo.StoryIntelligenceReviewCandidate_Save", new { request.ReviewCandidateGenerationID, request.ProjectID, request.BookID, request.Module, request.StableKey, request.CandidateType, request.DisplayName, request.SortOrder, request.Confidence, request.ExistingCanonicalID, request.CandidatePayloadJson }, commandType: CommandType.StoredProcedure); } public async Task SaveEvidenceAsync(int reviewCandidateId, int? sceneId, string evidenceText, decimal? confidence, int sortOrder) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.StoryIntelligenceReviewCandidateEvidence_Save", new { ReviewCandidateID = reviewCandidateId, SceneID = sceneId, EvidenceText = evidenceText, Confidence = confidence, SortOrder = sortOrder }, commandType: CommandType.StoredProcedure); } public async Task CompleteGenerationAsync(int generationId) { using var connection = connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync( "dbo.StoryIntelligenceReviewCandidateGeneration_Complete", new { ReviewCandidateGenerationID = generationId }, commandType: CommandType.StoredProcedure); } public async Task FailGenerationAsync(int generationId, string errorMessage) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.StoryIntelligenceReviewCandidateGeneration_Fail", new { ReviewCandidateGenerationID = generationId, ErrorMessage = errorMessage }, commandType: CommandType.StoredProcedure); } public async Task> ListPageAsync(int bookId, string module, int pageNumber, int pageSize) { using var connection = connectionFactory.CreateConnection(); var rows = await connection.QueryAsync( "dbo.StoryIntelligenceReviewCandidate_ListPage", new { BookID = bookId, Module = module, PageNumber = pageNumber, PageSize = pageSize }, commandType: CommandType.StoredProcedure); return rows.ToList(); } public async Task MarkReviewedAsync(StoryIntelligenceReviewCandidateDecisionRequest request) { using var connection = connectionFactory.CreateConnection(); return await connection.QuerySingleOrDefaultAsync( "dbo.StoryIntelligenceReviewCandidate_MarkReviewed", new { request.BookID, request.Module, request.StableKey, request.DecisionAction, request.DecisionTargetKey, request.DecisionCanonicalID }, commandType: CommandType.StoredProcedure); } public async Task ResetBookAsync(int bookId) { using var connection = connectionFactory.CreateConnection(); await connection.ExecuteAsync( "dbo.StoryIntelligenceReviewCandidate_ResetBook", new { BookID = bookId }, commandType: CommandType.StoredProcedure); } }