49 lines
3.8 KiB
C#
49 lines
3.8 KiB
C#
using System.Text.Json;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using PlotLine.Data;
|
|
using PlotLine.Services;
|
|
using PlotLine.Models;
|
|
|
|
if (args.Length < 3 || !int.TryParse(args[0], out var bookId) || !int.TryParse(args[1], out var userId))
|
|
throw new ArgumentException("Usage: <bookId> <userId> <expected database name> [--apply]. Set PLOT_DIRECTOR_SQL_CONNECTION securely. Default: dry run.");
|
|
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?> {
|
|
["ConnectionStrings:DefaultConnection"] = Environment.GetEnvironmentVariable("PLOT_DIRECTOR_SQL_CONNECTION") }).Build();
|
|
var factory = new SqlConnectionFactory(config);
|
|
using (var connection = factory.CreateConnection())
|
|
{
|
|
connection.Open();
|
|
if (connection.Database != args[2]) throw new InvalidOperationException("Unexpected database; no changes made.");
|
|
}
|
|
var pipeline = new StoryIntelligencePipelineRepository(factory);
|
|
var committed = await pipeline.ListCommittedRunsByBookAsync(bookId, userId);
|
|
var runs = new StoryIntelligenceResultRepository(factory);
|
|
var sourceRuns = await runs.ListRunsByBookForUserAsync(bookId, userId);
|
|
if (sourceRuns.Any(x => x.Status is StoryIntelligenceRunStatuses.Pending or StoryIntelligenceRunStatuses.Running))
|
|
throw new InvalidOperationException("Analysis is active; wait for completion before backfilling.");
|
|
var items = committed.Count > 0
|
|
? committed.Select(x => new OnboardingStoryIntelligenceBatchItem { ChapterID = x.ChapterID, RunID = x.StoryIntelligenceRunID }).ToList()
|
|
: sourceRuns.Where(x => x.ChapterID.HasValue && x.Status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings)
|
|
.Select(x => new OnboardingStoryIntelligenceBatchItem { ChapterID = x.ChapterID!.Value, RunID = x.StoryIntelligenceRunID }).ToList();
|
|
var projectId = committed.FirstOrDefault()?.ProjectID ?? sourceRuns.FirstOrDefault()?.ProjectID;
|
|
if (items.Count == 0 || !projectId.HasValue) throw new InvalidOperationException("No accessible scene analysis for this book.");
|
|
var batch = new OnboardingStoryIntelligenceBatch { BookID = bookId, UserID = userId, ProjectID = projectId.Value, Items = items };
|
|
var importer = new StoryIntelligenceLocationImportService(new StoryIntelligenceResultRepository(factory), new LocationRepository(factory),
|
|
new SceneRepository(factory), null!, NullLogger<StoryIntelligenceLocationImportService>.Instance);
|
|
var assessments = await importer.BuildSignificanceAsync(batch);
|
|
var repository = new StoryIntelligenceReviewCandidateRepository(factory);
|
|
var rows = await repository.ListLocationsAsync(bookId);
|
|
var missing = rows.Where(x => !assessments.ContainsKey(x.StableKey)).ToList();
|
|
Console.WriteLine($"Book {bookId}: {rows.Count} persisted locations; {rows.Count - missing.Count} matched to scene evidence; {missing.Count} unmatched.");
|
|
if (missing.Count > 0) throw new InvalidOperationException("Backfill stopped: candidate identities no longer match the source analysis. No decisions or canonical locations were changed.");
|
|
foreach (var group in rows.GroupBy(x => assessments[x.StableKey].Classification)) Console.WriteLine($"{group.Key}: {group.Count()}");
|
|
foreach (var row in rows.OrderByDescending(x => assessments[x.StableKey].Score).Take(12))
|
|
Console.WriteLine($"{row.DisplayName}: {assessments[row.StableKey].Score} ({assessments[row.StableKey].Classification})");
|
|
if (args.Contains("--apply"))
|
|
{
|
|
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
|
|
await repository.BackfillLocationSignificanceAsync(bookId, JsonSerializer.Serialize(rows.Select(x => new { key = x.StableKey, assessment = assessments[x.StableKey] }), options));
|
|
Console.WriteLine("Saved assessments; identities, decisions and canonical data preserved.");
|
|
}
|
|
else Console.WriteLine("Dry run; no changes made.");
|