refactor: 데모 브랜치 패키지 통일 com.example.medilightv2android → com.medithings.vesiscan

사용자앱 (feature/tab-navigation) 과 패키지 이름 일치. namespace + applicationId
둘 다 com.medithings.vesiscan 로 변경 (기존 데모 앱은 재설치 필요).

## 변경 범위
- Kotlin 148 파일: package + import 문 (714 occurrences)
- 디렉토리 이동: com/example/medilightv2android → com/medithings/vesiscan
  (main, test, androidTest 각각)
- app/build.gradle.kts: namespace, applicationId
- docs/FLAVOR_DEMO_STABLE.md: 참조 갱신
- V41DetectorCH4Test: BvDispatchResult.methodChosen → method (dto field name fix)

## 주의
- applicationId 가 바뀌므로 기존 데모 앱 (com.example.medilightv2android.demo) 은
  Android 관점에서 다른 앱으로 취급 — 재설치 시 PIN/설정 초기화됨.
- Fresh install 권장. 기존 앱 (com.example...) 은 별도로 uninstall 필요.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 14:30:43 +09:00
parent 55d55225ab
commit bcbc7b440c
159 changed files with 2865 additions and 717 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

+738
View File
@@ -0,0 +1,738 @@
FILE: 주간보고서_dwjang_20260604.pptx
SLIDE SIZE: W=14630400 H=8229600 EMU | W=16.00in H=9.00in
SLIDE COUNT: 7
LAYOUTS:
[0] 0. 제목 슬라이드
[1] 0. 제목 반전 슬라이드
[2] 1. 빈 화면
[3] 1. 빈 반전 화면
[4] 2. 목차 슬라이드
[5] 2. 목차 반전 슬라이드
======================================================================
SLIDE 1
layout: 0. 제목 반전 슬라이드
background: type=BACKGROUND (5)
shape_count: 2
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=6620256 T=3749039 W=2785378 H=584775 EMU | L=7.24in T=4.10in W=3.05in H=0.64in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=45720 mB=45720
text(60)='주간 진행 보고'
p0 lvl=0 align=None - text='주간 진행 보고'
r0 font='나눔고딕 ExtraBold' sz=32.0 b=True i=None c=inherit t='주간 '
r1 font='나눔고딕 ExtraBold' sz=32.0 b=True i=None c=inherit t='진행'
r2 font='나눔고딕 ExtraBold' sz=32.0 b=True i=None c=inherit t=' '
r3 font='나눔고딕 ExtraBold' sz=32.0 b=True i=None c=inherit t='보고'
- shape name='TextBox 2' type=TEXT_BOX (17) autoshape=
geom: L=6620256 T=4572000 W=3346429 H=369332 EMU | L=7.24in T=5.00in W=3.66in H=0.40in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=45720 mB=45720
text(60)='2026-06-01 (월) ~ 06-05 (금)'
p0 lvl=0 align=None - text='2026-06-01 (월) ~ 06-05 (금)'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#55606C t='2026-06-01 (월) ~ 06-0'
r1 font='나눔고딕' sz=18.0 b=True i=None c=#55606C t='5'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#55606C t=' ('
r3 font='나눔고딕' sz=18.0 b=True i=None c=#55606C t='금'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#55606C t=')'
======================================================================
SLIDE 2
layout: 1. 빈 화면
background: type=BACKGROUND (5)
shape_count: 4
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=402336 T=173736 W=6236208 H=457200 EMU | L=0.44in T=0.19in W=6.82in H=0.50in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=0 mB=45720
text(60)='Android App 진행 보고'
p0 lvl=0 align=None - text='Android App 진행 보고'
r0 font='나눔고딕 ExtraBold' sz=24.0 b=True i=None c=inherit t='Android App 진행 보고'
- shape name='TextBox 3' type=TEXT_BOX (17) autoshape=
geom: L=603504 T=868680 W=13240512 H=3842077 EMU | L=0.66in T=0.95in W=14.48in H=4.20in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=True mL=0 mR=91440 mT=45720 mB=45720
text(60)='테스트 페이지 + labdb 업로드 + BLE/IMU 파싱 준비 | • 모바일 앱 테스트용 페이지 구현 |'
p0 lvl=0 align=None - text='테스트 페이지 + labdb 업로드 + BLE/IMU 파싱 준비'
r0 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='테스트'
r1 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r2 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='페이지'
r3 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' + '
r4 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='labdb'
r5 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r6 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='업로드'
r7 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' + BLE/IMU '
r8 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='파싱'
r9 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r10 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='준비'
p1 lvl=0 align=None - text='• 모바일 앱 테스트용 페이지 구현'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='모바일'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 앱 '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='테스트용'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='페이지'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='구현'
p2 lvl=1 align=None - text='– Clinical Measurement 모드 — 라벨 입력 + 6채널 라이브 시각화'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– Clinical Measurement '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='모드'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' — '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='라벨'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='입력'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' + 6채널 '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='라이브'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='시각화'
p3 lvl=1 align=None - text='– 폴더 단위 저장 (Downloads/VesiScan_Sessions/)'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='폴더'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='단위'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='저장'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' (Downloads/'
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='VesiScan_Sessions'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='/)'
p4 lvl=0 align=None - text='• labdb 업로드 기능 (Phase A+B)'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='labdb'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='업로드'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='기능'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' (Phase A+B)'
p5 lvl=1 align=None - text='– EncryptedSharedPreferences로 apiKey 안전 보관'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='EncryptedSharedPreferences로'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='apiKey'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='안전'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='보관'
p6 lvl=1 align=None - text='– register → admin 승인 → /upload/json'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– register → admin '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='승인'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' → /upload/'
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='json'
p7 lvl=1 align=None - text='– End Session 시 자동 업로드 + 폴더 내 .uploaded 마커 + 수동 재시도 버튼'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– End Session 시 '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='자동'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='업로드'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' + '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='폴더'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 내 .uploaded '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='마커'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' + '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='수동'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='재시도'
r12 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r13 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='버튼'
p8 lvl=0 align=None - text='• BLE 명령어 수정 및 IMU 파싱 추가'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• BLE '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='명령어'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='수정'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 및 IMU '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='파싱'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='추가'
p9 lvl=1 align=None - text='– rim: 응답 188B (15 samples × 12B) BE int16 파싱'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– rim: '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='응답'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 188B (15 samples × 12B) BE int16 '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='파싱'
p10 lvl=1 align=None - text='– ImuPacketCollector — Accel ±4g (1/8192 LSB), Gyro ±500dps'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='ImuPacketCollector'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' — Accel ±4g (1/8192 LSB), Gyro ±500dps '
- shape name='AutoShape 2' type=AUTO_SHAPE (1) autoshape=
geom: L=7162800 T=3962400 W=304800 H=304800 EMU | L=7.83in T=4.33in W=0.33in H=0.33in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=TOP (1) wrap=True mL=91440 mR=91440 mT=45720 mB=45720
text(60)=''
p0 lvl=0 align=None - text=''
- shape name='그림 6' type=PICTURE (13) autoshape=
geom: L=9247388 T=297454 W=3157605 H=6841479 EMU | L=10.11in T=0.33in W=3.45in H=7.48in
line: color=inherit width_pt=None
======================================================================
SLIDE 3
layout: 1. 빈 화면
background: type=BACKGROUND (5)
shape_count: 5
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=402336 T=173736 W=6236208 H=457200 EMU | L=0.44in T=0.19in W=6.82in H=0.50in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=0 mB=45720
text(60)='Android App 진행 보고'
p0 lvl=0 align=None - text='Android App 진행 보고'
r0 font='나눔고딕 ExtraBold' sz=24.0 b=True i=None c=inherit t='Android App 진행 보고'
- shape name='TextBox 3' type=TEXT_BOX (17) autoshape=
geom: L=603504 T=868680 W=13240512 H=4306307 EMU | L=0.66in T=0.95in W=14.48in H=4.71in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=True mL=0 mR=91440 mT=45720 mB=45720
text(60)='labdb 통합 정리 | • dataType "001" JSON 저장 및 표시 | – 내부 임상 R&D '
p0 lvl=0 align=None - text='labdb 통합 정리'
r0 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='labdb'
r1 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r2 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='통합'
r3 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r4 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='정리'
p1 lvl=0 align=None - text='• dataType "001" JSON 저장 및 표시'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='dataType'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' "001" JSON '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='저장'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 및 '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='표시'
p2 lvl=1 align=None - text='– 내부 임상 R&D 구분용 — 외부 일반 VesiScan "000"과 분리'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='내부'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='임상'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' R&D '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='구분용'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' — '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='외부'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='일반'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='VesiScan'
r12 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' "000"과 '
r13 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='분리'
p3 lvl=1 align=None - text='– meta.json / measurement.json 양쪽에 data_type 필드 노출'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='meta.json'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' / '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='measurement.json'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='양쪽에'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='data_type'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='필드'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='노출'
p4 lvl=0 align=None - text='• labdb 웹에서 raw JSON 다운로드 가능'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='labdb'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='웹에서'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' raw JSON '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='다운로드'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='가능'
p5 lvl=1 align=None - text='– (raw 외 별도 처리 영역은 web 담당 — 미수정)'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– (raw 외 '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='별도'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='처리'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='영역은'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' web '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='담당'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' — '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='미수정'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=')'
p6 lvl=0 align=None - text='• 웹 프론트엔드 wall detection 알고리즘 V4.1로 변경'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• 웹 '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='프론트엔드'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' wall detection '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='알고리즘'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' V4.1로 '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='변경'
p7 lvl=0 align=None - text='• raw JSON 파일명에 session ID 포함 + 메타데이터 저장'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• raw JSON '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='파일명에'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' session ID '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='포함'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' + '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='메타데이터'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='저장'
p8 lvl=1 align=None - text='– 파일명만으로 측정 식별, examiner / posture / step / true_volume'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='파일명만으로'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='측정'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='식별'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=', examiner / posture / step / '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='true_volume'
p9 lvl=0 align=None - text='• PostgreSQL JSONB NULL byte 500 에러 해결'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• PostgreSQL JSONB NULL byte 500 '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='에러 해결'
p10 lvl=1 align=None - text='– 서버 /var/log/labdb.log 직접 확인 → BLE rid: 응답의 trailing NULL이'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='서버'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' /var/log/labdb.log '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='직접'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='확인'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' → BLE rid: '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='응답의'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' trailing '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='NULL이'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='원인'
p11 lvl=1 align=None - text='– BLE 파싱 + LabdbClient 양쪽에 재귀 sanitizer 추가'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– BLE '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='파싱'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' + '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='LabdbClient'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='양쪽에'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='재귀'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' sanitizer '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='추가'
- shape name='그림 7' type=PICTURE (13) autoshape=
geom: L=9135186 T=3057935 W=5076573 H=4302985 EMU | L=9.99in T=3.34in W=5.55in H=4.71in
line: color=inherit width_pt=None
- shape name='그림 9' type=PICTURE (13) autoshape=
geom: L=7075214 T=582514 W=3633182 H=2356549 EMU | L=7.74in T=0.64in W=3.97in H=2.58in
line: color=inherit width_pt=None
- shape name='그림 12' type=PICTURE (13) autoshape=
geom: L=10834869 T=582514 W=3393195 H=2356549 EMU | L=11.85in T=0.64in W=3.71in H=2.58in
line: color=inherit width_pt=None
======================================================================
SLIDE 4
layout: 1. 빈 화면
background: type=BACKGROUND (5)
shape_count: 3
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=402336 T=173736 W=6236208 H=457200 EMU | L=0.44in T=0.19in W=6.82in H=0.50in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=0 mB=45720
text(60)='Android App 진행 보고'
p0 lvl=0 align=None - text='Android App 진행 보고'
r0 font='나눔고딕 ExtraBold' sz=24.0 b=True i=None c=inherit t='Android App 진행 보고'
- shape name='TextBox 3' type=TEXT_BOX (17) autoshape=
geom: L=603504 T=868680 W=13240512 H=5781070 EMU | L=0.66in T=0.95in W=14.48in H=6.32in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=True mL=0 mR=91440 mT=45720 mB=45720
text(60)='펌웨어 업데이트 대응 — reb 패킷 신구조 (208B → 210B) | • payload 앞에 ch_in'
p0 lvl=0 align=None - text='펌웨어 업데이트 대응 — reb 패킷 신구조 (208B → 210B)'
r0 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='펌웨어'
r1 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r2 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='업데이트'
r3 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r4 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='대응'
r5 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' — reb '
r6 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='패킷'
r7 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r8 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='신구조'
r9 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' (208B → 210B)'
p1 lvl=0 align=None - text='• payload 앞에 ch_info 2B 추가 = [ch_session 1B] + [ch_num 1B]'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• payload '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='앞에'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='ch_info'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 2B '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='추가'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' = ['
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='ch_session'
r8 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 1B] + ['
r9 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='ch_num'
r10 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 1B]'
p2 lvl=1 align=None - text='– [tag 4B][ch_info 2B][num_sample 2B][adc 200B][crc 2B]'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– [tag 4B]['
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='ch_info'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 2B]['
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='num_sample'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 2B]['
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='adc'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 200B]['
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='crc'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 2B]'
p3 lvl=0 align=None - text='• 채널/세션 정보로 측정 set 식별'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='채널'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='/'
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='세션'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='정보로'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='측정'
r8 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' set '
r9 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='식별'
p4 lvl=1 align=None - text='– 동일 ch_session = 한 측정 set으로 6개 reb 묶음'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='동일'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='ch_session'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' = 한 '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='측정'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='set으로'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 6개 reb '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='묶음'
p5 lvl=1 align=None - text='– ch_num으로 채널 직접 indexing (도착 순서 의존 폐기)'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='ch_num으로'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='채널'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='직접'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' indexing ('
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='도착'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='순서'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='의존'
r12 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r13 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='폐기'
r14 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=')'
p6 lvl=0 align=None - text='• 누락/불일치 측정 set 자동 폐기'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='누락'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='/'
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='불일치'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='측정'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' set '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='자동'
r8 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r9 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='폐기'
p7 lvl=1 align=None - text='– session 불일치 / 중복 ch_num / 6채널 중 일부 누락 → 해당 set drop'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– session '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='불일치'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' / '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='중복'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='ch_num'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' / 6채널 중 '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='일부'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='누락'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' → '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='해당'
r12 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' set drop'
p8 lvl=1 align=None - text='– 다음 정상 set만 처리'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='다음'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='정상'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='set만'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='처리'
p9 lvl=0 align=None - text='• 한 곳 수정으로 전 화면 적용'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• 한 곳 '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='수정으로'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 전 '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='화면'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='적용'
p10 lvl=1 align=None - text='– 도넛차트(maa?) · placement · 임상(mtb?) 모두 동일 collector'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='도넛차트'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='(maa?) · placement · '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='임상'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='('
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='mtb'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='?) '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='모두'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='동일'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' collector'
p11 lvl=1 align=None - text='– PiezoPacketCollector 한 군데만 신구조로 재작성'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='PiezoPacketCollector'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 한 '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='군데만'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='신구조로'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='재작성'
p12 lvl=0 align=None - text='• Sensor 측정 시 IMU 값 — 가장 오래된 것 → 가장 최근 것으로 수정'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• Sensor '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='측정'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 시 IMU 값 — '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='가장'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='오래된'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' 것 → '
r7 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='가장'
r8 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r9 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='최근'
r10 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r11 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='것으로'
r12 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r13 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='수정'
p13 lvl=1 align=None - text='– FIFO 첫 샘플 사용 버그 → 측정 순간 자세 반영을 위해 마지막 샘플로'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– FIFO 첫 '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='샘플'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='사용'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='버그'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' → '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='측정'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='순간'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='자세'
r12 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r13 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='반영을'
r14 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r15 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='위해'
r16 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r17 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='마지막'
r18 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r19 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='샘플로'
p14 lvl=1 align=None - text=''
p15 lvl=1 align=None - text='총 15개 받는데 맨 처음 들어온거는 더 적을 수 있음.'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='총 '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='15'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='개 받는데 맨 처음 '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='들어온거는'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='더 적을 수 있음'
r6 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='.'
p16 lvl=1 align=None - text='마지막으로 받은 imu값(최신) 을 센서 측정 당시의 imu로 이해하면 될것.'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='마지막으로 받은 '
r1 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='imu'
r2 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='값'
r3 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='('
r4 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='최신'
r5 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t=') '
r6 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='을 센서 측정 당시의 '
r7 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='imu'
r8 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='로 이해하면 '
r9 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='될것'
r10 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='.'
- shape name='그림 5' type=PICTURE (13) autoshape=
geom: L=8661917 T=1550078 W=2991267 H=4534533 EMU | L=9.47in T=1.70in W=3.27in H=4.96in
line: color=inherit width_pt=None
======================================================================
SLIDE 5
layout: 1. 빈 화면
background: type=BACKGROUND (5)
shape_count: 3
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=402336 T=173736 W=6236208 H=457200 EMU | L=0.44in T=0.19in W=6.82in H=0.50in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=0 mB=45720
text(60)='Android App 진행 보고'
p0 lvl=0 align=None - text='Android App 진행 보고'
r0 font='나눔고딕 ExtraBold' sz=24.0 b=True i=None c=inherit t='Android App 진행 보고'
- shape name='TextBox 3' type=TEXT_BOX (17) autoshape=
geom: L=603504 T=868680 W=13240512 H=6858000 EMU | L=0.66in T=0.95in W=14.48in H=7.50in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=True mL=0 mR=91440 mT=45720 mB=45720
text(60)='ClinicalLive — V4.1 Wall Peak 오버레이 (사내 가시화 요청) | • 사내 임상 테스'
p0 lvl=0 align=None - text='ClinicalLive — V4.1 Wall Peak 오버레이 (사내 가시화 요청)'
r0 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='ClinicalLive — V4.1 Wall Peak 오버레이 (사내 가'
p1 lvl=0 align=None - text='• 사내 임상 테스트 진행자 요청 — 실시간 peak 위치 가시화'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• 사내 임상 테스트 진행자 요청 — 실시간 peak 위치 가시화'
p2 lvl=0 align=None - text='• V4.1 wall detection (walldetect.V41Detector)을 매 mtb 사이클마다'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• V4.1 wall detection (walldetect.V41De'
p3 lvl=1 align=None - text='– 입력: 6채널 raw ADC + ProbeProfileDto (PiezoMonitoring과 동일 설정'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– 입력: 6채널 raw ADC + ProbeProfileDto (Pi'
p4 lvl=1 align=None - text='– 출력: 채널별 antIdx(전벽) / postIdx(후벽) Int?'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– 출력: 채널별 antIdx(전벽) / postIdx(후벽) Int?'
p5 lvl=1 align=None - text='– runCatching으로 감싸 detection 실패가 측정 자체에 영향 없도록'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– runCatching으로 감싸 detection 실패가 측정 자체에'
p6 lvl=0 align=None - text='• 표시'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• 표시'
p7 lvl=1 align=None - text='– 그래프 위 세로선 마커 — ant=green, post=orange (alpha 0.75)'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– 그래프 위 세로선 마커 — ant=green, post=orange'
p8 lvl=1 align=None - text='– 채널 헤더 우측 "a{antIdx} p{postIdx}" 컬러 텍스트'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– 채널 헤더 우측 "a{antIdx} p{postIdx}" 컬러 텍스'
p9 lvl=1 align=None - text='– 기존 raw max peak 숫자도 그대로 유지'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– 기존 raw max peak 숫자도 그대로 유지'
p10 lvl=0 align=None - text='• 저장 데이터는 변경 없음'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• 저장 데이터는 변경 없음'
p11 lvl=1 align=None - text='– measurement.json / labdb payload는 그대로 raw ADC'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– measurement.json / labdb payload는 그대로'
p12 lvl=1 align=None - text='– V4.1 결과는 화면 표시 전용 — 알고리즘 변경 시 같은 데이터로 재분석'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– V4.1 결과는 화면 표시 전용 — 알고리즘 변경 시 같은 데이터로'
- shape name='그림 4' type=PICTURE (13) autoshape=
geom: L=7315200 T=3161841 W=6946073 H=3101503 EMU | L=8.00in T=3.46in W=7.60in H=3.39in
line: color=inherit width_pt=None
======================================================================
SLIDE 6
layout: 1. 빈 화면
background: type=BACKGROUND (5)
shape_count: 4
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=402336 T=173736 W=6236208 H=457200 EMU | L=0.44in T=0.19in W=6.82in H=0.50in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=0 mB=45720
text(60)='Android App 진행 보고'
p0 lvl=0 align=None - text='Android App 진행 보고'
r0 font='나눔고딕 ExtraBold' sz=24.0 b=True i=None c=inherit t='Android App 진행 보고'
- shape name='TextBox 3' type=TEXT_BOX (17) autoshape=
geom: L=603504 T=868680 W=13240512 H=5586145 EMU | L=0.66in T=0.95in W=14.48in H=6.11in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=True mL=0 mR=91440 mT=45720 mB=45720
text(60)='사내 테스트 피드백 반영 — UX 정리 3건 및 오류 수정 | • 도넛차트 색깔 일관성 fix | – 증'
p0 lvl=0 align=None - text='사내 테스트 피드백 반영 — UX 정리 3건 및 오류 수정'
r0 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='사내'
r1 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r2 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='테스트'
r3 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r4 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='피드백'
r5 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r6 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='반영'
r7 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' — UX '
r8 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='정리'
r9 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' 3건'
r10 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t=' '
r11 font='나눔고딕 ExtraBold' sz=28.0 b=True i=None c=#222A33 t='및 오류 수정'
p1 lvl=0 align=None - text='• 도넛차트 색깔 일관성 fix'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='도넛차트'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='색깔'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='일관성'
r6 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' fix'
p2 lvl=1 align=None - text='– 증상: Single scan 309ml(노란색) 후 Auto scan 에서 색깔만 초록색으로 바뀌는 오'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='증상'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=': Single scan 309ml('
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='노란색'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=') 후 Auto scan '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='에서'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='색깔만'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='초록색으로'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='바뀌는 오류'
p3 lvl=2 align=None - text='· 표시값(maxVolumeMl)과 색깔(lastVolumeMl) source 불일치'
r0 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='· '
r1 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='표시값'
r2 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='('
r3 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='maxVolumeMl'
r4 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t=')과 '
r5 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='색깔'
r6 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='('
r7 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='lastVolumeMl'
r8 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t=') source '
r9 font='나눔고딕' sz=13.0 b=False i=None c=#55606C t='불일치'
p4 lvl=1 align=None - text='– 수정: bladderLevel 업데이트도 maxVolumeMl 기준으로 통일'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='수정'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=': '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='bladderLevel'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='업데이트도'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='maxVolumeMl'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='기준으로'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='통일'
p5 lvl=0 align=None - text='• Setup 화면 Auto Scan Interval 카드 추가'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• Setup '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='화면'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' Auto Scan Interval '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='카드'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' '
r5 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='추가'
p6 lvl=1 align=None - text='– PiezoPersonalizationView에 신규 카드 — 600~5000ms 슬라이더 + ±200m'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='PiezoPersonalizationView에'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='신규'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='카드'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' — 600~5000ms '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='슬라이더'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' + ±200ms '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='스테퍼'
p7 lvl=1 align=None - text='– 기존 MaxVolume / CatheterThreshold 카드와 동일 톤, 3카드 verticalSc'
r0 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='기존'
r2 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='MaxVolume'
r4 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' / '
r5 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='CatheterThreshold'
r6 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r7 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='카드와'
r8 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' '
r9 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='동일'
r10 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t=' 톤, 3카드 '
r11 font='나눔고딕' sz=15.0 b=False i=None c=#222A33 t='verticalScroll'
p8 lvl=0 align=None - text='• BLE 끊김 banner 제거 — Silent Reconnect'
r0 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='• BLE '
r1 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='끊김'
r2 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' banner '
r3 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t='제거'
r4 font='나눔고딕' sz=18.0 b=True i=None c=#1E88E5 t=' — Silent Reconnect'
p9 lvl=1 align=None - text='– PiezoMonitoring / VivamyoMonitoring 양쪽 banner 표시 제거'
r0 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='PiezoMonitoring'
r2 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t=' '
r3 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='/ '
r4 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='VivamyoMonitoring'
r5 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t=' 양쪽 '
r6 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='banner '
r7 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='표시 제거'
p10 lvl=1 align=None - text='– BleManager.scheduleAutoReconnect()가 이미 백그라운드 재연결 시도'
r0 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='BleManager.scheduleAutoReconnect'
r2 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='()'
r3 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='가 이미 백그라운드 '
r4 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='재연결'
r5 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t=' 시도'
p11 lvl=1 align=None - text='– 측정 일시정지 / 배터리 폴링 재시작 등 다른 처리는 그대로 유지'
r0 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='– '
r1 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='측정 일시정지 '
r2 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='/ '
r3 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='배터리 '
r4 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t='폴링'
r5 font='나눔고딕' sz=15.0 b=None i=None c=#222A33 t=' 재시작 등 다른 처리는 그대로 유지'
p12 lvl=0 align=None - text='• 첫 연결시 배터리 잔량 표시가 안되는 오류 수정'
r0 font='나눔고딕' sz=None b=True i=None c=#1E88E5 t='• '
r1 font='나눔고딕' sz=None b=True i=None c=#1E88E5 t='첫 '
r2 font='나눔고딕' sz=None b=True i=None c=#1E88E5 t='연결시'
r3 font='나눔고딕' sz=None b=True i=None c=#1E88E5 t=' 배터리 잔량 표시가 안되는 오류 수정'
p13 lvl=0 align=None - text=''
p14 lvl=0 align=None - text=''
p15 lvl=0 align=None - text=''
- shape name='그림 5' type=PICTURE (13) autoshape=
geom: L=2457674 T=5771838 W=6630325 H=1743318 EMU | L=2.69in T=6.31in W=7.25in H=1.91in
line: color=inherit width_pt=None
- shape name='그림 7' type=PICTURE (13) autoshape=
geom: L=10593990 T=1256249 W=2486422 H=5387248 EMU | L=11.59in T=1.37in W=2.72in H=5.89in
line: color=inherit width_pt=None
======================================================================
SLIDE 7
layout: 1. 빈 화면
background: type=BACKGROUND (5)
shape_count: 1
- shape name='TextBox 1' type=TEXT_BOX (17) autoshape=
geom: L=6243111 T=3530025 W=2144177 H=584775 EMU | L=6.83in T=3.86in W=2.34in H=0.64in
fill: type=BACKGROUND (5)
line: color=inherit width_pt=None
textframe: anchor=None wrap=False mL=0 mR=91440 mT=45720 mB=45720
text(60)='감사합니다'
p0 lvl=0 align=None - text='감사합니다'
r0 font='나눔고딕 ExtraBold' sz=32.0 b=True i=None c=inherit t='감사합니다'
+264
View File
@@ -0,0 +1,264 @@
"""Extract exact visual design info from the reference PPTX into _design_dump.txt."""
from pathlib import Path
import sys
from pptx import Presentation
from pptx.util import Emu
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE_TYPE
from pptx.oxml.ns import qn
PPT_DIR = Path(__file__).parent
SRC = PPT_DIR / "주간보고서_dwjang_20260604.pptx"
OUT = PPT_DIR / "_design_dump.txt"
def emu_to_in(v):
try:
return f"{Emu(v).inches:.2f}"
except Exception:
return "?"
def fmt_xy(left, top, width, height):
return (
f"L={left} T={top} W={width} H={height} EMU | "
f"L={emu_to_in(left)}in T={emu_to_in(top)}in "
f"W={emu_to_in(width)}in H={emu_to_in(height)}in"
)
def color_from_fill(fill):
"""Best-effort extraction of solid fill color as hex."""
try:
if fill.type is None:
return "none/inherit"
# 1 = SOLID per MSO_FILL_TYPE
if int(fill.type) == 1:
fc = fill.fore_color
try:
rgb = fc.rgb
if rgb is not None:
return f"#{str(rgb)}"
except Exception:
pass
try:
return f"theme:{fc.theme_color}"
except Exception:
pass
return f"type={fill.type}"
except Exception as e:
return f"err:{e.__class__.__name__}"
def color_from_line(line):
try:
c = line.color
try:
rgb = c.rgb
if rgb is not None:
return f"#{str(rgb)}"
except Exception:
pass
try:
return f"theme:{c.theme_color}"
except Exception:
return "inherit"
except Exception:
return "n/a"
def run_color(run):
try:
c = run.font.color
try:
rgb = c.rgb
if rgb is not None:
return f"#{str(rgb)}"
except Exception:
pass
try:
return f"theme:{c.theme_color}"
except Exception:
return "inherit"
except Exception:
return "inherit"
def slide_bg(slide):
"""Try to read slide background fill."""
try:
bg = slide.background
return color_from_fill(bg.fill)
except Exception as e:
return f"err:{e.__class__.__name__}"
def shape_type_name(shape):
try:
st = shape.shape_type
if st is None:
return "None"
return str(st)
except Exception:
return "?"
def autoshape_kind(shape):
try:
return str(shape.auto_shape_type)
except Exception:
return ""
def dump_text_frame(tf, out_lines, indent=" "):
for pi, para in enumerate(tf.paragraphs):
lvl = para.level
align = para.alignment
# Detect bullet via pPr/buChar/buAutoNum
pPr = para._pPr
bullet = "-"
if pPr is not None:
if pPr.find(qn("a:buNone")) is not None:
bullet = "noBul"
elif pPr.find(qn("a:buChar")) is not None:
bc = pPr.find(qn("a:buChar"))
bullet = f"buChar={bc.get('char')!r}"
elif pPr.find(qn("a:buAutoNum")) is not None:
bullet = "buAutoNum"
runs_info = []
full_txt = ""
for r in para.runs:
t = r.text or ""
full_txt += t
f = r.font
runs_info.append({
"text": t,
"name": f.name,
"size": (f.size.pt if f.size else None),
"bold": f.bold,
"italic": f.italic,
"color": run_color(r),
})
snippet = full_txt[:60].replace("\n", " ")
out_lines.append(
f"{indent}p{pi} lvl={lvl} align={align} {bullet} text={snippet!r}"
)
for ri, info in enumerate(runs_info):
tt = info["text"][:40].replace("\n", " ")
out_lines.append(
f"{indent} r{ri} font={info['name']!r} sz={info['size']} "
f"b={info['bold']} i={info['italic']} c={info['color']} t={tt!r}"
)
def dump_shape(shape, out_lines, indent=" "):
name = shape.name
st = shape_type_name(shape)
kind = autoshape_kind(shape) if st == "MSO_SHAPE_TYPE.AUTO_SHAPE (1)" else ""
try:
left, top, width, height = shape.left, shape.top, shape.width, shape.height
except Exception:
left = top = width = height = None
out_lines.append(
f"{indent}- shape name={name!r} type={st} autoshape={kind}"
)
out_lines.append(f"{indent} geom: {fmt_xy(left, top, width, height)}")
# Fill
try:
if shape.has_text_frame or st.startswith("MSO_SHAPE_TYPE.AUTO_SHAPE") or st == "MSO_SHAPE_TYPE.TEXT_BOX (17)":
fill_c = color_from_fill(shape.fill)
out_lines.append(f"{indent} fill: {fill_c}")
except Exception:
pass
# Line
try:
line = shape.line
lc = color_from_line(line)
try:
lw = line.width
lw_pt = (lw / 12700) if lw else None
except Exception:
lw_pt = None
out_lines.append(f"{indent} line: color={lc} width_pt={lw_pt}")
except Exception:
pass
# Picture
if st == "MSO_SHAPE_TYPE.PICTURE (13)":
try:
img = shape.image
out_lines.append(
f"{indent} picture: ext={img.ext} sha1={img.sha1[:10]} "
f"size_bytes={len(img.blob)} filename={getattr(img, 'filename', None)!r}"
)
except Exception as e:
out_lines.append(f"{indent} picture: err={e}")
# Text
if shape.has_text_frame:
tf = shape.text_frame
# vertical anchor
try:
va = tf.vertical_anchor
except Exception:
va = None
try:
wrap = tf.word_wrap
except Exception:
wrap = None
out_lines.append(
f"{indent} textframe: anchor={va} wrap={wrap} "
f"mL={tf.margin_left} mR={tf.margin_right} mT={tf.margin_top} mB={tf.margin_bottom}"
)
# Full text first 60 chars
full = (shape.text_frame.text or "").replace("\n", " | ")
out_lines.append(f"{indent} text(60)={full[:60]!r}")
dump_text_frame(tf, out_lines, indent=indent + " ")
# Group
if st == "MSO_SHAPE_TYPE.GROUP (6)":
out_lines.append(f"{indent} group children:")
for child in shape.shapes:
dump_shape(child, out_lines, indent=indent + " ")
def main():
prs = Presentation(SRC)
lines = []
lines.append(f"FILE: {SRC.name}")
lines.append(
f"SLIDE SIZE: W={prs.slide_width} H={prs.slide_height} EMU | "
f"W={emu_to_in(prs.slide_width)}in H={emu_to_in(prs.slide_height)}in"
)
lines.append(f"SLIDE COUNT: {len(prs.slides)}")
lines.append("")
# Layouts available
lines.append("LAYOUTS:")
for i, lay in enumerate(prs.slide_layouts):
lines.append(f" [{i}] {lay.name}")
lines.append("")
# Master fonts / theme colors (best effort)
try:
theme_xml = prs.slide_masters[0].element.getroottree()
except Exception:
pass
for si, slide in enumerate(prs.slides, start=1):
lines.append("=" * 70)
lines.append(f"SLIDE {si}")
lines.append(f" layout: {slide.slide_layout.name}")
lines.append(f" background: {slide_bg(slide)}")
lines.append(f" shape_count: {len(slide.shapes)}")
for shp in slide.shapes:
dump_shape(shp, lines, indent=" ")
lines.append("")
OUT.write_text("\n".join(lines), encoding="utf-8")
print(f"WROTE: {OUT} lines={len(lines)}")
if __name__ == "__main__":
main()
+439
View File
@@ -0,0 +1,439 @@
"""
주간보고서 자동 생성 — 2026-06-08(월) ~ 06-12(금) 작업분.
기존 양식 (주간보고서_dwjang_20260604.pptx) 의 슬라이드 마스터/레이아웃을
그대로 가져다 쓰고, 컨텐츠만 교체.
디자인 규칙 (기존 양식에서 추출):
- 16:9 화이트 마스터, 별도 헤더바/배경 데코 없음 (텍스트만)
- 컨텐츠 슬라이드 좌상단 "Android App 진행 보고" 24pt ExtraBold 러닝헤더
- 본문 textbox: L=0.66" T=0.95" W=14.48"
· 제목 28pt ExtraBold #222A33
· L1 "• " + 두 칸 · 18pt Bold + accent blue #1E88E5
· L2 "– " + 두 칸 · 15pt regular #222A33
· L3 "· " + 여섯 칸 · 13pt regular #55606C
- 스크린샷은 본문 우측 또는 하단에 카드/테두리 없이 배치
"""
from pathlib import Path
from copy import deepcopy
from lxml import etree
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
PPT_DIR = Path(__file__).parent
TEMPLATE = PPT_DIR / "주간보고서_dwjang_20260604.pptx"
OUT_PATH = PPT_DIR / "주간보고서_dwjang_20260611.pptx"
# 팔레트 (정확한 추출)
COLOR_TEXT = RGBColor(0x22, 0x2A, 0x33)
COLOR_TEXT_SUB = RGBColor(0x55, 0x60, 0x6C)
COLOR_ACCENT = RGBColor(0x1E, 0x88, 0xE5)
FONT_BODY = "나눔고딕"
FONT_HEAD = "나눔고딕 ExtraBold"
# ──────────────────────────────────────────────────────────────
# Helper: 기존 slide 다 지우기
# ──────────────────────────────────────────────────────────────
def clear_slides(prs):
"""Presentation 의 모든 슬라이드를 제거 + dangling slide part 도 drop."""
sld_id_lst = prs.slides._sldIdLst
prs_part = prs.part
# rId → slide part 매핑 수집
to_drop_rids = []
for sld_id in list(sld_id_lst):
rid = sld_id.get(
'{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id'
)
if rid:
to_drop_rids.append(rid)
sld_id_lst.remove(sld_id)
# 관련 part 와 rel 제거
for rid in to_drop_rids:
# part_related_by_id → 슬라이드 part 가져오기
try:
prs_part.drop_rel(rid)
except KeyError:
pass
# ──────────────────────────────────────────────────────────────
# 텍스트 설정 유틸
# ──────────────────────────────────────────────────────────────
def set_run(run, text, *, font=FONT_BODY, size=14, bold=False,
color=COLOR_TEXT):
run.text = text
run.font.name = font
# ea/cs 폰트도 같이 — 나눔고딕 깨짐 방지
rPr = run._r.get_or_add_rPr()
# eastAsia
ea = rPr.find('{http://schemas.openxmlformats.org/drawingml/2006/main}ea')
if ea is None:
ea = etree.SubElement(rPr, '{http://schemas.openxmlformats.org/drawingml/2006/main}ea')
ea.set('typeface', font)
cs = rPr.find('{http://schemas.openxmlformats.org/drawingml/2006/main}cs')
if cs is None:
cs = etree.SubElement(rPr, '{http://schemas.openxmlformats.org/drawingml/2006/main}cs')
cs.set('typeface', font)
run.font.size = Pt(size)
run.font.bold = bold
if color is not None:
run.font.color.rgb = color
def add_textbox(slide, left, top, width, height):
tb = slide.shapes.add_textbox(left, top, width, height)
tf = tb.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.05)
tf.margin_right = Inches(0.05)
tf.margin_top = Inches(0.0)
tf.margin_bottom = Inches(0.0)
return tb, tf
def add_running_header(slide):
"""좌상단 '주간 진행 보고 — Android App' 러닝헤더."""
tb, tf = add_textbox(slide, Inches(0.44), Inches(0.19),
Inches(8), Inches(0.5))
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.LEFT
run = p.add_run()
set_run(run, "Android App 진행 보고",
font=FONT_HEAD, size=24, bold=True, color=COLOR_TEXT)
def add_slide_title(tf, title_text):
"""본문 textbox 첫 문단으로 슬라이드 타이틀."""
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.LEFT
run = p.add_run()
set_run(run, title_text, font=FONT_HEAD, size=28, bold=True,
color=COLOR_TEXT)
def add_bullets(tf, lines):
"""lines = [(level, text)] 레벨별 prefix/폰트/컬러 자동."""
for level, text in lines:
p = tf.add_paragraph()
p.alignment = PP_ALIGN.LEFT
p.level = 0 # 우리는 모든 indent 를 prefix 로 처리 (양식과 동일)
run = p.add_run()
if level == 0:
prefix = "• "
size = 18; bold = True; color = COLOR_ACCENT
elif level == 1:
prefix = "– "
size = 15; bold = False; color = COLOR_TEXT
else:
prefix = "· "
size = 13; bold = False; color = COLOR_TEXT_SUB
set_run(run, prefix + text, font=FONT_BODY, size=size, bold=bold,
color=color)
def add_spacer(tf, count=1, size=18):
for _ in range(count):
p = tf.add_paragraph()
run = p.add_run()
set_run(run, "", font=FONT_BODY, size=size)
def add_caption(slide, text, left, top, width):
tb, tf = add_textbox(slide, left, top, width, Inches(0.32))
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
run = p.add_run()
set_run(run, text, font=FONT_BODY, size=11, color=COLOR_TEXT_SUB)
def fit_picture(slide, path, left, top, max_w, max_h, *, phone=True):
"""폰(1080:2340) 또는 태블릿(1200:1920) 비율 유지하며 박스 안에 fit."""
if not path.exists():
return None
aspect = (1080 / 2340) if phone else (1200 / 1920)
if max_w / max_h > aspect:
h = max_h
w = int(h * aspect)
else:
w = max_w
h = int(w / aspect)
cx = left + (max_w - w) // 2
cy = top + (max_h - h) // 2
return slide.shapes.add_picture(str(path), cx, cy, w, h)
# ──────────────────────────────────────────────────────────────
# 슬라이드 빌더
# ──────────────────────────────────────────────────────────────
def build_title_slide(prs):
# Layout 0 = "0. 제목 반전 슬라이드"
layout = prs.slide_layouts[0]
s = prs.slides.add_slide(layout)
# 제목 plchldr 위치는 기존 양식에 따라 우측 텍스트 블록
# 기존: L=7.24" T=2.95" W=8.32" H=2.05"
tb, tf = add_textbox(s, Inches(7.24), Inches(2.95),
Inches(8.32), Inches(2.05))
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.LEFT
run = p.add_run()
set_run(run, "주간 진행 보고", font=FONT_HEAD, size=32, bold=True,
color=COLOR_TEXT)
# 부제 (날짜) L=7.24" T=5.00"
tb2, tf2 = add_textbox(s, Inches(7.24), Inches(5.00),
Inches(8.32), Inches(0.6))
p2 = tf2.paragraphs[0]
p2.alignment = PP_ALIGN.LEFT
run2 = p2.add_run()
set_run(run2, "2026-06-08 (월) ~ 06-12 (금)",
font=FONT_BODY, size=18, bold=True, color=COLOR_TEXT_SUB)
return s
def build_content_slide(prs, title, bullets, *, body_h=Inches(7.5),
pics=None, captions=None):
"""
title: slide title (28pt)
bullets: [(level, text), ...]
pics: optional list of (path, left_in, top_in, w_in, h_in, phone_bool)
captions: optional list of (text, left_in, top_in, w_in)
"""
layout = prs.slide_layouts[1] # 1. 빈 화면
s = prs.slides.add_slide(layout)
add_running_header(s)
# 본문 textbox
tb, tf = add_textbox(s, Inches(0.66), Inches(0.95),
Inches(14.48), body_h)
add_slide_title(tf, title)
add_spacer(tf, 1)
add_bullets(tf, bullets)
# 그림
if pics:
for (path, left, top, w, h, phone) in pics:
fit_picture(s, path, Inches(left), Inches(top),
Inches(w), Inches(h), phone=phone)
if captions:
for (text, left, top, w) in captions:
add_caption(s, text, Inches(left), Inches(top), Inches(w))
return s
def build_closing_slide(prs):
layout = prs.slide_layouts[1]
s = prs.slides.add_slide(layout)
# 정중앙 "감사합니다"
tb, tf = add_textbox(s, Inches(6.83), Inches(3.86),
Inches(4.5), Inches(1.0))
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
run = p.add_run()
set_run(run, "감사합니다", font=FONT_HEAD, size=32, bold=True,
color=COLOR_TEXT)
return s
# ──────────────────────────────────────────────────────────────
# 메인
# ──────────────────────────────────────────────────────────────
prs = Presentation(str(TEMPLATE))
clear_slides(prs)
# Slide 1 — Title
build_title_slide(prs)
# Slide 2 — 핵심 요약
build_content_slide(prs,
title="이번 주 핵심 요약 — 탭 navigation · 설정 통합 · BLE 안정성",
bullets=[
(0, "4탭 셸 Navigation 도입 (Home / 측정 / 이력 / 설정)"),
(1, "기존 시퀀셜 화면 흐름 → 탭 기반 재편, sub-tab 으로 세부 화면 통합"),
(1, "탭 셸 노출 화면 / 위저드 풀스크린 분기 정책"),
(0, "이력 / 설정 탭 통합"),
(1, "이력 — 측정 + 배뇨일지 sub-tab"),
(1, "설정 — 개인화 + 알림 + 앱 정보 sub-tab"),
(1, "PiezoMonitoring overlay 의 user 설정은 모두 설정 탭으로 이동 (중복 제거)"),
(0, "BLE 안정성 — 다중 기기 페어링 (Clinical 한정) + 펌웨어 호환 체크"),
(1, "KnownDeviceStore — Clinical 흐름에서만 저장, 일반 사용자는 매 세션 fresh scan"),
(1, "Disconnect / Delete Pairing 분리 (회의 피드백 반영)"),
(1, "펌웨어 < VBTFW0118 / mid? 미응답 시 경고 배너 · mfv? fallback"),
(0, "Polish · Bug fix"),
(1, "첫 설치 시 Consent 화면 노출 fix · Dev Tools 영역 dev 모드 전용"),
(1, "설정 탭 개인화 카드 컴팩트 모드 (한 화면에 3카드 fit)"),
],
)
# Slide 3 — 4탭 셸
build_content_slide(prs,
title="4탭 셸 Navigation 도입 — MainShell + 자동 라우팅",
bullets=[
(0, "탭 셸 노출 화면 (showsTabShell)"),
(1, "HOME, PIEZO_MONITORING, MEASUREMENT_HISTORY, VOIDING_DIARY, SETTINGS"),
(1, "선택 탭은 현재 AppScreen 으로부터 자동 도출 — 별도 state 없음"),
(0, "탭 셸 숨김 (풀스크린 가이드형)"),
(1, "측정 위저드 — DeviceScan / Personalization / PlacementGuide / URINE_CAMERA"),
(1, "사전 화면 — Onboarding / Consent / Registration / PIN / Calibration"),
(1, "Clinical R&D 흐름 — ClinicalHome / ClinicalLive (별도 컨텍스트 보존)"),
(0, "탭별 진입 동작"),
(1, "Home → goHome() · NirsManager stop + demo state cleanup 포함"),
(1, "측정 → startFromHome() · 연결되어 있으면 PIEZO_MONITORING, 아니면 DEVICE_SCAN"),
(1, "이력 → HistoryTabView · 측정 sub-tab 진입"),
(1, "설정 → SettingsTabView · 개인화 sub-tab 진입"),
(0, "BackHandler 정정 — 셸 화면 Back 시 PIEZO_MONITORING → HOME 자연 복귀"),
],
body_h=Inches(7.5),
pics=[
(PPT_DIR / "Screenshot_20260611_165624_VesiScan Demo.jpg",
11.0, 1.1, 4.4, 7.5, True),
],
captions=[
("Home 탭 · 4탭 셸 적용 화면", 11.0, 8.6, 4.4),
],
)
# Slide 4 — 이력 탭 통합
build_content_slide(prs,
title="이력 탭 통합 — MeasurementHistory + VoidingDiary",
bullets=[
(0, "신규 HistoryTabView · SingleChoiceSegmentedButtonRow 로 sub-tab 전환"),
(1, "측정 — piezo 센서 측정 기록 (기존 History / Trends 내부 탭 유지)"),
(1, "배뇨일지 — 수동 입력 기록 (24h 의료지표 카드 포함)"),
(0, "기존 화면 정리"),
(1, "MeasurementHistoryView — 상단 바 (Back + 타이틀) 제거"),
(1, "VoidingDiaryView — Scaffold / TopAppBar / onBack 제거"),
(1, "탭 셸이 헤더 제공 → 화면 내 중복 헤더 모두 정리"),
(0, "라우팅 단순화"),
(1, "AppScreen.MEASUREMENT_HISTORY → 측정 sub-tab"),
(1, "AppScreen.VOIDING_DIARY → 배뇨일지 sub-tab"),
(1, "둘 다 HistoryTabView 진입점, initial sub-tab 만 다름"),
],
body_h=Inches(7.0),
pics=[
(PPT_DIR / "Screenshot_20260611_165644_VesiScan Demo.jpg",
8.7, 1.1, 3.3, 7.5, True),
(PPT_DIR / "Screenshot_20260611_165648_VesiScan Demo.jpg",
12.1, 1.1, 3.3, 7.5, True),
],
captions=[
("측정 sub-tab", 8.7, 8.6, 3.3),
("배뇨일지 sub-tab", 12.1, 8.6, 3.3),
],
)
# Slide 5 — 설정 탭 통합
build_content_slide(prs,
title="설정 탭 통합 — 개인화 + 알림 + 앱 정보",
bullets=[
(0, "신규 SettingsTabView · 3 sub-tab"),
(1, "개인화 — Max Volume / Catheter Threshold / Auto Scan Interval (+dev: Ant Offset)"),
(1, "알림 — ON/OFF · 간격 · 시간대 · 사운드/진동/팝업 (Reminder)"),
(1, "앱 정보 — 앱 버전 · 기기 연결 · 펌웨어 · (dev) Serial · Unpair"),
(0, "embedded 플래그로 위저드 / 설정 모드 분기"),
(1, "위저드 (embedded=false): 헤더 + Bladdy + Continue 버튼"),
(1, "설정 탭 (embedded=true): 카드만 노출, 컴팩트 사이즈, LaunchedEffect 로 자동 저장"),
(0, "Disconnect / Delete Pairing 분리"),
(1, "Disconnect (회색) — bond 유지, 다음 측정 시 PIN 없이 빠른 재연결"),
(1, "Delete Pairing (빨강) — 확인 다이얼로그 후 unbond + known list 제거 (destructive)"),
],
body_h=Inches(7.0),
pics=[
(PPT_DIR / "Screenshot_20260611_165653_VesiScan Demo.jpg",
8.0, 1.1, 2.4, 7.5, True),
(PPT_DIR / "Screenshot_20260611_165657_VesiScan Demo.jpg",
10.5, 1.1, 2.4, 7.5, True),
(PPT_DIR / "Screenshot_20260611_165701_VesiScan Demo.jpg",
13.0, 1.1, 2.4, 7.5, True),
],
captions=[
("개인화", 8.0, 8.6, 2.4),
("알림", 10.5, 8.6, 2.4),
("앱 정보", 13.0, 8.6, 2.4),
],
)
# Slide 6 — BLE 안정성
build_content_slide(prs,
title="BLE 안정성 — Clinical 다중 기기 + 펌웨어 호환 체크",
bullets=[
(0, "KnownDeviceStore — JSON 리스트로 다중 기기 영속화"),
(1, "getAll / add / remove / mostRecent · 구 단일 키 자동 마이그레이션"),
(1, "Clinical 흐름 (ClinicalSessionStore.inClinicalFlow) 에서만 저장"),
(1, "일반 사용자: 매 세션 fresh scan + pair · 저장 0개"),
(0, "DeviceScanView 2섹션 (Clinical 전용)"),
(1, "Saved Devices — 카드 탭 = PIN 없이 즉시 재연결 · 휴지통 = Delete Pairing"),
(1, "New Devices — 스캔된 기기 중 known 에 없는 것만"),
(0, "펌웨어 호환 체크"),
(1, "BleManager.MIN_FIRMWARE_BUILD = 118 · parseFirmwareBuild() 끝자리 숫자 비교"),
(1, "FirmwareWarningBanner — 구버전 (주황) / 응답 없음 (빨강) / 정상 (숨김)"),
(1, "rid: 파서 fixed-width 12B 슬라이스 보정 + mfv? 4초 후 fallback"),
],
body_h=Inches(6.5),
pics=[
# 펌웨어 경고 배너 실제 작동 (가로 태블릿)
(PPT_DIR / "Screenshot_20260611-154306.png",
9.5, 1.1, 6.0, 5.5, False),
],
captions=[
("펌웨어 경고 배너 작동 — VBTFW0116 → VBTFW0118 권장 안내", 9.5, 6.75, 6.0),
],
)
# Slide 7 — Bug fix / Polish
build_content_slide(prs,
title="Bug fix · Polish — Consent 화면 / Dev Tools / 컴팩트",
bullets=[
(0, "Consent 화면 — 첫 설치 시 정상 노출 fix"),
(1, "기존 버그: completeOnboarding() 가 hasConsented=true 자동 마킹 → CONSENT 스킵"),
(1, "수정: hasConsented 자동 마킹 제거 · loadState 분기에 !hasConsented 추가"),
(1, "정상 흐름: ONBOARDING → CONSENT → REGISTRATION → PIN_SETUP → HOME"),
(0, "Dev Tools 영역 gating (PiezoMonitoring)"),
(1, "Logs & Charts 토글 + ChannelPanel / BleDebugPanel / Scan Log → isDevMode 한정"),
(1, "일반 사용자: Spot / Catheterize 액션 영역까지만 노출 (간결)"),
(0, "설정 탭 개인화 카드 — 컴팩트 모드"),
(1, "embedded=true 시 카드 padding / 폰트 / Stepper 크기 절반 수준"),
(1, "3카드 (dev 면 4카드) 한 화면 내 fit"),
],
body_h=Inches(6.5),
pics=[
(PPT_DIR / "Screenshot_20260611_165608_VesiScan Demo.jpg",
9.5, 1.1, 3.0, 7.5, True),
(PPT_DIR / "Screenshot_20260611_165613_VesiScan Demo.jpg",
12.7, 1.1, 3.0, 7.5, True),
],
captions=[
("Consent 상단", 9.5, 8.6, 3.0),
("Consent — 의료정보 동의", 12.7, 8.6, 3.0),
],
)
# Slide 8 — 결과물 / 다음 주
build_content_slide(prs,
title="결과물 정리 · 다음 주 계획",
bullets=[
(0, "feature/tab-navigation · 14 커밋 (master 5100616 → b427fab+)"),
(1, "Stage 1 — 4탭 셸 + 라우팅 (MainShell + AppScreen.SETTINGS)"),
(1, "Stage 2 — 이력 탭 sub-tab 통합 (HistoryTabView)"),
(1, "Stage 3 — 설정 탭 sub-tab 통합 (SettingsTabView + embedded mode)"),
(1, "Stage 4 — PiezoMonitoring 상단 바 · overlay 정리"),
(1, "BLE — 다중 기기 (Clinical) · Disconnect/Delete 분리 · 펌웨어 호환 체크"),
(0, "demo-final cherry-pick (1.1.0-design → 1.1.1-design)"),
(1, "BLE 안정성 패치만 backport — 다중 기기 + 펌웨어 호환 + rid 파서 fix"),
(0, "신규 문서 — docs/APP_STRUCTURE.md"),
(1, "탭 다이어그램 · AppScreen 카탈로그 · 핵심 파일 위치 · Back 분기표"),
(0, "다음 주 계획"),
(1, "디바이스 검증 — 일반/dev 모드 전체 흐름 풀 테스트 · Clinical 다중 기기 스위치"),
(1, "머지 정책 — feature/tab-navigation → master 머지 시점 결정"),
(1, "UX 폴리시 — Registration 필드 점검 · 빈 상태 onboarding 안내"),
],
)
# Slide 9 — 감사합니다
build_closing_slide(prs)
prs.save(str(OUT_PATH))
print(f"Saved: {OUT_PATH}")
print(f"Slides: {len(prs.slides)}")