feat(config): add new scoring parameters for phrase extraction and matching

This commit is contained in:
2026-07-24 11:38:51 -04:00
parent 6a0e71a30d
commit 68c9693711
3 changed files with 15 additions and 11 deletions
+5
View File
@@ -91,6 +91,7 @@ class EbookSearchConfig(BaseSettings):
phrase_min_tokens: int = 2
phrase_max_tokens: int = 5
phrase_max_entity_tokens: int = 8
phrase_yake_top_k: int = 1000
phrase_raw_ngram_min_count: int = 2
phrase_raw_count_score_threshold: int = 3
phrase_raw_count_high_score_threshold: int = 10
@@ -99,6 +100,10 @@ class EbookSearchConfig(BaseSettings):
phrase_target_protected_per_book: int = 100
phrase_default_allow_nested: bool = False
phrase_default_suppress_children: bool = True
phrase_bad_start_score_penalty: float = 10.0
phrase_bad_end_score_penalty: float = 10.0
phrase_multi_source_score_bonus: float = 2.0
phrase_multi_source_min_sources: int = 2
@field_validator("library_paths", mode="before")
@classmethod
@@ -29,10 +29,6 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__)
BAD_START_SCORE_PENALTY = 10.0
BAD_END_SCORE_PENALTY = 10.0
MULTI_SOURCE_SCORE_BONUS = 2.0
MULTI_SOURCE_MIN_SOURCES = 2
CAPITALIZED_PHRASE_RE = re.compile(r"\b(?:[A-Z][a-zA-Z']+)(?:\s+(?:of|the|and|in|on|for|[A-Z][a-zA-Z']+)){0,6}")
@@ -177,19 +173,17 @@ def get_yake_extractor(max_ngram: int, top_k: int) -> KeywordExtractor:
def extract_yake_candidates(
book_text: str,
config: EbookSearchConfig,
top_k: int = 1000,
) -> dict[str, PhraseCandidate]:
"""Extract YAKE keyphrases when the optional YAKE package is installed.
Args:
book_text (str): Full book text to extract keyphrases from.
config (EbookSearchConfig): Runtime phrase-tuning settings.
top_k (int): Maximum number of YAKE keyphrases to request.
Returns:
dict[str, PhraseCandidate]: Candidates keyed by normalized phrase, with YAKE scores.
"""
extractor = get_yake_extractor(config.phrase_max_tokens, top_k)
extractor = get_yake_extractor(config.phrase_max_tokens, config.phrase_yake_top_k)
out: dict[str, PhraseCandidate] = {}
for phrase_text, yake_score in extractor.extract_keywords(book_text):
normalized = normalize_candidate_phrase(phrase_text, config)
@@ -487,14 +481,14 @@ def score_candidate(candidate: PhraseCandidate, config: EbookSearchConfig) -> fl
float: Combined score from sources, frequency, and length, less any penalties.
"""
score = source_score(candidate) + frequency_score(candidate, config) + token_count_score(candidate, config)
if non_raw_source_count(candidate) >= MULTI_SOURCE_MIN_SOURCES:
score += MULTI_SOURCE_SCORE_BONUS
if non_raw_source_count(candidate) >= config.phrase_multi_source_min_sources:
score += config.phrase_multi_source_score_bonus
if candidate.phrase_norm in get_ignored_phrases():
score -= 100.0
if has_bad_start(candidate.phrase_norm):
score -= BAD_START_SCORE_PENALTY
score -= config.phrase_bad_start_score_penalty
if has_bad_end(candidate.phrase_norm):
score -= BAD_END_SCORE_PENALTY
score -= config.phrase_bad_end_score_penalty
return score
@@ -159,6 +159,11 @@ def test_score_candidate_rewards_multiple_non_raw_sources(config: EbookSearchCon
assert score_candidate(multi_source, config) == score_candidate(single_source, config) + 2.0 + 2.0
tuned_config = config.model_copy(
update={"phrase_multi_source_min_sources": 2, "phrase_multi_source_score_bonus": 3.0}
)
assert score_candidate(multi_source, tuned_config) == score_candidate(single_source, tuned_config) + 2.0 + 3.0
def test_score_candidate_caps_frequency_contribution(config: EbookSearchConfig) -> None:
"""Very frequent raw-only phrases should no longer out-score sourced entities."""