SET ANSI_NULLS ON; GO SET QUOTED_IDENTIFIER ON; GO IF OBJECT_ID(N'dbo.AuthorPlotSeeds', N'U') IS NULL BEGIN CREATE TABLE dbo.AuthorPlotSeeds ( AuthorPlotSeedID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_AuthorPlotSeeds PRIMARY KEY, ProjectID int NOT NULL, BookID int NOT NULL, Description nvarchar(max) NOT NULL, Status nvarchar(30) NOT NULL CONSTRAINT DF_AuthorPlotSeeds_Status DEFAULT N'Active', MatchedPlotLineID int NULL, Source nvarchar(40) NOT NULL CONSTRAINT DF_AuthorPlotSeeds_Source DEFAULT N'Author', CreatedUtc datetime2 NOT NULL CONSTRAINT DF_AuthorPlotSeeds_CreatedUtc DEFAULT SYSUTCDATETIME(), UpdatedUtc datetime2 NOT NULL CONSTRAINT DF_AuthorPlotSeeds_UpdatedUtc DEFAULT SYSUTCDATETIME(), CONSTRAINT FK_AuthorPlotSeeds_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID), CONSTRAINT FK_AuthorPlotSeeds_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID) ON DELETE CASCADE, CONSTRAINT FK_AuthorPlotSeeds_MatchedPlotLines FOREIGN KEY (MatchedPlotLineID) REFERENCES dbo.PlotLines(PlotLineID) ON DELETE SET NULL ); END; GO IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_AuthorPlotSeeds_Books' AND parent_object_id = OBJECT_ID(N'dbo.AuthorPlotSeeds')) BEGIN ALTER TABLE dbo.AuthorPlotSeeds DROP CONSTRAINT FK_AuthorPlotSeeds_Books; ALTER TABLE dbo.AuthorPlotSeeds WITH CHECK ADD CONSTRAINT FK_AuthorPlotSeeds_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID) ON DELETE CASCADE; END; GO IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_AuthorPlotSeeds_MatchedPlotLines' AND parent_object_id = OBJECT_ID(N'dbo.AuthorPlotSeeds')) BEGIN ALTER TABLE dbo.AuthorPlotSeeds DROP CONSTRAINT FK_AuthorPlotSeeds_MatchedPlotLines; ALTER TABLE dbo.AuthorPlotSeeds WITH CHECK ADD CONSTRAINT FK_AuthorPlotSeeds_MatchedPlotLines FOREIGN KEY (MatchedPlotLineID) REFERENCES dbo.PlotLines(PlotLineID) ON DELETE SET NULL; END; GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_AuthorPlotSeeds_Book_Status' AND object_id = OBJECT_ID(N'dbo.AuthorPlotSeeds')) CREATE INDEX IX_AuthorPlotSeeds_Book_Status ON dbo.AuthorPlotSeeds(BookID, Status); GO IF OBJECT_ID(N'dbo.WholeBookPlotSynthesisRuns', N'U') IS NULL BEGIN CREATE TABLE dbo.WholeBookPlotSynthesisRuns ( WholeBookPlotSynthesisRunID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WholeBookPlotSynthesisRuns PRIMARY KEY, ProjectID int NOT NULL, BookID int NOT NULL, UserID int NOT NULL, SourceStoryIntelligenceRunID int NULL, SourceStoryIntelligenceRunKey AS ISNULL(SourceStoryIntelligenceRunID, 0) PERSISTED, CharacterResolutionVersion nvarchar(120) NOT NULL, Status nvarchar(30) NOT NULL CONSTRAINT DF_WholeBookPlotSynthesisRuns_Status DEFAULT N'Queued', QueuedUtc datetime2 NOT NULL CONSTRAINT DF_WholeBookPlotSynthesisRuns_QueuedUtc DEFAULT SYSUTCDATETIME(), StartedUtc datetime2 NULL, CompletedUtc datetime2 NULL, FailedUtc datetime2 NULL, AttemptCount int NOT NULL CONSTRAINT DF_WholeBookPlotSynthesisRuns_AttemptCount DEFAULT 0, Model nvarchar(100) NULL, OpenAIResponseID nvarchar(120) NULL, InputTokens int NULL, OutputTokens int NULL, CachedInputTokens int NULL, TotalTokens int NULL, DurationMs bigint NULL, ErrorMessage nvarchar(max) NULL, ErrorDetail nvarchar(max) NULL, ResultJson nvarchar(max) NULL, IsCurrent bit NOT NULL CONSTRAINT DF_WholeBookPlotSynthesisRuns_IsCurrent DEFAULT 1, UpdatedUtc datetime2 NOT NULL CONSTRAINT DF_WholeBookPlotSynthesisRuns_UpdatedUtc DEFAULT SYSUTCDATETIME(), CONSTRAINT FK_WholeBookPlotSynthesisRuns_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID), CONSTRAINT FK_WholeBookPlotSynthesisRuns_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID) ON DELETE CASCADE, CONSTRAINT FK_WholeBookPlotSynthesisRuns_AppUser FOREIGN KEY (UserID) REFERENCES dbo.AppUser(UserID), CONSTRAINT FK_WholeBookPlotSynthesisRuns_SourceRun FOREIGN KEY (SourceStoryIntelligenceRunID) REFERENCES dbo.StoryIntelligenceRuns(StoryIntelligenceRunID) ON DELETE SET NULL ); END; GO IF COL_LENGTH(N'dbo.WholeBookPlotSynthesisRuns', N'SourceStoryIntelligenceRunKey') IS NULL ALTER TABLE dbo.WholeBookPlotSynthesisRuns ADD SourceStoryIntelligenceRunKey AS ISNULL(SourceStoryIntelligenceRunID, 0) PERSISTED; GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_WholeBookPlotSynthesisRuns_Queue' AND object_id = OBJECT_ID(N'dbo.WholeBookPlotSynthesisRuns')) CREATE INDEX IX_WholeBookPlotSynthesisRuns_Queue ON dbo.WholeBookPlotSynthesisRuns(Status, QueuedUtc) INCLUDE (BookID, UserID); GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'UX_WholeBookPlotSynthesisRuns_CurrentVersion' AND object_id = OBJECT_ID(N'dbo.WholeBookPlotSynthesisRuns')) CREATE UNIQUE INDEX UX_WholeBookPlotSynthesisRuns_CurrentVersion ON dbo.WholeBookPlotSynthesisRuns(BookID, SourceStoryIntelligenceRunKey, CharacterResolutionVersion) WHERE IsCurrent = 1 AND Status IN (N'Queued', N'Running', N'Completed'); GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'WholeBookPlotSynthesisRunID') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD WholeBookPlotSynthesisRunID int NULL; GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'AuthorPlotSeedID') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD AuthorPlotSeedID int NULL; GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'ExistingPlotLineMatchConfidence') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD ExistingPlotLineMatchConfidence decimal(5,4) NULL; GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'ExistingPlotLineMatchReason') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD ExistingPlotLineMatchReason nvarchar(max) NULL; GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'SeedMatchConfidence') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD SeedMatchConfidence decimal(5,4) NULL; GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'SeedMatchReason') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD SeedMatchReason nvarchar(max) NULL; GO IF COL_LENGTH(N'dbo.StoryIntelligenceNarrativeCandidates', N'Source') IS NULL ALTER TABLE dbo.StoryIntelligenceNarrativeCandidates ADD Source nvarchar(60) NULL; GO IF OBJECT_ID(N'dbo.StoryIntelligenceNarrativeCandidateEvents', N'U') IS NULL BEGIN CREATE TABLE dbo.StoryIntelligenceNarrativeCandidateEvents ( NarrativeCandidateEventID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_StoryIntelligenceNarrativeCandidateEvents PRIMARY KEY, NarrativeCandidateID int NOT NULL, WholeBookPlotSynthesisRunID int NULL, EventKey nvarchar(260) NOT NULL, SceneID int NOT NULL, EventType nvarchar(100) NOT NULL, EventTitle nvarchar(200) NOT NULL, EventDescription nvarchar(max) NULL, Evidence nvarchar(max) NULL, Confidence decimal(5,4) NULL, SortOrder int NOT NULL CONSTRAINT DF_StoryIntelligenceNarrativeCandidateEvents_SortOrder DEFAULT 0, IsIgnored bit NOT NULL CONSTRAINT DF_StoryIntelligenceNarrativeCandidateEvents_IsIgnored DEFAULT 0, AcceptedThreadEventID int NULL, CreatedUtc datetime2 NOT NULL CONSTRAINT DF_StoryIntelligenceNarrativeCandidateEvents_CreatedUtc DEFAULT SYSUTCDATETIME(), UpdatedUtc datetime2 NOT NULL CONSTRAINT DF_StoryIntelligenceNarrativeCandidateEvents_UpdatedUtc DEFAULT SYSUTCDATETIME(), CONSTRAINT FK_StoryIntelligenceNarrativeCandidateEvents_Candidates FOREIGN KEY (NarrativeCandidateID) REFERENCES dbo.StoryIntelligenceNarrativeCandidates(NarrativeCandidateID) ON DELETE CASCADE, CONSTRAINT FK_StoryIntelligenceNarrativeCandidateEvents_SynthesisRun FOREIGN KEY (WholeBookPlotSynthesisRunID) REFERENCES dbo.WholeBookPlotSynthesisRuns(WholeBookPlotSynthesisRunID), CONSTRAINT FK_StoryIntelligenceNarrativeCandidateEvents_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID), CONSTRAINT FK_StoryIntelligenceNarrativeCandidateEvents_AcceptedThreadEvents FOREIGN KEY (AcceptedThreadEventID) REFERENCES dbo.ThreadEvents(ThreadEventID) ); END; GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'UX_StoryIntelligenceNarrativeCandidateEvents_Key' AND object_id = OBJECT_ID(N'dbo.StoryIntelligenceNarrativeCandidateEvents')) CREATE UNIQUE INDEX UX_StoryIntelligenceNarrativeCandidateEvents_Key ON dbo.StoryIntelligenceNarrativeCandidateEvents(NarrativeCandidateID, EventKey); GO IF COL_LENGTH(N'dbo.ThreadEvents', N'Source') IS NULL ALTER TABLE dbo.ThreadEvents ADD Source nvarchar(60) NOT NULL CONSTRAINT DF_ThreadEvents_Source DEFAULT N'Author'; GO IF COL_LENGTH(N'dbo.ThreadEvents', N'SourceReferenceID') IS NULL ALTER TABLE dbo.ThreadEvents ADD SourceReferenceID int NULL; GO IF COL_LENGTH(N'dbo.ThreadEvents', N'SourceKey') IS NULL ALTER TABLE dbo.ThreadEvents ADD SourceKey nvarchar(260) NULL; GO IF COL_LENGTH(N'dbo.ThreadEvents', N'Confidence') IS NULL ALTER TABLE dbo.ThreadEvents ADD Confidence decimal(5,4) NULL; GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'UX_ThreadEvents_SourceKey' AND object_id = OBJECT_ID(N'dbo.ThreadEvents')) CREATE UNIQUE INDEX UX_ThreadEvents_SourceKey ON dbo.ThreadEvents(Source, SourceKey) WHERE SourceKey IS NOT NULL; GO CREATE OR ALTER PROCEDURE dbo.AuthorPlotSeed_SaveForBook @ProjectID int, @BookID int, @SeedsJson nvarchar(max) AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @Seeds TABLE (Description nvarchar(max) NOT NULL, SortOrder int IDENTITY(1,1)); INSERT @Seeds (Description) SELECT DISTINCT LEFT(LTRIM(RTRIM(JSON_VALUE(value, '$.description'))), 4000) FROM OPENJSON(@SeedsJson) WHERE NULLIF(LTRIM(RTRIM(JSON_VALUE(value, '$.description'))), N'') IS NOT NULL; UPDATE dbo.AuthorPlotSeeds SET Status = N'Archived', UpdatedUtc = SYSUTCDATETIME() WHERE ProjectID = @ProjectID AND BookID = @BookID AND Source = N'Author' AND Status = N'Active' AND NOT EXISTS ( SELECT 1 FROM @Seeds seed WHERE seed.Description = AuthorPlotSeeds.Description ); INSERT dbo.AuthorPlotSeeds (ProjectID, BookID, Description, Status, Source) SELECT @ProjectID, @BookID, seed.Description, N'Active', N'Author' FROM @Seeds seed WHERE NOT EXISTS ( SELECT 1 FROM dbo.AuthorPlotSeeds existing WHERE existing.ProjectID = @ProjectID AND existing.BookID = @BookID AND existing.Source = N'Author' AND existing.Description = seed.Description AND existing.Status = N'Active' ); END; GO CREATE OR ALTER PROCEDURE dbo.AuthorPlotSeed_ListByBook @BookID int AS BEGIN SET NOCOUNT ON; SELECT AuthorPlotSeedID, ProjectID, BookID, Description, Status, MatchedPlotLineID, Source, CreatedUtc, UpdatedUtc FROM dbo.AuthorPlotSeeds WHERE BookID = @BookID AND Status = N'Active' ORDER BY AuthorPlotSeedID; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisRun_Queue @ProjectID int, @BookID int, @UserID int, @SourceStoryIntelligenceRunID int = NULL, @CharacterResolutionVersion nvarchar(120) AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @ExistingID int; SELECT TOP (1) @ExistingID = WholeBookPlotSynthesisRunID FROM dbo.WholeBookPlotSynthesisRuns WHERE BookID = @BookID AND ISNULL(SourceStoryIntelligenceRunID, 0) = ISNULL(@SourceStoryIntelligenceRunID, 0) AND CharacterResolutionVersion = @CharacterResolutionVersion AND IsCurrent = 1 AND Status IN (N'Queued', N'Running', N'Completed') ORDER BY WholeBookPlotSynthesisRunID DESC; IF @ExistingID IS NULL BEGIN INSERT dbo.WholeBookPlotSynthesisRuns (ProjectID, BookID, UserID, SourceStoryIntelligenceRunID, CharacterResolutionVersion, Status) VALUES (@ProjectID, @BookID, @UserID, @SourceStoryIntelligenceRunID, @CharacterResolutionVersion, N'Queued'); SET @ExistingID = CAST(SCOPE_IDENTITY() AS int); END; SELECT * FROM dbo.WholeBookPlotSynthesisRuns WHERE WholeBookPlotSynthesisRunID = @ExistingID; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisRun_Retry @BookID int, @UserID int AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @PreviousID int, @ProjectID int, @SourceRunID int, @Version nvarchar(120); SELECT TOP (1) @PreviousID = WholeBookPlotSynthesisRunID, @ProjectID = ProjectID, @SourceRunID = SourceStoryIntelligenceRunID, @Version = CharacterResolutionVersion FROM dbo.WholeBookPlotSynthesisRuns WHERE BookID = @BookID AND UserID = @UserID AND IsCurrent = 1 AND Status = N'Failed' ORDER BY WholeBookPlotSynthesisRunID DESC; IF @PreviousID IS NULL BEGIN SELECT TOP (0) * FROM dbo.WholeBookPlotSynthesisRuns; RETURN; END; UPDATE dbo.WholeBookPlotSynthesisRuns SET IsCurrent = 0, Status = N'Superseded', UpdatedUtc = SYSUTCDATETIME() WHERE WholeBookPlotSynthesisRunID = @PreviousID; INSERT dbo.WholeBookPlotSynthesisRuns (ProjectID, BookID, UserID, SourceStoryIntelligenceRunID, CharacterResolutionVersion, Status) VALUES (@ProjectID, @BookID, @UserID, @SourceRunID, @Version, N'Queued'); SELECT * FROM dbo.WholeBookPlotSynthesisRuns WHERE WholeBookPlotSynthesisRunID = CAST(SCOPE_IDENTITY() AS int); END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisRun_ClaimNext @LeaseMinutes int = 90 AS BEGIN SET NOCOUNT ON; SET XACT_ABORT ON; DECLARE @RunID int; SELECT TOP (1) @RunID = WholeBookPlotSynthesisRunID FROM dbo.WholeBookPlotSynthesisRuns WITH (UPDLOCK, READPAST) WHERE IsCurrent = 1 AND ( Status = N'Queued' OR (Status = N'Running' AND StartedUtc < DATEADD(minute, -ABS(@LeaseMinutes), SYSUTCDATETIME())) ) ORDER BY QueuedUtc, WholeBookPlotSynthesisRunID; IF @RunID IS NULL BEGIN SELECT TOP (0) * FROM dbo.WholeBookPlotSynthesisRuns; RETURN; END; UPDATE dbo.WholeBookPlotSynthesisRuns SET Status = N'Running', StartedUtc = SYSUTCDATETIME(), AttemptCount = AttemptCount + 1, UpdatedUtc = SYSUTCDATETIME() WHERE WholeBookPlotSynthesisRunID = @RunID; SELECT * FROM dbo.WholeBookPlotSynthesisRuns WHERE WholeBookPlotSynthesisRunID = @RunID; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisRun_GetCurrentByBook @BookID int AS BEGIN SET NOCOUNT ON; SELECT TOP (1) * FROM dbo.WholeBookPlotSynthesisRuns WHERE BookID = @BookID AND IsCurrent = 1 ORDER BY WholeBookPlotSynthesisRunID DESC; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisRun_Complete @WholeBookPlotSynthesisRunID int, @Model nvarchar(100), @OpenAIResponseID nvarchar(120) = NULL, @InputTokens int = NULL, @OutputTokens int = NULL, @CachedInputTokens int = NULL, @TotalTokens int = NULL, @DurationMs bigint = NULL, @ResultJson nvarchar(max) AS BEGIN SET NOCOUNT ON; UPDATE dbo.WholeBookPlotSynthesisRuns SET Status = N'Completed', CompletedUtc = SYSUTCDATETIME(), Model = @Model, OpenAIResponseID = @OpenAIResponseID, InputTokens = @InputTokens, OutputTokens = @OutputTokens, CachedInputTokens = @CachedInputTokens, TotalTokens = @TotalTokens, DurationMs = @DurationMs, ResultJson = @ResultJson, ErrorMessage = NULL, ErrorDetail = NULL, UpdatedUtc = SYSUTCDATETIME() WHERE WholeBookPlotSynthesisRunID = @WholeBookPlotSynthesisRunID; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisRun_Fail @WholeBookPlotSynthesisRunID int, @ErrorMessage nvarchar(max), @ErrorDetail nvarchar(max) = NULL, @DurationMs bigint = NULL AS BEGIN SET NOCOUNT ON; UPDATE dbo.WholeBookPlotSynthesisRuns SET Status = N'Failed', FailedUtc = SYSUTCDATETIME(), ErrorMessage = @ErrorMessage, ErrorDetail = @ErrorDetail, DurationMs = @DurationMs, UpdatedUtc = SYSUTCDATETIME() WHERE WholeBookPlotSynthesisRunID = @WholeBookPlotSynthesisRunID; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesis_ContextGet @ProjectID int, @BookID int AS BEGIN SET NOCOUNT ON; SELECT BookTitle = b.BookTitle FROM dbo.Books b WHERE b.BookID = @BookID AND b.ProjectID = @ProjectID AND b.IsArchived = 0; SELECT c.ChapterID, c.ChapterNumber, c.ChapterTitle, s.SceneID, s.SceneNumber, s.SceneTitle, s.StructuralSummary, s.StructuralSetting, pov.CharacterName AS PovCharacterName, sms.SourceText FROM dbo.Chapters c INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0 INNER JOIN dbo.SceneManuscriptSources sms ON sms.SceneID = s.SceneID LEFT JOIN dbo.Characters pov ON pov.CharacterID = COALESCE(s.POVCharacterID, c.POVCharacterID) WHERE c.BookID = @BookID AND c.IsArchived = 0 ORDER BY c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID; SELECT CharacterID, CanonicalName = CharacterName FROM dbo.Characters WHERE ProjectID = @ProjectID AND IsArchived = 0 ORDER BY CharacterName; SELECT alias.CharacterID, alias.Alias FROM dbo.CharacterAliases alias INNER JOIN dbo.Characters character ON character.CharacterID = alias.CharacterID WHERE character.ProjectID = @ProjectID AND character.IsArchived = 0 ORDER BY alias.CharacterID, alias.Alias; SELECT pl.PlotLineID, Name = pl.PlotLineName, pl.Description, Importance = pi.ImportanceName, pl.BookID FROM dbo.PlotLines pl LEFT JOIN dbo.PlotImportance pi ON pi.PlotImportanceID = pl.PlotImportanceID WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0 ORDER BY pl.SortOrder, pl.PlotLineName; SELECT pt.PlotThreadID, pt.PlotLineID, Title = pt.ThreadTitle, pt.Summary, Status = ts.StatusName FROM dbo.PlotThreads pt INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID LEFT JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID WHERE pl.ProjectID = @ProjectID AND pl.IsArchived = 0 AND pt.IsArchived = 0 ORDER BY pt.PlotLineID, pt.Importance DESC, pt.ThreadTitle; EXEC dbo.AuthorPlotSeed_ListByBook @BookID; SELECT SceneCount = ( SELECT COUNT(1) FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID WHERE c.BookID = @BookID AND c.IsArchived = 0 AND s.IsArchived = 0 ), SourceRowCount = ( SELECT COUNT(1) FROM dbo.SceneManuscriptSources sms INNER JOIN dbo.Scenes s ON s.SceneID = sms.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID WHERE c.BookID = @BookID AND c.IsArchived = 0 AND s.IsArchived = 0 ), MissingSourceCount = ( SELECT COUNT(1) FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID LEFT JOIN dbo.SceneManuscriptSources sms ON sms.SceneID = s.SceneID WHERE c.BookID = @BookID AND c.IsArchived = 0 AND s.IsArchived = 0 AND sms.SceneID IS NULL ), DuplicateSourceSceneCount = 0; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisCandidate_Save @WholeBookPlotSynthesisRunID int, @CandidateType nvarchar(30), @CandidateKey nvarchar(220), @ProposedTitle nvarchar(200), @ProposedDescription nvarchar(max) = NULL, @Category nvarchar(100) = NULL, @InferredState nvarchar(100) = NULL, @ChangeType nvarchar(40) = NULL, @Significance nvarchar(20) = NULL, @ExistingPlotLineID int = NULL, @ExistingPlotThreadID int = NULL, @RelatedPlotLineID int = NULL, @ParentCandidateKey nvarchar(220) = NULL, @ParentNarrativeCandidateID int = NULL, @AuthorPlotSeedID int = NULL, @ExistingPlotLineMatchConfidence decimal(5,4) = NULL, @ExistingPlotLineMatchReason nvarchar(max) = NULL, @SeedMatchConfidence decimal(5,4) = NULL, @SeedMatchReason nvarchar(max) = NULL, @Confidence decimal(5,4) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @ProjectID int, @BookID int, @NarrativeCandidateID int; SELECT @ProjectID = ProjectID, @BookID = BookID FROM dbo.WholeBookPlotSynthesisRuns WHERE WholeBookPlotSynthesisRunID = @WholeBookPlotSynthesisRunID; SELECT @NarrativeCandidateID = NarrativeCandidateID FROM dbo.StoryIntelligenceNarrativeCandidates WHERE BookID = @BookID AND CandidateType = @CandidateType AND CandidateKey = @CandidateKey; IF @NarrativeCandidateID IS NULL BEGIN INSERT dbo.StoryIntelligenceNarrativeCandidates ( ProjectID, BookID, CandidateType, CandidateKey, ProposedTitle, ProposedDescription, Category, InferredState, ChangeType, Significance, ExistingPlotLineID, ExistingPlotThreadID, RelatedPlotLineID, ParentCandidateKey, ParentNarrativeCandidateID, WholeBookPlotSynthesisRunID, AuthorPlotSeedID, ExistingPlotLineMatchConfidence, ExistingPlotLineMatchReason, SeedMatchConfidence, SeedMatchReason, Confidence, Source ) VALUES ( @ProjectID, @BookID, @CandidateType, @CandidateKey, @ProposedTitle, @ProposedDescription, @Category, @InferredState, @ChangeType, @Significance, @ExistingPlotLineID, @ExistingPlotThreadID, @RelatedPlotLineID, @ParentCandidateKey, @ParentNarrativeCandidateID, @WholeBookPlotSynthesisRunID, @AuthorPlotSeedID, @ExistingPlotLineMatchConfidence, @ExistingPlotLineMatchReason, @SeedMatchConfidence, @SeedMatchReason, @Confidence, N'WholeBookPlotSynthesis' ); SET @NarrativeCandidateID = CAST(SCOPE_IDENTITY() AS int); END ELSE BEGIN UPDATE dbo.StoryIntelligenceNarrativeCandidates SET ProposedTitle = @ProposedTitle, ProposedDescription = @ProposedDescription, Category = @Category, InferredState = @InferredState, ChangeType = @ChangeType, Significance = @Significance, ExistingPlotLineID = @ExistingPlotLineID, ExistingPlotThreadID = @ExistingPlotThreadID, RelatedPlotLineID = @RelatedPlotLineID, ParentCandidateKey = @ParentCandidateKey, ParentNarrativeCandidateID = @ParentNarrativeCandidateID, WholeBookPlotSynthesisRunID = @WholeBookPlotSynthesisRunID, AuthorPlotSeedID = @AuthorPlotSeedID, ExistingPlotLineMatchConfidence = @ExistingPlotLineMatchConfidence, ExistingPlotLineMatchReason = @ExistingPlotLineMatchReason, SeedMatchConfidence = @SeedMatchConfidence, SeedMatchReason = @SeedMatchReason, Confidence = @Confidence, Source = N'WholeBookPlotSynthesis', UpdatedUtc = SYSUTCDATETIME() WHERE NarrativeCandidateID = @NarrativeCandidateID AND Status = N'Pending'; END; SELECT @NarrativeCandidateID; END; GO CREATE OR ALTER PROCEDURE dbo.WholeBookPlotSynthesisCandidateEvent_Save @NarrativeCandidateID int, @SynthesisRunID int, @EventKey nvarchar(260), @SceneID int, @EventType nvarchar(100), @EventTitle nvarchar(200), @EventDescription nvarchar(max) = NULL, @Evidence nvarchar(max) = NULL, @Confidence decimal(5,4) = NULL, @SortOrder int = 0 AS BEGIN SET NOCOUNT ON; MERGE dbo.StoryIntelligenceNarrativeCandidateEvents AS target USING (SELECT @NarrativeCandidateID AS NarrativeCandidateID, @EventKey AS EventKey) AS source ON target.NarrativeCandidateID = source.NarrativeCandidateID AND target.EventKey = source.EventKey WHEN MATCHED THEN UPDATE SET WholeBookPlotSynthesisRunID = @SynthesisRunID, SceneID = @SceneID, EventType = @EventType, EventTitle = @EventTitle, EventDescription = @EventDescription, Evidence = @Evidence, Confidence = @Confidence, SortOrder = @SortOrder, UpdatedUtc = SYSUTCDATETIME() WHEN NOT MATCHED THEN INSERT (NarrativeCandidateID, WholeBookPlotSynthesisRunID, EventKey, SceneID, EventType, EventTitle, EventDescription, Evidence, Confidence, SortOrder) VALUES (@NarrativeCandidateID, @SynthesisRunID, @EventKey, @SceneID, @EventType, @EventTitle, @EventDescription, @Evidence, @Confidence, @SortOrder); END; GO CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceNarrativeCandidate_Save @ProjectID int, @BookID int, @CandidateType nvarchar(30), @CandidateKey nvarchar(220), @ProposedTitle nvarchar(200), @ProposedDescription nvarchar(max) = NULL, @Category nvarchar(100) = NULL, @InferredState nvarchar(100) = NULL, @ChangeType nvarchar(40) = NULL, @Significance nvarchar(20) = NULL, @ExistingPlotLineID int = NULL, @ExistingPlotThreadID int = NULL, @WholeBookPlotSynthesisRunID int = NULL, @AuthorPlotSeedID int = NULL, @ExistingPlotLineMatchConfidence decimal(5,4) = NULL, @ExistingPlotLineMatchReason nvarchar(max) = NULL, @SeedMatchConfidence decimal(5,4) = NULL, @SeedMatchReason nvarchar(max) = NULL, @Source nvarchar(60) = NULL, @RelatedPlotLineID int = NULL, @ParentCandidateKey nvarchar(220) = NULL, @ParentNarrativeCandidateID int = NULL, @Confidence decimal(5,4) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @NarrativeCandidateID int; SELECT @NarrativeCandidateID = NarrativeCandidateID FROM dbo.StoryIntelligenceNarrativeCandidates WHERE BookID = @BookID AND CandidateType = @CandidateType AND CandidateKey = @CandidateKey; IF @ParentNarrativeCandidateID IS NULL AND @ParentCandidateKey IS NOT NULL BEGIN SELECT @ParentNarrativeCandidateID = NarrativeCandidateID FROM dbo.StoryIntelligenceNarrativeCandidates WHERE BookID = @BookID AND CandidateType = N'PlotLine' AND CandidateKey = @ParentCandidateKey; END; IF @NarrativeCandidateID IS NULL BEGIN INSERT dbo.StoryIntelligenceNarrativeCandidates (ProjectID, BookID, CandidateType, CandidateKey, ProposedTitle, ProposedDescription, Category, InferredState, ChangeType, Significance, ExistingPlotLineID, ExistingPlotThreadID, RelatedPlotLineID, ParentCandidateKey, ParentNarrativeCandidateID, WholeBookPlotSynthesisRunID, AuthorPlotSeedID, ExistingPlotLineMatchConfidence, ExistingPlotLineMatchReason, SeedMatchConfidence, SeedMatchReason, Confidence, Source) VALUES (@ProjectID, @BookID, @CandidateType, @CandidateKey, @ProposedTitle, @ProposedDescription, @Category, @InferredState, @ChangeType, @Significance, @ExistingPlotLineID, @ExistingPlotThreadID, @RelatedPlotLineID, @ParentCandidateKey, @ParentNarrativeCandidateID, @WholeBookPlotSynthesisRunID, @AuthorPlotSeedID, @ExistingPlotLineMatchConfidence, @ExistingPlotLineMatchReason, @SeedMatchConfidence, @SeedMatchReason, @Confidence, COALESCE(@Source, N'SceneSignals')); SET @NarrativeCandidateID = CAST(SCOPE_IDENTITY() AS int); END ELSE BEGIN UPDATE dbo.StoryIntelligenceNarrativeCandidates SET ProposedTitle = @ProposedTitle, ProposedDescription = @ProposedDescription, Category = @Category, InferredState = @InferredState, ChangeType = @ChangeType, Significance = @Significance, ExistingPlotLineID = @ExistingPlotLineID, ExistingPlotThreadID = @ExistingPlotThreadID, RelatedPlotLineID = @RelatedPlotLineID, ParentCandidateKey = @ParentCandidateKey, ParentNarrativeCandidateID = @ParentNarrativeCandidateID, WholeBookPlotSynthesisRunID = @WholeBookPlotSynthesisRunID, AuthorPlotSeedID = @AuthorPlotSeedID, ExistingPlotLineMatchConfidence = @ExistingPlotLineMatchConfidence, ExistingPlotLineMatchReason = @ExistingPlotLineMatchReason, SeedMatchConfidence = @SeedMatchConfidence, SeedMatchReason = @SeedMatchReason, Confidence = @Confidence, Source = COALESCE(@Source, Source, N'SceneSignals'), UpdatedUtc = SYSUTCDATETIME() WHERE NarrativeCandidateID = @NarrativeCandidateID AND Status = N'Pending'; END; SELECT @NarrativeCandidateID; END; GO CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceNarrativeCandidate_ListByBook @BookID int AS BEGIN SET NOCOUNT ON; SELECT nc.NarrativeCandidateID, nc.ProjectID, nc.BookID, nc.CandidateType, nc.CandidateKey, nc.ProposedTitle, nc.ProposedDescription, nc.Category, nc.InferredState, nc.ChangeType, nc.Significance, nc.ExistingPlotLineID, epl.PlotLineName AS ExistingPlotLineName, nc.ExistingPlotThreadID, ept.ThreadTitle AS ExistingPlotThreadTitle, nc.WholeBookPlotSynthesisRunID, nc.AuthorPlotSeedID, aps.Description AS AuthorPlotSeedDescription, aps.Source AS AuthorPlotSeedSource, nc.ExistingPlotLineMatchConfidence, nc.ExistingPlotLineMatchReason, nc.SeedMatchConfidence, nc.SeedMatchReason, nc.Source, nc.RelatedPlotLineID, rpl.PlotLineName AS RelatedPlotLineName, nc.ParentCandidateKey, nc.ParentNarrativeCandidateID, parent.ProposedTitle AS ParentCandidateTitle, nc.MergeTargetNarrativeCandidateID, nc.Confidence, nc.Status, nc.ReviewAction, nc.AcceptedPlotLineID, apl.PlotLineName AS AcceptedPlotLineName, nc.AcceptedPlotThreadID, apt.ThreadTitle AS AcceptedPlotThreadTitle, nc.ReviewedByUserID, nc.ReviewedUtc, nc.CreatedUtc, nc.UpdatedUtc FROM dbo.StoryIntelligenceNarrativeCandidates nc LEFT JOIN dbo.PlotLines epl ON epl.PlotLineID = nc.ExistingPlotLineID LEFT JOIN dbo.AuthorPlotSeeds aps ON aps.AuthorPlotSeedID = nc.AuthorPlotSeedID LEFT JOIN dbo.PlotThreads ept ON ept.PlotThreadID = nc.ExistingPlotThreadID LEFT JOIN dbo.PlotLines rpl ON rpl.PlotLineID = nc.RelatedPlotLineID LEFT JOIN dbo.StoryIntelligenceNarrativeCandidates parent ON parent.NarrativeCandidateID = nc.ParentNarrativeCandidateID LEFT JOIN dbo.PlotLines apl ON apl.PlotLineID = nc.AcceptedPlotLineID LEFT JOIN dbo.PlotThreads apt ON apt.PlotThreadID = nc.AcceptedPlotThreadID WHERE nc.BookID = @BookID ORDER BY CASE nc.CandidateType WHEN N'PlotLine' THEN 0 ELSE 1 END, nc.ProposedTitle; SELECT e.NarrativeCandidateEvidenceID, e.NarrativeCandidateID, e.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, c.ChapterTitle, e.ContributionType, e.Evidence, e.SortOrder, e.CreatedUtc FROM dbo.StoryIntelligenceNarrativeCandidateEvidence e INNER JOIN dbo.StoryIntelligenceNarrativeCandidates nc ON nc.NarrativeCandidateID = e.NarrativeCandidateID INNER JOIN dbo.Scenes s ON s.SceneID = e.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID WHERE nc.BookID = @BookID ORDER BY e.NarrativeCandidateID, c.SortOrder, s.SortOrder, e.SortOrder; SELECT ev.NarrativeCandidateEventID, ev.NarrativeCandidateID, ev.WholeBookPlotSynthesisRunID, ev.EventKey, ev.SceneID, s.SceneNumber, s.SceneTitle, c.ChapterNumber, c.ChapterTitle, ev.EventType, ev.EventTitle, ev.EventDescription, ev.Evidence, ev.Confidence, ev.SortOrder, ev.IsIgnored, ev.AcceptedThreadEventID, ev.CreatedUtc, ev.UpdatedUtc FROM dbo.StoryIntelligenceNarrativeCandidateEvents ev INNER JOIN dbo.StoryIntelligenceNarrativeCandidates nc ON nc.NarrativeCandidateID = ev.NarrativeCandidateID INNER JOIN dbo.Scenes s ON s.SceneID = ev.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID WHERE nc.BookID = @BookID ORDER BY ev.NarrativeCandidateID, c.SortOrder, s.SortOrder, ev.SortOrder; END; GO CREATE OR ALTER PROCEDURE dbo.ThreadEvent_Save @ThreadEventID int = NULL, @PlotThreadID int, @SceneID int, @EventTypeID int, @PlotEventTypeID int = 2, @TargetPlotLineID int = NULL, @EventTitle nvarchar(200), @EventDescription nvarchar(max) = NULL, @Source nvarchar(60) = N'Author', @SourceReferenceID int = NULL, @SourceKey nvarchar(260) = NULL, @Confidence decimal(5,4) = NULL AS BEGIN SET NOCOUNT ON; IF (@ThreadEventID IS NULL OR @ThreadEventID = 0) AND @SourceKey IS NOT NULL BEGIN SELECT @ThreadEventID = ThreadEventID FROM dbo.ThreadEvents WHERE Source = @Source AND SourceKey = @SourceKey; END; IF @ThreadEventID IS NULL OR @ThreadEventID = 0 BEGIN INSERT dbo.ThreadEvents (PlotThreadID, SceneID, EventTypeID, PlotEventTypeID, TargetPlotLineID, EventTitle, EventDescription, Source, SourceReferenceID, SourceKey, Confidence) VALUES (@PlotThreadID, @SceneID, @EventTypeID, @PlotEventTypeID, @TargetPlotLineID, @EventTitle, @EventDescription, @Source, @SourceReferenceID, @SourceKey, @Confidence); SET @ThreadEventID = CAST(SCOPE_IDENTITY() AS int); END ELSE BEGIN UPDATE dbo.ThreadEvents SET PlotThreadID = @PlotThreadID, SceneID = @SceneID, EventTypeID = @EventTypeID, PlotEventTypeID = @PlotEventTypeID, TargetPlotLineID = @TargetPlotLineID, EventTitle = @EventTitle, EventDescription = @EventDescription, Source = @Source, SourceReferenceID = @SourceReferenceID, SourceKey = @SourceKey, Confidence = @Confidence, UpdatedDate = SYSUTCDATETIME() WHERE ThreadEventID = @ThreadEventID AND (Source <> N'Author' OR @Source = N'Author'); END; DECLARE @EventTypeName nvarchar(100); SELECT @EventTypeName = TypeName FROM dbo.ThreadEventTypes WHERE ThreadEventTypeID = @EventTypeID; IF @EventTypeName = N'Introduced' BEGIN UPDATE pt SET IntroducedSceneID = COALESCE(IntroducedSceneID, @SceneID), ThreadStatusID = CASE WHEN ts.StatusName = N'Planned' THEN introduced.ThreadStatusID ELSE pt.ThreadStatusID END, UpdatedDate = SYSUTCDATETIME() FROM dbo.PlotThreads pt INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID CROSS APPLY (SELECT ThreadStatusID FROM dbo.ThreadStatuses WHERE StatusName = N'Introduced') introduced WHERE pt.PlotThreadID = @PlotThreadID; END; IF @EventTypeName IN (N'Resolved', N'Payoff') BEGIN UPDATE pt SET ActualResolutionSceneID = COALESCE(ActualResolutionSceneID, @SceneID), ThreadStatusID = CASE WHEN @EventTypeName = N'Resolved' THEN resolved.ThreadStatusID ELSE pt.ThreadStatusID END, UpdatedDate = SYSUTCDATETIME() FROM dbo.PlotThreads pt CROSS APPLY (SELECT ThreadStatusID FROM dbo.ThreadStatuses WHERE StatusName = N'Resolved') resolved WHERE pt.PlotThreadID = @PlotThreadID; END; SELECT @ThreadEventID; END; GO