@@ -206,6 +206,10 @@ impossible_speed=${analysis.impossibleSpeed}
206206cell_trust=${analysis.cellTrustScore} %
207207sig_spike=${analysis.signalSpikeDetected}
208208down+spike=${analysis.downgradeWithSignalSpike}
209+ down+new_tower=${analysis.downgradeWithNewTower}
210+ lac_tac_changed=${analysis.lacTacChanged}
211+ operator_changed=${analysis.operatorChanged}
212+ roaming=${analysis.isRoaming}
209213fp_pct=${String .format(" %.0f" , analysis.falsePositiveLikelihood)} %
210214normal_handoff=${analysis.isLikelyNormalHandoff}
2112155g_beam=${analysis.isLikely5gBeamSteering}
@@ -388,6 +392,49 @@ ${detection.signalStrength.displayName}"""
388392 return wrapGemmaPrompt(content)
389393 }
390394
395+ // ==================== COMPACT BLE PROMPT ====================
396+
397+ /* *
398+ * Compact BLE analysis prompt - ~350 tokens
399+ */
400+ fun compactBlePrompt (
401+ detection : Detection ,
402+ analysis : BleAnalysisData
403+ ): String {
404+ val trackerInfo = if (analysis.isTrackerDevice) {
405+ " follow=${analysis.isFollowingUser} ,locs=${analysis.distinctLocationCount} ,dur=${analysis.durationMinutes} min,susp=${analysis.suspicionScore} /100,possess=${analysis.isPossessionSignal} "
406+ } else " "
407+
408+ val spamInfo = if (analysis.isBleSpam) {
409+ " spam=${analysis.spamType} ,events=${analysis.spamEventsCount} ,rate=${String .format(" %.1f" , analysis.spamEventsPerSecond)} /s"
410+ } else " "
411+
412+ val content = """ $ABBREVIATION_LEGEND
413+
414+ BLE dev analysis. Respond JSON:
415+ {"is_threat":true/false,"dev_type":"${analysis.deviceCategory.displayName} ","conf":0-100,"fp_pct":0-100,"explain":"1-2 sentences","action":"what to do"}
416+
417+ BLE_DATA:
418+ name=${sanitize(analysis.deviceName) ? : " unknown" }
419+ mac=${sanitize(analysis.macAddress)}
420+ cat=${analysis.deviceCategory.displayName}
421+ sig=${analysis.rssi} dBm
422+ adv_rate=${String .format(" %.1f" , analysis.advertisingRate)} pps
423+ connect=${analysis.isConnectable}
424+ ${analysis.estimatedDistanceMeters?.let { " dist=${String .format(" %.1f" , it)} m" } ? : " " }
425+ ${if (trackerInfo.isNotEmpty()) " TRACKER:$trackerInfo " else " " }
426+ ${if (spamInfo.isNotEmpty()) " SPAM:$spamInfo " else " " }
427+ fp_pct=${String .format(" %.0f" , analysis.falsePositiveLikelihood)} %
428+ consumer=${analysis.isLikelyConsumerDevice}
429+ passing=${analysis.isPassingBy}
430+
431+ RULES:fp_pct>50=likely benign BLE dev
432+
433+ JSON:"""
434+
435+ return wrapGemmaPrompt(content)
436+ }
437+
391438 // ==================== HELPER FUNCTIONS ====================
392439
393440 /* *
@@ -414,6 +461,19 @@ ${detection.signalStrength.displayName}"""
414461 is EnrichedDetectorData .Satellite -> {
415462 " SAT:type=${data.detectorType} ,${data.riskIndicators.take(2 ).joinToString(" ," )} "
416463 }
464+ is EnrichedDetectorData .Ble -> {
465+ val a = data.analysis
466+ " BLE:cat=${a.deviceCategory.displayName} ,sig=${a.rssi} dBm,adv=${String .format(" %.1f" , a.advertisingRate)} pps" +
467+ (if (a.isTrackerDevice) " ,follow=${a.isFollowingUser} ,susp=${a.suspicionScore} " else " " ) +
468+ (if (a.isBleSpam) " ,spam=${a.spamType} " else " " ) +
469+ " ,fp=${String .format(" %.0f" , a.falsePositiveLikelihood)} %"
470+ }
471+ is EnrichedDetectorData .WifiSsidMatch -> {
472+ " WIFI_SSID:ssid=${data.ssid.ifEmpty { " (hidden)" }} ,ch=${data.channel} ,sig=${data.rssiDbm} dBm,band=${data.frequencyBand} ,oui=${data.ouiVendor ? : " unk" } ,aps=${data.nearbyApCount} ,method=${data.detectionMethodName} "
473+ }
474+ is EnrichedDetectorData .RfEnvironment -> {
475+ " RF:type=${data.anomalyType} ,score=${data.rfThreatScore} ,nets=${data.totalNetworks} ,hidden=${data.hiddenNetworkCount} ,fp=${String .format(" %.0f" , data.falsePositiveLikelihood)} %"
476+ }
417477 }
418478 }
419479
@@ -429,6 +489,8 @@ ${detection.signalStrength.displayName}"""
429489 is EnrichedDetectorData .Gnss -> return enrichedData.analysis.falsePositiveLikelihood.toInt()
430490 is EnrichedDetectorData .Ultrasonic -> return enrichedData.analysis.falsePositiveLikelihood.toInt()
431491 is EnrichedDetectorData .WifiFollowing -> return enrichedData.analysis.falsePositiveLikelihood.toInt()
492+ is EnrichedDetectorData .Ble -> return enrichedData.analysis.falsePositiveLikelihood.toInt()
493+ is EnrichedDetectorData .RfEnvironment -> return enrichedData.falsePositiveLikelihood.toInt()
432494 else -> {}
433495 }
434496
0 commit comments