63 lines
4.8 KiB
C#

using System.Text.Json;
using Dapper;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using PlotLine.Data;
using PlotLine.Services;
var key = "ConnectionStrings__DefaultConnection=";
var connectionString = File.ReadLines("/etc/plotdirector/plotdirector.env").First(x => x.StartsWith(key, StringComparison.Ordinal))[key.Length..];
var factory = new SqlConnectionFactory(new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?> { { "ConnectionStrings:DefaultConnection", connectionString } }).Build());
using var connection = factory.CreateConnection();
connection.Open();
if (connection.Database != "PlotDirector_Development") throw new Exception("Unexpected database");
var targets = (await connection.QueryAsync<(int GenerationID, int ProjectID, int BookID, int UserID)>("""
SELECT g.ReviewCandidateGenerationID GenerationID,g.ProjectID,g.BookID,
(SELECT TOP(1) UserID FROM dbo.ProjectUserAccess WHERE ProjectID=g.ProjectID AND IsActive=1 ORDER BY CASE WHEN AccessRole=N'Owner' THEN 0 ELSE 1 END,UserID) UserID
FROM dbo.StoryIntelligenceReviewCandidateGenerations g
WHERE g.IsCurrent=1 AND g.Status=N'Ready' AND g.Module=N'Relationships' AND EXISTS
(SELECT 1 FROM dbo.StoryIntelligenceReviewCandidates c WHERE c.ReviewCandidateGenerationID=g.ReviewCandidateGenerationID AND c.DecisionStatus=N'Pending')
""")).ToList();
var pipelines = new StoryIntelligencePipelineStateService(new StoryIntelligencePipelineRepository(factory), NullLogger<StoryIntelligencePipelineStateService>.Instance);
var builder = new StoryIntelligenceRelationshipImportService(new StoryIntelligenceResultRepository(factory), new CharacterRepository(factory), new SceneRepository(factory), pipelines, NullLogger<StoryIntelligenceRelationshipImportService>.Instance);
var repository = new StoryIntelligenceReviewCandidateRepository(factory);
foreach (var target in targets)
{
var runs = await pipelines.ListCommittedRunsByBookAsync(target.BookID, target.UserID);
Console.WriteLine($"Committed runs={runs.Count}; Active characters={(await new CharacterRepository(factory).ListCharactersAsync(target.ProjectID)).Count}");
var runRows = await new StoryIntelligenceResultRepository(factory).ListRunsByBookForUserAsync(target.BookID, target.UserID);
Console.WriteLine($"Analysis runs={runRows.Count}");
var batch = new OnboardingStoryIntelligenceBatch
{
ProjectID = target.ProjectID,
BookID = target.BookID,
UserID = target.UserID,
Items = runs.Count > 0
? runs.OrderByDescending(x => x.ChapterNumber).Select(x => new OnboardingStoryIntelligenceBatchItem { ChapterID = x.ChapterID, ChapterNumber = (int)x.ChapterNumber, RunID = x.StoryIntelligenceRunID }).ToList()
: runRows.Where(x => x.ChapterID.HasValue).OrderByDescending(x => x.ChapterNumber).Select(x => new OnboardingStoryIntelligenceBatchItem { ChapterID = x.ChapterID!.Value, ChapterNumber = (int)(x.ChapterNumber ?? 0), RunID = x.StoryIntelligenceRunID }).ToList()
};
// Deliberately reverse input chapters to verify first/last labels are independent of processing order.
var review = await builder.BuildReviewAsync(batch);
var sorted = review.Candidates.OrderBy(x => x.FirstChapterNumber).ThenBy(x => x.FirstSceneNumber).ThenBy(x => x.FirstSceneID).Select(x => x.Key);
if (!review.Candidates.Select(x => x.Key).SequenceEqual(sorted)) throw new Exception("Nonchronological candidates");
Console.WriteLine($"Book={target.BookID}; Candidates={review.Candidates.Count}; Types={string.Join(", ", review.Candidates.GroupBy(x => x.RelationshipType).Select(g => $"{g.Key}:{g.Count()}"))}");
var json = JsonSerializer.Serialize(review.Candidates, new JsonSerializerOptions(JsonSerializerDefaults.Web));
if (args.Contains("--apply"))
{
var previous = new List<PlotLine.Models.StoryIntelligenceReviewCandidateRow>();
for (var pageNumber = 1; ; pageNumber++)
{
var page = await repository.ListPageAsync(target.BookID, "Relationships", pageNumber, 100);
previous.AddRange(page);
if (page.Count < 100) break;
}
var proposedKeys = review.Candidates.Select(x => x.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
if (previous.Any(x => !proposedKeys.Contains(x.StableKey)))
throw new Exception("Some pending pairs could not be rebuilt. Investigate before refreshing this book.");
var backup = $"/var/tmp/plotdirector-relationship-review-{target.BookID}-{DateTime.UtcNow:yyyyMMddHHmmss}.json";
await File.WriteAllTextAsync(backup, JsonSerializer.Serialize(previous));
Console.WriteLine($"Pending snapshot: {backup}");
await repository.RefreshPendingRelationshipsAsync(target.BookID, target.GenerationID, json);
Console.WriteLine($"Refreshed pending suggestions for book {target.BookID}; author decisions preserved.");
}
}