SET ANSI_NULLS ON; GO SET QUOTED_IDENTIFIER ON; GO IF OBJECT_ID(N'dbo.WarningTypes', N'U') IS NULL BEGIN CREATE TABLE dbo.WarningTypes ( WarningTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WarningTypes PRIMARY KEY, TypeName nvarchar(150) NOT NULL, Description nvarchar(max) NULL, SortOrder int NOT NULL, IsActive bit NOT NULL CONSTRAINT DF_WarningTypes_IsActive DEFAULT 1, CONSTRAINT UQ_WarningTypes_TypeName UNIQUE (TypeName) ); END; GO IF OBJECT_ID(N'dbo.WarningSeverities', N'U') IS NULL BEGIN CREATE TABLE dbo.WarningSeverities ( WarningSeverityID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_WarningSeverities PRIMARY KEY, SeverityName nvarchar(50) NOT NULL, SortOrder int NOT NULL, IsActive bit NOT NULL CONSTRAINT DF_WarningSeverities_IsActive DEFAULT 1, CONSTRAINT UQ_WarningSeverities_SeverityName UNIQUE (SeverityName) ); END; GO IF OBJECT_ID(N'dbo.ValidationRuns', N'U') IS NULL BEGIN CREATE TABLE dbo.ValidationRuns ( ValidationRunID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ValidationRuns PRIMARY KEY, ProjectID int NOT NULL, BookID int NULL, StartedDate datetime2 NOT NULL CONSTRAINT DF_ValidationRuns_StartedDate DEFAULT SYSUTCDATETIME(), CompletedDate datetime2 NULL, TriggeredBy nvarchar(100) NULL, ScopeDescription nvarchar(200) NULL, CONSTRAINT FK_ValidationRuns_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID), CONSTRAINT FK_ValidationRuns_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID) ); END; GO IF OBJECT_ID(N'dbo.ContinuityWarnings', N'U') IS NULL BEGIN CREATE TABLE dbo.ContinuityWarnings ( ContinuityWarningID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ContinuityWarnings PRIMARY KEY, ValidationRunID int NULL, ProjectID int NOT NULL, BookID int NULL, ChapterID int NULL, SceneID int NULL, EntityType nvarchar(80) NOT NULL, EntityID int NULL, WarningTypeID int NOT NULL, WarningSeverityID int NOT NULL, Message nvarchar(500) NOT NULL, Details nvarchar(max) NULL, IsDismissed bit NOT NULL CONSTRAINT DF_ContinuityWarnings_IsDismissed DEFAULT 0, IsIntentional bit NOT NULL CONSTRAINT DF_ContinuityWarnings_IsIntentional DEFAULT 0, CreatedDate datetime2 NOT NULL CONSTRAINT DF_ContinuityWarnings_CreatedDate DEFAULT SYSUTCDATETIME(), LastDetectedDate datetime2 NOT NULL CONSTRAINT DF_ContinuityWarnings_LastDetectedDate DEFAULT SYSUTCDATETIME(), CONSTRAINT FK_ContinuityWarnings_ValidationRuns FOREIGN KEY (ValidationRunID) REFERENCES dbo.ValidationRuns(ValidationRunID), CONSTRAINT FK_ContinuityWarnings_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID), CONSTRAINT FK_ContinuityWarnings_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID), CONSTRAINT FK_ContinuityWarnings_Chapters FOREIGN KEY (ChapterID) REFERENCES dbo.Chapters(ChapterID), CONSTRAINT FK_ContinuityWarnings_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID), CONSTRAINT FK_ContinuityWarnings_Types FOREIGN KEY (WarningTypeID) REFERENCES dbo.WarningTypes(WarningTypeID), CONSTRAINT FK_ContinuityWarnings_Severities FOREIGN KEY (WarningSeverityID) REFERENCES dbo.WarningSeverities(WarningSeverityID) ); END; GO IF OBJECT_ID(N'dbo.SceneDependencyTypes', N'U') IS NULL BEGIN CREATE TABLE dbo.SceneDependencyTypes ( SceneDependencyTypeID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneDependencyTypes PRIMARY KEY, TypeName nvarchar(100) NOT NULL, SortOrder int NOT NULL, IsActive bit NOT NULL CONSTRAINT DF_SceneDependencyTypes_IsActive DEFAULT 1, CONSTRAINT UQ_SceneDependencyTypes_TypeName UNIQUE (TypeName) ); END; GO IF OBJECT_ID(N'dbo.SceneDependencies', N'U') IS NULL BEGIN CREATE TABLE dbo.SceneDependencies ( SceneDependencyID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_SceneDependencies PRIMARY KEY, SourceSceneID int NOT NULL, TargetSceneID int NOT NULL, SceneDependencyTypeID int NOT NULL, Description nvarchar(max) NULL, CreatedDate datetime2 NOT NULL CONSTRAINT DF_SceneDependencies_CreatedDate DEFAULT SYSUTCDATETIME(), UpdatedDate datetime2 NOT NULL CONSTRAINT DF_SceneDependencies_UpdatedDate DEFAULT SYSUTCDATETIME(), IsArchived bit NOT NULL CONSTRAINT DF_SceneDependencies_IsArchived DEFAULT 0, CONSTRAINT FK_SceneDependencies_SourceScene FOREIGN KEY (SourceSceneID) REFERENCES dbo.Scenes(SceneID), CONSTRAINT FK_SceneDependencies_TargetScene FOREIGN KEY (TargetSceneID) REFERENCES dbo.Scenes(SceneID), CONSTRAINT FK_SceneDependencies_Types FOREIGN KEY (SceneDependencyTypeID) REFERENCES dbo.SceneDependencyTypes(SceneDependencyTypeID) ); END; GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ContinuityWarnings_Project' AND object_id = OBJECT_ID(N'dbo.ContinuityWarnings')) CREATE INDEX IX_ContinuityWarnings_Project ON dbo.ContinuityWarnings(ProjectID, BookID, SceneID, IsDismissed, IsIntentional, WarningSeverityID); GO MERGE dbo.WarningSeverities AS target USING (VALUES (N'Info', 10), (N'Warning', 20), (N'Error', 30)) AS source (SeverityName, SortOrder) ON target.SeverityName = source.SeverityName WHEN NOT MATCHED THEN INSERT (SeverityName, SortOrder) VALUES (source.SeverityName, source.SortOrder); GO MERGE dbo.WarningTypes AS target USING (VALUES (N'Thread Resolved Before Introduced', N'Plot thread appears resolved before introduction.', 10), (N'Question Answered Before Raised', N'Question event appears answered before raised.', 20), (N'Clue Without Payoff', N'Clue has no payoff or resolution event.', 30), (N'Thread Dormant', N'Open plot thread has gone quiet for many scenes.', 40), (N'Asset Used Before Introduced', N'Asset appears used before it is introduced.', 50), (N'Asset State Dependency Missing', N'Asset dependency may not have been satisfied.', 60), (N'Asset Custody Conflict', N'Asset custody may not match use.', 70), (N'Asset Location Conflict', N'Asset location changes without a movement event.', 80), (N'Character Knowledge Conflict', N'Character knowledge may need a record.', 90), (N'Scene Dependency Broken', N'Scene dependency order may be broken.', 100), (N'Character Location Conflict', N'Character appears in conflicting scene locations.', 110), (N'Scene Date Order Conflict', N'Narrative order and exact dates may conflict.', 120), (N'Long Unexplained Time Gap', N'Long gap without time passage explanation.', 130), (N'Sustained High Intensity', N'Metric remains high for many scenes.', 140), (N'Sustained Low Intensity', N'Metric remains low for many scenes.', 150), (N'Missing Scene Location', N'Scene has no primary location.', 160), (N'Missing Scene Purpose', N'Scene has no purpose.', 170), (N'Missing Scene Outcome', N'Scene has no outcome.', 180), (N'Too Many Open Major Threads', N'Many major plot threads appear open at the same time.', 190) ) AS source (TypeName, Description, SortOrder) ON target.TypeName = source.TypeName WHEN NOT MATCHED THEN INSERT (TypeName, Description, SortOrder) VALUES (source.TypeName, source.Description, source.SortOrder); GO MERGE dbo.SceneDependencyTypes AS target USING (VALUES (N'Must Occur Before', 10), (N'Emotional Setup', 20), (N'Reveal Setup', 30), (N'Action Consequence', 40), (N'Travel Dependency', 50), (N'Relationship Dependency', 60), (N'Knowledge Dependency', 70), (N'Object Dependency', 80) ) AS source (TypeName, SortOrder) ON target.TypeName = source.TypeName WHEN NOT MATCHED THEN INSERT (TypeName, SortOrder) VALUES (source.TypeName, source.SortOrder); GO CREATE OR ALTER PROCEDURE dbo.WarningLookup_All AS BEGIN SET NOCOUNT ON; SELECT WarningTypeID, TypeName, Description, SortOrder, IsActive FROM dbo.WarningTypes WHERE IsActive = 1 ORDER BY SortOrder; SELECT WarningSeverityID, SeverityName, SortOrder, IsActive FROM dbo.WarningSeverities WHERE IsActive = 1 ORDER BY SortOrder; END; GO CREATE OR ALTER PROCEDURE dbo.ValidationRun_Create @ProjectID int, @BookID int = NULL, @TriggeredBy nvarchar(100) = NULL, @ScopeDescription nvarchar(200) = NULL AS BEGIN SET NOCOUNT ON; INSERT dbo.ValidationRuns (ProjectID, BookID, TriggeredBy, ScopeDescription) VALUES (@ProjectID, @BookID, @TriggeredBy, @ScopeDescription); DECLARE @ValidationRunID int = CAST(SCOPE_IDENTITY() AS int); RETURN @ValidationRunID; END; GO CREATE OR ALTER PROCEDURE dbo.ValidationRun_Complete @ValidationRunID int AS BEGIN SET NOCOUNT ON; UPDATE dbo.ValidationRuns SET CompletedDate = SYSUTCDATETIME() WHERE ValidationRunID = @ValidationRunID; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ClearActive @ProjectID int, @BookID int = NULL, @SceneID int = NULL AS BEGIN SET NOCOUNT ON; DELETE cw FROM dbo.ContinuityWarnings cw WHERE cw.ProjectID = @ProjectID AND (@BookID IS NULL OR cw.BookID = @BookID) AND (@SceneID IS NULL OR cw.SceneID = @SceneID) AND cw.IsDismissed = 0 AND cw.IsIntentional = 0; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Upsert @ValidationRunID int = NULL, @ProjectID int, @BookID int = NULL, @ChapterID int = NULL, @SceneID int = NULL, @EntityType nvarchar(80), @EntityID int = NULL, @WarningTypeName nvarchar(150), @SeverityName nvarchar(50), @Message nvarchar(500), @Details nvarchar(max) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @WarningTypeID int = (SELECT WarningTypeID FROM dbo.WarningTypes WHERE TypeName = @WarningTypeName); DECLARE @WarningSeverityID int = (SELECT WarningSeverityID FROM dbo.WarningSeverities WHERE SeverityName = @SeverityName); IF @WarningTypeID IS NULL OR @WarningSeverityID IS NULL RETURN; DECLARE @ExistingID int; SELECT TOP (1) @ExistingID = ContinuityWarningID FROM dbo.ContinuityWarnings WHERE ProjectID = @ProjectID AND ISNULL(BookID, 0) = ISNULL(@BookID, 0) AND ISNULL(ChapterID, 0) = ISNULL(@ChapterID, 0) AND ISNULL(SceneID, 0) = ISNULL(@SceneID, 0) AND EntityType = @EntityType AND ISNULL(EntityID, 0) = ISNULL(@EntityID, 0) AND WarningTypeID = @WarningTypeID AND IsDismissed = 0 AND IsIntentional = 0; IF @ExistingID IS NULL BEGIN INSERT dbo.ContinuityWarnings (ValidationRunID, ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details) VALUES (@ValidationRunID, @ProjectID, @BookID, @ChapterID, @SceneID, @EntityType, @EntityID, @WarningTypeID, @WarningSeverityID, @Message, @Details); END ELSE BEGIN UPDATE dbo.ContinuityWarnings SET ValidationRunID = @ValidationRunID, WarningSeverityID = @WarningSeverityID, Message = @Message, Details = @Details, LastDetectedDate = SYSUTCDATETIME() WHERE ContinuityWarningID = @ExistingID; END END; GO CREATE OR ALTER VIEW dbo.NarrativeSceneOrder AS SELECT p.ProjectID, b.BookID, c.ChapterID, s.SceneID, b.SortOrder AS BookSortOrder, c.SortOrder AS ChapterSortOrder, s.SortOrder AS SceneSortOrder, ROW_NUMBER() OVER (PARTITION BY p.ProjectID ORDER BY b.SortOrder, b.BookNumber, c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID) AS GlobalSceneIndex, ROW_NUMBER() OVER (PARTITION BY b.BookID ORDER BY c.SortOrder, c.ChapterNumber, s.SortOrder, s.SceneNumber, s.SceneID) AS BookSceneIndex, b.BookTitle, b.BookNumber, c.ChapterNumber, c.ChapterTitle, s.SceneNumber, s.SceneTitle, s.PrimaryLocationID, s.StartDateTime, s.RelativeTimeText, s.ScenePurposeNotes, s.SceneOutcomeNotes FROM dbo.Projects p INNER JOIN dbo.Books b ON b.ProjectID = p.ProjectID AND b.IsArchived = 0 INNER JOIN dbo.Chapters c ON c.BookID = b.BookID AND c.IsArchived = 0 INNER JOIN dbo.Scenes s ON s.ChapterID = c.ChapterID AND s.IsArchived = 0 WHERE p.IsArchived = 0; GO CREATE OR ALTER PROCEDURE dbo.NarrativeSceneOrder_GetByProject @ProjectID int, @BookID int = NULL AS BEGIN SET NOCOUNT ON; SELECT * FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) ORDER BY GlobalSceneIndex; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateProject @ProjectID int, @BookID int = NULL, @SceneID int = NULL, @TriggeredBy nvarchar(100) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @ValidationRunID int; DECLARE @Scope nvarchar(200) = CASE WHEN @SceneID IS NOT NULL THEN N'Scene validation' WHEN @BookID IS NOT NULL THEN N'Book validation' ELSE N'Project validation' END; EXEC @ValidationRunID = dbo.ValidationRun_Create @ProjectID = @ProjectID, @BookID = @BookID, @TriggeredBy = @TriggeredBy, @ScopeDescription = @Scope; EXEC dbo.ContinuityWarning_ClearActive @ProjectID = @ProjectID, @BookID = @BookID, @SceneID = @SceneID; DECLARE @Warnings TABLE ( BookID int NULL, ChapterID int NULL, SceneID int NULL, EntityType nvarchar(80), EntityID int NULL, WarningTypeName nvarchar(150), SeverityName nvarchar(50), Message nvarchar(500), Details nvarchar(max) NULL ); ;WITH ScopeScenes AS ( SELECT * FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Missing Scene Location', N'Info', N'Possible issue: this scene has no primary location yet.', N'Add a primary location if the scene depends on where characters or assets are.' FROM ScopeScenes WHERE PrimaryLocationID IS NULL; ;WITH ScopeScenes AS ( SELECT nso.* FROM dbo.NarrativeSceneOrder nso WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR nso.SceneID = @SceneID) ) INSERT @Warnings SELECT ss.BookID, ss.ChapterID, ss.SceneID, N'Scene', ss.SceneID, N'Missing Scene Purpose', N'Info', N'Possible issue: this scene has no purpose label or purpose notes.', N'Add a scene purpose if this is not intentional.' FROM ScopeScenes ss WHERE ISNULL(NULLIF(LTRIM(RTRIM(ss.ScenePurposeNotes)), N''), N'') = N'' AND NOT EXISTS (SELECT 1 FROM dbo.ScenePurposes sp WHERE sp.SceneID = ss.SceneID); ;WITH ScopeScenes AS ( SELECT * FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) AND (@SceneID IS NULL OR SceneID = @SceneID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Missing Scene Outcome', N'Info', N'Possible issue: this scene has no outcome notes.', N'Add an outcome if the result of the scene needs tracking.' FROM ScopeScenes WHERE ISNULL(NULLIF(LTRIM(RTRIM(SceneOutcomeNotes)), N''), N'') = N''; ;WITH Ordered AS ( SELECT *, LAG(SceneID) OVER (ORDER BY GlobalSceneIndex) AS PreviousSceneID, LAG(StartDateTime) OVER (ORDER BY GlobalSceneIndex) AS PreviousStartDateTime FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Scene Date Order Conflict', N'Warning', N'Possible issue: this narrative scene has an earlier story date than the scene before it.', CONCAT(N'Previous date: ', CONVERT(nvarchar(30), PreviousStartDateTime, 120), N'. Current date: ', CONVERT(nvarchar(30), StartDateTime, 120), N'.') FROM Ordered WHERE (@SceneID IS NULL OR SceneID = @SceneID) AND PreviousStartDateTime IS NOT NULL AND StartDateTime IS NOT NULL AND StartDateTime < PreviousStartDateTime; ;WITH Ordered AS ( SELECT *, LAG(StartDateTime) OVER (ORDER BY GlobalSceneIndex) AS PreviousStartDateTime FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'Scene', SceneID, N'Long Unexplained Time Gap', N'Info', N'Possible issue: there is a long story-time gap before this scene without a clear time-passage note.', CONCAT(N'Gap: ', DATEDIFF(day, PreviousStartDateTime, StartDateTime), N' days.') FROM Ordered o WHERE (@SceneID IS NULL OR SceneID = @SceneID) AND PreviousStartDateTime IS NOT NULL AND StartDateTime IS NOT NULL AND DATEDIFF(day, PreviousStartDateTime, StartDateTime) > 14 AND ISNULL(NULLIF(LTRIM(RTRIM(RelativeTimeText)), N''), N'') = N'' AND NOT EXISTS ( SELECT 1 FROM dbo.ScenePurposes sp INNER JOIN dbo.ScenePurposeTypes spt ON spt.ScenePurposeTypeID = sp.ScenePurposeTypeID WHERE sp.SceneID = o.SceneID AND spt.PurposeName = N'Time Passage' ); ;WITH Events AS ( SELECT pt.PlotThreadID, pt.ThreadTitle, pt.Importance, ts.IsOpenStatus, te.SceneID, tet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0 INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0 INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID WHERE (@BookID IS NULL OR nso.BookID = @BookID) ), ThreadRanges AS ( SELECT PlotThreadID, ThreadTitle, Importance, IsOpenStatus, MIN(CASE WHEN TypeName = N'Introduced' THEN GlobalSceneIndex END) AS IntroducedIndex, MIN(CASE WHEN TypeName IN (N'Resolved', N'Payoff') THEN GlobalSceneIndex END) AS ResolvedIndex, MIN(CASE WHEN TypeName = N'Question Raised' THEN GlobalSceneIndex END) AS QuestionRaisedIndex, MIN(CASE WHEN TypeName = N'Question Answered' THEN GlobalSceneIndex END) AS QuestionAnsweredIndex, MAX(GlobalSceneIndex) AS LastEventIndex, MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID, MIN(SceneID) AS AnySceneID FROM Events GROUP BY PlotThreadID, ThreadTitle, Importance, IsOpenStatus ) INSERT @Warnings SELECT BookID, ChapterID, AnySceneID, N'PlotThread', PlotThreadID, N'Thread Resolved Before Introduced', N'Warning', CONCAT(N'Possible issue: "', ThreadTitle, N'" appears resolved before it is introduced.'), NULL FROM ThreadRanges WHERE ResolvedIndex IS NOT NULL AND IntroducedIndex IS NOT NULL AND ResolvedIndex < IntroducedIndex; ;WITH Events AS ( SELECT pt.PlotThreadID, pt.ThreadTitle, te.SceneID, tet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0 INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0 INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID WHERE (@BookID IS NULL OR nso.BookID = @BookID) ), ThreadRanges AS ( SELECT PlotThreadID, ThreadTitle, MIN(CASE WHEN TypeName = N'Question Raised' THEN GlobalSceneIndex END) AS QuestionRaisedIndex, MIN(CASE WHEN TypeName = N'Question Answered' THEN GlobalSceneIndex END) AS QuestionAnsweredIndex, MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID, MIN(SceneID) AS AnySceneID FROM Events GROUP BY PlotThreadID, ThreadTitle ) INSERT @Warnings SELECT BookID, ChapterID, AnySceneID, N'PlotThread', PlotThreadID, N'Question Answered Before Raised', N'Warning', CONCAT(N'Possible issue: "', ThreadTitle, N'" is answered before it is raised.'), NULL FROM ThreadRanges WHERE QuestionAnsweredIndex IS NOT NULL AND QuestionRaisedIndex IS NOT NULL AND QuestionAnsweredIndex < QuestionRaisedIndex; ;WITH Clues AS ( SELECT pt.PlotThreadID, pt.ThreadTitle, MIN(nso.SceneID) AS SceneID, MIN(nso.BookID) AS BookID, MIN(nso.ChapterID) AS ChapterID FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID AND pt.IsArchived = 0 INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0 INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID WHERE tet.TypeName = N'Clue Planted' AND (@BookID IS NULL OR nso.BookID = @BookID) GROUP BY pt.PlotThreadID, pt.ThreadTitle ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'PlotThread', PlotThreadID, N'Clue Without Payoff', N'Info', CONCAT(N'Possible issue: clue thread "', ThreadTitle, N'" has no payoff, answer, reveal, or resolution yet.'), NULL FROM Clues c WHERE NOT EXISTS ( SELECT 1 FROM dbo.ThreadEvents te INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID WHERE te.PlotThreadID = c.PlotThreadID AND tet.TypeName IN (N'Payoff', N'Question Answered', N'Revealed', N'Resolved') ); ;WITH LastThreadEvent AS ( SELECT pt.PlotThreadID, pt.ThreadTitle, MAX(nso.GlobalSceneIndex) AS LastEventIndex, MAX(nso.BookID) AS BookID, MAX(nso.ChapterID) AS ChapterID, MAX(nso.SceneID) AS SceneID FROM dbo.PlotThreads pt INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID AND ts.IsOpenStatus = 1 INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0 INNER JOIN dbo.ThreadEvents te ON te.PlotThreadID = pt.PlotThreadID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = te.SceneID WHERE pt.IsArchived = 0 AND (@BookID IS NULL OR nso.BookID = @BookID) GROUP BY pt.PlotThreadID, pt.ThreadTitle ), MaxScope AS ( SELECT MAX(GlobalSceneIndex) AS MaxSceneIndex FROM dbo.NarrativeSceneOrder WHERE ProjectID = @ProjectID AND (@BookID IS NULL OR BookID = @BookID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'PlotThread', PlotThreadID, N'Thread Dormant', N'Info', CONCAT(N'Possible issue: "', ThreadTitle, N'" has no event for more than 8 scenes.'), CONCAT(N'Scenes since last event: ', MaxSceneIndex - LastEventIndex, N'.') FROM LastThreadEvent CROSS JOIN MaxScope WHERE MaxSceneIndex - LastEventIndex > 8; ;WITH OpenMajor AS ( SELECT COUNT(*) AS OpenCount FROM dbo.PlotThreads pt INNER JOIN dbo.ThreadStatuses ts ON ts.ThreadStatusID = pt.ThreadStatusID AND ts.IsOpenStatus = 1 INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID AND pl.ProjectID = @ProjectID AND pl.IsArchived = 0 WHERE pt.IsArchived = 0 AND pt.Importance >= 8 AND (@BookID IS NULL OR pl.BookID IS NULL OR pl.BookID = @BookID) ) INSERT @Warnings SELECT @BookID, NULL, NULL, N'Project', @ProjectID, N'Too Many Open Major Threads', N'Info', CONCAT(N'Possible issue: there are ', OpenCount, N' high-importance open threads.'), N'This may be intentional, but it can make the active story load feel heavy.' FROM OpenMajor WHERE OpenCount > 10; ;WITH AssetEventsOrdered AS ( SELECT ae.StoryAssetID, sa.AssetName, ae.SceneID, aet.TypeName, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex FROM dbo.AssetEvents ae INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0 INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID WHERE (@BookID IS NULL OR nso.BookID = @BookID) ), AssetFirsts AS ( SELECT StoryAssetID, AssetName, MIN(CASE WHEN TypeName IN (N'First Mentioned', N'Mentioned', N'Found') THEN GlobalSceneIndex END) AS IntroducedIndex, MIN(CASE WHEN TypeName IN (N'Used', N'Opened', N'Revealed', N'Destroyed', N'Given', N'Taken', N'Hidden', N'Lost', N'Stolen') THEN GlobalSceneIndex END) AS UsedIndex, MIN(CASE WHEN TypeName IN (N'Used', N'Opened', N'Revealed', N'Destroyed', N'Given', N'Taken', N'Hidden', N'Lost', N'Stolen') THEN SceneID END) AS UsedSceneID, MIN(BookID) AS BookID, MIN(ChapterID) AS ChapterID FROM AssetEventsOrdered GROUP BY StoryAssetID, AssetName ) INSERT @Warnings SELECT BookID, ChapterID, UsedSceneID, N'StoryAsset', StoryAssetID, N'Asset Used Before Introduced', N'Warning', CONCAT(N'Possible issue: "', AssetName, N'" appears used before it is introduced.'), NULL FROM AssetFirsts WHERE UsedIndex IS NOT NULL AND (IntroducedIndex IS NULL OR UsedIndex < IntroducedIndex); ;WITH TargetEvents AS ( SELECT ad.AssetDependencyID, ad.SourceAssetID, source.AssetName AS SourceAssetName, ad.TargetAssetID, target.AssetName AS TargetAssetName, ad.SourceRequiredStateID, ad.TargetBlockedStateID, ae.SceneID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex FROM dbo.AssetDependencies ad INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID AND adt.IsValidatingDependency = 1 INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID AND target.ProjectID = @ProjectID INNER JOIN dbo.AssetEvents ae ON ae.StoryAssetID = ad.TargetAssetID AND ae.ToStateID = ad.TargetBlockedStateID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = ae.SceneID WHERE ad.IsArchived = 0 AND ad.SourceRequiredStateID IS NOT NULL AND ad.TargetBlockedStateID IS NOT NULL AND (@BookID IS NULL OR nso.BookID = @BookID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'AssetDependency', AssetDependencyID, N'Asset State Dependency Missing', N'Warning', CONCAT(N'Possible issue: "', TargetAssetName, N'" reaches a blocked state before "', SourceAssetName, N'" reaches its required state.'), NULL FROM TargetEvents te WHERE NOT EXISTS ( SELECT 1 FROM dbo.AssetEvents sourceEvent INNER JOIN dbo.NarrativeSceneOrder sourceScene ON sourceScene.SceneID = sourceEvent.SceneID WHERE sourceEvent.StoryAssetID = te.SourceAssetID AND sourceEvent.ToStateID = te.SourceRequiredStateID AND sourceScene.GlobalSceneIndex <= te.GlobalSceneIndex ); ;WITH AssetPlaces AS ( SELECT sal.StoryAssetID, sa.AssetName, sal.LocationID, sal.SceneID, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex, LAG(sal.LocationID) OVER (PARTITION BY sal.StoryAssetID ORDER BY nso.GlobalSceneIndex) AS PreviousLocationID FROM dbo.SceneAssetLocations sal INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = sal.StoryAssetID AND sa.ProjectID = @ProjectID AND sa.IsArchived = 0 INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sal.SceneID WHERE (@BookID IS NULL OR nso.BookID = @BookID) ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'StoryAsset', StoryAssetID, N'Asset Location Conflict', N'Warning', CONCAT(N'Possible issue: "', AssetName, N'" appears in a new location without a movement-style event.'), N'Add a Move, Given, Taken, Found, or Location Changed event if the location change needs explaining.' FROM AssetPlaces ap WHERE PreviousLocationID IS NOT NULL AND PreviousLocationID <> LocationID AND NOT EXISTS ( SELECT 1 FROM dbo.AssetEvents ae INNER JOIN dbo.AssetEventTypes aet ON aet.AssetEventTypeID = ae.AssetEventTypeID WHERE ae.StoryAssetID = ap.StoryAssetID AND ae.SceneID = ap.SceneID AND aet.TypeName IN (N'Moved', N'Given', N'Taken', N'Found', N'Location Changed') ); INSERT @Warnings SELECT DISTINCT nso.BookID, nso.ChapterID, sc.SceneID, N'Character', sc.CharacterID, N'Character Knowledge Conflict', N'Info', CONCAT(N'Possible issue: ', ch.CharacterName, N' has knowledge notes in this scene but no structured knowledge record.'), N'This is only a low-severity prompt to add Character Knowledge if useful.' FROM dbo.SceneCharacters sc INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID WHERE ch.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID) AND ISNULL(NULLIF(LTRIM(RTRIM(sc.KnowledgeNotes)), N''), N'') <> N'' AND NOT EXISTS (SELECT 1 FROM dbo.CharacterKnowledge ck WHERE ck.SceneID = sc.SceneID AND ck.CharacterID = sc.CharacterID); INSERT @Warnings SELECT targetOrder.BookID, targetOrder.ChapterID, sd.TargetSceneID, N'SceneDependency', sd.SceneDependencyID, N'Scene Dependency Broken', N'Warning', N'Possible issue: a scene dependency says an earlier setup should occur before this scene, but the order is reversed.', sd.Description FROM dbo.SceneDependencies sd INNER JOIN dbo.NarrativeSceneOrder sourceOrder ON sourceOrder.SceneID = sd.SourceSceneID INNER JOIN dbo.NarrativeSceneOrder targetOrder ON targetOrder.SceneID = sd.TargetSceneID WHERE sd.IsArchived = 0 AND sourceOrder.ProjectID = @ProjectID AND (@BookID IS NULL OR targetOrder.BookID = @BookID) AND (@SceneID IS NULL OR targetOrder.SceneID = @SceneID) AND sourceOrder.GlobalSceneIndex > targetOrder.GlobalSceneIndex; ;WITH CharacterSceneLocations AS ( SELECT sc.SceneID, sc.CharacterID, COUNT(DISTINCT sc.LocationID) AS LocationCount, MAX(ch.CharacterName) AS CharacterName, MAX(nso.BookID) AS BookID, MAX(nso.ChapterID) AS ChapterID FROM dbo.SceneCharacters sc INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID AND ch.ProjectID = @ProjectID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = sc.SceneID LEFT JOIN dbo.PresenceTypes pt ON pt.PresenceTypeID = sc.PresenceTypeID WHERE sc.LocationID IS NOT NULL AND (@BookID IS NULL OR nso.BookID = @BookID) AND (@SceneID IS NULL OR sc.SceneID = @SceneID) AND ISNULL(pt.TypeName, N'') NOT IN (N'Nearby', N'Hidden', N'Overhearing', N'Off-screen') GROUP BY sc.SceneID, sc.CharacterID ) INSERT @Warnings SELECT BookID, ChapterID, SceneID, N'Character', CharacterID, N'Character Location Conflict', N'Warning', CONCAT(N'Possible issue: ', CharacterName, N' appears in more than one location in the same scene.'), NULL FROM CharacterSceneLocations WHERE LocationCount > 1; ;WITH MetricRows AS ( SELECT smv.MetricTypeID, smt.MetricName, smv.SceneID, smv.Value, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex, ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID ORDER BY nso.GlobalSceneIndex) AS rn, ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID, CASE WHEN smv.Value >= 8 THEN 1 ELSE 0 END ORDER BY nso.GlobalSceneIndex) AS grpRn FROM dbo.SceneMetricValues smv INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = smv.SceneID WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) ), HighGroups AS ( SELECT MetricTypeID, MetricName, rn - grpRn AS grp, COUNT(*) AS SceneCount, MIN(SceneID) AS FirstSceneID, MAX(SceneID) AS LastSceneID, MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID FROM MetricRows WHERE Value >= 8 GROUP BY MetricTypeID, MetricName, rn - grpRn HAVING COUNT(*) > 6 ) INSERT @Warnings SELECT BookID, ChapterID, LastSceneID, N'Metric', MetricTypeID, N'Sustained High Intensity', N'Info', CONCAT(N'Possible reader fatigue: ', MetricName, N' remains high for ', SceneCount, N' consecutive scenes.'), CONCAT(N'Scene range: ', FirstSceneID, N' to ', LastSceneID, N'.') FROM HighGroups; ;WITH MetricRows AS ( SELECT smv.MetricTypeID, smt.MetricName, smv.SceneID, smv.Value, nso.BookID, nso.ChapterID, nso.GlobalSceneIndex, ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID ORDER BY nso.GlobalSceneIndex) AS rn, ROW_NUMBER() OVER (PARTITION BY smv.MetricTypeID, CASE WHEN smv.Value <= 2 THEN 1 ELSE 0 END ORDER BY nso.GlobalSceneIndex) AS grpRn FROM dbo.SceneMetricValues smv INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID INNER JOIN dbo.NarrativeSceneOrder nso ON nso.SceneID = smv.SceneID WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID) AND smt.MetricName IN (N'Overall Intensity', N'Tension', N'Emotional Weight', N'Action') ), LowGroups AS ( SELECT MetricTypeID, MetricName, rn - grpRn AS grp, COUNT(*) AS SceneCount, MIN(SceneID) AS FirstSceneID, MAX(SceneID) AS LastSceneID, MAX(BookID) AS BookID, MAX(ChapterID) AS ChapterID FROM MetricRows WHERE Value <= 2 GROUP BY MetricTypeID, MetricName, rn - grpRn HAVING COUNT(*) > 8 ) INSERT @Warnings SELECT BookID, ChapterID, LastSceneID, N'Metric', MetricTypeID, N'Sustained Low Intensity', N'Info', CONCAT(N'Possible issue: ', MetricName, N' remains low for ', SceneCount, N' consecutive scenes.'), CONCAT(N'Scene range: ', FirstSceneID, N' to ', LastSceneID, N'.') FROM LowGroups; INSERT dbo.ContinuityWarnings (ValidationRunID, ProjectID, BookID, ChapterID, SceneID, EntityType, EntityID, WarningTypeID, WarningSeverityID, Message, Details) SELECT @ValidationRunID, @ProjectID, w.BookID, w.ChapterID, w.SceneID, w.EntityType, w.EntityID, wt.WarningTypeID, ws.WarningSeverityID, w.Message, w.Details FROM @Warnings w INNER JOIN dbo.WarningTypes wt ON wt.TypeName = w.WarningTypeName INNER JOIN dbo.WarningSeverities ws ON ws.SeverityName = w.SeverityName WHERE NOT EXISTS ( SELECT 1 FROM dbo.ContinuityWarnings existing WHERE existing.ProjectID = @ProjectID AND ISNULL(existing.BookID, 0) = ISNULL(w.BookID, 0) AND ISNULL(existing.ChapterID, 0) = ISNULL(w.ChapterID, 0) AND ISNULL(existing.SceneID, 0) = ISNULL(w.SceneID, 0) AND existing.EntityType = w.EntityType AND ISNULL(existing.EntityID, 0) = ISNULL(w.EntityID, 0) AND existing.WarningTypeID = wt.WarningTypeID AND (existing.IsDismissed = 1 OR existing.IsIntentional = 1) ); EXEC dbo.ValidationRun_Complete @ValidationRunID; SELECT @ValidationRunID AS ValidationRunID, SUM(CASE WHEN ws.SeverityName = N'Error' THEN 1 ELSE 0 END) AS ErrorCount, SUM(CASE WHEN ws.SeverityName = N'Warning' THEN 1 ELSE 0 END) AS WarningCount, SUM(CASE WHEN ws.SeverityName = N'Info' THEN 1 ELSE 0 END) AS InfoCount, COUNT(*) AS TotalCount FROM dbo.ContinuityWarnings cw INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID WHERE cw.ValidationRunID = @ValidationRunID; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateBook @BookID int, @TriggeredBy nvarchar(100) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @ProjectID int = (SELECT ProjectID FROM dbo.Books WHERE BookID = @BookID); EXEC dbo.ContinuityValidation_ValidateProject @ProjectID = @ProjectID, @BookID = @BookID, @TriggeredBy = @TriggeredBy; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityValidation_ValidateScene @SceneID int, @TriggeredBy nvarchar(100) = NULL AS BEGIN SET NOCOUNT ON; DECLARE @ProjectID int, @BookID int; SELECT @ProjectID = b.ProjectID, @BookID = b.BookID FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE s.SceneID = @SceneID; EXEC dbo.ContinuityValidation_ValidateProject @ProjectID = @ProjectID, @BookID = @BookID, @SceneID = @SceneID, @TriggeredBy = @TriggeredBy; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_List @ProjectID int, @BookID int = NULL, @SceneID int = NULL, @EntityType nvarchar(80) = NULL, @EntityID int = NULL, @WarningTypeID int = NULL, @WarningSeverityID int = NULL, @IncludeDismissed bit = 0, @IncludeIntentional bit = 0 AS BEGIN SET NOCOUNT ON; SELECT cw.ContinuityWarningID, cw.ValidationRunID, cw.ProjectID, cw.BookID, b.BookTitle, cw.ChapterID, c.ChapterNumber, c.ChapterTitle, cw.SceneID, s.SceneNumber, s.SceneTitle, cw.EntityType, cw.EntityID, cw.WarningTypeID, wt.TypeName AS WarningTypeName, cw.WarningSeverityID, ws.SeverityName, cw.Message, cw.Details, cw.IsDismissed, cw.IsIntentional, cw.CreatedDate, cw.LastDetectedDate FROM dbo.ContinuityWarnings cw INNER JOIN dbo.WarningTypes wt ON wt.WarningTypeID = cw.WarningTypeID INNER JOIN dbo.WarningSeverities ws ON ws.WarningSeverityID = cw.WarningSeverityID LEFT JOIN dbo.Books b ON b.BookID = cw.BookID LEFT JOIN dbo.Chapters c ON c.ChapterID = cw.ChapterID LEFT JOIN dbo.Scenes s ON s.SceneID = cw.SceneID WHERE cw.ProjectID = @ProjectID AND (@BookID IS NULL OR cw.BookID = @BookID) AND (@SceneID IS NULL OR cw.SceneID = @SceneID) AND (@EntityType IS NULL OR cw.EntityType = @EntityType) AND (@EntityID IS NULL OR cw.EntityID = @EntityID) AND (@WarningTypeID IS NULL OR cw.WarningTypeID = @WarningTypeID) AND (@WarningSeverityID IS NULL OR cw.WarningSeverityID = @WarningSeverityID) AND (@IncludeDismissed = 1 OR cw.IsDismissed = 0) AND (@IncludeIntentional = 1 OR cw.IsIntentional = 0) ORDER BY ws.SortOrder DESC, cw.LastDetectedDate DESC, cw.ContinuityWarningID DESC; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByProject @ProjectID int, @IncludeDismissed bit = 0, @IncludeIntentional bit = 0 AS BEGIN SET NOCOUNT ON; EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByBook @ProjectID int, @BookID int, @IncludeDismissed bit = 0, @IncludeIntentional bit = 0 AS BEGIN SET NOCOUNT ON; EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @BookID = @BookID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByScene @SceneID int, @IncludeDismissed bit = 0, @IncludeIntentional bit = 0 AS BEGIN SET NOCOUNT ON; DECLARE @ProjectID int = (SELECT b.ProjectID FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE s.SceneID = @SceneID); EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @SceneID = @SceneID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_ListByEntity @ProjectID int, @EntityType nvarchar(80), @EntityID int, @IncludeDismissed bit = 0, @IncludeIntentional bit = 0 AS BEGIN SET NOCOUNT ON; EXEC dbo.ContinuityWarning_List @ProjectID = @ProjectID, @EntityType = @EntityType, @EntityID = @EntityID, @IncludeDismissed = @IncludeDismissed, @IncludeIntentional = @IncludeIntentional; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_SceneCounts @ProjectID int, @BookID int = NULL AS BEGIN SET NOCOUNT ON; SELECT cw.SceneID, COUNT(*) AS WarningCount FROM dbo.ContinuityWarnings cw WHERE cw.ProjectID = @ProjectID AND cw.SceneID IS NOT NULL AND cw.IsDismissed = 0 AND cw.IsIntentional = 0 AND (@BookID IS NULL OR cw.BookID = @BookID) GROUP BY cw.SceneID; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_SetState @ContinuityWarningID int, @IsDismissed bit = NULL, @IsIntentional bit = NULL AS BEGIN SET NOCOUNT ON; UPDATE dbo.ContinuityWarnings SET IsDismissed = COALESCE(@IsDismissed, IsDismissed), IsIntentional = COALESCE(@IsIntentional, IsIntentional) WHERE ContinuityWarningID = @ContinuityWarningID; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Dismiss @ContinuityWarningID int AS BEGIN SET NOCOUNT ON; EXEC dbo.ContinuityWarning_SetState @ContinuityWarningID = @ContinuityWarningID, @IsDismissed = 1; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_MarkIntentional @ContinuityWarningID int AS BEGIN SET NOCOUNT ON; EXEC dbo.ContinuityWarning_SetState @ContinuityWarningID = @ContinuityWarningID, @IsIntentional = 1; END; GO CREATE OR ALTER PROCEDURE dbo.ContinuityWarning_Restore @ContinuityWarningID int AS BEGIN SET NOCOUNT ON; UPDATE dbo.ContinuityWarnings SET IsDismissed = 0, IsIntentional = 0 WHERE ContinuityWarningID = @ContinuityWarningID; END; GO