PLANNING v2.0: 자동전시시스템 재정의, 6역할 웹/모바일 분리, 도메인 M10~M18(옥션 M15·관람객 M10·마케팅/공개사이트 M12·BI M16(운영사 ROI 포함)·CMS M17·관리자 M18), WISE/UIWS 공통·시스템관리 레이어 §5B(2FA OTP), 옥션/견적서/역경매, M2/M3 3안 생성→선택/병합. design.md v1.1(Stitch 정합·SCR 매핑). 하네스 재구성: 전문 에이전트 17종(아키텍트 AA·SA·TA·DA·NA / 공통 common-dev / 코어 backend·frontend·db / 도메인 bidding·visitor·cms·bi·admin / AI ai-dev·visualizer / QA·devops) + kintex-impl-orchestrator v2.0(Phase A~E) + IMPLEMENTATION_BACKLOG v2.0 + CLAUDE.md. 백엔드 스캐폴드: Spring Boot 3.2.5(com.zioinfo.kintex) + JWT/RBAC + WebSocket + 버전형 룰엔진 + M2~M5 컨트롤러(M3 사전검증·M4 견적·M5 RenderJob 실구현, 공간경로 501 대기). compileJava SUCCESS. 자산: 평면도 JPG 15장 + 매니페스트(R4 트렌치·CAD 해소). CAD 93MB·build는 gitignore. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.9 KiB
8.9 KiB
나노바나나 워커 ↔ 백엔드 RenderJob 계약 (v1)
대상:
tools/nanobanana/worker.py(Python 워커 사이드카) ↔ Spring Boot 백엔드(M5-1). 근거: PLANNING §6(나노바나나 파이프라인 v1.2) · §8(아키텍처) · IMPLEMENTATION_BACKLOG S-4/M5-1~M5-3. 이 문서는 큐 메시지 형식과 완료 이벤트 형식의 단일 출처다. 백엔드 M5 계약과 정합해야 한다.
1. 파이프라인 위치
Spring Boot(M5-1) Redis Python 워커(S-4 / M5-2·M5-3) 오브젝트 스토리지 / WebSocket
───────────────── ─────── ───────────────────────── ──────────────────────────
RenderJob 발행 ──LPUSH────▶ list: kintex:renderjobs ──BLPOP──▶ worker.process_job()
│ scene → build_booth_prompt
│ → render_shot (생성형)
│ 또는 S6 → render_wiring_overlay_raster (래스터)
│ → 이미지 + 메타데이터
├──put──▶ object storage (이미지 + .meta.json 사이드카)
└──PUBLISH──▶ channel: kintex:renderjob:events
│
Spring 구독 ◀──완료 이벤트────────────────────────────────────────────────────────────┘
└─ WebSocket/STOMP 로 프론트에 진행·완료 푸시
- 워커는 상태 기계의 소비자일 뿐, RenderJob 원본(상태·쿼터·캐시)의 소유는 백엔드(PostgreSQL RenderJob 테이블)다.
- 워커는 결과 이미지·메타데이터를 오브젝트 스토리지에 적재하고 완료 이벤트만 발행한다. DB 상태 전이는 백엔드가 이벤트를 받아 수행한다.
2. 큐 메시지 — RenderJob (백엔드 → 워커)
- 큐: Redis list
kintex:renderjobs(envNANOBANANA_QUEUE). - 인입:
LPUSH/ 소비:BLPOP(워커, timeout 폴링). - 페이로드: UTF-8 JSON 1건.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
job_id |
string | ✔ | RenderJob PK(백엔드 발급). 이벤트·산출물 키의 상관 ID. |
event_id |
string | 행사 ID. 스토리지 경로·쿼터 스코프. | |
booth_id |
string | 부스 ID. 스토리지 경로·상관. | |
shot_preset |
string | ✔ | S1~S7 (PLANNING §6-3). S6은 래스터 합성 경로. |
scene |
object | ✔* | PLANNING §6-2 scene 스키마(hall·booth·design·lighting·wiring). 생성형 샷 필수. |
seed |
int | 컷 간 일관성(B-12). 생략 시 참조체인 일관성 폴백. | |
reference_image |
string | 참조 이미지 경로/URI(빈 부스 실측·간이 렌더). 구조 보존. 목 모드는 무시. | |
layers |
string[] | 활성 레이어(furniture·lighting·electrical·network). 생략 시 샷별 기본값. |
|
wiring |
object | ✔** | S6 전용. scene.wiring 미제공 시 사용. {power,network,plumbing}[]. |
hall_dims_m |
[number,number] | S6 좌표계 기준(m). 생략 시 booth.size_m → hall.dims_m 폴백. | |
options |
object | px_per_m(S6), kinds(S6 렌더 레이어) 등 렌더 옵션. |
* 생성형 샷(S1~S5,S7)은 scene 필수. ** S6은 scene.wiring 또는 최상위 wiring 중 하나 필수.
예:
{
"job_id": "rj_01H...",
"event_id": "evt_2026_kes",
"booth_id": "A-102",
"shot_preset": "S2",
"seed": 12345,
"reference_image": "s3://kintex/refs/hall7_empty.jpg",
"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"},
"lighting": {"mode": "night", "color_temp_k": 4000},
"render_hints": {"style": "tech"}
}
}
3. 완료 이벤트 (워커 → 백엔드)
- 채널: Redis pub/sub
kintex:renderjob:events(envNANOBANANA_EVENT_CHANNEL). - 발행:
PUBLISH. 백엔드가 구독해 WebSocket/STOMP로 릴레이(§8). - 페이로드: UTF-8 JSON 1건.
| 필드 | 타입 | 설명 |
|---|---|---|
type |
string | renderjob.completed | renderjob.failed. |
job_id |
string | 상관 ID. |
event_id / booth_id / shot_preset |
string | 에코백. |
status |
string | DONE(생성/합성 성공) | FAILED. |
image_ref |
object | 산출물 참조(§4). 실패 시 null. |
meta |
object | 이미지 메타데이터(§5). 실패 시 부분. |
error |
object | {code, message} — 실패 시. 스택트레이스·키 미포함. |
emitted_at |
string(ISO-8601 UTC) | 이벤트 발행 시각. |
- 에러 코드(친화적 분류, PLANNING §6-5):
AUTH(키 무효) ·QUOTA(429·쿼터) ·SAFETY·BAD_REQUEST·RENDER_ERROR. - 쿼터는 성공(
DONE) 시에만 차감 — 백엔드가 완료 이벤트 수신 시 처리(실패는 소모 안 함).
4. image_ref — 산출물 참조
{
"backend": "local" | "s3" | "gcs",
"key": "evt_2026_kes/A-102/rj_01H..._S2.png",
"uri": "file:///.../output/visualizations/evt_2026_kes/A-102/rj_01H..._S2.png",
"sidecar_key": "evt_2026_kes/A-102/rj_01H..._S2.png.meta.json",
"content_type": "image/png"
}
- 사이드카
.meta.json은 이미지와 항상 함께 적재된다(결정적, B-03). - 기본 백엔드는 로컬 파일시스템(
NANOBANANA_OUTPUT_DIR, degraded 오브젝트 스토리지). 운영 시 S3/GCS 어댑터로 교체.
5. meta — 이미지 메타데이터 (§6-6 워터마크·고지 필수)
client.build_metadata 산출 + 워커 증강:
| 필드 | 설명 |
|---|---|
generated_at |
생성 시각(ISO-8601 UTC). |
schema_hash |
scene 스키마 SHA-256(정렬 직렬화). 동일 해시 캐시 키(§6-4). |
model_version |
모델명(gemini-3.1-flash-image-preview 등) 또는 mock. |
shot_preset |
샷 프리셋. |
seed |
시드(있으면). |
render_path |
generative | backend_raster_composite(S6). |
watermark_required |
항상 true. |
watermark_text |
"AI 생성 예상 이미지 — 실제 시공 결과와 다를 수 있음". |
notice |
"계약·심사 서류 사용 금지(도면만 유효)". |
live |
true(실 Gemini 호출) | false(목/degraded). |
degraded |
true면 플레이스홀더(G1 미승인·목 모드). |
6. G1 게이트 — 실 Gemini 호출 조건 (PLANNING R12)
| 조건 | 결과 |
|---|---|
NANOBANANA_LIVE=1 그리고 GEMINI_API_KEY 존재 |
실 호출(생성형 샷 → NanoBananaClient.render_shot). |
| 그 외(기본) | 목/degraded 모드 — 플레이스홀더 이미지 + 정상 메타데이터. 파이프라인 구조는 성립. |
- 목 모드는 키·네트워크 없이 동작한다.
GEMINI_API_KEY는 불필요하며 어디에도 로그·기록하지 않는다. - S6(래스터 합성)은 G1과 무관 — 항상 로컬 PIL 결정적 합성(생성형 아님). 목/live 모두 동일 경로.
- 소유자 승인(G1) 확정 후
NANOBANANA_LIVE=1+ 서버 envGEMINI_API_KEY설정으로 무코드변경 전환.
7. 환경변수
| 변수 | 기본값 | 용도 |
|---|---|---|
NANOBANANA_LIVE |
(미설정=목) | 1일 때만 실 Gemini 호출 시도(+키 필요). |
GEMINI_API_KEY |
— | 실 호출 시에만. 코드·로그·이벤트·응답 기록 금지. |
NANOBANANA_MODEL |
gemini-3.1-flash-image-preview |
모델 오버라이드(client.MODEL_NAME). |
REDIS_URL |
redis://localhost:6379/0 |
큐/이벤트 연결(지연 연결). |
NANOBANANA_QUEUE |
kintex:renderjobs |
RenderJob 큐 리스트 키. |
NANOBANANA_EVENT_CHANNEL |
kintex:renderjob:events |
완료 이벤트 pub/sub 채널. |
NANOBANANA_OUTPUT_DIR |
output/visualizations |
로컬 오브젝트 스토리지 루트. |
8. 운영 불변식
- 지연 연결: Redis 미기동이어도
import·process_job(job)직접 호출은 성립(스모크·단위 테스트용). - 결정적 사이드카: 모든 산출물에
.meta.json동반(§5). - 성공 시에만 쿼터 차감: 실패 이벤트는 소모 신호 아님.
- 비밀 미노출: 이벤트·로그·에러에 키·IP·스택트레이스 금지(§6-5, GUARDiA 보안 제약).
- S6 결정성: 배선 오버레이는 생성 모델 미개입(좌표 정합 목적).