Correct relationship intelligence classification
This commit is contained in:
parent
feed4549bf
commit
85286428c2
@ -55,6 +55,7 @@ var tests = new (string Name, Action Test)[]
|
||||
("Relationship progression keeps chronological state", RelationshipProgressionKeepsChronologicalState),
|
||||
("Relationship observations do not create event spam", RelationshipObservationsDoNotCreateEventSpam),
|
||||
("Relationship review orders by first story appearance", RelationshipReviewOrdersByFirstStoryAppearance),
|
||||
("Relationship evidence priority preserves explicit semantics", RelationshipEvidencePriorityPreservesExplicitSemantics),
|
||||
("Knowledge signals map to existing knowledge states", KnowledgeSignalsMapToExistingStates),
|
||||
("Knowledge recipient resolves narrator through POV", KnowledgeRecipientResolvesNarratorThroughPov),
|
||||
("Knowledge identity reveal preserves subjective timing", KnowledgeIdentityRevealPreservesSubjectiveTiming),
|
||||
@ -2209,6 +2210,28 @@ static void RelationshipReviewOrdersByFirstStoryAppearance()
|
||||
Assert(!service.Contains(".OrderByDescending(candidate => candidate.Appearances.Select(appearance => appearance.SceneID).Distinct().Count())", StringComparison.Ordinal), "Relationship review should not prioritise frequent relationships ahead of earlier first appearances.");
|
||||
}
|
||||
|
||||
static void RelationshipEvidencePriorityPreservesExplicitSemantics()
|
||||
{
|
||||
Assert(RelationshipTypeFromEvidence("dependence", "My brother helped with the machine.", null, "Nora", "Elias") == "Sibling", "Explicit brother evidence should become Sibling.");
|
||||
Assert(RelationshipTypeFromEvidence("affection", "One pile was just me and Mum, my entire childhood.", null, "Nora", "Nora's mother") == "Parent", "Explicit parent evidence should become Parent.");
|
||||
Assert(RelationshipTypeFromEvidence("affection", "Iris is my best friend; it has been that way since school.", null, "Nora", "Iris") == "Friend", "Explicit best-friend evidence should become Friend.");
|
||||
Assert(RelationshipTypeFromEvidence("dependence", "Nora goes next door to Mrs Alder and asks for help.", null, "Nora", "Mrs Alder") == "Neighbour", "Next-door context should become Neighbour.");
|
||||
Assert(RelationshipTypeFromEvidence("obligation", "They wait together.", "Nora meets coworkers Jae and Priya at the tram stop.", "Nora", "Jae") == "Professional", "Scene coworker context should become Professional.");
|
||||
Assert(RelationshipTypeFromEvidence("trust", "Dr Vale reassures Mira during the assessment.", "Dr Vale is Mira's examiner.", "Mira", "Dr Vale") == "Educational", "Generic examiner/candidate context should become Educational.");
|
||||
Assert(RelationshipTypeFromEvidence("affection", "Jon looks Mira up and down and flirts.", null, "Jon", "Mira") != "Romantic", "One-sided attraction/flirtation should not establish Romantic.");
|
||||
Assert(RelationshipTypeFromEvidence("conflict", "Two siblings argue and insult one another.", null, "Mira", "Talia") == "Sibling", "Sibling evidence should outrank conflict.");
|
||||
Assert(RelationshipTypeFromEvidence("conflict", "Mira attacks Oren after he harms her friend.", null, "Mira", "Oren") == "Rival", "One-off adversarial interaction should stay hostile/adversarial, not friendly or romantic.");
|
||||
Assert(RelationshipTypeFromEvidence("dependence", "Mary arrives to take them to the theatre and Nora gets into Mary's car.", null, "Nora", "Mary") == "Unknown", "One incidental lift should not become Ally.");
|
||||
Assert(RelationshipTypeFromEvidence("secrecy", "Nora searches for her sister under another name.", null, "Nora", "Mira") == "Sibling", "Identity timing belongs to Knowledge; objective sibling evidence should remain Sibling.");
|
||||
Assert(RelationshipTypeFromEvidence("family", "Nora's adoptive mother speaks to Nora.", null, "Nora", "Nora's adoptive mother") == "Parent", "Adoptive family evidence should classify the direct relationship without transitive assumptions.");
|
||||
Assert(RelationshipTypeFromEvidence("admiration", "Nora regards Priya as like an older sister after the rescue.", null, "Nora", "Priya") != "Sibling", "Figurative sibling language should not create an objective Sibling relationship.");
|
||||
Assert(RelationshipTypeFromEvidence("information", "Pat addresses Nora directly about looking for her sister.", null, "Pat", "Nora") != "Sibling", "Third-party kinship discussion should not make the speakers siblings.");
|
||||
Assert(RelationshipTypeFromEvidence("conflict", "Oren kisses Mira before assaulting her.", null, "Oren", "Mira") == "Rival", "Assault evidence should outrank kiss wording and remain adversarial.");
|
||||
Assert(RelationshipTypeFromEvidence("family", "Mira's mum tells the group to look after Mira.", null, "Mira", "Mira's mum") == "Parent", "Owned mum/mother variants should classify as Parent.");
|
||||
Assert(RelationshipTypeFromEvidence("family", "Mira's mother tells the group to look after Mira.", null, "Mira", "Mira’s mum") == "Parent", "Curly apostrophe owned kinship names should classify as Parent.");
|
||||
Assert(RelationshipTypeFromEvidence("affection", "Nora likes Alison and sees her as like an older sister.", "Alison is a nurse who recently moved back to town.", "Nora", "Alison") != "Medical", "Occupation-only context should not create a Medical relationship.");
|
||||
}
|
||||
|
||||
static void KnowledgeSignalsMapToExistingStates()
|
||||
{
|
||||
var states = new List<KnowledgeState>
|
||||
@ -2312,6 +2335,17 @@ static string RelationshipTypeFromSignal(string signal, string? evidence = null)
|
||||
return (string)method!.Invoke(null, [signal, evidence])!;
|
||||
}
|
||||
|
||||
static string RelationshipTypeFromEvidence(string signal, string? evidence, string? sceneContext, string characterAName, string characterBName)
|
||||
{
|
||||
var method = typeof(StoryIntelligenceRelationshipImportService).GetMethod(
|
||||
"RelationshipTypeFromEvidence",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||
Assert(method is not null, "RelationshipTypeFromEvidence was not found.");
|
||||
var classification = method!.Invoke(null, [signal, evidence, sceneContext, characterAName, characterBName]);
|
||||
Assert(classification is not null, "RelationshipTypeFromEvidence returned null.");
|
||||
return classification.GetType().GetProperty("Type")?.GetValue(classification) as string ?? string.Empty;
|
||||
}
|
||||
|
||||
static string ResolveContextualAssetName(string name, string? ownerOrHolder, Scene scene)
|
||||
{
|
||||
var method = typeof(StoryIntelligenceAssetImportService).GetMethod(
|
||||
|
||||
@ -362,14 +362,20 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
decimal? confidence,
|
||||
IReadOnlyList<RelationshipEvent> existingEvents)
|
||||
{
|
||||
var characterA = characterIndex.Find(characterAName);
|
||||
var characterB = characterIndex.Find(characterBName);
|
||||
var characterA = characterIndex.Find(characterAName, importedScene);
|
||||
var characterB = characterIndex.Find(characterBName, importedScene);
|
||||
if (characterA is null || characterB is null || characterA.CharacterID == characterB.CharacterID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = PairKey(characterA.CharacterID, characterB.CharacterID);
|
||||
var classification = RelationshipTypeFromEvidence(
|
||||
relationshipSignal,
|
||||
evidence,
|
||||
importedScene.StructuralSummary,
|
||||
characterA.CharacterName,
|
||||
characterB.CharacterName);
|
||||
if (!groups.TryGetValue(key, out var candidate))
|
||||
{
|
||||
existingByPair.TryGetValue(key, out var existingMatch);
|
||||
@ -379,15 +385,18 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
characterA.CharacterName,
|
||||
characterB.CharacterID,
|
||||
characterB.CharacterName,
|
||||
RelationshipTypeFromSignal(relationshipSignal, evidence),
|
||||
classification.Type,
|
||||
existingMatch?.CharacterRelationshipID,
|
||||
existingMatch is null ? null : $"{existingMatch.CharacterAName} / {existingMatch.RelationshipTypeName} / {existingMatch.CharacterBName}");
|
||||
candidate.RelationshipTypeClassification = classification;
|
||||
groups[key] = candidate;
|
||||
}
|
||||
|
||||
if ((confidence ?? 0m) >= (candidate.Confidence ?? 0m))
|
||||
if (classification.CompareTo(candidate.RelationshipTypeClassification) > 0
|
||||
|| classification.CompareTo(candidate.RelationshipTypeClassification) == 0 && (confidence ?? 0m) >= (candidate.Confidence ?? 0m))
|
||||
{
|
||||
candidate.RelationshipType = RelationshipTypeFromSignal(relationshipSignal, evidence);
|
||||
candidate.RelationshipType = classification.Type;
|
||||
candidate.RelationshipTypeClassification = classification;
|
||||
}
|
||||
|
||||
candidate.Confidence = Max(candidate.Confidence, confidence);
|
||||
@ -519,64 +528,109 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
}
|
||||
|
||||
private static string RelationshipTypeFromSignal(string? signal, string? evidence = null)
|
||||
=> RelationshipTypeFromEvidence(signal, evidence, null, null, null).Type;
|
||||
|
||||
private static RelationshipTypeClassification RelationshipTypeFromEvidence(
|
||||
string? signal,
|
||||
string? evidence,
|
||||
string? sceneContext,
|
||||
string? characterAName,
|
||||
string? characterBName)
|
||||
{
|
||||
var value = NormaliseForMatching($"{signal} {evidence}");
|
||||
if (ContainsAny(value, "mother", "father", "sister", "brother", "parent", "daughter", "son", " child ", "aunt", "uncle", "cousin", "family", "sibling", "parent and child"))
|
||||
var relationshipText = NormaliseForMatching($"{evidence} {characterAName} {characterBName}");
|
||||
var explicitText = NormaliseForMatching($"{evidence} {sceneContext} {characterAName} {characterBName}");
|
||||
var allText = NormaliseForMatching($"{signal} {evidence} {sceneContext} {characterAName} {characterBName}");
|
||||
if (ContainsAny(relationshipText, "best friend", "close friend", "friend since", "childhood friend"))
|
||||
{
|
||||
return "Family";
|
||||
return new("Friend", 500);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "romance", "romantic", "lover", "spouse", "husband", "wife", "girlfriend", "boyfriend", "love interest", "former partner", "partner like", "partner-like", "attraction", "flirt", "kiss", "proposal", "engaged"))
|
||||
if (HasDirectSiblingEvidence(evidence, characterAName, characterBName))
|
||||
{
|
||||
return "Romantic";
|
||||
return new("Sibling", 490);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "teacher", "student", "instructor", "learner", "mentor", "pupil", "tutor", "driving instructor"))
|
||||
if (HasDirectParentEvidence(evidence, characterAName, characterBName))
|
||||
{
|
||||
return "Educational";
|
||||
return new("Parent", 480);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "police", "detective", "suspect", "witness", "authority", "guard", "prisoner", "investigator", "person of interest"))
|
||||
if (ContainsAny(explicitText, "neighbour", "neighbor", "next door"))
|
||||
{
|
||||
return "Authority";
|
||||
return new("Neighbour", 470);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "criminal", "accomplice", "offender", "victim", "gang", "conspirator"))
|
||||
if (ContainsAny(explicitText, "coworker", "co worker", "co-worker", "colleague", "workmate", "coworkers", "co workers", "co-workers", "colleagues"))
|
||||
{
|
||||
return "Criminal";
|
||||
return new("Professional", 460);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "doctor", "patient", "nurse", "therapist", "medical"))
|
||||
if (ContainsAny(explicitText, "examiner", "candidate", "instructor", "learner", "teacher", "student", "mentor", "pupil", "tutor"))
|
||||
{
|
||||
return "Medical";
|
||||
return new("Educational", 455);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "neighbour", "neighbor"))
|
||||
if (ContainsAny(relationshipText, "aunt", "uncle", "cousin", "family", "like a sister", "like a brother"))
|
||||
{
|
||||
return "Neighbour";
|
||||
return new("Family", 420);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "colleague", "coworker", "co worker", "boss", "employee", "manager", "client", "professional", "work relationship"))
|
||||
if (ContainsAny(allText, "rival", "enemy", "adversarial", "conflict", "mistrust", "antagonist", "competitor", "confront", "attack", "attacks", "assault", "assaults", "insult", "anger", "hostile"))
|
||||
{
|
||||
return "Professional";
|
||||
return new("Rival", 370);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "friend", "friendship", "best friend", "close friend", "childhood friend", "trust between friends", "affection between friends", "trust", "familiarity", "comfortable together", "socialise", "socialize", "spend time together", "spends time together", "hug", "hugs", "gift", "gifts", "present", "presents"))
|
||||
if (ContainsAny(allText, "romance", "romantic", "lover", "spouse", "husband", "wife", "girlfriend", "boyfriend", "love interest", "former partner", "partner like", "partner-like", "kiss", "proposal", "engaged", "couple"))
|
||||
{
|
||||
return "Friend";
|
||||
return new("Romantic", 360);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "rival", "enemy", "adversarial", "conflict", "mistrust", "antagonist", "competitor"))
|
||||
if (ContainsAny(allText, "teacher", "student", "instructor", "learner", "mentor", "pupil", "tutor", "driving instructor", "examiner", "candidate"))
|
||||
{
|
||||
return "Rival";
|
||||
return new("Educational", 350);
|
||||
}
|
||||
|
||||
if (ContainsAny(value, "ally", "protector", "supporter", "confidant", "dependence", "cooperation"))
|
||||
if (ContainsAny(allText, "police", "detective", "suspect", "witness", "authority", "guard", "prisoner", "investigator", "person of interest"))
|
||||
{
|
||||
return "Ally";
|
||||
return new("Authority", 340);
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
if (ContainsAny(allText, "criminal", "accomplice", "offender", "victim", "gang", "conspirator"))
|
||||
{
|
||||
return new("Criminal", 330);
|
||||
}
|
||||
|
||||
if (ContainsAny(relationshipText, "doctor patient", "patient doctor", "nurse patient", "patient nurse", "therapist patient", "patient therapist", "treats", "treated by", "medical care"))
|
||||
{
|
||||
return new("Medical", 320);
|
||||
}
|
||||
|
||||
if (ContainsAny(allText, "neighbour", "neighbor"))
|
||||
{
|
||||
return new("Neighbour", 310);
|
||||
}
|
||||
|
||||
if (ContainsAny(allText, "colleague", "coworker", "co worker", "boss", "employee", "manager", "client", "professional", "work relationship"))
|
||||
{
|
||||
return new("Professional", 300);
|
||||
}
|
||||
|
||||
if (ContainsAny(NormaliseForMatching(signal), "friend", "friendship"))
|
||||
{
|
||||
return new("Friend", 290);
|
||||
}
|
||||
|
||||
if (ContainsAny(allText, "friendship", "trust between friends", "affection between friends", "familiarity", "comfortable together", "socialise", "socialize", "spend time together", "spends time together"))
|
||||
{
|
||||
return new("Friend", 270);
|
||||
}
|
||||
|
||||
if (ContainsAny(allText, "ally", "protector", "supporter", "confidant", "protects", "supports"))
|
||||
{
|
||||
return new("Ally", 250);
|
||||
}
|
||||
|
||||
return new("Unknown", 0);
|
||||
}
|
||||
|
||||
private static bool HasMeaningfulRelationshipEvent(RelationshipAppearanceImport appearance)
|
||||
@ -585,6 +639,67 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
return RelationshipEventWords.Any(word => value.Contains(word, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool HasDirectSiblingEvidence(string? evidence, string? characterAName, string? characterBName)
|
||||
{
|
||||
var clean = Clean(evidence);
|
||||
if (string.IsNullOrWhiteSpace(clean)
|
||||
|| FigurativeSibling.IsMatch(clean)
|
||||
|| ReportedThirdPartyKinship.IsMatch(clean))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var normalised = NormaliseForMatching(clean);
|
||||
return ContainsAny(normalised, "my brother", "my sister", "her brother", "her sister", "his brother", "his sister", "their brother", "their sister")
|
||||
|| ContainsAny(normalised, "two siblings", "both siblings", "the siblings")
|
||||
|| HasOwnedKinshipBetweenParticipants("brother", characterAName, characterBName)
|
||||
|| HasOwnedKinshipBetweenParticipants("sister", characterAName, characterBName)
|
||||
|| HasOwnedKinshipBetweenParticipants("sibling", characterAName, characterBName);
|
||||
}
|
||||
|
||||
private static bool HasDirectParentEvidence(string? evidence, string? characterAName, string? characterBName)
|
||||
{
|
||||
var normalised = NormaliseForMatching(evidence);
|
||||
return ContainsAny(normalised, "my mother", "my mum", "my mom", "my father", "my dad", "me and mum", "me and mother", "me and dad", "me and father")
|
||||
|| HasOwnedKinshipBetweenParticipants("mother", characterAName, characterBName)
|
||||
|| HasOwnedKinshipBetweenParticipants("father", characterAName, characterBName)
|
||||
|| HasOwnedKinshipBetweenParticipants("mum", characterAName, characterBName)
|
||||
|| HasOwnedKinshipBetweenParticipants("mom", characterAName, characterBName)
|
||||
|| HasOwnedKinshipBetweenParticipants("dad", characterAName, characterBName);
|
||||
}
|
||||
|
||||
private static bool HasOwnedKinshipBetweenParticipants(string kinship, string? characterAName, string? characterBName)
|
||||
=> IsOwnedKinshipFor(characterAName, characterBName, kinship)
|
||||
|| IsOwnedKinshipFor(characterBName, characterAName, kinship);
|
||||
|
||||
private static bool IsOwnedKinshipFor(string? possibleKinshipCharacter, string? possibleOwnerCharacter, string kinship)
|
||||
{
|
||||
var owner = Clean(possibleOwnerCharacter);
|
||||
if (string.IsNullOrWhiteSpace(owner))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var identityName = KinshipIdentityName(kinship);
|
||||
var expected = NormaliseForMatching($"{owner}'s {identityName}");
|
||||
var actual = NormaliseForMatching(possibleKinshipCharacter);
|
||||
if (string.Equals(actual, expected, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
string[] equivalentKinshipNames = identityName switch
|
||||
{
|
||||
"mother" => ["mother", "mum", "mom"],
|
||||
"father" => ["father", "dad"],
|
||||
var value => [value]
|
||||
};
|
||||
var qualified = equivalentKinshipNames.Select(name => NormaliseForMatching($"{owner}'s {name}"))
|
||||
.Concat(new[] { "adoptive", "biological", "step", "foster" }
|
||||
.SelectMany(qualifier => equivalentKinshipNames.Select(name => NormaliseForMatching($"{owner}'s {qualifier} {name}"))));
|
||||
return qualified.Any(value => string.Equals(actual, value, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static (decimal ChapterNumber, decimal SceneNumber, int SortOrder, int SceneID) StoryOrder(RelationshipAppearanceImport appearance)
|
||||
=> (appearance.ChapterNumber, appearance.SceneNumber, appearance.SortOrder, appearance.SceneID);
|
||||
|
||||
@ -728,6 +843,12 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
|
||||
private static readonly Regex Whitespace = new(@"\s+", RegexOptions.Compiled);
|
||||
private static readonly Regex NonWord = new(@"[^\p{L}\p{N}]+", RegexOptions.Compiled);
|
||||
private static readonly Regex FigurativeSibling = new(@"\blike\s+(?:a|an)?\s*(?:older|younger|big|little)?\s*(?:sister|brother)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex ReportedThirdPartyKinship = new(@"\b(?:about|for|of)\s+(?:looking|searching|look|search|find|finding)\s+for\s+(?:his|her|their|my)\s+(?:sister|brother|mother|father|mum|mom|dad)\b|\babout\s+(?:his|her|their|my)\s+(?:sister|brother|mother|father|mum|mom|dad)\b", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly HashSet<string> StandaloneKinshipReferences = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"mum", "mom", "mother", "dad", "father", "parent", "parents", "brother", "sister", "sibling", "aunt", "uncle"
|
||||
};
|
||||
|
||||
private static readonly string[] RelationshipSignalWords =
|
||||
[
|
||||
@ -763,6 +884,7 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
public int CharacterBID { get; } = characterBId;
|
||||
public string CharacterBName { get; } = characterBName;
|
||||
public string RelationshipType { get; set; } = relationshipType;
|
||||
public RelationshipTypeClassification RelationshipTypeClassification { get; set; } = new("Unknown", 0);
|
||||
public int? ExistingRelationshipID { get; } = existingRelationshipId;
|
||||
public string? ExistingRelationshipLabel { get; } = existingRelationshipLabel;
|
||||
public decimal? Confidence { get; set; }
|
||||
@ -799,9 +921,67 @@ public sealed class StoryIntelligenceRelationshipImportService(
|
||||
byName[key] = new CharacterMatch(characterId, characterName);
|
||||
}
|
||||
|
||||
public CharacterMatch? Find(string? name)
|
||||
=> byName.TryGetValue(Clean(name), out var match) ? match : null;
|
||||
public CharacterMatch? Find(string? name, Scene scene)
|
||||
{
|
||||
var clean = Clean(name);
|
||||
if (string.IsNullOrWhiteSpace(clean))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IsStandaloneKinshipReference(clean))
|
||||
{
|
||||
var pov = Clean(scene.POVCharacterName ?? scene.POVSuggestedName);
|
||||
if (!string.IsNullOrWhiteSpace(pov))
|
||||
{
|
||||
var ownedReference = $"{pov}'s {KinshipIdentityName(clean)}";
|
||||
if (byName.TryGetValue(ownedReference, out var localMatch))
|
||||
{
|
||||
return localMatch;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return byName.TryGetValue(clean, out var match) ? match : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsStandaloneKinshipReference(string name)
|
||||
=> StandaloneKinshipReferences.Contains(Normalise(name));
|
||||
|
||||
private static string KinshipIdentityName(string name)
|
||||
=> Normalise(name) switch
|
||||
{
|
||||
"mum" or "mom" => "mother",
|
||||
"dad" => "father",
|
||||
var value => value
|
||||
};
|
||||
|
||||
private sealed record RelationshipTypeClassification(string Type, int Score) : IComparable<RelationshipTypeClassification>
|
||||
{
|
||||
public int CompareTo(RelationshipTypeClassification? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var score = Score.CompareTo(other.Score);
|
||||
return score != 0 ? score : Specificity(Type).CompareTo(Specificity(other.Type));
|
||||
}
|
||||
}
|
||||
|
||||
private static int Specificity(string type)
|
||||
=> type switch
|
||||
{
|
||||
"Sibling" or "Parent" or "Child" or "Adoptive Parent" or "Biological Parent" or "Spouse" => 5,
|
||||
"Neighbour" or "Educational" or "Professional" or "Medical" or "Authority" => 4,
|
||||
"Friend" or "Romantic" or "Criminal" or "Rival" => 3,
|
||||
"Family" or "Ally" => 2,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
private sealed record CharacterMatch(int CharacterID, string CharacterName);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user