From 1c29b4ee8ac8b02ec1530b7eef9ff1b1c43fd425 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 24 May 2026 15:55:12 +0900 Subject: [PATCH] =?UTF-8?q?feat(autoScale):=20calculateScale=20TDD=20(v0-p?= =?UTF-8?q?lan=20Task=208=20=E2=80=94=20=ED=95=B5=EC=8B=AC=20=EC=B0=A8?= =?UTF-8?q?=EB=B3=84=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/features/autoScale/calculateScale.ts: - 사진 어깨 픽셀 / 카메라 옷 너비 픽셀 = 스케일 - 카메라 옷 너비 ≤ 0 → scale=1, confidence=0 폴백 - 비율 [0.3, 3.0] 클램핑 → 클램핑 시 confidence=0.5, 정상 시 confidence=1.0 src/features/autoScale/__tests__/calculateScale.test.ts: - 6 케이스 TDD: 정상비율 / 0폴백 / 음수폴백 / 상한클램핑 / 하한클램핑 / 클램핑X 검증: - npx jest src/features/autoScale: 6 passed - 전체 npm test: 5 suites / 15 tests passed (sanity 1 + pose 1 + photoValidation 3 + maskSplit 4 + autoScale 6) - npx tsc --noEmit: 무에러 남은 부분 (v0-plan Task 8): - detectClothWidthPx (네이티브 saliency 호출) — v0 폴백 scale=1, 보강은 Task 12에서 - usePinchScale hook (gesture-handler) — UI 통합 시점에 작성 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/features/autoScale/.gitkeep | 0 .../__tests__/calculateScale.test.ts | 48 +++++++++++++++++++ src/features/autoScale/calculateScale.ts | 22 +++++++++ 3 files changed, 70 insertions(+) delete mode 100644 src/features/autoScale/.gitkeep create mode 100644 src/features/autoScale/__tests__/calculateScale.test.ts create mode 100644 src/features/autoScale/calculateScale.ts diff --git a/src/features/autoScale/.gitkeep b/src/features/autoScale/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/features/autoScale/__tests__/calculateScale.test.ts b/src/features/autoScale/__tests__/calculateScale.test.ts new file mode 100644 index 0000000..fdea04e --- /dev/null +++ b/src/features/autoScale/__tests__/calculateScale.test.ts @@ -0,0 +1,48 @@ +import { calculateScale } from '../calculateScale'; + +describe('calculateScale', () => { + it('사진 어깨 / 카메라 옷 너비 비율을 그대로 반환한다', () => { + const result = calculateScale({ + photoShoulderPx: 200, + cameraClothWidthPx: 400, + }); + expect(result.scale).toBe(0.5); + expect(result.confidence).toBeGreaterThan(0); + }); + + it('카메라 옷 너비가 0이면 scale=1, confidence=0으로 폴백한다', () => { + const result = calculateScale({ + photoShoulderPx: 200, + cameraClothWidthPx: 0, + }); + expect(result.scale).toBe(1); + expect(result.confidence).toBe(0); + }); + + it('카메라 옷 너비가 음수여도 0과 동일하게 폴백한다', () => { + const result = calculateScale({ + photoShoulderPx: 200, + cameraClothWidthPx: -5, + }); + expect(result.scale).toBe(1); + expect(result.confidence).toBe(0); + }); + + it('비율이 3.0을 초과하면 3.0으로 클램핑 + confidence 낮춤', () => { + const result = calculateScale({ photoShoulderPx: 200, cameraClothWidthPx: 10 }); + expect(result.scale).toBe(3.0); + expect(result.confidence).toBeLessThan(1); + }); + + it('비율이 0.3 미만이면 0.3으로 클램핑 + confidence 낮춤', () => { + const result = calculateScale({ photoShoulderPx: 10, cameraClothWidthPx: 200 }); + expect(result.scale).toBe(0.3); + expect(result.confidence).toBeLessThan(1); + }); + + it('클램핑 안 되는 정상 비율이면 confidence=1', () => { + const result = calculateScale({ photoShoulderPx: 200, cameraClothWidthPx: 200 }); + expect(result.scale).toBe(1.0); + expect(result.confidence).toBe(1); + }); +}); diff --git a/src/features/autoScale/calculateScale.ts b/src/features/autoScale/calculateScale.ts new file mode 100644 index 0000000..86b961a --- /dev/null +++ b/src/features/autoScale/calculateScale.ts @@ -0,0 +1,22 @@ +export interface ScaleInput { + photoShoulderPx: number; + cameraClothWidthPx: number; +} + +export interface ScaleResult { + scale: number; + confidence: number; +} + +export const MIN_SCALE = 0.3; +export const MAX_SCALE = 3.0; + +export function calculateScale({ photoShoulderPx, cameraClothWidthPx }: ScaleInput): ScaleResult { + if (cameraClothWidthPx <= 0) { + return { scale: 1, confidence: 0 }; + } + const raw = photoShoulderPx / cameraClothWidthPx; + const scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, raw)); + const confidence = scale === raw ? 1 : 0.5; + return { scale, confidence }; +}