Phase 12I: Knowledge Regression Warnings.
This commit is contained in:
parent
f401416fbd
commit
6a2aeeea90
@ -2544,7 +2544,7 @@ public sealed class StoryStateService(
|
|||||||
warnings.AddRange(BuildMultipleCharacterLocationWarnings(characterTimeline.Appearances, enabledCharacterIds, context));
|
warnings.AddRange(BuildMultipleCharacterLocationWarnings(characterTimeline.Appearances, enabledCharacterIds, context));
|
||||||
warnings.AddRange(BuildImpossibleMovementWarnings(characterTimeline.Appearances, enabledCharacterIds, context));
|
warnings.AddRange(BuildImpossibleMovementWarnings(characterTimeline.Appearances, enabledCharacterIds, context));
|
||||||
warnings.AddRange(BuildUnknownCharacterLocationWarnings(characterTimeline.Characters, characterTimeline.Appearances, enabledCharacterIds, context));
|
warnings.AddRange(BuildUnknownCharacterLocationWarnings(characterTimeline.Characters, characterTimeline.Appearances, enabledCharacterIds, context));
|
||||||
warnings.AddRange(await BuildKnowledgeChronologyWarningsAsync(characterTimeline.Characters, enabledCharacterIds, context));
|
warnings.AddRange(await BuildKnowledgeRegressionWarningsAsync(characterTimeline.Characters, enabledCharacterIds, context));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.IncludeAssets)
|
if (filter.IncludeAssets)
|
||||||
@ -2975,7 +2975,7 @@ public sealed class StoryStateService(
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<ContinuityWarningViewModel>> BuildKnowledgeChronologyWarningsAsync(
|
private async Task<List<ContinuityWarningViewModel>> BuildKnowledgeRegressionWarningsAsync(
|
||||||
IReadOnlyList<Character> projectCharacters,
|
IReadOnlyList<Character> projectCharacters,
|
||||||
IReadOnlySet<int> enabledCharacterIds,
|
IReadOnlySet<int> enabledCharacterIds,
|
||||||
RangeContext context)
|
RangeContext context)
|
||||||
@ -2996,54 +2996,52 @@ public sealed class StoryStateService(
|
|||||||
|
|
||||||
foreach (var group in knowledgeEvents.GroupBy(KnowledgeIdentity))
|
foreach (var group in knowledgeEvents.GroupBy(KnowledgeIdentity))
|
||||||
{
|
{
|
||||||
var established = group
|
var progressionEvents = group
|
||||||
.Where(x => string.Equals(x.KnowledgeStateName, "Knows", StringComparison.OrdinalIgnoreCase))
|
.Select(x => new { Item = x, Rank = KnowledgeStateRank(x.KnowledgeStateName) })
|
||||||
.OrderBy(x => context.SceneIndexes.GetValueOrDefault(x.SceneID, int.MaxValue))
|
.Where(x => x.Rank.HasValue)
|
||||||
.ThenBy(x => x.CreatedDate)
|
.OrderBy(x => context.SceneIndexes.GetValueOrDefault(x.Item.SceneID, int.MaxValue))
|
||||||
.ThenBy(x => x.CharacterKnowledgeID)
|
.ThenBy(x => x.Item.CreatedDate)
|
||||||
.FirstOrDefault();
|
.ThenBy(x => x.Item.CharacterKnowledgeID)
|
||||||
|
|
||||||
if (established is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var establishedSceneIndex = context.SceneIndexes.GetValueOrDefault(established.SceneID, int.MaxValue);
|
|
||||||
var earlierReferences = group
|
|
||||||
.Where(x => x.CharacterKnowledgeID != established.CharacterKnowledgeID
|
|
||||||
&& context.AllowedSceneIds.Contains(x.SceneID)
|
|
||||||
&& context.SceneIndexes.GetValueOrDefault(x.SceneID, int.MaxValue) < establishedSceneIndex)
|
|
||||||
.GroupBy(x => x.SceneID)
|
|
||||||
.Select(x => x.OrderBy(row => row.CreatedDate).ThenBy(row => row.CharacterKnowledgeID).First())
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var reference in earlierReferences)
|
for (var i = 1; i < progressionEvents.Count; i++)
|
||||||
{
|
{
|
||||||
|
var previous = progressionEvents[i - 1];
|
||||||
|
var current = progressionEvents[i];
|
||||||
|
if (current.Rank!.Value >= previous.Rank!.Value || !context.AllowedSceneIds.Contains(current.Item.SceneID))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var knowledgeDescription = KnowledgeTitle(current.Item);
|
||||||
var warning = BuildWarning(
|
var warning = BuildWarning(
|
||||||
$"Knowledge Chronology Violation|K:{established.CharacterKnowledgeID}",
|
$"KnowledgeRegression|Knowledge:{NormaliseWarningKeyPart(knowledgeDescription)}",
|
||||||
"Warning",
|
"Warning",
|
||||||
$"{character.CharacterName} appears to know \"{KnowledgeTitle(established)}\" before this knowledge is established.",
|
KnowledgeRegressionMessage(character.CharacterName, knowledgeDescription, previous.Item.KnowledgeStateName, current.Item.KnowledgeStateName),
|
||||||
reference.SceneID,
|
current.Item.SceneID,
|
||||||
context,
|
context,
|
||||||
characterId: character.CharacterID);
|
characterId: character.CharacterID);
|
||||||
|
|
||||||
warning.WarningTypeDisplayName = "Knowledge Chronology Violation";
|
warning.WarningTypeDisplayName = "Knowledge Regression";
|
||||||
warning.CharacterName = character.CharacterName;
|
warning.CharacterName = character.CharacterName;
|
||||||
warning.KnowledgeId = established.CharacterKnowledgeID;
|
warning.KnowledgeId = current.Item.CharacterKnowledgeID;
|
||||||
warning.KnowledgeTitle = KnowledgeTitle(established);
|
warning.KnowledgeTitle = knowledgeDescription;
|
||||||
warning.EstablishedSceneId = established.SceneID;
|
warning.KnowledgeDescription = knowledgeDescription;
|
||||||
warning.EstablishedSceneDisplayName = context.SceneContexts.GetValueOrDefault(established.SceneID)?.SceneDisplayName
|
warning.PreviousKnowledgeState = previous.Item.KnowledgeStateName;
|
||||||
?? context.SceneLabels.GetValueOrDefault(established.SceneID, SceneLabel(established.SceneNumber, established.SceneTitle));
|
warning.CurrentKnowledgeState = current.Item.KnowledgeStateName;
|
||||||
warning.ReferencedSceneId = reference.SceneID;
|
warning.PreviousSceneId = previous.Item.SceneID;
|
||||||
warning.ReferencedSceneDisplayName = context.SceneContexts.GetValueOrDefault(reference.SceneID)?.SceneDisplayName
|
warning.PreviousSceneDisplayName = context.SceneContexts.GetValueOrDefault(previous.Item.SceneID)?.SceneDisplayName
|
||||||
?? context.SceneLabels.GetValueOrDefault(reference.SceneID, SceneLabel(reference.SceneNumber, reference.SceneTitle));
|
?? context.SceneLabels.GetValueOrDefault(previous.Item.SceneID, SceneLabel(previous.Item.SceneNumber, previous.Item.SceneTitle));
|
||||||
|
warning.CurrentSceneId = current.Item.SceneID;
|
||||||
|
warning.CurrentSceneDisplayName = context.SceneContexts.GetValueOrDefault(current.Item.SceneID)?.SceneDisplayName
|
||||||
|
?? context.SceneLabels.GetValueOrDefault(current.Item.SceneID, SceneLabel(current.Item.SceneNumber, current.Item.SceneTitle));
|
||||||
warnings.Add(warning);
|
warnings.Add(warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return warnings
|
return warnings
|
||||||
.GroupBy(x => new { x.CharacterID, x.KnowledgeId, x.ReferencedSceneId, x.EstablishedSceneId })
|
.GroupBy(x => new { x.CharacterID, x.KnowledgeDescription, x.CurrentSceneId })
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@ -3257,6 +3255,63 @@ public sealed class StoryStateService(
|
|||||||
return string.IsNullOrWhiteSpace(item.Description) ? "Knowledge" : item.Description.Trim();
|
return string.IsNullOrWhiteSpace(item.Description) ? "Knowledge" : item.Description.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int? KnowledgeStateRank(string stateName)
|
||||||
|
{
|
||||||
|
return stateName switch
|
||||||
|
{
|
||||||
|
"Unaware" => 0,
|
||||||
|
"Suspects" => 1,
|
||||||
|
"Partially Knows" => 2,
|
||||||
|
"Knows" => 3,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string KnowledgeRegressionMessage(string characterName, string knowledgeDescription, string previousState, string currentState)
|
||||||
|
{
|
||||||
|
if (string.Equals(previousState, "Knows", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& string.Equals(currentState, "Unaware", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"{characterName} appears unaware of \"{knowledgeDescription}\" after previously knowing it.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(previousState, "Knows", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& string.Equals(currentState, "Suspects", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"{characterName} appears to regress from knowing to suspecting \"{knowledgeDescription}\".";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(previousState, "Knows", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& string.Equals(currentState, "Partially Knows", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"{characterName} appears to lose understanding of \"{knowledgeDescription}\".";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(currentState, "Unaware", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return $"{characterName} appears to lose awareness of \"{knowledgeDescription}\".";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{characterName} appears to regress from {KnowledgeStatePhrase(previousState)} to {KnowledgeStatePhrase(currentState)} \"{knowledgeDescription}\".";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string KnowledgeStatePhrase(string stateName)
|
||||||
|
{
|
||||||
|
return stateName switch
|
||||||
|
{
|
||||||
|
"Unaware" => "being unaware of",
|
||||||
|
"Suspects" => "suspecting",
|
||||||
|
"Partially Knows" => "partially knowing",
|
||||||
|
"Knows" => "knowing",
|
||||||
|
_ => stateName.ToLowerInvariant()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormaliseWarningKeyPart(string value)
|
||||||
|
{
|
||||||
|
return string.Join(" ", value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
|
||||||
|
}
|
||||||
|
|
||||||
private static void ApplyAcknowledgements(
|
private static void ApplyAcknowledgements(
|
||||||
IReadOnlyList<ContinuityWarningViewModel> warnings,
|
IReadOnlyList<ContinuityWarningViewModel> warnings,
|
||||||
IReadOnlyList<ContinuityWarningAcknowledgement> acknowledgements)
|
IReadOnlyList<ContinuityWarningAcknowledgement> acknowledgements)
|
||||||
@ -4080,6 +4135,13 @@ public sealed class ContinuityValidationService(
|
|||||||
WarningKey = warning.WarningKey,
|
WarningKey = warning.WarningKey,
|
||||||
KnowledgeId = warning.KnowledgeId,
|
KnowledgeId = warning.KnowledgeId,
|
||||||
KnowledgeTitle = warning.KnowledgeTitle,
|
KnowledgeTitle = warning.KnowledgeTitle,
|
||||||
|
KnowledgeDescription = warning.KnowledgeDescription,
|
||||||
|
PreviousKnowledgeState = warning.PreviousKnowledgeState,
|
||||||
|
CurrentKnowledgeState = warning.CurrentKnowledgeState,
|
||||||
|
PreviousSceneId = warning.PreviousSceneId,
|
||||||
|
PreviousSceneDisplayName = warning.PreviousSceneDisplayName,
|
||||||
|
CurrentSceneId = warning.CurrentSceneId,
|
||||||
|
CurrentSceneDisplayName = warning.CurrentSceneDisplayName,
|
||||||
EstablishedSceneId = warning.EstablishedSceneId,
|
EstablishedSceneId = warning.EstablishedSceneId,
|
||||||
EstablishedSceneDisplayName = warning.EstablishedSceneDisplayName,
|
EstablishedSceneDisplayName = warning.EstablishedSceneDisplayName,
|
||||||
ReferencedSceneId = warning.ReferencedSceneId,
|
ReferencedSceneId = warning.ReferencedSceneId,
|
||||||
@ -4103,7 +4165,7 @@ public sealed class ContinuityValidationService(
|
|||||||
|
|
||||||
private static string WarningCategoryForGenerated(ContinuityWarningViewModel warning)
|
private static string WarningCategoryForGenerated(ContinuityWarningViewModel warning)
|
||||||
{
|
{
|
||||||
if (string.Equals(warning.WarningTypeDisplayName, "Knowledge Chronology Violation", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(warning.WarningTypeDisplayName, "Knowledge Regression", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return "Knowledge";
|
return "Knowledge";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -333,6 +333,13 @@ public sealed class ContinuityWarningViewModel
|
|||||||
public string? AcknowledgementNotes { get; set; }
|
public string? AcknowledgementNotes { get; set; }
|
||||||
public int? KnowledgeId { get; set; }
|
public int? KnowledgeId { get; set; }
|
||||||
public string KnowledgeTitle { get; set; } = string.Empty;
|
public string KnowledgeTitle { get; set; } = string.Empty;
|
||||||
|
public string KnowledgeDescription { get; set; } = string.Empty;
|
||||||
|
public string PreviousKnowledgeState { get; set; } = string.Empty;
|
||||||
|
public string CurrentKnowledgeState { get; set; } = string.Empty;
|
||||||
|
public int? PreviousSceneId { get; set; }
|
||||||
|
public string PreviousSceneDisplayName { get; set; } = string.Empty;
|
||||||
|
public int? CurrentSceneId { get; set; }
|
||||||
|
public string CurrentSceneDisplayName { get; set; } = string.Empty;
|
||||||
public int? EstablishedSceneId { get; set; }
|
public int? EstablishedSceneId { get; set; }
|
||||||
public string EstablishedSceneDisplayName { get; set; } = string.Empty;
|
public string EstablishedSceneDisplayName { get; set; } = string.Empty;
|
||||||
public int? ReferencedSceneId { get; set; }
|
public int? ReferencedSceneId { get; set; }
|
||||||
@ -1689,6 +1696,13 @@ public sealed class ProjectWarningViewModel
|
|||||||
public string WarningKey { get; set; } = string.Empty;
|
public string WarningKey { get; set; } = string.Empty;
|
||||||
public int? KnowledgeId { get; set; }
|
public int? KnowledgeId { get; set; }
|
||||||
public string KnowledgeTitle { get; set; } = string.Empty;
|
public string KnowledgeTitle { get; set; } = string.Empty;
|
||||||
|
public string KnowledgeDescription { get; set; } = string.Empty;
|
||||||
|
public string PreviousKnowledgeState { get; set; } = string.Empty;
|
||||||
|
public string CurrentKnowledgeState { get; set; } = string.Empty;
|
||||||
|
public int? PreviousSceneId { get; set; }
|
||||||
|
public string PreviousSceneDisplayName { get; set; } = string.Empty;
|
||||||
|
public int? CurrentSceneId { get; set; }
|
||||||
|
public string CurrentSceneDisplayName { get; set; } = string.Empty;
|
||||||
public int? EstablishedSceneId { get; set; }
|
public int? EstablishedSceneId { get; set; }
|
||||||
public string EstablishedSceneDisplayName { get; set; } = string.Empty;
|
public string EstablishedSceneDisplayName { get; set; } = string.Empty;
|
||||||
public int? ReferencedSceneId { get; set; }
|
public int? ReferencedSceneId { get; set; }
|
||||||
|
|||||||
@ -175,6 +175,24 @@
|
|||||||
{
|
{
|
||||||
<small class="text-muted d-block">Knowledge: @warning.KnowledgeTitle</small>
|
<small class="text-muted d-block">Knowledge: @warning.KnowledgeTitle</small>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(warning.PreviousKnowledgeState) && !string.IsNullOrWhiteSpace(warning.CurrentKnowledgeState))
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block">State: @warning.PreviousKnowledgeState → @warning.CurrentKnowledgeState</small>
|
||||||
|
}
|
||||||
|
@if (warning.PreviousSceneId.HasValue)
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block">
|
||||||
|
Previous:
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.PreviousSceneId">@warning.PreviousSceneDisplayName</a>
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
@if (warning.CurrentSceneId.HasValue)
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block">
|
||||||
|
Current:
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.CurrentSceneId">@warning.CurrentSceneDisplayName</a>
|
||||||
|
</small>
|
||||||
|
}
|
||||||
@if (warning.EstablishedSceneId.HasValue)
|
@if (warning.EstablishedSceneId.HasValue)
|
||||||
{
|
{
|
||||||
<small class="text-muted d-block">
|
<small class="text-muted d-block">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user