PlotDirector/PlotLine/Sql/169_Phase26E_StoryIntelligenceConcurrentExecution.sql

159 lines
6.5 KiB
Transact-SQL

CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_ClaimNextPendingFair
@MaxConcurrentRunsPerBook int = 2,
@MaxConcurrentRunsPerUser int = 3,
@ClaimLeaseMinutes int = 90
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @Now datetime2(7) = SYSUTCDATETIME();
DECLARE @SafeMaxPerBook int = CASE WHEN ISNULL(@MaxConcurrentRunsPerBook, 0) < 1 THEN 1 ELSE @MaxConcurrentRunsPerBook END;
DECLARE @SafeMaxPerUser int = CASE WHEN ISNULL(@MaxConcurrentRunsPerUser, 0) < 1 THEN 1 ELSE @MaxConcurrentRunsPerUser END;
DECLARE @SafeClaimLeaseMinutes int = CASE WHEN ISNULL(@ClaimLeaseMinutes, 0) < 15 THEN 15 ELSE @ClaimLeaseMinutes END;
DECLARE @StoryIntelligenceRunID int;
BEGIN TRANSACTION;
UPDATE dbo.StoryIntelligenceRuns
SET Status = N'Pending',
CurrentStage = N'Pending',
CurrentMessage = N'Returned to the Story Intelligence queue after an expired worker claim.',
StartedUtc = NULL,
UpdatedUtc = @Now
WHERE Status = N'Running'
AND CancellationRequestedUtc IS NULL
AND UpdatedUtc < DATEADD(minute, -@SafeClaimLeaseMinutes, @Now);
;WITH PendingRuns AS
(
SELECT
pending.StoryIntelligenceRunID,
pending.CreatedUtc,
pending.UserID,
pending.BookID,
ActiveForBook = (
SELECT COUNT_BIG(1)
FROM dbo.StoryIntelligenceRuns activeBook
WHERE activeBook.Status = N'Running'
AND activeBook.BookID = pending.BookID
),
ActiveForUser = (
SELECT COUNT_BIG(1)
FROM dbo.StoryIntelligenceRuns activeUser
WHERE activeUser.Status = N'Running'
AND activeUser.UserID = pending.UserID
)
FROM dbo.StoryIntelligenceRuns pending WITH (UPDLOCK, READPAST)
WHERE pending.Status = N'Pending'
AND (
pending.BookID IS NULL
OR (
SELECT COUNT_BIG(1)
FROM dbo.StoryIntelligenceRuns activeBook
WHERE activeBook.Status = N'Running'
AND activeBook.BookID = pending.BookID
) < @SafeMaxPerBook
)
AND (
SELECT COUNT_BIG(1)
FROM dbo.StoryIntelligenceRuns activeUser
WHERE activeUser.Status = N'Running'
AND activeUser.UserID = pending.UserID
) < @SafeMaxPerUser
)
SELECT TOP (1) @StoryIntelligenceRunID = StoryIntelligenceRunID
FROM PendingRuns
ORDER BY
ActiveForBook,
ActiveForUser,
CreatedUtc,
StoryIntelligenceRunID;
IF @StoryIntelligenceRunID IS NOT NULL
BEGIN
UPDATE dbo.StoryIntelligenceRuns
SET Status = N'Running',
StartedUtc = @Now,
CurrentStage = N'ChapterStructure',
CurrentMessage = N'Running Chapter Structure analysis.',
UpdatedUtc = @Now
WHERE StoryIntelligenceRunID = @StoryIntelligenceRunID
AND Status = N'Pending';
END;
COMMIT TRANSACTION;
IF @StoryIntelligenceRunID IS NULL
BEGIN
SELECT TOP (0)
StoryIntelligenceRunID, UserID, ProjectID, BookID, ChapterID, ChapterNumber, Status, SourceType, SourceLabel,
SourceText, SourceWordCount, SourceCharacterCount, SourceParagraphCount, SourceChapterCount, PromptVersion,
PromptVersionsSummary, KnownCharactersJson, Model, StartedUtc, CompletedUtc, FailureStage, TotalInputTokens,
TotalOutputTokens, TotalTokens, TotalDurationMs, EstimatedCostGBP, EstimatedCostUSD,
ErrorMessage, ErrorDetail, CurrentStage, CurrentMessage, TotalDetectedScenes,
CompletedScenes, FailedScenes, CancellationRequestedUtc, CancelledUtc, CreatedUtc, UpdatedUtc
FROM dbo.StoryIntelligenceRuns;
RETURN;
END;
SELECT
StoryIntelligenceRunID, UserID, ProjectID, BookID, ChapterID, ChapterNumber, Status, SourceType, SourceLabel,
SourceText, SourceWordCount, SourceCharacterCount, SourceParagraphCount, SourceChapterCount, PromptVersion,
PromptVersionsSummary, KnownCharactersJson, Model, StartedUtc, CompletedUtc, FailureStage, TotalInputTokens,
TotalOutputTokens, TotalTokens, TotalDurationMs, EstimatedCostGBP, EstimatedCostUSD,
ErrorMessage, ErrorDetail, CurrentStage, CurrentMessage, TotalDetectedScenes,
CompletedScenes, FailedScenes, CancellationRequestedUtc, CancelledUtc, CreatedUtc, UpdatedUtc
FROM dbo.StoryIntelligenceRuns
WHERE StoryIntelligenceRunID = @StoryIntelligenceRunID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceRun_ListActiveBookSummaryForUser
@UserID int
AS
BEGIN
SET NOCOUNT ON;
SELECT
r.UserID,
r.ProjectID,
p.ProjectName AS ProjectTitle,
r.BookID,
b.BookTitle,
b.Subtitle AS BookSubtitle,
COUNT(1) AS RunCount,
SUM(CASE WHEN r.Status = N'Pending' THEN 1 ELSE 0 END) AS PendingRunCount,
SUM(CASE WHEN r.Status = N'Running' THEN 1 ELSE 0 END) AS RunningRunCount,
SUM(CASE WHEN r.Status IN (N'Completed', N'CompletedWithWarnings') THEN 1 ELSE 0 END) AS CompletedRunCount,
SUM(CASE WHEN r.Status IN (N'Failed', N'Cancelled') THEN 1 ELSE 0 END) AS FailedRunCount,
SUM(ISNULL(r.TotalDetectedScenes, 0)) AS TotalDetectedScenes,
SUM(ISNULL(r.CompletedScenes, 0)) AS CompletedScenes,
SUM(ISNULL(r.FailedScenes, 0)) AS FailedScenes,
SUM(ISNULL(r.TotalDurationMs, 0)) AS TotalDurationMs,
MIN(r.CreatedUtc) AS FirstCreatedUtc,
MAX(r.UpdatedUtc) AS 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.UserID = @UserID
AND r.Status IN (N'Pending', N'Running')
GROUP BY r.UserID, r.ProjectID, p.ProjectName, r.BookID, b.BookTitle, b.Subtitle
ORDER BY MAX(r.UpdatedUtc) DESC, MIN(r.CreatedUtc);
END;
GO
IF NOT EXISTS
(
SELECT 1
FROM sys.indexes
WHERE name = N'IX_StoryIntelligenceRuns_QueueFairness'
AND object_id = OBJECT_ID(N'dbo.StoryIntelligenceRuns')
)
BEGIN
CREATE INDEX IX_StoryIntelligenceRuns_QueueFairness
ON dbo.StoryIntelligenceRuns (Status, UserID, BookID, CreatedUtc, StoryIntelligenceRunID)
INCLUDE (ProjectID, ChapterID, ChapterNumber, UpdatedUtc, TotalDetectedScenes, CompletedScenes, FailedScenes, TotalDurationMs);
END;
GO