Bugfix/logical-flaw sweep: counting, races, baseline, pruning, RSSI
Root cause of the non-monotonic sighting count (60->29->63): MAC-rotating ecosystems mint a new tracker row per rotation, tracker rows were never pruned, and the UI re-sorts constantly, so different rows' counts read as one sequence. Compounded by no ingestion throttle and a lost-update race. Fixes: - ScanService: throttle the persisted path to <=1 sighting/device/15s (presence engine still sees every advert). Stops count inflation + DB hammering. @Volatile clone-alert ts; clear throttle map on stop. - TrackerRepository: Mutex around record() (was a lost-update race: concurrent observations did get()->compute->upsert() with no lock). Always compute the co-movement assessment for display. - VigilDatabase: prune stale non-approved tracker rows (were never deleted); +lastRssi/peakRssi/distinctPlaces/effectiveSightings for grounding; version 2. - BaselineManager: count DWELL (one visit per 10-min window), not per-sighting. Old per-sighting count let a busy street or chatty tracker mint a fake 'home' in seconds and auto-trust a real stalker — a safety flaw. - BleTrackerScanner: Apple filter now matches only Find My type 0x12, not every Apple device (iPhones/Watches/AirPods). - UI: show distinct places + last RSSI on cards; detail sheet shows last/peak RSSI, distinct places, co-movement sightings, adverts logged; Active list filters to devices seen in the last 10 min so rotated identities drop off. - Tests: CoMovementEvaluatorTest locks dedup, the RSSI gate, and the place/separated thresholds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0187UiEtasyowhEBYs6s9iF5
This commit is contained in:
@@ -18,23 +18,29 @@ import org.soulstone.vigil.data.db.PlaceEntity
|
||||
object BaselineManager {
|
||||
|
||||
const val DAY_MS = 86_400_000L
|
||||
const val ANCHOR_MIN_VISITS = 60
|
||||
const val DWELL_BUCKET_MS = 10 * 60_000L // count at most one "visit" per 10-min dwell window
|
||||
const val ANCHOR_MIN_VISITS = 18 // ~3h cumulative dwell before a cell counts as home/work
|
||||
const val BASELINE_MIN_DAYS = 3
|
||||
|
||||
/** Record a location fix in its cell; returns whether that cell is now an anchor. */
|
||||
/**
|
||||
* Note presence in a cell; returns whether it's an anchor. Crucially a cell
|
||||
* accrues at most ONE visit per [DWELL_BUCKET_MS], so the anchor signal tracks
|
||||
* time actually spent there — not how many trackers or adverts were seen.
|
||||
*
|
||||
* The previous per-sighting count was a real safety flaw: a busy street or a
|
||||
* single chatty tracker could mint a fake "home" in seconds, which would then
|
||||
* baseline-trust (silence alerts for) any tracker seen there — including a real
|
||||
* stalking device.
|
||||
*/
|
||||
suspend fun noteLocation(placeDao: PlaceDao, geohash6: String, now: Long): Boolean {
|
||||
val existing = placeDao.get(geohash6)
|
||||
val visits = (existing?.visitCount ?: 0) + 1
|
||||
val existing = placeDao.get(geohash6) ?: run {
|
||||
placeDao.upsert(PlaceEntity(geohash6 = geohash6, visitCount = 1, lastSeen = now, anchor = false))
|
||||
return false
|
||||
}
|
||||
if (now - existing.lastSeen < DWELL_BUCKET_MS) return existing.anchor
|
||||
val visits = existing.visitCount + 1
|
||||
val anchor = visits >= ANCHOR_MIN_VISITS
|
||||
placeDao.upsert(
|
||||
PlaceEntity(
|
||||
geohash6 = geohash6,
|
||||
label = existing?.label ?: "",
|
||||
visitCount = visits,
|
||||
lastSeen = now,
|
||||
anchor = anchor
|
||||
)
|
||||
)
|
||||
placeDao.upsert(existing.copy(visitCount = visits, lastSeen = now, anchor = anchor))
|
||||
return anchor
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user