docs(claude): PDF 분석 provider 토글 구조 반영

[작업내용] :
- PDF 도면 임포트 섹션에 src/lib/pdf-analyzer provider 모듈(PDF_ANALYZER_PROVIDER 토글, openrouter 경로의 json_schema/native 고정/inline base64 제약) 기술

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
nalpari 2026-07-02 11:16:01 +09:00
parent 8c650b2010
commit 22d690655b

View File

@ -56,7 +56,7 @@ A modal under `components/floor-plan/modal/<feature>` pairs with a hook under `h
**Plan persistence** (serialize/restore) is in `src/lib/canvas.js` and `src/util/canvas-util.js`. Recent commits (`a5e794b3`, `2aef32e0`) added a three-layer guard (entry / cache / save) and `SizeSetting` input guards against 0-area apertures/shadows/dormers — when touching plan load/save or size inputs, do not weaken or remove these guards without understanding the failure mode (`InvalidStateError` from corrupted plans).
**PDF 도면 임포트** — 배치면 초기설정 모달의 PDF 입력 모드 → `src/app/api/gemini/floor-plan/route.js`(Gemini 분석, 세션 인증 + 사용자별 분당 5회 레이트리밋) → `src/hooks/pdf-import/usePdfImport.js`. **분석 직후 외벽선은 항상 미확정으로만 그린다**(`PlacementShapeSetting.handleSaveBtn``applyOuterline(mmPoints, { autoFix:false })``useLine.addLine` 경유 outerLine 생성(attributes/planeSize/fontSize) + `outerLinePoint` 정점 circle, `outerLineFix=false`). 그 후 `handleSaveBtn``setSelectedMenu('outline')` + `setCurrentMenu(MENU.ROOF_COVERING.AUTO_OUTERLINE_FIX)` 단일 전환만 한다 — `MenuDepth01``currentMenu` 변화를 감지해 `useMenu.handleMenu``AUTO_OUTERLINE_FIX` case 로 **'외벽선 자동 확정' 팝업(`AutoOuterLineFixSetting`)** 을 자동으로 띄운다. **확정 여부 분기는 이 팝업의 스위치(`autoOuterLineFixState`, 기본 ON) + [확인]** 에서 한다(`handleConfirm`): **ON** — 외벽선/정점/points 는 이미 `applyOuterline` 이 그려둔 상태라 `canvas.outerLineFix=true` + `setOuterLineFix(true)` 플래그만 켜면 수동 확정(`useOuterLineWall.handleFix`) 직후와 동일해진다(WALL/ROOF 폴리곤은 지붕형상 설정 적용 시점에 생성). 미확정으로 저장된 직전 상태를 덮어쓰도록 `saveCanvas` 한 뒤 `setCurrentMenu(ROOF_SHAPE_SETTINGS)`. **OFF** — 미확정 유지 + `setCurrentMenu(EXTERIOR_WALL_LINE)` 로 외벽선 확정 팝업(`WallLineSetting`)으로 보낸다. **팝업 전환은 `currentMenu` 메커니즘으로만** — 직접 `addPopup` 하지 말 것(MenuDepth01 의 `closeAll` 과 충돌). 신규 메뉴 `AUTO_OUTERLINE_FIX``subMenusState` 에 노출하지 않는 일회성 팝업이다. ⚠️ `basicSettingSave({ skipSideEffects:true })` 는 메뉴를 건드리지 않아야 한다 — `setMenuByRoofSize``if (!skipSideEffects)` 밖에 있으면 async 저장(`await post`) 직후 `currentMenu``EXTERIOR_WALL_LINE` 로 덮어써, AUTO_OUTERLINE_FIX 팝업 대신 WallLineSetting 으로 되돌아간다(2026-06-21 버그). **좌표에 축소 scale 을 곱하지 말 것** — 'QLine 길이×10 = 표시 mm' 불변식이 깨진다. 화면맞춤은 `viewportTransform` 줌 + `canvasZoomState` 동기화로만 처리한다. **지붕형상 자동 선택 + 설정완료 자동 적용** — Gemini 응답은 `roofShape`(RoofShapeDescriptor `{type,ridge,flows,confidence?}`, route.js 가 `normalizeRoofShape` 로 어휘 정규화)를 추가로 반환한다. `analyzePdfAndApply` 가 이를 `pdfRoofShapeDescriptorState`(outerLineAtom)에 저장(분석 시작 시 null 리셋)하고, ROOF_SHAPE_SETTINGS 진입 시 `useRoofShapeSetting` 마운트 effect 가 `matchRoofShapePattern`(`@/common/roofShapePattern`, flows 집합+type/ridge guard)으로 매칭해 **버튼을 `setShapeNum` 으로 선택**하고, `shapeNum` 이 matched 로 커밋된 뒤 별도 effect(`autoSaveShapeNumRef` 가드, deps `[shapeNum]`)가 **`handleSave`(설정완료)를 자동 클릭**해 지붕형상까지 적용한다(2026-06-25 사용자 지시로 'pre-select만 → 설정완료 자동 적용'으로 전환). ⚠️ **마운트 effect 에서 `setShapeNum(matched)` 직후 같은 틱에 `handleSave` 를 호출하지 말 것**`handleSave``shapeNum` 을 클로저로 읽어 stale(기본값 1) 로 잘못된 패턴이 적용된다. 반드시 `[shapeNum]` effect 로 커밋 후 실행. consume-once 로 atom 리셋(비-PDF 재진입 stale 방지). 매처 null(복합형)·roofShape 누락은 자동선택/적용 없이 graceful(수동 선택). 매칭 결과는 콘솔에 `✅ N번 버튼 「…」 매칭 → 자동 선택 + 설정완료` / `❌ 매칭되는 버튼 없음`(NEXT_PUBLIC_ENABLE_LOGGING=true)으로 명시. ⚠️ Gemini 의 실제 `roofShape` 출력 품질(특히 片流れ flows 방향)은 런타임 수동 QA·프롬프트 튜닝 영역.
**PDF 도면 임포트** — 배치면 초기설정 모달의 PDF 입력 모드 → `src/app/api/gemini/floor-plan/route.js`(세션 인증 + 사용자별 분당 5회 레이트리밋) → `src/hooks/pdf-import/usePdfImport.js`. 분석 호출은 `src/lib/pdf-analyzer/` provider 모듈이 담당 — env `PDF_ANALYZER_PROVIDER`(gemini|openrouter, 기본 gemini)로 서버측 선택, 클라이언트 무변경. gemini.js 는 Files-API fallback + `MEDIA_RESOLUTION_HIGH`, openrouter.js 는 openai SDK(baseURL=openrouter.ai) + `OPENROUTER_MODEL`(기본 anthropic/claude-fable-5) + `response_format: json_schema(strict)` + `plugins: file-parser/native` 명시 고정(OCR 엔진으로의 조용한 전환 방지). OpenRouter 경로는 Files-API 대체가 없어 항상 inline base64(대용량은 provider 에러로 표면화). **분석 직후 외벽선은 항상 미확정으로만 그린다**(`PlacementShapeSetting.handleSaveBtn``applyOuterline(mmPoints, { autoFix:false })``useLine.addLine` 경유 outerLine 생성(attributes/planeSize/fontSize) + `outerLinePoint` 정점 circle, `outerLineFix=false`). 그 후 `handleSaveBtn``setSelectedMenu('outline')` + `setCurrentMenu(MENU.ROOF_COVERING.AUTO_OUTERLINE_FIX)` 단일 전환만 한다 — `MenuDepth01``currentMenu` 변화를 감지해 `useMenu.handleMenu``AUTO_OUTERLINE_FIX` case 로 **'외벽선 자동 확정' 팝업(`AutoOuterLineFixSetting`)** 을 자동으로 띄운다. **확정 여부 분기는 이 팝업의 스위치(`autoOuterLineFixState`, 기본 ON) + [확인]** 에서 한다(`handleConfirm`): **ON** — 외벽선/정점/points 는 이미 `applyOuterline` 이 그려둔 상태라 `canvas.outerLineFix=true` + `setOuterLineFix(true)` 플래그만 켜면 수동 확정(`useOuterLineWall.handleFix`) 직후와 동일해진다(WALL/ROOF 폴리곤은 지붕형상 설정 적용 시점에 생성). 미확정으로 저장된 직전 상태를 덮어쓰도록 `saveCanvas` 한 뒤 `setCurrentMenu(ROOF_SHAPE_SETTINGS)`. **OFF** — 미확정 유지 + `setCurrentMenu(EXTERIOR_WALL_LINE)` 로 외벽선 확정 팝업(`WallLineSetting`)으로 보낸다. **팝업 전환은 `currentMenu` 메커니즘으로만** — 직접 `addPopup` 하지 말 것(MenuDepth01 의 `closeAll` 과 충돌). 신규 메뉴 `AUTO_OUTERLINE_FIX``subMenusState` 에 노출하지 않는 일회성 팝업이다. ⚠️ `basicSettingSave({ skipSideEffects:true })` 는 메뉴를 건드리지 않아야 한다 — `setMenuByRoofSize``if (!skipSideEffects)` 밖에 있으면 async 저장(`await post`) 직후 `currentMenu``EXTERIOR_WALL_LINE` 로 덮어써, AUTO_OUTERLINE_FIX 팝업 대신 WallLineSetting 으로 되돌아간다(2026-06-21 버그). **좌표에 축소 scale 을 곱하지 말 것** — 'QLine 길이×10 = 표시 mm' 불변식이 깨진다. 화면맞춤은 `viewportTransform` 줌 + `canvasZoomState` 동기화로만 처리한다. **지붕형상 자동 선택 + 설정완료 자동 적용** — Gemini 응답은 `roofShape`(RoofShapeDescriptor `{type,ridge,flows,confidence?}`, route.js 가 `normalizeRoofShape` 로 어휘 정규화)를 추가로 반환한다. `analyzePdfAndApply` 가 이를 `pdfRoofShapeDescriptorState`(outerLineAtom)에 저장(분석 시작 시 null 리셋)하고, ROOF_SHAPE_SETTINGS 진입 시 `useRoofShapeSetting` 마운트 effect 가 `matchRoofShapePattern`(`@/common/roofShapePattern`, flows 집합+type/ridge guard)으로 매칭해 **버튼을 `setShapeNum` 으로 선택**하고, `shapeNum` 이 matched 로 커밋된 뒤 별도 effect(`autoSaveShapeNumRef` 가드, deps `[shapeNum]`)가 **`handleSave`(설정완료)를 자동 클릭**해 지붕형상까지 적용한다(2026-06-25 사용자 지시로 'pre-select만 → 설정완료 자동 적용'으로 전환). ⚠️ **마운트 effect 에서 `setShapeNum(matched)` 직후 같은 틱에 `handleSave` 를 호출하지 말 것**`handleSave``shapeNum` 을 클로저로 읽어 stale(기본값 1) 로 잘못된 패턴이 적용된다. 반드시 `[shapeNum]` effect 로 커밋 후 실행. consume-once 로 atom 리셋(비-PDF 재진입 stale 방지). 매처 null(복합형)·roofShape 누락은 자동선택/적용 없이 graceful(수동 선택). 매칭 결과는 콘솔에 `✅ N번 버튼 「…」 매칭 → 자동 선택 + 설정완료` / `❌ 매칭되는 버튼 없음`(NEXT_PUBLIC_ENABLE_LOGGING=true)으로 명시. ⚠️ Gemini 의 실제 `roofShape` 출력 품질(특히 片流れ flows 방향)은 런타임 수동 QA·프롬프트 튜닝 영역.
### State — Recoil, split by domain