From bcdd630a00a2615d324dd06f272fa79a7b619d41 Mon Sep 17 00:00:00 2001 From: sangwook yoo Date: Wed, 24 Jun 2026 13:54:15 +0900 Subject: [PATCH] =?UTF-8?q?feat(roof-shape):=20=EC=A7=80=EB=B6=95=ED=98=95?= =?UTF-8?q?=EC=83=81=20=ED=8C=A8=ED=84=B4=20=EB=A7=A4=EC=B2=98=20matchRoof?= =?UTF-8?q?ShapePattern=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : - descriptor(type/ridge/flows)→shapeNum|null 순수 매칭 함수 - flows 집합 1차 키 + type/ridge guard(모순 거부, 결측 관대) - 폴백 null(autoMatchable=false인 4 비반환), confidence 무시 - Gemini 분석 확장(B)·자동 클릭 배선(C)은 다음 작업으로 분리 Co-Authored-By: Claude Opus 4.8 --- src/common/roofShapePattern.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/common/roofShapePattern.js b/src/common/roofShapePattern.js index 1a4a1abc..abba02a6 100644 --- a/src/common/roofShapePattern.js +++ b/src/common/roofShapePattern.js @@ -104,3 +104,25 @@ export const ROOF_SHAPE_PATTERNS = [ // shapeNum 으로 카탈로그 항목 조회. 없으면 null. export const getRoofShapePatternByShapeNum = (shapeNum) => ROOF_SHAPE_PATTERNS.find((pattern) => pattern.shapeNum === shapeNum) || null + +// descriptor(RoofShapeDescriptor) → 매칭되는 shapeNum | null. +// 매칭 키: flows 집합(1차) + type/ridge guard(모순 거부). confidence 는 무시(임계값 정책은 호출자). +// 실패·모순·무효 입력은 모두 null — autoMatchable:false(shapeNum 4)는 절대 반환하지 않는다. +export const matchRoofShapePattern = (descriptor) => { + if (!descriptor || typeof descriptor !== 'object') return null + const { type, ridge, flows } = descriptor + if (!Array.isArray(flows)) return null + + const FLOW_VALUES = Object.values(ROOF_FLOW) + if (flows.some((flow) => !FLOW_VALUES.includes(flow))) return null + + const flowKey = JSON.stringify([...new Set(flows)].sort()) + const candidate = ROOF_SHAPE_PATTERNS.find((pattern) => pattern.autoMatchable && JSON.stringify([...pattern.flows].sort()) === flowKey) + if (!candidate) return null + + // type/ridge 가 주어졌는데 후보와 모순이면 거부(결측이면 flows 만으로 통과 — 관대) + if (type && type !== candidate.type) return null + if (ridge && ridge !== candidate.ridge) return null + + return candidate.shapeNum +}