fix(logs): §C+E · purge dead code 활성화 + record() 통합 관문 (cloud-mvp d8425d0 이식)

This commit is contained in:
2026-08-04 16:18:07 +09:00
parent 4d8ada0634
commit 253312b4b0
4 changed files with 95 additions and 0 deletions
@@ -63,6 +63,18 @@ class MainActivity : ComponentActivity() {
com.medithings.vesiscan.telemetry.AlarmAggregator.start()
com.medithings.vesiscan.telemetry.NotificationSubscriber.start(this)
// 2026-08-04: LOG_STORAGE_ANALYSIS §C fix — 앱 시작 시 오래된 로그 자동 정리.
// 실패해도 필수기능 영향 없음 (silent · UC-05).
try {
val app = applicationContext
com.medithings.vesiscan.services.MeasurementLogService.getInstance(app)
.purgeOldLogs(keepDays = 365)
com.medithings.vesiscan.services.BleDebugLogger.getInstance(app)
.purgeOldLogs(keepDays = 7)
com.medithings.vesiscan.services.AdcCsvLogger.purgeOldSessions(keepDays = 30)
com.medithings.vesiscan.services.ClinicalSessionStore.purgeOldSessions(keepDays = 90)
} catch (_: Exception) { }
enableEdgeToEdge()
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
setContent {
@@ -121,4 +121,21 @@ object AdcCsvLogger {
}
} catch (_: Exception) {}
}
/**
* VesiScan_ADC/ 폴더의 keepDays 일 이전 CSV 삭제 (§C purge).
* 2026-08-04: LOG_STORAGE_ANALYSIS Priority 3 — 시간당 40MB 누적 방지.
*/
fun purgeOldSessions(keepDays: Int = 30) {
try {
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_YEAR, -keepDays)
val cutoff = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(calendar.time)
val dir = getDir()
dir.listFiles { f -> f.extension == "csv" }?.forEach { file ->
val prefix = file.nameWithoutExtension.take(10)
if (prefix < cutoff) file.delete()
}
} catch (_: Exception) { }
}
}
@@ -384,4 +384,23 @@ object ClinicalSessionStore {
lastFinalizedFolder = null
return dir
}
/**
* VesiScan_Sessions/ 폴더의 keepDays 일 이전 세션 삭제 (§C purge).
* 2026-08-04: LOG_STORAGE_ANALYSIS Priority 3 — 시간당 최대 40MB 누적 방지.
* lastModified 기준. currentSession 진행 중 폴더 제외.
*/
fun purgeOldSessions(keepDays: Int = 90) {
runCatching {
val cutoffMs = System.currentTimeMillis() - keepDays * 24L * 3600L * 1000L
val activeFolder = currentLogDir()
val downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val root = File(downloads, "VesiScan_Sessions")
if (!root.exists() || !root.isDirectory) return@runCatching
root.listFiles { f -> f.isDirectory }?.forEach { folder ->
if (folder == activeFolder) return@forEach
if (folder.lastModified() < cutoffMs) folder.deleteRecursively()
}
}
}
}
@@ -53,6 +53,38 @@ class MeasurementLogService private constructor(private val context: Context) {
// -- Log Events --
/**
* 통합 관문 (2026-08-04 · LOG_STORAGE_ANALYSIS §E fix).
* 호출부는 이 함수 하나만 호출하면 4개 sink 자동 fan-out.
* 기존 개별 함수는 그대로 유지 (backwards compat).
*/
fun record(outcome: MeasurementOutcome): Int {
val scanId = outcome.scanId
?: com.medithings.vesiscan.services.AdcCsvLogger.nextScanId()
runCatching {
com.medithings.vesiscan.ble.BleManager.current?.debugLogger?.measurementResult(
volumeMl = outcome.volumeMl,
lrRatio = outcome.lrRatio,
validChannels = outcome.validChannels,
urineLines = outcome.urineLines,
otsuThreshold = outcome.otsuThreshold,
scanId = scanId,
)
}
runCatching {
logMeasurement(outcome.volumeMl, outcome.channels, outcome.rawADC, scanId = scanId)
}
runCatching {
com.medithings.vesiscan.services.AdcCsvLogger.log(
outcome.rawADC,
outcome.volumeMl,
outcome.lrRatio,
scanId = scanId,
)
}
return scanId
}
/**
* Log a spot measurement result.
*/
@@ -247,3 +279,18 @@ data class ChannelLogEntry(
val urineLen: Int? = null,
val score: Double? = null
)
/**
* 단일 측정 사이클의 모든 sink 에 fan-out 될 결과 (2026-08-04 · §E 통합 관문 입력).
* scanId = null 이면 record() 가 자동 발급.
*/
data class MeasurementOutcome(
val volumeMl: Double?,
val lrRatio: Double = 1.0,
val rawADC: List<List<UShort>>,
val channels: List<ChannelLogEntry>,
val validChannels: Int = 0,
val urineLines: List<Int> = emptyList(),
val otsuThreshold: Double = 0.0,
val scanId: Int? = null,
)