PlotDirector/PlotLine/Sql/019_Phase1S_Scenarios.sql
Nick Beckley 480b557f81 Phase 1S
2026-06-01 14:49:23 +01:00

494 lines
23 KiB
Transact-SQL

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID(N'dbo.Scenarios', N'U') IS NULL
BEGIN
CREATE TABLE dbo.Scenarios
(
ScenarioID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_Scenarios PRIMARY KEY,
ProjectID int NOT NULL,
BookID int NULL,
ScenarioName nvarchar(200) NOT NULL,
Description nvarchar(max) NULL,
BaseCreatedDate datetime2 NOT NULL CONSTRAINT DF_Scenarios_BaseCreatedDate DEFAULT SYSUTCDATETIME(),
IsAppliedToMain bit NOT NULL CONSTRAINT DF_Scenarios_IsAppliedToMain DEFAULT 0,
IsArchived bit NOT NULL CONSTRAINT DF_Scenarios_IsArchived DEFAULT 0,
CreatedDate datetime2 NOT NULL CONSTRAINT DF_Scenarios_CreatedDate DEFAULT SYSUTCDATETIME(),
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_Scenarios_UpdatedDate DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_Scenarios_Projects FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
CONSTRAINT FK_Scenarios_Books FOREIGN KEY (BookID) REFERENCES dbo.Books(BookID)
);
END;
GO
IF OBJECT_ID(N'dbo.ScenarioSceneOrder', N'U') IS NULL
BEGIN
CREATE TABLE dbo.ScenarioSceneOrder
(
ScenarioSceneOrderID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ScenarioSceneOrder PRIMARY KEY,
ScenarioID int NOT NULL,
SceneID int NOT NULL,
ProposedChapterID int NULL,
ProposedSortOrder int NOT NULL,
Notes nvarchar(max) NULL,
CreatedDate datetime2 NOT NULL CONSTRAINT DF_ScenarioSceneOrder_CreatedDate DEFAULT SYSUTCDATETIME(),
UpdatedDate datetime2 NOT NULL CONSTRAINT DF_ScenarioSceneOrder_UpdatedDate DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_ScenarioSceneOrder_Scenarios FOREIGN KEY (ScenarioID) REFERENCES dbo.Scenarios(ScenarioID),
CONSTRAINT FK_ScenarioSceneOrder_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID),
CONSTRAINT FK_ScenarioSceneOrder_Chapters FOREIGN KEY (ProposedChapterID) REFERENCES dbo.Chapters(ChapterID),
CONSTRAINT UQ_ScenarioSceneOrder_ScenarioScene UNIQUE (ScenarioID, SceneID)
);
END;
GO
IF OBJECT_ID(N'dbo.ScenarioValidationRuns', N'U') IS NULL
BEGIN
CREATE TABLE dbo.ScenarioValidationRuns
(
ScenarioValidationRunID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ScenarioValidationRuns PRIMARY KEY,
ScenarioID int NOT NULL,
StartedDate datetime2 NOT NULL CONSTRAINT DF_ScenarioValidationRuns_StartedDate DEFAULT SYSUTCDATETIME(),
CompletedDate datetime2 NULL,
WarningCount int NOT NULL CONSTRAINT DF_ScenarioValidationRuns_WarningCount DEFAULT 0,
CONSTRAINT FK_ScenarioValidationRuns_Scenarios FOREIGN KEY (ScenarioID) REFERENCES dbo.Scenarios(ScenarioID)
);
END;
GO
IF OBJECT_ID(N'dbo.ScenarioWarnings', N'U') IS NULL
BEGIN
CREATE TABLE dbo.ScenarioWarnings
(
ScenarioWarningID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ScenarioWarnings PRIMARY KEY,
ScenarioValidationRunID int NOT NULL,
ScenarioID int NOT NULL,
SceneID int NULL,
EntityType nvarchar(80) NOT NULL,
EntityID int NULL,
WarningTypeName nvarchar(150) NOT NULL,
SeverityName nvarchar(50) NOT NULL,
Message nvarchar(500) NOT NULL,
Details nvarchar(max) NULL,
CreatedDate datetime2 NOT NULL CONSTRAINT DF_ScenarioWarnings_CreatedDate DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_ScenarioWarnings_Runs FOREIGN KEY (ScenarioValidationRunID) REFERENCES dbo.ScenarioValidationRuns(ScenarioValidationRunID),
CONSTRAINT FK_ScenarioWarnings_Scenarios FOREIGN KEY (ScenarioID) REFERENCES dbo.Scenarios(ScenarioID),
CONSTRAINT FK_ScenarioWarnings_Scenes FOREIGN KEY (SceneID) REFERENCES dbo.Scenes(SceneID)
);
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_List
@ProjectID int
AS
BEGIN
SET NOCOUNT ON;
SELECT sc.ScenarioID, sc.ProjectID, sc.BookID, sc.ScenarioName, sc.Description, sc.BaseCreatedDate,
sc.IsAppliedToMain, sc.IsArchived, sc.CreatedDate, sc.UpdatedDate,
p.ProjectName, b.BookTitle,
COUNT(DISTINCT sso.SceneID) AS SceneCount,
COUNT(DISTINCT sw.ScenarioWarningID) AS WarningCount
FROM dbo.Scenarios sc
INNER JOIN dbo.Projects p ON p.ProjectID = sc.ProjectID
LEFT JOIN dbo.Books b ON b.BookID = sc.BookID
LEFT JOIN dbo.ScenarioSceneOrder sso ON sso.ScenarioID = sc.ScenarioID
LEFT JOIN dbo.ScenarioWarnings sw ON sw.ScenarioID = sc.ScenarioID
WHERE sc.ProjectID = @ProjectID AND sc.IsArchived = 0
GROUP BY sc.ScenarioID, sc.ProjectID, sc.BookID, sc.ScenarioName, sc.Description, sc.BaseCreatedDate,
sc.IsAppliedToMain, sc.IsArchived, sc.CreatedDate, sc.UpdatedDate, p.ProjectName, b.BookTitle
ORDER BY sc.UpdatedDate DESC, sc.ScenarioName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Get
@ScenarioID int
AS
BEGIN
SET NOCOUNT ON;
SELECT sc.ScenarioID, sc.ProjectID, sc.BookID, sc.ScenarioName, sc.Description, sc.BaseCreatedDate,
sc.IsAppliedToMain, sc.IsArchived, sc.CreatedDate, sc.UpdatedDate,
p.ProjectName, b.BookTitle,
(SELECT COUNT(*) FROM dbo.ScenarioSceneOrder WHERE ScenarioID = sc.ScenarioID) AS SceneCount,
(SELECT COUNT(*) FROM dbo.ScenarioWarnings WHERE ScenarioID = sc.ScenarioID) AS WarningCount
FROM dbo.Scenarios sc
INNER JOIN dbo.Projects p ON p.ProjectID = sc.ProjectID
LEFT JOIN dbo.Books b ON b.BookID = sc.BookID
WHERE sc.ScenarioID = @ScenarioID AND sc.IsArchived = 0;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Create
@ProjectID int,
@BookID int = NULL,
@ScenarioName nvarchar(200),
@Description nvarchar(max) = NULL
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.Scenarios (ProjectID, BookID, ScenarioName, Description)
VALUES (@ProjectID, @BookID, @ScenarioName, @Description);
DECLARE @ScenarioID int = CAST(SCOPE_IDENTITY() AS int);
INSERT dbo.ScenarioSceneOrder (ScenarioID, SceneID, ProposedChapterID, ProposedSortOrder)
SELECT @ScenarioID, nso.SceneID, nso.ChapterID,
ROW_NUMBER() OVER (ORDER BY nso.GlobalSceneIndex) * 10
FROM dbo.NarrativeSceneOrder nso
WHERE nso.ProjectID = @ProjectID AND (@BookID IS NULL OR nso.BookID = @BookID)
ORDER BY nso.GlobalSceneIndex;
SELECT @ScenarioID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Timeline
@ScenarioID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ProjectID int = (SELECT ProjectID FROM dbo.Scenarios WHERE ScenarioID = @ScenarioID);
DECLARE @BookID int = (SELECT BookID FROM dbo.Scenarios WHERE ScenarioID = @ScenarioID);
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
FROM dbo.Books
WHERE ProjectID = @ProjectID AND IsArchived = 0 AND (@BookID IS NULL OR BookID = @BookID)
ORDER BY SortOrder, BookNumber;
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary, c.SortOrder,
c.RevisionStatusID, rs.StatusName AS RevisionStatusName, c.CreatedDate, c.UpdatedDate, c.IsArchived
FROM dbo.Chapters c
INNER JOIN dbo.Books b ON b.BookID = c.BookID
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
WHERE b.ProjectID = @ProjectID AND c.IsArchived = 0 AND b.IsArchived = 0 AND (@BookID IS NULL OR b.BookID = @BookID)
ORDER BY b.SortOrder, c.SortOrder, c.ChapterNumber;
SELECT sso.ScenarioSceneOrderID, sso.ScenarioID, sso.SceneID, s.ChapterID AS OriginalChapterID,
ISNULL(sso.ProposedChapterID, s.ChapterID) AS ProposedChapterID, sso.ProposedSortOrder, sso.Notes,
ch.ChapterNumber, ch.ChapterTitle, s.SceneNumber, s.SceneTitle, s.Summary, rs.StatusName AS RevisionStatusName,
COALESCE(NULLIF(s.RelativeTimeText, N''), CONVERT(nvarchar(20), s.StartDateTime, 106), tm.TimeModeName) AS TimeLabel,
threadData.ThreadLabels, assetData.AssetLabels, characterData.CharacterLabels, loc.LocationPath AS LocationLabel,
(SELECT COUNT(*) FROM dbo.ScenarioWarnings sw WHERE sw.ScenarioID = @ScenarioID AND sw.SceneID = s.SceneID) AS WarningCount
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = s.RevisionStatusID
INNER JOIN dbo.TimeModes tm ON tm.TimeModeID = s.TimeModeID
LEFT JOIN dbo.LocationPaths loc ON loc.LocationID = s.PrimaryLocationID
OUTER APPLY
(
SELECT STRING_AGG(CONVERT(nvarchar(max), pt.ThreadTitle), N', ') AS ThreadLabels
FROM dbo.ThreadEvents te INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
WHERE te.SceneID = s.SceneID
) threadData
OUTER APPLY
(
SELECT STRING_AGG(CONVERT(nvarchar(max), sa.AssetName), N', ') AS AssetLabels
FROM dbo.AssetEvents ae INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID
WHERE ae.SceneID = s.SceneID
) assetData
OUTER APPLY
(
SELECT STRING_AGG(CONVERT(nvarchar(max), c.CharacterName), N', ') AS CharacterLabels
FROM dbo.SceneCharacters sc INNER JOIN dbo.Characters c ON c.CharacterID = sc.CharacterID
WHERE sc.SceneID = s.SceneID
) characterData
WHERE sso.ScenarioID = @ScenarioID
ORDER BY ch.SortOrder, ch.ChapterNumber, sso.ProposedSortOrder, s.SceneID;
SELECT ScenarioWarningID, ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details, CreatedDate
FROM dbo.ScenarioWarnings
WHERE ScenarioID = @ScenarioID
ORDER BY CASE SeverityName WHEN N'Critical' THEN 4 WHEN N'Warning' THEN 3 WHEN N'Info' THEN 2 ELSE 1 END DESC, CreatedDate DESC;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_MoveScene
@ScenarioID int,
@SceneID int,
@AnchorSceneID int = NULL,
@TargetChapterID int = NULL,
@Position nvarchar(20)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ChapterID int;
DECLARE @NewSortOrder int;
DECLARE @AnchorSortOrder int;
IF @AnchorSceneID IS NOT NULL
BEGIN
SELECT @ChapterID = ISNULL(ProposedChapterID, s.ChapterID), @AnchorSortOrder = ProposedSortOrder
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
WHERE sso.ScenarioID = @ScenarioID AND sso.SceneID = @AnchorSceneID;
IF @Position = N'Before'
SELECT @NewSortOrder = ISNULL(MAX(ProposedSortOrder), @AnchorSortOrder - 20) + ((@AnchorSortOrder - ISNULL(MAX(ProposedSortOrder), @AnchorSortOrder - 20)) / 2)
FROM dbo.ScenarioSceneOrder
WHERE ScenarioID = @ScenarioID AND ISNULL(ProposedChapterID, 0) = ISNULL(@ChapterID, 0) AND ProposedSortOrder < @AnchorSortOrder;
ELSE
SELECT @NewSortOrder = (@AnchorSortOrder + ISNULL(MIN(ProposedSortOrder), @AnchorSortOrder + 20)) / 2
FROM dbo.ScenarioSceneOrder
WHERE ScenarioID = @ScenarioID AND ISNULL(ProposedChapterID, 0) = ISNULL(@ChapterID, 0) AND ProposedSortOrder > @AnchorSortOrder;
END
ELSE
BEGIN
SET @ChapterID = @TargetChapterID;
SELECT @NewSortOrder = ISNULL(MAX(ProposedSortOrder), 0) + 10
FROM dbo.ScenarioSceneOrder
WHERE ScenarioID = @ScenarioID AND ProposedChapterID = @ChapterID;
END;
UPDATE dbo.ScenarioSceneOrder
SET ProposedChapterID = @ChapterID,
ProposedSortOrder = ISNULL(@NewSortOrder, ProposedSortOrder),
UpdatedDate = SYSUTCDATETIME()
WHERE ScenarioID = @ScenarioID AND SceneID = @SceneID;
;WITH ordered AS
(
SELECT ScenarioSceneOrderID,
ROW_NUMBER() OVER (PARTITION BY ProposedChapterID ORDER BY ProposedSortOrder, ScenarioSceneOrderID) * 10 AS NextSortOrder
FROM dbo.ScenarioSceneOrder
WHERE ScenarioID = @ScenarioID
)
UPDATE sso
SET ProposedSortOrder = ordered.NextSortOrder,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.ScenarioSceneOrder sso
INNER JOIN ordered ON ordered.ScenarioSceneOrderID = sso.ScenarioSceneOrderID;
UPDATE dbo.Scenarios SET UpdatedDate = SYSUTCDATETIME() WHERE ScenarioID = @ScenarioID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Validate
@ScenarioID int
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM dbo.ScenarioWarnings WHERE ScenarioID = @ScenarioID;
INSERT dbo.ScenarioValidationRuns (ScenarioID)
VALUES (@ScenarioID);
DECLARE @RunID int = CAST(SCOPE_IDENTITY() AS int);
;WITH ScenarioOrder AS
(
SELECT sso.SceneID, sso.ScenarioID, ISNULL(sso.ProposedChapterID, s.ChapterID) AS ChapterID,
ch.BookID, ROW_NUMBER() OVER (ORDER BY b.SortOrder, ch.SortOrder, sso.ProposedSortOrder, s.SceneID) AS ScenarioIndex,
s.SceneTitle, s.StartDateTime
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
WHERE sso.ScenarioID = @ScenarioID
)
INSERT dbo.ScenarioWarnings (ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details)
SELECT @RunID, @ScenarioID, targetScene.SceneID, N'SceneDependency', sd.SceneDependencyID, N'Scene Dependency Broken', N'Warning',
N'Scenario warning: a required scene dependency is out of order.',
CONCAT(sourceScene.SceneTitle, N' should occur before ', targetScene.SceneTitle, N'.')
FROM dbo.SceneDependencies sd
INNER JOIN dbo.SceneDependencyTypes sdt ON sdt.SceneDependencyTypeID = sd.SceneDependencyTypeID
INNER JOIN ScenarioOrder sourceScene ON sourceScene.SceneID = sd.SourceSceneID
INNER JOIN ScenarioOrder targetScene ON targetScene.SceneID = sd.TargetSceneID
WHERE sd.IsArchived = 0 AND sdt.TypeName = N'Must Occur Before' AND sourceScene.ScenarioIndex >= targetScene.ScenarioIndex;
;WITH ScenarioOrder AS
(
SELECT sso.SceneID, sso.ScenarioID, ROW_NUMBER() OVER (ORDER BY b.SortOrder, ch.SortOrder, sso.ProposedSortOrder, s.SceneID) AS ScenarioIndex,
s.SceneTitle, s.StartDateTime
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
WHERE sso.ScenarioID = @ScenarioID
)
INSERT dbo.ScenarioWarnings (ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details)
SELECT @RunID, @ScenarioID, later.SceneID, N'Scene', later.SceneID, N'Date / Order Conflict', N'Info',
N'Scenario warning: an exact scene date appears earlier than a previous scenario scene.',
CONCAT(later.SceneTitle, N' is dated before ', earlier.SceneTitle, N'.')
FROM ScenarioOrder earlier
INNER JOIN ScenarioOrder later ON later.ScenarioIndex = earlier.ScenarioIndex + 1
WHERE earlier.StartDateTime IS NOT NULL AND later.StartDateTime IS NOT NULL AND later.StartDateTime < earlier.StartDateTime;
;WITH ScenarioOrder AS
(
SELECT sso.SceneID, ROW_NUMBER() OVER (ORDER BY b.SortOrder, ch.SortOrder, sso.ProposedSortOrder, s.SceneID) AS ScenarioIndex
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
WHERE sso.ScenarioID = @ScenarioID
),
ThreadEventsOrdered AS
(
SELECT te.PlotThreadID, te.ThreadEventID, te.SceneID, pt.ThreadTitle, tet.TypeName,
so.ScenarioIndex,
MIN(CASE WHEN tet.TypeName LIKE N'%Setup%' THEN so.ScenarioIndex END) OVER (PARTITION BY te.PlotThreadID) AS FirstSetupIndex
FROM dbo.ThreadEvents te
INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID
INNER JOIN dbo.ThreadEventTypes tet ON tet.ThreadEventTypeID = te.EventTypeID
INNER JOIN ScenarioOrder so ON so.SceneID = te.SceneID
)
INSERT dbo.ScenarioWarnings (ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details)
SELECT @RunID, @ScenarioID, SceneID, N'PlotThread', PlotThreadID, N'Plot Thread Order', N'Warning',
N'Scenario warning: a reveal or payoff may occur before its setup.',
CONCAT(ThreadTitle, N' has ', TypeName, N' before the first setup beat.')
FROM ThreadEventsOrdered
WHERE (TypeName LIKE N'%Reveal%' OR TypeName LIKE N'%Payoff%')
AND FirstSetupIndex IS NOT NULL AND ScenarioIndex < FirstSetupIndex;
;WITH ScenarioOrder AS
(
SELECT sso.SceneID, ROW_NUMBER() OVER (ORDER BY b.SortOrder, ch.SortOrder, sso.ProposedSortOrder, s.SceneID) AS ScenarioIndex
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
WHERE sso.ScenarioID = @ScenarioID
),
AssetFirstSeen AS
(
SELECT ae.StoryAssetID, MIN(so.ScenarioIndex) AS FirstSeenIndex
FROM dbo.AssetEvents ae
INNER JOIN ScenarioOrder so ON so.SceneID = ae.SceneID
GROUP BY ae.StoryAssetID
)
INSERT dbo.ScenarioWarnings (ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details)
SELECT @RunID, @ScenarioID, NULL, N'AssetDependency', ad.AssetDependencyID, N'Asset Dependency Order', N'Info',
N'Scenario warning: an asset appears before a validating dependency source appears.',
CONCAT(target.AssetName, N' appears before ', source.AssetName, N'.')
FROM dbo.AssetDependencies ad
INNER JOIN dbo.AssetDependencyTypes adt ON adt.AssetDependencyTypeID = ad.AssetDependencyTypeID
INNER JOIN dbo.StoryAssets source ON source.StoryAssetID = ad.SourceAssetID
INNER JOIN dbo.StoryAssets target ON target.StoryAssetID = ad.TargetAssetID
INNER JOIN AssetFirstSeen sourceSeen ON sourceSeen.StoryAssetID = ad.SourceAssetID
INNER JOIN AssetFirstSeen targetSeen ON targetSeen.StoryAssetID = ad.TargetAssetID
WHERE ad.IsArchived = 0 AND adt.IsValidatingDependency = 1 AND targetSeen.FirstSeenIndex < sourceSeen.FirstSeenIndex;
;WITH ScenarioOrder AS
(
SELECT sso.SceneID, ROW_NUMBER() OVER (ORDER BY b.SortOrder, ch.SortOrder, sso.ProposedSortOrder, s.SceneID) AS ScenarioIndex
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
WHERE sso.ScenarioID = @ScenarioID
),
MetricSteps AS
(
SELECT smv.SceneID, smt.MetricName, smv.Value, so.ScenarioIndex,
LAG(smv.Value) OVER (PARTITION BY smt.MetricName ORDER BY so.ScenarioIndex) AS PreviousValue
FROM dbo.SceneMetricValues smv
INNER JOIN dbo.SceneMetricTypes smt ON smt.MetricTypeID = smv.MetricTypeID
INNER JOIN ScenarioOrder so ON so.SceneID = smv.SceneID
WHERE smt.MetricName IN (N'Overall Intensity', N'Tension', N'Emotional Weight')
)
INSERT dbo.ScenarioWarnings (ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details)
SELECT @RunID, @ScenarioID, SceneID, N'Scene', SceneID, N'Pacing Jump', N'Info',
N'Scenario warning: a pacing/emotional metric changes sharply after this move.',
CONCAT(MetricName, N' changes from ', PreviousValue, N' to ', Value, N'.')
FROM MetricSteps
WHERE PreviousValue IS NOT NULL AND ABS(Value - PreviousValue) >= 6;
DECLARE @WarningCount int = (SELECT COUNT(*) FROM dbo.ScenarioWarnings WHERE ScenarioValidationRunID = @RunID);
UPDATE dbo.ScenarioValidationRuns SET CompletedDate = SYSUTCDATETIME(), WarningCount = @WarningCount WHERE ScenarioValidationRunID = @RunID;
SELECT @WarningCount;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Compare
@ScenarioID int
AS
BEGIN
SET NOCOUNT ON;
;WITH MainOrder AS
(
SELECT nso.SceneID, nso.GlobalSceneIndex,
CONCAT(nso.BookTitle, N' / Ch ', nso.ChapterNumber, N' / Scene ', nso.SceneNumber) AS MainPosition
FROM dbo.NarrativeSceneOrder nso
INNER JOIN dbo.ScenarioSceneOrder sso ON sso.SceneID = nso.SceneID AND sso.ScenarioID = @ScenarioID
),
ScenarioOrder AS
(
SELECT sso.SceneID,
ROW_NUMBER() OVER (ORDER BY b.SortOrder, ch.SortOrder, sso.ProposedSortOrder, s.SceneID) AS ScenarioIndex,
CONCAT(b.BookTitle, N' / Ch ', ch.ChapterNumber, N' / Scene ', s.SceneNumber) AS ScenarioPosition,
s.SceneTitle
FROM dbo.ScenarioSceneOrder sso
INNER JOIN dbo.Scenes s ON s.SceneID = sso.SceneID
INNER JOIN dbo.Chapters ch ON ch.ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID)
INNER JOIN dbo.Books b ON b.BookID = ch.BookID
WHERE sso.ScenarioID = @ScenarioID
)
SELECT so.SceneID, so.SceneTitle, mo.MainPosition, so.ScenarioPosition,
mo.GlobalSceneIndex AS MainIndex, so.ScenarioIndex, CAST(CASE WHEN mo.GlobalSceneIndex <> so.ScenarioIndex OR mo.MainPosition <> so.ScenarioPosition THEN 1 ELSE 0 END AS bit) AS HasMoved
FROM ScenarioOrder so
INNER JOIN MainOrder mo ON mo.SceneID = so.SceneID
WHERE mo.GlobalSceneIndex <> so.ScenarioIndex OR mo.MainPosition <> so.ScenarioPosition
ORDER BY so.ScenarioIndex;
SELECT ScenarioWarningID, ScenarioValidationRunID, ScenarioID, SceneID, EntityType, EntityID, WarningTypeName, SeverityName, Message, Details, CreatedDate
FROM dbo.ScenarioWarnings
WHERE ScenarioID = @ScenarioID
ORDER BY CASE SeverityName WHEN N'Critical' THEN 4 WHEN N'Warning' THEN 3 WHEN N'Info' THEN 2 ELSE 1 END DESC, CreatedDate DESC;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_ApplyToMain
@ScenarioID int
AS
BEGIN
SET NOCOUNT ON;
UPDATE s
SET ChapterID = ISNULL(sso.ProposedChapterID, s.ChapterID),
SortOrder = sso.ProposedSortOrder,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.Scenes s
INNER JOIN dbo.ScenarioSceneOrder sso ON sso.SceneID = s.SceneID
WHERE sso.ScenarioID = @ScenarioID;
;WITH affected AS
(
SELECT DISTINCT ChapterID FROM dbo.Scenes s INNER JOIN dbo.ScenarioSceneOrder sso ON sso.SceneID = s.SceneID WHERE sso.ScenarioID = @ScenarioID
),
ordered AS
(
SELECT s.SceneID, ROW_NUMBER() OVER (PARTITION BY s.ChapterID ORDER BY s.SortOrder, s.SceneID) * 10 AS NextSortOrder
FROM dbo.Scenes s
INNER JOIN affected a ON a.ChapterID = s.ChapterID
WHERE s.IsArchived = 0
)
UPDATE s
SET SortOrder = ordered.NextSortOrder,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.Scenes s
INNER JOIN ordered ON ordered.SceneID = s.SceneID;
UPDATE dbo.Scenarios
SET IsAppliedToMain = 1,
UpdatedDate = SYSUTCDATETIME()
WHERE ScenarioID = @ScenarioID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Scenario_Archive
@ScenarioID int
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Scenarios SET IsArchived = 1, UpdatedDate = SYSUTCDATETIME() WHERE ScenarioID = @ScenarioID;
END;
GO