use std::collections::BTreeMap;
use super::compile::AnchorMap;
use super::resolve::ResolutionStatus;
use super::{AdmissionKind, DOMAIN_VALUE, Judgement, RaterKind, RowForm};
pub(crate) trait ClaimPayload: Clone + PartialEq {
fn extract(j: &Judgement) -> Option<Self>;
fn mean(rows: &[Self]) -> Self;
fn operative(&self, params: &Self::Params) -> f64;
type Params: Clone;
}
impl ClaimPayload for f64 {
fn extract(j: &Judgement) -> Option<Self> {
j.magnitude
}
fn mean(rows: &[Self]) -> Self {
let count = u32::try_from(rows.len()).unwrap_or(u32::MAX);
rows.iter().sum::<f64>() / f64::from(count)
}
fn operative(&self, _params: &()) -> f64 {
*self
}
type Params = ();
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct EstimatePayload(pub(crate) f64, pub(crate) f64);
impl ClaimPayload for EstimatePayload {
fn extract(j: &Judgement) -> Option<Self> {
Some(EstimatePayload(j.est_lower?, j.est_upper?))
}
fn mean(rows: &[Self]) -> Self {
let count = u32::try_from(rows.len()).unwrap_or(u32::MAX);
let len = f64::from(count);
let l = rows.iter().map(|p| p.0).sum::<f64>() / len;
let u = rows.iter().map(|p| p.1).sum::<f64>() / len;
EstimatePayload(l, u)
}
fn operative(&self, params: &f64) -> f64 {
crate::estimate::operative_cost((self.0, self.1), *params)
}
type Params = f64;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum ClaimTier {
Migrated,
Agent,
Human,
Pin,
}
impl ClaimTier {
pub(crate) fn is_anchored(self) -> bool {
matches!(self, ClaimTier::Pin | ClaimTier::Human)
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ResolvedClaimGeneric<P: ClaimPayload> {
pub(crate) operative: f64,
pub(crate) payload: P,
pub(crate) tier: ClaimTier,
pub(crate) conflict: Option<ClaimConflict>,
pub(crate) rows: u32,
pub(crate) attribution: Option<ClaimAttribution>,
}
pub(crate) type ResolvedClaim = ResolvedClaimGeneric<f64>;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub(crate) struct ClaimAttribution {
pub by: Option<String>,
pub date: Option<String>,
pub observed_at: Option<String>,
pub basis: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ClaimConflict {
pub low: f64,
pub high: f64,
pub distinct: u32,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ClaimFinding {
Conflict {
domain: String,
item: String,
tier: ClaimTier,
low: f64,
high: f64,
distinct: u32,
rows: u32,
},
}
impl ClaimFinding {
pub(crate) fn nominates_reprobe(&self) -> bool {
let ClaimFinding::Conflict { tier, .. } = self;
tier.is_anchored()
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ClaimResolutionGeneric<P: ClaimPayload> {
pub anchored: BTreeMap<String, ResolvedClaimGeneric<P>>,
pub priors: BTreeMap<String, ResolvedClaimGeneric<P>>,
pub lensed: BTreeMap<(String, String), ResolvedClaimGeneric<P>>,
pub findings: Vec<ClaimFinding>,
}
pub(crate) type ClaimResolution = ClaimResolutionGeneric<f64>;
impl<P: ClaimPayload> Default for ClaimResolutionGeneric<P> {
fn default() -> Self {
Self {
anchored: BTreeMap::new(),
priors: BTreeMap::new(),
lensed: BTreeMap::new(),
findings: Vec::new(),
}
}
}
impl<P: ClaimPayload> ClaimResolutionGeneric<P> {
pub(crate) fn anchor_map(&self) -> AnchorMap {
self.anchored
.iter()
.map(|(item, claim)| (item.clone(), claim.operative))
.collect()
}
}
struct PayloadRow<P: ClaimPayload> {
tier: ClaimTier,
payload: P,
params: P::Params,
attribution: ClaimAttribution,
}
pub(crate) fn resolve_claims_generic<P: ClaimPayload>(
rows: &[(&Judgement, ResolutionStatus)],
domain: &str,
params: &P::Params,
) -> ClaimResolutionGeneric<P> {
let mut unlensed: BTreeMap<String, Vec<PayloadRow<P>>> = BTreeMap::new();
let mut lensed_groups: BTreeMap<(String, String), Vec<PayloadRow<P>>> = BTreeMap::new();
for (j, status) in rows {
if j.domain != domain
|| !matches!(j.form, RowForm::Anchor)
|| !matches!(
status,
ResolutionStatus::Active | ResolutionStatus::InertLens
)
{
continue;
}
let Some(payload) = P::extract(j) else {
continue;
};
let row = PayloadRow {
tier: tier_of(j),
payload,
params: params.clone(),
attribution: ClaimAttribution {
by: j.by.clone(),
date: j.date.clone(),
observed_at: j.observed_at.clone(),
basis: j.basis.clone(),
},
};
match &j.lens {
Some(lens) => lensed_groups
.entry((lens.clone(), j.a.clone()))
.or_default()
.push(row),
None => unlensed.entry(j.a.clone()).or_default().push(row),
}
}
let mut out = ClaimResolutionGeneric::default();
for (item, group) in unlensed {
let claim = resolve_group::<P>(&group, params);
if let Some(conflict) = &claim.conflict {
out.findings.push(ClaimFinding::Conflict {
domain: domain.to_string(),
item: item.clone(),
tier: claim.tier,
low: conflict.low,
high: conflict.high,
distinct: conflict.distinct,
rows: claim.rows,
});
}
if claim.tier.is_anchored() {
out.anchored.insert(item, claim);
} else {
out.priors.insert(item, claim);
}
}
for (key, group) in lensed_groups {
out.lensed.insert(key, resolve_group::<P>(&group, params));
}
out
}
pub(crate) fn resolve_claims(rows: &[(&Judgement, ResolutionStatus)]) -> ClaimResolution {
resolve_claims_generic::<f64>(rows, DOMAIN_VALUE, &())
}
fn tier_of(j: &Judgement) -> ClaimTier {
match (&j.admission, &j.rater) {
(Some(AdmissionKind::Pin), _) => ClaimTier::Pin,
(None, RaterKind::Human) => ClaimTier::Human,
(None, RaterKind::Agent) => ClaimTier::Agent,
(None, RaterKind::Migrated) => ClaimTier::Migrated,
}
}
fn resolve_group<P: ClaimPayload>(
group: &[PayloadRow<P>],
params: &P::Params,
) -> ResolvedClaimGeneric<P> {
let winning = group
.iter()
.map(|r| r.tier)
.max()
.unwrap_or(ClaimTier::Migrated); let winning_rows: Vec<&PayloadRow<P>> = group.iter().filter(|r| r.tier == winning).collect();
let attribution = match winning_rows.as_slice() {
[only] => Some(only.attribution.clone()),
_ => None,
};
let payloads: Vec<P> = winning_rows.iter().map(|r| r.payload.clone()).collect();
let operatives: Vec<f64> = winning_rows
.iter()
.map(|r| r.payload.operative(&r.params))
.collect();
let rows = u32::try_from(payloads.len()).unwrap_or(u32::MAX);
let mut distinct = 0_u32;
for i in 0..payloads.len() {
let Some(current) = payloads.get(i) else {
break;
};
if !payloads.get(..i).is_some_and(|s| s.contains(current)) {
distinct += 1;
}
}
let (operative, payload, conflict) = if distinct > 1 {
let mean_payload = P::mean(&payloads);
let mean_op = mean_payload.operative(params);
let (low, high) = (
operatives
.iter()
.copied()
.min_by(f64::total_cmp)
.unwrap_or(mean_op),
operatives
.iter()
.copied()
.max_by(f64::total_cmp)
.unwrap_or(mean_op),
);
(
mean_op,
mean_payload,
Some(ClaimConflict {
low,
high,
distinct,
}),
)
} else if let Some(only) = winning_rows.first() {
(
only.payload.operative(&only.params),
only.payload.clone(),
None,
)
} else {
let fallback = P::mean(&[]);
(fallback.operative(params), fallback, None)
};
ResolvedClaimGeneric {
operative,
payload,
tier: winning,
conflict,
rows,
attribution,
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::{
ClaimFinding, ClaimPayload, ClaimResolution, ClaimResolutionGeneric, ClaimTier,
EstimatePayload, resolve_claims, resolve_claims_generic,
};
use crate::comparison::resolve::{ResolutionStatus, StatusMap, resolve};
use crate::comparison::{
AdmissionKind, COMPARISON_SCHEMA, COMPARISON_VERSION, ComparisonSession, DOMAIN_ESTIMATE,
FRAME_COST_ANCHOR, FRAME_VALUE_ANCHOR, Judgement, RaterKind, RowForm, SessionHeader,
};
fn anchor(uid: &str, item: &str, magnitude: f64, rater: RaterKind) -> Judgement {
let migrated = matches!(rater, RaterKind::Migrated);
Judgement {
uid: uid.to_string(),
seq: 0,
a: item.to_string(),
b: None,
response: None,
domain: crate::comparison::DOMAIN_VALUE.to_string(),
frame: FRAME_VALUE_ANCHOR.to_string(),
form: RowForm::Anchor,
magnitude: Some(magnitude),
supersedes: None,
lens: None,
rater,
by: None,
note: None,
date: (!migrated).then(|| "2026-07-16".to_string()),
observed_at: migrated.then(|| "2026-07-16".to_string()),
basis: None,
est_lower: None,
est_upper: None,
admission: None,
}
}
fn pin(uid: &str, item: &str, magnitude: f64) -> Judgement {
let mut j = anchor(uid, item, magnitude, RaterKind::Human);
j.admission = Some(AdmissionKind::Pin);
j
}
fn lensed(uid: &str, item: &str, magnitude: f64, lens: &str) -> Judgement {
let mut j = anchor(uid, item, magnitude, RaterKind::Human);
j.lens = Some(lens.to_string());
j
}
fn active(rows: &[Judgement]) -> Vec<(&Judgement, ResolutionStatus)> {
rows.iter()
.map(|j| {
let status = if j.lens.is_some() {
ResolutionStatus::InertLens
} else {
ResolutionStatus::Active
};
(j, status)
})
.collect()
}
fn session(uid: &str, judgements: Vec<Judgement>) -> ComparisonSession {
ComparisonSession {
schema: COMPARISON_SCHEMA.to_string(),
version: COMPARISON_VERSION,
session: SessionHeader {
uid: uid.to_string(),
date: "2026-07-16".to_string(),
audience: None,
},
judgements,
tombstones: Vec::new(),
}
}
fn conflict_of<'a>(claims: &'a ClaimResolution, item: &str) -> &'a ClaimFinding {
claims
.findings
.iter()
.find(|f| {
let ClaimFinding::Conflict { item: i, .. } = f;
i == item
})
.expect("conflict finding present")
}
fn est_anchor(uid: &str, item: &str, lower: f64, upper: f64, rater: RaterKind) -> Judgement {
let migrated = matches!(rater, RaterKind::Migrated);
Judgement {
uid: uid.to_string(),
seq: 0,
a: item.to_string(),
b: None,
response: None,
domain: DOMAIN_ESTIMATE.to_string(),
frame: FRAME_COST_ANCHOR.to_string(),
form: RowForm::Anchor,
magnitude: None,
supersedes: None,
lens: None,
rater,
by: None,
note: None,
date: (!migrated).then(|| "2026-07-16".to_string()),
observed_at: migrated.then(|| "2026-07-16".to_string()),
basis: None,
est_lower: Some(lower),
est_upper: Some(upper),
admission: None,
}
}
fn est_pin(uid: &str, item: &str, lower: f64, upper: f64) -> Judgement {
let mut j = est_anchor(uid, item, lower, upper, RaterKind::Human);
j.admission = Some(AdmissionKind::Pin);
j
}
fn est_lensed(uid: &str, item: &str, lower: f64, upper: f64, lens: &str) -> Judgement {
let mut j = est_anchor(uid, item, lower, upper, RaterKind::Human);
j.lens = Some(lens.to_string());
j
}
fn est_session(uid: &str, judgements: Vec<Judgement>) -> ComparisonSession {
ComparisonSession {
schema: COMPARISON_SCHEMA.to_string(),
version: COMPARISON_VERSION,
session: SessionHeader {
uid: uid.to_string(),
date: "2026-07-16".to_string(),
audience: None,
},
judgements,
tombstones: Vec::new(),
}
}
fn est_conflict_of<'a, P: ClaimPayload>(
claims: &'a ClaimResolutionGeneric<P>,
item: &str,
) -> &'a ClaimFinding {
claims
.findings
.iter()
.find(|f| {
let ClaimFinding::Conflict { item: i, .. } = f;
i == item
})
.expect("conflict finding present")
}
#[test]
fn pin_outranks_all_tiers_under_derived_ord() {
assert!(ClaimTier::Migrated < ClaimTier::Agent);
assert!(ClaimTier::Agent < ClaimTier::Human);
assert!(ClaimTier::Human < ClaimTier::Pin);
let all = [
ClaimTier::Pin,
ClaimTier::Migrated,
ClaimTier::Human,
ClaimTier::Agent,
];
assert_eq!(all.iter().max(), Some(&ClaimTier::Pin));
}
#[test]
fn resolution_is_invariant_under_row_permutation() {
let rows = [
anchor("h1", "SL-100", 5.0, RaterKind::Human),
anchor("h2", "SL-100", 5.0, RaterKind::Human),
anchor("h3", "SL-100", 7.0, RaterKind::Human),
anchor("a1", "SL-200", 2.0, RaterKind::Agent),
anchor("m1", "SL-300", 1.0, RaterKind::Migrated),
lensed("l1", "SL-100", 9.0, "user-value"),
];
let forward = active(&rows);
let baseline = resolve_claims(&forward);
let mut reversed = forward.clone();
reversed.reverse();
assert_eq!(resolve_claims(&reversed), baseline);
let mut rotated = forward.clone();
rotated.rotate_left(3);
assert_eq!(resolve_claims(&rotated), baseline);
}
#[test]
fn corroborating_rows_resolve_without_conflict() {
let rows = [
anchor("h1", "SL-100", 5.0, RaterKind::Human),
anchor("h2", "SL-100", 5.0, RaterKind::Human),
anchor("h3", "SL-100", 5.0, RaterKind::Human),
];
let claims = resolve_claims(&active(&rows));
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.operative, 5.0);
assert_eq!(claim.tier, ClaimTier::Human);
assert_eq!(claim.conflict, None);
assert_eq!(claim.rows, 3);
assert!(claims.findings.is_empty());
}
#[test]
fn conflict_takes_the_multiset_mean_with_interval_and_distinct() {
let rows = [
anchor("h1", "SL-100", 5.0, RaterKind::Human),
anchor("h2", "SL-100", 5.0, RaterKind::Human),
anchor("h3", "SL-100", 7.0, RaterKind::Human),
];
let claims = resolve_claims(&active(&rows));
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.operative, 17.0 / 3.0, "multiset mean");
let conflict = claim.conflict.as_ref().expect("conflict present");
assert_eq!((conflict.low, conflict.high), (5.0, 7.0));
assert_eq!(conflict.distinct, 2);
assert_eq!(claim.rows, 3);
assert_eq!(
conflict_of(&claims, "SL-100"),
&ClaimFinding::Conflict {
domain: "value".to_string(),
item: "SL-100".to_string(),
tier: ClaimTier::Human,
low: 5.0,
high: 7.0,
distinct: 2,
rows: 3,
}
);
}
#[test]
fn conflicting_pins_raise_a_contested_pin_finding() {
let rows = [pin("p1", "SL-100", 3.0), pin("p2", "SL-100", 9.0)];
let claims = resolve_claims(&active(&rows));
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.tier, ClaimTier::Pin);
assert_eq!(claim.operative, 6.0);
let finding = conflict_of(&claims, "SL-100");
assert!(
matches!(
finding,
ClaimFinding::Conflict {
tier: ClaimTier::Pin,
..
}
),
"contested pin: {finding:?}"
);
assert!(finding.nominates_reprobe());
}
#[test]
fn reprobe_nomination_is_anchored_tiers_only() {
let rows = [
anchor("h1", "SL-100", 4.0, RaterKind::Human),
anchor("h2", "SL-100", 6.0, RaterKind::Human),
anchor("a1", "SL-200", 1.0, RaterKind::Agent),
anchor("a2", "SL-200", 3.0, RaterKind::Agent),
anchor("m1", "SL-300", 1.0, RaterKind::Migrated),
anchor("m2", "SL-300", 5.0, RaterKind::Migrated),
];
let claims = resolve_claims(&active(&rows));
assert!(conflict_of(&claims, "SL-100").nominates_reprobe());
assert!(!conflict_of(&claims, "SL-200").nominates_reprobe());
assert!(!conflict_of(&claims, "SL-300").nominates_reprobe());
assert_eq!(claims.findings.len(), 3);
}
#[test]
fn lower_tiers_contribute_nothing_to_a_won_item() {
let rows = [
anchor("h1", "SL-100", 6.0, RaterKind::Human),
anchor("a1", "SL-100", 1.0, RaterKind::Agent),
anchor("a2", "SL-100", 99.0, RaterKind::Agent),
anchor("m1", "SL-100", 42.0, RaterKind::Migrated),
];
let claims = resolve_claims(&active(&rows));
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.operative, 6.0);
assert_eq!(claim.conflict, None);
assert_eq!(claim.rows, 1);
assert!(!claims.priors.contains_key("SL-100"));
assert!(claims.findings.is_empty());
}
#[test]
fn agent_and_migrated_claims_route_to_priors() {
let rows = [
anchor("a1", "SL-100", 2.0, RaterKind::Agent),
anchor("m1", "SL-200", 3.0, RaterKind::Migrated),
];
let claims = resolve_claims(&active(&rows));
assert!(claims.anchored.is_empty());
assert_eq!(claims.priors["SL-100"].tier, ClaimTier::Agent);
assert_eq!(claims.priors["SL-200"].tier, ClaimTier::Migrated);
}
#[test]
fn cross_session_same_tier_claims_conflict_never_latest_wins() {
let s1 = session("s1", vec![anchor("p", "SL-100", 4.0, RaterKind::Human)]);
let s2 = session("s2", vec![anchor("q", "SL-100", 8.0, RaterKind::Human)]);
let sessions = [s1, s2];
let res = resolve(&sessions, &StatusMap::new()).expect("resolve ok");
let claims = resolve_claims(&res.rows);
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.operative, 6.0);
assert!(claim.conflict.is_some());
assert_eq!(claim.rows, 2);
}
#[test]
fn identical_refire_changes_no_value_and_raises_no_conflict() {
let once = [session(
"s1",
vec![anchor("p", "SL-100", 4.0, RaterKind::Human)],
)];
let twice = [
session("s1", vec![anchor("p", "SL-100", 4.0, RaterKind::Human)]),
session("s2", vec![anchor("q", "SL-100", 4.0, RaterKind::Human)]),
];
let res_once = resolve(&once, &StatusMap::new()).expect("resolve ok");
let res_twice = resolve(&twice, &StatusMap::new()).expect("resolve ok");
let one = resolve_claims(&res_once.rows);
let two = resolve_claims(&res_twice.rows);
assert_eq!(
one.anchored["SL-100"].operative,
two.anchored["SL-100"].operative
);
assert_eq!(two.anchored["SL-100"].conflict, None);
assert_eq!(two.anchored["SL-100"].rows, 2);
assert!(two.findings.is_empty());
}
#[test]
fn lens_isolation_holds_non_vacuously_in_both_directions() {
let mixed = [
session(
"s1",
vec![
anchor("h1", "SL-100", 5.0, RaterKind::Human),
anchor("a1", "SL-200", 2.0, RaterKind::Agent),
lensed("l1", "SL-100", 9.0, "user-value"),
lensed("l3", "SL-300", 1.0, "ops-value"),
],
),
session("s2", vec![lensed("l2", "SL-100", 3.0, "user-value")]),
];
let res = resolve(&mixed, &StatusMap::new()).expect("resolve ok");
for uid in ["l1", "l2", "l3"] {
let (_, status) = res
.rows
.iter()
.find(|(j, _)| j.uid == uid)
.expect("row present");
assert_eq!(status, &ResolutionStatus::InertLens);
}
let with_lensed = resolve_claims(&res.rows);
assert!(!with_lensed.lensed.is_empty());
let key = ("user-value".to_string(), "SL-100".to_string());
assert!(with_lensed.lensed[&key].conflict.is_some());
assert_eq!(with_lensed.lensed[&key].operative, 6.0);
let unlensed_only = [
session(
"s1",
vec![
anchor("h1", "SL-100", 5.0, RaterKind::Human),
anchor("a1", "SL-200", 2.0, RaterKind::Agent),
],
),
session("s2", vec![]),
];
let res2 = resolve(&unlensed_only, &StatusMap::new()).expect("resolve ok");
let without_lensed = resolve_claims(&res2.rows);
assert_eq!(with_lensed.anchored, without_lensed.anchored);
assert_eq!(with_lensed.priors, without_lensed.priors);
}
#[test]
fn lensed_conflicts_do_not_enter_the_finding_stream() {
let rows = [
lensed("l1", "SL-100", 1.0, "user-value"),
lensed("l2", "SL-100", 9.0, "user-value"),
];
let claims = resolve_claims(&active(&rows));
let key = ("user-value".to_string(), "SL-100".to_string());
assert!(claims.lensed[&key].conflict.is_some());
assert!(claims.findings.is_empty());
}
#[test]
fn anchor_map_never_launders_agent_or_migrated_claims() {
let minted = |tier: usize, item: &str, uid: &str| -> Judgement {
match tier {
0 => anchor(uid, item, 1.0, RaterKind::Migrated),
1 => anchor(uid, item, 2.0, RaterKind::Agent),
2 => anchor(uid, item, 3.0, RaterKind::Human),
_ => pin(uid, item, 4.0),
}
};
for mask in 0..16_u32.pow(3) {
let mut rows: Vec<Judgement> = Vec::new();
for (i, item) in ["SL-100", "SL-200", "SL-300"].iter().enumerate() {
let tiers = (mask / 16_u32.pow(u32::try_from(i).unwrap())) % 16;
for tier in 0..4 {
if tiers & (1 << tier) != 0 {
rows.push(minted(tier, item, &format!("j{i}t{tier}")));
}
}
}
let claims = resolve_claims(&active(&rows));
let map = claims.anchor_map();
let anchored_values: BTreeMap<String, f64> = claims
.anchored
.iter()
.map(|(k, c)| (k.clone(), c.operative))
.collect();
assert_eq!(map, anchored_values, "anchor_map ≡ anchored, mask {mask}");
for claim in claims.anchored.values() {
assert!(claim.tier.is_anchored());
}
for (item, claim) in &claims.priors {
assert!(!claim.tier.is_anchored());
assert!(!map.contains_key(item));
}
}
}
#[test]
fn non_live_rows_carry_no_claim() {
let rows = [
anchor("dead", "SL-100", 9.0, RaterKind::Human),
anchor("live", "SL-100", 5.0, RaterKind::Human),
anchor("tomb", "SL-200", 3.0, RaterKind::Human),
];
let tagged: Vec<(&Judgement, ResolutionStatus)> = vec![
(
&rows[0],
ResolutionStatus::Superseded {
by: "live".to_string(),
},
),
(&rows[1], ResolutionStatus::Active),
(&rows[2], ResolutionStatus::Tombstoned),
];
let claims = resolve_claims(&tagged);
let claim = &claims.anchored["SL-100"];
assert_eq!((claim.operative, claim.rows), (5.0, 1));
assert_eq!(claim.conflict, None);
assert!(!claims.anchored.contains_key("SL-200"));
}
#[test]
fn pairwise_rows_never_enter_the_claims_pass() {
let mut order = anchor("ord", "SL-100", 5.0, RaterKind::Human);
order.form = RowForm::Order;
order.b = Some("SL-200".to_string());
order.response = Some(crate::comparison::Response::PreferA);
order.frame = crate::comparison::FRAME_EQUAL_EFFORT.to_string();
let rows = [order];
let claims = resolve_claims(&active(&rows));
assert_eq!(claims, ClaimResolution::default());
}
#[test]
fn estimate_claim_payload_tier_ordering() {
let est_rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("a1", "SL-100", 1.0, 3.0, RaterKind::Agent),
est_anchor("m1", "SL-100", 5.0, 5.0, RaterKind::Migrated),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> = est_rows
.iter()
.map(|j| (j, ResolutionStatus::Active))
.collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.tier, ClaimTier::Human);
assert!((claim.operative - 5.9).abs() < 1e-12);
}
#[test]
fn estimate_permutation_invariance() {
let h1 = est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human);
let h2 = est_anchor("h2", "SL-100", 2.0, 8.0, RaterKind::Human);
let h3 = est_anchor("h3", "SL-100", 4.0, 6.0, RaterKind::Human);
let rows = [
(&h1, ResolutionStatus::Active),
(&h2, ResolutionStatus::Active),
(&h3, ResolutionStatus::Active),
];
let baseline = resolve_claims_generic::<EstimatePayload>(&rows, DOMAIN_ESTIMATE, &0.65);
let mut reversed = rows.to_vec();
reversed.reverse();
assert_eq!(
resolve_claims_generic::<EstimatePayload>(&reversed, DOMAIN_ESTIMATE, &0.65),
baseline
);
}
#[test]
fn estimate_corroboration_without_conflict() {
let rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("h2", "SL-100", 2.0, 8.0, RaterKind::Human),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
let claim = &claims.anchored["SL-100"];
assert!((claim.operative - 5.9).abs() < 1e-12);
assert_eq!(claim.conflict, None);
assert_eq!(claim.rows, 2);
}
#[test]
fn estimate_conflict_over_ranges() {
let rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("h2", "SL-100", 1.0, 3.0, RaterKind::Human),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
let claim = &claims.anchored["SL-100"];
assert!((claim.operative - 4.1).abs() < 1e-12);
let conflict = claim.conflict.as_ref().expect("conflict present");
assert!((conflict.low - 2.3).abs() < 1e-12);
assert!((conflict.high - 5.9).abs() < 1e-12);
assert_eq!(conflict.distinct, 2);
}
#[test]
fn estimate_conflicting_pins_contested() {
let rows = [
est_pin("p1", "SL-100", 2.0, 8.0),
est_pin("p2", "SL-100", 1.0, 3.0),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
assert_eq!(claims.anchored["SL-100"].tier, ClaimTier::Pin);
assert!(claims.anchored["SL-100"].conflict.is_some());
let finding = est_conflict_of(&claims, "SL-100");
assert!(matches!(
finding,
ClaimFinding::Conflict {
tier: ClaimTier::Pin,
..
}
));
assert!(finding.nominates_reprobe());
}
#[test]
fn estimate_cross_session_concurrency() {
let s1 = est_session(
"s1",
vec![est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human)],
);
let s2 = est_session(
"s2",
vec![est_anchor("h2", "SL-100", 4.0, 6.0, RaterKind::Human)],
);
let sessions = [s1, s2];
let res = resolve(&sessions, &StatusMap::new()).expect("resolve ok");
let claims = resolve_claims_generic::<EstimatePayload>(&res.rows, DOMAIN_ESTIMATE, &0.65);
assert!((claims.anchored["SL-100"].operative - 5.6).abs() < 1e-12);
assert!(claims.anchored["SL-100"].conflict.is_some());
assert_eq!(claims.anchored["SL-100"].rows, 2);
}
#[test]
fn estimate_lens_isolation_both_directions() {
let mixed = [
est_session(
"s1",
vec![
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_lensed("l1", "SL-100", 1.0, 3.0, "user-value"),
],
),
est_session(
"s2",
vec![est_lensed("l2", "SL-100", 4.0, 6.0, "user-value")],
),
];
let res = resolve(&mixed, &StatusMap::new()).expect("resolve ok");
let claims = resolve_claims_generic::<EstimatePayload>(&res.rows, DOMAIN_ESTIMATE, &0.65);
assert!(!claims.lensed.is_empty());
assert!(
claims.lensed[&("user-value".into(), "SL-100".into())]
.conflict
.is_some()
);
let unlensed_only = [est_session(
"s1",
vec![est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human)],
)];
let res2 = resolve(&unlensed_only, &StatusMap::new()).expect("resolve ok");
let without = resolve_claims_generic::<EstimatePayload>(&res2.rows, DOMAIN_ESTIMATE, &0.65);
assert_eq!(claims.anchored, without.anchored);
}
#[test]
fn estimate_anchor_map_no_laundering() {
let rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("a1", "SL-200", 1.0, 3.0, RaterKind::Agent),
est_anchor("m1", "SL-300", 5.0, 5.0, RaterKind::Migrated),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
let map = claims.anchor_map();
assert_eq!(map.len(), 1);
assert!((map["SL-100"] - 5.9).abs() < 1e-12);
assert!(!map.contains_key("SL-200"));
assert!(!map.contains_key("SL-300"));
}
#[test]
fn estimate_distinct_payloads_same_operative_conflict() {
let rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("h2", "SL-100", 4.0, 6.0, RaterKind::Human),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims = resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.5);
let claim = &claims.anchored["SL-100"];
let conflict = claim.conflict.as_ref().expect("conflict fires");
assert_eq!(conflict.distinct, 2);
assert!((conflict.low - 5.0).abs() < 1e-12);
assert!((conflict.high - 5.0).abs() < 1e-12);
assert!((claim.operative - 5.0).abs() < 1e-12);
}
#[test]
fn estimate_no_compile_consumer_noop() {
let value_row = anchor("v1", "SL-100", 5.0, RaterKind::Human);
let active_rows = vec![(&value_row, ResolutionStatus::Active)];
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
assert!(claims.anchored.is_empty());
assert!(claims.priors.is_empty());
assert!(claims.findings.is_empty());
}
#[test]
fn estimate_duplicate_posture_no_op() {
let rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("h2", "SL-100", 2.0, 8.0, RaterKind::Human),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &0.65);
assert!(claims.anchored["SL-100"].conflict.is_none());
assert_eq!(claims.anchored["SL-100"].rows, 2);
}
#[test]
fn linearity_affine_of_mean_equals_mean_of_affines() {
let skew = 0.65;
let payloads = [
EstimatePayload(2.0, 8.0),
EstimatePayload(1.0, 3.0),
EstimatePayload(4.0, 4.0),
EstimatePayload(0.5, 10.0),
];
let affine_of_mean = EstimatePayload::mean(&payloads).operative(&skew);
let mean_of_affines = payloads.iter().map(|p| p.operative(&skew)).sum::<f64>()
/ f64::from(u32::try_from(payloads.len()).unwrap());
assert!(
(affine_of_mean - mean_of_affines).abs() < 1e-9,
"linearity: affine(mean)={affine_of_mean} vs mean(affines)={mean_of_affines}"
);
}
#[test]
fn linearity_floor_composes_after_aggregation() {
let skew = 0.65;
let rows = [
est_anchor("h1", "SL-100", 2.0, 8.0, RaterKind::Human),
est_anchor("h2", "SL-100", 4.0, 6.0, RaterKind::Human),
];
let active_rows: Vec<(&Judgement, ResolutionStatus)> =
rows.iter().map(|j| (j, ResolutionStatus::Active)).collect();
let claims =
resolve_claims_generic::<EstimatePayload>(&active_rows, DOMAIN_ESTIMATE, &skew);
let claim = &claims.anchored["SL-100"];
assert_eq!(claim.operative, claim.payload.operative(&skew));
assert!((claim.operative - 5.6).abs() < 1e-12);
}
#[test]
fn linearity_sub_epsilon_corner_floors_deterministically() {
let skew = 0.0;
let zero = EstimatePayload(0.0, 0.0);
let once = zero.operative(&skew);
assert!(once > 0.0, "positivity axiom: floored above zero");
assert_eq!(once, zero.operative(&skew), "determinism");
assert_eq!(once, crate::estimate::EPSILON);
let mixed = [EstimatePayload(0.0, 0.0), EstimatePayload(0.0, 0.0)];
assert_eq!(
EstimatePayload::mean(&mixed).operative(&skew),
crate::estimate::EPSILON
);
}
}