PlotDirector/PlotLine/Sql/147_Phase22C_SafeIllustrationGenerationPolicy.sql

417 lines
17 KiB
Transact-SQL

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'Quality') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD Quality nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'Size') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD Size nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'OutputFormat') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD OutputFormat nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'ImageKind') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD ImageKind nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'CanonicalName') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD CanonicalName nvarchar(200) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'BaseType') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD BaseType nvarchar(80) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'SpecificityLevel') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD SpecificityLevel nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'IsFallbackClassification') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD IsFallbackClassification bit NOT NULL CONSTRAINT DF_IllustrationLibraryItems_IsFallbackClassification DEFAULT 0;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'ReuseScope') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD ReuseScope nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'SourceProjectID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD SourceProjectID int NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'SourceBookID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD SourceBookID int NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'SourceChapterID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD SourceChapterID int NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'SourceSceneID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD SourceSceneID int NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'LinkedEntityType') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD LinkedEntityType nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'LinkedEntityID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD LinkedEntityID int NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'GenerationReason') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD GenerationReason nvarchar(200) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'GenerationStatus') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD GenerationStatus nvarchar(40) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'IsArchived') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD IsArchived bit NOT NULL CONSTRAINT DF_IllustrationLibraryItems_IsArchived DEFAULT 0;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'ArchivedAt') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD ArchivedAt datetime2(0) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'ArchiveReason') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD ArchiveReason nvarchar(500) NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'SupersededByImageID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD SupersededByImageID int NULL;
GO
IF COL_LENGTH(N'dbo.IllustrationLibraryItems', N'ReuseOfImageID') IS NULL
ALTER TABLE dbo.IllustrationLibraryItems ADD ReuseOfImageID int NULL;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_IllustrationLibraryItems_ArchiveReuse' AND object_id = OBJECT_ID(N'dbo.IllustrationLibraryItems'))
CREATE INDEX IX_IllustrationLibraryItems_ArchiveReuse ON dbo.IllustrationLibraryItems(IsArchived, IsActive, Category, Status, BaseType, ReuseScope);
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_IllustrationLibraryItems_SourceContext' AND object_id = OBJECT_ID(N'dbo.IllustrationLibraryItems'))
CREATE INDEX IX_IllustrationLibraryItems_SourceContext ON dbo.IllustrationLibraryItems(SourceProjectID, SourceBookID, SourceChapterID, SourceSceneID, LinkedEntityType, LinkedEntityID);
GO
UPDATE dbo.IllustrationLibraryItems
SET ImageKind = COALESCE(ImageKind, Category),
CanonicalName = COALESCE(CanonicalName, JSON_VALUE(CASE WHEN ISJSON(MetadataJson) = 1 THEN MetadataJson ELSE N'{}' END, '$.canonicalName'), DisplayTitle),
BaseType = COALESCE(BaseType, JSON_VALUE(CASE WHEN ISJSON(MetadataJson) = 1 THEN MetadataJson ELSE N'{}' END, '$.baseType'), JSON_VALUE(CASE WHEN ISJSON(MetadataJson) = 1 THEN MetadataJson ELSE N'{}' END, '$.locationType'), JSON_VALUE(CASE WHEN ISJSON(SpecificationJson) = 1 THEN SpecificationJson ELSE N'{}' END, '$.locationType'), JSON_VALUE(CASE WHEN ISJSON(SpecificationJson) = 1 THEN SpecificationJson ELSE N'{}' END, '$.objectType')),
SpecificityLevel = COALESCE(SpecificityLevel, JSON_VALUE(CASE WHEN ISJSON(MetadataJson) = 1 THEN MetadataJson ELSE N'{}' END, '$.specificityLevel'), CASE WHEN StableCode LIKE N'demand-%' THEN N'Archetype' ELSE N'Generic' END),
IsFallbackClassification = CASE
WHEN IsFallbackClassification = 1 THEN 1
WHEN Category = N'Location' AND (DisplayTitle LIKE N'%Unknown Interior%' OR StableCode LIKE N'%unknowninterior%' OR JSON_VALUE(CASE WHEN ISJSON(SpecificationJson) = 1 THEN SpecificationJson ELSE N'{}' END, '$.locationType') = N'UnknownInterior') THEN 1
WHEN Category = N'Asset' AND (DisplayTitle LIKE N'%Unknown Object%' OR StableCode LIKE N'%unknownobject%' OR JSON_VALUE(CASE WHEN ISJSON(SpecificationJson) = 1 THEN SpecificationJson ELSE N'{}' END, '$.objectType') = N'UnknownObject') THEN 1
ELSE 0
END,
ReuseScope = COALESCE(ReuseScope, CASE
WHEN StableCode LIKE N'demand-%' THEN N'Book'
WHEN StableCode LIKE N'loc-%' OR StableCode LIKE N'char-%' OR StableCode LIKE N'asset-%' THEN N'Global'
ELSE N'Project'
END),
GenerationStatus = COALESCE(GenerationStatus, Status),
UpdatedUtc = SYSUTCDATETIME()
WHERE IsActive = 1
AND (
ImageKind IS NULL
OR CanonicalName IS NULL
OR SpecificityLevel IS NULL
OR ReuseScope IS NULL
OR GenerationStatus IS NULL
);
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_List
@Category nvarchar(40) = NULL,
@Status nvarchar(40) = NULL,
@Search nvarchar(120) = NULL
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM dbo.IllustrationLibraryItems
WHERE IsArchived = 0
AND (@Category IS NULL OR Category = @Category)
AND (@Status IS NULL OR Status = @Status)
AND (
@Search IS NULL
OR StableCode LIKE N'%' + @Search + N'%'
OR DisplayTitle LIKE N'%' + @Search + N'%'
OR ShortDescription LIKE N'%' + @Search + N'%'
OR CanonicalName LIKE N'%' + @Search + N'%'
OR BaseType LIKE N'%' + @Search + N'%'
)
ORDER BY
CASE Status
WHEN N'Generating' THEN 1
WHEN N'Queued' THEN 2
WHEN N'Generated' THEN 3
WHEN N'Planned' THEN 4
WHEN N'Failed' THEN 5
WHEN N'Rejected' THEN 6
WHEN N'Approved' THEN 7
ELSE 8
END,
UpdatedUtc DESC,
IllustrationLibraryItemID DESC;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_Summary
AS
BEGIN
SET NOCOUNT ON;
SELECT
COUNT(1) AS TotalCount,
ISNULL(SUM(CASE WHEN Status = N'Planned' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS PlannedCount,
ISNULL(SUM(CASE WHEN Status = N'Queued' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS QueuedCount,
ISNULL(SUM(CASE WHEN Status = N'Generating' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS GeneratingCount,
ISNULL(SUM(CASE WHEN Status = N'Generated' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS GeneratedCount,
ISNULL(SUM(CASE WHEN Status = N'Approved' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS ApprovedCount,
ISNULL(SUM(CASE WHEN Status = N'Rejected' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS RejectedCount,
ISNULL(SUM(CASE WHEN Status = N'Failed' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS FailedCount,
ISNULL(SUM(CASE WHEN Status = N'Superseded' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS SupersededCount,
ISNULL(SUM(CASE WHEN Status = N'Disabled' AND IsArchived = 0 THEN 1 ELSE 0 END), 0) AS DisabledCount,
ISNULL(SUM(CASE WHEN IsArchived = 1 THEN 1 ELSE 0 END), 0) AS ArchivedCount
FROM dbo.IllustrationLibraryItems;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_UpsertPlanned
@Category nvarchar(40),
@StableCode nvarchar(120),
@DisplayTitle nvarchar(200),
@ShortDescription nvarchar(500) = NULL,
@MetadataJson nvarchar(max) = NULL,
@SpecificationJson nvarchar(max) = NULL
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1
FROM dbo.IllustrationLibraryItems
WHERE StableCode = @StableCode
AND ParentItemID IS NULL
AND IsActive = 1
AND IsArchived = 0
AND Status IN (N'Planned', N'Queued', N'Generating', N'Generated', N'Approved')
)
BEGIN
SELECT * FROM dbo.IllustrationLibraryItems
WHERE StableCode = @StableCode
AND ParentItemID IS NULL
AND IsActive = 1
AND IsArchived = 0
AND Status IN (N'Planned', N'Queued', N'Generating', N'Generated', N'Approved');
RETURN;
END;
INSERT dbo.IllustrationLibraryItems
(Category, StableCode, DisplayTitle, ShortDescription, MetadataJson, SpecificationJson, Status, ImageKind, CanonicalName, BaseType, SpecificityLevel, IsFallbackClassification, ReuseScope, SourceProjectID, SourceBookID, SourceChapterID, SourceSceneID, LinkedEntityType, LinkedEntityID, GenerationReason, GenerationStatus)
VALUES
(
@Category,
@StableCode,
@DisplayTitle,
@ShortDescription,
@MetadataJson,
@SpecificationJson,
N'Planned',
COALESCE(JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.imageKind'), @Category),
COALESCE(JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.canonicalName'), @DisplayTitle),
COALESCE(JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.baseType'), JSON_VALUE(CASE WHEN ISJSON(@SpecificationJson) = 1 THEN @SpecificationJson ELSE N'{}' END, CASE WHEN @Category = N'Location' THEN '$.locationType' WHEN @Category = N'Asset' THEN '$.objectType' ELSE '$.presentation' END)),
COALESCE(JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.specificityLevel'), N'Archetype'),
CASE WHEN JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.isFallbackClassification') IN (N'true', N'True', N'1') THEN 1 ELSE 0 END,
COALESCE(JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.reuseScope'), N'Book'),
TRY_CONVERT(int, JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.sourceProjectId')),
TRY_CONVERT(int, JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.sourceBookId')),
TRY_CONVERT(int, JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.sourceChapterId')),
TRY_CONVERT(int, JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.sourceSceneId')),
JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.linkedEntityType'),
TRY_CONVERT(int, JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.linkedEntityId')),
COALESCE(JSON_VALUE(CASE WHEN ISJSON(@MetadataJson) = 1 THEN @MetadataJson ELSE N'{}' END, '$.generationReason'), N'Demand generation'),
N'Planned'
);
SELECT * FROM dbo.IllustrationLibraryItems WHERE IllustrationLibraryItemID = SCOPE_IDENTITY();
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_Queue
@IllustrationLibraryItemID int = NULL,
@Category nvarchar(40) = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.IllustrationLibraryItems
SET Status = N'Queued',
GenerationStatus = N'Queued',
QueuedUtc = SYSUTCDATETIME(),
UpdatedUtc = SYSUTCDATETIME(),
LastError = NULL
WHERE (@IllustrationLibraryItemID IS NULL OR IllustrationLibraryItemID = @IllustrationLibraryItemID)
AND (@Category IS NULL OR Category = @Category)
AND Status IN (N'Planned', N'Failed', N'Rejected')
AND IsActive = 1
AND IsArchived = 0;
SELECT @@ROWCOUNT AS AffectedRows;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_ClaimNext
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
DECLARE @Claimed TABLE (IllustrationLibraryItemID int NOT NULL);
;WITH nextItem AS
(
SELECT TOP (1) *
FROM dbo.IllustrationLibraryItems WITH (UPDLOCK, READPAST, ROWLOCK)
WHERE Status = N'Queued' AND IsActive = 1 AND IsArchived = 0
ORDER BY QueuedUtc, IllustrationLibraryItemID
)
UPDATE nextItem
SET Status = N'Generating',
GenerationStatus = N'Generating',
AttemptCount = AttemptCount + 1,
StartedUtc = SYSUTCDATETIME(),
UpdatedUtc = SYSUTCDATETIME()
OUTPUT inserted.IllustrationLibraryItemID INTO @Claimed;
SELECT item.*
FROM dbo.IllustrationLibraryItems item
INNER JOIN @Claimed claimed ON claimed.IllustrationLibraryItemID = item.IllustrationLibraryItemID;
COMMIT TRANSACTION;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_MarkGenerated
@IllustrationLibraryItemID int,
@FilePath nvarchar(500),
@ThumbnailPath nvarchar(500),
@Width int,
@Height int,
@FileSizeBytes bigint,
@MimeType nvarchar(100),
@Provider nvarchar(80),
@Model nvarchar(120),
@PromptText nvarchar(max),
@PromptTemplateVersion nvarchar(40),
@ProviderRequestID nvarchar(160) = NULL,
@Quality nvarchar(40) = NULL,
@Size nvarchar(40) = NULL,
@OutputFormat nvarchar(40) = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.IllustrationLibraryItems
SET Status = N'Generated',
GenerationStatus = N'Generated',
FilePath = @FilePath,
ThumbnailPath = @ThumbnailPath,
Width = @Width,
Height = @Height,
FileSizeBytes = @FileSizeBytes,
MimeType = @MimeType,
Provider = @Provider,
Model = @Model,
Quality = @Quality,
Size = @Size,
OutputFormat = @OutputFormat,
PromptText = @PromptText,
PromptTemplateVersion = @PromptTemplateVersion,
ProviderRequestID = @ProviderRequestID,
CompletedUtc = SYSUTCDATETIME(),
UpdatedUtc = SYSUTCDATETIME(),
LastError = NULL
WHERE IllustrationLibraryItemID = @IllustrationLibraryItemID
AND Status = N'Generating'
AND IsArchived = 0;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_MarkFailed
@IllustrationLibraryItemID int,
@LastError nvarchar(1000)
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.IllustrationLibraryItems
SET Status = N'Failed',
GenerationStatus = N'Failed',
LastError = @LastError,
CompletedUtc = SYSUTCDATETIME(),
UpdatedUtc = SYSUTCDATETIME()
WHERE IllustrationLibraryItemID = @IllustrationLibraryItemID
AND Status = N'Generating'
AND IsArchived = 0;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_GetApproved
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM dbo.IllustrationLibraryItems
WHERE Status = N'Approved'
AND IsApproved = 1
AND IsActive = 1
AND IsArchived = 0
AND FilePath IS NOT NULL
ORDER BY StableCode, ApprovedUtc DESC;
END;
GO
CREATE OR ALTER PROCEDURE dbo.IllustrationLibraryItem_ArchiveGeneratedSet
@ProjectID int = NULL,
@BookID int = NULL,
@ImportSessionID int = NULL,
@CreatedBeforeUtc datetime2(0) = NULL,
@StableCodePrefix nvarchar(120) = NULL,
@Reason nvarchar(500) = NULL
AS
BEGIN
SET NOCOUNT ON;
UPDATE item
SET IsArchived = 1,
IsActive = 0,
ArchivedAt = COALESCE(ArchivedAt, SYSUTCDATETIME()),
ArchiveReason = COALESCE(@Reason, N'Archived generated illustration set out of active reuse pool.'),
GenerationStatus = COALESCE(GenerationStatus, Status),
UpdatedUtc = SYSUTCDATETIME()
FROM dbo.IllustrationLibraryItems item
WHERE item.IsArchived = 0
AND item.Status IN (N'Generated', N'Approved')
AND item.Provider = N'OpenAI'
AND (@ProjectID IS NULL OR item.SourceProjectID = @ProjectID)
AND (@BookID IS NULL OR item.SourceBookID = @BookID)
AND (@ImportSessionID IS NULL OR TRY_CONVERT(int, JSON_VALUE(CASE WHEN ISJSON(item.MetadataJson) = 1 THEN item.MetadataJson ELSE N'{}' END, '$.sourceImportSessionId')) = @ImportSessionID)
AND (@CreatedBeforeUtc IS NULL OR item.CreatedUtc < @CreatedBeforeUtc)
AND (@StableCodePrefix IS NULL OR item.StableCode LIKE @StableCodePrefix + N'%');
SELECT @@ROWCOUNT AS ArchivedCount;
END;
GO