diff --git a/docs/superpowers/specs/2026-06-24-roof-shape-pattern-catalog-design.md b/docs/superpowers/specs/2026-06-24-roof-shape-pattern-catalog-design.md new file mode 100644 index 00000000..ad862731 --- /dev/null +++ b/docs/superpowers/specs/2026-06-24-roof-shape-pattern-catalog-design.md @@ -0,0 +1,134 @@ +# 지붕형상 패턴 카탈로그 — 설계 (Design Spec) + +- 일자: 2026-06-24 +- 브랜치: `feature/pv-btob` +- 상태: 설계 확정 (구현 대기) + +## 1. 목적 / 배경 + +PDF 도면 업로드 → 외벽선 미확정 분석 → **외벽선 자동 확정 팝업**(`AutoOuterLineFixSetting`)까지는 구현 완료. 다음 목표는 **분석된 도면으로부터 지붕 형상을 유추해, 지붕형상 설정 팝업(`RoofShapeSetting`)의 8개 패턴 버튼 중 매칭되는 것을 자동 클릭**하는 기능이다. + +그 자동 매칭을 구현하려면, 먼저 **8개 패턴 각각이 "무엇인지"를 매칭 가능한 시그니처로 정의·저장**해 두어야 한다. 본 스펙은 그 **카탈로그 데이터 모듈**만을 다룬다. + +### 범위 경계 (중요) +- **이번 작업(본 스펙)**: 8패턴 카탈로그 상수 모듈 1개 + 경량 접근자 + distinct 자기검증. +- **다음 작업(별도)**: ① PDF/Gemini 분석을 확장해 지붕형상 descriptor 산출(屋根伏図의 棟/軒先/ケラバ → 용마루·흐름), ② `matchRoofShapePattern(descriptor) → shapeNum` 매칭 로직, ③ 매칭된 버튼 자동 클릭/적용. +- 본 작업에서 **매칭 함수는 구현하지 않는다.** descriptor 계약(스키마)만 명시해 카탈로그가 다음 단계와 짝이 맞도록 한다. (CLAUDE.md *Simplicity First*) + +## 2. 8패턴의 실제 구조 (코드 검증) + +근거: `src/hooks/roofcover/useRoofShapeSetting.js` 의 `shapeMenu`(L164~173) 와 `handleSave`(L184~486) — `saveRidge` / `saveAPattern` / `saveBPattern` / case 4~8. + +| shapeNum | 버튼 nameKey | 지붕유형 | 용마루 | 변 역할 배정 (코드 근거) | 흐름방향(화면축) | +|---|---|---|---|---|---| +| 1 | `modal.roof.shape.setting.ridge` (용마루) | 寄棟 hip | 모서리 hip | **전 변 = 처마**(`saveRidge`) | top·bottom·left·right | +| 2 | `modal.roof.shape.setting.patten.a` (패턴A) | 切妻 gable | 가로 | 상하=처마 / 좌우=케라바(`saveAPattern`) | top·bottom | +| 3 | `modal.roof.shape.setting.patten.b` (패턴B) | 切妻 gable | 세로 | 좌우=처마 / 상하=케라바(`saveBPattern`) | left·right | +| 4 | `modal.roof.shape.setting.side` (변별로 설정) | 수동 | 없음 | 변마다 수동(처마/케라바/벽/팔작/반절처/한쪽흐름) | — | +| 5 | `commons.west` (서) | 片流れ shed | shed | 처마=bottom, shed=top, 케라바=좌우 (case 5) | bottom | +| 6 | `commons.east` (동) | 片流れ shed | shed | 처마=top, shed=bottom, 케라바=좌우 (case 6) | top | +| 7 | `commons.south` (남) | 片流れ shed | shed | 처마=right, shed=left, 케라바=상하 (case 7) | right | +| 8 | `commons.north` (북) | 片流れ shed | shed | 처마=left, shed=right, 케라바=상하 (case 8) | left | + +### 5~8 화면축 ↔ 버튼 정확 매핑 (전사 근거) +정규 winding(`outerLines[0].direction === 'bottom'`) 기준. `case 4` 의 SHED `direction` 분기(bottom→east, top→west, left→south, right→north)와 교차검증 일치: + +- 처마(flow) **BOTTOM** → 버튼 **5 (서/west)** +- 처마 **TOP** → 버튼 **6 (동/east)** +- 처마 **RIGHT** → 버튼 **7 (남/south)** +- 처마 **LEFT** → 버튼 **8 (북/north)** + +> winding 이 다르면 case 5~8 의 `else` 분기가 top/bottom(또는 left/right)을 미러링한다. `initLineSetting`(L488~516)이 `outerLines[0].direction==='right'` 일 때 top↔bottom 을 스왑해 정규화하며, PDF 임포트 좌표는 `usePdfImport.ensureCounterClockwise` 로 CCW 정규화된다. 카탈로그는 **정규 winding 기준 canonical 값**을 기록하고, 다음 단계 매칭기는 동일 정규 프레임의 flow 를 산출해야 한다. + +## 3. 시그니처 어휘 (방위적용 前 → 화면축 기준) + +지붕형상 설정 시점에는 실제 방위가 없다 — `방위적용`(`MENU.MODULE_CIRCUIT_SETTING.PLAN_ORIENTATION`)은 모듈/회로 단계다. 따라서: + +- 흐름방향은 **화면축 4방향**(`top/bottom/left/right`)으로 인코딩한다. +- 5~8 의 `서/동/남/북`은 코드가 쓰는 **앱 고유 라벨**(`direction: 'west'|'east'|'south'|'north'`)로 그대로 보존한다(버튼 정체성). + +## 4. 데이터 모듈 설계 + +신규 파일: **`src/common/roofShapePattern.js`** + +```js +// 화면축 흐름방향 (방위적용 前) +export const ROOF_FLOW = { TOP: 'top', BOTTOM: 'bottom', LEFT: 'left', RIGHT: 'right' } + +// 지붕 유형 +export const ROOF_SHAPE_TYPE = { HIP: 'hip', GABLE: 'gable', SHED: 'shed', MANUAL: 'manual' } + +// 용마루 방향 +export const ROOF_RIDGE = { HIP: 'hip', HORIZONTAL: 'horizontal', VERTICAL: 'vertical', SHED: 'shed', NONE: 'none' } + +/** + * 지붕형상 설정 팝업(RoofShapeSetting)의 8개 버튼 카탈로그. + * shapeNum 은 useRoofShapeSetting.shapeMenu 의 id 이자 자동 클릭 대상(= setShapeNum 값). + * 매칭 키: type + ridge + flows(집합). 8개가 상호 배타적이라 flows 집합만으로도 유일 식별 가능. + */ +export const ROOF_SHAPE_PATTERNS = [ + { shapeNum: 1, key: 'ridge', nameKey: 'modal.roof.shape.setting.ridge', + type: 'hip', ridge: 'hip', flows: ['top', 'bottom', 'left', 'right'], autoMatchable: true, + note: '寄棟 — 전 변 처마, 사방 흐름' }, + { shapeNum: 2, key: 'patternA', nameKey: 'modal.roof.shape.setting.patten.a', + type: 'gable', ridge: 'horizontal', flows: ['top', 'bottom'], autoMatchable: true, + note: '切妻 — 상하 처마/좌우 케라바, 가로 용마루' }, + { shapeNum: 3, key: 'patternB', nameKey: 'modal.roof.shape.setting.patten.b', + type: 'gable', ridge: 'vertical', flows: ['left', 'right'], autoMatchable: true, + note: '切妻 — 좌우 처마/상하 케라바, 세로 용마루' }, + { shapeNum: 5, key: 'west', nameKey: 'commons.west', + type: 'shed', ridge: 'shed', flows: ['bottom'], direction: 'west', autoMatchable: true }, + { shapeNum: 6, key: 'east', nameKey: 'commons.east', + type: 'shed', ridge: 'shed', flows: ['top'], direction: 'east', autoMatchable: true }, + { shapeNum: 7, key: 'south', nameKey: 'commons.south', + type: 'shed', ridge: 'shed', flows: ['right'], direction: 'south', autoMatchable: true }, + { shapeNum: 8, key: 'north', nameKey: 'commons.north', + type: 'shed', ridge: 'shed', flows: ['left'], direction: 'north', autoMatchable: true }, + // 변별로 설정 — 자동매칭 비대상(폴백). 깨끗한 매칭 실패 시 후보로만 표시. + { shapeNum: 4, key: 'side', nameKey: 'modal.roof.shape.setting.side', + type: 'manual', ridge: 'none', flows: [], autoMatchable: false }, +] + +// 경량 접근자 +export const getRoofShapePatternByShapeNum = (shapeNum) => + ROOF_SHAPE_PATTERNS.find((p) => p.shapeNum === shapeNum) || null +``` + +설계 원칙: +- 변 역할 배정 로직(처마/케라바 실제 적용)은 **`useRoofShapeSetting` 에 그대로 둔다.** 카탈로그는 **매칭 룩업 테이블** 역할만 하며 역할 배정 로직을 중복 보유하지 않는다(중복 = 유지보수 리스크). +- 자동 클릭은 다음 단계에서 `shapeNum` → `setShapeNum(shapeNum)` → 기존 `handleSave` 경로로 처리한다(별도 신규 적용 로직 불필요). + +## 5. 다음 단계 계약 (본 작업 미구현, 스펙에만 명시) + +``` +// PDF 분석 확장이 산출할 지붕형상 서술자 +RoofShapeDescriptor = { + type: 'hip' | 'gable' | 'shed' | 'manual', + ridge: 'hip' | 'horizontal' | 'vertical' | 'shed' | 'none', + flows: Array<'top'|'bottom'|'left'|'right'>, // 정규 winding 화면축 + direction?: 'west'|'east'|'south'|'north', // shed 일 때 + confidence?: number, // 0~1 +} + +// 다음 작업에서 구현: +matchRoofShapePattern(descriptor) -> shapeNum | null // flows 집합 + type/ridge 대조, 실패 시 4(수동) 폴백 or null +``` + +## 6. 검증 + +테스트 러너 미연동(CLAUDE.md). 따라서: +- **distinct 불변식**: 8개 항목의 `flows` 집합(+`type`,`ridge`)이 상호 유일함을 보장. 모듈 내 순수 헬퍼 또는 주석으로 문서화(불변식 명시). 별도 테스트 프레임워크는 도입하지 않는다. +- `yarn lint` 통과(에러 0, 경고 최소화) + 빌드 통과. +- Prettier: 싱글쿼트, **세미콜론 없음**, tabWidth 2, printWidth 150. + +## 7. 영향 범위 / 변경 파일 + +- **신규**: `src/common/roofShapePattern.js` +- **무변경**: `useRoofShapeSetting.js`, `RoofShapeSetting.jsx`, Gemini route, `usePdfImport.js` — 본 작업은 데이터 추가만, 기존 플로우 미변경. +- (선택) 문서: 본 스펙으로 충분. CLAUDE.md 갱신은 다음 매칭 단계에서 함께. + +## 8. 리스크 / 미해결 + +- 5~8 winding 의존성: canonical 값으로 기록하되, 다음 매칭기가 동일 정규 프레임의 flow 를 생성해야 정확. (분석 확장 작업의 책임으로 이관.) +- L자·凹凸 복합 footprint: 1·2·3·5~8 의 깔끔한 매칭은 직사각형 계열을 가정. 복합형은 패턴 4(수동) 폴백 — 폴백 판정 정책은 다음 단계. +- 본 카탈로그는 **버튼 식별/매칭 전용**이며, 실제 지붕 생성은 기존 `handleSave` 가 담당한다.