Route completed review modules to canonical pages
This commit is contained in:
parent
07113f9b36
commit
0839b8e13d
@ -282,6 +282,7 @@ var tests = new (string Name, Action Test)[]
|
|||||||
("Story Intelligence Review Centre uses bounded summary query", StoryIntelligenceReviewCentreUsesBoundedSummaryQuery),
|
("Story Intelligence Review Centre uses bounded summary query", StoryIntelligenceReviewCentreUsesBoundedSummaryQuery),
|
||||||
("Story Intelligence detailed review GETs use selected module paths", StoryIntelligenceDetailedReviewGetsUseSelectedModulePaths),
|
("Story Intelligence detailed review GETs use selected module paths", StoryIntelligenceDetailedReviewGetsUseSelectedModulePaths),
|
||||||
("Story Intelligence completed Character review remains inspectable", StoryIntelligenceCompletedCharacterReviewRemainsInspectable),
|
("Story Intelligence completed Character review remains inspectable", StoryIntelligenceCompletedCharacterReviewRemainsInspectable),
|
||||||
|
("Story Intelligence Review Centre completed modules use canonical destinations", StoryIntelligenceReviewCentreCompletedModulesUseCanonicalDestinations),
|
||||||
("Story Intelligence detailed reviews read persisted candidate pages", StoryIntelligenceDetailedReviewsReadPersistedCandidatePages),
|
("Story Intelligence detailed reviews read persisted candidate pages", StoryIntelligenceDetailedReviewsReadPersistedCandidatePages),
|
||||||
("Story Intelligence Job 6 uses bounded concurrent chapter execution", StoryIntelligenceJob6UsesBoundedConcurrentChapterExecution),
|
("Story Intelligence Job 6 uses bounded concurrent chapter execution", StoryIntelligenceJob6UsesBoundedConcurrentChapterExecution),
|
||||||
("Story Intelligence global progress uses whole run scene totals", StoryIntelligenceGlobalProgressUsesWholeRunSceneTotals),
|
("Story Intelligence global progress uses whole run scene totals", StoryIntelligenceGlobalProgressUsesWholeRunSceneTotals),
|
||||||
@ -728,7 +729,8 @@ static void StoryIntelligenceReviewCentreCountsOnlyNeedsAttentionCategories()
|
|||||||
Assert(viewModel.Contains("CharacterNeedsAttentionCount => CharacterReview.SummaryNeedsAttentionCount ?? CharacterReview.Candidates.Count", StringComparison.Ordinal), "Character count should prefer lightweight summary counts.");
|
Assert(viewModel.Contains("CharacterNeedsAttentionCount => CharacterReview.SummaryNeedsAttentionCount ?? CharacterReview.Candidates.Count", StringComparison.Ordinal), "Character count should prefer lightweight summary counts.");
|
||||||
Assert(viewModel.Contains("LocationNeedsAttentionCount => LocationReview.SummaryNeedsAttentionCount ?? LocationReview.Candidates.Count", StringComparison.Ordinal), "Location count should prefer lightweight summary counts.");
|
Assert(viewModel.Contains("LocationNeedsAttentionCount => LocationReview.SummaryNeedsAttentionCount ?? LocationReview.Candidates.Count", StringComparison.Ordinal), "Location count should prefer lightweight summary counts.");
|
||||||
Assert(viewModel.Contains("NarrativeNeedsAttentionCount => NarrativeReview.SummaryNeedsAttentionCount ?? NarrativeReview.Candidates.Count", StringComparison.Ordinal), "Narrative count should prefer lightweight summary counts.");
|
Assert(viewModel.Contains("NarrativeNeedsAttentionCount => NarrativeReview.SummaryNeedsAttentionCount ?? NarrativeReview.Candidates.Count", StringComparison.Ordinal), "Narrative count should prefer lightweight summary counts.");
|
||||||
Assert(centre.Contains("Scene Metrics", StringComparison.Ordinal) && centre.Contains("TotalAnalysedScenes", StringComparison.Ordinal), "Metrics should report analysed scene count.");
|
Assert(centre.Contains("Scene Metrics", StringComparison.Ordinal) && centre.Contains("MetricValueCount", StringComparison.Ordinal), "Metrics should report actual metric availability.");
|
||||||
|
Assert(!centre.Contains("Review Metrics", StringComparison.Ordinal), "Metrics should not present a fake review workflow.");
|
||||||
Assert(!summary.Contains("Locations identified", StringComparison.Ordinal), "Summary should not present total identified rows as review workload.");
|
Assert(!summary.Contains("Locations identified", StringComparison.Ordinal), "Summary should not present total identified rows as review workload.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,7 +759,7 @@ static void StoryIntelligenceReviewCentreUsesIndependentTileGrid()
|
|||||||
Assert(centre.Contains("can be reviewed in any order", StringComparison.Ordinal), "Header should communicate independent review order.");
|
Assert(centre.Contains("can be reviewed in any order", StringComparison.Ordinal), "Header should communicate independent review order.");
|
||||||
Assert(centre.Contains("Locked until Characters are resolved", StringComparison.Ordinal), "Locked dependent tiles should be visible and clearly locked.");
|
Assert(centre.Contains("Locked until Characters are resolved", StringComparison.Ordinal), "Locked dependent tiles should be visible and clearly locked.");
|
||||||
Assert(centre.Contains(">Locked</span>", StringComparison.Ordinal), "Locked tile action should render as non-clickable text.");
|
Assert(centre.Contains(">Locked</span>", StringComparison.Ordinal), "Locked tile action should render as non-clickable text.");
|
||||||
Assert(centre.Contains("Available", StringComparison.Ordinal) && centre.Contains("View Metrics", StringComparison.Ordinal), "Scene Metrics should be available, not a mandatory review workload.");
|
Assert(centre.Contains("No metrics available", StringComparison.Ordinal) && centre.Contains("View on Timeline", StringComparison.Ordinal), "Scene Metrics should distinguish unavailable metrics from Timeline navigation.");
|
||||||
Assert(!centre.Contains("Next", StringComparison.Ordinal) && !centre.Contains("Previous", StringComparison.Ordinal), "Review Centre should not contain wizard navigation.");
|
Assert(!centre.Contains("Next", StringComparison.Ordinal) && !centre.Contains("Previous", StringComparison.Ordinal), "Review Centre should not contain wizard navigation.");
|
||||||
Assert(!centre.Contains("Core Import", StringComparison.Ordinal) && !centre.Contains("Analyse Manuscript", StringComparison.Ordinal), "Review Centre should not show phase panels.");
|
Assert(!centre.Contains("Core Import", StringComparison.Ordinal) && !centre.Contains("Analyse Manuscript", StringComparison.Ordinal), "Review Centre should not show phase panels.");
|
||||||
|
|
||||||
@ -1174,6 +1176,53 @@ static void StoryIntelligenceCompletedCharacterReviewRemainsInspectable()
|
|||||||
Assert(view.Contains("AliasTargetOptions", StringComparison.Ordinal), "Completed Character page should use canonical Character data when no pending candidates exist.");
|
Assert(view.Contains("AliasTargetOptions", StringComparison.Ordinal), "Completed Character page should use canonical Character data when no pending candidates exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void StoryIntelligenceReviewCentreCompletedModulesUseCanonicalDestinations()
|
||||||
|
{
|
||||||
|
var root = PlotLineRoot();
|
||||||
|
var centre = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceReviewCentre.cshtml"));
|
||||||
|
var model = File.ReadAllText(Path.Combine(root, "ViewModels/OnboardingViewModels.cs"));
|
||||||
|
var persistence = File.ReadAllText(Path.Combine(root, "Models/StoryIntelligencePersistenceModels.cs"));
|
||||||
|
var service = File.ReadAllText(Path.Combine(root, "Services/OnboardingStoryIntelligenceService.cs"));
|
||||||
|
var metricSql = File.ReadAllText(Path.Combine(root, "Sql/178_Phase27E_ReviewCentreMetricSummary.sql"));
|
||||||
|
|
||||||
|
Assert(centre.Contains("\"Review Characters\"", StringComparison.Ordinal), "Incomplete Characters should use the Story Intelligence review action label.");
|
||||||
|
Assert(centre.Contains("\"StoryIntelligenceCharacters\"", StringComparison.Ordinal), "Incomplete Characters should route to Character review.");
|
||||||
|
Assert(centre.Contains("\"View Characters\"", StringComparison.Ordinal), "Complete Characters should use canonical action label.");
|
||||||
|
Assert(centre.Contains("\"Characters\"", StringComparison.Ordinal) && centre.Contains("new { projectId = Model.ProjectID }", StringComparison.Ordinal), "Complete Characters should route to Characters/Index with project context.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("\"Review Locations\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceLocations\"", StringComparison.Ordinal), "Incomplete Locations should route to Location review.");
|
||||||
|
Assert(centre.Contains("\"View Locations\"", StringComparison.Ordinal) && centre.Contains("\"Locations\"", StringComparison.Ordinal), "Complete Locations should route to Locations/Index.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("\"Review Assets\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceAssets\"", StringComparison.Ordinal), "Incomplete Assets should route to Asset review.");
|
||||||
|
Assert(centre.Contains("\"View Assets\"", StringComparison.Ordinal) && centre.Contains("\"StoryAssets\"", StringComparison.Ordinal), "Complete Assets should route to StoryAssets/Index.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("\"Review Relationships\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceRelationships\"", StringComparison.Ordinal), "Incomplete Relationships should route to Relationship review.");
|
||||||
|
Assert(centre.Contains("\"View Relationships\"", StringComparison.Ordinal) && centre.Contains("\"RelationshipMap\"", StringComparison.Ordinal), "Complete Relationships should route to RelationshipMap/Index.");
|
||||||
|
Assert(centre.Contains("new { projectId = Model.ProjectID, bookId = Model.BookID }", StringComparison.Ordinal), "Book-scoped canonical destinations should include book context.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("\"Review Knowledge\"", StringComparison.Ordinal) && centre.Contains("\"StoryIntelligenceKnowledge\"", StringComparison.Ordinal), "Incomplete Knowledge should route to Knowledge review.");
|
||||||
|
Assert(centre.Contains("\"View Knowledge\"", StringComparison.Ordinal) && centre.Contains("\"StoryBible\"", StringComparison.Ordinal), "Complete Knowledge should route to StoryBible/Index.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("Review Plot Lines & Threads", StringComparison.Ordinal), "Incomplete Plot should route to Plot review.");
|
||||||
|
Assert(centre.Contains("Url.Action(\"StoryIntelligenceNarrative\", \"Onboarding\"", StringComparison.Ordinal), "Incomplete Plot should use the Story Intelligence Plot review route.");
|
||||||
|
Assert(centre.Contains("Url.Action(\"Index\", \"Timeline\", new { projectId = Model.ProjectID, bookId = Model.BookID })", StringComparison.Ordinal), "Complete Plot and available Metrics should route to Timeline.");
|
||||||
|
Assert(centre.Contains("View on Timeline", StringComparison.Ordinal), "Complete Plot should label the canonical Timeline action.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("Model.MetricValueCount > 0", StringComparison.Ordinal), "Metrics availability should be based on actual metric values.");
|
||||||
|
Assert(centre.Contains("No metrics available", StringComparison.Ordinal), "Zero metrics should be clearly identified.");
|
||||||
|
Assert(centre.Contains("No action", StringComparison.Ordinal), "Zero metrics should not expose a misleading Review Metrics action.");
|
||||||
|
Assert(!centre.Contains("Review Metrics", StringComparison.Ordinal), "Review Centre should not advertise a non-existent metrics review workflow.");
|
||||||
|
|
||||||
|
Assert(model.Contains("public int MetricValueCount", StringComparison.Ordinal), "Review Centre model should expose metric availability.");
|
||||||
|
Assert(persistence.Contains("public int MetricValueCount", StringComparison.Ordinal), "Persisted summary should expose metric availability.");
|
||||||
|
Assert(service.Contains("MetricValueCount = summary.MetricValueCount", StringComparison.Ordinal), "Review Centre summary should hydrate metric availability.");
|
||||||
|
Assert(metricSql.Contains("MetricValueSummary", StringComparison.Ordinal) && metricSql.Contains("SceneMetricValues", StringComparison.Ordinal), "Metric summary SQL should count existing metric values.");
|
||||||
|
|
||||||
|
Assert(centre.Contains("var href = complete", StringComparison.Ordinal), "Module helper should switch destination by completion state.");
|
||||||
|
Assert(centre.Contains("Url.Action(reviewAction, \"Onboarding\", new { batchId = Model.BatchID })", StringComparison.Ordinal), "Incomplete modules should still open Story Intelligence review.");
|
||||||
|
Assert(centre.Contains("Url.Action(\"Index\", completeController, completeRouteValues)", StringComparison.Ordinal), "Complete modules should open canonical Index routes.");
|
||||||
|
}
|
||||||
|
|
||||||
static void StoryIntelligenceDetailedReviewsReadPersistedCandidatePages()
|
static void StoryIntelligenceDetailedReviewsReadPersistedCandidatePages()
|
||||||
{
|
{
|
||||||
var root = PlotLineRoot();
|
var root = PlotLineRoot();
|
||||||
|
|||||||
@ -179,6 +179,7 @@ public sealed class StoryIntelligenceReviewCentreSummary
|
|||||||
public int NarrativePendingCount { get; init; }
|
public int NarrativePendingCount { get; init; }
|
||||||
public int NarrativeThreadCount { get; init; }
|
public int NarrativeThreadCount { get; init; }
|
||||||
public int NarrativeEventCount { get; init; }
|
public int NarrativeEventCount { get; init; }
|
||||||
|
public int MetricValueCount { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StoryIntelligenceCommittedRunSummary
|
public sealed class StoryIntelligenceCommittedRunSummary
|
||||||
|
|||||||
@ -846,6 +846,7 @@ public sealed class OnboardingStoryIntelligenceService(
|
|||||||
BookID = summary.BookID,
|
BookID = summary.BookID,
|
||||||
ProjectName = summary.ProjectName,
|
ProjectName = summary.ProjectName,
|
||||||
BookTitle = summary.BookDisplayTitle,
|
BookTitle = summary.BookDisplayTitle,
|
||||||
|
MetricValueCount = summary.MetricValueCount,
|
||||||
CharacterReview = new StoryIntelligenceCharacterReviewViewModel
|
CharacterReview = new StoryIntelligenceCharacterReviewViewModel
|
||||||
{
|
{
|
||||||
HasCommittedScenes = hasCommittedScenes,
|
HasCommittedScenes = hasCommittedScenes,
|
||||||
|
|||||||
134
PlotLine/Sql/178_Phase27E_ReviewCentreMetricSummary.sql
Normal file
134
PlotLine/Sql/178_Phase27E_ReviewCentreMetricSummary.sql
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
SET ANSI_NULLS ON;
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.StoryIntelligenceReviewCentre_GetSummary
|
||||||
|
@BookID int,
|
||||||
|
@UserID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
;WITH RunSummary AS
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
r.BookID,
|
||||||
|
COUNT_BIG(*) AS RunCount,
|
||||||
|
SUM(CASE WHEN r.Status IN (N'Completed', N'CompletedWithWarnings') THEN 1 ELSE 0 END) AS CompletedRunCount,
|
||||||
|
SUM(CASE WHEN r.Status IN (N'Pending', N'Running') THEN 1 ELSE 0 END) AS ActiveRunCount,
|
||||||
|
SUM(CASE WHEN r.Status IN (N'Failed', N'Cancelled') THEN 1 ELSE 0 END) AS FailedRunCount,
|
||||||
|
SUM(COALESCE(r.TotalDetectedScenes, 0)) AS TotalDetectedScenes,
|
||||||
|
SUM(COALESCE(r.CompletedScenes, 0)) AS CompletedScenes,
|
||||||
|
SUM(COALESCE(r.FailedScenes, 0)) AS FailedScenes
|
||||||
|
FROM dbo.StoryIntelligenceRuns r
|
||||||
|
WHERE r.BookID = @BookID
|
||||||
|
GROUP BY r.BookID
|
||||||
|
),
|
||||||
|
NarrativeSummary AS
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
nc.BookID,
|
||||||
|
COUNT_BIG(*) AS NarrativeCandidateCount,
|
||||||
|
SUM(CASE WHEN nc.Status = N'Pending' THEN 1 ELSE 0 END) AS NarrativePendingCount,
|
||||||
|
SUM(CASE WHEN nc.CandidateType = N'Thread' THEN 1 ELSE 0 END) AS NarrativeThreadCount
|
||||||
|
FROM dbo.StoryIntelligenceNarrativeCandidates nc
|
||||||
|
WHERE nc.BookID = @BookID
|
||||||
|
AND nc.WholeBookPlotSynthesisRunID IS NOT NULL
|
||||||
|
GROUP BY nc.BookID
|
||||||
|
),
|
||||||
|
NarrativeEventSummary AS
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
nc.BookID,
|
||||||
|
COUNT_BIG(*) AS NarrativeEventCount
|
||||||
|
FROM dbo.StoryIntelligenceNarrativeCandidateEvents ev
|
||||||
|
INNER JOIN dbo.StoryIntelligenceNarrativeCandidates nc
|
||||||
|
ON nc.NarrativeCandidateID = ev.NarrativeCandidateID
|
||||||
|
WHERE nc.BookID = @BookID
|
||||||
|
AND nc.WholeBookPlotSynthesisRunID IS NOT NULL
|
||||||
|
GROUP BY nc.BookID
|
||||||
|
),
|
||||||
|
MetricValueSummary AS
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
chapter.BookID,
|
||||||
|
COUNT_BIG(*) AS MetricValueCount
|
||||||
|
FROM dbo.SceneMetricValues metric
|
||||||
|
INNER JOIN dbo.Scenes scene
|
||||||
|
ON scene.SceneID = metric.SceneID
|
||||||
|
INNER JOIN dbo.Chapters chapter
|
||||||
|
ON chapter.ChapterID = scene.ChapterID
|
||||||
|
WHERE chapter.BookID = @BookID
|
||||||
|
GROUP BY chapter.BookID
|
||||||
|
),
|
||||||
|
CurrentPlotRun AS
|
||||||
|
(
|
||||||
|
SELECT TOP (1)
|
||||||
|
run.BookID,
|
||||||
|
run.WholeBookPlotSynthesisRunID,
|
||||||
|
run.Status AS WholeBookPlotSynthesisStatus,
|
||||||
|
run.ErrorMessage AS WholeBookPlotSynthesisErrorMessage
|
||||||
|
FROM dbo.WholeBookPlotSynthesisRuns run
|
||||||
|
WHERE run.BookID = @BookID
|
||||||
|
AND run.IsCurrent = 1
|
||||||
|
ORDER BY run.WholeBookPlotSynthesisRunID DESC
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
CAST(COALESCE(rs.RunCount, 0) AS int) AS RunCount,
|
||||||
|
CAST(COALESCE(rs.CompletedRunCount, 0) AS int) AS CompletedRunCount,
|
||||||
|
CAST(COALESCE(rs.ActiveRunCount, 0) AS int) AS ActiveRunCount,
|
||||||
|
CAST(COALESCE(rs.FailedRunCount, 0) AS int) AS FailedRunCount,
|
||||||
|
CAST(COALESCE(rs.TotalDetectedScenes, 0) AS int) AS TotalDetectedScenes,
|
||||||
|
CAST(COALESCE(rs.CompletedScenes, 0) AS int) AS CompletedScenes,
|
||||||
|
CAST(COALESCE(rs.FailedScenes, 0) AS int) AS FailedScenes,
|
||||||
|
plot.WholeBookPlotSynthesisRunID,
|
||||||
|
plot.WholeBookPlotSynthesisStatus,
|
||||||
|
plot.WholeBookPlotSynthesisErrorMessage,
|
||||||
|
CAST(COALESCE(ns.NarrativeCandidateCount, 0) AS int) AS NarrativeCandidateCount,
|
||||||
|
CAST(COALESCE(ns.NarrativePendingCount, 0) AS int) AS NarrativePendingCount,
|
||||||
|
CAST(COALESCE(ns.NarrativeThreadCount, 0) AS int) AS NarrativeThreadCount,
|
||||||
|
CAST(COALESCE(nes.NarrativeEventCount, 0) AS int) AS NarrativeEventCount,
|
||||||
|
CAST(COALESCE(metrics.MetricValueCount, 0) AS int) AS MetricValueCount
|
||||||
|
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
|
||||||
|
LEFT JOIN RunSummary rs
|
||||||
|
ON rs.BookID = p.BookID
|
||||||
|
LEFT JOIN NarrativeSummary ns
|
||||||
|
ON ns.BookID = p.BookID
|
||||||
|
LEFT JOIN NarrativeEventSummary nes
|
||||||
|
ON nes.BookID = p.BookID
|
||||||
|
LEFT JOIN MetricValueSummary metrics
|
||||||
|
ON metrics.BookID = p.BookID
|
||||||
|
LEFT JOIN CurrentPlotRun plot
|
||||||
|
ON plot.BookID = p.BookID
|
||||||
|
WHERE p.BookID = @BookID
|
||||||
|
AND book.IsArchived = 0
|
||||||
|
AND project.IsArchived = 0;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
@ -184,6 +184,7 @@ public sealed class StoryIntelligenceProgressViewModel
|
|||||||
public int TotalDetectedScenes => Chapters.Sum(chapter => chapter.Scenes.Count > 0 ? chapter.Scenes.Count : chapter.TotalDetectedScenes ?? 0);
|
public int TotalDetectedScenes => Chapters.Sum(chapter => chapter.Scenes.Count > 0 ? chapter.Scenes.Count : chapter.TotalDetectedScenes ?? 0);
|
||||||
public int TotalCompletedScenes => Chapters.Sum(chapter => chapter.CompletedScenes ?? 0);
|
public int TotalCompletedScenes => Chapters.Sum(chapter => chapter.CompletedScenes ?? 0);
|
||||||
public int TotalAnalysedScenes => Math.Max(TotalCompletedScenes, PipelineDashboard.ScenesImported);
|
public int TotalAnalysedScenes => Math.Max(TotalCompletedScenes, PipelineDashboard.ScenesImported);
|
||||||
|
public int MetricValueCount { get; init; }
|
||||||
public int TotalFailedScenes => Chapters.Sum(chapter => chapter.FailedScenes ?? 0);
|
public int TotalFailedScenes => Chapters.Sum(chapter => chapter.FailedScenes ?? 0);
|
||||||
public int TotalAnalysedWords => Chapters.Sum(chapter => chapter.SourceWordCount ?? 0);
|
public int TotalAnalysedWords => Chapters.Sum(chapter => chapter.SourceWordCount ?? 0);
|
||||||
public decimal? TotalEstimatedCostUSD => Chapters.Any(chapter => chapter.EstimatedCostUSD.HasValue) ? Chapters.Sum(chapter => chapter.EstimatedCostUSD ?? 0m) : null;
|
public decimal? TotalEstimatedCostUSD => Chapters.Any(chapter => chapter.EstimatedCostUSD.HasValue) ? Chapters.Sum(chapter => chapter.EstimatedCostUSD ?? 0m) : null;
|
||||||
|
|||||||
@ -26,11 +26,14 @@
|
|||||||
@ReviewModule(
|
@ReviewModule(
|
||||||
"Characters",
|
"Characters",
|
||||||
Model.CharacterNeedsAttentionCount,
|
Model.CharacterNeedsAttentionCount,
|
||||||
Model.CharacterGateSatisfied,
|
Model.PipelineDashboard.CharacterStageComplete,
|
||||||
false,
|
false,
|
||||||
"Resolve character identities before reviewing dependent Story Intelligence.",
|
"Resolve character identities before reviewing dependent Story Intelligence.",
|
||||||
Model.CharacterGateSatisfied ? "Review Characters" : "Resolve Characters",
|
"Review Characters",
|
||||||
"StoryIntelligenceCharacters",
|
"StoryIntelligenceCharacters",
|
||||||
|
"View Characters",
|
||||||
|
"Characters",
|
||||||
|
new { projectId = Model.ProjectID },
|
||||||
true)
|
true)
|
||||||
|
|
||||||
@ReviewModule(
|
@ReviewModule(
|
||||||
@ -39,8 +42,11 @@
|
|||||||
Model.PipelineDashboard.AssetStageComplete,
|
Model.PipelineDashboard.AssetStageComplete,
|
||||||
!Model.CharacterGateSatisfied,
|
!Model.CharacterGateSatisfied,
|
||||||
"Important objects and possessions found in the manuscript.",
|
"Important objects and possessions found in the manuscript.",
|
||||||
ModuleAction("Assets", Model.AssetNeedsAttentionCount, Model.PipelineDashboard.AssetStageComplete),
|
"Review Assets",
|
||||||
"StoryIntelligenceAssets")
|
"StoryIntelligenceAssets",
|
||||||
|
"View Assets",
|
||||||
|
"StoryAssets",
|
||||||
|
new { projectId = Model.ProjectID })
|
||||||
|
|
||||||
@ReviewModule(
|
@ReviewModule(
|
||||||
"Knowledge",
|
"Knowledge",
|
||||||
@ -48,8 +54,11 @@
|
|||||||
Model.PipelineDashboard.KnowledgeStageComplete,
|
Model.PipelineDashboard.KnowledgeStageComplete,
|
||||||
!Model.CharacterGateSatisfied,
|
!Model.CharacterGateSatisfied,
|
||||||
"Facts, discoveries, beliefs, and suspicions found in the manuscript.",
|
"Facts, discoveries, beliefs, and suspicions found in the manuscript.",
|
||||||
ModuleAction("Knowledge", Model.KnowledgeNeedsAttentionCount, Model.PipelineDashboard.KnowledgeStageComplete),
|
"Review Knowledge",
|
||||||
"StoryIntelligenceKnowledge")
|
"StoryIntelligenceKnowledge",
|
||||||
|
"View Knowledge",
|
||||||
|
"StoryBible",
|
||||||
|
new { projectId = Model.ProjectID })
|
||||||
|
|
||||||
@ReviewModule(
|
@ReviewModule(
|
||||||
"Locations",
|
"Locations",
|
||||||
@ -57,8 +66,11 @@
|
|||||||
Model.PipelineDashboard.LocationStageComplete,
|
Model.PipelineDashboard.LocationStageComplete,
|
||||||
!Model.CharacterGateSatisfied,
|
!Model.CharacterGateSatisfied,
|
||||||
"Places found in the manuscript.",
|
"Places found in the manuscript.",
|
||||||
ModuleAction("Locations", Model.LocationNeedsAttentionCount, Model.PipelineDashboard.LocationStageComplete),
|
"Review Locations",
|
||||||
"StoryIntelligenceLocations")
|
"StoryIntelligenceLocations",
|
||||||
|
"View Locations",
|
||||||
|
"Locations",
|
||||||
|
new { projectId = Model.ProjectID })
|
||||||
|
|
||||||
@ReviewPlotSynthesis()
|
@ReviewPlotSynthesis()
|
||||||
|
|
||||||
@ -68,8 +80,11 @@
|
|||||||
Model.PipelineDashboard.RelationshipStageComplete,
|
Model.PipelineDashboard.RelationshipStageComplete,
|
||||||
!Model.CharacterGateSatisfied,
|
!Model.CharacterGateSatisfied,
|
||||||
"Character relationships found in scene evidence.",
|
"Character relationships found in scene evidence.",
|
||||||
ModuleAction("Relationships", Model.RelationshipNeedsAttentionCount, Model.PipelineDashboard.RelationshipStageComplete),
|
"Review Relationships",
|
||||||
"StoryIntelligenceRelationships")
|
"StoryIntelligenceRelationships",
|
||||||
|
"View Relationships",
|
||||||
|
"RelationshipMap",
|
||||||
|
new { projectId = Model.ProjectID, bookId = Model.BookID })
|
||||||
|
|
||||||
@ReviewMetrics(!Model.CharacterGateSatisfied)
|
@ReviewMetrics(!Model.CharacterGateSatisfied)
|
||||||
|
|
||||||
@ -82,7 +97,10 @@
|
|||||||
false,
|
false,
|
||||||
"Some chapters or scenes reported analysis issues.",
|
"Some chapters or scenes reported analysis issues.",
|
||||||
"Return to Book",
|
"Return to Book",
|
||||||
null)
|
"StoryIntelligenceReviewCentre",
|
||||||
|
"Return to Book",
|
||||||
|
"Books",
|
||||||
|
new { id = Model.BookID })
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -94,9 +112,6 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
@functions {
|
@functions {
|
||||||
private string ModuleAction(string title, int? count, bool complete)
|
|
||||||
=> count.GetValueOrDefault() > 0 || (!complete && !count.HasValue) ? $"Review {title}" : $"View {title}";
|
|
||||||
|
|
||||||
private Microsoft.AspNetCore.Html.IHtmlContent ReviewPlotSynthesis()
|
private Microsoft.AspNetCore.Html.IHtmlContent ReviewPlotSynthesis()
|
||||||
{
|
{
|
||||||
var blocked = !Model.CharacterGateSatisfied;
|
var blocked = !Model.CharacterGateSatisfied;
|
||||||
@ -146,8 +161,8 @@
|
|||||||
{
|
{
|
||||||
status = "Complete";
|
status = "Complete";
|
||||||
description = "Plot Lines and Threads review has been saved.";
|
description = "Plot Lines and Threads review has been saved.";
|
||||||
var href = Url.Action("StoryIntelligenceNarrative", "Onboarding", new { batchId = Model.BatchID }) ?? "#";
|
var href = Url.Action("Index", "Timeline", new { projectId = Model.ProjectID, bookId = Model.BookID }) ?? "#";
|
||||||
actionHtml = $"<a class=\"btn btn-outline-primary btn-sm\" href=\"{System.Net.WebUtility.HtmlEncode(href)}\">View Plot Lines & Threads</a>";
|
actionHtml = $"<a class=\"btn btn-outline-primary btn-sm\" href=\"{System.Net.WebUtility.HtmlEncode(href)}\">View on Timeline</a>";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -177,7 +192,7 @@
|
|||||||
|
|
||||||
if (string.Equals(title, "Scene Metrics", StringComparison.Ordinal))
|
if (string.Equals(title, "Scene Metrics", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return "Available";
|
return Model.MetricValueCount > 0 ? "Available" : "No metrics available";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (complete)
|
if (complete)
|
||||||
@ -200,13 +215,19 @@
|
|||||||
|
|
||||||
private Microsoft.AspNetCore.Html.IHtmlContent ReviewMetrics(bool blocked)
|
private Microsoft.AspNetCore.Html.IHtmlContent ReviewMetrics(bool blocked)
|
||||||
{
|
{
|
||||||
var status = StatusText("Scene Metrics", Model.TotalAnalysedScenes, false, blocked);
|
var hasMetrics = Model.MetricValueCount > 0;
|
||||||
|
var status = StatusText("Scene Metrics", Model.MetricValueCount, false, blocked);
|
||||||
var statusClass = blocked
|
var statusClass = blocked
|
||||||
? "story-review-centre-tile__status story-review-centre-tile__status--locked"
|
? "story-review-centre-tile__status story-review-centre-tile__status--locked"
|
||||||
: "story-review-centre-tile__status story-review-centre-tile__status--ready";
|
: hasMetrics
|
||||||
|
? "story-review-centre-tile__status story-review-centre-tile__status--ready"
|
||||||
|
: "story-review-centre-tile__status story-review-centre-tile__status--locked";
|
||||||
|
var href = Url.Action("Index", "Timeline", new { projectId = Model.ProjectID, bookId = Model.BookID }) ?? "#";
|
||||||
var actionHtml = blocked
|
var actionHtml = blocked
|
||||||
? "<span class=\"btn btn-outline-secondary btn-sm disabled\" aria-disabled=\"true\">Locked</span>"
|
? "<span class=\"btn btn-outline-secondary btn-sm disabled\" aria-disabled=\"true\">Locked</span>"
|
||||||
: "<span class=\"btn btn-outline-primary btn-sm disabled\" aria-disabled=\"true\">View Metrics</span>";
|
: hasMetrics
|
||||||
|
? $"<a class=\"btn btn-outline-primary btn-sm\" href=\"{System.Net.WebUtility.HtmlEncode(href)}\">View on Timeline</a>"
|
||||||
|
: "<span class=\"btn btn-outline-secondary btn-sm disabled\" aria-disabled=\"true\">No action</span>";
|
||||||
|
|
||||||
return new Microsoft.AspNetCore.Html.HtmlString($$"""
|
return new Microsoft.AspNetCore.Html.HtmlString($$"""
|
||||||
<article class="story-review-centre-tile{{(blocked ? " story-review-centre-tile--locked" : string.Empty)}}">
|
<article class="story-review-centre-tile{{(blocked ? " story-review-centre-tile--locked" : string.Empty)}}">
|
||||||
@ -226,8 +247,11 @@
|
|||||||
bool complete,
|
bool complete,
|
||||||
bool blocked,
|
bool blocked,
|
||||||
string description,
|
string description,
|
||||||
string actionLabel,
|
string reviewActionLabel,
|
||||||
string? action,
|
string reviewAction,
|
||||||
|
string completeActionLabel,
|
||||||
|
string completeController,
|
||||||
|
object completeRouteValues,
|
||||||
bool primary = false)
|
bool primary = false)
|
||||||
{
|
{
|
||||||
var status = StatusText(title, count, complete, blocked);
|
var status = StatusText(title, count, complete, blocked);
|
||||||
@ -239,9 +263,10 @@
|
|||||||
var tileClass = "story-review-centre-tile"
|
var tileClass = "story-review-centre-tile"
|
||||||
+ (primary ? " story-review-centre-tile--primary" : string.Empty)
|
+ (primary ? " story-review-centre-tile--primary" : string.Empty)
|
||||||
+ (blocked ? " story-review-centre-tile--locked" : string.Empty);
|
+ (blocked ? " story-review-centre-tile--locked" : string.Empty);
|
||||||
var href = action is null
|
var href = complete
|
||||||
? Url.Action("Details", "Books", new { id = Model.BookID }) ?? "#"
|
? Url.Action("Index", completeController, completeRouteValues) ?? "#"
|
||||||
: Url.Action(action, "Onboarding", new { batchId = Model.BatchID }) ?? "#";
|
: Url.Action(reviewAction, "Onboarding", new { batchId = Model.BatchID }) ?? "#";
|
||||||
|
var actionLabel = complete ? completeActionLabel : reviewActionLabel;
|
||||||
var actionHtml = blocked
|
var actionHtml = blocked
|
||||||
? "<span class=\"btn btn-outline-secondary btn-sm disabled\" aria-disabled=\"true\">Locked</span>"
|
? "<span class=\"btn btn-outline-secondary btn-sm disabled\" aria-disabled=\"true\">Locked</span>"
|
||||||
: $"<a class=\"btn btn-outline-primary btn-sm\" href=\"{System.Net.WebUtility.HtmlEncode(href)}\">{System.Net.WebUtility.HtmlEncode(actionLabel)}</a>";
|
: $"<a class=\"btn btn-outline-primary btn-sm\" href=\"{System.Net.WebUtility.HtmlEncode(href)}\">{System.Net.WebUtility.HtmlEncode(actionLabel)}</a>";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user