feat: 6ch 필터 + 도넛 확대 + Fill/Level 기준 변경 + 슬라이딩 윈도우

BV 계산:
- 6채널 모두 수신(buffer>=20) 시에만 BV 계산 (불완전 데이터 제외)

도넛 차트:
- 크기: compact 180→220dp, normal 240→280dp
- 선 두께: 20→22, 28→30dp
- 캐릭터: 70→80, 100→110dp
- 볼륨 텍스트: 16→18sp, maxVolumeMl → lastVolumeMl 표시

Info Cards:
- Fill: appState.fillPercentage → lastVolumeMl/maxVolume×100 (실측 기준)
- Level: appState.bladderLevel → lastVolumeMl/maxVolume×MAX_LEVEL
- 미측정 시 "—" 표시

Auto 측정:
- 배치 윈도우 → 슬라이딩 윈도우 (최대 10개 유지, 오래된 것 제거)
- 5개 이상이면 매 측정마다 trimmed mean 업데이트
- 이전: 10개 모이면 한 번에 업데이트 → clear
- 변경: [1,2,3,4,5]→출력, [2,3,4,5,6]→출력 (실시간 반영)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 12:13:10 +09:00
parent 802b9b1822
commit f3d3a9ce54
@@ -151,7 +151,8 @@ fun PiezoMonitoringView(appState: AppState) {
var totalWallCount = 0
val validChannels = channels.filter { it.isValid }
if (validChannels.isNotEmpty()) {
val allChannelsReceived = channels.filter { it.buffer.size >= 20 }.size >= 6
if (validChannels.isNotEmpty() && allChannelsReceived) {
val method = com.example.medilightv2android.managers.GreenZoneConstants.detectionMethod
val analyzerA = com.example.medilightv2android.managers.PiezoEchoAnalyzerA.shared
@@ -287,18 +288,20 @@ fun PiezoMonitoringView(appState: AppState) {
handler.post {
if (finalRawVol != null && finalRawVol > 0) {
if (isAutoMeasuring) {
if (finalTotalWalls >= 4) {
if (allChannelsReceived) {
autoVolumeWindow.add(finalRawVol)
Log.d("PiezoMonitor", "AUTO window[${autoVolumeWindow.size}/$autoWindowSize] +${"%.1f".format(finalRawVol)}ml (valid=$finalTotalWalls)")
// 슬라이딩 윈도우: 오래된 값 제거
while (autoVolumeWindow.size > autoWindowSize) autoVolumeWindow.removeAt(0)
Log.d("PiezoMonitor", "AUTO window[${autoVolumeWindow.size}/$autoWindowSize] +${"%.1f".format(finalRawVol)}ml")
} else {
Log.d("PiezoMonitor", "AUTO skip ${"%.1f".format(finalRawVol)}ml (valid=$finalTotalWalls < 4)")
Log.d("PiezoMonitor", "AUTO skip ${"%.1f".format(finalRawVol)}ml (channels incomplete)")
}
if (autoVolumeWindow.size >= autoWindowSize) {
// 윈도우에 5개 이상이면 매번 trimmed mean 업데이트
if (autoVolumeWindow.size >= 5) {
val displayVol = trimmedMean(autoVolumeWindow.toList())
lastVolumeMl = displayVol
maxVolumeMl = maxOf(maxVolumeMl, displayVol)
Log.d("PiezoMonitor", "AUTO trimmedMean=${"%.1f".format(displayVol)}ml from ${autoVolumeWindow.map { "%.0f".format(it) }}")
autoVolumeWindow.clear()
}
} else {
lastVolumeMl = finalRawVol
@@ -753,20 +756,20 @@ fun PiezoMonitoringView(appState: AppState) {
)
// Donut chart with Bladdy
val donutSize = if (isCompact) 180.dp else 240.dp
val donutSize = if (isCompact) 220.dp else 280.dp
Box(contentAlignment = Alignment.Center) {
DonutChartView(
progress = appState.bladderLevel.progress,
urgency = urgency,
lineWidth = if (isCompact) 20.dp else 28.dp,
lineWidth = if (isCompact) 22.dp else 30.dp,
size = donutSize
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
BladdyRiveView(urgency = urgency, size = if (isCompact) 70.dp else 100.dp)
BladdyRiveView(urgency = urgency, size = if (isCompact) 80.dp else 110.dp)
Spacer(modifier = Modifier.height(4.dp))
Text(
if (maxVolumeMl > 0) "${"%.0f".format(maxVolumeMl)} ml" else "${appState.estimatedVolume} ml",
fontSize = 16.sp,
if (lastVolumeMl > 0) "${"%.0f".format(lastVolumeMl)} ml" else "${appState.estimatedVolume} ml",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = urgency.accentColor,
modifier = Modifier
@@ -778,12 +781,15 @@ fun PiezoMonitoringView(appState: AppState) {
}
}
// Info cards
// Info cards (Fill/Level은 maxVolume 기준)
val maxVol = appState.piezoSettings.maxVolume.toDouble()
val fillPct = if (maxVol > 0 && lastVolumeMl > 0) (lastVolumeMl / maxVol * 100).coerceAtMost(100.0) else 0.0
val levelVal = if (maxVol > 0 && lastVolumeMl > 0) ((lastVolumeMl / maxVol) * BladderLevel.MAX_LEVEL).toInt().coerceIn(0, BladderLevel.MAX_LEVEL) else 0
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
val displayVol = if (lastVolumeMl > 0) "%.0f".format(lastVolumeMl) else "${appState.estimatedVolume}"
val displayVol = if (lastVolumeMl > 0) "%.0f".format(lastVolumeMl) else "—"
InfoCard("Volume", displayVol, "ml", urgency.accentColor, Modifier.weight(1f))
InfoCard("Fill", String.format("%.0f", appState.fillPercentage), "%", urgency.accentColor, Modifier.weight(1f))
InfoCard("Level", "${appState.bladderLevel.currentLevel}/${BladderLevel.MAX_LEVEL}", "", urgency.accentColor, Modifier.weight(1f))
InfoCard("Fill", if (fillPct > 0) "%.0f".format(fillPct) else "—", "%", urgency.accentColor, Modifier.weight(1f))
InfoCard("Level", if (levelVal > 0) "$levelVal/${BladderLevel.MAX_LEVEL}" else "—", "", urgency.accentColor, Modifier.weight(1f))
}
// Control buttons: Void/Catheterize / Auto / Spot