Phase 20O.1 Extend the Story Intelligence persistence/audit design so every import attempt is fully traceable.
This commit is contained in:
parent
35e76d8ac4
commit
650febab70
@ -32,15 +32,29 @@ public sealed class StoryIntelligenceResultRepository(ISqlConnectionFactory conn
|
|||||||
request.BookID,
|
request.BookID,
|
||||||
request.Status,
|
request.Status,
|
||||||
request.SourceType,
|
request.SourceType,
|
||||||
|
request.SourceFileName,
|
||||||
|
request.SourceFileSizeBytes,
|
||||||
|
request.SourceWordCount,
|
||||||
|
request.SourceCharacterCount,
|
||||||
|
request.SourceChapterCount,
|
||||||
|
request.SourceDetectedImagesCount,
|
||||||
|
request.SourceDetectedTablesCount,
|
||||||
|
request.SourceDetectedFootnotesCount,
|
||||||
|
request.SourceDetectedCommentsCount,
|
||||||
request.PromptVersion,
|
request.PromptVersion,
|
||||||
request.Model,
|
request.Model,
|
||||||
request.StartedUtc,
|
request.StartedUtc,
|
||||||
request.CompletedUtc,
|
request.CompletedUtc,
|
||||||
|
request.FailureStage,
|
||||||
request.TotalInputTokens,
|
request.TotalInputTokens,
|
||||||
request.TotalOutputTokens,
|
request.TotalOutputTokens,
|
||||||
request.TotalTokens,
|
request.TotalTokens,
|
||||||
request.TotalDurationMs,
|
request.TotalDurationMs,
|
||||||
request.ErrorMessage
|
request.EstimatedCostGBP,
|
||||||
|
request.EstimatedCostUSD,
|
||||||
|
request.PromptVersionsSummary,
|
||||||
|
request.ErrorMessage,
|
||||||
|
request.ErrorDetail
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|||||||
@ -2,9 +2,23 @@ namespace PlotLine.Models;
|
|||||||
|
|
||||||
public static class StoryIntelligenceRunStatuses
|
public static class StoryIntelligenceRunStatuses
|
||||||
{
|
{
|
||||||
|
public const string Pending = "Pending";
|
||||||
|
public const string Running = "Running";
|
||||||
public const string Completed = "Completed";
|
public const string Completed = "Completed";
|
||||||
public const string CompletedWithIssues = "CompletedWithIssues";
|
public const string CompletedWithWarnings = "CompletedWithWarnings";
|
||||||
public const string Failed = "Failed";
|
public const string Failed = "Failed";
|
||||||
|
public const string Cancelled = "Cancelled";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StoryIntelligenceFailureStages
|
||||||
|
{
|
||||||
|
public const string DocumentRead = "DocumentRead";
|
||||||
|
public const string ChapterStructure = "ChapterStructure";
|
||||||
|
public const string SceneSplit = "SceneSplit";
|
||||||
|
public const string SceneIntelligence = "SceneIntelligence";
|
||||||
|
public const string Validation = "Validation";
|
||||||
|
public const string Persistence = "Persistence";
|
||||||
|
public const string DomainImport = "DomainImport";
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StoryIntelligenceRunSaveRequest
|
public sealed class StoryIntelligenceRunSaveRequest
|
||||||
@ -14,15 +28,29 @@ public sealed class StoryIntelligenceRunSaveRequest
|
|||||||
public int? BookID { get; init; }
|
public int? BookID { get; init; }
|
||||||
public string Status { get; init; } = StoryIntelligenceRunStatuses.Completed;
|
public string Status { get; init; } = StoryIntelligenceRunStatuses.Completed;
|
||||||
public string SourceType { get; init; } = "AdminDryRun";
|
public string SourceType { get; init; } = "AdminDryRun";
|
||||||
|
public string? SourceFileName { get; init; }
|
||||||
|
public long? SourceFileSizeBytes { get; init; }
|
||||||
|
public int? SourceWordCount { get; init; }
|
||||||
|
public int? SourceCharacterCount { get; init; }
|
||||||
|
public int? SourceChapterCount { get; init; }
|
||||||
|
public int? SourceDetectedImagesCount { get; init; }
|
||||||
|
public int? SourceDetectedTablesCount { get; init; }
|
||||||
|
public int? SourceDetectedFootnotesCount { get; init; }
|
||||||
|
public int? SourceDetectedCommentsCount { get; init; }
|
||||||
public string PromptVersion { get; init; } = string.Empty;
|
public string PromptVersion { get; init; } = string.Empty;
|
||||||
public string Model { get; init; } = string.Empty;
|
public string Model { get; init; } = string.Empty;
|
||||||
public DateTime StartedUtc { get; init; }
|
public DateTime StartedUtc { get; init; }
|
||||||
public DateTime? CompletedUtc { get; init; }
|
public DateTime? CompletedUtc { get; init; }
|
||||||
|
public string? FailureStage { get; init; }
|
||||||
public int? TotalInputTokens { get; init; }
|
public int? TotalInputTokens { get; init; }
|
||||||
public int? TotalOutputTokens { get; init; }
|
public int? TotalOutputTokens { get; init; }
|
||||||
public int? TotalTokens { get; init; }
|
public int? TotalTokens { get; init; }
|
||||||
public long? TotalDurationMs { get; init; }
|
public long? TotalDurationMs { get; init; }
|
||||||
|
public decimal? EstimatedCostGBP { get; init; }
|
||||||
|
public decimal? EstimatedCostUSD { get; init; }
|
||||||
|
public string? PromptVersionsSummary { get; init; }
|
||||||
public string? ErrorMessage { get; init; }
|
public string? ErrorMessage { get; init; }
|
||||||
|
public string? ErrorDetail { get; init; }
|
||||||
public StoryIntelligenceChapterResultSaveRequest? ChapterResult { get; init; }
|
public StoryIntelligenceChapterResultSaveRequest? ChapterResult { get; init; }
|
||||||
public IReadOnlyList<StoryIntelligenceSceneResultSaveRequest> SceneResults { get; init; } = [];
|
public IReadOnlyList<StoryIntelligenceSceneResultSaveRequest> SceneResults { get; init; } = [];
|
||||||
}
|
}
|
||||||
@ -80,9 +108,13 @@ public sealed class StoryIntelligenceSavedRunListItem
|
|||||||
public int? BookID { get; init; }
|
public int? BookID { get; init; }
|
||||||
public string? BookTitle { get; init; }
|
public string? BookTitle { get; init; }
|
||||||
public string Status { get; init; } = string.Empty;
|
public string Status { get; init; } = string.Empty;
|
||||||
|
public string SourceType { get; init; } = string.Empty;
|
||||||
|
public string? FailureStage { get; init; }
|
||||||
public int SceneCount { get; init; }
|
public int SceneCount { get; init; }
|
||||||
public int? TotalTokens { get; init; }
|
public int? TotalTokens { get; init; }
|
||||||
public long? TotalDurationMs { get; init; }
|
public long? TotalDurationMs { get; init; }
|
||||||
|
public decimal? EstimatedCostGBP { get; init; }
|
||||||
|
public decimal? EstimatedCostUSD { get; init; }
|
||||||
public int ValidationErrorsCount { get; init; }
|
public int ValidationErrorsCount { get; init; }
|
||||||
public int ValidationWarningsCount { get; init; }
|
public int ValidationWarningsCount { get; init; }
|
||||||
}
|
}
|
||||||
@ -97,15 +129,29 @@ public sealed class StoryIntelligenceSavedRun
|
|||||||
public string? BookTitle { get; init; }
|
public string? BookTitle { get; init; }
|
||||||
public string Status { get; init; } = string.Empty;
|
public string Status { get; init; } = string.Empty;
|
||||||
public string SourceType { get; init; } = string.Empty;
|
public string SourceType { get; init; } = string.Empty;
|
||||||
|
public string? SourceFileName { get; init; }
|
||||||
|
public long? SourceFileSizeBytes { get; init; }
|
||||||
|
public int? SourceWordCount { get; init; }
|
||||||
|
public int? SourceCharacterCount { get; init; }
|
||||||
|
public int? SourceChapterCount { get; init; }
|
||||||
|
public int? SourceDetectedImagesCount { get; init; }
|
||||||
|
public int? SourceDetectedTablesCount { get; init; }
|
||||||
|
public int? SourceDetectedFootnotesCount { get; init; }
|
||||||
|
public int? SourceDetectedCommentsCount { get; init; }
|
||||||
public string PromptVersion { get; init; } = string.Empty;
|
public string PromptVersion { get; init; } = string.Empty;
|
||||||
|
public string? PromptVersionsSummary { get; init; }
|
||||||
public string Model { get; init; } = string.Empty;
|
public string Model { get; init; } = string.Empty;
|
||||||
public DateTime StartedUtc { get; init; }
|
public DateTime StartedUtc { get; init; }
|
||||||
public DateTime? CompletedUtc { get; init; }
|
public DateTime? CompletedUtc { get; init; }
|
||||||
|
public string? FailureStage { get; init; }
|
||||||
public int? TotalInputTokens { get; init; }
|
public int? TotalInputTokens { get; init; }
|
||||||
public int? TotalOutputTokens { get; init; }
|
public int? TotalOutputTokens { get; init; }
|
||||||
public int? TotalTokens { get; init; }
|
public int? TotalTokens { get; init; }
|
||||||
public long? TotalDurationMs { get; init; }
|
public long? TotalDurationMs { get; init; }
|
||||||
|
public decimal? EstimatedCostGBP { get; init; }
|
||||||
|
public decimal? EstimatedCostUSD { get; init; }
|
||||||
public string? ErrorMessage { get; init; }
|
public string? ErrorMessage { get; init; }
|
||||||
|
public string? ErrorDetail { get; init; }
|
||||||
public DateTime CreatedUtc { get; init; }
|
public DateTime CreatedUtc { get; init; }
|
||||||
public DateTime UpdatedUtc { get; init; }
|
public DateTime UpdatedUtc { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,11 +86,13 @@ public sealed class StoryIntelligenceResultPersistenceService(
|
|||||||
: completedUtc;
|
: completedUtc;
|
||||||
var errorCount = CountErrors(result);
|
var errorCount = CountErrors(result);
|
||||||
var warningCount = CountWarnings(result);
|
var warningCount = CountWarnings(result);
|
||||||
var status = !string.IsNullOrWhiteSpace(result.ErrorMessage) || result.PipelineStopped
|
var sceneFailed = result.SceneBlocks.Any(block => !block.Success && block.SplitValid);
|
||||||
|
var status = !string.IsNullOrWhiteSpace(result.ErrorMessage) || result.PipelineStopped || errorCount > 0 || sceneFailed
|
||||||
? StoryIntelligenceRunStatuses.Failed
|
? StoryIntelligenceRunStatuses.Failed
|
||||||
: errorCount > 0 || warningCount > 0 || result.SceneBlocks.Any(block => !block.Success && block.SplitValid)
|
: warningCount > 0
|
||||||
? StoryIntelligenceRunStatuses.CompletedWithIssues
|
? StoryIntelligenceRunStatuses.CompletedWithWarnings
|
||||||
: StoryIntelligenceRunStatuses.Completed;
|
: StoryIntelligenceRunStatuses.Completed;
|
||||||
|
var promptVersionsSummary = $"Chapter={result.ChapterPromptVersion}; Scene={result.ScenePromptVersion}";
|
||||||
|
|
||||||
return new StoryIntelligenceRunSaveRequest
|
return new StoryIntelligenceRunSaveRequest
|
||||||
{
|
{
|
||||||
@ -99,15 +101,21 @@ public sealed class StoryIntelligenceResultPersistenceService(
|
|||||||
BookID = form.BookID,
|
BookID = form.BookID,
|
||||||
Status = status,
|
Status = status,
|
||||||
SourceType = "AdminDryRun",
|
SourceType = "AdminDryRun",
|
||||||
PromptVersion = $"Chapter={result.ChapterPromptVersion}; Scene={result.ScenePromptVersion}",
|
SourceWordCount = CountWords(form.ChapterText),
|
||||||
|
SourceCharacterCount = string.IsNullOrEmpty(form.ChapterText) ? null : form.ChapterText.Length,
|
||||||
|
SourceChapterCount = 1,
|
||||||
|
PromptVersion = promptVersionsSummary,
|
||||||
|
PromptVersionsSummary = promptVersionsSummary,
|
||||||
Model = result.Model,
|
Model = result.Model,
|
||||||
StartedUtc = startedUtc,
|
StartedUtc = startedUtc,
|
||||||
CompletedUtc = completedUtc,
|
CompletedUtc = completedUtc,
|
||||||
|
FailureStage = DetermineFailureStage(result),
|
||||||
TotalInputTokens = result.TotalInputTokens,
|
TotalInputTokens = result.TotalInputTokens,
|
||||||
TotalOutputTokens = result.TotalOutputTokens,
|
TotalOutputTokens = result.TotalOutputTokens,
|
||||||
TotalTokens = SumTokens(result.TotalInputTokens, result.TotalOutputTokens),
|
TotalTokens = SumTokens(result.TotalInputTokens, result.TotalOutputTokens),
|
||||||
TotalDurationMs = result.TotalDurationMs,
|
TotalDurationMs = result.TotalDurationMs,
|
||||||
ErrorMessage = FirstError(result),
|
ErrorMessage = FirstError(result),
|
||||||
|
ErrorDetail = BuildErrorDetail(result),
|
||||||
ChapterResult = BuildChapterResult(form, result),
|
ChapterResult = BuildChapterResult(form, result),
|
||||||
SceneResults = result.SceneBlocks.Select(block => BuildSceneResult(form, result, block)).ToList()
|
SceneResults = result.SceneBlocks.Select(block => BuildSceneResult(form, result, block)).ToList()
|
||||||
};
|
};
|
||||||
@ -172,6 +180,11 @@ public sealed class StoryIntelligenceResultPersistenceService(
|
|||||||
private static int? SumTokens(int? inputTokens, int? outputTokens)
|
private static int? SumTokens(int? inputTokens, int? outputTokens)
|
||||||
=> inputTokens.HasValue || outputTokens.HasValue ? (inputTokens ?? 0) + (outputTokens ?? 0) : null;
|
=> inputTokens.HasValue || outputTokens.HasValue ? (inputTokens ?? 0) + (outputTokens ?? 0) : null;
|
||||||
|
|
||||||
|
private static int? CountWords(string? value)
|
||||||
|
=> string.IsNullOrWhiteSpace(value)
|
||||||
|
? null
|
||||||
|
: value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
|
||||||
|
|
||||||
private static int CountErrors(ChapterStoryIntelligenceDryRunResultViewModel result)
|
private static int CountErrors(ChapterStoryIntelligenceDryRunResultViewModel result)
|
||||||
=> (result.ChapterValidation?.Errors.Count ?? 0)
|
=> (result.ChapterValidation?.Errors.Count ?? 0)
|
||||||
+ result.SceneBlocks.Sum(block => block.Validation?.Errors.Count ?? 0);
|
+ result.SceneBlocks.Sum(block => block.Validation?.Errors.Count ?? 0);
|
||||||
@ -184,4 +197,57 @@ public sealed class StoryIntelligenceResultPersistenceService(
|
|||||||
=> !string.IsNullOrWhiteSpace(result.ErrorMessage)
|
=> !string.IsNullOrWhiteSpace(result.ErrorMessage)
|
||||||
? result.ErrorMessage
|
? result.ErrorMessage
|
||||||
: result.SceneBlocks.FirstOrDefault(block => !string.IsNullOrWhiteSpace(block.ErrorMessage))?.ErrorMessage;
|
: result.SceneBlocks.FirstOrDefault(block => !string.IsNullOrWhiteSpace(block.ErrorMessage))?.ErrorMessage;
|
||||||
|
|
||||||
|
private static string? DetermineFailureStage(ChapterStoryIntelligenceDryRunResultViewModel result)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(result.ErrorMessage)
|
||||||
|
&& !result.PipelineStopped
|
||||||
|
&& result.ChapterValidation?.Errors.Count is not > 0
|
||||||
|
&& result.SceneBlocks.All(block => block.Success || !block.SplitValid)
|
||||||
|
&& result.SceneBlocks.All(block => block.Validation?.Errors.Count is not > 0))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.ChapterValidation?.Errors.Count is > 0 || result.StopReason.Contains("validation", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return StoryIntelligenceFailureStages.Validation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.SceneBlocks.Any(block => !block.SplitValid))
|
||||||
|
{
|
||||||
|
return StoryIntelligenceFailureStages.SceneSplit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.SceneBlocks.Any(block => !block.Success || !string.IsNullOrWhiteSpace(block.ErrorMessage)))
|
||||||
|
{
|
||||||
|
return StoryIntelligenceFailureStages.SceneIntelligence;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StoryIntelligenceFailureStages.ChapterStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? BuildErrorDetail(ChapterStoryIntelligenceDryRunResultViewModel result)
|
||||||
|
{
|
||||||
|
var details = new List<string>();
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.StopReason))
|
||||||
|
{
|
||||||
|
details.Add(result.StopReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(result.ErrorMessage))
|
||||||
|
{
|
||||||
|
details.Add(result.ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
details.AddRange(result.ChapterValidation?.Errors.Select(issue => $"{issue.Path}: {issue.Message}") ?? []);
|
||||||
|
details.AddRange(result.SceneBlocks
|
||||||
|
.Where(block => !string.IsNullOrWhiteSpace(block.ErrorMessage))
|
||||||
|
.Select(block => $"Scene {block.TemporarySceneNumber}: {block.ErrorMessage}"));
|
||||||
|
details.AddRange(result.SceneBlocks
|
||||||
|
.Where(block => block.Validation is not null)
|
||||||
|
.SelectMany(block => block.Validation!.Errors.Select(issue => $"Scene {block.TemporarySceneNumber} {issue.Path}: {issue.Message}")));
|
||||||
|
|
||||||
|
return details.Count == 0 ? null : string.Join(Environment.NewLine, details);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
187
PlotLine/Sql/116_Phase20J_StoryIntelligenceRunAuditFields.sql
Normal file
187
PlotLine/Sql/116_Phase20J_StoryIntelligenceRunAuditFields.sql
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceFileName') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceFileName nvarchar(260) NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceFileSizeBytes') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceFileSizeBytes bigint NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceWordCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceWordCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceCharacterCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceCharacterCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceChapterCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceChapterCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceDetectedImagesCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceDetectedImagesCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceDetectedTablesCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceDetectedTablesCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceDetectedFootnotesCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceDetectedFootnotesCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'SourceDetectedCommentsCount') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD SourceDetectedCommentsCount int NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'FailureStage') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD FailureStage nvarchar(80) NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'ErrorDetail') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD ErrorDetail nvarchar(max) NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'EstimatedCostGBP') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD EstimatedCostGBP decimal(19,6) NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'EstimatedCostUSD') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD EstimatedCostUSD decimal(19,6) NULL;
|
||||||
|
GO
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceRuns', N'PromptVersionsSummary') IS NULL
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceRuns ADD PromptVersionsSummary nvarchar(500) NULL;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_Save
|
||||||
|
@UserID int,
|
||||||
|
@ProjectID int = NULL,
|
||||||
|
@BookID int = NULL,
|
||||||
|
@Status nvarchar(50),
|
||||||
|
@SourceType nvarchar(50),
|
||||||
|
@SourceFileName nvarchar(260) = NULL,
|
||||||
|
@SourceFileSizeBytes bigint = NULL,
|
||||||
|
@SourceWordCount int = NULL,
|
||||||
|
@SourceCharacterCount int = NULL,
|
||||||
|
@SourceChapterCount int = NULL,
|
||||||
|
@SourceDetectedImagesCount int = NULL,
|
||||||
|
@SourceDetectedTablesCount int = NULL,
|
||||||
|
@SourceDetectedFootnotesCount int = NULL,
|
||||||
|
@SourceDetectedCommentsCount int = NULL,
|
||||||
|
@PromptVersion nvarchar(200),
|
||||||
|
@Model nvarchar(100),
|
||||||
|
@StartedUtc datetime2,
|
||||||
|
@CompletedUtc datetime2 = NULL,
|
||||||
|
@FailureStage nvarchar(80) = NULL,
|
||||||
|
@TotalInputTokens int = NULL,
|
||||||
|
@TotalOutputTokens int = NULL,
|
||||||
|
@TotalTokens int = NULL,
|
||||||
|
@TotalDurationMs bigint = NULL,
|
||||||
|
@EstimatedCostGBP decimal(19,6) = NULL,
|
||||||
|
@EstimatedCostUSD decimal(19,6) = NULL,
|
||||||
|
@PromptVersionsSummary nvarchar(500) = NULL,
|
||||||
|
@ErrorMessage nvarchar(max) = NULL,
|
||||||
|
@ErrorDetail nvarchar(max) = NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
INSERT dbo.StoryIntelligenceRuns
|
||||||
|
(
|
||||||
|
UserID, ProjectID, BookID, Status, SourceType, SourceFileName, SourceFileSizeBytes,
|
||||||
|
SourceWordCount, SourceCharacterCount, SourceChapterCount, SourceDetectedImagesCount,
|
||||||
|
SourceDetectedTablesCount, SourceDetectedFootnotesCount, SourceDetectedCommentsCount,
|
||||||
|
PromptVersion, Model, StartedUtc, CompletedUtc, FailureStage, TotalInputTokens,
|
||||||
|
TotalOutputTokens, TotalTokens, TotalDurationMs, EstimatedCostGBP, EstimatedCostUSD,
|
||||||
|
PromptVersionsSummary, ErrorMessage, ErrorDetail
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@UserID, @ProjectID, @BookID, @Status, @SourceType, @SourceFileName, @SourceFileSizeBytes,
|
||||||
|
@SourceWordCount, @SourceCharacterCount, @SourceChapterCount, @SourceDetectedImagesCount,
|
||||||
|
@SourceDetectedTablesCount, @SourceDetectedFootnotesCount, @SourceDetectedCommentsCount,
|
||||||
|
@PromptVersion, @Model, @StartedUtc, @CompletedUtc, @FailureStage, @TotalInputTokens,
|
||||||
|
@TotalOutputTokens, @TotalTokens, @TotalDurationMs, @EstimatedCostGBP, @EstimatedCostUSD,
|
||||||
|
@PromptVersionsSummary, @ErrorMessage, @ErrorDetail
|
||||||
|
);
|
||||||
|
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() AS int) AS StoryIntelligenceRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_ListAdmin
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
r.StoryIntelligenceRunID,
|
||||||
|
r.CreatedUtc,
|
||||||
|
r.UserID,
|
||||||
|
r.ProjectID,
|
||||||
|
p.ProjectName AS ProjectTitle,
|
||||||
|
r.BookID,
|
||||||
|
b.BookTitle,
|
||||||
|
r.Status,
|
||||||
|
r.SourceType,
|
||||||
|
r.FailureStage,
|
||||||
|
COUNT(sr.SceneResultID) AS SceneCount,
|
||||||
|
r.TotalTokens,
|
||||||
|
r.TotalDurationMs,
|
||||||
|
r.EstimatedCostGBP,
|
||||||
|
r.EstimatedCostUSD,
|
||||||
|
COALESCE(cr.ValidationErrorsCount, 0) + COALESCE(SUM(sr.ValidationErrorsCount), 0) AS ValidationErrorsCount,
|
||||||
|
COALESCE(cr.ValidationWarningsCount, 0) + COALESCE(SUM(sr.ValidationWarningsCount), 0) AS ValidationWarningsCount
|
||||||
|
FROM dbo.StoryIntelligenceRuns r
|
||||||
|
LEFT JOIN dbo.Projects p ON p.ProjectID = r.ProjectID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = r.BookID
|
||||||
|
LEFT JOIN dbo.StoryIntelligenceChapterResults cr ON cr.StoryIntelligenceRunID = r.StoryIntelligenceRunID
|
||||||
|
LEFT JOIN dbo.StoryIntelligenceSceneResults sr ON sr.StoryIntelligenceRunID = r.StoryIntelligenceRunID
|
||||||
|
GROUP BY
|
||||||
|
r.StoryIntelligenceRunID, r.CreatedUtc, r.UserID, r.ProjectID, p.ProjectName,
|
||||||
|
r.BookID, b.BookTitle, r.Status, r.SourceType, r.FailureStage, r.TotalTokens,
|
||||||
|
r.TotalDurationMs, r.EstimatedCostGBP, r.EstimatedCostUSD,
|
||||||
|
cr.ValidationErrorsCount, cr.ValidationWarningsCount
|
||||||
|
ORDER BY r.CreatedUtc DESC;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_GetAdmin
|
||||||
|
@StoryIntelligenceRunID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
r.StoryIntelligenceRunID,
|
||||||
|
r.UserID,
|
||||||
|
r.ProjectID,
|
||||||
|
p.ProjectName AS ProjectTitle,
|
||||||
|
r.BookID,
|
||||||
|
b.BookTitle,
|
||||||
|
r.Status,
|
||||||
|
r.SourceType,
|
||||||
|
r.SourceFileName,
|
||||||
|
r.SourceFileSizeBytes,
|
||||||
|
r.SourceWordCount,
|
||||||
|
r.SourceCharacterCount,
|
||||||
|
r.SourceChapterCount,
|
||||||
|
r.SourceDetectedImagesCount,
|
||||||
|
r.SourceDetectedTablesCount,
|
||||||
|
r.SourceDetectedFootnotesCount,
|
||||||
|
r.SourceDetectedCommentsCount,
|
||||||
|
r.PromptVersion,
|
||||||
|
r.PromptVersionsSummary,
|
||||||
|
r.Model,
|
||||||
|
r.StartedUtc,
|
||||||
|
r.CompletedUtc,
|
||||||
|
r.FailureStage,
|
||||||
|
r.TotalInputTokens,
|
||||||
|
r.TotalOutputTokens,
|
||||||
|
r.TotalTokens,
|
||||||
|
r.TotalDurationMs,
|
||||||
|
r.EstimatedCostGBP,
|
||||||
|
r.EstimatedCostUSD,
|
||||||
|
r.ErrorMessage,
|
||||||
|
r.ErrorDetail,
|
||||||
|
r.CreatedUtc,
|
||||||
|
r.UpdatedUtc
|
||||||
|
FROM dbo.StoryIntelligenceRuns r
|
||||||
|
LEFT JOIN dbo.Projects p ON p.ProjectID = r.ProjectID
|
||||||
|
LEFT JOIN dbo.Books b ON b.BookID = r.BookID
|
||||||
|
WHERE r.StoryIntelligenceRunID = @StoryIntelligenceRunID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
@ -66,24 +66,49 @@ else
|
|||||||
<dl class="mt-3 mb-0">
|
<dl class="mt-3 mb-0">
|
||||||
<dt>Source type</dt>
|
<dt>Source type</dt>
|
||||||
<dd>@run.SourceType</dd>
|
<dd>@run.SourceType</dd>
|
||||||
|
<dt>Source file</dt>
|
||||||
|
<dd>@Display(run.SourceFileName)</dd>
|
||||||
|
<dt>Source file size</dt>
|
||||||
|
<dd>@DisplayBytes(run.SourceFileSizeBytes)</dd>
|
||||||
|
<dt>Source word count</dt>
|
||||||
|
<dd>@Display(run.SourceWordCount)</dd>
|
||||||
|
<dt>Source character count</dt>
|
||||||
|
<dd>@Display(run.SourceCharacterCount)</dd>
|
||||||
|
<dt>Source chapter count</dt>
|
||||||
|
<dd>@Display(run.SourceChapterCount)</dd>
|
||||||
|
<dt>Detected document features</dt>
|
||||||
|
<dd>
|
||||||
|
Images: @Display(run.SourceDetectedImagesCount),
|
||||||
|
tables: @Display(run.SourceDetectedTablesCount),
|
||||||
|
footnotes: @Display(run.SourceDetectedFootnotesCount),
|
||||||
|
comments: @Display(run.SourceDetectedCommentsCount)
|
||||||
|
</dd>
|
||||||
<dt>Project</dt>
|
<dt>Project</dt>
|
||||||
<dd>@DisplayName(run.ProjectTitle, run.ProjectID, "Project")</dd>
|
<dd>@DisplayName(run.ProjectTitle, run.ProjectID, "Project")</dd>
|
||||||
<dt>Book</dt>
|
<dt>Book</dt>
|
||||||
<dd>@DisplayName(run.BookTitle, run.BookID, "Book")</dd>
|
<dd>@DisplayName(run.BookTitle, run.BookID, "Book")</dd>
|
||||||
<dt>Prompt version</dt>
|
<dt>Prompt version</dt>
|
||||||
<dd>@run.PromptVersion</dd>
|
<dd>@run.PromptVersion</dd>
|
||||||
|
<dt>Prompt versions summary</dt>
|
||||||
|
<dd>@Display(run.PromptVersionsSummary)</dd>
|
||||||
<dt>Model</dt>
|
<dt>Model</dt>
|
||||||
<dd>@run.Model</dd>
|
<dd>@run.Model</dd>
|
||||||
<dt>Started</dt>
|
<dt>Started</dt>
|
||||||
<dd>@run.StartedUtc.ToString("yyyy-MM-dd HH:mm:ss") UTC</dd>
|
<dd>@run.StartedUtc.ToString("yyyy-MM-dd HH:mm:ss") UTC</dd>
|
||||||
<dt>Completed</dt>
|
<dt>Completed</dt>
|
||||||
<dd>@(run.CompletedUtc?.ToString("yyyy-MM-dd HH:mm:ss") ?? "None") UTC</dd>
|
<dd>@(run.CompletedUtc?.ToString("yyyy-MM-dd HH:mm:ss") ?? "None") UTC</dd>
|
||||||
|
<dt>Failure stage</dt>
|
||||||
|
<dd>@Display(run.FailureStage)</dd>
|
||||||
<dt>Input tokens</dt>
|
<dt>Input tokens</dt>
|
||||||
<dd>@Display(run.TotalInputTokens)</dd>
|
<dd>@Display(run.TotalInputTokens)</dd>
|
||||||
<dt>Output tokens</dt>
|
<dt>Output tokens</dt>
|
||||||
<dd>@Display(run.TotalOutputTokens)</dd>
|
<dd>@Display(run.TotalOutputTokens)</dd>
|
||||||
|
<dt>Estimated cost</dt>
|
||||||
|
<dd>@DisplayCost(run.EstimatedCostGBP, "GBP") / @DisplayCost(run.EstimatedCostUSD, "USD")</dd>
|
||||||
<dt>Error message</dt>
|
<dt>Error message</dt>
|
||||||
<dd>@Display(run.ErrorMessage)</dd>
|
<dd>@Display(run.ErrorMessage)</dd>
|
||||||
|
<dt>Error detail</dt>
|
||||||
|
<dd><pre class="mb-0">@Display(run.ErrorDetail)</pre></dd>
|
||||||
</dl>
|
</dl>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -174,6 +199,12 @@ else
|
|||||||
: $"{TimeSpan.FromMilliseconds(milliseconds.Value).TotalSeconds:0.00} sec"
|
: $"{TimeSpan.FromMilliseconds(milliseconds.Value).TotalSeconds:0.00} sec"
|
||||||
: "None";
|
: "None";
|
||||||
|
|
||||||
|
private static string DisplayBytes(long? bytes)
|
||||||
|
=> bytes.HasValue ? $"{bytes.Value:N0} bytes" : "None";
|
||||||
|
|
||||||
|
private static string DisplayCost(decimal? value, string currency)
|
||||||
|
=> value.HasValue ? $"{value.Value:0.000000} {currency}" : "None";
|
||||||
|
|
||||||
private static string DisplayName(string? title, int? id, string label)
|
private static string DisplayName(string? title, int? id, string label)
|
||||||
=> !string.IsNullOrWhiteSpace(title)
|
=> !string.IsNullOrWhiteSpace(title)
|
||||||
? title
|
? title
|
||||||
|
|||||||
@ -43,8 +43,11 @@
|
|||||||
<th>Created</th>
|
<th>Created</th>
|
||||||
<th>Project / Book</th>
|
<th>Project / Book</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Failure stage</th>
|
||||||
<th>Scenes</th>
|
<th>Scenes</th>
|
||||||
<th>Tokens</th>
|
<th>Tokens</th>
|
||||||
|
<th>Cost</th>
|
||||||
<th>Duration</th>
|
<th>Duration</th>
|
||||||
<th>Validation</th>
|
<th>Validation</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
@ -61,8 +64,11 @@
|
|||||||
<div class="muted">@DisplayName(run.BookTitle, run.BookID, "Book")</div>
|
<div class="muted">@DisplayName(run.BookTitle, run.BookID, "Book")</div>
|
||||||
</td>
|
</td>
|
||||||
<td>@run.Status</td>
|
<td>@run.Status</td>
|
||||||
|
<td>@Display(run.SourceType)</td>
|
||||||
|
<td>@Display(run.FailureStage)</td>
|
||||||
<td>@run.SceneCount.ToString("N0")</td>
|
<td>@run.SceneCount.ToString("N0")</td>
|
||||||
<td>@Display(run.TotalTokens)</td>
|
<td>@Display(run.TotalTokens)</td>
|
||||||
|
<td>@DisplayCost(run.EstimatedCostGBP, "GBP") / @DisplayCost(run.EstimatedCostUSD, "USD")</td>
|
||||||
<td>@DisplayDuration(run.TotalDurationMs)</td>
|
<td>@DisplayDuration(run.TotalDurationMs)</td>
|
||||||
<td>@run.ValidationErrorsCount.ToString("N0") error(s), @run.ValidationWarningsCount.ToString("N0") warning(s)</td>
|
<td>@run.ValidationErrorsCount.ToString("N0") error(s), @run.ValidationWarningsCount.ToString("N0") warning(s)</td>
|
||||||
<td><a class="btn btn-outline-primary btn-sm" asp-action="StoryIntelligenceRunDetails" asp-route-id="@run.StoryIntelligenceRunID">Open</a></td>
|
<td><a class="btn btn-outline-primary btn-sm" asp-action="StoryIntelligenceRunDetails" asp-route-id="@run.StoryIntelligenceRunID">Open</a></td>
|
||||||
@ -90,6 +96,9 @@
|
|||||||
: $"{TimeSpan.FromMilliseconds(milliseconds.Value).TotalSeconds:0.00} sec"
|
: $"{TimeSpan.FromMilliseconds(milliseconds.Value).TotalSeconds:0.00} sec"
|
||||||
: "None";
|
: "None";
|
||||||
|
|
||||||
|
private static string DisplayCost(decimal? value, string currency)
|
||||||
|
=> value.HasValue ? $"{value.Value:0.000000} {currency}" : "None";
|
||||||
|
|
||||||
private static string DisplayName(string? title, int? id, string label)
|
private static string DisplayName(string? title, int? id, string label)
|
||||||
=> !string.IsNullOrWhiteSpace(title)
|
=> !string.IsNullOrWhiteSpace(title)
|
||||||
? title
|
? title
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user