Files
VesiscanClinicalAndroid/docs/ALGO_BRANCH_COMPARISON.md
T
dw.jang a89936c862 docs(algo): ALGO_BRANCH_COMPARISON §3-4 METHOD_D vs METHOD_D_PHANTOM 심층 비교
기존 §3 BV Estimation 은 브랜치 비교 관점만 · 두 method 자체를 나란히 놓고
상세 대조하는 섹션 없어서 §3-4 로 추가 (사용자 요청).

포함 (7 sub-section):
  3-4-1. 부피 공식 (Frustum+Cap vs 4/3πr³)
  3-4-2. 파이프라인 stage 비교 표 (11 stage · METHOD_D 만 있는 것 명시)
  3-4-3. 결과 특성 표 (정확도 · 계산 비용 · 재현성 · phantom vs 인체)
  3-4-4. 예시 계산 (phantom 300mL · 지름 83mm · 두 method 각각 산수)
  3-4-5. 언제 어느 method 써야 하나 (권장 매트릭스)
  3-4-6. 코드 진입점 요약 (dispatcher)
  3-4-7. 향후 개선 여지 (하이브리드 · ellipsoid 확장 등)

핵심 근거:
  · phantom 300mL 실측 → METHOD_D_PHANTOM 296mL (-1.3%) vs METHOD_D 250mL (-17%)
  · 원인: 큰 대칭 방광은 구 가정이 정확 · Frustum+cap 은 adaptive 로도 저추정
2026-08-13 14:42:35 +09:00

