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(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("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("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("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(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.");
|
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);
|
?? await builds.GetByPreviewAsync(userId, preview.PreviewID);
|
||||||
if (existingBuild is not null && existingBuild.SceneMappings.Count > 0)
|
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);
|
var resumed = await BuildExistingStructureResultAsync(userId, preview, review, existingBuild);
|
||||||
await scanPreviews.SaveBuildResultAsync(userId, resumed);
|
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;
|
return resumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,51 +299,7 @@ public sealed class OnboardingService(
|
|||||||
var chapterDecisions = review.Chapters.Where(chapter => chapter.Include).OrderBy(chapter => chapter.ChapterNumber).ToList();
|
var chapterDecisions = review.Chapters.Where(chapter => chapter.Include).OrderBy(chapter => chapter.ChapterNumber).ToList();
|
||||||
|
|
||||||
await PublishBuildProgress(userId, preview.PreviewID, "Detecting scene boundaries...", 20, progress);
|
await PublishBuildProgress(userId, preview.PreviewID, "Detecting scene boundaries...", 20, progress);
|
||||||
var structuralScenes = new List<ManuscriptStructureSceneBoundary>();
|
var structuralScenes = await AnalyseApprovedChaptersAsync(userId, preview, review, book, progress);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
await PublishBuildProgress(userId, preview.PreviewID, "Validating detected scene structure...", 45, progress);
|
await PublishBuildProgress(userId, preview.PreviewID, "Validating detected scene structure...", 45, progress);
|
||||||
var request = new OnboardingManuscriptBuildRequest
|
var request = new OnboardingManuscriptBuildRequest
|
||||||
@ -382,6 +365,63 @@ public sealed class OnboardingService(
|
|||||||
return result;
|
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(
|
private async Task<OnboardingManuscriptBuildResult> BuildExistingStructureResultAsync(
|
||||||
int userId,
|
int userId,
|
||||||
ManuscriptScanPreview preview,
|
ManuscriptScanPreview preview,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user