377 lines
15 KiB
C#
377 lines
15 KiB
C#
using System.Data;
|
|
using Dapper;
|
|
using PlotLine.Models;
|
|
|
|
namespace PlotLine.Data;
|
|
|
|
public interface IStoryIntelligenceResultRepository
|
|
{
|
|
Task<int> SaveAsync(StoryIntelligenceRunSaveRequest request);
|
|
Task<int> QueueAdminTextAsync(StoryIntelligenceRunQueueRequest request);
|
|
Task<StoryIntelligenceQueuedRun?> ClaimNextPendingAsync();
|
|
Task UpdateProgressAsync(
|
|
int runId,
|
|
string? status = null,
|
|
string? currentStage = null,
|
|
string? currentMessage = null,
|
|
int? totalDetectedScenes = null,
|
|
int? completedScenes = null,
|
|
int? failedScenes = null,
|
|
int? totalInputTokens = null,
|
|
int? totalOutputTokens = null,
|
|
int? totalTokens = null,
|
|
long? totalDurationMs = null,
|
|
decimal? estimatedCostGBP = null,
|
|
decimal? estimatedCostUSD = null);
|
|
Task CompleteRunAsync(int runId, string status, int? totalInputTokens, int? totalOutputTokens, int? totalTokens, long? totalDurationMs, decimal? estimatedCostGBP, decimal? estimatedCostUSD);
|
|
Task FailRunAsync(int runId, string failureStage, string errorMessage, string? errorDetail, long? totalDurationMs);
|
|
Task RequestCancelAsync(int runId);
|
|
Task CancelRunAsync(int runId, long? totalDurationMs);
|
|
Task<int> SaveChapterResultAsync(int runId, StoryIntelligenceChapterResultSaveRequest chapter);
|
|
Task<int> SaveSceneResultAsync(int runId, int? chapterResultId, StoryIntelligenceSceneResultSaveRequest scene);
|
|
Task<IReadOnlyList<StoryIntelligenceSavedRunListItem>> ListRunsAsync();
|
|
Task<StoryIntelligenceSavedRun?> GetRunAsync(int runId);
|
|
Task<StoryIntelligenceSavedChapterResult?> GetChapterResultAsync(int runId);
|
|
Task<IReadOnlyList<StoryIntelligenceSavedSceneResult>> ListSceneResultsAsync(int runId);
|
|
}
|
|
|
|
public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory connectionFactory) : IStoryIntelligenceResultRepository
|
|
{
|
|
public async Task<int> SaveAsync(StoryIntelligenceRunSaveRequest request)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
connection.Open();
|
|
using var transaction = connection.BeginTransaction();
|
|
|
|
try
|
|
{
|
|
var runId = await connection.QuerySingleAsync<int>(
|
|
"dbo.StoryIntelligenceRun_Save",
|
|
new
|
|
{
|
|
request.UserID,
|
|
request.ProjectID,
|
|
request.BookID,
|
|
request.Status,
|
|
request.SourceType,
|
|
request.SourceFileName,
|
|
request.SourceFileSizeBytes,
|
|
request.SourceWordCount,
|
|
request.SourceCharacterCount,
|
|
request.SourceChapterCount,
|
|
request.SourceDetectedImagesCount,
|
|
request.SourceDetectedTablesCount,
|
|
request.SourceDetectedFootnotesCount,
|
|
request.SourceDetectedCommentsCount,
|
|
request.PromptVersion,
|
|
request.Model,
|
|
request.StartedUtc,
|
|
request.CompletedUtc,
|
|
request.FailureStage,
|
|
request.TotalInputTokens,
|
|
request.TotalOutputTokens,
|
|
request.TotalTokens,
|
|
request.TotalDurationMs,
|
|
request.EstimatedCostGBP,
|
|
request.EstimatedCostUSD,
|
|
request.PromptVersionsSummary,
|
|
request.ErrorMessage,
|
|
request.ErrorDetail
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
int? chapterResultId = null;
|
|
if (request.ChapterResult is not null)
|
|
{
|
|
var chapter = request.ChapterResult;
|
|
chapterResultId = await connection.QuerySingleAsync<int>(
|
|
"dbo.StoryIntelligenceChapterResult_Save",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
chapter.ProjectID,
|
|
chapter.BookID,
|
|
chapter.ChapterID,
|
|
chapter.ChapterNumber,
|
|
chapter.SourceLabel,
|
|
chapter.PromptVersion,
|
|
chapter.Model,
|
|
chapter.RawResponseJson,
|
|
chapter.OutputTextJson,
|
|
chapter.ParsedJson,
|
|
chapter.ValidationErrorsCount,
|
|
chapter.ValidationWarningsCount,
|
|
chapter.InputTokens,
|
|
chapter.OutputTokens,
|
|
chapter.TotalTokens,
|
|
chapter.DurationMs
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
foreach (var scene in request.SceneResults)
|
|
{
|
|
await connection.ExecuteAsync(
|
|
"dbo.StoryIntelligenceSceneResult_Save",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
ChapterResultID = chapterResultId,
|
|
scene.ProjectID,
|
|
scene.BookID,
|
|
scene.ChapterID,
|
|
scene.SceneID,
|
|
scene.TemporarySceneNumber,
|
|
scene.StartParagraph,
|
|
scene.EndParagraph,
|
|
scene.SourceLabel,
|
|
scene.PromptVersion,
|
|
scene.Model,
|
|
scene.RawResponseJson,
|
|
scene.OutputTextJson,
|
|
scene.ParsedJson,
|
|
scene.ValidationErrorsCount,
|
|
scene.ValidationWarningsCount,
|
|
scene.InputTokens,
|
|
scene.OutputTokens,
|
|
scene.TotalTokens,
|
|
scene.DurationMs
|
|
},
|
|
transaction,
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
transaction.Commit();
|
|
return runId;
|
|
}
|
|
catch
|
|
{
|
|
transaction.Rollback();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
public async Task<int> QueueAdminTextAsync(StoryIntelligenceRunQueueRequest request)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<int>(
|
|
"dbo.StoryIntelligenceRun_QueueAdminText",
|
|
new
|
|
{
|
|
request.UserID,
|
|
request.ProjectID,
|
|
request.BookID,
|
|
request.ChapterID,
|
|
request.ChapterNumber,
|
|
request.SourceType,
|
|
request.SourceLabel,
|
|
SourceText = request.ChapterText,
|
|
request.SourceFileName,
|
|
request.SourceFileSizeBytes,
|
|
request.SourceWordCount,
|
|
request.SourceCharacterCount,
|
|
request.SourceChapterCount,
|
|
request.Model,
|
|
request.PromptVersionsSummary
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<StoryIntelligenceQueuedRun?> ClaimNextPendingAsync()
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<StoryIntelligenceQueuedRun>(
|
|
"dbo.StoryIntelligenceRun_ClaimNextPending",
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task UpdateProgressAsync(
|
|
int runId,
|
|
string? status = null,
|
|
string? currentStage = null,
|
|
string? currentMessage = null,
|
|
int? totalDetectedScenes = null,
|
|
int? completedScenes = null,
|
|
int? failedScenes = null,
|
|
int? totalInputTokens = null,
|
|
int? totalOutputTokens = null,
|
|
int? totalTokens = null,
|
|
long? totalDurationMs = null,
|
|
decimal? estimatedCostGBP = null,
|
|
decimal? estimatedCostUSD = null)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await connection.ExecuteAsync(
|
|
"dbo.StoryIntelligenceRun_UpdateProgress",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
Status = status,
|
|
CurrentStage = currentStage,
|
|
CurrentMessage = currentMessage,
|
|
TotalDetectedScenes = totalDetectedScenes,
|
|
CompletedScenes = completedScenes,
|
|
FailedScenes = failedScenes,
|
|
TotalInputTokens = totalInputTokens,
|
|
TotalOutputTokens = totalOutputTokens,
|
|
TotalTokens = totalTokens,
|
|
TotalDurationMs = totalDurationMs,
|
|
EstimatedCostGBP = estimatedCostGBP,
|
|
EstimatedCostUSD = estimatedCostUSD
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task CompleteRunAsync(int runId, string status, int? totalInputTokens, int? totalOutputTokens, int? totalTokens, long? totalDurationMs, decimal? estimatedCostGBP, decimal? estimatedCostUSD)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await connection.ExecuteAsync(
|
|
"dbo.StoryIntelligenceRun_Complete",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
Status = status,
|
|
TotalInputTokens = totalInputTokens,
|
|
TotalOutputTokens = totalOutputTokens,
|
|
TotalTokens = totalTokens,
|
|
TotalDurationMs = totalDurationMs,
|
|
EstimatedCostGBP = estimatedCostGBP,
|
|
EstimatedCostUSD = estimatedCostUSD
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task FailRunAsync(int runId, string failureStage, string errorMessage, string? errorDetail, long? totalDurationMs)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await connection.ExecuteAsync(
|
|
"dbo.StoryIntelligenceRun_Fail",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
FailureStage = failureStage,
|
|
ErrorMessage = errorMessage,
|
|
ErrorDetail = errorDetail,
|
|
TotalDurationMs = totalDurationMs
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task RequestCancelAsync(int runId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await connection.ExecuteAsync(
|
|
"dbo.StoryIntelligenceRun_RequestCancel",
|
|
new { StoryIntelligenceRunID = runId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task CancelRunAsync(int runId, long? totalDurationMs)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await connection.ExecuteAsync(
|
|
"dbo.StoryIntelligenceRun_Cancel",
|
|
new { StoryIntelligenceRunID = runId, TotalDurationMs = totalDurationMs },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<int> SaveChapterResultAsync(int runId, StoryIntelligenceChapterResultSaveRequest chapter)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<int>(
|
|
"dbo.StoryIntelligenceChapterResult_Save",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
chapter.ProjectID,
|
|
chapter.BookID,
|
|
chapter.ChapterID,
|
|
chapter.ChapterNumber,
|
|
chapter.SourceLabel,
|
|
chapter.PromptVersion,
|
|
chapter.Model,
|
|
chapter.RawResponseJson,
|
|
chapter.OutputTextJson,
|
|
chapter.ParsedJson,
|
|
chapter.ValidationErrorsCount,
|
|
chapter.ValidationWarningsCount,
|
|
chapter.InputTokens,
|
|
chapter.OutputTokens,
|
|
chapter.TotalTokens,
|
|
chapter.DurationMs
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<int> SaveSceneResultAsync(int runId, int? chapterResultId, StoryIntelligenceSceneResultSaveRequest scene)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleAsync<int>(
|
|
"dbo.StoryIntelligenceSceneResult_Save",
|
|
new
|
|
{
|
|
StoryIntelligenceRunID = runId,
|
|
ChapterResultID = chapterResultId,
|
|
scene.ProjectID,
|
|
scene.BookID,
|
|
scene.ChapterID,
|
|
scene.SceneID,
|
|
scene.TemporarySceneNumber,
|
|
scene.StartParagraph,
|
|
scene.EndParagraph,
|
|
scene.SourceLabel,
|
|
scene.PromptVersion,
|
|
scene.Model,
|
|
scene.RawResponseJson,
|
|
scene.OutputTextJson,
|
|
scene.ParsedJson,
|
|
scene.ValidationErrorsCount,
|
|
scene.ValidationWarningsCount,
|
|
scene.InputTokens,
|
|
scene.OutputTokens,
|
|
scene.TotalTokens,
|
|
scene.DurationMs
|
|
},
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<StoryIntelligenceSavedRunListItem>> ListRunsAsync()
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var rows = await connection.QueryAsync<StoryIntelligenceSavedRunListItem>(
|
|
"dbo.StoryIntelligenceRun_ListAdmin",
|
|
commandType: CommandType.StoredProcedure);
|
|
return rows.ToList();
|
|
}
|
|
|
|
public async Task<StoryIntelligenceSavedRun?> GetRunAsync(int runId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<StoryIntelligenceSavedRun>(
|
|
"dbo.StoryIntelligenceRun_GetAdmin",
|
|
new { StoryIntelligenceRunID = runId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<StoryIntelligenceSavedChapterResult?> GetChapterResultAsync(int runId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
return await connection.QuerySingleOrDefaultAsync<StoryIntelligenceSavedChapterResult>(
|
|
"dbo.StoryIntelligenceChapterResult_GetByRun",
|
|
new { StoryIntelligenceRunID = runId },
|
|
commandType: CommandType.StoredProcedure);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<StoryIntelligenceSavedSceneResult>> ListSceneResultsAsync(int runId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
var rows = await connection.QueryAsync<StoryIntelligenceSavedSceneResult>(
|
|
"dbo.StoryIntelligenceSceneResult_ListByRun",
|
|
new { StoryIntelligenceRunID = runId },
|
|
commandType: CommandType.StoredProcedure);
|
|
return rows.ToList();
|
|
}
|
|
}
|