326 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# demo-final vs feature/cloud-mvp 알고리즘 브랜치 비교
_마지막 업데이트: 2026-08-11 · 대상: Alignment · Detection · BV Estimation_
## 배경
VesiScan-Basic Android 앱은 두 개의 활성 브랜치로 관리됨.
| 브랜치 | 용도 | 대상 방광 | 상태 |
|---|---|---|---|
| **demo-final** | 시연/데모 전용 · 안정판 | phantom (구 모형 방광) | 동결 시연 |
| **feature/cloud-mvp** | 실사용/개발 · 인체 임상 | 실제 인체 (타원 방광) | 활성 개발 |
**핵심 원칙**: 두 브랜치 알고리즘 결과가 서로 다른 것이 **정상 설계**. phantom (구) 과 인체 (비대칭 타원) 은 기하가 근본 다르므로 · 같은 코드로 두 상황 모두 정확 계산 불가.
- demo-final = 데모 시 팬텀에서 300ml 근처 값이 안정적으로 나오는 것이 목표. 구 공식 (V = 4/3πr³) 사용.
- cloud-mvp = 인체 정확도 (Halir-Flusser + adaptive_large_bladder_relax + b_si_floor 등 Python parity).
또한 cloud-mvp 는 **패키지 domain-driven 재배치**를 완료: `managers/*` → `piezo/*` · `alignment/*` · `common/util/*`. 두 브랜치 동일 파일이 경로만 다르고 로직은 대부분 동일한 경우가 많음.
---
## 1. Alignment (정렬 · placement guide)
### 1-1. 기본 설정값
| 항목 | demo-final | feature/cloud-mvp |
|---|---|---|
| `alignmentAlgo` (default) | **V1** | **V1** |
| `placementGuideMode` (default) | **SIMPLE** (표시명 "Gradient") | **SIMPLE** |
| V2 진입 조건 | clinical alignment session 만 | clinical alignment session 만 |
| `verticalHitRequired` (V2) | 3 | 3 |
| `VERTICAL_HIT_WINDOW / _MAJORITY` | 6 / 3 | 6 / 3 |
| `STABILIZE_LOST_WINDOW / _MAJORITY` | 4 / 3 | 4 / 3 |
| **V3 CenterAligner** (2-Pass batch) | ✅ 있음 | ❌ 없음 |
### 1-2. 로직 차이
- **V1/V2 state machine 은 두 브랜치 100% 동일** (phase 진입 · hysteresis · CV 계산 · MOVE_UP/DOWN 방향 판정).
- **RollingAligner의 `detectMultichannel` 호출부**:
- demo-final: `applyCross = false` (piezophantomtest ea3f15c 이식 · 2026-07-06)
→ 정렬 위치 선택은 base 검출로만 수행 · 교차채널 보정이 nch 를 흔들지 못하게 격리 · BV 산출은 별도 경로에서 `applyCross=true`.
- cloud-mvp: `applyCross` 파라미터 자체 제거 → 정렬 위치 판정에도 cross-channel 보정 결과 사용.
- **V3 CenterAligner (demo-final 전용)**: Pass 1 = 5 위치 각 20 cycle 스캔 → Rule A (ch3 필수 · 검출률 ≥ 80% · max nch · min bv_cv) 로 최적 위치 선택. Pass 2 = 재측정 검증. cloud-mvp 는 이 배치 알고리즘 자체가 없음.
### 1-3. 관련 파일
| 파일 | demo-final | cloud-mvp |
|---|---|---|
| PlacementGuideView | `ui/views/monitoring/PlacementGuideView.kt` (2317 L) | `alignment/ui/PlacementGuideView.kt` (2187 L) |
| AlignmentAdvisorV2 (+ RollingAligner) | `managers/AlignmentAdvisorV2.kt:677` (`applyCross=false`) | `alignment/AlignmentAdvisorV2.kt:680` (파라미터 제거) |
| AlignmentAdvisorV3 | `managers/AlignmentAdvisorV3.kt` (신규) | — |
| ImuAnalysis / ImuPostureClassifier | `managers/` | `alignment/` |
---
## 2. Detection (벽 검출)
### 2-1. 기본 설정값
| 항목 | demo-final | feature/cloud-mvp |
|---|---|---|
| `detectionMethod` (default) | **METHOD_C** (V4.1 SphereFit walls) | **METHOD_D** (Python for_app_share 1:1) |
| `MethodDParams.otsuRatio` | 0.88 | 0.88 |
| `MethodDParams.oscfarWin` | 9 | 9 |
| `MethodDParams.promGamma / shoulderPromGamma` | 1.5 / 1.5 | 1.5 / 1.5 |
| `MethodDParams.minWallLumenRatio` | 1.16 | 1.16 |
| `MethodDParams.minPostRawRatio` | 1.08 | 1.08 |
| `MethodDParams.antDMax / dMax` | 18 / 10 | 18 / 10 |
| Cross-channel stage 순서 | ①→②→③ (3-stage) | ①→**①.5**→②→③ (4-stage) |
| `VDIP_POST_RECOVERY` | ❌ 없음 | ✅ **true** (DIP_TOL=8.0mm · REACH_MARGIN=15.0mm) |
| `NEIGHBOR_TOP_VALIDATE` / `INWARD_POST_SEARCH` | true / true | true / true |
| `applyCross` 파라미터 | ✅ 있음 (default true) | ❌ 제거 (항상 적용) |
| `topRecoverWallRatio` 파라미터 | ❌ 없음 | ✅ 있음 (streaming relaxed pass 용) |
### 2-2. 로직 차이
- `MethodDParams` 는 모든 필드 100% 동일 (임계값 · 필터 파라미터 전부).
- `MethodDRunner` 는 105 lines 차이 (443L vs 548L). 실질 로직 차이 3가지:
1. **`applyVdipPostRecovery` (cloud-mvp only · stage ①.5)**
- V-dip 심부후벽 복구. 중간 center 채널 후벽 z 가 양 이웃 모두보다 `VDIP_DIP_TOL=8.0mm` 얕고, 두 이웃이 서로 8.0mm 이내 일치 (=under-detected 심부후벽) 이면 consensus 깊이로 재탐색.
- Gate (wall/lumen ratio + post_raw_ratio) 통과한 심부 peak 만 채택.
- 원본: piezophantomtest c800f5d (2026-08-06).
2. **`applyCross` 파라미터**
- demo-final: `detectMultichannel(..., applyCross: Boolean = true)` 유지 → 정렬 / BV 경로 분리 가능.
- cloud-mvp: 제거됨 → 두 경로 모두 cross-channel 적용된 결과 사용.
3. **`topRecoverWallRatio` 파라미터 (cloud-mvp only)**
- `applyNeighborTopValidate` Rule A (FN 복원) 재탐색 시 `minWallLumenRatio` gate override.
- `StreamingBladderEstimator` relaxed pass (게이트 1.16 → 1.13) 용 hook.
- **detection default 가 다른 것 자체가 브랜치 성격의 표현**: demo-final = phantom-검증된 V4.1 (안정 · 시연 유효), cloud-mvp = 임상용 METHOD_D (Python for_app_share 1:1).
### 2-3. 관련 파일
| 파일 | demo-final | cloud-mvp |
|---|---|---|
| MethodDRunner | `walldetect/MethodDRunner.kt` (443 L) | `piezo/walldetect/MethodDRunner.kt` (548 L) |
| MethodDParams | `walldetect/algo/methodd/MethodDParams.kt` | `piezo/walldetect/algo/methodd/MethodDParams.kt` (동일) |
| VDIP 신설 함수 | — | `MethodDRunner.kt:214-306` (`vdipDeepCandidate` + `applyVdipPostRecovery`) |
---
## 3. BV Estimation (부피 계산)
### 3-1. 기본 설정값
| 항목 | demo-final | feature/cloud-mvp |
|---|---|---|
| `bvMethod` (default) | **METHOD_D_PHANTOM** (2026-08-11 신설 · 구 공식) | **METHOD_D** (Halir + adaptive) |
| `BvMethod` enum | `{FRUSTUM, V41, METHOD_D, METHOD_D_PHANTOM}` (4개) | `{FRUSTUM, V41, METHOD_D}` (3개) |
| `distancePerSample` (dps) | **1.968 mm/sample** (2026-08-11 phantom rollback) | **1.936 mm/sample** (Python config_6ch.py:90) |
| `lrRatioOverride` (default) | 1.0 | 1.0 |
| `lrRatioMin / lrRatioMax` clip | ❌ 없음 | ✅ **0.5 / 1.5** (해부학적 clip) |
| `estimateBv(walls, adaptive=true)` 구현 | ✅ 있음 (동일) | ✅ 있음 (동일) |
| `estimateBvPhantomSphere(walls)` | ✅ **있음** (신규 · 구 공식) | ❌ 없음 |
| Halíř–Flusser (`EllipseFitSpecific`) | ✅ 있음 (로직 완전 동일) | ✅ 있음 |
| `adaptive_large_bladder_relax` (low_wide_endpoint) | ✅ 있음 (동일) | ✅ 있음 |
| `b_si_floor_ratio` / `_edge_min` | ✅ 있음 (동일) | ✅ 있음 |
| Subsample refined (Double ant/post) | ✅ 있음 | ✅ 있음 |
| `StreamingBladderEstimator` | ❌ 없음 | ✅ **있음** (age-based strict/relaxed 병합) |
| `useStreamingBv` dev toggle | ❌ 없음 | ✅ 있음 |
### 3-2. 로직 차이
- **`estimateBv(walls)` 본체는 두 브랜치 100% 동일**
- `evalOnce()` → `applyLumenInsetOne(0.15)` → `estimateBladderVolume6ch` → Halíř–Flusser 타원 fit → 결과 검사 (`capFrac < 0.20 && edge ≥ 0.48 && (edge ≥ 0.70 && hRel < 0.42)`) → low_wide_endpoint 이면 `urineInsetFrac=0.05 + bSiFloorRatio=0.85` 로 재계산.
- Python `runners.estimate_bv` 1:1.
- **`estimateBvPhantomSphere(walls)` — demo-final 전용 (2026-08-11 신설)**
- 위치: `PiezoBVEstimator.kt:600`
- 로직: center CH0~3 각 채널의 `(post − ant) × dps` 를 지름으로 보고 평균 → `V = 4/3·π·r³` (r = D/2). Halíř · adaptive · b_si_floor · lr_ratio 전부 우회.
- 검증: D = 42 samples × 1.968 dps = 82.7 mm → r = 41.35 mm → V ≈ 296 ml (300 ml phantom 시연에서 재현 성공).
- **인체 방광 (비대칭 타원) 에는 부적합** · phantom 전용.
- **`distancePerSample` 편차 (1.968 vs 1.936)**
- 두 브랜치 계산 결과가 다른 결정적 원인 중 하나.
- dps 는 `WdConfig.DPS_DEFAULT` 도 함께 동기화 (V41 · Geometry · AnatomicalGate 모두 이 상수 사용) → 한 값만 바꾸면 두 경로 (6ch estimate vs V41/gate) dps 불일치 방지.
- **`StreamingBladderEstimator` (cloud-mvp only)**
- 파일: `piezo/StreamingBladderEstimator.kt` (신규).
- 각 채널별 `age` (마지막 strict 검출 이후 trace 수) 유지 → strict 실패 채널을 최근 K trace 내 strict 이력 있으면 relaxed (게이트 1.13) 로만 복구 · 이력 없으면 완화 안 함 → 게이트 경계 flicker 억제.
- `MethodDRunner.detectMultichannel(topRecoverWallRatio=…)` 로 게이트 override.
- `PiezoMonitoringView:406` 에서 dev toggle 로 라우팅. 배뇨/탈착 시 `reset()`.
- **`lrRatioMin/Max` clip [0.5, 1.5]**
- cloud-mvp 만 도입 (`PiezoHW:164-165` · `computeLrRatio` 반환값 clip).
- demo-final 은 computed lr 그대로 (실제로는 `lrRatioOverride=1.0` 강제 사용해서 clip 무의미).
### 3-3. 관련 파일
| 파일 | demo-final | cloud-mvp |
|---|---|---|
| PiezoBVEstimator | `managers/PiezoBVEstimator.kt` (1174 L) | `piezo/PiezoBVEstimator.kt` (1124 L) |
| EllipseFitSpecific | `managers/EllipseFitSpecific.kt` | `piezo/EllipseFitSpecific.kt` (package 다름) |
| GreenZoneConstants | `managers/GreenZoneConstants.kt:38, :59` | `common/util/GreenZoneConstants.kt:38, :69` |
| BV 신설 함수 (sphere) | `PiezoBVEstimator.kt:600` `estimateBvPhantomSphere` | — |
| Streaming BV | — | `piezo/StreamingBladderEstimator.kt` (신규) |
| BV 분기점 | `ui/views/monitoring/PiezoMonitoringView.kt:487-491` (`METHOD_D_PHANTOM → estimateBvPhantomSphere`) | `piezo/ui/PiezoMonitoringView.kt:406-418` (`useStreamingBv → streamingBv.update(signals)`) |
### 3-4. 심층 비교 · METHOD_D vs METHOD_D_PHANTOM
두 method 는 **같은 detection walls 를 입력**받지만 · 부피 계산 철학이 근본 다름.
#### 3-4-1. 부피 공식
**METHOD_D** — 다각형 근사 + 타원 cap
```
BV = V_frustum + V_topCap + V_bottomCap
┌───────┬─────────┬──────────┐
│ 6채널 │ Halir │ Sphere │
│ frustum│ ellipse │ (fixed) │
│ 적분 │ fit │ │
└───────┴─────────┴──────────┘
```
방광을 6채널 벽 사이 다각형(frustum) + 상하 cap 으로 근사. `adaptive_large_bladder_relax` 로 큰 방광 처리 시 constraint 완화. Python `runners.estimate_bv` 1:1.
**METHOD_D_PHANTOM** — 순수 구
```
BV = (4/3) · π · r³
r = mean(D_center 4채널) / 2
D_ch = (post_ch - ant_ch) × dps
```
방광을 완전 대칭 구로 가정 · center CH0~CH3 지름 평균으로 반지름 산출. Cap · lr_ratio · Halir 다 우회.
#### 3-4-2. 파이프라인 stage 비교
| Stage | METHOD_D | METHOD_D_PHANTOM |
|---|---|---|
| 1. walls 입력 | `List<WallWithSpan?>` (subsample refined) | `List<WallWithSpan?>` (동일) |
| 2. lumen inset | `applyLumenInsetOne(0.15)` — 벽 15% 안쪽 이동 | ❌ 없음 (raw walls) |
| 3. center wall repair | gap 보간 · gap≥2 top drop | ❌ 없음 |
| 4. lr_ratio | `lrRatioOverride ?: computeLrRatio()` | ❌ 없음 (구 = 1:1) |
| 5. Frustum 계산 | 6채널 x-z 타원 · SI-sorted · frustum 적분 | ❌ 없음 |
| 6. Top cap | Halir-Flusser 타원 fit + `ellipse_cap_height` | ❌ 없음 |
| 7. Bottom cap | Sphere (`bottomKind = "sphere"`) | ❌ 없음 (전체가 구) |
| 8. adaptive relax | low_wide_endpoint 검사 → 재계산 | ❌ 없음 |
| 9. b_si_floor | 하한 `b_si ≥ 0.85 × edge_min` | ❌ 없음 |
| 10. Post outlier | `post_outlier_filter=false` (default) | ❌ 없음 |
| 11. 최종 | Σ frustum + top cap + bottom cap | `4/3 π (D̄/2)³` |
#### 3-4-3. 결과 특성
| 특성 | METHOD_D | METHOD_D_PHANTOM |
|---|---|---|
| **정확도 (실제 방광)** | 높음 (Python parity · Δ~0mL 검증) | 낮음 (구 가정) |
| **정확도 (phantom 300mL)** | 250mL (adaptive 로도 -17%) | **296mL** (-1.3%) ✓ |
| **CH3 flick 민감도** | 낮음 (frustum 은 채널 몇 개 빠져도 ok) | 높음 (평균에 직접 반영) |
| **비대칭 방광** | 잘 대응 (frustum + LR ratio) | 왜곡 심함 (구 가정) |
| **작은 방광 (<100mL)** | 정확 | 정확 (구 가정 잘 맞음) |
| **큰 방광 (>400mL)** | 정확 | 저추정 (구 부피 성장률 < 실제) |
| **계산 비용** | 높음 (타원 fit · SVD · relax 재계산) | 매우 낮음 (곱셈 몇 번) |
| **결과 재현성** | 파이썬 소수점까지 일치 | trivial · dps · walls 만 의존 |
#### 3-4-4. 예시 계산 (phantom 300mL · 지름 83mm)
Method D walls 예시:
```
CH0: ant=15, post=57 → span 42
CH1: ant=13, post=59 → span 46
CH2: ant=14, post=57 → span 43
CH3: ant=16, post=55 → span 39
CH4/5: ant=18, post=44 (LR wings)
```
**METHOD_D_PHANTOM 계산**:
```
D̄ = mean(42, 46, 43, 39) × 1.968 = 42.5 × 1.968 = 83.64 mm
r = 41.82 mm
V = 4/3 · π · 41.82³ = 306,326 mm³ ≈ 306 mL ✓
```
**METHOD_D 계산** (근사):
```
V_frustum = Σ frustum(z_i, z_{i+1}, S_i, S_{i+1}) (4채널 → 3 slice)
≈ 165 mL (본체)
V_top (Halir + adaptive) ≈ 55 mL (배꼽쪽 cap · b_si_floor 로 확장)
V_bottom (sphere) ≈ 30 mL (치골쪽 cap)
V_total ≈ 250 mL (-17% 저추정 · adaptive 로도 큰 phantom 처리 어려움)
```
#### 3-4-5. 언제 어느 것 써야 하나
| 시나리오 | 권장 method | 근거 |
|---|---|---|
| Phantom 시연 (구·타원 방광 모형) | **METHOD_D_PHANTOM** | 구 가정 정확 · 300mL 재현 |
| 인체 실측 (임상) | **METHOD_D** | 비대칭 방광 · Halir + adaptive 필수 |
| Alignment 위치 판정 | detection method 만 사용 (BV 무관) | walls 만 필요 |
| BV parity 테스트 (Python 대조) | **METHOD_D** | bit-perfect 일치 |
| CH3 검출 불안정 자세 | **METHOD_D** | frustum 이 채널 결손 관용 |
| 빠른 UI 반응 (계산 지연 최소) | METHOD_D_PHANTOM | O(1) 계산 |
#### 3-4-6. 코드 진입점 요약 (demo-final)
```kotlin
// PiezoMonitoringView.kt:487-491
val bvResult = if (bvMethod == BvMethod.METHOD_D_PHANTOM) {
estimateBvPhantomSphere(walls) // PiezoBVEstimator.kt:600 · ~30 L
} else {
estimateBv(walls) // PiezoBVEstimator.kt:595 · ~600 L
}
```
- `estimateBvPhantomSphere` — 30 lines · 순수 계산 · state 없음
- `estimateBv` — 600 lines + EllipseFitSpecific.kt (150 L) + adaptive wrapper
#### 3-4-7. 향후 개선 여지 (선택)
- **하이브리드 method**: walls 대칭성 분산 계산 → 낮으면 PHANTOM · 높으면 METHOD_D 자동 선택
- **METHOD_D_PHANTOM 확장**: 반지름 산출 시 CH1/CH2 만 사용 (CH0/CH3 는 flick 심함) 옵션
- **METHOD_D_PHANTOM_ELLIPSOID**: 구 대신 반지름 2개 (SI + AP) 로 회전 타원체 · 근사 개선
---
## 4. 브랜치 전용 파일/기능 요약
### demo-final only
- `managers/AlignmentAdvisorV3.kt` (CenterAligner · Rule A + bvcv batch scan-select)
- `BvMethod.METHOD_D_PHANTOM` enum value
- `fun estimateBvPhantomSphere(walls)` (구 공식 · phantom 시연 전용)
- `MethodDRunner.detectMultichannel(..., applyCross = false)` API 보존
### cloud-mvp only
- `piezo/StreamingBladderEstimator.kt` (age-based strict/relaxed 병합)
- `GreenZoneConstants.useStreamingBv` dev toggle + custom-setter audit logging
- `PiezoHW.lrRatioMin / lrRatioMax` clip + `coerceIn` in `computeLrRatio`
- `MethodDRunner.applyVdipPostRecovery` + `vdipDeepCandidate` (V-dip 심부후벽 복구)
- `MethodDRunner.detectMultichannel(..., topRecoverWallRatio: Double?)` API
- `PiezoHW.autoDetectPreset` PhiRedactor 마스킹 (cybersecurity DC-02)
- Custom-setter audit-logger wrappers on `_detectionMethod` / `_bvMethod` / `_lrRatioOverride` / `_placementGuideMode` / `_alignmentAlgo` / `_useStreamingBv` (cybersecurity UC-04)
- `piezo/ui/PiezoPersonalizationView.kt` streaming BV dev toggle UI
### 공통 (구현 동일 · 위치만 다름)
- Halíř–Flusser `EllipseFitSpecific` · `estimateBv(walls)` 본체 · `adaptive_large_bladder_relax` · `b_si_floor` · subsample refined · `MethodDParams` 전 필드 · V1/V2 alignment state machine 임계
---
## 5. 브랜치 default 조합 종합
| 축 | demo-final | cloud-mvp |
|---|---|---|
| 정렬 | V1 · Gradient (SIMPLE) | V1 · Gradient (SIMPLE) |
| 검출 | **METHOD_C** (V4.1 SphereFit walls) | **METHOD_D** (Python for_app_share) |
| BV | **METHOD_D_PHANTOM** (구 공식 · V=4/3πr³) | **METHOD_D** (Halir + adaptive) |
| dps | **1.968** mm/sample | **1.936** mm/sample |
| Streaming BV | — | dev toggle |
| Audit logging | — | 활성 |
## 6. 브랜치별 의사결정 기록 (핵심 커밋)
| 커밋 | 날짜 | 브랜치 | 변경 |
|---|---|---|---|
| `bcbc7b4` | 2026-07-02 | demo-final | 패키지 통일 (`com.example` → `com.medithings`) · BV 로직 안정판 |
| `d732150` | 2026-07-20 | demo-final | Halíř-Flusser ellipse fit 이식 (BV Δ 43→13mL) |
| `3152783` | 2026-07-20 | demo-final | estimateBv wrapper + adaptive_large_bladder_relax + b_si_floor |
| `430d9ad` | 2026-07-30 | demo-final | feature-folder refactor (147 파일 이동) |
| `1c3748b` | 2026-08-07 | demo-final | vdip_post_recovery 이식 (MethodDRunner) |
| `8cab1d0` | 2026-08-10 | demo-final | rollback 후 재조정 · detection=C + bv=METHOD_D |
| `d054b3b` | 2026-08-11 | demo-final | dps 1.936 → 1.968 (phantom 스케일 재현) |
| `d1d17f8` | 2026-08-11 | demo-final | METHOD_D_PHANTOM (구 공식) 신설 · demo default |
| `6dfe5e6` | 2026-08-11 | demo-final | `BleManager.disconnect()` 즉시 UI 반영 fix |
## 7. 향후 유지 원칙
1. **cloud-mvp 의 알고리즘 개선을 demo-final 로 무분별 이식하지 않기**
- Halir · adaptive 등은 인체 정확도 개선이지만 phantom 에서는 오히려 값 왜곡.
2. **demo-final 은 phantom 시연 재현성 최우선**
- 알고리즘 변경 시 300 ml phantom 결과가 296 ml 근처를 유지하는지 확인.
3. **동시 이식이 필요한 fix (예: BLE 안정성 · UI 버그) 는 두 브랜치 각각 별도 커밋**
- cherry-pick 대신 각 브랜치에서 명시적으로 적용 후 커밋 메시지에 "demo-final 이식" / "cloud-mvp 이식" 명시.