@model StoryIntelligenceDryRunViewModel @{ ViewData["Title"] = "Story Intelligence Dry Run"; var result = Model.Result; }

Admin utility

Story Intelligence Dry Run

This is a development dry run. It makes one live OpenAI extraction request only when submitted, and it does not save data or modify projects.

Manual scene test

Use short fictional sample text only. This page loads Scene-Prompt-V2.md, replaces runtime placeholders, makes a live extraction request when submitted, and displays the raw response without parsing or saving it.

Back to diagnostics
@if (result is not null) {

Dry-run result

@if (!result.Success) {
@result.ErrorMessage
}

Prompt version

@result.PromptVersion

Model

@result.Model

Duration

@(string.IsNullOrWhiteSpace(result.Duration) ? "Not run" : result.Duration)

Retries

@result.RetryCount.ToString("N0")

Input tokens
@(result.InputTokens.HasValue ? result.InputTokens.Value.ToString("N0") : "Not reported")
Output tokens
@(result.OutputTokens.HasValue ? result.OutputTokens.Value.ToString("N0") : "Not reported")
@if (!string.IsNullOrWhiteSpace(result.SceneJsonText)) {
}
@if (result.Validation is null) {
Validation did not run.
} else {

@ValidationSummary(result.Validation)

@RenderIssues("Errors", result.Validation.Errors) @RenderIssues("Warnings", result.Validation.Warnings) @RenderIssues("Information", result.Validation.Information) }
@if (result.ParsedScene is null) {
No parsed Scene Intelligence model is available.
} else { var scene = result.ParsedScene;

Scene Reference

Schema
@scene.SchemaVersion
Project
@Display(scene.SceneReference?.ProjectId)
Book
@Display(scene.SceneReference?.BookId)
Chapter
@Display(scene.SceneReference?.ChapterId) (@Display(scene.SceneReference?.ChapterNumber))
Scene
@Display(scene.SceneReference?.SceneId) (@Display(scene.SceneReference?.SceneNumber))
Source
@Display(scene.SceneReference?.SourceLabel)

Summary

Short: @Display(scene.Summary?.Short)

Detailed: @Display(scene.Summary?.Detailed)

Confidence: @Display(scene.Summary?.Confidence)

Purpose

@Display(scene.ScenePurpose?.ObservedFunction)

Confidence: @Display(scene.ScenePurpose?.Confidence)

Point Of View

Character
@Display(scene.PointOfView?.CharacterName)
Mode
@Display(scene.PointOfView?.NarrativeMode)
Evidence
@Display(scene.PointOfView?.Evidence)
Confidence
@Display(scene.PointOfView?.Confidence)

Setting

Location
@Display(scene.Setting?.LocationName)
Type
@Display(scene.Setting?.LocationType)
Room
@Display(scene.Setting?.GenericRoomType)
Time
@Display(scene.Setting?.TimeOfDay) / @Display(scene.Setting?.DateOrTimeReference)
Confidence
@Display(scene.Setting?.Confidence)
@RenderCollection("Characters", scene.Characters, character => $"{Display(character.Name)} - {Display(character.RoleInScene)} - mentioned only: {Display(character.MentionedOnly)} - confidence: {Display(character.Confidence)}") @RenderCollection("Locations", scene.Locations, location => $"{Display(location.Name)} - {Display(location.LocationType)} - present: {Display(location.PresentInScene)} - confidence: {Display(location.Confidence)}") @RenderCollection("Assets", scene.Assets, asset => $"{Display(asset.Name)} - {Display(asset.AssetType)} - status: {Display(asset.Status)} - holder: {Display(asset.OwnerOrHolder)}") @RenderCollection("Relationships", scene.Relationships, relationship => $"{Display(relationship.CharacterA)} / {Display(relationship.CharacterB)} - {Display(relationship.RelationshipSignal)} - confidence: {Display(relationship.Confidence)}") @RenderCollection("Knowledge Changes", scene.KnowledgeChanges, change => $"{Display(change.RecipientCharacter)} {Display(change.ChangeType)}: {Display(change.KnowledgeItem)}") @RenderCollection("Timeline Clues", scene.TimelineClues, clue => $"{Display(clue.Clue)} - {Display(clue.RelativeOrder)}") @RenderCollection("Questions Raised", scene.QuestionsRaised, question => $"{Display(question.Question)} - {Display(question.Scope)}") @RenderCollection("Questions Answered", scene.QuestionsAnswered, answer => $"{Display(answer.Question)} => {Display(answer.Answer)}") @RenderCollection("Observations", scene.Observations, observation => $"{Display(observation.SubjectName)} {Display(observation.Predicate)} {Display(observation.ObjectName)} - {Display(observation.ObservationType)}")

Metrics

@if (scene.Metrics is null || scene.Metrics.Count == 0) {

No metrics reported.

} else {
@foreach (var metric in scene.Metrics) {
@metric.Key
Score @Display(metric.Value?.Score), confidence @Display(metric.Value?.Confidence)
}
}

Source Limits

Contains ambiguity: @Display(scene.SourceLimits?.ContainsAmbiguity)

@RenderStringList("Ambiguity Notes", scene.SourceLimits?.AmbiguityNotes)
}
} @functions { private static string Display(object? value) => value switch { null => "None", string text when string.IsNullOrWhiteSpace(text) => "None", decimal number => number.ToString("0.##"), bool flag => flag ? "Yes" : "No", _ => value.ToString() ?? "None" }; private static string ValidationSummary(PlotLine.Models.StoryIntelligence.ValidationResult validation) => validation.IsValid ? $"✓ Passed with {validation.Warnings.Count:N0} warning(s)" : $"✖ Failed with {validation.Errors.Count:N0} error(s) and {validation.Warnings.Count:N0} warning(s)"; private static string SeveritySymbol(string severity) => severity switch { "Error" => "✖", "Warning" => "⚠", _ => "✓" }; private static Microsoft.AspNetCore.Html.IHtmlContent RenderIssues(string heading, IReadOnlyList issues) { var builder = new System.Text.StringBuilder(); builder.Append("
"); builder.Append("

").Append(Encode(heading)).Append(" (").Append(issues.Count.ToString("N0")).Append(")

"); if (issues.Count == 0) { builder.Append("

None.

"); } else { builder.Append("
"); foreach (var issue in issues) { builder.Append("
"); builder.Append("

") .Append(Encode(SeveritySymbol(issue.Severity))) .Append(' ') .Append(Encode(issue.Severity)) .Append(" ") .Append(Encode(issue.Path)) .Append("

"); builder.Append("

").Append(Encode(issue.Message)).Append("

"); builder.Append("

").Append(Encode(issue.SuggestedFix)).Append("

"); builder.Append("
"); } builder.Append("
"); } builder.Append("
"); return new Microsoft.AspNetCore.Html.HtmlString(builder.ToString()); } private static Microsoft.AspNetCore.Html.IHtmlContent RenderCollection(string heading, IReadOnlyList? items, Func formatter) { var builder = new System.Text.StringBuilder(); builder.Append("
"); builder.Append("

").Append(Encode(heading)).Append("

"); if (items is null || items.Count == 0) { builder.Append("

No items reported.

"); } else { builder.Append("
    "); foreach (var item in items) { builder.Append("
  1. ").Append(Encode(formatter(item))).Append("
  2. "); } builder.Append("
"); } builder.Append("
"); return new Microsoft.AspNetCore.Html.HtmlString(builder.ToString()); } private static Microsoft.AspNetCore.Html.IHtmlContent RenderStringList(string heading, IReadOnlyList? items) { var builder = new System.Text.StringBuilder(); builder.Append("

").Append(Encode(heading)).Append("

"); if (items is null || items.Count == 0) { builder.Append("

No items reported.

"); } else { builder.Append("
    "); foreach (var item in items) { builder.Append("
  • ").Append(Encode(item)).Append("
  • "); } builder.Append("
"); } return new Microsoft.AspNetCore.Html.HtmlString(builder.ToString()); } private static string Encode(string? value) => System.Net.WebUtility.HtmlEncode(value ?? string.Empty); }