@model AnalyticsViewModel @{ ViewData["Title"] = "Story Health"; var openThreads = Model.ThreadHealth.Where(x => x.IsOpenStatus).ToList(); var dormantThreads = Model.ThreadHealth.Where(x => x.IsDormant).ToList(); var unresolvedAssets = Model.AssetHealth.Where(x => !x.IsResolved).ToList(); var metricScenes = Model.MetricGraphs .SelectMany(graph => graph.Points) .GroupBy(point => point.SceneID) .Select(group => group.OrderBy(point => point.GlobalSceneIndex).First()) .OrderBy(point => point.GlobalSceneIndex) .Select(point => new { sceneId = point.SceneID, axisLabel = $"{point.ChapterNumber:g}.{point.SceneNumber:g}", chapterLabel = $"Chapter {point.ChapterNumber:g}", sceneNumber = point.SceneNumber.ToString("g"), sceneTitle = point.SceneTitle, bookTitle = point.BookTitle, timelineUrl = Url.Action("Index", "Timeline", new { projectId = Model.Project.ProjectID, bookId = point.BookID, selectedSceneId = point.SceneID }) }) .ToList(); var metricShapePayload = new { scenes = metricScenes, metrics = Model.MetricGraphs.Select(graph => new { metricName = graph.MetricName, points = graph.Points.Select(point => new { sceneId = point.SceneID, value = point.Value }) }) }; }
Workspace context @Model.Project.ProjectName

Analytics / Story health

@Model.Project.ProjectName

Timeline Warnings
Open threads@openThreads.Count
Dormant threads@dormantThreads.Count
Unresolved assets@unresolvedAssets.Count
Warning hotspots@Model.WarningHotspots.Count

Scene metric shape

@Model.MetricGraphs.Count metric@(Model.MetricGraphs.Count == 1 ? "" : "s")
@if (!Model.MetricGraphs.Any() || !metricScenes.Any()) {

Add scene metrics to begin seeing peaks and troughs across the story.

} else {

Compare selected pacing and emotional metrics across scenes in narrative order. Click a point to open that scene on the timeline.

Metric chart could not load. The raw metric rows are still available below.

Show raw metric rows
@foreach (var graph in Model.MetricGraphs) {
@graph.MetricName
@foreach (var point in graph.Points) { @point.Value }
}
}

Pacing review

@if (!Model.PacingReviewNotes.Any()) {

No sustained pacing runs found in this scope.

} else { @foreach (var note in Model.PacingReviewNotes) {
@note.MetricName @note.SceneRange @note.ValuePattern

@note.ReviewNote

} }

Revision summary

@foreach (var status in Model.RevisionSummary.Where(x => x.SceneCount > 0 || x.ChapterCount > 0)) {
@status.StatusName @status.SceneCount scenes
}

Needs attention

@foreach (var scene in Model.RevisionAttentionScenes.Take(8)) { @scene.RevisionStatusName Ch @scene.ChapterNumber / Scene @scene.SceneNumber: @scene.SceneTitle }

Scene purpose balance

@foreach (var purpose in Model.PurposeSummary) {
@purpose.PurposeName
@purpose.SceneCount
}

Missing purpose/outcome

@foreach (var gap in Model.SceneGaps.Take(8)) { @(gap.MissingPurpose ? "No purpose" : "") @(gap.MissingOutcome ? "No outcome" : "") Ch @gap.ChapterNumber / Scene @gap.SceneNumber: @gap.SceneTitle }

Warning hotspots

@foreach (var count in Model.WarningSeverityCounts) {
@count.SeverityName@count.WarningCount
} @foreach (var hotspot in Model.WarningHotspots.Take(8)) { @hotspot.WarningCount warning@(hotspot.WarningCount == 1 ? "" : "s") Ch @hotspot.ChapterNumber / Scene @hotspot.SceneNumber: @hotspot.SceneTitle }

Plot thread health

@foreach (var thread in Model.ThreadHealth.Where(x => x.IsHighImportanceOpen || x.IsDormant || x.HasClueWithoutPayoff || x.HasQuestionWithoutAnswer).Take(12)) {
@thread.ThreadTitle @thread.PlotLineName / @thread.ThreadStatusName / importance @thread.Importance @thread.LastTouchedSceneLabel
@if (thread.IsDormant) { Dormant } @if (thread.HasClueWithoutPayoff) { Clue without payoff } @if (thread.HasQuestionWithoutAnswer) { Question open }
}

Story asset health

@foreach (var asset in Model.AssetHealth.Where(x => !x.IsResolved || x.HasDependencies || x.Importance >= 8).Take(12)) { @asset.KindName / importance @asset.Importance @asset.AssetName @(asset.CurrentStateName ?? "No state") / @(asset.CurrentLocationPath ?? "No location") }

POV distribution

@foreach (var pov in Model.PovSummary) {
@pov.POVName@pov.SceneCount
} @foreach (var chapter in Model.MultiPovChapters.Take(6)) {

Ch @chapter.ChapterNumber: @chapter.ChapterTitle has @chapter.POVCount POVs.

}

Character appearances

@foreach (var character in Model.CharacterAppearances.Take(10)) { @character.SceneCount scenes @character.CharacterName } @foreach (var busy in Model.BusyScenes.Take(4)) {

Busy scene: Ch @busy.ChapterNumber / Scene @busy.SceneNumber has @busy.CharacterCount characters.

}

Location usage

@foreach (var location in Model.LocationUsage.Take(10)) { @if (location.LocationID.HasValue) { @location.SceneCount scenes @location.LocationName } else {
@location.LocationName@location.SceneCount
} }