Preserve author plot seed provenance
This commit is contained in:
parent
80d13c6a7f
commit
99d818ed9f
@ -204,6 +204,12 @@ var tests = new (string Name, Action Test)[]
|
|||||||
("Narrative production code remains fixture agnostic", NarrativeProductionCodeRemainsFixtureAgnostic),
|
("Narrative production code remains fixture agnostic", NarrativeProductionCodeRemainsFixtureAgnostic),
|
||||||
("Whole-book Plot Intelligence prompt carries full manuscript context", WholeBookPlotIntelligencePromptCarriesFullManuscriptContext),
|
("Whole-book Plot Intelligence prompt carries full manuscript context", WholeBookPlotIntelligencePromptCarriesFullManuscriptContext),
|
||||||
("Whole-book Plot Intelligence schema requires timeline event types", WholeBookPlotIntelligenceSchemaRequiresTimelineEventTypes),
|
("Whole-book Plot Intelligence schema requires timeline event types", WholeBookPlotIntelligenceSchemaRequiresTimelineEventTypes),
|
||||||
|
("Whole-book Plot Intelligence author seed inputs persist safely", WholeBookPlotIntelligenceAuthorSeedInputsPersistSafely),
|
||||||
|
("Whole-book Plot Intelligence structured author seed match survives", WholeBookPlotIntelligenceStructuredAuthorSeedMatchSurvives),
|
||||||
|
("Whole-book Plot Intelligence unmatched seeds become review candidates", WholeBookPlotIntelligenceUnmatchedSeedsBecomeReviewCandidates),
|
||||||
|
("Story Intelligence narrative review displays author seed provenance", StoryIntelligenceNarrativeReviewDisplaysAuthorSeedProvenance),
|
||||||
|
("Story Intelligence narrative review avoids raw author-facing IDs", StoryIntelligenceNarrativeReviewAvoidsRawAuthorFacingIds),
|
||||||
|
("Story Intelligence author seed decisions are preserved", StoryIntelligenceAuthorSeedDecisionsArePreserved),
|
||||||
("Whole-book Plot Intelligence production code remains fixture agnostic", WholeBookPlotIntelligenceProductionCodeRemainsFixtureAgnostic),
|
("Whole-book Plot Intelligence production code remains fixture agnostic", WholeBookPlotIntelligenceProductionCodeRemainsFixtureAgnostic),
|
||||||
("Story Intelligence narrative review creates canonical records via existing procs", StoryIntelligenceNarrativeReviewCreatesCanonicalRecordsViaExistingProcs),
|
("Story Intelligence narrative review creates canonical records via existing procs", StoryIntelligenceNarrativeReviewCreatesCanonicalRecordsViaExistingProcs),
|
||||||
("Story Intelligence narrative stage follows knowledge", StoryIntelligenceNarrativeStageFollowsKnowledge),
|
("Story Intelligence narrative stage follows knowledge", StoryIntelligenceNarrativeStageFollowsKnowledge),
|
||||||
@ -5386,6 +5392,141 @@ static void WholeBookPlotIntelligenceSchemaRequiresTimelineEventTypes()
|
|||||||
Assert(WholeBookPlotSynthesisStructuredOutputSchema.Contract.Strict, "Whole-book schema should use strict structured output.");
|
Assert(WholeBookPlotSynthesisStructuredOutputSchema.Contract.Strict, "Whole-book schema should use strict structured output.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void WholeBookPlotIntelligenceAuthorSeedInputsPersistSafely()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var controller = File.ReadAllText(Path.Combine(root, "Controllers/OnboardingController.cs"));
|
||||||
|
var contextView = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceAuthorPlotContext.cshtml"));
|
||||||
|
var repository = File.ReadAllText(Path.Combine(root, "Data/WholeBookPlotIntelligenceRepository.cs"));
|
||||||
|
var sql = File.ReadAllText(Path.Combine(root, "Sql/174_Phase27_WholeBookPlotIntelligence.sql"));
|
||||||
|
|
||||||
|
Assert(contextView.Contains("Descriptions.Count >= 3", StringComparison.Ordinal) && contextView.Contains("Enumerable.Repeat(string.Empty, 3 - Model.Descriptions.Count)", StringComparison.Ordinal), "Author context page should render the three default seed inputs.");
|
||||||
|
Assert(contextView.Contains("data-add-plot-context", StringComparison.Ordinal) && contextView.Contains("PlotSeeds[${index}]", StringComparison.Ordinal), "Author context page should allow arbitrary additional seed inputs.");
|
||||||
|
Assert(repository.Contains(".Where(description => !string.IsNullOrWhiteSpace(description))", StringComparison.Ordinal), "Repository should ignore empty seed text before persistence.");
|
||||||
|
Assert(repository.Contains(".Select(description => new { description = description.Trim() })", StringComparison.Ordinal), "Repository should persist trimmed non-empty seed text.");
|
||||||
|
Assert(sql.Contains("NULLIF(LTRIM(RTRIM(JSON_VALUE(value, '$.description'))), N'') IS NOT NULL", StringComparison.Ordinal), "Database save procedure should also ignore empty seed text.");
|
||||||
|
|
||||||
|
var saveIndex = controller.IndexOf("await wholeBookPlotIntelligence.SaveAuthorPlotContextAsync", StringComparison.Ordinal);
|
||||||
|
var startBookIndex = controller.IndexOf("await storyIntelligence.StartBookAsync(form.BookID)", StringComparison.Ordinal);
|
||||||
|
Assert(saveIndex >= 0 && startBookIndex > saveIndex, "Author seed persistence must complete before Analyse My Book queues analysis.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WholeBookPlotIntelligenceStructuredAuthorSeedMatchSurvives()
|
||||||
|
{
|
||||||
|
const string json = """
|
||||||
|
{
|
||||||
|
"metadata": { "bookStatus": "Draft", "summary": "A search is under way." },
|
||||||
|
"plotLines": [
|
||||||
|
{
|
||||||
|
"key": "PL-A",
|
||||||
|
"proposedTitle": "Hidden partnership",
|
||||||
|
"description": "A visible rivalry conceals cooperation.",
|
||||||
|
"significance": "Central author intent.",
|
||||||
|
"bookStatus": "Active",
|
||||||
|
"existingPlotLineID": 7001,
|
||||||
|
"existingPlotLineMatchConfidence": 0.82,
|
||||||
|
"existingPlotLineMatchReason": "Same long-running project line.",
|
||||||
|
"authorPlotSeedID": 9001,
|
||||||
|
"seedMatchConfidence": 0.91,
|
||||||
|
"seedMatchReason": "Matches the supplied author seed.",
|
||||||
|
"confidence": 0.88,
|
||||||
|
"threads": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
var parsed = WholeBookPlotSynthesisResult.FromJson(json);
|
||||||
|
var result = parsed.PlotLines.Single();
|
||||||
|
|
||||||
|
Assert(result.AuthorPlotSeedID == 9001, "Structured AuthorPlotSeedID should survive response parsing.");
|
||||||
|
Assert(result.SeedMatchConfidence == 0.91m, "Seed match confidence should survive response parsing.");
|
||||||
|
Assert(result.SeedMatchReason == "Matches the supplied author seed.", "Seed match reason should survive response parsing.");
|
||||||
|
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var repository = File.ReadAllText(Path.Combine(root, "Data/WholeBookPlotIntelligenceRepository.cs"));
|
||||||
|
var sql = File.ReadAllText(Path.Combine(root, "Sql/174_Phase27_WholeBookPlotIntelligence.sql"));
|
||||||
|
Assert(repository.Contains("plotLine.AuthorPlotSeedID", StringComparison.Ordinal), "Candidate materialisation should pass AuthorPlotSeedID to persistence.");
|
||||||
|
Assert(repository.Contains("plotLine.SeedMatchConfidence", StringComparison.Ordinal), "Candidate materialisation should pass seed confidence to persistence.");
|
||||||
|
Assert(repository.Contains("plotLine.SeedMatchReason", StringComparison.Ordinal), "Candidate materialisation should pass seed reason to persistence.");
|
||||||
|
Assert(sql.Contains("AuthorPlotSeedID = @AuthorPlotSeedID", StringComparison.Ordinal), "Candidate save procedure should retain AuthorPlotSeedID updates.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WholeBookPlotIntelligenceUnmatchedSeedsBecomeReviewCandidates()
|
||||||
|
{
|
||||||
|
var result = new WholeBookPlotSynthesisResult
|
||||||
|
{
|
||||||
|
Metadata = new WholeBookPlotSynthesisMetadata { BookStatus = "Draft", Summary = "One detected line." },
|
||||||
|
PlotLines =
|
||||||
|
[
|
||||||
|
new WholeBookPlotSynthesisPlotLineResult
|
||||||
|
{
|
||||||
|
Key = "detected",
|
||||||
|
ProposedTitle = "Detected line",
|
||||||
|
Description = "A detected line.",
|
||||||
|
Significance = "Detected",
|
||||||
|
BookStatus = "Active",
|
||||||
|
AuthorPlotSeedID = 1,
|
||||||
|
Threads = []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
var seeds = new[]
|
||||||
|
{
|
||||||
|
new AuthorPlotSeed { AuthorPlotSeedID = 1, Description = "A matched seed." },
|
||||||
|
new AuthorPlotSeed { AuthorPlotSeedID = 2, Description = "A dormant author seed with no current-book evidence.", MatchedPlotLineID = 42 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var preserved = WholeBookPlotIntelligenceService.PreserveUnmatchedAuthorSeedsForReviewForTest(result, seeds);
|
||||||
|
var unmatched = preserved.PlotLines.Single(plotLine => plotLine.AuthorPlotSeedID == 2);
|
||||||
|
|
||||||
|
Assert(unmatched.Description == "A dormant author seed with no current-book evidence.", "Unmatched author seed candidate should show the exact seed description.");
|
||||||
|
Assert(unmatched.BookStatus == "Limited current-book evidence", "Unmatched author seed candidate should explicitly represent limited evidence.");
|
||||||
|
Assert(unmatched.Threads.Count == 0, "Unmatched author seed candidate must not fabricate thread or scene evidence.");
|
||||||
|
Assert(unmatched.ExistingPlotLineID == 42, "Unmatched author seed candidate should retain an existing Plot Line match when one is already linked.");
|
||||||
|
Assert(unmatched.ExistingPlotLineMatchConfidence == 1m, "Existing author-linked Plot Line should default to merge rather than duplicate creation.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StoryIntelligenceNarrativeReviewDisplaysAuthorSeedProvenance()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var models = File.ReadAllText(Path.Combine(root, "Models/StoryIntelligenceNarrativeModels.cs"));
|
||||||
|
var viewModels = File.ReadAllText(Path.Combine(root, "ViewModels/OnboardingViewModels.cs"));
|
||||||
|
var service = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceNarrativeReviewService.cs"));
|
||||||
|
var view = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceNarrative.cshtml"));
|
||||||
|
var sql = File.ReadAllText(Path.Combine(root, "Sql/174_Phase27_WholeBookPlotIntelligence.sql"));
|
||||||
|
|
||||||
|
Assert(models.Contains("AuthorPlotSeedDescription", StringComparison.Ordinal), "Narrative candidate model should carry author seed text.");
|
||||||
|
Assert(viewModels.Contains("AuthorPlotSeedDescription", StringComparison.Ordinal), "Review ViewModel should expose author seed text.");
|
||||||
|
Assert(service.Contains("AuthorPlotSeedDescription = candidate.AuthorPlotSeedDescription", StringComparison.Ordinal), "Review ViewModel mapping should retain author seed text.");
|
||||||
|
Assert(sql.Contains("aps.Description AS AuthorPlotSeedDescription", StringComparison.Ordinal), "Candidate list proc should join author seed descriptions.");
|
||||||
|
Assert(view.Contains("<strong>Author provided</strong>", StringComparison.Ordinal), "Review UI should render quiet author provenance.");
|
||||||
|
Assert(view.Contains("@candidate.AuthorPlotSeedDescription", StringComparison.Ordinal), "Review UI should render exact author seed text.");
|
||||||
|
Assert(view.Contains("@candidate.SeedMatchReason", StringComparison.Ordinal), "Review UI should show the match or limited-evidence reason.");
|
||||||
|
Assert(view.Contains("Discovered by PlotDirector", StringComparison.Ordinal), "Review UI should distinguish discovered candidates.");
|
||||||
|
Assert(view.Contains("Existing Plot Line: @candidate.ExistingPlotLineName", StringComparison.Ordinal), "Review UI should display existing Plot Line names, not database IDs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StoryIntelligenceNarrativeReviewAvoidsRawAuthorFacingIds()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var view = File.ReadAllText(Path.Combine(root, "Views/Onboarding/StoryIntelligenceNarrative.cshtml"));
|
||||||
|
|
||||||
|
Assert(!view.Contains("Scene @item.SceneID", StringComparison.Ordinal), "Review UI should not expose raw SceneIDs as visible text.");
|
||||||
|
Assert(!view.Contains(">PlotLineID", StringComparison.Ordinal) && !view.Contains("PlotLineID:", StringComparison.Ordinal), "Review UI should not expose raw PlotLineID labels.");
|
||||||
|
Assert(!view.Contains(">AuthorPlotSeedID", StringComparison.Ordinal) && !view.Contains("AuthorPlotSeedID:", StringComparison.Ordinal), "Review UI should not expose raw AuthorPlotSeedID labels.");
|
||||||
|
Assert(view.Contains("@item.SceneLabel", StringComparison.Ordinal), "Review UI should display readable chapter/scene labels.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StoryIntelligenceAuthorSeedDecisionsArePreserved()
|
||||||
|
{
|
||||||
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
var sql = File.ReadAllText(Path.Combine(root, "Sql/174_Phase27_WholeBookPlotIntelligence.sql"));
|
||||||
|
var service = File.ReadAllText(Path.Combine(root, "Services/StoryIntelligenceNarrativeReviewService.cs"));
|
||||||
|
|
||||||
|
Assert(sql.Contains("AND Status = N'Pending'", StringComparison.Ordinal), "Candidate save/update should not overwrite already reviewed author decisions.");
|
||||||
|
Assert(service.Contains("string.Equals(candidate.Status, StoryIntelligenceNarrativeCandidateStatuses.Pending", StringComparison.Ordinal), "Narrative import should only apply pending candidates.");
|
||||||
|
}
|
||||||
|
|
||||||
static void WholeBookPlotIntelligenceProductionCodeRemainsFixtureAgnostic()
|
static void WholeBookPlotIntelligenceProductionCodeRemainsFixtureAgnostic()
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
var root = Path.Combine(AppContext.BaseDirectory, "../../../../PlotLine");
|
||||||
|
|||||||
@ -41,6 +41,8 @@ public sealed class StoryIntelligenceNarrativeCandidate
|
|||||||
public string? ExistingPlotThreadTitle { get; set; }
|
public string? ExistingPlotThreadTitle { get; set; }
|
||||||
public int? WholeBookPlotSynthesisRunID { get; set; }
|
public int? WholeBookPlotSynthesisRunID { get; set; }
|
||||||
public int? AuthorPlotSeedID { get; set; }
|
public int? AuthorPlotSeedID { get; set; }
|
||||||
|
public string? AuthorPlotSeedDescription { get; set; }
|
||||||
|
public string? AuthorPlotSeedSource { get; set; }
|
||||||
public decimal? ExistingPlotLineMatchConfidence { get; set; }
|
public decimal? ExistingPlotLineMatchConfidence { get; set; }
|
||||||
public string? ExistingPlotLineMatchReason { get; set; }
|
public string? ExistingPlotLineMatchReason { get; set; }
|
||||||
public decimal? SeedMatchConfidence { get; set; }
|
public decimal? SeedMatchConfidence { get; set; }
|
||||||
|
|||||||
@ -600,6 +600,13 @@ public sealed class StoryIntelligenceNarrativeReviewService(
|
|||||||
Significance = candidate.Significance ?? string.Empty,
|
Significance = candidate.Significance ?? string.Empty,
|
||||||
Confidence = candidate.Confidence.HasValue ? $"{candidate.Confidence.Value:P0}" : "Unknown",
|
Confidence = candidate.Confidence.HasValue ? $"{candidate.Confidence.Value:P0}" : "Unknown",
|
||||||
ExistingPlotLineID = candidate.ExistingPlotLineID,
|
ExistingPlotLineID = candidate.ExistingPlotLineID,
|
||||||
|
ExistingPlotLineName = candidate.ExistingPlotLineName,
|
||||||
|
AuthorPlotSeedID = candidate.AuthorPlotSeedID,
|
||||||
|
AuthorPlotSeedDescription = candidate.AuthorPlotSeedDescription ?? string.Empty,
|
||||||
|
AuthorPlotSeedSource = candidate.AuthorPlotSeedSource ?? string.Empty,
|
||||||
|
Source = candidate.Source ?? string.Empty,
|
||||||
|
ExistingPlotLineMatchReason = candidate.ExistingPlotLineMatchReason,
|
||||||
|
SeedMatchReason = candidate.SeedMatchReason,
|
||||||
RelatedPlotLineID = candidate.RelatedPlotLineID,
|
RelatedPlotLineID = candidate.RelatedPlotLineID,
|
||||||
RelatedPlotLineName = candidate.RelatedPlotLineName,
|
RelatedPlotLineName = candidate.RelatedPlotLineName,
|
||||||
ParentNarrativeCandidateID = candidate.ParentNarrativeCandidateID,
|
ParentNarrativeCandidateID = candidate.ParentNarrativeCandidateID,
|
||||||
|
|||||||
@ -138,7 +138,9 @@ public sealed class WholeBookPlotIntelligenceService(
|
|||||||
settings.EffectiveWholeBookPlotSynthesisModel,
|
settings.EffectiveWholeBookPlotSynthesisModel,
|
||||||
responseContract: WholeBookPlotSynthesisStructuredOutputSchema.Contract);
|
responseContract: WholeBookPlotSynthesisStructuredOutputSchema.Contract);
|
||||||
var outputJson = ExtractOutputText(result.RawResponseText);
|
var outputJson = ExtractOutputText(result.RawResponseText);
|
||||||
var parsed = WholeBookPlotSynthesisResult.FromJson(outputJson);
|
var parsed = PreserveUnmatchedAuthorSeedsForReview(
|
||||||
|
WholeBookPlotSynthesisResult.FromJson(outputJson),
|
||||||
|
context.AuthorSeeds);
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
|
|
||||||
await repository.CompleteAsync(new WholeBookPlotSynthesisCompletionRequest
|
await repository.CompleteAsync(new WholeBookPlotSynthesisCompletionRequest
|
||||||
@ -181,6 +183,57 @@ public sealed class WholeBookPlotIntelligenceService(
|
|||||||
public StoryIntelligenceResponseContract BuildResponseContractForTest()
|
public StoryIntelligenceResponseContract BuildResponseContractForTest()
|
||||||
=> WholeBookPlotSynthesisStructuredOutputSchema.Contract;
|
=> WholeBookPlotSynthesisStructuredOutputSchema.Contract;
|
||||||
|
|
||||||
|
public static WholeBookPlotSynthesisResult PreserveUnmatchedAuthorSeedsForReviewForTest(
|
||||||
|
WholeBookPlotSynthesisResult result,
|
||||||
|
IReadOnlyList<AuthorPlotSeed> authorSeeds)
|
||||||
|
=> PreserveUnmatchedAuthorSeedsForReview(result, authorSeeds);
|
||||||
|
|
||||||
|
private static WholeBookPlotSynthesisResult PreserveUnmatchedAuthorSeedsForReview(
|
||||||
|
WholeBookPlotSynthesisResult result,
|
||||||
|
IReadOnlyList<AuthorPlotSeed> authorSeeds)
|
||||||
|
{
|
||||||
|
var matchedSeedIds = result.PlotLines
|
||||||
|
.Where(plotLine => plotLine.AuthorPlotSeedID.HasValue)
|
||||||
|
.Select(plotLine => plotLine.AuthorPlotSeedID!.Value)
|
||||||
|
.ToHashSet();
|
||||||
|
var unmatchedSeeds = authorSeeds
|
||||||
|
.Where(seed => !matchedSeedIds.Contains(seed.AuthorPlotSeedID))
|
||||||
|
.ToList();
|
||||||
|
if (unmatchedSeeds.Count == 0)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var plotLines = result.PlotLines.ToList();
|
||||||
|
foreach (var seed in unmatchedSeeds)
|
||||||
|
{
|
||||||
|
plotLines.Add(new WholeBookPlotSynthesisPlotLineResult
|
||||||
|
{
|
||||||
|
Key = $"author-seed-{seed.AuthorPlotSeedID}",
|
||||||
|
ProposedTitle = "Author-provided Plot Line",
|
||||||
|
Description = seed.Description,
|
||||||
|
Significance = "Author intent",
|
||||||
|
BookStatus = "Limited current-book evidence",
|
||||||
|
ExistingPlotLineID = seed.MatchedPlotLineID,
|
||||||
|
ExistingPlotLineMatchConfidence = seed.MatchedPlotLineID.HasValue ? 1m : null,
|
||||||
|
ExistingPlotLineMatchReason = seed.MatchedPlotLineID.HasValue
|
||||||
|
? "Author seed is already linked to an existing Project Plot Line."
|
||||||
|
: null,
|
||||||
|
AuthorPlotSeedID = seed.AuthorPlotSeedID,
|
||||||
|
SeedMatchConfidence = null,
|
||||||
|
SeedMatchReason = "Author-provided seed retained for review; no detected whole-book Plot Line referenced this seed.",
|
||||||
|
Confidence = null,
|
||||||
|
Threads = []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WholeBookPlotSynthesisResult
|
||||||
|
{
|
||||||
|
Metadata = result.Metadata,
|
||||||
|
PlotLines = plotLines
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static string BuildPrompt(WholeBookPlotSynthesisContext context, bool includeOptionalContext = true)
|
private static string BuildPrompt(WholeBookPlotSynthesisContext context, bool includeOptionalContext = true)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|||||||
@ -696,7 +696,9 @@ BEGIN
|
|||||||
nc.ProposedTitle, nc.ProposedDescription, nc.Category, nc.InferredState, nc.ChangeType, nc.Significance,
|
nc.ProposedTitle, nc.ProposedDescription, nc.Category, nc.InferredState, nc.ChangeType, nc.Significance,
|
||||||
nc.ExistingPlotLineID, epl.PlotLineName AS ExistingPlotLineName,
|
nc.ExistingPlotLineID, epl.PlotLineName AS ExistingPlotLineName,
|
||||||
nc.ExistingPlotThreadID, ept.ThreadTitle AS ExistingPlotThreadTitle,
|
nc.ExistingPlotThreadID, ept.ThreadTitle AS ExistingPlotThreadTitle,
|
||||||
nc.WholeBookPlotSynthesisRunID, nc.AuthorPlotSeedID, nc.ExistingPlotLineMatchConfidence,
|
nc.WholeBookPlotSynthesisRunID, nc.AuthorPlotSeedID,
|
||||||
|
aps.Description AS AuthorPlotSeedDescription, aps.Source AS AuthorPlotSeedSource,
|
||||||
|
nc.ExistingPlotLineMatchConfidence,
|
||||||
nc.ExistingPlotLineMatchReason, nc.SeedMatchConfidence, nc.SeedMatchReason, nc.Source,
|
nc.ExistingPlotLineMatchReason, nc.SeedMatchConfidence, nc.SeedMatchReason, nc.Source,
|
||||||
nc.RelatedPlotLineID, rpl.PlotLineName AS RelatedPlotLineName,
|
nc.RelatedPlotLineID, rpl.PlotLineName AS RelatedPlotLineName,
|
||||||
nc.ParentCandidateKey, nc.ParentNarrativeCandidateID, parent.ProposedTitle AS ParentCandidateTitle,
|
nc.ParentCandidateKey, nc.ParentNarrativeCandidateID, parent.ProposedTitle AS ParentCandidateTitle,
|
||||||
@ -706,6 +708,7 @@ BEGIN
|
|||||||
nc.CreatedUtc, nc.UpdatedUtc
|
nc.CreatedUtc, nc.UpdatedUtc
|
||||||
FROM dbo.StoryIntelligenceNarrativeCandidates nc
|
FROM dbo.StoryIntelligenceNarrativeCandidates nc
|
||||||
LEFT JOIN dbo.PlotLines epl ON epl.PlotLineID = nc.ExistingPlotLineID
|
LEFT JOIN dbo.PlotLines epl ON epl.PlotLineID = nc.ExistingPlotLineID
|
||||||
|
LEFT JOIN dbo.AuthorPlotSeeds aps ON aps.AuthorPlotSeedID = nc.AuthorPlotSeedID
|
||||||
LEFT JOIN dbo.PlotThreads ept ON ept.PlotThreadID = nc.ExistingPlotThreadID
|
LEFT JOIN dbo.PlotThreads ept ON ept.PlotThreadID = nc.ExistingPlotThreadID
|
||||||
LEFT JOIN dbo.PlotLines rpl ON rpl.PlotLineID = nc.RelatedPlotLineID
|
LEFT JOIN dbo.PlotLines rpl ON rpl.PlotLineID = nc.RelatedPlotLineID
|
||||||
LEFT JOIN dbo.StoryIntelligenceNarrativeCandidates parent ON parent.NarrativeCandidateID = nc.ParentNarrativeCandidateID
|
LEFT JOIN dbo.StoryIntelligenceNarrativeCandidates parent ON parent.NarrativeCandidateID = nc.ParentNarrativeCandidateID
|
||||||
|
|||||||
@ -279,7 +279,10 @@ public sealed class StoryIntelligenceNarrativeReviewCandidateViewModel
|
|||||||
public string Significance { get; init; } = string.Empty;
|
public string Significance { get; init; } = string.Empty;
|
||||||
public string Confidence { get; init; } = "Unknown";
|
public string Confidence { get; init; } = "Unknown";
|
||||||
public int? ExistingPlotLineID { get; init; }
|
public int? ExistingPlotLineID { get; init; }
|
||||||
|
public string? ExistingPlotLineName { get; init; }
|
||||||
public int? AuthorPlotSeedID { get; init; }
|
public int? AuthorPlotSeedID { get; init; }
|
||||||
|
public string AuthorPlotSeedDescription { get; init; } = string.Empty;
|
||||||
|
public string AuthorPlotSeedSource { get; init; } = string.Empty;
|
||||||
public string Source { get; init; } = string.Empty;
|
public string Source { get; init; } = string.Empty;
|
||||||
public string? ExistingPlotLineMatchReason { get; init; }
|
public string? ExistingPlotLineMatchReason { get; init; }
|
||||||
public string? SeedMatchReason { get; init; }
|
public string? SeedMatchReason { get; init; }
|
||||||
|
|||||||
@ -76,6 +76,25 @@
|
|||||||
<label>Description</label>
|
<label>Description</label>
|
||||||
<textarea class="form-control" name="Candidates[@i].Description" rows="3">@candidate.ProposedDescription</textarea>
|
<textarea class="form-control" name="Candidates[@i].Description" rows="3">@candidate.ProposedDescription</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(candidate.AuthorPlotSeedDescription))
|
||||||
|
{
|
||||||
|
<div class="story-review-note">
|
||||||
|
<strong>Author provided</strong>
|
||||||
|
<p>@candidate.AuthorPlotSeedDescription</p>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(candidate.SeedMatchReason))
|
||||||
|
{
|
||||||
|
<p class="text-muted mb-0">@candidate.SeedMatchReason</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (string.Equals(candidate.Source, WholeBookPlotSynthesisSources.WholeBook, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
<p class="text-muted">Discovered by PlotDirector</p>
|
||||||
|
}
|
||||||
|
@if (candidate.ExistingPlotLineID.HasValue && !string.IsNullOrWhiteSpace(candidate.ExistingPlotLineName))
|
||||||
|
{
|
||||||
|
<p class="text-muted">Existing Plot Line: @candidate.ExistingPlotLineName</p>
|
||||||
|
}
|
||||||
@if (string.Equals(candidate.CandidateType, StoryIntelligenceNarrativeCandidateTypes.Thread, StringComparison.OrdinalIgnoreCase))
|
@if (string.Equals(candidate.CandidateType, StoryIntelligenceNarrativeCandidateTypes.Thread, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@if (!string.IsNullOrWhiteSpace(candidate.ParentCandidateTitle))
|
@if (!string.IsNullOrWhiteSpace(candidate.ParentCandidateTitle))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user