461 lines
30 KiB
C#
461 lines
30 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<StoryMemoryImportContext> GetImportContextAsync(int importSessionId);
|
|
Task SetAppearancePreferenceAsync(int importSessionId, string preference);
|
|
Task SetImportContextAsync(int importSessionId, string appearancePreference, string? primaryAgeGroup, string? storyEra, string? storyLocation);
|
|
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 = CASE WHEN target.EntityContext = N'Story' OR @EntityContext = N'Story' THEN N'Story' ELSE @EntityContext END,
|
|
SourceSectionType = CASE WHEN target.SourceSectionType = N'Story' OR @SourceSectionType = N'Story' THEN N'Story' ELSE @SourceSectionType END,
|
|
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)
|
|
=> (await GetImportContextAsync(importSessionId)).AppearancePreference;
|
|
|
|
public async Task<StoryMemoryImportContext> GetImportContextAsync(int importSessionId)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await EnsureImportPreferenceContextColumnsAsync(connection);
|
|
return await connection.QuerySingleOrDefaultAsync<StoryMemoryImportContext>(
|
|
"""
|
|
SELECT
|
|
AppearancePreference,
|
|
COALESCE(PrimaryAgeGroup, N'') AS PrimaryAgeGroup,
|
|
COALESCE(StoryEra, N'') AS StoryEra,
|
|
COALESCE(StoryLocation, N'') AS StoryLocation
|
|
FROM dbo.StoryMemoryImportPreferences
|
|
WHERE ImportSessionID = @ImportSessionID;
|
|
""",
|
|
new { ImportSessionID = importSessionId })
|
|
?? new StoryMemoryImportContext(StoryMemoryAppearancePreferences.Balanced, string.Empty, string.Empty, string.Empty);
|
|
}
|
|
|
|
public async Task SetAppearancePreferenceAsync(int importSessionId, string preference)
|
|
=> await SetImportContextAsync(importSessionId, preference, null, null, null);
|
|
|
|
public async Task SetImportContextAsync(int importSessionId, string appearancePreference, string? primaryAgeGroup, string? storyEra, string? storyLocation)
|
|
{
|
|
using var connection = connectionFactory.CreateConnection();
|
|
await EnsureImportPreferenceContextColumnsAsync(connection);
|
|
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,
|
|
PrimaryAgeGroup = COALESCE(@PrimaryAgeGroup, PrimaryAgeGroup),
|
|
StoryEra = COALESCE(@StoryEra, StoryEra),
|
|
StoryLocation = COALESCE(@StoryLocation, StoryLocation),
|
|
UpdatedUtc = SYSUTCDATETIME()
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (ImportSessionID, AppearancePreference, PrimaryAgeGroup, StoryEra, StoryLocation)
|
|
VALUES (@ImportSessionID, @AppearancePreference, COALESCE(@PrimaryAgeGroup, N''), COALESCE(@StoryEra, N''), COALESCE(@StoryLocation, N''));
|
|
""",
|
|
new
|
|
{
|
|
ImportSessionID = importSessionId,
|
|
AppearancePreference = appearancePreference,
|
|
PrimaryAgeGroup = TrimContext(primaryAgeGroup, 120),
|
|
StoryEra = TrimContext(storyEra, 120),
|
|
StoryLocation = TrimContext(storyLocation, 160)
|
|
});
|
|
}
|
|
|
|
private static async Task EnsureImportPreferenceContextColumnsAsync(System.Data.IDbConnection connection)
|
|
{
|
|
await connection.ExecuteAsync(
|
|
"""
|
|
IF COL_LENGTH(N'dbo.StoryMemoryImportPreferences', N'PrimaryAgeGroup') IS NULL
|
|
ALTER TABLE dbo.StoryMemoryImportPreferences ADD PrimaryAgeGroup nvarchar(120) NULL;
|
|
IF COL_LENGTH(N'dbo.StoryMemoryImportPreferences', N'StoryEra') IS NULL
|
|
ALTER TABLE dbo.StoryMemoryImportPreferences ADD StoryEra nvarchar(120) NULL;
|
|
IF COL_LENGTH(N'dbo.StoryMemoryImportPreferences', N'StoryLocation') IS NULL
|
|
ALTER TABLE dbo.StoryMemoryImportPreferences ADD StoryLocation nvarchar(160) NULL;
|
|
""");
|
|
}
|
|
|
|
private static string? TrimContext(string? value, int maxLength)
|
|
{
|
|
var clean = value?.Trim();
|
|
return string.IsNullOrWhiteSpace(clean) ? null : clean[..Math.Min(clean.Length, maxLength)];
|
|
}
|
|
|
|
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 });
|
|
}
|
|
}
|