PlotDirector/PlotLine/Sql/188_Phase27O_CharacterEnrichmentStructuredFacts.sql

473 lines
19 KiB
Transact-SQL

CREATE OR ALTER PROCEDURE dbo.CharacterEnrichmentRun_ClaimNext
@LeaseMinutes int = 90
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @RunID int;
BEGIN TRANSACTION;
SELECT TOP (1) @RunID = CharacterEnrichmentRunID
FROM dbo.CharacterEnrichmentRuns WITH (UPDLOCK, HOLDLOCK, READPAST)
WHERE IsCurrent = 1
AND (
Status = N'Queued'
OR (Status = N'Running' AND StartedUtc < DATEADD(minute, -ABS(@LeaseMinutes), SYSUTCDATETIME()))
)
ORDER BY QueuedUtc, CharacterEnrichmentRunID;
IF @RunID IS NULL
BEGIN
COMMIT TRANSACTION;
SELECT TOP (0) * FROM dbo.CharacterEnrichmentRuns;
RETURN;
END;
UPDATE dbo.CharacterEnrichmentRuns
SET Status = N'Running',
StartedUtc = SYSUTCDATETIME(),
AttemptCount = AttemptCount + 1,
CurrentStage = N'Preparing character analysis',
CurrentMessage = N'Preparing character analysis...',
UpdatedUtc = SYSUTCDATETIME()
WHERE CharacterEnrichmentRunID = @RunID
AND IsCurrent = 1
AND (
Status = N'Queued'
OR (Status = N'Running' AND StartedUtc < DATEADD(minute, -ABS(@LeaseMinutes), SYSUTCDATETIME()))
);
IF @@ROWCOUNT = 0
BEGIN
COMMIT TRANSACTION;
SELECT TOP (0) * FROM dbo.CharacterEnrichmentRuns;
RETURN;
END;
SELECT * FROM dbo.CharacterEnrichmentRuns WHERE CharacterEnrichmentRunID = @RunID;
COMMIT TRANSACTION;
END;
GO
CREATE OR ALTER PROCEDURE dbo.CharacterEnrichmentRun_Complete
@CharacterEnrichmentRunID int,
@Model nvarchar(100),
@InputTokens int = NULL,
@OutputTokens int = NULL,
@TotalTokens int = NULL,
@DurationMs bigint = NULL,
@ResultsJson nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @ProjectID int, @BookID int, @PipelineID int;
DECLARE @Results TABLE
(
CharacterID int NOT NULL PRIMARY KEY,
Summary nvarchar(max) NULL,
Confidence decimal(5,4) NOT NULL,
DateOfBirthValue nvarchar(120) NULL,
DateOfBirthConfidence decimal(5,4) NULL,
DateOfBirthEvidence nvarchar(700) NULL,
DateOfBirthSceneID int NULL,
AgeValue nvarchar(120) NULL,
AgeConfidence decimal(5,4) NULL,
AgeEvidence nvarchar(700) NULL,
AgeSceneID int NULL,
SexValue nvarchar(120) NULL,
SexConfidence decimal(5,4) NULL,
SexEvidence nvarchar(700) NULL,
SexSceneID int NULL,
HeightValue nvarchar(120) NULL,
HeightConfidence decimal(5,4) NULL,
HeightEvidence nvarchar(700) NULL,
HeightSceneID int NULL,
EyeColourValue nvarchar(120) NULL,
EyeColourConfidence decimal(5,4) NULL,
EyeColourEvidence nvarchar(700) NULL,
EyeColourSceneID int NULL
);
INSERT @Results
SELECT
CharacterID,
NULLIF(LTRIM(RTRIM(Summary)), N''),
Confidence,
NULLIF(LTRIM(RTRIM(DateOfBirthValue)), N''),
DateOfBirthConfidence,
NULLIF(LTRIM(RTRIM(DateOfBirthEvidence)), N''),
DateOfBirthSceneID,
NULLIF(LTRIM(RTRIM(AgeValue)), N''),
AgeConfidence,
NULLIF(LTRIM(RTRIM(AgeEvidence)), N''),
AgeSceneID,
CASE
WHEN LOWER(NULLIF(LTRIM(RTRIM(SexValue)), N'')) = N'female' THEN N'Female'
WHEN LOWER(NULLIF(LTRIM(RTRIM(SexValue)), N'')) = N'male' THEN N'Male'
WHEN LOWER(NULLIF(LTRIM(RTRIM(SexValue)), N'')) = N'other' THEN N'Other'
ELSE NULL
END,
SexConfidence,
NULLIF(LTRIM(RTRIM(SexEvidence)), N''),
SexSceneID,
NULLIF(LTRIM(RTRIM(HeightValue)), N''),
HeightConfidence,
NULLIF(LTRIM(RTRIM(HeightEvidence)), N''),
HeightSceneID,
NULLIF(LTRIM(RTRIM(EyeColourValue)), N''),
EyeColourConfidence,
NULLIF(LTRIM(RTRIM(EyeColourEvidence)), N''),
EyeColourSceneID
FROM OPENJSON(@ResultsJson)
WITH
(
CharacterID int '$.characterID',
Summary nvarchar(max) '$.summary',
Confidence decimal(5,4) '$.confidence',
DateOfBirthValue nvarchar(120) '$.dateOfBirth.value',
DateOfBirthConfidence decimal(5,4) '$.dateOfBirth.confidence',
DateOfBirthEvidence nvarchar(700) '$.dateOfBirth.evidence',
DateOfBirthSceneID int '$.dateOfBirth.sceneID',
AgeValue nvarchar(120) '$.ageAtStartOfSeries.value',
AgeConfidence decimal(5,4) '$.ageAtStartOfSeries.confidence',
AgeEvidence nvarchar(700) '$.ageAtStartOfSeries.evidence',
AgeSceneID int '$.ageAtStartOfSeries.sceneID',
SexValue nvarchar(120) '$.sex.value',
SexConfidence decimal(5,4) '$.sex.confidence',
SexEvidence nvarchar(700) '$.sex.evidence',
SexSceneID int '$.sex.sceneID',
HeightValue nvarchar(120) '$.height.value',
HeightConfidence decimal(5,4) '$.height.confidence',
HeightEvidence nvarchar(700) '$.height.evidence',
HeightSceneID int '$.height.sceneID',
EyeColourValue nvarchar(120) '$.eyeColour.value',
EyeColourConfidence decimal(5,4) '$.eyeColour.confidence',
EyeColourEvidence nvarchar(700) '$.eyeColour.evidence',
EyeColourSceneID int '$.eyeColour.sceneID'
)
WHERE CharacterID IS NOT NULL;
DECLARE @SexValues TABLE (SexName nvarchar(120) NOT NULL PRIMARY KEY, CharacterSexValueID int NOT NULL);
INSERT @SexValues (SexName, CharacterSexValueID)
SELECT SexName, CharacterSexValueID
FROM dbo.CharacterSexValues
WHERE IsSystem = 1
AND IsActive = 1
AND SexName IN (N'Male', N'Female', N'Other');
BEGIN TRANSACTION;
SELECT @ProjectID = ProjectID, @BookID = BookID, @PipelineID = StoryIntelligenceBookPipelineID
FROM dbo.CharacterEnrichmentRuns WITH (UPDLOCK, HOLDLOCK)
WHERE CharacterEnrichmentRunID = @CharacterEnrichmentRunID;
UPDATE c
SET DefaultDescription = CASE WHEN NULLIF(LTRIM(RTRIM(c.DefaultDescription)), N'') IS NULL THEN r.Summary ELSE c.DefaultDescription END,
BirthDate = CASE WHEN c.BirthDate IS NULL THEN TRY_CONVERT(date, r.DateOfBirthValue) ELSE c.BirthDate END,
AgeAtSeriesStart = CASE WHEN c.AgeAtSeriesStart IS NULL THEN TRY_CONVERT(int, r.AgeValue) ELSE c.AgeAtSeriesStart END,
SexValueID = CASE
WHEN c.SexValueID IS NULL
AND (NULLIF(LTRIM(RTRIM(c.Sex)), N'') IS NULL OR c.Sex = N'Unknown')
AND sexValue.CharacterSexValueID IS NOT NULL
THEN sexValue.CharacterSexValueID
ELSE c.SexValueID
END,
Sex = CASE
WHEN c.SexValueID IS NULL
AND (NULLIF(LTRIM(RTRIM(c.Sex)), N'') IS NULL OR c.Sex = N'Unknown')
AND sexValue.CharacterSexValueID IS NOT NULL
THEN r.SexValue
ELSE c.Sex
END,
Height = CASE WHEN NULLIF(LTRIM(RTRIM(c.Height)), N'') IS NULL THEN r.HeightValue ELSE c.Height END,
EyeColour = CASE WHEN NULLIF(LTRIM(RTRIM(c.EyeColour)), N'') IS NULL THEN r.EyeColourValue ELSE c.EyeColour END,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.Characters c
INNER JOIN @Results r ON r.CharacterID = c.CharacterID
LEFT JOIN @SexValues sexValue ON sexValue.SexName = r.SexValue
WHERE c.ProjectID = @ProjectID;
MERGE dbo.CharacterIntelligenceProfiles WITH (HOLDLOCK) AS target
USING
(
SELECT
ImportSessionID = @PipelineID,
ProjectID = @ProjectID,
BookID = @BookID,
CanonicalIdentityKey = CONCAT(N'character-enrichment-', r.CharacterID),
CanonicalName = c.CharacterName,
ProfileJson = (SELECT r.* FOR JSON PATH, WITHOUT_ARRAY_WRAPPER),
Confidence = r.Confidence,
NarrativeImportance = CAST(NULL AS nvarchar(60))
FROM @Results r
INNER JOIN dbo.Characters c ON c.CharacterID = r.CharacterID
WHERE @PipelineID IS NOT NULL
) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.CanonicalIdentityKey = source.CanonicalIdentityKey
WHEN MATCHED THEN
UPDATE SET CanonicalName = source.CanonicalName,
ProfileJson = source.ProfileJson,
Confidence = source.Confidence,
EvidenceVersion = N'CharacterEnrichmentV1',
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, ProjectID, BookID, CanonicalIdentityKey, CanonicalName, ProfileJson, Confidence, NarrativeImportance, IsRecurring, EvidenceVersion, LastUpdatedSceneResultID)
VALUES (source.ImportSessionID, source.ProjectID, source.BookID, source.CanonicalIdentityKey, source.CanonicalName, source.ProfileJson, source.Confidence, source.NarrativeImportance, 1, N'CharacterEnrichmentV1', NULL);
DELETE evidence
FROM dbo.CharacterEnrichmentEvidence evidence
INNER JOIN @Results r ON r.CharacterID = evidence.CharacterID
WHERE evidence.CharacterEnrichmentRunID = @CharacterEnrichmentRunID;
INSERT dbo.CharacterEnrichmentEvidence (CharacterEnrichmentRunID, CharacterID, SceneID, FieldName, Value, Evidence, Confidence)
SELECT @CharacterEnrichmentRunID, CharacterID, NULL, N'Summary', Summary, NULL, Confidence
FROM @Results
WHERE Summary IS NOT NULL
UNION ALL
SELECT @CharacterEnrichmentRunID, CharacterID, DateOfBirthSceneID, N'DateOfBirth', DateOfBirthValue, DateOfBirthEvidence, COALESCE(DateOfBirthConfidence, 0)
FROM @Results WHERE DateOfBirthValue IS NOT NULL AND DateOfBirthSceneID IS NOT NULL
UNION ALL
SELECT @CharacterEnrichmentRunID, CharacterID, AgeSceneID, N'AgeAtStartOfSeries', AgeValue, AgeEvidence, COALESCE(AgeConfidence, 0)
FROM @Results WHERE AgeValue IS NOT NULL AND AgeSceneID IS NOT NULL
UNION ALL
SELECT @CharacterEnrichmentRunID, CharacterID, SexSceneID, N'Sex', SexValue, SexEvidence, COALESCE(SexConfidence, 0)
FROM @Results WHERE SexValue IS NOT NULL AND SexSceneID IS NOT NULL
UNION ALL
SELECT @CharacterEnrichmentRunID, CharacterID, HeightSceneID, N'Height', HeightValue, HeightEvidence, COALESCE(HeightConfidence, 0)
FROM @Results WHERE HeightValue IS NOT NULL AND HeightSceneID IS NOT NULL
UNION ALL
SELECT @CharacterEnrichmentRunID, CharacterID, EyeColourSceneID, N'EyeColour', EyeColourValue, EyeColourEvidence, COALESCE(EyeColourConfidence, 0)
FROM @Results WHERE EyeColourValue IS NOT NULL AND EyeColourSceneID IS NOT NULL;
UPDATE dbo.CharacterEnrichmentRuns
SET Status = N'Completed',
ProcessedCharacters = TotalCharacters,
CurrentStage = N'Complete',
CurrentMessage = N'Character summaries are ready.',
CompletedUtc = SYSUTCDATETIME(),
Model = @Model,
InputTokens = @InputTokens,
OutputTokens = @OutputTokens,
TotalTokens = @TotalTokens,
DurationMs = @DurationMs,
ErrorMessage = NULL,
ErrorDetail = NULL,
UpdatedUtc = SYSUTCDATETIME()
WHERE CharacterEnrichmentRunID = @CharacterEnrichmentRunID;
COMMIT TRANSACTION;
END;
GO
CREATE OR ALTER PROCEDURE dbo.CharacterEnrichmentCharacterStatus_Get
@CharacterID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ProjectID int;
SELECT @ProjectID = c.ProjectID
FROM dbo.Characters c
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = c.ProjectID
WHERE c.CharacterID = @CharacterID
AND c.IsArchived = 0
AND pua.UserID = @UserID
AND pua.AccessRole = N'Owner'
AND pua.IsActive = 1;
IF @ProjectID IS NULL
BEGIN
SELECT TOP (0)
CharacterID = CAST(NULL AS int),
BookID = CAST(NULL AS int),
CharacterEnrichmentRunID = CAST(NULL AS int),
Status = CAST(NULL AS nvarchar(30)),
CurrentStage = CAST(NULL AS nvarchar(120)),
CurrentMessage = CAST(NULL AS nvarchar(300)),
LastAnalysedUtc = CAST(NULL AS datetime2),
ErrorMessage = CAST(NULL AS nvarchar(max)),
Summary = CAST(NULL AS nvarchar(max)),
DateOfBirth = CAST(NULL AS nvarchar(500)),
AgeAtStartOfSeries = CAST(NULL AS nvarchar(500)),
Sex = CAST(NULL AS nvarchar(500)),
Height = CAST(NULL AS nvarchar(500)),
EyeColour = CAST(NULL AS nvarchar(500)),
UpdatedUtc = CAST(NULL AS datetime2);
RETURN;
END;
DECLARE @Attempt TABLE
(
CharacterEnrichmentRunID int NULL,
BookID int NULL,
Status nvarchar(30) NULL,
CurrentStage nvarchar(120) NULL,
CurrentMessage nvarchar(300) NULL,
ErrorMessage nvarchar(max) NULL,
UpdatedUtc datetime2 NULL
);
INSERT @Attempt
SELECT TOP (1)
runRows.CharacterEnrichmentRunID,
runRows.BookID,
runRows.Status,
runRows.CurrentStage,
runRows.CurrentMessage,
runRows.ErrorMessage,
runRows.UpdatedUtc
FROM dbo.CharacterEnrichmentRuns runRows
WHERE runRows.UserID = @UserID
AND runRows.ProjectID = @ProjectID
AND runRows.CharacterID = @CharacterID
AND runRows.IsCurrent = 1
ORDER BY CASE runRows.Status WHEN N'Queued' THEN 0 WHEN N'Running' THEN 0 WHEN N'Failed' THEN 1 WHEN N'Completed' THEN 2 ELSE 9 END,
runRows.UpdatedUtc DESC,
runRows.CharacterEnrichmentRunID DESC;
DECLARE @SuccessRunID int, @SuccessBookID int, @LastAnalysedUtc datetime2;
SELECT TOP (1)
@SuccessRunID = runRows.CharacterEnrichmentRunID,
@SuccessBookID = runRows.BookID,
@LastAnalysedUtc = runRows.CompletedUtc
FROM dbo.CharacterEnrichmentRuns runRows
WHERE runRows.UserID = @UserID
AND runRows.ProjectID = @ProjectID
AND runRows.Status = N'Completed'
AND runRows.CompletedUtc IS NOT NULL
AND (
runRows.CharacterID = @CharacterID
OR EXISTS
(
SELECT 1
FROM dbo.CharacterEnrichmentEvidence evidence
WHERE evidence.CharacterEnrichmentRunID = runRows.CharacterEnrichmentRunID
AND evidence.CharacterID = @CharacterID
)
)
ORDER BY runRows.CompletedUtc DESC, runRows.CharacterEnrichmentRunID DESC;
SELECT
CharacterID = @CharacterID,
BookID = COALESCE((SELECT TOP (1) BookID FROM @Attempt), @SuccessBookID),
CharacterEnrichmentRunID = COALESCE((SELECT TOP (1) CharacterEnrichmentRunID FROM @Attempt), @SuccessRunID),
Status = COALESCE((SELECT TOP (1) Status FROM @Attempt), CASE WHEN @SuccessRunID IS NULL THEN N'Not analysed' ELSE N'Completed' END),
CurrentStage = (SELECT TOP (1) CurrentStage FROM @Attempt),
CurrentMessage = (SELECT TOP (1) CurrentMessage FROM @Attempt),
LastAnalysedUtc = @LastAnalysedUtc,
ErrorMessage = (SELECT TOP (1) ErrorMessage FROM @Attempt),
Summary = MAX(CASE WHEN evidence.FieldName = N'Summary' THEN evidence.Value END),
DateOfBirth = MAX(CASE WHEN evidence.FieldName = N'DateOfBirth' THEN evidence.Value END),
AgeAtStartOfSeries = MAX(CASE WHEN evidence.FieldName = N'AgeAtStartOfSeries' THEN evidence.Value END),
Sex = MAX(CASE WHEN evidence.FieldName = N'Sex' THEN evidence.Value END),
Height = MAX(CASE WHEN evidence.FieldName = N'Height' THEN evidence.Value END),
EyeColour = MAX(CASE WHEN evidence.FieldName = N'EyeColour' THEN evidence.Value END),
UpdatedUtc = COALESCE((SELECT TOP (1) UpdatedUtc FROM @Attempt), @LastAnalysedUtc, SYSUTCDATETIME())
FROM (SELECT 1 AS Anchor) anchor
LEFT JOIN dbo.CharacterEnrichmentEvidence evidence
ON evidence.CharacterEnrichmentRunID = @SuccessRunID
AND evidence.CharacterID = @CharacterID;
END;
GO
;WITH LatestEvidence AS
(
SELECT
evidence.CharacterID,
evidence.FieldName,
evidence.Value,
runRows.ProjectID,
rn = ROW_NUMBER() OVER
(
PARTITION BY evidence.CharacterID, evidence.FieldName
ORDER BY runRows.CompletedUtc DESC, evidence.CharacterEnrichmentEvidenceID DESC
)
FROM dbo.CharacterEnrichmentEvidence evidence
INNER JOIN dbo.CharacterEnrichmentRuns runRows ON runRows.CharacterEnrichmentRunID = evidence.CharacterEnrichmentRunID
WHERE runRows.Status = N'Completed'
AND evidence.FieldName IN (N'DateOfBirth', N'AgeAtStartOfSeries', N'Height', N'EyeColour', N'Sex')
),
Pivoted AS
(
SELECT
CharacterID,
ProjectID = MAX(ProjectID),
DateOfBirthValue = MAX(CASE WHEN FieldName = N'DateOfBirth' THEN Value END),
AgeValue = MAX(CASE WHEN FieldName = N'AgeAtStartOfSeries' THEN Value END),
HeightValue = MAX(CASE WHEN FieldName = N'Height' THEN Value END),
EyeColourValue = MAX(CASE WHEN FieldName = N'EyeColour' THEN Value END),
SexValue = MAX(CASE WHEN FieldName = N'Sex' THEN Value END)
FROM LatestEvidence
WHERE rn = 1
GROUP BY CharacterID
),
Normalised AS
(
SELECT
p.CharacterID,
p.ProjectID,
DateOfBirthValue = TRY_CONVERT(date, p.DateOfBirthValue),
AgeValue = TRY_CONVERT(int, CASE
WHEN p.AgeValue IS NULL THEN NULL
WHEN PATINDEX(N'%[^0-9]%', p.AgeValue + N'x') = 1 THEN NULL
ELSE LEFT(p.AgeValue, PATINDEX(N'%[^0-9]%', p.AgeValue + N'x') - 1)
END),
p.HeightValue,
p.EyeColourValue,
SexValue = CASE
WHEN LOWER(NULLIF(LTRIM(RTRIM(p.SexValue)), N'')) = N'female' THEN N'Female'
WHEN LOWER(NULLIF(LTRIM(RTRIM(p.SexValue)), N'')) = N'male' THEN N'Male'
WHEN LOWER(NULLIF(LTRIM(RTRIM(p.SexValue)), N'')) = N'other' THEN N'Other'
ELSE NULL
END
FROM Pivoted p
),
SexValues AS
(
SELECT SexName, CharacterSexValueID
FROM dbo.CharacterSexValues
WHERE IsSystem = 1
AND IsActive = 1
AND SexName IN (N'Male', N'Female', N'Other')
)
UPDATE c
SET BirthDate = CASE WHEN c.BirthDate IS NULL THEN n.DateOfBirthValue ELSE c.BirthDate END,
AgeAtSeriesStart = CASE WHEN c.AgeAtSeriesStart IS NULL THEN n.AgeValue ELSE c.AgeAtSeriesStart END,
Height = CASE WHEN NULLIF(LTRIM(RTRIM(c.Height)), N'') IS NULL THEN n.HeightValue ELSE c.Height END,
EyeColour = CASE WHEN NULLIF(LTRIM(RTRIM(c.EyeColour)), N'') IS NULL THEN n.EyeColourValue ELSE c.EyeColour END,
SexValueID = CASE
WHEN c.SexValueID IS NULL
AND (NULLIF(LTRIM(RTRIM(c.Sex)), N'') IS NULL OR c.Sex = N'Unknown')
AND sexValue.CharacterSexValueID IS NOT NULL
THEN sexValue.CharacterSexValueID
ELSE c.SexValueID
END,
Sex = CASE
WHEN c.SexValueID IS NULL
AND (NULLIF(LTRIM(RTRIM(c.Sex)), N'') IS NULL OR c.Sex = N'Unknown')
AND sexValue.CharacterSexValueID IS NOT NULL
THEN n.SexValue
ELSE c.Sex
END,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.Characters c
INNER JOIN Normalised n ON n.CharacterID = c.CharacterID
LEFT JOIN SexValues sexValue ON sexValue.SexName = n.SexValue
WHERE c.ProjectID = n.ProjectID
AND (
(c.BirthDate IS NULL AND n.DateOfBirthValue IS NOT NULL)
OR (c.AgeAtSeriesStart IS NULL AND n.AgeValue IS NOT NULL)
OR (NULLIF(LTRIM(RTRIM(c.Height)), N'') IS NULL AND n.HeightValue IS NOT NULL)
OR (NULLIF(LTRIM(RTRIM(c.EyeColour)), N'') IS NULL AND n.EyeColourValue IS NOT NULL)
OR (c.SexValueID IS NULL AND (NULLIF(LTRIM(RTRIM(c.Sex)), N'') IS NULL OR c.Sex = N'Unknown') AND sexValue.CharacterSexValueID IS NOT NULL)
);
GO