Phase 21N - Fix Entity Evidence Corruption
This commit is contained in:
parent
7001359281
commit
a54da80ea2
@ -568,6 +568,8 @@ static void IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictS
|
|||||||
var lad = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("tubby lad", ["tubby lad"], []);
|
var lad = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("tubby lad", ["tubby lad"], []);
|
||||||
var girl = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("girl", ["schoolgirl"], []);
|
var girl = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("girl", ["schoolgirl"], []);
|
||||||
var kevin = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Kevin", ["Kevin", "female instructor (Kevin's)"], ["female instructor", "Kevin's examiner"]);
|
var kevin = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Kevin", ["Kevin", "female instructor (Kevin's)"], ["female instructor", "Kevin's examiner"]);
|
||||||
|
var colin = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Colin", ["Colin mother waits nearby", "female instructor"], []);
|
||||||
|
var david = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("David", ["David the woman speaks first", "female examiner"], []);
|
||||||
|
|
||||||
Assert(femaleInstructor.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"female instructor should be Feminine, got {femaleInstructor.Presentation}.");
|
Assert(femaleInstructor.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"female instructor should be Feminine, got {femaleInstructor.Presentation}.");
|
||||||
Assert(maleExaminers.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"male examiners should be Masculine, got {maleExaminers.Presentation}.");
|
Assert(maleExaminers.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"male examiners should be Masculine, got {maleExaminers.Presentation}.");
|
||||||
@ -576,6 +578,10 @@ static void IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictS
|
|||||||
Assert(girl.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"girl should be Feminine, got {girl.Presentation}.");
|
Assert(girl.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Feminine, $"girl should be Feminine, got {girl.Presentation}.");
|
||||||
Assert(girl.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Child, $"schoolgirl should infer Child, got {girl.AgeBand}.");
|
Assert(girl.AgeBand == StoryIntelligenceIllustrationCompatibility.AgeBands.Child, $"schoolgirl should infer Child, got {girl.AgeBand}.");
|
||||||
Assert(kevin.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"Kevin should not inherit feminine presentation from his instructor, got {kevin.Presentation}.");
|
Assert(kevin.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"Kevin should not inherit feminine presentation from his instructor, got {kevin.Presentation}.");
|
||||||
|
Assert(colin.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"Colin should not inherit feminine presentation from neighbouring evidence, got {colin.Presentation}.");
|
||||||
|
Assert(colin.Warnings.Any(warning => warning.Contains("corrected Colin", StringComparison.OrdinalIgnoreCase)), "Colin correction should be visible in diagnostics warnings.");
|
||||||
|
Assert(david.Presentation == StoryIntelligenceIllustrationCompatibility.Presentations.Masculine, $"David should not inherit feminine presentation from neighbouring evidence, got {david.Presentation}.");
|
||||||
|
Assert(david.Warnings.Any(warning => warning.Contains("corrected David", StringComparison.OrdinalIgnoreCase)), "David correction should be visible in diagnostics warnings.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.IsGroupEntity("social workers"), "Social workers should be treated as a group entity.");
|
Assert(StoryIntelligenceIllustrationCompatibility.IsGroupEntity("social workers"), "Social workers should be treated as a group entity.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.IsGroupEntity("younger male examiners"), "Plural examiners should be treated as a group entity.");
|
Assert(StoryIntelligenceIllustrationCompatibility.IsGroupEntity("younger male examiners"), "Plural examiners should be treated as a group entity.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.IsGroupEntity("uniformed people"), "Uniformed people should be treated as a group entity.");
|
Assert(StoryIntelligenceIllustrationCompatibility.IsGroupEntity("uniformed people"), "Uniformed people should be treated as a group entity.");
|
||||||
@ -589,6 +595,10 @@ static void IllustrationAssignmentRepairEnforcesExplicitEvidenceGroupsAndStrictS
|
|||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("supercharger") == "CarPart", "Supercharger should not classify as full car.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("supercharger") == "CarPart", "Supercharger should not classify as full car.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("biscuit tin") == "Tin", "Biscuit tin should not classify as Photograph.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("biscuit tin") == "Tin", "Biscuit tin should not classify as Photograph.");
|
||||||
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("parcel") == "Parcel", "Parcel should not classify as Letter.");
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("parcel") == "Parcel", "Parcel should not classify as Letter.");
|
||||||
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("pass certificate") == "Document", "Pass certificate should classify as Document.");
|
||||||
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("driving licence") == "Document", "Driving licence should classify as Document.");
|
||||||
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("thin cotton blouse") == "Clothing", "Blouse should classify as Clothing.");
|
||||||
|
Assert(StoryIntelligenceIllustrationCompatibility.AssetType("two pairs of sunglasses") == "Clothing", "Sunglasses should classify as Clothing.");
|
||||||
|
|
||||||
var beth = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Beth", ["Beth is fifteen."], []);
|
var beth = StoryIntelligenceIllustrationCompatibility.InferCharacterEvidence("Beth", ["Beth is fifteen."], []);
|
||||||
var adult = Metadata("char-family-relative", "MatureAdult", "Feminine");
|
var adult = Metadata("char-family-relative", "MatureAdult", "Feminine");
|
||||||
|
|||||||
@ -67,6 +67,15 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
namePresentation.Value,
|
namePresentation.Value,
|
||||||
Presentations.Unknown);
|
Presentations.Unknown);
|
||||||
var presentationConfidence = new[] { titlePresentation.Confidence, entityPresentation.Confidence, relationshipPresentation.Confidence, pronounPresentation.Confidence, namePresentation.Confidence }.Max();
|
var presentationConfidence = new[] { titlePresentation.Confidence, entityPresentation.Confidence, relationshipPresentation.Confidence, pronounPresentation.Confidence, namePresentation.Confidence }.Max();
|
||||||
|
var warnings = new List<string>();
|
||||||
|
var guardedPresentation = ApplyPresentationGuard(
|
||||||
|
name,
|
||||||
|
scopedText,
|
||||||
|
presentation,
|
||||||
|
presentationConfidence,
|
||||||
|
warnings);
|
||||||
|
presentation = guardedPresentation.Value;
|
||||||
|
presentationConfidence = guardedPresentation.Confidence;
|
||||||
var explicitAge = ExplicitAgeBand(scopedText);
|
var explicitAge = ExplicitAgeBand(scopedText);
|
||||||
var relationshipAge = RelationshipAgeBand(name, scopedText);
|
var relationshipAge = RelationshipAgeBand(name, scopedText);
|
||||||
var namedAge = NameAgeBand(name);
|
var namedAge = NameAgeBand(name);
|
||||||
@ -89,7 +98,8 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
presentationConfidence,
|
presentationConfidence,
|
||||||
relationshipPresentation.Confidence,
|
relationshipPresentation.Confidence,
|
||||||
namePresentation.Confidence,
|
namePresentation.Confidence,
|
||||||
pronounPresentation.Confidence);
|
pronounPresentation.Confidence,
|
||||||
|
warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IllustrationCharacterMetadata CharacterMetadataFrom(IllustrationLibraryItem item)
|
public static IllustrationCharacterMetadata CharacterMetadataFrom(IllustrationLibraryItem item)
|
||||||
@ -259,13 +269,14 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
if (text.Contains("bottle", StringComparison.OrdinalIgnoreCase)) return "Bottle";
|
if (text.Contains("bottle", StringComparison.OrdinalIgnoreCase)) return "Bottle";
|
||||||
if (ContainsAny(text, "money", "cash", "coins", "banknote")) return "Money";
|
if (ContainsAny(text, "money", "cash", "coins", "banknote")) return "Money";
|
||||||
if (ContainsAny(text, "scarf", "scarves")) return "Clothing";
|
if (ContainsAny(text, "scarf", "scarves")) return "Clothing";
|
||||||
if (ContainsAny(text, "driving licence", "driver licence", "license", "licence")) return "Document";
|
if (ContainsAny(text, "driving licence", "driver licence", "provisional licence", "license", "licence", "pass certificate", "certificate", "paperwork")) return "Document";
|
||||||
if (ContainsAny(text, "ring", "necklace", "jewellery", "jewelry")) return "Jewellery";
|
if (ContainsAny(text, "ring", "necklace", "jewellery", "jewelry")) return "Jewellery";
|
||||||
if (ContainsAny(text, "gun", "pistol", "rifle", "weapon")) return "Weapon";
|
if (ContainsAny(text, "gun", "pistol", "rifle", "weapon")) return "Weapon";
|
||||||
if (ContainsAny(padded, " ford fiesta ", " fiesta ", " motor car ", " car ", " vehicle ")) return "Car";
|
if (ContainsAny(padded, " ford fiesta ", " fiesta ", " motor car ", " car ", " vehicle ")) return "Car";
|
||||||
if (ContainsAny(text, "supercharger", "engine part")) return "CarPart";
|
if (ContainsAny(text, "supercharger", "engine part")) return "CarPart";
|
||||||
if (ContainsAny(text, "biscuit tin", "tin")) return "Tin";
|
if (ContainsAny(text, "biscuit tin", "tin")) return "Tin";
|
||||||
if (ContainsAny(text, "parcel", "package")) return "Parcel";
|
if (ContainsAny(text, "parcel", "package")) return "Parcel";
|
||||||
|
if (ContainsAny(text, "blouse", "skirt", "jumper", "jumpers", "sunglasses", "stockings")) return "Clothing";
|
||||||
if (text.Contains("rucksack", StringComparison.OrdinalIgnoreCase)) return "Rucksack";
|
if (text.Contains("rucksack", StringComparison.OrdinalIgnoreCase)) return "Rucksack";
|
||||||
if (text.Contains("suitcase", StringComparison.OrdinalIgnoreCase)) return "Suitcase";
|
if (text.Contains("suitcase", StringComparison.OrdinalIgnoreCase)) return "Suitcase";
|
||||||
if (text.Contains("note", StringComparison.OrdinalIgnoreCase)) return "Note";
|
if (text.Contains("note", StringComparison.OrdinalIgnoreCase)) return "Note";
|
||||||
@ -306,6 +317,9 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
|| clean is "crowd" or "uniformed people" or "police" or "social services";
|
|| clean is "crowd" or "uniformed people" or "police" or "social services";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string PresentationGuardWarning(string name, string attemptedPresentation, string correctedPresentation)
|
||||||
|
=> $"WARNING: Entity evidence guard corrected {name} presentation from {attemptedPresentation} to {correctedPresentation}; title/name hard evidence overrode leaked or unrelated evidence.";
|
||||||
|
|
||||||
private static EvidenceSignal ExplicitAgeBand(string text)
|
private static EvidenceSignal ExplicitAgeBand(string text)
|
||||||
{
|
{
|
||||||
if (ContainsAny(text, "15", "fifteen")) return new(AgeBands.YoungTeen, 0.98m);
|
if (ContainsAny(text, "15", "fifteen")) return new(AgeBands.YoungTeen, 0.98m);
|
||||||
@ -369,6 +383,73 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
return EvidenceSignal.Unknown;
|
return EvidenceSignal.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static EvidenceSignal ApplyPresentationGuard(string name, string scopedText, string presentation, decimal confidence, ICollection<string> warnings)
|
||||||
|
{
|
||||||
|
var nameSignal = NameOrTitlePresentation(name);
|
||||||
|
if (nameSignal.Value == Presentations.Unknown)
|
||||||
|
{
|
||||||
|
return new(presentation, confidence);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (presentation == Presentations.Unknown || presentation == nameSignal.Value)
|
||||||
|
{
|
||||||
|
return new(nameSignal.Value, Math.Max(confidence, nameSignal.Confidence));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HasExplicitContradictoryPresentation(name, scopedText, presentation))
|
||||||
|
{
|
||||||
|
warnings.Add($"WARNING: {name} has explicit contradictory presentation evidence: {presentation} conflicts with {nameSignal.Value} name/title evidence.");
|
||||||
|
return new(presentation, confidence);
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings.Add(PresentationGuardWarning(name, presentation, nameSignal.Value));
|
||||||
|
return new(nameSignal.Value, Math.Max(confidence, nameSignal.Confidence));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EvidenceSignal NameOrTitlePresentation(string name)
|
||||||
|
{
|
||||||
|
var clean = CleanWords(name);
|
||||||
|
if (clean.Length == 0)
|
||||||
|
{
|
||||||
|
return EvidenceSignal.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StartsWithAny(clean, "mr", "father", "dad", "uncle", "brother", "son", "boy", "man", "male")
|
||||||
|
|| clean is "graham" or "george" or "john" or "david" or "simon" or "kevin" or "colin")
|
||||||
|
{
|
||||||
|
return new(Presentations.Masculine, 0.98m);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StartsWithAny(clean, "mrs", "miss", "ms", "mother", "mum", "mom", "aunt", "sister", "daughter", "girl", "woman", "female")
|
||||||
|
|| clean is "beth" or "maggie" or "rosie" or "grace" or "annie" or "helen" or "elen")
|
||||||
|
{
|
||||||
|
return new(Presentations.Feminine, 0.98m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EvidenceSignal.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasExplicitContradictoryPresentation(string name, string scopedText, string attemptedPresentation)
|
||||||
|
{
|
||||||
|
var clean = CleanWords(name);
|
||||||
|
if (clean.Length == 0 || string.IsNullOrWhiteSpace(scopedText))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = CleanWords(scopedText);
|
||||||
|
var contradictoryTerms = attemptedPresentation == Presentations.Feminine
|
||||||
|
? new[] { "female", "woman", "girl" }
|
||||||
|
: attemptedPresentation == Presentations.Masculine
|
||||||
|
? new[] { "male", "man", "boy" }
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return contradictoryTerms.Any(term =>
|
||||||
|
text.Contains($"{clean} is {term}", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| text.Contains($"{clean} was {term}", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| text.Contains($"{clean} as {term}", StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
private static string HairColour(string text) => NormalizeHairColour(text);
|
private static string HairColour(string text) => NormalizeHairColour(text);
|
||||||
private static string SkinTone(string text) => NormalizeSkinTone(text);
|
private static string SkinTone(string text) => NormalizeSkinTone(text);
|
||||||
private static string HairLength(string text) => NormalizeHairLength(text);
|
private static string HairLength(string text) => NormalizeHairLength(text);
|
||||||
@ -411,6 +492,15 @@ public static class StoryIntelligenceIllustrationCompatibility
|
|||||||
private static bool ContainsAny(string text, params string[] needles)
|
private static bool ContainsAny(string text, params string[] needles)
|
||||||
=> needles.Any(needle => text.Contains(needle, StringComparison.OrdinalIgnoreCase));
|
=> needles.Any(needle => text.Contains(needle, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool StartsWithAny(string text, params string[] prefixes)
|
||||||
|
=> prefixes.Any(prefix => text.Equals(prefix, StringComparison.OrdinalIgnoreCase) || text.StartsWith(prefix + " ", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static string CleanWords(string value)
|
||||||
|
{
|
||||||
|
var chars = value.Trim().ToLowerInvariant().Select(ch => char.IsLetterOrDigit(ch) ? ch : ' ').ToArray();
|
||||||
|
return string.Join(' ', new string(chars).Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||||
|
}
|
||||||
|
|
||||||
private static string EntityAttributeText(string text, string entityName)
|
private static string EntityAttributeText(string text, string entityName)
|
||||||
{
|
{
|
||||||
var cleanName = (entityName ?? string.Empty).Trim().ToLowerInvariant();
|
var cleanName = (entityName ?? string.Empty).Trim().ToLowerInvariant();
|
||||||
@ -529,7 +619,8 @@ public sealed record CharacterEvidenceProfile(
|
|||||||
decimal PresentationConfidence,
|
decimal PresentationConfidence,
|
||||||
decimal RelationshipConfidence,
|
decimal RelationshipConfidence,
|
||||||
decimal NameConfidence,
|
decimal NameConfidence,
|
||||||
decimal PronounConfidence)
|
decimal PronounConfidence,
|
||||||
|
IReadOnlyList<string> Warnings)
|
||||||
{
|
{
|
||||||
public bool HasStrongPresentationEvidence => PresentationConfidence >= 0.8m;
|
public bool HasStrongPresentationEvidence => PresentationConfidence >= 0.8m;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,7 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
{
|
{
|
||||||
var demandStatus = await RecordDemandAsync(projectId, observation, allScores);
|
var demandStatus = await RecordDemandAsync(projectId, observation, allScores);
|
||||||
var fallbackCandidateDiagnostics = JsonSerializer.Serialize(allScores.Take(6).Select(MatchDiagnostics.From), JsonOptions);
|
var fallbackCandidateDiagnostics = JsonSerializer.Serialize(allScores.Take(6).Select(MatchDiagnostics.From), JsonOptions);
|
||||||
var fallbackEvidenceJson = JsonSerializer.Serialize(observation.Evidence, JsonOptions);
|
var fallbackEvidenceJson = EvidenceTraceJson(observation, existing);
|
||||||
await assignments.UpsertAssignmentAsync(new StoryIntelligenceCharacterIllustrationAssignmentSave(
|
await assignments.UpsertAssignmentAsync(new StoryIntelligenceCharacterIllustrationAssignmentSave(
|
||||||
projectId,
|
projectId,
|
||||||
observation.CharacterKey,
|
observation.CharacterKey,
|
||||||
@ -132,7 +132,7 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
? $"Assigned {chosen.Candidate.Item.StableCode} from structured character evidence."
|
? $"Assigned {chosen.Candidate.Item.StableCode} from structured character evidence."
|
||||||
: $"Retained {chosen.Candidate.Item.StableCode}; existing assignment passed hard revalidation with score {chosen.Score:N0}.");
|
: $"Retained {chosen.Candidate.Item.StableCode}; existing assignment passed hard revalidation with score {chosen.Score:N0}.");
|
||||||
var candidateDiagnostics = JsonSerializer.Serialize(allScores.Take(6).Select(MatchDiagnostics.From), JsonOptions);
|
var candidateDiagnostics = JsonSerializer.Serialize(allScores.Take(6).Select(MatchDiagnostics.From), JsonOptions);
|
||||||
var evidenceJson = JsonSerializer.Serialize(observation.Evidence, JsonOptions);
|
var evidenceJson = EvidenceTraceJson(observation, existing);
|
||||||
|
|
||||||
var saved = await assignments.UpsertAssignmentAsync(new StoryIntelligenceCharacterIllustrationAssignmentSave(
|
var saved = await assignments.UpsertAssignmentAsync(new StoryIntelligenceCharacterIllustrationAssignmentSave(
|
||||||
projectId,
|
projectId,
|
||||||
@ -240,6 +240,22 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
private static string DemandKey(CharacterEvidence evidence)
|
private static string DemandKey(CharacterEvidence evidence)
|
||||||
=> string.Join('|', "character", evidence.AgeBand, evidence.Presentation, evidence.HairColour, evidence.SkinTone).ToLowerInvariant();
|
=> string.Join('|', "character", evidence.AgeBand, evidence.Presentation, evidence.HairColour, evidence.SkinTone).ToLowerInvariant();
|
||||||
|
|
||||||
|
private static string EvidenceTraceJson(CharacterObservation observation, StoryIntelligenceCharacterIllustrationAssignment? existing)
|
||||||
|
=> JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
character = observation.Name,
|
||||||
|
identityKey = observation.CharacterKey,
|
||||||
|
canonicalIdentity = observation.CharacterKey,
|
||||||
|
rawExtractedEvidence = observation.IdentityEvidence,
|
||||||
|
sceneEvidence = observation.TextEvidence,
|
||||||
|
relationshipEvidence = observation.RelationshipEvidence,
|
||||||
|
mergedEvidence = observation.TextEvidence.Concat(observation.RelationshipEvidence).ToArray(),
|
||||||
|
previousStoredEvidence = existing?.EvidenceJson,
|
||||||
|
finalMatcherEvidence = observation.Evidence,
|
||||||
|
evidenceSource = "Story Intelligence visualisation character state",
|
||||||
|
canonicalMergeReason = "Character state ID from canonical visualisation identity"
|
||||||
|
}, JsonOptions);
|
||||||
|
|
||||||
private static IllustrationGenerationSpecification DemandSpecification(StoryIntelligenceIllustrationDemand demand)
|
private static IllustrationGenerationSpecification DemandSpecification(StoryIntelligenceIllustrationDemand demand)
|
||||||
{
|
{
|
||||||
var age = NormaliseUnknown(demand.AgeBand, "YoungAdult");
|
var age = NormaliseUnknown(demand.AgeBand, "YoungAdult");
|
||||||
@ -482,6 +498,7 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
PreviousEvidence = existing?.EvidenceJson,
|
PreviousEvidence = existing?.EvidenceJson,
|
||||||
CurrentEvidence = evidenceJson,
|
CurrentEvidence = evidenceJson,
|
||||||
DemandKey = DemandKey(observation.Evidence),
|
DemandKey = DemandKey(observation.Evidence),
|
||||||
|
EvidenceWarnings = string.Join(" ", observation.Evidence.Warnings),
|
||||||
DemandStatus = "Satisfied",
|
DemandStatus = "Satisfied",
|
||||||
ResolvedPresentation = observation.Evidence.Presentation,
|
ResolvedPresentation = observation.Evidence.Presentation,
|
||||||
ResolvedAgeBand = observation.Evidence.AgeBand,
|
ResolvedAgeBand = observation.Evidence.AgeBand,
|
||||||
@ -520,8 +537,9 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
MatchingScore = allScores.FirstOrDefault()?.Score,
|
MatchingScore = allScores.FirstOrDefault()?.Score,
|
||||||
RejectedCandidates = diagnostics,
|
RejectedCandidates = diagnostics,
|
||||||
ReasonChosen = "No suitable unused illustration matched structured character evidence.",
|
ReasonChosen = "No suitable unused illustration matched structured character evidence.",
|
||||||
CurrentEvidence = JsonSerializer.Serialize(observation.Evidence, JsonOptions),
|
CurrentEvidence = EvidenceTraceJson(observation, null),
|
||||||
DemandKey = DemandKey(observation.Evidence),
|
DemandKey = DemandKey(observation.Evidence),
|
||||||
|
EvidenceWarnings = string.Join(" ", observation.Evidence.Warnings),
|
||||||
DemandStatus = demandStatus,
|
DemandStatus = demandStatus,
|
||||||
ResolvedPresentation = observation.Evidence.Presentation,
|
ResolvedPresentation = observation.Evidence.Presentation,
|
||||||
ResolvedAgeBand = observation.Evidence.AgeBand,
|
ResolvedAgeBand = observation.Evidence.AgeBand,
|
||||||
@ -584,7 +602,7 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
FallbackImageUrl = character.ImagePath,
|
FallbackImageUrl = character.ImagePath,
|
||||||
FallbackUsed = true,
|
FallbackUsed = true,
|
||||||
ReasonChosen = "Collective group entity; reusable group fallback used without unique portrait assignment or demand.",
|
ReasonChosen = "Collective group entity; reusable group fallback used without unique portrait assignment or demand.",
|
||||||
CurrentEvidence = JsonSerializer.Serialize(observation.Evidence, JsonOptions),
|
CurrentEvidence = EvidenceTraceJson(observation, null),
|
||||||
DemandStatus = "Not required for group entity",
|
DemandStatus = "Not required for group entity",
|
||||||
ResolvedPresentation = "Unknown",
|
ResolvedPresentation = "Unknown",
|
||||||
ResolvedAgeBand = "Unknown",
|
ResolvedAgeBand = "Unknown",
|
||||||
@ -627,10 +645,11 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
decimal RelationshipConfidence,
|
decimal RelationshipConfidence,
|
||||||
decimal NameConfidence,
|
decimal NameConfidence,
|
||||||
decimal PronounConfidence,
|
decimal PronounConfidence,
|
||||||
|
IReadOnlyList<string> Warnings,
|
||||||
CharacterEvidenceProfile Profile)
|
CharacterEvidenceProfile Profile)
|
||||||
{
|
{
|
||||||
public bool HasStrongPresentationEvidence => RelationshipConfidence >= 0.8m || NameConfidence >= 0.75m || PronounConfidence >= 0.8m;
|
public bool HasStrongPresentationEvidence => RelationshipConfidence >= 0.8m || NameConfidence >= 0.75m || PronounConfidence >= 0.8m;
|
||||||
public static CharacterEvidence Unknown { get; } = From(new CharacterEvidenceProfile("Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", 0, 0, 0, 0, 0));
|
public static CharacterEvidence Unknown { get; } = From(new CharacterEvidenceProfile("Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", "Unknown", 0, 0, 0, 0, 0, []));
|
||||||
public static CharacterEvidence From(CharacterEvidenceProfile profile)
|
public static CharacterEvidence From(CharacterEvidenceProfile profile)
|
||||||
=> new(
|
=> new(
|
||||||
profile.AgeBand,
|
profile.AgeBand,
|
||||||
@ -643,6 +662,7 @@ public sealed class StoryIntelligenceIllustrationMatchingService(
|
|||||||
profile.RelationshipConfidence,
|
profile.RelationshipConfidence,
|
||||||
profile.NameConfidence,
|
profile.NameConfidence,
|
||||||
profile.PronounConfidence,
|
profile.PronounConfidence,
|
||||||
|
profile.Warnings,
|
||||||
profile);
|
profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1683,7 +1683,7 @@ public sealed class StoryIntelligenceVisualisationSnapshotService(
|
|||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
Id = "location-pending",
|
Id = "location-pending",
|
||||||
LibraryCode = "loc-domestic-desk",
|
LibraryCode = string.Empty,
|
||||||
Name = "Location not yet identified",
|
Name = "Location not yet identified",
|
||||||
Label = "Awaiting scene location",
|
Label = "Awaiting scene location",
|
||||||
ImagePath = $"{FallbackRoot}/location-flat.svg"
|
ImagePath = $"{FallbackRoot}/location-flat.svg"
|
||||||
|
|||||||
@ -155,6 +155,7 @@ public sealed class StoryIntelligenceExperienceImageResolution
|
|||||||
public string? PreviousEvidence { get; init; }
|
public string? PreviousEvidence { get; init; }
|
||||||
public string? CurrentEvidence { get; init; }
|
public string? CurrentEvidence { get; init; }
|
||||||
public string? DemandKey { get; init; }
|
public string? DemandKey { get; init; }
|
||||||
|
public string? EvidenceWarnings { get; init; }
|
||||||
public string? DemandStatus { get; init; }
|
public string? DemandStatus { get; init; }
|
||||||
public string? ResolvedPresentation { get; init; }
|
public string? ResolvedPresentation { get; init; }
|
||||||
public string? ResolvedAgeBand { get; init; }
|
public string? ResolvedAgeBand { get; init; }
|
||||||
|
|||||||
@ -39,7 +39,11 @@
|
|||||||
<td>@row.Scene.SceneNumber</td>
|
<td>@row.Scene.SceneNumber</td>
|
||||||
<td><strong>@row.Character.Name</strong><br /><span class="muted">@row.Character.Id</span></td>
|
<td><strong>@row.Character.Name</strong><br /><span class="muted">@row.Character.Id</span></td>
|
||||||
<td><code>@(row.Character.ImageResolution.StableCode ?? row.Character.LibraryCode)</code><br />@row.Character.ImageResolution.Status</td>
|
<td><code>@(row.Character.ImageResolution.StableCode ?? row.Character.LibraryCode)</code><br />@row.Character.ImageResolution.Status</td>
|
||||||
<td>Age @Display(row.Character.ImageResolution.ResolvedAgeBand)<br />Presentation @Display(row.Character.ImageResolution.ResolvedPresentation)</td>
|
<td>
|
||||||
|
Age @Display(row.Character.ImageResolution.ResolvedAgeBand)<br />
|
||||||
|
Presentation @Display(row.Character.ImageResolution.ResolvedPresentation)<br />
|
||||||
|
<span class="text-warning">@Display(row.Character.ImageResolution.EvidenceWarnings)</span>
|
||||||
|
</td>
|
||||||
<td>@Revalidation(row.Character.ImageResolution)</td>
|
<td>@Revalidation(row.Character.ImageResolution)</td>
|
||||||
<td>@Display(row.Character.ImageResolution.ReasonChosen)<br /><span class="muted">@Display(row.Character.ImageResolution.IllustrationAllocationStatus)</span></td>
|
<td>@Display(row.Character.ImageResolution.ReasonChosen)<br /><span class="muted">@Display(row.Character.ImageResolution.IllustrationAllocationStatus)</span></td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user