docs(roof-shape): 지붕형상 패턴 카탈로그 설계 스펙 추가
[작업내용] : - PDF 분석→지붕형상 자동 매칭의 선행 단계로 8패턴 카탈로그 설계 정의 - 매칭 키: type+ridge+flows(화면축) 시그니처, flows 집합만으로 8패턴 유일 식별 - 5~8 화면축↔버튼 매핑을 useRoofShapeSetting case 4~8 에서 1:1 전사 - 매칭 함수·PDF 분석 확장은 다음 작업으로 분리(계약만 명시) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ca2b0c3dae
commit
105c8470ad
@ -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` 가 담당한다.
|
||||
Loading…
x
Reference in New Issue
Block a user