github-actions[bot]
deploy prod from 06dbd16a01ddcfe02b2d936c681e3e4eaa9b141f
8e756fd
Raw
History Blame Contribute Delete
3.03 kB
"""Map raw features to 0–100 per-side analysis scores."""
from __future__ import annotations
from .features import RawPositionFeatures, SideRawFeatures, _STARTING_MATERIAL_INDEX
def _clamp100(x: float) -> float:
return max(0.0, min(100.0, x))
def _linear(value: float, max_value: float) -> float:
if max_value <= 0:
return 0.0
return _clamp100(100.0 * value / max_value)
def _quality_from_penalty(penalty: float, max_penalty: float) -> float:
if max_penalty <= 0:
return 100.0
return _clamp100(100.0 - 100.0 * penalty / max_penalty)
def normalize_side(raw: SideRawFeatures) -> dict[str, float]:
"""Convert one side's raw features to named 0–100 scores (higher = better where noted)."""
closedness = _linear(raw.closedness_index, 8.0)
openness = _linear(raw.open_files, 8.0)
pawn_penalty = (
raw.isolated_pawns * 14.0
+ raw.backward_pawns * 12.0
+ raw.stacked_pawns * 8.0
)
pawn_bonus = (
raw.connected_pawns * 4.0
+ raw.candidate_passers * 10.0
+ raw.passed_pawns * 18.0
)
pawn_structure = _clamp100(50.0 + pawn_bonus - pawn_penalty)
king_penalty = (
raw.king_attacks * 4.0
+ raw.king_attackers * 12.0
+ raw.king_weak_squares * 10.0
)
king_bonus = raw.king_defenders * 6.0 + raw.shelter_mg / 500_000.0
king_safety = _quality_from_penalty(max(0.0, king_penalty - king_bonus), 80.0)
mobility = (
raw.knight_mobility
+ raw.bishop_mobility
+ raw.rook_mobility
+ raw.queen_mobility
)
piece_activity = _linear(mobility + raw.outposts * 3.0, 55.0)
passed_pawn_pressure = _linear(raw.passed_pressure, 40.0)
space = _linear(raw.center_control * 4.0 + raw.space_restricted, 24.0)
tactical_tension = _linear(raw.threat_units + raw.pawn_push_threats, 18.0)
tactical_complexity = _linear(raw.complexity_units, 20.0)
material = _linear(raw.material_index, float(_STARTING_MATERIAL_INDEX))
return {
"closedness": closedness,
"openness": openness,
"pawn_structure": pawn_structure,
"king_safety": king_safety,
"piece_activity": piece_activity,
"passed_pawn_pressure": passed_pawn_pressure,
"space": space,
"tactical_tension": tactical_tension,
"tactical_complexity": tactical_complexity,
"material": material,
}
def normalize_position(raw: RawPositionFeatures) -> dict:
"""Full position analysis dict with per-side 0–100 components."""
white = normalize_side(raw.white)
black = normalize_side(raw.black)
phase_middlegame = _linear(raw.phase_index, 24.0)
return {
"phase_middlegame": phase_middlegame,
"white": white,
"black": black,
}
COMPONENT_KEYS = (
"closedness",
"openness",
"pawn_structure",
"king_safety",
"piece_activity",
"passed_pawn_pressure",
"space",
"tactical_tension",
"tactical_complexity",
"material",
)