PlotDirector/PlotLine/Sql/008_Phase1G_ContinuityWarnings.sql
2026-06-01 09:03:59 +01:00

846 lines
41 KiB
Transact-SQL

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