PlotDirector/PlotLine/Data/StoryMemoryRepository.cs

413 lines
27 KiB
C#

using Dapper;
using PlotLine.Models;
namespace PlotLine.Data;
public interface IStoryMemoryRepository
{
Task ClearDerivedStateAsync(int importSessionId);
Task<IReadOnlySet<int>> ListProcessedSceneResultIdsAsync(int importSessionId, string processorVersion);
Task MarkSceneResultProcessedAsync(int importSessionId, int sceneResultId, string processorVersion, string snapshotHash);
Task<StoryMemoryCharacter> UpsertCharacterAsync(StoryMemoryCharacterSave request);
Task AddCharacterAliasAsync(int characterId, string alias, string normalisedAlias, string reason, int sceneResultId, decimal confidence);
Task AddCharacterAttributeAsync(int characterId, string attributeType, string value, decimal confidence, bool isExplicit, string evidenceSummary, int sceneResultId);
Task<StoryMemoryLocation> UpsertLocationAsync(StoryMemoryLocationSave request);
Task<StoryMemoryAsset> UpsertAssetAsync(StoryMemoryAssetSave request);
Task UpsertRelationshipAsync(int importSessionId, string sourceEntityType, int sourceEntityId, string targetEntityType, int targetEntityId, string relationshipType, decimal confidence, bool isExplicit, int sceneResultId);
Task<IReadOnlyList<StoryMemoryCharacter>> ListCharactersAsync(int importSessionId);
Task<IReadOnlyList<StoryMemoryCharacterAttribute>> ListCharacterAttributesAsync(int importSessionId);
Task<IReadOnlyList<StoryMemoryLocation>> ListLocationsAsync(int importSessionId);
Task<IReadOnlyList<StoryMemoryAsset>> ListAssetsAsync(int importSessionId);
Task<IReadOnlyList<StoryMemoryIllustrationAssignment>> ListAssignmentsAsync(int importSessionId);
Task<StoryMemoryIllustrationAssignment?> GetAssignmentAsync(int importSessionId, string entityType, int entityId);
Task UpsertAssignmentAsync(StoryMemoryIllustrationAssignmentSave request);
Task InvalidateAssignmentAsync(int assignmentId, string reason);
Task<StoryMemoryIllustrationDemand> UpsertDemandAsync(StoryMemoryIllustrationDemandSave request);
Task<string> GetAppearancePreferenceAsync(int importSessionId);
Task SetAppearancePreferenceAsync(int importSessionId, string preference);
Task<StoryMemoryDiagnostics> GetDiagnosticsAsync(int importSessionId);
}
public sealed class StoryMemoryRepository(ISqlConnectionFactory connectionFactory) : IStoryMemoryRepository
{
public async Task ClearDerivedStateAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
DELETE FROM dbo.StoryMemoryIllustrationDemands WHERE ImportSessionID = @ImportSessionID;
DELETE FROM dbo.StoryMemoryIllustrationAssignments WHERE ImportSessionID = @ImportSessionID;
DELETE FROM dbo.StoryMemoryRelationships WHERE ImportSessionID = @ImportSessionID;
DELETE a
FROM dbo.StoryMemoryCharacterAttributes a
INNER JOIN dbo.StoryMemoryCharacters c ON c.StoryMemoryCharacterID = a.StoryMemoryCharacterID
WHERE c.ImportSessionID = @ImportSessionID;
DELETE aliasRows
FROM dbo.StoryMemoryCharacterAliases aliasRows
INNER JOIN dbo.StoryMemoryCharacters c ON c.StoryMemoryCharacterID = aliasRows.StoryMemoryCharacterID
WHERE c.ImportSessionID = @ImportSessionID;
DELETE FROM dbo.StoryMemoryAssets WHERE ImportSessionID = @ImportSessionID;
DELETE FROM dbo.StoryMemoryLocations WHERE ImportSessionID = @ImportSessionID;
DELETE FROM dbo.StoryMemoryCharacters WHERE ImportSessionID = @ImportSessionID;
DELETE FROM dbo.StoryMemoryProcessedSceneResults WHERE ImportSessionID = @ImportSessionID;
""",
new { ImportSessionID = importSessionId });
}
public async Task<IReadOnlySet<int>> ListProcessedSceneResultIdsAsync(int importSessionId, string processorVersion)
{
using var connection = connectionFactory.CreateConnection();
var ids = await connection.QueryAsync<int>(
"""
SELECT SceneResultID
FROM dbo.StoryMemoryProcessedSceneResults
WHERE ImportSessionID = @ImportSessionID
AND ProcessorVersion = @ProcessorVersion;
""",
new { ImportSessionID = importSessionId, ProcessorVersion = processorVersion });
return ids.ToHashSet();
}
public async Task MarkSceneResultProcessedAsync(int importSessionId, int sceneResultId, string processorVersion, string snapshotHash)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
MERGE dbo.StoryMemoryProcessedSceneResults WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @SceneResultID AS SceneResultID, @ProcessorVersion AS ProcessorVersion) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.SceneResultID = source.SceneResultID
AND target.ProcessorVersion = source.ProcessorVersion
WHEN MATCHED THEN
UPDATE SET SnapshotHash = @SnapshotHash, ProcessedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, SceneResultID, ProcessorVersion, SnapshotHash)
VALUES (@ImportSessionID, @SceneResultID, @ProcessorVersion, @SnapshotHash);
""",
new { ImportSessionID = importSessionId, SceneResultID = sceneResultId, ProcessorVersion = processorVersion, SnapshotHash = snapshotHash });
}
public async Task<StoryMemoryCharacter> UpsertCharacterAsync(StoryMemoryCharacterSave request)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<StoryMemoryCharacter>(
"""
MERGE dbo.StoryMemoryCharacters WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @CanonicalIdentityKey AS CanonicalIdentityKey) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.CanonicalIdentityKey = source.CanonicalIdentityKey
WHEN MATCHED THEN
UPDATE SET
DisplayName = CASE WHEN LEN(@DisplayName) > LEN(target.DisplayName) THEN @DisplayName ELSE target.DisplayName END,
EntityContext = @EntityContext,
SourceSectionType = @SourceSectionType,
IsNamed = CASE WHEN @IsNamed = 1 THEN 1 ELSE target.IsNamed END,
IsGroupEntity = CASE WHEN @IsGroupEntity = 1 THEN 1 ELSE target.IsGroupEntity END,
IsResolved = CASE WHEN @IsResolved = 1 THEN 1 ELSE target.IsResolved END,
IsNarrator = CASE WHEN @IsNarrator = 1 THEN 1 ELSE target.IsNarrator END,
IsPrimaryPOV = CASE WHEN @IsPrimaryPOV = 1 THEN 1 ELSE target.IsPrimaryPOV END,
Significance = CASE WHEN @Significance > target.Significance THEN @Significance ELSE target.Significance END,
LastUpdatedSceneResultID = @SceneResultID,
EvidenceVersion = target.EvidenceVersion + 1,
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, ProjectID, BookID, CanonicalIdentityKey, DisplayName, EntityContext, SourceSectionType, IsNamed, IsGroupEntity, IsResolved, IsNarrator, IsPrimaryPOV, Significance, CreatedSceneResultID, LastUpdatedSceneResultID)
VALUES (@ImportSessionID, @ProjectID, @BookID, @CanonicalIdentityKey, @DisplayName, @EntityContext, @SourceSectionType, @IsNamed, @IsGroupEntity, @IsResolved, @IsNarrator, @IsPrimaryPOV, @Significance, @SceneResultID, @SceneResultID);
SELECT *
FROM dbo.StoryMemoryCharacters
WHERE ImportSessionID = @ImportSessionID
AND CanonicalIdentityKey = @CanonicalIdentityKey;
""",
request);
}
public async Task AddCharacterAliasAsync(int characterId, string alias, string normalisedAlias, string reason, int sceneResultId, decimal confidence)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
MERGE dbo.StoryMemoryCharacterAliases WITH (HOLDLOCK) AS target
USING (SELECT @StoryMemoryCharacterID AS StoryMemoryCharacterID, @NormalisedAlias AS NormalisedAlias, @SourceSceneResultID AS SourceSceneResultID) AS source
ON target.StoryMemoryCharacterID = source.StoryMemoryCharacterID
AND target.NormalisedAlias = source.NormalisedAlias
AND ISNULL(target.SourceSceneResultID, -1) = ISNULL(source.SourceSceneResultID, -1)
WHEN NOT MATCHED THEN
INSERT (StoryMemoryCharacterID, Alias, NormalisedAlias, ResolutionReason, SourceSceneResultID, Confidence)
VALUES (@StoryMemoryCharacterID, @Alias, @NormalisedAlias, @ResolutionReason, @SourceSceneResultID, @Confidence);
""",
new { StoryMemoryCharacterID = characterId, Alias = alias, NormalisedAlias = normalisedAlias, ResolutionReason = reason, SourceSceneResultID = sceneResultId, Confidence = confidence });
}
public async Task AddCharacterAttributeAsync(int characterId, string attributeType, string value, decimal confidence, bool isExplicit, string evidenceSummary, int sceneResultId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
MERGE dbo.StoryMemoryCharacterAttributes WITH (HOLDLOCK) AS target
USING (SELECT @StoryMemoryCharacterID AS StoryMemoryCharacterID, @AttributeType AS AttributeType, @NormalisedValue AS NormalisedValue, @SourceSceneResultID AS SourceSceneResultID) AS source
ON target.StoryMemoryCharacterID = source.StoryMemoryCharacterID
AND target.AttributeType = source.AttributeType
AND target.NormalisedValue = source.NormalisedValue
AND ISNULL(target.SourceSceneResultID, -1) = ISNULL(source.SourceSceneResultID, -1)
AND target.SupersededUtc IS NULL
WHEN MATCHED THEN
UPDATE SET Confidence = CASE WHEN @Confidence > target.Confidence THEN @Confidence ELSE target.Confidence END,
IsExplicit = CASE WHEN @IsExplicit = 1 THEN 1 ELSE target.IsExplicit END,
EvidenceSummary = @EvidenceSummary
WHEN NOT MATCHED THEN
INSERT (StoryMemoryCharacterID, AttributeType, NormalisedValue, Confidence, IsExplicit, EvidenceSummary, SourceSceneResultID)
VALUES (@StoryMemoryCharacterID, @AttributeType, @NormalisedValue, @Confidence, @IsExplicit, @EvidenceSummary, @SourceSceneResultID);
""",
new { StoryMemoryCharacterID = characterId, AttributeType = attributeType, NormalisedValue = value, Confidence = confidence, IsExplicit = isExplicit, EvidenceSummary = evidenceSummary, SourceSceneResultID = sceneResultId });
}
public async Task<StoryMemoryLocation> UpsertLocationAsync(StoryMemoryLocationSave request)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<StoryMemoryLocation>(
"""
MERGE dbo.StoryMemoryLocations WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @CanonicalIdentityKey AS CanonicalIdentityKey) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.CanonicalIdentityKey = source.CanonicalIdentityKey
WHEN MATCHED THEN
UPDATE SET DisplayName = @DisplayName,
SemanticType = @SemanticType,
Subtype = @Subtype,
InteriorExterior = @InteriorExterior,
LastSeenSceneResultID = @SceneResultID,
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, ProjectID, BookID, CanonicalIdentityKey, DisplayName, SemanticType, Subtype, InteriorExterior, FirstSeenSceneResultID, LastSeenSceneResultID)
VALUES (@ImportSessionID, @ProjectID, @BookID, @CanonicalIdentityKey, @DisplayName, @SemanticType, @Subtype, @InteriorExterior, @SceneResultID, @SceneResultID);
SELECT *
FROM dbo.StoryMemoryLocations
WHERE ImportSessionID = @ImportSessionID AND CanonicalIdentityKey = @CanonicalIdentityKey;
""",
request);
}
public async Task<StoryMemoryAsset> UpsertAssetAsync(StoryMemoryAssetSave request)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<StoryMemoryAsset>(
"""
MERGE dbo.StoryMemoryAssets WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @CanonicalIdentityKey AS CanonicalIdentityKey) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.CanonicalIdentityKey = source.CanonicalIdentityKey
WHEN MATCHED THEN
UPDATE SET DisplayName = @DisplayName,
SemanticType = @SemanticType,
Subtype = @Subtype,
Colour = @Colour,
Era = @Era,
LastSeenSceneResultID = @SceneResultID,
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, ProjectID, BookID, CanonicalIdentityKey, DisplayName, SemanticType, Subtype, Colour, Era, FirstSeenSceneResultID, LastSeenSceneResultID)
VALUES (@ImportSessionID, @ProjectID, @BookID, @CanonicalIdentityKey, @DisplayName, @SemanticType, @Subtype, @Colour, @Era, @SceneResultID, @SceneResultID);
SELECT *
FROM dbo.StoryMemoryAssets
WHERE ImportSessionID = @ImportSessionID AND CanonicalIdentityKey = @CanonicalIdentityKey;
""",
request);
}
public async Task UpsertRelationshipAsync(int importSessionId, string sourceEntityType, int sourceEntityId, string targetEntityType, int targetEntityId, string relationshipType, decimal confidence, bool isExplicit, int sceneResultId)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
MERGE dbo.StoryMemoryRelationships WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @SourceEntityType AS SourceEntityType, @SourceEntityID AS SourceEntityID, @TargetEntityType AS TargetEntityType, @TargetEntityID AS TargetEntityID, @RelationshipType AS RelationshipType) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.SourceEntityType = source.SourceEntityType
AND target.SourceEntityID = source.SourceEntityID
AND target.TargetEntityType = source.TargetEntityType
AND target.TargetEntityID = source.TargetEntityID
AND target.RelationshipType = source.RelationshipType
AND target.Active = 1
WHEN MATCHED THEN
UPDATE SET Confidence = CASE WHEN @Confidence > target.Confidence THEN @Confidence ELSE target.Confidence END,
IsExplicit = CASE WHEN @IsExplicit = 1 THEN 1 ELSE target.IsExplicit END,
LastUpdatedSceneResultID = @SceneResultID,
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, SourceEntityType, SourceEntityID, TargetEntityType, TargetEntityID, RelationshipType, Confidence, IsExplicit, FirstSeenSceneResultID, LastUpdatedSceneResultID)
VALUES (@ImportSessionID, @SourceEntityType, @SourceEntityID, @TargetEntityType, @TargetEntityID, @RelationshipType, @Confidence, @IsExplicit, @SceneResultID, @SceneResultID);
""",
new { ImportSessionID = importSessionId, SourceEntityType = sourceEntityType, SourceEntityID = sourceEntityId, TargetEntityType = targetEntityType, TargetEntityID = targetEntityId, RelationshipType = relationshipType, Confidence = confidence, IsExplicit = isExplicit, SceneResultID = sceneResultId });
}
public async Task<IReadOnlyList<StoryMemoryCharacter>> ListCharactersAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryMemoryCharacter>("SELECT * FROM dbo.StoryMemoryCharacters WHERE ImportSessionID = @ImportSessionID ORDER BY Significance DESC, DisplayName;", new { ImportSessionID = importSessionId })).ToList();
}
public async Task<IReadOnlyList<StoryMemoryCharacterAttribute>> ListCharacterAttributesAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryMemoryCharacterAttribute>(
"""
SELECT a.*
FROM dbo.StoryMemoryCharacterAttributes a
INNER JOIN dbo.StoryMemoryCharacters c ON c.StoryMemoryCharacterID = a.StoryMemoryCharacterID
WHERE c.ImportSessionID = @ImportSessionID
AND a.SupersededUtc IS NULL;
""",
new { ImportSessionID = importSessionId })).ToList();
}
public async Task<IReadOnlyList<StoryMemoryLocation>> ListLocationsAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryMemoryLocation>("SELECT * FROM dbo.StoryMemoryLocations WHERE ImportSessionID = @ImportSessionID ORDER BY DisplayName;", new { ImportSessionID = importSessionId })).ToList();
}
public async Task<IReadOnlyList<StoryMemoryAsset>> ListAssetsAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryMemoryAsset>("SELECT * FROM dbo.StoryMemoryAssets WHERE ImportSessionID = @ImportSessionID ORDER BY DisplayName;", new { ImportSessionID = importSessionId })).ToList();
}
public async Task<IReadOnlyList<StoryMemoryIllustrationAssignment>> ListAssignmentsAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return (await connection.QueryAsync<StoryMemoryIllustrationAssignment>("SELECT * FROM dbo.StoryMemoryIllustrationAssignments WHERE ImportSessionID = @ImportSessionID AND InvalidatedUtc IS NULL;", new { ImportSessionID = importSessionId })).ToList();
}
public async Task<StoryMemoryIllustrationAssignment?> GetAssignmentAsync(int importSessionId, string entityType, int entityId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<StoryMemoryIllustrationAssignment>(
"SELECT * FROM dbo.StoryMemoryIllustrationAssignments WHERE ImportSessionID = @ImportSessionID AND EntityType = @EntityType AND EntityID = @EntityID AND InvalidatedUtc IS NULL;",
new { ImportSessionID = importSessionId, EntityType = entityType, EntityID = entityId });
}
public async Task UpsertAssignmentAsync(StoryMemoryIllustrationAssignmentSave request)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
MERGE dbo.StoryMemoryIllustrationAssignments WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @EntityType AS EntityType, @EntityID AS EntityID) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.EntityType = source.EntityType
AND target.EntityID = source.EntityID
AND target.InvalidatedUtc IS NULL
WHEN MATCHED THEN
UPDATE SET IllustrationLibraryItemID = @IllustrationLibraryItemID,
StableCode = @StableCode,
AssignmentStatus = @AssignmentStatus,
IsFallback = @IsFallback,
MatchScore = @MatchScore,
Confidence = @Confidence,
AssignmentReason = @AssignmentReason,
EvidenceVersion = @EvidenceVersion,
LastValidatedUtc = SYSUTCDATETIME(),
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, EntityType, EntityID, IllustrationLibraryItemID, StableCode, AssignmentStatus, IsFallback, MatchScore, Confidence, AssignmentReason, EvidenceVersion)
VALUES (@ImportSessionID, @EntityType, @EntityID, @IllustrationLibraryItemID, @StableCode, @AssignmentStatus, @IsFallback, @MatchScore, @Confidence, @AssignmentReason, @EvidenceVersion);
""",
request);
}
public async Task InvalidateAssignmentAsync(int assignmentId, string reason)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
UPDATE dbo.StoryMemoryIllustrationAssignments
SET InvalidatedUtc = SYSUTCDATETIME(),
InvalidationReason = @Reason,
UpdatedUtc = SYSUTCDATETIME()
WHERE StoryMemoryIllustrationAssignmentID = @AssignmentID
AND InvalidatedUtc IS NULL;
""",
new { AssignmentID = assignmentId, Reason = reason });
}
public async Task<StoryMemoryIllustrationDemand> UpsertDemandAsync(StoryMemoryIllustrationDemandSave request)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<StoryMemoryIllustrationDemand>(
"""
MERGE dbo.StoryMemoryIllustrationDemands WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID, @EntityType AS EntityType, @ArchetypeKey AS ArchetypeKey) AS source
ON target.ImportSessionID = source.ImportSessionID
AND target.EntityType = source.EntityType
AND target.ArchetypeKey = source.ArchetypeKey
WHEN MATCHED THEN
UPDATE SET DemandCount = DemandCount + 1,
WaitingEntityCount = @WaitingEntityCount,
Status = @Status,
QueuedIllustrationLibraryItemID = COALESCE(@QueuedIllustrationLibraryItemID, target.QueuedIllustrationLibraryItemID),
GeneratedIllustrationLibraryItemID = COALESCE(@GeneratedIllustrationLibraryItemID, target.GeneratedIllustrationLibraryItemID),
UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, ProjectID, EntityType, ArchetypeKey, WaitingEntityCount, Status, QueuedIllustrationLibraryItemID, GeneratedIllustrationLibraryItemID)
VALUES (@ImportSessionID, @ProjectID, @EntityType, @ArchetypeKey, @WaitingEntityCount, @Status, @QueuedIllustrationLibraryItemID, @GeneratedIllustrationLibraryItemID);
SELECT *
FROM dbo.StoryMemoryIllustrationDemands
WHERE ImportSessionID = @ImportSessionID AND EntityType = @EntityType AND ArchetypeKey = @ArchetypeKey;
""",
request);
}
public async Task<string> GetAppearancePreferenceAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleOrDefaultAsync<string>(
"SELECT AppearancePreference FROM dbo.StoryMemoryImportPreferences WHERE ImportSessionID = @ImportSessionID;",
new { ImportSessionID = importSessionId })
?? StoryMemoryAppearancePreferences.Balanced;
}
public async Task SetAppearancePreferenceAsync(int importSessionId, string preference)
{
using var connection = connectionFactory.CreateConnection();
await connection.ExecuteAsync(
"""
MERGE dbo.StoryMemoryImportPreferences WITH (HOLDLOCK) AS target
USING (SELECT @ImportSessionID AS ImportSessionID) AS source
ON target.ImportSessionID = source.ImportSessionID
WHEN MATCHED THEN
UPDATE SET AppearancePreference = @AppearancePreference, UpdatedUtc = SYSUTCDATETIME()
WHEN NOT MATCHED THEN
INSERT (ImportSessionID, AppearancePreference)
VALUES (@ImportSessionID, @AppearancePreference);
""",
new { ImportSessionID = importSessionId, AppearancePreference = preference });
}
public async Task<StoryMemoryDiagnostics> GetDiagnosticsAsync(int importSessionId)
{
using var connection = connectionFactory.CreateConnection();
return await connection.QuerySingleAsync<StoryMemoryDiagnostics>(
"""
SELECT
@ImportSessionID AS ImportSessionID,
(SELECT COUNT(*) FROM dbo.StoryMemoryCharacters WHERE ImportSessionID = @ImportSessionID) AS CharacterCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryLocations WHERE ImportSessionID = @ImportSessionID) AS LocationCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryAssets WHERE ImportSessionID = @ImportSessionID) AS AssetCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryCharacterAliases a INNER JOIN dbo.StoryMemoryCharacters c ON c.StoryMemoryCharacterID = a.StoryMemoryCharacterID WHERE c.ImportSessionID = @ImportSessionID) AS AliasCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryCharacterAttributes a INNER JOIN dbo.StoryMemoryCharacters c ON c.StoryMemoryCharacterID = a.StoryMemoryCharacterID WHERE c.ImportSessionID = @ImportSessionID AND a.SupersededUtc IS NULL) AS AttributeCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryRelationships WHERE ImportSessionID = @ImportSessionID AND Active = 1) AS RelationshipCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryIllustrationAssignments WHERE ImportSessionID = @ImportSessionID AND InvalidatedUtc IS NULL) AS AssignmentCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryIllustrationAssignments WHERE ImportSessionID = @ImportSessionID AND InvalidatedUtc IS NULL AND IsFallback = 1) AS FallbackCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryIllustrationDemands WHERE ImportSessionID = @ImportSessionID) AS DemandCount,
(SELECT COUNT(*) FROM dbo.StoryMemoryProcessedSceneResults WHERE ImportSessionID = @ImportSessionID) AS ProcessedSceneCount,
(SELECT MAX(SceneResultID) FROM dbo.StoryMemoryProcessedSceneResults WHERE ImportSessionID = @ImportSessionID) AS LastProcessedSceneResultID,
COALESCE((SELECT AppearancePreference FROM dbo.StoryMemoryImportPreferences WHERE ImportSessionID = @ImportSessionID), N'Balanced') AS AppearancePreference;
""",
new { ImportSessionID = importSessionId });
}
}