Commit critical import artifacts

This commit is contained in:
2026-04-19 13:38:52 +02:00
parent 5fcfd3e381
commit 0aac1bd734
2188 changed files with 238467 additions and 97 deletions

View File

@@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using RolemasterDb.App.Data;
using RolemasterDb.App.Domain;
using RolemasterDb.ImportTool.Parsing;
@@ -35,7 +34,8 @@ public sealed class CriticalImportLoader(string databasePath)
return removedTableCount;
}
public async Task<ImportCommandResult> LoadAsync(ParsedCriticalTable table, CancellationToken cancellationToken = default)
public async Task<ImportCommandResult> LoadAsync(ParsedCriticalTable table,
CancellationToken cancellationToken = default)
{
await using var dbContext = CreateDbContext();
await dbContext.Database.EnsureCreatedAsync(cancellationToken);
@@ -48,16 +48,16 @@ public sealed class CriticalImportLoader(string databasePath)
.Include(item => item.Columns)
.Include(item => item.RollBands)
.Include(item => item.Results)
.ThenInclude(result => result.CriticalGroup)
.ThenInclude(result => result.CriticalGroup)
.Include(item => item.Results)
.ThenInclude(result => result.CriticalColumn)
.ThenInclude(result => result.CriticalColumn)
.Include(item => item.Results)
.ThenInclude(result => result.CriticalRollBand)
.ThenInclude(result => result.CriticalRollBand)
.Include(item => item.Results)
.ThenInclude(result => result.Effects)
.ThenInclude(result => result.Effects)
.Include(item => item.Results)
.ThenInclude(result => result.Branches)
.ThenInclude(branch => branch.Effects)
.ThenInclude(result => result.Branches)
.ThenInclude(branch => branch.Effects)
.SingleOrDefaultAsync(item => item.Slug == table.Slug, cancellationToken);
if (entity is null)
@@ -78,7 +78,8 @@ public sealed class CriticalImportLoader(string databasePath)
var columnsByKey = SynchronizeColumns(entity, table);
var rollBandsByLabel = SynchronizeRollBands(entity, table);
var existingResultsByKey = entity.Results.ToDictionary(
item => CreateResultKey(item.CriticalGroup?.GroupKey, item.CriticalColumn.ColumnKey, item.CriticalRollBand.Label),
item => CreateResultKey(item.CriticalGroup?.GroupKey, item.CriticalColumn.ColumnKey,
item.CriticalRollBand.Label),
StringComparer.Ordinal);
var importedKeys = new HashSet<string>(StringComparer.Ordinal);
@@ -109,7 +110,8 @@ public sealed class CriticalImportLoader(string databasePath)
}
foreach (var unmatchedResult in entity.Results
.Where(item => !importedKeys.Contains(CreateResultKey(item.CriticalGroup?.GroupKey, item.CriticalColumn.ColumnKey, item.CriticalRollBand.Label)))
.Where(item => !importedKeys.Contains(CreateResultKey(item.CriticalGroup?.GroupKey,
item.CriticalColumn.ColumnKey, item.CriticalRollBand.Label)))
.ToList())
{
if (unmatchedResult.IsCurated)
@@ -128,7 +130,8 @@ public sealed class CriticalImportLoader(string databasePath)
return new ImportCommandResult(entity.Slug, entity.Columns.Count, entity.RollBands.Count, entity.Results.Count);
}
public async Task<int> RefreshImageArtifactsAsync(ParsedCriticalTable table, CancellationToken cancellationToken = default)
public async Task<int> RefreshImageArtifactsAsync(ParsedCriticalTable table,
CancellationToken cancellationToken = default)
{
await using var dbContext = CreateDbContext();
await dbContext.Database.EnsureCreatedAsync(cancellationToken);
@@ -138,20 +141,22 @@ public sealed class CriticalImportLoader(string databasePath)
var entity = await dbContext.CriticalTables
.AsSplitQuery()
.Include(item => item.Results)
.ThenInclude(result => result.CriticalGroup)
.ThenInclude(result => result.CriticalGroup)
.Include(item => item.Results)
.ThenInclude(result => result.CriticalColumn)
.ThenInclude(result => result.CriticalColumn)
.Include(item => item.Results)
.ThenInclude(result => result.CriticalRollBand)
.ThenInclude(result => result.CriticalRollBand)
.SingleOrDefaultAsync(item => item.Slug == table.Slug, cancellationToken);
if (entity is null)
{
throw new InvalidOperationException($"Critical table '{table.Slug}' does not exist in the target database.");
throw new InvalidOperationException(
$"Critical table '{table.Slug}' does not exist in the target database.");
}
var existingResultsByKey = entity.Results.ToDictionary(
item => CreateResultKey(item.CriticalGroup?.GroupKey, item.CriticalColumn.ColumnKey, item.CriticalRollBand.Label),
item => CreateResultKey(item.CriticalGroup?.GroupKey, item.CriticalColumn.ColumnKey,
item.CriticalRollBand.Label),
StringComparer.Ordinal);
var refreshedCount = 0;
@@ -172,6 +177,66 @@ public sealed class CriticalImportLoader(string databasePath)
return refreshedCount;
}
public async Task<IReadOnlyList<CriticalImageArtifactMetadata>> LoadImageArtifactMetadataAsync(
string tableSlug,
CancellationToken cancellationToken = default)
{
await using var dbContext = CreateDbContext();
await dbContext.Database.EnsureCreatedAsync(cancellationToken);
await RolemasterDbSchemaUpgrader.EnsureLatestAsync(dbContext, cancellationToken);
var rows = await dbContext.CriticalResults
.AsNoTracking()
.Where(item => item.CriticalTable.Slug == tableSlug)
.OrderBy(item => item.Id)
.Select(item => new
{
item.Id,
item.SourcePageNumber,
item.SourceImagePath,
item.SourceImageCropJson
})
.ToListAsync(cancellationToken);
if (rows.Count == 0)
{
throw new InvalidOperationException($"Critical table '{tableSlug}' does not exist in the target database.");
}
var metadata = new List<CriticalImageArtifactMetadata>(rows.Count);
foreach (var row in rows)
{
if (string.IsNullOrWhiteSpace(row.SourceImagePath))
{
throw new InvalidOperationException(
$"Critical result {row.Id} in table '{tableSlug}' is missing SourceImagePath.");
}
if (string.IsNullOrWhiteSpace(row.SourceImageCropJson))
{
throw new InvalidOperationException(
$"Critical result {row.Id} in table '{tableSlug}' is missing SourceImageCropJson.");
}
var crop = JsonSerializer.Deserialize<CriticalSourceImageCrop>(row.SourceImageCropJson);
if (crop is null)
{
throw new InvalidOperationException(
$"Critical result {row.Id} in table '{tableSlug}' has invalid SourceImageCropJson.");
}
if (row.SourcePageNumber is not null && row.SourcePageNumber != crop.PageNumber)
{
throw new InvalidOperationException(
$"Critical result {row.Id} in table '{tableSlug}' has mismatched source page metadata.");
}
metadata.Add(new CriticalImageArtifactMetadata(row.Id, row.SourceImagePath, crop));
}
return metadata;
}
private RolemasterDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<RolemasterDbContext>()
@@ -205,7 +270,8 @@ public sealed class CriticalImportLoader(string databasePath)
return groupsByKey;
}
private static Dictionary<string, CriticalColumn> SynchronizeColumns(CriticalTable entity, ParsedCriticalTable table)
private static Dictionary<string, CriticalColumn> SynchronizeColumns(CriticalTable entity,
ParsedCriticalTable table)
{
var columnsByKey = entity.Columns.ToDictionary(item => item.ColumnKey, StringComparer.OrdinalIgnoreCase);
@@ -230,7 +296,8 @@ public sealed class CriticalImportLoader(string databasePath)
return columnsByKey;
}
private static Dictionary<string, CriticalRollBand> SynchronizeRollBands(CriticalTable entity, ParsedCriticalTable table)
private static Dictionary<string, CriticalRollBand> SynchronizeRollBands(CriticalTable entity,
ParsedCriticalTable table)
{
var rollBandsByLabel = entity.RollBands.ToDictionary(item => item.Label, StringComparer.OrdinalIgnoreCase);
@@ -300,7 +367,8 @@ public sealed class CriticalImportLoader(string databasePath)
: JsonSerializer.Serialize(item.SourceImageCrop, JsonOptions);
}
private static void ReplaceResultChildren(RolemasterDbContext dbContext, CriticalResult result, ParsedCriticalResult item)
private static void ReplaceResultChildren(RolemasterDbContext dbContext, CriticalResult result,
ParsedCriticalResult item)
{
foreach (var branch in result.Branches)
{
@@ -346,7 +414,8 @@ public sealed class CriticalImportLoader(string databasePath)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var group in entity.Groups
.Where(item => !activeGroupKeys.Contains(item.GroupKey) && !importedGroupKeys.Contains(item.GroupKey))
.Where(item =>
!activeGroupKeys.Contains(item.GroupKey) && !importedGroupKeys.Contains(item.GroupKey))
.ToList())
{
dbContext.CriticalGroups.Remove(group);
@@ -361,7 +430,8 @@ public sealed class CriticalImportLoader(string databasePath)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var column in entity.Columns
.Where(item => !activeColumnKeys.Contains(item.ColumnKey) && !importedColumnKeys.Contains(item.ColumnKey))
.Where(item =>
!activeColumnKeys.Contains(item.ColumnKey) && !importedColumnKeys.Contains(item.ColumnKey))
.ToList())
{
dbContext.CriticalColumns.Remove(column);
@@ -376,7 +446,8 @@ public sealed class CriticalImportLoader(string databasePath)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var rollBand in entity.RollBands
.Where(item => !activeRollBandLabels.Contains(item.Label) && !importedRollBandLabels.Contains(item.Label))
.Where(item =>
!activeRollBandLabels.Contains(item.Label) && !importedRollBandLabels.Contains(item.Label))
.ToList())
{
dbContext.CriticalRollBands.Remove(rollBand);
@@ -437,4 +508,4 @@ public sealed class CriticalImportLoader(string databasePath)
private static string NormalizeKey(string? value) =>
string.IsNullOrWhiteSpace(value) ? string.Empty : value.Trim().ToUpperInvariant();
}
}