Fix independent review centre state
This commit is contained in:
parent
794606f723
commit
5a7b8c79c7
@ -270,6 +270,7 @@ var tests = new (string Name, Action Test)[]
|
|||||||
("Story Intelligence Review Centre uses independent tile grid", StoryIntelligenceReviewCentreUsesIndependentTileGrid),
|
("Story Intelligence Review Centre uses independent tile grid", StoryIntelligenceReviewCentreUsesIndependentTileGrid),
|
||||||
("Story Intelligence Review Centre opens with character gate", StoryIntelligenceReviewCentreOpensWithCharacterGate),
|
("Story Intelligence Review Centre opens with character gate", StoryIntelligenceReviewCentreOpensWithCharacterGate),
|
||||||
("Story Intelligence review modules are independent after character gate", StoryIntelligenceReviewModulesAreIndependentAfterCharacterGate),
|
("Story Intelligence review modules are independent after character gate", StoryIntelligenceReviewModulesAreIndependentAfterCharacterGate),
|
||||||
|
("Story Intelligence review module completion is durable and independent", StoryIntelligenceReviewModuleCompletionIsDurableAndIndependent),
|
||||||
("Story Intelligence review module pages omit wizard chrome", StoryIntelligenceReviewModulePagesOmitWizardChrome),
|
("Story Intelligence review module pages omit wizard chrome", StoryIntelligenceReviewModulePagesOmitWizardChrome),
|
||||||
("Story Intelligence downstream review uses completed scene analysis", StoryIntelligenceDownstreamReviewUsesCompletedSceneAnalysis),
|
("Story Intelligence downstream review uses completed scene analysis", StoryIntelligenceDownstreamReviewUsesCompletedSceneAnalysis),
|
||||||
("Story Intelligence review posts do not use success interstitials", StoryIntelligenceReviewPostsDoNotUseSuccessInterstitials),
|
("Story Intelligence review posts do not use success interstitials", StoryIntelligenceReviewPostsDoNotUseSuccessInterstitials),
|
||||||
@ -802,6 +803,77 @@ static void StoryIntelligenceReviewModulesAreIndependentAfterCharacterGate()
|
|||||||
Assert(locationsAction.Contains("RedirectToReviewCentreWithCharacterGate", StringComparison.Ordinal), "Direct dependent module URLs should return to Review Centre when the character gate is closed.");
|
Assert(locationsAction.Contains("RedirectToReviewCentreWithCharacterGate", StringComparison.Ordinal), "Direct dependent module URLs should return to Review Centre when the character gate is closed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void StoryIntelligenceReviewModuleCompletionIsDurableAndIndependent()
|
||||||
|
{
|
||||||
|
var root = PlotLineRoot();
|
||||||
|
var model = File.ReadAllText(Path.Combine(root, "Models/StoryIntelligencePersistenceModels.cs"));
|
||||||
|
var repository = File.ReadAllText(Path.Combine(root, "Data/StoryIntelligencePipelineRepository.cs"));
|
||||||
|
var pipeline = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligencePipelineStateService.cs"));
|
||||||
|
var onboardingStory = File.ReadAllText(Path.Combine(root, "Services/OnboardingStoryIntelligenceService.cs"));
|
||||||
|
var storyService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceService.cs"));
|
||||||
|
var coreServices = File.ReadAllText(Path.Combine(root, "Services/CoreServices.cs"));
|
||||||
|
var characterService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceCharacterImportService.cs"));
|
||||||
|
var locationService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceLocationImportService.cs"));
|
||||||
|
var assetService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceAssetImportService.cs"));
|
||||||
|
var relationshipService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceRelationshipImportService.cs"));
|
||||||
|
var knowledgeService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceKnowledgeImportService.cs"));
|
||||||
|
var narrativeService = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceNarrativeReviewService.cs"));
|
||||||
|
var migration = File.ReadAllText(Path.Combine(root, "Sql/175_Phase27B_StoryIntelligenceIndependentReviewState.sql"));
|
||||||
|
var synthesisSql = File.ReadAllText(Path.Combine(root, "Sql/174_Phase27_WholeBookPlotIntelligence.sql"));
|
||||||
|
|
||||||
|
foreach (var column in new[]
|
||||||
|
{
|
||||||
|
"CharacterReviewCompletedUtc",
|
||||||
|
"LocationReviewCompletedUtc",
|
||||||
|
"AssetReviewCompletedUtc",
|
||||||
|
"RelationshipReviewCompletedUtc",
|
||||||
|
"KnowledgeReviewCompletedUtc",
|
||||||
|
"NarrativeReviewCompletedUtc"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Assert(model.Contains($"public DateTime? {column}", StringComparison.Ordinal), $"{column} should be exposed on persisted pipeline state.");
|
||||||
|
Assert(migration.Contains($"ADD {column} datetime2 NULL", StringComparison.Ordinal), $"{column} should be added by migration.");
|
||||||
|
Assert(migration.Contains($"p.{column}", StringComparison.Ordinal), $"{column} should be returned by pipeline read procedures.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(repository.Contains("request.MarkCharacterReviewComplete", StringComparison.Ordinal), "Pipeline upsert should pass durable character completion.");
|
||||||
|
Assert(repository.Contains("request.MarkNarrativeReviewComplete", StringComparison.Ordinal), "Pipeline upsert should pass durable narrative completion.");
|
||||||
|
Assert(pipeline.Contains("MarkCharacterReviewComplete = true", StringComparison.Ordinal), "Character import should mark only character review complete.");
|
||||||
|
Assert(pipeline.Contains("MarkNarrativeReviewComplete = true", StringComparison.Ordinal), "Narrative import should mark only narrative review complete.");
|
||||||
|
Assert(pipeline.Contains("MarkLocationReviewComplete = true", StringComparison.Ordinal), "Location import should mark durable location completion.");
|
||||||
|
Assert(pipeline.Contains("MarkAssetReviewComplete = true", StringComparison.Ordinal), "Asset import should mark durable asset completion.");
|
||||||
|
Assert(pipeline.Contains("MarkRelationshipReviewComplete = true", StringComparison.Ordinal), "Relationship import should mark durable relationship completion.");
|
||||||
|
Assert(pipeline.Contains("MarkKnowledgeReviewComplete = true", StringComparison.Ordinal), "Knowledge import should mark durable knowledge completion.");
|
||||||
|
Assert(migration.Contains("CharacterReviewCompletedUtc = CASE WHEN @MarkCharacterReviewComplete = 1 THEN COALESCE(CharacterReviewCompletedUtc", StringComparison.Ordinal), "Pipeline upsert must preserve character completion when other modules update.");
|
||||||
|
Assert(migration.Contains("NarrativeReviewCompletedUtc = CASE WHEN @MarkNarrativeReviewComplete = 1 THEN COALESCE(NarrativeReviewCompletedUtc", StringComparison.Ordinal), "Pipeline upsert must preserve narrative completion when other modules update.");
|
||||||
|
Assert(migration.Contains("WHERE NarrativeReviewCompletedUtc IS NULL", StringComparison.Ordinal), "Migration repair should be non-destructive.");
|
||||||
|
|
||||||
|
Assert(onboardingStory.Contains("pipeline?.CharacterReviewCompletedUtc is not null", StringComparison.Ordinal), "Review Centre should hydrate character completion from durable state.");
|
||||||
|
Assert(onboardingStory.Contains("pipeline?.LocationReviewCompletedUtc is not null", StringComparison.Ordinal), "Review Centre should hydrate location completion independently.");
|
||||||
|
Assert(onboardingStory.Contains("pipeline?.AssetReviewCompletedUtc is not null", StringComparison.Ordinal), "Review Centre should hydrate asset completion independently.");
|
||||||
|
Assert(onboardingStory.Contains("pipeline?.RelationshipReviewCompletedUtc is not null", StringComparison.Ordinal), "Review Centre should hydrate relationship completion independently.");
|
||||||
|
Assert(onboardingStory.Contains("pipeline?.KnowledgeReviewCompletedUtc is not null", StringComparison.Ordinal), "Review Centre should hydrate knowledge completion independently.");
|
||||||
|
Assert(onboardingStory.Contains("pipeline?.NarrativeReviewCompletedUtc is not null", StringComparison.Ordinal), "Review Centre should hydrate narrative completion independently.");
|
||||||
|
Assert(onboardingStory.Contains("batch.CharacterStageComplete = state.CharacterReviewCompletedUtc is not null", StringComparison.Ordinal), "Resume should hydrate character completion from durable state only.");
|
||||||
|
Assert(!onboardingStory.Contains("state.CurrentStage is StoryIntelligencePipelineStages.LocationReview", StringComparison.Ordinal), "Resume should not infer character completion from post-character stage ordinals.");
|
||||||
|
|
||||||
|
Assert(characterService.Contains("pipeline?.CharacterReviewCompletedUtc is not null", StringComparison.Ordinal) && characterService.Contains("Candidates = []", StringComparison.Ordinal), "Completed character review should not resurrect stale scene candidates.");
|
||||||
|
Assert(locationService.Contains("pipeline?.LocationReviewCompletedUtc is not null", StringComparison.Ordinal), "Completed location review should not resurrect stale scene candidates.");
|
||||||
|
Assert(assetService.Contains("pipeline?.AssetReviewCompletedUtc is not null", StringComparison.Ordinal), "Completed asset review should not resurrect stale scene candidates.");
|
||||||
|
Assert(relationshipService.Contains("pipeline?.RelationshipReviewCompletedUtc is not null", StringComparison.Ordinal), "Completed relationship review should not resurrect stale scene candidates.");
|
||||||
|
Assert(knowledgeService.Contains("pipeline?.KnowledgeReviewCompletedUtc is not null", StringComparison.Ordinal), "Completed knowledge review should not resurrect stale scene candidates.");
|
||||||
|
Assert(narrativeService.Contains("pipeline?.NarrativeReviewCompletedUtc is not null", StringComparison.Ordinal), "Completed plot review should not resurrect stale plot candidates.");
|
||||||
|
|
||||||
|
Assert(!storyService.Contains("StoryIntelligenceReadyForLocations", StringComparison.Ordinal), "Dashboard should not use old post-character location readiness ordinals.");
|
||||||
|
Assert(!storyService.Contains("StoryIntelligenceReadyForAssets", StringComparison.Ordinal), "Dashboard should not use old post-character asset readiness ordinals.");
|
||||||
|
Assert(!storyService.Contains("StoryIntelligenceReadyForRelationships", StringComparison.Ordinal), "Dashboard should not use old post-character relationship readiness ordinals.");
|
||||||
|
Assert(!storyService.Contains("StoryIntelligenceReadyForKnowledge", StringComparison.Ordinal), "Dashboard should not use old post-character knowledge readiness ordinals.");
|
||||||
|
Assert(coreServices.Contains("pipeline.CharacterReviewCompletedUtc is null", StringComparison.Ordinal), "Book action should not show character review required once durable character completion exists.");
|
||||||
|
|
||||||
|
Assert(synthesisSql.Contains("AND Status = N'Pending'", StringComparison.Ordinal), "Whole-book candidate regeneration should preserve reviewed plot decisions.");
|
||||||
|
Assert(synthesisSql.Contains("Status IN (N'Queued', N'Running', N'Completed')", StringComparison.Ordinal), "Synthesis queue should preserve current completed runs rather than replacing them.");
|
||||||
|
}
|
||||||
|
|
||||||
static void StoryIntelligenceReviewModulePagesOmitWizardChrome()
|
static void StoryIntelligenceReviewModulePagesOmitWizardChrome()
|
||||||
{
|
{
|
||||||
var root = PlotLineRoot();
|
var root = PlotLineRoot();
|
||||||
@ -3566,16 +3638,16 @@ static void StoryIntelligenceReviewCentreCompletionIsCategoryScoped()
|
|||||||
var controller = File.ReadAllText(Path.Combine(root, "Controllers/OnboardingController.cs"));
|
var controller = File.ReadAllText(Path.Combine(root, "Controllers/OnboardingController.cs"));
|
||||||
var reviewView = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceReviewCentre.cshtml"));
|
var reviewView = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceReviewCentre.cshtml"));
|
||||||
|
|
||||||
Assert(onboardingStory.Contains("LocationStageComplete = locationReview.IsComplete", StringComparison.Ordinal), "Location tile completion should come from the Location review service.");
|
Assert(onboardingStory.Contains("LocationStageComplete = locationComplete", StringComparison.Ordinal), "Location tile completion should come from scoped durable/review state.");
|
||||||
Assert(onboardingStory.Contains("AssetStageComplete = assetReview.IsComplete", StringComparison.Ordinal), "Asset tile completion should come from the Asset review service.");
|
Assert(onboardingStory.Contains("AssetStageComplete = assetComplete", StringComparison.Ordinal), "Asset tile completion should come from scoped durable/review state.");
|
||||||
Assert(onboardingStory.Contains("RelationshipStageComplete = relationshipReview.IsComplete", StringComparison.Ordinal), "Relationship tile completion should come from the Relationship review service.");
|
Assert(onboardingStory.Contains("RelationshipStageComplete = relationshipComplete", StringComparison.Ordinal), "Relationship tile completion should come from scoped durable/review state.");
|
||||||
Assert(onboardingStory.Contains("KnowledgeStageComplete = knowledgeReview.IsComplete", StringComparison.Ordinal), "Knowledge tile completion should come from the Knowledge review service.");
|
Assert(onboardingStory.Contains("KnowledgeStageComplete = knowledgeComplete", StringComparison.Ordinal), "Knowledge tile completion should come from scoped durable/review state.");
|
||||||
Assert(onboardingStory.Contains("NarrativeStageComplete = narrative.IsComplete", StringComparison.Ordinal), "Narrative tile completion should come from the Narrative review service.");
|
Assert(onboardingStory.Contains("NarrativeStageComplete = narrativeComplete", StringComparison.Ordinal), "Narrative tile completion should come from scoped durable/review state.");
|
||||||
Assert(onboardingStory.Contains("batch.LocationStageComplete = false;", StringComparison.Ordinal), "Resume should not hydrate Location completion from the single pipeline stage marker.");
|
Assert(onboardingStory.Contains("batch.LocationStageComplete = state.LocationReviewCompletedUtc is not null", StringComparison.Ordinal), "Resume should hydrate Location completion from durable state.");
|
||||||
Assert(onboardingStory.Contains("batch.AssetStageComplete = false;", StringComparison.Ordinal), "Resume should not hydrate Asset completion from the single pipeline stage marker.");
|
Assert(onboardingStory.Contains("batch.AssetStageComplete = state.AssetReviewCompletedUtc is not null", StringComparison.Ordinal), "Resume should hydrate Asset completion from durable state.");
|
||||||
Assert(onboardingStory.Contains("batch.RelationshipStageComplete = false;", StringComparison.Ordinal), "Resume should not hydrate Relationship completion from the single pipeline stage marker.");
|
Assert(onboardingStory.Contains("batch.RelationshipStageComplete = state.RelationshipReviewCompletedUtc is not null", StringComparison.Ordinal), "Resume should hydrate Relationship completion from durable state.");
|
||||||
Assert(onboardingStory.Contains("batch.KnowledgeStageComplete = false;", StringComparison.Ordinal), "Resume should not hydrate Knowledge completion from the single pipeline stage marker.");
|
Assert(onboardingStory.Contains("batch.KnowledgeStageComplete = state.KnowledgeReviewCompletedUtc is not null", StringComparison.Ordinal), "Resume should hydrate Knowledge completion from durable state.");
|
||||||
Assert(onboardingStory.Contains("batch.NarrativeStageComplete = false;", StringComparison.Ordinal), "Resume should not hydrate Narrative completion from the single pipeline stage marker.");
|
Assert(onboardingStory.Contains("batch.NarrativeStageComplete = state.NarrativeReviewCompletedUtc is not null", StringComparison.Ordinal), "Resume should hydrate Narrative completion from durable state.");
|
||||||
Assert(reviewView.Contains("Model.PipelineDashboard.RelationshipStageComplete", StringComparison.Ordinal), "Review Centre should render the scoped dashboard completion flags.");
|
Assert(reviewView.Contains("Model.PipelineDashboard.RelationshipStageComplete", StringComparison.Ordinal), "Review Centre should render the scoped dashboard completion flags.");
|
||||||
|
|
||||||
var locationGet = ExtractMethod(controller, "StoryIntelligenceLocations");
|
var locationGet = ExtractMethod(controller, "StoryIntelligenceLocations");
|
||||||
|
|||||||
@ -70,7 +70,13 @@ public sealed class StoryIntelligencePipelineRepository(ISqlConnectionFactory co
|
|||||||
request.CurrentReviewStage,
|
request.CurrentReviewStage,
|
||||||
request.Status,
|
request.Status,
|
||||||
request.CompletedUtc,
|
request.CompletedUtc,
|
||||||
request.LastRunID
|
request.LastRunID,
|
||||||
|
request.MarkCharacterReviewComplete,
|
||||||
|
request.MarkLocationReviewComplete,
|
||||||
|
request.MarkAssetReviewComplete,
|
||||||
|
request.MarkRelationshipReviewComplete,
|
||||||
|
request.MarkKnowledgeReviewComplete,
|
||||||
|
request.MarkNarrativeReviewComplete
|
||||||
},
|
},
|
||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +62,12 @@ public sealed class StoryIntelligenceBookPipelineState
|
|||||||
public string? CurrentReviewStage { get; init; }
|
public string? CurrentReviewStage { get; init; }
|
||||||
public string Status { get; init; } = StoryIntelligencePipelineStatuses.NeedsReview;
|
public string Status { get; init; } = StoryIntelligencePipelineStatuses.NeedsReview;
|
||||||
public DateTime? CompletedUtc { get; init; }
|
public DateTime? CompletedUtc { get; init; }
|
||||||
|
public DateTime? CharacterReviewCompletedUtc { get; init; }
|
||||||
|
public DateTime? LocationReviewCompletedUtc { get; init; }
|
||||||
|
public DateTime? AssetReviewCompletedUtc { get; init; }
|
||||||
|
public DateTime? RelationshipReviewCompletedUtc { get; init; }
|
||||||
|
public DateTime? KnowledgeReviewCompletedUtc { get; init; }
|
||||||
|
public DateTime? NarrativeReviewCompletedUtc { get; init; }
|
||||||
public int? LastRunID { get; init; }
|
public int? LastRunID { get; init; }
|
||||||
public DateTime CreatedUtc { get; init; }
|
public DateTime CreatedUtc { get; init; }
|
||||||
public DateTime UpdatedUtc { get; init; }
|
public DateTime UpdatedUtc { get; init; }
|
||||||
@ -130,6 +136,12 @@ public sealed class StoryIntelligenceBookPipelineSaveRequest
|
|||||||
public string Status { get; init; } = StoryIntelligencePipelineStatuses.NeedsReview;
|
public string Status { get; init; } = StoryIntelligencePipelineStatuses.NeedsReview;
|
||||||
public DateTime? CompletedUtc { get; init; }
|
public DateTime? CompletedUtc { get; init; }
|
||||||
public int? LastRunID { get; init; }
|
public int? LastRunID { get; init; }
|
||||||
|
public bool MarkCharacterReviewComplete { get; init; }
|
||||||
|
public bool MarkLocationReviewComplete { get; init; }
|
||||||
|
public bool MarkAssetReviewComplete { get; init; }
|
||||||
|
public bool MarkRelationshipReviewComplete { get; init; }
|
||||||
|
public bool MarkKnowledgeReviewComplete { get; init; }
|
||||||
|
public bool MarkNarrativeReviewComplete { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StoryIntelligenceCommittedRunSummary
|
public sealed class StoryIntelligenceCommittedRunSummary
|
||||||
|
|||||||
@ -1382,8 +1382,9 @@ public sealed class BookService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isInProgress = string.Equals(pipeline.Status, StoryIntelligencePipelineStatuses.InProgress, StringComparison.OrdinalIgnoreCase);
|
var isInProgress = string.Equals(pipeline.Status, StoryIntelligencePipelineStatuses.InProgress, StringComparison.OrdinalIgnoreCase);
|
||||||
var needsCharacterReview = string.Equals(pipeline.CurrentReviewStage, StoryIntelligencePipelineStages.CharacterReview, StringComparison.OrdinalIgnoreCase)
|
var needsCharacterReview = pipeline.CharacterReviewCompletedUtc is null
|
||||||
|| string.Equals(pipeline.CurrentStage, StoryIntelligencePipelineStages.CharacterReview, StringComparison.OrdinalIgnoreCase);
|
&& (string.Equals(pipeline.CurrentReviewStage, StoryIntelligencePipelineStages.CharacterReview, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(pipeline.CurrentStage, StoryIntelligencePipelineStages.CharacterReview, StringComparison.OrdinalIgnoreCase));
|
||||||
return new StoryIntelligenceBookActionViewModel
|
return new StoryIntelligenceBookActionViewModel
|
||||||
{
|
{
|
||||||
HasCanonicalSceneSources = hasCanonicalSceneSources,
|
HasCanonicalSceneSources = hasCanonicalSceneSources,
|
||||||
|
|||||||
@ -647,7 +647,13 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
var narrative = await narrativeReview.GetReviewAsync(batch);
|
var narrative = await narrativeReview.GetReviewAsync(batch);
|
||||||
var plotSynthesisRun = await wholeBookPlotIntelligence.GetCurrentAsync(batch.BookID);
|
var plotSynthesisRun = await wholeBookPlotIntelligence.GetCurrentAsync(batch.BookID);
|
||||||
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
var narrativePendingCount = narrative.Candidates.Count;
|
var characterComplete = batch.CharacterStageComplete || pipeline?.CharacterReviewCompletedUtc is not null || characterReview.IsComplete;
|
||||||
|
var locationComplete = batch.LocationStageComplete || pipeline?.LocationReviewCompletedUtc is not null || locationReview.IsComplete;
|
||||||
|
var assetComplete = batch.AssetStageComplete || pipeline?.AssetReviewCompletedUtc is not null || assetReview.IsComplete;
|
||||||
|
var relationshipComplete = batch.RelationshipStageComplete || pipeline?.RelationshipReviewCompletedUtc is not null || relationshipReview.IsComplete;
|
||||||
|
var knowledgeComplete = batch.KnowledgeStageComplete || pipeline?.KnowledgeReviewCompletedUtc is not null || knowledgeReview.IsComplete;
|
||||||
|
var narrativeComplete = batch.NarrativeStageComplete || pipeline?.NarrativeReviewCompletedUtc is not null || narrative.IsComplete;
|
||||||
|
var narrativePendingCount = narrativeComplete ? 0 : narrative.Candidates.Count;
|
||||||
var narrativeThreadCount = narrative.Candidates.Count(candidate =>
|
var narrativeThreadCount = narrative.Candidates.Count(candidate =>
|
||||||
string.Equals(candidate.CandidateType, StoryIntelligenceNarrativeCandidateTypes.Thread, StringComparison.OrdinalIgnoreCase));
|
string.Equals(candidate.CandidateType, StoryIntelligenceNarrativeCandidateTypes.Thread, StringComparison.OrdinalIgnoreCase));
|
||||||
var narrativeEventCount = narrative.Candidates.Sum(candidate => candidate.Events.Count);
|
var narrativeEventCount = narrative.Candidates.Sum(candidate => candidate.Events.Count);
|
||||||
@ -691,24 +697,24 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
SceneAnalysisComplete = pipeline is not null && SceneAnalysisComplete(pipeline),
|
SceneAnalysisComplete = pipeline is not null && SceneAnalysisComplete(pipeline),
|
||||||
CharactersIdentified = characterReview.Candidates.Count + batch.CharacterDecisions.Count,
|
CharactersIdentified = characterReview.Candidates.Count + batch.CharacterDecisions.Count,
|
||||||
CharactersCreatedOrLinked = batch.CharacterDecisions.Count(decision => decision.CreatedOrLinked),
|
CharactersCreatedOrLinked = batch.CharacterDecisions.Count(decision => decision.CreatedOrLinked),
|
||||||
CharacterStageComplete = characterReview.IsComplete,
|
CharacterStageComplete = characterComplete,
|
||||||
LocationsIdentified = locationReview.Candidates.Count + batch.LocationDecisions.Count,
|
LocationsIdentified = locationReview.Candidates.Count + batch.LocationDecisions.Count,
|
||||||
LocationsCreatedOrLinked = batch.LocationDecisions.Count(decision => decision.CreatedOrLinked),
|
LocationsCreatedOrLinked = batch.LocationDecisions.Count(decision => decision.CreatedOrLinked),
|
||||||
LocationStageComplete = locationReview.IsComplete,
|
LocationStageComplete = locationComplete,
|
||||||
AssetsIdentified = assetReview.Candidates.Count + batch.AssetDecisions.Count,
|
AssetsIdentified = assetReview.Candidates.Count + batch.AssetDecisions.Count,
|
||||||
AssetsCreatedOrLinked = batch.AssetDecisions.Count(decision => decision.CreatedOrLinked),
|
AssetsCreatedOrLinked = batch.AssetDecisions.Count(decision => decision.CreatedOrLinked),
|
||||||
AssetStageComplete = assetReview.IsComplete,
|
AssetStageComplete = assetComplete,
|
||||||
RelationshipsIdentified = relationshipReview.Candidates.Count + batch.RelationshipDecisions.Count,
|
RelationshipsIdentified = relationshipReview.Candidates.Count + batch.RelationshipDecisions.Count,
|
||||||
RelationshipsCreatedOrLinked = batch.RelationshipDecisions.Count(decision => decision.CreatedOrLinked),
|
RelationshipsCreatedOrLinked = batch.RelationshipDecisions.Count(decision => decision.CreatedOrLinked),
|
||||||
RelationshipStageComplete = relationshipReview.IsComplete,
|
RelationshipStageComplete = relationshipComplete,
|
||||||
KnowledgeIdentified = knowledgeReview.Candidates.Count + batch.KnowledgeDecisions.Count,
|
KnowledgeIdentified = knowledgeReview.Candidates.Count + batch.KnowledgeDecisions.Count,
|
||||||
KnowledgeCreatedOrLinked = batch.KnowledgeDecisions.Count(decision => decision.CreatedOrLinked),
|
KnowledgeCreatedOrLinked = batch.KnowledgeDecisions.Count(decision => decision.CreatedOrLinked),
|
||||||
KnowledgeStageComplete = knowledgeReview.IsComplete,
|
KnowledgeStageComplete = knowledgeComplete,
|
||||||
NarrativeCandidatesIdentified = narrative.Candidates.Count + (batch.LastNarrativeImportResult?.TotalReviewed ?? 0),
|
NarrativeCandidatesIdentified = narrative.Candidates.Count + (batch.LastNarrativeImportResult?.TotalReviewed ?? 0),
|
||||||
NarrativeCandidatesApplied = (batch.LastNarrativeImportResult?.PlotLinesCreated ?? 0)
|
NarrativeCandidatesApplied = (batch.LastNarrativeImportResult?.PlotLinesCreated ?? 0)
|
||||||
+ (batch.LastNarrativeImportResult?.PlotLinesMerged ?? 0)
|
+ (batch.LastNarrativeImportResult?.PlotLinesMerged ?? 0)
|
||||||
+ (batch.LastNarrativeImportResult?.ThreadsCreated ?? 0),
|
+ (batch.LastNarrativeImportResult?.ThreadsCreated ?? 0),
|
||||||
NarrativeStageComplete = narrative.IsComplete
|
NarrativeStageComplete = narrativeComplete
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1045,24 +1051,12 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
Items = batchItems
|
Items = batchItems
|
||||||
};
|
};
|
||||||
|
|
||||||
var characterStageComplete = HasCompletedStage(state, StoryIntelligencePipelineStages.CharacterImport)
|
batch.CharacterStageComplete = state.CharacterReviewCompletedUtc is not null;
|
||||||
|| state.CurrentStage is StoryIntelligencePipelineStages.LocationReview
|
batch.LocationStageComplete = state.LocationReviewCompletedUtc is not null;
|
||||||
or StoryIntelligencePipelineStages.LocationImport
|
batch.AssetStageComplete = state.AssetReviewCompletedUtc is not null;
|
||||||
or StoryIntelligencePipelineStages.AssetReview
|
batch.RelationshipStageComplete = state.RelationshipReviewCompletedUtc is not null;
|
||||||
or StoryIntelligencePipelineStages.AssetImport
|
batch.KnowledgeStageComplete = state.KnowledgeReviewCompletedUtc is not null;
|
||||||
or StoryIntelligencePipelineStages.RelationshipReview
|
batch.NarrativeStageComplete = state.NarrativeReviewCompletedUtc is not null;
|
||||||
or StoryIntelligencePipelineStages.RelationshipImport
|
|
||||||
or StoryIntelligencePipelineStages.KnowledgeReview
|
|
||||||
or StoryIntelligencePipelineStages.KnowledgeImport
|
|
||||||
or StoryIntelligencePipelineStages.NarrativeReview
|
|
||||||
or StoryIntelligencePipelineStages.NarrativeImport
|
|
||||||
or StoryIntelligencePipelineStages.Complete;
|
|
||||||
batch.CharacterStageComplete = characterStageComplete;
|
|
||||||
batch.LocationStageComplete = false;
|
|
||||||
batch.AssetStageComplete = false;
|
|
||||||
batch.RelationshipStageComplete = false;
|
|
||||||
batch.KnowledgeStageComplete = false;
|
|
||||||
batch.NarrativeStageComplete = false;
|
|
||||||
|
|
||||||
await batchStore.SaveAsync(batch);
|
await batchStore.SaveAsync(batch);
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,19 @@ public sealed class StoryIntelligenceAssetImportService(
|
|||||||
public async Task<StoryIntelligenceAssetReviewViewModel> BuildReviewAsync(OnboardingStoryIntelligenceBatch batch)
|
public async Task<StoryIntelligenceAssetReviewViewModel> BuildReviewAsync(OnboardingStoryIntelligenceBatch batch)
|
||||||
{
|
{
|
||||||
var data = await BuildCandidateDataAsync(batch);
|
var data = await BuildCandidateDataAsync(batch);
|
||||||
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
|
if (batch.AssetStageComplete || pipeline?.AssetReviewCompletedUtc is not null)
|
||||||
|
{
|
||||||
|
return new StoryIntelligenceAssetReviewViewModel
|
||||||
|
{
|
||||||
|
HasCommittedScenes = data.HasCommittedScenes,
|
||||||
|
CanImport = false,
|
||||||
|
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
||||||
|
IsComplete = data.HasCommittedScenes,
|
||||||
|
Candidates = []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var decidedKeys = batch.AssetDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var decidedKeys = batch.AssetDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
||||||
return new StoryIntelligenceAssetReviewViewModel
|
return new StoryIntelligenceAssetReviewViewModel
|
||||||
|
|||||||
@ -72,6 +72,19 @@ public sealed class StoryIntelligenceCharacterImportService(
|
|||||||
public async Task<StoryIntelligenceCharacterReviewViewModel> BuildReviewAsync(OnboardingStoryIntelligenceBatch batch)
|
public async Task<StoryIntelligenceCharacterReviewViewModel> BuildReviewAsync(OnboardingStoryIntelligenceBatch batch)
|
||||||
{
|
{
|
||||||
var data = await BuildCandidateDataAsync(batch);
|
var data = await BuildCandidateDataAsync(batch);
|
||||||
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
|
if (batch.CharacterStageComplete || pipeline?.CharacterReviewCompletedUtc is not null)
|
||||||
|
{
|
||||||
|
return new StoryIntelligenceCharacterReviewViewModel
|
||||||
|
{
|
||||||
|
HasCommittedScenes = data.HasCommittedScenes,
|
||||||
|
CanImport = false,
|
||||||
|
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
||||||
|
IsComplete = data.HasCommittedScenes,
|
||||||
|
Candidates = []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var decidedKeys = batch.CharacterDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var decidedKeys = batch.CharacterDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
||||||
var existingIndex = await BuildCharacterIndexAsync(batch.ProjectID);
|
var existingIndex = await BuildCharacterIndexAsync(batch.ProjectID);
|
||||||
|
|||||||
@ -34,13 +34,18 @@ public sealed class StoryIntelligenceKnowledgeImportService(
|
|||||||
var decidedKeys = batch.KnowledgeDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var decidedKeys = batch.KnowledgeDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var selectedStateIds = batch.KnowledgeSelections.ToDictionary(selection => selection.Key, selection => selection.KnowledgeStateID, StringComparer.OrdinalIgnoreCase);
|
var selectedStateIds = batch.KnowledgeSelections.ToDictionary(selection => selection.Key, selection => selection.KnowledgeStateID, StringComparer.OrdinalIgnoreCase);
|
||||||
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
||||||
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
|
if (batch.KnowledgeStageComplete || pipeline?.KnowledgeReviewCompletedUtc is not null)
|
||||||
|
{
|
||||||
|
visibleCandidates = [];
|
||||||
|
}
|
||||||
|
|
||||||
return new StoryIntelligenceKnowledgeReviewViewModel
|
return new StoryIntelligenceKnowledgeReviewViewModel
|
||||||
{
|
{
|
||||||
HasCommittedScenes = data.HasCommittedScenes,
|
HasCommittedScenes = data.HasCommittedScenes,
|
||||||
CanImport = visibleCandidates.Count > 0,
|
CanImport = visibleCandidates.Count > 0,
|
||||||
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
||||||
IsComplete = data.HasCommittedScenes && visibleCandidates.Count == 0,
|
IsComplete = data.HasCommittedScenes && (visibleCandidates.Count == 0 || batch.KnowledgeStageComplete || pipeline?.KnowledgeReviewCompletedUtc is not null),
|
||||||
KnowledgeStateOptions = lookupData.KnowledgeStates
|
KnowledgeStateOptions = lookupData.KnowledgeStates
|
||||||
.OrderBy(state => state.SortOrder)
|
.OrderBy(state => state.SortOrder)
|
||||||
.ThenBy(state => state.StateName)
|
.ThenBy(state => state.StateName)
|
||||||
|
|||||||
@ -70,6 +70,19 @@ public sealed class StoryIntelligenceLocationImportService(
|
|||||||
public async Task<StoryIntelligenceLocationReviewViewModel> BuildReviewAsync(OnboardingStoryIntelligenceBatch batch)
|
public async Task<StoryIntelligenceLocationReviewViewModel> BuildReviewAsync(OnboardingStoryIntelligenceBatch batch)
|
||||||
{
|
{
|
||||||
var data = await BuildCandidateDataAsync(batch);
|
var data = await BuildCandidateDataAsync(batch);
|
||||||
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
|
if (batch.LocationStageComplete || pipeline?.LocationReviewCompletedUtc is not null)
|
||||||
|
{
|
||||||
|
return new StoryIntelligenceLocationReviewViewModel
|
||||||
|
{
|
||||||
|
HasCommittedScenes = data.HasCommittedScenes,
|
||||||
|
CanImport = false,
|
||||||
|
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
||||||
|
IsComplete = data.HasCommittedScenes,
|
||||||
|
Candidates = []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var decidedKeys = batch.LocationDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var decidedKeys = batch.LocationDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
||||||
return new StoryIntelligenceLocationReviewViewModel
|
return new StoryIntelligenceLocationReviewViewModel
|
||||||
|
|||||||
@ -36,8 +36,11 @@ public sealed class StoryIntelligenceNarrativeReviewService(
|
|||||||
var rows = (await candidates.ListByBookAsync(batch.BookID))
|
var rows = (await candidates.ListByBookAsync(batch.BookID))
|
||||||
.Where(IsWholeBookCandidate)
|
.Where(IsWholeBookCandidate)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
|
var reviewComplete = batch.NarrativeStageComplete || pipeline?.NarrativeReviewCompletedUtc is not null;
|
||||||
var pending = rows
|
var pending = rows
|
||||||
.Where(candidate => string.Equals(candidate.Status, StoryIntelligenceNarrativeCandidateStatuses.Pending, StringComparison.OrdinalIgnoreCase))
|
.Where(candidate => !reviewComplete
|
||||||
|
&& string.Equals(candidate.Status, StoryIntelligenceNarrativeCandidateStatuses.Pending, StringComparison.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
var plotLines = await plots.ListPlotLinesAsync(batch.ProjectID);
|
var plotLines = await plots.ListPlotLinesAsync(batch.ProjectID);
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ public sealed class StoryIntelligenceNarrativeReviewService(
|
|||||||
{
|
{
|
||||||
HasCommittedScenes = await HasCommittedScenesAsync(batch),
|
HasCommittedScenes = await HasCommittedScenesAsync(batch),
|
||||||
CanImport = pending.Count > 0,
|
CanImport = pending.Count > 0,
|
||||||
IsComplete = rows.Count > 0 && pending.Count == 0,
|
IsComplete = reviewComplete || (rows.Count > 0 && pending.Count == 0),
|
||||||
PlotLineOptions = plotLines
|
PlotLineOptions = plotLines
|
||||||
.OrderBy(plotLine => plotLine.SortOrder)
|
.OrderBy(plotLine => plotLine.SortOrder)
|
||||||
.ThenBy(plotLine => plotLine.PlotLineName)
|
.ThenBy(plotLine => plotLine.PlotLineName)
|
||||||
|
|||||||
@ -108,7 +108,8 @@ public sealed class StoryIntelligencePipelineStateService(
|
|||||||
CurrentReviewStage = StoryIntelligencePipelineStages.LocationReview,
|
CurrentReviewStage = StoryIntelligencePipelineStages.LocationReview,
|
||||||
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
||||||
CompletedUtc = null,
|
CompletedUtc = null,
|
||||||
LastRunID = null
|
LastRunID = null,
|
||||||
|
MarkCharacterReviewComplete = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +124,8 @@ public sealed class StoryIntelligencePipelineStateService(
|
|||||||
CurrentReviewStage = StoryIntelligencePipelineStages.AssetReview,
|
CurrentReviewStage = StoryIntelligencePipelineStages.AssetReview,
|
||||||
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
||||||
CompletedUtc = null,
|
CompletedUtc = null,
|
||||||
LastRunID = null
|
LastRunID = null,
|
||||||
|
MarkLocationReviewComplete = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +140,8 @@ public sealed class StoryIntelligencePipelineStateService(
|
|||||||
CurrentReviewStage = StoryIntelligencePipelineStages.RelationshipReview,
|
CurrentReviewStage = StoryIntelligencePipelineStages.RelationshipReview,
|
||||||
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
||||||
CompletedUtc = null,
|
CompletedUtc = null,
|
||||||
LastRunID = null
|
LastRunID = null,
|
||||||
|
MarkAssetReviewComplete = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +156,8 @@ public sealed class StoryIntelligencePipelineStateService(
|
|||||||
CurrentReviewStage = StoryIntelligencePipelineStages.KnowledgeReview,
|
CurrentReviewStage = StoryIntelligencePipelineStages.KnowledgeReview,
|
||||||
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
||||||
CompletedUtc = null,
|
CompletedUtc = null,
|
||||||
LastRunID = null
|
LastRunID = null,
|
||||||
|
MarkRelationshipReviewComplete = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +172,8 @@ public sealed class StoryIntelligencePipelineStateService(
|
|||||||
CurrentReviewStage = StoryIntelligencePipelineStages.NarrativeReview,
|
CurrentReviewStage = StoryIntelligencePipelineStages.NarrativeReview,
|
||||||
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
Status = StoryIntelligencePipelineStatuses.NeedsReview,
|
||||||
CompletedUtc = null,
|
CompletedUtc = null,
|
||||||
LastRunID = null
|
LastRunID = null,
|
||||||
|
MarkKnowledgeReviewComplete = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +188,8 @@ public sealed class StoryIntelligencePipelineStateService(
|
|||||||
CurrentReviewStage = null,
|
CurrentReviewStage = null,
|
||||||
Status = StoryIntelligencePipelineStatuses.Complete,
|
Status = StoryIntelligencePipelineStatuses.Complete,
|
||||||
CompletedUtc = DateTime.UtcNow,
|
CompletedUtc = DateTime.UtcNow,
|
||||||
LastRunID = null
|
LastRunID = null,
|
||||||
|
MarkNarrativeReviewComplete = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,12 +34,18 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
|||||||
var selectedTypeIds = batch.RelationshipSelections.ToDictionary(selection => selection.Key, selection => selection.RelationshipTypeID, StringComparer.OrdinalIgnoreCase);
|
var selectedTypeIds = batch.RelationshipSelections.ToDictionary(selection => selection.Key, selection => selection.RelationshipTypeID, StringComparer.OrdinalIgnoreCase);
|
||||||
var decidedKeys = batch.RelationshipDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
var decidedKeys = batch.RelationshipDecisions.Select(decision => decision.Key).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
var visibleCandidates = data.Candidates.Where(candidate => !decidedKeys.Contains(candidate.Key)).ToList();
|
||||||
|
var pipeline = await pipelineState.GetForBookAsync(batch.BookID, batch.UserID);
|
||||||
|
if (batch.RelationshipStageComplete || pipeline?.RelationshipReviewCompletedUtc is not null)
|
||||||
|
{
|
||||||
|
visibleCandidates = [];
|
||||||
|
}
|
||||||
|
|
||||||
return new StoryIntelligenceRelationshipReviewViewModel
|
return new StoryIntelligenceRelationshipReviewViewModel
|
||||||
{
|
{
|
||||||
HasCommittedScenes = data.HasCommittedScenes,
|
HasCommittedScenes = data.HasCommittedScenes,
|
||||||
CanImport = visibleCandidates.Count > 0,
|
CanImport = visibleCandidates.Count > 0,
|
||||||
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
AlreadyLinkedCount = data.AlreadyLinkedCount,
|
||||||
IsComplete = data.HasCommittedScenes && visibleCandidates.Count == 0,
|
IsComplete = data.HasCommittedScenes && (visibleCandidates.Count == 0 || batch.RelationshipStageComplete || pipeline?.RelationshipReviewCompletedUtc is not null),
|
||||||
RelationshipTypeOptions = typeOptions,
|
RelationshipTypeOptions = typeOptions,
|
||||||
Candidates = visibleCandidates.Select(candidate => new StoryIntelligenceRelationshipReviewCandidateViewModel
|
Candidates = visibleCandidates.Select(candidate => new StoryIntelligenceRelationshipReviewCandidateViewModel
|
||||||
{
|
{
|
||||||
|
|||||||
@ -281,10 +281,7 @@ public sealed class StoryIntelligenceService(
|
|||||||
StoryIntelligencePipelineStages.LocationReview => "Review Centre",
|
StoryIntelligencePipelineStages.LocationReview => "Review Centre",
|
||||||
StoryIntelligencePipelineStages.CharacterReview => "Character Resolution Needed",
|
StoryIntelligencePipelineStages.CharacterReview => "Character Resolution Needed",
|
||||||
StoryIntelligencePipelineStages.SceneReview => "Needs Scene Review",
|
StoryIntelligencePipelineStages.SceneReview => "Needs Scene Review",
|
||||||
_ when StoryIntelligenceReadyForKnowledge(state) => "Review Centre",
|
_ when StoryIntelligenceCharacterReviewComplete(state) => "Review Centre",
|
||||||
_ when StoryIntelligenceReadyForRelationships(state) => "Review Centre",
|
|
||||||
_ when StoryIntelligenceReadyForAssets(state) => "Review Centre",
|
|
||||||
_ when StoryIntelligenceReadyForLocations(state) => "Review Centre",
|
|
||||||
_ => string.Equals(state.Status, StoryIntelligencePipelineStatuses.InProgress, StringComparison.OrdinalIgnoreCase)
|
_ => string.Equals(state.Status, StoryIntelligencePipelineStatuses.InProgress, StringComparison.OrdinalIgnoreCase)
|
||||||
? "Story Intelligence In Progress"
|
? "Story Intelligence In Progress"
|
||||||
: "Story Intelligence Needs Review"
|
: "Story Intelligence Needs Review"
|
||||||
@ -304,36 +301,12 @@ public sealed class StoryIntelligenceService(
|
|||||||
StoryIntelligencePipelineStages.LocationReview => "Open the Review Centre and choose which saved category to handle.",
|
StoryIntelligencePipelineStages.LocationReview => "Open the Review Centre and choose which saved category to handle.",
|
||||||
StoryIntelligencePipelineStages.CharacterReview => "Resolve character identities before reviewing dependent story intelligence.",
|
StoryIntelligencePipelineStages.CharacterReview => "Resolve character identities before reviewing dependent story intelligence.",
|
||||||
StoryIntelligencePipelineStages.SceneReview => "Story Intelligence analysis has not finished for this book.",
|
StoryIntelligencePipelineStages.SceneReview => "Story Intelligence analysis has not finished for this book.",
|
||||||
_ when StoryIntelligenceReadyForKnowledge(state) => "Open the Review Centre and choose which saved category to handle.",
|
_ when StoryIntelligenceCharacterReviewComplete(state) => "Open the Review Centre and choose which saved category to handle.",
|
||||||
_ when StoryIntelligenceReadyForRelationships(state) => "Open the Review Centre and choose which saved category to handle.",
|
|
||||||
_ when StoryIntelligenceReadyForAssets(state) => "Open the Review Centre and choose which saved category to handle.",
|
|
||||||
_ when StoryIntelligenceReadyForLocations(state) => "Open the Review Centre and choose which saved category to handle.",
|
|
||||||
_ => "Open the Review Centre and choose which saved category to handle."
|
_ => "Open the Review Centre and choose which saved category to handle."
|
||||||
};
|
};
|
||||||
|
|
||||||
private static bool StoryIntelligenceReadyForKnowledge(StoryIntelligenceBookPipelineState state)
|
private static bool StoryIntelligenceCharacterReviewComplete(StoryIntelligenceBookPipelineState state)
|
||||||
=> string.Equals(state.LastCompletedStage, StoryIntelligencePipelineStages.RelationshipImport, StringComparison.OrdinalIgnoreCase)
|
=> state.CharacterReviewCompletedUtc is not null;
|
||||||
|| state.CurrentStage is StoryIntelligencePipelineStages.KnowledgeReview or StoryIntelligencePipelineStages.KnowledgeImport;
|
|
||||||
|
|
||||||
private static bool StoryIntelligenceReadyForRelationships(StoryIntelligenceBookPipelineState state)
|
|
||||||
=> string.Equals(state.LastCompletedStage, StoryIntelligencePipelineStages.AssetImport, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| state.CurrentStage is StoryIntelligencePipelineStages.RelationshipReview or StoryIntelligencePipelineStages.RelationshipImport
|
|
||||||
or StoryIntelligencePipelineStages.KnowledgeReview or StoryIntelligencePipelineStages.KnowledgeImport;
|
|
||||||
|
|
||||||
private static bool StoryIntelligenceReadyForAssets(StoryIntelligenceBookPipelineState state)
|
|
||||||
=> string.Equals(state.LastCompletedStage, StoryIntelligencePipelineStages.LocationImport, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| state.CurrentStage is StoryIntelligencePipelineStages.AssetReview or StoryIntelligencePipelineStages.AssetImport
|
|
||||||
or StoryIntelligencePipelineStages.RelationshipReview or StoryIntelligencePipelineStages.RelationshipImport
|
|
||||||
or StoryIntelligencePipelineStages.KnowledgeReview or StoryIntelligencePipelineStages.KnowledgeImport
|
|
||||||
|| (string.Equals(state.CurrentStage, StoryIntelligencePipelineStages.Complete, StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !string.Equals(state.LastCompletedStage, StoryIntelligencePipelineStages.KnowledgeImport, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
private static bool StoryIntelligenceReadyForLocations(StoryIntelligenceBookPipelineState state)
|
|
||||||
=> string.Equals(state.LastCompletedStage, StoryIntelligencePipelineStages.CharacterImport, StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| state.CurrentStage is StoryIntelligencePipelineStages.LocationReview or StoryIntelligencePipelineStages.LocationImport
|
|
||||||
or StoryIntelligencePipelineStages.AssetReview or StoryIntelligencePipelineStages.AssetImport
|
|
||||||
or StoryIntelligencePipelineStages.RelationshipReview or StoryIntelligencePipelineStages.RelationshipImport
|
|
||||||
or StoryIntelligencePipelineStages.KnowledgeReview or StoryIntelligencePipelineStages.KnowledgeImport;
|
|
||||||
|
|
||||||
private static string FormatElapsed(TimeSpan elapsed)
|
private static string FormatElapsed(TimeSpan elapsed)
|
||||||
=> elapsed.TotalMinutes < 1
|
=> elapsed.TotalMinutes < 1
|
||||||
|
|||||||
@ -0,0 +1,255 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceBookPipelines', N'CharacterReviewCompletedUtc') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceBookPipelines ADD CharacterReviewCompletedUtc datetime2 NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceBookPipelines', N'LocationReviewCompletedUtc') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceBookPipelines ADD LocationReviewCompletedUtc datetime2 NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceBookPipelines', N'AssetReviewCompletedUtc') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceBookPipelines ADD AssetReviewCompletedUtc datetime2 NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceBookPipelines', N'RelationshipReviewCompletedUtc') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceBookPipelines ADD RelationshipReviewCompletedUtc datetime2 NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceBookPipelines', N'KnowledgeReviewCompletedUtc') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceBookPipelines ADD KnowledgeReviewCompletedUtc datetime2 NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF COL_LENGTH(N'dbo.StoryIntelligenceBookPipelines', N'NarrativeReviewCompletedUtc') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.StoryIntelligenceBookPipelines ADD NarrativeReviewCompletedUtc datetime2 NULL;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET CharacterReviewCompletedUtc = COALESCE(CharacterReviewCompletedUtc, UpdatedUtc, SYSUTCDATETIME())
|
||||||
|
WHERE CharacterReviewCompletedUtc IS NULL
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
LastCompletedStage IN
|
||||||
|
(
|
||||||
|
N'CharacterImport',
|
||||||
|
N'LocationImport',
|
||||||
|
N'AssetImport',
|
||||||
|
N'RelationshipImport',
|
||||||
|
N'KnowledgeImport',
|
||||||
|
N'NarrativeImport'
|
||||||
|
)
|
||||||
|
OR CurrentStage IN
|
||||||
|
(
|
||||||
|
N'LocationReview',
|
||||||
|
N'LocationImport',
|
||||||
|
N'AssetReview',
|
||||||
|
N'AssetImport',
|
||||||
|
N'RelationshipReview',
|
||||||
|
N'RelationshipImport',
|
||||||
|
N'KnowledgeReview',
|
||||||
|
N'KnowledgeImport',
|
||||||
|
N'NarrativeReview',
|
||||||
|
N'NarrativeImport',
|
||||||
|
N'Complete'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET NarrativeReviewCompletedUtc = COALESCE(NarrativeReviewCompletedUtc, CompletedUtc, UpdatedUtc, SYSUTCDATETIME())
|
||||||
|
WHERE NarrativeReviewCompletedUtc IS NULL
|
||||||
|
AND LastCompletedStage = N'NarrativeImport'
|
||||||
|
AND Status = N'Complete';
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET LocationReviewCompletedUtc = COALESCE(LocationReviewCompletedUtc, UpdatedUtc, SYSUTCDATETIME())
|
||||||
|
WHERE LocationReviewCompletedUtc IS NULL
|
||||||
|
AND LastCompletedStage = N'LocationImport';
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET AssetReviewCompletedUtc = COALESCE(AssetReviewCompletedUtc, UpdatedUtc, SYSUTCDATETIME())
|
||||||
|
WHERE AssetReviewCompletedUtc IS NULL
|
||||||
|
AND LastCompletedStage = N'AssetImport';
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET RelationshipReviewCompletedUtc = COALESCE(RelationshipReviewCompletedUtc, UpdatedUtc, SYSUTCDATETIME())
|
||||||
|
WHERE RelationshipReviewCompletedUtc IS NULL
|
||||||
|
AND LastCompletedStage = N'RelationshipImport';
|
||||||
|
GO
|
||||||
|
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET KnowledgeReviewCompletedUtc = COALESCE(KnowledgeReviewCompletedUtc, UpdatedUtc, SYSUTCDATETIME())
|
||||||
|
WHERE KnowledgeReviewCompletedUtc IS NULL
|
||||||
|
AND LastCompletedStage = N'KnowledgeImport';
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceBookPipeline_GetByBook
|
||||||
|
@BookID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT p.StoryIntelligenceBookPipelineID, p.ProjectID, project.ProjectName,
|
||||||
|
p.BookID, book.BookTitle, book.Subtitle AS BookSubtitle,
|
||||||
|
p.CurrentStage, p.LastCompletedStage, p.CurrentReviewStage, p.Status,
|
||||||
|
p.CompletedUtc, p.CharacterReviewCompletedUtc, p.LocationReviewCompletedUtc,
|
||||||
|
p.AssetReviewCompletedUtc, p.RelationshipReviewCompletedUtc, p.KnowledgeReviewCompletedUtc,
|
||||||
|
p.NarrativeReviewCompletedUtc, p.LastRunID, p.CreatedUtc, p.UpdatedUtc
|
||||||
|
FROM dbo.StoryIntelligenceBookPipelines p
|
||||||
|
INNER JOIN dbo.Projects project ON project.ProjectID = p.ProjectID
|
||||||
|
INNER JOIN dbo.Books book ON book.BookID = p.BookID
|
||||||
|
WHERE p.BookID = @BookID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceBookPipeline_Upsert
|
||||||
|
@ProjectID int,
|
||||||
|
@BookID int,
|
||||||
|
@CurrentStage nvarchar(80),
|
||||||
|
@LastCompletedStage nvarchar(80) = NULL,
|
||||||
|
@CurrentReviewStage nvarchar(80) = NULL,
|
||||||
|
@Status nvarchar(40),
|
||||||
|
@CompletedUtc datetime2 = NULL,
|
||||||
|
@LastRunID int = NULL,
|
||||||
|
@MarkCharacterReviewComplete bit = 0,
|
||||||
|
@MarkLocationReviewComplete bit = 0,
|
||||||
|
@MarkAssetReviewComplete bit = 0,
|
||||||
|
@MarkRelationshipReviewComplete bit = 0,
|
||||||
|
@MarkKnowledgeReviewComplete bit = 0,
|
||||||
|
@MarkNarrativeReviewComplete bit = 0
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM dbo.StoryIntelligenceBookPipelines WHERE BookID = @BookID)
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.StoryIntelligenceBookPipelines
|
||||||
|
SET ProjectID = @ProjectID,
|
||||||
|
CurrentStage = @CurrentStage,
|
||||||
|
LastCompletedStage = @LastCompletedStage,
|
||||||
|
CurrentReviewStage = @CurrentReviewStage,
|
||||||
|
Status = @Status,
|
||||||
|
CompletedUtc = @CompletedUtc,
|
||||||
|
LastRunID = COALESCE(@LastRunID, LastRunID),
|
||||||
|
CharacterReviewCompletedUtc = CASE WHEN @MarkCharacterReviewComplete = 1 THEN COALESCE(CharacterReviewCompletedUtc, SYSUTCDATETIME()) ELSE CharacterReviewCompletedUtc END,
|
||||||
|
LocationReviewCompletedUtc = CASE WHEN @MarkLocationReviewComplete = 1 THEN COALESCE(LocationReviewCompletedUtc, SYSUTCDATETIME()) ELSE LocationReviewCompletedUtc END,
|
||||||
|
AssetReviewCompletedUtc = CASE WHEN @MarkAssetReviewComplete = 1 THEN COALESCE(AssetReviewCompletedUtc, SYSUTCDATETIME()) ELSE AssetReviewCompletedUtc END,
|
||||||
|
RelationshipReviewCompletedUtc = CASE WHEN @MarkRelationshipReviewComplete = 1 THEN COALESCE(RelationshipReviewCompletedUtc, SYSUTCDATETIME()) ELSE RelationshipReviewCompletedUtc END,
|
||||||
|
KnowledgeReviewCompletedUtc = CASE WHEN @MarkKnowledgeReviewComplete = 1 THEN COALESCE(KnowledgeReviewCompletedUtc, SYSUTCDATETIME()) ELSE KnowledgeReviewCompletedUtc END,
|
||||||
|
NarrativeReviewCompletedUtc = CASE WHEN @MarkNarrativeReviewComplete = 1 THEN COALESCE(NarrativeReviewCompletedUtc, SYSUTCDATETIME()) ELSE NarrativeReviewCompletedUtc END,
|
||||||
|
UpdatedUtc = SYSUTCDATETIME()
|
||||||
|
WHERE BookID = @BookID;
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT dbo.StoryIntelligenceBookPipelines
|
||||||
|
(ProjectID, BookID, CurrentStage, LastCompletedStage, CurrentReviewStage, Status, CompletedUtc, LastRunID,
|
||||||
|
CharacterReviewCompletedUtc, LocationReviewCompletedUtc, AssetReviewCompletedUtc,
|
||||||
|
RelationshipReviewCompletedUtc, KnowledgeReviewCompletedUtc, NarrativeReviewCompletedUtc)
|
||||||
|
VALUES
|
||||||
|
(@ProjectID, @BookID, @CurrentStage, @LastCompletedStage, @CurrentReviewStage, @Status, @CompletedUtc, @LastRunID,
|
||||||
|
CASE WHEN @MarkCharacterReviewComplete = 1 THEN SYSUTCDATETIME() ELSE NULL END,
|
||||||
|
CASE WHEN @MarkLocationReviewComplete = 1 THEN SYSUTCDATETIME() ELSE NULL END,
|
||||||
|
CASE WHEN @MarkAssetReviewComplete = 1 THEN SYSUTCDATETIME() ELSE NULL END,
|
||||||
|
CASE WHEN @MarkRelationshipReviewComplete = 1 THEN SYSUTCDATETIME() ELSE NULL END,
|
||||||
|
CASE WHEN @MarkKnowledgeReviewComplete = 1 THEN SYSUTCDATETIME() ELSE NULL END,
|
||||||
|
CASE WHEN @MarkNarrativeReviewComplete = 1 THEN SYSUTCDATETIME() ELSE NULL END);
|
||||||
|
END;
|
||||||
|
|
||||||
|
EXEC dbo.StoryIntelligenceBookPipeline_GetByBook @BookID = @BookID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceBookPipeline_GetByBookForUser
|
||||||
|
@BookID int,
|
||||||
|
@UserID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT p.StoryIntelligenceBookPipelineID, p.ProjectID, project.ProjectName,
|
||||||
|
p.BookID, book.BookTitle, book.Subtitle AS BookSubtitle,
|
||||||
|
p.CurrentStage, p.LastCompletedStage, p.CurrentReviewStage, p.Status,
|
||||||
|
p.CompletedUtc, p.CharacterReviewCompletedUtc, p.LocationReviewCompletedUtc,
|
||||||
|
p.AssetReviewCompletedUtc, p.RelationshipReviewCompletedUtc, p.KnowledgeReviewCompletedUtc,
|
||||||
|
p.NarrativeReviewCompletedUtc, p.LastRunID, p.CreatedUtc, p.UpdatedUtc
|
||||||
|
FROM dbo.StoryIntelligenceBookPipelines p
|
||||||
|
INNER JOIN dbo.Projects project ON project.ProjectID = p.ProjectID
|
||||||
|
INNER JOIN dbo.Books book ON book.BookID = p.BookID
|
||||||
|
INNER JOIN dbo.ProjectUserAccess access ON access.ProjectID = p.ProjectID
|
||||||
|
AND access.UserID = @UserID
|
||||||
|
AND access.IsActive = 1
|
||||||
|
WHERE p.BookID = @BookID
|
||||||
|
AND book.IsArchived = 0
|
||||||
|
AND project.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceBookPipeline_GetByIDForUser
|
||||||
|
@StoryIntelligenceBookPipelineID int,
|
||||||
|
@UserID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT p.StoryIntelligenceBookPipelineID, p.ProjectID, project.ProjectName,
|
||||||
|
p.BookID, book.BookTitle, book.Subtitle AS BookSubtitle,
|
||||||
|
p.CurrentStage, p.LastCompletedStage, p.CurrentReviewStage, p.Status,
|
||||||
|
p.CompletedUtc, p.CharacterReviewCompletedUtc, p.LocationReviewCompletedUtc,
|
||||||
|
p.AssetReviewCompletedUtc, p.RelationshipReviewCompletedUtc, p.KnowledgeReviewCompletedUtc,
|
||||||
|
p.NarrativeReviewCompletedUtc, p.LastRunID, p.CreatedUtc, p.UpdatedUtc
|
||||||
|
FROM dbo.StoryIntelligenceBookPipelines p
|
||||||
|
INNER JOIN dbo.Projects project ON project.ProjectID = p.ProjectID
|
||||||
|
INNER JOIN dbo.Books book ON book.BookID = p.BookID
|
||||||
|
INNER JOIN dbo.ProjectUserAccess access ON access.ProjectID = p.ProjectID
|
||||||
|
AND access.UserID = @UserID
|
||||||
|
AND access.IsActive = 1
|
||||||
|
WHERE p.StoryIntelligenceBookPipelineID = @StoryIntelligenceBookPipelineID
|
||||||
|
AND book.IsArchived = 0
|
||||||
|
AND project.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceBookPipeline_ListForUser
|
||||||
|
@UserID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT p.StoryIntelligenceBookPipelineID, p.ProjectID, project.ProjectName,
|
||||||
|
p.BookID, book.BookTitle, book.Subtitle AS BookSubtitle,
|
||||||
|
p.CurrentStage, p.LastCompletedStage, p.CurrentReviewStage, p.Status,
|
||||||
|
p.CompletedUtc, p.CharacterReviewCompletedUtc, p.LocationReviewCompletedUtc,
|
||||||
|
p.AssetReviewCompletedUtc, p.RelationshipReviewCompletedUtc, p.KnowledgeReviewCompletedUtc,
|
||||||
|
p.NarrativeReviewCompletedUtc, p.LastRunID, p.CreatedUtc, p.UpdatedUtc
|
||||||
|
FROM dbo.StoryIntelligenceBookPipelines p
|
||||||
|
INNER JOIN dbo.Projects project ON project.ProjectID = p.ProjectID
|
||||||
|
INNER JOIN dbo.Books book ON book.BookID = p.BookID
|
||||||
|
INNER JOIN dbo.ProjectUserAccess access ON access.ProjectID = p.ProjectID
|
||||||
|
AND access.UserID = @UserID
|
||||||
|
AND access.IsActive = 1
|
||||||
|
WHERE book.IsArchived = 0
|
||||||
|
AND project.IsArchived = 0
|
||||||
|
ORDER BY
|
||||||
|
CASE p.Status WHEN N'NeedsReview' THEN 0 WHEN N'InProgress' THEN 1 ELSE 2 END,
|
||||||
|
p.UpdatedUtc DESC;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
Loading…
x
Reference in New Issue
Block a user