Repair partial scene import retry
This commit is contained in:
parent
4f82dc308a
commit
a2cf6a96a8
@ -2171,6 +2171,10 @@ static void WordSyncRetryResumesExistingStructure()
|
||||
Assert(resumeCheck >= 0 && sceneDetection > resumeCheck, "Existing structural builds should be checked before rerunning scene boundary AI.");
|
||||
Assert(onboarding.Contains("BuildExistingStructureResultAsync", StringComparison.Ordinal), "Retry should rebuild marker instructions from persisted structure.");
|
||||
Assert(onboarding.Contains("BuildSceneMarkerInstructionsFromSources", StringComparison.Ordinal), "Retry should use stored SceneID paragraph positions for marker sync.");
|
||||
Assert(onboarding.Contains("missingSceneSources", StringComparison.Ordinal), "Retry should detect partial builds with missing scene source text.");
|
||||
Assert(onboarding.Contains("Completing saved scene source text before updating Word markers", StringComparison.Ordinal), "Retry should repair missing scene sources before marker sync.");
|
||||
Assert(onboarding.Contains("await SaveSceneManuscriptSourcesAsync(preview, recoveredScenes, existingBuild)", StringComparison.Ordinal), "Retry should persist recovered scene source text for an existing build.");
|
||||
Assert(onboarding.Contains("await manuscriptTextStatistics.RecalculateBookAsync(preview.BookID)", StringComparison.Ordinal), "Retry should repair missing scene text statistics when source text already exists.");
|
||||
Assert(complete.Contains("Resync Word markers", StringComparison.Ordinal), "Scene detection complete page should expose a marker repair action for existing imported documents.");
|
||||
Assert(complete.Contains("Scene detection is not rerun.", StringComparison.Ordinal), "Marker repair UI should explain it reuses existing structure.");
|
||||
Assert(repository.Contains("Onboarding_ManuscriptBuild_GetByPreview", StringComparison.Ordinal), "Existing build lookup should use a stored procedure.");
|
||||
|
||||
@ -260,9 +260,36 @@ public sealed class OnboardingService(
|
||||
?? await builds.GetByPreviewAsync(userId, preview.PreviewID);
|
||||
if (existingBuild is not null && existingBuild.SceneMappings.Count > 0)
|
||||
{
|
||||
var existingSceneSources = await builds.ListSceneManuscriptSourcesByBookAsync(preview.BookID);
|
||||
var sourceSceneIds = existingSceneSources.Select(source => source.SceneID).ToHashSet();
|
||||
var missingSceneSources = existingBuild.SceneMappings
|
||||
.Where(mapping => !sourceSceneIds.Contains(mapping.SceneID))
|
||||
.ToList();
|
||||
var repairedSceneSources = missingSceneSources.Count > 0;
|
||||
if (repairedSceneSources)
|
||||
{
|
||||
await PublishBuildProgress(userId, preview.PreviewID, "Completing saved scene source text before updating Word markers...", 65, progress, existingBuild);
|
||||
var recoveredScenes = await AnalyseApprovedChaptersAsync(userId, preview, review, book, progress);
|
||||
await SaveStructuralChapterSourcesAsync(preview, existingBuild);
|
||||
await SaveSceneManuscriptSourcesAsync(preview, recoveredScenes, existingBuild);
|
||||
await coreImportCharacterMapping.MapBookAsync(preview.ProjectID, preview.BookID);
|
||||
}
|
||||
else
|
||||
{
|
||||
await manuscriptTextStatistics.RecalculateBookAsync(preview.BookID);
|
||||
}
|
||||
|
||||
var resumed = await BuildExistingStructureResultAsync(userId, preview, review, existingBuild);
|
||||
await scanPreviews.SaveBuildResultAsync(userId, resumed);
|
||||
await PublishBuildProgress(userId, preview.PreviewID, "Resuming Word marker sync. No scene detection was rerun.", 90, progress, resumed);
|
||||
await PublishBuildProgress(
|
||||
userId,
|
||||
preview.PreviewID,
|
||||
repairedSceneSources
|
||||
? "Scene source text recovered. Updating Word markers."
|
||||
: "Resuming Word marker sync. No scene detection was rerun.",
|
||||
90,
|
||||
progress,
|
||||
resumed);
|
||||
return resumed;
|
||||
}
|
||||
|
||||
@ -272,51 +299,7 @@ public sealed class OnboardingService(
|
||||
var chapterDecisions = review.Chapters.Where(chapter => chapter.Include).OrderBy(chapter => chapter.ChapterNumber).ToList();
|
||||
|
||||
await PublishBuildProgress(userId, preview.PreviewID, "Detecting scene boundaries...", 20, progress);
|
||||
var structuralScenes = new List<ManuscriptStructureSceneBoundary>();
|
||||
for (var chapterIndex = 0; chapterIndex < chapterDecisions.Count; chapterIndex++)
|
||||
{
|
||||
var chapterDecision = chapterDecisions[chapterIndex];
|
||||
var chapterPreview = preview.Chapters.FirstOrDefault(item => item.TemporaryChapterKey == chapterDecision.TemporaryChapterKey)
|
||||
?? throw new InvalidOperationException($"The approved chapter '{chapterDecision.Title}' could not be found in the scan preview.");
|
||||
if (string.IsNullOrWhiteSpace(chapterPreview.ChapterText))
|
||||
{
|
||||
throw new InvalidOperationException($"The approved chapter '{chapterDecision.Title}' has no scanned text. Scan the manuscript again before importing.");
|
||||
}
|
||||
|
||||
if (chapterPreview.ParagraphMap.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"The approved chapter '{chapterDecision.Title}' does not have paragraph mapping. Scan the manuscript again before importing.");
|
||||
}
|
||||
|
||||
var analysisPercent = 20 + (chapterIndex * 20 / Math.Max(1, chapterDecisions.Count));
|
||||
await PublishBuildProgress(
|
||||
userId,
|
||||
preview.PreviewID,
|
||||
$"Analysing Chapter {chapterIndex + 1:N0} of {chapterDecisions.Count:N0} - {chapterDecision.Title}",
|
||||
analysisPercent,
|
||||
progress);
|
||||
var analysis = await structureAnalysis.AnalyseChapterAsync(new ManuscriptStructureAnalysisRequest
|
||||
{
|
||||
ProjectID = preview.ProjectID,
|
||||
BookID = preview.BookID,
|
||||
PreviewID = preview.PreviewID,
|
||||
TemporaryChapterKey = chapterDecision.TemporaryChapterKey,
|
||||
ChapterNumber = chapterDecision.ChapterNumber,
|
||||
SourceLabel = $"{preview.DocumentTitle ?? "Word manuscript"}: {chapterDecision.Title}",
|
||||
StoryEra = BookContextOptions.StoryEraLabel(book.StoryEra),
|
||||
PrimaryLocale = BookContextOptions.PrimaryLocaleLabel(book.PrimaryLocaleCode),
|
||||
ChapterText = chapterPreview.ChapterText,
|
||||
ParagraphMap = chapterPreview.ParagraphMap,
|
||||
SourceParagraphs = chapterPreview.SourceParagraphs
|
||||
});
|
||||
|
||||
if (!analysis.Success)
|
||||
{
|
||||
throw new InvalidOperationException($"Scene boundary detection failed for '{chapterDecision.Title}': {analysis.ErrorMessage}");
|
||||
}
|
||||
|
||||
structuralScenes.AddRange(analysis.Scenes);
|
||||
}
|
||||
var structuralScenes = await AnalyseApprovedChaptersAsync(userId, preview, review, book, progress);
|
||||
|
||||
await PublishBuildProgress(userId, preview.PreviewID, "Validating detected scene structure...", 45, progress);
|
||||
var request = new OnboardingManuscriptBuildRequest
|
||||
@ -382,6 +365,63 @@ public sealed class OnboardingService(
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<ManuscriptStructureSceneBoundary>> AnalyseApprovedChaptersAsync(
|
||||
int userId,
|
||||
ManuscriptScanPreview preview,
|
||||
ManuscriptScanReviewDecision review,
|
||||
Book book,
|
||||
Func<OnboardingBuildProgress, Task>? progress)
|
||||
{
|
||||
var chapterDecisions = review.Chapters.Where(chapter => chapter.Include).OrderBy(chapter => chapter.ChapterNumber).ToList();
|
||||
var structuralScenes = new List<ManuscriptStructureSceneBoundary>();
|
||||
for (var chapterIndex = 0; chapterIndex < chapterDecisions.Count; chapterIndex++)
|
||||
{
|
||||
var chapterDecision = chapterDecisions[chapterIndex];
|
||||
var chapterPreview = preview.Chapters.FirstOrDefault(item => item.TemporaryChapterKey == chapterDecision.TemporaryChapterKey)
|
||||
?? throw new InvalidOperationException($"The approved chapter '{chapterDecision.Title}' could not be found in the scan preview.");
|
||||
if (string.IsNullOrWhiteSpace(chapterPreview.ChapterText))
|
||||
{
|
||||
throw new InvalidOperationException($"The approved chapter '{chapterDecision.Title}' has no scanned text. Scan the manuscript again before importing.");
|
||||
}
|
||||
|
||||
if (chapterPreview.ParagraphMap.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"The approved chapter '{chapterDecision.Title}' does not have paragraph mapping. Scan the manuscript again before importing.");
|
||||
}
|
||||
|
||||
var analysisPercent = 20 + (chapterIndex * 20 / Math.Max(1, chapterDecisions.Count));
|
||||
await PublishBuildProgress(
|
||||
userId,
|
||||
preview.PreviewID,
|
||||
$"Analysing Chapter {chapterIndex + 1:N0} of {chapterDecisions.Count:N0} - {chapterDecision.Title}",
|
||||
analysisPercent,
|
||||
progress);
|
||||
var analysis = await structureAnalysis.AnalyseChapterAsync(new ManuscriptStructureAnalysisRequest
|
||||
{
|
||||
ProjectID = preview.ProjectID,
|
||||
BookID = preview.BookID,
|
||||
PreviewID = preview.PreviewID,
|
||||
TemporaryChapterKey = chapterDecision.TemporaryChapterKey,
|
||||
ChapterNumber = chapterDecision.ChapterNumber,
|
||||
SourceLabel = $"{preview.DocumentTitle ?? "Word manuscript"}: {chapterDecision.Title}",
|
||||
StoryEra = BookContextOptions.StoryEraLabel(book.StoryEra),
|
||||
PrimaryLocale = BookContextOptions.PrimaryLocaleLabel(book.PrimaryLocaleCode),
|
||||
ChapterText = chapterPreview.ChapterText,
|
||||
ParagraphMap = chapterPreview.ParagraphMap,
|
||||
SourceParagraphs = chapterPreview.SourceParagraphs
|
||||
});
|
||||
|
||||
if (!analysis.Success)
|
||||
{
|
||||
throw new InvalidOperationException($"Scene boundary detection failed for '{chapterDecision.Title}': {analysis.ErrorMessage}");
|
||||
}
|
||||
|
||||
structuralScenes.AddRange(analysis.Scenes);
|
||||
}
|
||||
|
||||
return structuralScenes;
|
||||
}
|
||||
|
||||
private async Task<OnboardingManuscriptBuildResult> BuildExistingStructureResultAsync(
|
||||
int userId,
|
||||
ManuscriptScanPreview preview,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user