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:
48
src/features/autoScale/__tests__/calculateScale.test.ts
Normal file
48
src/features/autoScale/__tests__/calculateScale.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
22
src/features/autoScale/calculateScale.ts
Normal file
22
src/features/autoScale/calculateScale.ts
Normal 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 };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user