build_tools.name_selector.policy

Policy evaluation logic for name candidates.

This module contains the core evaluation function that scores a name candidate against a name class policy. It implements the ✓/~/✗ scoring model defined in the Name Class Matrix.

Scoring Model

  • Preferred (✓): Feature present → +1 score

  • Tolerated (~): Feature present → 0 score (neutral)

  • Discouraged (✗): Feature present → Reject (hard) or -10 (soft)

The evaluation considers only features that are TRUE in the candidate. Features that are FALSE do not contribute to the score (absence is neutral).

Evaluation Modes

Hard Mode (default):

Any discouraged feature present causes immediate rejection. The candidate is not scored further.

Soft Mode:

Discouraged features apply a -10 penalty instead of rejection. Useful for exploring edge cases or when flexibility is needed.

Usage

>>> from build_tools.name_selector.policy import evaluate_candidate
>>> from build_tools.name_selector.name_class import NameClassPolicy
>>>
>>> policy = NameClassPolicy(
...     name="first_name",
...     description="Test",
...     syllable_range=(2, 3),
...     features={"ends_with_vowel": "preferred", "ends_with_stop": "discouraged"},
... )
>>> candidate = {
...     "name": "kali",
...     "features": {"ends_with_vowel": True, "ends_with_stop": False},
... }
>>> admitted, score, details = evaluate_candidate(candidate, policy, mode="hard")
>>> admitted
True
>>> score
1
>>> details["preferred_hits"]
['ends_with_vowel']

Attributes

PREFERRED_SCORE

TOLERATED_SCORE

DISCOURAGED_PENALTY

Functions

evaluate_candidate(candidate, policy[, mode])

Evaluate a name candidate against a name class policy.

check_syllable_count(candidate, policy)

Check if a candidate's syllable count is within policy range.

Module Contents

build_tools.name_selector.policy.PREFERRED_SCORE = 1
build_tools.name_selector.policy.TOLERATED_SCORE = 0
build_tools.name_selector.policy.DISCOURAGED_PENALTY = -10
build_tools.name_selector.policy.evaluate_candidate(candidate, policy, mode='hard')[source]

Evaluate a name candidate against a name class policy.

Scores the candidate based on which of its TRUE features match preferred, tolerated, or discouraged designations in the policy.

Parameters

candidatedict

Candidate dictionary with “name”, “features”, and optionally “syllables”. Features must be a dict[str, bool].

policyNameClassPolicy

The policy to evaluate against.

mode{“hard”, “soft”}, optional

Evaluation mode. “hard” rejects on any discouraged feature. “soft” applies a -10 penalty instead. Default: “hard”.

Returns

tuple[bool, int, dict]
  • admitted: True if candidate passes policy, False if rejected

  • score: Numeric score (higher is better)

  • details: Evaluation details for debugging

Details dict structure:
  • preferred_hits: list[str] - Preferred features that are TRUE

  • tolerated_hits: list[str] - Tolerated features that are TRUE

  • discouraged_hits: list[str] - Discouraged features that are TRUE

  • rejection_reason: str | None - Reason for rejection (if any)

Examples

>>> # Candidate with preferred feature
>>> candidate = {"name": "kali", "features": {"ends_with_vowel": True}}
>>> admitted, score, details = evaluate_candidate(candidate, policy)
>>> admitted, score
(True, 1)
>>> # Candidate with discouraged feature (hard mode)
>>> candidate = {"name": "kalt", "features": {"ends_with_stop": True}}
>>> admitted, score, details = evaluate_candidate(candidate, policy, mode="hard")
>>> admitted
False
>>> details["rejection_reason"]
'ends_with_stop'

Notes

Only TRUE features are evaluated. If a feature is FALSE in the candidate, it does not contribute to the score regardless of its policy designation.

This means “discouraged” means “discouraged when present”, not “required to be absent”.

build_tools.name_selector.policy.check_syllable_count(candidate, policy)[source]

Check if a candidate’s syllable count is within policy range.

Parameters

candidatedict

Candidate dictionary with “syllables” key (list of syllable strings).

policyNameClassPolicy

The policy with syllable_range constraint.

Returns

bool

True if syllable count is within range, False otherwise.

Examples

>>> policy = NameClassPolicy(..., syllable_range=(2, 3))
>>> check_syllable_count({"syllables": ["ka", "li"]}, policy)
True
>>> check_syllable_count({"syllables": ["ka"]}, policy)
False