feat(t211): return SKU-matched image search candidates

This commit is contained in:
QiuSW
2026-07-27 18:35:59 +08:00
parent 667a64f6c2
commit edfee2c35e
26 changed files with 1838 additions and 153 deletions
@@ -72,12 +72,20 @@ type ExecutionProvenance struct {
}
type CandidateEvaluation struct {
Decision string `json:"decision"`
Score float64 `json:"score"`
Matched []string `json:"matched"`
MissingOrUncertain []string `json:"missing_or_uncertain"`
RejectionReasons []string `json:"rejection_reasons"`
Confidence float64 `json:"confidence"`
Decision string `json:"decision"`
Score float64 `json:"score"`
Matched []string `json:"matched"`
MissingOrUncertain []string `json:"missing_or_uncertain"`
RejectionReasons []string `json:"rejection_reasons"`
Confidence float64 `json:"confidence"`
HardConstraints []CandidateHardConstraintEvaluation `json:"hard_constraints"`
}
type CandidateHardConstraintEvaluation struct {
Kind string `json:"kind"`
Expected string `json:"expected"`
Status string `json:"status"`
Evidence string `json:"evidence"`
}
type ExecutionCandidate struct {
@@ -523,11 +531,23 @@ func validateCandidateCommand(command StoreExecutionCandidatesCommand) error {
} else if command.Provenance != nil {
return executionResultInvalid("provenance", "must be omitted for MANUAL_FIRST")
}
strictSKUMatching := command.ExecutionMode == aiAssistedMode &&
command.Provenance.SchemaVersion >= 2
for index, candidate := range command.Candidates {
if candidate.Ordinal != index+1 || !validCandidate(candidate, command.ExecutionMode) {
if candidate.Ordinal != index+1 ||
!validCandidate(candidate, command.ExecutionMode) ||
(strictSKUMatching && !validSKUMatchedCandidate(candidate)) {
return executionResultInvalid("candidates", "must be continuous, bounded observations")
}
}
if strictSKUMatching && len(command.Candidates) > 0 &&
(command.Recommendation == nil ||
command.Recommendation.CandidateOrdinal != 1) {
return executionResultInvalid(
"recommendation",
"must select the first sorted SKU-matched candidate",
)
}
if command.Recommendation != nil {
recommendation := command.Recommendation
if recommendation.CandidateOrdinal < 1 ||
@@ -607,7 +627,43 @@ func validEvaluation(value *CandidateEvaluation) bool {
}
return validStringList(value.Matched, 12, 160) &&
validStringList(value.MissingOrUncertain, 12, 160) &&
validStringList(value.RejectionReasons, 12, 160)
validStringList(value.RejectionReasons, 12, 160) &&
validCandidateHardConstraints(value.HardConstraints)
}
func validSKUMatchedCandidate(candidate ExecutionCandidate) bool {
value := candidate.Evaluation
return value != nil &&
value.Decision == "REVIEW" &&
value.Score >= 0.75 &&
value.Confidence >= 0.75 &&
len(value.RejectionReasons) == 0 &&
len(value.HardConstraints) == 2
}
func validCandidateHardConstraints(
values []CandidateHardConstraintEvaluation,
) bool {
if len(values) == 0 {
return true
}
if len(values) != 2 {
return false
}
seen := map[string]struct{}{}
for _, value := range values {
if (value.Kind != "COLOR" && value.Kind != "SIZE") ||
value.Status != "MATCH" ||
!validAuditText(value.Expected, 128) ||
!validAuditText(value.Evidence, 160) {
return false
}
if _, duplicate := seen[value.Kind]; duplicate {
return false
}
seen[value.Kind] = struct{}{}
}
return len(seen) == 2
}
func validProvenance(value *ExecutionProvenance) bool {
@@ -0,0 +1,109 @@
package usecase
import (
"strings"
"testing"
)
func TestValidateCandidateCommandAcceptsEmptyMatchedBatch(t *testing.T) {
command := validAIExecutionCandidateCommand()
command.Candidates = nil
command.Recommendation = nil
if err := validateCandidateCommand(command); err != nil {
t.Fatalf("validate empty matched batch: %v", err)
}
}
func TestValidateCandidateCommandAcceptsMatchedColorAndSize(t *testing.T) {
command := validAIExecutionCandidateCommand()
if err := validateCandidateCommand(command); err != nil {
t.Fatalf("validate matched candidate: %v", err)
}
}
func TestValidateCandidateCommandRejectsUnknownHardConstraint(t *testing.T) {
command := validAIExecutionCandidateCommand()
command.Candidates[0].Evaluation.HardConstraints[1].Status = "UNKNOWN"
if err := validateCandidateCommand(command); err == nil {
t.Fatal("expected unknown hard constraint to be rejected")
}
}
func TestValidateCandidateCommandRejectsV2CandidateWithoutHardConstraints(t *testing.T) {
command := validAIExecutionCandidateCommand()
command.Candidates[0].Evaluation.HardConstraints = nil
if err := validateCandidateCommand(command); err == nil {
t.Fatal("expected missing v2 hard constraints to be rejected")
}
}
func TestValidateCandidateCommandRejectsWeakV2Candidate(t *testing.T) {
command := validAIExecutionCandidateCommand()
command.Candidates[0].Evaluation.Score = 0.74
if err := validateCandidateCommand(command); err == nil {
t.Fatal("expected weak v2 candidate to be rejected")
}
}
func TestValidateCandidateCommandRejectsV2RecommendationAfterFirstCandidate(t *testing.T) {
command := validAIExecutionCandidateCommand()
second := command.Candidates[0]
second.Ordinal = 2
second.Title = "拼多多图片候选 2"
command.Candidates = append(command.Candidates, second)
command.Recommendation.CandidateOrdinal = 2
if err := validateCandidateCommand(command); err == nil {
t.Fatal("expected v2 recommendation after first candidate to be rejected")
}
}
func validAIExecutionCandidateCommand() StoreExecutionCandidatesCommand {
return StoreExecutionCandidatesCommand{
TaskContentSHA256: strings.Repeat("a", 64),
ExecutionMode: aiAssistedMode,
SearchQuery: "PDD_IMAGE_SEARCH",
Provenance: &ExecutionProvenance{
ProviderID: "openai-compatible",
Model: "test-model",
PromptVersion: "candidate-evaluation-v2",
SchemaVersion: 2,
},
Candidates: []ExecutionCandidate{
{
Ordinal: 1,
Title: "拼多多图片候选 1",
Evaluation: &CandidateEvaluation{
Decision: "REVIEW",
Score: 0.9,
Matched: []string{"图片相似"},
Confidence: 0.9,
HardConstraints: []CandidateHardConstraintEvaluation{
{
Kind: "COLOR",
Expected: "BLACK",
Status: "MATCH",
Evidence: "截图显示黑色",
},
{
Kind: "SIZE",
Expected: "L",
Status: "MATCH",
Evidence: "截图显示 L 码",
},
},
},
},
},
Recommendation: &CandidateRecommendation{
CandidateOrdinal: 1,
PolicyVersion: "sku-hard-constraints-v1",
Reasons: []string{"SKU 颜色和尺码硬约束均匹配"},
},
}
}