fix(android): retain verified spec selections (#224)

This commit is contained in:
QiuSW
2026-09-05 16:33:24 +08:00
parent cdcb6930d0
commit 095dbfacbf
3 changed files with 39 additions and 30 deletions
@@ -608,7 +608,11 @@ class PurchaseRehearsalExecutor(
return matchingCandidates.size == 1 && matchingCandidates.single() == target
}
private data class ExactSpecSelectionProof(val dimension: String, val target: String)
private data class ExactSpecSelectionProof(
val dimension: String,
val target: String,
val panelType: SpecPanelType,
)
private fun exactSpecSelectionProof(
screen: ParsedPddScreen,
@@ -620,7 +624,7 @@ class PurchaseRehearsalExecutor(
val tokenCandidates = screen.dimensions.filter { it.key == dimension }.flatMap { it.values }.filter { candidate ->
if (dimension == "size") SpecValueNormalizer.primarySizeToken(candidate.text) == token else candidate.text == token
}
return ExactSpecSelectionProof(dimension, target).takeIf {
return ExactSpecSelectionProof(dimension, target, screen.specPanelType).takeIf {
tokenCandidates.size == 1 && tokenCandidates.single().text == target
}
}
@@ -631,6 +635,7 @@ class PurchaseRehearsalExecutor(
val summaryTokenMatched: Boolean,
val candidateCount: Int,
val proofPresent: Boolean,
val targetMatchCount: Int = 0,
val relocationAttempted: Boolean = false,
val relocationSwipes: Int = 0,
)
@@ -643,15 +648,21 @@ class PurchaseRehearsalExecutor(
): FinalSpecVerification {
val candidates = screen.dimensions.filter { it.key == dimension }.flatMap { it.values }
val matchingNodes = candidates.filter { it.text == target }
val proofPresent = proof?.dimension == dimension && proof.target == target
val proofPresent = proof?.dimension == dimension && proof.target == target && proof.panelType == screen.specPanelType
if (matchingNodes.size == 1 && (matchingNodes.single().node.selected || matchingNodes.single().node.checked)) {
return FinalSpecVerification(true, "selected_node", false, candidates.size, proofPresent)
return FinalSpecVerification(true, "selected_node", false, candidates.size, proofPresent, matchingNodes.size)
}
if (candidates.any { it.text != target && (it.node.selected || it.node.checked) }) {
return FinalSpecVerification(false, "visible_selected_conflict", false, candidates.size, proofPresent, matchingNodes.size)
}
val token = if (dimension == "size") SpecValueNormalizer.primarySizeToken(target) else target.trim()
val summaryTokenMatched = !token.isNullOrBlank() && SpecValueNormalizer.summaryHasExactToken(screen.selectedSummary, token)
if (!summaryTokenMatched) {
return FinalSpecVerification(false, "summary_token_missing", false, candidates.size, proofPresent)
if (screen.selectedSummary == null && screen.specPanelOpen && proofPresent) {
return FinalSpecVerification(true, "attempt_selection_state", false, candidates.size, true, matchingNodes.size)
}
return FinalSpecVerification(false, "summary_token_missing", false, candidates.size, proofPresent, matchingNodes.size)
}
// PDD can rerender or scroll the previously selected dimension out of
// the current viewport while the selected summary remains visible. A
@@ -662,15 +673,15 @@ class PurchaseRehearsalExecutor(
if (dimension == "size") SpecValueNormalizer.primarySizeToken(candidate.text) == token else candidate.text == token
}
if (visibleTokenMatches.size == 1 && visibleTokenMatches.single().text == target) {
return FinalSpecVerification(true, "visible_candidate", true, candidates.size, proofPresent)
return FinalSpecVerification(true, "visible_candidate", true, candidates.size, proofPresent, matchingNodes.size)
}
if (visibleTokenMatches.isNotEmpty()) {
return FinalSpecVerification(false, "visible_candidate_conflict", true, candidates.size, proofPresent)
return FinalSpecVerification(false, "visible_candidate_conflict", true, candidates.size, proofPresent, matchingNodes.size)
}
return if (proofPresent) {
FinalSpecVerification(true, "attempt_selection_proof", true, candidates.size, true)
FinalSpecVerification(true, "attempt_selection_proof", true, candidates.size, true, matchingNodes.size)
} else {
FinalSpecVerification(false, "selection_proof_missing", true, candidates.size, false)
FinalSpecVerification(false, "selection_proof_missing", true, candidates.size, false, matchingNodes.size)
}
}
@@ -685,7 +696,7 @@ class PurchaseRehearsalExecutor(
return FinalSpecVerification(false, it.code, false, 0, proof != null)
}
var verification = verifyExactSpecSelection(screen, dimension, target, proof)
if (verification.confirmed || verification.candidateCount > 0) return verification
if (verification.confirmed || verification.targetMatchCount > 0 || verification.reason == "visible_selected_conflict") return verification
if (!screen.specPanelOpen) return verification.copy(reason = "relocation_panel_closed", relocationAttempted = true)
var signature = screen.dimensions.joinToString("|") { item ->
@@ -728,7 +739,7 @@ class PurchaseRehearsalExecutor(
relocationAttempted = true,
relocationSwipes = swipes,
)
if (verification.confirmed || verification.candidateCount > 0) return verification
if (verification.confirmed || verification.targetMatchCount > 0 || verification.reason == "visible_selected_conflict") return verification
val refreshedSignature = screen.dimensions.joinToString("|") { item ->
"${item.key}:${item.values.joinToString(",") { value -> "${value.text}:${value.available}" }}"
}
@@ -837,7 +848,8 @@ class PurchaseRehearsalExecutor(
val panel = screen.specPanelType.name.lowercase()
val diagnostic = "dimension=$dimension,reason=${verification.reason},panel=$panel," +
"summary=${screen.selectedSummary != null},tokenMatched=${verification.summaryTokenMatched}," +
"candidates=${verification.candidateCount},proof=${verification.proofPresent}," +
"candidates=${verification.candidateCount},targetMatches=${verification.targetMatchCount}," +
"proof=${verification.proofPresent}," +
"relocated=${verification.relocationAttempted},relocationSwipes=${verification.relocationSwipes}"
return failure(SPEC_SELECTION_UNCONFIRMED, "最终规格复核未能确认精确选中状态 [$diagnostic]")
}