Phase 12G: Knowledge Continuity Warnings.
This commit is contained in:
parent
738b9f496d
commit
e008da0cf4
@ -2546,6 +2546,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.IncludeAssets)
|
if (filter.IncludeAssets)
|
||||||
@ -2565,7 +2566,7 @@ public sealed class StoryStateService(
|
|||||||
.ThenBy(x => x.BookDisplayName)
|
.ThenBy(x => x.BookDisplayName)
|
||||||
.ThenBy(x => x.ChapterDisplayName)
|
.ThenBy(x => x.ChapterDisplayName)
|
||||||
.ThenBy(x => x.SceneID ?? int.MaxValue)
|
.ThenBy(x => x.SceneID ?? int.MaxValue)
|
||||||
.ThenBy(x => x.WarningType)
|
.ThenBy(x => x.WarningTypeDisplayName)
|
||||||
.ThenBy(x => x.Message)
|
.ThenBy(x => x.Message)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@ -2976,6 +2977,79 @@ public sealed class StoryStateService(
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<List<ContinuityWarningViewModel>> BuildKnowledgeChronologyWarningsAsync(
|
||||||
|
IReadOnlyList<Character> projectCharacters,
|
||||||
|
IReadOnlySet<int> enabledCharacterIds,
|
||||||
|
RangeContext context)
|
||||||
|
{
|
||||||
|
var warnings = new List<ContinuityWarningViewModel>();
|
||||||
|
var charactersToCheck = projectCharacters
|
||||||
|
.Where(character => !enabledCharacterIds.Any() || enabledCharacterIds.Contains(character.CharacterID))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var character in charactersToCheck)
|
||||||
|
{
|
||||||
|
var knowledgeEvents = (await characters.ListKnowledgeByCharacterAsync(character.CharacterID))
|
||||||
|
.Where(x => context.SceneIndexes.ContainsKey(x.SceneID))
|
||||||
|
.OrderBy(x => context.SceneIndexes.GetValueOrDefault(x.SceneID, int.MaxValue))
|
||||||
|
.ThenBy(x => x.CreatedDate)
|
||||||
|
.ThenBy(x => x.CharacterKnowledgeID)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var group in knowledgeEvents.GroupBy(KnowledgeIdentity))
|
||||||
|
{
|
||||||
|
var established = group
|
||||||
|
.Where(x => string.Equals(x.KnowledgeStateName, "Knows", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(x => context.SceneIndexes.GetValueOrDefault(x.SceneID, int.MaxValue))
|
||||||
|
.ThenBy(x => x.CreatedDate)
|
||||||
|
.ThenBy(x => x.CharacterKnowledgeID)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
foreach (var reference in earlierReferences)
|
||||||
|
{
|
||||||
|
var warning = BuildWarning(
|
||||||
|
$"Knowledge Chronology Violation|K:{established.CharacterKnowledgeID}",
|
||||||
|
"Warning",
|
||||||
|
$"{character.CharacterName} appears to know \"{KnowledgeTitle(established)}\" before this knowledge is established.",
|
||||||
|
reference.SceneID,
|
||||||
|
context,
|
||||||
|
characterId: character.CharacterID);
|
||||||
|
|
||||||
|
warning.WarningTypeDisplayName = "Knowledge Chronology Violation";
|
||||||
|
warning.CharacterName = character.CharacterName;
|
||||||
|
warning.KnowledgeId = established.CharacterKnowledgeID;
|
||||||
|
warning.KnowledgeTitle = KnowledgeTitle(established);
|
||||||
|
warning.EstablishedSceneId = established.SceneID;
|
||||||
|
warning.EstablishedSceneDisplayName = context.SceneContexts.GetValueOrDefault(established.SceneID)?.SceneDisplayName
|
||||||
|
?? context.SceneLabels.GetValueOrDefault(established.SceneID, SceneLabel(established.SceneNumber, established.SceneTitle));
|
||||||
|
warning.ReferencedSceneId = reference.SceneID;
|
||||||
|
warning.ReferencedSceneDisplayName = context.SceneContexts.GetValueOrDefault(reference.SceneID)?.SceneDisplayName
|
||||||
|
?? context.SceneLabels.GetValueOrDefault(reference.SceneID, SceneLabel(reference.SceneNumber, reference.SceneTitle));
|
||||||
|
warnings.Add(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings
|
||||||
|
.GroupBy(x => new { x.CharacterID, x.KnowledgeId, x.ReferencedSceneId, x.EstablishedSceneId })
|
||||||
|
.Select(x => x.First())
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private static LocationActivityEntityViewModel? BuildCurrentCharacterPresence(
|
private static LocationActivityEntityViewModel? BuildCurrentCharacterPresence(
|
||||||
Character character,
|
Character character,
|
||||||
IReadOnlyList<SceneCharacter> assignments,
|
IReadOnlyList<SceneCharacter> assignments,
|
||||||
@ -3143,6 +3217,7 @@ public sealed class StoryStateService(
|
|||||||
{
|
{
|
||||||
WarningKey = BuildWarningKey(warningType, sceneId, characterId, assetId),
|
WarningKey = BuildWarningKey(warningType, sceneId, characterId, assetId),
|
||||||
WarningType = warningType,
|
WarningType = warningType,
|
||||||
|
WarningTypeDisplayName = warningType,
|
||||||
Severity = severity,
|
Severity = severity,
|
||||||
Message = message,
|
Message = message,
|
||||||
CharacterID = characterId,
|
CharacterID = characterId,
|
||||||
@ -3154,6 +3229,36 @@ public sealed class StoryStateService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string KnowledgeIdentity(CharacterKnowledgeItem item)
|
||||||
|
{
|
||||||
|
if (item.StoryAssetID.HasValue)
|
||||||
|
{
|
||||||
|
return $"Asset:{item.StoryAssetID.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.PlotThreadID.HasValue)
|
||||||
|
{
|
||||||
|
return $"Thread:{item.PlotThreadID.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"Description:{(item.Description ?? string.Empty).Trim().ToUpperInvariant()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string KnowledgeTitle(CharacterKnowledgeItem item)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.AssetName))
|
||||||
|
{
|
||||||
|
return item.AssetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.ThreadTitle))
|
||||||
|
{
|
||||||
|
return item.ThreadTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(item.Description) ? "Knowledge" : item.Description.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
private static void ApplyAcknowledgements(
|
private static void ApplyAcknowledgements(
|
||||||
IReadOnlyList<ContinuityWarningViewModel> warnings,
|
IReadOnlyList<ContinuityWarningViewModel> warnings,
|
||||||
IReadOnlyList<ContinuityWarningAcknowledgement> acknowledgements)
|
IReadOnlyList<ContinuityWarningAcknowledgement> acknowledgements)
|
||||||
|
|||||||
@ -319,9 +319,11 @@ public sealed class ContinuityWarningViewModel
|
|||||||
{
|
{
|
||||||
public string WarningKey { get; set; } = string.Empty;
|
public string WarningKey { get; set; } = string.Empty;
|
||||||
public string WarningType { get; set; } = string.Empty;
|
public string WarningType { get; set; } = string.Empty;
|
||||||
|
public string WarningTypeDisplayName { get; set; } = string.Empty;
|
||||||
public string Severity { get; set; } = "Information";
|
public string Severity { get; set; } = "Information";
|
||||||
public string Message { get; set; } = string.Empty;
|
public string Message { get; set; } = string.Empty;
|
||||||
public int? CharacterID { get; set; }
|
public int? CharacterID { get; set; }
|
||||||
|
public string CharacterName { get; set; } = string.Empty;
|
||||||
public int? AssetID { get; set; }
|
public int? AssetID { get; set; }
|
||||||
public int? SceneID { get; set; }
|
public int? SceneID { get; set; }
|
||||||
public string SceneDisplayName { get; set; } = string.Empty;
|
public string SceneDisplayName { get; set; } = string.Empty;
|
||||||
@ -329,6 +331,12 @@ public sealed class ContinuityWarningViewModel
|
|||||||
public string ChapterDisplayName { get; set; } = string.Empty;
|
public string ChapterDisplayName { get; set; } = string.Empty;
|
||||||
public bool IsAcknowledged { get; set; }
|
public bool IsAcknowledged { get; set; }
|
||||||
public string? AcknowledgementNotes { get; set; }
|
public string? AcknowledgementNotes { get; set; }
|
||||||
|
public int? KnowledgeId { get; set; }
|
||||||
|
public string KnowledgeTitle { get; set; } = string.Empty;
|
||||||
|
public int? EstablishedSceneId { get; set; }
|
||||||
|
public string EstablishedSceneDisplayName { get; set; } = string.Empty;
|
||||||
|
public int? ReferencedSceneId { get; set; }
|
||||||
|
public string ReferencedSceneDisplayName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterJourneyViewModel
|
public sealed class CharacterJourneyViewModel
|
||||||
|
|||||||
@ -382,7 +382,11 @@ else if (Model.Filter.DisplayMode == "ContinuityWarnings")
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Severity</th>
|
<th>Severity</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Warning Type</th>
|
<th>Type</th>
|
||||||
|
<th>Character</th>
|
||||||
|
<th>Knowledge</th>
|
||||||
|
<th>Established</th>
|
||||||
|
<th>Earlier Reference</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
<th>Scene</th>
|
<th>Scene</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
@ -403,7 +407,47 @@ else if (Model.Filter.DisplayMode == "ContinuityWarnings")
|
|||||||
<span class="badge bg-warning text-dark">Active</span>
|
<span class="badge bg-warning text-dark">Active</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>@warning.WarningType</td>
|
<td>@warning.WarningTypeDisplayName</td>
|
||||||
|
<td>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(warning.CharacterName))
|
||||||
|
{
|
||||||
|
@warning.CharacterName
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="muted">-</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if (!string.IsNullOrWhiteSpace(warning.KnowledgeTitle))
|
||||||
|
{
|
||||||
|
@warning.KnowledgeTitle
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="muted">-</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if (warning.EstablishedSceneId.HasValue)
|
||||||
|
{
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.EstablishedSceneId">@warning.EstablishedSceneDisplayName</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="muted">-</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if (warning.ReferencedSceneId.HasValue)
|
||||||
|
{
|
||||||
|
<a asp-controller="Timeline" asp-action="Index" asp-route-projectId="@Model.Project.ProjectID" asp-route-selectedSceneId="@warning.ReferencedSceneId">@warning.ReferencedSceneDisplayName</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="muted">-</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>@warning.Message</div>
|
<div>@warning.Message</div>
|
||||||
@if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
|
@if (warning.IsAcknowledged && !string.IsNullOrWhiteSpace(warning.AcknowledgementNotes))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user