Jerlshin's picture
defect fix career
f0894e2
Raw
History Blame Contribute Delete
6.58 kB
from __future__ import annotations
from collections.abc import Mapping
from typing import Final, final
from pydantic import BaseModel, ConfigDict
from redstack.config.schema import ScoringPolicy
from redstack.domain.candidate.eligibility import EligibilityReport
from redstack.domain.candidate.integrity import IntegrityReport
from redstack.domain.enums import ScoreComponent
from redstack.domain.errors import ScoreInvariantError
from redstack.domain.ids import CandidateId, Multiplier, Score, UnitScore
from redstack.domain.provenance import EvidenceRef
from redstack.domain.scoring import (
GateOutcome,
ScoreBreakdown,
ScoredCandidate,
ScoreComponentValue,
ScoringWeights,
)
_COMPONENT_ORDER: Final[tuple[ScoreComponent, ...]] = tuple(ScoreComponent)
ComponentRaw = tuple[UnitScore, tuple[EvidenceRef, ...]]
@final
class ScoringEngine(BaseModel):
"""Stateless, pure scoring engine; weights + policy are injected, immutable."""
model_config = ConfigDict(frozen=True, extra="forbid", arbitrary_types_allowed=False)
weights: ScoringWeights
policy: ScoringPolicy
def __init__(self, **data: object) -> None:
super().__init__(**data)
# Weight set must equal ScoreComponent exactly (ArtifactContractError is
# raised at load; here we defensively re-assert against silent drift).
missing = set(_COMPONENT_ORDER) - set(self.weights.weights)
if missing:
raise ScoreInvariantError(
f"ScoringWeights missing components: {sorted(c.value for c in missing)}"
)
# ------------------------------------------------------------------ public
def score(
self,
*,
candidate_id: CandidateId,
components: Mapping[ScoreComponent, ComponentRaw],
integrity: IntegrityReport,
eligibility: EligibilityReport,
behavioral_multiplier: Multiplier,
logistics_multiplier: Multiplier,
archetype_adjustment: float,
confidence: UnitScore,
) -> ScoredCandidate:
"""Produce the ``ScoredCandidate`` with a fully reconstructable breakdown."""
component_values = self._component_values(components)
base = self._sum_weighted(component_values)
integrity_gate = self._integrity_gate(integrity)
eligibility_gate = self._eligibility_gate(eligibility)
gated = not (integrity_gate.passed and eligibility_gate.passed)
if gated:
final = Score(float(self.policy.floor))
beh = behavioral_multiplier
log = logistics_multiplier
adjustment = 0.0 # no multipliers/adjustments applied to a floored row
else:
combined = (
float(base)
* float(behavioral_multiplier)
* float(logistics_multiplier)
+ archetype_adjustment
)
final = Score(self._shrink(combined, confidence))
beh = behavioral_multiplier
log = logistics_multiplier
adjustment = archetype_adjustment
self._assert_finite(float(base), float(final))
breakdown = ScoreBreakdown(
components=component_values,
base_relevance=base,
integrity_gate=integrity_gate,
eligibility_gate=eligibility_gate,
behavioral_multiplier=beh,
logistics_multiplier=log,
archetype_adjustment=adjustment,
final_score=final,
)
if gated and float(final) != float(self.policy.floor):
raise ScoreInvariantError("gated candidate not floored")
return ScoredCandidate(
candidate_id=candidate_id,
final_score=final,
breakdown=breakdown,
tiebreak_key=candidate_id,
)
# --------------------------------------------------------------- internals
def _component_values(
self, components: Mapping[ScoreComponent, ComponentRaw]
) -> tuple[ScoreComponentValue, ...]:
values: list[ScoreComponentValue] = []
for component in _COMPONENT_ORDER:
raw, evidence = components.get(
component, (UnitScore(0.0), ())
)
weight = float(self.weights.weights[component])
weighted = float(raw) * weight
values.append(
ScoreComponentValue(
component=component,
raw=raw,
weight=weight,
weighted=weighted,
evidence=evidence,
)
)
return tuple(values)
@staticmethod
def _sum_weighted(values: tuple[ScoreComponentValue, ...]) -> Score:
# Summed in ScoreComponent order (values already ordered); float32-stable.
total = 0.0
for value in values:
total += value.weighted
return Score(total)
def _shrink(self, combined: float, confidence: UnitScore) -> float:
"""Shrink ``combined`` toward the neutral prior in proportion to confidence.
``shrunk = prior + confidence·(combined − prior)``: full confidence keeps the
value, zero confidence collapses to the prior. Monotone in ``combined`` for a
fixed confidence, so ranking order is preserved among equal-confidence rows.
"""
prior = self.policy.neutral_prior
return prior + float(confidence) * (combined - prior)
@staticmethod
def _integrity_gate(report: IntegrityReport) -> GateOutcome:
if not report.is_honeypot:
return GateOutcome(passed=True, reason=None)
reason = next(
(f.code for f in report.findings if f.severity.value == "hard"), None
)
if reason is None and report.findings:
reason = report.findings[0].code
return GateOutcome(passed=False, reason=reason)
@staticmethod
def _eligibility_gate(report: EligibilityReport) -> GateOutcome:
if report.is_eligible:
return GateOutcome(passed=True, reason=None)
reason = report.hard_blocks[0].code if report.hard_blocks else None
return GateOutcome(passed=False, reason=reason)
@staticmethod
def _assert_finite(base: float, final: float) -> None:
for label, value in (("base_relevance", base), ("final_score", final)):
if value != value or value in (float("inf"), float("-inf")):
raise ScoreInvariantError(f"{label} is non-finite ({value!r})")
__all__: tuple[str, ...] = ("ComponentRaw", "ScoringEngine")