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:
@@ -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)}")
|
||||
Reference in New Issue
Block a user