Phase 16D.2 - Character Discovery Confidence and Selection Refinement

This commit is contained in:
Nick Beckley 2026-06-27 20:27:44 +01:00
parent 7bee51834b
commit 1ee77d953d
3 changed files with 32 additions and 10 deletions

View File

@ -50,7 +50,8 @@ public sealed class WordCompanionService(
"Let", "Maybe", "Please", "God", "Something", "After", "Did", "Even", "All", "Nothing", "Have", "Let", "Maybe", "Please", "God", "Something", "After", "Did", "Even", "All", "Nothing", "Have",
"Don", "Won", "Could", "Would", "Should", "Do", "Does", "Had", "Has", "Was", "Were", "Is", "Am", "Don", "Won", "Could", "Would", "Should", "Do", "Does", "Had", "Has", "Was", "Were", "Is", "Am",
"Right", "Really", "Now", "Look", "Listen", "Sorry", "Thanks", "Fine", "Great", "Sure", "Course", "Right", "Really", "Now", "Look", "Listen", "Sorry", "Thanks", "Fine", "Great", "Sure", "Course",
"Thing", "Things", "Everyone", "Everything", "Anything", "Someone", "Nobody", "People" "Thing", "Things", "Everyone", "Everything", "Anything", "Someone", "Nobody", "People",
"Still", "Err", "Er", "Erm", "Alright", "Allright", "Alrite"
}; };
private static readonly HashSet<string> CharacterDiscoveryTemporalWords = new(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> CharacterDiscoveryTemporalWords = new(StringComparer.OrdinalIgnoreCase)
{ {
@ -72,6 +73,11 @@ public sealed class WordCompanionService(
"said", "asked", "replied", "answered", "whispered", "shouted", "called", "muttered", "cried", "snapped", "said", "asked", "replied", "answered", "whispered", "shouted", "called", "muttered", "cried", "snapped",
"sighed", "laughed", "continued", "began", "told", "yelled" "sighed", "laughed", "continued", "began", "told", "yelled"
}; };
private static readonly HashSet<string> CharacterDiscoveryLocationIndicators = new(StringComparer.OrdinalIgnoreCase)
{
"Road", "Lane", "Street", "Close", "Avenue", "Drive", "Way", "Hill", "Hospital", "Inn", "Church", "School",
"House", "Farm", "Hall", "Court", "Station", "Hotel", "Shop", "Cafe", "Café", "Pub", "Park", "Gardens"
};
public async Task<WordCompanionProjectsResponse> ListProjectsAsync() public async Task<WordCompanionProjectsResponse> ListProjectsAsync()
{ {
var userId = RequireUserId(); var userId = RequireUserId();
@ -431,6 +437,7 @@ public sealed class WordCompanionService(
var hasMultiWordName = words.Length > 1; var hasMultiWordName = words.Length > 1;
var hasNameLikeWord = words.Any(word => CharacterDiscoveryNameLikeWords.Contains(TrimHonorificPunctuation(word))); var hasNameLikeWord = words.Any(word => CharacterDiscoveryNameLikeWords.Contains(TrimHonorificPunctuation(word)));
var hasTemporalWord = words.Any(word => CharacterDiscoveryTemporalWords.Contains(TrimHonorificPunctuation(word))); var hasTemporalWord = words.Any(word => CharacterDiscoveryTemporalWords.Contains(TrimHonorificPunctuation(word)));
var hasLocationIndicator = words.Any(word => CharacterDiscoveryLocationIndicators.Contains(TrimHonorificPunctuation(word)));
var nonSentenceStartMentions = evidence.MentionCount - evidence.SentenceStartMentions; var nonSentenceStartMentions = evidence.MentionCount - evidence.SentenceStartMentions;
var mostlySentenceStart = evidence.MentionCount > 0 && evidence.SentenceStartMentions >= Math.Ceiling(evidence.MentionCount * 0.8m); var mostlySentenceStart = evidence.MentionCount > 0 && evidence.SentenceStartMentions >= Math.Ceiling(evidence.MentionCount * 0.8m);
@ -448,15 +455,15 @@ public sealed class WordCompanionService(
} }
if (evidence.DialogueEvidenceCount > 0) if (evidence.DialogueEvidenceCount > 0)
{ {
score += 3; score += 5;
} }
if (hasMultiWordName) if (hasMultiWordName)
{ {
score += 2; score += 5;
} }
if (hasHonorific) if (hasHonorific)
{ {
score += 2; score += 4;
} }
if (hasNameLikeWord) if (hasNameLikeWord)
{ {
@ -466,6 +473,10 @@ public sealed class WordCompanionService(
{ {
score -= 1; score -= 1;
} }
if (hasLocationIndicator && evidence.DialogueEvidenceCount == 0 && !hasHonorific)
{
score -= 2;
}
if (mostlySentenceStart) if (mostlySentenceStart)
{ {
score -= 2; score -= 2;
@ -475,18 +486,23 @@ public sealed class WordCompanionService(
score -= 1; score -= 1;
} }
if (score < 0) if (score < 0 && !hasLocationIndicator)
{ {
return null; return null;
} }
var confidence = score >= 5 ? "High" : score >= 2 ? "Medium" : "Low"; var confidence = score >= 4 ? "High" : score >= 2 ? "Medium" : "Low";
if (hasLocationIndicator && evidence.DialogueEvidenceCount == 0 && !hasHonorific)
{
confidence = score >= 4 ? "Medium" : "Low";
}
return new WordCompanionCharacterCandidateDto return new WordCompanionCharacterCandidateDto
{ {
Text = evidence.Text, Text = evidence.Text,
MentionCount = evidence.MentionCount, MentionCount = evidence.MentionCount,
Confidence = confidence, Confidence = confidence,
Reason = BuildCandidateReason(evidence, hasHonorific, hasMultiWordName, hasTemporalWord, mostlySentenceStart) Reason = BuildCandidateReason(evidence, hasHonorific, hasMultiWordName, hasTemporalWord, hasLocationIndicator, mostlySentenceStart)
}; };
} }
@ -495,6 +511,7 @@ public sealed class WordCompanionService(
bool hasHonorific, bool hasHonorific,
bool hasMultiWordName, bool hasMultiWordName,
bool hasTemporalWord, bool hasTemporalWord,
bool hasLocationIndicator,
bool mostlySentenceStart) bool mostlySentenceStart)
{ {
if (evidence.DialogueEvidenceCount > 0) if (evidence.DialogueEvidenceCount > 0)
@ -507,11 +524,16 @@ public sealed class WordCompanionService(
} }
if (hasMultiWordName) if (hasMultiWordName)
{ {
if (hasLocationIndicator)
{
return "Possible location";
}
return "Proper-name phrase"; return "Proper-name phrase";
} }
if (hasTemporalWord) if (hasTemporalWord)
{ {
return "Possible name"; return "Possible name or temporal word";
} }
if (mostlySentenceStart) if (mostlySentenceStart)
{ {

View File

@ -1102,7 +1102,7 @@
const checkbox = document.createElement("input"); const checkbox = document.createElement("input");
checkbox.type = "checkbox"; checkbox.type = "checkbox";
checkbox.value = candidate.text || ""; checkbox.value = candidate.text || "";
checkbox.checked = String(candidate.confidence || "").toLowerCase() !== "very low"; checkbox.checked = String(candidate.confidence || "").toLowerCase() === "high";
checkbox.addEventListener("change", updateFirstRunButtons); checkbox.addEventListener("change", updateFirstRunButtons);
const name = document.createElement("strong"); const name = document.createElement("strong");

File diff suppressed because one or more lines are too long