mirror of
https://github.com/andwati/ledga.git
synced 2026-10-18 03:12:55 +00:00
v1.3.6: auto-dismiss stale Fuliza insight + refresh after reparse
The Fuliza outstanding insight is stateful — it should disappear the moment the underlying outstanding hits 0. Today it sticks until manually dismissed, even after data updates. InsightEngine now tracks a STATEFUL_KEYS set (just "fuliza:outstanding" for now). If a rule that owns one of those keys returns no candidates in this run, the row is deleted outright rather than being left around as a tombstone. SettingsViewModel.reparseAll() now also calls insightsRepository .generateAll() once the row update completes. So the moment the user finishes "Re-parse ALL transactions", any stale Fuliza alert reading the limit-as-outstanding goes away, and any newly correct alerts show up immediately.
This commit is contained in:
@@ -27,8 +27,8 @@ android {
|
||||
applicationId = "com.ledga.app"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode = 8
|
||||
versionName = "1.3.5"
|
||||
versionCode = 9
|
||||
versionName = "1.3.6"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -88,6 +88,9 @@ interface InsightDao {
|
||||
@Query("DELETE FROM insights WHERE id = :id")
|
||||
suspend fun deleteById(id: Long)
|
||||
|
||||
@Query("DELETE FROM insights WHERE naturalKey = :key")
|
||||
suspend fun deleteByKey(key: String)
|
||||
|
||||
@Query("DELETE FROM insights WHERE generatedAt < :before AND dismissedAt IS NOT NULL")
|
||||
suspend fun pruneOldDismissed(before: Long)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,13 @@ class InsightEngine @Inject constructor(
|
||||
}
|
||||
|
||||
candidates.forEach { upsert(it, now) }
|
||||
// Stateful keys: if a rule that "owns" a singleton key didn't emit
|
||||
// this run, the underlying condition is gone — delete the stale row
|
||||
// outright (vs leaving it to expire 30 days post-dismiss).
|
||||
val emittedKeys = candidates.map { it.naturalKey }.toSet()
|
||||
STATEFUL_KEYS.forEach { key ->
|
||||
if (key !in emittedKeys) insightDao.deleteByKey(key)
|
||||
}
|
||||
insightDao.pruneOldDismissed(before = now - 30L * DAY_MS)
|
||||
return candidates.size
|
||||
}
|
||||
@@ -85,5 +92,19 @@ class InsightEngine @Inject constructor(
|
||||
|
||||
companion object {
|
||||
private const val DAY_MS = 86_400_000L
|
||||
|
||||
/**
|
||||
* Natural keys whose underlying condition is stateful — only true when
|
||||
* a current event matches. If a rule using one of these keys returns
|
||||
* no candidates this run, the existing insight is stale and should
|
||||
* be deleted, not just re-emitted.
|
||||
*
|
||||
* Per-period keys (anomaly:1:2026-W12, nudge:2:2026-04, fees:2026-04)
|
||||
* are NOT stateful in this sense — they refer to a fixed past period
|
||||
* and should persist in history.
|
||||
*/
|
||||
private val STATEFUL_KEYS = setOf(
|
||||
"fuliza:outstanding",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.ledga.app.data.repository.ExportResult
|
||||
import com.ledga.app.data.repository.FontScale
|
||||
import com.ledga.app.data.repository.ImportFromFileResult
|
||||
import com.ledga.app.data.repository.ImportResult
|
||||
import com.ledga.app.data.repository.InsightsRepository
|
||||
import com.ledga.app.data.repository.ReparseResult
|
||||
import com.ledga.app.data.repository.SettingsRepository
|
||||
import com.ledga.app.data.repository.SmsImporter
|
||||
@@ -31,7 +32,8 @@ class SettingsViewModel @Inject constructor(
|
||||
private val transactionRepository: TransactionRepository,
|
||||
private val smsImporter: SmsImporter,
|
||||
private val exportImportRepository: ExportImportRepository,
|
||||
private val backupRepository: BackupRepository
|
||||
private val backupRepository: BackupRepository,
|
||||
private val insightsRepository: InsightsRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
val themeMode: StateFlow<ThemeMode> = settingsRepository.getThemeMode()
|
||||
@@ -123,6 +125,11 @@ class SettingsViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
_reparseAllResult.value = transactionRepository.reparseAllTransactions()
|
||||
// Refresh insights immediately so the Home teaser + Insights
|
||||
// screen reflect the corrected data. Stateful insights (like
|
||||
// Fuliza outstanding) get auto-dismissed by the engine when
|
||||
// their condition is no longer true.
|
||||
insightsRepository.generateAll()
|
||||
} finally {
|
||||
_reparseAllRunning.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user