51 lines
1.5 KiB
Python
51 lines
1.5 KiB
Python
"""A suspect is a status change, by coding or by care.
|
|
|
|
Coding makes a status the person already has visible to the score. Care
|
|
changes the underlying state. Demographic reweighting multiplies only the
|
|
coding delta. The two are never collapsed into one number.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SuspectMove:
|
|
"""How far each mechanism moves the score."""
|
|
|
|
coding_delta: float
|
|
care_delta: float
|
|
coding_after_reweight: float
|
|
|
|
|
|
def suspect_move(
|
|
*,
|
|
before_status: str,
|
|
after_status: str,
|
|
score_before: float,
|
|
score_coding: float,
|
|
score_care: float,
|
|
weight_before: float,
|
|
weight_after: float,
|
|
) -> SuspectMove:
|
|
"""Return the coding delta, the care delta, and the reweighted coding delta.
|
|
|
|
Raises:
|
|
ValueError: the status does not change, the weights are equal, or
|
|
the starting weight is zero.
|
|
"""
|
|
if before_status == after_status:
|
|
raise ValueError("a suspect is a status change")
|
|
if weight_before == weight_after:
|
|
raise ValueError("reweight needs a different weight")
|
|
if weight_before == 0:
|
|
raise ValueError("weight_before must not be zero")
|
|
coding_delta = score_coding - score_before
|
|
care_delta = score_care - score_before
|
|
return SuspectMove(
|
|
coding_delta=coding_delta,
|
|
care_delta=care_delta,
|
|
coding_after_reweight=coding_delta * (weight_after / weight_before),
|
|
)
|