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
+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()