Tighten structural asset phrase extraction

This commit is contained in:
Nick Beckley 2026-08-28 14:03:10 +00:00
parent 6aea9736d4
commit 3bf8f88f16
2 changed files with 47 additions and 2 deletions

View File

@ -42,6 +42,7 @@ var tests = new (string Name, Action Test)[]
("Asset canonical keys merge trivial variants", AssetCanonicalKeysMergeTrivialVariants),
("Asset canonical keys merge contextual vehicle variants", AssetCanonicalKeysMergeContextualVehicleVariants),
("Asset contextual names resolve POV ownership", AssetContextualNamesResolvePovOwnership),
("Asset structural phrases trim neighbouring nouns", AssetStructuralPhrasesTrimNeighbouringNouns),
("Relationship signals map to broad lookup types", RelationshipSignalsMapToBroadTypes),
("Relationship signals infer friendship without romance", RelationshipSignalsInferFriendshipWithoutRomance),
("Relationship events preserve chronological story order", RelationshipEventsPreserveChronologicalStoryOrder),
@ -1326,6 +1327,11 @@ static void AssetContextualNamesResolvePovOwnership()
Assert(IsAssetNameCandidate("Beth's Memory Tin"), "Owned Memory Tin-style significant objects should be retained.");
}
static void AssetStructuralPhrasesTrimNeighbouringNouns()
{
Assert(CleanStructuralAssetPhrase("memory tin and photographs") == "memory tin", "Structural asset extraction should not bundle neighbouring nouns after and.");
}
static void RelationshipSignalsMapToBroadTypes()
{
Assert(RelationshipTypeFromSignal("Friend") == "Friend", "Friend should map to Friend.");
@ -1423,6 +1429,15 @@ static string ResolveContextualAssetName(string name, string? ownerOrHolder, Sce
return (string)method!.Invoke(null, [name, ownerOrHolder, scene])!;
}
static string CleanStructuralAssetPhrase(string name)
{
var method = typeof(StoryIntelligenceAssetImportService).GetMethod(
"CleanStructuralAssetPhrase",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
Assert(method is not null, "CleanStructuralAssetPhrase was not found.");
return (string)method!.Invoke(null, [name])!;
}
static IComparable StoryOrderKey((int SceneID, decimal ChapterNumber, decimal SceneNumber, int SortOrder) shim)
{
var nested = typeof(StoryIntelligenceRelationshipImportService).GetNestedType(

View File

@ -462,7 +462,7 @@ public sealed class StoryIntelligenceAssetImportService(
foreach (Match match in StructuralPossessiveAssetPattern.Matches(source))
{
var owner = match.Groups["owner"].Value;
var objectName = match.Groups["asset"].Value;
var objectName = CleanStructuralAssetPhrase(match.Groups["asset"].Value);
if (string.IsNullOrWhiteSpace(owner) || string.IsNullOrWhiteSpace(objectName))
{
continue;
@ -482,7 +482,7 @@ public sealed class StoryIntelligenceAssetImportService(
foreach (Match match in StructuralSignificantAssetPattern.Matches(source))
{
var objectName = match.Groups["asset"].Value;
var objectName = CleanStructuralAssetPhrase(match.Groups["asset"].Value);
if (string.IsNullOrWhiteSpace(objectName) || !IsOwnedAssetName(objectName))
{
continue;
@ -912,6 +912,26 @@ public sealed class StoryIntelligenceAssetImportService(
private static string CleanAssetName(string? value)
=> StoryIntelligenceEntityTextNormaliser.CleanName(value);
private static string CleanStructuralAssetPhrase(string? value)
{
var clean = CleanAssetName(value);
if (string.IsNullOrWhiteSpace(clean))
{
return string.Empty;
}
foreach (var separator in StructuralAssetTailSeparators)
{
var index = clean.IndexOf(separator, StringComparison.OrdinalIgnoreCase);
if (index > 0)
{
clean = clean[..index].Trim();
}
}
return clean;
}
private static string RemoveLeadingArticle(string value)
=> value.StartsWith("the ", StringComparison.OrdinalIgnoreCase)
? value[4..]
@ -930,6 +950,16 @@ public sealed class StoryIntelligenceAssetImportService(
private static HashSet<string> CreateSet(params string[] values)
=> values.Select(Normalise).ToHashSet(StringComparer.OrdinalIgnoreCase);
private static readonly string[] StructuralAssetTailSeparators =
[
" and ",
" or ",
" while ",
" that ",
" which ",
","
];
private static readonly Regex StructuralPossessiveAssetPattern = new(
@"\b(?<owner>[A-Z][\p{L}'-]{1,40})'s\s+(?<asset>[A-Z][\p{L}\p{N}'-]*(?:\s+[A-Z][\p{L}\p{N}'-]*){0,3})\b",
RegexOptions.Compiled);