@model AnalyticsViewModel @{ ViewData["Title"] = "Story Health"; ViewData["ProjectSection"] = "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, FocusType = "scene", FocusID = 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 }) }) }; }

Analytics / Story health

@Model.Project.ProjectName

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

} }

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 }
}

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.

}