fix(labdb): 600·601 의 sensor.imu 를 labdb 표준 모양(샘플 하나)으로 — CSV 내보내기에 IMU 가 비어 있었다
labdb 에서 CSV 로 받으면 ax..gz 열이 빈칸이고 raw JSON 에는 IMU 가 있다(2026-09-29
보고). 사이트의 파싱 문제가 아니다 — labdb 표준(labdb.md "000" 레코드)은
`sensor.imu` 가 샘플 하나 `{ax,ay,az,gx,gy,gz}` 이고 CSV 내보내기는 그 여섯 값을
편다. 병원 임상(600)·정렬(601)은 2026-09-21 부터 거기에 회차 샘플 전부(배열)를
넣고 있었다. 사내 임상(001)은 처음부터 표준대로 보내고 있었다.
· LabdbSensor.of(samples): imu = 마지막 샘플 하나(표준·CSV 에 나옴),
imu_samples = 전부(FIFO 순), imu_sample_count. 없으면 빈 객체(0 으로 안 채움).
001 의 LabdbUploader·tools/labdb_upload.py 와 같은 모양.
· HospitalLabdbPayload·AlignLabdbPayload 가 이것을 쓴다.
· tools/align_analyze.py 는 imu_samples 를 우선 읽고, 09-21~28 분(imu 가 배열)도
그대로 받는다.
· docs/LABDB_DATATYPES.md §IMU 를 새 모양으로. 09-21~28 업로드분은 CSV 에서
IMU 가 비는 이유와, 내보내기에서 배열이면 마지막 원소를 쓰는 선택 사항을 적었다.
시험: LabdbSensorTest 3건 신규, Align/Hospital 페이로드 시험을 새 모양으로
(services.labdb 38건 통과). 전체 168건 중 실패 3건은 옛 세션 임시 경로에 쓰는
덤프 시험(Cm3PerTraceDump·GoldenDump·PrecisionDump)으로 이 변경과 무관하다.
병원 폰(SM-A155N)에 설치 완료.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -42,12 +42,14 @@ import java.util.Locale
|
||||
* phase : "sweep" | "confirm" ← 2026-09-18 추가
|
||||
* cycle_idx : 그 위치 안에서의 순번
|
||||
* channels : 6 × {ch, peak, peakIdx, data[~100]}
|
||||
* sensor : {} 또는 {imu: [{ax,ay,az,gx,gy,gz}, …]} ← 2026-09-21 추가
|
||||
* sensor : {} 또는 {imu:{ax..gz}, imu_samples:[…], imu_sample_count}
|
||||
* ← 2026-09-21 추가 · 2026-09-29 표준 모양([LabdbSensor])
|
||||
* ```
|
||||
*
|
||||
* ## IMU
|
||||
* `align_{n}cm[_confirm]_imu.csv` 가 있으면 cycle 별로 `sensor.imu` 에 싣는다. 없으면
|
||||
* `sensor` 는 빈 객체다 — **부재가 곧 "그때는 안 쟀다"** 이므로 0 으로 채우지 않는다.
|
||||
* `align_{n}cm[_confirm]_imu.csv` 가 있으면 cycle 별로 `sensor` 에 싣는다([LabdbSensor.of]
|
||||
* — `imu` 는 마지막 샘플 하나, `imu_samples` 는 전부). 없으면 `sensor` 는 빈 객체다 —
|
||||
* **부재가 곧 "그때는 안 쟀다"** 이므로 0 으로 채우지 않는다.
|
||||
* 정렬은 사람이 프로브를 옮겨 가며 재는 과정이라, 어느 위치의 파형이 이상할 때 그것이
|
||||
* 자리 탓인지 그 순간 흔들린 탓인지 IMU 없이는 가를 수 없다.
|
||||
*
|
||||
@@ -152,9 +154,7 @@ object AlignLabdbPayload {
|
||||
put("align_cm", cm)
|
||||
put("phase", phase)
|
||||
put("cycle_idx", ci)
|
||||
put("sensor", JSONObject().apply {
|
||||
imuByCycle[ci]?.let { put("imu", it) }
|
||||
})
|
||||
put("sensor", LabdbSensor.of(imuByCycle[ci]))
|
||||
put("commandType", "MTB")
|
||||
put("channels", chans)
|
||||
})
|
||||
|
||||
@@ -40,10 +40,14 @@ import java.util.TimeZone
|
||||
* 배터리·온도는 병원 CSV 에 열이 없다. `sensor` 에 넣지 않는다 — 0 이나 null 로 채우면
|
||||
* 나중에 "배터리가 0 이었다"로 읽힌다.
|
||||
*
|
||||
* ## IMU (2026-09-21 추가)
|
||||
* `*_imu.csv` 형제 파일이 있으면 `sensor.imu` 로 싣는다. 없으면 `sensor` 는 종전대로
|
||||
* 빈 객체다 — **이 필드의 부재가 곧 "그때는 안 쟀다"** 라서, 없는 회차를 0 으로 채우면
|
||||
* 안 된다. 예전 업로드분에는 애초에 파일이 없다.
|
||||
* ## IMU (2026-09-21 추가 · 2026-09-29 표준 모양)
|
||||
* `*_imu.csv` 형제 파일이 있으면 회차별로 `sensor` 에 싣는다([LabdbSensor.of] — `imu` 는
|
||||
* 마지막 샘플 하나, `imu_samples` 는 전부). 없으면 `sensor` 는 종전대로 빈 객체다 —
|
||||
* **이 필드의 부재가 곧 "그때는 안 쟀다"** 라서, 없는 회차를 0 으로 채우면 안 된다.
|
||||
* 예전 업로드분에는 애초에 파일이 없다.
|
||||
*
|
||||
* 09-21~09-28 업로드분은 `sensor.imu` 가 **배열**이었다. labdb 표준은 샘플 하나라 CSV
|
||||
* 내보내기의 `ax..gz` 가 비어 나왔다 — raw JSON 에는 다 있다.
|
||||
*
|
||||
* 같은 자세로 20회 반복하는 프로토콜이라, 어떤 회차만 값이 튈 때 환자가 움직인 것인지
|
||||
* 알고리즘 문제인지 가르는 근거가 IMU 다.
|
||||
@@ -133,10 +137,8 @@ object HospitalLabdbPayload {
|
||||
put("rowIndex", rep)
|
||||
put("datetime", iso(times[rep]))
|
||||
put("commandType", "MTB")
|
||||
// IMU 가 있으면 `sensor.imu`, 없으면 빈 객체(= 안 쟀다).
|
||||
put("sensor", JSONObject().apply {
|
||||
imuByRepeat[rep]?.let { put("imu", it) }
|
||||
})
|
||||
// IMU 가 있으면 표준 모양(imu · imu_samples), 없으면 빈 객체(= 안 쟀다).
|
||||
put("sensor", LabdbSensor.of(imuByRepeat[rep]))
|
||||
put("channels", chans)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* LabdbSensor — labdb 레코드의 `sensor` 필드를 **표준 모양**으로 만든다.
|
||||
*
|
||||
* labdb 표준(labdb.md `"000"` 레코드 · §5-6 CSV 내보내기)은 `sensor.imu` 가 **샘플 하나**
|
||||
* `{ax,ay,az,gx,gy,gz}` 다. CSV 내보내기는 그 여섯 값을 `ax..gz` 열로 편다 — 레코드당
|
||||
* 값 하나라 배열은 펼 수 없다.
|
||||
*
|
||||
* 병원 임상(600)·정렬(601)은 2026-09-21 부터 `sensor.imu` 에 **회차 샘플 전부(배열)** 를
|
||||
* 넣고 있었다. 서버는 JSON 을 그대로 저장하니 raw 내보내기에는 다 보였지만, CSV 의
|
||||
* `ax..gz` 는 빈칸이었다(2026-09-29 발견 — 분석팀이 CSV 로 받으면 IMU 가 없다).
|
||||
* 사이트의 파싱 잘못이 아니라 우리가 표준을 어긴 것이다.
|
||||
*
|
||||
* 그래서 사내 임상(001, [LabdbUploader.cycleToRecord]·`tools/labdb_upload.py`)이 처음부터
|
||||
* 쓰던 모양으로 셋을 맞춘다:
|
||||
*
|
||||
* imu : 마지막(가장 최근 = piezo 캡처 직전) 샘플 하나 — 표준. CSV 에 나온다.
|
||||
* imu_samples : 회차 샘플 전부(FIFO 순). raw JSON 에서 그대로. 기울기 재계산은 이것으로.
|
||||
* imu_sample_count : 그 개수.
|
||||
*
|
||||
* 샘플이 없으면 **빈 객체** — 부재가 곧 "그때는 안 쟀다" 다. 0 으로 채우지 않는다.
|
||||
*/
|
||||
package com.medithings.vesiscan.services.labdb
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
object LabdbSensor {
|
||||
|
||||
private val AXES = listOf("ax", "ay", "az", "gx", "gy", "gz")
|
||||
|
||||
/**
|
||||
* @param samples `[{ax,ay,az,gx,gy,gz}, …]` FIFO 순(마지막이 최신). null·빈 배열이면 빈 객체.
|
||||
*/
|
||||
fun of(samples: JSONArray?): JSONObject {
|
||||
val out = JSONObject()
|
||||
if (samples == null || samples.length() == 0) return out
|
||||
val latest = samples.optJSONObject(samples.length() - 1) ?: return out
|
||||
out.put("imu", JSONObject().apply {
|
||||
// 없는 축은 빼 둔다 — 0 을 넣으면 "0 이었다"가 된다.
|
||||
for (k in AXES) if (latest.has(k)) put(k, latest.getDouble(k))
|
||||
})
|
||||
out.put("imu_samples", samples)
|
||||
out.put("imu_sample_count", samples.length())
|
||||
return out
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user