63 lines
4.8 KiB
C#
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.");
|
|
}
|
|
}
|