From 3da6b1c0924a999a7ac5bd623df4282898a9066e Mon Sep 17 00:00:00 2001 From: zio Date: Sat, 11 Jul 2026 16:40:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(harness):=20ReRoomAI=20=EB=B6=84=EC=84=9D?= =?UTF-8?q?=20=EB=B0=98=EC=98=81=20=E2=80=94=20PLANNING=20v1.1=20=EB=82=98?= =?UTF-8?q?=EB=85=B8=EB=B0=94=EB=82=98=EB=82=98=20=ED=8C=8C=EC=9D=B4?= =?UTF-8?q?=ED=94=84=EB=9D=BC=EC=9D=B8=20=ED=99=95=EC=A0=95=20+=20client.p?= =?UTF-8?q?y=20=EC=9E=AC=EC=9E=91=EC=84=B1=20+=20Stitch=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/analysis/reroomai-source.md 신규: gemini-3.1-flash-image-preview image-to-image, 보존/교체 프롬프트, CompareSlider/전처리 차용 - PLANNING v1.1: §6-4/6-5 나노바나나 호출스택·부스 프롬프트 빌더 확정, R11 해소·R12(Gemini 외부API 승인 게이트) 추가 - tools/nanobanana/client.py 전면 재작성: build_booth_prompt 4단 조립, 메타데이터 임베드, S6 래스터 합성 분리, seed 폴백, 방어 로직 - BACKLOG B-02/B-03/B-09/B-11/B-12 done - kintex.com 2차 검증 순증 사실 반영(기본부스 사양·이격·홀 스펙) Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/skills/nanobanana-visualize/SKILL.md | 67 +- docs/BACKLOG.md | 10 +- docs/PLANNING.md | 119 ++- docs/analysis/kintex-website.md | 4 +- docs/analysis/reroomai-source.md | 179 ++++ kintex-harness-v1.0.1.zip | Bin 55187 -> 0 bytes output/visualizations/_smoke_wiring.png | Bin 0 -> 2279 bytes .../_smoke_wiring.png.meta.json | 15 + tools/nanobanana/README.md | 76 +- .../__pycache__/client.cpython-314.pyc | Bin 0 -> 40232 bytes tools/nanobanana/client.py | 809 ++++++++++++++++-- tools/nanobanana/prompts/booth_photo.txt | 35 +- 12 files changed, 1170 insertions(+), 144 deletions(-) create mode 100644 docs/analysis/reroomai-source.md delete mode 100644 kintex-harness-v1.0.1.zip create mode 100644 output/visualizations/_smoke_wiring.png create mode 100644 output/visualizations/_smoke_wiring.png.meta.json create mode 100644 tools/nanobanana/__pycache__/client.cpython-314.pyc diff --git a/.claude/skills/nanobanana-visualize/SKILL.md b/.claude/skills/nanobanana-visualize/SKILL.md index 6661058..b38418a 100644 --- a/.claude/skills/nanobanana-visualize/SKILL.md +++ b/.claude/skills/nanobanana-visualize/SKILL.md @@ -5,16 +5,63 @@ description: 부스/인테리어/배선/조명 설계 데이터를 나노바나 # 나노바나나 시공 결과 시각화 스킬 +ReRoomAI 실증 패턴(`docs/analysis/reroomai-source.md`)을 표준화한 파이프라인. +"부스 골격 보존 + 표면(집기·조명·배선) 교체" image-to-image 편집. + +## 호출 표준 +- **모델**: `gemini-3.1-flash-image-preview` (나노바나나 2). 환경변수 `NANOBANANA_MODEL` 로 오버라이드 가능. +- **호출**: `tools/nanobanana/client.py`의 `NanoBananaClient`만 사용(직접 API 호출 금지). + google-genai(`google.genai`) SDK로 `generate_content(model, contents=[Part.from_bytes(...), text])`. + 참조 이미지는 `inlineData(mimeType+base64)`, 지시문은 `text` — parts 배열로 전달. mimeType 자동 감지. +- **보안**: API 키는 환경변수 `GEMINI_API_KEY` 에서만 로드. 코드/로그/커밋/응답에 절대 기록 금지. + ★ 실제 Gemini 호출은 **소유자 승인 대기**(reroomai-source.md §8) — 승인 전 실호출 강제 금지. + +## 프롬프트 빌더 (구조화 사전 조립 + 보존/교체 분리) +`build_booth_prompt(scene, shot_preset, layers)` 가 PLANNING §6-2 스키마를 4단으로 조립: +1. **대상+스타일** — `BOOTH_TYPES`(독립/조립/코너/아일랜드) × `BOOTH_STYLES`(럭셔리/테크/친환경/미니멀/표준) + 치수·브랜드컬러. +2. **보존 잠금** — 부스 외곽 치수·기둥·바닥 트렌치 그리드·천장 트러스·통로 방향·카메라 앵글 유지. +3. **교체 지정** — `FIXTURE_LAYERS`(집기/조명/전기/네트워크) 레이어별 조각 + 간판 문구(한글)·자재·존·색온도. +4. **사진 품질** — photorealistic tradeshow, natural lighting, high detail, no people/real logos. + +입력은 §6-2 `scene`(hall/booth/design/lighting/wiring + shot/render_hints). 중첩/평면 모두 관대 파싱. + ## 절차 -1. 입력 확인: 부스 스펙(홀, 크기, 타입, 브랜드 컬러, 자재, 조명)이 구조화되어 있는지 확인. 없으면 사용자/설계 데이터에서 추출해 dict로 정리. -2. `GEMINI_API_KEY` 환경변수 확인. 없으면 발급 안내 후 중단. -3. `tools/nanobanana/client.py`의 `NanoBananaClient` 사용 (직접 API 호출 금지). -4. 표준 샷 세트(S1~S7, tools/nanobanana/README.md 표 참조) 순서로 생성: S1 정면 주간 → S2 정면 야간 → S3 통로 → S4 내부. 조감 요청이면 S7(view="aerial"), 배선 요청이면 S6 `generate_wiring_overlay`(network/power/plumbing). -5. 빈 부스 실측 사진이 있으면 `reference_image`로 전달해 실제 홀 구조를 보존 (S5 before/after 쌍 구성). -6. 생성 이미지에 "AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음" 워터마크를 합성(Pillow)한 뒤 산출 폴더 `output/visualizations/`에 저장. -7. 같은 부스의 추가 샷은 첫 생성 이미지를 참조 이미지로 재사용해 아이덴티티를 유지. +1. 입력 확인: `scene` dict(§6-2)가 구조화됐는지 확인. 없으면 설계 데이터/사용자에서 추출. +2. `GEMINI_API_KEY` 확인. 없으면 발급 안내 후 중단. (승인 전이면 실호출 보류) +3. `NanoBananaClient().render_shot(scene, shot_preset="S1", reference_image=빈부스사진, seed=고정값)`. +4. 표준 샷 세트 S1~S7 순서로 생성: + - S1 정면 주간 → S2 정면 야간 → S3 통로 → S4 내부 (모두 `render_shot`) + - S5 Before/After: S1 결과 + `reference_image`(빈 부스 실측) 쌍 + - S7 홀 조감: `render_shot(shot_preset="S7")` + - **S6 배선 오버레이**: 생성형 아님 → `render_wiring_overlay_raster(wiring, hall_dims_m, kinds, base_image)` + (도면 위 전기 적/네트워크 청/급배수 녹 경로를 좌표 정합 래스터 합성. 시공 검증용 1차 산출물). + 발표/설명용 보조 이미지가 필요할 때만 `generate_wiring_overlay`(생성형, 검증 사용 금지). +5. 빈 부스 실측 사진은 `reference_image`로 전달 → 실제 홀 구조 보존 합성(S5 before/after 쌍). +6. 워터마크: 모든 산출 이미지에 "AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음" 합성(Pillow) 후 + `output/visualizations/`에 저장. `GeneratedImage.save()`가 사이드카 `.meta.json`(생성일·스키마 해시· + 모델 버전·seed·워터마크 고지)을 결정적으로 기록하고 PNG는 tEXt 청크에도 임베드. +7. 일관성: 같은 부스의 추가 샷은 **동일 `seed`** 전달(B-12). seed 미지원 SDK/모델이면 자동으로 + **참조체인 기반 일관성**으로 폴백(첫 생성 이미지를 다음 샷 `reference_image`로 재사용). + +## 표준 샷 세트 S1~S7 +| 샷 | 호출 | 용도 | +|---|---|---| +| S1 정면 주간 | `render_shot(scene,"S1")` | 기본 컨펌 컷 | +| S2 정면 야간 | `render_shot(scene,"S2")` | 조명 연출 평가 | +| S3 통로 뷰 | `render_shot(scene,"S3")` | 관람객 동선 | +| S4 내부 뷰 | `render_shot(scene,"S4")` | 집기 배치 | +| S5 Before/After | S1 + `reference_image` 쌍 | 시공 전/후 비교 | +| S6 배선 오버레이 | `render_wiring_overlay_raster(...)` ★래스터 합성 | 시공 검증(좌표 정합) | +| S7 홀 조감 | `render_shot(scene,"S7")` | 배치안 비교 | + +## 프로덕션 방어 (ReRoomAI route.ts 패턴) +- **입력 크기 가드**: 참조 이미지 8MB 상한 + 긴 쪽 1024px 다운스케일/JPEG 0.85(`downscale_for_upload`). +- **SAFETY 처리**: `finish_reason == SAFETY` → `NanoBananaSafetyError`. +- **에러 분기**: 키 무효→`NanoBananaAuthError`, 쿼터/429→`NanoBananaQuotaError`, SAFETY→`NanoBananaSafetyError`. +- **쿼터 차감**: `usage_recorder` 훅은 **성공 시에만** 호출(실패는 카운트 소모 안 함). ## 품질 체크 -- 부스 크기 비율이 스펙과 맞는가 (3x3 부스가 대형 부스처럼 보이면 재생성) -- 텍스트 사이니지가 한국어인가 -- 실존 브랜드 로고/사람 얼굴이 없는가 +- 부스 크기 비율이 스펙과 맞는가 (3x3 부스가 대형처럼 보이면 재생성). +- 텍스트 사이니지가 한국어로 정확한가 (실패 시 간판 영역 후처리 합성 — PLANNING §6-4). +- 실존 브랜드 로고/사람 얼굴이 없는가. +- 계약·심사 서류에는 생성 이미지 사용 금지(도면만 유효 — PLANNING §6-5). diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 594de5c..67d75ec 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -5,14 +5,14 @@ | ID | 심각도 | 내용 | 담당 에이전트 | 상태 | |---|---|---|---|---| | B-01 | Major | 조립부스 옵션(간판·가구·조명 선택) 신청 UI 부재 — PLANNING M3 Phase 1 범위인데 design.md에 화면 없음. SCR-05/06에 조립부스 모드 추가 필요 | designer | open | -| B-02 | Major | S6 배선 오버레이의 시공 검증용 산출은 생성형이 아닌 백엔드 래스터 합성 렌더러로 구현 필요 (client.py에는 주의 주석만 반영됨) | developer | open | -| B-03 | Major | 나노바나나 입력 스키마를 PLANNING §6-2 정식 스키마(booth polygon, zones, shot preset, render_hints)로 확장 + GeneratedImage에 메타데이터(생성일·스키마 해시·모델 버전) 임베드 | visualizer/developer | open | +| B-02 | Major | S6 배선 오버레이의 시공 검증용 산출은 생성형이 아닌 백엔드 래스터 합성 렌더러로 구현 필요 (client.py에는 주의 주석만 반영됨) | developer | **done** (v1.2) — `render_wiring_overlay_raster()` 신설(로컬 PIL, 좌표 정합, power=적/network=청/plumbing=녹, 트렌치 마커·범례, base_image 합성). 생성형 `generate_wiring_overlay`는 발표 보조용으로 분리 | +| B-03 | Major | 나노바나나 입력 스키마를 PLANNING §6-2 정식 스키마(booth polygon, zones, shot preset, render_hints)로 확장 + GeneratedImage에 메타데이터(생성일·스키마 해시·모델 버전) 임베드 | visualizer/developer | **done** (v1.2) — `build_booth_prompt(scene)`가 §6-2 scene(hall/booth/design/lighting/wiring/shot/render_hints) 수용, `GeneratedImage.save()`가 사이드카 `.meta.json`+PNG tEXt 청크로 생성일·스키마 해시(SHA-256)·모델 버전·seed 결정적 임베드 | | B-04 | Minor | design.md 화면 수 표기(13) → 실제 웹 12+모바일 2=14 정정 | designer | open | | B-05 | Minor | SCR-02 프롬프트 사이드바 "정산" 메뉴 — IA와 불일치 정리 | designer | open | | B-06 | Minor | SCR-01 이메일 인증 로그인 프롬프트 누락 | designer | open | | B-07 | Minor | SCR-03 "공유" 버튼 프롬프트 누락 + 위반 요약(2/3)과 캔버스 핀 샘플(적2·주1) 불일치 | designer | open | | B-08 | Minor | SCR-04 샘플 지표 산술 오류(510부스 ↔ 판매면적 4,420㎡) | designer | open | -| B-09 | Minor | analysis 문서 내 D-15/D-25 표기 혼재 정리 | planner | open | +| B-09 | Minor | analysis 문서 내 D-15/D-25 표기 혼재 정리 | planner | **done** (v1.1) — kintex-website.md §5 마일스톤을 D-150/D-30/D-25/D-7로 통일(유틸리티=D-25), D-15 오기 제거 | | B-10 | Minor | 홀매니저(SCR-10/11)·현장 모바일(SCR-M1/M2) 화면에 Phase 2/3 라벨 부착, 조명 배치안 제안(M4a) 전용 UI 검토 | designer | open | -| B-11 | Major | ReRoomAI 소스 분석(docs/analysis/reroomai-source.md) — 소스 폴더 연결 대기 중. 완료 후 PLANNING §6 ReRoomAI 연계 절 보강 | planner/visualizer | blocked | -| B-12 | Minor | client.py에 동일 시드/일관성 파라미터(PLANNING §6-3) 지원 검토 (Gemini API 시드 지원 범위 확인 필요) | visualizer | open | +| B-11 | Major | ReRoomAI 소스 분석(docs/analysis/reroomai-source.md) — 소스 폴더 연결 대기 중. 완료 후 PLANNING §6 ReRoomAI 연계 절 보강 | planner/visualizer | **done** (v1.1) — 소스 분석 완료·PLANNING §6-4/6-5 확정(모델·SDK·구조화 사전·보존/교체 프롬프트 템플릿·RenderJob 방어·UX), §10 R11 해소·R12 추가 | +| B-12 | Minor | client.py에 동일 시드/일관성 파라미터(PLANNING §6-3) 지원 검토 (Gemini API 시드 지원 범위 확인 필요) | visualizer | **done** (v1.2) — `render_shot(..., seed=)` 지원. `GenerateContentConfig(seed=)`로 전달 시도하되 SDK/모델 미지원(TypeError)이면 자동으로 참조체인 기반 일관성(첫 컷을 다음 샷 reference_image로 재사용)으로 폴백. SKILL.md에 명시. ★실호출 검증은 소유자 승인 후 | diff --git a/docs/PLANNING.md b/docs/PLANNING.md index 71f45f6..f991de6 100644 --- a/docs/PLANNING.md +++ b/docs/PLANNING.md @@ -1,8 +1,8 @@ # 킨텍스 AI 전시관리 시스템 기획서 -> 작성: 기획 에이전트(planner) · 작성일: 2026-07-11 · 버전: v1.0 +> 작성: 기획 에이전트(planner) · 작성일: 2026-07-11 · 버전: v1.1 > 근거 문서: `docs/analysis/kintex-website.md` (킨텍스 웹사이트 25개 페이지 + 전시주최자매뉴얼 PDF + 참가업체 매뉴얼 PDF 전수 분석, 2026-07-11) -> 후속 연계 예정: `docs/analysis/reroomai-source.md` (ReRoomAI 이미지 파이프라인 소스 분석 — 추가 시 6장 갱신) +> 근거 문서: `docs/analysis/reroomai-source.md` (ReRoomAI 이미지 파이프라인 소스 분석, 2026-07-11) — 6장 나노바나나 파이프라인 제어 파라미터 확정 근거 --- @@ -253,14 +253,16 @@ graph TB ## 6. 나노바나나 시각화 파이프라인 +> **제어 파라미터 확정 (v1.1)**: 본 장의 모델·SDK·프롬프트 아키텍처·방어 로직은 ReRoomAI 소스 분석(`docs/analysis/reroomai-source.md`)의 실증 기법으로 확정되었다. ReRoomAI는 "건축 골격 보존 + 표면 요소 교체"라는 동형(同型) 문제(빈 부스 골격 유지 + 배치·인테리어·조명·배선 시공 결과 합성)를 검증된 단일 호출로 해결하고 있어, 그 호출 스택·프롬프트 패턴·방어 로직을 부스 도메인으로 치환해 채택한다. + ### 6-1. 파이프라인 개요 ``` -구조화 데이터(M2~M4) → 씬 컴파일러(3D 간이 렌더/도면 래스터) → 프롬프트 빌더 -→ Gemini 이미지 생성(참조 이미지 조건부) → 후처리(워터마크·메타데이터) → CDN 캐시 +구조화 데이터(M2~M4) → 씬 컴파일러(3D 간이 렌더/도면 래스터, 긴 쪽 1024px) → 프롬프트 빌더(구조화 사전 조립) +→ Gemini image-to-image 편집(참조 이미지 inlineData + 지시문 text) → RenderJob 방어·검수 → 후처리(워터마크·메타데이터) → CDN 캐시 ``` -핵심 원칙: **텍스트 프롬프트 단독 생성이 아니라, 시스템이 만든 도면/간이 렌더를 참조 이미지로 넣어 구조(부스 위치·크기·통로)를 보존하고 스타일만 사실화**한다. 이는 ReRoomAI의 "참조 이미지 기반 구조 보존 + 스타일 변환" 패턴을 재사용하는 것으로, 소스 상세 분석(`docs/analysis/reroomai-source.md`)이 추가되는 대로 본 절의 제어 파라미터를 구체화한다(연계 예정). +핵심 원칙: **텍스트 프롬프트 단독 생성이 아니라, 시스템이 만든 도면/간이 렌더를 참조 이미지로 넣어 구조(부스 위치·크기·통로·앵글)를 보존하고 스타일만 사실화**한다. ReRoomAI의 "참조 이미지 기반 구조 보존 + 스타일 변환" 패턴을 부스 도메인으로 이식한다. Stable Diffusion·ControlNet 등 별도 파이프라인 없이 **Gemini 이미지 모델 단일 호출**이 원본 구조를 참조·보존한다는 점이 ReRoomAI에서 실증되었다. ### 6-2. 입력 데이터 스키마 (요약) @@ -294,16 +296,100 @@ render_hints: { style: "photoreal_tradeshow", crowd: "sparse" } | S6 배선 오버레이 | S1/평면 탑뷰에 전기(적)·네트워크(청)·급배수(녹) 경로 오버레이 | 시공업체·홀매니저 검증용 | M4 저장 시 | | S7 홀 전경(조감) | 홀 전체 아이소메트릭 | 주최자 배치안 비교 | M2 배치안 생성 시 | -S6는 사실화 생성이 아니라 **도면 정합 오버레이가 우선**(정확성이 목적) — 래스터 합성 후 배경만 생성 이미지 사용. +S6(배선 오버레이)는 **생성형 이미지가 아니라 백엔드 래스터 합성이 우선**이다. 시공업체·홀매니저의 검증 산출물이므로 좌표 정확성이 목적이며, 생성 모델의 재질/색 왜곡이 개입되면 안 된다 → PostGIS 배선 지오메트리(전기 적·네트워크 청·급배수 녹)를 S1/평면 탑뷰 위에 백엔드 렌더러로 정확 오버레이하고, **배경만(부스 골격·바닥) 생성 이미지를 사용**한다. 이 경로는 Gemini 호출 없이(또는 배경 1회만) 결정적으로 산출된다. (BACKLOG B-02 — developer 트랙 백엔드 래스터 렌더러로 구현) -### 6-4. 프롬프트 전략 +### 6-4. 모델·SDK·프롬프트 아키텍처 (ReRoomAI 실증 기법 확정) -1. **2단 구성**: (a) 시스템 고정부 — "한국 전시장 KINTEX 홀 내부, 콘크리트 폴리싱 바닥, 층고 12m 트러스 천장…" 등 홀별 사전 촬영 참조 사진과 함께 고정, (b) 가변부 — 스키마를 자연어로 직렬화(치수·자재·간판 문구 명시). -2. **구조 보존**: 씬 컴파일러의 간이 렌더(회색 박스 수준)를 참조 이미지로 제공, "이 구도·배치를 유지하고 사실적 재질로" 지시. 텍스트 간판 문구는 생성 후 오탈자 검수(비전 모델 자동 확인) — **한글 텍스트 렌더링 불안정은 알려진 한계**로, 실패 시 간판 영역 후처리 합성. -3. **일관성**: 같은 부스의 S1~S5는 동일 시드/참조 체인으로 생성해 컷 간 자재·색상 일치 유도. 완전한 일치는 보장 불가 — UI에 "컷별 차이 존재 가능" 고지. -4. **비용 제어**: 저장 시 자동 생성은 S1·S7만, 나머지는 온디맨드. 동일 스키마 해시 캐시. +**(1) 호출 스택 — 검증된 정답 채택** -### 6-5. 워터마크·고지 정책 (필수) +| 항목 | 확정값 | 근거 | +|---|---|---| +| SDK | `@google/genai` (Google 공식) | ReRoomAI `route.ts` 프로덕션 검증 | +| 모델 | `gemini-3.1-flash-image-preview` (나노바나나 2 / Nano Banana 2) | 동일 | +| 호출 형태 | `ai.models.generateContent()` — image-to-image 편집 | 동일 | +| 입력 구성 | `parts` 배열에 **입력 이미지 `{ inlineData: { mimeType, data(base64) } }` + 지시문 `{ text }`** 동시 전달 | 멀티모달 인페인팅형 편집 — 원본 구조 참조·보존 | +| 응답 처리 | candidate에서 `inlineData`(생성 이미지 base64) 추출 | 동일 | + +`tools/nanobanana/`(프로젝트 규칙) 모듈이 이 호출 패턴을 표준화하며, `nanobanana-visualize` 스킬이 부스 도메인 래퍼를 제공한다. + +**(2) 프롬프트 = 구조화 사전 조립 + "보존/교체 명시적 분리"** + +ReRoomAI `lib/constants.ts`의 핵심 패턴 — UI 라벨(한글)과 프롬프트 조각(영문)을 한 객체에 묶어 **UI 선택과 서버 프롬프트가 단일 출처를 공유** — 를 부스 도메인 구조화 사전으로 이식한다. 각 항목은 `{ id, label(한글), prompt(영문), swatch? }` 형태. + +```ts +// 부스 유형 — 골격 특성 프롬프트 조각 +BOOTH_TYPES = [ + { id: "assembled", label: "조립부스", prompt: "standard octanorm shell scheme booth, aluminium frame walls" }, + { id: "independent", label: "독립부스", prompt: "custom-built independent booth, free-standing structure" }, + { id: "corner", label: "코너부스", prompt: "corner booth open on two aisle-facing sides" }, + { id: "island", label: "아일랜드부스", prompt: "island booth open on all four sides" }, +] + +// 부스 스타일 — 표면·분위기 프롬프트 조각(+ swatch 색3종) +BOOTH_STYLES = [ + { id: "luxury", label: "럭셔리", prompt: "premium luxury exhibition design: warm wood, brass accents, layered lighting" }, + { id: "tech", label: "테크", prompt: "high-tech booth: LED walls, dark palette, cool white accent lighting" }, + { id: "eco", label: "친환경", prompt: "sustainable booth: recycled timber, greenery, warm diffuse lighting" }, + { id: "minimal", label: "미니멀", prompt: "minimal booth: clean white surfaces, hidden lighting, uncluttered" }, +] + +// 시공 레이어 — 조명/전기/네트워크 레이어별 프롬프트 조각(변형 렌더용) +FIXTURE_LAYERS = { + lighting: { day: "even daylight-balanced exhibition lighting", + night: "dramatic accent spotlights on products, dimmed ambient" }, + power: "power outlets and distribution box neatly integrated at booth base", + network: "network access point and cabling routed along booth structure", +} +``` + +이 사전들은 M2(부스 유형)·M3(스타일·집기)·M4(조명 모드·배선 레이어) UI 선택값과 그대로 매핑되어, 프롬프트 빌더가 조각을 조립한다. + +**(3) 부스 도메인 프롬프트 템플릿 (보존/교체 명시 잠금)** + +ReRoomAI 4단 구성(대상+스타일 → 보존 잠금 → 교체 지정 → 사진 품질)을 부스로 치환: + +``` +Render this exhibition booth as if construction is complete. +① 대상+스타일: {BOOTH_TYPES.prompt} at KINTEX exhibition hall, styled as {BOOTH_STYLES.prompt}. +② 보존 잠금(KEEP EXACTLY THE SAME): + booth outer footprint dimensions, structural columns, floor trench grid, + ceiling truss, aisle direction and the camera angle. +③ 교체·배치(REPLACE / PLACE): + fixtures — desks, shelves, banners, signage; + {FIXTURE_LAYERS.lighting[mode]}; {FIXTURE_LAYERS.power}; {FIXTURE_LAYERS.network}; + carpet and booth wall graphics — to match the target style. +④ 사진 품질: + photorealistic trade-show photography, natural exposure, high detail, + Korean exhibition hall interior (concrete polished / carpet floor per hall). +``` + +- **가변 직렬화**: 씬 스키마(6-2)의 치수·자재·간판 문구를 자연어로 직렬화해 위 슬롯에 주입. 홀별 사전 촬영 참조 사진을 함께 참조 이미지로 전달(홀6 카펫·홀7 콘크리트 폴리싱 등). +- **구조 보존 강화**: 씬 컴파일러 간이 렌더(회색 박스 수준)를 참조 이미지 `inlineData`로 제공 → "이 구도·배치를 유지, 사실적 재질로만". +- **간판 텍스트**: 생성 후 비전 모델로 오탈자 자동 검수. **한글 텍스트 렌더링 불안정은 알려진 한계** → 실패 시 간판 영역 후처리 합성. +- **레이어 변형**: `FIXTURE_LAYERS.lighting.night`만 교체하면 S2(야간 점등), 배선 레이어 강조는 S6(단, S6은 6-3대로 래스터 합성 우선). + +**(4) 일관성·비용 제어** + +- **일관성**: 같은 부스 S1~S5는 동일 참조 체인(간이 렌더 + 직전 결과)으로 생성해 컷 간 자재·색상 일치 유도. 완전 일치는 보장 불가 — UI에 "컷별 차이 존재 가능" 고지. (Gemini 시드 지원 범위는 구현 시 확인 — BACKLOG B-12) +- **비용 제어**: 저장 시 자동 생성은 S1·S7만, 나머지는 온디맨드. **동일 스키마 해시 캐시**. RenderJob 쿼터는 성공 시에만 차감(6-5). + +### 6-5. RenderJob 방어 로직 및 UX (ReRoomAI (D)(E)(G) 이식) + +**(1) Canvas 1024px 전처리 (전송량·비용·지연 동시 절감)** +- 클라이언트에서 도면/현장 사진을 **긴 쪽 1024px 다운스케일 → `toDataURL('image/jpeg', 0.85)` → base64 data URL**로 변환 후 전송. ReRoomAI `Studio.handleImageFile`과 동일. 부스 간이 렌더도 동일 규격으로 정규화. + +**(2) RenderJob 워커 방어 로직 (ReRoomAI `route.ts` 골격 복제)** +- **다층 크기 가드**: content-length 상한(8MB) → base64 실크기(×1.33) 재검증. +- **mimeType 자동 감지**: data URL에서 mimeType + base64 정규식 분리(하드코딩 금지). +- **SAFETY 처리**: 응답 `finishReason==='SAFETY'` → 차단 에러로 분기. +- **에러 분기 → 친화적 한글 메시지**: `API_KEY_INVALID` / `RESOURCE_EXHAUSTED·quota·429` / `SAFETY` 구분. +- **성공 시에만 쿼터 차감**: 실패는 사용량 소모 안 함. ReRoomAI는 인메모리 `Map`이나(재시작·다중 인스턴스 취약) — 본 시스템은 **PostgreSQL/Redis 기반 RenderJob 쿼터**(행사별 상한, 6-4 비용 제어와 통합)로 대체. +- 전 과정 비동기(8장): RenderJob 큐잉 → 워커 Gemini 호출 → WebSocket 완료 푸시 → 재시도·비용 상한·스키마 해시 캐시 워커 레벨 관리. + +**(3) 단계별 로딩 UX** +- ReRoomAI `LOADING_STATUSES` 순환 패턴을 부스용으로 치환: **"부스 골격 인식 → 집기 배치 → 전기·조명 배선 → 최종 고화질 렌더링"**을 `aria-live`로 순환 표시. 진행 배지("생성 중… 평균 40초")와 연결. 결과는 Before/After 드래그 슬라이더(ReRoomAI `CompareSlider.tsx` 거의 무수정 재사용 — clip-path + 포인터 캡처 + 키보드/ARIA)로 S5(빈 부스 ↔ 시공 후) 표시. + +### 6-6. 워터마크·고지 정책 (필수) - 모든 생성 이미지에 시각 워터마크 "**AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음**" + 메타데이터(생성일, 스키마 해시, 모델 버전) 임베드. - 계약·심사 서류에는 생성 이미지 사용 금지(도면만 유효) — 시스템이 서류 생성 시 자동 배제. @@ -317,11 +403,12 @@ S6는 사실화 생성이 아니라 **도면 정합 오버레이가 우선**(정 | 데이터셋 | 내용(분석 문서 실측값) | 확보 방법 | 상태 | |---|---|---|---| -| 홀 마스터 | 1전시장 홀1~5(홀당 10,611~10,773㎡, 63×171m, 층고 15m, 5t/㎡, 약 600부스), 2전시장 홀6(5,580㎡, 층고 10m, 2t/㎡, 카펫, 200부스)·홀7/8(각 11,290㎡, 126×90×12m, 510부스)·홀9(13,238㎡)/홀10(13,072㎡, 132×99×15m). 반홀 분할(1A 4,941㎡/1B 5,670㎡ 등) | 공개 스펙 + 킨텍스 CAD 원본 요청 | 공개값 확보, CAD 협의 필요 | +| 홀 마스터 | 1전시장 홀1~5(홀당 10,611~10,773㎡, **171×63×15m**, 5t/㎡, 약 600부스) + **옥외전시장 2,849㎡**, 2전시장 홀6(5,580㎡=**93×60×10m**, 2t/㎡, 카펫, 200부스)·홀7/8(각 11,290㎡, 126×90×12m, 510부스)·홀9(13,238㎡)/홀10(13,072㎡, 132×99×15m). 반홀 분할(1A 4,941㎡/1B 5,670㎡ 등). **제3전시장(2028) 홀11~18 계획** | 공개 스펙 + 킨텍스 CAD 원본 요청 | 공개값 확보(2차 검증 반영), CAD 협의 필요 | | 트렌치 그리드 | 홀별 트렌치 위치·공급 매트릭스(전기·급배수·압축공기·전화·인터넷, 홀1·7 가스) | **킨텍스 제공 필수** — 미공개 | 미확보 (Phase 1 착수 조건) | | 요율 마스터 | 전시홀 2,250원/㎡(12h), 로비 10,000원/㎡, 옥외 2,000원/㎡, 이벤트홀 2,420원/㎡, 성수기 +10%/비수기 -10%/1전시장 +10%, 용도 외 30% 할증, 예치금 15~20% | 공개 임대요율표 | 확보 | | 유틸리티 요금 | 전기 1kW 55,000원·분전반 50A 100,000원, 압축공기 150,000원/구, 급배수 150,000원/구, 인터넷 150,000원/회선(KT 80,000원 병존 — 정합 확인 필요) | 참가업체 매뉴얼 | 확보(검증 필요) | -| 규정 룰셋 | 높이 5m, 리깅 6.5~8.5m+구조계산서 D-7, 복층 1/2, 방염, 소음 70~75dB, 금지작업 목록 | 매뉴얼 → 룰 엔진 코드화 | 확보 | +| 부스 표준 사양 | **조립(기본)부스 표준 포함 품목: 스포트라이트 5개·220V 2구 콘센트 1개·방염 A급 바닥재·기본 전력 1kW/부스. 프리미엄 부스 6×3×4m·2kW.** 초과 전력·조명은 유틸리티 추가 신청 | 참가업체 매뉴얼(kintex.com 2차 검증) | 확보 | +| 규정 룰셋 | 높이 5m, 리깅 6.5~8.5m+구조계산서 D-7, 복층 1/2, 방염, 소음 70~75dB, **이격(인접 벽 30cm·천장 60cm), 조명 반입 금지(지정 조명만 사용)**, 금지작업 목록 | 매뉴얼 → 룰 엔진 코드화 | 확보 | | 회의실 마스터 | 37개 회의실, 시간대별 요금 (P2 범위) | 공개 요율표 | 확보 | ### 7-2. 외부 연동 @@ -425,7 +512,8 @@ graph LR | R8 | 요금·규정 데이터의 공식성 — 웹 공개값(예: 인터넷 150,000원 vs KT 80,000원 병존)이 실계약가와 다를 수 있음 | 중간 | 견적에 "공시가 기준, 최종가는 킨텍스 확정" 고지, 요율 마스터를 킨텍스 확인본으로 교체하는 절차 마련 | | R9 | 이해관계 충돌 — 장치업체는 설계 자동화를 일감 위협으로 인식 가능 | 중간 | 포지셔닝을 '초안+검증 도구'로: 반려 감소·컨펌 단축이라는 업체 이득 강조, M7로 수주 채널 제공 | | R10 | 개인정보·영업비밀 — 참가업체 부스 설계는 경쟁사에 민감 | 중간 | 행사 단위 격리, 부스 데이터 접근은 소유 참가업체+주최자+홀매니저로 한정 | -| R11 | ReRoomAI 파이프라인 분석 미완 — 구조 보존 제어 상세는 소스 분석 후 확정 | 낮음 | 6장을 '연계 예정'으로 버전 관리, 분석 도착 시 v1.1 갱신 | +| R11 | ~~ReRoomAI 파이프라인 분석 미완 — 구조 보존 제어 상세는 소스 분석 후 확정~~ **[해소 v1.1]** | 낮음 | **해소(2026-07-11)**: `reroomai-source.md` 분석 완료 → 6장 모델·SDK·프롬프트 아키텍처·방어 로직 확정(6-4/6-5). BACKLOG B-11 done | +| R12 | **나노바나나(Gemini) 외부 API 미승인** — GUARDiA 외부 API 금지 원칙상 현재 승인 예외는 `api.anthropic.com`뿐이며, Gemini(`generativelanguage.googleapis.com`)는 미승인. kintex는 GUARDiA ITSM(관공서 관제)과 별개 도메인의 독립 저장소(`zio/kintex`)이나, 승인 없이 M5 구현 착수 시 원칙 위반 | 높음 | **M5 파이프라인 구현 착수 전 소유자 승인 확정 선행(게이트)**. 승인 시 `GEMINI_API_KEY`는 서버 env로만 로드(코드·DB·커밋·로그·응답 기록 금지, ReRoomAI (E) 방어 패턴 준수). **미승인 시 완화책**: 온프레미스 이미지 생성(SDXL 등) 폴백 어댑터 검토 — 단 image-to-image 구조보존 품질 재평가 필요 | --- @@ -434,3 +522,4 @@ graph LR | 버전 | 일자 | 작성자 | 내용 | |---|---|---|---| | v1.0 | 2026-07-11 | planner | 최초 작성 — kintex-website.md 분석 기반 전체 기획. ReRoomAI 소스 분석(reroomai-source.md)은 추가 시 6장 갱신 예정 | +| v1.1 | 2026-07-11 | planner | ReRoomAI 소스 분석 반영. ①§6 나노바나나 파이프라인 제어 파라미터 확정 — 모델 `gemini-3.1-flash-image-preview`(나노바나나 2)·`@google/genai` SDK·image-to-image(inlineData+text parts) 호출 스택, 구조화 사전(BOOTH_TYPES·BOOTH_STYLES·FIXTURE_LAYERS) + "보존/교체 명시 분리" 부스 프롬프트 템플릿(§6-4), Canvas 1024px 전처리·RenderJob 방어 로직(크기 가드·SAFETY·에러 분기·성공 시에만 쿼터 차감)·단계별 로딩 UX 신설(§6-5). ②S6 배선 오버레이 백엔드 래스터 합성 우선 명확화(§6-3). ③§7 마스터 데이터 순증 반영(기본부스 표준 품목·프리미엄 6×3×4m 2kW·조명 반입 금지·이격 30/60cm·옥외 2,849㎡·홀6 93×60×10m·제3전시장 홀11~18). ④§10 R11 해소, R12(Gemini 외부 API 미승인·소유자 승인 게이트) 추가. BACKLOG B-11 done, B-09 정리 | diff --git a/docs/analysis/kintex-website.md b/docs/analysis/kintex-website.md index 7d63e46..dd3e4d9 100644 --- a/docs/analysis/kintex-website.md +++ b/docs/analysis/kintex-website.md @@ -54,7 +54,7 @@ - 홀당 10,611~10,773㎡ (A/B 2분할 가능, 예: 1A 4,941㎡/1B 5,670㎡), 63×171m, 층고 15m, 바닥하중 5t/㎡, 콘크리트 폴리싱, 홀당 약 600부스(3×3m) - 트렌치 유틸리티: 전기·급배수·압축공기·전화·인터넷 (홀1은 가스 포함) - **2전시장**: 홀 6~10 (5개 홀), 54,470㎡ - - 홀6: 5,580㎡(6A/B/C 각 1,860㎡), 층고 10m, 2t/㎡, 카펫 마감, 200부스 + - 홀6: 5,580㎡=93×60×10m(6A/B/C 각 1,860㎡), 2t/㎡, 카펫 마감, 200부스 - 홀7·8: 각 11,290㎡, 126×90×12m, 5t/㎡, 510부스 (홀7 가스 포함) - 홀9: 13,238㎡ / 홀10: 13,072㎡, 132×99×15m, 5t/㎡ - 이동식 파티션으로 홀 분할·통합, 고층고로 복층부스·리깅 지원 @@ -79,7 +79,7 @@ 2. **장치공사 도면 심사 자동화**: 독립부스 평면도·입면도·전기도면·리깅 구조계산서를 kxwp에 제출하면 사람이 검토하는 구조 → 도면 파싱 AI로 방염·높이·하중(2~5t/㎡ 홀별 상이)·리깅 높이(6.5~8.5m) 규정 위반 자동 플래깅 3. **유틸리티(전기·급배수·압축공기·통신) 신청·배선 최적화**: 참가업체가 위치표시도를 수기로 그려 등록하는 프로세스 → 부스 좌표 기반 트렌치 최단 배선 자동 산출, 분전반 용량(kW 합산) 자동 계산·요금 자동 견적, 마감기한(D-25 등) 리마인더 자동화 4. **임대 견적·배정 자동화**: 기본단가 2,250원/㎡ × 월별(±10%)·홀별(+10%) 차등 × 반홀 단위 + 관리비 예치금 15~20% 규칙이 명확 → 챗봇/견적기 자동화 용이. 홀 배정도 행사일정 DB와 연동한 가용성 실시간 조회 가능 -5. **서류 워크플로 디지털화**: 배정신청서·주최자신고서류가 아직 **HWP 양식 + 이메일/방문 제출** → 웹폼 + AI 서류 검수(누락 항목, 재해대처계획서 적정성)로 전환. D-150/D-30/D-15/D-7 마일스톤 자동 트래킹 +5. **서류 워크플로 디지털화**: 배정신청서·주최자신고서류가 아직 **HWP 양식 + 이메일/방문 제출** → 웹폼 + AI 서류 검수(누락 항목, 재해대처계획서 적정성)로 전환. D-150(배정)/D-30(사전협의)/D-25(유틸리티)/D-7(신고서류) 마일스톤 자동 트래킹 6. **반입/반출 물류 스케줄링**: 화물차 순번제·통행증·하역장 배정을 수작업 운영 → 차량 예약 슬롯 시스템 + 대기열 최적화, 중량물(5t) 우선순위 자동 배치 7. **등록업체 매칭**: 739개 업체 DB가 단순 리스트 → 행사 유형·부스 규모·지역 기반 업체 추천, 견적 비교 8. **참가업체 공통 포털 부재**: 유틸리티 신청이 전시회별 주최자 시스템에 파편화 → 킨텍스 공통 참가업체 e-서비스 플랫폼(신청→위치도→결제→현장 개통 확인)이 가장 큰 공백 diff --git a/docs/analysis/reroomai-source.md b/docs/analysis/reroomai-source.md new file mode 100644 index 0000000..1ea4b5b --- /dev/null +++ b/docs/analysis/reroomai-source.md @@ -0,0 +1,179 @@ +# ReRoomAI 소스코드 분석 + +> 분석일: 2026-07-11 · 대상: `C:\GUARDiA\workspace\ReRoomAI` — AI 공간 인테리어 리디자인 웹 서비스 +> 목적: 킨텍스 전시 부스 "시공 후 결과 사진 생성 AI" 시스템(PLANNING §6 나노바나나 파이프라인)의 구조보존 제어 파라미터 확정 +> 연계: PLANNING.md R11 / BACKLOG B-11 해소 근거 문서 + +--- + +## 1. 프로젝트 개요 + +**ReRoom AI**는 방 사진 한 장을 업로드하고 공간 유형·인테리어 스타일을 선택하면 약 10초 만에 새로운 분위기의 방으로 재렌더링하는 AI 인테리어 리디자인 웹 서비스다. + +핵심 설계 철학은 **"건축학적 뼈대 보존 + 표면 요소 교체"**. 원본 방의 벽·창문·문·천장·**카메라 시야 구도(perspective)**는 그대로 유지하고 가구·조명·색감·장식만 선택한 테마로 바꾼다. 이것이 킨텍스 부스 시스템에 이식할 가장 중요한 개념 — "빈 부스 공간 골격은 유지하고 그 안에 배치·인테리어·조명·배선만 시공된 결과를 합성"과 동일한 문제 구조다. + +주요 기능: +- 6가지 공간 유형(거실/침실/주방/욕실/서재/원룸) × 8가지 스타일(모던/미니멀/북유럽/인더스트리얼/재팬디/미드센추리/한옥/호텔라운지) +- **이중 API 모드**: 데모 모드(서버 키, 브라우저 로컬 2회 + IP당 하루 10회 제한) / BYOK 모드(사용자 개인 키, 무제한) +- **Before/After 드래그 비교 슬라이더**(마우스·터치·키보드 지원) +- 결과 PNG 다운로드 + "다른 스타일로 다시 디자인"(원본 재업로드 없이 연속 실험) +- 클라이언트 Canvas 전처리(긴 쪽 1024px 다운스케일) + +--- + +## 2. 기술 스택 + +| 레이어 | 기술 | +|--------|------| +| 프레임워크 | **Next.js 16.2.10** (App Router, Node runtime) — 단일 리포 서버리스, Vercel 배포 지향 | +| 언어 | TypeScript 5 | +| UI | React 19.2.4 | +| 스타일 | Tailwind CSS v4 (`@theme` 커스텀 토큰), Pretendard + Noto Serif KR (`next/font`) | +| **AI SDK** | **`@google/genai` ^2.10.0** (Google 공식 SDK) | +| **모델** | **`gemini-3.1-flash-image-preview`** (= 나노바나나 2 / Nano Banana 2) | +| 의존성 | 극히 단순 — `@google/genai`, `next`, `react`, `react-dom` **4개뿐** | + +> 주목: `AGENTS.md`에 "이 Next.js는 학습 데이터와 다르다, 코드 작성 전 `node_modules/next/dist/docs/` 가이드를 읽으라"는 경고. Next 16 신버전이라 관례가 다를 수 있음. + +--- + +## 3. 디렉터리 구조 + +``` +ReRoomAI/ +├── app/ +│ ├── layout.tsx # 폰트·메타데이터·OG +│ ├── page.tsx # 섹션 조립: Header→Hero→StyleGallery→HowItWorks→Studio→Faq→Footer +│ ├── globals.css # 디자인 토큰(@theme)·그레인/리빌 +│ └── api/generate/route.ts # ★ Gemini 이미지 생성 라우트(IP 레이트리밋 포함) — 시스템의 심장 +├── components/ +│ ├── Studio.tsx # ★ 메인 인터랙션(업로드·옵션·생성·결과) — 클라이언트 플로우 전체 +│ ├── CompareSlider.tsx # ★ Before/After 슬라이더(clip-path + 포인터캡처) +│ ├── Header/Hero/StyleGallery/StyleCards/HowItWorks/Faq/Footer.tsx # 랜딩 섹션 +│ └── Reveal.tsx # 스크롤 리빌 래퍼 +├── lib/ +│ ├── constants.ts # ★ 공간유형·스타일 정의 (UI와 서버 프롬프트 단일 출처) +│ └── useLocalStorage.ts # SSR 안전 localStorage 훅 +├── .env.example # GEMINI_API_KEY +└── package.json +``` + +핵심 파일 4개: `app/api/generate/route.ts`(백엔드 생성), `lib/constants.ts`(프롬프트 사전), `components/Studio.tsx`(클라이언트 플로우), `components/CompareSlider.tsx`(결과 비교 UI). + +--- + +## 4. AI / 이미지 생성 파이프라인 ★ + +### 사용 모델·API +- **Google Gemini `gemini-3.1-flash-image-preview`(나노바나나 2)** 를 `@google/genai` 공식 SDK의 `ai.models.generateContent()`로 호출. +- **입력 이미지 + 텍스트 지시문을 함께 전달**하는 멀티모달 image-to-image(인페인팅형 편집). Stable Diffusion·ControlNet 등 미사용 — 순수 Gemini 이미지 모델 단일 호출로 원본 구조를 참조·보존. + +### 이미지 흐름 (입력 → 변환 → 결과) + +``` +[클라이언트 Studio.tsx] +1. 파일 업로드(드래그앤드롭/클릭) — image/* 검증, 10MB 상한 +2. Canvas 전처리: 긴 쪽 1024px 다운스케일 → canvas.toDataURL('image/jpeg', 0.85) → base64 data URL (전송량·비용 절감 핵심) +3. POST /api/generate { image(base64), roomTypeId, styleId, byokKey } + +[서버 route.ts — runtime='nodejs', maxDuration=60] +4. content-length 8MB 가드 → JSON 파싱 → roomType/style 유효성 검증 +5. API 키 결정: BYOK 우선, 없으면 process.env.GEMINI_API_KEY +6. 데모 모드면 IP당 일일 제한(Map 인메모리) 체크 +7. data URL에서 mimeType + base64 분리(정규식), 8MB*1.33 재검증 +8. 프롬프트 조립 → Gemini generateContent 호출 +9. 응답 candidate에서 inlineData(생성 이미지 base64) 추출 + - finishReason==='SAFETY' → 차단 에러 + - 성공 시에만 IP 카운트 차감(실패는 횟수 소모 안 함) +10. { image: base64 } 반환 + +[클라이언트] +11. `data:image/png;base64,${data.image}`로 resultImage 세팅 +12. CompareSlider에 before(업로드)·after(결과) 주입 +``` + +### 프롬프트 구성 방식 (★ 가장 차용 가치 높음) + +**구조화된 사전(dictionary) 조각을 템플릿에 조립**. `lib/constants.ts`가 UI 라벨(한글)과 프롬프트 조각(영문)을 한 객체에 묶어 **UI 선택과 서버 프롬프트가 단일 출처 공유**: + +```ts +// 공간 유형: { id, label(한글), prompt(영문) } +{ id: "living_room", label: "거실", prompt: "living room" } + +// 스타일: { id, label, desc, swatch(색3종), prompt(상세 지시문) } +{ id: "modern", label: "모던", swatch:["#2b2b2e",...], + prompt: "sleek modern style: clean lines, neutral palette with charcoal and greige, + low-profile furniture, matte finishes, statement lighting" } +``` + +최종 지시문 템플릿(route.ts:106): +``` +Redesign this {roomType.prompt} interior in {style.prompt}. +Keep the room architecture — walls, windows, doors, ceiling and camera perspective — exactly the same. +Replace furniture, lighting, color palette and decor to match the target style. +Photorealistic interior photography, natural lighting, high detail. +``` + +프롬프트 4단 구성: **① 대상+스타일 지정 → ② "보존할 것" 명시적 잠금(architecture/perspective) → ③ "교체할 것" 명시 → ④ 사진 품질 지시(photorealistic/natural lighting/high detail)**. 이 "보존/교체 명시적 분리" 패턴이 부스 시스템의 핵심. + +--- + +## 5. 킨텍스 부스 시스템 차용 패턴 ★★★ + +(전시 부스 배치·인테리어·전기·조명·네트워크 배선 자동화 → 나노바나나 시공 후 결과 사진) + +**(A) 모델·SDK 선택 — 검증된 정답 그대로 채택** +- `@google/genai` + `gemini-3.1-flash-image-preview`, `generateContent`에 `{ inlineData:{mimeType,data} }`(입력 이미지) + `{ text: instruction }`(지시문)을 `parts` 배열로 전달하는 호출 형태 복제. `nanobanana-visualize` 스킬이 이 호출 패턴을 표준화. + +**(B) "보존/교체 명시적 분리" 프롬프트 아키텍처 — 부스 도메인 치환** +- ReRoom "walls·windows·ceiling·camera perspective exactly same" → 부스 **"부스 외곽 치수·기둥·바닥 트렌치 그리드·천장 트러스·통로 방향·카메라 앵글 유지"**. +- ReRoom "furniture·lighting·color·decor 교체" → 부스 **"집기(데스크·선반·배너·사이니지)·조명 기구·전기 콘센트·네트워크 AP·카펫/부스 벽면 그래픽 배치"**. +- **구조화 사전 재사용**: `ROOM_TYPES`/`STYLES` → 부스 도메인 사전 + - `BOOTH_TYPES`(독립/조립/코너/아일랜드, 3×3·6×3…) + - `BOOTH_STYLES`(럭셔리/테크/친환경/미니멀…) + - `FIXTURE_LAYERS`(조명 레이어·전기 배선 레이어·네트워크 배선 레이어) — **레이어별 프롬프트 조각** 조립로 "조명만 야간 연출"(S2)·"배선 오버레이"(S6) 변형 렌더. +- 각 객체가 `{ id, label(한글), prompt(영문), swatch }`를 갖고 **UI 선택 ↔ 서버 프롬프트 단일 출처 공유** 유지 시 유지보수 비용 급감. + +**(C) Before/After 비교 슬라이더(`CompareSlider.tsx`) — 거의 무수정 재사용** +- `clip-path: inset(0 {100-pos}% 0 0)`로 After 레이어 클립 → 리사이즈에도 픽셀 정렬 유지. 포인터 캡처 + 키보드(방향키·Shift 큰 스텝) + ARIA slider 접근성 완비 자립 컴포넌트. design.md S5(시공 전 빈 부스 ↔ 시공 후 렌더)에 그대로 투입. React/Next 스택이면 파일 복사 수준. + +**(D) 클라이언트 Canvas 전처리(`Studio.handleImageFile`)** +- 긴 쪽 1024px 다운스케일 + JPEG 0.85 → base64. 전송량·모델 비용·응답시간 동시 절감 필수 전처리. 부스 도면/현장 사진 동일 적용. + +**(E) API 라우트 방어 로직 — 프로덕션 안정성 템플릿** +- 다층 크기 가드(content-length 8MB → base64 ×1.33 재검증), `finishReason==='SAFETY'` 처리, **에러 분기**(API_KEY_INVALID / RESOURCE_EXHAUSTED·quota·429 / SAFETY)로 친화적 한글 메시지. **성공 시에만 사용량 차감**. 부스 API(RenderJob 워커)의 골격으로 복제. + +**(F) 이중 키 모드(데모/BYOK) + IP 레이트리밋** +- 서버 인메모리 `Map` 24h 윈도우. 전시회 현장 태블릿 데모 노출에 유용. **인메모리라 재시작·다중 인스턴스 취약** → 우리 시스템은 PostgreSQL/Redis 기반 RenderJob 쿼터로 대체(PLANNING §6-4 비용 제어와 통합). + +**(G) 단계별 로딩 UX** +- `LOADING_STATUSES`("공간 구조 분석 → 스타일 요소 배치 → 조명·색상 튜닝 → 최종 고화질 렌더링") 2.5초 순환. 부스용 "부스 골격 인식 → 집기 배치 → 전기·조명 배선 → 최종 렌더링"으로 치환. design.md 진행 배지("생성 중… 평균 40초")와 연결. + +--- + +## 6. UI / 화면 구성 + +단일 페이지(랜딩) 서비스. `page.tsx` 세로 조립: Header → Hero(CompareSlider LCP priority) → StyleGallery(카드 클릭 시 `window` 커스텀이벤트 `reroom:style`로 Studio 전달) → HowItWorks → **Studio ★** → Faq → Footer. + +**Studio 흐름**(단일 컴포넌트가 입력→로딩→결과 3상태 조건부 렌더): +1. **입력** — 좌: `01 원본 업로드`(드래그존+미리보기), 우: `02 공간 유형`(pill), `03 스타일`(스와치 카드). 하단: BYOK 토글+키, 에러 alert, "생성하기". +2. **로딩** — 바운스 도트 + 순환 상태 텍스트(`aria-live`) + "약 10초". +3. **결과** — "Redesign Complete" 배지 + CompareSlider + [PNG 다운로드]/[다른 스타일]/[다른 사진]. + +상태 전부 로컬 `useState` + `useLocalStorage`(무료횟수·BYOK·키 영속). 전역 스토어·라우팅 없음 — 극경량 단일화면 SPA. 부스 초기 PoC도 이 패턴으로 빠르게 구현 후 확장 가능. + +--- + +## 7. 설계 착수 3대 차용 결론 + +1. **호출 스택 그대로**: `@google/genai` → `gemini-3.1-flash-image-preview`, 입력이미지(inlineData)+지시문(text) parts 전달 image-to-image 편집. `route.ts`가 프로덕션 API 골격 템플릿. +2. **프롬프트 = 구조화 사전 조립 + 보존/교체 명시 분리**: `constants.ts` `{id,label,prompt}`를 부스 도메인(부스타입·스타일·조명/전기/네트워크 레이어)으로 치환, "골격·앵글 유지 / 집기·배선·조명 배치" 명시 잠금 템플릿. +3. **CompareSlider·Canvas 전처리·단계별 로딩 UX** 파일 복사 수준 재사용(React/Next 전제). + +--- + +## 8. ★ 보안 결정 필요 사항 (선행) + +나노바나나(Gemini) 이미지 생성은 **외부 API 호출**이다. GUARDiA 보안 제약(루트 CLAUDE.md)상 외부 API는 원칙 금지이며 현재 승인된 예외는 `api.anthropic.com`뿐 — **Gemini(generativelanguage.googleapis.com)는 미승인**. 단, 본 kintex 프로젝트는 GUARDiA ITSM(관공서 인프라 관제)과 별개 도메인의 독립 저장소(`zio/kintex`)이며, PLANNING v1.0과 사용자 요구가 명시적으로 나노바나나를 채택하고 있다. + +**조치**: 부스 M5 파이프라인 구현 착수 전 소유자에게 **Gemini 외부 호출 승인 여부**를 확정한다. 승인 시 `GEMINI_API_KEY`를 서버 env로만 로드(코드·DB·커밋·로그·응답 기록 금지, ReRoomAI (E) 방어 패턴 준수). 미승인 시 대안 — 온프레미스 이미지 생성(SDXL 등) 어댑터로 폴백하되 image-to-image 구조보존 품질은 재평가 필요. diff --git a/kintex-harness-v1.0.1.zip b/kintex-harness-v1.0.1.zip deleted file mode 100644 index 8219eb8a51b5c87bb677fdb8a53f8ae113155b53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55187 zcma&M1C%Dgnl1X5ZQHhO+qP}nwr$(hWxI=AW|!^ivRT1 z`e%XbC|2_)bT>xC}5pR;i?FaUt^_a5TE0F=dqMCHWj?M(lPM0=!Ut#$fekjzwm z%5N$nZokurPmQU=Xf>KBK`Dh67gs9Bs8&^@iqKlKaYo)+c0*UG6p^BCaE}7vfUyaN zC@;(DGAgV4N@w&P1e|W=jPRa^UGZddxg1Y-LU|@j3lw+(sZb46hoq^ix|32-e@0I6 zxB}Z|qeAfBF6<<)izPv1o$RIg`9;|yrrBJ!hWz%0^a*HO>g+Q$r>O?@)QoB8CSJ5v zHeZAVN$4BL=V72Z&0b2vL%ChR-?S5jqp99BYIs#NCd)|!IoA59F#XxG(QJPjSU=|v z=BW<)v0MDAgEXoZ(IxC^DZKVLq)!kDl%KVy7H6Rhp087&vgywj~YcKSRhtq*T#@?k@fWtW1?4Ad+I#- zG7GVVPsoNm1)vsM?CesI`1>j%0iDHFbOV<%tuzk}aZ9bbLIx}j&ATj;CcTt4I{4v7 z4m|CNN_n=Wt?pobN%)KX7GkNW(A|JPSr>SogbF81kG0^7UfyJ9Em;PBC6c5ZqTT$P zO>0sF_D7KG&weXUMCf2RViU0p(WRwbrC2s$rr>%y*ry~7hpNN#>S*_4mhN7RF_7@- zf=O_683ss1eVPt2x{{RF1&F`nmBH5=jecFLkde!ve>T+X_uXc6|rEufli z>-odgbQ>%io1mOjX3SV;NLzP|yJ2B^O;YMyM6)G%$wHGHMH&g`{Oz`w{7^6NcQCLJ z!JAiH&5S%}n
  • gipFr;T-AFUti6rw$#Lr3;JMsV?hm?hg>82e7K9$<0KEDg& z#un`NjpCXYg5Nkudq9}gNd|;6nCF_f80xfcp~ByQe{uUCgF|n`cMBaH0PunR-?%Lz zE2Jj+zqtL6Q8MsfM@g8vp2DU&X5VuICp8BYb<>mEVt1_=rS*8VeHwd}GD@&mU3iBy z@Rd7Sa0qj9x+`ZK5Jac7cA+#I3NW1Zn?=VTt}g_i-!8T(4c3rI&ip%bCtkBWBG=^T z0eRnXBPgM2bnV+uQ|M8iWhM$9*P|x z(-k*DF5vj}q*;z!xCtBgmEPJxx;KA8#_{AyZSp&7w#EpmcJM@?7I4cr_02ze5MN`Y zR_5lVz$1J&f{n?kvgBnI*6oEoiDfjyUChz9GWNe?1bSEqbo9tk1XwUT9XSa##7w3y zEn@Vp?*?+*I3e8z&CJ`^0W|Oim3o75&0L2J>9eIP>1eP;p#1e5$~4mLi5V53MLMkp zIBvm$j8dT3dsqFbRYm~pLhR-X&84Qp#_;A%x@Lqr%hQ?L-lKx*tZ7vTL@q)?<`o&q zUqBs5T|U@&Q|4MSCl#g1a%Nu>H3!E<4693=9V7lC6hwI5MJ&B;DZGmKmK)T~RyAWx zKUXQHfA?b;=*rU4$FkX(M*-6ZNAm3BN%#hp6+|euGkb`n}ckg``4fr(;o@BFeI-EvFY|ucz{2Pithd z?X+30wI__b{vd{X6v)W8{DR!4ce?~Qjn)@qs4L3S1`iU8C{fde2ZRb%cY9x9KvBHp0lOsr ziXbUK-M-9Zdw~k*+8jbBf^0OxWPMRVhI~Iuo3+R|&`1~7wepRUO?X3Hkx9fBLNgiU zmdKwU2Kd|<1)W?W^QD5%&MO9fs;IY>IV@BOVtVJrCl1^t=2_Vb7lO-6iW(xih=Uf= z>R&dQN1Wf>t^>FVDfpnRXM7;>_Q;;xx%Ce?sr@L7bu^eaSoD$U$-Xhx-7rms(?aq!$8lcxyZk?2q;{P4vu|;0b@O5PY^2;2S$b3X>V3U1MYEP$c| zVqPMLb$zFm#z!dd4dww@9W-5RT{wTnJfdTaxP{mU%9YyJ8r!Jy#qLa{e&x=CaH|O`z@qAU7COB#a_l<8$|m$n1)&5& z)MWN3?sg~*>m=f3jtPz17{-W0#oc_Za9xICFcAXg7QkB!-eYzffSE)W9y#rKTM+C- z>!hHA@O~|pHbniY9HU71Odr{rWrQ zbb$a~6Kb4;zm@HxBnoO%GunLnYGr`55^2N}Fy4VIw8q1|f@}tm?*mGO%#=uHt-B@l zPKn%;4b-PiZcU%TaJD%Mt~T2P9Xf2JJs1x|(^_6h<6@(EP_U~DB;SEsit%`IVV zAR>_lP2zXXOjdvi$2@vvP2^2@A<<9_LPdO`QQ~pJ-KNLQXAhz|n^su7`#*|0?!Sm^m@Y7Lf4?&g3jiSc9}rD!t<3CQ z=^efPsq!5E!u~gUu?@>c0cZWOJ79GA)=ZoUgfz$2Y*K900y(5S3Ex_H6R;9x!0 zqBe^uL77xoRuobznch~`x_Lesf=aD7=n|Pusq8ED^EB%ff$wAoYc8g?xq5|Q`my&U z-{sn?AcPR|+WDs*mlID;ho*g$hNT<}>;4sP_c#BYK0VUSb^PjwJn>FSe0P)F2haHR zEETTL@7xy~3TOHghW>jEd~cg~Q_oaSgpI~~jg1}on2rX6PJq6hBry?S4Hn1sU1HlV2x&V`2XbNBQ~^YPawUh z(zb}>E-u-vE5f-*hdHRxpJB7%7B8QdA0;wzzQp4B&OWV=0!lNv(8%2FmAZ=#xFAw1 zs)Xp4i>6YoxYf$^+3-|M6JPdn-1E|-VS430Al1}X!+?=`wx1GzY>FZY_{fKUT#*qK zgtoQ`sFZ;>?w9d)5|+txfm!s)Bjt2A?e}@!`1gapA#bljY?u5{M*rPfB+ue_Lr}33 zg7K!7qEl}I7{9D(BDfd)6~<8c}AsIaFF9ak@>fj5&0P74EEQpMS-u3lJ?t1*uv3xHNay4%NgO~_ zZGNGe+Y@LrGvJYx&iM+(W1N6WAwX70y?^D_l_#N$&|R`zX{?^w%x!?WOGCIIVF;H2 z=bOvvg?|&og%WVUFNp`sF=V&^-Bzho9IXyQhc+%7a*P!6ZlC5j8Z2<_L*WBz_hpt+ zNO9E`wdUsoX&L3?dU=Hxf`eD6!Ou65G8tnUtu7k_Uy0@k!_D+PQ#E#_-M)-P4K6-f zc*K1wJOve;_>-wZIaPYtN>f!ySU?$W*i}`61&${J=1De3BtuPt?yTBQg$MM;&MC6% zT-D8vnl~@7-~(WbeMz`Q&_DvDHg{B?V1VCQ9t&qc0K~v*mxaxOeG1RJfE3oW*K{`* zzUQN9 z9;pc7es1X4sT)Lt?W54=p6G;Lv^vDJ=M2>5b?wXs)XlUgvNISQUzeBn2tH^uVIxe z*_`86`FA3$O6;s2-?Up_QkEZ3Y-N;z<4UQ5I@WqB_KUB=bFB`+yY@!MM)U?OKUGgQ zXp<+7P411Xp&J3R9n5vH8l)<RCyyNQ1~sJ3g?I^TYr;u zV^pG;l3Q+<9wsIrTE3(sdlj1q!=YUZil)lP4=uH%* zC=DGoi7Yzu^%VaGX^BEjxX)*Ef2m~HCBg>s~mhD;1&@xf-qd5{iF>&Ow23VYY#5jC4;xlL38n;w;0$zDWIf?ZU$KHItwo+EqS)g2dfEhoj*Vx?F5q4A@-c~_5Hr+64w}wjG`;1l@lRw4EL%j zD_uHON&-gIc%s0&Y~;yJ1Iz+~T8JqU^7te|5JAm~ZZqPd@3I;ZvduGck!#p|1qK%N z;d;~1gvkWDjQj9~=mS*^Q>zUv3h@qJZ@&}mWAsJ`B?rMCVg)oaI_o#lWm2h_sMF%Gc+9nw2A_^i&1*|FMVISEd z?qyP_{!F@)6Jj3XRTagijeOvvA~>2Lt}jLK49F(`K|+PmLWHAYyanOe+E_BhFPLna z34~U1ntZcB9Uh~bPEgrJC=+7O8j_SQqo{3jD{39`=Wv%i9p8{j3P+G4i27%fC~sJ^ zIE(8{A@mePH7=`jRbrA=B5(?O0UDowW+BoK+~Mh|z>lokByi>oZwZUvv1lw}eX~QO z(g`gGx^t_H{T+GeBi7GKF-IqLYzX&58#?*6g=R!2jLwmiPG_sI1{NGalZxnVpRuMY+pbe+)H4?r6e?8@B_G&ui@PckuWzvon)OF(}A%7CHhusUdV%o&WIBZ>EYqM8y_ZEnTIVwnySL6KpvTw>qqyuGTKNtk>qrP@ z<)IY#R=1k|3seIki0^tpkP0-_2eiV}T+-&!XbP2Tf);){Tsw}nX(xiO%;hqcJ z*ok0otUbBQb3p`P_A+t1i@o}OY`nxt!3qj*=ax-x(rJr3VJ&5`(c#)csd$HE$?7>Q zmB0Lv$!w=&3mw&E|2b`qK0tZ8)WuyO%?jE+9bV#Vl_Q>L1UXXd%wQ90DnJi5u=u;J z@tF%Fy2xkg@60>40>r^)S&#qtVb$+hjwSat3`;w=hjdbDX0#J(4Y8Nwd#Do|B_8rD z^b>M~Q20lLs)ml&etl2OvYi0kr=+8I=OQviJzPRuP^A!D#v6lVB@?a_oL8PkVm9~A zv)%J;YFx#4-?e}}$_hJvqt@dBQ|g%_n;^~VFf1J(d5<{-W>FO9ly5T>9D$R^>UKoV z^^XW-{;R?i3lxVP>X*8@k&7mtPGfpO89qr`N`4&O|Y z5Z`TzgfNi(m_HW^A(53yeh0SSnFPVB5PIO!uG_cJ`w(95lQ`3%Fr1scY}Ye&pn9$Strm&!65e0{415O0^2N4UyW@FV5c{eGhpq?*sF#vi#*! z)$DW!8sYuXXqY5gVrqeG+upp1F_`HhC#V5j_uZ4Egs-UUb<~}c7Q$) zfSzo*(|Zr7Z`7Ja*Y1=^xD~2r*{fUHtkk+sTQ8NrP92(n*JK|#^0-}c9IxtmsA zQnI%A;%ic$r|;?D>HL*F`s-W1PHD!3n&_k6NYGnW-S2Kd1p=T2IRlqy9a zpYX6|U?~#Nuuo$DAI*lyCMPt@l8Ax6lM#%Dw=j**#=^u=B&3T{Y|kCVX3<~fWmsNT zqN-dyr%(hlKVe8wCl2c*TqbCIEdU-B@hije9pWqxTZY!V6;%?5-d{7PBO@;(m>=ud z;1xCpRPfma87!Jmcs^!1EiJM=2Ww)E++wy#jf%I%XajX_>uPx4jh`r8X2L?0qQptv z1o=7G0CTl7sHx}nlQmCJ3H3HNJt~yqx2`tj$0OcI(tx^?u)`qI3t^AE!he& z@M;%&jR>IExK6~L>VrU#J#%i0v2VB+!GCxMD+F^?Ju3|HZ%dq(5YJ6)cqk`P)af0 z62&dSH*3-MtGikx758D&&3vw5<2y6xtazw#_v+ysqH-;Ep-_qANvbMx$@42Xk)q$d zEar}9X-o2p(6WuBR4Va{gFeMLj+;U!0l4BZ0-byrpe`xkjn(=K_Ax(eZMm+~i&cRl za}gA?j+9S`Jmoc+TIWH?i)SUn=R*M9rJjkP{uZL;YMOMnkFHl!`o7D*p4GTKg8%cI zx`EPjBxp3Zg?l~1|M#7*myM?A%{ld=b(_!Ti!9X~IyQMJ9z^SF;zN|d);DY}`4XG#hXi56J@SW0B!eIXjVmo9(xc(UtXF^L<>(%)O?VKAMtU@)6xKgkygz#g z8@T+5C;aJ!S8DM>gcQcY6)w3{R!c8olt|Estbd_7R;3tyh@D#ulCgK|&D-mXk!XEJ zqLFA5s)NF{B~!W>p?3^b`o znFIA3f?IwUd8;S!6wkamf&F2#so+;WnX~*nw;khvGw~pdLE`w=N+7;l1tzrNbgyL# zP1W#Fu!o%$lY1KReYGd%RP?0{&LS+Se==(sp$74K9BXTtA~oR%I+vWC%#N|R9L`I1 zi569!12xKsdwQcV{L`h|p*Wv@s?v!9d~r#hmCpt(`b`QeBt4I2pPxUj#^$IUyO+TS z(7z&&)4xR?*nj;WQwNj(j4~_#e^KTiD9a*DIZ(oX%YU^10NTHyn3}m*S=j%-_3i%y zT&w?Sy#OKJOq!=WCl<3(=6F)EWa?G(cJcPr8ARxUrRl&i z_nH);2}H`;2&sIaZpk7L$~0aH4byMZX9108kBfv1axV9=R`z4<7#p9>mceHo@czAnr;qGcFZcD2yI8373>^=L_7x9U zzppdAgZTHheN!$LzI<-i(}3)iZJoMK0+>7ZA9p4=z3mQ{mL~n#%nSuqn=5bjgNG3J zB%dG>Z!TPK#GbqnPhc)g8CZe^K6CER{u z=iuUSK5hwL#)F}*1=uWH0tDX8H+nb>pWL$t<8FSDcXVM--+<1Ty&aubTm9Cyv760a zVVgFwhj3vzaSjmhAQiKDxi`AS3K#J8Z15Uwy`hCb_@Q%AGsdOCurgjQ>*Gb=@gwZ} zfNH!|pBCa<6UIF~#OI>!k?Ubo+N}8y$LeC`MdDbwiJxuKz;ujyk!l{aW&)(-EG%eSMq8J*VN-1T!X&MZXXDW6~XqV1d^@mg?NkJ^h9kU$aB{BFAO=(HYKK8*v=pC@ zT2{uS-)EuKBMolm+WKEGVfudcwVOX>x3CKB$J$+rwXpq28uWc>h--T~foa9K>W0xI ztdt1B=aC8{{|&w3!Q^>6$=p#XwJ3y;bIP)&Svc3f*_Hz?Uy$YkYRsXIsbD`U(HDZ0w!C?| zk6^p;^19#@<+?|$#!(V$-eAw$?iF$S&fjTgqZXX5r znVrSD-3qR(awn6BpFIUWua}=aL9B%Noy}$gZFP@2Rk3FIzGJOlooK#sXS7B&7z4o( z@Skv-{R@4+SOZub-U;@;ohzq*|Gh?w^(E5%z_PsGs?oa@U~0I_i=F?yJTNdHRYLN| z=lzL1#jm0BSJx`*P~542T{$&I6IaxUMDdA)cs#RNu{g6I|pF`I| z!mMYz{LucMnU_|Enm*iaZ+|AvPhg%A&*Zz9qk$k_`+uGSdj|+_eb%xX;Q-kk9&11s(%Ax#W1cbyN$?ZNU zwO{!*;>Bv6KLtJKrGQr3Gd%9N*5tx+pbETSWy36QYo(I6qOB}zHxrb3yI4;pD(1Pp z*~4!M;sWeRiTq&1b&QTlH@8;C(nKi+aTi)1myrpKHp&hp9x5cf-O(I!$VQwkxXz!Q zoSl7m2o@b+YPPKS1B^XiROjS}!xOh@XzB{o3xoGj;ExMb#v_6Wj->>V$vyccw6EqU z;y+h#dBkfILbOhesjX8?g{`vM_cfnUVhemLdv|R0-q%K-9s*8O6i;wCiL*|5`+UnS zqBbLJWru(^Vfmc=2tI)&`zpq`IkYO>SC?;PJ_%K0heYAz)NN~p=TpSAWhHy8 zA10VXNKD^JTWyiS{pb+)1hY8Jd!A*~_T#a*7g-7B9a@%~ zpd)tM_~`vV}Haa&cq`!H0lI zSD@*%KYwInadST?m7YdOE-dncyU0w#8Eo6~^^<5SHQ_kDeQG`HZr5;FkN4h55xlht zG9sNo#6oDXTFlbi{_Z(y@7D#UDfb`KWnY1e+X{8p50>p0D*&S5%Lj{hw4zz@=)|(Y z?sWs1aRxZX^eW0yfEN&)Y#0%yKqQNn2GuV-KViJo}&fX$to7G@xY6kZ~*hL)w z+@XQW^kl%VQ9)Wcr7Zslw4`*YEfV9zaU{UZ6eWrcTzN|tS&0c&611_UDZR(eaTEv= zZlTg@qCAug>kgn+m&V77S|oE&el>?_%pWnvUnDb}MzP`_Fz7n6BFQy*CkMI(j&TM$ z-SEH{58SKaB5?;d4PWN6aVbp~Oz%C$Lq^-BB8IEWVG?liB4C^c-bL@rw5z*n9vp)e z)ybqeZ2XPbBB!TPi8M+I@G1cQy4CK}vcB3XDJrS}N+SSu;n@C|2gqZN0s7n3uSZy| z33B*73*A0ZfbXeS{&Wt04E%+KL6E8)Xeq(WmjRqu>6hDvs@Au2E>q%#uUjJxS^9ah zaK(LS!7d;@sZQhR)XCx)NmKZ~s?RMO;oPK5a-_$5Eg!e|OTlxDqTS8n;)Lfvx9b_b*7bC#50cEXy)I!en>h&I1`afxMolnE*Js;L~$_Zb-dq`9|1a}YA|-~f_k8@k8SOQMxuxwjC>;3_SnDd~G$|sX5;1gqJ_w4w z9{MP*^ES9{l;=;c06R;O;hv6EpZq9V1`FdzE3YxsbNH+bZrVEJd{+o1u{FO26-p-uPUZ`kg|qMxH49^WKY8qJ8bC;mw?v*ncz@2tCp z`E0iqcwDRTL@qVpCS71TQ`{IR=V>2m=Ho4$mJhN3y)I4|Z!@zHs)_YjH5-|8-?h-G zONw~y>WD%PN(VyTXVz2=OI`?u+jv)i2fDD(eH!>lK3KVr@)nj3GkBXR%UOAdI6hKX zrg;aIPy&|OqbJA^gCHCweGO+R$lCo38D0N))J|gnCNTXRwEdjz;-^U{)TZbRfshfB z@MAHWN$oXxB#nXD9acLUU~}JVPiu&}@zme}rBzD{_B(`&0A%pttUWU7?!?3x26zNs zwjvj0_UVs$)Z`rPd94Mt!7{1Jfn$h+Z56ZO?;m59fdbcg4+3 z7)yVcG57`k`pHHjLDz)R3r=-+r^1OHgRJ36a+kZrB1R0naE3F=S);96TUdmCBm}1> zw9&13EDY6}QR%5r67R+HM|!pVzWgxC+i zR-JI_V3)bPcRy)3YGMLYY;s0Si3&~GJwkd!n7A!p#kFtE7*OerNKqr&EOaNzSfUl< zjS)OLTSOQHNmn#kZUS3h3H^ZPJxA z!WgLmci_|r83RqY6@$pLpZEyrfEb;6l+6KBbq;0)?Jl?RwRNs@w=*J^)6>Rwt}*x# zmwb=eRO*z`=B%J!Y$$2fPCHx=*97vP5*Ge}DeU=P70Qpj&39lsNfIvEZI7(A_$(^- z=ck6;Ch~7>FppEQorekK%P;ad%1+MJ_U9^=xlH4Qr{A7qOP>kn;2|>oST6SEsBfX*>AN-|dg?a~cVh#R{kw0S3)L{0XZQA{4Gj)@oPjFIi0D-^-@R z_Ib)xv?phBBj_0nubu9Ot(6s4s6nx~IpLP*4)JLgvfa+u8gwrjUYGYnok|qLtCkru zd?~MDwl;u-j>V~+jXC73ZZBzYDecFKV+;Ad`)jD;IuhpBPe^q zjdIR^EgCiSK0XLtM@e>UM1tI^Rm;aJ8 z#{k0PIs7JrH`4%nPeOAD7QOk`ztuteIS8H2t7V--)8w1`VLmtoHpj9Vq_9)XAYXSf)nkDe+($eoL@`Uwqpp6%alwBzLrn5{kRCB^Y3fFHzj`Sc-|_HW4&ICKqx3j9 z5d;9EThWq>mjiJRglgoH1|wwK{GAi>9FGvhi``1d;-p*_07+H zW3QFrViXaVz!aGE=6R&N9xI1tQCo_ERlwx%z6@;yws8-fpH;0I6A`nWq_OUWpJK<(z2f8o5cVgg~?x5^y+rqYn82KGi;98#QI~N(3rWcb76?O z6;&uy*rx+0q6rnq+(av!9mAfC>z6997fZ|lciQK4DrQ)z2WqJ_kNcudu_CADL*r|v zAa0nbYkXW{W&^8Jtp*K4lF?G*RMz58v!lfUlY;I+9>PyI%CxSEBAX*bNKnGeKBJEG z3vC3$vQI!UV|I16(HF{iluf4MzR8Edr6?I7Z12E^9DBam~y&wyNxf zqnr~aAZD!4j#SqGKNAOta?t7UI5Oj+`LHl0OY$Fm!;25tfdTvwzZS5Y`M6{m88FL8 z%7&K>-81DT%Ry^+ptw6adDWke2V6HK>%)&NOF7r;S^BmQXiaM!i76U`zX%4oTm0~Z zWn?*ot*_7meF0ziG8Qd{2jnLy;WqJ`IHI3dD>2KmsnaMFpbgh=D$IF?98hnq=23fx zc=wg{?HFs%O@ngVLQJc zY+Zc`vsekjqc%2@%-5CrzTn$4?E1YX@d>erwW)0Q5zD_BSqVvN$uT!ZUE|0a_(+}Y zMu`sn-DZ-3V`Wl16tp>I9gOqD?}SK-z$5qyak)r;lkj}s+YUE!nSf75;?0ISVcOWy zMnFVu1&ByUG!JvNS8m8~5G})w(z4exf+w7SyJ-UQ?p5QtxCFrNCA_e0T#N?T+wT-l z8+A4G6Qf^{A}$`Z(Cv~TPz8w-ZJf;pt>KtvMT;e?JCZu!U9vQRWeJMk0FRh3{K02IIi%9Tc}-G{@y(b@$xxiyh@GC}q&KaaqXgI0vA3 zvUagfI9Nn_vl2?-R>2fSC~W2qmo}-c*u?J}Pyv}kyPv^2m!Ya4IRd+P-etiz^$H+e-HjJtl6#hNxMDIqMj@WW>75_9~G%YfKG{Q>+Ek zSNe_m-!^;PH7DiLlz7vOeA>Q0vJATdPpnBff+>u@%N#J@GI46dC7XP92H|!l^=_c%XV;=b zVPq_z+Ty48;&vZz%a>KzTuPO|p1X?6adjK( z1U`%y=*UaM|C&z@7834zSNndN3|T+|Yv_@byX)OC-)d+bXXUP$sHC?k@jf49u?g&h zhAcG0#f~$)K8d{8BaEm`VelX%#Eo!}=6wpQbi;F{rEG@m4Y^$Iq(<1&Bak{-k$i{z zxJ{vIW%{Hg0*iZn>J%OwYOO#iwVWh!Hb^4!eY5EEN6g-E7tW3Uqb)dpjPj^lxw@*? zrs3O!;pSnG8nU#UmugMlZz?@^K6fdx~^(Rki8sQxiaQ?#Q@wR9g-MT1Cl% zwMn#@B0u!Cb&C@iO3Nh|PkaK+NVyFKOB5cnhP^;|?om#ACMk-(Hi*~RT6C14$?M}s z<=G)?{P-@hG6Ba|Zf}c~Tl-r=>9&)ctE=9&Y(i9+rgkvS5EIcXw&7M|WT};JHRCC} zAcCtpRU!SjFRUY5V-P(onC~cT0)vjknRs|xYi+8E(nFUec;d!-Mg}fCDygb*{0tB% z8q4UV<1Sf=>4N7HZ9l}bb)xA!iqcQEOQ)~*^djBt@V-GeYuMK#RXw}oV1oy)> z!MhZ9G;w8b>OOCe8nuBNa=ke9V_ovyvh2(ItpP?eOiUxGBkTIb4Qokl%l_&TXe3Unp5o_^Q%>qeOP$B ziSC}XOK@i=x^ai^R+AOUL28|1lzXz6ghp-7lO+64O+J6iksGQ?AUPX+*EHI?u_ka9 z@2r6E^HjcsFjHR=+;@N$NTw8$3%81i7&Ffs1+__Bwt#=4ha)FeX(Sn3_vAj1U%AAT zqsIWx&N@GrRTO0U@qVIxZ1L864IjRswfQ}Enz$c@1W{B_4cD{G{b>TUaJx3r0mV{+K4_!(yZF)Rr6|I@c`&W<|CdmsLLFudEM)f0G$gLq}3cp zk0a$D^1Rtl;Bxr{Q0fSQRkDuWwE`Gl8PW6{gV@o0k+-FSubeekDThe4TO z{0J?dG9?gm#-wJxjLXd36*=Yc{k|lGVN!3Sj9c-C+c1Hgf(HI+0xWq1SVloOsu8Zd z)cp7xmtl~-=etUm2>Bl=WZ#a=p)xW(KQl%qT|c!w8Wl3eGi+;JLL1KhP!fiZxgS5W=n=b^O%~(3PGs0I=5*!J=$ag+7m9|Xw*G7?QE~=i9_ZOPj zVu9@si~v#yQEnp=rqeTvMc!CaDF-GO7If*QQ$x&tnNa13!rjj-%3R5SnU`fs<5GzX z2Bs*?J%g|a&D`cSBGq9py)6*@gLcLJeZ^)Z>85NNxBeft!26~1u1>x^hns5*v6E<# zCwf1<;JdpGvyGnI8r+M1T0asdj`1}GWP$P7xIB+f4i)aA8OwQ0M$-)0jey9z#vfsN zK=EEhw{&QpwrN~8NxKvxV3wO!%+tNH%EV<`=lGQsy5U7;G$MknicE)$KcghagZb~n zolo>!I(tGxqh>?W$LwYz9`|2?J8O$9iA5FFurLYJJXJm-?gitD7;dS=>L8|$D7Sf^ z@*=SwT{28|65Qo+H=tgyYu0|GMp9Z#pIIY?(&?3}k$*0j4qfKRayswY6>3ha!T4E0 zvm20{Ph=4kQ)??t)H-}aOCQ!^UY^xP5;1p4;Y{|foG-5MZhIRa%aJiKc0nOg3A+5| zw7V?Fnk>9F>!&`%7Ss58C8Uks<8DY?)wT?>)~Fj0&pAe{h20@B#2Gaimb9Il;*CI{ zD6a03x?9u8=EG)4x3V+11cZM2+K4RF$Sl2wJELy`;=ku@;48fDTaan8zkLA<@hFpsr7Lt505+?f zU&N&)|MiG!fR--f;+X6tl`dQ`cj5+{1l4U5D-HNVrl*pZ8qbb53H$wNX^ttOW-dfE zsF8!}c*2SsMJ&Ze6k+~O7erKOuxgCKUP6ib+UVw^kwW_l)b;lKP$%b%{MRK+xgWi3 zQDj41L%rx`>z_#B% z+<>2lW2EVG83GY*(66floclI?QS8lAll{0O<&I~H0TEYEtE8op0zHh&~K$gE=tT-0`_lEiXb)NvAhFUL8 z`BmREY7{l%3k`}`YBT;=jqyOu_mgf!3JKM#4Z)ZiE))b*dqt+`FL4)o-2eXmLb4$^ zpg};4z7rhvdWJ-oVoLXj1goaHm=DbeK#XZXBf8upbKXC`+f#FCj5Zdm0*o3Pt5nC? zCkt$Av3o6As>Jiv_ae0x9HVLs%}aP7bokQkkzo6OWl6Z-BN!~IO?Ea#(a|SQNpzAz zAWxwy!BvfT{Q^}dQ7*qq6?!O7u2!OpDm5O0l@0n%Dm`goygH<4UW1^d`@|(VJgu97 zIqOanFDB22lkk>0z3n}X7l&F*fd3jr zq&YxYqoJ$*Um8V<_i>`?XyqgFnu_w|r)(85Ip@+Ht59@T^C!vjmh(k1Hj_}RVT>Av zbG1U~n{=eabSo2Tx^$Q!8t~T>$}Zg~p*I%!*LY{AvL%4tscreQxVq&JI`pmN@#0bO z+k5^KlqW3 zobp(aikQU~C56OuJvgGqWf~H-q{t*2^7_sWjlRMtW0SSh{*qESuWNF3o6`R!lPl)s z53|D#bT7w!r?2Z><}hl!+xE3bS_y=bcw@CdsFpF;ip=g;>@k(F)uI|+wlu>B+AoIq zkT194cg-&ho-f%OaynmE8c5Fh!fNpK$M|`Di43j{Z1s03NkU$ENIK8aLa9`K_v??L zb&irRf?aUD>fsB2=2*i|{4hnJg+-(|u2W@}!U62q_$Yhb{(i+o(* z6{2{zt4s3MEFp7b3dFv&OwEN=u09-*DP-a{ADa?So-!e3)%r?v_~x5=&J$gZT{@CW zCB*xZ9nn#qCrnoP%=-K~tTQFN(7H!%tbQ*SD#7s^Wctx_6kdR()H??~@z*`YEt< zInCNeU1kwSQqqxwKSaT!GW%Z?wS0vy-|82!qBJSb7C7#yP5!o@Hc>aQJ}n`Vq*0X@ zqY4)B#p0MRsd}T0A|pZT)K?U)+D2S$+Ov4CIin#`HrA{%Y7pK14(#Gs6=d>BB?Ju) zVeyu6es}65J13=Vlm+@DlpZmV8RRCxJiUJ*_~}ZP-6#v7r(*`ug0nmbb2i8#2=N$) zG+v?C^mA5koGY9ZRR6jyLMox{oG1f!-`rP4qhaDJxe>~YMgdSSX@*M_NDK2;#fi|U za`qoJizl9n0po@ls@`SEP))tX38RgAr612R4Jv@AoJ`5a+XPH;2ES$BtNL_dQ2Lvx zko`cVE%56lY&wNAd?4M+)1$Ddie=8|pa*R#x`ykBUYyM>Q(;#HkHmfb?%2ENuNw9f zMfnmkU3vh*=EczYkGf!+MxTF+qD}s!Una!=5z|xvxziJ7#o-mK9?cQv%Z>CXTOs?2 z!p;;~P6`iCLR>XE1(~hhquoyl;^e=EBGhKDr3ba?{<^TUL0A`Kl*&xkD-@4)x-=vj zPSAQ_p_J?0`jKmNG*wB)ie`5zN-0Alu92IuSfnIbHA=c2h~e`!!WnD@1($#k@Ly#R3m|C{NY+Xt^Qg!VCY)I5{y z+1CRTz0Dq*t5XPHHX

    X}}2B2^vPcS3lTM4r~+rjT(1TZHnjMWb6kR=WFe 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 diff --git a/output/visualizations/_smoke_wiring.png b/output/visualizations/_smoke_wiring.png new file mode 100644 index 0000000000000000000000000000000000000000..eb650e0bd56830f05f32adb89c70a600f2fb4f22 GIT binary patch literal 2279 zcmc&#`#;nF9^aQx7$ISdHGPxTn9C;jOF9zcOO8tm$;LFBnYny9?)e%bq|mUXT7{Wg zrrgq%`eGuE+`@6)TvG{seLvsFIgfMv2j_?P`}ufXp0C&I{d_!Lk9Wo|E)HO6h%^WU z0z00ycL#74I5GRhfcx~oJQV;8{fsxA6hfgBV$6aG6ap=PPQd#I&_QrRlpzL*GC>*` zc%V$oj10{TO!W-V#%Rnb1PWz_LcxV`Cg*?($UPNk1PY!&^QQ*Ti6HppfIt#p`_lp< z=zudYj7$xS2%!_;tT(5Y0pgK8L|_7Z1PBMGhD8%-@RKk&g+PxEqmkggwuJp(fn-^iADp@ zr;!6_B!3zqJTinvz=PF7WZ1cKW-!bGX1qN}wqQ4vFw znhX<_HVAo5q6bN^siXp!DC?exT>?PKV*O*-VqMK*eT%3#2`0?CC%oSR6ETWlqB4f4 z^ctMSi;)L`q!b+OZ9QU2md0$Im3%bbtnJ78a?2HVlC%G}~hBILD6}ycF zh0)ZIF=p(Pjk7VWPYJ@~1}#l>>BZVv6tUrMuKA2LF==(@7ZUG3U=eBjygc1Tfy9OR z-*u3Q^tD9wwyVd~*xrLC5DZ5MXEdP?Za z1N5k@-0V}yJrqctf{tK#JLxaq*B+QU`A3%{v#`Kg&nd*d)XrFDQei3(p0{5zRr9)z zs_ZevjMM3ISZC*hc@rz`^aVu)u$)|(X+%&^(5z^EPI_7?VwkPxVe=&?^W(s-p;6Ch}pNlx}0eB>pFg}r!+e|8w!OcwC|4a z!+Ogsh0Dw1?TNde=X!ft)`=VQeO+B$jg5``u!%)x?8ZFMUw`=SnZc?Oc4b9HP;f8; zf#_do=>IO0mrX3`v@JMRp_BRBi+B~ii}OoMP&KuTH`5PXS#62C34wvr$>LHQNM|fI zesi@WsGdC1h%9#Y^ax#$*T6p)5n5I8s=X=!)v9Eq2SYKGqKgUkwEH}E%|{ED49?d>dvrbUdP z&f7w>Fh4eKdTGfDuHuGt({ei>&2Q#%gNdC^_E;=dUd!re`J3xn{_P1+9{j|JTTY2F z);w}?U~{vH69Q;i%w)=>*#Tj0b#+_Zx8>!r=CHA+${F_K?OH!?r4yz4V0p~>u<{AZ zvc#pRxTA`Xc)TGKw7EI18frWH=y&$e{euoaav#kJgx#BiIK8;Xk1=Pv-d^0j&IvPhJ{}+#>VqHf*Ic5p2U7xEY$bE zfWvv=p5@o~5MF&i^!N8W%PbFL=K|my?xMB=-#kVsL^LP; zyc?Y~Q|a}7VWT;Eg>n3lwDR|d;niB{rt}4ss*~m(x3+r+y?nCkYKq>?RB*XVLLuqO z6-P%QXS;jtkKJ%&c7L6EC8IktFyH|@ zNTg8Y_K5*2hv6I_ZqB8}#y)Lp`{Rpnh}nuO3NCC#T7-IiBI|p$jHgWMqZgr#?^u}^ zCTbOOwjQsXbh zW7c{+Yi}bn(-2=NG4E+fl3wr#S#%Shsa|!{Jl;cM^b!|C(o4W-oR9rI(W zO-c&0U#S?&j9zLT9{`cV0sJ~3PWA!t)_^40cJz|(sielElo~%|QX`5|(<9GDURSRw zloB{oQy**E)IUcSSWiIMCc_@4RGBm%abw)5o#P;=TZ8}0HM9*P8F%~zgsuD>dFT>Y z@OJWWm5+Esd6UHXK@iuN(vgF_WY1Tc1Dw9<&NzBQ(9F8kQ z+eEmAYVef--noqIPmPC3NuKCht5MGYuz)d`GqO|l6NIbIF9te)gi#{uB#9n}C~H5) z)UYohY_|hcW;)vDZvG@joMhC>Rj<2j_|B8%Iaf121%ljE<)0KXD=6P_+S=%R0)+Du lq*j`(Pzm~ffx?47#CN7D84KR{t-roo$1^VW)pnOs{scx$95Mg^ literal 0 HcmV?d00001 diff --git a/output/visualizations/_smoke_wiring.png.meta.json b/output/visualizations/_smoke_wiring.png.meta.json new file mode 100644 index 0000000..2a5c0a2 --- /dev/null +++ b/output/visualizations/_smoke_wiring.png.meta.json @@ -0,0 +1,15 @@ +{ + "generated_at": "2026-07-11T07:32:19.154568+00:00", + "render_path": "backend_raster_composite", + "kinds": [ + "power", + "network" + ], + "rendered_counts": { + "power": 1, + "network": 1 + }, + "px_per_m": 40, + "watermark_required": true, + "watermark_text": "AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음" +} \ No newline at end of file diff --git a/tools/nanobanana/README.md b/tools/nanobanana/README.md index 1c94856..e733f93 100644 --- a/tools/nanobanana/README.md +++ b/tools/nanobanana/README.md @@ -1,39 +1,71 @@ # tools/nanobanana — Gemini 이미지 생성(나노바나나) 연동 모듈 부스/인테리어/배선/조명 설계 데이터를 "시공 후 결과 사진"으로 렌더링한다. +설계 기준: ReRoomAI 실증 패턴(`docs/analysis/reroomai-source.md`) + PLANNING §6. ## 준비 ```bash -pip install google-genai +pip install google-genai Pillow export GEMINI_API_KEY=발급받은_키 # https://aistudio.google.com ``` +- 모델 기본값: `gemini-3.1-flash-image-preview`(나노바나나 2). `NANOBANANA_MODEL`로 오버라이드. +- ★ 실제 Gemini 호출은 소유자 승인 대기(reroomai-source.md §8). 키/네트워크 없이도 import·구조는 성립. -## 빠른 시작 +## 빠른 시작 (§6-2 scene 스키마) ```python from tools.nanobanana.client import NanoBananaClient + +scene = { + "hall": {"id": "제1전시장 7홀", "dims_m": [126, 90], "ceiling_m": 12}, + "booth": {"id": "A-102", "size_m": [6, 3], "type": "independent"}, + "design": { + "signage": {"text": "주식회사 가디아"}, + "brand_color": "#0052A5", + "zones": [{"type": "demo"}, {"type": "consult"}], + "materials": [{"part": "wall", "finish": "matte_white"}], + }, + "lighting": {"mode": "night", "color_temp_k": 4000}, + "wiring": {"power": [{"from_trench": [1, 1], "to": [5, 2], "kw": 3}]}, + "render_hints": {"style": "tech"}, +} + c = NanoBananaClient() -img = c.generate_booth_photo({ - "hall": "제1전시장 3홀", - "size_m": (6, 3), - "booth_type": "목공부스", - "brand_color": "#0052A5", - "materials": ["백색 무광 목공", "우드 포인트월", "그레이 카펫"], - "lighting": {"color_temp_k": 4000, "accents": ["로고 스포트", "쇼케이스 라인조명"]}, -}, view="front", time_of_day="night") -img.save("booth_night.png") +img = c.render_shot(scene, shot_preset="S2", + reference_image="empty_booth.jpg", # 빈 부스 실측 → 구조 보존 + seed=12345) # 컷 간 일관성(B-12) +img.save("output/visualizations/booth_S2.png") # 사이드카 .meta.json 동시 기록 ``` -## 표준 샷 세트 S1~S7 (PLANNING §6 기준, visualizer 에이전트가 관리) +## 표준 샷 세트 S1~S7 (PLANNING §6-3) | 샷 | 호출 | 용도 | |---|---|---| -| S1 정면 주간 | `generate_booth_photo(view="front", time_of_day="day")` | 기본 시안 확인 | -| S2 정면 야간 | `generate_booth_photo(view="front", time_of_day="night")` | 조명 연출 평가 | -| S3 통로 뷰 | `generate_booth_photo(view="aisle")` | 관람객 시점 동선 평가 | -| S4 내부 뷰 | `generate_booth_photo(view="interior")` | 인테리어/집기 배치 | -| S5 Before/After | S1 결과 + `reference_image`(빈 부스 실측) 쌍 | 시공 전/후 비교 슬라이더 | -| S6 배선 오버레이 | `generate_wiring_overlay(kind="network|power|plumbing")` | 발표/설명용 보조 이미지 (시공 검증은 백엔드 래스터 합성 사용) | -| S7 홀 전경 조감 | `generate_booth_photo(view="aerial")` | 배치안 비교(SCR-04) | +| S1 정면 주간 | `render_shot(scene,"S1")` | 기본 시안 확인 | +| S2 정면 야간 | `render_shot(scene,"S2")` | 조명 연출 평가 | +| S3 통로 뷰 | `render_shot(scene,"S3")` | 관람객 시점 동선 | +| S4 내부 뷰 | `render_shot(scene,"S4")` | 인테리어/집기 배치 | +| S5 Before/After | S1 결과 + `reference_image`(빈 부스 실측) 쌍 | 시공 전/후 비교 | +| S6 배선 오버레이 | `render_wiring_overlay_raster(wiring, hall_dims_m, kinds)` ★래스터 합성 | 시공 검증(좌표 정합) | +| S7 홀 전경 조감 | `render_shot(scene,"S7")` | 배치안 비교(SCR-04) | -## 정책 -- 생성 이미지는 "AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음" 워터마크를 붙여 서비스에 노출한다 (계약 서류 사용 금지). -- 참조 이미지(빈 부스 실측 사진)를 넣으면 실제 홀 구조를 보존한 합성이 된다 (ReRoomAI 패턴). +### S6 — 생성형 아님 (B-02) +시공 검증용 S6는 좌표 정합이 보장되는 **백엔드 래스터 합성**이다(로컬 PIL): +```python +from tools.nanobanana.client import render_wiring_overlay_raster +ov = render_wiring_overlay_raster(scene["wiring"], hall_dims_m=(6, 3), + kinds=["power", "network", "plumbing"], + base_image="floorplan.png") # 선택 +ov.save("output/visualizations/booth_S6_wiring.png") +``` +`generate_wiring_overlay(layout, kind)`(생성형)는 발표/설명용 보조 이미지 전용 — 시공 검증 사용 금지. + +## 하위호환 +평면 `booth_spec` dict를 쓰던 기존 코드는 `generate_booth_photo(booth_spec, view, time_of_day)`가 +내부적으로 §6-2 scene 으로 변환 후 `render_shot`에 위임한다(그대로 동작). + +## 방어·정책 +- 입력 이미지 8MB 상한 + 긴 쪽 1024px 다운스케일/JPEG 0.85(`downscale_for_upload`). +- 에러 분기: `NanoBananaAuthError`(키 무효) / `NanoBananaQuotaError`(429·쿼터) / `NanoBananaSafetyError`(SAFETY). +- 쿼터/카운트는 **성공 시에만** 차감(`usage_recorder` 훅). +- 모든 산출 이미지에 "AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음" 워터마크(계약 서류 사용 금지). +- 메타데이터(생성일·스키마 해시·모델 버전·seed)는 사이드카 `.meta.json` + PNG tEXt 청크로 임베드(B-03). +- API 키는 환경변수 `GEMINI_API_KEY`에서만 로드 — 코드/로그/커밋에 절대 기록 금지. diff --git a/tools/nanobanana/__pycache__/client.cpython-314.pyc b/tools/nanobanana/__pycache__/client.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e83d35807204b0796bba87d49e0f223e747d5271 GIT binary patch literal 40232 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