feat(autoScale): calculateScale TDD (v0-plan Task 8 — 핵심 차별화)

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-24 15:55:12 +09:00
parent 8ec53d95f0
commit 1c29b4ee8a
3 changed files with 70 additions and 0 deletions

View File

@@ -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);
});
});

View File

@@ -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 };
}