fix(t212): preserve ranked candidate identity

This commit is contained in:
QiuSW
2026-07-27 22:31:17 +08:00
parent ca60cc1edc
commit 4b795a4224
7 changed files with 382 additions and 115 deletions
@@ -0,0 +1,87 @@
package com.roubao.autopilot.procurement
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertThrows
import org.junit.Test
class ExecutionCandidateIdentityPolicyTest {
@Test
fun `recommended candidate uses ranked first instead of source ordinal`() {
val bindings = ExecutionCandidateIdentityPolicy.bind(
identities = listOf(
identity(sourceOrdinal = 3, rankedOrdinal = 1),
identity(sourceOrdinal = 1, rankedOrdinal = 2),
identity(sourceOrdinal = 2, rankedOrdinal = 3)
),
candidates = listOf(candidate(1), candidate(2), candidate(3))
)
val recommended = ExecutionCandidateIdentityPolicy.recommended(bindings)
assertEquals(1, recommended?.ordinal)
assertEquals(3, bindings.first().identity.sourceOrdinal)
assertEquals("evidence-1", recommended?.evidenceLocalIDs?.single())
}
@Test
fun `filtered source ordinals keep continuous ranked identities`() {
val bindings = ExecutionCandidateIdentityPolicy.bind(
identities = listOf(
identity(sourceOrdinal = 2, rankedOrdinal = 1),
identity(sourceOrdinal = 5, rankedOrdinal = 2)
),
candidates = listOf(candidate(1), candidate(2))
)
assertEquals(
listOf(2, 5),
bindings.map { it.identity.sourceOrdinal }
)
assertEquals(
listOf(1, 2),
bindings.map { it.candidate.ordinal }
)
}
@Test
fun `empty candidates have no recommendation`() {
assertNull(
ExecutionCandidateIdentityPolicy.recommended(
ExecutionCandidateIdentityPolicy.bind(
identities = emptyList(),
candidates = emptyList()
)
)
)
}
@Test
fun `missing ranked ordinal is rejected instead of falling back`() {
assertThrows(IllegalArgumentException::class.java) {
ExecutionCandidateIdentityPolicy.bind(
identities = listOf(
identity(sourceOrdinal = 3, rankedOrdinal = 1)
),
candidates = listOf(candidate(2))
)
}
}
private fun identity(
sourceOrdinal: Int,
rankedOrdinal: Int
): ExecutionCandidateSourceIdentity =
ExecutionCandidateSourceIdentity(
sourceOrdinal = sourceOrdinal,
rankedOrdinal = rankedOrdinal,
evidenceSha256 = sourceOrdinal.toString(16).padStart(64, '0')
)
private fun candidate(ordinal: Int): ExecutionCandidateDraft =
ExecutionCandidateDraft(
ordinal = ordinal,
title = "candidate-$ordinal",
evidenceLocalIDs = listOf("evidence-$ordinal")
)
}
@@ -6,7 +6,7 @@ import org.junit.Test
class CandidateTopFivePolicyTest {
@Test
fun `sorts eligible matches and limits result to five`() {
val selected = CandidateTopFivePolicy.select(
val ranked = CandidateTopFivePolicy.rank(
batch(
assessment(1, score = 0.80, confidence = 0.90),
assessment(2, score = 0.90, confidence = 0.80),
@@ -24,7 +24,18 @@ class CandidateTopFivePolicyTest {
)
)
assertEquals(listOf(3, 2, 5, 6, 7), selected.map { it.ordinal })
assertEquals(
listOf(3, 2, 5, 6, 7),
ranked.map { it.sourceOrdinal }
)
assertEquals(
listOf(1, 2, 3, 4, 5),
ranked.map { it.rankedOrdinal }
)
assertEquals(
ranked.map { it.assessment.evidenceSha256 },
ranked.map { it.evidenceSha256 }
)
}
@Test
@@ -45,6 +56,24 @@ class CandidateTopFivePolicyTest {
assertEquals(listOf(1), selected.map { it.ordinal })
}
@Test
fun `original third candidate becomes stable first ranked candidate`() {
val ranked = CandidateTopFivePolicy.rank(
batch(
assessment(1, score = 0.80, confidence = 0.90),
assessment(2, score = 0.85, confidence = 0.90),
assessment(3, score = 0.95, confidence = 0.95)
)
)
assertEquals(3, ranked.first().sourceOrdinal)
assertEquals(1, ranked.first().rankedOrdinal)
assertEquals(
3.toString(16).padStart(64, '0'),
ranked.first().evidenceSha256
)
}
private fun batch(vararg assessments: CandidateAssessment): CandidateReviewBatch =
CandidateReviewBatch(
assessments = assessments.toList(),