Skip to content

Commit 310177e

Browse files
committed
fix(category): name a heuristic category only at blocking confidence
e19299e made the resolver's heuristic mappings reachable by reading the raw signals. A refuting review showed the cost: aggressive mode blocks a lone 50-point neighbor-spoof match, which also fires on local banks and schools, and with Scam set to Allow that block became category_policy:scam:allow and the call rang. The resolver's own trust rule reserves a category for strong evidence, so the heuristic branch now requires the score the default setting blocks at (60). Confident blocks keep their category; the weak one stays a plain heuristic block. Tests: CallCategoryResolverTest adds the aggressive-mode case (50 is Unknown, 60 is Scam). Existing heuristic cases use 65 and 70 and are unchanged.
1 parent b5c266b commit 310177e

3 files changed

Lines changed: 25 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ All notable changes to CallShield will be documented in this file.
4343
campaign range as a robocall, and a toll-free one as a telemarketer, so
4444
the action you picked for that category applies. Those mappings existed
4545
but never matched, because they looked for internal names the block
46-
description had already turned into plain words.
46+
description had already turned into plain words. They only apply to a
47+
block as confident as the default setting requires, so a lone
48+
neighbor-spoof match that only aggressive mode blocks stays uncategorized
49+
and no category rule can let it through.
4750

4851
- CallShield starts faster when a call wakes it. Looking for an interrupted
4952
backup restore held the main thread about 0.7 seconds on every start

‎app/src/main/java/com/sysadmindoc/callshield/data/CallCategory.kt‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ enum class CallCategory(
3939
}
4040

4141
object CallCategoryResolver {
42+
/** The heuristic score the default setting blocks at; below it a signal is too weak to name a category. */
43+
private const val HEURISTIC_CATEGORY_MIN_CONFIDENCE = 60
44+
4245
/** Convenience overload for callers holding a persisted `BlockedCall`
4346
* (which doesn't carry a SpamCheckResult type field but does carry
4447
* matchReason, description, and confidence). */
@@ -117,8 +120,11 @@ object CallCategoryResolver {
117120
// Heuristic reasons: map the strongest signals. A live result carries
118121
// the raw tokens in `signals`; its description is display text in the
119122
// app language (and always had the underscores replaced), so it only
120-
// helps callers that still pass tokens there.
121-
if (result.reasonCode == BlockReasonCode.HEURISTIC) {
123+
// helps callers that still pass tokens there. Only at the confidence
124+
// the default setting blocks at: aggressive mode blocks on a single
125+
// 50-point neighbor-spoof match, which also fires on local banks and
126+
// schools, and naming that Scam would let a Scam rule decide it.
127+
if (result.reasonCode == BlockReasonCode.HEURISTIC && result.confidence >= HEURISTIC_CATEGORY_MIN_CONFIDENCE) {
122128
val evidence = (result.signals + desc).joinToString(" ")
123129
when {
124130
"wangiri" in evidence -> return CallCategory.Wangiri

‎app/src/test/java/com/sysadmindoc/callshield/data/CallCategoryResolverTest.kt‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,19 @@ class CallCategoryResolverTest {
100100
assertEquals(CallCategory.Unknown, CallCategoryResolver.resolve(live("suspicious", "号码格式异常", "invalid_format")))
101101
}
102102

103+
@Test
104+
fun `a weak heuristic block names no category, so no category rule can wave it through`() {
105+
// Aggressive mode blocks a lone neighbor-spoof match at 50. With Scam
106+
// set to Allow, naming it Scam let the call ring.
107+
val weak =
108+
BlockResult
109+
.block("heuristic", type = "spoofed", description = "Possible neighbor spoofing", confidence = 50, signals = listOf("neighbor_spoof"))
110+
.toSpamCheckResult()
111+
112+
assertEquals(CallCategory.Unknown, CallCategoryResolver.resolve(weak))
113+
assertEquals(CallCategory.Scam, CallCategoryResolver.resolve(weak.copy(confidence = 60)))
114+
}
115+
103116
@Test
104117
fun `heuristic with rapid_fire resolves to Robocall`() {
105118
val result =

0 commit comments

Comments
 (0)