Compare commits

...

17 Commits

Author SHA1 Message Date
zio
e67db2e773 merge: reconcile with server-side deploy snapshot 58aa844 (keep local tree)
Remote main was force-rewritten by the deploy pipeline into a single snapshot
commit built from local 0add2cd/490b723. Local HEAD supersedes it (deliverables
docs, icons.tsx, title-icon sweep, gitignore, solution-name fix), so this merge
keeps the local tree verbatim while preserving the remote commit in history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 04:32:48 +09:00
zio
37aeebdd28 솔루션명 수정 2026-07-13 04:23:07 +09:00
zio
277c895b47 chore: gitignore graphify-out and screenshot generated outputs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:30:30 +09:00
zio
490b723a45 chore(mobile): v0.2.0 source snapshot for reproducibility
- app.json: version 0.2.0 / versionCode 2, drop expo-screen-capture plugin
- add lib/i18n (ko/en/ja/zh), lib/roleTrack, LanguageContext + LanguageSelector
- profile/security hardening across screens (login/register/forgot/profile/tabs)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:29:38 +09:00
zio
0add2cd417 fix(web): restore public header login + track-tab icons/tooltips, visitor CTA to /tickets/lookup, accent-button white-text sweep
- PublicShell: track tabs get icons + title tooltips (right-aligned), login link restored at >=768px
- VisitorTrackPage: wayfinding/checkin CTAs point to #guide/#transport//tickets/lookup instead of /login
- accent buttons: enforce --color-on-accent (white text) across approval/docs/home/movein/schedule/work/public

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:29:12 +09:00
zio
f3b800f9be fix(auth): grant global ADMIN hall-manager scope to resolve 403 on event-scoped APIs
KintexPrincipal.hasAccess/roleFor ignored global role_code=ADMIN, so
admin@ users got 403 on all event-scoped endpoints. Add isAdmin() and
treat ADMIN as hall-manager equivalent (all-event read + approve).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 00:15:25 +09:00
zio
c77cba7c5b feat(web): home calendar holiday markers + unify public header into single frame
- HomePage: wire fetchHolidaySet, is-holiday cell class + holiday name label
- home.css: is-holiday red daynum, kx-home__cal-holiday-name, neutral-100 grid lines
- PublicShell: absorb 3-track switcher into single top header (COEX 1-frame), drop dark topbar
- public.css: remove .kxp-topbar styles, tracktabs bar/menu variants

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 23:56:59 +09:00
zio
a5a78f1ea1 feat(web): exhibitor mgmt, Nifty detail views, app-download QR, settings customizer, home todos + i18n
- exhibitors: organizer exhibitor roster page + read-only backend (Controller/Service/@Mapper/dto), /exhibitors route
- detail views: Nifty blog-style .kx-detailview for auction & review-details (+shared.css)
- app download: AppQrCodePage visual/class hardening
- settings customizer: customizerStore + SettingsCustomizer panel (density/accent), AppShell gear wiring, menu-fallback removal
- home: todos aggregation (8 sources) HomePage/homeApi/HomeMapper/HomeService
- i18n: merge 112 new keys across ko/en/zh/ja (parity 0 missing)

No new migrations (read-only queries); Flyway stays at V49.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 23:36:02 +09:00
zio
55a3114141 feat(login): add password show/hide (eye) toggle on login form
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:31:34 +09:00
zio
7316cd99c5 feat(public): rotating 5-slide hero backdrop with real photos on public tracks
Add PublicHeroSlides cross-fade component (5 real JPGs) wired into
gateway/visitor/business/agency hero backdrops. Rotation handled by
usePublicMotion ([data-hero-slides], reduced-motion aware); .pub-hero__slides
layering + is-active transition in public.css. Graceful gradient fallback on
image load error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:09:35 +09:00
zio
c595560d82 fix(system/message): avoid bean-name collision with existing work/message module
Existing com.zioinfo.kintex.work.message.{MessageController,MessageService,MessageMapper}
share default bean names with the new system/message beans, causing
ConflictingBeanDefinitionException at startup (health-check fail -> rollback).
- rename mapper interface MessageMapper -> SysMessageMapper (MyBatis bean name can't
  be overridden by annotation value)
- explicit bean names: @RestController("sysMessageController"), @Service("sysMessageService")
No request-path change (new: /api/admin/messages, /api/messages/{code}/render).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:51:24 +09:00
zio
276293ebe7 feat(system,ui): message-template admin (V48) + empty-table demo seed (V49) + Nifty design/token alignment & public card visuals
- system/message: sys_message template CRUD + {key} render API (ADMIN-gated, render for authed)
- V48: sys_message table + 36 seeded templates (9 codes x ko/en/zh/ja), idempotent upsert
- V49: backfill 12 empty domain tables (render_job, cms_media, logistics_*, mail_log,
  edm_followup, webhook_*, login_history, sys_role_menu, audit_log); idempotent guards.
  followup ON CONFLICT targets business key (event_id, lead_id) to skip pre-seeded rows
- frontend: Nifty token/typography sweep (--fs-*/--fw-*), public e-card 3:4 media + cover fit,
  footer copy update across ko/en/zh/ja

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:40:38 +09:00
zio
3077cb04f5 feat(public,settlement): coex-style public visuals + Nifty calendar + local poster assets + e-2026-live close-loop demo (V46,V47)
- public: motion hooks (usePublicMotion), gateway/track/shell visual refresh, stat-band white->on-accent contrast fix
- schedule: Nifty calendar restyle (neutral cell borders, today warning-bg)
- settlement: card padding fix
- i18n: +public visual keys (ko/en/zh/ja)
- backend: PublicEventDto.posterUrl (additive, non-breaking) via PublicSiteMapper/Service
- db: V46 rewires event.poster_url to local /media/posters assets (34 imgs); V47 seeds e-2026-live close-loop (invoice/payment/tax/refund, auction/bid/award, visitor/lead, edm/sponsorship)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:59:59 +09:00
zio
55e27b3309 fix(theme): white text on colored buttons/badges in dark mode (--color-on-accent)
Root cause: dark theme remaps --color-white to --dk-surface (a dark color),
so every 'color: var(--color-white)' on a colored background (primary/error/
success buttons, badges, chips, hero CTAs) rendered dark/invisible text in
dark mode (e.g. '등록 신청' blue button showed navy text).

Add always-white --color-on-accent token and repoint 77 text-color usages
across 28 files to it. Background/border usages of --color-white untouched
(they legitimately flip to dark surface). Full-CSS audit applied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:34:30 +09:00
zio
87f310f40a fix(brand): replace Tomcat default favicon.ico with KINTEX-branded icon
Root cause of persistent Tomcat cat favicon: public/favicon.ico was the
Tomcat default (21630 bytes), shipped by every build; browsers prefer
/favicon.ico over the correct favicon.png. Regenerate favicon.ico from the
KINTEX wordmark PNG (multi-size 16/32/48/64) and bump cache-bust to v3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:10:56 +09:00
zio
a2b2bd9110 feat(ui): add line shield-check icon to 2FA challenge panel
- icons.tsx: new IconShieldCheck (stroke/line, 24 viewBox standard)
- OtpChallengePanel: shield badge above 2차 인증 title (was icon-less/bare)
- login.css: .kx-otp__badge circular primary-tinted badge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 20:05:01 +09:00
zio
6e95ac6273 fix(schedule,ui): mark holidays on mini-calendar + neutral cell borders; fix disabled CTA label contrast
- ExhibitionSchedulePage MiniCalendar: pass/receive holidays prop, apply
  is-holiday class + title/aria per date cell
- schedule.css: neutral-200 border on kx-cal cells; is-holiday cells in error color
- mobile.css: disabled CTA label neutral-500 (contrast fix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 19:47:31 +09:00
73 changed files with 11134 additions and 92 deletions

View File

@ -0,0 +1,278 @@
# KINTEX AI 전시·행사시스템 — 개발계획서
> - 문서 종류: 개발계획서(Development Plan)
> - 프로젝트: **KINTEX AI 전시·행사시스템** (KINTEX AI Exhibition & Event System)
> - 작성일: 2026-07-12 · 버전: v1.0
> - 근거 문서: `docs/PLANNING.md`(v3.4) · `docs/IMPLEMENTATION_BACKLOG.md`(v2.0) · `docs/BUILD_DEPLOY.md` · `docs/architecture/*` · 실제 구현 스키마(Flyway V1~V49)
> - 산출물 정책: 개발계획서·설계서는 개발 착수 시점 1회 작성(본 문서). 사용자/운영자 지침서는 UI 정렬 안정화 후 별도 산출.
> - 보안: 본 문서에는 자격증명·비밀번호·SSH·내부 IP·시크릿을 기재하지 않는다.
---
## 목차
1. 사업 개요
2. 목표 및 기대효과
3. 개발 범위(모듈)
4. 기술 스택
5. 시스템 아키텍처 개요
6. 개발 방법론 및 조직(에이전트 트랙)
7. 개발 단계(Phase)와 WBS
8. 마일스톤 및 일정
9. 선행 게이트 및 전제조건
10. 리스크 관리
11. 품질 관리 방침
12. 보안 방침
13. 배포(CI/CD) 계획
---
## 1. 사업 개요
### 1-1. 배경
킨텍스(KINTEX, 한국국제전시장)는 총 전시면적 108,011㎡(2028년 제3전시장 완공 시 178,000㎡)를 운영하나, 전시 준비 실무는 **HWP 서식 다운로드 → 이메일/방문 제출**, **CAD 수작업 배치도 → 홀매니저 육안 검수**, **수기 위치표시도 기반 유틸리티 신청**에 머물러 있다. 온라인 작업신고(kxwp.kintex.com)는 서류 업로드 창구 수준이며, 참가업체 유틸리티 신청은 킨텍스 공통 플랫폼 없이 전시회별 주최자 사무국 시스템으로 파편화되어 있다.
### 1-2. 비전
**"신청서를 내는 순간, 시공 후 사진을 먼저 본다."**
전시장 운영 전 과정(홀 배정 → 부스 배치 → 장치공사 설계 → 전기/조명 → 네트워크/유틸리티 배선 → 반입/반출)을 AI로 자동 설계·검증하고, 그 결과를 **나노바나나(Gemini 이미지 생성)로 '공사 후 결과 사진'처럼 시공 전에 미리 생성**해 보여주는 것이 핵심 차별화다.
### 1-3. 정체성 확장(제품 진화)
- **v2.0 자동전시시스템**: 부스 시공 시각화(P0 코어 M2~M5)를 심장으로 두되, 전시 기획·판매·시공·운영·관람객·사후분석 전 주기를 자동화하는 베뉴 운영 플랫폼으로 확장(도메인 모듈 M10~M18 신설).
- **v3.0 다중 전시관 SaaS**: KINTEX를 기준 테넌트(#1)로 두고 코엑스(COEX) 등을 테넌트로 온보딩하는 멀티테넌트 SaaS. 기능은 보존하고 `tenant_id` 격리 레이어만 순증.
- **v3.2 대외 접점 3-트랙**: visitor(관람객) / business(주최자·참가업체) / agency(공사·장치·협력사) 3-트랙 IA 재구성.
- **v3.4 AI 사용성 & 토큰 최소화**: 전 화면 인라인 AI 진입점 표준화 + LLM 토큰 최소화 6원칙(§8A).
> 근거: PLANNING §1·§1-2·§1A·§2-2·§8A.
---
## 2. 목표 및 기대효과
| 목표(To-Be) | 현재(As-Is) | 개선 |
|---|---|---|
| 홀 배정 견적 | 문의→협의→견적 수일 | 규칙 엔진 즉시 자동 견적(2,250원/㎡ × 성수기/전시장 계수) |
| 부스 배치도 초안 | CAD 수작업 수일~수주 | 조건 입력 후 수 분 내 복수 안(1·2·3안) 생성·선택/병합 |
| 도면 규정 검수 | 홀매니저 육안 | 규정 위반 자동 플래깅(높이 5m·방염·리깅 등) 후 사람 확정 |
| 유틸리티 위치표시도 | 참가업체 수기 작도 | 부스 좌표 클릭 → 자동 배선안 + 자동 견적 |
| 시공 결과 예측 | 불가(외주 조감도) | 표준 샷 세트 자동 생성(정면/야간/통로/배선 오버레이) |
> 근거: PLANNING §1-3 목표(정량).
---
## 3. 개발 범위(모듈)
### 3-1. 코어(P0, 불변) — 설계·시각화
- **M2 플로어플랜 스튜디오** — 부스 배치 자동 생성(제약 솔버 중심), 3안 생성 → 선택/병합, 규정 자동 검증(통로·바닥하중·비상구).
- **M3 부스 설계 스튜디오** — 조립/독립 부스 설계 3안, 규정 사전 검증(높이 5m·리깅 6.5~8.5m·방염·이격).
- **M4 유틸리티 설계** — (a) 전기·조명, (b) 네트워크·급배수·압축공기. 트렌치 최단 배선 자동 산출, 위치표시도 자동 생성, 자동 견적.
- **M5 나노바나나 시각화** — M2~M4 구조화 데이터를 프롬프트로 컴파일해 "시공 후 사진" 표준 샷 세트(S1~S7) 생성.
### 3-2. 판매·발주·정산(P1)
- **M1** 홀 배정·자동 견적 / **M6** 서류·마일스톤 워크플로 / **M7** 등록업체 매칭·견적 / **M9** 정산·결제
- **M15 공사/장치 옥션(핵심 플로우)** — AI 설계자료를 근거로 등록업체가 견적서(Quotation) 제출 → 역경매(라운드·실시간 순위) → 낙찰(Award) → 계약/발주 연동. 폐루프의 연결고리.
- **M8** 반입/반출 물류 슬롯(P2)
### 3-3. 관람·참가·마케팅
- **M10 관람객 등록·티켓·배지·체크인·리드캡처(P1)** / **M12 마케팅·EDM·공개 홍보 사이트(P1, SEO·다국어)**
- **M11** 비즈니스 매칭 / **M13** wayfinding·실내 내비 / **M14** 현장운영(혼잡·안전·주차·에너지) (P2)
### 3-4. 경영·콘텐츠·관리(P1)
- **M16 경영분석 BI** — 운영사(킨텍스) 관점 수익성/ROI(가동률·매출구성·행사별 P&L·전시장 ROI·리텐션/LTV·수율·경영진 KPI) + 참가사 관점 ROI.
- **M17 CMS** — 콘텐츠·공지·참가업체 마이크로사이트·다국어·사이니지 연계.
- **M18 관리자 백오피스** — §5B 시스템관리 + 킨텍스 마스터데이터(홀·요율·규정 룰셋·등록업체).
### 3-5. 공통/시스템관리 레이어(§5B, P1·전 모듈 선행 기반)
UIWS(GUARDiA 표준 프레임워크) 이식: 인증(JWT+2FA/OTP·로그인 실패 잠금·admin 비번 env 주입), 시스템관리(사용자·역할/권한 RBAC·공통코드·메뉴·감사로그·시스템설정), 공통 업무기능(worklog·schedule·message·notice·opinion·search·meeting·report·notification 등).
> 근거: PLANNING §5(M1~M9)·§5A(M10~M18)·§5B(공통 레이어)·기능 우선순위 총괄표.
---
## 4. 기술 스택
| 레이어 | 기술 | 비고 |
|---|---|---|
| 프론트엔드 | **React 18/19 + Vite + TypeScript** | 반응형(데스크톱=설계/에디터, 모바일=조회·승인·현장), 역할별 번들 분리 |
| 백엔드 | **Spring Boot 3.x(Java 17) + MyBatis** | REST + WebSocket(STOMP), 룰 엔진·배치/배선 엔진(서비스 계층) |
| DB | **PostgreSQL + PostGIS** | 부스 폴리곤·트렌치 포인트·배선 LineString 공간 데이터, Flyway 마이그레이션 |
| 비동기 | **Redis 작업 큐** | RenderJob·서류·알림 |
| 이미지 생성 | **나노바나나 Python 워커 사이드카**(`tools/nanobanana`) | google-genai + `gemini-3.1-flash-image-preview`, Spring이 큐로 트리거 |
| 인증 | **JWT + RBAC + TOTP 2FA** | 행사 단위 RBAC, UIWS 표준 이식 |
| AI(텍스트) | **Claude 기본 + 설정형 전환**(AiTextRouter/AiConfig) | Claude→Ollama 소형모델 폴백, 토큰 최소화 라우팅 |
| 모바일 | Expo SDK 51 + React Native + TypeScript(`mobile/`) | 코드베이스 1개·배포 타깃 2개(B2B 운영/B2C 관람객) |
- GUARDiA 표준 프레임워크(Spring Boot + React + MyBatis + PostgreSQL) 정렬.
- 나노바나나 image-to-image 파이프라인은 Python SDK(google-genai) 검증 자산을 재사용하기 위해 **Python 워커 사이드카로 유지**하고, Spring은 큐·오케스트레이션·상태 관리만 담당.
> 근거: PLANNING §8 아키텍처, CLAUDE.md 기술 스택 확정표.
---
## 5. 시스템 아키텍처 개요
- **역할별 분리 프론트 + 공유 백엔드**: organizer·exhibitor·contractor·ops·admin 5개 인증 앱 + public(공개 사이트)·visitor(관람객 앱). 공유 디자인 시스템·공유 컴포넌트·공유 API 계약 상속(최소권한·공격면 축소).
- **공유 Spring Boot 백엔드**: REST + WebSocket, 룰 엔진(요율/규정), 배치·배선 엔진(PostGIS), 옥션 엔진(M15), BI 집계(M16), CMS(M17), Redis 큐, 나노바나나 워커.
- **데이터**: PostgreSQL+PostGIS(업무·공간 데이터) + 오브젝트 스토리지(도면·생성 이미지·서식·콘텐츠).
- **멀티테넌시**: 공유 스키마 + `tenant_id` 컬럼(단일 DB) 논리 격리, fail-closed 다층 방어(필터→서비스 가드→MyBatis 강제 바인딩→PostGIS).
- **AI 사용성·토큰 최소화**: 단일 `/ai/ask` 계약, 결정론 우선 라우팅(사실조회는 LLM 미호출), 소형모델 우선 티어링, RAG 발췌·캐시·구조화 출력.
> 상세는 설계서(`02_설계서.md`) 참조. 근거: PLANNING §8·§8-1·§8-2·§8A.
---
## 6. 개발 방법론 및 조직(에이전트 트랙)
- **방법론**: 하네스 기반 에이전트 오케스트레이션(`kintex-impl-orchestrator`). Phase A(아키텍처) → B(공통 레이어) → C(부스 코어) → D(도메인) → E(배포)의 단계적 진행, 각 모듈 완성 직후 QA 점진 검증.
- **문서 워크플로(CLAUDE.md)**: planner(기획, PLANNING.md) → designer(디자인, design.md·Stitch 경유) → developer(구현, src/) → visualizer(나노바나나) → reviewer(교차 검증).
- **개발 조직(에이전트 트랙)**:
| 구분 | 에이전트 | 역할 |
|---|---|---|
| 거버넌스 | kintex-pm·dev-pm·pmo | 범위·일정·게이트·품질 게이트·산출물 관리 |
| 아키텍트 | kintex-aa·sa·ta·da·na | 애플리케이션·시스템·기술·데이터·네트워크 아키텍처 |
| 공통 | kintex-common-dev | UIWS 공통/시스템관리·2FA 이식 |
| 코어 | kintex-backend-dev·frontend-dev·db-engineer | Spring/MyBatis·React·PostGIS 스키마 |
| 도메인 | kintex-bidding-dev·visitor-dev·cms-dev·bi-dev·admin-dev | M15·M10·M12/M17·M16·M18 |
| AI·시각화 | kintex-ai-dev·visualizer | Claude AI 기능·나노바나나 |
| 품질·배포 | kintex-qa·devops-dev | 경계면 QA·CI/CD |
| 모바일 | kintex-mobile-dev | Expo 앱 트랙 |
> 근거: CLAUDE.md 하네스(kintex-impl-orchestrator) 로스터, IMPLEMENTATION_BACKLOG 담당 약칭.
---
## 7. 개발 단계(Phase)와 WBS
### Phase A — 아키텍처·거버넌스
| ID | 작업 | 산출물 |
|---|---|---|
| A-1 | 애플리케이션 아키텍처(모듈 경계·레이어·API 표준·패키지) | architecture/app.md |
| A-2 | 시스템 아키텍처·NFR(확장성·HA·성능·보안영역·배포 토폴로지) | architecture/system.md |
| A-3 | 기술 표준(스택·빌드/배포·개발표준·관측성·AiTextRouter) | architecture/tech.md |
| A-4 | 데이터 아키텍처(전사 ERD·공간데이터·마스터·공통코드·BI 마트) | architecture/data.md |
| A-5 | 네트워크 아키텍처(DMZ/내부망·방화벽·부하분산) | architecture/network.md |
| A-6 | 아키텍처↔PLANNING 정합 검증 | 불일치 0/티켓화 |
### Phase B — 공통/시스템관리 레이어(★전 모듈 선행)
| ID | 작업 | 완료 기준 |
|---|---|---|
| B-0 | 스캐폴드(Spring Boot·Vite·PostGIS·Flyway) | 빌드·마이그레이션 통과 |
| B-1 | 인증 JWT+RBAC(6역할) + 2FA(TOTP) + 로그인 실패 잠금 + admin 비번 env | 2FA 등록/검증/초기화 왕복 |
| B-2 | 시스템관리(사용자·역할/권한·공통코드·메뉴·감사로그·설정) | CRUD·RBAC 가드·감사 기록 |
| B-3 | 공통 업무기능(worklog·schedule·message·notice·opinion·search·meeting·report·notification) | 각 모듈 API 왕복 |
| B-4 | 공통 컴포넌트(예외·응답봉투·감사 AOP·알림 단일화) + 프론트 공통 | AA 표준 정합 |
| B-5 | 점진 QA(2FA·RBAC·비번/PII 미노출·경계면) | 통과까지 반려 |
### Phase C — P0 부스 시공 코어
| ID | 모듈 | 완료 기준 |
|---|---|---|
| C-M2 | 플로어플랜(Booth polygon·Trench·PostGIS 규정검증·3안 생성/병합) | 3안 생성·병합·규정 재검증 |
| C-M3 | 부스 설계(조립/독립·규정 사전검증·3안/병합) | 위반 사전 플래깅·컨펌 |
| C-M4 | 유틸리티 배선(Wiring LineString·자동 견적·위치표시도) | 최단 배선·견적·위치표시도 |
| C-M5 | 나노바나나(RenderJob·Redis·WebSocket·워커·S6 래스터·Before/After) | (G1 시)실생성/미승인시 목·워터마크 강제 |
| C-C | 공통 화면(로그인·주최자·참가업체·홀매니저·현장 모바일) | 역할별 진입·상태 처리 |
### Phase D — P1 도메인(역할별 포털 분리)
| ID | 모듈 | 완료 기준 |
|---|---|---|
| D-M15 | 공사/장치 옥션(견적서 제출→역경매→낙찰, 등록업체만 응찰) | 견적서·역경매·낙찰 왕복 |
| D-M10 | 관람객·현장(등록·티켓·배지/QR·체크인·리드캡처) | 등록→배지→체크인→리드 |
| D-M12/17 | 마케팅·공개사이트·CMS(SEO·다국어·게시 워크플로) | 공개 조회·게시·SEO |
| D-M16 | 경영분석 BI(가동률·P&L·리텐션·KPI·Recharts) | 지표 집계·드릴다운·내보내기 |
| D-M18 | 관리자 백오피스(시스템관리+마스터데이터·룰셋 버전) | RBAC·마스터 CRUD·룰셋 버전 |
| D-M1/6/7/9 | 배정·서류·매칭·정산 | 견적·마일스톤·정산 |
### Phase E — P2 + 빌드·배포
M11 비즈매칭 / M13 wayfinding / M14 현장운영 + 역할별 프론트 번들·Spring jar·워커 서비스 빌드·Gitea CI/CD·systemd·env(G2 후).
### Phase F — 벤치마킹 유래 추가(티켓·관람객)
스마트티켓 부정입장 방지(회전 QR)·환불 규정 정교화·티켓/배지 다국어·세션 정원 RSVP·간편결제·라이브 공지(ⓑ 보강 6) + 티켓 오픈 알림·주차 연계·혼잡 안내·QR 명함 교환·대기열·멤버십·개인화 홈·셀프 체크인 키오스크(ⓒ 신규 8).
> 근거: IMPLEMENTATION_BACKLOG v2.0 Phase A~F 전문.
---
## 8. 마일스톤 및 일정(로드맵)
| Phase | 기간(목표) | 범위 요약 |
|---|---|---|
| Phase 1 — 설계·시각화 코어(MVP) | ~4개월 | §5B 공통 레이어 선행 + M2(단일 홀 근사 도면)·M3·M4·M5(S1·S2·S6·S7) |
| Phase 2 — 워크플로·운영 통합 | ~4개월 | M1·M6·M7·M9 + 홀매니저 대시보드 + 전 홀(10개) 도면 확장 |
| Phase 3 — 현장·확장 | ~4개월+ | M8·kxwp 정식 API·정밀 조도·다국어 규정 챗봇·관람객 플로어플랜 |
- 각 Phase 종료 시 reviewer 에이전트 교차 검증(기획-디자인-구현 정합성).
- Phase 1 착수 조건: Gemini API 키·홀 참조 사진·트렌치 좌표(미확보 시 공개 스펙 기반 '가정' 그리드).
> 근거: PLANNING §9 로드맵.
---
## 9. 선행 게이트 및 전제조건
| 게이트 | 내용 | 영향 |
|---|---|---|
| **G1** | 나노바나나(Gemini) 외부 호출 소유자 승인(PLANNING R12) | M5 실호출·배포 전. 미승인 시 목/degraded |
| **G2** | 배포 대상 서버·포트(개발 `kintex.zioinfo.co.kr` 포트 8021, 운영 별개 도메인) | Phase E 배포 착수 전 |
| **G3** | 모바일 EAS 클라우드 빌드 소유자 승인 | 앱 실빌드·QR 배포 전 |
전제조건(킨텍스 협의): CAD 도면·트렌치 실측 제공, kxwp 연동 논의, 요금 정합성 확인(인터넷 150,000 vs KT 80,000). 확보 자산: 홀별 평면도 JPG 15장 + CAD(제1전시장 트렌치 DWG) → PLANNING R4 부분 해소.
> 근거: IMPLEMENTATION_BACKLOG 선행 게이트·확보 자산, CLAUDE.md 하네스 게이트.
---
## 10. 리스크 관리
| # | 리스크 | 영향 | 완화 |
|---|---|---|---|
| R1 | AI 생성 이미지가 실제 시공과 다름(계약 오인 분쟁) | 높음 | 전 이미지 워터마크·고지, 계약/심사 서류 자동 배제, "시공 기준은 도면" 동의 |
| R2 | 도면 심사 책임(자동 통과 ≠ 승인) | 높음 | 시스템을 '사전 필터'로 정의, 최종 승인 주체 명시, 면책·룰셋 버전 기록 |
| R3 | kxwp 연동 불확실성(폐쇄형·API 미공개) | 중간 | 파일 자동 생성+수동 업로드 릴레이로 독립 가치 확보, 병행 협의 |
| R4 | 트렌치·CAD 실측 미확보 | 높음 | '가정' 그리드 라벨링 + 킨텍스 데이터 제공을 Phase 2 전제로 계약화 |
| R6 | 이미지 생성 비용·지연(부스 수백 개) | 중간 | 자동 생성 S1·S7 한정, 온디맨드+스키마 해시 캐시, 행사별 쿼터 |
| R8 | 요금·규정 공개값과 실계약가 차이 | 중간 | "공시가 기준, 최종가는 킨텍스 확정" 고지, 룰셋 버전 교체 절차 |
| R10 | 부스 설계 영업비밀 민감성 | 중간 | 행사 단위 격리, 소유 참가업체+주최자+홀매니저로 접근 한정 |
| R12 | 나노바나나(Gemini) 외부 API 승인 게이트 | 높음 | G1 승인 선행, 미승인 시 온프레미스 이미지 생성 폴백 검토 |
> 근거: PLANNING §10 리스크 및 제약(R1~R12).
---
## 11. 품질 관리 방침
- **점진 QA**: 각 모듈 완성 직후 kintex-qa가 백엔드 응답 shape ↔ 프론트 호출 경계면 불일치를 교차 검증. 공간 로직(배치·배선·규정) 정합, 빌드 회귀 확인.
- **품질 게이트(반려 사유)**: 자격증명·PII·스택트레이스 미노출, AI 이미지 워터마크 강제, 등록업체 응찰 게이트, admin 비번 env 주입.
- **빌드 게이트(push 전)**: 시크릿 커밋 차단, Flyway 번호 충돌 검사, 변경분 compileJava/tsc·lint 실패 시 push 차단.
- **정합성 검증**: reviewer 에이전트가 기획-디자인-구현 3자 정합을 Phase 종료 시 교차 검증.
> 근거: IMPLEMENTATION_BACKLOG 진행 규칙·B-5, BUILD_DEPLOY §3.
---
## 12. 보안 방침
- **인증·인가**: JWT + 행사 단위 RBAC(ORGANIZER·EXHIBITOR·CONTRACTOR·HALL_MANAGER) + 플랫폼/테넌트 관리자 계층 + TOTP 2차 인증(업무 사용자 필수, 관람객 미강제).
- **자격증명 보호**: `password_hash`(BCrypt)·`otp_secret`(TOTP)는 API 응답에서 완전 제외. admin 비밀번호는 env(`ADMIN_PASSWORD_ENC` AES-256-GCM + 별도 키파일) 주입, 하드코딩 `admin123` 시드 금지.
- **테넌트 격리**: 전 계층 `tenant_id` 강제, 크로스-테넌트 접근은 플랫폼 슈퍼관리자 전용 API로만(감사).
- **외부 API**: 온프레미스 원칙. 예외 승인 = Claude API(`api.anthropic.com`), 나노바나나(Gemini)는 G1 승인 게이트. 키는 서버 env로만 로드(코드·DB·커밋·로그·응답 기록 금지).
- **에러 응답**: 스택트레이스 미노출 — 요약 메시지만 전달. 감사로그(승인·낙찰·설계 변경·룰셋 개정·리드 접근)에 전수 기록.
> 근거: PLANNING §5B-3·§8-2·§10, CLAUDE.md 보안 제약, 실제 스키마(app_user 컬럼 코멘트).
---
## 13. 배포(CI/CD) 계획
- **실행 단위 3개**: 백엔드(`./gradlew bootJar` → jar, systemd) / 프론트(Vite `npm run build` → dist, nginx 정적 서빙, 역할별 번들 분리) / 나노바나나 워커(Python 큐 소비 데몬).
- **파이프라인**: `workspace/kintex → git push → Gitea(zio/kintex) → webhook → deploy_server`. 빌드(bootJar·vite build) → Flyway 마이그레이션 → jar 재기동·dist 배포 → 헬스 게이트(`GET /health`).
- **Fail-Safe**: 백업 → 배포 → 헬스체크(200) → 실패 시 롤백(이전 jar 유지). clean bootJar 검증 후 교체.
- **인프라**: systemd 유닛(백엔드·워커), nginx vhost(`/`=프론트 정적, `/api/`·`/ws`=백엔드), TLS certbot, AI env drop-in.
- **개발 도메인**: `kintex.zioinfo.co.kr`(포트 8021, PostGIS+Redis+Flyway). 운영 배포는 소유자 승인 필수(G2).
> 근거: BUILD_DEPLOY §1·§4·§5, CLAUDE.md 하네스(kintex-impl-orchestrator) G2 게이트.
---
> 후속 산출: 본 개발계획서와 함께 `02_설계서.md`를 산출한다. **사용자지침서·운영자지침서는 UI 정렬 안정화 이후 별도 산출**(본 산출 범위 아님 — deliverables 갱신 정책상 완성+QA 통과 후 최신 메뉴 반영).

Binary file not shown.

View File

@ -0,0 +1,420 @@
# KINTEX AI 전시·행사시스템 — 설계서
> - 문서 종류: 설계서(System Design Document)
> - 프로젝트: **KINTEX AI 전시·행사시스템** (KINTEX AI Exhibition & Event System)
> - 작성일: 2026-07-12 · 버전: v1.0
> - 근거 문서: `docs/PLANNING.md`(v3.4) · `docs/design.md`(v2.4) · `docs/architecture/{app,system,tech,data,network,sso-hr-integration}.md` · `docs/COMMON_CODES.md` · **실제 구현 스키마(Flyway `V1~V49`)** · 실제 컨트롤러(REST 계약)
> - 보안: 본 문서에는 자격증명·비밀번호·SSH·내부 IP·시크릿을 기재하지 않는다.
> - 비고: 본 설계서는 구현된 스키마·API 계약을 정본으로 삼고, 계획(설계 완료·미구현) 항목은 그 취지를 명시한다.
---
## 목차
1. 시스템 아키텍처
2. 애플리케이션 아키텍처(레이어·모듈·의존성)
3. 데이터 설계
4. API 설계
5. 화면 설계
6. AI 설계(§8A)
7. 인증·인가 설계
8. 네트워크·보안영역 설계
9. 비기능 요구사항(NFR)
---
## 1. 시스템 아키텍처
### 1-1. 전체 구성
**6개 역할별 분리 프론트(React·Vite·TS) + 단일 공유 Spring Boot 백엔드 + 나노바나나 Python 워커 사이드카**를 SSO·역할 RBAC 위에 얹고, PostGIS·Redis 큐·오브젝트 스토리지를 공유한다.
```
[엣지/DMZ] CDN · WAF/nginx(TLS 종단)
[역할별 프론트] organizer · exhibitor · contractor · ops · admin(내부) · public/visitor
│ (SSO · 역할 RBAC 게이트)
[공유 Spring Boot 백엔드] REST + WebSocket(STOMP)
│ 룰 엔진(요율/규정) · 배치·배선 엔진(PostGIS) · 옥션 엔진(M15) · BI 집계(M16) · CMS(M17) · 공개 API
[비동기] Redis 큐 → 나노바나나 워커(google-genai) · 서류/PDF/EDM 워커 → 오브젝트 스토리지
│ (완료 시 WebSocket 푸시)
[데이터] PostgreSQL + PostGIS (프라이머리 + 읽기 복제) · 오브젝트 스토리지
```
### 1-2. 대원칙(5)
1. **단일 공간 데이터 모델** — 부스 폴리곤·트렌치 포인트·배선 경로를 PostGIS로 일원화(설계·시각화·검증·정산·wayfinding·BI가 동일 원천 재사용).
2. **이미지 생성 전면 비동기** — Spring이 RenderJob을 Redis 큐에 발행 → Python 워커가 Gemini로 생성 → 오브젝트 스토리지 적재 → WebSocket 완료 푸시. `GEMINI_API_KEY`는 워커 전유(백엔드 미취급).
3. **역할별 프론트 분리** — 최소권한·공격면 축소, 공유 디자인 시스템·컴포넌트·API 계약 상속.
4. **룰셋 = 버전 관리 데이터** — 규정(높이·방염·하중)·요율을 코드가 아닌 `rulesets/*.json` + master_data로 관리(연 단위 개정 무중단 반영).
5. **공개 vs 내부 백오피스 보안영역 분리**(§8).
### 1-3. 배포 토폴로지(존)
- **공개 존(DMZ)**: Reverse Proxy/WAF·nginx·TLS, CDN, 공개 프론트 SSR/SSG, 공개 API GW(화이트리스트 엔드포인트만), PG 콜백.
- **애플리케이션 존(내부망)**: 인증 프론트 정적 서빙(organizer·exhibitor·contractor·ops), 공유 백엔드 jar × N(systemd, 무상태 수평 확장), 워커 데몬 × M.
- **관리 존(내부 전용)**: admin 백오피스(웹 전용, VPN/허용 IP + 2FA 강제).
- **데이터 존(최내곽)**: PostgreSQL+PostGIS(프라이머리+스탠바이)·읽기 복제·Redis HA·오브젝트 스토리지. **아웃바운드 없음(유출 경로 제거)**.
> 물리 서버·도메인·포트는 G2 게이트 확정 후 Phase E에서 실체화(개발 도메인은 `kintex.zioinfo.co.kr`, 포트 8021). 근거: architecture/system.md·network.md.
---
## 2. 애플리케이션 아키텍처(레이어·모듈·의존성)
### 2-1. 패키지 구조
루트 `com.zioinfo.kintex`. 횡단 계층 + 도메인 모듈:
- `common`(응답봉투·페이징·에러·감사 AOP·공통코드 캐시 — **무의존**), `config`(Security·WebSocket·Redis·MyBatis), `auth`(JWT·RBAC·2FA/OTP·가드), `rules`(규정·요율 룰셋 로딩·평가), `system`(시스템관리), `work`(공통 업무), `module`(도메인 m2~m5 등).
- 리소스: `application.yml`(env 플레이스홀더), `mybatis/mapper/**/*.xml`(공간 SQL `ST_*`), `rulesets/`(compliance·rates JSON = 데이터).
### 2-2. 레이어링 표준
| 계층 | 책임 | 금지 |
|---|---|---|
| Controller | HTTP 바인딩·`@Valid`·RBAC 가드 호출·서비스 위임·`ApiResponse` 래핑 | 비즈니스 로직·SQL·트랜잭션·매퍼 직접 호출 |
| Service(interface+Impl) | 비즈니스 규칙·`@Transactional` 경계·룰 엔진 호출·매퍼 오케스트레이션·DTO 조립 | HTTP 타입 참조 |
| Mapper(MyBatis) | DB 접근·공간 SQL(ST_*) 바인딩 | 비즈니스 분기·DTO 조립 |
| DTO(record) / domain | 불변 전송 객체 / 순수 값객체(프레임워크 무의존) | — |
- 공간 연산은 매퍼 XML의 PostGIS SQL로 수행(서비스는 스칼라/GeoJSON 결과만 사용). 조회 `readOnly=true`, 버전 리소스는 낙관적 잠금(불일치=409).
### 2-3. 의존성 규칙(단방향·순환 금지)
- `module.mN``rules·auth·common`(횡단 허용). `common`**무의존**.
- **모듈 간 명시 방향만**: M3→M2, M4→M2, M5→M2·M3·M4, M15→M2·M3·M4·M5·M7, M9→M1·M4·M15, M13→M2, M14→M4·M10, M16→전 모듈(읽기), M12→M10·M17.
- 역방향 필요 시 직접 참조 금지 → **도메인 이벤트·공유 식별자로 디커플**(예: M15 낙찰→M9는 발주 링크). 모듈 간 결합은 서비스 인터페이스로만. 검증 = ArchUnit 빌드타임 아키텍처 테스트(역참조·순환 CI 차단).
- 백엔드는 **단일 공유 모듈러 모놀리스**(역할별로 쪼개지 않음) — 모듈 경계 + 의존 규칙으로 코드 레벨 강제, 노출 표면은 경로 접두 + RBAC로 분리.
> 근거: architecture/app.md.
---
## 3. 데이터 설계
### 3-1. 데이터 표준(명명·타입)
- 테이블/컬럼: PostgreSQL 무인용 소문자 `snake_case`, PK `<엔티티>_id`(도메인은 UUID), WISE 이식 테이블은 정본 PK 유지.
- 지오메트리 `geom`/`<용도>_geom`, 암호화 PII `<필드>_enc`, 코드 컬럼 `status`/`<의미>_code`, 시각 `*_at`(timestamptz, UTC 저장/Asia/Seoul 표시), 금액 `numeric`(KRW).
- **DTO(camelCase) ↔ 컬럼(snake_case)**: MyBatis `map-underscore-to-camel-case=true`.
- **응답 제외(불변)**: `*_enc`·`password_hash`·`otp_secret`·내부 IP/SSH·내부 식별자는 API 응답 완전 제외.
### 3-2. FK 최소화 원칙 (소유자 지시 2026-07-12)
- **원칙: "FK는 최소화, 공통코드로 관리."** 신규 도메인/테넌트 테이블은 물리 `FOREIGN KEY`를 두지 않고, 참조무결성은 **애플리케이션 레이어 검증 + `*_id` 소프트 참조 명명**으로 보장.
- **근거**: ① 멀티테넌트 복합 PK(`(tenant_id, id)`) 전환 시 단일 id FK가 `UNIQUE(id)` 보조제약을 강제하는 마찰 ② MyBatis가 조인·삭제 순서 제어 ③ 멱등 `ON CONFLICT` 시드 순서 자유 ④ 부스 재배치(replaceBooths) 시 자식 재지정 빈번(V3 `utility_order`·`render_job`이 이미 소프트 참조 = 정본 사례).
- **물리 FK 예외 화이트리스트(그 외 신규 FK 신설 금지)**: (W1) `common_code.grp_code`, (W2) `sys_menu.parent_id`(self), (W3) `sys_role_permission`, (W4) `sys_role_menu` — 전역 시스템/RBAC 무결성 필수분만.
- **소프트 참조 보완**: 임계 트랜잭션(낙찰·정산) 명시적 부모 존재 검증, 삭제는 서비스가 자식 선처리(soft-delete `use_yn='N'` 우선), 논리참조 인덱스 `(tenant_id, <ref>_id)`, 야간 고아 스캔(DQ 게이트).
### 3-3. 공통코드 표준
- 범주형 컬럼(상태·유형·심각도 등 열거 가능 소수값)은 자유문자열/DB enum이 아닌 `common_code_group`/`common_code`로 관리. 컬럼엔 **코드값(영문 상수)만 저장**, 표시명(한글)은 조인/캐시. **DB `ENUM` 물리타입 지양**.
- **코드 vs 마스터 경계**: 열거 가능 소수값=공통코드(BOOTH_TYPE·RENDER_STATUS·SHOT_PRESET 등), 다건·CRUD·버전 대상=마스터/룰셋(홀·요율·규정 룰셋·등록업체). 정본 = `docs/COMMON_CODES.md`.
- 확정 코드그룹 예: `EVENT_ROLE`(ORGANIZER·EXHIBITOR·CONTRACTOR·HALL_MANAGER), `PORTAL_ROLE`, `BOOTH_TYPE`(independent·assembled), `COMPLIANCE_SEVERITY`(block·warn·pass), `RENDER_STATUS`(QUEUED·RUNNING·DONE·FAILED), `SHOT_PRESET`(S1~S7), `VERIFY_METHOD`(EMAIL·OTP).
### 3-4. 공간 데이터 표준
- **SRID `0`**(홀 로컬 평면 데카르트 좌표, 단위 미터). 지리좌표(4326) 아님, `geometry`(geography 아님).
- 부스 = `Polygon`, 트렌치 = `Point`(+ 배선 run은 LineString), 배선 = `LineString`/`MultiLineString`. 전 `geom` **GiST 인덱스 필수**.
- 저장 전 `ST_IsValid`·닫힌 링·홀 내포 검증(실패 시 400). 미실측 트렌치는 `is_assumed=true` 플래그(PLANNING R4).
- 파생 연산: 최단 배선(라우팅), 통로 폭 검증(버퍼), 면적 정산(ST_Area)이 모두 SQL 수준에서 수행.
### 3-5. 핵심 엔티티(구현 스키마 기준)
**신원·마스터(V2)**
- `app_user`(id·email·display_name·`password_hash`(BCrypt, 응답 제외)·hall_manager·`otp_secret`(응답 제외)·failed_login_count·locked_until·status) — V7에서 otp_enabled·verify_method·role_code·dept_id·last_login_at 순증, V26에서 tenant_id 순증.
- `company`(id·registration_no(사업자번호, 초대 검증 키)·name·category(14분류)·region·**registered**(미등록 응찰 차단)).
- `event`·`hall`(exhibition_center·label·width/depth/height_m·area_m2·floor_load_t_per_m2·floor_type·booth_capacity·has_gas·is_assumed_trench)·`hall_assignment`.
- `event_member`(event_id·user_id·**role_code**(ORGANIZER·EXHIBITOR·CONTRACTOR·HALL_MANAGER)·booth_id·company_id) — 행사 단위 RBAC 멤버십.
- `booth_standard`(assembled·premium spec jsonb)·`master_data`(RATE·UTILITY_FEE·COMPLIANCE + ruleset_version).
**공간 코어(V3, P0)**
- `trench`(hall_id·geom(Point)·supply_power/water/air/network/gas·is_assumed).
- `hall_exit`(geom(Point)·clearance_m — 비상구 이격 버퍼).
- `layout`(event_id·hall_id·version·status, UNIQUE(event_id,hall_id,version)).
- `booth`(layout_id·booth_no·booth_type·geom(Polygon)·size_w/d_m·height_m·floor_load_t_per_m2·premium).
- `design_plan`(booth_id·version·status·spec jsonb, UNIQUE(booth_id,version)).
- `utility_order`(event_id·booth_id(소프트 참조)·quote jsonb·wiring(MultiLineString)·location_diagram_url).
- `render_job`(event_id·booth_id(소프트)·shot_preset(S1~S7)·status·image_url·schema_hash·model_version·error_message(요약만)).
**옥션 M15(V16)**
- `auction`(event_id·category·auction_type(reverse/rfq)·award_criteria(lowest/comprehensive)·weight_price/reputation/delivery·round·deadline·material_package_id·materials).
- `auction_invite`(등록업체만, UNIQUE(auction_id,company_id) — 미등록 원천 차단).
- `bid`(=Quotation: subtotal·vat·total·lead_days·valid_until·terms·`lines` jsonb·version·status, UNIQUE(auction_id,company_id) — 재응찰 시 버전 증가).
- `award`(옥션당 1건, bid_id·reason·awarded_by — 감사 추적)·`company_reputation`(rating·jobs_done·claim_rate).
- 봉인 입찰: 마감 전 경쟁 견적 비공개(서비스 레이어 강제 마스킹), 마감 후 발주자 전체 공개.
**관람·발주·정산·콘텐츠(V14~V24 등)**
- M6: `event_milestone`·`required_document`·`document_review_issue`(V14). M8: `dock`·`dock_reservation`(V15), `logistics_*`(V33).
- M10: `visitor_registration`·`lead`(V17). M12: `edm_campaign`·`sponsorship_package`·`sponsorship_sponsor`(V18). 공개: `exhibit_inquiry`(V20)·`visitor_guide`·`transport_info`(V43).
- M17 CMS: `cms_content`·`cms_translation`·`microsite`(V19), `cms_content_version`·`cms_media`(V21).
- M9: `invoice`·`invoice_payment`(V24), `invoice_refund`·`tax_invoice`(V34). M1 판매: `booth_sale`(V27). 결재: `approval`·`approval_line`·`approval_history`(V29).
**공통·시스템관리(V7·V8·V37)**
- `common_code_group`/`common_code`·`sys_menu`·`sys_role`/`sys_permission`/`sys_role_permission`·`sys_setting`·`audit_log`(actor·action·target·summary·ruleset_version·result·ip_hint)(V7).
- 공통 업무(V8): `worklog`·`schedule`·`message`/`message_recipient`·`notice`·`opinion`/`opinion_comment`·`meeting`/`meeting_action`·`report`·`notification`.
- 시스템 기초(V37): `dept`·`sys_program`·`sys_role_menu`·`sys_auth_policy`·`login_history`·`error_log`. 기타: `login_slide`(V11)·`holiday`(V41)·`sys_message`(V48)·`mail_log`(V36)·`webhook_*`(V35)·`ai_config`(V30).
**멀티테넌시(V26·V31)**
- `tenant`(id(slug: kintex·coex)·name·domain·status). KINTEX = 테넌트 #1 시드.
- 핵심 테이블 `tenant_id` 순증(`NOT NULL DEFAULT 'kintex'` 백필, 회귀 0) + `(tenant_id, …)` 선두 복합 인덱스. V31에서 테넌트 루트(event·hall·app_user)를 `PRIMARY KEY (tenant_id, id)` 복합 PK로 전환.
### 3-6. 마이그레이션 요약(Flyway V1~V49)
| 버전 | 주제 | 주요 산출 |
|---|---|---|
| V1 | 확장 | PostGIS·pgcrypto 등 extension |
| V2 | 신원·마스터 | app_user·company·event·hall·hall_assignment·event_member·booth_standard·master_data |
| V3 | 공간 코어(P0) | trench·hall_exit·layout·booth·design_plan·utility_order·render_job(+GiST) |
| V4·V5·V6 | 마스터 시드 | 홀 마스터·트렌치 그리드·마스터/데모 시드 |
| V7 | 시스템관리·보안 | common_code(_group)·sys_menu·sys_role/permission·sys_setting·audit_log |
| V8 | 공통 업무 | worklog·schedule·message·notice·opinion·meeting·report·notification |
| V9 | 공개 인증 | password_reset |
| V10 | 시드 | 10년치 행사 시드 |
| V11·V12·V13 | 부가 | login_slide·TOTP 2FA·카탈로그/대시보드 인덱스 |
| V14 | M6 | event_milestone·required_document·document_review_issue |
| V15 | M8 | dock·dock_reservation |
| V16 | M15 옥션 | auction·auction_invite·bid·award·company_reputation(+시드) |
| V17 | M10 | visitor_registration·lead |
| V18 | M12 | edm_campaign·sponsorship_package·sponsorship_sponsor |
| V19 | M17 CMS | cms_content·cms_translation·microsite |
| V20 | 공개 사이트 | exhibit_inquiry |
| V21 | 확장 | cms_content_version·cms_media(체크인·리드·CMS 확장) |
| V23 | 일정 보강 | schedule 확장 |
| V24 | M9 정산 | invoice·invoice_payment |
| V25 | M1 | 홀 배정 확장 |
| V26 | 멀티테넌시 1단계 | tenant + 핵심 테이블 tenant_id 순증·복합 인덱스 |
| V27 | 부스 판매 | booth_sale |
| V28 | 옥션 부가 | BOQ·옥션 수수료 |
| V29 | 결재 | approval·approval_line·approval_history |
| V30 | AI | ai_config |
| V31 | 테넌트 표준 | 테넌트 루트 복합 PK((tenant_id, id)) 전환 |
| V32 | 시드 | 2026 하반기 킨텍스 행사 |
| V33 | M8 확장 | logistics_equipment/request·rental_item/order·inbound |
| V34 | M9 확장 | invoice_refund·tax_invoice |
| V35 | 자동화 | webhook_subscription·delivery·inbound·edm_followup_log |
| V36 | 메일 | mail_log |
| V37 | 시스템 기초 | dept·sys_program·sys_role_menu·sys_auth_policy·login_history·error_log |
| V38·V39 | 데모 | full·realism 데모 시드 |
| V41·V42 | 마스터/데모 | holiday(2050)·대량 데모 시드 |
| V43 | 관람 가이드 | visitor_guide·transport_info + `v_event_calendar`·`v_event_monthly_summary`(뷰) |
| V44 | 성능 | 성능 인덱스 |
| V45·V46·V47 | 부가/데모 | 프로필 사진·포스터 로컬 에셋·라이브 행사 폐루프 시드 |
| V48 | 메시지 | sys_message(템플릿) |
| V49 | 데모 | 빈 테이블 데모 시드 |
> 멱등(`IF NOT EXISTS`·`ON CONFLICT`) + 비파괴 순증 원칙. V22·V40은 결번.
### 3-7. VIEW/MVIEW/배치 전략
- **`v_*` VIEW**: 실시간 파생·경량 조인·상태 산출(예 `v_event_calendar` — 날짜→UPCOMING/ONGOING/ENDED 산출로 별도 상태 컬럼 불필요). tenant_id 필터·민감 컬럼 미노출.
- **`mv_*` MATERIALIZED VIEW**: 무겁고 실시간성 낮은 집계(BI KPI·가동률·리드/ROI). 새로고침 전략 명시(야간 배치·`REFRESH ... CONCURRENTLY`·mview 인덱스).
- **BI 데이터마트(M16)**: 스타 스키마 `FACT_BOOKING/SETTLEMENT/UTILITY/AUCTION/VISITOR` + `DIM_DATE/HALL/EVENT/EXHIBITOR` + `KPI_SNAPSHOT`(built_at 각인). 별도 `mart` 스키마, 야간 ETL 또는 읽기 복제. **운영 DB 직조회 금지**.
- **배치 카탈로그(J1~J9)**: mview 새로고침·KPI 스냅샷·마감 알림/할일·EDM/옥션 통지·세션 만료 정리·고아 검출(DQ)·PII 보존/파기(행사종료+1년)·행사 crawl·정산 롤업. 공통 요건: 멱등·분산락·실패 격리·감사·테넌트 스코프.
> 근거: architecture/data.md.
---
## 4. API 설계
### 4-1. 표준
- 베이스 `/api`. 공개=`/api/public/**`, 워커=`/api/internal/**`, 행사 스코프=`/api/events/{eventId}/…`, 플랫폼 관리=`/api/admin/**`(hasRole ADMIN), 인증=`/api/auth/**`. 비-CRUD 액션은 하위 동사 세그먼트(POST).
- 응답 봉투 `ApiResponse<T>`{success·data·error{code,message}}, 목록 `PageResponse<T>`{items·page·size·total}.
- **ErrorCode→HTTP**: VALIDATION 400 · UNAUTHORIZED 401 · FORBIDDEN 403 · NOT_FOUND 404 · CONFLICT 409 · **COMPLIANCE_BLOCKED 422** · **RENDER_QUOTA_EXCEEDED 429** · **NOT_REGISTERED_COMPANY 403** · NOT_IMPLEMENTED 501 · INTERNAL 500.
### 4-2. 도메인별 주요 엔드포인트(실제 컨트롤러 기준)
| 도메인 | 베이스 경로 | 컨트롤러 |
|---|---|---|
| 인증(로그인·2FA·비번찾기·프로필사진·앱무결성) | `/api/auth`, `/api/auth/app-integrity` | AuthController·PublicAuthController·TwoFactorController·ProfilePhotoController·AppIntegrityController |
| M2 플로어플랜 | `/api/events/{eventId}/halls/{hallId}/layout` | FloorplanController |
| M3 부스 설계 | `/api/events/{eventId}/booths/{boothId}/design` | DesignController |
| M4 유틸리티 | `/api/events/{eventId}/booths/{boothId}/utility` | UtilityController |
| M5 렌더잡 | `/api/events/{eventId}`(render), `/api/internal/render`(워커 콜백) | RenderJobController·RenderWorkerCallbackController |
| M15 옥션 | `/api/auctions`, `/api/contractor` | AuctionController·ContractorController |
| M1 홀 배정·부스 판매 | `/api/halls`, `/api/booth-sales`, `/api/events`(catalog) | HallAssignController·BoothSalesController·EventCatalogController |
| M6 서류·마일스톤 | `/api/events/{eventId}`(document) | DocumentController |
| M8 물류 | `/api/events/{eventId}`, `/api/events/{eventId}/logistics` | LogisticsController·LogisticsExtController |
| M9 정산 | `/api/settlement` | SettlementController |
| M10 관람객·리드 | `/api/visitors` 등(VisitorController) | VisitorController |
| M12 마케팅 | `/api/events/{eventId}`(marketing) | MarketingController |
| M16 BI·분석 | `/api/analytics`, `/api/events/{eventId}/analytics`, `/api/events/{eventId}/dashboard`, `/api/admin/dashboard` | AnalyticsController·AnalyticsOverviewController·DashboardController·AdminDashboardController |
| M17 CMS·마이크로사이트 | `/api/cms/contents`, `/api/cms/media`, `/api/exhibitors/{exhibitorId}/microsite` | CmsContentController·CmsMediaController·MicrositeController |
| 공개 사이트·가이드·AI 도우미 | `/api/public`, `/api/public/cms`, `/api/public/microsites`, `/api/public/ai` | PublicSiteController·PublicGuideController·PublicCmsController·PublicMicrositeController·VisitorAssistantController |
| 현장 운영 | `/api/events/{eventId}/ops` | OpsController |
| 공통 업무(§5B) | `/api/work/{worklogs,schedules,messages,notices,opinions,search,meetings,reports,stats,approvals,notifications}` | Worklog·Schedule·Message·Notice·Opinion·Search·Meeting·Report·Stats·Approval·Notification Controller |
| 시스템관리(M18) | `/api/admin/{users,roles,roles/{role}/menus,programs,depts,companies,settings,audit,login-history,error-log,mail-config,notify-config,system-health,rulesets,auth-policy}` | SysUser·Role·RoleMenu·Program·Dept·Company·Setting·AuditLog·LoginHistory·ErrorLog·MailConfig·NotifyConfig·SystemHealth·AdminRuleset·AuthPolicy Controller |
| 테넌트 | `/api/admin/tenants` | TenantAdminController |
| AI 설정·NL 질의 | `/api/admin/ai`, `/api/ai` | AiConfigController·NlQueryController |
| 웹훅 | `/api/webhooks/in`, `/api/admin/webhooks` | WebhookInbound·WebhookAdmin Controller |
| 헬스 | `/health`, `/api/home` | HealthController·HomeController |
### 4-3. WebSocket(STOMP)·큐 계약
- `GET /ws`(SockJS), prefix `/topic`(서버→클라)·`/app`(클라→서버). 토픽 `/topic/render/{jobId}`·`/topic/auction/{auctionId}`·`/topic/events/{eventId}/notifications`·checkin. 페이로드는 REST DTO 재사용.
- Redis 큐: RenderJob 큐 `kintex:renderjob:queue`, 상태 `...:job:{jobId}`, 쿼터 `...:quota:{eventId}`. **성공 시에만 쿼터 차감**. G1 미승인 시에도 큐잉/상태는 동작(목/degraded).
> 근거: architecture/app.md, 실제 controller 매핑.
---
## 5. 화면 설계
### 5-1. 화면 총괄
design.md v2.4 집계 **총 89화면** — 웹 코어·도메인 52(SCR-01~51, SCR-HOME) + 관리자 10(SCR-A1~A10) + 공개사이트 8(SCR-P1~P8) + 3-트랙 관문 4(SCR-T0~T3) + 모바일 15(SCR-M1~M15). 역할 약칭: 주=주최자·참=참가업체·장=장치/공사업체·홀=킨텍스 직원·관=관리자·대=일반 대중/관람객.
### 5-2. 웹 코어·도메인(대표)
| SCR | 화면 | 모듈 | 역할 |
|---|---|---|---|
| SCR-01 | 로그인 & 행사 워크스페이스 선택 | 공통 | 전 |
| SCR-HOME | 로그인 후 메인 홈(랜딩 대시보드) | 공통·§5B·M1·M12 | 전 |
| SCR-02 | 주최자 대시보드(D-데이 마일스톤 타임라인) | M1·M6 | 주 |
| SCR-03 | 부스 배치 에디터(DnD+AI 자동배치+규정 오버레이) | M2 | 주 |
| SCR-04 | 배치안 비교(S7 홀 전경 조감) | M2·M5 | 주 |
| SCR-05 | 참가업체 부스 홈 | M3·M6 | 참 |
| SCR-06 | 부스 설계 스튜디오(스펙→나노바나나 4샷+before/after) | M3·M5 | 참·장 |
| SCR-07 | 유틸리티 배선 뷰(트렌치 오버레이+자동 견적) | M4 | 참·장 |
| SCR-08 | 유틸리티 신청 요약·위치표시도 | M4b | 참 |
| SCR-09 | 장치업체 규정 검증 리포트 | M3 | 장 |
| SCR-10/11 | 홀매니저 승인 큐 / 검수 상세 | M2·M3·M6 | 홀 |
| SCR-12 | 시각화 갤러리(S1~S7) | M5 | 전 |
| SCR-13 | 경영분석 대시보드 | M16 | 주·홀·관 |
| SCR-18~29 | 홀배정·부스판매·정산·서류·매칭·물류·옥션(개설·응찰·견적서·낙찰) | M1·M6·M7·M8·M9·M15 | 역할별 |
| SCR-30~37 | 관람객 등록·체크인·리드·EDM·스폰서십·CMS·마이크로사이트·다국어 | M10·M12·M17 | 역할별 |
| SCR-38 | 업체 수주 부스 대시보드 | 업체포털 | 장 |
| SCR-39~48 | §5B 공통 업무(업무일지·일정·쪽지·공지·의견·검색·회의록·업무보고·알림·마이페이지) | §5B | 전 |
| SCR-49~51 | 회원가입·비밀번호 재설정·2차 인증(OTP) 설정 | §5B-3 | 전 |
### 5-3. 관리자 백오피스(SCR-A*, M18·§5B-1, MDI 적용)
SCR-A1 사용자 · A2 역할·권한(RBAC) · A3 공통코드 · A4 메뉴 · A5 감사로그 · A6 시스템설정 · A7 마스터데이터(홀·요율·요금·부스표준·등록업체) · A8 규정 룰셋 버전 · A9 테넌트 온보딩(플랫폼 슈퍼관리자) · A10 AI 플랫폼 설정(AiConfig).
### 5-4. 공개 홍보 사이트(SCR-P*, MDI 비적용·SEO/다국어)
SCR-P1 홈 · P2 행사 상세 · P3 공개 인터랙티브 플로어플랜 · P4 관람객 사전등록 · P5 마이크로사이트 공개 뷰 · P6 참가/부스 문의 · P7 입장권 예매(티켓팅) · P8 예매 확인·취소.
### 5-5. 3-트랙 IA(§3C, SCR-T*)
코엑스 3-사이트 IA를 킨텍스 단일 도메인 위에 **visitor / business / agency** 3-트랙 얕은 진입 레이어로 정형화. 신규 라우트 4개(`/`=SCR-T0 관문, `/visitor`=T1, `/business`=T2, `/agency`=T3)만 추가하고 기존 딥라우트·MDI·티켓 공개 라우트는 불변(회귀 0).
- **SCR-T0 관문**(`/`, 미인증): 제품 히어로 + 3-트랙 대형 분기 카드(관람 1순위) + 행사 하이라이트 레일.
- **SCR-T1 visitor**: 세련된 마케팅 홈페이지로 격상, 중심 = 대화형 AI 관람 도우미(히어로 정중앙 자연어 입력창 + 예시 질문칩). 라이트/다크 토글(공개 예외).
- **SCR-T2 business**: B2B 실무 톤, 주최자(홀 임대)/참가업체(부스 신청) 2-분기 가치 제안 + 임대 절차 스텝 → 로그인 CTA.
- **SCR-T3 agency**: 옥션/역경매 차별점 강조, 등록업체 안내(739개사·14분류·미등록 시공 엄금) + 진행 중 공사 옥션 공고 리스트 → 등록업체 로그인 CTA.
- **공개 셸**: PublicShell + 상단 3-트랙 전환 탭(TrackSwitcher), 좌측 "KINTEX AI 전시·행사시스템" 워드마크, 우측 언어(한/영/중/일)·로그인. 서브도메인 테넌트 컨텍스트 하 해당 테넌트 콘텐츠만 노출.
### 5-6. 역할 → 랜딩·메뉴 결정 규칙(웹·모바일 공통)
- 미인증 기본 진입 = visitor 공개 관람 랜딩(SCR-T0). 인증 후 역할 매핑 트랙의 전용 메인으로 랜딩:
- primaryTrack 우선순위: 내부-admin > 내부-ops > business > agency > visitor.
- 랜딩: ADMIN→관리 메인(SCR-16) / MANAGER·HALL_MANAGER→운영 메인 / ORGANIZER·EXHIBITOR→비즈니스 메인(SCR-T2) / CONTRACTOR→에이전시 메인(SCR-T3) / VISITOR→관람객 메인(SCR-T1).
- 좌측 메뉴 그룹(GROUPS: ops·design·visitor·finance·work·system)은 트랙 필터로 노출/숨김. `system` 그룹은 ADMIN만.
- 모바일 하단 탭바는 역할별 4~5탭 구성.
### 5-7. 디자인 시스템(요약)
- **브랜드**: 킨텍스 CI 블루 계열 B2B 실무 톤. AI 생성물은 항상 "AI 생성/초안" 라벨.
- **컬러 토큰(kx)**: `primary-600 #0066B3`(주 브랜드), `primary-700 #004C86`, `ai-accent #6D4AFF`(AI 전용), success `#0E8A5F`·warning `#B45309`·error `#D92D20`, `canvas-bg #1C2536`(에디터 다크 서피스), 배선 전기 `#EF4444`/네트워크 `#3B82F6`/급배수 `#22C55E`. WCAG AA 이상.
- **타이포**: Pretendard(숫자·좌표 tabular), Display 28 / H1 24 / Body 14, 테이블 행 44px(모바일 48).
- **컴포넌트**: StatusBadge(작성중→제출→AI검토→승인→반려→시공→검수), DdayChip(D-3 warning), ViolationFlag(차단 빨강/경고 주황 + 도면 위 번호 핀 1:1), AI 라벨(보라 외곽선), 생성 이미지 상시 고지문(제거 불가). 라운드 버튼 4px/카드 8px(상한)/칩 pill. 12컬럼·컨테이너 max 1440px.
- **MDI 셸**: 좌측 사이드바(240px)+상단 문서 탭바+DocumentHost, ShellFooter 36px. 메뉴 클릭=탭 열기(라우트 이동 아님), 비활성 탭 상태 보존, 최대 12탭, 세션 `localStorage`(`kintex.mdi.{role}.{eventId}`). MDI 적용=관리자·주최자·참가·장치·홀매니저 / 비적용=공개(P·T)·모바일.
- **캘린더**: WISE(UIWS `CalendarView`) 패턴 문자 그대로 — 월 6주×7열, 기간 이벤트=가로 spanning bar, "+N" 팝업, 전시장 세그먼트 필터는 홀 마스터 센터 distinct 동적 옵션(하드코딩 금지).
- **반응형·다크모드**: 웹 1440px(설계·에디터·대시보드)/모바일 390px(조회·승인·현장). 다크모드 Phase 1 미지원(에디터 캔버스만 다크), 공개 마케팅 T1은 예외적 토글. 전 화면 풀블리드·공백 없는 반응형(전역 NFR).
### 5-8. 모바일(SCR-M*, Expo/RN 390px)
조회·승인·현장 전용(캔버스 편집 미제공, 뷰어+승인 액션만). 하단 탭 4개. SCR-M1 시공업체 현장 체크리스트 · M2 홀매니저 현장 검수 · M3 역할별 홈 · M4 리드캡처(배지 스캔) · M5 관람객 홈·배지/QR · M6 wayfinding · M7 플로어플랜·부스 검색 · M8 비즈매칭 · M9 세션·아젠다 · M10 반입 통행증·안전 · M11 알림센터 · M12 서류·승인 조회 · M13 옥션 순위 · M14 티켓 예매 · M15 내 티켓 지갑.
### 5-9. Stitch 연동
전 화면 Stitch 경유(프로젝트 `9385904003821333054`, 디자인 시스템 "Precision Enterprise AI"). 생성 화면 = `stitch_kintex_ai_system_architect/`(각 디렉터리 `code.html`·`screen.png`). design.md가 토큰·컴포넌트 권위(상충 시 design.md 우선). 신규 정의 SCR-HOME·SCR-T0~T3는 미생성(designer Stitch 의뢰 대상).
> 근거: design.md v2.4 §1~§4, PLANNING §2-3.
---
## 6. AI 설계(§8A — 사용성 & 토큰 최소화)
### 6-1. 사용성 표준(U1~U7)
전 화면 공통 인라인 AI 진입점(`AiAssistant`) + 예시 질문 칩 + 원탭 액션 + 다음 명령 제시(규칙 기반 우선) + **구조화 카드 + 근거 인용**(출처 레코드 링크, 근거 없으면 "모름" 폴백) + 대화 히스토리(요약 압축) + 접근성/다국어/모바일/음성. 관람객·업무 사용자가 단일 `AiAssistant` + 단일 `/ai/ask` 계약 공유, 노출 위치·칩·허용 액션만 역할/트랙으로 스코프.
### 6-2. 토큰 최소화 6원칙(P1~P6)
- **P1 결정론 우선 라우팅**: 사실 조회형(일정·교통·주차·마감일·요금·부스 위치·통계)은 DB/뷰가 직접 응답(LLM 미호출). LLM은 요약·추천·자연어 종합에만.
- **P2 소형모델 우선 티어링**: 온프레미스 소형(Ollama qwen3:1.7b/llama3.2:1b) → 난도·실패 시 Claude 승급(AiTextRouter).
- **P3 RAG 발췌**: top_k 제한·컬럼 프로젝션으로 짧은 컨텍스트만(전체 문서/테이블 주입 금지).
- **P4 캐싱**: 응답 캐시(의도+파라미터+tenant+locale, TTL)·프롬프트 프리픽스·기간 요약 재사용.
- **P5 출력 상한·구조화**: max_tokens 상한 + JSON/카드 구조화.
- **P6 집계는 SQL로**: 통계·랭킹·추이는 데이터마트 SQL이 산출, LLM은 설명·해석만.
### 6-3. 계약·측정
- **단일 계약** `POST /ai/ask`: 입력 `{question, contextRef, locale, history}`, 출력 `{answerCard, citations[], followups[], route(direct|small|escalated), usage(tokens·cached)}`.
- IntentRouter는 규칙 테이블(공통코드)로 관리, 사실조회 의도는 DB 리졸버 매핑, 미매핑만 LLM 경로. 캐시 키 `hash(intent+params+tenant_id+locale)`.
- AI 프로바이더: Claude 기본(`api.anthropic.com`, 키 env only) + AiTextRouter(실패 시 Ollama 폴백) + AiConfig 설정 화면(하드코딩 금지). 나노바나나(Gemini)는 별도 이미지 파이프라인(본 절 토큰 원칙 대상 아님, G1 게이트).
- **측정지표**(`ai_usage_log` 적재, tenant/모듈 비용 귀속): LLM 우회율 ≥40%·소형모델 처리율 ≥70%·캐시 적중률 ≥30%·평균 입력 ≤1500/출력 ≤400·근거 인용률 ≥95%(초기 가설).
> 근거: PLANNING §8A. 현행 visitor-assistant DB 근거 응답·AiTextRouter 폴백과 정합(표준화).
---
## 7. 인증·인가 설계
### 7-1. 현행(구현) — JWT + RBAC + 2FA
- **JWT(HS256)**: 클레임 sub·name·roles(eventId→역할)·hm(홀매니저)·plat(플랫폼 역할)·otp. STATELESS·CSRF disable. 공개(permitAll): `GET /health`·`POST /api/auth/login`·`/ws/**`·`/api/internal/render/callback`·(Phase D)`/api/public/**`.
- **이중 RBAC**: 플랫폼 역할(JWT `plat`+`hasRole`) × 행사 역할(JWT `roles`/`hm`+`EventAccessGuard.requireRole`). 열람=행사 멤버 or 홀매니저, 편집·액션=역할별. **등록업체 게이트(불변)**: CONTRACTOR 응찰은 등록업체 검증 필수(미등록=NOT_REGISTERED_COMPANY 403).
- **2차 인증(TOTP RFC6238)**: SHA1·30s·6자리·±1 윈도우. 최초 QR 등록, 마이페이지 재설정/해제, 관리자 OTP 초기화. 대상 = 업무 사용자 필수, 일반 관람객 미강제(승격 시 필수 전환).
- **로그인 실패 잠금**(failed_login_count·locked_until) + 관리자 해제. **admin 비밀번호** = env `ADMIN_PASSWORD_ENC`(AES-256-GCM) + 별도 키파일 주입, 기동 시 BCrypt 재시드(`admin123` 하드코딩 금지).
- 민감 컬럼(`password_hash`·`otp_secret`)은 API 응답에서 완전 제외.
### 7-2. 계정 체계(단일 통합 + 가입 트랙 분리)
- 계정/RBAC는 단일 통합, 가입 트랙만 분리: 업무 트랙(B2B, 승인/초대+2FA 필수) vs 관람객 트랙(B2C, 간편가입/게스트 예매·2FA 미강제). 관람객→바이어/참가 승격은 단일 계정 등급 상향(리드·비즈매칭·재방문 이력 연속성 유지).
### 7-3. Open SSO / HR 연동 (계획 — 설계 완료·미구현)
- **프로토콜**: OIDC(Authorization Code + PKCE) 우선 + SAML 2.0 옵션. **IdP = Keycloak 자체 호스팅(온프레미스, 내부망 IdP — 외부 API 금지 비해당)**.
- **기존 JWT+2FA와 공존(교체 아님)**: SSO는 로그인 게이트웨이만 교체, 성공 후 백엔드가 기존 `JwtService.issue()`로 동일 형태 앱 JWT를 브로커 발급 → 전 화면·RBAC·행사 스코프 불변. `/api/auth/oidc/callback`만 신설, 로컬 로그인 폴백 유지(IdP 장애 대비).
- **2FA**: IdP realm이 OTP 제공 시 위임(이중 2FA 방지), 미구성·로컬 폴백은 기존 TotpService 유지.
- **HR 연동**: 어댑터 `HrDirectoryClient`(+Mock)로 외부 HR API를 정본 조달 + 로컬 읽기 캐시 스냅샷(미가용 시 degraded). PII 최소(주민번호·연락처·급여 미수집), 삭제는 소프트. 전역 역할·hall_manager는 IdP 그룹/클레임 매핑, **행사 역할(event_member)은 로컬 권위 유지**.
- **3자 매핑**: SSO subject(`sub`) ↔ HR 사번(`empNo`) ↔ 로컬 `app_user.id`(연결 테이블 `user_identity`). 이메일 단독 매칭 금지, 충돌 시 자동 병합 금지·관리자 수동 링크. JIT 프로비저닝(첫 SSO 로그인 시 password_hash 없이 생성).
- **이행**: 피처플래그(`sso.enabled`) 4단계(준비→coexist→pilot→cutover→정착), 각 단계 롤백 게이트. 신설 모델(`user_identity`·`hr_dept_snapshot`·`hr_emp_snapshot`·`hr_sync_log`·`dept.source`)은 DA/backend 인계 백로그(미구현).
> 근거: architecture/sso-hr-integration.md, PLANNING §5B-3, 실제 스키마.
---
## 8. 네트워크·보안영역 설계
### 8-1. 존 모델(4계층 + 관리 존)
| 존 | 구성 | 인바운드 | 아웃바운드 |
|---|---|---|---|
| 엣지(무신뢰) | CDN·WAF/DDoS | 인터넷 80/443 | DMZ LB |
| DMZ(공개) | 공개 LB(TLS 종단)·public SSR·visitor 게이트웨이·공개 API GW·PG 콜백 | 엣지에서만 | 내부망 API GW(제한), **데이터 존 직결 금지** |
| 내부망(인증·운영) | 내부 LB·SSO 게이트·내부 API GW·공유 백엔드 | DMZ(허용 API)·관리 존(IP/VPN) | 데이터 존·AI 워커·승인 아웃바운드 |
| AI 워커 존 | Redis 큐·나노바나나 워커·Ollama | 내부망(큐 소비만) | 데이터 존(OBJ), Gemini egress 단일 경로만 |
| 데이터 존(최내곽) | PostgreSQL+PostGIS·오브젝트 스토리지·BI 읽기 복제 | 내부망·AI 워커(서비스 계정만) | **없음(전면 차단)** |
| 관리 존 | 배스천·관측성·백업 | 운영자 VPN/허용 IP | 대상 존 SSH·수집 |
- **핵심 규칙**: DMZ → 데이터 존 직접 접근 절대 금지(내부망 백엔드 API 경유). 데이터 존 아웃바운드 없음.
### 8-2. 공개 vs 내부 백오피스 분리
- DMZ: 공개 홍보 사이트(`www.`/`expo.`, SSR·CDN·SEO·다국어·비인증·쓰기 없음), 관람객 앱(`visitor.`·쓰기 제한).
- 내부망(인증): `organizer.`·`exhibitor.`·`contractor.`(등록업체 검증). 내부망(운영): `ops.`(IP/VPN 제한). 관리: `admin.`(M18) = VPN/허용 IP allowlist + 2FA 강제 + 감사로그 전량 + 웹 전용.
- 공개(DMZ) 3원칙: 쓰기 없음 · 데이터 존 직결 불가 · 캐시/CDN 적극.
### 8-3. 방화벽·LB·TLS·아웃바운드
- 기본 정책 = DROP(default-deny). DB 포트(5432)·Redis·OBJ 인터넷 미노출. DMZ↔내부망은 HTTPS/API만.
- WAF: OWASP Top10, 업로드 확장자·MIME·크기 이중 검증, PG 콜백 IP allowlist+서명 검증, admin·옥션 레이트리밋 강화.
- LB: 공개 LB(DMZ) / 내부 LB(백엔드, 무상태 JWT 라운드로빈+헬스체크) / WebSocket 스티키. TLS는 LB 종단(HSTS·TLS1.2+), 존 간 mTLS 옵션.
- **아웃바운드 게이트(승인 4목적지)**: `api.anthropic.com`(승인, 실패 시 Ollama 폴백)·`generativelanguage.googleapis.com`(G1 미승인, 워커에서만)·PG 결제·SMTP. 그 외 전량 차단. 키 격리=네트워크 격리(Gemini 키=워커 존, Claude/PG 키=백엔드, 데이터 존 무아웃바운드).
### 8-4. 보안 불변(위반 = QA 반려)
① 스택트레이스 미노출(요약만) ② 민감정보(자격증명·PII) 응답 완전 제외 ③ `GEMINI_API_KEY` 백엔드 미취급(M5는 큐 발행까지만) ④ AI 이미지 항상 워터마크·고지 강제 ⑤ admin 비번 env 주입 ⑥ 크로스-테넌트 접근은 플랫폼 슈퍼관리자 전용 API(감사).
> 근거: architecture/network.md·app.md, CLAUDE.md 보안 제약.
---
## 9. 비기능 요구사항(NFR)
### 9-1. 성능·용량
- 플로어플랜 3안 생성 수 분 내, 배치·배선 상호작용 P95 < 2s, 부스 목록 P95 < 500ms, 이미지 단건 평균 ~40s(비동기·SLA 대상 아님), 공개 캐시 히트 P95 < 200ms, 옥션 순위 갱신 < 1s.
- 대형 행사 3,000~5,000부스(홀당 200~600). **Hikari max = `${DB_POOL_MAX:3}` + PgBouncer 권고**(공유 PG 포화 방지).
### 9-2. 가용성
- 코어 인증·설계·조회 경로 HA SLO 99.5%(성수기 99.9% 지향). 백엔드 무상태 수평 확장(세션·순위·타이머·쿼터·캐시 Redis 외부화), Redis HA(Sentinel/Cluster+AOF), PG 프라이머리+읽기 복제(BI·공개조회 오프로드).
### 9-3. 기술 표준(핀 버전)
- 백엔드: Java 17 · Spring Boot 3.2.5 · Gradle · **MyBatis 3.0.3**(JPA 금지, `@MapperScan(annotationClass=Mapper.class)`) · jjwt 0.12.5 · UTF-8 강제.
- 프론트: React 18.3.1 · Vite 5.4.8 · TypeScript 5.6.2(strict) · react-router-dom 6 · @tanstack/react-query 5 · zustand 4(Redux 금지). 빌드 `tsc -b && vite build`(타입 에러=빌드 실패).
- 워커: Python 3.11+ · google-genai · `gemini-3.1-flash-image-preview`. 실 호출은 `NANOBANANA_LIVE=1`+`GEMINI_API_KEY` 동시 충족 시만(기본 목/degraded).
- 데이터: PostgreSQL+PostGIS(`kintex_db`)·Redis·오브젝트 스토리지·Flyway.
- 관측성: Actuator+Micrometer 권고(`/actuator/health` 배포 게이트·`/metrics`). 로그 보안 불변(자격증명·IP·PII·스택트레이스 금지, `include-stacktrace:never`).
### 9-4. 접근성·국제화
- WCAG AA 이상, 차트 색 외 패턴/라벨 병기, KPI·표 aria-label, 키보드 포커스 링 `primary-600` 2px, 전 아이콘 선(stroke) SVG(이모지 금지), i18n 키 분리(한/영/중/일 로케일 숫자/통화/날짜 포맷), `prefers-reduced-motion` 준수.
> 근거: architecture/system.md·tech.md, design.md 전역 NFR.
---
> **후속 산출 명시**: 본 설계서와 개발계획서(`01_개발계획서.md`)는 개발 착수 시점 산출물이다. **사용자지침서·운영자지침서는 UI 정렬 안정화 이후 별도 산출**한다(deliverables 갱신 정책상 완성+QA 통과 후 최신 메뉴를 반영해야 하므로 이번 범위에서 제외).

Binary file not shown.

View File

@ -0,0 +1,854 @@
# -*- coding: utf-8 -*-
"""
킨텍스 자동전시시스템 DA(데이터 아키텍처) 산출물 생성기
1) 데이터베이스설계서.pptx (행안부 DB설계서 표준 / 16:9)
2) 테이블정의서.xlsx (실제 Flyway DDL 역추출)
내용 소스(권위) = src/backend/.../db/migration/V1~V9 실제 DDL.
테이블·컬럼·타입·NULL·PK/FK·기본값·인덱스·공간타입을 프로그램으로 파싱(수기 발명 금지).
컬럼 설명은 DDL 인라인 주석·COMMENT ON 그대로 사용. 없으면 generic 표준 컬럼만 보강.
디자인 = KINTEX Blue #0066B3 / AI Purple #6D4AFF / 맑은 고딕. ERD·도식은 python-pptx 도형 직접 작도.
보안 불변 = 비밀번호/API키/서버IP/SSH 미기재. 암호화 컬럼은 정책만 기술(AES-256-GCM).
재실행: python gen_da_deliverables.py
"""
import os, re, glob
HERE = os.path.dirname(os.path.abspath(__file__))
OUT_DIR = os.path.dirname(HERE) # docs/deliverables/DA산출물
MIG_DIR = os.path.abspath(os.path.join(
HERE, "..", "..", "..", "..", "src", "backend", "src", "main", "resources", "db", "migration"))
assert os.path.isdir(MIG_DIR), f"migration dir not found: {MIG_DIR}"
# ======================================================================
# 1. DDL 파서 (Flyway V1~V9 → 테이블 스키마 모델)
# ======================================================================
CONSTRAINT_STARTS = ("PRIMARY KEY", "UNIQUE", "FOREIGN KEY", "CHECK", "CONSTRAINT", "REFERENCES ")
GENERIC_DESC = {
"id": "식별자(PK)", "created_at": "생성일시", "updated_at": "수정일시",
"event_id": "행사 참조(FK event)", "status": "상태 코드", "sort_order": "정렬 순서",
"use_yn": "사용 여부(Y/N)", "dept_id": "부서 식별자", "writer_id": "작성자",
"writer_name": "작성자명", "author_id": "작성자", "author_name": "작성자명",
"owner_id": "소유자", "owner_name": "소유자명", "title": "제목", "content": "내용",
"read_at": "읽은 일시", "recipient_id": "수신자",
}
def _split_type(rest):
"""rest 문자열의 선두에서 데이터타입 토큰을 추출. geometry(Point,0)·numeric(8,2) 등 괄호 내부 콤마 허용."""
m = re.match(r"\s*([A-Za-z_][A-Za-z0-9_]*(?:\([^)]*\))?)", rest)
if not m:
return "", rest
return m.group(1), rest[m.end():]
def _parse_default(rest):
m = re.search(r"\bDEFAULT\s+(now\(\)|gen_random_uuid\(\)|'[^']*'|true|false|[0-9]+\.?[0-9]*|[A-Za-z_][\w]*)",
rest, re.IGNORECASE)
return m.group(1) if m else ""
def parse_ddl():
tables = {} # name -> {"cols":[...], "pk":set, "uniques":[...], "indexes":[...], "version":Vx, "order":int}
order = 0
files = sorted(glob.glob(os.path.join(MIG_DIR, "V*.sql")),
key=lambda p: int(re.match(r"V(\d+)", os.path.basename(p)).group(1)))
comments = {} # (table,col) -> desc (COMMENT ON)
for path in files:
ver = re.match(r"(V\d+)", os.path.basename(path)).group(1)
raw = open(path, encoding="utf-8").read()
# COMMENT ON COLUMN t.c IS '...'
for cm in re.finditer(r"COMMENT\s+ON\s+COLUMN\s+(\w+)\.(\w+)\s+IS\s+'((?:[^']|'')*)'", raw, re.IGNORECASE):
comments[(cm.group(1), cm.group(2))] = cm.group(3).replace("''", "'")
# CREATE TABLE IF NOT EXISTS name ( ... );
for tm in re.finditer(r"CREATE\s+TABLE\s+IF\s+NOT\s+EXISTS\s+(\w+)\s*\((.*?)\n\)\s*;",
raw, re.IGNORECASE | re.DOTALL):
tname, body = tm.group(1), tm.group(2)
if tname not in tables:
order += 1
tables[tname] = {"cols": [], "pk": set(), "uniques": [], "indexes": [],
"version": ver, "order": order}
t = tables[tname]
for line in body.split("\n"):
code = line.split("--", 1)[0].strip()
cmt = line.split("--", 1)[1].strip() if "--" in line else ""
if not code:
continue
code = code.rstrip(",").strip()
up = code.upper()
# 테이블 레벨 제약
if up.startswith("PRIMARY KEY"):
for c in re.findall(r"\((.*?)\)", code)[0].split(","):
t["pk"].add(c.strip())
continue
if up.startswith("UNIQUE"):
t["uniques"].append(re.findall(r"\((.*?)\)", code)[0].strip())
continue
if up.startswith(CONSTRAINT_STARTS):
continue
# 컬럼 정의
cm2 = re.match(r"(\w+)\s+(.*)", code)
if not cm2:
continue
cname = cm2.group(1)
dtype, rest = _split_type(cm2.group(2))
ru = rest.upper()
is_pk = "PRIMARY KEY" in ru
if is_pk:
t["pk"].add(cname)
fk = re.search(r"REFERENCES\s+(\w+)\s*\((\w+)\)", rest, re.IGNORECASE)
nn = ("NOT NULL" in ru) or is_pk
uniq = ("UNIQUE" in ru) and "PRIMARY KEY" not in ru
dflt = _parse_default(rest)
t["cols"].append({
"name": cname, "type": dtype,
"null": "N" if nn else "Y",
"pk": is_pk, "fk": (fk.group(1) if fk else None),
"fkcol": (fk.group(2) if fk else None),
"unique": uniq, "default": dflt, "desc": cmt,
})
# ALTER TABLE t ADD COLUMN IF NOT EXISTS c type ...
for am in re.finditer(r"ALTER\s+TABLE\s+(\w+)\s+ADD\s+COLUMN\s+IF\s+NOT\s+EXISTS\s+(.+?);",
raw, re.IGNORECASE):
tname = am.group(1)
if tname not in tables:
continue
t = tables[tname]
code = am.group(2).strip()
cm2 = re.match(r"(\w+)\s+(.*)", code)
if not cm2:
continue
cname = cm2.group(1)
if any(c["name"] == cname for c in t["cols"]):
continue
dtype, rest = _split_type(cm2.group(2))
ru = rest.upper()
t["cols"].append({
"name": cname, "type": dtype,
"null": "N" if "NOT NULL" in ru else "Y",
"pk": False, "fk": None, "fkcol": None,
"unique": "UNIQUE" in ru, "default": _parse_default(rest),
"desc": f"({ver} 순증 컬럼)",
})
# CREATE INDEX ... ON t ...
for im in re.finditer(r"CREATE\s+INDEX\s+IF\s+NOT\s+EXISTS\s+(\w+)\s+ON\s+(\w+)\s*(.*?);",
raw, re.IGNORECASE | re.DOTALL):
idxname, tname, spec = im.group(1), im.group(2), im.group(3).strip()
if tname in tables:
gist = "GIST" in spec.upper()
cols = re.findall(r"\((.*?)\)", spec)
tables[tname]["indexes"].append({
"name": idxname, "spec": (cols[0] if cols else spec),
"gist": gist,
})
# 설명 보강: COMMENT ON → generic
for tname, t in tables.items():
for c in t["cols"]:
if not c["desc"]:
c["desc"] = comments.get((tname, c["name"]), "")
if not c["desc"] and c["name"] in GENERIC_DESC:
c["desc"] = GENERIC_DESC[c["name"]]
return tables
TABLES = parse_ddl()
# ---- 테이블 한글명 · 주제영역 매핑 (라벨링) ----------------------------------
KO = {
"app_user": "사용자", "company": "등록업체", "event": "행사",
"hall": "전시홀 마스터", "hall_assignment": "행사-홀 배정", "event_member": "행사 참여자(RBAC)",
"booth_standard": "부스 표준사양", "master_data": "마스터데이터(요율·규정)",
"trench": "트렌치(급전 포인트)", "hall_exit": "비상구", "layout": "배치안(버전)",
"booth": "부스(폴리곤)", "design_plan": "부스 설계안", "utility_order": "유틸리티 신청",
"render_job": "AI 렌더잡", "common_code_group": "공통코드 그룹", "common_code": "공통코드",
"sys_menu": "시스템 메뉴", "sys_role": "역할", "sys_permission": "권한",
"sys_role_permission": "역할-권한 매핑", "sys_setting": "시스템 설정", "audit_log": "감사 로그",
"worklog": "업무일지", "schedule": "일정", "message": "쪽지",
"message_recipient": "쪽지 수신자", "notice": "공지", "opinion": "의견",
"opinion_comment": "의견 댓글", "meeting": "회의", "meeting_action": "회의 액션아이템",
"report": "보고서", "notification": "알림", "password_reset": "비밀번호 재설정 코드",
}
# 주제영역(Subject Area): (코드, 한글, 테이블목록, 색상키)
AREAS = [
("MASTER", "행사·조직·권한·마스터",
["event", "hall", "hall_assignment", "event_member", "app_user", "company",
"booth_standard", "master_data"]),
("SPATIAL", "공간·설계·시각화(부스 코어)",
["trench", "hall_exit", "layout", "booth", "design_plan", "utility_order", "render_job"]),
("SYSTEM", "시스템관리·인증",
["common_code_group", "common_code", "sys_menu", "sys_role", "sys_permission",
"sys_role_permission", "sys_setting", "audit_log", "password_reset"]),
("COMMON", "공통 업무 모듈",
["worklog", "schedule", "message", "message_recipient", "notice", "opinion",
"opinion_comment", "meeting", "meeting_action", "report", "notification"]),
]
AREA_OF = {}
for code, ko, tlist in AREAS:
for tn in tlist:
AREA_OF[tn] = ko
TOTAL_TABLES = len(TABLES)
TOTAL_COLS = sum(len(t["cols"]) for t in TABLES.values())
TOTAL_IDX = sum(len(t["indexes"]) for t in TABLES.values())
SPATIAL_COLS = [(tn, c["name"], c["type"]) for tn, t in TABLES.items()
for c in t["cols"] if c["type"].lower().startswith("geometry")]
FK_EDGES = [(tn, c["name"], c["fk"]) for tn, t in TABLES.items()
for c in t["cols"] if c["fk"]]
# ======================================================================
# 2. PPTX — 데이터베이스설계서
# ======================================================================
from pptx import Presentation
from pptx.util import Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE, MSO_CONNECTOR
from pptx.oxml.ns import qn
BLUE = RGBColor(0x00, 0x66, 0xB3)
BLUE_DK = RGBColor(0x00, 0x44, 0x7A)
BLUE_LT = RGBColor(0xE1, 0xEF, 0xF9)
PURPLE = RGBColor(0x6D, 0x4A, 0xFF)
PURPLE_LT = RGBColor(0xEC, 0xE8, 0xFF)
INK = RGBColor(0x10, 0x18, 0x28)
MUTED = RGBColor(0x66, 0x70, 0x85)
LINE = RGBColor(0xE4, 0xE7, 0xEC)
BG = RGBColor(0xF9, 0xFA, 0xFB)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
GREEN = RGBColor(0x12, 0x9E, 0x63)
AMBER = RGBColor(0xE0, 0x8A, 0x00)
TEAL = RGBColor(0x0E, 0x7C, 0x86)
CARD = RGBColor(0xFF, 0xFF, 0xFF)
FONT = "맑은 고딕"
IN = 914400
def inch(v): return int(v * IN)
prs = Presentation()
prs.slide_width = 12192000
prs.slide_height = 6858000
BLANK = prs.slide_layouts[6]
def slide(): return prs.slides.add_slide(BLANK)
def _fill(sp, color):
if color is None: sp.fill.background()
else:
sp.fill.solid(); sp.fill.fore_color.rgb = color
def rect(s, x, y, w, h, fill=None, line=None, line_w=1.0, shape=MSO_SHAPE.RECTANGLE, shadow=False):
sp = s.shapes.add_shape(shape, inch(x), inch(y), inch(w), inch(h))
_fill(sp, fill)
if line is None: sp.line.fill.background()
else:
sp.line.color.rgb = line; sp.line.width = Pt(line_w)
sp.shadow.inherit = False # creates an empty <a:effectLst/>
if shadow:
el = sp._element.spPr
ef = el.find(qn('a:effectLst'))
if ef is None:
ef = el.makeelement(qn('a:effectLst'), {}); el.append(ef)
sh = ef.makeelement(qn('a:outerShdw'),
{'blurRad':'80000','dist':'30000','dir':'5400000','rotWithShape':'0'})
clr = sh.makeelement(qn('a:srgbClr'), {'val':'101828'})
alp = clr.makeelement(qn('a:alpha'), {'val':'16000'})
clr.append(alp); sh.append(clr); ef.append(sh)
return sp
def text(s, x, y, w, h, content, size=13, color=INK, bold=False, align=PP_ALIGN.LEFT,
anchor=MSO_ANCHOR.TOP, line_spacing=1.0, space_after=2, wrap=True):
tb = s.shapes.add_textbox(inch(x), inch(y), inch(w), inch(h))
tf = tb.text_frame; tf.word_wrap = wrap; tf.vertical_anchor = anchor
tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0
if isinstance(content, str): content = [content]
for i, para in enumerate(content):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = align; p.line_spacing = line_spacing
p.space_after = Pt(space_after); p.space_before = Pt(0)
segs = [{"t": para}] if isinstance(para, str) else ([para] if isinstance(para, dict) else para)
for seg in segs:
r = p.add_run(); r.text = seg.get("t", "")
r.font.name = FONT; r.font.size = Pt(seg.get("size", size))
r.font.bold = seg.get("bold", bold); r.font.color.rgb = seg.get("color", color)
rPr = r._r.get_or_add_rPr()
rPr.append(rPr.makeelement(qn('a:ea'), {'typeface': FONT}))
return tb
def page_bg(s): rect(s, -0.1, -0.1, 13.53, 7.7, fill=BG)
def chip(s, x, y, w, h, label, fill, txtcolor=WHITE, size=9.5, bold=True):
rect(s, x, y, w, h, fill=fill, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, y, w, h, label, size=size, color=txtcolor, bold=bold, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
def card(s, x, y, w, h, fill=CARD, line=LINE):
return rect(s, x, y, w, h, fill=fill, line=line, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=True)
def header(s, kicker, title, idx):
page_bg(s)
rect(s, 0, 0, 13.333, 0.12, fill=BLUE)
rect(s, 0.55, 0.5, 0.62, 0.62, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, 0.5, 0.62, 0.62, f"{idx:02d}", size=20, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
text(s, 1.32, 0.5, 10.5, 0.28, kicker, size=11, color=PURPLE, bold=True)
text(s, 1.32, 0.74, 11.4, 0.44, title, size=22, color=INK, bold=True)
rect(s, 0.55, 1.28, 12.23, 0.02, fill=LINE)
text(s, 0.55, 7.06, 9, 0.3, "킨텍스 자동전시시스템 구축 · 데이터베이스 설계서", size=8.5, color=MUTED)
text(s, 11.3, 7.06, 1.48, 0.3, "2026-07-11", size=8.5, color=MUTED, align=PP_ALIGN.RIGHT)
def bullets(s, x, y, w, items, size=12.5, gap=0.44, head=None):
if head:
text(s, x, y, w, 0.32, head, size=12, color=PURPLE, bold=True); y += 0.42
for it in items:
rect(s, x, y+0.07, 0.12, 0.12, fill=BLUE, shape=MSO_SHAPE.OVAL)
if isinstance(it, tuple):
text(s, x+0.28, y, w-0.28, gap, [[{"t": it[0]+" ", "bold":True, "color":INK, "size":size},
{"t": it[1], "color":MUTED, "size":size-1}]], line_spacing=1.05)
else:
text(s, x+0.28, y, w-0.28, gap, it, size=size, color=INK, line_spacing=1.05)
y += gap
return y
# ---- ERD 엔티티 박스 (파싱 데이터 구동) ----
def erd_entity(s, x, y, w, tname, maxrows=6, accent=BLUE):
t = TABLES[tname]
# 표시 컬럼: PK → FK → 대표 컬럼
pk = [c for c in t["cols"] if c["pk"]]
fk = [c for c in t["cols"] if c["fk"] and not c["pk"]]
rest = [c for c in t["cols"] if not c["pk"] and not c["fk"]]
show = (pk + fk + rest)[:maxrows]
rh = 0.26
h = 0.5 + len(show)*rh
rect(s, x, y, w, h, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=True)
rect(s, x, y, w, 0.44, fill=accent, shape=MSO_SHAPE.ROUND_2_SAME_RECTANGLE)
text(s, x+0.12, y, w-0.2, 0.44, [[{"t": tname, "size":10.5, "color":WHITE, "bold":True},
{"t": " "+KO.get(tname, ""), "size":8, "color":RGBColor(0xD6,0xE6,0xF6)}]],
anchor=MSO_ANCHOR.MIDDLE)
cy = y + 0.5
for c in show:
key = "PK" if c["pk"] else ("FK" if c["fk"] else "")
kc = PURPLE if c["pk"] else (TEAL if c["fk"] else MUTED)
geo = c["type"].lower().startswith("geometry")
text(s, x+0.12, cy-0.03, 0.42, rh, key, size=7.5, color=kc, bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, x+0.54, cy-0.03, w-0.62, rh,
[[{"t": c["name"], "size":8.6, "color":(INK if key else RGBColor(0x33,0x3B,0x4A)), "bold":bool(key)},
{"t": " : "+c["type"], "size":7.6, "color":(GREEN if geo else MUTED)}]],
anchor=MSO_ANCHOR.MIDDLE)
cy += rh
return {"x": x, "y": y, "w": w, "h": h, "cx": x+w/2, "cy": y+h/2}
def connect(s, a, b, color=RGBColor(0x9A,0xA6,0xB8)):
cn = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(a["cx"]), inch(a["cy"]),
inch(b["cx"]), inch(b["cy"]))
cn.line.color.rgb = color; cn.line.width = Pt(1.4)
cn.shadow.inherit = False
return cn
# ================= 슬라이드 1 : 표지 =================
s = slide()
rect(s, -0.1, -0.1, 13.53, 7.7, fill=BLUE_DK)
rect(s, 6.4, -1.5, 9, 11, fill=BLUE, shape=MSO_SHAPE.PARALLELOGRAM)
rect(s, 9.2, -2.0, 7, 12, fill=PURPLE, shape=MSO_SHAPE.PARALLELOGRAM)
rect(s, 0.85, 0.8, 0.42, 0.42, fill=WHITE, shape=MSO_SHAPE.OVAL)
rect(s, 0.97, 0.92, 0.18, 0.18, fill=PURPLE, shape=MSO_SHAPE.OVAL)
text(s, 1.42, 0.78, 6, 0.46, "KINTEX · WISE AI", size=15, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
chip(s, 0.85, 2.35, 1.95, 0.42, "데이터베이스 설계서", PURPLE, WHITE, size=12)
chip(s, 2.92, 2.35, 2.2, 0.42, "Database Design", RGBColor(0x2B,0x4C,0x7E), WHITE, size=10.5)
text(s, 0.85, 2.95, 11, 1.9, [
[{"t":"킨텍스 자동전시시스템", "size":44, "color":WHITE, "bold":True}],
[{"t":"데이터베이스 설계서", "size":44, "color":WHITE, "bold":True}]], line_spacing=1.02)
text(s, 0.88, 4.7, 11, 0.5, "PostgreSQL 15 + PostGIS · Flyway 형상관리 · kintex 전용 DB",
size=15.5, color=RGBColor(0xC7,0xDD,0xF2))
text(s, 0.88, 5.24, 11.4, 0.4,
"공간데이터(부스 POLYGON · 트렌치 POINT · 배선 LINESTRING) 일원화 · 행사 단위 격리 · 감사추적",
size=12, color=RGBColor(0xB9,0xC7,0xEA))
rect(s, 0.85, 6.05, 11.63, 0.02, fill=RGBColor(0x3A,0x5A,0x8C))
text(s, 0.85, 6.25, 8.6, 0.4, [[{"t":"산출물 ","size":11,"color":RGBColor(0x90,0xA6,0xC9)},
{"t":f"개념·논리·물리 데이터 모델 / 테이블 {TOTAL_TABLES} · 컬럼 {TOTAL_COLS} · 인덱스 {TOTAL_IDX}","size":11,"color":WHITE,"bold":True}]])
text(s, 0.85, 6.62, 9.6, 0.4, [[{"t":"근거 ","size":11,"color":RGBColor(0x90,0xA6,0xC9)},
{"t":"Flyway V1~V10 실제 DDL 역추출 (스키마 권위)","size":10.5,"color":WHITE}]])
text(s, 9.7, 6.25, 2.78, 0.4, "작성일 2026-07-11", size=11, color=WHITE, bold=True, align=PP_ALIGN.RIGHT)
text(s, 9.7, 6.62, 2.78, 0.4, "DA · kintex-da", size=9.5, color=RGBColor(0x90,0xA6,0xC9), align=PP_ALIGN.RIGHT)
# ================= 슬라이드 2 : 목차 =================
s = slide()
header(s, "CONTENTS", "목차", 0)
rect(s, 0.55, 0.5, 0.62, 0.62, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, 0.5, 0.62, 0.62, "C", size=22, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
toc = [
("01","개요","PostgreSQL+PostGIS · Flyway · kintex 전용 DB"),
("02","데이터 아키텍처 원칙","행사 단위 격리 · 공간타입 선정 · 감사로그"),
("03","개념 데이터 모델","핵심 주제영역 관계 개요도"),
("04","논리 모델(ERD)","주제영역별 엔터티·관계 ERD"),
("05","물리 모델 요약","테이블 그룹 · 공간컬럼 · 인덱스 전략"),
("06","데이터 표준","명명 규칙 · 공통코드 · 날짜/통화"),
("07","데이터 보안","암호화·PII·응답제외·감사추적"),
("08","백업·이관 전략","Flyway 마이그레이션 운영 원칙"),
]
cw = 5.85
for i,(no,t,d) in enumerate(toc):
col = i // 4; row = i % 4
x = 0.7 + col*(cw+0.35); y = 1.62 + row*1.28
rect(s, x, y, cw, 1.1, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
rect(s, x, y, 0.09, 1.1, fill=BLUE if col==0 else PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.28, y, 1.0, 1.1, no, size=26, color=(BLUE if col==0 else PURPLE), bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, x+1.3, y+0.24, cw-1.5, 0.4, t, size=15, color=INK, bold=True)
text(s, x+1.3, y+0.64, cw-1.5, 0.34, d, size=9.5, color=MUTED)
# ================= 슬라이드 3 : 01 개요 =================
s = slide()
header(s, "OVERVIEW", "01. 개요 — DBMS · 형상관리 · DB 구성", 1)
text(s, 0.55, 1.45, 12.2, 0.5,
"킨텍스 자동전시시스템은 전시 생애주기(판매→AI 설계·시각화→공사 옥션→운영→경영분석) 전 과정을 단일 DB에 담는다. "
"공간데이터를 애플리케이션과 분리하지 않고 PostGIS로 DB에 일원화하여 배치·배선·규정검증을 ST_* 연산으로 수행한다.",
size=12, color=INK, line_spacing=1.12)
# 좌: 스택 카드
cards = [
("DBMS", "PostgreSQL 15", "관계형 + 트랜잭션 정합", BLUE),
("공간확장", "PostGIS", "POLYGON·POINT·LINESTRING 지오메트리", GREEN),
("암호화", "pgcrypto", "gen_random_uuid() · 해시", PURPLE),
("형상관리", "Flyway", "V1~V10 버전 마이그레이션(불변·순증)", AMBER),
]
for i,(k,v,d,c) in enumerate(cards):
x = 0.55 + i*3.06
card(s, x, 2.2, 2.86, 1.35)
rect(s, x, 2.2, 2.86, 0.1, fill=c, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.22, 2.4, 2.5, 0.3, k, size=10, color=c, bold=True)
text(s, x+0.22, 2.72, 2.5, 0.4, v, size=15, color=INK, bold=True)
text(s, x+0.22, 3.16, 2.5, 0.34, d, size=9, color=MUTED, line_spacing=1.05)
# 통계 스트립
stats = [(f"{TOTAL_TABLES}", "테이블"), (f"{TOTAL_COLS}", "컬럼"), (f"{len(AREAS)}", "주제영역"),
(f"{len(SPATIAL_COLS)}", "공간 컬럼"), (f"{TOTAL_IDX}", "인덱스"), ("V1V10", "Flyway")]
for i,(v,l) in enumerate(stats):
x = 0.55 + i*2.03
rect(s, x, 3.85, 1.9, 1.0, fill=BLUE_LT, line=LINE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, 4.0, 1.9, 0.44, v, size=23, color=BLUE_DK, bold=True, align=PP_ALIGN.CENTER)
text(s, x, 4.5, 1.9, 0.3, l, size=10, color=MUTED, align=PP_ALIGN.CENTER)
# DB 구성 원칙
bullets(s, 0.55, 5.15, 12.2, [
("kintex 전용 DB", "GUARDiA 타 솔루션과 물리 DB 분리(kintex 전용 스키마). 접속·권한은 서비스 계정 최소권한."),
("공간·업무 단일 저장", "부스 폴리곤·배선 라인·요율·관람 데이터를 한 트랜잭션 경계에서 일관 관리."),
("멱등·순증 마이그레이션", "V7 이후는 CREATE IF NOT EXISTS·ADD COLUMN IF NOT EXISTS로 재적용 안전(운영 무중단)."),
], size=11.5, gap=0.5)
# ================= 슬라이드 4 : 02 아키텍처 원칙 =================
s = slide()
header(s, "PRINCIPLES", "02. 데이터 아키텍처 원칙", 2)
princ = [
("행사 단위 격리(멀티테넌시)", BLUE,
["물리 tenant_id 컬럼 대신 event_id + event_member(RBAC)로 논리 격리",
"핵심 트랜잭션 테이블은 event_id FK·ON DELETE CASCADE로 행사 경계 정합",
"hall·company·master_data 등 마스터는 전 행사 공유(참조 무결성)"]),
("공간데이터 PostGIS 일원화", GREEN,
["부스=POLYGON, 트렌치·비상구=POINT, 배선=MultiLineString (SRID 0 홀 로컬 m)",
"GiST 공간 인덱스로 ST_Intersects/Area/Distance 고속 연산",
"배치·배선 재저장 대비 booth_id 소프트 참조(감사·쿼터 보존)"]),
("감사 추적·이력 보존", PURPLE,
["audit_log에 actor·action·target·ruleset_version·result 기록",
"render_job은 이력·쿼터 정본(부스 교체와 무관하게 감사 보존)",
"layout·design_plan·booth_standard 버전(version) 관리"]),
("보안 내재화", AMBER,
["password_hash·otp_secret·code_hash 컬럼은 API 응답/로그 완전 제외",
"민감 시크릿 설정은 sys_setting.secret_yn='Y'로 마스킹",
"error_message는 사용자 요약만(스택트레이스 저장 금지)"]),
]
for i,(title,c,items) in enumerate(princ):
x = 0.55 + (i%2)*6.15; y = 1.5 + (i//2)*2.6
card(s, x, y, 5.95, 2.4)
rect(s, x, y, 0.11, 2.4, fill=c, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.35, y+0.2, 5.4, 0.4, title, size=14, color=INK, bold=True)
yy = y+0.72
for it in items:
rect(s, x+0.4, yy+0.06, 0.1, 0.1, fill=c, shape=MSO_SHAPE.OVAL)
text(s, x+0.62, yy, 5.15, 0.5, it, size=10.3, color=MUTED, line_spacing=1.05)
yy += 0.52
# ================= 슬라이드 5 : 03 개념 데이터 모델 =================
s = slide()
header(s, "CONCEPTUAL", "03. 개념 데이터 모델 — 주제영역 관계 개요", 3)
text(s, 0.55, 1.42, 12.2, 0.4,
"구현된 4개 주제영역과 소유 데이터의 관계. 행사(EVENT)를 축으로 공간·설계 코어와 공통·시스템 레이어가 결합한다.",
size=11.5, color=MUTED)
# 중앙 EVENT 허브
hub = {"cx":6.6, "cy":3.9}
rect(s, 5.75, 3.3, 1.7, 1.2, fill=BLUE_DK, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=True)
text(s, 5.75, 3.3, 1.7, 1.2, [[{"t":"EVENT","size":15,"color":WHITE,"bold":True}],
[{"t":"행사(테넌트 축)","size":9,"color":RGBColor(0xC7,0xDD,0xF2)}]], align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
sa = [
("MASTER","행사·조직·권한·마스터","event · hall · company · event_member",8,BLUE, 0.7, 1.95),
("SPATIAL","공간·설계·시각화","layout · booth · utility_order · render_job",7,GREEN, 9.35, 1.95),
("COMMON","공통 업무 모듈","worklog · schedule · notice · meeting …",11,PURPLE, 0.7, 4.75),
("SYSTEM","시스템관리·인증","sys_* · common_code · audit_log · password_reset",9,AMBER, 9.35, 4.75),
]
for code,ko,ex,n,c,x,y in sa:
# 연결선 (먼저)
box_cx = x+1.6; box_cy = y+0.75
cn = s.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, inch(box_cx), inch(box_cy), inch(hub["cx"]), inch(hub["cy"]))
cn.line.color.rgb = RGBColor(0xC2,0xCC,0xDA); cn.line.width = Pt(1.6); cn.shadow.inherit=False
for code,ko,ex,n,c,x,y in sa:
card(s, x, y, 3.2, 1.5)
rect(s, x, y, 3.2, 0.12, fill=c, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.24, y+0.24, 2.7, 0.34, ko, size=13, color=INK, bold=True)
chip(s, x+2.45, y+0.24, 0.62, 0.3, f"{n}T", c, WHITE, size=9)
text(s, x+0.24, y+0.68, 2.9, 0.7, ex, size=9.3, color=MUTED, line_spacing=1.08)
text(s, 0.55, 6.5, 12.2, 0.4,
"관계 요지: EVENT 1─N HallAssignment/EventMember/Layout/Notice/Worklog(event_id). Layout 1─N Booth(POLYGON) 1─N DesignPlan·UtilityOrder(배선)·RenderJob. Company·User ─ EventMember로 행사 RBAC 성립.",
size=9.8, color=INK, line_spacing=1.1)
# ================= 슬라이드 6~9 : 04 논리 모델 ERD (주제영역별) =================
def erd_slide(area_ko, idx, placements, extra_note, cross_notes=None):
s = slide()
header(s, "LOGICAL ERD", f"04. 논리 모델 — {area_ko}", 4)
# 연결선 먼저(엔티티 아래로 깔림)
boxes = {}
# 1차: 좌표만 계산해 중심 확보
for tn, x, y, w in placements:
t = TABLES[tn]
pk=[c for c in t["cols"] if c["pk"]]; fk=[c for c in t["cols"] if c["fk"] and not c["pk"]]
rest=[c for c in t["cols"] if not c["pk"] and not c["fk"]]
show=(pk+fk+rest); show=show[:6]
h = 0.5+len(show)*0.26
boxes[tn]={"x":x,"y":y,"w":w,"h":h,"cx":x+w/2,"cy":y+h/2}
for tn,_,_,_ in placements:
for c in TABLES[tn]["cols"]:
if c["fk"] and c["fk"] in boxes and c["fk"]!=tn:
connect(s, boxes[tn], boxes[c["fk"]])
accents={"MASTER":BLUE,"SPATIAL":GREEN,"SYSTEM":AMBER,"COMMON":PURPLE}
acc = accents.get(idx, BLUE)
for tn,x,y,w in placements:
erd_entity(s, x, y, w, tn, accent=acc)
if extra_note:
text(s, 0.55, 6.78, 12.2, 0.3, extra_note, size=9, color=MUTED, line_spacing=1.05)
return s
# ERD-1 MASTER
erd_slide("행사·조직·권한·마스터", "MASTER", [
("event", 5.6, 1.5, 2.5),
("hall", 9.9, 1.5, 2.8),
("hall_assignment", 9.9, 4.05, 2.8),
("event_member", 5.6, 3.7, 2.85),
("app_user", 0.6, 1.5, 2.75),
("company", 0.6, 4.55, 2.75),
], "관계: event 1─N hall_assignment·event_member. app_user·company ─ event_member(행사 RBAC). 독립 마스터 booth_standard·master_data 는 물리 모델 목록(슬라이드 10) 참조.")
# ERD-2 SPATIAL
erd_slide("공간·설계·시각화(부스 코어)", "SPATIAL", [
("hall", 0.6, 1.5, 2.5),
("trench", 0.6, 4.2, 2.5),
("hall_exit", 3.45, 4.2, 2.4),
("layout", 3.6, 1.5, 2.5),
("booth", 6.55, 1.5, 2.6),
("design_plan", 9.65, 1.5, 2.7),
("utility_order", 6.55, 4.35,2.9),
("render_job", 9.85, 4.35,2.75),
], "관계: hall 1─N trench·hall_exit·layout. layout 1─N booth(POLYGON) 1─N design_plan. booth ─ utility_order(배선 LineString)·render_job(AI 샷). booth_id 소프트 참조.")
# ERD-3 SYSTEM
erd_slide("시스템관리·인증", "SYSTEM", [
("common_code_group", 0.6, 1.5, 2.7),
("common_code", 0.6, 4.0, 2.7),
("sys_role", 3.6, 1.5, 2.6),
("sys_role_permission",3.6,4.0, 2.75),
("sys_permission", 6.55, 1.5, 2.6),
("sys_menu", 9.6, 1.5, 2.8),
("sys_setting", 6.55, 4.05, 2.6),
("audit_log", 9.6, 4.05, 2.8),
], "관계: common_code_group 1─N common_code. sys_role ─ sys_role_permission ─ sys_permission(M:N). sys_menu 자기참조(parent_id). password_reset(공개 인증)은 별도. audit_log 전 모듈 공유.")
# ERD-4 COMMON
s = slide()
header(s, "LOGICAL ERD", "04. 논리 모델 — 공통 업무 모듈", 4)
# 부모-자식 3쌍 + 독립 목록
pairs = [("message","message_recipient"),("opinion","opinion_comment"),("meeting","meeting_action")]
px = 0.6
for i,(p,ch) in enumerate(pairs):
x = 0.6 + i*4.15
bp = erd_entity(s, x, 1.5, 3.0, p, accent=PURPLE)
bc = erd_entity(s, x+0.3, 3.9, 3.0, ch, accent=RGBColor(0x8B,0x6B,0xE0))
# 연결(부모→자식) 먼저 그리지 못했으니 라벨로 대체
text(s, 0.6, 6.05, 11.9, 0.3,
"부모 1─N 자식(FK·ON DELETE CASCADE): message→message_recipient · opinion→opinion_comment · meeting→meeting_action",
size=9.5, color=TEAL, bold=True)
# 독립 테이블 스트립
indep = ["worklog","schedule","notice","report","notification"]
text(s, 0.6, 6.42, 3, 0.3, "행사 스코프 독립 테이블", size=10, color=MUTED, bold=True)
for i,tn in enumerate(indep):
x = 3.0 + i*1.85
rect(s, x, 6.4, 1.75, 0.4, fill=BLUE_LT, line=LINE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, 6.4, 1.75, 0.4, tn, size=9.5, color=BLUE_DK, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ================= 슬라이드 10 : 05 물리 모델 요약 (테이블 그룹) =================
s = slide()
header(s, "PHYSICAL", "05. 물리 모델 요약 — 테이블 그룹", 5)
col_x = [0.55, 6.7]
for gi,(code,ko,tlist) in enumerate(AREAS):
x = col_x[gi%2]; y = 1.5 + (gi//2)*2.65
card(s, x, y, 5.9, 2.45)
acc = {"MASTER":BLUE,"SPATIAL":GREEN,"SYSTEM":AMBER,"COMMON":PURPLE}[code]
rect(s, x, y, 5.9, 0.5, fill=acc, shape=MSO_SHAPE.ROUND_2_SAME_RECTANGLE)
text(s, x+0.25, y, 4.8, 0.5, ko, size=13, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, x+5.0, y, 0.7, 0.5, f"{len(tlist)}T", size=13, color=WHITE, bold=True, align=PP_ALIGN.RIGHT, anchor=MSO_ANCHOR.MIDDLE)
yy = y+0.62
for j,tn in enumerate(tlist):
cx = x+0.25 + (j%2)*2.9
cyy = yy + (j//2)*0.315
text(s, cx, cyy, 2.85, 0.3, [[{"t":"· ","color":acc,"bold":True,"size":10},
{"t":tn,"size":9.3,"color":INK,"bold":True},
{"t":" "+KO.get(tn,""),"size":7.8,"color":MUTED}]], anchor=MSO_ANCHOR.MIDDLE)
# ================= 슬라이드 11 : 05b 공간 컬럼·인덱스 전략 =================
s = slide()
header(s, "PHYSICAL — SPATIAL & INDEX", "05. 공간 컬럼 · 인덱스 전략", 5)
# 공간 컬럼 표
text(s, 0.55, 1.45, 6, 0.32, "공간(PostGIS) 컬럼", size=13, color=GREEN, bold=True)
sp_rows = [("테이블","컬럼","지오메트리","용도")]
sp_meta = {
("trench","geom"):"급전/급수/네트워크 공급 포인트",
("hall_exit","geom"):"비상구 위치(이격 버퍼 검증)",
("booth","geom"):"부스 경계 폴리곤(면적·간섭)",
("utility_order","wiring"):"전기/네트워크/급배수 배선 경로",
}
for tn,cn,ty in SPATIAL_COLS:
sp_rows.append((tn, cn, ty, sp_meta.get((tn,cn),"")))
ry = 1.85
for i,row in enumerate(sp_rows):
fill = BLUE if i==0 else (WHITE if i%2 else BLUE_LT)
tc = WHITE if i==0 else INK
rect(s, 0.55, ry, 6.1, 0.4, fill=fill, line=LINE)
xs=[0.65,2.05,3.35,4.75]; ws=[1.35,1.25,1.4,1.85]
for k,val in enumerate(row):
text(s, xs[k], ry, ws[k], 0.4, val, size=8.6, color=(tc if k<3 else (tc if i==0 else MUTED)),
bold=(i==0 or k==0), anchor=MSO_ANCHOR.MIDDLE)
ry += 0.4
text(s, 0.55, ry+0.1, 6.1, 0.6,
"SRID 0 = 홀 로컬 평면(미터). ST_Area/Distance/Length 결과 단위 = m·㎡.",
size=9, color=MUTED, line_spacing=1.1)
# 인덱스 전략
text(s, 6.95, 1.45, 5.8, 0.32, "인덱스 전략", size=13, color=BLUE, bold=True)
idx_items = [
("GiST 공간 인덱스", f"geometry 컬럼 4종(booth·trench·hall_exit·utility_order.wiring) 전부 GiST"),
("B-Tree 조회 인덱스", "FK·정렬 축: writer_id+work_date, recipient_id, event_id 등"),
("복합 인덱스", "render_job(booth_id,shot_preset) · (event_id,status) 쿼터·상태 조회"),
("정렬 최적화", "audit_log(created_at DESC) · notice(event_id,pinned DESC,created_at DESC)"),
(f"총 인덱스 {TOTAL_IDX}", "PK 자동 인덱스 별도 · 공간 4 · B-Tree 다수"),
]
yy = 1.9
for k,v in idx_items:
card(s, 6.95, yy, 5.75, 0.78)
text(s, 7.15, yy+0.12, 5.4, 0.3, k, size=11, color=INK, bold=True)
text(s, 7.15, yy+0.44, 5.4, 0.3, v, size=8.8, color=MUTED, line_spacing=1.05)
yy += 0.9
# ================= 슬라이드 12 : 06 데이터 표준 =================
s = slide()
header(s, "STANDARDS", "06. 데이터 표준 — 명명 · 코드 · 날짜/통화", 6)
std = [
("명명 규칙(실제 DDL 관찰)", BLUE, [
"테이블·컬럼 = snake_case 단수형 (app_user, booth, render_job)",
"PK = id (varchar 애플리케이션 채번) / 대량 로그 = bigserial (audit_log·password_reset)",
"FK = <참조테이블>_id (event_id, booth_id, hall_id, layout_id)",
"불리언 = is_/has_/*_yn 혼용 · 금액/치수 = numeric(정밀도,스케일)",
"타임스탬프 = *_at (timestamptz) / 날짜 = *_date (date)",
]),
("공통코드 체계(common_code)", PURPLE, [
"grp_code(그룹) 1─N code — 14개 그룹 시드(USE_YN·USER_ROLE·WORK_STATUS …)",
"화면 드롭다운·상태값을 코드로 표준화 (하드코딩 배제)",
"정렬 sort_order · 사용여부 use_yn(Y/N) · 확장 attr1",
]),
("날짜·통화·단위 표준", GREEN, [
"일시 = timestamptz(UTC 저장, 표시 KST) · 날짜 = date",
"통화 = KRW 정수(원) 원칙, 요율/정산은 numeric — 부동소수 금지",
"치수 = 미터(m)·면적 ㎡ · 하중 t/㎡ (numeric 고정 스케일)",
]),
]
yy = 1.5
for title,c,items in std:
h = 0.5 + len(items)*0.34 + 0.15
card(s, 0.55, yy, 12.2, h)
rect(s, 0.55, yy, 0.11, h, fill=c, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.85, yy+0.16, 11.6, 0.34, title, size=13.5, color=INK, bold=True)
iy = yy+0.6
for it in items:
rect(s, 0.95, iy+0.06, 0.09, 0.09, fill=c, shape=MSO_SHAPE.OVAL)
text(s, 1.14, iy, 11.4, 0.32, it, size=10.3, color=MUTED)
iy += 0.34
yy += h + 0.16
# ================= 슬라이드 13 : 07 데이터 보안 =================
s = slide()
header(s, "SECURITY", "07. 데이터 보안 — 암호화 · PII · 응답제외 · 감사", 7)
# 응답 제외/암호화 컬럼 표
text(s, 0.55, 1.45, 7, 0.32, "민감 컬럼 처리 정책", size=13, color=BLUE, bold=True)
sec_rows = [("테이블.컬럼","정책")]
sec_rows += [
("app_user.password_hash","BCrypt 해시 · API 응답/로그 완전 제외"),
("app_user.otp_secret","TOTP 시크릿 · AES-256-GCM 암호화 저장 · 응답 제외"),
("password_reset.code_hash","BCrypt(6자리) · 만료 10분·1회성 · 응답/로그 제외"),
("sys_setting.secret_yn='Y'","시크릿 설정값 마스킹 반환"),
("render_job.error_message","사용자 요약만 · 스택트레이스 저장 금지"),
("booth.assigned_company_name","표시용(내부 식별자·민감정보 미포함)"),
]
ry = 1.85
for i,(a,b) in enumerate(sec_rows):
fill = BLUE if i==0 else (WHITE if i%2 else BLUE_LT)
tc = WHITE if i==0 else INK
rect(s, 0.55, ry, 6.5, 0.52, fill=fill, line=LINE)
text(s, 0.68, ry, 2.75, 0.52, a, size=8.4, color=tc, bold=(i==0), anchor=MSO_ANCHOR.MIDDLE)
text(s, 3.5, ry, 3.45, 0.52, b, size=8.3, color=(tc if i==0 else MUTED), bold=(i==0),
anchor=MSO_ANCHOR.MIDDLE, line_spacing=1.0)
ry += 0.52
# 우측 원칙 카드
text(s, 7.3, 1.45, 5.4, 0.32, "보안 불변 원칙", size=13, color=PURPLE, bold=True)
sec_cards = [
("PII 최소화·격리", "관람/리드 PII 집중영역 접근 제한 · BI 마트는 집계/익명키만 반입(개인식별자 0)"),
("암호화 저장", "비밀번호·OTP·재설정코드는 해시/AES-256-GCM · 평문 저장 금지"),
("API 응답 스키마 통제", "ServerOut류 민감 컬럼 응답 완전 제외 · 스택트레이스 미노출"),
("감사 추적", "audit_log(actor·action·target·ruleset_version·result·ip_hint) 전 변경 기록"),
("행사 격리 접근제어", "event_member 역할(ORGANIZER·EXHIBITOR·CONTRACTOR·HALL_MANAGER) 스코프"),
]
yy = 1.85
for k,v in sec_cards:
card(s, 7.3, yy, 5.45, 0.92)
rect(s, 7.3, yy, 0.09, 0.92, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 7.55, yy+0.13, 5.1, 0.3, k, size=11, color=INK, bold=True)
text(s, 7.55, yy+0.45, 5.1, 0.42, v, size=8.7, color=MUTED, line_spacing=1.05)
yy += 1.02
# ================= 슬라이드 14 : 08 백업·이관 전략 =================
s = slide()
header(s, "BACKUP & MIGRATION", "08. 백업 · 이관 전략 (Flyway 형상관리)", 8)
mig = [
("Flyway 버전 마이그레이션", BLUE, [
"V1 확장 → V2 신원·마스터 → V3 공간코어 → V4~V6 시드 → V7 시스템관리 → V8 공통업무 → V9 공개인증 → V10 10년 시드",
"적용 이력은 flyway_schema_history 테이블에 체크섬으로 기록·검증",
"V1~V6 불변 원칙 · V7 이후 순증(additive)·멱등만 수행(운영 무중단)",
]),
("백업 전략", GREEN, [
"일 1회 논리 백업(pg_dump) + WAL 아카이브 기반 PITR(시점복구)",
"PostGIS 지오메트리·공간 인덱스 포함 전체 스키마 백업",
"오브젝트 스토리지(렌더 이미지·도면)는 서명·만료 URL로 별도 보존",
]),
("이관·복구 절차", PURPLE, [
"복구 = 최신 백업 복원 → Flyway migrate로 최신 버전까지 재적용(정본 순서 보장)",
"환경 승격(dev→운영): 동일 마이그레이션 스크립트 적용으로 스키마 동형 보장",
"시드(V4~V6·V10)와 스키마(V1~V3·V7~V9) 분리로 데이터 오염 없이 재구축",
]),
]
yy = 1.5
for title,c,items in mig:
h = 0.5 + len(items)*0.4 + 0.1
card(s, 0.55, yy, 12.2, h)
rect(s, 0.55, yy, 0.11, h, fill=c, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.85, yy+0.16, 11.6, 0.34, title, size=13.5, color=INK, bold=True)
iy = yy+0.62
for it in items:
rect(s, 0.95, iy+0.07, 0.09, 0.09, fill=c, shape=MSO_SHAPE.OVAL)
text(s, 1.14, iy, 11.4, 0.38, it, size=10.2, color=MUTED, line_spacing=1.08)
iy += 0.4
yy += h + 0.16
text(s, 0.55, 6.75, 12.2, 0.3,
"보안 불변: 본 설계서·백업 매뉴얼에 비밀번호·API키·서버 IP·SSH 접속정보를 기재하지 않는다.",
size=9, color=MUTED, bold=True)
# ---- 저장 ----
PPTX_PATH = os.path.join(OUT_DIR, "데이터베이스설계서.pptx")
prs.save(PPTX_PATH)
N_SLIDES = len(prs.slides._sldIdLst)
# ======================================================================
# 3. XLSX — 테이블정의서
# ======================================================================
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
BLUE_HEX = "0066B3"; BLUEDK_HEX = "00447A"; BAND_HEX = "E1EFF9"; ROW_HEX = "F4F8FC"
thin = Side(style="thin", color="D0D7E2")
BORDER = Border(left=thin, right=thin, top=thin, bottom=thin)
HFONT = Font(name="맑은 고딕", size=10, bold=True, color="FFFFFF")
BFONT = Font(name="맑은 고딕", size=10)
BOLD = Font(name="맑은 고딕", size=10, bold=True, color="101828")
TITLE_FONT = Font(name="맑은 고딕", size=11, bold=True, color="FFFFFF")
HEAD_FILL = PatternFill("solid", fgColor=BLUE_HEX)
BAND_FILL = PatternFill("solid", fgColor=BLUEDK_HEX)
ALT_FILL = PatternFill("solid", fgColor=ROW_HEX)
CEN = Alignment(horizontal="center", vertical="center", wrap_text=True)
LEFT = Alignment(horizontal="left", vertical="center", wrap_text=True)
wb = openpyxl.Workbook()
# ---- 시트1: 테이블목록 ----
ws = wb.active; ws.title = "테이블목록"
ws.sheet_view.showGridLines = False
cols1 = [("No",6),("테이블명",22),("한글명",24),("주제영역",24),("Flyway",10),("컬럼수",8),("비고",40)]
for j,(h,w) in enumerate(cols1, start=1):
c = ws.cell(1, j, h); c.font=HFONT; c.fill=HEAD_FILL; c.alignment=CEN; c.border=BORDER
ws.column_dimensions[get_column_letter(j)].width = w
ordered = sorted(TABLES.items(), key=lambda kv: kv[1]["order"])
for i,(tn,t) in enumerate(ordered, start=1):
note = []
if any(c["type"].lower().startswith("geometry") for c in t["cols"]): note.append("PostGIS 공간")
if t["pk"] and len(t["pk"])>1: note.append("복합 PK("+",".join(sorted(t["pk"]))+")")
if t["uniques"]: note.append("UNIQUE("+"; ".join(t["uniques"])+")")
if any(c["name"] in ("password_hash","otp_secret","code_hash") for c in t["cols"]): note.append("민감:응답제외")
row = [i, tn, KO.get(tn,""), AREA_OF.get(tn,""), t["version"], len(t["cols"]), " · ".join(note)]
for j,val in enumerate(row, start=1):
c = ws.cell(i+1, j, val)
c.font = BOLD if j==2 else BFONT
c.alignment = CEN if j in (1,5,6) else LEFT
c.border = BORDER
if i%2==0: c.fill = ALT_FILL
ws.freeze_panes = "A2"
ws.auto_filter.ref = f"A1:G{len(ordered)+1}"
# ---- 주제영역별 시트 ----
DATA_COLS = [("No",5),("컬럼명",26),("데이터타입",20),("NULL",7),("",13),("기본값",18),("설명",56)]
def key_str(c):
parts=[]
if c["pk"]: parts.append("PK")
if c["fk"]: parts.append(f"FK→{c['fk']}")
if c["unique"]: parts.append("U")
return " ".join(parts)
for code, ko, tlist in AREAS:
title = ko.split("(")[0].strip()
sname = {"MASTER":"마스터·조직·권한","SPATIAL":"공간·설계·시각화",
"SYSTEM":"시스템관리·인증","COMMON":"공통업무"}[code]
ws = wb.create_sheet(sname)
ws.sheet_view.showGridLines = False
for j,(h,w) in enumerate(DATA_COLS, start=1):
c = ws.cell(1, j, h); c.font=HFONT; c.fill=HEAD_FILL; c.alignment=CEN; c.border=BORDER
ws.column_dimensions[get_column_letter(j)].width = w
r = 2
ncol = len(DATA_COLS)
for tn in tlist:
t = TABLES[tn]
# 밴드 행(테이블 헤더)
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=ncol)
bc = ws.cell(r, 1, f"{tn}{KO.get(tn,'')} [{t['version']}]"
+ (" · 인덱스 "+", ".join(ix["name"] for ix in t["indexes"]) if t["indexes"] else ""))
bc.font = TITLE_FONT; bc.fill = BAND_FILL; bc.alignment = LEFT
for j in range(1, ncol+1):
ws.cell(r, j).border = BORDER
ws.cell(r, j).fill = BAND_FILL
r += 1
for k,c in enumerate(t["cols"], start=1):
vals = [k, c["name"], c["type"], c["null"], key_str(c), c["default"], c["desc"]]
for j,val in enumerate(vals, start=1):
cc = ws.cell(r, j, val)
cc.font = BOLD if (j==2 and c["pk"]) else BFONT
cc.alignment = CEN if j in (1,4) else LEFT
cc.border = BORDER
if k%2==0: cc.fill = ALT_FILL
if j==5 and c["fk"]:
cc.font = Font(name="맑은 고딕", size=9, color="0E7C86", bold=True)
r += 1
r += 1 # 테이블 간 공백
ws.freeze_panes = "A2"
XLSX_PATH = os.path.join(OUT_DIR, "테이블정의서.xlsx")
wb.save(XLSX_PATH)
print("=" * 60)
print(f"[PPTX] {PPTX_PATH}")
print(f" 슬라이드 {N_SLIDES}")
print(f"[XLSX] {XLSX_PATH}")
print(f" 시트 {len(wb.sheetnames)} : {wb.sheetnames}")
print(f"[SCHEMA] 테이블 {TOTAL_TABLES} · 컬럼 {TOTAL_COLS} · 인덱스 {TOTAL_IDX} · 공간컬럼 {len(SPATIAL_COLS)} · FK {len(FK_EDGES)}")
print("=" * 60)

Binary file not shown.

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
"""공용 docx 헬퍼 — 한글 폰트/표/제목 스타일. 각 문서 생성기가 import."""
from docx.shared import Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
NAVY = RGBColor(0x1F, 0x29, 0x3A)
ACCENT = RGBColor(0x11, 0x6D, 0xC3)
GREY = RGBColor(0x55, 0x5F, 0x6B)
KFONT = "맑은 고딕"
def setup(doc):
st = doc.styles["Normal"]; st.font.name = KFONT; st.font.size = Pt(10.5)
_rf(st.element.get_or_add_rPr())
for m in ("Heading 1","Heading 2","Heading 3"):
doc.styles[m].font.name = KFONT; _rf(doc.styles[m].element.get_or_add_rPr())
s = doc.sections[0]
s.top_margin = Cm(2.2); s.bottom_margin = Cm(2.0); s.left_margin = Cm(2.3); s.right_margin = Cm(2.3)
def _rf(rpr):
rf = rpr.find(qn('w:rFonts'))
if rf is None:
rf = OxmlElement('w:rFonts'); rpr.append(rf)
for a in ('w:ascii','w:hAnsi','w:eastAsia','w:cs'):
rf.set(qn(a), KFONT)
def shade(cell, hexcolor):
tcpr = cell._tc.get_or_add_tcPr()
sh = OxmlElement('w:shd'); sh.set(qn('w:val'),'clear'); sh.set(qn('w:fill'),hexcolor)
tcpr.append(sh)
def h(doc, level, text):
p = doc.add_heading(text, level=level)
for run in p.runs:
run.font.color.rgb = NAVY if level == 1 else ACCENT
return p
def para(doc, text, size=10.5, bold=False, italic=False, color=None, align=None, space_after=6):
p = doc.add_paragraph(); r = p.add_run(text); r.bold = bold; r.italic = italic
r.font.size = Pt(size)
if color: r.font.color.rgb = color
if align: p.alignment = align
p.paragraph_format.space_after = Pt(space_after)
return p
def bullets(doc, items, style="List Bullet"):
for it in items:
p = doc.add_paragraph(it, style=style); p.paragraph_format.space_after = Pt(2)
def table(doc, headers, rows, widths=None, header_fill="1F293A", first_col_bold=False, fontsize=9.5):
t = doc.add_table(rows=1, cols=len(headers)); t.style = "Table Grid"; t.alignment = WD_TABLE_ALIGNMENT.CENTER
hd = t.rows[0].cells
for i, htext in enumerate(headers):
hd[i].text = ""; run = hd[i].paragraphs[0].add_run(htext)
run.bold = True; run.font.size = Pt(fontsize); run.font.color.rgb = RGBColor(0xFF,0xFF,0xFF)
shade(hd[i], header_fill)
for row in rows:
cells = t.add_row().cells
for i, val in enumerate(row):
cells[i].text = ""; run = cells[i].paragraphs[0].add_run(str(val)); run.font.size = Pt(fontsize)
if first_col_bold and i == 0: run.bold = True
cells[i].paragraphs[0].paragraph_format.space_after = Pt(1)
if widths:
for r_ in t.rows:
for i, w in enumerate(widths):
r_.cells[i].width = Cm(w)
doc.add_paragraph().paragraph_format.space_after = Pt(4)
return t

View File

@ -0,0 +1,785 @@
# -*- coding: utf-8 -*-
"""
킨텍스 자동전시시스템 구축 개발계획서 PPTX 생성기
- 소스: docs/PLANNING.md(v3.0), docs/IMPLEMENTATION_BACKLOG.md, docs/WORK_STATUS.md,
docs/architecture/*, CLAUDE.md 기술 스택표
- 디자인: KINTEX Blue #0066B3 / AI Purple #6D4AFF / 중립 그레이 / 맑은 고딕 / 16:9
- 도식(추진체계도·시스템 구성도·간트) python-pptx 도형으로 직접 그림(외부 이미지 금지)
- 보안 불변: 비밀번호/API키/서버IP/SSH 계정 미기재
재실행: python gen_dev_plan_pptx.py
"""
import os
from pptx import Presentation
from pptx.util import Emu, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import MSO_AUTO_SIZE
from pptx.oxml.ns import qn
# ---------------- 팔레트 ----------------
BLUE = RGBColor(0x00, 0x66, 0xB3) # KINTEX Blue (primary)
BLUE_DK = RGBColor(0x00, 0x44, 0x7A) # deep
BLUE_LT = RGBColor(0xE1, 0xEF, 0xF9) # light tint
PURPLE = RGBColor(0x6D, 0x4A, 0xFF) # AI accent
PURPLE_LT = RGBColor(0xEC, 0xE8, 0xFF)
INK = RGBColor(0x10, 0x18, 0x28) # text
MUTED = RGBColor(0x66, 0x70, 0x85) # secondary text
LINE = RGBColor(0xE4, 0xE7, 0xEC) # borders
BG = RGBColor(0xF9, 0xFA, 0xFB) # page bg
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
GREEN = RGBColor(0x12, 0x9E, 0x63) # 완료
AMBER = RGBColor(0xE0, 0x8A, 0x00) # 진행
SLATE = RGBColor(0x98, 0xA2, 0xB3) # 예정
CARD = RGBColor(0xFF, 0xFF, 0xFF)
HALL_BG = RGBColor(0xF2, 0xF4, 0xF7)
FONT = "맑은 고딕"
# 16:9
EMU_W = 12192000
EMU_H = 6858000
prs = Presentation()
prs.slide_width = EMU_W
prs.slide_height = EMU_H
BLANK = prs.slide_layouts[6]
IN = 914400
def inch(v): return int(v * IN)
# ---------------- 헬퍼 ----------------
def slide():
return prs.slides.add_slide(BLANK)
def _set_fill(shape, color):
if color is None:
shape.fill.background()
else:
shape.fill.solid()
shape.fill.fore_color.rgb = color
def _no_line(shape):
shape.line.fill.background()
def rect(s, x, y, w, h, fill=None, line=None, line_w=1.0, shape=MSO_SHAPE.RECTANGLE, shadow=False):
sp = s.shapes.add_shape(shape, inch(x), inch(y), inch(w), inch(h))
_set_fill(sp, fill)
if line is None:
_no_line(sp)
else:
sp.line.color.rgb = line
sp.line.width = Pt(line_w)
sp.shadow.inherit = False # creates an empty <a:effectLst/>
if shadow:
el = sp._element.spPr
ef = el.find(qn('a:effectLst'))
if ef is None:
ef = el.makeelement(qn('a:effectLst'), {})
el.append(ef)
sh = ef.makeelement(qn('a:outerShdw'),
{'blurRad':'90000','dist':'38100','dir':'5400000','rotWithShape':'0'})
clr = sh.makeelement(qn('a:srgbClr'), {'val':'101828'})
alp = clr.makeelement(qn('a:alpha'), {'val':'18000'})
clr.append(alp); sh.append(clr); ef.append(sh)
return sp
def _apply_runs(tf, para, segs, align, default_size, default_color, default_bold):
para.alignment = align
first = True
for seg in segs:
text = seg.get("t","")
r = para.add_run() if not first else para.runs[0] if para.runs else para.add_run()
# ensure a run exists
if first and not para.runs:
r = para.add_run()
r.text = text
r.font.name = FONT
r.font.size = Pt(seg.get("size", default_size))
r.font.bold = seg.get("bold", default_bold)
r.font.color.rgb = seg.get("color", default_color)
# set east asian font
rPr = r._r.get_or_add_rPr()
ea = rPr.makeelement(qn('a:ea'), {'typeface':FONT})
rPr.append(ea)
first = False
def text(s, x, y, w, h, content, size=14, color=INK, bold=False, align=PP_ALIGN.LEFT,
anchor=MSO_ANCHOR.TOP, line_spacing=1.0, space_after=2, wrap=True):
"""content: str 또는 list[para], para = str 또는 list[seg], seg={'t','size','color','bold'}"""
tb = s.shapes.add_textbox(inch(x), inch(y), inch(w), inch(h))
tf = tb.text_frame
tf.word_wrap = wrap
tf.vertical_anchor = anchor
tf.margin_left = 0; tf.margin_right = 0; tf.margin_top = 0; tf.margin_bottom = 0
if isinstance(content, str):
content = [content]
for i, para in enumerate(content):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = align
p.line_spacing = line_spacing
p.space_after = Pt(space_after)
p.space_before = Pt(0)
if isinstance(para, str):
segs = [{"t": para}]
elif isinstance(para, dict):
segs = [para]
else:
segs = para
for seg in segs:
r = p.add_run()
r.text = seg.get("t","")
r.font.name = FONT
r.font.size = Pt(seg.get("size", size))
r.font.bold = seg.get("bold", bold)
r.font.color.rgb = seg.get("color", color)
rPr = r._r.get_or_add_rPr()
ea = rPr.makeelement(qn('a:ea'), {'typeface':FONT})
rPr.append(ea)
return tb
def page_bg(s, color=BG):
rect(s, -0.1, -0.1, 13.53, 7.7, fill=color)
PAGE = {"n": 0}
def content_header(s, kicker, title, idx):
"""본문 슬라이드 공통 헤더 + 푸터"""
page_bg(s, BG)
# top accent bar
rect(s, 0, 0, 13.333, 0.12, fill=BLUE)
# section number chip
rect(s, 0.55, 0.5, 0.62, 0.62, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, 0.5, 0.62, 0.62, f"{idx:02d}", size=20, color=WHITE, bold=True,
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
text(s, 1.32, 0.5, 10.5, 0.28, kicker, size=11, color=PURPLE, bold=True)
text(s, 1.32, 0.74, 11.4, 0.44, title, size=22, color=INK, bold=True)
rect(s, 0.55, 1.28, 12.23, 0.02, fill=LINE)
# footer
text(s, 0.55, 7.06, 8, 0.3, "킨텍스 자동전시시스템 구축 · 개발계획서", size=8.5, color=MUTED)
text(s, 11.3, 7.06, 1.48, 0.3, "2026-07-11", size=8.5, color=MUTED, align=PP_ALIGN.RIGHT)
def chip(s, x, y, w, h, label, fill, txtcolor=WHITE, size=9.5, bold=True):
rect(s, x, y, w, h, fill=fill, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, y, w, h, label, size=size, color=txtcolor, bold=bold,
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
def card(s, x, y, w, h, fill=CARD, line=LINE):
return rect(s, x, y, w, h, fill=fill, line=line, line_w=1.0,
shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=True)
# ================================================================
# 1. 표지
# ================================================================
s = slide()
rect(s, -0.1, -0.1, 13.53, 7.7, fill=BLUE_DK)
# 대각 그라디언트 느낌: 겹치는 반투명 도형
rect(s, 6.4, -1.5, 9, 11, fill=BLUE, shape=MSO_SHAPE.PARALLELOGRAM)
rect(s, 9.2, -2.0, 7, 12, fill=PURPLE, shape=MSO_SHAPE.PARALLELOGRAM)
# 좌상 브랜드
rect(s, 0.85, 0.8, 0.42, 0.42, fill=WHITE, shape=MSO_SHAPE.OVAL)
rect(s, 0.97, 0.92, 0.18, 0.18, fill=PURPLE, shape=MSO_SHAPE.OVAL)
text(s, 1.42, 0.78, 6, 0.46, "KINTEX · WISE AI", size=15, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
# 문서 종류 배지
chip(s, 0.85, 2.35, 1.75, 0.42, "개발계획서", PURPLE, WHITE, size=12)
chip(s, 2.72, 2.35, 2.1, 0.42, "Development Plan", RGBColor(0x2B,0x4C,0x7E), WHITE, size=10.5)
# 타이틀
text(s, 0.85, 2.95, 11, 1.9, [
[{"t":"킨텍스 자동전시시스템", "size":46, "color":WHITE, "bold":True}],
[{"t":"구축 개발계획서", "size":46, "color":WHITE, "bold":True}],
], line_spacing=1.02)
text(s, 0.88, 4.78, 11, 0.5,
"Exhibition Automation Platform — 다중 전시관 멀티테넌트 SaaS",
size=16, color=RGBColor(0xC7,0xDD,0xF2), bold=False)
# 폐루프 한 줄
text(s, 0.88, 5.35, 11.4, 0.4,
"판매 → AI 설계·시각화 → 공사 옥션 → 시공 → 운영 → 경영분석 폐루프 자동화",
size=12.5, color=RGBColor(0xB9,0xC7,0xEA))
# 하단 메타 바
rect(s, 0.85, 6.15, 11.63, 0.02, fill=RGBColor(0x3A,0x5A,0x8C))
text(s, 0.85, 6.35, 8, 0.4, [
[{"t":"사업명 ","size":11,"color":RGBColor(0x8FA,0x00,0x00) if False else RGBColor(0x90,0xA6,0xC9)},
{"t":"킨텍스 자동전시시스템 구축","size":11.5,"color":WHITE,"bold":True}],
])
text(s, 0.85, 6.72, 9, 0.4, [
[{"t":"기술스택 ","size":11,"color":RGBColor(0x90,0xA6,0xC9)},
{"t":"React(Vite)+TS · Spring Boot 3.x(Java17)+MyBatis · PostgreSQL(PostGIS) · Redis · 나노바나나 워커 · Claude AI","size":10.5,"color":WHITE}],
])
text(s, 9.7, 6.35, 2.78, 0.4, "작성일 2026-07-11", size=11, color=WHITE, bold=True, align=PP_ALIGN.RIGHT)
text(s, 9.7, 6.72, 2.78, 0.4, "planner · designer · dev team", size=9.5, color=RGBColor(0x90,0xA6,0xC9), align=PP_ALIGN.RIGHT)
# ================================================================
# 2. 목차
# ================================================================
s = slide()
content_header(s, "CONTENTS", "목차", 0)
# 위 헤더의 00 chip을 목차용으로 덮기
rect(s, 0.55, 0.5, 0.62, 0.62, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, 0.5, 0.62, 0.62, "C", size=22, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
toc = [
("01", "사업 개요", "배경·목적·범위 / 전시 생애주기 폐루프"),
("02", "시스템 구성", "아키텍처 개요 / 역할별 포털 / 나노바나나 파이프라인"),
("03", "개발 범위", "모듈 M1~M18 + 공통·시스템관리 레이어(§5B)"),
("04", "추진 체계", "거버넌스·아키텍트·개발·QA·DevOps 조직"),
("05", "추진 일정", "Phase A~E 마스터 일정(완료/진행/예정)"),
("06", "개발 방법론·품질관리", "에이전트 파이프라인 · 점진 QA · CI/CD"),
("07", "보안 · 비기능요건(NFR)", "외부API 통제 · AI 워터마크 · 접근성·다국어"),
("08", "리스크 및 대응", "기술·데이터·연동·보안 리스크"),
("09", "기대 효과", "정량·정성 효과 / 확장 로드맵"),
]
col_w = 5.85
for i, (no, t, d) in enumerate(toc):
col = i // 5
row = i % 5
x = 0.7 + col * (col_w + 0.35)
y = 1.62 + row * 1.02
rect(s, x, y, col_w, 0.88, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
rect(s, x, y, 0.09, 0.88, fill=BLUE if col==0 else PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.28, y, 0.9, 0.88, no, size=24, color=(BLUE if col==0 else PURPLE), bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, x+1.15, y+0.15, col_w-1.3, 0.35, t, size=14.5, color=INK, bold=True)
text(s, x+1.15, y+0.5, col_w-1.3, 0.32, d, size=9.5, color=MUTED)
# ================================================================
# 섹션 간지 헬퍼
# ================================================================
def divider(num, kr, en, points):
s = slide()
rect(s, -0.1, -0.1, 13.53, 7.7, fill=BLUE_DK)
rect(s, 8.6, -1.5, 8, 11, fill=BLUE, shape=MSO_SHAPE.PARALLELOGRAM)
rect(s, 10.9, -2, 6, 12, fill=PURPLE, shape=MSO_SHAPE.PARALLELOGRAM)
text(s, 1.0, 2.15, 5, 2.2, num, size=150, color=RGBColor(0x2C,0x4E,0x82), bold=True)
rect(s, 1.15, 3.9, 0.9, 0.09, fill=PURPLE)
text(s, 1.15, 4.05, 8, 0.8, kr, size=34, color=WHITE, bold=True)
text(s, 1.18, 4.85, 8, 0.4, en, size=13, color=RGBColor(0x9C,0xB4,0xD8), bold=True)
# 우측 포인트
for i, p in enumerate(points):
yy = 2.35 + i*0.62
rect(s, 8.05, yy+0.06, 0.16, 0.16, fill=PURPLE, shape=MSO_SHAPE.OVAL)
text(s, 8.35, yy, 4.4, 0.5, p, size=12.5, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
return s
# ================================================================
# 3. 사업 개요 (간지 + 본문)
# ================================================================
divider("01", "사업 개요", "PROJECT OVERVIEW",
["배경 · 목적 · 범위", "전시 생애주기 폐루프", "다중 전시관 SaaS 비전"])
s = slide()
content_header(s, "01 · 사업 개요", "배경 · 목적 · 추진 범위", 1)
# 좌: 배경/목적
card(s, 0.55, 1.5, 6.05, 2.55)
text(s, 0.85, 1.7, 5.5, 0.35, "추진 배경 (As-Is)", size=13, color=BLUE, bold=True)
for i, t in enumerate([
"전시면적 108,011㎡ 국내 최대 전시장이나 준비 실무는 HWP 서식·이메일 제출에 정체",
"CAD 수작업 배치도 → 홀매니저 육안 검수, 수기 위치표시도 기반 유틸리티 신청",
"참가업체 유틸리티 신청이 행사별 사무국 시스템으로 파편화(공통 플랫폼 부재)",
"시공 결과를 개장일에 처음 확인 — 사전 시각화 수단 없음",
]):
y = 2.1 + i*0.46
rect(s, 0.9, y+0.07, 0.1, 0.1, fill=MUTED, shape=MSO_SHAPE.OVAL)
text(s, 1.12, y, 5.25, 0.44, t, size=10.5, color=INK, line_spacing=1.0)
card(s, 6.75, 1.5, 6.03, 2.55)
text(s, 7.05, 1.7, 5.5, 0.35, "추진 목적 (To-Be)", size=13, color=PURPLE, bold=True)
for i, t in enumerate([
"전시장 운영 전 과정을 AI로 자동 설계·검증하는 베뉴 운영 플랫폼 구축",
"핵심 차별화: 나노바나나(Gemini)로 시공 전 '공사 후 결과 사진' 선(先)시각화",
"설계 → 시각화 → 발주(옥션) → 시공 → 운영 → 분석의 폐루프 완성",
"KINTEX 기준 테넌트 + 코엑스 등 다중 전시관 멀티테넌트 SaaS 확장",
]):
y = 2.1 + i*0.46
rect(s, 7.1, y+0.07, 0.1, 0.1, fill=PURPLE, shape=MSO_SHAPE.OVAL)
text(s, 7.32, y, 5.25, 0.44, t, size=10.5, color=INK, line_spacing=1.0)
# 하단: 비전 배너 + 폐루프
rect(s, 0.55, 4.28, 12.23, 0.62, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.85, 4.28, 11.6, 0.62,
"비전 “신청서를 내는 순간, 시공 후 사진을 먼저 본다”",
size=17, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, 0.55, 5.12, 6, 0.3, "전시 생애주기 폐루프", size=12, color=INK, bold=True)
loop = [("판매·기획", BLUE, "M1·M16"), ("AI 설계·시각화", PURPLE, "M2~M5"),
("공사 옥션", BLUE, "M15"), ("시공", BLUE, "M7·M8"),
("운영", BLUE, "M10·M14"), ("경영분석", PURPLE, "M16")]
bw = 1.83; gap = 0.19; x0 = 0.55; yb = 5.5
for i, (lb, cl, mod) in enumerate(loop):
x = x0 + i*(bw+gap)
rect(s, x, yb, bw, 0.78, fill=(PURPLE_LT if cl==PURPLE else BLUE_LT), line=cl, line_w=1.2, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, yb+0.1, bw, 0.34, lb, size=11.5, color=(PURPLE if cl==PURPLE else BLUE_DK), bold=True, align=PP_ALIGN.CENTER)
text(s, x, yb+0.44, bw, 0.28, mod, size=9, color=MUTED, align=PP_ALIGN.CENTER)
if i < len(loop)-1:
rect(s, x+bw+0.01, yb+0.28, gap+0.02, 0.22, fill=None)
text(s, x+bw-0.03, yb+0.16, 0.28, 0.5, "", size=15, color=SLATE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ================================================================
# 4. 시스템 구성 (간지 + 아키텍처 도식)
# ================================================================
divider("02", "시스템 구성", "SYSTEM ARCHITECTURE",
["3-Tier + 나노바나나 사이드카", "역할별 6개 포털 분리", "PostGIS 공간 데이터 모델"])
s = slide()
content_header(s, "02 · 시스템 구성", "전체 아키텍처 개요도", 2)
def band(y, h, fill, line, label, labelcolor):
rect(s, 0.55, y, 12.23, h, fill=fill, line=line, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
rect(s, 0.55, y, 1.55, h, fill=line, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, y, 1.55, h, label, size=10.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
def node(x, y, w, h, title, sub, fill, tcol, line=None):
rect(s, x, y, w, h, fill=fill, line=(line or LINE), line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, y+0.08, w, 0.3, title, size=10, color=tcol, bold=True, align=PP_ALIGN.CENTER)
if sub:
text(s, x, y+0.36, w, 0.24, sub, size=7.8, color=MUTED, align=PP_ALIGN.CENTER)
# Presentation tier — 포털
band(1.5, 0.86, BLUE_LT, BLUE, "프론트\n(React)", WHITE)
ports = ["주최자 콘솔","참가업체 포털","업체 포털","운영 대시보드","관리자 백오피스","공개사이트·앱"]
pw = 1.62
for i,p in enumerate(ports):
node(2.25+i*1.72, 1.66, pw, 0.54, p, "", WHITE, INK, BLUE)
# API tier
band(2.55, 0.9, WHITE, BLUE_DK, "백엔드\n(Spring)", WHITE)
node(2.25, 2.72, 2.7, 0.56, "REST + WebSocket(STOMP)", "JWT·RBAC·2FA(OTP)", WHITE, INK, BLUE)
node(5.1, 2.72, 2.4, 0.56, "룰 엔진 / 배치·배선 엔진", "요율·규정·PostGIS 연산", WHITE, INK, BLUE)
node(7.65, 2.72, 2.2, 0.56, "AiTextRouter", "Claude 기본·설정형 전환", WHITE, PURPLE, PURPLE)
node(10.0, 2.72, 2.65, 0.56, "RenderJob 큐 발행", "Redis 비동기", WHITE, INK, BLUE)
# AI / 워커 tier
band(3.62, 0.9, PURPLE_LT, PURPLE, "AI·시각화", WHITE)
node(2.25, 3.79, 4.15, 0.56, "나노바나나 Python 워커 (사이드카)", "google-genai · gemini-3.1-flash-image-preview", PURPLE_LT, PURPLE, PURPLE)
node(6.55, 3.79, 3.0, 0.56, "Claude API (AI 지능)", "설계·검수·예측 · Ollama 폴백", PURPLE_LT, PURPLE, PURPLE)
node(9.72, 3.79, 2.93, 0.56, "WebSocket 완료 푸시", "렌더 결과 실시간 통지", WHITE, INK, PURPLE)
# Data tier
band(4.69, 0.9, HALL_BG, BLUE_DK, "데이터", WHITE)
node(2.25, 4.86, 3.3, 0.56, "PostgreSQL + PostGIS", "부스 폴리곤·트렌치·배선 LineString", WHITE, INK, BLUE_DK)
node(5.7, 4.86, 2.4, 0.56, "Redis", "작업 큐·세션·캐시", WHITE, INK, BLUE_DK)
node(8.25, 4.86, 2.05, 0.56, "오브젝트 스토리지", "렌더 이미지·문서", WHITE, INK, BLUE_DK)
node(10.45, 4.86, 2.2, 0.56, "감사로그·마스터", "룰셋·요율 버전", WHITE, INK, BLUE_DK)
# 외부 연동
text(s, 0.55, 5.86, 12, 0.3, "외부·기존 시스템 연동", size=10.5, color=INK, bold=True)
ext = ["kxwp 작업신고","등록업체 DB 739","행사일정 시스템","PG 결제","Gemini","사이니지/wayfinding"]
for i,e in enumerate(ext):
node(0.55+i*2.06, 6.16, 1.96, 0.5, e, "", WHITE, MUTED, LINE)
# ================================================================
# 5. 개발 범위 (모듈 표)
# ================================================================
divider("03", "개발 범위", "DEVELOPMENT SCOPE",
["코어 M2~M5 (P0)", "도메인 M1·M6~M18", "공통·시스템관리 §5B"])
s = slide()
content_header(s, "03 · 개발 범위", "모듈 맵 M1 ~ M18", 3)
def module_card(x, y, w, h, code, name, pr, cl):
rect(s, x, y, w, h, fill=CARD, line=LINE, line_w=1.0, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
rect(s, x, y, w, 0.07, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.14, y+0.14, w-0.28, 0.26, [[{"t":code+" ","size":11,"color":cl,"bold":True},
{"t":pr,"size":8,"color":WHITE,"bold":True}]], )
# priority chip
rect(s, x+w-0.72, y+0.13, 0.58, 0.24, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+w-0.72, y+0.13, 0.58, 0.24, pr, size=8, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
text(s, x+0.14, y+0.4, w-0.28, 0.5, name, size=9.2, color=INK, line_spacing=0.95)
# 계열별 컬럼
groups = [
("설계·시각화 코어", PURPLE, [
("M2","플로어플랜 스튜디오","P0"),
("M3","부스 설계 스튜디오","P0"),
("M4","유틸리티 설계(전기·조명·네트워크)","P0"),
("M5","나노바나나 시각화","P0"),
]),
("판매·발주·정산", BLUE, [
("M1","홀 배정·자동 견적","P1"),
("M6","서류·마일스톤 워크플로","P1"),
("M7","등록업체 매칭·견적","P1"),
("M15","공사/장치 옥션(역경매)","P1"),
("M8·M9","반입/반출 물류 · 정산·결제","P1·P2"),
]),
("관람·참가·마케팅", BLUE, [
("M10","관람객 등록·배지·리드캡처","P1"),
("M12","마케팅·공개 홍보 사이트","P1"),
("M11·M13","비즈매칭 · wayfinding","P2"),
("M14","현장운영(혼잡·안전·에너지)","P2"),
]),
("경영·콘텐츠·관리", PURPLE, [
("M16","경영분석 BI(P&L·가동률·예측)","P1"),
("M17","CMS·마이크로사이트·다국어","P1"),
("M18","관리자 백오피스·마스터데이터","P1"),
]),
]
cw = 2.94; gap = 0.16; x0 = 0.55
for gi,(gname, gcolor, mods) in enumerate(groups):
x = x0 + gi*(cw+gap)
rect(s, x, 1.5, cw, 0.44, fill=gcolor, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, 1.5, cw, 0.44, gname, size=11, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
for mi,(code,name,pr) in enumerate(mods):
y = 2.06 + mi*0.9
module_card(x, y, cw, 0.82, code, name, pr, gcolor)
# 범례
text(s, 0.55, 6.62, 12, 0.3, [[
{"t":"P0 ","size":9.5,"color":PURPLE,"bold":True},{"t":"핵심 차별화·첫 상용 필수 ","size":9.5,"color":MUTED},
{"t":"P1 ","size":9.5,"color":BLUE,"bold":True},{"t":"운영 효율 핵심 ","size":9.5,"color":MUTED},
{"t":"P2 ","size":9.5,"color":SLATE,"bold":True},{"t":"확장 │ 총 18개 도메인 모듈 + 공통·시스템관리 레이어(§5B)","size":9.5,"color":MUTED},
]])
# 개발 범위 2 — 공통 레이어 §5B
s = slide()
content_header(s, "03 · 개발 범위", "공통 · 시스템관리 레이어 (§5B · UIWS/WISE 표준 이식)", 3)
text(s, 0.55, 1.45, 12.2, 0.4,
"GUARDiA 표준 프레임워크(UIWS)와 동일 스택 — 시스템관리·공통 업무기능·인증을 재설계 없이 이식하여 전 도메인 모듈의 선행 기반으로 정착",
size=11, color=MUTED)
# 3개 카드 영역
def scard(x, y, w, h, title, tcolor, items):
card(s, x, y, w, h)
rect(s, x, y, w, 0.5, fill=tcolor, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, y, w, 0.5, title, size=12.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
for i,it in enumerate(items):
yy = y+0.66+i*0.4
rect(s, x+0.2, yy+0.07, 0.09, 0.09, fill=tcolor, shape=MSO_SHAPE.OVAL)
text(s, x+0.4, yy, w-0.55, 0.38, it, size=9.6, color=INK, line_spacing=0.95)
scard(0.55, 2.0, 3.95, 4.35, "시스템관리 (M18 통합)", BLUE, [
"사용자 관리 (6역할 + 등록업체·관람객 계정)",
"역할/권한 RBAC (플랫폼·행사 이중 평가)",
"공통코드 (홀·부스유형·공종14분류·요금)",
"메뉴 관리 (역할별 포털 IA 구동)",
"감사로그 (승인·낙찰·룰셋개정·리드접근)",
"시스템설정 (요율/규정 룰셋 버전)",
"킨텍스 마스터데이터 (홀·요율·규정·업체)",
])
scard(4.65, 2.0, 3.95, 4.35, "공통 업무 기능", PURPLE, [
"worklog 업무일지 · schedule 일정",
"message 쪽지 · notice 공지",
"opinion 의견 · search 통합검색",
"meeting 회의록(STT→Jasper PDF)",
"report/stats 업무보고·통계(PDF)",
"notification 알림센터(WebSocket)",
"preference 개인화(즐겨찾기·최근방문)",
])
scard(8.75, 2.0, 4.03, 4.35, "인증 (JWT + 2FA/OTP)", BLUE_DK, [
"JWT + RBAC 역할 게이트",
"2차 인증 OTP (TOTP RFC6238)",
"최초 QR 등록 · 재설정 · 관리자 초기화",
"로그인 실패 잠금 + 관리자 해제",
"admin 비번 env 주입 (AES-256-GCM)",
"admin123 하드코딩 시드 금지",
"TB_USER·OTP·TB_AUDIT_LOG 멱등 이식",
])
# ================================================================
# 6. 추진 체계 (조직도)
# ================================================================
divider("04", "추진 체계", "PROJECT ORGANIZATION",
["거버넌스 (PM·개발PM·PMO)", "아키텍트 5인 (AA/SA/TA/DA/NA)", "구현·품질·배포 팀"])
s = slide()
content_header(s, "04 · 추진 체계", "프로젝트 조직 및 역할", 4)
def org(x, y, w, h, title, sub, fill, tcol, line=None, big=False):
rect(s, x, y, w, h, fill=fill, line=(line or fill), line_w=1.2, shape=MSO_SHAPE.ROUNDED_RECTANGLE, shadow=big)
text(s, x, y+(0.1 if sub else 0), w, (0.32 if sub else h), title, size=(12 if big else 10.5),
color=tcol, bold=True, align=PP_ALIGN.CENTER, anchor=(MSO_ANCHOR.TOP if sub else MSO_ANCHOR.MIDDLE))
if sub:
subcol = MUTED if fill == WHITE else WHITE
text(s, x, y+0.36, w, h-0.4, sub, size=8, color=subcol, align=PP_ALIGN.CENTER)
# 최상위 거버넌스
org(4.9, 1.5, 3.55, 0.72, "프로젝트 총괄 (PM)", "범위·일정·리스크·게이트(G1·G2)", BLUE, WHITE, big=True)
# 연결선
rect(s, 6.66, 2.22, 0.02, 0.3, fill=SLATE)
# 2단: 개발PM / PMO
org(2.95, 2.55, 3.35, 0.66, "개발 PM", "WBS·스프린트·번다운·품질게이트", PURPLE, WHITE)
org(7.05, 2.55, 3.35, 0.66, "PMO", "관리표준·형상관리·QA 거버넌스·변경관리", PURPLE, WHITE)
rect(s, 6.66, 2.52, 0.02, 0.16, fill=SLATE)
# 3단: 아키텍트 그룹 + 구현 그룹
rect(s, 0.55, 3.5, 12.23, 0.4, fill=BLUE_LT, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.7, 3.5, 6, 0.4, "아키텍트 그룹", size=11, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
arch = [("AA","앱 아키텍처"),("SA","시스템·NFR"),("TA","기술표준"),("DA","데이터·ERD"),("NA","네트워크·보안")]
for i,(a,d) in enumerate(arch):
org(0.55+i*2.46, 4.0, 2.32, 0.66, a, d, WHITE, BLUE, BLUE)
rect(s, 0.55, 4.86, 12.23, 0.4, fill=PURPLE_LT, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.7, 4.86, 6, 0.4, "구현 그룹", size=11, color=PURPLE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
impl = [("공통/시스템","common-dev"),("백엔드","backend-dev"),("프론트","frontend-dev"),
("DB","db-engineer"),("도메인","bidding·visitor\ncms·bi·admin"),("AI·시각화","ai-dev·visualizer")]
for i,(a,d) in enumerate(impl):
org(0.55+i*2.05, 5.36, 1.93, 0.72, a, d, WHITE, PURPLE, PURPLE)
# 하단: 품질·배포
org(3.4, 6.2, 3.0, 0.66, "품질 (kintex-qa)", "점진 QA·경계면·보안 불변", GREEN, WHITE)
org(6.95, 6.2, 3.0, 0.66, "배포 (devops-dev)", "CI/CD·systemd·env", RGBColor(0x2B,0x4C,0x7E), WHITE)
# ================================================================
# 7. 추진 일정 (간트)
# ================================================================
divider("05", "추진 일정", "PROJECT SCHEDULE",
["Phase A~E 마스터 일정", "완료 / 진행 / 예정 구분", "공통 레이어 선행 원칙"])
s = slide()
content_header(s, "05 · 추진 일정", "Phase A ~ E 마스터 일정 (WORK_STATUS 반영)", 5)
# 상태 범례
for i,(lb,cl) in enumerate([("완료",GREEN),("진행",AMBER),("예정",SLATE)]):
rect(s, 9.8+i*1.0, 1.42, 0.24, 0.24, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 10.08+i*1.0, 1.4, 0.9, 0.28, lb, size=9.5, color=INK, anchor=MSO_ANCHOR.MIDDLE)
# 간트 좌표계
gx = 3.55 # 바 시작 x
gw = 9.15 # 바 영역 폭
units = 10 # 타임 단위
uw = gw/units
# 타임 눈금 라벨
for i in range(units+1):
text(s, gx+i*uw-0.4, 1.72, 0.8, 0.24, f"T{i}", size=7.5, color=SLATE, align=PP_ALIGN.CENTER)
rect(s, gx+i*uw, 1.98, 0.008, 4.2, fill=RGBColor(0xF0,0xF1,0xF4))
rows = [
("Phase A", "아키텍처·거버넌스 (AA/SA/TA/DA/NA)", 0, 1.5, GREEN, "완료"),
("Phase B", "공통·시스템관리 레이어 (2FA/OTP·공통기능)", 1.2, 2.0, GREEN, "완료"),
("Phase C", "P0 부스 코어 M2~M5 (배치·설계·배선·시각화)", 2.6, 2.6, AMBER, "진행"),
("Phase C", " ├ 스캐폴드·인증·매퍼 배선·룰엔진·나노바나나", 2.6, 1.6, GREEN, "완료"),
("Phase C", " └ 3안 생성·병합·실데이터 전환·모바일", 4.2, 1.4, AMBER, "진행"),
("Phase D", "P1 도메인 — 옥션 M15·관람객 M10", 4.8, 1.9, SLATE, "예정"),
("Phase D", "P1 도메인 — CMS/공개사이트 M12·M17·BI M16·관리자 M18", 5.4, 2.2, SLATE, "예정"),
("Phase D", "P1 배정·서류·매칭·정산 M1·M6·M7·M9", 5.8, 1.8, SLATE, "예정"),
("Phase E", "P2 확장 (매칭 M11·wayfinding M13·현장운영 M14)", 7.2, 1.6, SLATE, "예정"),
("Phase E", "빌드·배포 (역할별 번들·CI/CD·멀티테넌시)", 7.6, 2.2, AMBER, "진행"),
]
ry = 2.12
rh = 0.42
for i,(ph, name, start, dur, cl, st) in enumerate(rows):
y = ry + i*rh
if i % 2 == 0:
rect(s, 0.55, y-0.02, 12.23, rh-0.02, fill=RGBColor(0xFC,0xFC,0xFD))
text(s, 0.6, y, 0.85, rh-0.06, ph, size=8.5, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, 1.45, y, 2.05, rh-0.06, name, size=8.2, color=INK, anchor=MSO_ANCHOR.MIDDLE)
bx = gx + start*uw
bw = max(dur*uw, 0.3)
rect(s, bx, y+0.06, bw, rh-0.18, fill=cl, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, bx, y+0.04, bw, rh-0.14, st, size=7.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# 현재 시점 마커
nowx = gx + 4.9*uw
rect(s, nowx, 1.98, 0.02, 4.2, fill=PURPLE)
text(s, nowx-0.5, 6.2, 1.0, 0.24, "현재", size=8, color=PURPLE, bold=True, align=PP_ALIGN.CENTER)
text(s, 0.55, 6.55, 12.2, 0.3,
"원칙 Phase B(공통 레이어)는 전 도메인 선행 기반 — 인증·시스템관리·공통기능 정착 후 코어/도메인 착수. 각 모듈 완성 직후 점진 QA.",
size=9, color=MUTED)
# ================================================================
# 8. 개발 방법론·품질
# ================================================================
divider("06", "개발 방법론 · 품질관리", "METHODOLOGY & QUALITY",
["에이전트 파이프라인", "점진 QA · 보안 불변 게이트", "CI/CD 자동배포"])
s = slide()
content_header(s, "06 · 개발 방법론 · 품질관리", "에이전트 파이프라인 · 점진 QA · CI/CD", 6)
# 파이프라인
text(s, 0.55, 1.5, 8, 0.3, "① 에이전트 기반 개발 파이프라인", size=12, color=BLUE, bold=True)
pipe = [("기획","planner",PURPLE),("설계","AA/SA/TA/DA/NA",BLUE),("디자인","designer",PURPLE),
("구현","backend·frontend·db·domain",BLUE),("검증","kintex-qa",GREEN),("배포","devops-dev",BLUE_DK)]
pw2 = 1.9; g2 = 0.14
for i,(t,d,cl) in enumerate(pipe):
x = 0.55+i*(pw2+g2)
rect(s, x, 1.9, pw2, 0.74, fill=WHITE, line=cl, line_w=1.3, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, 1.98, pw2, 0.3, t, size=11, color=cl, bold=True, align=PP_ALIGN.CENTER)
text(s, x, 2.3, pw2, 0.3, d, size=7.6, color=MUTED, align=PP_ALIGN.CENTER)
if i < len(pipe)-1:
text(s, x+pw2-0.05, 1.9, 0.24, 0.74, "", size=16, color=SLATE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ② 점진 QA
card(s, 0.55, 2.95, 6.05, 2.05)
text(s, 0.8, 3.12, 5.5, 0.3, "② 점진 QA — 모듈 완성 직후 검증", size=12, color=PURPLE, bold=True)
for i,t in enumerate([
"백엔드 응답 shape ↔ 프론트 훅/컴포넌트 호출 경계면 교차 비교",
"공간 로직(배치·배선·규정검증) 정합 · PostGIS 연산 검증",
"보안 불변: 자격증명·PII·스택트레이스 미노출 강제",
"AI 생성 이미지 워터마크·고지 강제 · 등록업체 응찰 게이트",
"빌드 회귀(compileJava·tsc) · 통과까지 반려",
]):
y=3.5+i*0.29
rect(s, 0.85, y+0.06, 0.09, 0.09, fill=GREEN, shape=MSO_SHAPE.OVAL)
text(s, 1.05, y, 5.4, 0.28, t, size=9.3, color=INK)
# ③ CI/CD
card(s, 6.75, 2.95, 6.03, 2.05)
text(s, 7.0, 3.12, 5.5, 0.3, "③ CI/CD 자동배포 파이프라인", size=12, color=BLUE, bold=True)
cicd = ["push","Gitea webhook","deploy_kintex.sh","백엔드 bootJar + 프론트 Vite 빌드","배포 → health check","실패 시 롤백"]
for i,c in enumerate(cicd):
y=3.5+i*0.245
rect(s, 7.05, y, 0.28, 0.2, fill=BLUE, shape=MSO_SHAPE.OVAL)
text(s, 7.06, y-0.01, 0.28, 0.22, str(i+1), size=8, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
text(s, 7.45, y-0.02, 5.2, 0.24, c, size=9.3, color=INK, anchor=MSO_ANCHOR.MIDDLE)
# 하단 지표 스트립
metrics = [("18","도메인 모듈"),("6","역할별 포털"),("39+","Flyway 테이블(현재)"),("A~E","5개 Phase"),("3-Tier","+ AI 사이드카")]
mw=2.34
for i,(v,l) in enumerate(metrics):
x=0.55+i*(mw+0.09)
rect(s, x, 5.2, mw, 0.95, fill=BLUE if i%2==0 else PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x, 5.34, mw, 0.5, v, size=26, color=WHITE, bold=True, align=PP_ALIGN.CENTER)
text(s, x, 5.86, mw, 0.26, l, size=9.5, color=WHITE, align=PP_ALIGN.CENTER)
text(s, 0.55, 6.5, 12.2, 0.3,
"AI 지능 = Claude 기본 + 설정형 전환(AiTextRouter/AiConfig) · Ollama 폴백 │ 이미지 = 나노바나나(Gemini) 라이브",
size=9, color=MUTED)
# ================================================================
# 9. 보안·NFR
# ================================================================
divider("07", "보안 · 비기능요건", "SECURITY & NFR",
["외부 API 통제 (예외 명시)", "자격증명 보호 · AI 워터마크", "접근성·다국어·테마"])
s = slide()
content_header(s, "07 · 보안 · 비기능요건(NFR)", "보안 불변 원칙 및 품질 속성", 7)
# 보안 불변 (좌)
card(s, 0.55, 1.5, 6.05, 3.05)
rect(s, 0.55, 1.5, 6.05, 0.5, fill=BLUE_DK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, 1.5, 6.05, 0.5, "보안 불변 (Security Invariants)", size=13, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
sec = [
("외부 API 통제","온프레미스 원칙 · 예외 = Claude(승인)·Gemini(G1). 키는 서버 env 로드, 코드·커밋·로그 미기재"),
("자격증명 보호","IP·SSH계정·비밀번호를 API 응답·에러메시지에 미노출"),
("암호화 저장","민감 자격증명 AES-256-GCM · admin 비번 env 주입(admin123 금지)"),
("AI 이미지 고지","나노바나나 생성 전 이미지 워터마크·실물 상이 고지 강제"),
("에러 응답","스택트레이스 미노출 — 요약 메시지만 전달"),
]
for i,(h,d) in enumerate(sec):
y=2.15+i*0.47
rect(s, 0.8, y+0.04, 0.11, 0.11, fill=BLUE, shape=MSO_SHAPE.OVAL)
text(s, 1.02, y, 5.45, 0.44, [[{"t":h+"","size":9.6,"color":INK,"bold":True},
{"t":d,"size":9,"color":MUTED}]], line_spacing=0.95)
# NFR (우)
card(s, 6.75, 1.5, 6.03, 3.05)
rect(s, 6.75, 1.5, 6.03, 0.5, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 6.75, 1.5, 6.03, 0.5, "비기능요건 (NFR)", size=13, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
nfr = [
("웹 접근성","WCAG 준수 · 키보드·스크린리더 대응"),
("다국어(i18n)","한 · 영 · 중 · 일 (공개사이트·CMS 우선)"),
("라이트/다크 테마","전 화면 테마 토큰 · 반응형 풀스크린"),
("반응형 채널 분리","데스크톱=설계/에디터 · 모바일=현장/조회/승인"),
("성능·확장","Redis 비동기 큐 · 렌더 워커 사이드카 분리"),
("멀티테넌시 격리","tenant_id 전파 · 전시관 단위 데이터·권한 격리"),
]
for i,(h,d) in enumerate(nfr):
y=2.15+i*0.4
rect(s, 7.0, y+0.04, 0.11, 0.11, fill=PURPLE, shape=MSO_SHAPE.OVAL)
text(s, 7.22, y, 5.4, 0.38, [[{"t":h+"","size":9.6,"color":INK,"bold":True},
{"t":d,"size":9,"color":MUTED}]], line_spacing=0.95)
# 하단: 3중 권한 구조
rect(s, 0.55, 4.78, 12.23, 0.42, fill=INK, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.55, 4.78, 12.23, 0.42, "3중 권한 구조 테넌트(전시관) 격리 ⊃ 행사(Event) 워크스페이스 ⊃ 행사 RBAC",
size=11.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# 인증 요약 3칩
for i,(t,d,cl) in enumerate([("JWT + RBAC","6역할 게이트",BLUE),("2FA / OTP","TOTP RFC6238",PURPLE),
("감사로그","전수 기록·개인정보 접근",BLUE_DK)]):
x=0.55+i*4.11
rect(s, x, 5.4, 3.97, 0.72, fill=WHITE, line=cl, line_w=1.3, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.2, 5.5, 3.7, 0.3, t, size=12, color=cl, bold=True)
text(s, x+0.2, 5.82, 3.7, 0.26, d, size=9.5, color=MUTED)
# ================================================================
# 10. 리스크 및 대응
# ================================================================
divider("08", "리스크 및 대응", "RISK MANAGEMENT",
["기술·데이터·연동 리스크", "영향도·발생가능성 평가", "완화 방안"])
s = slide()
content_header(s, "08 · 리스크 및 대응", "주요 리스크 및 대응 방안", 8)
# 표 헤더
hx=[0.55, 1.3, 5.9, 8.0] # 구분/리스크/영향/대응 시작
cols = [(0.55,0.75,"구분"),(1.3,4.6,"리스크"),(5.9,2.1,"영향"),(8.0,4.78,"대응 방안")]
rect(s, 0.55, 1.5, 12.23, 0.44, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
for x,w,h in cols:
text(s, x+0.12, 1.5, w-0.15, 0.44, h, size=10.5, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
risks = [
("기술","나노바나나 생성 이미지가 실제 시공과 상이","높음",AMBER,"전 이미지 워터마크·고지 강제, 참조이미지 구조보존 파이프라인, 검수 단계"),
("데이터","홀 실측 CAD·트렌치 좌표 미확보","높음",AMBER,"확보 자산(평면도 15장+CAD) 활용, 미확보 시 공개규격 근사도면으로 Phase 진행"),
("연동","kxwp 내부 API 미공개(폐쇄형)","중간",SLATE,"초기 제출용 파일 생성+업로드 안내 릴레이, 연동은 킨텍스 협의 후"),
("보안","외부 API 호출·자격증명 노출","높음",AMBER,"Claude·Gemini만 예외 승인, env 로드·미커밋, 응답 미노출, 감사로그"),
("규정","소방·구조 최종 유권해석은 시스템 밖","중간",SLATE,"AI는 사전 필터(위반 플래깅)까지, 최종 판정은 관할기관·구조기술사"),
("일정","공통 레이어 지연 시 도메인 착수 지연","중간",SLATE,"Phase B 선행 완료(현재 완료), 모듈 병렬화·점진 QA로 흡수"),
("시장","옥션 업체 참여율·응찰 품질 불확실","중간",SLATE,"등록업체 DB 검증 게이트, AI 자료 기반 동일사양 비교로 참여 유인"),
]
ry=1.98; rh=0.56
for i,(g,r,imp,impc,resp) in enumerate(risks):
y=ry+i*rh
if i%2==0:
rect(s, 0.55, y, 12.23, rh, fill=RGBColor(0xFC,0xFC,0xFD))
rect(s, 0.55, y, 12.23, 0.01, fill=LINE)
text(s, 0.67, y, 0.7, rh, g, size=9.5, color=BLUE_DK, bold=True, anchor=MSO_ANCHOR.MIDDLE)
text(s, 1.42, y, 4.4, rh, r, size=9.5, color=INK, anchor=MSO_ANCHOR.MIDDLE)
rect(s, 5.9, y+0.16, 0.95, 0.24, fill=impc, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 5.9, y+0.15, 0.95, 0.26, imp, size=8.5, color=WHITE, bold=True, align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
text(s, 8.0, y, 4.7, rh, resp, size=8.8, color=MUTED, anchor=MSO_ANCHOR.MIDDLE, line_spacing=0.92)
# ================================================================
# 11. 기대 효과
# ================================================================
divider("09", "기대 효과", "EXPECTED BENEFITS",
["정량 효과 (리드타임)", "정성 효과 (의사결정·투명성)", "확장 로드맵"])
s = slide()
content_header(s, "09 · 기대 효과", "정량 · 정성 기대 효과", 9)
text(s, 0.55, 1.48, 8, 0.3, "정량 효과 — 리드타임 단축", size=12.5, color=BLUE, bold=True)
quant = [
("홀 배정 견적","수일","즉시"),
("배치도 초안","수일~수주","수 분"),
("규정 검수","육안 검토","자동 플래깅"),
("유틸리티 신청","수기 작도","클릭 + 자동견적"),
("시공 결과 예측","불가(외주)","표준 샷 자동생성"),
]
qw=2.4
for i,(k,a,b) in enumerate(quant):
x=0.55+i*(qw+0.06)
card(s, x, 1.85, qw, 1.85)
text(s, x, 2.0, qw, 0.3, k, size=10, color=INK, bold=True, align=PP_ALIGN.CENTER)
rect(s, x+0.3, 2.34, qw-0.6, 0.02, fill=LINE)
text(s, x, 2.42, qw, 0.26, "As-Is", size=7.5, color=SLATE, align=PP_ALIGN.CENTER)
text(s, x, 2.62, qw, 0.3, a, size=10.5, color=MUTED, align=PP_ALIGN.CENTER)
text(s, x, 2.98, qw, 0.24, "", size=12, color=PURPLE, bold=True, align=PP_ALIGN.CENTER)
text(s, x, 3.2, qw, 0.3, b, size=11, color=BLUE, bold=True, align=PP_ALIGN.CENTER)
text(s, 0.55, 3.95, 8, 0.3, "정성 효과", size=12.5, color=PURPLE, bold=True)
qual = [
("의사결정 가속","도면을 읽지 못해도 사진 수준 시각화로 주최자·참가업체 의사결정"),
("영업 자료화","완공 예상 이미지로 참가업체 유치·컨펌 커뮤니케이션 비용 절감"),
("가격 투명화","옥션 역경매로 동일 사양 위 공정 비교 → 시공 발주 투명·최적화"),
("데이터 일원화","공간 데이터(PostGIS) 단일 원천에서 검증·시각화·정산·BI·wayfinding"),
]
for i,(h,d) in enumerate(qual):
x=0.55+(i%2)*6.15
y=4.32+(i//2)*0.66
card(s, x, y, 6.05, 0.58)
rect(s, x, y, 0.08, 0.58, fill=PURPLE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, x+0.24, y+0.08, 5.7, 0.28, h, size=10.5, color=INK, bold=True)
text(s, x+0.24, y+0.34, 5.7, 0.22, d, size=8.6, color=MUTED)
# 확장 로드맵
rect(s, 0.55, 5.78, 12.23, 0.44, fill=BLUE, shape=MSO_SHAPE.ROUNDED_RECTANGLE)
text(s, 0.7, 5.78, 4, 0.44, "확장 로드맵", size=11, color=WHITE, bold=True, anchor=MSO_ANCHOR.MIDDLE)
road = ["KINTEX 기준 운영","코엑스 등 다중 전시관 온보딩","전시테크 SaaS 사업화","kxwp·PG·사이니지 정식 연동"]
for i,r in enumerate(road):
text(s, 2.6+i*2.55, 5.78, 2.5, 0.44, " "+r, size=9.3, color=WHITE, anchor=MSO_ANCHOR.MIDDLE)
text(s, 0.55, 6.4, 12.2, 0.5,
"종합 “신청서를 내는 순간 시공 후 사진을 먼저 본다” — 설계·시각화·발주·운영·분석 폐루프를 하나의 데이터·계정 체계로 자동화하여 킨텍스를 전시 운영 표준 플랫폼으로 전환",
size=10, color=INK, bold=True, line_spacing=1.0)
# ================================================================
# 저장
# ================================================================
OUT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "개발계획서.pptx")
prs.save(OUT)
print("SAVED:", OUT)
print("SLIDES:", len(prs.slides._sldIdLst))

View File

@ -0,0 +1,337 @@
# -*- coding: utf-8 -*-
"""
사업수행계획서(.docx) 행안부 정보화사업 산출물 표준 목차 준용.
데이터 소스: sources.py(PLANNING·IMPLEMENTATION_BACKLOG·FEATURE_BACKLOG_100 파싱).
소스 문서 갱신 스크립트 재실행만으로 결정적 재생성.
보안: 서버 IP·비밀번호·SSH·시크릿 미기재.
"""
import os
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
import sources as S
from _docxutil import setup, h, para, bullets, table, NAVY, ACCENT, GREY
OUT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "사업수행계획서.docx"))
doc = Document(); setup(doc)
M = S.META
CA = WD_ALIGN_PARAGRAPH.CENTER
# ---------- 표지 ----------
for _ in range(4): doc.add_paragraph()
para(doc, f"{M['project_en']} — 사업수행계획서", size=13, color=ACCENT, align=CA, space_after=4)
p = doc.add_paragraph(); p.alignment = CA
r = p.add_run(M["title"]); r.bold = True; r.font.size = Pt(24); r.font.color.rgb = NAVY
doc.add_paragraph()
para(doc, M["std"], size=11, color=GREY, align=CA)
for _ in range(6): doc.add_paragraph()
table(doc, ["구분","내용"],
[["문서명", M["title"]],["문서 버전", M["version"]],["작성일", M["date"]],
["작성 주관", M["owner"]],["발주처(수요기관)", M["client"]],
["사업 유형","정보시스템 구축(SI) — AI 기반 전시 운영 자동화 플랫폼"],
["보안 등급","대외비 — 자격증명·내부망 정보 미기재"]],
widths=[4.5, 11.5], first_col_bold=True)
doc.add_page_break()
# ---------- 문서정보 / 개정이력 / 목차 ----------
h(doc,1,"문서 정보 및 개정 이력")
h(doc,2,"문서 정보")
table(doc, ["항목","내용"],
[["문서 제목", M["title"]],["산출 단계","사업 착수(계획) 단계 표준 산출물"],
["표준 근거", M["std"]],
["작성 근거 문서","PLANNING v3.0 · IMPLEMENTATION_BACKLOG · FEATURE_BACKLOG_100 · GUARDIA_ALIGNMENT · SECURITY · architecture/*"],
["문서 관리","형상관리(Git) 하 버전 통제 — docs/deliverables/ 관리, _gen 재실행으로 재생성"]],
widths=[4.5, 11.5], first_col_bold=True)
h(doc,2,"개정 이력")
table(doc, ["버전","일자","작성/개정자","주요 변경 내용"],
[[M["version"], M["date"], "사업 PMO", "사업수행계획서 최초 작성(표준 목차 14장 체계)"]],
widths=[2.0, 3.0, 3.5, 7.5])
para(doc, "※ 본 계획서는 사업 진척에 따라 형상관리 절차(14장)에 의거 개정하며, 모든 변경은 개정 이력에 기록한다.",
size=9, italic=True, color=GREY)
h(doc,2,"목차")
for t_ in ["1. 사업 개요","2. 사업 범위","3. 추진 전략 및 개발 방법론","4. 추진 체계·조직 및 역할(R&R)",
"5. WBS 및 마스터 일정","6. 투입 인력 및 투입 계획(M/M)","7. 품질보증 계획","8. 보안 및 개인정보보호 계획",
"9. 시험(테스트) 계획 개요","10. 이관·교육·유지보수 계획","11. 위험(리스크) 관리","12. 의사소통·보고 체계",
"13. 산출물 목록","14. 형상관리"]:
pp = doc.add_paragraph(t_); pp.paragraph_format.space_after = Pt(2); pp.runs[0].font.size = Pt(10.5)
doc.add_page_break()
# ---------- 1. 사업 개요 ----------
h(doc,1,"1. 사업 개요")
h(doc,2,"1.1 추진 배경")
para(doc,"킨텍스는 총 전시면적 108,011㎡(국내 최대, 제3전시장 완공 시 약 178,000㎡)를 운영하지만, 전시 준비 실무는 여전히 "
"표준 서식 다운로드 후 이메일·방문 제출, CAD 수작업 배치도의 육안 검수, 수기 위치표시도 기반 유틸리티 신청에 머물러 있다. "
"참가업체 유틸리티 신청은 킨텍스 공통 플랫폼 없이 전시회별 주최자 사무국 시스템으로 파편화되어 있다.")
para(doc,"공간 데이터(배치도·도면·위치표시도)가 전부 이미지·수기로 오가며 구조화되지 않아 자동 검증·시각화·정산의 원천 데이터가 부재하고, "
"D-150/D-30/D-25/D-7 마일스톤이 시스템화되어 있지 않으며, 시공 결과를 사전에 확인할 수단이 없다는 구조적 공백이 확인되었다.")
h(doc,2,"1.2 사업 목적")
bullets(doc,[
"전시장 운영 전 과정(홀 배정→부스 배치→장치공사 설계→전기/조명→네트워크/유틸리티 배선→반입/반출)을 AI로 자동 설계·검증한다.",
"AI 설계 결과를 나노바나나(이미지 생성)로 '시공 후 결과 사진'처럼 시공 전에 미리 생성하여 사진 수준 시각화를 제공한다.",
"기획·판매·시공·운영·관람객·사후분석 전 주기를 하나의 공간 데이터·계정 체계로 통합한 베뉴 운영 플랫폼을 구축한다.",
"AI 설계·시각화 산출물을 공사/장치 옥션(역경매)의 응찰 근거로 소비하여 설계→시각화→발주→시공 폐루프를 완성한다.",
])
h(doc,2,"1.3 비전 및 정체성")
para(doc,'비전 슬로건: "신청서를 내는 순간, 시공 후 사진을 먼저 본다."', bold=True, color=ACCENT)
para(doc,"본 시스템은 킨텍스를 기준 테넌트(#1)로 하되 코엑스 등 여타 전시관을 온보딩하는 다중 전시관 멀티테넌트 SaaS로 확장 가능한 구조로 설계한다. "
"기능(모듈)은 전부 보존하고 그 위에 전시관 단위 데이터·권한·컨텍스트 격리 레이어를 얹는다.")
h(doc,2,"1.4 정량 목표(As-Is → To-Be)")
table(doc, ["목표 지표","현재(As-Is)","목표(To-Be)"],
[["홀 배정 견적 회신","문의→협의→견적 수일","조건 입력 후 즉시 자동 가견적"],
["부스 배치도 초안","CAD 수작업 수일~수주","조건 입력 후 수 분 내 복수 안 생성"],
["도면 규정 검수","홀매니저 육안 검토","규정 위반 자동 플래깅 후 사람 확정"],
["유틸리티 위치표시도","참가업체 수기 작도","부스 좌표 클릭 → 자동 배선안·자동 견적"],
["시공 결과 예측","불가(외주 조감도)","표준 샷 세트 자동 생성(정면/야간/통로)"]],
widths=[4.5, 5.5, 6.0])
h(doc,2,"1.5 기대 효과")
bullets(doc,[
"참가업체·주최자의 의사결정 가속 및 참가업체 유치 영업 자료 확보(시공 예상 이미지 기반).",
"규정 위반 사전 검출로 도면 반려 재작업 감소 및 홀매니저 검수 시간 단축.",
"'현장 추가신청 불가' 유틸리티의 신청 누락 방지 — 참가업체 최대 리스크 제거.",
"역경매 옥션을 통한 시공 발주 가격 최적화·투명화(동일 사양 위 공정 비교).",
"공간 데이터 표준화로 자동 검증·시각화·정산·wayfinding·경영분석의 단일 원천 확보.",
])
# ---------- 2. 사업 범위 (모듈: sources.modules) ----------
h(doc,1,"2. 사업 범위")
h(doc,2,"2.1 대상 시스템 및 모듈")
para(doc,f"본 사업은 설계·시각화 코어(P0), 판매·발주·정산, 관람·참가·마케팅, 경영·콘텐츠·관리 계열의 총 {len(S.modules())}개 모듈과 "
"공통·시스템관리 레이어(UIWS/WISE 이식)로 구성된다. (모듈 목록은 PLANNING 총괄표에서 자동 추출)")
mod_rows = [[mid, name, pri] for (mid, name, pri) in S.modules()]
table(doc, ["모듈","명칭","우선순위"], mod_rows, widths=[2.2, 11.3, 2.5])
h(doc,2,"2.2 우선순위 정의")
bullets(doc,[
"P0 — 핵심 차별화 + 첫 상용 행사 적용 필수(부스 시공 코어 M2~M5).",
"P1 — 운영 효율 핵심(판매·발주·정산·관람·마케팅·경영분석·관리자·공통 레이어).",
"P2 — 확장(물류·비즈매칭·wayfinding·현장운영).",
])
h(doc,2,"2.3 범위 제외(Non-Goals)")
bullets(doc,[
"킨텍스 임대계약의 법적 전자체결은 후속 단계 검토 — 초기에는 견적·서류 준비까지.",
"구조계산서의 구조 안전성 최종 판정은 AI가 대신하지 않는다(규정 체크·누락 검출까지, 최종 판정은 구조기술사/킨텍스).",
"AI 생성 시각화는 참고용이며 계약·인허가 서류로 사용 불가(워터마크·고지 강제).",
"신규 전시관 온보딩은 마스터데이터 입력 수준까지 — 고유 규정 파서 자동 생성 제외.",
"정밀 실내 측위(UWB/BLE 비콘)는 시설 인프라 협의 전제 — 초기에는 존-레벨 폴백.",
])
# ---------- 3. 추진 전략·방법론 ----------
h(doc,1,"3. 추진 전략 및 개발 방법론")
h(doc,2,"3.1 기본 추진 전략")
bullets(doc,[
"공통 레이어 선행: 인증(JWT+2FA)·시스템관리·공통 업무기능을 전 도메인 모듈보다 선행 구축.",
"코어 우선: 핵심 차별화 P0 부스 시공 코어(M2~M5)를 우선 확보하여 첫 상용 행사에 적용.",
"단일 공간 데이터 모델: M2→M3→M4→M5가 하나의 PostGIS 지오메트리를 공유.",
"폐루프 완성: 코어 산출물을 M15 옥션 응찰 근거로 소비하여 설계→시각화→발주→시공 종결.",
"표준 프레임워크 준수: GUARDiA 표준(UIMS/WISE) 스택·인증·공통모듈·보안·배포를 상속하고 도메인만 확장.",
])
h(doc,2,"3.2 개발 방법론")
para(doc,"반복·점진(Iterative & Incremental) 방식. Phase A~E 단계 위에서 각 모듈은 설계→구현→모듈 완성 직후 점진 QA→통합의 짧은 반복을 수행하며, "
"QA가 경계면·회귀·보안 불변을 검증하고 통과할 때까지 반려한다.")
ph = S.phases()
table(doc, ["단계","성격","핵심 활동"],
[[f"Phase {p[0]}", p[1], " · ".join(t[1] for t in p[2][:3]) + ("" if len(p[2])>3 else "")] for p in ph],
widths=[2.4, 4.6, 9.0])
h(doc,2,"3.3 기술 아키텍처(확정 스택)")
table(doc, ["레이어","기술","비고"],
[["프론트엔드","React 18/19 + Vite + TypeScript","역할별 포털 번들 분리(데스크톱=설계, 모바일=현장·조회)"],
["백엔드","Spring Boot 3.x(Java 17) + MyBatis","REST + WebSocket(STOMP), 룰·배치/배선 엔진"],
["데이터베이스","PostgreSQL + PostGIS","부스 폴리곤·트렌치 포인트·배선 LineString 공간 데이터"],
["비동기","Redis 작업 큐","RenderJob·서류·알림 비동기 처리"],
["이미지 생성","나노바나나 Python 워커(사이드카)","백엔드는 큐 발행만, 생성 키는 워커 env 전용"],
["AI(텍스트)","Claude 기본 + 설정형 전환(AiTextRouter)","실패 시 온프레미스 모델 자동 폴백"],
["형상/배포","Git(Gitea)+CI/CD Webhook+systemd/nginx","무중단·헬스게이트·롤백(Fail-Safe)"]],
widths=[2.8, 6.2, 7.0])
para(doc, f"개발 도메인은 서비스명 수준으로 {M['dev_domain']}을 사용하며, 운영 도메인은 후속 확정한다.", size=9, italic=True, color=GREY)
# ---------- 4. 추진 체계·조직·역할 ----------
h(doc,1,"4. 추진 체계·조직 및 역할(R&R)")
h(doc,2,"4.1 추진 체계")
para(doc,"발주처(킨텍스)—사업 총괄(PMO)—아키텍트 그룹—개발 그룹(공통·코어·도메인)—품질(QA)·배포(DevOps)의 계층으로 구성한다. "
"AI 에이전트 팀(kintex-impl-orchestrator)이 각 트랙 구현을 조율하며, 기획(planner)·디자인(designer)·검증(reviewer)을 경유한다.")
h(doc,2,"4.2 조직 및 역할(R&R)")
table(doc, ["그룹","역할","주요 책임(R&R)"],
[["거버넌스","PMO / 사업 PM","일정·범위·리스크·이해관계자 관리, 발주처 보고, 형상·품질 총괄"],
["아키텍트","AA(애플리케이션)","모듈 경계·레이어·API 표준·패키지·의존성 규칙"],
["아키텍트","SA(시스템)","확장성·HA·성능·배포 토폴로지·비기능요건(NFR)"],
["아키텍트","TA(기술)","기술 표준·빌드/배포·개발 표준·관측성·AiTextRouter"],
["아키텍트","DA(데이터)","전사 ERD·공간데이터·마스터·공통코드·BI 데이터마트"],
["아키텍트","NA(네트워크)","DMZ/내부망 분리·방화벽·부하분산·공개/백오피스 망 분리"],
["공통","공통/시스템관리 개발","인증(2FA)·시스템관리·공통 업무기능(UIWS 이식)"],
["코어","백엔드 개발","M1~M9 REST/WebSocket·룰 엔진·배선 엔진(PostGIS)"],
["코어","프론트엔드 개발","역할별 포털 화면·플로어플랜 캔버스·Before/After 뷰"],
["코어","DB 엔지니어","PostGIS 스키마·마이그레이션·매퍼·마스터 시드"],
["AI·시각화","AI 개발","배치 자동생성·규정검증 보조·매칭·서류검수·챗봇(Claude)"],
["AI·시각화","시각화(visualizer)","나노바나나 프롬프트·워커·워터마크 강제"],
["도메인","옥션/관람객/CMS/BI/관리자 개발","M15/M10/M12·M17/M16/M18 도메인 구현"],
["품질·배포","QA","경계면·회귀·보안 불변 점진 검증(통과까지 반려)"],
["품질·배포","DevOps","빌드·CI/CD·systemd/nginx·환경변수 배선"]],
widths=[2.2, 4.0, 9.8])
# ---------- 5. WBS·마스터 일정 (sources.phases + SCHEDULE) ----------
h(doc,1,"5. WBS 및 마스터 일정")
h(doc,2,"5.1 작업 분해 구조(WBS) 개요")
para(doc,"WBS는 Phase A~E의 5개 단계와 하위 작업 패키지로 구성한다(IMPLEMENTATION_BACKLOG에서 자동 추출). "
"상세 작업 분해·담당·의존·기간은 별첨 [WBS_및_일정.xlsx]의 'WBS' 시트에 관리한다.")
table(doc, ["Phase","작업 패키지(대표)","완료 기준(대표)"],
[[f"{p[0]}. {p[1]}", " / ".join(t[1] for t in p[2][:3]), p[2][0][3] if p[2] else ""] for p in ph],
widths=[3.6, 8.0, 4.4])
h(doc,2,"5.2 마스터 일정 및 마일스톤")
para(doc,"총 사업 기간은 약 28주(7개월) 기준이며, 착수 시점 확정 후 절대 일정을 확정한다(아래는 착수일 기준 상대 주차).")
table(doc, ["Phase","시작","종료","주요 마일스톤"],
[[f"{r_[0]}. {r_[1]}", r_[2], r_[3], r_[4]] for r_ in S.SCHEDULE],
widths=[4.2, 1.8, 1.8, 8.2])
para(doc,"※ Phase B는 Phase A와 일부 병행 착수하며, Phase B(공통 레이어) 완료가 Phase C/D 도메인 착수의 선행 전제이다.",
size=9, italic=True, color=GREY)
# ---------- 6. 투입 인력 (sources.STAFFING) ----------
h(doc,1,"6. 투입 인력 및 투입 계획(M/M)")
h(doc,2,"6.1 투입 인력 계획")
para(doc,"역할별 투입 인력과 단계별 투입 공수(M/M)는 아래와 같다. 상세는 별첨 [WBS_및_일정.xlsx]의 '투입인력계획' 시트에 관리한다.")
table(doc, ["역할","인원(명)","등급","주 투입 Phase","투입 공수(M/M)"],
[list(x) for x in S.STAFFING], widths=[5.5, 2.0, 2.0, 2.8, 3.7])
total_mm = sum(float(x[4]) for x in S.STAFFING)
total_head = sum(int(x[1]) for x in S.STAFFING)
para(doc, f"합계(계획): 인원 약 {total_head}명(중복 참여 포함), 총 투입 공수 약 {total_mm:.1f} M/M(추정치, 착수 시 상세 산정).",
bold=True, size=10, color=NAVY)
h(doc,2,"6.2 투입 원칙")
bullets(doc,[
"Phase B(공통 레이어)에 개발 인력을 선집중하여 전 도메인 기반을 조기 확보.",
"아키텍트는 Phase A에 집중 투입 후 각 Phase 착수 시 표준 준수를 검수.",
"QA는 전 Phase 상시 배치하여 모듈 완성 직후 점진 검증을 수행.",
])
# ---------- 7. 품질보증 ----------
h(doc,1,"7. 품질보증 계획")
h(doc,2,"7.1 품질 목표 및 기준")
bullets(doc,[
"기능 품질: 모듈별 완료 기준(WBS) 충족, 경계면(API 응답 shape↔프론트 호출) 불일치 0.",
"코드 품질: 표준 프레임워크·아키텍처 표준 준수, 정적 분석·코드 리뷰 통과.",
"회귀 품질: 스모크/회귀 테스트로 기존 기능 무회귀 보장.",
"보안 품질: 보안 불변 규칙(8장) 위반 0 — 위반 시 반려 사유.",
])
h(doc,2,"7.2 품질보증 활동")
table(doc, ["활동","시점","담당","산출"],
[["아키텍처 정합 검증","Phase A 종료","reviewer","불일치 티켓·시정 확인"],
["점진 QA(모듈 완성 직후)","각 모듈 완성","QA","경계면·회귀·보안 검증 결과"],
["코드 리뷰","커밋/PR","개발 리드","리뷰 코멘트·조치"],
["스모크·회귀 테스트","배포 전","QA/DevOps","테스트 리포트"],
["보안 체크리스트 점검","배포·리뷰 전","QA","체크리스트 결과(8장)"]],
widths=[4.5, 3.0, 3.0, 5.5])
h(doc,2,"7.3 품질 게이트")
para(doc,"각 Phase 종료 및 배포 직전에 품질 게이트를 두어 완료 기준·경계면 정합·보안 체크리스트·회귀 통과를 확인하고 통과 시에만 다음 단계로 진행한다.")
# ---------- 8. 보안·개인정보 ----------
h(doc,1,"8. 보안 및 개인정보보호 계획")
h(doc,2,"8.1 보안 불변 원칙")
table(doc, ["구분","원칙"],
[["외부 API 정책","온프레미스 모델 우선. 텍스트 AI는 승인된 단일 예외 경로만, 이미지 생성은 킨텍스 고유 승인 예외(워커 전용). 그 외 외부 API 금지."],
["자격증명 보호","내부 IP·SSH 계정·비밀번호·해시·API 키·워커 토큰을 API 응답·로그·에러·커밋에 절대 노출 금지."],
["암호화 저장","admin 비밀번호는 env(AES-256-GCM 암호문)+별도 키파일로 기동 시 재시드(하드코딩 시드 금지). 저장 자격증명은 암호화 컬럼에만."],
["인증·권한","JWT(HS256)+2차 인증(OTP/EMAIL)+로그인 실패 잠금. 행사 단위 RBAC. 미등록 장치업체 초대·응찰 차단."],
["AI 이미지 워터마크","시각화 결과는 항상 워터마크·고지 포함(참고용, 계약·인허가 서류 사용 불가)."],
["오류 응답","스택트레이스·relation/컬럼명·내부 경로 미노출. 코드+요약 메시지만 반환."],
["감사 추적","관리자·인증·권한·낙찰·설계변경·개인정보 접근 등 민감 액션 감사 로그 기록(비밀값 미기재)."]],
widths=[3.5, 12.5])
h(doc,2,"8.2 개인정보보호")
bullets(doc,[
"관람객 배지 QR·리드캡처 데이터는 수집·이용 동의 및 보존 기간 정책을 필수 적용.",
"리드(개인정보) 접근은 감사 로그로 전수 기록, 표시 필드는 비민감 항목으로 최소화.",
"EDM 발송은 정보통신망법·수신 동의 규정을 준수.",
"개인정보는 최소 수집·목적 제한·접근 통제(RBAC) 원칙을 적용.",
])
h(doc,2,"8.3 보안 체크리스트(배포·리뷰 전)")
bullets(doc,[
"응답/로그/커밋에 IP·SSH·비번·해시·API 키·워커 토큰 노출 0(grep).",
"AI 키(텍스트/이미지)는 env only — 코드·DB·커밋 미포함.",
"admin 비번 하드코딩 시드 없음(env 재시드).",
"시각화 이미지 응답에 워터마크 필드 포함.",
"DB/서버 오류 → 요약 메시지(스택·relation 명 미노출).",
"관리자 게이트·행사 RBAC·미등록업체 차단 동작.",
])
# ---------- 9. 시험 계획 ----------
h(doc,1,"9. 시험(테스트) 계획 개요")
h(doc,2,"9.1 시험 레벨")
table(doc, ["레벨","대상","방식"],
[["단위 시험","서비스·매퍼·룰 엔진·배선 엔진","개발자 단위 테스트, 공간 연산(PostGIS) 정합 검증"],
["통합 시험","모듈 간 경계면(API↔프론트, 큐↔워커)","API 응답 shape 대조, WebSocket/큐 왕복"],
["시스템 시험","역할별 시나리오(주최자/참가/업체/홀매니저/관람객)","E2E 시나리오·규정 검증·시각화 워터마크 확인"],
["회귀·스모크","전 기능","배포 전 스모크(헬스+라우터 등록+로그인 프로브)·회귀"],
["보안 시험","보안 불변 규칙","자격증명 미노출·RBAC 우회·스택트레이스 차단 점검"]],
widths=[2.6, 6.4, 7.0])
h(doc,2,"9.2 시험 원칙")
bullets(doc,[
"각 모듈 완성 직후 QA가 점진 검증하고, 통과할 때까지 반려.",
"공간 로직(배치·배선·규정 검증)과 시각화 워터마크 강제는 필수 검증 항목.",
"시험 케이스·결과는 별첨 [WBS_및_일정.xlsx]의 '시험케이스' 시트와 형상관리 하 리포트로 관리.",
])
# ---------- 10. 이관·교육·유지보수 ----------
h(doc,1,"10. 이관·교육 및 유지보수 계획")
h(doc,2,"10.1 이관 계획")
bullets(doc,[
"빌드 산출물(프론트 번들·백엔드 실행 파일·워커 서비스)과 마스터데이터·형상 이력을 발주처 운영 환경으로 이관.",
"이관 시 무중단·헬스게이트·롤백(Fail-Safe) 배포 절차를 적용.",
"운영 도메인·운영 환경변수 배선은 발주처 승인 하에 수행.",
])
h(doc,2,"10.2 교육 계획")
table(doc, ["대상","교육 내용","형식"],
[["운영자/관리자","백오피스(RBAC·마스터데이터·규정 룰셋)·감사·시스템설정","운영자 지침서+실습"],
["킨텍스 운영팀(홀매니저)","배치 검수·규정 플래그·현장 운영 대시보드","사용자 지침서+실습"],
["주최자/참가업체","포털 사용(배정·설계·유틸리티·옥션·관람객)","사용자 지침서·온라인 가이드"]],
widths=[4.0, 8.0, 4.0])
h(doc,2,"10.3 유지보수 계획")
bullets(doc,[
"장애 대응·정기 점검·규정/요율 개정 반영(룰셋 버전 관리)을 유지보수 범위로 한다.",
"운영 런북(헬스체크·재기동·롤백)을 기준으로 SM 운영 체계를 수립.",
"규정·요율 연 단위 개정은 관리자 백오피스 룰셋 버전 관리로 안전 반영.",
])
# ---------- 11. 위험 관리 (sources.RISKS) ----------
h(doc,1,"11. 위험(리스크) 관리")
para(doc,"주요 리스크와 대응 방안은 아래와 같으며, 형상관리 하 리스크 대장으로 지속 관리한다.")
table(doc, ["ID","리스크","영향","대응 방안"],
[list(x) for x in S.RISKS], widths=[1.2, 4.2, 2.6, 8.0])
# ---------- 12. 의사소통·보고 ----------
h(doc,1,"12. 의사소통·보고 체계")
table(doc, ["보고/회의","주기","참여","내용"],
[["착수 보고","사업 착수 시","발주처·PMO","범위·일정·조직·리스크 확정"],
["주간 진척 보고","주 1회","PMO·개발 리드","진척·이슈·리스크·다음 주 계획"],
["Phase 종료 보고","각 Phase 종료","발주처·PMO","산출물·품질 게이트 결과·다음 단계"],
["단계별 검토회의","마일스톤","전 트랙","경계면 정합·아키텍처 준수 점검"],
["완료(준공) 보고","사업 종료","발주처·PMO","통합 QA 결과·이관·교육·유지보수 인계"]],
widths=[3.6, 2.6, 3.4, 6.4])
para(doc,"긴급 이슈는 즉시 에스컬레이션하며, 모든 의사결정·변경은 형상관리(14장) 절차로 기록한다.", size=9, italic=True, color=GREY)
# ---------- 13. 산출물 목록 (sources.DELIVERABLES) ----------
h(doc,1,"13. 산출물 목록(단계별 표준 산출물)")
para(doc,"단계별 표준 산출물은 아래와 같으며, 상세 목록·형식·담당·상태는 별첨 [WBS_및_일정.xlsx]의 '산출물목록' 시트에 관리한다.")
table(doc, ["단계","산출물","형식","담당","상태"],
[[d[0], d[1], d[2], d[3], d[4]] for d in S.DELIVERABLES],
widths=[2.4, 6.2, 2.2, 3.0, 2.2])
# ---------- 14. 형상관리 ----------
h(doc,1,"14. 형상관리")
h(doc,2,"14.1 형상관리 원칙")
bullets(doc,[
"모든 소스·문서·설정은 Git(Gitea) 저장소로 버전 통제하며, 변경은 커밋 이력으로 추적.",
"산출물(계획서·설계서·시험·지침서)은 docs/deliverables/ 지정 경로에서 형상 관리.",
"DB 스키마는 Flyway 순번 마이그레이션을 형상 권위로 하며, baseline 규율과 시드 멱등화를 준수.",
"배포는 형상(커밋) 기준 CI/CD Webhook으로 무중단·헬스게이트·롤백 수행.",
])
h(doc,2,"14.2 형상 항목 및 절차")
table(doc, ["형상 항목","관리 방식","변경 절차"],
[["소스코드","Git 브랜치·PR·리뷰","리뷰 승인 후 병합, 커밋 메시지 이력화"],
["문서/산출물","Git 버전·개정 이력","개정 이력 기록 후 갱신"],
["DB 스키마","Flyway 마이그레이션","신규 마이그 번호=현재 최대+1(충돌 검사)"],
["설정/환경변수","env 주입·시크릿 분리","키·비번은 형상에서 제외(gitignore)"],
["배포","CI/CD Webhook·헬스게이트","실패 시 자동 롤백"]],
widths=[3.4, 5.6, 7.0])
doc.add_paragraph()
para(doc,"— 본 사업수행계획서는 행정안전부 정보화사업 산출물 표준 목차를 준용하여 킨텍스 자동전시시스템 구축 사업의 원본 내용으로 작성되었다. —",
size=9, italic=True, color=GREY, align=CA)
doc.save(OUT)
print("SAVED:", OUT)

View File

@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
"""
sources.py 산출물 생성의 단일 데이터 소스 로더.
docs/PLANNING.md · docs/IMPLEMENTATION_BACKLOG.md · docs/FEATURE_BACKLOG_100.md · docs/architecture/*
파싱하여 구조화 데이터(모듈·Phase/WBS·기능/요구사항) 반환한다.
소스 문서가 갱신되면 _gen 재실행만으로 산출물이 갱신된다.
파싱 실패 임베디드 폴백을 사용해 결정적 산출을 보장한다.
"""
import os, re
DOCS = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) # kintex/docs
ROOT = os.path.abspath(os.path.join(DOCS, "..")) # kintex
META = {
"project": "킨텍스 자동전시시스템",
"project_en": "Exhibition Automation Platform",
"title": "킨텍스 자동전시시스템 구축 사업수행계획서",
"version": "v1.0",
"date": "2026-07-11",
"owner": "지오정보기술(주) — GUARDiA 플랫폼 사업본부",
"client": "킨텍스(KINTEX, 한국국제전시장)",
"dev_domain": "kintex.zioinfo.co.kr(개발)",
"std": "행정안전부 정보화사업 산출물 표준 목차 준용",
}
def _read(rel):
p = os.path.join(DOCS, rel)
try:
with open(p, "r", encoding="utf-8") as f:
return f.read()
except Exception:
return ""
def _table_rows(text, min_cols=2):
"""마크다운 표의 데이터 행(| a | b |)을 셀 리스트로 반환(헤더/구분선 제외)."""
out = []
for line in text.splitlines():
s = line.strip()
if not s.startswith("|"):
continue
if re.match(r"^\|[\s:\-\|]+\|?$", s): # separator
continue
cells = [c.strip() for c in s.strip("|").split("|")]
if len(cells) >= min_cols:
out.append(cells)
return out
# ---------------- 모듈(M1~M18 + §5B) : PLANNING 기능 우선순위 총괄표 ----------------
_MOD_FALLBACK = [
("M2","플로어플랜 스튜디오(부스 배치)","P0"),("M3","부스 설계 스튜디오","P0"),
("M4","유틸리티 설계 자동화(전기·조명/네트워크·급배수)","P0"),("M5","나노바나나 시각화","P0"),
("M1","홀 배정·자동 견적","P1"),("M6","서류·마일스톤 워크플로","P1"),
("M7","등록업체 매칭·견적","P1"),("M9","정산·결제","P1"),
("M15","공사/장치 옥션(역경매·견적서·낙찰)","P1"),("M10","관람객 등록·배지·체크인·리드캡처","P1"),
("M12","마케팅·EDM·공개 홍보 사이트","P1"),("M16","경영분석 BI","P1"),
("M17","CMS(콘텐츠·마이크로사이트·다국어)","P1"),("M18","관리자 백오피스","P1"),
("§5B","공통/시스템관리 레이어 + JWT+2FA","P1"),
("M8","반입/반출 물류","P2"),("M11","비즈니스 매칭","P2"),
("M13","wayfinding·실내 내비","P2"),("M14","현장운영(혼잡·안전·주차·에너지)","P2"),
]
def modules():
text = _read("PLANNING.md")
mods, seen = [], set()
for cells in _table_rows(text, min_cols=4):
mid = cells[0].replace("**","").strip()
if not re.match(r"^(M\d+|§5B)$", mid):
continue
if mid in seen:
continue
name = re.sub(r"\*\*|`", "", cells[1]).strip()
pri = ""
for c in cells:
m = re.search(r"P0|P1|P2", c)
if m: pri = m.group(0); break
if not name or not pri:
continue
seen.add(mid); mods.append((mid, name, pri))
if len(mods) < 15:
return list(_MOD_FALLBACK)
order = {"P0":0,"P1":1,"P2":2}
mods.sort(key=lambda x: order.get(x[2],9))
return mods
# ---------------- Phase / WBS : IMPLEMENTATION_BACKLOG Phase 표 ----------------
_PHASE_FALLBACK = [
("A","아키텍처·거버넌스",[
("A-1","애플리케이션 아키텍처(모듈 경계·API 표준)","아키텍트(AA)","docs/architecture/app.md"),
("A-2","시스템 아키텍처·NFR(확장성·HA·성능·보안영역)","아키텍트(SA)","docs/architecture/system.md"),
("A-3","기술 표준(스택·빌드/배포·관측성·AiTextRouter)","아키텍트(TA)","docs/architecture/tech.md"),
("A-4","데이터 아키텍처(전사 ERD·공간·마스터·BI마트)","아키텍트(DA)","ERD·data.md"),
("A-5","네트워크 아키텍처(DMZ/내부망·방화벽·부하분산)","아키텍트(NA)","network.md"),
("A-6","아키텍처↔PLANNING 정합 검증","reviewer","불일치 0/티켓화"),
]),
("B","공통/시스템관리 레이어(전 모듈 선행)",[
("B-0","스캐폴드(백엔드·프론트·DB·마이그레이션)","BE·FE·DB","빌드·마이그레이션 통과"),
("B-1","인증 JWT+RBAC+2차인증(OTP)+실패잠금+admin env","COM·BE","2FA 등록/검증/초기화 왕복"),
("B-2","시스템관리(사용자·역할·공통코드·메뉴·감사·설정)","COM·ADM·DB","CRUD·RBAC 가드·감사"),
("B-3","공통 업무기능(일지·일정·쪽지·통계·공지·회의·알림)","COM·DB","각 모듈 API 왕복"),
("B-4","공통 컴포넌트·프론트 공통(2FA·그리드·달력·모달)","COM·FE","아키텍처 표준 정합"),
("B-5","점진 QA(2FA·RBAC·PII·경계면)","QA","통과까지 반려"),
]),
("C","P0 부스 시공 코어",[
("C-M2","플로어플랜(배치 3안·병합·규정검증 PostGIS)","DB·BE·AI·FE·QA","3안 생성·병합·규정 재검증"),
("C-M3","부스 설계(조립/독립·규정 사전검증·3안)","BE·AI·FE·QA","위반 사전 플래깅·컨펌"),
("C-M4","유틸리티 배선(전기/네트워크/급배수·견적·위치표시도)","DB·BE·FE·QA","최단 배선·견적·위치표시도"),
("C-M5","나노바나나 시각화(RenderJob·워커·워터마크)","BE·VIZ·FE·QA","실생성/미승인 시 목·워터마크"),
("C-C","역할별 공통 화면(로그인·대시보드·현장 모바일)","FE·QA","역할별 진입·상태 처리"),
]),
("D","P1 도메인(역할별 포털)",[
("D-M15","공사/장치 옥션(견적서 제출·역경매·낙찰)","BID·BE·DB·FE·QA","견적서·역경매·낙찰 왕복"),
("D-M10","관람객·현장(등록·티켓·배지·체크인·리드)","VIS·BE·DB·FE·QA","등록→배지→체크인→리드"),
("D-M12/17","마케팅·공개사이트·CMS(SEO·다국어·EDM)","CMS·BE·FE·QA","공개 조회·게시 워크플로"),
("D-M16","경영분석 BI(가동률·P&L·ROI·수요예측·KPI)","BI·DA·BE·FE·QA","지표 집계·드릴다운"),
("D-M18","관리자 백오피스(마스터데이터·룰셋 버전)","ADM·BE·FE·QA","RBAC·마스터 CRUD·룰셋"),
("D-M1/6/7/9","배정·서류·매칭·정산","BE·FE·DB·QA","견적·마일스톤·정산"),
]),
("E","P2 + 빌드·배포",[
("E-M11","비즈니스 매칭(추천·미팅예약)","VIS·AI","추천·미팅예약"),
("E-M13","wayfinding·실내 내비(M2 재사용)","VIS·FE","부스 검색·경로"),
("E-M14","현장운영(혼잡·안전·주차·에너지)","BE·FE","대시보드·경보"),
("E-DEP","역할별 번들·jar·워커 빌드·CI/CD·systemd","DEV","push→배포"),
]),
]
def phases():
"""[(letter, title, [(id, task, owner, done), ...]), ...]"""
text = _read("IMPLEMENTATION_BACKLOG.md")
if not text:
return _PHASE_FALLBACK
out = []
cur = None
lines = text.splitlines()
hdr_re = re.compile(r"^##\s+Phase\s+([A-E])\s*[—\-]\s*(.+)$")
for line in lines:
hm = hdr_re.match(line.strip())
if hm:
if cur: out.append(cur)
title = re.sub(r"\([^)]*\)", "", hm.group(2))
title = re.sub(r"[★((].*$", "", title).strip()
cur = (hm.group(1), title, [])
continue
s = line.strip()
if cur and s.startswith("|"):
if re.match(r"^\|[\s:\-\|]+\|?$", s):
continue
cells = [c.strip() for c in s.strip("|").split("|")]
if not cells or cells[0] in ("ID","Phase") or not re.match(r"^[A-E]-", cells[0]):
continue
# 표 형태 2종: (ID,작업,담당,완료) 또는 (ID,모듈,작업,담당,완료)
if len(cells) >= 5:
_id, task, owner, done = cells[0], cells[2], cells[3], cells[4]
elif len(cells) == 4:
_id, task, owner, done = cells[0], cells[1], cells[2], cells[3]
else:
continue
cur[2].append((_id, re.sub(r"\*\*|`","",task), re.sub(r"\*\*","",owner), re.sub(r"\*\*","",done)))
if cur: out.append(cur)
ok = [p for p in out if p[2]]
return ok if len(ok) >= 4 else _PHASE_FALLBACK
# 마스터 일정(착수 상대 주차) — 큐레이션(마일스톤)
SCHEDULE = [
("A","아키텍처·거버넌스","W1","W3","M1 아키텍처 베이스라인 승인(W3)"),
("B","공통/시스템관리 레이어","W3","W8","M2 공통 레이어·인증(2FA) 완료(W8)"),
("C","P0 부스 시공 코어","W8","W16","M3 P0 부스 코어 시연·첫 상용 행사 적용(W16)"),
("D","P1 도메인","W16","W24","M4 옥션·관람객·BI·관리자 통합(W24)"),
("E","P2 + 빌드·배포","W24","W28","M5 통합 QA 통과·운영 배포(W28)"),
]
# ---------------- 기능/요구사항 : FEATURE_BACKLOG_100 C1~C12 ----------------
def features():
"""[(fid, name, category, priority, ai, module), ...] — 100대 기능(RTM/요구사항 원천)."""
text = _read("FEATURE_BACKLOG_100.md")
out, seen = [], set()
cur_cat = ""
cat_re = re.compile(r"^###\s+(C\d+)\.\s*(.+?)\s*(?:\(\d+\)|—|$)")
stop = False
for line in text.splitlines():
s = line.strip()
cm = cat_re.match(s)
if cm:
cur_cat = cm.group(1) + " " + re.sub(r"\s*[—-].*$","",cm.group(2)).strip()
stop = False
continue
# C 표가 끝나고 'AI 자동화 맵' 등 다른 섹션이면 중단 표시
if s.startswith("##") and "카테고리" not in s and not s.startswith("###"):
stop = True
if stop:
continue
m = re.match(r"^\|\s*(F\d{3})\s*\|(.+)$", s)
if m and cur_cat:
fid = m.group(1)
if fid in seen:
continue
cells = [c.strip() for c in m.group(2).strip("|").split("|")]
name = re.sub(r"\*\*|`","",cells[0]).strip() if cells else fid
pri = next((re.search(r"P[012]",c).group(0) for c in cells if re.search(r"P[012]",c)), "")
ai = ""
for c in cells:
cc = c.strip()
if cc in ("Y","N","부분"):
ai = cc; break
module = next((re.sub(r"\*\*|`","",c) for c in cells if re.search(r"(M\d+|§5B|§1A|§6)", c)), "")
seen.add(fid)
out.append((fid, name, cur_cat, pri, ai, module))
return out
# ---------------- 산출물 목록(표준 단계별) ----------------
DELIVERABLES = [
# (단계, 산출물명, 형식, 담당, 상태, 비고)
("착수(계획)","사업수행계획서","DOCX","PMO","작성완료","본 산출물(gen_docx)"),
("착수(계획)","WBS 및 마스터 일정","XLSX","PMO","작성완료","gen_xlsx(WBS/일정/투입/산출물)"),
("착수(계획)","산출물 목록","XLSX","PMO","작성완료","gen_xlsx(산출물목록 시트)"),
("착수(계획)","착수보고 자료","PPTX","PMO","작성완료","gen_pptx(착수보고)"),
("분석","요구사항 정의서","DOCX","기획(planner)","작성완료","gen_reqspec(100대 기능 원천)"),
("분석","요구사항 추적표(RTM)","XLSX","QA/기획","작성완료","gen_xlsx(RTM 시트)"),
("분석","기획서(PLANNING)","MD","기획(planner)","기보유","docs/PLANNING.md v3.0"),
("설계","개발계획서","DOCX","PMO/개발리드","작성완료","gen_devplan"),
("설계","화면설계서·UI 스펙(design)","MD/DOCX","디자이너","기보유","docs/design.md"),
("설계","아키텍처 설계서(앱/시스템/기술/데이터/네트워크)","MD","아키텍트","기보유","docs/architecture/*"),
("설계","데이터 모델(전사 ERD·BI 데이터마트)","MD/SQL","DA/DB","진행","설계 트랙"),
("설계","API 설계서(계약)","MD","AA/백엔드","기보유","docs/API_GUIDE.md"),
("구현","소스코드(백엔드·프론트·워커)","Git","개발","진행","src/, tools/nanobanana"),
("구현","DB 스키마·마이그레이션","SQL","DB","진행","Flyway"),
("시험","시험계획서","DOCX","QA","작성완료","gen_testplan"),
("시험","시험케이스·시험결과서","XLSX","QA","작성완료","gen_xlsx(시험케이스 시트)"),
("이관","배포/운영 런북·이관 확인서","MD","DevOps","기보유","docs/OPS_RUNBOOK.md"),
("교육","사용자 지침서·운영자 지침서","DOCX/PPTX","기술문서","예정","완성+QA 후"),
("종료","완료(준공) 보고서","DOCX","PMO","예정","사업 종료 시"),
]
# ---------------- 투입 인력 계획(M/M) ----------------
STAFFING = [
("사업 PM/PMO","1","특급","A~E 상시","6.5"),
("아키텍트(AA/SA/TA)","2","특급","A 집중","3.0"),
("데이터 아키텍트(DA)","1","고급","A·D","2.0"),
("네트워크 아키텍트(NA)","1","고급","A·E","1.0"),
("공통/시스템관리 개발","2","중급","B 집중","4.0"),
("백엔드 개발","3","중·고급","B~D","12.0"),
("프론트엔드 개발","3","중·고급","B~E","12.0"),
("DB 엔지니어(PostGIS)","1","고급","B~D","4.5"),
("AI 개발","1","고급","C~D","3.0"),
("시각화(나노바나나)","1","고급","C","2.0"),
("도메인 개발(옥션/관람객/CMS/BI/관리자)","3","중·고급","D 집중","9.0"),
("QA","2","중급","B~E 상시","8.0"),
("DevOps","1","고급","B·E","2.5"),
("디자이너","1","고급","A~E","3.0"),
]
RISKS = [
("R1","홀 실측 CAD 원본 미확보","배치 정밀도","공개 규격 근사 도면으로 진행, 확보 시 대체"),
("R2","이미지 생성(외부) 승인 게이트","M5 시각화","미승인 시 목/degraded, 승인 후 라이브"),
("R3","외부 시스템(작업신고/PG/KT) 연동 미공개","M6/M9/M4b","제출용 파일·릴레이 방식, 협의 후 연동"),
("R4","등록업체 옥션 참여율·응찰 품질","M15 실효성","등록업체 게이트·AI 자료 공정 비교 유인"),
("R5","개인정보(리드·배지) 규정 위반","법적 리스크","동의·보존정책·감사·최소 수집"),
("R6","AI 시각화 오용(계약 서류)","분쟁 리스크","워터마크·고지 강제(UI·응답·PDF)"),
("R7","공통 레이어 지연","일정","Phase B 인력 선집중·병행 착수"),
("R8","규정·요율 개정","정합성","룰셋 버전 관리·과거 버전 보존"),
]
if __name__ == "__main__":
print("modules:", len(modules()))
ph = phases(); print("phases:", [(p[0], len(p[2])) for p in ph])
print("features:", len(features()))
print("sample feature:", features()[0] if features() else None)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
import re, json, io, sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
slides = json.load(open(r"C:\GUARDiA\workspace\kintex\_workspace\proposal\_qa_slides.json", encoding="utf-8"))
# Full REQ universe
UNIV = {}
for pfx, cnt in [("F", 81), ("A", 20), ("N", 21), ("S", 20)]:
for i in range(1, cnt + 1):
UNIV[f"{pfx}-{i:03d}"] = None # slide where covered
# Parse each text line for badge tokens with prefix-context + range + continuation.
# Grammar we support in a run:
# REQ-<P>-<n> single
# REQ-<P>-<n>~<m> range (same prefix)
# ...·<P>-<n> new prefix single continuation
# ...·<P>-<n>~<m> new prefix range continuation
# ...·<n> bare number continues LAST prefix (e.g. A-011·012)
# ...·<n>~<m> bare range continues last prefix (e.g. 067~069)
cover = {} # req -> list of slides
def add(pfx, a, b, sn):
for i in range(a, b + 1):
key = f"{pfx}-{i:03d}"
cover.setdefault(key, set()).add(sn)
# token: optional prefix letter, number, optional ~ number
tok = re.compile(r"(?:REQ-)?(?:([FANS])-)?(\d{2,3})(?:\s*~\s*(?:[FANS]-)?(\d{2,3}))?")
for s in slides:
sn = s["n"]
for t in s["texts"]:
# only scan segments that look like badge context (contain REQ- or letter-number dot chains)
for m in re.finditer(r"REQ-[FANS]-\d{2,3}(?:\s*~\s*\d{2,3})?(?:[·,\s]+(?:[FANS]-)?\d{2,3}(?:\s*~\s*\d{2,3})?)*", t):
seg = m.group(0)
last_pfx = None
for mm in tok.finditer(seg):
p, a, b = mm.group(1), mm.group(2), mm.group(3)
if p:
last_pfx = p
if last_pfx is None:
continue
ai = int(a)
bi = int(b) if b else ai
# sanity: within universe range
add(last_pfx, ai, bi, sn)
missing = sorted(k for k in UNIV if k not in cover)
print("=== RANGE-EXPANDED BADGE COVERAGE ===")
print("COVERED (badge):", len(cover), "/ 142")
print("NOT badge-covered (", len(missing), "):", ", ".join(missing))
# priority 7
prio = ["F-008", "F-010", "F-026", "F-032", "A-003", "A-005", "A-008"]
print("\n=== PRIORITY 7 BADGE STATUS ===")
for r in prio:
print(f"{r}: badge slides = {sorted(cover.get(r, []))}")
# dump per-covered for the previously-flagged 18
flagged = ["F-005","F-008","F-010","F-026","F-032","F-034","F-036","F-038","F-039","F-054","F-074","F-075","F-076","A-003","A-005","A-008","S-005","S-006"]
print("\n=== PREVIOUSLY-FLAGGED 18: badge coverage after range expansion ===")
for r in flagged:
sl = sorted(cover.get(r, []))
print(f"{r}: {'BADGE ' + str(sl) if sl else 'NO BADGE'}")

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
import json, io, sys, re
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
S = json.load(open('_qa_slides.json', encoding='utf-8'))
ROMAN = ["","","","","","","",""]
print("=== PER-SLIDE STRUCTURE (chapter line + first content) ===")
for s in S:
n = s["n"]
chap = ""
for t in s["texts"]:
# chapter header like "Ⅲ. 기술·기능 · ..."
if any(t.startswith(r + ".") or t.startswith(r + " ") for r in ROMAN):
chap = t
break
# headline = the longest early text that's not chapter/boilerplate
head = ""
for t in s["texts"][:6]:
if t == chap: continue
if "킨텍스" in t or "2026-07" in t or t in ROMAN: continue
if len(t) > len(head): head = t
print(f"{n:>3}| {chap[:46]:<46} | {head[:52]}")
print("\n=== 배점 / SCORING TOKENS ===")
pats = ["80/20","35/80","90/10","70+20","90(","기술 90","기술 80","가격 10","가격 20","정성 70","정량 20","재정렬","공고 확정"]
for p in pats:
hits=[(s['n'],t[:80]) for s in S for t in s['texts'] if p in t]
print(f'--- {p!r}: {len(hits)}')
for n,t in hits[:8]: print(f' {n}: {t}')
print("\n=== HONESTY LABELS ===")
for p in ["[검증됨]","[구현 계획]","[협의]","[가정]","[Non-Goal]","[공고"]:
hits=[(s['n'],t[:74]) for s in S for t in s['texts'] if p in t]
print(f'--- {p!r}: {len(hits)}')
for n,t in hits[:14]: print(f' {n}: {t}')
print("\n=== WATERMARK / AI NOTICE ===")
for p in ["워터마크","AI 생성","AI-generated","watermark","합성 이미지","예상 이미지","시공 예"]:
hits=[(s['n'],t[:74]) for s in S for t in s['texts'] if p.lower() in t.lower()]
print(f'--- {p!r}: {len(hits)} : slides {sorted(set(n for n,_ in hits))}')

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,242 @@
# 킨텍스 자동전시시스템 구축 — 제안서 디자인 시스템 (Design System)
> 작성: proposal-visual-designer · 작성일: 2026-07-11 · 버전 v1.0
> 짝 문서: `visual_concepts.md`(도식 12종·표지·간지 시각 컨셉) · `assets/icons/*.svg`(선 아이콘 20종) · `proposal_outline.md`(63슬라이드) · `tech_advisory.md` §1-4(도식 사양)
> 목적: **디자인 템플릿 PPTX 부재** → KINTEX 브랜드 기반 디자인 시스템을 직접 정의. deck-designer(`proposal_deck.pptx` python-pptx)가 이 스펙을 벗어나지 않는다.
> 스타일 정합: 같은 사업 산출물 패밀리(`docs/deliverables/_gen/gen_dev_plan_pptx.py` 개발계획서)와 **동일 팔레트·폰트·16:9·도형 직접 작도** 톤 유지. 제안서는 개발계획서보다 **표지·간지 시각 임팩트를 1단계 상향**(수주 문서 특성).
> 원칙: 시크릿(키·비번·내부IP) 미기재. 과도한 장식 금지 — **평가위원 가독성 우선**. 명료성·위계·일관성 > 장식.
---
## 0. 아트디렉션 한 줄
**"도면이 아니라 사진으로 컨펌한다"** — 전시장의 공간감(블루)과 AI 생성의 지능감(퍼플)을 대비시키되, 본문은 흰 여백과 카드로 **읽기 쉬운 공공 제안서**를 유지한다. 표지·간지·핵심 도식(3중 해자·나노바나나 파이프라인)에만 시각 에너지를 집중하고, 나머지는 절제한다.
---
## 1. 컬러 팔레트
### 1-1. 주조·강조색 (Brand)
| 토큰 | HEX | RGB | 역할 | WCAG(흰 배경 대비) |
|---|---|---|---|---|
| `BLUE` (KINTEX Blue) | **#0066B3** | 0,102,179 | **주조색** — 헤더 액센트바·섹션칩·주요 강조·표 헤더 | 5.3:1 (AA 본문/AAA 대형) |
| `BLUE_DK` (Deep) | **#00447A** | 0,68,122 | 표지/간지 배경·최심층 데이터존·강조 대비 | 8.9:1 (AAA) |
| `BLUE_LT` (Tint) | **#E1EFF9** | 225,239,249 | 블루 계열 카드 배경·밴드 배경·연한 강조 | 배경 전용 |
| `PURPLE` (AI Purple) | **#6D4AFF** | 109,74,255 | **강조색** — AI/나노바나나/지능 요소 전용·간지 포인트·2차 액센트 | 5.0:1 (AA) |
| `PURPLE_DK` | **#4A2FCC** | 74,47,204 | 퍼플 위 텍스트 대비 보강·AI 심층 | 7.4:1 (AAA) |
| `PURPLE_LT` (Tint) | **#ECE8FF** | 236,232,255 | AI 카드 배경·퍼플 밴드 배경 | 배경 전용 |
> **색 규율(불변)**: 퍼플은 **AI·나노바나나·지능·예측** 맥락에만 쓴다(남발 금지). 블루=구조/공간/시스템/신뢰. 이 이원 대비가 "결정적 알고리즘(블루) + AI 보조(퍼플)" 3층 신뢰 프레이밍을 색으로 각인한다.
### 1-2. 중립 그레이 스케일 (Neutral)
| 토큰 | HEX | 명도 단계 | 역할 |
|---|---|---|---|
| `INK` | **#101828** | 950 | 본문 기본 텍스트·제목 |
| `MUTED` | **#667085** | 500 | 보조 텍스트·캡션·부제 | (흰 배경 4.6:1 AA) |
| `SLATE` | **#98A2B3** | 400 | 비활성·예정 상태·눈금·화살표 |
| `LINE` | **#E4E7EC** | 200 | 카드 테두리·구분선 |
| `BG` | **#F9FAFB** | 50 | 본문 슬라이드 페이지 배경 |
| `HALL_BG` | **#F2F4F7** | 100 | 데이터/평면도 영역 배경·표 zebra 짝수행 |
| `WHITE` | **#FFFFFF** | 0 | 카드·본문 배경·다크 배경 위 텍스트 |
### 1-3. 상태색 (Status / Semantic)
| 토큰 | HEX | 의미 | 사용처 |
|---|---|---|---|
| `GREEN` | **#129E63** | 완료·라이브·검증됨·통과 | 간트 완료바·"이미 돈다" 배지·QA 통과·라이브 실적 |
| `AMBER` | **#E08A00** | 진행·주의·중간 리스크 | 간트 진행바·리스크 매트릭스 중위험·확인필요 |
| `RED` | **#D92D20** | 차단·위험·실격 리스크·전기배선 | 등록 게이트 403·고위험·**S6 전기 배선 범례(적)** |
| `CYAN` | **#0BA5EC** | 정보·네트워크 배선 | **S6 네트워크 배선 범례(청)**·정보 배지 |
| `TEAL` | **#0E9384** | 급배수·부가 | **S6 급배수 배선 범례(녹)** — GREEN과 구분 위해 청록 |
> **배선 3색 규약(도식 ⑥ PostGIS 오버레이·④ Before/After 배선 범례 고정)**: 전기=`RED #D92D20`, 네트워크=`CYAN #0BA5EC`, 급배수=`TEAL #0E9384`. tech_advisory §1-4 ⑥의 "적/청/녹"을 색맹 안전(적-청-청록 조합, 명도차 확보)으로 확정. 상태 GREEN(완료)과 배선 TEAL(급배수)을 분리해 혼동 방지.
### 1-4. 접근성·색맹 안전
- 본문 텍스트(INK/MUTED)는 모두 흰/연한 배경에서 WCAG AA 이상.
- 다크 배경(BLUE_DK/BLUE) 위 텍스트는 WHITE 또는 `#C7DDF2`(표지 부제, 대비 확보) 사용.
- 상태·배선은 **색 단독 의존 금지** — 항상 텍스트 라벨/아이콘/패턴 병기(완료=✓·진행=●·차단=✕, 배선=색+실선/파선/점선 스타일 병기).
- 색맹(적록) 안전: 완료/위험을 GREEN/RED만으로 구분하지 않고 아이콘·위치로 이중 인코딩.
---
## 2. 타이포그래피
### 2-1. 폰트 패밀리
| 용도 | 폰트 | 대체(fallback) |
|---|---|---|
| 국문 기본 | **맑은 고딕 (Malgun Gothic)** | Pretendard, 본고딕(Noto Sans KR) |
| 영문·숫자 | 맑은 고딕(동일 지정) | Segoe UI, Arial |
> deck-designer는 개발계획서와 동일하게 `FONT = "맑은 고딕"`을 전 run에 지정하고, `<a:ea typeface="맑은 고딕"/>`(East Asian) 명시로 한글 렌더 일관성 확보. 외부 폰트 임베드 없이 시스템 폰트로 결정론적 생성.
### 2-2. 타입 위계 (16:9 · pt 기준)
| 위계 | 크기(pt) | 굵기 | 색 | 자간·행간 | 사용처 |
|---|---|---|---|---|---|
| 표지 대제목 (Display) | **44** | Bold | WHITE | line 1.02 | 표지 사업명 2줄 |
| 간지 넘버 (Section No.) | **150** | Bold | BLUE_DK(음영) | — | 간지 배경 대형 숫자 |
| 간지 제목 (Divider Title) | **34** | Bold | WHITE | — | 간지 섹션 국문명 |
| 본문 슬라이드 제목 (H1) | **22** | Bold | INK | line 1.0 | content_header 타이틀 |
| 소제목 (H2) | **13~14** | Bold | BLUE/PURPLE | — | 카드 헤더·블록 제목 |
| 강조 배너 (Lead) | **16~18** | Bold | WHITE/INK | line 1.0 | 비전 배너·핵심 문장 |
| 본문 (Body) | **10.5~11** | Regular | INK | line 1.0 | 불릿·설명 |
| 표 셀 (Table) | **8.5~10** | Regular/Bold | INK/MUTED | line 0.92 | 표 내용 |
| 캡션·부제 (Caption) | **8~9.5** | Regular | MUTED | — | 도표 캡션·부가설명 |
| 킥커 (Kicker) | **11** | Bold | PURPLE | — | 헤더 상단 섹션 라벨(대문자 영문 병기) |
| 배지·칩 (Chip) | **8~10** | Bold | WHITE | — | 배점 배지·상태칩·우선순위 |
| 지표 수치 (Metric) | **26~46** | Bold | WHITE/BLUE | — | 기대효과·통계 대형 숫자 |
| 푸터 (Footer) | **8.5** | Regular | MUTED | — | 슬라이드 하단 |
> **본문 최소 크기 10pt 하한**(투사 가독성). 표 셀만 예외적으로 8.5pt까지 허용하되 정보 밀도 높은 표에 한정. 캡션 8pt 하한.
---
## 3. 그리드·여백 (16:9)
### 3-1. 캔버스
- 슬라이드: **13.333 in × 7.5 in** (12192000 × 6858000 EMU), 와이드 16:9.
- deck-designer 좌표계는 개발계획서와 동일 `inch(v)=v*914400` 헬퍼 사용.
### 3-2. 안전 마진·그리드
| 항목 | 값(in) | 비고 |
|---|---|---|
| 좌우 마진 | **0.55** | 콘텐츠 좌측단=0.55, 우측단=12.78 (폭 12.23) |
| 상단 (본문) | 헤더 하단 구분선 **1.28**, 콘텐츠 시작 **1.45~1.5** | content_header 아래 |
| 하단 | 푸터 **7.06**, 콘텐츠 하한 **6.9** | |
| 상단 액센트바 | y=0, 높이 **0.12**, BLUE | 전 본문 공통 |
| 컬럼 그리드 | **12 컬럼** 개념 (실전: 2·3·4·5·6분할) | 카드 폭=(12.23 - gap×(n-1))/n |
| 표준 gutter | **0.16~0.35** | 카드 간격 |
### 3-3. 정렬·화이트스페이스 원칙
- **좌측 정렬 기본**. 중앙 정렬은 칩·배지·지표 수치·간지 한정.
- 카드는 항상 `LINE` 테두리 1pt + 라운드(ROUNDED_RECTANGLE) + 옅은 그림자(alpha 18%). 그림자는 카드·주요 노드에만(남발 금지).
- **한 슬라이드 1 메시지** — 도식 1개 또는 카드 2~4개 + 하단 캡션 1줄. 밀도 상한 준수(P3 나노바나나 슬라이드도 도식+캡션+방어배지까지).
- 여백은 위계 신호 — 섹션 간 0.3in 이상 공백 확보. 꽉 채우지 않는다.
---
## 4. 슬라이드 4종 레이아웃 규격
### 4-1. 표지 (Cover) — S01
- 배경: `BLUE_DK` 풀블리드 + 우측 겹침 평행사변형 2매(`BLUE` 큰 것, `PURPLE` 작은 것)로 대각 에너지. **여기에 나노바나나 시공 예측 히어로 이미지(워터마크)를 우측 평행사변형 영역에 반투명 합성**(개발계획서 대비 상향 포인트, `visual_concepts.md` §표지 참조).
- 좌상: 원형 브랜드 마크(WHITE 원+PURPLE 코어) + "KINTEX · WISE AI" 15pt WHITE Bold.
- 문서종류 배지 2개: "기술제안서"(PURPLE) + "Technical Proposal"(딥블루).
- 대제목: 사업명 2줄 44pt WHITE Bold. 그 아래 슬로건 배너 강조.
- 하단 메타바: 사업명·기술스택·작성일 (라인 구분).
- **슬로건 각인**: "신청서를 내는 순간, 시공 후 사진을 먼저 본다." — 부제로 대형 배치.
### 4-2. 간지 (Section Divider) — 각 P1~P9 앞
- 배경: `BLUE_DK` 풀블리드 + 우측 평행사변형 2매(BLUE/PURPLE).
- 좌측: 대형 섹션 넘버 **150pt**(BLUE_DK보다 살짝 밝은 `#2C4E82` 음영톤) + PURPLE 언더바 + 국문 제목 34pt + 영문 13pt.
- 우측: 해당 섹션 핵심 포인트 3개(PURPLE 원 불릿 + WHITE 12.5pt).
- **배점 배지**: 간지 우상단에 해당 섹션 평가배점 배지(예 "P3 · 기술제안 35점")를 추가 — 배점 규율 시각화(개발계획서엔 없던 제안서 전용 요소).
### 4-3. 본문 (Content) — 표준
- `content_header(kicker, title, idx)`: 상단 액센트바(BLUE 0.12) + 섹션번호 칩(라운드, BLUE) + 킥커(PURPLE 11pt) + 제목(INK 22pt) + 구분선(1.28) + 푸터.
- 콘텐츠 영역: y 1.45~6.9. 카드/표/도식 배치.
- 하단 **캡션 1줄** 필수(도식 슬라이드) — MUTED 9pt, tech_advisory 각 도식 "캡션" 텍스트 그대로.
- REQ 배지(선택): 우상단에 해당 슬라이드 대응 REQ-ID 소형 배지(회색) — RTM 정합 가시화.
### 4-4. 도식 (Diagram) — 핵심 12종
- 본문 헤더 동일 + 콘텐츠 전체를 단일 도식에 할애.
- 도형은 **python-pptx 직접 작도**(외부 이미지 금지, 개발계획서 원칙 계승). 노드=ROUNDED_RECTANGLE, 연결=화살표 텍스트("→"/"") 또는 얇은 라인 rect, 아이콘=본 시스템 SVG(단색 stroke, 슬라이드에선 도형 근사 또는 SVG를 EMF/PNG 없이 도형으로 재현).
- 각 도식 상세는 `visual_concepts.md`에 좌표·색·라벨 수준으로 명세.
---
## 5. 컴포넌트 스타일
| 컴포넌트 | 규칙 |
|---|---|
| **카드 (Card)** | ROUNDED_RECTANGLE, fill WHITE, line LINE 1pt, 그림자(alpha 18%, blur 90k, dist 38k). 헤더 있으면 상단 컬러 바(0.07~0.5) 또는 좌측 컬러 스트립(0.08~0.09). |
| **섹션 넘버 칩** | 0.62×0.62 라운드, BLUE(또는 PURPLE), WHITE 20pt Bold 중앙. |
| **배점 배지** | 라운드칩, fill=섹션색, WHITE 8~10pt Bold. "20점"/"35점" 등. 배점 큰 P3는 PURPLE 강조. |
| **상태 칩** | 라운드, 완료=GREEN·진행=AMBER·예정=SLATE·차단=RED. 텍스트+색 이중. |
| **불릿** | 작은 OVAL(0.09~0.11) 컬러 점 + 텍스트. 컬러=맥락색(블루/퍼플/상태). 이모지 불릿 금지 — **선 아이콘 또는 도형 점만**. |
| **콜아웃/배너** | 풀폭 라운드 rect, fill=BLUE(비전)/INK(권한구조)/PURPLE(AI강조), WHITE Bold 중앙. |
| **번호 배지** | 작은 OVAL, fill=색, WHITE 8pt Bold 숫자. 프로세스 스텝. |
| **프로세스 스텝** | 카드 나열 + 사이 화살표("" 16pt SLATE). 좌→우 흐름. |
| **표 (Table)** | 헤더=BLUE 풀폭 라운드바 WHITE Bold, 본문 zebra(짝수행 `#FCFCFD`/HALL_BG), 행 구분 LINE 0.01, 셀 8.5~10pt. 강조행(낙찰 등)=BLUE_LT + Bold. |
| **아이콘** | 본 시스템 `assets/icons/*.svg` — 선(stroke) 스타일, 24×24, stroke-width 1.75, currentColor. 슬라이드 내 색=맥락색. |
---
## 6. 차트 스타일 (BI 도식 · S38·S39·S61)
- 라이브러리 톤: Recharts 목업 재현(막대·라인·도넛·KPI 카드).
- **시퀀셜(단일 계열)**: BLUE 계열 명도 그라데이션(#00447A→#0066B3→#4A90D9→#9CC3E8).
- **카테고리(다계열)**: BLUE / PURPLE / TEAL / AMBER / SLATE 순환(최대 5, 색맹 안전 순서). 6번째부터 명도 변주.
- **비교(Before/After·As-Is/To-Be)**: As-Is=SLATE(회색, 과거), To-Be=BLUE(현재/개선). 개선 화살표=PURPLE.
- **강조 수치**: 대형 Metric(26~46pt) + 단위 소형. KPI 카드는 BLUE/PURPLE 교차 fill + WHITE 수치.
- 축·그리드: SLATE 얇은 선, 라벨 8pt MUTED. 범례는 색+텍스트+마커 형태 병기.
- 도넛/파이: 최대 5조각, 나머지 "기타" 회색 통합. 중앙에 총계 수치.
---
## 7. 아이콘 시스템 (선 스타일 SVG — 직접 제작)
### 7-1. 공통 제작 규격 (전 20종 통일)
- **viewBox**: `0 0 24 24`
- **fill**: `none` (면 채움 절대 금지)
- **stroke**: `currentColor` (슬라이드에서 맥락색 상속)
- **stroke-width**: `1.75` (전 아이콘 동일 시각 무게)
- **stroke-linecap**: `round`, **stroke-linejoin**: `round`
- **그리드 정렬**: 24 그리드, 여백 2(콘텐츠 영역 2~22), 코너 반경 통일감.
- **외부 라이브러리·폰트아이콘·온라인 생성 금지** — 전량 직접 패스 작도.
- 한 세트 통일 메타포: 기하 단순·동일 디테일 밀도(오버피팅 금지).
### 7-2. 아이콘 20종 (최소 16종 요구 → 20종 제작)
| # | 파일명 | 의미 | 주 사용 슬라이드 |
|---|---|---|---|
| 1 | `ai-sparkle.svg` | AI/스파클/지능 | 나노바나나·AI 전 슬라이드·간지 |
| 2 | `booth-layout.svg` | 부스/레이아웃/배치 | M2 플로어플랜·모듈맵 |
| 3 | `wiring-power.svg` | 배선/전기/유틸리티 | M4 배선·아키텍처 |
| 4 | `camera-render.svg` | 카메라/렌더링/시각화 | 나노바나나 샷세트·표지 |
| 5 | `auction-gavel.svg` | 옥션/경매 망치 | M15 옥션 폐루프 |
| 6 | `visitor-badge.svg` | 관람객/배지 | M10 관람·등록 |
| 7 | `shield-security.svg` | 보안/방패 | P5 보안·불변원칙 |
| 8 | `watermark.svg` | 워터마크 | AI 이미지 보안·워터마크 |
| 9 | `tenant-building.svg` | 테넌트/전시관/빌딩 | 멀티테넌시·확장 |
| 10 | `schedule-gantt.svg` | 일정/간트 | P8 일정·마일스톤 |
| 11 | `org-people.svg` | 조직/사람 | 추진체계도·페르소나 |
| 12 | `check-verify.svg` | 체크/검증/규정 | 규정검증·QA·커버리지 |
| 13 | `dashboard-chart.svg` | 대시보드/차트/BI | M16 경영분석·기대효과 |
| 14 | `mobile.svg` | 모바일/반응형 | 현장·모바일·접근성 |
| 15 | `document-rtm.svg` | 문서/서류/RTM | M6 서류·RTM·커버리지 |
| 16 | `risk-warning.svg` | 리스크/경고 | 리스크 매트릭스·확인필요 |
| 17 | `postgis-spatial.svg` | 공간데이터/PostGIS/폴리곤 | 단일 공간원천·배선 |
| 18 | `pipeline-flow.svg` | 파이프라인/큐/비동기 | 나노바나나 파이프라인·아키텍처 |
| 19 | `network-zone.svg` | 망분리/보안영역 | 보안영역 분리도 |
| 20 | `closed-loop.svg` | 폐루프/순환 | 생애주기 폐루프·옥션 |
> 16종 요구 대비 **20종 제작**(공간데이터·파이프라인·망분리·폐루프 4종 추가 — 킨텍스 도식 핵심 메타포 보강).
### 7-3. deck-designer 아이콘 사용 지침
- python-pptx는 SVG 직접 임베드가 어려우므로, 각 아이콘은 **(a) 슬라이드 헤더/칩 옆 시각 앵커**로 쓸 때는 대응 도형(원+선) 근사 작도, **(b) 아이콘 자체가 콘텐츠일 때**는 SVG를 사전 PNG 변환(cairosvg/inkscape) 후 삽입 가능. 변환 시 stroke 색=맥락색으로 렌더.
- 색 상속: 아이콘 stroke는 배치 맥락색(블루=구조, 퍼플=AI, 상태색=상태)으로 지정.
---
## 8. deck-designer 전달 요약 (준수 계약)
1. **팔레트 상수**를 §1 표 그대로 코드 상단에 정의(개발계획서 `gen_dev_plan_pptx.py`와 동일 변수명 재사용 + PURPLE_DK·RED·CYAN·TEAL 추가).
2. **폰트** 맑은 고딕 전 run + East Asian 지정.
3. **레이아웃 4종**(§4) 헬퍼 함수화: `cover()`, `divider(no,kr,en,points,score)`, `content_header()`, 도식별 함수.
4. **12종 도식 + 표준 3종**(추진체계·리스크·기대효과)은 `visual_concepts.md` 좌표·라벨대로 작도. 재해석 금지.
5. **색 규율**: 퍼플=AI 전용, 배선 3색 고정(적/청/청록), 상태색 의미 고정.
6. **가독성 우선**: 본문 10pt 하한, 한 슬라이드 1메시지, 캡션 1줄, 여백 확보.
7. **보안**: 시크릿·내부IP 도식/라벨에 미기재. 외부 API는 "승인 예외"로만 표기.

View File

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
"""
Stitch 화면 스크린샷 다운로드 + 매니페스트 생성
- MCP list_screens 결과(제목SCR/모듈/그룹 매핑) screenshot.downloadUrl을 assets/screens/ 저장
- screens_manifest.json 갱신(파일명title·scr·module·group·device·order)
- build_deck.py assets/screens/ + manifest 재스캔해 갤러리 자동 재구성
재실행: 화면이 생기면 MCP 재조회 SCREENS 갱신하여 다시 실행.
"""
import os, json, urllib.request, ssl
BASE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(BASE, "assets", "screens")
os.makedirs(OUT, exist_ok=True)
# title -> (scr, module, korean_name, group, device)
META = {
"Login & Workspace Selection - KINTEX AI System": ("SCR-01","공통","로그인 & 행사 워크스페이스 선택","공통·인증","DESKTOP"),
"Organizer Dashboard - KINTEX AI System": ("SCR-02","M1","주최자 대시보드","주최자","DESKTOP"),
"Booth Layout Editor - KINTEX AI System": ("SCR-03","M2","부스 배치 에디터","주최자","DESKTOP"),
"Layout Comparison - KINTEX AI System": ("SCR-04","M2","배치안 비교(S7 조감)","주최자","DESKTOP"),
"Exhibition Schedule - KINTEX AI System (Updated Icons)": ("SCR-15","M1·M6","전시 일정 관리","주최자","DESKTOP"),
"Exhibitor Home - KINTEX AI System": ("SCR-05","M1","참가업체 부스 홈","참가업체","DESKTOP"),
"Booth Design Studio - KINTEX AI System": ("SCR-06","M3","부스 설계 스튜디오","참가업체","DESKTOP"),
"Utility Wiring View - KINTEX AI System": ("SCR-07","M4","유틸리티 배선 뷰","참가업체","DESKTOP"),
"Utility Order Summary - KINTEX AI System": ("SCR-08","M4","유틸리티 신청 요약·위치표시도","참가업체","DESKTOP"),
"Compliance Report - KINTEX AI System": ("SCR-09","M2","규정 검증 리포트","장치업체","DESKTOP"),
"Manager Approval Queue - KINTEX AI System": ("SCR-10","M6","홀매니저 승인 큐","홀매니저·운영","DESKTOP"),
"Review Details - KINTEX AI System": ("SCR-11","M6","홀매니저 검수 상세","홀매니저·운영","DESKTOP"),
"Hall Operations - KINTEX AI System (Updated Icons)": ("SCR-14","M14","홀 현장운영 대시보드","홀매니저·운영","DESKTOP"),
"Visualization Gallery - KINTEX AI System": ("SCR-12","M5","시각화 갤러리","시각화·전역","DESKTOP"),
"Business Intelligence - KINTEX AI System (Updated Icons)": ("SCR-13","M16","경영분석 대시보드","경영분석","DESKTOP"),
"Admin Dashboard - KINTEX AI System (Updated Icons)": ("SCR-16","M18","관리자 대시보드","관리자 백오피스","DESKTOP"),
"Component Guide - KINTEX AI System (Updated Icons)": ("SCR-17","디자인","컴포넌트 가이드(스타일가이드)","스타일가이드","DESKTOP"),
"Contractor On-site Checklist - KINTEX AI System": ("SCR-M1","현장","시공업체 현장 체크리스트","모바일 현장","MOBILE"),
"Manager Field Inspection - KINTEX AI System": ("SCR-M2","현장","홀매니저 현장 검수","모바일 현장","MOBILE"),
}
# title -> screenshot downloadUrl (from MCP list_screens; 최신 조회분)
URLS = {
"Login & Workspace Selection - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLtUqdYm2030_UAU-An_JAmVNGl2eTumZ5vNnz9T5WeXoyFc7pvkO4RQPoHLdsFqg4shnjDOxWNOMGVPtMLH8A_SUNvd7GFISwRGX1vZ8hMkZIsiU4DPDg-6qV6EpX1j0qHjpu9zHPyWtO_6nWcRDGaOsweZTkjvZ2UeTD0GgyqCETLufFh69ZQvlcuOpM0VSH7spqLDwPq-hpoWme6S_WBSg9b6ygHB3lUvfkOgs5wWtPOgIcZ6bCn1ZB8",
"Organizer Dashboard - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLtAyvwUEakGIhFdDB_6cQqB8DzBPnb7TdrSUBehA1gVEzNKg-ppC3sF1LcgF_vr9bceJ3voOhAcUcC5CYbSiPb0IjMoHHpwfCqm5rqI9vTqHjdvIpg69oMO09Rx_wF0k_XCY6P9doDNj8SaW5TjcsbuvGT4NHO-5uBDeT65_MF-ZTdRZ_jF2mugRi0KqBS5dnAvnJhQH-UFdIO3v_Wtaj8hkXkvS--sth05JgI1hNMUZJrjHGiROzBGxw",
"Booth Layout Editor - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLvOGbKVWZuHy5ALELagNRlBkIwl8byuXEe1LcPgKuP7o5XaRZrsE6UTVJC821aK01hdrs19oson_NDDMnoo7nqU6kgu5NgJJdxK6cdF5lmzr2jziNxWEwHDPoibYcIeAFsCaIZy49a6mYUzrY2ZhQDqAT0AZ3YV8Cfq_dMkuyaGaHvKRBt_1cbRRFa0MgZsN9l1Ed6w6GPKLjKF7Sw3fOpdYxTwhLz6BrpIBBkNYsk97t7nzA9Hv2mTHw",
"Layout Comparison - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLvUxlOFlFr0MwnaD8KN10xT9gsFq0_wyilol_aWYLj2Ev_kHhNKJ5PHug_6KwEemQtsQjZDEP8zEPpkdZvUq-TTOubFN3NStmGaIlf1-GvpPzq5uLMGrneKXsp7ccThjFjq556VQhhr2WHlyqlSEhtx37cd0i9Iggo3GfPwcNYNUMd6kizoWzKTy_LMvTjhMAdMGwblLvMN1bYDhoQ75Aty2eSwf92NSzsLccQPk5EGw_GicpZRenkeDQ",
"Exhibition Schedule - KINTEX AI System (Updated Icons)":"https://lh3.googleusercontent.com/aida/AP1WRLthlJCkcQzW168tISpRl5BVhGWN6aZ-dTxxpnJYLqrWUQ132vn-w0x23XA_y8JF7e-q28ZqqJy_Z9tqc2J_OK99-RPYpT7nbFmoD2H-NC7MBHRp7RSegRK-hE5WKT3r8MNtFPG1PvcDIdvSiNPqy_YYcdOCYDvjwVZAPmhgBSiWFyZTZjB6Plwq6et0F5SIZQThhmK_lFcy0K9Jcodwk2YiCcIET47cTYj02BpEieNO5RK7gj6fsL3OonI",
"Exhibitor Home - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLvzY0N-4aD4E4apccqfR3yNCRkuFKEOc4O4uJIbkaHQQrDi-gDwKS5pFJPEvcAKBfrGgi10XY32J6VSfgiX_AzVomJDuoinMjWzTi_XSXSzJgravcI-ABUo-ep_p9rdNP2lX7T01WqML0uhOYGD2jlhjhusVpqsYBYsRmFhi6NhQMYqUZdBH_ndL9YW6g6wAOObXXVGKgMUBr0d-YHI0FyhKM0uAi1CYQ2QQiNDSJtZdtOHS6yn44MwUhs",
"Booth Design Studio - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLu1TWc00TspuC4jpGfgrvt9_kRQIWg84Zr4FW1l_YVePzu5jTqkYR7UmO1Xw9nuydukCbwIh3OiOqpedk9Tt4fwrTQkqrxJEZYNmM3dr-GRhvbYhO2UFrmlJzhdMeFtXuTKnv3nTqzWyTgBySTbXYncfySbTSzC06dleCMKamOHEgGI0M60g_9LBT-STycoHohAFWuUY2Z4XIlkN0gJJ0B0P3y9JTEX7S3RTiiO8RNhAoBskY2ZjQId7TI",
"Utility Wiring View - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLt0_fkmB3ACojGDXezlhbpya-s-re7WgfEAXim10VWfAMNWet_cpx3addY23p2Kzcd-1NXVbgmQh-5tGokg19E0ui7H5yU2IVbU28GOquQu-ngiRv6CpAcMtQfe-zjzfrSOA1DT2mT-zQTUYd068i6HwJJv5734uU4hFuvLU0xCExlfe8kxnp5Ct8IfC11Aoh_jLvPMCqsMmOux0mtYRf4Sp4hEaBfJr1BMwIX4dAlvpTV-PJxiJ_M5Ymo",
"Utility Order Summary - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLtyOGoy4l69iClGRq0bO8uTbAjZx54sF890NtTmAKPztU6OwKtfsUDZ8c_KcynLP1btwWj1VMXUu0MHKCx75n5gCNpxuRRpwAgdoQM6Mxzwt-TuWLyAhM4CW7MXKJEpVVA0vQmXjqV6hM177yZ0kTJ6aN0u2C-mvzELug_hDensEwivvFZn4OHfK1UF3HY7GxuPLZB4smGXiMqHH3vRGD1feNV1USWxfTGq0G5JhIu79TI1mMyTbmrtvg4",
"Compliance Report - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLuh3R_GPiSwqoywspJfxh9c8o-4qNRFGsRQJ6hNtS4OyvqArAB3AVT4ZDkQrJjZPutTFFG0k7JNr_egmfBAfUn_LjOO5AB-eqTA3MoAdGxmqEa7qThF7hCIz2lfKInkL4N-GSrNdZtBnJMb95f9sRUDzNaLk-wQZZcUv2ee6Uo1CvWEu5mdYScTal09vaepqghDUi2Td8oxzTjQ_dcRGUHDcZoncSuvPg2fPhHECW99CiTwD3RMU2es470",
"Manager Approval Queue - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLt0jyggUFD_uIa47eZ71yDp7_r-yDJxUvGJcxuBhlHcjPsilp_Su2XDR17WyNcxPWdd5t_6nJCIkd9JjblfA8ENMW2QvuszG7RawM33gvFJG5uS8_JFoctdLz8Oq1WDAcdJH3ObRgv3EDDfqFY9LXvAnzKA8d2qsR31fz5ulTfulgUyCUdckbSdheECcc1lrtx4TLwafiwUggI1y8zDNVHtOcJuw4cG9OWqxMxreCghnJ3D4YthwnWUAQ",
"Review Details - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLtZn9UwuYCpWjWC9ZlUKYKZO6-8LWF92u8BZhUpgNrIjPQVa2NiS6zv5Zz9k2NQSPYCWdj2OQBRpB7u1hNi3AmMvObMtvvewMBeeDrDInB22eFS6DcbuT_Goa5bsxMTIWp0jx5PKR2rGZtp-78nHn2NNi7p96d_2ddXJ3eBtwAq-NRFL7jbuFbGWrIW0wFkJ8ThMazQ33UxOFsLmD_PXCrnWyt_NcNO-ans631jlKL-VdL_FfKBNDaxylw",
"Hall Operations - KINTEX AI System (Updated Icons)":"https://lh3.googleusercontent.com/aida/AP1WRLtVuCoXA2RksHSQq2vVewDWWsoduCoY9CD0n9rVvsD-ShiGsQCPBCFJWakyIjNNPqF9GASb3u5rs3bqHSUd_kk15d_014FfzpYuiWyrjTleULMsFDsnhaW9n-jpO7_ayRndbZWh8zBjpZ2zVuUReCYRFP_BVwr2O3RYyKCEVXcY6l6dzMywa3EozvEhV87GSS-CxKhm4cBb5RcI7j_5j4IgFj9RYxDzq89agjhTxjsWA9DIx5lq32BSof0",
"Visualization Gallery - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLuuyZPo--qWn5qMTYI0fWcrBj1LuMzudztIvctYqof9RweLOLVb1Xu7a-xTUB615pRKZp-AAOPQZgRcqQUN6bT_XIyykoLT4yFQXXOxMqIXI-FulW61CdiBOfo6U43Z9YI37oXm_aFtV9PsEgPrsjj3aIzNYxxtSAkOJzVx-G215-uAVdcikNeYoPvnSkgoBP5OjzR5futn48YmVMNsEmbVXe0YVIZbwQOvlIa31TDNPiw2xzJyvdGUwrg",
"Business Intelligence - KINTEX AI System (Updated Icons)":"https://lh3.googleusercontent.com/aida/AP1WRLubNXF75G8McZ1gryw7VDigsKOl63B9nuVSi6_tJCXAYF4ZIhEuJlNfptxhTWSxP5CwN5cUWxHVnnviC-qO8pxChg_Xf5GpImd-UTXb_JIdqzGT2dtr0qkNN15FqPMvk4WenixdezsLNsU4moxl72lOoqJvxHi0xuBp4SmRzJNTd1Z7XK29ycdk-80LCfvPBHZrbT2iGZGreshY63kpwu4dlZarjt_xQO1qqTm_ZYkpIM79xJPMT-jMNyQ",
"Admin Dashboard - KINTEX AI System (Updated Icons)":"https://lh3.googleusercontent.com/aida/AP1WRLu3zdb3wXg6Dvq77ymBp4iT7wU7uo9G5t8cfb5X2_TZr0XNtST2AnmFqsrZO56E1p1Xz6xbQesdd_59tfcpsoUMkB5WO1fja-HFLK06JtFeGkZxitnG-87qCgauSPphlsjOtSHaEiZmPfbg5yh-a5PelIKe2bHjM-FmX4hm7_UItkgaC-FoEBEfsNwHnALPBbWvz6u8ZZhuXceh8G0V_SSH0ks9t75boEooOPg15xEJ4KszZHdlb2OFCl0",
"Component Guide - KINTEX AI System (Updated Icons)":"https://lh3.googleusercontent.com/aida/AP1WRLsLW88myjTGHETfGth2ywdFnjE3y692dNnxaDKncS_DwVuqegjWfznVhrKE8VXJLSWkmIiqREk13m-FKKAK7nwJGAtUX2r7f54KrcEd0Na2B-xFqEqzQovlfFi2tbxletF3WKJUqNGp_IHkvlghOymIz7IBUOeA5UABGGqfJdSBGIso2Fqh1jX5Pu0LEfP1HsfL167lTgS4t1oe94hDp9QC-MQgD2U-KOYdoUaZ_WINLtFeZ5-3Uvo05YI",
"Contractor On-site Checklist - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLtA_bsgM_D2rk4HbtM-snlKWWvXP8Lm4dGEvLQcGFgf6tFBkHXiK5fiT-T2UCzS46RkEwV8GDjM3sHFRfh65PQ4Ssnj-p5DVhZO0JCx3_jmTDumyl3kYbL5ssEU15psKPJ8tU_yX-MlUelq-Yhh6lgkGYqbTFUy8aQYD5M3F-FUAKQLUjSTb_SqHHg4dae9RqB6otbfl5Y0KiA4QDkAURFWBgJnJeOmuONOr-r6BTKS4ZPmu7DnAXQPYK0",
"Manager Field Inspection - KINTEX AI System":"https://lh3.googleusercontent.com/aida/AP1WRLtStbjv9lr4kBFrhu9LuUu7yXXGrFg_8HKk4DPRjSpxcslk4GPz85moG6zJzCQrt_tVWRi255BLH9NKp8wH-W_kBcPEy9_T4xVBPbYkytnT6oHIMW5HXz0OAkhq1Izk-EI7Xj_-FfAhKUpjgAXhg_7sGZMrkD6sXgVk57W6-fZNmN-OqYY2872tL-JK_EgVAdY7joxaUoqVOizVZB2Mw_ElFBRTFCPz3iO8UTwhQs2q55OYSqP-n0in7pc",
}
# 갤러리 그룹 순서
GROUP_ORDER = ["공통·인증","주최자","참가업체","장치업체","홀매니저·운영","시각화·전역","경영분석","관리자 백오피스","스타일가이드","모바일 현장"]
def scr_num(scr):
s = scr.replace("SCR-","")
if s.startswith("M"): return 100+int(s[1:])
return int(s.split("·")[0]) if s[0].isdigit() else 999
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def slug(scr, kor):
return scr.replace("·","_") + ".png"
manifest = {}
ok = fail = 0
for title, meta in META.items():
scr, module, kor, group, device = meta
url = URLS.get(title)
fn = slug(scr, kor)
fp = os.path.join(OUT, fn)
if url:
try:
req = urllib.request.Request(url, headers={"User-Agent":"Mozilla/5.0"})
data = urllib.request.urlopen(req, context=ctx, timeout=60).read()
with open(fp, "wb") as f:
f.write(data)
ok += 1
except Exception as e:
print("FAIL", scr, title, repr(e)[:80]); fail += 1
continue
elif not os.path.exists(fp):
print("SKIP(no url, no file)", scr); continue
manifest[fn] = {"scr":scr,"module":module,"name":kor,"group":group,"device":device,
"title":title,"order":scr_num(scr)}
with open(os.path.join(OUT, "screens_manifest.json"), "w", encoding="utf-8") as f:
json.dump({"group_order":GROUP_ORDER,"screens":manifest}, f, ensure_ascii=False, indent=1)
print(f"DOWNLOADED ok={ok} fail={fail} manifest={len(manifest)} -> {OUT}")

View File

@ -0,0 +1,342 @@
# 나라장터(g2b) 정보시스템 구축 부문 — 표준 제안서 목차·평가배점 조사 및 킨텍스 제안서 재편 매핑
> 작성: rfp-analyst · 작성일: 2026-07-12 · 목적: 킨텍스 제안서(P1~P9, 63슬라이드) 목차 재편 근거
> 원칙: **확인된 1차 출처(법령·실제 RFP)만 사실로 기재**, 그 외는 [가정]/[통례] 표기. 시크릿 미기재.
> 대상 제안서 파일은 **읽기만**(수정 금지) — 본 문서는 매핑·권고만 산출.
> 짝 문서: `proposal_outline.md`(현행 P1~P9) · `rfp_analysis.md`(§4 배점 가정) · `tech_advisory.md`(§5 WBS·§6 SM) · `rtm.md`
---
## 0. 핵심 결론 (요약)
1. **표준 목차의 단일 출처**는 조달청 「협상에 의한 계약 제안서평가 세부기준」 **[별지 제5호서식] 제안서 서식(예시: 소프트웨어개발·유지관리사업)** — 7개 대장( 일반현황 ~ Ⅶ 그 밖의 사항)이다. 이 목차가 **평가부문 = 제안서 장(章)**으로 1:1 정렬된다.
2. **정성평가 평가항목의 단일 출처**는 같은 세부기준 **[별표 3의2] 소프트웨어 및 시스템 개발사업** — "정보시스템 구축"에 해당하는 표준 평가표. 부문: **전략 및 방법론 / 수행계획(기술·기능) / 수행기반 / 프로젝트 관리 / 프로젝트 지원 / 하도급 / 필수 제안**.
3. **배점은 세부기준이 고정하지 않는다.** 제9조⑤에 따라 **수요기관이 항목별 배점한도를 결정**(항목당 30점 초과 금지, 하도급 5점 이상, 국산제품 활용 2점 이내). 정보화사업 총 배점은 「행정기관·공공기관 정보시스템 구축·운영 지침」 **제18조**에 따라 **기술 90(정성 70 + 정량 20) / 가격 10**(원칙), HW 비중 50% 이상 사업은 **기술 80 / 가격 20** 허용.
4. **현행 킨텍스 제안서의 80/20 배점 가정(rfp_analysis §4)은 정보화 표준(90/10)과 다르다** — 실제 공고 확인 전까지 [가정]이며, SW 중심 사업이면 90/10이 표준. (본 문서는 지적만, proposal 파일 수정 없음.)
5. 현행 63슬라이드는 **표준 7대장에 전부 매핑 가능**하나, **신설이 필요한 장 4개**( 일반현황 / Ⅴ·Ⅵ 프로젝트 관리·지원 보강 / 상생협력·하도급 / 정량평가 대응)가 있다. 미배치 슬라이드 0.
---
## 1. 조달청 기술평가 표준 항목·배점 (출처·연도 명시)
### 1-1. 전체 배점 체계 (법령 확정 사실)
| 구분 | 배점 | 출처(조항) |
|---|---|---|
| 종합 = 기술능력평가 + 입찰가격평가 | 100 | 세부기준 제9조① |
| 기술능력평가 | **80점** (세부기준 원칙) / 정보화사업은 **90점**(지침 제18조) | 세부기준 제9조①1 · 지침 제18조 |
| 입찰가격평가 | 20점(원칙) / 정보화 10점 | 세부기준 제9조①2 |
| ─ 정성평가 | (기술 90 기준) **70점** | 실제 RFP 표본 §1 |
| ─ 정량평가 | **20점 이내**(총 배점한도) | 세부기준 제9조④ |
| HW 비중 50%↑ 예외 | 기술 80 / 가격 20 허용 | 지침 제18조 |
> **출처 정본**: 조달청 「협상에 의한 계약 제안서평가 세부기준」(조달청지침, 최신 개정 **2024. 9. 6. 시행**; 2017. 6. 1. 최초 시행). 정보화 총배점은 「행정기관 및 공공기관 정보시스템 구축·운영 지침」(행정안전부고시 제2024-66호, **2024. 8. 28. 시행**) 제18조.
### 1-2. 정성평가 표준 평가표 — [별표 3의2] 소프트웨어 및 시스템 개발사업 (정보시스템 구축 표준)
> **세부기준은 각 세부항목에 고정 점수를 부여하지 않는다**(수요기관이 배점한도 결정). 아래는 **평가항목 ↔ 세부평가항목 구조**(법령 원문 그대로). 배점 예시는 §1-3 실제 RFP 표본 참조.
| 평가항목(부문) | 세부평가항목 | 채점 관점(평가위원이 무엇을 보는가) |
|---|---|---|
| **전략 및 방법론** | 사업이해도 | 사업 목표·특성 부합, 구체성·적절성. (기획용역 ISP 수행자 참여 시 1등급 하향) |
| | 추진전략 | 수행 일정·위험요소 고려한 추진전략 타당성 |
| | 추진체계 | 수행조직(전문업체·기술지원·공동수급체·하도급) 역할·책임·품질확보·협력 방안 |
| | 사업추진방법론 | 방법론 구체성·적절성, 적용사례·경험, 단계별 산출물 제시 |
| **수행계획**(≈기술 및 기능) | 시스템 요구사항 | 장비 규격 충족·인터페이스·확장성·공급/설치·유지관리 |
| | 국산제품 활용 기여도 | 국산장비 도입방안 적절성·국내기업 지원 (배점 **2점 이내**, 국내입찰 한정) |
| | 기능 요구사항 | 기능·기대·제약 대비 구현방안 구체성·실현가능성 |
| | 성능 요구사항 | 성능충족 구현·테스트·방법론·분석도구 |
| | 인터페이스 요구사항 | 타 시스템 연계 방안·사용자 UI 분석/설계/구현/테스트 |
| | 유지관리 정책 | 제조사 유지관리(기간·비용·부품·보증) |
| **수행기반** | 적용기술 | 적용 기술의 확장·실현 가능성 |
| | 개발환경 | 장비·작업장소 등 인프라 준비·대응 |
| | 보안 요구사항 | 기술적 보안 구현(설계→구현→검증 단계 반영) |
| | 제약사항 | 기술·표준(**표준 프레임워크 적용 포함**)·언어·방법론·법제도·안전관리 제약 대응 |
| **프로젝트 관리** | 일정관리 | 수행기간·세부일정·단계별 산출물 연계 |
| | 품질관리 | 품질관리 범위·절차·점검, 품질보증 인증 근거 |
| | 기밀보안관리 | 물리적·관리적 보안 체계·대책 |
| | 위험 및 이슈 관리 | 위험·이슈 식별·분석·절차·대응 |
| **프로젝트 지원** | 인수인계 | 인계 전략·검사 대상/방법·충족조건·인계계획 |
| | 교육훈련 | 운영자·관리자·사용자 교육 내용·방법·기간·인원·횟수 |
| | 기술지원 | 지원 범위·수준·기술 매뉴얼 |
| | 하자보수 계획 | 하자담보 기간 내 하자보수 범위·조치절차·대응 |
| **하도급** | 하도급 계획의 적정성 | 하도급 금액/계약금액 비율 적정성 (배점 **5점 이상 필수**; **20억원 이상 SW사업은 제외 불가**) |
| **필수 제안** | (수요기관 지정) | 제안요청 필수사항 반영 여부 |
### 1-3. 정량평가 항목 (별표 8~14, 총 20점 이내)
| 정량 항목 | 출처 | 비고 |
|---|---|---|
| 경영상태 | 별표8 | 기업신용평가 |
| 수행실적 | 별표9 | 동일·유사 사업 실적 |
| 책임성·성실성 | 별표10 | |
| **상생협력** | **별표11** | 중소기업 공동수급체 참여 지분율 — 50%↑=5.0점 … 35%미만=1.0점 (**최대 5점**). 중소기업 단독=최고등급, 참여지분 0=0점 |
| 상용SW 유지관리 하도급금액 적정성 | 별표12 | |
| SW기술자료 임치 | 별표13 | |
| 정량 필수제안 | 별표14 | |
### 1-4. 실제 RFP 배점 표본 (구체 숫자 예시) — g2b R25BK00879799 (정보시스템 구축, HW 포함형, 30p 이내)
> 표본은 90/10(기술 90+가격 10) 채택. 부문 세분은 수요기관 재량임을 실증.
| 구분 | 평가항목 | 배점 |
|---|---|---|
| 정량(20) | 보유인력 5 · 경영상태 5 · 수행실적 5 · 사회적책임 3 · 지리적거리 2 | 20 |
| 정성(70) | 사업의 이해도(10) · 시스템 구성(20) · 시스템 기능(20) · 운영방안(10) · 사업관리 및 지원(10) | 70 |
| 가격(10) | 입찰가격평가 | 10 |
---
## 2. 정보시스템 부문 표준 제안서 목차 (장·절 구조)
### 2-1. 표준 목차 정본 — [별지 제5호서식] 제안서 서식(예시: 소프트웨어개발·유지관리사업)
> 출처: 세부기준 별지 제5호서식(법령 원문). **평가부문과 장 구조가 일치**하도록 설계됨. 각 절 옆은 대응 평가항목(별표 3의2).
```
. 일반현황
1. 입찰자 일반현황 (일반현황·주요 연혁·자본금·매출액)
2. 해당 사업의 수행조직 및 업무분장 → [정성] 추진체계 / [정량] 경영·실적
3. 해당 사업의 핵심투입인력 및 이력사항 → [정량] 보유인력
Ⅱ. 전략 및 방법론
1. 사업 이해도 → 전략및방법론·사업이해도
2. 추진전략 → 추진전략·추진체계
3. 적용기술 → 수행기반·적용기술
4. 표준 프레임워크 적용 → 수행기반·제약사항(표준FW)
5. 개발 방법론 → 사업추진방법론
Ⅲ. 기술 및 기능
1. 시스템 장비구성 요구사항 → 수행계획·시스템 요구사항
2. 기능 요구사항 → 수행계획·기능 요구사항
3. 보안 요구사항 → 수행기반·보안 요구사항
4. 데이터 요구사항 → (기능/데이터)
5. 시스템 운영 요구사항
6. 제약사항 → 수행기반·제약사항
Ⅳ. 성능 및 품질
1. 성능 요구사항 → 수행계획·성능 요구사항
2. 품질 요구사항 → 프로젝트관리·품질관리
3. 인터페이스 요구사항 → 수행계획·인터페이스 요구사항
4. 테스트 요구사항
. 프로젝트 관리
1. 관리방법론
2. 관리역량
3. 일정계획 → 프로젝트관리·일정관리
4. 개발장비
Ⅵ. 프로젝트 지원
1. 품질보증 → 프로젝트관리·품질관리
2. 시험운영
3. 교육훈련 → 프로젝트지원·교육훈련
4. 유지보수 → 프로젝트지원·기술지원/하자보수
5. 기밀보안 → 프로젝트관리·기밀보안관리
6. 비상대책
Ⅶ. 그 밖의 사항 → 기대효과·확장·상생협력 등
```
### 2-2. 실제 RFP 표본의 목차 요구 (간소형 예시) — g2b R25BK00879799 (30p 이내)
```
. 사업의 이해 : 과제수행목표·제안개요·제안사현황·추진체계및전략·제안의특징및장점
Ⅱ. 시스템 관련사항: 총괄시스템구성전략·기능별시스템구성및기능·성능구현 사전운영방안·향후활용방안
Ⅲ. 사업관리 및 지원: 사업관리(보고·일정·위험)·시스템지원방안(교육·기술지원·유지보수)
Ⅳ. 첨부자료
※ "발주처 요청 외 제안사가 중요하다고 판단한 목차·내용은 별도 기술하고 근거자료 첨부"
```
> **판독**: 소형·HW형 RFP는 별지 5호서식을 3~4장으로 압축한다. **대형 SW 구축**은 별지 5호서식 7대장을 그대로 쓰는 경향. 킨텍스 규모(전 전시 파이프라인)는 **7대장 채택 권장**.
### 2-3. 장별 "무엇을 담는가" (평가위원 채점 관점 요약)
| 장 | 담을 내용 | 채점 포인트 |
|---|---|---|
| 일반현황 | 제안사 개요·실적·재무, 수행조직도·R&R, 핵심인력 이력 | 정량(보유인력·경영·실적) 근거 + 조직 신뢰 |
| Ⅱ 전략·방법론 | 사업이해도, 추진전략/체계, 적용기술, 표준FW, 개발방법론 | "우리가 사업을 제대로 이해했고, 검증된 방법으로 한다"는 확신. **차별화(Win Theme) 메시지의 1차 노출 지점** |
| Ⅲ 기술·기능 | 요구기능 구현방안, 시스템 구성, 보안·데이터, 제약 대응 | 요구사항 대비 구현 구체성·실현가능성. **최다 배점 통례 → 기능 차별화 총력** |
| Ⅳ 성능·품질 | 성능충족·테스트, 품질보증, 인터페이스, 가용성/SLA | 정량 목표치·검증방법 제시 |
| 프로젝트 관리 | 관리방법론·역량, 일정계획, 위험·이슈, 품질관리 | 일정 준수·리스크 통제 실행력 |
| Ⅵ 프로젝트 지원 | 인수인계, 교육, 기술지원, **유지보수(SM)**, 기밀보안, 비상대책 | 구축 후 지속가능성·이관·운영 |
| Ⅶ 그 밖의 사항 | 기대효과, 확장, 상생협력·하도급, 부가제안 | 정성 가점·정책부합 |
---
## 3. 재편 매핑표 — 현행 킨텍스 P1~P9 (63슬라이드) → 표준 7대장
> 규칙: 각 슬라이드를 표준 장·절로 이동. **차별화(나노바나나·AI 폐루프)는 배점 높은 Ⅲ(기술·기능) + Ⅱ(전략·방법론) 접점에 배치**해 노출 극대화. 미배치 0.
### 3-1. 전면부 (표지·요약)
| 현행 | 표준 배치 | 비고 |
|---|---|---|
| S01 표지 | 표지 | 유지 |
| S02 제안 개요 | ** 일반현황 §1 앞 or 제안개요 간지** | 제안사 소개는 Ⅰ으로 흡수 |
| S03 목차 | 목차 | 7대장 인덱스로 재구성 |
| S04 제안 핵심요약(3중 해자) | **Ⅱ 전략·방법론 §2 추진전략** 도입(요약 간지) | Win Theme 조기 각인 |
| S05 배점 대응 맵 | 목차 직후 간지 | 표준 배점(90/10)으로 갱신 권고 |
| S06 [가정] 고지 간지 | 부록 or 서두 노트 | 유지 |
### 3-2. P1 사업이해 → Ⅱ 전략·방법론 §1
| 현행 | 표준 배치 |
|---|---|
| S07 사업 배경·목적 | **Ⅱ §1 사업 이해도** |
| S08 As-Is 프로세스 병목 | **Ⅱ §1 사업 이해도** |
| S09 이해관계자·페르소나 | **Ⅱ §1 사업 이해도** |
| S10 To-Be 목표(정량) | **Ⅱ §1 사업 이해도** (→ 효과 지표는 Ⅶ와 연계) |
### 3-3. P2 추진전략 → Ⅱ 전략·방법론 §2·§5 +
| 현행 | 표준 배치 |
|---|---|
| S11 비전·포지셔닝 | **Ⅱ §2 추진전략** |
| S12 3중 해자 전략 | **Ⅱ §2 추진전략** (차별화 앵커) |
| S13 Win Theme 5선 | **Ⅱ §2 추진전략** (배점 정렬 배지 병기) |
| S14 전시 생애주기 폐루프 | **Ⅱ §5 개발 방법론**(생애주기 방법론) |
| S15 추진체계·수행조직 | ** 일반현황 §2 수행조직·업무분장** (+ Ⅱ §2 추진체계 교차) |
### 3-4. P3 기술제안 (25슬라이드) → Ⅲ 기술·기능 중심 + Ⅱ 적용기술 + Ⅳ 일부
> **핵심 전략**: 나노바나나·AI 폐루프는 **Ⅱ §3 적용기술**(방법론·차별화 관점)과 **Ⅲ §2 기능 요구사항**(구현 관점) **두 장에 걸쳐 노출** → 전략및방법론·기술및기능 양쪽 배점 동시 획득.
| 현행 | 표준 배치 |
|---|---|
| S16 전체 모듈맵 | **Ⅲ 기술·기능 도입부**(기능 조망) |
| **S17~S23 나노바나나 시공예측(7)** | **Ⅱ §3 적용기술**(S17 개요·S19 구조보존·S20 파이프라인·S23 AI신뢰) + **Ⅲ §2 기능 요구사항**(S18 샷세트·S21 Before/After·S22 RenderJob 방어). **7슬라이드 절대 유지** |
| S24 플로어플랜 스튜디오(M2) | **Ⅲ §2 기능 요구사항** (+ 적용기술 교차) |
| S25 배치 규정 자동검증 | **Ⅲ §2 기능 요구사항** |
| S26 부스 설계 스튜디오(M3) | **Ⅲ §2 기능 요구사항** |
| S27 유틸리티 설계(M4)·PostGIS 배선 | **Ⅲ §2 기능 요구사항** (+ Ⅲ §4 데이터 요구사항 교차) |
| S28 위치표시도·유틸리티 신청 | **Ⅲ §2 기능 요구사항** |
| S29 옥션 개요·폐루프 | **Ⅲ §2 기능 요구사항** (차별화 폐루프) |
| S30 역경매·실시간 순위 | **Ⅲ §2 기능 요구사항** |
| S31 종합평가 낙찰 | **Ⅲ §2 기능 요구사항** |
| S32 등록 게이트·발주 전환 | **Ⅲ §2 기능 요구사항** (+ Ⅲ §3 보안 교차) |
| S33 판매·홀배정·견적(M1) | **Ⅲ §2 기능 요구사항** |
| S34 서류·마일스톤(M6) | **Ⅲ §2 기능 요구사항** (+ Ⅲ §5 시스템 운영 교차) |
| S35 관람객 등록·배지·리드(M10) | **Ⅲ §2 기능 요구사항** |
| S36 비즈매칭·마케팅·공개사이트(M11·12·17) | **Ⅲ §2 기능 요구사항** |
| S37 정산·결제(M9) | **Ⅲ §2 기능 요구사항** |
| S38 경영분석 BI-1(M16) | **Ⅲ §2 기능 요구사항** (+ Ⅲ §4 데이터 교차) |
| S39 경영분석 BI-2(M16) | **Ⅲ §2 기능 요구사항** |
| S40 현장운영·물류(M8·13·14) | **Ⅲ §2 기능 요구사항** |
### 3-5. P4 아키텍처 → Ⅱ §3·§4 + Ⅲ §1·§4·§5
| 현행 | 표준 배치 |
|---|---|
| S41 전체 아키텍처 | **Ⅲ §1 시스템 장비구성 요구사항** (+ Ⅱ §3 적용기술) |
| S42 단일 공간 데이터 원천(PostGIS) | **Ⅲ §4 데이터 요구사항** |
| S43 확장성·비동기 | **Ⅳ §1 성능 요구사항** (+ Ⅶ 확장 연계) |
| S44 AI 플랫폼 라우팅(Claude→Ollama) | **Ⅱ §3 적용기술** |
| S45 연동·마스터데이터 | **Ⅳ §3 인터페이스 요구사항** (+ Ⅲ §5 시스템 운영) |
### 3-6. P5 보안·개인정보 → Ⅲ §3 + Ⅴ·Ⅵ 기밀보안
> 표준은 **기술적 보안 = Ⅲ §3 보안 요구사항**, **관리적 기밀보안 = 기밀보안관리 / Ⅵ §5 기밀보안**으로 분리. 현행 P5를 분할 배치.
| 현행 | 표준 배치 |
|---|---|
| S46 보안 개요·불변 원칙 | **Ⅲ §3 보안 요구사항** (개요) |
| S47 보안영역 분리(망 구성) | **Ⅲ §3 보안 요구사항** (+ Ⅲ §1 구성) |
| S48 인증·접근통제(2FA·RBAC) | **Ⅲ §3 보안 요구사항** |
| S49 AI·이미지 보안(워터마크) | **Ⅲ §3 보안 요구사항** (차별화 보안) |
| S50 개인정보·감사·안전코딩 | **Ⅲ §3 보안 요구사항** + ** §? 기밀보안관리 / Ⅵ §5 기밀보안** (관리적 요소 이관) |
### 3-7. P6 비기능 → Ⅳ 성능·품질
| 현행 | 표준 배치 |
|---|---|
| S51 성능·가용성 SLA | **Ⅳ §1 성능 요구사항** |
| S52 나노바나나 성능 | **Ⅳ §1 성능 요구사항** |
| S53 접근성·다국어·테마·반응형 | **Ⅳ §2 품질 요구사항** |
| S54 관측성·데이터 | **Ⅳ §2 품질 요구사항** (+ 관리 연계) |
### 3-8. P7 멀티테넌시·확장 → Ⅲ §5 + Ⅶ
| 현행 | 표준 배치 |
|---|---|
| S55 멀티테넌트 SaaS 비전 | **Ⅶ 그 밖의 사항**(확장 비전) |
| S56 테넌트 격리 아키텍처 | **Ⅲ §5 시스템 운영 요구사항** (+ Ⅲ §3 보안 격리) |
| S57 온보딩·2계층 관리자 | **Ⅲ §5 시스템 운영 요구사항** |
### 3-9. P8 사업관리·일정·조직 → 프로젝트 관리 + Ⅵ
| 현행 | 표준 배치 |
|---|---|
| S58 추진 일정·Phase | ** §3 일정계획** |
| S59 선행 게이트·리스크 관리 | ** §1 관리방법론 / 위험·이슈 관리** |
| S60 품질·형상관리 | ** §2 관리역량(품질관리)** + **Ⅵ §1 품질보증** |
### 3-10. P9 기대효과 → Ⅶ 그 밖의 사항
| 현행 | 표준 배치 |
|---|---|
| S61 정량 기대효과 | **Ⅶ 그 밖의 사항** |
| S62 정성·전략 효과 | **Ⅶ 그 밖의 사항** |
| S63 확장 비전·마무리 | **Ⅶ 그 밖의 사항** |
### 3-11. 매핑 커버리지 요약
| 표준 장 | 배치된 현행 슬라이드 | 소계 |
|---|---|---|
| 표지·목차 | S01·S03·S05·S06 | 4 |
| 일반현황 | S02·S15 (+ **신설 필요**) | 2 |
| Ⅱ 전략·방법론 | S04·S07~S14·S17·S19·S20·S23·S41(교차)·S44 | 14 |
| Ⅲ 기술·기능 | S16·S18·S21·S22·S24~S40·S42·S45(교차)·S46~S50·S56·S57 | 34 |
| Ⅳ 성능·품질 | S43·S51~S54 | 5 |
| 프로젝트 관리 | S58·S59·S60 | 3 |
| Ⅵ 프로젝트 지원 | S50(관리적 일부)·S60(품질보증) (+ **대폭 신설 필요**) | ~1 |
| Ⅶ 그 밖의 사항 | S55·S61·S62·S63 | 4 |
| **미배치** | — | **0** |
---
## 4. 표준 목차 채택 시 신설 필요 장(章)과 내용 소스
> 현행 63슬라이드가 **표준 대비 취약하거나 부재**한 영역. 실제 공고 시 감점·실격 방지를 위해 신설.
| 신설/보강 대상 | 이유 | 내용 소스(지목) |
|---|---|---|
| ** 일반현황 (신설)** | 제안사 일반현황·연혁·자본금·매출, 수행조직도·R&R, **핵심투입인력 이력**은 정량평가(보유인력·경영·실적) 근거. 현행에 사실상 부재(S02·S15 부분). | `tech_advisory §5-2` 직군별 M/M 산정 → 투입인력표. 제안사 실적은 발주 확인 후 확보(⚠확인필요) |
| **Ⅵ 프로젝트 지원 (대폭 신설)** | 인수인계·교육훈련·기술지원·**유지보수(SM)**·비상대책은 표준 정성 부문. 현행 P8이 품질/형상만 얇게 다룸. | `tech_advisory §6 SM 운영 체계`(SLA·장애대응·정기점검·CI/CD 운영·ITSM 프로세스·조직 R&R §6-1) · §5-1 WBS(SM 이관·인수인계) |
| ** 프로젝트 관리 보강** | 표준은 관리방법론·관리역량·위험/이슈를 독립 부문으로 배점. 현행 배점 2점 가정으로 과소. | `tech_advisory §5 WBS·공수` · `proposal_strategy §4 리스크` |
| **상생협력·하도급 (신설)** | 정량 별표11(중소기업 참여지분율, 최대 5점)·정성 하도급 계획 적정성(5점 이상). **20억↑ SW사업은 하도급 평가 제외 불가**. 현행 없음. | 공동수급/중소기업 참여 계획·하도급 계획서(별지 제14호서식) — 컨소시엄 구성 확정 후(⚠확인필요) |
| **정량평가 대응 절 (신설)** | 보유인력·경영상태·수행실적·사회적책임은 필수 증빙 미제출 시 최하점. 별첨 근거 필요. | 일반현황 + 별첨(증빙) — 발주 확인 후 |
| **표준 프레임워크 적용 절 (Ⅱ §4)** | 별표3의2 제약사항에 "표준 프레임워크 적용 포함" 명시. 공공 SW 필수 관점. | 확정 스택(Spring Boot/React) — 전자정부 표준FW 적용 여부는 ⚠확인필요(현재 스택은 GUARDiA 표준) |
---
## 5. 재편 권고 (Win Theme 노출 극대화)
1. **7대장 채택**: 킨텍스 규모(전 전시 생애주기)는 별지 5호서식 7대장 구조가 정합. 현행 P1~P9를 §3 매핑대로 재배열.
2. **나노바나나(S17~S23) 이중 노출**: Ⅱ §3 적용기술(방법론·차별화)과 Ⅲ §2 기능 요구사항(구현) 양쪽에 배치 → **전략및방법론 + 기술및기능 배점 동시 겨냥**. 7슬라이드 축소 금지.
3. **AI 폐루프(배치·설계·배선·옥션 S24~S32)**: Ⅲ §2 기능 요구사항에 집중 배치(최다 배점 장). 폐루프 차별화 메시지를 기능 채점표에 직접 정렬.
4. **보안 분할**: 기술적 보안 → Ⅲ §3, 관리적 기밀보안 → Ⅴ·Ⅵ. 표준 채점 구조에 맞춰 배점 누수 방지.
5. **Ⅵ 프로젝트 지원 신설**로 "구축 후 지속가능성"(SM·교육·이관) 확보 — 공공 평가위원 필수 확인 항목.
6. **배점 표기 갱신**: 현행 80/20 가정 → 정보화 표준 90/10(정성70+정량20)로 갱신 권고(실제 공고 확인 시 확정). *proposal 파일 수정은 별도 지시 필요 — 본 문서는 근거만 제공.*
---
## 6. 출처 URL 전체 목록
**1차 출처 (법령·실제 문서 — 본문 근거)**
- 조달청 협상에 의한 계약 제안서평가 세부기준 (국가법령정보센터): https://law.go.kr/LSW/admRulLsInfoP.do?admRulSeq=2100000088849
- 조달청 협상에 의한 계약 제안서평가 세부기준 PDF (별표3의2·별표11·별지5호서식 원문 추출): https://bidform.co.kr/updata/notifile/ab689c32028cee2185e44dcd24c0fb89.pdf
- 실제 나라장터 정보시스템 구축 RFP 제안서 작성요령(배점·목차 표본, g2b R25BK00879799): https://www.g2b.go.kr/pn/pnp/pnpe/UntyAtchFile/downloadFile.do?bidPbancNo=R25BK00879799&bidPbancOrd=000&fileType=&fileSeq=3
- 행정기관 및 공공기관 정보시스템 구축·운영 지침 제18조(평가배점) (국가법령정보센터): https://www.law.go.kr/LSW//admRulInfoP.do?admRulSeq=2100000246404&chrClsCd=010201
- 정보시스템 구축·운영 지침 제18조(평가배점) — 국민건강보험공단 법령DB: https://www.nhis.or.kr/lm/lmxsrv/law/joHistoryContent.do?SEQ=1541&SEQ_CONTENTS=4250027&DATE_START=20240828&DATE_END=20190823
**참고 출처 (통례·가이드)**
- KOSA 소프트웨어 사업 가이드북 v3.0: https://www.sw.or.kr/upload/common/027d281c-2a7a-4886-b96b-e9ad04c7a309.pdf
- 공공 SW사업 제안요청서 작성 예시(SW 법제도 반영): https://smallake.kr/wp-content/uploads/2023/03/붙임공공SW사업-제안요청서-작성-예시.pdf
- NIPA SW사업 단계별 발주 가이드: https://www.nipa.kr/home/2-7-1-1/7089
- 조달청 협상에 의한 계약 제안서평가 세부기준(2026.1.26. 시행 안내, 도로교통공단): https://www.koroad.or.kr/main/board/22/305400/board_view.do
---
## 7. 확인 필요 (삭제 금지)
| # | 항목 | 사유 |
|---|---|---|
| T1 | 실제 킨텍스 공고의 배점·목차 강제 여부 | 본 문서는 표준·표본 근거. 실제 공고가 별지 5호서식과 다른 목차를 강제할 수 있음 |
| T2 | 정보화(SW중심) vs HW중심 판정 | 90/10 vs 80/20 결정. 킨텍스 시스템은 SW 중심 → 90/10 추정, 공고 확인 필요 |
| T3 | 컨소시엄·중소기업 참여·하도급 구성 | 상생협력(별표11)·하도급(별표3의2) 대응은 컨소시엄 확정 후 작성 |
| T4 | 전자정부 표준프레임워크 적용 요구 여부 | 별표3의2 제약사항에 표준FW 명시 — 현행 스택(GUARDiA 표준)과 정합성 공고 확인 |
| T5 | 사업금액 20억원 이상 여부 | 20억↑이면 하도급 평가항목 제외 불가(세부기준 제9조⑤ 단서) |

View File

@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
"""
경량 SVG(stroke) -> PNG 래스터라이저 (cairo 불필요, PIL만 사용)
- assets/icons/*.svg (viewBox 0 0 24 24, fill none, stroke currentColor, stroke-width 1.75, round cap/join)
- 지원: <path d> M/m L/l H/h V/v C/c S/s A/a Z/z, <circle>, <rect rx transform=rotate>
- 렌더: 고배율 supersample LANCZOS 다운스케일, 라운드 /조인 = 두꺼운 라인 + 정점
- : stroke=맥락색(RGB). fill 없음( 아이콘).
재사용: from icon_raster import render_icon; render_icon('ai-sparkle', (r,g,b), out_png, px=192)
"""
import os, re, math
from PIL import Image, ImageDraw
ICON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "icons")
_num = re.compile(r'[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?')
def _nums(s):
return [float(x) for x in _num.findall(s)]
# ---- cubic / arc flattening ----
def _cubic(p0, p1, p2, p3, n=18):
pts = []
for i in range(1, n+1):
t = i/n; mt = 1-t
x = mt**3*p0[0] + 3*mt*mt*t*p1[0] + 3*mt*t*t*p2[0] + t**3*p3[0]
y = mt**3*p0[1] + 3*mt*mt*t*p1[1] + 3*mt*t*t*p2[1] + t**3*p3[1]
pts.append((x, y))
return pts
def _arc(p0, rx, ry, phi, large, sweep, p1, n=24):
# endpoint -> center parameterization (SVG spec)
if rx == 0 or ry == 0:
return [p1]
phi = math.radians(phi)
cosp, sinp = math.cos(phi), math.sin(phi)
dx = (p0[0]-p1[0])/2.0; dy = (p0[1]-p1[1])/2.0
x1p = cosp*dx + sinp*dy; y1p = -sinp*dx + cosp*dy
rx, ry = abs(rx), abs(ry)
lam = x1p*x1p/(rx*rx) + y1p*y1p/(ry*ry)
if lam > 1:
s = math.sqrt(lam); rx *= s; ry *= s
num = rx*rx*ry*ry - rx*rx*y1p*y1p - ry*ry*x1p*x1p
den = rx*rx*y1p*y1p + ry*ry*x1p*x1p
co = math.sqrt(max(0.0, num/den)) if den else 0.0
if large == sweep:
co = -co
cxp = co*rx*y1p/ry; cyp = -co*ry*x1p/rx
cx = cosp*cxp - sinp*cyp + (p0[0]+p1[0])/2.0
cy = sinp*cxp + cosp*cyp + (p0[1]+p1[1])/2.0
def ang(ux, uy, vx, vy):
d = math.sqrt((ux*ux+uy*uy)*(vx*vx+vy*vy))
c = max(-1.0, min(1.0, (ux*vx+uy*vy)/d)) if d else 1.0
a = math.acos(c)
if ux*vy - uy*vx < 0: a = -a
return a
th1 = ang(1, 0, (x1p-cxp)/rx, (y1p-cyp)/ry)
dth = ang((x1p-cxp)/rx, (y1p-cyp)/ry, (-x1p-cxp)/rx, (-y1p-cyp)/ry)
if not sweep and dth > 0: dth -= 2*math.pi
if sweep and dth < 0: dth += 2*math.pi
pts = []
for i in range(1, n+1):
th = th1 + dth*i/n
x = cosp*rx*math.cos(th) - sinp*ry*math.sin(th) + cx
y = sinp*rx*math.cos(th) + cosp*ry*math.sin(th) + cy
pts.append((x, y))
return pts
# flag-aware arc arg regex: rx ry rot large sweep(single 0/1) x y (repeatable)
_NN = r'[-+]?(?:\d*\.\d+|\d+\.?)'
_ARC = re.compile(
r'(' + _NN + r')[,\s]*(' + _NN + r')[,\s]*(' + _NN + r')[,\s]*'
r'([01])[,\s]*([01])[,\s]*(' + _NN + r')[,\s]*(' + _NN + r')')
def _parse_path(d):
"""반환: list of subpaths, each subpath = list of (x,y) polyline points."""
chunks = re.findall(r'([MmLlHhVvCcSsAaZz])([^MmLlHhVvCcSsAaZz]*)', d)
subs = []; cur = []
x = y = 0.0; sx = sy = 0.0
prev_ctrl = None; prev_cmd = None
for cmd, arg in chunks:
rel = cmd.islower(); c = cmd.upper()
if c == 'A':
for m in _ARC.finditer(arg):
rx, ry, rot, large, sweep, nx, ny = (float(m.group(k)) for k in range(1, 8))
if rel: nx += x; ny += y
cur += _arc((x, y), rx, ry, rot, int(large), int(sweep), (nx, ny))
x, y = nx, ny
prev_cmd = c; prev_ctrl = None; continue
vals = _nums(arg)
if c == 'Z':
cur.append((sx, sy)); x, y = sx, sy
subs.append(cur); cur = []
prev_cmd = c; prev_ctrl = None; continue
j = 0
first_pair = True
while j < len(vals) or (c in ('M', 'L', 'C', 'S', 'H', 'V') and False):
if c == 'M':
nx, ny = vals[j], vals[j+1]; j += 2
if rel: nx += x; ny += y
if first_pair:
if cur: subs.append(cur)
cur = [(nx, ny)]; sx, sy = nx, ny
else:
cur.append((nx, ny))
x, y = nx, ny; first_pair = False
elif c == 'L':
nx, ny = vals[j], vals[j+1]; j += 2
if rel: nx += x; ny += y
cur.append((nx, ny)); x, y = nx, ny
elif c == 'H':
nx = vals[j]; j += 1
if rel: nx += x
cur.append((nx, y)); x = nx
elif c == 'V':
ny = vals[j]; j += 1
if rel: ny += y
cur.append((x, ny)); y = ny
elif c == 'C':
x1, y1, x2, y2, nx, ny = vals[j:j+6]; j += 6
if rel: x1+=x; y1+=y; x2+=x; y2+=y; nx+=x; ny+=y
cur += _cubic((x, y), (x1, y1), (x2, y2), (nx, ny))
prev_ctrl = (x2, y2); x, y = nx, ny
elif c == 'S':
x2, y2, nx, ny = vals[j:j+4]; j += 4
if rel: x2+=x; y2+=y; nx+=x; ny+=y
if prev_cmd in ('C', 'S') and prev_ctrl:
x1 = 2*x - prev_ctrl[0]; y1 = 2*y - prev_ctrl[1]
else:
x1, y1 = x, y
cur += _cubic((x, y), (x1, y1), (x2, y2), (nx, ny))
prev_ctrl = (x2, y2); x, y = nx, ny
else:
break
if j >= len(vals):
break
prev_cmd = c
if c not in ('C', 'S'):
prev_ctrl = None
if cur: subs.append(cur)
return subs
def _circle_pts(cx, cy, r, n=48):
return [(cx + r*math.cos(2*math.pi*i/n), cy + r*math.sin(2*math.pi*i/n)) for i in range(n+1)]
def _rect_pts(x, y, w, h, rx=0, transform=None, n=6):
rx = min(rx, w/2, h/2) if rx else 0
pts = []
if rx <= 0:
pts = [(x, y), (x+w, y), (x+w, y+h), (x, y+h), (x, y)]
else:
def arc(cx, cy, a0, a1):
return [(cx+rx*math.cos(math.radians(a)), cy+rx*math.sin(math.radians(a)))
for a in [a0 + (a1-a0)*k/n for k in range(n+1)]]
pts += [(x+rx, y)]
pts += [(x+w-rx, y)]
pts += arc(x+w-rx, y+rx, -90, 0)
pts += [(x+w, y+h-rx)]
pts += arc(x+w-rx, y+h-rx, 0, 90)
pts += [(x+rx, y+h)]
pts += arc(x+rx, y+h-rx, 90, 180)
pts += [(x, y+rx)]
pts += arc(x+rx, y+rx, 180, 270)
if transform:
m = re.match(r'rotate\(\s*([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)\s*\)', transform)
if m:
ang = math.radians(float(m.group(1))); ox = float(m.group(2)); oy = float(m.group(3))
ca, sa = math.cos(ang), math.sin(ang)
pts = [((px-ox)*ca-(py-oy)*sa+ox, (px-ox)*sa+(py-oy)*ca+oy) for px, py in pts]
return pts
def _extract(svg_text):
"""SVG -> list of polylines (in 24-unit viewBox coords)."""
polys = []
for m in re.finditer(r'<path[^>]*\bd="([^"]+)"', svg_text):
polys += _parse_path(m.group(1))
for m in re.finditer(r'<circle[^>]*>', svg_text):
a = m.group(0)
cx = re.search(r'cx="([-\d.]+)"', a); cy = re.search(r'cy="([-\d.]+)"', a); r = re.search(r'r="([-\d.]+)"', a)
if cx and cy and r:
polys.append(_circle_pts(float(cx.group(1)), float(cy.group(1)), float(r.group(1))))
for m in re.finditer(r'<rect[^>]*>', svg_text):
a = m.group(0)
def g(k, d=0.0):
mm = re.search(k+r'="([-\d.]+)"', a); return float(mm.group(1)) if mm else d
tr = re.search(r'transform="([^"]+)"', a)
polys.append(_rect_pts(g('x'), g('y'), g('width'), g('height'), g('rx', 0),
tr.group(1) if tr else None))
return polys
_cache = {}
def render_icon(name, rgb, out_png, px=192, stroke_w=1.75, pad=1.0):
"""name: 파일명(확장자 무관). rgb: (r,g,b) 0-255. out_png 저장. 이미 있으면 재사용."""
key = (name, tuple(rgb), px)
if key in _cache and os.path.exists(out_png):
return out_png
path = os.path.join(ICON_DIR, name if name.endswith('.svg') else name+'.svg')
with open(path, 'r', encoding='utf-8') as f:
svg = f.read()
polys = _extract(svg)
SS = 4 # supersample
W = px*SS
scale = W/24.0
img = Image.new('RGBA', (W, W), (0, 0, 0, 0))
dr = ImageDraw.Draw(img)
col = (int(rgb[0]), int(rgb[1]), int(rgb[2]), 255)
lw = max(1, int(round(stroke_w*scale)))
rcap = lw/2.0
for poly in polys:
if len(poly) < 2:
continue
sp = [(px_*scale, py_*scale) for px_, py_ in poly]
dr.line(sp, fill=col, width=lw, joint='curve')
# round caps/joins via dots at each vertex
for (vx, vy) in sp:
dr.ellipse([vx-rcap, vy-rcap, vx+rcap, vy+rcap], fill=col)
img = img.resize((px, px), Image.LANCZOS)
img.save(out_png)
_cache[key] = out_png
return out_png
if __name__ == "__main__":
# 테스트: 20종 x 1색 몽타주
outdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_iconpng")
os.makedirs(outdir, exist_ok=True)
names = sorted(f[:-4] for f in os.listdir(ICON_DIR) if f.endswith('.svg'))
cols = 5
cell = 96
mont = Image.new('RGBA', (cols*cell, ((len(names)+cols-1)//cols)*cell), (255, 255, 255, 255))
from PIL import ImageDraw as _ID
md = _ID.Draw(mont)
for i, nm in enumerate(names):
outp = os.path.join(outdir, nm+".png")
render_icon(nm, (0x00, 0x66, 0xB3), outp, px=72)
ic = Image.open(outp)
cx = (i % cols)*cell + 12; cy = (i//cols)*cell + 6
mont.alpha_composite(ic, (cx, cy))
md.text((cx, cy+74), nm[:14], fill=(60, 60, 60))
mont.convert('RGB').save(os.path.join(outdir, "_montage.png"))
print("OK", len(names), "icons ->", os.path.join(outdir, "_montage.png"))

View File

@ -0,0 +1,998 @@
# 킨텍스 자동전시시스템 구축 — 제안서 본문 (Proposal Content · 슬라이드 단위)
> 작성: proposal-writer · 작성일: 2026-07-11 · 버전 v1.0
> 입력: `proposal_outline.md`(63슬라이드 배점 가중 목차) · `proposal_strategy.md`(Win Theme 5·리스크 R-1~R-8) · `tech_advisory.md`(§0 라이브 실적/구현 계획 구분·도식 12종 사양·NFR 21·보안 20·공수 ~150M/M·SM) · `rfp_analysis.md`+`rtm.md`(REQ 142)
> 산출 규격: 슬라이드별 [번호·섹션·헤드라인(3초 메시지)·본문 불릿(근거)·도표 지시(tech_advisory 도식 ID)·대응 REQ·출처]. 문체 = **주장 → 근거(라이브 실적/설계 문서) → 효과**. 과장 금지 — tech_advisory §0 구분표에 따라 **[검증됨]**(구축 완료·라이브)과 **[구현 계획]**(설계 확정·미완)을 표기 라벨로 명확 구분.
> 보안 불변: 시크릿(키·비번·IP·SSH) 미기재. AI 생성 이미지는 워터마크·고지 정책 명시.
> deck-designer 지침: 도식 지시의 "도식ID"는 `tech_advisory.md §1-4`의 번호(①~⑫)와 1:1. 아이콘 전량 선(stroke) SVG.
---
## 표기 범례 (전 슬라이드 공통)
- **[검증됨]** = WORK_STATUS(2026-07-11) 라이브 확인분. "이미 기동 중".
- **[구현 계획]** = 설계·데이터모델 확정, 구현 대기. "표준대로 채운다".
- **[협의]** = 발주처 협의·확인 필요(⚠) 항목. 정직 고지 대상.
- **REQ** 열 = 해당 슬라이드가 충족하는 요구사항 ID(RTM 반영 근거).
---
# 0. 표지·목차 (S01~S06)
## S01 · 표지
- **섹션**: 표지
- **헤드라인**: **"신청서를 내는 순간, 시공 후 사진을 먼저 본다."**
- **본문**
- 사업명: 킨텍스(KINTEX) 자동전시시스템 구축
- 서브 슬로건: "설계 → 시각화 → 발주 → 시공 → 운영 → 분석. 킨텍스에서 한 흐름으로 끝난다."
- 제안사·제안일 표기 영역(로고 자리)
- **도표 지시**: 배경 = 나노바나나 시공 예측 히어로 이미지(Before 빈 부스 → After 시공 사진). **워터마크 항상 표기**("AI 생성 예상 이미지 — 실제 시공과 다를 수 있음. 계약·심사 서류 사용 금지"). 도식ID 없음(히어로).
- **REQ**: REQ-A-001, REQ-S-009
- **출처**: strategy §0, PLANNING §1-2
## S02 · 제안 개요
- **섹션**: 표지
- **헤드라인**: "우리는 도면이 아니라 사진으로 컨펌하고, 그 사진으로 발주까지 종결한다."
- **본문**
- 우리는 누구: GUARDiA 표준 프레임워크(WISE/UIWS) 검증 자산 위에 전시 도메인 코어를 얹은 팀
- 왜 우리인가: ①나노바나나 시공 예측 ②PostGIS 실측 공간데이터 ③공사 옥션 폐루프 — **세 결합을 이미 라이브로 검증** [검증됨]
- 차별점 1줄: "말이 아니라 이미 돈다" — 인프라·인증·PostGIS 코어·나노바나나·자동배포가 기동 중
- **도표 지시**: 제안사 핵심 자산 아이콘 3종(선 SVG) — 시공예측·공간데이터·옥션폐루프.
- **REQ**: REQ-A-001, REQ-N-008, REQ-F-025
- **출처**: tech_advisory §0-1, strategy §0
## S03 · 목차
- **섹션**: 표지
- **헤드라인**: "배점을 따라 읽으십시오 — 제안서 무게가 곧 배점 배분입니다."
- **본문**
- P1 사업이해 · P2 추진전략 · P3 기술제안 · P4 아키텍처 · P5 보안·개인정보 · P6 비기능 · P7 멀티테넌시·확장 · P8 사업관리·일정·조직 · P9 기대효과
- P3(기술제안)이 전체 25슬라이드(≈40%) — 배점 35점 정렬
- **도표 지시**: 목차 인덱스(9섹션) + 각 섹션 배점 배지 병기. 도식ID 없음.
- **REQ**: (내비게이션 — 개별 REQ 없음)
- **출처**: outline 배분 총괄표
## S04 · 제안 핵심 요약(1p)
- **섹션**: 표지
- **헤드라인**: "단일 요소는 모방할 수 있으나, 세 결합은 재현하기 어렵다 — 우리만의 해자."
- **본문**
- Win Theme 5선: WT-1 시공 예측 · WT-2 배치·설계·배선 3안+규정검증 · WT-3 옥션 폐루프 · WT-4 보안 3중 격리 · WT-5 멀티테넌트 SaaS
- 3중 해자: 나노바나나 시공 예측 ∩ PostGIS 실측 공간데이터 ∩ 공사 옥션 폐루프
- 배점의 44%(35/80)가 P3에 집중 → 제안 무게 집중 근거
- **도표 지시**: **도식① 3중 해자 벤다이어그램**(3원 60% 겹침, 중앙 교집합 "재현 난이도 높은 결합 = 우리만의 해자(Moat)"). tech_advisory §1-4 ①.
- **REQ**: REQ-A-001·002, REQ-N-008·017, REQ-F-025
- **출처**: strategy §3-3, tech_advisory §1-4 ①
## S05 · 배점 대응 맵
- **섹션**: 표지
- **헤드라인**: "우리 제안은 배점이 큰 곳에 정확히 무게를 실었다."
- **본문**
- AI 시각화·설계(핵심) 20점 → WT-1·WT-2 전면(P3 나노바나나 7슬라이드+배치·설계·배선 5슬라이드)
- 기능 구현 15점 → 모듈맵 1장 조망 + 옥션·관람·BI 상세
- 아키텍처 10·보안 10·비기능 8·사업이해·전략 10·멀티테넌시 5·사업관리 2
- **도표 지시**: 배점-섹션 정렬 매트릭스(80점 히트맵). 도식ID 없음(인포그래픽).
- **REQ**: (배점 정렬 — 전 REQ 계열 조망)
- **출처**: strategy §2, outline 배분 총괄
## S06 · [가정] 고지 간지
- **섹션**: 표지
- **헤드라인**: "가정은 숨기지 않고 드러냅니다 — 실제 공고 확인 시 즉시 재정렬합니다."
- **본문**
- 실제 RFP 부재 → 평가배점·제출규격은 공공 SI 통례 기준 **[가정]** (리스크 R-4 정면 방어)
- 목차는 안전 정렬(사업이해→전략→기술→아키→보안→비기능→확장→관리→기대효과)
- 확인 필요 8항목(C1~C8)은 부록 A에 집약 → 정직성을 강점화
- **도표 지시**: 간단 노트 카드(리스크 R-4 방어 문구). 도식ID 없음.
- **REQ**: (투명 고지 — 개별 REQ 없음)
- **출처**: strategy §4 R-4, rfp_analysis §4·§5
---
# P1. 사업이해 (S07~S10)
## S07 · 사업 배경·목적
- **섹션**: P1 사업이해
- **헤드라인**: "108,011㎡의 아시아 3위 전시장, 준비는 여전히 HWP·CAD 수작업이다."
- **본문**
- 킨텍스 규모: 현 108,011㎡ → 2028년 제3전시장 완공 시 178,000㎡(국내 최대·아시아권 상위)
- 현행 병목: 부스 배치는 CAD 외주 수작업, 신고서류는 HWP 수기, 배선·위치표시도 육안 작도
- 목적: 전시 준비 전 과정을 AI로 자동화해 리드타임·오류·외주비용을 동시 절감
- **도표 지시**: 킨텍스 규모 인포그래픽(면적·홀 수·연간 행사 규모 지표 카드). 도식ID 없음.
- **REQ**: (사업 맥락 — 개별 REQ 없음)
- **출처**: PLANNING §1-1
## S08 · As-Is 프로세스 병목
- **섹션**: P1 사업이해
- **헤드라인**: "D-150부터 D-7까지, 마디마다 사람이 기다리고 CAD를 외주한다."
- **본문**
- D-150 판매·견적: 견적 산출을 담당자가 수기 회신 → 대기 발생
- D-30 배치·설계: CAD 외주 수일~수주, 규정 위반은 홀매니저 육안 검수 병목
- D-25 유틸리티: 전기·네트워크·급배수 신청이 개별 채널로 파편화
- D-7 제출·검수: kxwp 제출·서류 육안 검토
- **도표 지시**: **As-Is 타임라인 다이어그램**(D-데이별 Pain Point 표시). 도식ID 없음(표준 타임라인).
- **REQ**: (병목 진단 — 개별 REQ 없음, To-Be 근거)
- **출처**: PLANNING §3-1·3-2
## S09 · 이해관계자·페르소나
- **섹션**: P1 사업이해
- **헤드라인**: "6역할+2관리자, 각자의 과업·고통·기대를 정확히 안다."
- **본문**
- 주최자(organizer): 배치·행사 총괄 — CAD 수작업 고통
- 참가업체(exhibitor): 도면을 못 읽어 개장일에 결과 첫 대면 — 시공 예측 니즈
- 공사·장치업체(contractor): 리스트+엑셀 매칭, 견적 비교 부재
- 홀매니저(ops): 규정 육안 검수 병목
- 관람객(visitor): 등록·배지·현장 체크인
- 일반 대중(public): 공개 홍보·플로어플랜 열람
- +플랫폼 관리자·테넌트 관리자
- **도표 지시**: 페르소나 카드 6종(과업·Pain·기대 3행). 도식ID 없음.
- **REQ**: REQ-F-081(역할별 포털의 근거)
- **출처**: PLANNING §2
## S10 · To-Be 목표(정량)
- **섹션**: P1 사업이해
- **헤드라인**: "견적은 즉시, 배치는 수 분, 검수는 자동, 결과는 사진으로."
- **본문**
- 견적: 수일 대기 → **즉시**(규칙엔진 2,250원/㎡ 자동 계산)
- 배치: 수주 CAD 외주 → **수 분 3안 자동생성**
- 규정 검수: 육안 → **제출 전 자동 플래깅**
- 위치표시도: 수기 작도 → **좌표 클릭 자동 작도**
- 시공 결과: 개장일 첫 대면 → **신청 시점 사진 예측**
- **도표 지시**: As-Is→To-Be 대비표(정량 개선 5행). 도식ID 없음(표).
- **REQ**: REQ-F-002, REQ-F-007, REQ-F-009, REQ-F-017, REQ-A-001
- **출처**: PLANNING §1-3
---
# P2. 추진전략 (S11~S15)
## S11 · 비전·포지셔닝
- **섹션**: P2 추진전략
- **헤드라인**: "도면과 표를 보여주는 전시테크가 아니라, 사진으로 컨펌하는 베뉴 운영 플랫폼."
- **본문**
- 기존 전시테크는 도면·3D 뷰어 수준 → 우리는 **사진급 시공 예측**으로 의사결정
- 단일 폐루프: 설계 자료가 곧 발주 근거 → 발주까지 한 흐름에서 종결
- 데이터 주권: 온프레미스 Ollama 기본, 외부 AI는 승인 예외만
- **도표 지시**: 비전 슬로건 대형 타이포. 도식ID 없음.
- **REQ**: (포지셔닝 — 개별 REQ 없음)
- **출처**: PLANNING §1-2, strategy §0
## S12 · 3중 해자 전략
- **섹션**: P2 추진전략
- **헤드라인**: "셋 중 하나는 흉내낼 수 있어도, 셋의 결합은 재현 난이도가 높다."
- **본문**
- 원A 나노바나나 시공 예측: image-to-image 구조보존·40초·S1~S7 [검증됨: G1 승인·워커 라이브]
- 원B PostGIS 실측 공간데이터: 부스 폴리곤·트렌치 포인트·배선 LineString 단일 원천 [검증됨: M2~M5 매퍼 라이브]
- 원C 공사 옥션 폐루프: AI 자료→응찰→낙찰→발주 자동전환 [구현 계획: Phase D]
- **도표 지시**: **도식① 3중 해자 벤다이어그램**(S04와 동일·상세 재강조). tech_advisory §1-4 ①.
- **REQ**: REQ-A-001·002, REQ-N-008·017, REQ-F-025·032
- **출처**: strategy §3-3, tech_advisory §0-1·§1-4 ①
## S13 · Win Theme 5선
- **섹션**: P2 추진전략
- **헤드라인**: "다섯 개의 차별화 메시지가 배점 다섯 곳을 정확히 겨눈다."
- **본문**
- WT-1 "도면이 아니라 사진으로 컨펌한다" — 배점 20(AI 시각화 핵심)
- WT-2 "수 분 내 3안, 제출 전 규정 자동검증" — 배점 20+15 교차
- WT-3 "사진 수준 자료로 응찰하고 발주까지 끝낸다" — 배점 15(폐루프)
- WT-4 "AI가 만든 것은 표시하고, 남의 데이터는 차단한다" — 배점 10(보안)
- WT-5 "코드 배포 없이 데이터 온보딩만으로 새 전시관을 연다" — 배점 5(확장)
- **도표 지시**: Win Theme 5카드(각 카드에 배점 배지). 도식ID 없음.
- **REQ**: REQ-A-001, REQ-F-007·025, REQ-S-009·018, REQ-F-077
- **출처**: strategy §1
## S14 · 전시 생애주기 폐루프
- **섹션**: P2 추진전략
- **헤드라인**: "설계 → 시각화 → 발주 → 시공 → 운영 → 분석이 하나의 순환으로 닫힌다."
- **본문**
- 판매·기획(M1) → 설계·시각화(M2~M5) → 발주·계약(M15·M6·M9) → 참가·관람(M10·M11) → 현장운영(M8·M13·M14) → 사후·경영(M16)
- 각 단계 산출물이 다음 단계 입력 → 재입력·중복 작업 제거
- 분석(M16)이 다음 행사 기획(M1)으로 환류
- **도표 지시**: 생애주기 폐루프 순환도(M모듈 매핑). 도식ID 없음(순환도).
- **REQ**: (생애주기 조망 — 상세 REQ는 P3에서 반영)
- **출처**: PLANNING §1-2-1
## S15 · 추진체계·수행조직
- **섹션**: P2 추진전략
- **헤드라인**: "거버넌스·아키텍트·코어·도메인·AI·QA·DevOps가 게이트로 맞물린다."
- **본문**
- 거버넌스: 총괄 PM · PMO · 개발 PM
- 아키텍트 풀: AA · SA · TA · DA · NA(5종 아키텍처 확정 완료)
- 실행: 공통·코어(백엔드·프론트·DB) · 도메인(옥션·관람·CMS·BI·관리자) · AI·시각화 · QA · DevOps
- 게이트: 선행 게이트(G1·G2) + Phase 게이트 QA 통과
- **도표 지시**: **추진체계도(조직도)** — 거버넌스+아키텍트+실행팀 계층. tech_advisory §1-4 말미(추가 표준 도식).
- **REQ**: REQ-N-004(품질 게이트)
- **출처**: 하네스 팀 구성, tech_advisory §5-1
---
# P3. 기술제안 (S16~S40, 25슬라이드 · 배점 35 · 최다 집중)
## P3-A. 모듈 조망
## S16 · 전체 모듈맵
- **섹션**: P3 기술제안
- **헤드라인**: "18개 모듈+공통 레이어를 한 장으로 조망 — P0 코어 4개가 심장이다."
- **본문**
- P0 코어(강조): M2 플로어플랜·M3 부스설계·M4 유틸리티·M5 나노바나나
- P1 도메인: M1 판매·M6 서류·M7 등록·M9 정산·M15 옥션·M10 관람·M12/M17 CMS·M16 BI·M18 관리자
- P2 확장: M8 물류·M11 비즈매칭·M13 wayfinding·M14 현장 IoT
- **공통/시스템관리 레이어(§5B) — 전 모듈 선행 기반** [검증됨: WISE 이식 Flyway V1~V9]: 공통코드·메뉴(F-073), 공통 업무모듈 일지·일정·쪽지·공지·검색(F-074), 회의록 STT·업무보고 통계 PDF(F-075), 통합 알림센터 WebSocket(F-076)
- 멀티테넌시(§1A): tenant_id 격리 기반 [구현 계획]
- 100대 기능 성숙도: 기구현 63·부분 18·신규 19(정량)
- **도표 지시**: **도식② 모듈맵(생애주기 6단계 × 우선순위 P0/P1/P2 매트릭스)** + 하단 공통 레이어 띠. tech_advisory §1-4 ②.
- **REQ**: REQ-F-073·074·075·076, 전 모듈 조망
- **출처**: rfp_analysis §2-1, tech_advisory §1-4 ②·§0
## P3-B. 나노바나나 시공 예측 — WT-1 (S17~S23, 7슬라이드 · 축소 금지)
## S17 · 나노바나나 개요
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "시공 전에 사진으로 의사결정한다 — 경쟁 전시테크가 못 하는 차별화."
- **본문**
- 문제: 참가업체는 도면을 못 읽어 개장일에야 결과를 처음 본다(PLANNING §2)
- 해법: image-to-image로 빈 부스(도면/실측)를 시공 후 사진으로 예측
- 실증: Gemini 라이브(G1 소유자 승인 완료), `gemini-3.1-flash-image-preview` 워커 상주 [검증됨]
- 효과: 조감도 외주(비용·시간) 대체, 신청 시점 의사결정
- **도표 지시**: 히어로 Before/After 실물(워터마크 표기). 도식ID 없음(히어로).
- **REQ**: REQ-A-001
- **출처**: strategy §1 WT-1, tech_advisory §0-1·§2 WT-1
## S18 · 표준 샷세트 S1~S7
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "정면·야간·통로·내부·Before/After·배선·홀전경 — 7컷을 평균 40초에."
- **본문**
- S1 정면 부스 전경 / S2 야간 조명 연출(조도 배치안 반영) / S3 통로 시점 / S4 부스 내부 / S5 Before/After / S6 배선 오버레이 / S7 홀 전경
- 자동생성은 비용·품질 제어 위해 **S1·S7 한정**, 나머지는 온디맨드 [구현 계획: 샷세트 파이프라인]
- 모든 컷에 워터마크·고지 항상 포함
- **도표 지시**: 샷세트 7컷 그리드(각 컷 워터마크 배지). 도식ID 없음(그리드).
- **REQ**: REQ-A-001, REQ-F-019(조명 조도 배치안)
- **출처**: tech_advisory §2 WT-1, PLANNING §6
## S19 · image-to-image 구조보존
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "골격·치수·통로·카메라 앵글은 잠그고, 스타일·재질만 사실화한다."
- **본문**
- 참조 이미지 `inlineData` + 구조화 프롬프트 사전(BOOTH_TYPES·BOOTH_STYLES·FIXTURE_LAYERS)
- **보존(잠금)**: 부스 골격·치수·통로·카메라 앵글 → 왜곡·과장 방지
- **교체(사실화)**: 스타일·재질·마감만 → 사진 리얼리티
- 검증 패턴: ReRoomAI image-to-image 보존/교체 분리 이식(reroomai-source.md)
- **도표 지시**: 보존/교체 파라미터 도식(잠금 항목 vs 교체 항목 2열). 도식ID 없음.
- **REQ**: REQ-A-002, REQ-A-003
- **출처**: tech_advisory §2 WT-1, docs/analysis/reroomai-source.md
## S20 · AI 파이프라인 아키텍처
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "생성은 전면 비동기 — 사용자는 대기하지 않고, 완료는 WebSocket으로 밀어준다."
- **본문**
- 흐름: 사용자 요청 → Spring RenderJob 생성·쿼터 확인 → Redis 큐(leftPush) → Python 워커(BLPOP) → Gemini image-to-image → 워터마크 후처리·S6 로컬 PIL 래스터 → 오브젝트 스토리지 → pub/sub → 백엔드 relay → WebSocket `/topic/render/{jobId}` → 프론트 완료 푸시
- degraded 분기: G1 미승인/Gemini 장애 시 워커 목(mock) 응답·구조·워터마크 유지(REQ-N-005)
- 성공 시에만 쿼터 차감, `GEMINI_API_KEY`는 워커 env only
- **도표 지시**: **도식③ 나노바나나 파이프라인 플로우(8박스 좌→우)**. tech_advisory §1-4 ③.
- **REQ**: REQ-A-007, REQ-N-006
- **출처**: tech_advisory §1-4 ③·§2 WT-1
## S21 · Before/After·배선 오버레이
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "드래그 한 번으로 빈 부스와 시공 후를 비교하고, 배선을 색으로 겹쳐 본다."
- **본문**
- Before/After: 좌우 분할 카드 + 중앙 드래그 핸들, ARIA slider·키보드 좌우 조작(WCAG AA)
- S6 배선 오버레이: Gemini 미경유, 백엔드/워커 로컬 PIL **결정적 래스터 합성**(좌표 정확성·비용/지연 제외)
- 색상 규약: 전기=적색, 네트워크=청색, 급배수=녹색
- **도표 지시**: **도식④ Before/After 슬라이더**(좌 "Before: 빈 부스", 우 "After: 시공 후 예상(AI 생성·워터마크)", 하단 워터마크 배지). tech_advisory §1-4 ④.
- **REQ**: REQ-A-004, REQ-A-005
- **출처**: tech_advisory §1-4 ④·§2 WT-1
## S22 · RenderJob 방어·품질
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "다층 방어로 실패를 흡수하고, 성공했을 때만 쿼터를 쓴다."
- **본문**
- 크기가드·mimeType 자동감지·SAFETY 분기·친화적 한글 에러
- 간판 한글 렌더 실패 시 **간판영역 후처리 합성**(리스크 R-5 방어)
- 대량 생성 제어: 자동생성 S1·S7 한정 + 동일 스키마해시 캐시 + 행사별 쿼터(성공 시만 차감)
- **도표 지시**: RenderJob 상태 시퀀스(방어 계층 표시). 도식ID 없음(시퀀스).
- **REQ**: REQ-A-006, REQ-A-008, REQ-N-007
- **출처**: tech_advisory §2 WT-1, strategy §4 R-5
## S23 · AI 신뢰 원칙
- **섹션**: P3 기술제안(WT-1)
- **헤드라인**: "결정적 알고리즘이 중심, LLM은 보조, 최종 확정은 사람 — 워터마크는 불변."
- **본문**
- 3층 신뢰: 결정적 알고리즘(제약 솔버·PostGIS) 중심 + LLM 보조 + 사람 최종 확정
- 워터마크 항상 포함(`watermarkRequired:true`), 계약·심사 서류 사용 금지 고지 제거 불가
- AI 답변은 RAG 근거·인용·환각 차단(abstain)으로 오판 리스크 차단(리스크 R-6 방어)
- **도표 지시**: AI 3층 신뢰 구조 도식(결정론→LLM→사람). 도식ID 없음.
- **REQ**: REQ-S-009, REQ-A-017
- **출처**: tech_advisory §2 WT-2·WT-4, strategy §4 R-6
## P3-C. 배치·설계·배선 AI 자동화 — WT-2 (S24~S28, 5슬라이드)
## S24 · 플로어플랜 스튜디오(M2)
- **섹션**: P3 기술제안(WT-2)
- **헤드라인**: "제약 솔버가 상이한 목표로 정확히 3안 — 사람이 선택·병합·확정한다."
- **본문**
- 3안 자동생성: **결정적 제약 솔버**(비-LLM)가 통로 효율/프리미엄 부스/균형 등 목표별 생성, LLM은 조건해석만(REQ-A-009)
- 선택/병합 편집 + 최종안 버전기록·부스좌표 확정·초대링크(REQ-F-008·010)
- M2 매퍼(PostGIS 공간 SQL) [검증됨], 3안 생성·병합·집계 API [구현 계획]
- **도표 지시**: **도식⑤ 3안 비교 화면**(3열 카드·각 카드 최적화 목표·미니 배치도·지표, 하단 액션바 선택/병합/재검증). tech_advisory §1-4 ⑤.
- **REQ**: REQ-F-007·008·010, REQ-A-009
- **출처**: tech_advisory §1-4 ⑤·§2 WT-2
## S25 · 배치 규정 자동검증
- **섹션**: P3 기술제안(WT-2)
- **헤드라인**: "피난통로·바닥하중·비상구를 제출 전에 자동으로 플래깅한다."
- **본문**
- 룰엔진(`compliance-v*.json` 버전 데이터) + PostGIS 공간 판정: 통로폭 `ST_Distance/ST_Buffer`, 비상구 `ST_Intersects`, 홀 이탈 `ST_Contains`, 바닥하중 홀별
- 병합 후 재검증 필수, 위반 시 `COMPLIANCE_BLOCKED`(422)
- 원칙: 규정검증=플래깅까지, 구조안전 판정은 구조기술사/킨텍스(Non-Goal 명시, 리스크 R-6)
- **도표 지시**: 규정 위반 플래그 대시보드(위반 항목·홀별 하중·비상구 교차 목록). 도식ID 없음(대시보드).
- **REQ**: REQ-F-009, REQ-A-010
- **출처**: tech_advisory §2 WT-2, strategy §4 R-6
## S26 · 부스 설계 스튜디오(M3)
- **섹션**: P3 기술제안(WT-2)
- **헤드라인**: "조립부스 3D 프리뷰·독립부스 3안, 높이·리깅·방염·이격은 사전 검증."
- **본문**
- 조립부스 옵션·3D 프리뷰(REQ-F-011), 독립부스 설계 3안·선택/병합·버전(REQ-F-012)
- 설계 규정 사전검증: 높이 5m·리깅 6.5~8.5m·방염·이격(REQ-F-013)
- 도면 업로드 비전 추출·검증(REQ-F-014·A-020)
- **도표 지시**: 설계 3안 + 규정 체크리스트(높이·리깅·방염·이격). 도식ID 없음.
- **REQ**: REQ-F-011·012·013·014, REQ-A-020
- **출처**: tech_advisory §2 WT-2, PLANNING §M3
## S27 · 유틸리티 설계(M4)
- **섹션**: P3 기술제안(WT-2)
- **헤드라인**: "기기→kW→분전반을 자동 산출하고, 트렌치→분전반 최단 배선을 PostGIS가 그린다."
- **본문**
- 전기 용량·분전반 자동산출: 기기목록→kW 합산→분전반 산출(REQ-F-015)
- 배선 경로: 최근접 트렌치 KNN(`geom <-> point`)→최단 배선 LineString(`ST_Length`, 통로 횡단 최소 휴리스틱)(REQ-F-016)
- 실측 근거: 홀별 평면도 JPG 15장 + CAD "평면,트렌치.dwg" 확보 [검증됨], 미확보 구간은 "가정 좌표(실측 대기)" 라벨(리스크 R-2)
- **도표 지시**: **도식⑥ PostGIS 배선 오버레이**(부스 폴리곤·트렌치 포인트◆·분전반▣·색상별 LineString, 전기 적/네트 청/급배수 녹). tech_advisory §1-4 ⑥.
- **REQ**: REQ-F-015, REQ-F-016
- **출처**: tech_advisory §1-4 ⑥·§2 WT-2, strategy §4 R-2
## S28 · 위치표시도·유틸리티 신청
- **섹션**: P3 기술제안(WT-2)
- **헤드라인**: "좌표 클릭으로 위치표시도를 자동 작도하고, 신청·견적은 D-25 역산으로 챙긴다."
- **본문**
- 위치표시도 자동생성: 좌표 클릭 자동 작도, 수기 작도 폐지(REQ-F-017)
- 네트워크·급배수·압축공기 신청 + 마감 리마인더(D-25 역산)(REQ-F-018)
- 자동 견적 연동
- **도표 지시**: 위치표시도 자동생성 예시(좌표 클릭 → 작도 결과). 도식ID 없음.
- **REQ**: REQ-F-017, REQ-F-018
- **출처**: tech_advisory §2 WT-2, PLANNING §M4
## P3-D. 공사 옥션 폐루프 — WT-3 (S29~S32, 4슬라이드)
## S29 · 옥션 개요·폐루프
- **섹션**: P3 기술제안(WT-3)
- **헤드라인**: "AI가 만든 설계 자료가 곧 응찰 근거이고, 한 흐름에서 발주까지 끝난다."
- **본문**
- AI 자료 패키지(M2 배치+M3 설계+M4 배선/BOQ+M5 시공예측 이미지)가 응찰 근거(`attached_refs`)
- 옥션 유형 설정형(REVERSE 역경매/RFQ/FIXED), 물량서(BOQ) 자동산출(REQ-F-031)
- 데이터모델: `TB_AUCTION 1─N TB_QUOTATION ─ TB_AWARD` [구현 계획: Phase D-M15]
- **도표 지시**: **도식⑦ 옥션 폐루프 흐름도**(AI 자료 패키지→옥션 개설→등록 게이트→견적 제출→실시간 순위→종합평가 낙찰→발주 자동전환). tech_advisory §1-4 ⑦.
- **REQ**: REQ-F-025·026, REQ-F-031
- **출처**: tech_advisory §1-4 ⑦·§2 WT-3
## S30 · 역경매·실시간 순위
- **섹션**: P3 기술제안(WT-3)
- **헤드라인**: "라운드·마감·최저가·내 위치를 실시간으로, 자동마감까지."
- **본문**
- 실시간 순위 = Redis 정렬셋 + WebSocket `/topic/auction/{auctionId}` 델타 푸시
- 라운드 마감 = Redis 타이머(자동마감)
- 익명 옵션·내 위치 표시(REQ-F-027)
- 스캐폴드 4계층·큐·WS 패턴 확정으로 실현성 입증 [구현 계획]
- **도표 지시**: 실시간 순위 보드 목업(업체 순위·최저가·잔여 시간). 도식ID 없음(목업).
- **REQ**: REQ-F-027
- **출처**: tech_advisory §2 WT-3
## S31 · 종합평가 낙찰
- **섹션**: P3 기술제안(WT-3)
- **헤드라인**: "가격만이 아니라 평판·납기까지 가중 스코어로 투명하게 낙찰한다."
- **본문**
- 가중 스코어: 가격+평판+납기(`weight` jsonb, 옥션별 설정형, 예: 가격 0.6/평판 0.25/납기 0.15)(REQ-F-028)
- 항목별 비교표·선정 사유 제시
- 등록업체 AI 매칭 추천(REQ-F-030), 물량서(BOQ) 근거(REQ-F-031)
- **도표 지시**: **도식⑧ 종합평가 스코어 비교표**(업체 행 × 평가항목 열 + 가중 종합점수 + 낙찰 하이라이트). tech_advisory §1-4 ⑧.
- **REQ**: REQ-F-028, REQ-F-030, REQ-F-031
- **출처**: tech_advisory §1-4 ⑧·§2 WT-3
## S32 · 등록 게이트·발주 전환
- **섹션**: P3 기술제안(WT-3)
- **헤드라인**: "미등록 업체는 응찰 자체가 차단(403)되고, 낙찰은 계약·발주로 자동 전환된다."
- **본문**
- 등록 게이트: 미등록 업체 응찰 원천 차단 `NOT_REGISTERED_COMPANY`(403), M7이 `TB_COMPANY.kintex_registered` 검증 권위 → 킨텍스 "등록업체 시공 필수" 규정을 시스템이 강제(REQ-S-007)
- 발주 전환: Award → 계약/발주 자동전환(M6·M9 연동, 이벤트 디커플·순환 금지)(REQ-F-032)
- **도표 지시**: 등록 검증 시퀀스 + 발주 전환 흐름. 도식ID 없음(시퀀스).
- **REQ**: REQ-F-029, REQ-F-032, REQ-S-007
- **출처**: tech_advisory §2 WT-3·§4 REQ-S-007
## P3-E. 판매·서류·워크플로 (S33~S34)
## S33 · 판매·홀배정·견적(M1)
- **섹션**: P3 기술제안
- **헤드라인**: "가용성은 실시간 캘린더로, 견적은 규칙엔진으로 즉시, 신청은 웹폼→HWP로."
- **본문**
- 홀/반홀 가용성 실시간 캘린더(REQ-F-001)
- 규칙엔진 자동 견적(2,250원/㎡, 요율 공식본 [협의: C4])(REQ-F-002)
- 배정신청 웹폼→HWP 자동생성(REQ-F-003), 부스 판매 인벤토리(REQ-F-004), 온라인 셀프 선택·판매(REQ-F-005)
- 임대계약 전자서명(선택, REQ-F-006)
- **도표 지시**: 견적 규칙엔진 계산 흐름(면적×요율→견적). 도식ID 없음.
- **REQ**: REQ-F-001·002·003·004·005·006
- **출처**: PLANNING §M1, rfp_analysis(C4 요율)
## S34 · 서류·마일스톤(M6)
- **섹션**: P3 기술제안
- **헤드라인**: "D-150/30/25/7을 역산해 알리고, 웹폼은 HWP/PDF로, 검수는 AI가 돕는다."
- **본문**
- 마일스톤 자동생성·역산 알림(REQ-F-020), 신고서류 웹폼→HWP/PDF 자동생성 7종(REQ-F-021)
- 서류 버전·전자결재 워크플로(REQ-F-023), OCR 계약·납품서 자동등록(REQ-F-024·A-020)
- AI 서류 검수(누락·불일치)(REQ-A-011), 규정 자연어 챗봇(근거·인용)(REQ-A-012)
- kxwp 제출 릴레이: 웹폼→HWP/PDF까지 API 무관하게 완결, 릴레이 단계만 킨텍스 IT 협의 [협의: C2, 리스크 R-3](REQ-F-022)
- **도표 지시**: 마일스톤 역산 타임라인(D-데이별 서류·알림). 도식ID 없음(타임라인).
- **REQ**: REQ-F-020·021·022·023·024, REQ-A-011·012
- **출처**: tech_advisory §2, strategy §4 R-3
## P3-F. 관람·참가 (S35~S36)
## S35 · 관람객 등록·배지·리드(M10)
- **섹션**: P3 기술제안
- **헤드라인**: "사전등록→모바일 배지→현장 체크인→리드까지, 오프라인에서도 끊기지 않는다."
- **본문**
- 온라인 사전등록(유형별·중복검증)(REQ-F-037), 모바일 배지/QR 발급(REQ-F-038)
- 현장 QR 체크인·즉석 인쇄·집계(REQ-F-039), **오프라인 체크인 폴백**(REQ-F-040)
- 참가업체 리드캡처(REQ-F-041), AI 리드 스코어링·분류(REQ-F-042·A-018), 세그먼트 EDM(REQ-F-043)
- 티켓발권·AI폼빌더·스마트배지(선택, REQ-F-044)
- **도표 지시**: 등록→체크인→리드 여정도. 도식ID 없음(여정도).
- **REQ**: REQ-F-037·038·039·040·041·042·043·044, REQ-A-018
- **출처**: PLANNING §M10, tech_advisory §7
## S36 · 비즈매칭·마케팅·공개사이트(M11·M12·M17)
- **섹션**: P3 기술제안
- **헤드라인**: "AI가 매칭을 추천하고, 다국어 공개사이트·마이크로사이트가 SEO로 관람객을 모은다."
- **본문**
- AI 비즈매칭 추천(REQ-F-045·A-018), 미팅 슬롯·성과 리포트(REQ-F-046), 이벤트 모바일앱·세션·채팅·설문(REQ-F-047)
- 공개 홍보 사이트(SEO·다국어)(REQ-F-048), 공개 인터랙티브 플로어플랜(REQ-F-049), 세그먼트 EDM·캠페인 자동화(REQ-F-050), 스폰서십 패키지·대시보드(REQ-F-051)
- CMS 게시 워크플로·버전(REQ-F-067), 마이크로사이트(REQ-F-068), 다국어 콘텐츠·hreflang(REQ-F-069)
- AI 카피·이미지 초안·다국어 번역(REQ-A-015)
- **도표 지시**: 공개사이트 + 마이크로사이트 목업. 도식ID 없음(목업).
- **REQ**: REQ-F-045·046·047·048·049·050·051·067·068·069, REQ-A-015·018
- **출처**: PLANNING §M11·M12·M17, tech_advisory §1-2
## P3-G. 정산·경영분석·현장 (S37~S40)
## S37 · 정산·결제(M9)
- **섹션**: P3 기술제안
- **헤드라인**: "납부는 스케줄로, 결제는 PG로, 예치금은 실사용까지 투명하게 정산한다."
- **본문**
- 납부 스케줄 자동생성·알림(REQ-F-056), 유틸리티·부대 PG 결제(REQ-F-057)
- 예치금 실사용 정산 투명화(REQ-F-058), 세금계산서·옥션 수수료 정산(REQ-F-059)
- 환불·취소 규정 자동(선택, REQ-F-060)
- **도표 지시**: 정산 흐름도(납부→결제→예치금 정산→세금계산서). 도식ID 없음.
- **REQ**: REQ-F-056·057·058·059·060
- **출처**: PLANNING §M9
## S38 · 경영분석 BI(M16)-1
- **섹션**: P3 기술제안
- **헤드라인**: "가동률·매출구성·행사별 P&L·㎡당 수익을 운영사 관점 한 화면에."
- **본문**
- 홀·기간별 가동률 대시보드(REQ-F-061), 매출 구성·행사별 P&L(REQ-F-062)
- 전시장 ROI·RevPAD·㎡당 수익(REQ-F-063)
- 데이터: BI 집계·조회는 읽기복제 오프로드(운영 프라이머리 보호)
- **도표 지시**: BI 대시보드 목업(Recharts — 가동률·매출구성·P&L·RevPAD). 도식ID 없음(목업).
- **REQ**: REQ-F-061·062·063
- **출처**: PLANNING §M16, tech_advisory §3 REQ-N-010
## S39 · 경영분석 BI(M16)-2
- **섹션**: P3 기술제안
- **헤드라인**: "리텐션·LTV·참가사 ROI를 권한 격리로, AI가 수요를 예측하고 KPI를 브리핑한다."
- **본문**
- 참가사 리텐션·LTV 코호트(REQ-F-064), 참가업체 관점 ROI(권한 격리)(REQ-F-065), 경영진 KPI 대시보드·드릴다운(REQ-F-066)
- AI 수요예측·수율/동적가격(REQ-A-013), AI KPI 브리핑·자연어 조회 Text-to-SQL(REQ-A-014)
- BI 마트는 개인 식별자 비반입(집계만), 참가업체는 자사 데이터만(격리)
- **도표 지시**: KPI 드릴다운 + AI 인사이트 카드. 도식ID 없음.
- **REQ**: REQ-F-064·065·066, REQ-A-013·014
- **출처**: PLANNING §M16, tech_advisory §4 REQ-S-013
## S40 · 현장운영·물류(M8·M13·M14)
- **섹션**: P3 기술제안
- **헤드라인**: "반입/반출 슬롯·통행증 QR·wayfinding·이상탐지 — 선택 기능은 시설 협의 전제로."
- **본문**
- 반입/반출 슬롯 예약(REQ-F-033), 통행증 QR·중량물 우선(REQ-F-034), 지게차·부대장비 렌탈(REQ-F-035), 철거 대기열 시뮬레이션(REQ-F-036)
- 실내 wayfinding·POI·경로(REQ-F-052), 실시간 혼잡 모니터·예측(REQ-F-053·A-019), 홀 전력부하·주차 점유(REQ-F-054), 안전 위반·이상탐지(REQ-F-055·A-019)
- 사이니지 콘텐츠 배포(REQ-F-070)
- **[협의]**: wayfinding 비콘·IoT·사이니지 HW는 킨텍스 시설 인프라 협의 전제(C7, 리스크 R-7). 미협의 시에도 코어 가치(WT-1~4)는 HW 독립
- **도표 지시**: 현장 운영 상황판(혼잡·전력·안전·물류 슬롯). 도식ID 없음(상황판).
- **REQ**: REQ-F-033·034·035·036·052·053·054·055·070, REQ-A-019
- **출처**: PLANNING §M8·M13·M14, strategy §4 R-7
---
# P4. 아키텍처·기술스택 (S41~S45, 배점 10)
## S41 · 전체 아키텍처
- **섹션**: P4 아키텍처
- **헤드라인**: "확정 스택으로 이미 기동 중 — 도상이 아니라 동작하는 아키텍처다."
- **본문**
- 4-Tier + 사이드카: React 18/19(Vite·TS) 역할별 분리 → SSO(JWT+2FA)·이중 RBAC → 공유 Spring Boot 3.2.5(Java 17)+MyBatis 모듈러 모놀리스 → PostgreSQL+PostGIS(+읽기복제)/Redis/오브젝트 스토리지
- 사이드카: 나노바나나 Python 워커(이미지) + Claude AI(AiTextRouter·Ollama 폴백)(텍스트)
- 실증: 인프라·CI/CD·인증·PostGIS 코어·나노바나나 [검증됨]
- **도표 지시**: **도식⑨ 시스템 아키텍처 다이어그램(6밴드 계층 + 우측 외부 게이트 컬럼)**. tech_advisory §1-4 ⑨.
- **REQ**: REQ-A-016, REQ-F-081(역할별 프론트)
- **출처**: tech_advisory §1-1·§1-4 ⑨·§0-1
## S42 · 단일 공간 데이터 원천
- **섹션**: P4 아키텍처
- **헤드라인**: "부스 폴리곤·트렌치 포인트·배선 LineString은 하나의 원천에서만 산다."
- **본문**
- M2 폴리곤·M4 배선 매퍼가 권위, 소비 모듈은 조회만·중복 저장 금지(REQ-N-017)
- PostGIS 단일 공간 원천(SRID 0 홀 로컬 미터), 전 `geom` GiST 인덱스 실시간(REQ-N-008)
- 검증 게이트: ArchUnit·계약 검수로 중복 저장 방지
- **도표 지시**: 공간 데이터 원천 모델(폴리곤·포인트·LineString 단일 원천). 도식ID 없음(개념 모델).
- **REQ**: REQ-N-008, REQ-N-017
- **출처**: tech_advisory §3(N-008·017), data.md §0-2
## S43 · 확장성·비동기
- **섹션**: P4 아키텍처
- **헤드라인**: "워커는 큐 깊이로 독립 확장하고, BI·공개는 읽기복제로 오프로드한다."
- **본문**
- 워커 독립 스케일: 나노바나나·서류·EDM 워커 큐 깊이 기반 증감(REQ-N-009)
- 읽기복제 오프로드: BI 집계·공개 조회·플로어플랜 열람 읽기복제 라우팅(REQ-N-010)
- 공개영역 CDN·요청률 상한(REQ-N-019), 워커 장애 재큐(멱등 Job)(REQ-N-011)
- 역할별 프론트 번들 분리(REQ-F-081, 방식 확정 대기 [구현 계획])
- **도표 지시**: 배포 토폴로지도(프론트·백엔드 2인스턴스·워커 N·읽기복제·CDN). 도식ID 없음(토폴로지).
- **REQ**: REQ-N-009·010·011·019, REQ-F-081
- **출처**: tech_advisory §3, system.md §3-1
## S44 · AI 플랫폼 라우팅
- **섹션**: P4 아키텍처
- **헤드라인**: "Claude 기본, 장애·폐쇄망이면 Ollama 폴백 — LLM 직접호출은 금지, 중앙 경유만."
- **본문**
- `AiTextRouter`: Claude 기본(`api.anthropic.com` 승인 예외) → Ollama 폴백(REQ-A-016)
- 나노바나나는 큐/콜백만(백엔드는 이미지 미취급), degraded 시 목 응답(REQ-A-007, N-005)
- AiTextRouter/AiConfig는 UIWS 패턴 [구현 계획: 착수 대기]
- **도표 지시**: AI 라우터 폴백 흐름(Claude→실패→Ollama). 도식ID 없음.
- **REQ**: REQ-A-007, REQ-A-016
- **출처**: tech_advisory §1-2 AI(텍스트)·§6
## S45 · 연동·마스터데이터
- **섹션**: P4 아키텍처
- **헤드라인**: "외부는 게이트웨이로, 룰셋·요율은 버전 데이터로 무중단 개정한다."
- **본문**
- API·웹훅·CRM/ERP 연동 게이트웨이(선택, REQ-F-080)
- 룰셋·마스터데이터 버전관리(요율·규정 룰셋), M18 백오피스·감사 기록으로 무중단 개정(REQ-F-072)
- 외부 게이트: Gemini(워커존)·Claude(백엔드)·PG 결제·kxwp 릴레이·등록업체 DB 739
- **도표 지시**: 연동 인터페이스 맵(내부↔외부 게이트웨이). 도식ID 없음.
- **REQ**: REQ-F-072, REQ-F-080
- **출처**: tech_advisory §1-2, §6-3
---
# P5. 보안·개인정보 (S46~S50, 배점 10 · WT-4)
## S46 · 보안 개요·불변 원칙
- **섹션**: P5 보안·개인정보
- **헤드라인**: "AI가 만든 것은 표시하고, 남의 데이터는 차단한다 — REQ-S 20항목 전량 커버."
- **본문**
- 불변 원칙 4대: 외부 API 격리 · 워터마크 항상 · PII 격리 · 테넌트 fail-closed
- 보안 성숙도: 인증·워터마크·admin env는 [검증됨](설계가 아니라 동작하는 통제)
- REQ-S 20항목 커버리지 100%
- **도표 지시**: REQ-S 20항목 커버리지 표(항목·충족방안·상태). 도식ID 없음(표).
- **REQ**: REQ-S 전량(001~020)
- **출처**: tech_advisory §4·§0-1
## S47 · 보안영역 분리
- **섹션**: P5 보안·개인정보
- **헤드라인**: "공개 DMZ는 쓰기 권한이 없고, 데이터존은 아웃바운드가 0이다."
- **본문**
- 존 계층: 인터넷→엣지(CDN·WAF)→DMZ(공개, 쓰기 없음)→내부망(인증)→AI 워커존(egress 제한)→데이터존(최내곽, 아웃바운드 전면 차단)
- 철칙: DMZ→데이터존 직접 접근 절대 금지, 데이터존 아웃바운드 0(유출 경로 제거)
- egress 화이트리스트: 승인 4목적지만(Claude·Gemini·PG·SMTP), 그 외 DROP
- fail-closed 다층 테넌트 격리와 정합(REQ-S-018)
- **도표 지시**: **도식⑩ 보안영역 분리도(존 다이어그램 + 관리존 + egress 컬럼)**. tech_advisory §1-4 ⑩.
- **REQ**: REQ-S-020, REQ-S-018, REQ-N-018
- **출처**: tech_advisory §1-4 ⑩·§4, network.md §2
## S48 · 인증·접근통제
- **섹션**: P5 보안·개인정보
- **헤드라인**: "JWT+2FA는 이미 라이브, admin 비번은 env로 재시드, RBAC는 3중이다."
- **본문**
- JWT(HS256)+TOTP 2FA(RFC6238)+로그인 실패 잠금 [검증됨: Flyway V7/V8](REQ-S-005)
- admin 비번 env 재시드(AES-256-GCM, admin123 금지) [검증됨: env 시더](REQ-S-004)
- 행사 RBAC 3중(테넌트 격리 ⊃ 행사 RBAC, 열람/편집 게이트)(REQ-S-006), admin/internal 엔드포인트 게이트(REQ-S-008)
- RBAC·감사·시스템설정 백오피스(REQ-F-071)
- 미등록 업체 차단(403)과 연계(REQ-S-007)
- **도표 지시**: 인증 흐름 + RBAC 3중 구조. 도식ID 없음.
- **REQ**: REQ-S-004·005·006·007·008, REQ-F-071
- **출처**: tech_advisory §4·§0-1(라이브)
## S49 · AI·이미지 보안
- **섹션**: P5 보안·개인정보
- **헤드라인**: "생성 이미지는 워터마크·금지 고지 불변, 외부 API 키는 워커 env에만 산다."
- **본문**
- 워터마크 항상: M5 응답 `watermarkRequired:true`+text+notice(계약·심사 서류 금지), 제거 불가 [검증됨](REQ-S-009)
- 외부 API 정책: Ollama 기본 + Claude/Gemini만 승인 예외, egress 그 외 DROP(REQ-S-001)
- `GEMINI_API_KEY` 워커 env only(백엔드 미취급, 워커존 단일 egress)(REQ-S-002)
- RAG 근거·인용·환각 차단(abstain)(REQ-A-017)
- **도표 지시**: 워터마크 실물 + 외부 API 격리(워커존 단일 egress). 도식ID 없음.
- **REQ**: REQ-S-001·002·009, REQ-A-017
- **출처**: tech_advisory §4·§2 WT-4
## S50 · 개인정보·감사·안전코딩
- **섹션**: P5 보안·개인정보
- **헤드라인**: "PII는 동의·마스킹·암호화·응답 제외, 민감 액션은 전수 감사, 코드는 안전하게."
- **본문**
- PII: 동의 분리(privacy/marketing/share)·최소수집·`*_enc` AES-256-GCM·응답 완전 제외·보존/파기(REQ-S-013)
- 자격증명·키 완전 제외(응답/로그/커밋 마스킹)(REQ-S-003), 스택트레이스 미노출(REQ-S-010), DataAccessException 요약 매핑(REQ-S-011)
- 민감 액션 감사 전수(`TB_AUDIT_LOG` tenant_id 포함, `@Audited` AOP)(REQ-S-012)
- 안전코딩: 입력 검증(REQ-S-014)·XSS/CSP(REQ-S-015)·인젝션 차단 MyBatis `#{}`(REQ-S-016)·IDOR/CSRF/JWT 회전(REQ-S-017)
- 관측성: 로그·메트릭·트레이스에 PII/스택트레이스 미기록(REQ-N-018)
- **도표 지시**: PII 마스킹 예시 + 감사로그 표. 도식ID 없음.
- **REQ**: REQ-S-003·010·011·012·013·014·015·016·017, REQ-N-018
- **출처**: tech_advisory §4, data.md §5
---
# P6. 비기능 (S51~S54, 배점 8)
## S51 · 성능·가용성 SLA
- **섹션**: P6 비기능
- **헤드라인**: "코어 SLO 99.5%(성수기 99.9% 지향), 무중단 롤링·PITR·degraded로 버틴다."
- **본문**
- 코어 SLO 99.5%(성수기 99.9% 지향), 무상태 백엔드+Redis 외부화(REQ-N-001)
- 백엔드 2인스턴스·헬스체크 LB·롤링 무중단(REQ-N-002), PG 스탠바이 복제·자동 failover·PITR(REQ-N-003)
- degraded 모드: Gemini 장애=목 응답, Claude 장애=Ollama 폴백, PG 복제지연=프라이머리 폴백(REQ-N-005)
- 워커 장애 재큐(멱등)(REQ-N-011)
- **도표 지시**: SLA 수치표 + HA 구성도(프론트·백엔드·Redis·PG·워커 다중화). 도식ID 없음(표+구성도).
- **REQ**: REQ-N-001·002·003·005·011
- **출처**: tech_advisory §3·§6-2, system.md §3-2
## S52 · 나노바나나 성능
- **섹션**: P6 비기능
- **헤드라인**: "단건 평균 40초, 1024px 전처리·자동생성 한정·캐시·쿼터로 대량을 제어한다."
- **본문**
- 단건 평균 ≤40s, 전면 비동기(WS 완료 푸시)(REQ-N-006)
- 전처리: 클라 Canvas 1024px 다운스케일 + JPEG 0.85
- 대량 제어: 자동생성 S1·S7 한정 + 동일 스키마해시 캐시 + 행사별 쿼터(성공 시만 차감)(REQ-N-007)
- **도표 지시**: 렌더 성능 지표 카드(40초·1024px·캐시 히트·쿼터). 도식ID 없음.
- **REQ**: REQ-N-006, REQ-N-007
- **출처**: tech_advisory §3, system.md §3-3
## S53 · 접근성·다국어·테마·반응형
- **섹션**: P6 비기능
- **헤드라인**: "WCAG 2.1 AA+KWCAG 2.2, 한/영/중/일, 라이트/다크, 데스크톱/모바일 전부."
- **본문**
- 접근성: WCAG 2.1 AA + 공공 KWCAG 2.2 병행(REQ-N-012), 캔버스/맵 비시각 대안(데이터 테이블·키보드 부스 선택)(REQ-N-013)
- 다국어: i18n 외부화(한 기본+영/중/일)·hreflang·로케일 포맷(REQ-N-014)
- 테마: 라이트/다크 CSS 토큰 2벌·prefers-color-scheme(REQ-N-015)
- 반응형: 데스크톱=설계/에디터, 모바일=현장/조회/승인(REQ-N-016)
- [구현 계획: 전면 스윕 대기]
- **도표 지시**: 접근성·다국어 매트릭스. 도식ID 없음(매트릭스).
- **REQ**: REQ-N-012·013·014·015·016, REQ-F-069(다국어 정합)
- **출처**: tech_advisory §3, PLANNING §8
## S54 · 관측성·데이터
- **섹션**: P6 비기능
- **헤드라인**: "로그·메트릭·트레이스에 PII·스택트레이스는 남기지 않고, BI는 배치 ETL로 정합한다."
- **본문**
- 관측성: 로그·메트릭·트레이스 관리존 수집, 자격증명·PII·스택트레이스 미기록(`include-stacktrace: never`)(REQ-N-018)
- BI 데이터마트: 스타 스키마(FACT/DIM) 야간 배치 ETL 또는 읽기복제 적재, KpiSnapshot(원천-집계 정합 오차 0)(REQ-N-021)
- 공개영역 CDN·요청률 상한과 정합(REQ-N-019)
- **도표 지시**: 관측성 파이프라인(수집→저장→대시보드). 도식ID 없음.
- **REQ**: REQ-N-018, REQ-N-021, REQ-N-019
- **출처**: tech_advisory §3, data.md §6, tech.md §5
---
# P7. 멀티테넌시·확장 (S55~S57, 배점 5 · WT-5)
## S55 · 멀티테넌트 SaaS 비전
- **섹션**: P7 멀티테넌시·확장
- **헤드라인**: "KINTEX #1에서 검증하고, COEX #2로 확장하며, 제3전시장을 표준으로 맞는다."
- **본문**
- KINTEX=기준 테넌트 #1, COEX=테넌트 #2(온보딩 대상)
- 제3전시장(2028, 178,000㎡) 확장 + 국내 전시산업 표준 플랫폼화
- 개발 부담 최소화: 멀티테넌시=격리 레이어만 추가, 기능 로직 불변(리스크 R-8)
- **도표 지시**: 테넌트 확장 로드맵(KINTEX→COEX→제3전시장→타 전시관). 도식ID 없음(로드맵).
- **REQ**: (비전 — 상세 REQ는 S56·S57)
- **출처**: strategy §1 WT-5·§4 R-8, PLANNING §1A
## S56 · 테넌트 격리 아키텍처
- **섹션**: P7 멀티테넌시·확장
- **헤드라인**: "한 층이라도 불일치하면 차단 — 컨텍스트→서비스→MyBatis→PostGIS 4층 방어."
- **본문**
- 격리 축: `tenant_id`(전시관) ⊃ `event_id`(행사) ⊃ 행사 RBAC 3중
- fail-closed 4층: ①컨텍스트 필터(서브도메인+소속, 불일치 거부)→②서비스 가드→③MyBatis 인터셉터(tenant_id 자동 주입)→④PostGIS(tenant_id 선두 복합 인덱스)(REQ-S-018)
- tenant_id 선두 복합 인덱스로 격리 쿼리 성능(REQ-N-020)
- [구현 계획: 스키마·컨텍스트 구현 대기, 백필 전 단일테넌트 무중단→검증 후 필터 활성화]
- **도표 지시**: **도식⑪ 멀티테넌시 격리 아키텍처도(수직 4층 방어 스택 + 좌측 테넌트 컨텍스트 진입)**. tech_advisory §1-4 ⑪.
- **REQ**: REQ-F-077·078, REQ-S-018, REQ-N-020
- **출처**: tech_advisory §1-3·§1-4 ⑪
## S57 · 온보딩·2계층 관리자
- **섹션**: P7 멀티테넌시·확장
- **헤드라인**: "코드 배포 없이 데이터 온보딩 6단계로 새 전시관을 연다."
- **본문**
- 온보딩: 코드 배포 없이 데이터 온보딩(홀·요율·규정 룰셋·마스터데이터 입력 수준)(REQ-F-077)
- 2계층 관리자: 플랫폼 슈퍼관리자(테넌트 스위처) / 테넌트 관리자(단일 스코프)(REQ-F-079)
- 테넌트 컨텍스트 해소(REQ-F-078)
- 공통코드·메뉴 관리(§5B, REQ-F-073) 기반 재사용
- **도표 지시**: 온보딩 6단계 도식 + 테넌트 스위처 화면. 도식ID 없음(단계 도식).
- **REQ**: REQ-F-077·078·079, REQ-F-073
- **출처**: tech_advisory §1-3, strategy §1 WT-5
---
# P8. 사업관리·일정·조직 (S58~S60, 배점 2)
## S58 · 추진 일정·Phase
- **섹션**: P8 사업관리·일정·조직
- **헤드라인**: "Phase 1 코어 → Phase 2 워크플로/운영 → Phase 3 현장/확장, 게이트로 진행."
- **본문**
- Phase 1(~4M): 아키텍처(A)·공통레이어(B, 2FA/시스템관리)·P0 코어(C: M2·M3·M4·M5)
- Phase 2(~4M): 옥션 M15·관람 M10·BI M16·관리자 M18·판매/서류/정산 M1/6/7/9·CMS M12/17
- Phase 3(~4M+): 멀티테넌시·M11·M13·M14·배포 안정화·SM 이관
- 공수: 총 ~150 M/M 규모(핵심 146 + PMO/QA 4~8), 라이브 스캐폴드로 Phase A/B 공수 실측 절감
- [주의: 예산·사업기간은 실제 RFP 부재로 가정, 공고 확인 시 재정렬]
- **도표 지시**: **도식⑫ 일정 간트(Phase × 모듈 + 마일스톤 다이아몬드 G1·G2·Phase 게이트·QA 게이트·오픈)**. tech_advisory §1-4 ⑫.
- **REQ**: (일정 — 실행력 근거)
- **출처**: tech_advisory §1-4 ⑫·§5, PLANNING §9
## S59 · 선행 게이트·리스크 관리
- **섹션**: P8 사업관리·일정·조직
- **헤드라인**: "약한 고리를 숨기지 않고 드러내 방어한다 — 선제 대응이 곧 신뢰다."
- **본문**
- 선행 게이트: G1 나노바나나 승인 [검증됨: 완료]·G2 배포서버 [검증됨: 완료]
- 리스크 R-1~R-8 정면 방어: 외부 API(승인 게이트+degraded 폴백)·CAD 실측(가정 라벨+협의 선행)·kxwp(단계적 접근)·배점 가정(투명 고지)·이미지 품질(후처리 합성)·AI 오판(사람 확정)·HW 연동(협의 전제)·확장 부담(격리 레이어만)
- 확인 필요 8항목(C1~C8) 관리
- **도표 지시**: **리스크 매트릭스(발생×영향 2×2에 R-1~R-8·C1~C8 배치)** + 게이트. tech_advisory §1-4 말미(추가 표준 도식).
- **REQ**: (리스크 관리 — 방어 논리)
- **출처**: strategy §4, rfp_analysis §6, tech_advisory §0
## S60 · 품질·형상관리
- **섹션**: P8 사업관리·일정·조직
- **헤드라인**: "배포는 헬스체크로 지키고 실패하면 롤백, 의존성은 SCA 게이트로 막는다."
- **본문**
- Fail-Safe 배포: 백업→배포→헬스체크 200→실패 시 롤백(이전 jar 유지) [검증됨: deploy_kintex.sh](REQ-N-004)
- QA 게이트: 점진 경계면 검증·보안 불변·빌드 회귀(Phase 게이트 통과 후 진행)
- SCA CI 게이트: OWASP Dependency-Check 정기 스캔·패치(REQ-S-019)
- 형상관리: Gitea `zio/kintex`·Conventional Commits·main 보호·환경 분리(dev/staging/prod)
- **도표 지시**: 품질 게이트 흐름(빌드→테스트→SCA→헬스체크→롤백). 도식ID 없음.
- **REQ**: REQ-N-004, REQ-S-019
- **출처**: tech_advisory §6-5·§0-1(라이브), §4 REQ-S-019
---
# P9. 기대효과 (S61~S63, 가점)
## S61 · 정량 기대효과
- **섹션**: P9 기대효과
- **헤드라인**: "견적 수일→즉시, 배치 수주→수 분, 검수 육안→자동, 결과는 사진으로."
- **본문**
- 견적: 수일 대기 → 즉시(규칙엔진)
- 배치: 수주 CAD 외주 → 수 분 3안 자동생성
- 규정 검수: 육안 → 제출 전 자동 플래깅
- 위치표시도: 수기 → 좌표 클릭 자동 작도
- 시공 결과: 개장일 첫 대면 → 신청 시점 사진 예측
- **도표 지시**: **기대효과 정량 인포그래픽(5지표 Before/After)**. tech_advisory §1-4 말미(추가 표준 도식).
- **REQ**: REQ-F-002·007·009·017, REQ-A-001
- **출처**: PLANNING §1-3, strategy §5
## S62 · 정성·전략 효과
- **섹션**: P9 기대효과
- **헤드라인**: "참가업체는 영업력을, 홀매니저는 시간을, 킨텍스는 데이터 주권을 얻는다."
- **본문**
- 참가업체: 사진급 자료로 영업력·수주력 강화
- 홀매니저: 규정 검수 병목 해소로 처리량 증대
- 킨텍스: 온프레미스 우선·외부 AI 격리로 데이터 주권 확보
- 산업: 국내 전시산업 표준 플랫폼화
- **도표 지시**: 이해관계자별 효과 카드. 도식ID 없음.
- **REQ**: (정성 효과 — 전략)
- **출처**: strategy §3
## S63 · 확장 비전·마무리
- **섹션**: P9 기대효과
- **헤드라인**: "킨텍스에서 검증하고, 코엑스로 확장하며, 다시 사진으로 컨펌한다."
- **본문**
- 확장: COEX·제3전시장·타 전시관 SaaS 확장
- 재사용: 검증된 공통 프레임워크(WISE/UIWS) 위에 도메인 코어 이식
- 클로징: "신청서를 내는 순간, 시공 후 사진을 먼저 본다."
- **도표 지시**: 확장 비전 로드맵 + 클로징 슬로건 대형 타이포. 도식ID 없음.
- **REQ**: REQ-F-077(확장 근거)
- **출처**: strategy §1 WT-5
---
# 부록 (선택 삽입)
## 부록 A · 확인 필요·협의 사항
- C1 평가배점·제출규격(가정) · C2 kxwp API(REQ-F-022) · C3 CAD·트렌치 실측(REQ-F-016 정확도) · C4 인터넷 요율 공식본(REQ-F-002) · C5 나노바나나 G1(완료) · C6 코엑스 실측(REQ-F-077) · C7 사이니지·비콘·IoT HW(REQ-F-070·052~055) · C8 기타 협의
- 정직성 어필: 모든 [가정]·[협의]를 1장 집약 → 발주처 협업 자세
## 부록 B · REQ 커버리지 매트릭스
- 아래 "RTM 반영표" 참조(142 REQ ↔ 슬라이드 1:1)
## 부록 C · 100대 기능 성숙도
- 기구현 63 · 부분 18 · 신규 19(정량, rfp_analysis §4)
---
# RTM 반영표 (REQ → 슬라이드 · 142 REQ 전량)
> 규칙: 각 REQ의 **주 반영 슬라이드**(굵게) + 보조 슬라이드. QA는 이 표로 142 REQ 반영상태 ☑ 최종 검증. 미반영 0.
## REQ-F (기능 81)
| REQ | 요약 | 반영 슬라이드 |
|---|---|---|
| REQ-F-001 | 홀 가용성 캘린더 | **S33** |
| REQ-F-002 | 규칙엔진 견적 | **S33** · S10·S61 |
| REQ-F-003 | 웹폼→HWP | **S33** |
| REQ-F-004 | 부스판매 인벤토리 | **S33** |
| REQ-F-005 | 온라인 셀프선택 | **S33** |
| REQ-F-006 | 임대계약 전자서명 | **S33** |
| REQ-F-007 | 배치 3안 자동생성 | **S24** · S10·S61·S13 |
| REQ-F-008 | 배치안 선택/병합 | **S24** |
| REQ-F-009 | 배치 규정 검증·재검증 | **S25** · S10·S61 |
| REQ-F-010 | 최종안 버전·좌표·초대링크 | **S24** |
| REQ-F-011 | 조립부스 3D 프리뷰 | **S26** |
| REQ-F-012 | 독립부스 3안·버전 | **S26** |
| REQ-F-013 | 설계 규정 사전검증 | **S26** |
| REQ-F-014 | 도면 비전 추출·검증 | **S26** |
| REQ-F-015 | 전기용량·분전반 산출 | **S27** |
| REQ-F-016 | 배선경로 PostGIS 최단 | **S27** · S42 |
| REQ-F-017 | 위치표시도 자동생성 | **S28** · S10·S61 |
| REQ-F-018 | 네트워크·급배수·압축공기 신청 | **S28** |
| REQ-F-019 | 조명 조도 배치안 | **S18** |
| REQ-F-020 | 마일스톤 역산 알림 | **S34** |
| REQ-F-021 | 신고서류 웹폼→HWP/PDF | **S34** |
| REQ-F-022 | kxwp 릴레이 ⚠ | **S34** · S59(리스크 R-3) |
| REQ-F-023 | 서류 버전·전자결재 | **S34** |
| REQ-F-024 | OCR 계약·납품서 | **S34** |
| REQ-F-025 | 역경매 옥션 개설 | **S29** · S12·S13 |
| REQ-F-026 | AI 자료 기반 응찰·PDF | **S29** |
| REQ-F-027 | 실시간 순위·자동마감 | **S30** |
| REQ-F-028 | 종합평가 낙찰 스코어 | **S31** |
| REQ-F-029 | 등록업체 게이트 | **S32** |
| REQ-F-030 | 등록업체 AI 매칭 | **S31** |
| REQ-F-031 | 물량서 BOQ 산출 | **S31** · S29 |
| REQ-F-032 | 낙찰→발주 자동전환 | **S32** · S12 |
| REQ-F-033 | 반입/반출 슬롯 | **S40** |
| REQ-F-034 | 통행증 QR | **S40** |
| REQ-F-035 | 지게차·렌탈 | **S40** |
| REQ-F-036 | 철거 대기열 시뮬 | **S40** |
| REQ-F-037 | 사전등록 | **S35** |
| REQ-F-038 | 모바일 배지/QR | **S35** |
| REQ-F-039 | 현장 체크인·집계 | **S35** |
| REQ-F-040 | 오프라인 체크인 폴백 | **S35** · S51 |
| REQ-F-041 | 리드캡처 | **S35** |
| REQ-F-042 | AI 리드 스코어링 | **S35** |
| REQ-F-043 | 세그먼트 EDM | **S35** |
| REQ-F-044 | 티켓발권·스마트배지 | **S35** |
| REQ-F-045 | AI 비즈매칭 | **S36** |
| REQ-F-046 | 미팅 슬롯·리포트 | **S36** |
| REQ-F-047 | 이벤트 모바일앱 | **S36** |
| REQ-F-048 | 공개 홍보 사이트 | **S36** |
| REQ-F-049 | 인터랙티브 플로어플랜 | **S36** |
| REQ-F-050 | 세그먼트 EDM 캠페인 | **S36** |
| REQ-F-051 | 스폰서십 패키지 | **S36** |
| REQ-F-052 | wayfinding | **S40** |
| REQ-F-053 | 혼잡 모니터·예측 | **S40** |
| REQ-F-054 | 전력부하·주차 | **S40** |
| REQ-F-055 | 안전 이상탐지 | **S40** |
| REQ-F-056 | 납부 스케줄 | **S37** |
| REQ-F-057 | PG 결제 | **S37** |
| REQ-F-058 | 예치금 정산 | **S37** |
| REQ-F-059 | 세금계산서·수수료 | **S37** |
| REQ-F-060 | 환불·취소 규정 | **S37** |
| REQ-F-061 | 가동률 대시보드 | **S38** |
| REQ-F-062 | 매출·P&L | **S38** |
| REQ-F-063 | ROI·RevPAD·㎡당 수익 | **S38** |
| REQ-F-064 | 리텐션·LTV | **S39** |
| REQ-F-065 | 참가업체 ROI 격리 | **S39** |
| REQ-F-066 | 경영진 KPI 드릴다운 | **S39** |
| REQ-F-067 | CMS 게시 워크플로 | **S36** |
| REQ-F-068 | 마이크로사이트 | **S36** |
| REQ-F-069 | 다국어 콘텐츠·hreflang | **S36** · S53 |
| REQ-F-070 | 사이니지 배포 ⚠ | **S40** · S59 |
| REQ-F-071 | RBAC·감사 백오피스 | **S48** |
| REQ-F-072 | 룰셋·마스터데이터 버전 | **S45** |
| REQ-F-073 | 공통코드·메뉴 | **S16** · S57 |
| REQ-F-074 | 공통 업무모듈 | **S16** |
| REQ-F-075 | 회의록 STT·업무보고 PDF | **S16** |
| REQ-F-076 | 통합 알림센터 WebSocket | **S16** |
| REQ-F-077 | 멀티테넌트 격리·온보딩 | **S56·S57** · S55·S63 |
| REQ-F-078 | 테넌트 컨텍스트 해소 | **S56** · S57 |
| REQ-F-079 | 2계층 관리자 | **S57** |
| REQ-F-080 | API·웹훅·CRM/ERP 연동 | **S45** |
| REQ-F-081 | 역할별 프론트 포털 | **S43** · S41·S09 |
## REQ-A (AI 20)
| REQ | 요약 | 반영 슬라이드 |
|---|---|---|
| REQ-A-001 | 샷세트 S1~S7 | **S17·S18** · S01·S12·S61 |
| REQ-A-002 | image-to-image 구조보존 | **S19** · S12 |
| REQ-A-003 | 구조화 프롬프트 사전 | **S19** |
| REQ-A-004 | S6 배선 오버레이 래스터 | **S21** |
| REQ-A-005 | Before/After 슬라이더 | **S21** |
| REQ-A-006 | RenderJob 방어 | **S22** |
| REQ-A-007 | 비동기 큐·WebSocket | **S20** · S44 |
| REQ-A-008 | 간판 텍스트 검수·후처리 | **S22** |
| REQ-A-009 | 배치 3안(제약 솔버+LLM) | **S24** |
| REQ-A-010 | AI 규정 검증 | **S25** · S26 |
| REQ-A-011 | AI 서류 검수 | **S34** |
| REQ-A-012 | 규정 챗봇(근거·인용) | **S34** |
| REQ-A-013 | AI 수요예측·동적가격 | **S39** |
| REQ-A-014 | KPI 브리핑·Text-to-SQL | **S39** |
| REQ-A-015 | AI 카피·번역 | **S36** |
| REQ-A-016 | AI 라우터(Claude→Ollama) | **S44** · S41 |
| REQ-A-017 | RAG·인용·환각 차단 | **S49** · S23 |
| REQ-A-018 | AI 리드 스코어·매칭 | **S35·S36** |
| REQ-A-019 | 현장 이상탐지·예측 | **S40** |
| REQ-A-020 | 도면 비전·OCR 등록 | **S26·S34** |
## REQ-N (비기능 21)
| REQ | 요약 | 반영 슬라이드 |
|---|---|---|
| REQ-N-001 | SLO 99.5% | **S51** |
| REQ-N-002 | 2인스턴스 롤링 무중단 | **S51** · S43 |
| REQ-N-003 | PG 스탠바이·PITR | **S51** |
| REQ-N-004 | Fail-Safe 배포 | **S60** |
| REQ-N-005 | degraded 모드 | **S51** · S20·S44 |
| REQ-N-006 | 나노바나나 40초·1024px | **S52** · S20 |
| REQ-N-007 | 대량 생성 제어 | **S52** · S22 |
| REQ-N-008 | 공간쿼리 GiST 실시간 | **S42** · S12 |
| REQ-N-009 | 워커 독립 스케일 | **S43** |
| REQ-N-010 | BI/공개 읽기복제 | **S43** · S38 |
| REQ-N-011 | 워커 재큐(멱등) | **S43** · S51 |
| REQ-N-012 | WCAG 2.1 AA·KWCAG 2.2 | **S53** |
| REQ-N-013 | 캔버스 비시각 대안 | **S53** |
| REQ-N-014 | 다국어 i18n·hreflang | **S53** |
| REQ-N-015 | 라이트/다크 테마 | **S53** |
| REQ-N-016 | 반응형 | **S53** |
| REQ-N-017 | 단일 공간 원천 | **S42** |
| REQ-N-018 | 관측성·미기록 | **S54** · S50 |
| REQ-N-019 | 공개 CDN·요청률 상한 | **S43** · S54 |
| REQ-N-020 | tenant_id 선두 복합 인덱스 | **S56** |
| REQ-N-021 | BI 데이터마트 배치 ETL | **S54** · S38 |
## REQ-S (보안 20)
| REQ | 요약 | 반영 슬라이드 |
|---|---|---|
| REQ-S-001 | 외부 API 정책 | **S49** · S47 |
| REQ-S-002 | GEMINI 키 워커 env only | **S49** |
| REQ-S-003 | 자격증명·키 완전 제외 | **S50** |
| REQ-S-004 | admin 비번 env 재시드 | **S48** |
| REQ-S-005 | JWT+2FA·실패 잠금 | **S48** |
| REQ-S-006 | 행사 RBAC 3중 | **S48** |
| REQ-S-007 | 미등록 업체 차단(403) | **S32** · S48 |
| REQ-S-008 | admin/internal 게이트 | **S48** |
| REQ-S-009 | 이미지 워터마크 항상 | **S49** · S23·S01 |
| REQ-S-010 | 스택트레이스 미노출 | **S50** |
| REQ-S-011 | DataAccessException 요약 | **S50** |
| REQ-S-012 | 감사로그 전수 | **S50** |
| REQ-S-013 | PII 동의·마스킹·암호화 | **S50** · S39 |
| REQ-S-014 | 입력 검증 | **S50** |
| REQ-S-015 | XSS·CSP | **S50** |
| REQ-S-016 | 인젝션 차단 | **S50** |
| REQ-S-017 | IDOR·CSRF·JWT 회전 | **S50** |
| REQ-S-018 | fail-closed 테넌트 격리 | **S56** · S47 |
| REQ-S-019 | 의존성 SCA CI 게이트 | **S60** · S50 |
| REQ-S-020 | 보안영역 분리 | **S47** |
---
## 커버리지 요약
| 유형 | REQ 수 | 반영 | 미반영 |
|---|---|---|---|
| REQ-F (기능) | 81 | 81 | 0 |
| REQ-A (AI) | 20 | 20 | 0 |
| REQ-N (비기능) | 21 | 21 | 0 |
| REQ-S (보안) | 20 | 20 | 0 |
| **총계** | **142** | **142** | **0** |
**슬라이드 총계**: 63(S01~S63) + 부록 3(A·B·C)
**RTM 커버리지**: 142/142 반영, **미반영 0**
**과장 방지 준수**: 라이브 확인분(인프라·CI/CD·나노바나나·인증·워터마크·PostGIS 코어·평면도 자산)만 [검증됨], 도메인 모듈·멀티테넌시·집계 API·i18n/접근성 전면 적용은 [구현 계획]으로 구분 표기. 시크릿(키·비번·IP·SSH) 전면 미기재. AI 생성 이미지는 전 슬라이드에서 워터마크·금지 고지 정책 명시.

View File

@ -0,0 +1,210 @@
# 킨텍스 자동전시시스템 구축 — 제안서 목차 (Proposal Outline · PPT)
> 작성: proposal-strategist · 작성일: 2026-07-11 · 버전 v1.0
> 짝 문서: `proposal_strategy.md`(Win Theme·평가대응·리스크) · `rtm.md`(REQ↔섹션 매핑) · `rfp_analysis.md`(배점·규격)
> 형식: **PowerPoint(.pptx) 기술제안서**, 분량 자율(권장 40~80p) → **목표 ~63슬라이드**. 실제 공고 확인 시 재정렬(rfp_analysis C1).
> 목차 코드: P1 사업이해 · P2 추진전략 · P3 기술제안 · P4 아키텍처 · P5 보안·개인정보 · P6 비기능 · P7 멀티테넌시·확장 · P8 사업관리·일정·조직 · P9 기대효과 (RTM 정합).
> 배분 원칙: **배점 가중** — P3(35점) ≈ 40%, P4·P5(각 10점) 각 ~8%, P6(8점) ~6%, P1+P2(10점) ~16%, P7(5점) ~5%, P8(2점) ~5%, P9 ~5%.
---
## 슬라이드 배분 총괄 (배점 정렬)
| 구간 | 목차 | 평가배점(가정) | 슬라이드 수 | 비중 | 핵심 Win Theme |
|---|---|---|---|---|---|
| 표지·목차·오버헤드 | — | — | 6 | 10% | 슬로건 각인 |
| P1 | 사업이해 | 10(P1+P2 공유) | 4 | 6% | 도메인 이해 |
| P2 | 추진전략 | 10(P1+P2 공유) | 5 | 8% | 3중 해자 |
| **P3** | **기술제안** | **20+15=35** | **25** | **40%** | **WT-1·2·3 총력** |
| P4 | 아키텍처·기술스택 | 10 | 5 | 8% | 확정 스택·공간원천 |
| P5 | 보안·개인정보 | 10 | 5 | 8% | WT-4 |
| P6 | 비기능 | 8 | 4 | 6% | SLA 수치 |
| P7 | 멀티테넌시·확장 | 5 | 3 | 5% | WT-5 |
| P8 | 사업관리·일정·조직 | 2 | 3 | 5% | 실행력·게이트 |
| P9 | 기대효과 | 가점 | 3 | 5% | 정량 ROI |
| **합계** | | **80(+가격20)** | **63** | 100% | |
---
## 0. 표지·목차 (6슬라이드)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 기획 |
|---|---|---|---|
| S01 | 표지 | 사업명 + 슬로건 "신청서를 내는 순간, 시공 후 사진을 먼저 본다" | 나노바나나 시공 예측 히어로 이미지(워터마크) 배경 |
| S02 | 제안 개요 | 우리는 누구·왜 우리인가 3줄 | 제안사 로고·핵심 자산 아이콘(선 SVG) |
| S03 | 목차 | P1~P9 네비게이션 | 목차 인덱스(9섹션 배점 배지 병기) |
| S04 | 제안 핵심 요약(1p) | Win Theme 5선 + 3중 해자 한눈에 | **3중 해자 벤다이어그램**(시공예측∩PostGIS∩옥션폐루프) |
| S05 | 배점 대응 맵 | 우리 제안이 배점 어디를 어떻게 겨냥하는가 | **배점-섹션 정렬 매트릭스**(80점 히트맵) |
| S06 | [가정] 고지 간지 | 배점·규격 가정 투명 고지·공고 시 재정렬 | 간단 노트 카드(리스크 R-4 방어) |
---
## P1. 사업이해 (4슬라이드 · 배점 사업이해·추진전략 10)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ/근거 |
|---|---|---|---|---|
| S07 | 사업 배경·목적 | 킨텍스 108,011㎡(2028년 178,000㎡), 전시 준비 여전히 HWP·CAD 수작업 | 킨텍스 규모 인포그래픽 | PLANNING §1-1 |
| S08 | As-Is 프로세스 병목 | D-150/30/25/7 타임라인의 Pain Point(견적 대기·CAD 외주·kxwp 육안검수·유틸리티 파편화) | **As-Is 타임라인 다이어그램**(D-데이별 병목 표시) | PLANNING §3-1·3-2 |
| S09 | 이해관계자·페르소나 | 6역할+2관리자 계층의 과업·고통·기대 | **페르소나 카드 6종**(주최자·참가·업체·홀매니저·관람객·대중) | PLANNING §2 |
| S10 | To-Be 목표(정량) | 견적 즉시·배치 수분·규정 자동검증·위치표시도 자동·시공 예측 | **As-Is→To-Be 대비표**(정량 개선 5행) | PLANNING §1-3 |
---
## P2. 추진전략 (5슬라이드 · 배점 P1과 공유)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ/근거 |
|---|---|---|---|---|
| S11 | 비전·포지셔닝 | "도면이 아니라 사진으로 컨펌한다" 베뉴 운영 플랫폼 | 비전 슬로건 대형 타이포 | PLANNING §1-2 |
| S12 | **3중 해자 전략** | 시공예측∩PostGIS∩옥션폐루프 결합의 재현 난이도 | **3원 벤다이어그램(재강조·상세)** | strategy §3-3 |
| S13 | Win Theme 5선 | WT-1~5 슬로건·배점 정렬 | **Win Theme 5카드**(배점 배지 병기) | strategy §1 |
| S14 | 전시 생애주기 폐루프 | 설계→시각화→발주→시공→운영→분석 한 흐름 | **생애주기 폐루프 순환도**(M모듈 매핑) | PLANNING §1-2-1 |
| S15 | 추진체계·수행조직 | 아키텍트·코어·도메인·AI·QA·DevOps 팀 + 게이트 | **추진체계도(조직도)** | 하네스 팀 구성 |
---
## P3. 기술제안 (25슬라이드 · 배점 AI시각화·설계 20 + 기능구현 15 = 35 · **최다 집중**)
### P3-A. 모듈 조망 (1)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S16 | 전체 모듈맵 | M1~M18+§5B+§1A 한 장 조망, P0 코어 강조 | **모듈맵(생애주기×우선순위 매트릭스)** | rfp_analysis §2-1 |
### P3-B. 나노바나나 시공 예측 — WT-1 최우선 (7)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S17 | 나노바나나 개요 | "시공 전에 사진으로 의사결정" 경쟁 미보유 차별화 | 히어로 Before/After 실물 | REQ-A-001 |
| S18 | 표준 샷세트 S1~S7 | 정면·야간·통로·내부·Before/After·배선·홀전경 | **샷세트 7컷 그리드**(워터마크) | REQ-A-001, F-019 |
| S19 | image-to-image 구조보존 | 골격·치수·통로·앵글 잠금, 스타일만 사실화 | **보존/교체 파라미터 도식** | REQ-A-002·003 |
| S20 | AI 파이프라인 아키텍처 | Spring 큐→Redis→Python 워커→오브젝트스토리지→WebSocket | **나노바나나 파이프라인 플로우** | REQ-A-007, N-006 |
| S21 | Before/After·배선 오버레이 | 드래그 슬라이더(ARIA), S6 백엔드 래스터 합성(전기적·네트청·급배수녹) | Before/After 슬라이더 + 배선 색상 범례 | REQ-A-004·005 |
| S22 | RenderJob 방어·품질 | 다층 크기가드·SAFETY 분기·간판 한글 후처리·성공 시만 쿼터 | **RenderJob 상태 시퀀스**(방어 계층) | REQ-A-006·008 |
| S23 | AI 신뢰 원칙 | 결정적 알고리즘+LLM 보조+사람 확정, 워터마크 불변 | AI 3층 신뢰 구조 도식 | REQ-S-009, A-017 |
### P3-C. 배치·설계·배선 AI 자동화 — WT-2 (5)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S24 | 플로어플랜 스튜디오(M2) | 부스 배치 3안 자동생성·선택/병합·버전 | **3안 비교 화면 목업** | REQ-F-007·008·010, A-009 |
| S25 | 배치 규정 자동검증 | 피난통로·바닥하중 홀별·비상구·복층·소방 병합 후 재검증 | **규정 위반 플래그 대시보드** | REQ-F-009, A-010 |
| S26 | 부스 설계 스튜디오(M3) | 조립 3D 프리뷰·독립부스 3안·규정 사전검증(높이·리깅·방염·이격)·도면 비전추출 | 설계 3안+규정 체크리스트 | REQ-F-011~014, A-020 |
| S27 | 유틸리티 설계(M4) | 기기→kW→분전반 자동산출, 트렌치→분전반 PostGIS 최단 배선 | **PostGIS 배선 경로 오버레이** | REQ-F-015·016 |
| S28 | 위치표시도·유틸리티 신청 | 좌표 클릭 자동 작도(수기 폐지)·자동 견적·D-25 역산 | 위치표시도 자동생성 예시 | REQ-F-017·018 |
### P3-D. 공사 옥션 폐루프 — WT-3 (4)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S29 | 옥션 개요·폐루프 | AI 설계자료→응찰 근거→발주까지 한 흐름 | **옥션 폐루프 흐름도**(M2~M5→M15→M6·M9) | REQ-F-025·026 |
| S30 | 역경매·실시간 순위 | 라운드·마감·최저가·내 위치(익명 옵션)·자동마감 | **실시간 순위 보드 목업** | REQ-F-027 |
| S31 | 종합평가 낙찰 | 가격+평판+납기 가중 스코어·항목별 비교표 | **종합평가 스코어 비교표** | REQ-F-028·030·031 |
| S32 | 등록 게이트·발주 전환 | 미등록 업체 응찰 원천 차단(403)·낙찰→계약/발주 자동전환 | **등록 검증 시퀀스** + 발주 전환 | REQ-F-029·032, S-007 |
### P3-E. 판매·서류·워크플로 (2)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S33 | 판매·홀배정·견적(M1) | 가용성 캘린더·규칙엔진 견적(2,250원/㎡)·웹폼→HWP·셀프 부스선택 | 견적 규칙엔진 계산 흐름 | REQ-F-001~005 |
| S34 | 서류·마일스톤(M6) | D-150/30/25/7 역산·웹폼→HWP/PDF 7종·AI 서류검수·kxwp 릴레이(⚠협의)·전자결재·OCR | 마일스톤 역산 타임라인 | REQ-F-020~024, A-011 |
### P3-F. 관람·참가 (2)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S35 | 관람객 등록·배지·리드(M10) | 사전등록·모바일 배지/QR·현장 체크인·오프라인 폴백·리드캡처·세그먼트 EDM | 등록→체크인→리드 여정도 | REQ-F-037~043 |
| S36 | 비즈매칭·마케팅·공개사이트(M11·M12·M17) | AI 비즈매칭·SEO 다국어 공개사이트·인터랙티브 플로어플랜·마이크로사이트·CMS 게시 워크플로 | 공개사이트+마이크로사이트 목업 | REQ-F-045·048~051·067~069 |
### P3-G. 정산·경영분석·현장 (4)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S37 | 정산·결제(M9) | 납부 스케줄·PG 결제·예치금 실사용 투명 정산·세금계산서·옥션 수수료 | 정산 흐름도 | REQ-F-056~059 |
| S38 | 경영분석 BI(M16)-1 | 가동률·매출구성·행사별 P&L·RevPAD·㎡당 수익(운영사 관점) | **BI 대시보드 목업(Recharts)** | REQ-F-061~063 |
| S39 | 경영분석 BI(M16)-2 | 리텐션·LTV 코호트·참가사 ROI(권한 격리)·경영진 KPI·AI 수요예측·KPI 브리핑·Text-to-SQL | KPI 드릴다운 + AI 인사이트 카드 | REQ-F-064~066, A-013·014 |
| S40 | 현장운영·물류(M8·M13·M14) | 반입/반출 슬롯·통행증 QR·wayfinding·혼잡/전력/안전 이상탐지(선택·협의 전제) | 현장 운영 상황판 | REQ-F-033~036·052~055, A-019 |
---
## P4. 아키텍처·기술스택 (5슬라이드 · 배점 10)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S41 | 전체 아키텍처 | 확정 스택 React18/19+Spring Boot 3.x+MyBatis+PostgreSQL/PostGIS+Redis+나노바나나 Python 워커 | **시스템 아키텍처 다이어그램**(전 계층) | rfp_analysis §1, REQ-A-016 |
| S42 | 단일 공간 데이터 원천 | M2폴리곤·M4배선 매퍼 권위, 소비 모듈 조회만·중복 금지, PostGIS GiST 실시간 | **공간 데이터 원천 모델**(폴리곤·포인트·LineString) | REQ-N-008·017 |
| S43 | 확장성·비동기 | 워커 독립스케일(큐 깊이)·읽기복제 BI 오프로드·공개영역 CDN·역할별 프론트 분리 | **배포 토폴로지도** | REQ-N-009·010·019, F-081 |
| S44 | AI 플랫폼 라우팅 | Claude 기본→Ollama 폴백·중앙경유·LLM 직접호출 금지, 나노바나나 큐/콜백 | **AI 라우터 폴백 흐름** | REQ-A-007·016 |
| S45 | 연동·마스터데이터 | API·웹훅·CRM/ERP 게이트웨이·룰셋/마스터데이터 버전관리·무중단 개정 | 연동 인터페이스 맵 | REQ-F-072·080 |
---
## P5. 보안·개인정보 (5슬라이드 · 배점 10 · WT-4)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S46 | 보안 개요·불변 원칙 | "AI가 만든 것은 표시, 남의 데이터는 차단" REQ-S 20 커버리지 | **REQ-S 20항목 커버리지 표** | REQ-S 전량 |
| S47 | 보안영역 분리 | 공개 DMZ 쓰기 불가·데이터존 직결 불가·DMZ↔내부 HTTPS만 | **보안영역 분리도(망 구성)** | REQ-S-020, N-018 |
| S48 | 인증·접근통제 | JWT+2FA OTP(TOTP)·실패 잠금·admin 비번 env 재시드·RBAC 3중·admin/internal 게이트 | 인증 흐름 + RBAC 3중 구조 | REQ-S-004~008 |
| S49 | AI·이미지 보안 | 나노바나나 워터마크 항상·계약/심사 금지 고지·외부 API 워커 env only·RAG 환각차단 | **워터마크 실물** + 외부 API 격리 | REQ-S-001·002·009, A-017 |
| S50 | 개인정보·감사·안전코딩 | PII 동의·마스킹·AES-256-GCM·응답 제외·감사로그 전수·XSS/인젝션/IDOR·SCA 게이트·스택트레이스 미노출 | PII 마스킹 예시 + 감사로그 표 | REQ-S-010~017·019 |
---
## P6. 비기능 (4슬라이드 · 배점 8)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S51 | 성능·가용성 SLA | SLO 99.5%(성수기 99.9%)·백엔드 2인스턴스 롤링·PG 스탠바이+PITR·degraded 모드 | **SLA 수치표** + HA 구성도 | REQ-N-001~005·011 |
| S52 | 나노바나나 성능 | 평균 40초·1024px+JPEG 0.85 전처리·자동생성 S1·S7 한정·캐시·쿼터 | 렌더 성능 지표 카드 | REQ-N-006·007 |
| S53 | 접근성·다국어·테마·반응형 | WCAG 2.1 AA+KWCAG 2.2·비시각 대안·i18n 한/영/중/일·hreflang·라이트/다크·데스크톱/모바일 | 접근성·다국어 매트릭스 | REQ-N-012~016 |
| S54 | 관측성·데이터 | 로그·메트릭·트레이스(PII/스택트레이스 미기록)·BI 데이터마트 배치 ETL/읽기복제 | 관측성 파이프라인 | REQ-N-018·021 |
---
## P7. 멀티테넌시·확장 (3슬라이드 · 배점 5 · WT-5)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S55 | 멀티테넌트 SaaS 비전 | KINTEX #1 검증→COEX 확장, 제3전시장 대비 표준 플랫폼 | 테넌트 확장 로드맵 | §1A, WT-5 |
| S56 | 테넌트 격리 아키텍처 | tenant_id 전파·fail-closed 다층 격리(컨텍스트→서비스→MyBatis→PostGIS)·선두 복합 인덱스 | **멀티테넌시 격리 아키텍처도** | REQ-F-077·078, S-018, N-020 |
| S57 | 온보딩·2계층 관리자 | 코드 배포 없이 데이터 온보딩 6단계·플랫폼/테넌트 관리자·테넌트 스위처 | **온보딩 6단계 도식** | REQ-F-077·079 |
---
## P8. 사업관리·일정·조직 (3슬라이드 · 배점 2)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ |
|---|---|---|---|---|
| S58 | 추진 일정·Phase | Phase 1 코어(~4M)·2 워크플로/운영(~4M)·3 현장/확장(~4M+) | **일정 간트 차트**(Phase×모듈) | PLANNING §9 |
| S59 | 선행 게이트·리스크 관리 | G1 나노바나나 승인·G2 배포서버·확인필요 8항목(C1~C8) 관리 | **리스크 매트릭스**(발생×영향) + 게이트 | strategy §4, rfp_analysis §6 |
| S60 | 품질·형상관리 | Fail-Safe 배포(헬스체크·롤백)·QA 게이트·SCA CI·경계면 검증 | 품질 게이트 흐름 | REQ-N-004, S-019 |
---
## P9. 기대효과 (3슬라이드 · 가점)
| # | 슬라이드 | 핵심 메시지 | 그래픽/도표 | REQ/근거 |
|---|---|---|---|---|
| S61 | 정량 기대효과 | 견적 수일→즉시·배치 수주→수분·검수 육안→자동·위치표시도 자동·시공 예측 가능 | **기대효과 정량 인포그래픽**(5지표 Before/After) | PLANNING §1-3 |
| S62 | 정성·전략 효과 | 참가업체 영업력·홀매니저 병목 해소·데이터 주권·전시산업 표준화 | 이해관계자별 효과 카드 | strategy §3 |
| S63 | 확장 비전·마무리 | COEX·제3전시장·타 전시관 SaaS 확장, 슬로건 재각인 | 확장 비전 로드맵 + 클로징 슬로건 | WT-5 |
---
## 부록 (선택 삽입)
| 항목 | 내용 | 근거 |
|---|---|---|
| A. 확인 필요·협의 사항 | C1~C8([가정]·⚠확인필요) 집약 — 정직성 어필, 리스크 R-1~R-8 방어 요약 | rfp_analysis §6, strategy §4 |
| B. REQ 커버리지 매트릭스 | 142 REQ(F81·A20·N21·S20)↔P1~P9 반영 표(RTM 요약) | rtm.md |
| C. 100대 기능 성숙도 | 기구현 63·부분 18·신규 19 정량 | rfp_analysis §4 |
---
## deck-designer·writer 전달 핵심
- **필수 도식 12종**(반드시 제작): ①3중 해자 벤다이어그램(S04·S12) ②모듈맵(S16) ③나노바나나 파이프라인 플로우(S20) ④Before/After 슬라이더(S21) ⑤3안 비교 화면(S24) ⑥PostGIS 배선 오버레이(S27) ⑦옥션 폐루프 흐름도(S29) ⑧종합평가 스코어 비교표(S31) ⑨시스템 아키텍처 다이어그램(S41) ⑩보안영역 분리도(S47) ⑪멀티테넌시 격리 아키텍처도(S56) ⑫일정 간트(S58).
- **추진체계도**(S15)·**리스크 매트릭스**(S59)·**기대효과 인포그래픽**(S61)은 공공 제안 표준 필수.
- **아이콘**: 전량 선(line/stroke) 스타일 SVG 직접 제작(외부 라이브러리 금지) — proposal-visual-designer 산출 준수.
- **RTM 정합 검증**: 각 슬라이드 REQ 매핑을 rtm.md 반영상태(☐→☑)로 갱신. 142 REQ 누락 0 — qa가 본 목차의 REQ 열로 교차 점검.
- **배점 규율**: P3(S16~S40, 25슬라이드)가 전체의 40%. 나노바나나(S17~S23) 7슬라이드는 절대 축소 금지(최고 배점 20). 선택 기능(S40 등)은 압축 유지.

View File

@ -0,0 +1,117 @@
# 제안서 최종 QA 보고 — 킨텍스 자동전시시스템 (나라장터 표준 7대장 재조립판)
> QA: proposal-qa · 일자: 2026-07-12 · 대상: `docs/deliverables/제안서.pptx` (97슬라이드) + `_workspace/proposal/build_deck.py`
> 검증 방법: python-pptx 텍스트 추출(`_qa_extract.py`) + REQ range 전개 대조(`_qa_rtm.py`) + 구조/배점/라벨 스캔(`_qa_struct.py`) + PowerPoint COM PNG 렌더(`render_check.py`) 시각 확인.
> **최종 판정: 승인(PASS)** — 중대 결함 3건 수정 완료·재빌드·재검증. 잔여 이슈는 발주처 확인 항목([가정]/[협의])뿐(정직하게 라벨링됨).
---
## 종합 결과
| # | 검증 항목 | 판정 | 비고 |
|---|---|---|---|
| 1 | 표준 목차 정합(7대장·신설 9슬라이드) | ✅ PASS | ~Ⅶ 순차, 신설 9종 전량 실재 |
| 2 | RTM 커버리지(142 REQ) | ✅ PASS(수정 후) | 142/142. 결함 3건(F-008·F-010·F-026) 수정 |
| 3 | 배점 정렬(90/10) | ✅ PASS | 80/20·35/80 잔재 0, 재정렬 각주 존재 |
| 4 | 정직성 라벨 | ✅ PASS | tech_advisory §0과 일치 |
| 5 | 보안(노출/워터마크) | ✅ PASS | 자격증명 노출 0, AI 고지 존재 |
| 6 | 구조 품질(목차·간지·번호) | ✅ PASS | 목차↔장 순서·배점 배지 정합 |
**수정 건수: 3건**(build_deck.py 3개 편집 → 재빌드 → COM 재렌더 → 통과)
---
## 1. 표준 목차 정합 — PASS
7대장이 `g2b_standard_toc.md` §2-1(별지 제5호서식)과 순서·구조 일치. 각 장 앞 섹션 간지(7개) 배치.
| 장 | 슬라이드 | 섹션 간지 |
|---|---|---|
| 일반현황 | S6~S9 | S5 GENERAL STATUS |
| Ⅱ 전략·방법론 | S11~S25 | S10 STRATEGY & METHODOLOGY |
| Ⅲ 기술·기능 | S27~S56 | S26 TECHNOLOGY & FUNCTION |
| Ⅳ 성능·품질 | S58~S63 | S57 PERFORMANCE & QUALITY |
| 프로젝트 관리 | S65~S68 | S64 PROJECT MANAGEMENT |
| Ⅵ 프로젝트 지원 | S70~S73 | S69 PROJECT SUPPORT |
| Ⅶ 그 밖의 사항 | S75~S79 | S74 멀티테넌시·확장 |
**신설 9슬라이드 전량 실재·내용 소스 정합:**
투입인력 M/M(S7·tech_advisory §5-2) · 정량평가(S9) · 표준FW(S24) · 관리방법론(S66 PMBOK) · 인수인계교육(S70) · SM/SLA(S71 §6) · 장애비상(S72) · 기밀보안(S73) · 상생협력(S76 별표11).
---
## 2. RTM 커버리지 — PASS(수정 후 142/142)
REQ 배지가 range 표기(예: `REQ-F-073~076`, 연속 `REQ-F-045~051·067~069`)를 포함하므로 range 전개 후 대조. 이전 QA에서 모호했던 7건 우선 확정:
| REQ | 최초 상태 | 확정 결과 |
|---|---|---|
| F-008 배치안 선택/병합 편집 | 콘텐츠 O(S31)·배지 X | **수정**: S31 배지에 008 추가 |
| F-010 최종안 버전기록·좌표확정·초대링크 | 콘텐츠 X·배지 X (진성 누락) | **수정**: S31에 4번째 플로우 칩 "[최종안 확정·초대링크]" + 캡션 + 배지 010 추가 |
| F-026 AI자료 기반 견적서 응찰·PDF·버전 | 콘텐츠 O(S36)·배지 X | **수정**: S36 배지에 026 추가 |
| F-032 낙찰→발주 자동전환 | 배지 O(S39) | 이상 없음 |
| A-003 구조화 프롬프트 보존/교체 | 배지 O(S20) | 이상 없음 |
| A-005 Before/After 슬라이더 | 배지 O(S29) | 이상 없음 |
| A-008 간판 텍스트 비전 검수 | 배지 O(S30) | 이상 없음 |
**range 전개 후 잔여 유일 진성 누락 = F-010**(초대링크/최종안 확정 콘텐츠·배지 모두 부재) → 콘텐츠+배지 동시 보강으로 해소.
수정 후 union(단독 토큰 + range 전개) 대조: **142/142 전량 반영, 미반영 0.** 부록 B(S82) "미반영 0" 표기가 사실과 일치함(수정 전에는 F-008/010/026 미배지로 과장 소지 → 해소).
---
## 3. 배점 정렬 — PASS
- 표기 일관: **기술 90(정성70+정량20) / 가격 10** (S3 배점맵, S4 [가정] 간지). `기술 80`·`가격 20`·`80/20`·`35/80` 잔재 **0건**.
- 재정렬 각주 존재: S3 "실제 공고 확인 시 재정렬", S4·S7·S81 동일 취지. 정보화 지침 제18조 원칙값 근거 명기.
- S3 배점맵이 각 부문↔장(Ⅱ·Ⅲ최다·Ⅳ·Ⅴ·Ⅵ·Ⅰ·Ⅶ) 정확 매핑.
---
## 4. 정직성 라벨 — PASS
표본: [검증됨] 16 · [구현 계획] 4 · [협의] 2 · [가정] 5 · [Non-Goal] 1. `tech_advisory §0`(라이브 실적 vs 구현 계획)과 일치:
- **[검증됨]**(라이브): 나노바나나 Gemini 워커(S17·19), PostGIS 매퍼(S17), JWT+2FA(S52), CI/CD Fail-Safe(S24), 인프라·코어(S48) — §0-1과 일치.
- **[구현 계획]**: 옥션 M15 폐루프(S17·36·37 "Phase D-M15"), 멀티테넌시(S27), F-081 프론트 분리(S60) — §0-2와 일치. 과장(구현 완료 오기) 0.
---
## 5. 보안 — PASS
- 전 슬라이드 텍스트 스캔: IPv4/서버IP · `sk-`/`AIza` API키값 · SSH계정 · 비밀번호 값 노출 **0건**.
- AI 생성/워터마크 고지: 갤러리 도입부(S85) "본 화면은 Google Stitch 기반 AI 생성 디자인 시안…" 고지 박스 존재. M5 이미지 워터마크 불변(REQ-S-009) [검증됨] S22·50·53. 갤러리 전 시안(S85~97) "AI 생성" 고지 병기.
---
## 6. 구조 품질 — PASS
- 목차(S2 "나라장터 정보시스템 표준 7대장")↔실제 장 순서·슬라이드 번호 정합.
- 간지 배점 배지(S3): 부문↔장 매핑·배점 숫자 정확.
- 부록 A(100대 기능 성숙도)·B(REQ 매트릭스)·C(가정·협의 집약)·D(화면 갤러리) 순 구성.
---
## 수정 내역 (build_deck.py)
| 슬라이드 | 편집 | 커버 REQ |
|---|---|---|
| S31 배치 3안 | 배지 `REQ-F-007·A-009`→`REQ-F-007·008·010·A-009`; 액션칩 3→4([최종안 확정·초대링크] 추가); 캡션에 F-008·F-010 근거 명시 | F-008, F-010 |
| S36 옥션 폐루프 | 배지 `REQ-F-025·031`→`REQ-F-025·026·031` (콘텐츠 "견적서 제출 PDF·버전" 기존재) | F-026 |
재빌드(97슬라이드) → COM 렌더 → S31/S36 시각 확인(오버플로·깨짐 없음, 4칩 균등 배치).
---
## 잔여 이슈 (결함 아님 — 발주처 확인 항목)
`g2b_standard_toc.md §7` T1~T5 및 부록 C 집약대로 정직 라벨링됨. QA 통과를 막지 않음:
- 배점 90/10, 예산·M/M(~150), 투입인력 실명·단가 → [가정], 공고 확정 시 재정렬/기재
- 상생협력·하도급 구체 지분 → [공고·컨소 확정 시 기재]
- wayfinding 비콘·IoT·사이니지 HW(F-070·052~055) → [협의] 시설 인프라 전제
- kxwp 연동(F-022) → ⚠확인필요
---
## 최종 판정: **승인(PASS)**
7대장 표준 목차·신설 9슬라이드 완비, RTM 142/142(수정 3건 후), 배점 90/10 일관, 정직성 라벨 정합, 보안 노출 0, AI 고지 존재. 실제 공고 확인 시 배점·목차·투입계획 재정렬만 반영하면 제출 가능.

View File

@ -0,0 +1,150 @@
# 킨텍스 자동전시시스템 구축 — 수주 전략서 (Proposal Strategy)
> 작성: proposal-strategist · 작성일: 2026-07-11 · 버전 v1.0
> 입력: `rfp_analysis.md`(REQ 142 · 배점 가정 기술80/가격20) · `rtm.md`(P1~P9 매핑) · `docs/PLANNING.md` §1~§2
> 원칙: **배점이 전략을 지배한다.** 과장·허위 금지 — rfp_analysis에 근거 있는 주장만 기재. 시크릿(키·비번·내부IP) 미기재.
> 성격: 실제 RFP 부재 → 평가배점·제출규격은 **[가정]**(공공 SI 통례). 실제 공고 확인 시 교체.
---
## 0. 전략 요약 (Executive Summary)
**한 문장 포지셔닝**: 킨텍스 자동전시시스템은 "도면과 표를 보여주는" 기존 전시테크가 아니라, **"신청서를 내는 순간 시공 후 사진을 먼저 보여주고, 그 사진으로 발주까지 종결하는" 유일한 폐루프 플랫폼**이다.
**수주 논리 3단**:
1. **배점의 44%(35/80)가 P3 기술제안**(AI 시각화·설계 20 + 기능 구현 15)에 몰려 있다 → 제안서 분량·메시지·그래픽의 40% 이상을 P3에, 그중 절반을 나노바나나+M2~M5 폐루프에 집중한다.
2. 경쟁(글로벌 전시테크·기존 공공 SI)은 **나노바나나 시공 예측 + PostGIS 실측 공간데이터 + 옥션 폐루프**의 결합을 재현할 수 없다 → 이 3결합을 "우리만의 해자(moat)"로 전면화한다.
3. 공공 발주처의 최대 리스크(외부 API·개인정보·안전서류 오용)를 **선제 방어 논리**(G1 승인 게이트+degraded 폴백, AI 워터마크 불변, PII·테넌트 3중 격리)로 무장해 감점을 원천 차단한다.
**슬로건 후보(표지·간지용)**:
- 메인: **"신청서를 내는 순간, 시공 후 사진을 먼저 본다."** (PLANNING §1-2 비전 직인용)
- 서브: **"설계 → 시각화 → 발주 → 시공 → 운영 → 분석. 킨텍스에서 한 흐름으로 끝난다."**
---
## 1. Win Theme (차별화 메시지 5선)
> 각 Win Theme = ①발주처 핵심 니즈 ②우리 차별화 메시지(슬로건) ③배점 정렬 ④근거(REQ/PLANNING) ⑤증빙 방식. 배점 가중 순으로 배열.
### WT-1. 시공 전에 사진으로 의사결정한다 — 나노바나나 시공 예측 (최우선 · 배점 20)
- **발주처 니즈**: 참가업체·주최자가 "도면을 읽지 못해 개장일에야 결과를 처음 본다"(PLANNING §2 참가업체 Pain). 조감도 외주는 비용·시간.
- **슬로건**: **"도면이 아니라 사진으로 컨펌한다."**
- **차별화 메시지**: image-to-image 구조보존(골격·치수·통로·카메라 앵글 잠금, 스타일만 사실화)으로 표준 샷세트 S1~S7(정면·야간조명·통로·내부·Before/After·배선 오버레이·홀전경)을 평균 40초에 자동 생성. **경쟁 전시테크가 보유하지 못한 기능**(rfp_analysis §4 win 우선순위 ①).
- **배점 정렬**: "AI 시각화·설계 자동화(핵심 차별화) 20점" 직격. 대응 REQ-A-001~008, REQ-F-019.
- **증빙**: Before/After 드래그 슬라이더 실물 캡처, 샷세트 예시 이미지(워터마크 포함), RenderJob 파이프라인 도식.
### WT-2. 배치·설계·배선을 AI가 3안 자동생성하고 규정을 자동검증한다 (배점 20+15 교차)
- **발주처 니즈**: 주최자 CAD 수작업 수일~수주, 홀매니저 육안 규정 검수 병목(PLANNING §1-3, §3-2).
- **슬로건**: **"수 분 내 3안, 제출 전 규정 자동검증."**
- **차별화 메시지**: 제약 솔버+LLM 조건해석으로 부스 배치/부스 설계 각 3안 자동생성·선택/병합·버전관리(REQ-F-007~013). 규정(피난통로·바닥하중 홀별·높이 5m·리깅 6.5~8.5m·방염·이격)을 제출 전 자동 플래깅. PostGIS 최단경로로 트렌치→분전반 배선·위치표시도 자동 작도(수기 폐지, REQ-F-015~017).
- **배점 정렬**: AI 시각화·설계(핵심) 20 + 기능 구현 15의 코어. 대응 REQ-A-009·010·020, REQ-F-007~018.
- **증빙**: 3안 비교 화면, 규정 위반 플래그 대시보드, PostGIS 배선 경로 오버레이, "사람이 최종 확정"(AI는 보조) 명시.
### WT-3. AI 설계자료가 곧 발주 근거가 된다 — 공사 옥션 폐루프 (배점 15 · 폐루프 핵심)
- **발주처 니즈**: 등록업체 739개가 단순 리스트+엑셀, 매칭·견적 비교 부재(PLANNING §3-2).
- **슬로건**: **"사진 수준 자료로 응찰하고, 한 흐름에서 발주까지 끝낸다."**
- **차별화 메시지**: M2~M5가 생성한 배치·설계·배선/BOQ·시공 예측 이미지가 곧 역경매 응찰 근거. 실시간 순위·종합평가 낙찰(가격+평판+납기)→계약/발주 자동전환(REQ-F-025~032). **미등록 업체 응찰 원천 차단(403)** 으로 킨텍스 "등록업체 시공 필수" 규정을 시스템이 강제.
- **배점 정렬**: 기능 구현 15 + 보안(등록 게이트). 대응 REQ-F-025~032, REQ-S-007.
- **증빙**: 옥션 흐름도(설계자료→응찰→실시간순위→낙찰→발주), 종합평가 스코어 비교표, 등록 게이트 차단 시퀀스.
### WT-4. 공공이 안심하는 AI — 워터마크·PII·테넌트 3중 격리 불변 (배점 10)
- **발주처 니즈**: 생성 이미지의 계약·심사 서류 오용 우려, 관람객/리드 개인정보 보호, 다전시관 데이터 혼입 방지.
- **슬로건**: **"AI가 만든 것은 반드시 표시하고, 남의 데이터는 원천 차단한다."**
- **차별화 메시지**: M5 이미지 응답에 워터마크 **항상** 포함(watermarkRequired:true·계약/심사 서류 금지 고지, REQ-S-009). PII 동의·마스킹·AES-256-GCM·응답 완전 제외(REQ-S-013). fail-closed 다층 테넌트 격리(컨텍스트 필터→서비스 가드→MyBatis 인터셉터→PostGIS, REQ-S-018). 외부 API는 Ollama 기본+Claude/Gemini만 승인 예외(REQ-S-001).
- **배점 정렬**: 보안·개인정보 10점. 대응 REQ-S 20항목 전량.
- **증빙**: 보안영역 분리도(DMZ/내부망), 워터마크 실물, 감사로그 전수 표, PII 마스킹 예시.
### WT-5. 킨텍스에서 검증하고 코엑스로 확장한다 — 멀티테넌트 SaaS (배점 5 · 미래가치)
- **발주처 니즈**: 킨텍스 제3전시장(2028, 178,000㎡) 확장 + 국내 전시산업 표준 플랫폼화.
- **슬로건**: **"코드 배포 없이 데이터 온보딩만으로 새 전시관을 연다."**
- **차별화 메시지**: KINTEX=기준 테넌트 #1, COEX=테넌트 #2 온보딩 6단계(마스터데이터 입력 수준). tenant_id 전파·2계층 관리자(플랫폼/테넌트)·컨텍스트 해소(REQ-F-077~079). 기능은 전부 보존, 데이터·권한만 전시관 단위 분리.
- **배점 정렬**: 멀티테넌시·확장성 5점 + 사업 미래 비전. 대응 REQ-F-077~079, REQ-N-020, REQ-S-018.
- **증빙**: 멀티테넌시 격리 아키텍처, 온보딩 6단계 도식, 테넌트 스위처 화면.
---
## 2. 평가항목별 대응 전략 (배점 가중)
> 각 평가부문에 "무엇을·어떻게 보여줄지". 정량 항목=수치, 정성 항목=스토리. 배점 큰 순.
| # | 평가부문 | 배점 | 대응 전략 (증빙·실적·방법론·도표) | 주요 REQ | 목차 |
|---|---|---|---|---|---|
| 1 | **AI 시각화·설계 자동화(핵심)** | **20** | WT-1·WT-2 전면. 나노바나나 파이프라인 도식 + Before/After 실물 + 3안 생성·규정검증·PostGIS 배선 화면. "결정적 알고리즘 중심(제약 솔버) + LLM 보조 + 사람 최종확정" 3층 구조로 **AI 신뢰성** 강조 | REQ-A-001~010·020, F-007~019 | P3 |
| 2 | **기능 구현(M1~M18)** | 15 | 모듈맵 1장으로 전 모듈 조망 → 배점 큰 옥션(WT-3)·관람·BI를 상세, 선택(P2) 기능은 "구현 로드맵"으로 간결. 100대 기능 성숙도(기구현 63·부분 18·신규 19) **정량 제시** | REQ-F 전량, 특히 F-025~032 | P3 |
| 3 | **AI 설계 자동화(폐루프)** | (20 내) | WT-3. 옥션 흐름도 + 종합평가 스코어 비교표 + 발주 자동전환. AI 자료→응찰 근거 연결이 **경쟁이 못 하는 폐루프** | REQ-F-025~032, S-007 | P3 |
| 4 | 아키텍처·기술스택 | 10 | 확정 스택(React18/19+Spring Boot 3.x+MyBatis+PostgreSQL/PostGIS+Redis+나노바나나 Python 워커) 정합. 단일 공간 원천(M2폴리곤·M4배선 매퍼 권위)·워커 독립스케일·읽기복제 오프로드·역할별 프론트 분리 도식 | REQ-N-008~010·017·019, A-007·016, F-072·080·081 | P4 |
| 5 | 보안·개인정보(불변) | 10 | WT-4. REQ-S 20항목 커버리지 표 + 보안영역 분리도 + 워터마크·PII·감사로그·2FA. **감점/실격 리스크를 정면 방어** | REQ-S 20 전량, A-006·017 | P5 |
| 6 | 비기능(성능·가용성·접근성·다국어) | 8 | SLO 99.5%(성수기 99.9% 지향)·백엔드 2인스턴스 롤링 무중단·PG 스탠바이+PITR·나노바나나 40초/1024px·WCAG 2.1 AA+KWCAG 2.2·i18n 한/영/중/일·라이트/다크. **수치 SLA 표** | REQ-N 21 전량 | P6 |
| 7 | 멀티테넌시·확장성(COEX) | 5 | WT-5. 격리 아키텍처 + 온보딩 6단계 + tenant_id 선두 복합 인덱스. 제3전시장·타 전시관 확장 미래가치 | REQ-F-077~079, N-020, S-018 | P7 |
| 8 | 사업이해·추진전략 | 10 | As-Is 병목(HWP·CAD 수작업·kxwp·유틸리티 파편화) → To-Be 정량목표(견적 즉시·배치 수분·위치표시도 자동) 대비표. **킨텍스 실무 프로세스 정확 이해**(D-150/30/25/7 타임라인)로 도메인 신뢰 확보 | 전체 맥락 | P1·P2 |
| 9 | 사업관리·품질·일정 | 2 | Phase 1~3(코어→워크플로/운영→현장/확장) 간트 + QA 게이트 + Fail-Safe 배포 + 선행게이트(G1·G2) 관리. 배점 낮으나 실행력 신뢰 | REQ-N-004, S-019 | P8 |
| — | 기대효과 | (가점) | To-Be 정량 개선(견적 수일→즉시, 배치 수주→수분, 검수 육안→자동) + ROI·표준화·확장 스토리 | 목표표(§1-3) | P9 |
**분량 배분 원칙**: P3(35점)에 전체 슬라이드의 ~40%, 그중 절반을 나노바나나+M2~M5. P4·P5(각 10점) 각 ~8%, P6(8점) ~6%, P1+P2(10점) ~16%, P7(5점) ~5%, P8(2점) ~5%, P9 ~5%. 선택 기능은 압축.
---
## 3. 경쟁사 대비 강점 (Competitive Positioning)
> 근거 있는 비교만. 경쟁을 폄하하지 않고 "우리가 결합한 것"을 강조.
### 3-1. 글로벌 전시테크(ExpoPlatform·VenueSight·Swapcard·RainFocus 등) 대비
| 축 | 글로벌 전시테크 | 킨텍스 자동전시시스템 | 근거 |
|---|---|---|---|
| 시공 결과 시각화 | 도면·3D 뷰어 수준, 사진급 시공 예측 **미보유** | 나노바나나 image-to-image 구조보존 사진 생성 | PLANNING §1-2·§6, WT-1 |
| 공간 데이터 | 좌표·구역 관리 | **PostGIS 실측**(부스 폴리곤·트렌치 포인트·배선 LineString) 단일 원천 | REQ-N-008·017, data.md |
| 발주 연결 | 리드/매칭까지, 공사 발주 폐루프 약함 | AI 설계자료→역경매 응찰→낙찰→발주 자동전환 폐루프 | WT-3, §M15 |
| 킨텍스 도메인 정합 | 범용 글로벌 규격 | 킨텍스 규정(높이 5m·리깅·방염·바닥하중)·요율(2,250원/㎡)·D-데이 타임라인 내재화 | PLANNING §3, §7-1 |
| 데이터 주권 | 해외 SaaS·데이터 국외 | 온프레미스 Ollama 기본, 외부 AI는 승인 예외만·워커 env 격리 | REQ-S-001·002 |
**메시지**: "글로벌 전시테크의 기능(부스판매·리드·비즈매칭·BI)은 이식하되, 그들이 못 하는 **실측 공간데이터×시공 예측 이미지×옥션 폐루프**를 관통시켰다."
### 3-2. 기존 공공 SI(범용 웹시스템 구축) 대비
| 축 | 범용 SI | 킨텍스 자동전시시스템 | 근거 |
|---|---|---|---|
| AI 내재화 | 챗봇·검색 부가 수준 | 배치 3안·규정검증·시공 예측·수요예측이 **핵심 업무 자동화** | REQ-A 20항목 |
| 공간 연산 | 일반 CRUD | PostGIS GiST 실시간 공간쿼리(트렌치 KNN·통로버퍼·비상구 교차) | REQ-N-008 |
| 표준 프레임워크 | 개별 구축 | GUARDiA 표준(WISE/UIWS) 공통·시스템관리·JWT+2FA 이식으로 검증된 자산 재사용 | REQ-F-071~076, §5B |
| 확장성 | 단일 기관 | 멀티테넌트 SaaS(COEX 온보딩)로 재사용·표준화 | WT-5, §1A |
**메시지**: "범용 SI가 아니라 전시 도메인 전용 AI 자동화 플랫폼. 검증된 공통 프레임워크(UIWS/WISE) 위에 도메인 코어(M2~M5)와 폐루프(M15)를 얹어 **개발 리스크와 기간을 동시에 단축**한다."
### 3-3. 우리만의 3중 해자(Moat) — 한 장 요약
**나노바나나 시공 예측 이미지 ∩ PostGIS 실측 공간데이터 ∩ 공사 옥션 폐루프** — 이 세 가지의 결합은 단일 요소로는 모방 가능하나 **셋의 결합은 재현 난이도가 높다**. 제안서 P3 도입부에 "3원 벤다이어그램" 1장으로 각인.
---
## 4. 리스크 방어 논리 ([가정]·⚠확인필요 정면 대응)
> 평가위원이 반드시 파고들 "약한 고리"를 **선제적으로 드러내고 방어**한다. 숨기면 감점, 정면 대응하면 신뢰.
| # | 리스크(발주처 우려) | 방어 논리 (정면 대응) | 근거 | 제안서 위치 |
|---|---|---|---|---|
| R-1 | **G1 나노바나나 Gemini 외부 API 미승인 시 핵심 기능 붕괴?** | "승인 게이트 + degraded 폴백" 2중 설계. G1 미승인/Gemini 장애 시 워커 **목(mock) 응답·구조·워터마크 유지**로 화면·워크플로 계속 동작(REQ-N-005). 승인 확보를 **선행 게이트로 프로젝트 관리**(kintex-pm 통제). 외부 API는 워커 env only·백엔드 미취급으로 보안 격리 | REQ-S-001·002, N-005, C5 | P3 도입·P5·P8 |
| R-2 | **CAD·트렌치 실측 데이터 미확보로 배선·배치 정확도 의심?** | 미확보 정직 고지 + "공개 스펙 기반 가정 그리드 + '가정' 라벨"로 데모, **실측 반영을 킨텍스 협의 선행작업으로 명시**. 확보 자산(홀별 평면도 JPG 15장·CAD DWG "평면,트렌치.dwg")으로 실현성 입증 | C3, PLANNING R4, CLAUDE.md 확보자산 | P3 배선·P8 |
| R-3 | **kxwp/kxfp 폐쇄형 API 미공개로 제출 연동 불가?** | "초기 수동 릴레이 → 목표 API 연동" 단계적 접근. 웹폼→HWP/PDF 자동생성까지는 API 무관하게 완결, 릴레이 단계만 킨텍스 IT 협의(⚠확인필요 명시). 리스크를 범위·일정에 반영 | REQ-F-022, C2 | P3 서류·P8 |
| R-4 | **평가배점·제출규격이 가정이라 목차 정합성 의심?** | 배점 가정을 **투명 고지**하고 "실제 공고 확인 시 목차·분량 즉시 재정렬" 유연성 제시. 목차는 공공 SI 통례(사업이해→전략→기술→아키→보안→비기능→확장→관리→기대효과)로 안전 정렬 | rfp_analysis §4·§5, C1 | P1·목차 간지 |
| R-5 | **간판 한글 텍스트 등 생성 이미지 품질 편차?** | 간판 텍스트 비전 오탈자 검수 + 한글 렌더 실패 시 **간판영역 후처리 합성**(REQ-A-008). 자동생성은 S1·S7 한정·캐시·쿼터로 품질/비용 제어(REQ-N-007) | REQ-A-008, N-007, R5 | P3 AI·P6 |
| R-6 | **AI 환각·오판(규정 오검증·설계 오류) 책임?** | "AI는 보조, 사람이 최종 확정" 원칙 불변. 규정검증=플래깅까지, 구조안전 판정은 구조기술사/킨텍스(Non-Goal 명시). AI 답변은 RAG 근거·인용·환각 차단(abstain)(REQ-A-017) | Non-Goal §1-4, REQ-A-017 | P3·P5 |
| R-7 | **사이니지 HW·비콘·IoT 센서 연동 불확실?** | 선택(P2) 기능으로 분류, "킨텍스 시설 인프라 협의 전제" 명시. 미협의 시에도 코어 가치 불변(WT-1~4는 HW 독립) | REQ-F-070·052~055, C7 | P3 현장·P8 |
| R-8 | **다전시관 확장이 개발 부담을 키우지 않나?** | 멀티테넌시=격리 레이어만 추가(tenant_id 전파), 기능 로직 불변. 백필 전 단일테넌트 무중단 동작, 검증 후 필터 활성화(회귀 방지) | §1A-5, REQ-F-077 | P7 |
**방어 원칙**: 모든 [가정]·⚠확인필요 항목은 제안서 부록 "확인 필요·협의 사항" 1장으로 집약해 **정직성**을 오히려 강점화(발주처와의 협업 자세 어필).
---
## 5. writer·deck-designer 전달 지침
- **메시지 우선순위**: WT-1 > WT-2 > WT-3 > WT-4 > WT-5 순으로 슬라이드 무게. P3 도입부에 "3중 해자" 벤다이어그램 필수.
- **정량 강조**: 견적 수일→즉시, 배치 수주→수분, 검수 육안→자동, 40초 렌더, SLO 99.5%, REQ 142 커버리지 등 숫자를 표지·간지·기대효과에 반복 노출.
- **AI 신뢰 프레이밍**: "결정적 알고리즘(제약 솔버·PostGIS) 중심 + LLM 보조 + 사람 최종확정" 3층을 모든 AI 슬라이드 캡션에 삽입 → 공공 AI 불안 해소.
- **RTM 정합**: 전 142 REQ가 P1~P9 어딘가에 반영되도록 목차(§proposal_outline.md)의 REQ 매핑을 준수. 누락 0.
- **금지**: 시크릿·내부IP·미확보 데이터의 확정적 서술 금지. 미확보는 "가정/협의 전제" 라벨 필수.

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
"""PowerPoint COM 렌더링 검증 — 전 슬라이드 PNG export."""
import sys, os, glob
import win32com.client as win32
PPTX = sys.argv[1] if len(sys.argv) > 1 else r"C:\GUARDiA\workspace\kintex\docs\deliverables\제안서.pptx"
OUT = sys.argv[2] if len(sys.argv) > 2 else r"C:\Users\ython\AppData\Local\Temp\claude\C--GUARDiA-workspace-kintex\85f77606-5d61-424b-8a38-f8b61b56b54c\scratchpad\render"
os.makedirs(OUT, exist_ok=True)
for f in glob.glob(os.path.join(OUT, "*.png")):
try: os.remove(f)
except: pass
app = win32.Dispatch("PowerPoint.Application")
try:
app.Visible = True
except Exception:
pass
pres = app.Presentations.Open(PPTX, ReadOnly=True, Untitled=False, WithWindow=False)
n = pres.Slides.Count
for i in range(1, n+1):
pres.Slides(i).Export(os.path.join(OUT, f"s{i:03d}.png"), "PNG", 1280, 720)
pres.Close(); app.Quit()
print("RENDERED", n, "slides ->", OUT)

View File

@ -0,0 +1,366 @@
# 킨텍스 자동전시시스템 구축 — 요구사항 분석서 (가상 RFP 기반)
> 작성: rfp-analyst · 작성일: 2026-07-11 · 버전 v1.0
> **성격**: 실제 RFP 문서는 부재하며, 발주처(킨텍스, 한국국제전시장)의 내부 기획 문서를 "가상 RFP"의 요구사항 원천으로 삼아 제안서 작성·평가 대응용으로 구조화 추출한 것이다.
> **근거 문서(출처)**: `docs/PLANNING.md`(v3.0), `docs/FEATURE_BACKLOG_100.md`(v1.1), `docs/SECURITY.md`, `docs/architecture/{app,system,tech,data,network}.md`, `CLAUDE.md`(kintex 섹션).
> **원칙**: 문서에 없는 요구는 발명하지 않는다(각 REQ에 출처 병기). 비밀번호·키·IP 등 시크릿은 미기재. 가정(assumption)은 "[가정]"으로 명시.
---
## 1. 사업 개요 (가정 포함)
| 항목 | 내용 | 근거/비고 |
|---|---|---|
| 사업명 | 킨텍스 자동전시시스템(Exhibition Automation Platform) 구축 | PLANNING §1-2-1 |
| 발주처(가정) | 킨텍스(KINTEX, 한국국제전시장) — 기준 테넌트 #1 | PLANNING §1A-1 |
| 사업 목적 | 전시장 운영 전 주기(홀 배정→부스 배치→장치공사 설계→전기/조명→네트워크/유틸리티 배선→반입/반출→관람객·정산·경영분석)를 AI로 자동 설계·검증하고, 시공 결과를 나노바나나(Gemini 이미지 생성)로 "공사 후 사진"처럼 사전 시각화 | PLANNING §1-2 |
| 정체성 | 부스 시공 도구를 넘어 전시 기획·판매·시공·운영·관람·사후분석 전 주기를 아우르는 베뉴 운영 플랫폼 + 다중 전시관(Venue) 멀티테넌트 SaaS(KINTEX #1, COEX 온보딩) | PLANNING §1-2-1·§1A |
| 범위 모듈 | 코어 M2~M5(P0), 판매·발주·정산 M1·M6·M7·M9·M15, 관람·참가 M8·M10~M14, 경영·콘텐츠·관리 M16~M18, 공통/시스템관리 레이어 §5B, 멀티테넌시 §1A | PLANNING §4-1 |
| 확정 기술스택 | React 18/19(Vite·TS) + Spring Boot 3.x(Java 17) + MyBatis + PostgreSQL(PostGIS) + Redis + 나노바나나 Python 워커 사이드카 | PLANNING §8, CLAUDE.md |
| AI 플랫폼 | 텍스트=Claude 기본(AiTextRouter/AiConfig, `ANTHROPIC_API_KEY` env) → Ollama 폴백 / 이미지=나노바나나(Gemini `gemini-3.1-flash-image-preview`) | FEATURE_BACKLOG §0, PLANNING §6-4 |
| 사용자 역할 | 6 행사역할(주최자·참가업체·장치/시공업체·홀매니저·관람객·일반대중) + 2 관리자계층(테넌트 관리자·플랫폼 슈퍼관리자) | PLANNING §2·§1A-4 |
| 로드맵(가정) | Phase 1 코어(~4개월)·Phase 2 워크플로/운영(~4개월)·Phase 3 현장/확장(~4개월+) | PLANNING §9 |
| 선행 게이트 | G1 나노바나나 Gemini 승인(외부 API), G2 개발/배포 서버(kintex.zioinfo.co.kr, PostGIS+Redis) | CLAUDE.md, PLANNING §10 R12 |
**[가정] 명시 항목**: 예산·계약방식·정확한 사업기간·평가배점·제출규격은 실제 RFP가 없어 공공 SI 통례를 가정한다(§4·§5).
---
## 2. 과업 범위
### 2-1. 기능 과업 범위 (모듈별)
| 계열 | 모듈 | 우선순위 | 범위 요지 | 출처 |
|---|---|---|---|---|
| 코어(불변) | M2 플로어플랜 스튜디오 | P0 | 부스 배치 3안 자동생성·선택/병합·규정 자동검증·버전 | PLANNING §M2 |
| 코어 | M3 부스 설계 스튜디오 | P0 | 조립/독립부스 설계 3안·규정 사전검증·도면 비전추출 | PLANNING §M3 |
| 코어 | M4 유틸리티 설계 | P0 | 전기·조명 용량/분전반/배선(M4a), 네트워크·급배수·압축공기·위치표시도(M4b) | PLANNING §M4 |
| 코어 | M5 나노바나나 시각화 | P0 | 시공 후 예상사진 표준 샷세트(S1~S7)·Before/After·워터마크 | PLANNING §M5·§6 |
| 판매·발주 | M1 홀 배정·자동견적 | P1 | 가용성 캘린더·규칙엔진 견적·배정신청 서식 | PLANNING §M1 |
| 판매·발주 | M6 서류·마일스톤 | P1 | D-150/30/25/7 역산·웹폼→HWP/PDF·AI 서류검수·kxwp 릴레이 | PLANNING §M6 |
| 판매·발주 | M7 등록업체 매칭 | P1 | 14분류×739개 업체 추천·RFQ·등록업체 검증 | PLANNING §M7 |
| 판매·발주 | M15 공사/장치 옥션 | P1(핵심) | 역경매·견적서(Quotation) 응찰·실시간 순위·종합평가 낙찰·발주전환 | PLANNING §M15 |
| 판매·발주 | M9 정산·결제 | P1 | 납부스케줄·PG결제·예치금 실사용 정산·세금계산서 | PLANNING §M9 |
| 물류 | M8 반입/반출 물류 | P2 | 슬롯예약·통행증 QR·중량물 우선·지게차 | PLANNING §M8 |
| 관람 | M10 관람객 등록·배지·리드 | P1 | 사전등록·모바일 배지/QR·현장 체크인·리드캡처·오프라인 폴백 | PLANNING §M10 |
| 관람 | M11 비즈니스 매칭 | P2 | AI 미팅추천·슬롯예약·성과 리포트 | PLANNING §M11 |
| 관람 | M12 마케팅·공개 홍보사이트 | P1 | SEO·다국어 공개사이트·인터랙티브 플로어플랜·세그먼트 EDM | PLANNING §M12 |
| 관람 | M13 wayfinding | P2 | 실내 내비(블루닷/존레벨)·POI 검색·경로 | PLANNING §M13 |
| 현장 | M14 현장운영 | P2 | 혼잡·전력부하·주차·안전위반 플래그 | PLANNING §M14 |
| 경영 | M16 경영분석 BI | P1(승격) | 가동률·매출구성·P&L·RevPAD·리텐션/LTV·수요예측·KPI(운영사+참가사 관점 분리) | PLANNING §M16·§M16-1 |
| 경영 | M17 CMS | P1 | 콘텐츠·공지·마이크로사이트·다국어·게시 워크플로·사이니지 | PLANNING §M17 |
| 관리 | M18 관리자 백오피스 | P1 | RBAC·감사·마스터데이터·룰셋 버전관리 | PLANNING §M18 |
| 공통 | §5B 공통/시스템관리 레이어 | P1(선행기반) | UIWS 표준 이식: 시스템관리·공통 업무모듈·JWT+2FA/OTP | PLANNING §5B |
| 멀티테넌시 | §1A 다중 전시관 SaaS | P1 | tenant_id 격리·컨텍스트 해소·2계층 관리자·온보딩 | PLANNING §1A·§8-2 |
### 2-2. 비기능 과업 범위
| 축 | 범위 | 출처 |
|---|---|---|
| 성능 | RenderJob 평균 40초·1024px 전처리, PostGIS GiST 실시간 응답, 읽기복제 BI 오프로드 | system.md §3-3, PLANNING §6-5 |
| 가용성/HA | 코어 경로 SLO 99.5%(성수기 99.9% 지향), 백엔드 최소 2인스턴스·롤링 무중단, PG 프라이머리-스탠바이+PITR, degraded 모드 | system.md §3-2 |
| 확장성 | 워커 독립 스케일(큐 깊이 기반), 공개영역 CDN, 역할별 프론트 번들 분리 | system.md §3-1 |
| 접근성 | WCAG 2.1 AA(핵심여정 AAA 지향), 공공 KWCAG 2.2 병행 | FEATURE_BACKLOG §6-N1 |
| 다국어 | 한(기본)+영/중/일, i18n 외부화·hreflang·로케일 포맷 | FEATURE_BACKLOG §6-N3 |
| 테마 | CSS 토큰 라이트/다크 2벌·시스템 연동 | FEATURE_BACKLOG §6-N4 |
| 공간데이터 | 부스 폴리곤·트렌치 포인트·배선 LineString 단일 PostGIS 원천 | data.md, PLANNING §7-3 |
| 보안 | §3 참조(REQ-S) | SECURITY.md |
### 2-3. 범위 제외(Non-Goals)
- 킨텍스 임대계약 법적 전자계약 체결(Phase 3 이후 검토) — 초기엔 견적·서류 준비까지. (PLANNING §1-4)
- 구조계산서의 구조 안전성 최종 판정(AI는 항목 체크·누락 검출까지, 판정은 구조기술사/킨텍스). (PLANNING §1-4·§M3)
- 소방 법규 최종 유권해석·승인(시스템은 사전 필터). (PLANNING §M2)
- 신규 전시관 고유 도메인 로직 자동생성(온보딩은 마스터데이터 입력 수준). (PLANNING §1-4·§1A-5)
---
## 3. 요구사항 목록
> ID 체계: 기능 `REQ-F-###`, 비기능 `REQ-N-###`, 보안 `REQ-S-###`, AI `REQ-A-###`.
> 유형: **필수** / **선택**(P2·확장·조건부). 확인 필요 항목은 "⚠확인필요"로 표기(삭제 금지).
### 3-1. 기능 요구사항 (REQ-F)
#### M1 판매·홀배정·견적
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-001 | 홀/반홀 가용성 실시간 캘린더 조회(행사일정 DB 연동) | 필수 | F001, §M1 |
| REQ-F-002 | 규칙엔진 자동 견적(2,250원/㎡×면적×일수×성수기/비수기/1전시장 계수+초과시간+예치금 15~20%) | 필수 | F002, §M1 |
| REQ-F-003 | 배정신청 웹폼 → 킨텍스 제출 서식(HWP) 자동 생성 | 필수 | F004, §M1 |
| REQ-F-004 | 부스 판매 인벤토리 관리(부스 단위 판매/홀드/예약 상태) | 필수 | F005 |
| REQ-F-005 | 온라인 부스 셀프 선택·판매(인터랙티브 맵) | 필수 | F006 |
| REQ-F-006 | 임대계약 전자서명 워크플로 | 선택 | F007(Phase3, Non-Goal 완화) |
#### M2 플로어플랜 스튜디오 (P0)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-007 | 홀 선택·조건 입력 후 부스 배치 정확히 3안 자동생성(제약 솔버, 상이한 최적화 목표) | 필수 | F008, §M2 |
| REQ-F-008 | 배치안 선택 또는 구역/블록 병합 편집(안별 레이어 토글·드래그) | 필수 | F009, §M2 |
| REQ-F-009 | 배치 규정 자동검증(피난통로·바닥하중 홀별·비상구·복층가능홀·소방 체크리스트), 병합 후 재검증 | 필수 | F010, §M2 |
| REQ-F-010 | 최종안 버전 기록(선택/병합 출처 안번호 추적)·부스별 좌표/번호 확정·참가업체 초대 링크 발급 | 필수 | §M2 |
#### M3 부스 설계 스튜디오 (P0)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-011 | 조립부스 옵션(간판·가구·조명) 웹 선택·3D 프리뷰 | 필수 | F012, §M3 |
| REQ-F-012 | 독립부스 레이아웃·파라메트릭 구조 3안 자동생성·선택/병합·버전 기록 | 필수 | F011, §M3 |
| REQ-F-013 | 설계 규정 사전검증(높이 5m·리깅 6.5~8.5m·복층 1/2·방염·이격 30/60cm·금지작업) 제출 전 플래깅 | 필수 | F013, §M3 |
| REQ-F-014 | 도면(PDF/이미지) 업로드 비전 추출·치수/구조 검증("참고용 검증" 라벨) | 필수 | F014, §M3 |
#### M4 유틸리티 설계 (P0)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-015 | 기기목록→kW 합산→분전반 용량/수량 자동 산출 | 필수 | F015, §M4a |
| REQ-F-016 | 최근접 트렌치→분전반 배선 경로 자동생성(통로 횡단 최소화, PostGIS 최단) | 필수 | F016, §M4 |
| REQ-F-017 | 유틸리티 위치표시도 자동생성(좌표 클릭, 수기 작도 폐지) | 필수 | F017, §M4b |
| REQ-F-018 | 네트워크·급배수·압축공기 신청·자동견적·마감(D-25) 역산 리마인더 | 필수 | F038, §M4b |
| REQ-F-019 | 조명 조도목표별 배치안 제안(전시품 강조/상담존) | 선택 | F018, §M4a |
#### M6 서류·마일스톤
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-020 | D-150/30/25/7 마일스톤 자동생성·역산 알림 | 필수 | F021, §M6 |
| REQ-F-021 | 신고서류 7종+ 웹폼→HWP/PDF 자동생성(킨텍스 제출 형식) | 필수 | F022, §M6 |
| REQ-F-022 | kxwp 제출 파일 릴레이·업로드 안내(초기 수동 릴레이, 목표 API) ⚠확인필요(폐쇄형 API 미공개) | 필수 | F027, §M6·R3 |
| REQ-F-023 | 서류 버전·다단계 전자결재 워크플로 | 필수 | F028, §5B |
| REQ-F-024 | OCR 계약·납품서 자동 등록 | 필수 | F026 |
#### M15 공사/장치 옥션 (P1 핵심)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-025 | 역경매 옥션 개설·라운드·마감(유형 설정형: 역경매/단일라운드 RFQ/고정가 비교) | 필수 | F029, §M15 |
| REQ-F-026 | AI 생성 자료(M2배치·M3설계·M4배선/BOQ·M5이미지) 열람 기반 견적서(Quotation) 제출·PDF·버전 | 필수 | F030, §M15 |
| REQ-F-027 | 실시간 순위·최저가·내 위치 노출(익명 옵션)·라운드 종료 자동마감 | 필수 | F031, §M15 |
| REQ-F-028 | 종합평가 낙찰 스코어(가격+평판+납기 가중)·항목별 비교표 | 필수 | F032, §M15 |
| REQ-F-029 | 등록업체 검증 게이트 — 미등록 업체 응찰 원천 차단 | 필수 | F033, §M15·§M7 |
| REQ-F-030 | 등록업체 AI 매칭 추천(규모·업종·지역) | 필수 | F034, §M7 |
| REQ-F-031 | 물량서(BOQ) 설계기반 자동산출 | 필수 | F035, §M4·§M15 |
| REQ-F-032 | 낙찰(Award)→계약/발주 문서 자동전환(M6·M9 연동) | 필수 | F036, §M15 |
#### M8 물류
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-033 | 반입/반출 하역장 슬롯 예약제 | 선택 | F039, §M8 |
| REQ-F-034 | 통행증 QR 발급·중량물 우선배치 | 선택 | F040, §M8 |
| REQ-F-035 | 지게차·부대장비 신청 연동, 부대물품 렌탈 주문 | 선택 | F041·F042, §M8 |
| REQ-F-036 | 철거 피크 대기열 시뮬레이션 | 선택 | F044, §M8 |
#### M10 관람객 등록·배지·리드
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-037 | 온라인 사전등록(유형별 폼·중복 검증) | 필수 | F045, §M10 |
| REQ-F-038 | 모바일 배지/QR 발급 | 필수 | F047, §M10 |
| REQ-F-039 | 현장 QR 체크인·즉석 배지 인쇄·실시간 입장 집계 | 필수 | F048, §M10 |
| REQ-F-040 | 오프라인 체크인 폴백(현장 네트워크 장애 대비 후동기화) | 필수 | F049, §M10, network.md |
| REQ-F-041 | 참가업체 리드캡처(배지 QR 스캔·관심도·메모) | 필수 | F050, §M10 |
| REQ-F-042 | AI 리드 스코어링·자동 분류 | 선택 | F051 |
| REQ-F-043 | 리드 팔로업 세그먼트 EDM 자동 | 필수 | F052, §M10·§M12 |
| REQ-F-044 | 티켓 발권·유료 등록 결제, AI 폼빌더, 스마트배지 | 선택 | F053·F046·F054 |
#### M11 비즈매칭·이벤트앱
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-045 | AI 비즈매칭 추천(프로필·intent) | 선택 | F055, §M11 |
| REQ-F-046 | 미팅 슬롯 예약·일정관리·성과 리포트 | 선택 | F056·F062, §M11 |
| REQ-F-047 | 이벤트 모바일앱(일정·부스·프로필), 세션·아젠다, 인앱 채팅, 실시간 설문 | 선택 | F057~F061 |
#### M12 마케팅·공개 홍보사이트
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-048 | 공개 홍보 사이트(SEO·다국어 한/영/중/일·SSR/정적) | 필수 | F063, §M12 |
| REQ-F-049 | 공개 인터랙티브 플로어플랜(M2 데이터 부산물) | 필수 | F064, §M12 |
| REQ-F-050 | 세그먼트 EDM·캠페인 자동화·리마인더 | 필수 | F065, §M12 |
| REQ-F-051 | 스폰서십 패키지·판매·이행 관리 + 스폰서 대시보드 | 필수 | F070·F071 |
#### M13·M14 현장/wayfinding
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-052 | 실내 wayfinding(블루닷/존레벨 폴백)·POI 검색·경로안내 | 선택 | F072·F073, §M13 |
| REQ-F-053 | 실시간 입장·혼잡 모니터·오버플로 예측 | 선택 | F074, §M14 |
| REQ-F-054 | 홀 전력부하 집계·에너지 모니터, 주차 점유 연동 | 선택 | F075·F077, §M14 |
| REQ-F-055 | 안전 위반·소음·금지작업 자동 플래그·신고, 현장 이상탐지 알림 | 선택 | F076·F079, §M14 |
#### M9 정산·결제
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-056 | 납부 스케줄(계약금/중도금/잔금/예치금) 자동생성·알림 | 필수 | F080, §M9 |
| REQ-F-057 | 유틸리티·부대 PG 결제 | 필수 | F081, §M9 |
| REQ-F-058 | 예치금 대비 실사용(검침) 정산 투명화 | 필수 | F082, §M9 |
| REQ-F-059 | 세금계산서·정산 리포트 자동, 옥션 수수료 정산 연동 | 필수 | F083·F084, §M9 |
| REQ-F-060 | 환불·취소 규정 자동 적용 | 선택 | F085 |
#### M16 경영분석 BI
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-061 | 홀·기간별 가동률(occupancy) 대시보드(반홀 분할·성수기 구분) | 필수 | F086, §M16-1① |
| REQ-F-062 | 매출 구성(임대·유틸리티·부대·옥션 수수료)·행사별 P&L | 필수 | F087, §M16-1②③ |
| REQ-F-063 | 전시장 ROI·RevPAD·㎡당 수익(㎡ 정규화) | 필수 | F088, §M16-1④ |
| REQ-F-064 | 참가사 리텐션·LTV 코호트 | 필수 | F089, §M16-1⑤ |
| REQ-F-065 | 참가업체 관점 ROI(리드 기반) — 운영사 관점과 대시보드/권한 격리 | 필수 | F091, §M16-1 |
| REQ-F-066 | 경영진 KPI 대시보드(목표 대비 실적·드릴다운) | 필수 | F093, §M16-1⑦ |
#### M17 CMS
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-067 | 전시 콘텐츠·공지 게시 워크플로(초안→검수→게시)·버전관리 | 필수 | F068, §M17 |
| REQ-F-068 | 참가업체 마이크로사이트(부스·제품·나노바나나 예상샷) | 필수 | F067, §M17 |
| REQ-F-069 | 다국어(한/영/중/일) 콘텐츠 관리·hreflang | 필수 | F069, §M17 |
| REQ-F-070 | 디지털 사이니지 콘텐츠 중앙 배포 ⚠확인필요(사이니지 HW 연동 킨텍스 협의) | 선택 | F078, §M17 |
#### M18·§5B·§1A 관리·공통·멀티테넌시
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-F-071 | RBAC·감사로그·시스템설정 백오피스(admin. 웹 전용) | 필수 | F094, §M18·§5B-1 |
| REQ-F-072 | 룰셋·마스터데이터(홀·요율·규정·부스표준) 버전관리·무중단 개정 | 필수 | F095, §M18 |
| REQ-F-073 | 공통코드·메뉴 관리(도메인 코드: 홀/부스유형/공종14/유틸요금) | 필수 | §5B-1 |
| REQ-F-074 | 공통 업무모듈: 업무일지·일정·쪽지·공지·의견·통합검색 | 필수 | F098, §5B-2 |
| REQ-F-075 | 회의록(녹음→STT→회의록 PDF), 일/주/월/분기/연 업무보고·통계 PDF | 필수 | §5B-2 |
| REQ-F-076 | 통합 알림센터(마감 D-데이·낙찰·승인·결제, WebSocket) | 필수 | §5B-2 |
| REQ-F-077 | 멀티테넌트 격리·전시관 온보딩(코드 배포 없이 데이터 온보딩 6단계) | 필수 | F096, §1A·§8-2 |
| REQ-F-078 | 테넌트 컨텍스트 해소(서브도메인 우선+사용자소속 보조, 불일치 거부) | 필수 | §1A-3 |
| REQ-F-079 | 2계층 관리자(플랫폼 슈퍼관리자 테넌트스위처 / 테넌트 관리자 단일스코프) | 필수 | §1A-4 |
| REQ-F-080 | API·웹훅·CRM/ERP 연동 게이트웨이 | 선택 | F099 |
| REQ-F-081 | 역할별 프론트 포털 분리(organizer·exhibitor·contractor·ops·admin·public+visitor) 공유 디자인시스템 상속 | 필수 | §2-1, §8-1 |
### 3-2. AI 요구사항 (REQ-A)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-A-001 | 나노바나나 시공 예상 사진 표준 샷세트(S1 정면·S2 야간·S3 통로·S4 내부·S5 Before/After·S7 홀전경) 생성 | 필수 | F019, §6-3 |
| REQ-A-002 | image-to-image 구조보존(참조이미지 inlineData+지시문) — 골격/치수/통로/카메라 앵글 잠금, 스타일만 사실화 | 필수 | §6-4 |
| REQ-A-003 | 구조화 프롬프트 사전(BOOTH_TYPES·BOOTH_STYLES·FIXTURE_LAYERS) 조립·보존/교체 명시 분리 | 필수 | §6-4 |
| REQ-A-004 | S6 배선 오버레이 백엔드 래스터 합성 우선(전기 적·네트워크 청·급배수 녹, 좌표 정확) | 필수 | §6-3 |
| REQ-A-005 | Before/After 드래그 슬라이더(빈 부스↔시공 후, ARIA·키보드) | 필수 | F020, §6-5 |
| REQ-A-006 | RenderJob 방어(다층 크기가드·mimeType 자동감지·SAFETY 분기·친화적 한글 에러·성공 시에만 쿼터 차감) | 필수 | §6-5 |
| REQ-A-007 | 단계별 로딩 UX(골격인식→집기→배선→렌더) aria-live 순환·비동기 큐·WebSocket 완료 푸시 | 필수 | §6-5, §8 |
| REQ-A-008 | 간판 텍스트 비전 오탈자 검수 + 한글 렌더 실패 시 간판영역 후처리 합성 | 필수 | §6-4, R5 |
| REQ-A-009 | 부스 배치 3안 AI 자동생성(제약 솔버 + LLM 조건해석, 결정적 알고리즘 중심) | 필수 | F008, §M2 |
| REQ-A-010 | AI 규정 자동검증(배치·설계 규정 위반 플래깅) | 필수 | F010·F013 |
| REQ-A-011 | AI 서류 검수(누락·배치도-계획서 불일치 검출) | 필수 | F023, §M6 |
| REQ-A-012 | 규정 자연어 챗봇(근거·인용 기반) | 선택 | F025 |
| REQ-A-013 | AI 수요예측·수율/동적가격 시뮬레이션 | 필수 | F090·F003, §M16-1⑥ |
| REQ-A-014 | AI 경영진 KPI 요약·인사이트 브리핑 자동, 자연어 조회(Text-to-SQL) | 필수/선택 | F093/F092 |
| REQ-A-015 | AI 카피·이미지 초안 생성, 다국어 콘텐츠 자동번역+검수 | 필수 | F066·F069, §M12·§M17 |
| REQ-A-016 | AI 플랫폼 라우터(Claude 기본→Ollama 폴백)·중앙경유·LLM 직접호출 신설 금지 | 필수 | F100, §6, FEATURE_BACKLOG §0 |
| REQ-A-017 | AI 답변 근거(RAG)·인용·환각 차단(abstain) | 필수 | FEATURE_BACKLOG §0 |
| REQ-A-018 | AI 리드 스코어링, 비즈매칭 추천, 세션/부스 추천 | 선택 | F051·F055·F059 |
| REQ-A-019 | 현장 이상탐지·혼잡/철거 대기열 예측 | 선택 | F074·F044·F079 |
| REQ-A-020 | 도면 비전 추출(치수·구조), OCR 문서 자동등록 | 필수 | F014·F026 |
### 3-3. 비기능 요구사항 (REQ-N)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-N-001 | 코어 인증·설계·조회 경로 가용성 SLO 99.5%(성수기 99.9% 지향) | 필수 | system.md §3-2 |
| REQ-N-002 | 백엔드 최소 2 인스턴스+헬스체크 LB·롤링 무중단 배포 | 필수 | system.md §3-2 |
| REQ-N-003 | PostgreSQL 프라이머리-스탠바이 스트리밍 복제+자동 failover+PITR 백업 | 필수 | system.md §3-2 |
| REQ-N-004 | Fail-Safe 배포(백업→배포→헬스체크 200→실패 시 롤백) | 필수 | system.md §3-2 |
| REQ-N-005 | degraded 모드(G1 미승인/Gemini 장애 시 워커 목 응답·구조·워터마크 유지, Claude 실패 시 Ollama 폴백) | 필수 | system.md §3-2 |
| REQ-N-006 | 나노바나나 생성 성능: 평균 40초 목표·1024px 다운스케일+JPEG 0.85 전처리 | 필수 | system.md §3-3, §6-5 |
| REQ-N-007 | 대량 동시 생성 제어: 자동생성 S1·S7 한정·동일 스키마해시 캐시·행사별 RenderJob 쿼터 | 필수 | system.md §3-3, R6 |
| REQ-N-008 | 공간쿼리 성능: 전 geom 컬럼 GiST 인덱스(트렌치 KNN·통로버퍼·비상구 교차 실시간) | 필수 | data.md §3-4 |
| REQ-N-009 | 워커 독립 스케일(큐 깊이 기반 증감), 백엔드 응답성과 분리 | 필수 | system.md §3-1 |
| REQ-N-010 | BI·공개조회·플로어플랜 열람 읽기복제 오프로드(운영 프라이머리 보호) | 필수 | system.md §3-1, §8-1 |
| REQ-N-011 | 워커 장애 시 가시성 타임아웃 후 재큐(멱등 Job) | 필수 | system.md §3-2 |
| REQ-N-012 | 웹접근성 WCAG 2.1 AA(핵심여정 AAA 지향)·공공 KWCAG 2.2 병행 | 필수 | FEATURE_BACKLOG §6-N1 |
| REQ-N-013 | 캔버스/맵 비시각 대안(데이터 테이블·검색·키보드 선택) 필수 제공 | 필수 | FEATURE_BACKLOG §6-N1 |
| REQ-N-014 | 다국어 i18n 외부화(한 기본+영/중/일)·로케일 전환·hreflang·로케일 포맷 | 필수 | FEATURE_BACKLOG §6-N3 |
| REQ-N-015 | 라이트/다크 테마 CSS 토큰 2벌·prefers-color-scheme 연동·선택 영속 | 필수 | FEATURE_BACKLOG §6-N4 |
| REQ-N-016 | 반응형(데스크톱=설계/에디터, 모바일=현장/조회/승인) | 필수 | PLANNING §8, §2-1 |
| REQ-N-017 | 단일 공간 원천(M2폴리곤·M4배선 매퍼 권위, 소비 모듈 조회만·중복 저장 금지) | 필수 | app.md, data.md |
| REQ-N-018 | 관측성(로그·메트릭·트레이스) 관리존 수집, 로그에 자격증명·PII·스택트레이스 미기록 | 필수 | network.md, tech.md |
| REQ-N-019 | 공개영역 CDN·요청률 상한·slowloris 타임아웃(가용성 계층 방어) | 필수 | network.md §7 |
| REQ-N-020 | tenant_id 선두 복합 인덱스 표준화(격리 쿼리 성능) | 필수 | §8-2 |
| REQ-N-021 | BI 데이터마트(스타 스키마 Fact/Dim) 야간 배치 ETL 또는 읽기복제 적재 | 필수 | data.md, §M16-1 |
### 3-4. 보안 요구사항 (REQ-S)
| REQ-ID | 요구사항 | 유형 | 출처 |
|---|---|---|---|
| REQ-S-001 | 외부 API 정책: 온프레미스 Ollama 기본, Claude(api.anthropic.com)·Gemini(나노바나나)만 승인 예외, 그 외 금지 | 필수 | SECURITY.md §1 |
| REQ-S-002 | GEMINI_API_KEY 나노바나나 Python 워커 env only(백엔드 미취급), Spring은 큐/콜백만 | 필수 | SECURITY.md §1 |
| REQ-S-003 | 자격증명·키·내부IP·SSH·워커토큰 응답/로그/에러/커밋 완전 제외·마스킹 | 필수 | SECURITY.md §2 |
| REQ-S-004 | admin 비밀번호 env `ADMIN_PASSWORD_ENC`(AES-256-GCM)+별도 키파일→기동 시 BCrypt 재시드, 하드코딩 시드 금지 | 필수 | SECURITY.md §3 |
| REQ-S-005 | JWT(HS256)+2차 인증 OTP/EMAIL(TOTP RFC6238)+로그인 실패 잠금(관리자 해제) | 필수 | SECURITY.md §4, §5B-3 |
| REQ-S-006 | 행사 단위 RBAC(열람=멤버/홀매니저, 편집=역할별 게이트) — 테넌트 격리 ⊃ 행사 RBAC 3중 | 필수 | SECURITY.md §4, §2 |
| REQ-S-007 | 미등록 장치업체 초대·응찰 차단(NOT_REGISTERED_COMPANY 403) | 필수 | SECURITY.md §4, §M15 |
| REQ-S-008 | `/api/admin/**`=ADMIN 게이트, `/api/internal/**`=워커 공유 시크릿(X-Worker-Token)만 | 필수 | SECURITY.md §4 |
| REQ-S-009 | M5 나노바나나 이미지 응답 항상 watermarkRequired:true+watermarkText+notice(계약·심사 서류 금지) | 필수 | SECURITY.md §5, §6-6 |
| REQ-S-010 | 오류 응답 스택트레이스·relation/컬럼명·내부경로 미노출(ApiResponse.error 요약만) | 필수 | SECURITY.md §6 |
| REQ-S-011 | DataAccessException @RestControllerAdvice로 INTERNAL 요약 매핑(테이블/컬럼 누출 차단) | 필수 | SECURITY.md §6, system.md |
| REQ-S-012 | 민감 액션(승인·낙찰·설계변경·룰셋개정·리드접근·로그인/권한변경) TB_AUDIT_LOG 전수(tenant_id 포함) | 필수 | SECURITY.md §7, §6-N2 |
| REQ-S-013 | PII(관람객·리드) 동의·보존정책·최소수집·마스킹·응답 완전 제외, `*_enc` AES-256-GCM | 필수 | data.md §5, R10 |
| REQ-S-014 | 입력 검증(Bean Validation 화이트리스트·파일 MIME/크기 가드) | 필수 | FEATURE_BACKLOG §6-N2 |
| REQ-S-015 | XSS 방지(React 이스케이프·dangerouslySetInnerHTML 금지·CMS sanitize·CSP) | 필수 | FEATURE_BACKLOG §6-N2 |
| REQ-S-016 | 인젝션 차단(MyBatis `#{}` 강제·`${}` 금지, PostGIS/명령/LDAP) | 필수 | FEATURE_BACKLOG §6-N2 |
| REQ-S-017 | IDOR 방지(리소스 소유·tenant_id 검증), CSRF/SameSite·JWT 만료·회전 | 필수 | FEATURE_BACKLOG §6-N2 |
| REQ-S-018 | fail-closed 다층 테넌트 격리(컨텍스트 필터→서비스 가드→MyBatis 공통 인터셉터→PostGIS) | 필수 | §8-2 |
| REQ-S-019 | 의존성 취약점 SCA(OWASP Dependency-Check) CI 게이트·정기 패치 | 필수 | FEATURE_BACKLOG §6-N2 |
| REQ-S-020 | 보안영역 분리(공개 DMZ 쓰기권한 없음·데이터존 직결 불가, DMZ↔내부 HTTPS만·DB 프로토콜 횡단 금지) | 필수 | network.md |
---
## 4. 평가 관점 (공공 SI 표준 배점 — 가정)
> 실제 RFP·평가표가 없어 공공 정보화 SI 통례를 가정한다. **[가정]** — 실제 공고 확인 시 교체.
| 구분 | 평가부문 | 배점(가정) | win theme 우선순위 근거 |
|---|---|---|---|
| 기술 | 사업이해·추진전략 | 10 | 전시 도메인·As-Is 병목 이해(§3) |
| 기술 | **AI 시각화·설계 자동화(핵심 차별화)** | **20** | M2~M5 P0 코어 + 나노바나나 시공 예측(경쟁 전시테크 미보유 폐루프) |
| 기술 | 기능 구현(M1~M18 전 모듈) | 15 | 100대 기능(이미 63·부분 18·신규 19) |
| 기술 | 아키텍처·기술스택 | 10 | 확정 스택 정합·PostGIS 공간데이터·Python 워커 사이드카 |
| 기술 | 보안·개인정보(SECURITY 불변) | 10 | REQ-S 20항목·워터마크·PII·테넌트 격리 |
| 기술 | 비기능(성능·가용성·접근성·다국어) | 8 | SLO 99.5%·WCAG AA·KWCAG·i18n |
| 기술 | 멀티테넌시·확장성(COEX 온보딩) | 5 | §1A SaaS 확장 |
| 기술 | 사업관리·품질·일정 | 2 | Phase 1~3·QA 게이트 |
| **기술 소계** | | **80** | |
| 가격 | 입찰가격 | 20 | |
| **총계** | | **100** | |
**win theme 우선순위(배점 가중)**: ① 나노바나나 시공 예측 시각화(최고 차별화, 20점) → ② 부스 배치·설계·배선 AI 자동화 폐루프(M2~M5) → ③ 공사 옥션(M15) 발주 종결 → ④ 보안·개인정보·워터마크(공공 신뢰) → ⑤ 멀티테넌트 SaaS 확장.
---
## 5. 제출 규격 (가정)
> 실제 RFP 규격이 없어 공공 SI 제안 통례를 가정한다. **[가정]** — 실제 공고 확인 시 교체.
| 항목 | 가정값 | 비고 |
|---|---|---|
| 제안서 형식 | PowerPoint(.pptx) 기술제안서 | proposal-builder 파이프라인 정합 |
| 분량 | 자율(권장 40~80p) | 실제 공고 확인 필요 |
| 목차 | 사업이해→추진전략→기술제안(AI 시각화·설계·옥션·관람·BI)→아키텍처→보안→비기능→멀티테넌시→사업관리→기대효과 | §4 배점 정합 |
| 서식 | 발주처 지정 시 준수, 미지정 시 표준 제안 템플릿 | ⚠확인필요 |
| 필수 도식 | 모듈맵(§4)·아키텍처(§8)·나노바나나 파이프라인(§6)·옥션 플로우(§M15)·추진체계·일정(Phase 1~3) | PLANNING 근거 |
| 제출 부수·마감 | ⚠확인필요(실제 공고) | — |
| 자격요건 | ⚠확인필요(실적·인증 — CSAP/ISMS 등 공공 요건 가능성) | — |
---
## 6. 확인 필요·상충 항목 (삭제 금지)
| # | 항목 | 사유 |
|---|---|---|
| C1 | 예산·계약방식·사업기간·평가배점·제출규격 | 실제 RFP 부재 — 전부 가정, 공고 확인 시 교체 |
| C2 | kxwp/kxfp API 연동 | 폐쇄형·API 미공개, 킨텍스 IT 협의 필수(R3) — 초기 수동 릴레이 |
| C3 | 트렌치·CAD 실측 데이터 | 미확보, 공개 스펙 기반 가정 그리드+'가정' 라벨(R4) |
| C4 | 인터넷 요금 정합성(150,000원 vs KT 80,000원 병존) | 킨텍스 확인 필요(R8) |
| C5 | 나노바나나 Gemini 외부 API 승인(G1) | 미승인 시 온프레미스 폴백 검토(R12) |
| C6 | 코엑스 등 신규 전시관 홀·요율·규정 실측 | 미확보, 온보딩 시 입력·근거 없는 추정 금지(§1A-5) |
| C7 | 사이니지 HW·비콘(wayfinding)·IoT 센서 연동 | 킨텍스 시설 인프라 협의 전제(M13·M14·M17) |
| C8 | 자격요건(공공 인증 CSAP/ISMS 등) | 실제 공고 확인 필요 |
---
## 부록. REQ 총계
| 유형 | 개수 |
|---|---|
| REQ-F (기능) | 81 |
| REQ-A (AI) | 20 |
| REQ-N (비기능) | 21 |
| REQ-S (보안) | 20 |
| **총계** | **142** |
</content>

View File

@ -0,0 +1,196 @@
# 요구사항 추적표 (RTM) — 킨텍스 자동전시시스템 제안서
> 작성: rfp-analyst · 작성일: 2026-07-11 · 버전 v1.0
> 짝 문서: `rfp_analysis.md`(요구사항 정의). 본 RTM은 **REQ-ID ↔ 제안서 목차 섹션 1:1 매핑 준비 + 평가부문 매핑 + 반영 상태**의 단일 진실원천이다.
> 사용법: writer/deck-designer가 각 REQ를 제안서 본문/슬라이드에 반영하며 "제안서 섹션" 및 "반영상태(☐미반영/◑작성중/☑완료)"를 갱신한다. QA는 전 REQ 반영 여부를 이 표로 교차 점검한다.
> 제안서 목차 코드: **P1**사업이해 · **P2**추진전략 · **P3**기술제안(AI 시각화·설계·옥션·관람·BI) · **P4**아키텍처 · **P5**보안·개인정보 · **P6**비기능 · **P7**멀티테넌시·확장 · **P8**사업관리·일정·조직 · **P9**기대효과. (평가부문은 rfp_analysis §4 배점 기준)
---
## 1. 기능 요구사항 (REQ-F)
| REQ-ID | 요구사항 요약 | 유형 | 모듈 | 제안서 섹션(준비) | 평가부문 | 반영상태 |
|---|---|---|---|---|---|---|
| REQ-F-001 | 홀/반홀 가용성 실시간 캘린더 | 필수 | M1 | P3 판매·견적 | 기능 구현 | ☐ |
| REQ-F-002 | 규칙엔진 자동 견적 | 필수 | M1 | P3 판매·견적 | 기능 구현 | ☐ |
| REQ-F-003 | 배정신청 웹폼→HWP 자동생성 | 필수 | M1 | P3 판매·견적 | 기능 구현 | ☐ |
| REQ-F-004 | 부스 판매 인벤토리 관리 | 필수 | M1/M2 | P3 판매·견적 | 기능 구현 | ☐ |
| REQ-F-005 | 온라인 부스 셀프 선택·판매 | 필수 | M2 | P3 판매·견적 | 기능 구현 | ☐ |
| REQ-F-006 | 임대계약 전자서명 | 선택 | M6/M9 | P3 판매·견적 | 기능 구현 | ☐ |
| REQ-F-007 | 부스 배치 3안 자동생성 | 필수 | M2 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-008 | 배치안 선택/병합 편집 | 필수 | M2 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-009 | 배치 규정 자동검증·병합 후 재검증 | 필수 | M2 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-010 | 최종안 버전기록·부스좌표 확정·초대링크 | 필수 | M2 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-011 | 조립부스 옵션·3D 프리뷰 | 필수 | M3 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-012 | 독립부스 설계 3안·선택/병합·버전 | 필수 | M3 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-013 | 설계 규정 사전검증(높이·리깅·방염·이격) | 필수 | M3 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-014 | 도면 업로드 비전 추출·검증 | 필수 | M3 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-015 | 전기 용량·분전반 자동산출 | 필수 | M4a | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-016 | 배선 경로 자동생성(PostGIS 최단) | 필수 | M4 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-017 | 위치표시도 자동생성 | 필수 | M4b | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-F-018 | 네트워크·급배수·압축공기 신청·마감 리마인더 | 필수 | M4b | P3 AI 시각화·설계 | 기능 구현 | ☐ |
| REQ-F-019 | 조명 조도 배치안 제안 | 선택 | M4a | P3 AI 시각화·설계 | AI 시각화·설계 | ☐ |
| REQ-F-020 | 마일스톤 자동생성·역산 알림 | 필수 | M6 | P3 서류·워크플로 | 기능 구현 | ☐ |
| REQ-F-021 | 신고서류 웹폼→HWP/PDF 자동생성 | 필수 | M6 | P3 서류·워크플로 | 기능 구현 | ☐ |
| REQ-F-022 | kxwp 제출 파일 릴레이 ⚠확인필요 | 필수 | M6 | P3 서류·워크플로 | 기능 구현 | ☐ |
| REQ-F-023 | 서류 버전·전자결재 워크플로 | 필수 | M6/§5B | P3 서류·워크플로 | 기능 구현 | ☐ |
| REQ-F-024 | OCR 계약·납품서 자동등록 | 필수 | AI | P3 서류·워크플로 | 기능 구현 | ☐ |
| REQ-F-025 | 역경매 옥션 개설·라운드·마감 | 필수 | M15 | P3 공사 옥션 | AI 시각화·설계(폐루프) | ☐ |
| REQ-F-026 | AI 자료 기반 견적서 응찰·PDF·버전 | 필수 | M15 | P3 공사 옥션 | AI 시각화·설계(폐루프) | ☐ |
| REQ-F-027 | 실시간 순위·자동마감 | 필수 | M15 | P3 공사 옥션 | 기능 구현 | ☐ |
| REQ-F-028 | 종합평가 낙찰 스코어·비교표 | 필수 | M15 | P3 공사 옥션 | 기능 구현 | ☐ |
| REQ-F-029 | 등록업체 검증 게이트(응찰 차단) | 필수 | M15/M7 | P3 공사 옥션 / P5 보안 | 보안·개인정보 | ☐ |
| REQ-F-030 | 등록업체 AI 매칭 추천 | 필수 | M7 | P3 공사 옥션 | 기능 구현 | ☐ |
| REQ-F-031 | 물량서(BOQ) 자동산출 | 필수 | M4/M15 | P3 공사 옥션 | AI 시각화·설계 | ☐ |
| REQ-F-032 | 낙찰→계약·발주 자동전환 | 필수 | M15 | P3 공사 옥션 | 기능 구현 | ☐ |
| REQ-F-033 | 반입/반출 슬롯 예약 | 선택 | M8 | P3 참가·물류 | 기능 구현 | ☐ |
| REQ-F-034 | 통행증 QR·중량물 우선 | 선택 | M8 | P3 참가·물류 | 기능 구현 | ☐ |
| REQ-F-035 | 지게차·부대장비·렌탈 주문 | 선택 | M8 | P3 참가·물류 | 기능 구현 | ☐ |
| REQ-F-036 | 철거 대기열 시뮬레이션 | 선택 | M8 | P3 참가·물류 | AI 시각화·설계 | ☐ |
| REQ-F-037 | 온라인 사전등록(유형별·중복검증) | 필수 | M10 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-038 | 모바일 배지/QR 발급 | 필수 | M10 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-039 | 현장 QR 체크인·즉석 인쇄·집계 | 필수 | M10 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-040 | 오프라인 체크인 폴백 | 필수 | M10 | P3 관람 / P6 비기능 | 비기능 | ☐ |
| REQ-F-041 | 참가업체 리드캡처 | 필수 | M10 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-042 | AI 리드 스코어링·분류 | 선택 | AI | P3 관람 | AI 시각화·설계 | ☐ |
| REQ-F-043 | 리드 팔로업 세그먼트 EDM | 필수 | M10/M12 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-044 | 티켓발권·AI폼빌더·스마트배지 | 선택 | M10 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-045 | AI 비즈매칭 추천 | 선택 | M11 | P3 관람 | AI 시각화·설계 | ☐ |
| REQ-F-046 | 미팅 슬롯·성과 리포트 | 선택 | M11 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-047 | 이벤트 모바일앱·세션·채팅·설문 | 선택 | M11 | P3 관람 | 기능 구현 | ☐ |
| REQ-F-048 | 공개 홍보 사이트(SEO·다국어) | 필수 | M12 | P3 마케팅·공개 | 기능 구현 | ☐ |
| REQ-F-049 | 공개 인터랙티브 플로어플랜 | 필수 | M12 | P3 마케팅·공개 | 기능 구현 | ☐ |
| REQ-F-050 | 세그먼트 EDM·캠페인 자동화 | 필수 | M12 | P3 마케팅·공개 | 기능 구현 | ☐ |
| REQ-F-051 | 스폰서십 패키지·대시보드 | 필수 | M12/M16 | P3 마케팅·공개 | 기능 구현 | ☐ |
| REQ-F-052 | 실내 wayfinding·POI·경로 | 선택 | M13 | P3 현장운영 | 기능 구현 | ☐ |
| REQ-F-053 | 실시간 혼잡 모니터·예측 | 선택 | M14 | P3 현장운영 | AI 시각화·설계 | ☐ |
| REQ-F-054 | 홀 전력부하·주차 점유 | 선택 | M14 | P3 현장운영 | 기능 구현 | ☐ |
| REQ-F-055 | 안전 위반·이상탐지 알림 | 선택 | M14 | P3 현장운영 | AI 시각화·설계 | ☐ |
| REQ-F-056 | 납부 스케줄 자동생성·알림 | 필수 | M9 | P3 정산 | 기능 구현 | ☐ |
| REQ-F-057 | 유틸리티·부대 PG 결제 | 필수 | M9 | P3 정산 | 기능 구현 | ☐ |
| REQ-F-058 | 예치금 실사용 정산 투명화 | 필수 | M9 | P3 정산 | 기능 구현 | ☐ |
| REQ-F-059 | 세금계산서·옥션 수수료 정산 | 필수 | M9 | P3 정산 | 기능 구현 | ☐ |
| REQ-F-060 | 환불·취소 규정 자동 | 선택 | M9 | P3 정산 | 기능 구현 | ☐ |
| REQ-F-061 | 홀·기간별 가동률 대시보드 | 필수 | M16 | P3 경영 BI | 기능 구현 | ☐ |
| REQ-F-062 | 매출 구성·행사별 P&L | 필수 | M16 | P3 경영 BI | 기능 구현 | ☐ |
| REQ-F-063 | 전시장 ROI·RevPAD·㎡당 수익 | 필수 | M16 | P3 경영 BI | 기능 구현 | ☐ |
| REQ-F-064 | 참가사 리텐션·LTV 코호트 | 필수 | M16 | P3 경영 BI | 기능 구현 | ☐ |
| REQ-F-065 | 참가업체 관점 ROI(권한 격리) | 필수 | M16 | P3 경영 BI / P5 보안 | 기능 구현 | ☐ |
| REQ-F-066 | 경영진 KPI 대시보드·드릴다운 | 필수 | M16 | P3 경영 BI | 기능 구현 | ☐ |
| REQ-F-067 | CMS 게시 워크플로·버전 | 필수 | M17 | P3 마케팅·공개 | 기능 구현 | ☐ |
| REQ-F-068 | 참가업체 마이크로사이트 | 필수 | M17 | P3 마케팅·공개 | 기능 구현 | ☐ |
| REQ-F-069 | 다국어 콘텐츠·hreflang | 필수 | M17 | P3 마케팅·공개 / P6 | 비기능 | ☐ |
| REQ-F-070 | 사이니지 콘텐츠 배포 ⚠확인필요 | 선택 | M17 | P3 현장운영 | 기능 구현 | ☐ |
| REQ-F-071 | RBAC·감사·시스템설정 백오피스 | 필수 | M18/§5B | P5 보안 / P8 관리 | 보안·개인정보 | ☐ |
| REQ-F-072 | 룰셋·마스터데이터 버전관리 | 필수 | M18 | P4 아키텍처 / P8 | 아키텍처 | ☐ |
| REQ-F-073 | 공통코드·메뉴 관리 | 필수 | §5B | P8 관리 | 기능 구현 | ☐ |
| REQ-F-074 | 공통 업무모듈(일지·일정·쪽지·공지·검색) | 필수 | §5B | P8 관리 | 기능 구현 | ☐ |
| REQ-F-075 | 회의록(STT)·업무보고 통계 PDF | 필수 | §5B | P8 관리 | 기능 구현 | ☐ |
| REQ-F-076 | 통합 알림센터(WebSocket) | 필수 | §5B | P8 관리 | 기능 구현 | ☐ |
| REQ-F-077 | 멀티테넌트 격리·전시관 온보딩 | 필수 | §1A | P7 멀티테넌시 | 멀티테넌시·확장 | ☐ |
| REQ-F-078 | 테넌트 컨텍스트 해소 | 필수 | §1A | P7 멀티테넌시 | 멀티테넌시·확장 | ☐ |
| REQ-F-079 | 2계층 관리자(플랫폼/테넌트) | 필수 | §1A | P7 멀티테넌시 / P5 | 멀티테넌시·확장 | ☐ |
| REQ-F-080 | API·웹훅·CRM/ERP 연동 게이트웨이 | 선택 | 연동 | P4 아키텍처 | 아키텍처 | ☐ |
| REQ-F-081 | 역할별 프론트 포털 분리 | 필수 | §2-1 | P4 아키텍처 | 아키텍처 | ☐ |
## 2. AI 요구사항 (REQ-A)
| REQ-ID | 요구사항 요약 | 유형 | 모듈 | 제안서 섹션(준비) | 평가부문 | 반영상태 |
|---|---|---|---|---|---|---|
| REQ-A-001 | 나노바나나 시공 예상사진 샷세트 S1~S7 | 필수 | M5 | P3 AI 시각화(핵심) | AI 시각화·설계(핵심) | ☐ |
| REQ-A-002 | image-to-image 구조보존(골격/앵글 잠금) | 필수 | M5 | P3 AI 시각화(핵심) | AI 시각화·설계(핵심) | ☐ |
| REQ-A-003 | 구조화 프롬프트 사전·보존/교체 분리 | 필수 | M5 | P3 AI 시각화(핵심) | AI 시각화·설계(핵심) | ☐ |
| REQ-A-004 | S6 배선 오버레이 백엔드 래스터 합성 | 필수 | M5 | P3 AI 시각화(핵심) | AI 시각화·설계(핵심) | ☐ |
| REQ-A-005 | Before/After 드래그 슬라이더 | 필수 | M5 | P3 AI 시각화(핵심) | AI 시각화·설계(핵심) | ☐ |
| REQ-A-006 | RenderJob 방어(크기가드·SAFETY·쿼터) | 필수 | M5 | P3 AI 시각화 / P5 보안 | 보안·개인정보 | ☐ |
| REQ-A-007 | 단계별 로딩·비동기 큐·WebSocket 푸시 | 필수 | M5 | P4 아키텍처 | 아키텍처 | ☐ |
| REQ-A-008 | 간판 텍스트 비전 검수·후처리 합성 | 필수 | M5 | P3 AI 시각화 | AI 시각화·설계 | ☐ |
| REQ-A-009 | 부스 배치 3안 AI(제약 솔버+LLM) | 필수 | M2 | P3 AI 시각화·설계 | AI 시각화·설계(핵심) | ☐ |
| REQ-A-010 | AI 규정 자동검증(배치·설계) | 필수 | M2/M3 | P3 AI 시각화·설계 | AI 시각화·설계 | ☐ |
| REQ-A-011 | AI 서류 검수(누락·불일치) | 필수 | M6 | P3 서류·워크플로 | AI 시각화·설계 | ☐ |
| REQ-A-012 | 규정 자연어 챗봇(근거·인용) | 선택 | AI | P3 서류·워크플로 | AI 시각화·설계 | ☐ |
| REQ-A-013 | AI 수요예측·수율/동적가격 | 필수 | M16 | P3 경영 BI | AI 시각화·설계 | ☐ |
| REQ-A-014 | AI KPI 브리핑·자연어 조회(Text-to-SQL) | 필수/선택 | M16 | P3 경영 BI | AI 시각화·설계 | ☐ |
| REQ-A-015 | AI 카피·이미지 초안·다국어 번역 | 필수 | M12/M17 | P3 마케팅·공개 | AI 시각화·설계 | ☐ |
| REQ-A-016 | AI 플랫폼 라우터(Claude→Ollama 폴백) | 필수 | §6 | P4 아키텍처 / P5 보안 | 아키텍처 | ☐ |
| REQ-A-017 | AI 근거(RAG)·인용·환각 차단 | 필수 | §6 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-A-018 | AI 리드 스코어·매칭·추천 | 선택 | M10/M11 | P3 관람 | AI 시각화·설계 | ☐ |
| REQ-A-019 | 현장 이상탐지·혼잡/철거 예측 | 선택 | M14/M8 | P3 현장운영 | AI 시각화·설계 | ☐ |
| REQ-A-020 | 도면 비전 추출·OCR 문서 등록 | 필수 | M3/AI | P3 AI 시각화·설계 | AI 시각화·설계 | ☐ |
## 3. 비기능 요구사항 (REQ-N)
| REQ-ID | 요구사항 요약 | 유형 | 제안서 섹션(준비) | 평가부문 | 반영상태 |
|---|---|---|---|---|---|
| REQ-N-001 | 코어 가용성 SLO 99.5%(성수기 99.9%) | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-002 | 백엔드 2인스턴스·롤링 무중단 | 필수 | P4 아키텍처 / P6 | 비기능 | ☐ |
| REQ-N-003 | PG 스탠바이 복제·failover·PITR | 필수 | P4 아키텍처 / P6 | 비기능 | ☐ |
| REQ-N-004 | Fail-Safe 배포(헬스체크·롤백) | 필수 | P8 사업관리 | 사업관리·품질 | ☐ |
| REQ-N-005 | degraded 모드(목 응답·폴백) | 필수 | P4 아키텍처 / P6 | 비기능 | ☐ |
| REQ-N-006 | 나노바나나 40초·1024px 전처리 | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-007 | 대량 생성 제어(S1·S7·캐시·쿼터) | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-008 | 공간쿼리 GiST 실시간 | 필수 | P4 아키텍처 | 아키텍처 | ☐ |
| REQ-N-009 | 워커 독립 스케일 | 필수 | P4 아키텍처 | 아키텍처 | ☐ |
| REQ-N-010 | BI/공개 읽기복제 오프로드 | 필수 | P4 아키텍처 | 아키텍처 | ☐ |
| REQ-N-011 | 워커 장애 재큐(멱등 Job) | 필수 | P4 아키텍처 / P6 | 비기능 | ☐ |
| REQ-N-012 | WCAG 2.1 AA·KWCAG 2.2 | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-013 | 캔버스/맵 비시각 대안 | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-014 | 다국어 i18n·hreflang·로케일 포맷 | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-015 | 라이트/다크 테마 CSS 토큰 | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-016 | 반응형(데스크톱/모바일) | 필수 | P6 비기능 | 비기능 | ☐ |
| REQ-N-017 | 단일 공간 원천(중복 저장 금지) | 필수 | P4 아키텍처 | 아키텍처 | ☐ |
| REQ-N-018 | 관측성(로그·PII/스택트레이스 미기록) | 필수 | P5 보안 / P6 | 비기능 | ☐ |
| REQ-N-019 | 공개영역 CDN·요청률 상한 | 필수 | P4 아키텍처 / P6 | 비기능 | ☐ |
| REQ-N-020 | tenant_id 선두 복합 인덱스 | 필수 | P7 멀티테넌시 / P4 | 멀티테넌시·확장 | ☐ |
| REQ-N-021 | BI 데이터마트 배치 ETL/읽기복제 | 필수 | P4 아키텍처 | 아키텍처 | ☐ |
## 4. 보안 요구사항 (REQ-S)
| REQ-ID | 요구사항 요약 | 유형 | 제안서 섹션(준비) | 평가부문 | 반영상태 |
|---|---|---|---|---|---|
| REQ-S-001 | 외부 API 정책(Ollama 기본·Claude/Gemini 예외) | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-002 | GEMINI_API_KEY 워커 env only | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-003 | 자격증명·키 응답/로그/커밋 완전 제외 | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-004 | admin 비번 env 재시드(AES-256-GCM) | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-005 | JWT+2FA OTP(TOTP)·실패 잠금 | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-006 | 행사 RBAC(테넌트 격리 ⊃ 행사 3중) | 필수 | P5 보안 / P7 | 보안·개인정보 | ☐ |
| REQ-S-007 | 미등록 업체 차단(403) | 필수 | P5 보안 / P3 옥션 | 보안·개인정보 | ☐ |
| REQ-S-008 | admin/internal 엔드포인트 게이트 | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-009 | M5 이미지 워터마크 항상 포함 | 필수 | P5 보안 / P3 AI | 보안·개인정보 | ☐ |
| REQ-S-010 | 오류 응답 스택트레이스 미노출 | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-011 | DataAccessException 요약 매핑 | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-012 | 민감 액션 감사로그 전수(tenant_id) | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-013 | PII 동의·보존·마스킹·AES-256-GCM | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-014 | 입력 검증(Bean Validation·파일 가드) | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-015 | XSS 방지·CSP·CMS sanitize | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-016 | 인젝션 차단(MyBatis #{} 강제) | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-017 | IDOR 방지·CSRF·JWT 회전 | 필수 | P5 보안 | 보안·개인정보 | ☐ |
| REQ-S-018 | fail-closed 다층 테넌트 격리 | 필수 | P7 멀티테넌시 / P5 | 멀티테넌시·확장 | ☐ |
| REQ-S-019 | 의존성 SCA CI 게이트 | 필수 | P5 보안 / P8 | 사업관리·품질 | ☐ |
| REQ-S-020 | 보안영역 분리(DMZ/내부망) | 필수 | P4 아키텍처 / P5 | 보안·개인정보 | ☐ |
---
## 5. 커버리지 요약
| 유형 | REQ 수 | 필수 | 선택 |
|---|---|---|---|
| REQ-F (기능) | 81 | 62 | 19 |
| REQ-A (AI) | 20 | 15 | 5 |
| REQ-N (비기능) | 21 | 21 | 0 |
| REQ-S (보안) | 20 | 20 | 0 |
| **총계** | **142** | **118** | **24** |
**제안서 섹션 커버리지 목표**: P3(기술제안)에 최다 REQ 집중(AI 시각화·설계 win theme), P5(보안)에 REQ-S 20 + 관련 REQ-A/F, P4(아키텍처)에 REQ-N/A 인프라 항목, P7(멀티테넌시)에 §1A 계열. QA는 전 142개 REQ의 반영상태가 ☑ 완료되었는지 본 표로 최종 검증한다.
## 6. 확인 필요 REQ (제안 전 발주처 협의)
| REQ-ID | 확인 사항 | rfp_analysis 참조 |
|---|---|---|
| REQ-F-022 | kxwp API 연동(폐쇄형·미공개) | C2 |
| REQ-F-070 | 사이니지 HW 연동 | C7 |
| REQ-F-052/053/054/055 | wayfinding 비콘·IoT 센서 인프라 | C7 |
| REQ-A-001~008 | 나노바나나 Gemini 승인(G1) — 미승인 시 온프레미스 폴백 | C5 |
| REQ-F-002 | 인터넷 요금 정합성·요율 공식본 | C4 |
| REQ-F-077 | 코엑스 등 신규 전시관 실측 데이터 | C6 |
</content>

View File

@ -0,0 +1,374 @@
# 킨텍스 자동전시시스템 구축 — 기술·아키텍처 자문서 (Tech Advisory)
> 작성: proposal-tech-advisor(아키텍처 허브) · 작성일: 2026-07-11 · 버전 v1.0
> 입력: `rfp_analysis.md`(REQ 142)·`rtm.md`·`proposal_strategy.md`+`proposal_outline.md`(Win Theme 5·필수 도식 12종)·**`docs/architecture/{app,system,tech,data,network}.md`**(AA/SA/TA/DA/NA 확정 아키텍처 5종)·`docs/PLANNING.md` v3.0·`docs/SECURITY.md`·`docs/WORK_STATUS.md`(라이브 실적)·kintex `CLAUDE.md`(스택 확정).
> **성격**: 아키텍처 전문가 풀(AA·SA·TA·DA·NA)이 이미 확정한 5종 아키텍처를 **재발명하지 않고**, 제안서(writer·visual-designer·deck-designer)용으로 **종합·상충 조정·재구성**한 기술 자문. 전문가 풀 산출물이 이미 충분해 추가 소집(kintex-sa/ta/da) 불요 — 공백(SM 운영·SLA·WBS/공수·도식 사양)만 본 문서가 저술.
> **원칙(불변)**: ①과장 금지 — WORK_STATUS 라이브 확인분만 "구축 완료/검증됨", 나머지는 "구현 계획". ②시크릿(키·비번·내부IP) 미기재. ③실현 가능성 우선 — 확정 스택·기존 스캐폴드·이식 자산으로 뒷받침.
---
## 0. 기술 신뢰성 기반 — 라이브 실적 vs 구현 계획 (과장 방지 핵심)
> 평가위원 최대 관심 = "제안이 말뿐인가, 실제로 되는가". 본 절이 그 답이다. WORK_STATUS(2026-07-11) 라이브 확인분과 구현 계획을 **명확히 구분**한다. 제안서 P8(사업관리)·P4(아키텍처) 신뢰 근거로 인용.
### 0-1. 구축 완료·라이브 (검증됨 — "이미 돈다")
| 영역 | 라이브 실적 | 검증 근거 |
|---|---|---|
| **개발/배포 인프라** | `kintex.zioinfo.co.kr` TLS + 백엔드 8021 + PostGIS + Redis + 나노바나나 워커 systemd 상주 | WORK_STATUS §4 인프라(라이브) |
| **CI/CD 자동배포** | Gitea webhook → `deploy_kintex.sh`(bootJar+vite build→배포→health→롤백). push 시 자동배포 | WORK_STATUS §2·§7 |
| **나노바나나 이미지 생성** | Gemini 라이브(G1 소유자 승인 완료), `gemini-3.1-flash-image-preview` 워커 | WORK_STATUS §2·§4 |
| **인증(JWT/RBAC/2FA)** | JWT+RBAC 6역할 + TOTP OTP + 로그인 실패 잠금 + admin env 시더(admin123 금지) | WORK_STATUS §4, Flyway V7/V8 |
| **P0 코어 매퍼(PostGIS)** | M2~M5 매퍼 배선(501 해소)·룰엔진·공간 SQL | WORK_STATUS §4 백엔드(라이브) |
| **공통 레이어(WISE 이식)** | 시스템관리·공통 업무기능·공개 인증(register/forgot/reset) | Flyway V1~V9(테이블 39) |
| **프론트(Stitch 반영)** | 로그인(히어로+CI+회원가입/비번찾기/아이디기억) + 부스 설계 스튜디오·대시보드·갤러리·신규 8+5화면 | WORK_STATUS §4·§6 |
| **평면도/CAD 자산** | 홀별 평면도 JPG 15 + CAD(제1전시장 "평면,트렌치.dwg") 확보 → 트렌치 실측 원천 | WORK_STATUS §4, CLAUDE.md 확보자산 |
**메시지**: "제안 아키텍처는 도상이 아니라 **이미 기동 중인 시스템**이다. 스캐폴드·인증·PostGIS 코어·나노바나나·자동배포 파이프라인이 라이브로 검증됐다." (전시테크/범용 SI가 제시하지 못하는 실증 우위)
### 0-2. 구현 계획 (설계 확정·미완 — "표준대로 채운다")
| 영역 | 상태 | 근거 |
|---|---|---|
| 도메인 모듈 M15 옥션·M10 관람·M12/17 CMS·M16 BI·M18 관리자 | 설계 확정, 구현 대기(Phase D) | IMPLEMENTATION_BACKLOG Phase D |
| 멀티테넌시(tenant_id 전파·컨텍스트 해소) | 데이터모델 확정, 스키마·컨텍스트 구현 대기 | WORK_STATUS §6, data.md §1A |
| 실데이터 집계 전환(신규 5화면 샘플→API) | 화면 이식 완료, 집계 API 미구축 | WORK_STATUS §6, UNDEVELOPED_BACKLOG §1 |
| 접근성/i18n/테마 전면 적용 | NFR 표준 확정, 전면 스윕 대기 | WORK_STATUS §6 |
| 역할별 프론트 번들 분리 | 방식 확정 대기(모노레포 vs 서브패스) | tech.md §9-5 |
| AiTextRouter/AiConfig(텍스트 AI) | 설계 확정(UIWS 패턴), 착수 대기 | tech.md §6-1·R-T7 |
**원칙**: 위 항목은 제안서에서 **"구현 계획"**으로만 서술. "구현 완료"로 오기 금지(감점/신뢰 훼손 리스크).
---
## 1. 목표 아키텍처 종합 (5종 아키텍처 → 제안서 재구성)
### 1-1. 목표 아키텍처 4-Tier + 사이드카 (한 문장)
**React 18/19(Vite·TS) 역할별 분리 프론트 → SSO(JWT+2FA)·이중 RBAC → 공유 Spring Boot 3.2.5(Java 17)+MyBatis 모듈러 모놀리스 → PostgreSQL+PostGIS 단일 공간 원천(+읽기복제) / Redis 비동기 큐 / 오브젝트 스토리지**, 그 위에 **나노바나나 Python 워커 사이드카**(이미지 생성)와 **Claude AI(AiTextRouter·Ollama 폴백)**(텍스트 지능)를 **얇은 큐/HTTP 계약으로 결합**한다. (app.md §1·§10, system.md §1~§2, tech.md §1)
### 1-2. 계층별 종합 (전문가 풀 산출 통합)
| 계층 | 구성(확정) | 권위 문서 | 핵심 근거 |
|---|---|---|---|
| **표현(App)** | 역할별 6프론트(organizer·exhibitor·contractor·ops·admin·public/visitor) 번들 분리 + 공유 디자인시스템/컴포넌트/api-client 상속. 데스크톱=설계·에디터, 모바일=현장·조회·승인 | app.md §10, system.md §2-1 | 최소권한·공격면 축소, 중복구현 금지 |
| **인증·인가** | 단일 JWT SSO(HS256, jjwt 0.12.5) + TOTP 2FA(RFC6238) + 로그인 실패 잠금 + **이중 RBAC**(플랫폼 `plat`/행사 `roles·hm`) | app.md §6, tech.md §7 | WISE/UIWS 이식(재설계 금지) |
| **애플리케이션(백엔드)** | 공유 Spring Boot **모듈러 모놀리스**(`com.zioinfo.kintex.module.mN`, 4계층 Controller/Service/Mapper/DTO) + 룰엔진 + 배치/배선 엔진(PostGIS) + 옥션·BI·CMS 서비스 | app.md §1~§5·§10-2 | 단일 공유 백엔드·경로/RBAC로 역할 표면 분리 |
| **비동기** | Redis 큐(RenderJob·서류·EDM·알림) + 옥션 실시간 순위/타이머 + 캐시/쿼터/WS 세션 상관. STOMP over WebSocket 실시간 푸시 | app.md §8·§9, system.md §6-3 | 백엔드 무상태화 → 수평 확장 |
| **AI(이미지)** | 나노바나나 Python 워커(google-genai, `gemini-3.1-flash-image-preview`) — image-to-image 구조보존 + 워터마크 후처리 + S6 로컬 PIL 래스터 | tech.md §1-3, system.md ADR-1 | google-genai=Python SDK, Java 재구현 회피 |
| **AI(텍스트)** | `AiTextRouter`: Claude 기본(`api.anthropic.com` 승인 예외) → **Ollama 폴백**(폐쇄망/장애). 부스 배치는 **제약 솔버 주도**, LLM은 조건해석만 | tech.md §6 | 결정론 우선·환각 차단·데이터 주권 |
| **데이터** | PostgreSQL+PostGIS 단일 공간 원천(부스 Polygon·트렌치 Point·배선 LineString, SRID 0) + 프라이머리-스탠바이 복제 + 읽기복제(BI·공개 오프로드) + BI 데이터마트(스타 스키마) | data.md §2~§6 | 단일 공간 원천 불변·운영/분석 분리 |
| **저장(바이너리)** | 오브젝트 스토리지(도면·생성이미지·서식·PDF·콘텐츠) — 경로만 DB, CDN 프론팅 | data.md §0-1, system.md §2-1 | 대용량 독립 확장 |
| **네트워크·보안영역** | 4존+관리존(엣지·DMZ·내부망·AI워커·데이터·관리) 계층 방어, DMZ→데이터존 직결 금지, egress 화이트리스트(Claude·Gemini·PG·SMTP) | network.md §2~§5 | 공개/내부 물리·논리 분리·폐쇄망 지향 |
### 1-3. 멀티테넌시 격리 구조 (다중 전시관 SaaS)
- **격리 축**: `tenant_id`(전시관: KINTEX #1·COEX #2…) ⊃ `event_id`(행사) ⊃ 행사 RBAC(역할). 3중 격리.
- **fail-closed 다층 방어**(REQ-S-018): ①테넌트 컨텍스트 필터(서브도메인 우선+사용자소속 보조, 불일치 거부) → ②서비스 가드 → ③MyBatis 공통 인터셉터(tenant_id 자동 주입) → ④PostGIS/쿼리(선두 복합 인덱스). 어느 한 층이라도 불일치면 차단.
- **온보딩**: 코드 배포 없이 **데이터 온보딩**(홀·요율·규정 룰셋·마스터데이터 입력). 기능 로직 불변. (app.md §6, data.md §4-4, system.md §3-1 멀티테넌시)
- **주의(현실)**: 멀티테넌시는 **구현 계획**(WORK_STATUS §6). 제안 시 "데이터모델 확정→스키마 tenant_id→컨텍스트 배선" 3단계 로드맵으로 서술, 백필 전 단일테넌트 무중단 동작→검증 후 필터 활성화(회귀 방지).
### 1-4. 필수 도식 12종 — 작도 사양 (deck-designer 직접 작도용)
> 각 도식의 **박스·연결·라벨 텍스트**를 명세. visual-designer(선 SVG 아이콘)·deck-designer(python-pptx)가 재해석 없이 작도. 모든 라벨은 한국어, 아이콘은 선(stroke) 스타일.
#### ① 3중 해자 벤다이어그램 (S04·S12)
- **형태**: 3원 교집합 벤다이어그램. 3개 동일 크기 원 60% 겹침.
- **원 라벨**: 원A "나노바나나 시공 예측 이미지"(부제: image-to-image 구조보존·40초·S1~S7), 원B "PostGIS 실측 공간데이터"(부제: 부스 폴리곤·트렌치 포인트·배선 LineString 단일 원천), 원C "공사 옥션 폐루프"(부제: AI 자료→응찰→낙찰→발주 자동전환).
- **중앙 교집합(3원 겹침)**: 강조 배지 "재현 난이도 높은 결합 = 우리만의 해자(Moat)". 2원 교집합에는 회색 "단일 요소는 모방 가능".
- **하단 캡션**: "셋의 결합은 경쟁 전시테크·범용 SI가 재현하기 어렵다."
#### ② 모듈맵 — 생애주기 × 우선순위 매트릭스 (S16)
- **형태**: 가로축=전시 생애주기 6단계(판매·기획 → 설계·시각화 → 발주·계약 → 참가·관람 → 현장운영 → 사후·경영), 세로축=우선순위(P0 코어 / P1 / P2).
- **박스(모듈 칩)**: P0행에 **M2 플로어플랜·M3 부스설계·M4 유틸리티·M5 나노바나나**(강조색·굵은 테두리). P1행 M1·M6·M7·M9·M15(옥션 강조)·M10·M12·M16·M17·M18·§5B 공통·§1A 멀티테넌시. P2행 M8·M11·M13·M14.
- **하단 레이어 띠**: "공통/시스템관리 레이어(§5B) — 전 모듈 선행 기반" + "멀티테넌시(§1A) — tenant_id 격리".
- **캡션**: "18개 모듈 + 공통 레이어를 한 장으로 조망. P0 4개가 심장."
#### ③ 나노바나나 파이프라인 플로우 (S20)
- **형태**: 좌→우 수평 플로우, 8박스.
- **박스·라벨**: `[사용자: 렌더 요청]``[Spring 백엔드: RenderJob 생성·쿼터 확인]``[Redis 큐 kintex:renderjob:queue(leftPush)]``[나노바나나 Python 워커(BLPOP 소비)]``[Gemini image-to-image(G1 승인·라이브)]``[워터마크 후처리·S6 로컬 PIL 래스터]``[오브젝트 스토리지 적재]``[pub/sub → 백엔드 relay → WebSocket/STOMP /topic/render/{jobId}]``[프론트: 완료 푸시·갤러리 갱신]`.
- **분기 주석**: 워커 하단에 점선 "degraded: G1 미승인/Gemini 장애 시 목(mock) 응답·구조·워터마크 유지". "성공 시에만 쿼터 차감".
- **보안 배지**: `GEMINI_API_KEY`는 워커 env only(백엔드 미취급).
- **캡션**: "이미지 생성 전면 비동기 — 사용자 동기 대기 없음, 평균 40초."
#### ④ Before/After 슬라이더 (S21)
- **형태**: 좌우 분할 이미지 카드 + 중앙 세로 드래그 핸들.
- **라벨**: 좌 "Before: 빈 부스(도면/실측)", 우 "After: 시공 후 예상(AI 생성·워터마크)". 핸들 "드래그로 비교".
- **접근성 배지**: "ARIA slider·키보드 좌우 조작 지원(WCAG AA)".
- **하단**: 워터마크 텍스트 예시 배지 "AI 생성 예상 이미지 — 실제 시공과 다를 수 있음. 계약·심사 서류 사용 금지."
#### ⑤ 3안 비교 화면 (S24)
- **형태**: 3열 카드(안1·안2·안3) + 상단 공통 홀 정보 바 + 하단 액션 바.
- **카드 라벨**: 각 카드 상단 "안 N — 최적화 목표"(예: 안1 "통로 효율 최대", 안2 "프리미엄 부스 최대", 안3 "균형"). 카드 본문 미니 배치도 썸네일 + 지표(판매면적 ㎡·부스 수·규정 위반 0/N).
- **액션 바**: `[안 선택]` `[구역/블록 병합 편집]` `[병합 후 재검증]`.
- **캡션**: "제약 솔버가 상이한 최적화 목표로 정확히 3안 생성. 사람이 선택·병합·최종 확정."
#### ⑥ PostGIS 배선 오버레이 (S27)
- **형태**: 홀 평면도 위 배선 경로 오버레이. 부스 폴리곤(연회색) + 트렌치 포인트(◆) + 분전반(▣) + 배선 LineString(색상별).
- **색상 범례**: 전기=적색, 네트워크=청색, 급배수=녹색(S6 규약 정합).
- **주석 배지**: "최근접 트렌치 KNN(`geom <-> point`)", "최단 경로(`ST_Length`)·통로 횡단 최소화", 가정 트렌치엔 "가정 좌표(실측 대기)" 배지.
- **캡션**: "수기 위치표시도 폐지 — 좌표 클릭 자동 작도. SRID 0 홀 로컬 미터, GiST 인덱스 실시간."
#### ⑦ 옥션 폐루프 흐름도 (S29)
- **형태**: 순환/좌→우 흐름, 좌측에 AI 자료 패키지 스택.
- **박스**: `[M2 배치 + M3 설계 + M4 배선/BOQ + M5 시공예측 이미지]`(AI 생성 자료 패키지) → `[옥션 개설(역경매/RFQ/고정가)]``[등록업체 검증 게이트: 미등록 응찰 차단 403]``[견적서(Quotation) 제출·PDF·버전]``[실시간 순위·라운드 마감(Redis 정렬셋·WS 델타)]``[종합평가 낙찰(가격+평판+납기)]``[Award → 계약/발주 자동전환(M6·M9 연동)]`.
- **캡션**: "AI 설계자료가 곧 발주 근거. 사진 수준 자료로 응찰→발주까지 한 흐름."
#### ⑧ 종합평가 스코어 비교표 (S31)
- **형태**: 업체 행 × 평가항목 열 표 + 가중 합계 열 + 낙찰 하이라이트 행.
- **열**: 업체명 | 견적금액(점수) | 평판(점수) | 납기(점수) | **가중 종합점수** | 순위.
- **가중치 주석 바**: "가중치(예): 가격 0.6 / 평판 0.25 / 납기 0.15 — 옥션별 설정형(`weight` jsonb)".
- **하이라이트**: 1위 행 강조 + "낙찰(Award)" 배지 + "선정 사유·항목별 비교" 캡션.
#### ⑨ 시스템 아키텍처 다이어그램 — 전 계층 (S41)
- **형태**: 상하 6밴드 계층도(system.md §2 논리 아키텍처 재구성).
- **밴드1 엣지/DMZ**: `[CDN·정적캐시]` `[WAF/Reverse Proxy(nginx)·TLS 종단·레이트리밋]`.
- **밴드2 역할별 프론트**: organizer·exhibitor·contractor·ops·admin(내부 배지)·public/visitor(공개 SSR/SEO/다국어 배지).
- **밴드3 SSO·RBAC**: `[JWT SSO + TOTP 2FA · 이중 RBAC(플랫폼/행사)]`.
- **밴드4 공유 백엔드**: `[REST + WebSocket/STOMP]` 아래 `[룰엔진]` `[배치·배선 엔진·PostGIS]` `[옥션 M15]` `[BI M16·KpiSnapshot]` `[CMS M17]` `[공개 API read-only]`.
- **밴드5 비동기**: `[Redis 큐·순위·타이머·캐시·쿼터]``[나노바나나 워커]` `[서류·EDM 워커]`.
- **밴드6 데이터**: `[PostgreSQL+PostGIS]` `[읽기복제(BI·공개)]` `[오브젝트 스토리지]`.
- **우측 외부 게이트 컬럼**: `[Gemini(G1·라이브)]` `[Claude(AiTextRouter)]` `[PG 결제]` `[kxwp 릴레이]` `[등록업체 DB 739]`.
- **연결 주석**: 워커→Gemini "egress only", 워커→백엔드 "WebSocket 완료 푸시", 백엔드→읽기복제 "BI/공개 오프로드".
#### ⑩ 보안영역 분리도 (망 구성) (S47)
- **형태**: 좌→우 신뢰도 상승 존 다이어그램(network.md §2).
- **존 박스**: `[인터넷]``[엣지: CDN·WAF·DDoS]``[DMZ(공개): 공개LB·public SSR·visitor GW·공개 API GW·PG콜백 — 쓰기권한 없음]``[내부망(인증): 내부LB·SSO/2FA·내부 API GW·공유 백엔드]``[AI 워커존(egress 제한): Redis·나노바나나·Ollama]``[데이터존(최내곽): PostgreSQL+PostGIS·오브젝트 스토리지·읽기복제 — 아웃바운드 전면 차단]`. 상단 `[관리존: 배스천·관측성]`.
- **철칙 배지(굵게)**: "DMZ → 데이터존 직접 접근 절대 금지", "데이터존 아웃바운드 0(유출 경로 제거)", "admin. = VPN/허용IP+2FA, 인터넷 미도달".
- **egress 컬럼**: 승인 4목적지만(Claude·Gemini·PG·SMTP), 그 외 DROP.
#### ⑪ 멀티테넌시 격리 아키텍처도 (S56)
- **형태**: 수직 4층 방어 스택 + 좌측 테넌트 컨텍스트 진입.
- **진입**: `[요청: 서브도메인(kintex./coex.) + JWT 사용자 소속]``[테넌트 컨텍스트 해소: 불일치 시 거부]`.
- **4층 박스(위→아래)**: `[①컨텍스트 필터]``[②서비스 가드]``[③MyBatis 공통 인터셉터(tenant_id 자동 주입)]``[④PostGIS/쿼리: tenant_id 선두 복합 인덱스]`.
- **우측 데이터**: `[TB_* … WHERE tenant_id = ctx]` 격리 표현. 하단 배지 "fail-closed — 한 층이라도 불일치면 차단".
- **캡션**: "KINTEX #1 검증 → COEX #2 코드 배포 없이 데이터 온보딩. 기능 불변, 데이터·권한만 분리."
#### ⑫ 일정 간트 (Phase × 모듈) (S58)
- **형태**: 가로 타임라인 3구간(Phase 1 ~4M / Phase 2 ~4M / Phase 3 ~4M+), 세로 워크스트림 막대.
- **막대(Phase 1)**: 아키텍처(A)·공통레이어(B, 2FA/시스템관리)·P0 코어(C: M2·M3·M4·M5).
- **막대(Phase 2)**: 옥션 M15·관람 M10·BI M16·관리자 M18·판매/서류/정산 M1/6/7/9·CMS M12/17.
- **막대(Phase 3)**: 멀티테넌시·M11·M13·M14·배포 안정화·SM 이관.
- **마일스톤 다이아몬드**: G1(나노바나나 승인·완료)·G2(배포서버·완료)·Phase 게이트·QA 게이트·오픈.
- **캡션**: "선행 게이트 통제 + Phase 게이트 QA 통과 후 진행."
> **추가 표준 도식**(공공 제안 필수, outline 명시): 추진체계도(S15)=거버넌스(PM/PMO/dev-PM)+아키텍트(AA·SA·TA·DA·NA)+코어/도메인/AI/QA/DevOps 팀 조직도 / 리스크 매트릭스(S59)=발생×영향 2×2에 R-1~R-8·C1~C8 배치 / 기대효과 인포그래픽(S61)=견적·배치·검수·위치표시도·시공예측 5지표 Before/After. 사양은 §2·§5·strategy §4 참조.
---
## 2. Win Theme 기술 근거 (5선 — 실현 가능성 입증)
### WT-1. 나노바나나 시공 예측 (배점 20 · 최우선)
- **기술 골자**: RenderJob 큐(Redis `kintex:renderjob:queue`) → Python 워커(BLPOP) → Gemini **image-to-image 구조보존** → 워터마크 후처리 → 오브젝트 스토리지 → WebSocket `/topic/render/{jobId}` 완료 푸시.
- **구조보존 메커니즘**(REQ-A-002·003): 참조 이미지 `inlineData` + 구조화 프롬프트 사전(BOOTH_TYPES·BOOTH_STYLES·FIXTURE_LAYERS)에서 **보존(골격·치수·통로·카메라 앵글) / 교체(스타일·재질만 사실화) 명시 분리**. ReRoomAI 검증 패턴 이식(reroomai-source.md).
- **방어**(REQ-A-006): 다층 크기가드·mimeType 자동감지·SAFETY 분기·친화적 한글 에러·**성공 시에만 쿼터 차감**. 간판 한글 렌더 실패 시 간판영역 후처리 합성(REQ-A-008).
- **S6 배선 오버레이**(REQ-A-004): Gemini 미경유 — **백엔드/워커 로컬 PIL 결정적 래스터 합성**(전기 적·네트 청·급배수 녹). 좌표 정확성 확보·비용/지연 제외.
- **실현 근거**: **라이브 검증**(G1 승인·워커 Gemini 실동작, WORK_STATUS §4). "말이 아니라 이미 돈다."
- **도식**: ③파이프라인·④Before/After.
### WT-2. 배치·설계·배선 AI 3안 + 규정 자동검증 (배점 20+15 교차)
- **기술 골자**: 부스 배치 3안은 **결정적 제약 솔버**(비-LLM)가 상이한 최적화 목표로 생성, LLM(Claude)은 조건 해석·설명만(REQ-A-009). → 공공 "AI 신뢰성" 프레이밍의 핵심(결정론 주도).
- **규정 자동검증**(REQ-F-009·013, REQ-A-010): 룰셋(`compliance-v*.json` 버전 데이터)을 룰엔진이 평가 + PostGIS 공간 판정(통로폭 `ST_Distance/ST_Buffer`·비상구 `ST_Intersects`·홀 이탈 `ST_Contains`·바닥하중 홀별). 병합 후 재검증. 위반 시 `COMPLIANCE_BLOCKED`(422).
- **배선 자동 작도**(REQ-F-015~017): 기기목록→kW 합산→분전반 산출, 최근접 트렌치 KNN(`geom <-> point`)→최단 배선 LineString(`ST_Length`, 통로 횡단 최소 휴리스틱), 위치표시도 좌표 클릭 자동 작도(수기 폐지).
- **AI 신뢰 3층**: 결정적 알고리즘(제약 솔버·PostGIS) 중심 + LLM 보조 + **사람 최종 확정**. 규정검증=플래깅까지, 구조안전 판정은 구조기술사/킨텍스(Non-Goal). RAG 근거·인용·환각 차단(abstain, REQ-A-017).
- **실현 근거**: M2~M5 매퍼(PostGIS 공간 SQL)·룰엔진 **라이브**(WORK_STATUS §4). 3안 생성·병합·집계 API는 구현 계획.
- **도식**: ⑤3안 비교·⑥PostGIS 배선.
### WT-3. 공사 옥션 폐루프 (배점 15 · 폐루프 핵심)
- **기술 골자**: `TB_AUCTION 1─N TB_QUOTATION ─ TB_AWARD`. 옥션 유형 설정형(REVERSE/RFQ/FIXED). AI 자료 패키지(M2~M5 참조 `attached_refs`) 열람 기반 견적서 제출·PDF·버전. 실시간 순위=Redis 정렬셋 + WebSocket `/topic/auction/{auctionId}` 델타 푸시, 라운드 마감=Redis 타이머.
- **등록 게이트**(REQ-S-007): 미등록 업체 응찰 원천 차단 `NOT_REGISTERED_COMPANY`(403). M7이 `TB_COMPANY.kintex_registered` 검증 권위. → 킨텍스 "등록업체 시공 필수" 규정을 시스템이 강제.
- **종합평가·발주 전환**(REQ-F-028·032): 가중 스코어(가격+평판+납기, `weight` jsonb) → Award → 계약/발주 자동전환(M6·M9 연동, 이벤트 디커플·순환 금지).
- **실현 근거**: 옥션은 **구현 계획**(Phase D-M15). 스캐폴드 4계층·큐·WS 패턴·데이터모델 확정으로 실현성 입증.
- **도식**: ⑦옥션 폐루프·⑧종합평가 스코어.
### WT-4. 3중 보안 — 워터마크·PII·테넌트 (배점 10)
- **워터마크**(REQ-S-009): M5 이미지 응답 **항상** `watermarkRequired:true`+`watermarkText`+`notice`(계약·심사 서류 금지). 제거 불가(계약 강제, app.md §5-6). 워커 목/live 공통.
- **PII 격리**(REQ-S-013): 관람객/리드 `*_enc` AES-256-GCM 컬럼 암호화, 응답 완전 제외/마스킹, 동의 분리(privacy 필수/marketing 선택/share), 접근 감사 전수. BI 마트는 개인 식별자 비반입(집계만).
- **테넌트 3중**(REQ-S-018): §1-3 fail-closed 4층(컨텍스트→서비스→MyBatis→PostGIS).
- **외부 API 격리**(REQ-S-001·002): Ollama 기본 + Claude/Gemini만 승인 예외. `GEMINI_API_KEY` 워커 env only(백엔드 미취급), egress 워커존 단일 경로.
- **실현 근거**: 인증(JWT/RBAC/2FA/admin env)·워터마크 **라이브**. PII 암호화·테넌트 필터는 구현 계획.
- **도식**: ⑩보안영역 분리·⑪멀티테넌시 격리.
### WT-5. 멀티테넌트 SaaS 확장 (배점 5 · 미래가치)
- **기술 골자**: §1-3. tenant_id 전파 + 2계층 관리자(플랫폼 슈퍼관리자 테넌트스위처 / 테넌트 관리자 단일스코프) + 컨텍스트 해소. 온보딩 6단계=마스터데이터 입력 수준.
- **개발 부담 최소화**(strategy R-8): 멀티테넌시=격리 레이어만 추가, 기능 로직 불변. 백필 전 단일테넌트 무중단→검증 후 필터 활성화(회귀 방지).
- **실현 근거**: 구현 계획(WORK_STATUS §6). GUARDiA 표준 프레임워크 멀티테넌트 패턴 재사용.
- **도식**: ⑪멀티테넌시 격리.
---
## 3. 비기능(NFR) 충족안 — REQ-N 21건 전부 대응 (P6·P4)
> 근거: system.md §3(확장성·HA·성능·용량)·tech.md §5(관측성·성능)·data.md §3·§6·network.md §7. **SLO 99.5%(성수기 99.9% 지향)**.
| REQ-ID | 요구 | 충족 방안(기술) | 측정/게이트 | 근거 |
|---|---|---|---|---|
| REQ-N-001 | 코어 SLO 99.5% | 무상태 백엔드 + Redis 외부화 + HA(§아래) | 월 가용성 ≥99.5%, `/actuator/health` | system.md §3-2 |
| REQ-N-002 | 백엔드 2인스턴스·롤링 무중단 | 최소 2 인스턴스 + 헬스체크 LB + 롤링 배포 | 무중단 배포 검증(스테이징→prod) | system.md §3-2 |
| REQ-N-003 | PG 스탠바이·failover·PITR | 프라이머리-스탠바이 스트리밍 복제 + Patroni/관리형 자동 failover + PITR | RPO 최소·RTO 분 단위 | system.md §3-2 |
| REQ-N-004 | Fail-Safe 배포 | 백업→배포→헬스체크 200→실패 시 롤백(이전 jar 유지) | **라이브**(deploy_kintex.sh) | WORK_STATUS §2 |
| REQ-N-005 | degraded 모드 | G1 미승인/Gemini 장애=워커 목 응답(구조·워터마크 유지), Claude 실패=Ollama 폴백, PG 복제지연=프라이머리 폴백 | 무네트워크 스모크 통과 | system.md §3-2 |
| REQ-N-006 | 나노바나나 40초·1024px | 전면 비동기(WS 완료 푸시) + 클라 Canvas 1024px 다운스케일+JPEG 0.85 전처리 | 단건 평균 ≤40s | system.md §3-3 |
| REQ-N-007 | 대량 생성 제어 | 자동생성 **S1·S7 한정** + 동일 스키마해시 캐시 + 행사별 RenderJob 쿼터(`RENDER_EVENT_QUOTA` 기본 500·성공 시만 차감) | 쿼터/캐시 히트율 | system.md §3-3, R6 |
| REQ-N-008 | 공간쿼리 GiST 실시간 | 전 `geom` 컬럼 GiST 인덱스(트렌치 KNN·통로버퍼·비상구 교차) | 배치·배선 P95 <2s | data.md §3-4 |
| REQ-N-009 | 워커 독립 스케일 | 나노바나나·서류·EDM 워커 큐 깊이 기반 증감, 백엔드 응답성과 분리 | 큐 적체 모니터 | system.md §3-1 |
| REQ-N-010 | BI/공개 읽기복제 오프로드 | BI 집계·공개 조회·플로어플랜 열람 읽기복제 라우팅, 운영 프라이머리 보호 | 프라이머리 부하 격리 | system.md §3-1 |
| REQ-N-011 | 워커 장애 재큐(멱등) | 무상태 소비자 N대 + 가시성 타임아웃 후 재큐(멱등 Job) + AOF 지속화 + ACK | 진행 Job 유실 0 | system.md §3-2 |
| REQ-N-012 | WCAG 2.1 AA·KWCAG 2.2 | 시맨틱 마크업·ARIA·키보드·대비, 공공 KWCAG 2.2 병행 점검 | 접근성 자동+수동 감사 | rfp §6-N1 |
| REQ-N-013 | 캔버스/맵 비시각 대안 | 플로어플랜/배선 캔버스에 데이터 테이블·검색·키보드 부스 선택 대안 필수 제공 | 비시각 경로 검증 | rfp §6-N1 |
| REQ-N-014 | 다국어 i18n·hreflang | i18n 외부화(한 기본+영/중/일)·로케일 전환·hreflang·로케일 포맷 | 4로케일 전환 | rfp §6-N3 |
| REQ-N-015 | 라이트/다크 테마 | CSS 토큰 2벌·prefers-color-scheme 연동·선택 영속 | 테마 왕복 | rfp §6-N4 |
| REQ-N-016 | 반응형 | 데스크톱=설계/에디터, 모바일=현장/조회/승인 | 브레이크포인트 검증 | PLANNING §8 |
| REQ-N-017 | 단일 공간 원천 | M2 폴리곤·M4 배선 매퍼 권위, 소비 모듈 조회만·중복 저장 금지 | ArchUnit·계약 검수 | app.md §3-2, data.md §0-2 |
| REQ-N-018 | 관측성·미기록 | 로그·메트릭·트레이스 관리존 수집, 자격증명·PII·스택트레이스 미기록(`include-stacktrace: never`) | 로그 감사 | tech.md §5, network.md §8 |
| REQ-N-019 | 공개영역 CDN·요청률 상한 | 공개 SSG+CDN 캐시·요청률 상한·slowloris 타임아웃·동시연결 캡 | 스파이크 CDN 흡수 | network.md §7 |
| REQ-N-020 | tenant_id 선두 복합 인덱스 | 격리 쿼리 표준 인덱스(tenant_id 선두) | 격리 쿼리 성능 | data.md, §1-3 |
| REQ-N-021 | BI 데이터마트 배치 ETL | 스타 스키마(FACT/DIM) 야간 배치 ETL 또는 읽기복제 적재, KpiSnapshot | 원천-집계 정합(오차 0) | data.md §6 |
**HA 구성 요약(P6 SLA 표 입력)**: 프론트/공개=CDN 다중엣지+nginx 다중화 / 백엔드=2+ 인스턴스 헬스체크 LB·롤링 / Redis=Sentinel(자동 failover)+AOF / PostgreSQL=프라이머리-스탠바이+자동 failover+PITR / 워커=무상태 N대·재큐 / 배포=Fail-Safe(롤백). **SLO: 코어 99.5%(성수기 99.9% 지향), 나노바나나 생성=best-effort 비동기(큐 소진 목표).**
**용량 산정(P4·P6 근거)**: 단일 대형 행사 3,000~5,000부스(수만 지오메트리·GiST 필수) / RenderJob 부스 3,000×자동 2샷(S1·S7)+온디맨드=수천~1만 이미지(수 GB~수십 GB, OBJ+CDN·캐시 억제) / 리드·체크인 수만~수십만(파티셔닝 후보) / 동시 사용자 인증 수백+공개 수천~수만(CDN 흡수) / **DB 연결풀 Hikari max 3(`DB_POOL_MAX`)+PgBouncer 권고**(공유 PG 포화 방지). (system.md §3-4)
---
## 4. 보안 충족안 — REQ-S 20건 전부 대응 (P5 · 시크릿 값 미기재)
> 근거: SECURITY.md·app.md §5-6·§6·network.md·data.md §5·tech.md §7. **불변(위반=QA 반려)**.
| REQ-ID | 요구 | 충족 방안(기술) | 근거 |
|---|---|---|---|
| REQ-S-001 | 외부 API 정책 | Ollama 기본 + Claude(`api.anthropic.com`)·Gemini만 승인 예외, egress 화이트리스트 그 외 DROP | network.md §5 |
| REQ-S-002 | GEMINI 키 워커 env only | 나노바나나 워커 env only, Spring은 큐/콜백만, 워커존 단일 egress | tech.md §3-5 |
| REQ-S-003 | 자격증명·키 완전 제외 | 응답/로그/에러/커밋 마스킹, `*_enc`·해시·내부IP·워커토큰 미노출 | tech.md §7 |
| REQ-S-004 | admin 비번 env 재시드 | `ADMIN_PASSWORD_ENC`(AES-256-GCM)+별도 키파일→기동 시 BCrypt 재시드, admin123 금지 | **라이브**(env 시더) |
| REQ-S-005 | JWT+2FA+실패 잠금 | JWT(HS256)+TOTP(RFC6238)+로그인 실패 잠금(관리자 해제) | **라이브**(V7/V8) |
| REQ-S-006 | 행사 RBAC 3중 | 테넌트 격리 ⊃ 행사 RBAC(열람=멤버/홀매니저, 편집=역할별 게이트) | app.md §6 |
| REQ-S-007 | 미등록 업체 차단 | M15 응찰·초대 `NOT_REGISTERED_COMPANY`(403), M7 검증 권위 | app.md §6-2 |
| REQ-S-008 | admin/internal 게이트 | `/api/admin/**`=hasRole(ADMIN), `/api/internal/**`=X-Worker-Token | app.md §5-5 |
| REQ-S-009 | 이미지 워터마크 항상 | M5 응답 항상 watermarkRequired:true+text+notice, 제거 불가 | **라이브**·app.md §5-6 |
| REQ-S-010 | 오류 스택트레이스 미노출 | `server.error.include-*: never` + ApiResponse.error 요약만 | tech.md §5-1 |
| REQ-S-011 | DataAccessException 요약 매핑 | `@RestControllerAdvice GlobalExceptionHandler` INTERNAL 요약(테이블/컬럼 누출 차단) | app.md §7-2 |
| REQ-S-012 | 민감 액션 감사 전수 | 승인·낙찰·설계변경·룰셋개정·리드접근·로그인/권한 `TB_AUDIT_LOG`(tenant_id 포함, `@Audited` AOP) | app.md §7-3 |
| REQ-S-013 | PII 동의·마스킹·암호화 | 동의 분리(privacy/marketing/share)·최소수집·`*_enc` AES-256-GCM·응답 제외·보존/파기 | data.md §5 |
| REQ-S-014 | 입력 검증 | Bean Validation 화이트리스트·파일 MIME/크기 가드(WAF+앱 이중) | tech.md §7, network.md §4-2 |
| REQ-S-015 | XSS 방지·CSP | React 이스케이프·dangerouslySetInnerHTML 금지·CMS sanitize·CSP | rfp §6-N2 |
| REQ-S-016 | 인젝션 차단 | MyBatis `#{}` 강제·`${}` 금지, PostGIS/명령/LDAP 파라미터 바인딩 | rfp §6-N2 |
| REQ-S-017 | IDOR·CSRF·JWT 회전 | 리소스 소유·tenant_id 검증, CSRF/SameSite, JWT 만료·회전 | rfp §6-N2 |
| REQ-S-018 | fail-closed 테넌트 격리 | 4층(컨텍스트 필터→서비스 가드→MyBatis 인터셉터→PostGIS) | §1-3 |
| REQ-S-019 | 의존성 SCA CI 게이트 | OWASP Dependency-Check CI 게이트·정기 패치 | rfp §6-N2 |
| REQ-S-020 | 보안영역 분리 | 공개 DMZ 쓰기 없음·데이터존 직결 불가·DMZ↔내부 HTTPS(mTLS 권장)만·DB 프로토콜 횡단 금지 | network.md §2·§4 |
**보안 성숙도 강조점(P5)**: ①이미 라이브인 인증·워터마크·admin env(4·5·9번 실증) → "설계가 아니라 동작하는 통제". ②데이터존 아웃바운드 0으로 유출 경로 자체 제거(network.md 철칙). ③외부 AI 키 격리=네트워크 격리 정합(Gemini 워커존만, Claude 백엔드만, 데이터존 무egress).
---
## 5. WBS·공수(M/M) 산정 (P8)
> 근거: IMPLEMENTATION_BACKLOG Phase A~E·PLANNING §9 로드맵(Phase 1~3 각 ~4개월)·142 REQ·18 모듈. 산정 기반=유사 공공 AI SI 생산성 + 기능 규모(100대 기능: 기구현 63·부분 18·신규 19) + 라이브 스캐폴드로 인한 **착수 리스크·초기 구축 공수 절감** 반영.
### 5-1. WBS 구조 (Phase × 산출물)
| Phase | 기간(가정) | 주요 작업(WBS) | 완료 게이트 |
|---|---|---|---|
| **A 아키텍처·거버넌스** | 착수~1M | AA/SA/TA/DA/NA 5종 아키텍처(**완료**)·정합 검증 | 아키텍처 확정·불일치 0 |
| **B 공통/시스템관리 레이어** | ~Phase1 내 | 인증(JWT/2FA)·시스템관리·공통 업무기능·공통 컴포넌트(WISE 이식) | 2FA/RBAC/감사 왕복(**대부분 라이브**) |
| **C P0 부스 코어** | Phase1(~4M) | M2 플로어플랜(3안·규정검증)·M3 부스설계·M4 배선·M5 나노바나나 | 3안·병합·재검증·시각화 |
| **D P1 도메인** | Phase2(~4M) | M15 옥션·M10 관람·M12/17 CMS/공개·M16 BI·M18 관리자·M1/6/7/9 | 각 모듈 왕복·QA 통과 |
| **E P2·배포·확장** | Phase3(~4M+) | M11·M13·M14·**멀티테넌시**·역할별 번들 분리·배포 안정화·SM 이관 | (G2 후)배포·이관 |
### 5-2. 직군별 공수 산정 (M/M)
> 12개월(Phase 1~3) 기준. 라이브 스캐폴드/인프라/인증 실적으로 초기 구축 공수를 이미 일부 소화 → 산정에 반영(과다 산정 금지). Phase A는 대부분 완료(감독 공수만 잔여).
| 직군 | Phase 1 | Phase 2 | Phase 3 | 합계(M/M) | 산정 근거 |
|---|---|---|---|---|---|
| 총괄 PM | 4 | 4 | 4 | 12 | 상주 1.0 FTE |
| 아키텍트(AA·SA·TA·DA·NA) | 8 | 4 | 2 | 14 | Phase A 집중(완료)→거버넌스 감독 |
| 백엔드(공통·코어·도메인) | 12 | 16 | 12 | 40 | 18모듈 코어, 최대 투입 직군 |
| 프론트엔드(역할별 6포털) | 8 | 12 | 8 | 28 | Stitch 이식+역할별 번들 |
| DB 엔지니어(PostGIS) | 4 | 4 | 2 | 10 | 공간 스키마·매퍼·BI 마트·멀티테넌시 |
| AI 개발(Claude·제약 솔버) | 4 | 4 | 2 | 10 | 배치 솔버·규정보조·예측·AiTextRouter |
| 시각화(나노바나나 워커) | 4 | 2 | 2 | 8 | 파이프라인(라이브)·샷세트·품질 |
| QA | 4 | 6 | 6 | 16 | 점진 경계면·보안 불변·회귀 |
| DevOps/인프라 | 2 | 2 | 4 | 8 | CI/CD(라이브)·배포·이관 |
| **소계** | **50** | **54** | **42** | **146** | |
- **총 공수: 약 146 M/M**(핵심 개발) + PMO/형상관리·품질보증 오버헤드(약 4~8 M/M) → **총 ~150 M/M 규모**.
- **가격평가·투입계획 정합**: 위 산정은 가격제안 투입인력표(직군·기간·M/M)와 1:1 매핑. 라이브 실적(스캐폴드·인증·인프라·나노바나나)이 Phase A/B 공수를 실측 절감 → 동일 규모 대비 **착수 리스크·초기 구축비 우위**.
- **주의**: 예산·사업기간은 실제 RFP 부재로 [가정](공공 SI 통례). 실제 공고 확인 시 M/M·기간 즉시 재정렬.
---
## 6. SM 운영 체계 (SLA·장애대응·정기점검·CI/CD 운영)
> SI 구축 후 유지보수/운영 제안. **이미 라이브인 자동배포 파이프라인을 실적으로 인용**(과장 아님 — WORK_STATUS §2·§7 검증분).
### 6-1. 운영 체계·조직(R&R)
- **서비스 데스크·ITSM 프로세스**: 요청(SR)·장애(Incident)·변경(Change)·배포(Release)·문제(Problem·RCA). 공통 업무모듈(통합검색·알림·회의록)·감사로그 기반 티켓 이력.
- **조직**: 운영 총괄 1 + 백엔드/프론트 유지보수 + DB/인프라(DevOps) + AI 워커 담당 + QA. 근무체계=평일 상주 + 행사 성수기(3~5·9~11월) 온콜.
### 6-2. SLA 지표
| 지표 | 목표 | 측정 |
|---|---|---|
| 코어 서비스 가용성 | **99.5%**(성수기 99.9% 지향) | 월 가용성·`/actuator/health` |
| 장애 응답시간 | 심각도별(P1 즉시·P2 30분·P3 4시간) | 티켓 타임스탬프 |
| 장애 복구시간(MTTR) | P1 ≤2h·P2 ≤1영업일 | 복구 완료 기록 |
| 배포 성공률 | ≥99%(Fail-Safe 롤백 자동) | 배포 로그 health 게이트 |
| 나노바나나 큐 소진 | best-effort(피크 워커 스케일아웃) | 큐 적체·처리량 |
측정·보고: 월간 SLA 리포트(가용성·MTTR·티켓·배포). 페널티 체계는 계약 조건에 따름(RFP 확인 필요).
### 6-3. 유지보수 활동
- **예방정비·정기점검**: DB 인덱스/통계·복제 지연·백업(PITR) 정상성, Redis AOF·큐 백로그, 워커 하트비트, 디스크/스토리지 용량, 인증서(TLS) 만료 점검.
- **패치/보안 업데이트**: OWASP Dependency-Check SCA 정기 스캔(REQ-S-019)·의존성 패치, OS/미들웨어 보안 패치. 룰셋(요율·규정) **무중단 개정**(버전 데이터, M18 백오피스·감사 기록).
- **성능 튜닝**: PostGIS 공간 쿼리(GiST·EXPLAIN ANALYZE)·Hikari 풀·읽기복제 라우팅·BI 배치 ETL 시간 최적화.
- **형상관리**: Gitea `zio/kintex`·Conventional Commits·main 보호·환경 분리(dev/staging/prod).
### 6-4. 장애 대응
- **등급분류·에스컬레이션**: P1(서비스 중단)~P4(경미). 자동 알림(WebSocket/통합 알림센터)·온콜 에스컬레이션.
- **RCA·재발방지**: 장애 후 근본원인 분석(RCA) 리포트·재발방지 조치 티켓화. 감사로그·관측성(로그/메트릭/트레이스, PII·스택트레이스 미기록)로 추적.
- **degraded/BCP·DR**: G1/Gemini 장애=워커 목 응답(구조·워터마크 유지), Claude 장애=Ollama 폴백, PG 장애=자동 failover+PITR, 배포 실패=자동 롤백. 데이터 백업·복구 절차(RPO 최소·RTO 분 단위).
### 6-5. CI/CD 운영 (라이브 실적 인용)
- **자동배포 파이프라인(라이브·검증됨)**: `workspace/kintex` → Gitea webhook → `deploy_kintex.sh`(bootJar + vite build → 배포 → **health 게이트 → 실패 시 롤백**). push 시 자동배포. (WORK_STATUS §2·§7)
- **운영 함정 숙지(실측 교훈)**: 서버 `/opt/zioinfo/deploy_server.py`(kintex 블록)·`/opt/kintex/` 수정 시 **반영+재시작 필수**(로컬만 고치면 웹훅 1ms no-op), 백엔드도 반드시 빌드, `sh ./gradlew`(실행권한), 로컬 vite rollup win32 크래시=**서버 빌드 신뢰**(tsc 통과 검증).
### 6-6. 이행/이관
- 인수인계(WORK_STATUS.md 인수인계 문서 체계 활용)·안정화·지식이전(운영자/개발자 지침서 PPT·프로그램 사양서)·종료 시 이관 계획. 운영 도메인 `kintex.wise.ai.kr` 전환(후속).
---
## 7. RTM 기술 파트 갱신 입력 (REQ ↔ tech_advisory 절 매핑)
> rfp-analyst RTM 반영상태 갱신용. 각 REQ 계열이 본 자문서 어느 절에서 기술적으로 대응되는지 매핑. QA는 이 표로 기술 파트 커버리지 교차 점검.
| REQ 계열 | 범위 | tech_advisory 대응 절 | 제안서 목차 |
|---|---|---|---|
| REQ-F-007~019(배치·설계·배선 코어) | M2~M4 | §2 WT-2, §1-2, 도식 ⑤⑥ | P3 |
| REQ-F-025~032(옥션) | M15 | §2 WT-3, 도식 ⑦⑧ | P3 |
| REQ-F-037~055(관람·현장·물류) | M8·M10~M14 | §1-2 App/데이터, §3(오프라인 폴백 N-폴백) | P3 |
| REQ-F-061~066(BI) | M16 | §1-2 데이터, §3 REQ-N-021, data.md 마트 | P3 |
| REQ-F-071~081(관리·공통·멀티테넌시·포털) | M18·§5B·§1A | §1-2·§1-3, 도식 ⑪ | P4·P5·P7·P8 |
| REQ-F-001~006·020~024·033~036·048~051·056~060·067~070 | 판매·서류·정산·CMS·공개 | §1-2 계층·§2 WT, §6(룰셋 운영) | P3 |
| **REQ-A-001~008**(나노바나나) | M5 | §2 WT-1, 도식 ③④ | P3(핵심) |
| REQ-A-009·010·020(배치·규정·비전) | M2/M3 | §2 WT-2 | P3 |
| REQ-A-013·014(예측·KPI·Text-to-SQL) | M16 | §1-2 AI(텍스트), §3 N-021 | P3 |
| REQ-A-016·017(라우터·RAG·환각) | §6 | §1-2 AI(텍스트), §4(abstain) | P4·P5 |
| REQ-A-011·012·015·018·019(검수·챗봇·카피·리드·이상탐지) | 다수 | §1-2 AI(텍스트) | P3 |
| **REQ-N-001~021** | 비기능 21 | **§3 전체 표(21행 1:1)** | P6·P4 |
| **REQ-S-001~020** | 보안 20 | **§4 전체 표(20행 1:1)** | P5 |
| (인프라·HA·배포·관측성) | N-002·003·004·018 | §1-2·§3·§6 SM·§0 실적 | P4·P6·P8 |
---
## 요약 보고 (오케스트레이터·writer·deck-designer용)
**1. 도식 12종 사양 완비 여부**: ✅ **12/12 완비**(§1-4). ①3중 해자 벤 ②모듈맵 ③나노바나나 파이프라인 ④Before/After ⑤3안 비교 ⑥PostGIS 배선 ⑦옥션 폐루프 ⑧종합평가 스코어 ⑨시스템 아키텍처 ⑩보안영역 분리 ⑪멀티테넌시 격리 ⑫일정 간트 — 각 박스·연결·라벨 텍스트 작도 사양 기술. 추가 표준 도식 3종(추진체계도·리스크 매트릭스·기대효과)은 사양 위치 지정(§1-4 말미).
**2. REQ 대응 커버리지**: **미대응 0**. REQ-N 21건 전부(§3 표 21행 1:1), REQ-S 20건 전부(§4 표 20행 1:1), REQ-F/REQ-A는 §1-2·§2 Win Theme·§7 매핑표로 전 계열 대응. 총 142 REQ 기술 파트 커버(§7 RTM 갱신 입력).
**3. 공수 총 M/M**: **약 146 M/M**(핵심 개발, 직군 9종×Phase 3구간) + PMO/QA 오버헤드 4~8 → **총 ~150 M/M 규모**(12개월·Phase 1~3). 라이브 스캐폴드·인증·인프라·나노바나나 실적으로 Phase A/B 공수 실측 절감 반영 = 동일 규모 대비 착수 우위.
**4. 과장 방지 준수**: 라이브 확인분(인프라·CI/CD·나노바나나·인증·워터마크·PostGIS 코어·평면도 자산)만 "구축 완료/검증됨", 도메인 모듈·멀티테넌시·집계 API·i18n/접근성 전면 적용은 "구현 계획"으로 명확 구분(§0). 시크릿(키·비번·내부IP) 전면 미기재.
**5. 파일**: `C:\GUARDiA\workspace\kintex\_workspace\proposal\tech_advisory.md`

View File

@ -0,0 +1,274 @@
# 킨텍스 자동전시시스템 구축 — 도식 시각 컨셉 (Visual Concepts)
> 작성: proposal-visual-designer · 작성일: 2026-07-11 · 버전 v1.0
> 짝 문서: `design_system.md`(팔레트·타이포·레이아웃) · `assets/icons/*.svg`(선 아이콘 20종) · `tech_advisory.md` §1-4(도식 12종 박스·연결·라벨 사양) · `proposal_outline.md`(슬라이드 배치)
> 목적: 필수 도식 12종 + 표준 3종(추진체계·리스크·기대효과) + 표지·간지·3중 해자의 **시각 컨셉을 좌표·색·라벨 수준으로 구체화**. deck-designer가 python-pptx로 재해석 없이 작도.
> 좌표 규약: 단위 **inch**, 16:9 캔버스 13.333×7.5, 콘텐츠 폭 12.23(x 0.55~12.78), 본문 콘텐츠 y 1.45~6.9. 색 토큰=`design_system.md` §1. 폰트 맑은 고딕.
> 공통: 노드=ROUNDED_RECTANGLE, 카드 그림자 alpha 18%, 연결 화살표="→"(15pt)/""(16pt) SLATE, 하단 캡션 MUTED 9pt 1줄. **본문 10pt 하한 준수**.
---
## A. 표지 (S01) — "도면이 아니라 사진으로 컨펌한다"
**컨셉**: 좌측은 절제된 텍스트 존, 우측은 나노바나나 시공 예측 이미지가 대각으로 침투 — "왼쪽은 제안(텍스트), 오른쪽은 결과(사진)"의 물리적 은유.
- **배경**: `BLUE_DK` 풀블리드(-0.1,-0.1,13.53,7.7).
- **우측 대각 이미지 존**: 평행사변형 2매 겹침 — ①`BLUE` (6.4,-1.5,9,11, PARALLELOGRAM) ②`PURPLE` (9.2,-2.0,7,12, PARALLELOGRAM). **그 위에 나노바나나 생성 부스 시공 사진(워터마크 포함)을 반투명(alpha ~35%) 합성** — 이미지 없으면 blue→purple 그라디언트 도형으로 대체(폴백). 이미지 우하단에 소형 워터마크 배지 "AI 생성 예상 이미지".
- **좌상 브랜드**: WHITE OVAL(0.85,0.8,0.42) + PURPLE 코어 OVAL(0.97,0.92,0.18) + "KINTEX · WISE AI" 15pt WHITE Bold (1.42,0.78).
- **문서 배지**: (0.85,2.35) "기술제안서" PURPLE 12pt / (2.72,2.35) "Technical Proposal" 딥블루 10.5pt.
- **대제목** (0.85,2.9,11,1.9): 2줄 46pt WHITE Bold — "킨텍스 자동전시시스템" / "구축 기술제안서". line 1.02.
- **슬로건 배너**(핵심): (0.85,4.75,11.4,0.7) 반투명 화이트 라운드 rect(alpha 12%) 위 PURPLE 좌측 바 + "신청서를 내는 순간, 시공 후 사진을 먼저 본다." 18pt WHITE Bold.
- **서브 카피** (0.88,5.6): "설계 → 시각화 → 발주 → 시공 → 운영 → 분석. 킨텍스에서 한 흐름으로 끝난다." 12.5pt `#B9C7EA`.
- **하단 메타바** (0.85,6.15 라인): 사업명·기술스택 2줄 + 우측 "작성일 2026-07-11". 개발계획서 표지 하단과 동일 구조.
- **아이콘 앵커**: 슬로건 좌측에 `camera-render` 근사 도형(WHITE stroke).
---
## B. 간지 (Section Divider) — P1~P9 공통 템플릿
**컨셉**: 개발계획서 간지 계승 + **배점 배지 추가**(제안서 배점 규율 시각화).
- 배경 `BLUE_DK` + 우측 평행사변형 2매(BLUE 8.6,-1.5,8,11 / PURPLE 10.9,-2,6,12).
- 좌측 대형 넘버 150pt `#2C4E82` (1.0,2.15). PURPLE 언더바(1.15,3.9,0.9,0.09). 국문 제목 34pt WHITE(1.15,4.05). 영문 13pt `#9CB4D8`(1.18,4.85).
- 우측 포인트 3개: PURPLE OVAL 불릿(8.05,y) + WHITE 12.5pt(8.35,y), y=2.35+i*0.62.
- **★배점 배지 (우상단, 신규)**: (10.3,0.7,2.2,0.62) 라운드 rect, fill=섹션색(P3=PURPLE, 그 외 BLUE), WHITE Bold — 예 "기술제안 · 35점". P3만 PURPLE로 최다 배점 강조.
- 섹션별 넘버/제목/영문/포인트/배점:
| 섹션 | No | 제목 | EN | 배점배지 | 포인트 3 |
|---|---|---|---|---|---|
| P1 | 01 | 사업이해 | BUSINESS UNDERSTANDING | 사업이해·전략 10점(BLUE) | As-Is 병목 / 6역할 페르소나 / To-Be 정량목표 |
| P2 | 02 | 추진전략 | STRATEGY | Win Theme 5선(BLUE) | 3중 해자 / 생애주기 폐루프 / 추진체계 |
| P3 | 03 | 기술제안 | TECHNICAL SOLUTION | 기술제안 · 35점(PURPLE) | 나노바나나 시공예측 / 배치·설계·배선 AI / 옥션 폐루프 |
| P4 | 04 | 아키텍처·기술스택 | ARCHITECTURE | 아키텍처 10점(BLUE) | 확정 스택 4-Tier / 단일 공간원천 / AI 라우팅 |
| P5 | 05 | 보안·개인정보 | SECURITY & PRIVACY | 보안 10점(BLUE) | 워터마크 / PII 3중격리 / 망분리 |
| P6 | 06 | 비기능(NFR) | NON-FUNCTIONAL | 비기능 8점(BLUE) | SLO 99.5% / 렌더 40초 / 접근성·다국어 |
| P7 | 07 | 멀티테넌시·확장 | MULTI-TENANCY | 확장 5점(BLUE) | 격리 아키텍처 / 온보딩 6단계 / COEX 확장 |
| P8 | 08 | 사업관리·일정·조직 | PROJECT MANAGEMENT | 사업관리 2점(BLUE) | Phase 간트 / 선행게이트 / 품질관리 |
| P9 | 09 | 기대효과 | EXPECTED BENEFITS | 가점(PURPLE) | 정량 ROI / 정성효과 / 확장비전 |
---
## C. 필수 도식 12종
### ① 3중 해자 벤다이어그램 (S04 요약판 · S12 상세판) ★최중요
**컨셉**: 3원 60% 교집합. 중앙 교집합만 컬러 채도 최대(해자), 2원 교집합은 회색(모방 가능). "결합의 희소성"을 색 채도로 표현.
- **3원 배치**(정삼각 구도, 반경 r=1.9): 원A 중심(5.4,3.6) `PURPLE` stroke 2.5pt + PURPLE_LT 반투명 fill / 원B 중심(7.9,3.6) `BLUE` / 원C 중심(6.65,5.5) `TEAL`. OVAL 도형, fill은 반투명(alpha 22%)로 겹침 표현.
- **원 라벨**(각 원 바깥 가까이):
- 원A(좌상) `ai-sparkle` 아이콘 + "나노바나나 시공 예측 이미지" 13pt PURPLE Bold / 부제 "image-to-image 구조보존 · 40초 · S1~S7" 8.5pt MUTED.
- 원B(우상) `postgis-spatial` + "PostGIS 실측 공간데이터" 13pt BLUE Bold / "부스 폴리곤·트렌치·배선 LineString 단일 원천".
- 원C(하) `closed-loop`+`auction-gavel` + "공사 옥션 폐루프" 13pt TEAL Bold / "AI 자료→응찰→낙찰→발주 자동전환".
- **중앙 3원 교집합**(6.65,4.2 부근): 강조 배지 라운드 rect(INK fill) + WHITE Bold "우리만의 해자(Moat)" 11pt / 소자 "재현 난이도 높은 결합".
- **2원 교집합 3곳**: 소형 회색 라벨 "단일 요소는 모방 가능" 8pt SLATE.
- **하단 캡션**(중앙 정렬): "셋의 결합은 경쟁 전시테크·범용 SI가 재현하기 어렵다." 9.5pt MUTED.
- S04(요약판)=벤+한줄, S12(상세판)=벤 좌측 + 우측에 3결합 재현난이도 근거 3불릿 카드(strategy §3-3).
### ② 모듈맵 — 생애주기 × 우선순위 매트릭스 (S16)
- **매트릭스 프레임**: x=생애주기 6단계 열, y=우선순위 3행(P0/P1/P2). 콘텐츠 영역(0.55,1.7)~(12.78,6.2).
- **열 헤더**(y=1.7, 각 폭 ~1.95): "판매·기획" / "설계·시각화" / "발주·계약" / "참가·관람" / "현장운영" / "사후·경영". BLUE 밴드 WHITE 10pt Bold.
- **행 라벨**(좌측 x=0.55, 폭 0.9): P0(PURPLE)/P1(BLUE)/P2(SLATE) 세로 배지.
- **모듈 칩** 배치:
- **P0행**(굵은 테두리 2pt·PURPLE·강조): M2 플로어플랜(설계열) · M3 부스설계 · M4 유틸리티 · M5 나노바나나. 각 칩에 대응 아이콘(`booth-layout`/`camera-render`/`wiring-power`/`ai-sparkle`).
- **P1행**(BLUE 1pt): M1(판매)·M16(사후, 2회 span)·M6/M7(발주)·M15 옥션(발주·강조 테두리)·M10(참가)·M12·M17(참가/사후)·M18(전열 하단)·§5B·§1A.
- **P2행**(SLATE): M8·M11·M13·M14(현장운영열 집중).
- **하단 레이어 띠 2줄**(y~5.8): ①BLUE_LT 풀폭 "공통/시스템관리 레이어(§5B) — 전 모듈 선행 기반" ②PURPLE_LT "멀티테넌시(§1A) — tenant_id 격리".
- **캡션**: "18개 모듈 + 공통 레이어를 한 장으로 조망. P0 4개가 심장." 9.5pt.
### ③ 나노바나나 파이프라인 플로우 (S20) ★WT-1
**컨셉**: 좌→우 8박스 수평 플로우. 전면 비동기를 "동기 대기 없음" 배지로 각인. AI 구간(워커·Gemini)만 PURPLE, 나머지 BLUE.
- **박스 8개**(y=2.6, 높이 0.95, 폭 ~1.35, gap 0.06):
1. `[사용자: 렌더 요청]` BLUE_LT (아이콘 `mobile`)
2. `[Spring 백엔드: RenderJob 생성·쿼터 확인]` WHITE/BLUE
3. `[Redis 큐 leftPush]` WHITE/BLUE (아이콘 `pipeline-flow`)
4. `[나노바나나 Python 워커 (BLPOP)]` PURPLE_LT/PURPLE (아이콘 `ai-sparkle`)
5. `[Gemini image-to-image (G1 승인·라이브)]` PURPLE_LT/PURPLE
6. `[워터마크 후처리 · S6 로컬 PIL 래스터]` PURPLE_LT/PURPLE (아이콘 `watermark`)
7. `[오브젝트 스토리지 적재]` WHITE/BLUE
8. `[WebSocket/STOMP → 프론트 완료 푸시·갤러리 갱신]` BLUE_LT
- 박스 사이 "→" 15pt SLATE.
- **분기 주석**(워커 하단 점선 콜아웃, AMBER 테두리): "degraded: G1 미승인/Gemini 장애 시 목(mock) 응답 — 구조·워터마크 유지". 옆에 "성공 시에만 쿼터 차감" GREEN 소배지.
- **보안 배지**(우상단): `shield-security` + "GEMINI_API_KEY = 워커 env only (백엔드 미취급)" 9pt RED 테두리.
- **라이브 배지**: 박스4·5에 GREEN "라이브" 소배지("이미 돈다").
- **캡션**: "이미지 생성 전면 비동기 — 사용자 동기 대기 없음, 평균 40초." 9.5pt.
### ④ Before/After 슬라이더 (S21)
- **좌우 분할 이미지 카드**(2.0,2.0,9.3,3.3): 좌반부=빈 부스(HALL_BG + 도면 라인 오버레이), 우반부=시공 후(BLUE→PURPLE 그라디언트 + 나노바나나 이미지 자리).
- **중앙 세로 드래그 핸들**(x≈6.65): 세로 WHITE 라인 3pt + 중앙 원형 핸들(WHITE OVAL + `` 양방향 화살표).
- **라벨**: 좌상단 "Before: 빈 부스 (도면/실측)" 칩 SLATE / 우상단 "After: 시공 후 예상 (AI 생성)" 칩 PURPLE.
- **접근성 배지**(하단): `check-verify` + "ARIA slider · 키보드 좌우 조작 지원 (WCAG AA)" 9pt.
- **워터마크 고지 배지**(맨 하단, RED 테두리): "AI 생성 예상 이미지 — 실제 시공과 다를 수 있음. 계약·심사 서류 사용 금지." 8.5pt.
- **우측 배선 색상 범례 미니**(S21이 배선 오버레이 겸함): 전기=RED / 네트워크=CYAN / 급배수=TEAL 3줄(색 사각+텍스트+선스타일 실선/파선/점선).
### ⑤ 3안 비교 화면 목업 (S24)
- **상단 홀 정보 바**(0.55,1.6,12.23,0.5) BLUE_LT: "제1전시장 5홀 · 판매가능 10,080㎡ · 통로규정 v3" 좌 + `booth-layout` 아이콘.
- **3열 카드**(각 3.9 폭, gap 0.28, y 2.25, 높이 3.3):
- 카드 상단 헤더바: "안 1 — 통로 효율 최대"(BLUE) / "안 2 — 프리미엄 부스 최대"(PURPLE) / "안 3 — 균형"(TEAL).
- 본문: 미니 배치도 썸네일(HALL_BG + 부스 폴리곤 격자 도형) + 지표 3행 "판매면적 ㎡ / 부스 수 / 규정위반 0건"(위반0=GREEN 체크 `check-verify`).
- 안2는 프리미엄 강조(굵은 부스 몇 개), 안3은 균등 격자.
- **하단 액션 바**(0.55,5.75,12.23,0.6): 버튼칩 3개 "[안 선택]" BLUE / "[구역·블록 병합 편집]" WHITE 테두리 / "[병합 후 재검증]" PURPLE.
- **캡션**: "제약 솔버가 상이한 최적화 목표로 정확히 3안 생성. 사람이 선택·병합·최종 확정." (AI 신뢰 3층 프레이밍).
### ⑥ PostGIS 배선 오버레이 (S27) ★WT-2
**컨셉**: 홀 평면도 위 실측 배선. 3색 규약 고정 + 색맹 안전(선 스타일 병기).
- **평면도 캔버스**(0.55,1.7,8.6,4.6) HALL_BG 라운드:
- **부스 폴리곤** 다수: 연회색(`#D8DEE8`) 사각 격자 도형.
- **트렌치 포인트** ◆: TEAL 다이아 소도형, 라인 따라 배치.
- **분전반** ▣: INK 사각 + 라벨.
- **배선 LineString 3색**(부스→최근접 트렌치→분전반):
- 전기 = `RED` 실선 2pt
- 네트워크 = `CYAN` 파선(dash 6 3)
- 급배수 = `TEAL` 점선(dot 2 3)
- **가정 트렌치**엔 AMBER "가정 좌표(실측 대기)" 소배지.
- **우측 범례·주석 패널**(9.35,1.7,3.4,4.6) WHITE 카드:
- 제목 "배선 색상 범례" + 3색 행(색+선스타일+라벨).
- 주석 배지 3개: "최근접 트렌치 KNN (geom <-> point)" / "최단 경로 ST_Length · 통로 횡단 최소" / "SRID 0 홀 로컬 미터 · GiST 실시간".
- 아이콘 `wiring-power`·`postgis-spatial`.
- **캡션**: "수기 위치표시도 폐지 — 좌표 클릭 자동 작도."
### ⑦ 옥션 폐루프 흐름도 (S29) ★WT-3
**컨셉**: 좌측 AI 자료 패키지 스택 → 우측 순환 흐름. "AI 자료가 곧 발주 근거"를 좌→우 물리 연결로.
- **좌측 AI 자료 패키지 스택**(0.55,2.0,2.6,3.0) PURPLE_LT 카드, 4겹 쌓임 효과:
- "M2 배치 + M3 설계 + M4 배선/BOQ + M5 시공예측 이미지" 각 줄, 아이콘 `booth-layout`/`camera-render`. 상단 `ai-sparkle`.
- **우측 흐름 7박스**(순환 배열 또는 좌→우 2행 지그재그, y 2.0~5.5):
1. `[옥션 개설 (역경매/RFQ/고정가)]` BLUE (`auction-gavel`)
2. `[등록업체 검증 게이트: 미등록 응찰 차단 403]` RED 테두리 강조 (`shield-security`)
3. `[견적서 제출 · PDF · 버전]` WHITE/BLUE (`document-rtm`)
4. `[실시간 순위 · 라운드 마감 (Redis 정렬셋·WS 델타)]` BLUE (`dashboard-chart`)
5. `[종합평가 낙찰 (가격+평판+납기)]` PURPLE (`check-verify`)
6. `[Award → 계약/발주 자동전환 (M6·M9)]` TEAL
- 6→다시 자료로 순환 화살표(`closed-loop` 곡선 화살표).
- **게이트2 강조**: RED "미등록 = 응찰 원천 차단" 배지 — 킨텍스 규정 시스템 강제 포인트.
- **캡션**: "AI 설계자료가 곧 발주 근거. 사진 수준 자료로 응찰→발주까지 한 흐름."
### ⑧ 종합평가 스코어 비교표 (S31)
- **표**(0.55,2.0,12.23): 헤더 BLUE 풀폭 — 열: "업체명 | 견적금액(점수) | 평판(점수) | 납기(점수) | **가중 종합점수** | 순위".
- **행 4~5개**(zebra): 예 A사/B사/C사/D사. 각 셀 숫자 + 괄호 환산점수.
- **가중치 주석 바**(표 상단 또는 하단): "가중치(예): 가격 0.6 / 평판 0.25 / 납기 0.15 — 옥션별 설정형(weight jsonb)" 9pt MUTED.
- **1위 행 강조**: BLUE_LT 배경 + Bold + 우측 "낙찰(Award)" GREEN 배지 + `check-verify`.
- **우측 또는 하단 캡션 카드**: "선정 사유·항목별 비교 근거 자동 산출". 종합점수 열은 막대 미니바(가중합 시각화, BLUE 그라데이션).
- **캡션**: "가격만이 아니라 평판·납기 가중 — 투명·최적 발주."
### ⑨ 시스템 아키텍처 다이어그램 — 전 계층 (S41)
**컨셉**: 개발계획서 아키텍처 밴드도 계승·확장. 상하 6밴드 + 우측 외부 게이트 컬럼.
- **밴드**(좌측 라벨칩 1.55폭 + 본문, 폭 12.23):
1. **엣지/DMZ**(y~1.5): `[CDN·정적캐시]` `[WAF/Reverse Proxy(nginx)·TLS·레이트리밋]` BLUE_LT.
2. **역할별 프론트**(y~2.15): organizer·exhibitor·contractor·ops·admin(내부 배지 회색)·public/visitor(공개 SSR/SEO/다국어 배지). 6노드.
3. **SSO·RBAC**(y~2.8): `[JWT SSO + TOTP 2FA · 이중 RBAC(플랫폼/행사)]` 풀폭 (`shield-security`).
4. **공유 백엔드**(y~3.45, 높이 늘림): `[REST + WebSocket/STOMP]` 상단 + 하위 `[룰엔진]` `[배치·배선 엔진·PostGIS]` `[옥션 M15]` `[BI M16]` `[CMS M17]` `[공개 API read-only]`.
5. **비동기**(y~4.5): `[Redis 큐·순위·타이머·캐시·쿼터]``[나노바나나 워커]`(PURPLE) `[서류·EDM 워커]`.
6. **데이터**(y~5.15): `[PostgreSQL+PostGIS]` `[읽기복제(BI·공개)]` `[오브젝트 스토리지]`.
- **우측 외부 게이트 컬럼**(x~11.4 세로): `[Gemini(G1·라이브)]` `[Claude(AiTextRouter)]` `[PG 결제]` `[kxwp 릴레이]` `[등록업체 DB 739]`. 라이브=GREEN 배지.
- **연결 주석**(얇은 선+라벨): 워커→Gemini "egress only" / 워커→백엔드 "WebSocket 완료 푸시" / 백엔드→읽기복제 "BI/공개 오프로드".
- 계층별 색: 표현=BLUE_LT, 백엔드=WHITE/BLUE, AI밴드=PURPLE_LT, 데이터=HALL_BG.
### ⑩ 보안영역 분리도 (망 구성) (S47) ★WT-4
**컨셉**: 좌→우 신뢰도 상승 존. 철칙 배지를 굵게. `network-zone` 메타포.
- **존 박스 6개**(좌→우, 각 폭 ~2.0, y 2.2 높이 3.0, 우로 갈수록 테두리 진하게):
1. `[인터넷]` SLATE
2. `[엣지: CDN·WAF·DDoS]` BLUE_LT
3. `[DMZ(공개): 공개LB·public SSR·visitor GW·공개 API·PG콜백 — 쓰기권한 없음]` BLUE
4. `[내부망(인증): 내부LB·SSO/2FA·내부 API·공유 백엔드]` BLUE_DK
5. `[AI 워커존(egress 제한): Redis·나노바나나·Ollama]` PURPLE
6. `[데이터존(최내곽): PostgreSQL+PostGIS·오브젝트·읽기복제 — 아웃바운드 전면 차단]` INK
- 존 사이 화살표 "HTTPS만" 라벨.
- **상단 관리존**(가로 띠): `[관리존: 배스천·관측성 — VPN/허용IP+2FA, 인터넷 미도달]`.
- **철칙 배지 3개**(하단, RED 굵은 테두리): "DMZ → 데이터존 직접 접근 절대 금지" / "데이터존 아웃바운드 0 (유출경로 제거)" / "admin = VPN/허용IP+2FA".
- **egress 컬럼**(우측): "승인 4목적지만 — Claude·Gemini·PG·SMTP / 그 외 DROP" (`shield-security`).
- **캡션**: "공개/내부 물리·논리 분리 · 데이터존 무egress로 유출경로 자체 제거."
### ⑪ 멀티테넌시 격리 아키텍처도 (S56)
**컨셉**: 수직 4층 방어 스택. fail-closed를 "한 층이라도 불일치 = 차단" 배지로.
- **좌측 진입**(1.0,2.0,3.2,1.4): `[요청: 서브도메인(kintex./coex.) + JWT 사용자 소속]``[테넌트 컨텍스트 해소: 불일치 시 거부]` (`tenant-building`).
- **중앙 4층 방어 스택**(위→아래, 각 폭 5.0, 높이 0.85, y 2.0~5.5):
1. `[①컨텍스트 필터]` BLUE_LT
2. `[②서비스 가드]` BLUE
3. `[③MyBatis 공통 인터셉터 (tenant_id 자동 주입)]` BLUE_DK
4. `[④PostGIS/쿼리: tenant_id 선두 복합 인덱스]` PURPLE
- 층 사이 아래 화살표.
- **우측 데이터 표현**(9.3,2.0,3.4,3.5) WHITE 카드: `[TB_* … WHERE tenant_id = ctx]` 격리 도형 — KINTEX(#1)·COEX(#2) 두 색 파티션 시각화.
- **하단 배지**(RED): "fail-closed — 한 층이라도 불일치면 차단".
- **캡션**: "KINTEX #1 검증 → COEX #2 코드 배포 없이 데이터 온보딩. 기능 불변, 데이터·권한만 분리."
### ⑫ 일정 간트 (Phase × 모듈) (S58)
**컨셉**: 개발계획서 간트 계승. 3구간 + 라이브 실적(GREEN 완료)으로 "이미 돈다" 실증.
- **타임라인**(gx=3.55, gw=9.15, 3구간 Phase1/2/3 각 ~4M 눈금).
- **상태 범례**(우상단): 완료 GREEN / 진행 AMBER / 예정 SLATE.
- **워크스트림 막대 행**(좌측 Phase+워크스트림 라벨, 우측 막대):
- Phase1: 아키텍처(A·완료 GREEN) / 공통레이어 B 2FA·시스템관리(완료 GREEN) / P0 코어 M2·M3·M4·M5(진행 AMBER, 하위 "스캐폴드·인증·매퍼·룰엔진·나노바나나=완료 GREEN" / "3안·병합·실데이터=진행 AMBER").
- Phase2: 옥션 M15 / 관람 M10 / BI M16 / 관리자 M18 / 판매·서류·정산 M1·M6·M7·M9 / CMS M12·M17 (예정 SLATE).
- Phase3: 멀티테넌시 / M11·M13·M14 / 배포 안정화·SM 이관 (예정 SLATE, 배포 일부 진행 AMBER).
- **마일스톤 다이아몬드**(◆): G1(나노바나나 승인·완료 GREEN) · G2(배포서버·완료 GREEN) · Phase 게이트 · QA 게이트 · 오픈.
- **현재 시점 마커**: PURPLE 세로선 + "현재".
- **캡션**: "선행 게이트 통제 + Phase 게이트 QA 통과 후 진행. G1·G2 완료 — 실증 기반." (`schedule-gantt`).
---
## D. 표준 필수 도식 3종
### ⑬ 추진체계도 (조직도) (S15)
- **최상위**(중앙 4.9,1.5,3.55,0.72) BLUE: "프로젝트 총괄 (PM)" / "범위·일정·리스크·게이트(G1·G2)" (`org-people`).
- **2단**(연결선 아래): "개발 PM"(PURPLE) / "PMO"(PURPLE).
- **3단 아키텍트 그룹**(BLUE_LT 띠): AA·SA·TA·DA·NA 5노드(WHITE/BLUE) — "앱/시스템·NFR/기술표준/데이터·ERD/네트워크·보안".
- **4단 구현 그룹**(PURPLE_LT 띠): 공통/시스템·백엔드·프론트·DB·도메인(bidding·visitor·cms·bi·admin)·AI·시각화 6노드(WHITE/PURPLE).
- **하단**: 품질(kintex-qa · GREEN) / 배포(devops-dev · 딥블루).
- 개발계획서 조직도와 동일 구조 재사용 — 제안서용으로 "발주처 보고 라인" 소표기 추가 가능.
### ⑭ 리스크 매트릭스 (S59)
**컨셉**: 발생가능성 × 영향도 2축 4사분면 버블 + 게이트.
- **매트릭스**(2.5,1.8,6.5,4.5): x=발생가능성(낮→높), y=영향도(낮→높). 4사분면 배경 명도(우상=RED_LT 위험, 좌하=GREEN_LT 안전).
- **버블**(R-1~R-8 배치, tech_advisory/strategy §4):
- R-1(나노바나나 미승인) 고영향·중발생 → 우상 근처, AMBER.
- R-2(CAD·트렌치 미확보) 고영향·중발생 AMBER.
- R-3(kxwp 폐쇄 API) 중·중 SLATE.
- R-4(배점 가정) 중·중, R-5(간판 품질) 중·중, R-6(AI 환각) 고·저, R-7(HW 연동) 중·저, R-8(확장 부담) 저·저 GREEN.
- 버블 크기=영향도, 색=위험등급, 라벨 "R-n" + 툴팁 텍스트 소자.
- **우측 게이트·대응 패널**(9.3,1.8,3.4,4.5) WHITE 카드: "선행 게이트 G1(완료)·G2(완료)" + "각 리스크 방어 논리 1줄" 스크롤 리스트 + `check-verify`/`risk-warning`.
- **캡션**: "약한 고리를 선제 노출·방어 — 숨기지 않아 신뢰."
### ⑮ 기대효과 정량 인포그래픽 (S61)
- **5지표 Before/After 카드**(5열, 각 2.4 폭, y 1.85, 높이 1.85) — 개발계획서 기대효과 카드 계승:
| 지표 | As-Is(SLATE) | ↓(PURPLE) | To-Be(BLUE) |
|---|---|---|---|
| 홀 배정 견적 | 수일 | | 즉시 |
| 배치도 초안 | 수일~수주 | | 수 분 |
| 규정 검수 | 육안 검토 | | 자동 플래깅 |
| 유틸리티 신청 | 수기 작도 | | 클릭+자동견적 |
| 시공 결과 예측 | 불가(외주) | | 표준 샷 자동생성 |
- 각 카드: 지표명 Bold + As-Is(회색) + ↓ 화살표 PURPLE + To-Be(BLUE Bold) + 대응 아이콘(`document-rtm`/`booth-layout`/`check-verify`/`wiring-power`/`camera-render`).
- **하단 임팩트 지표 스트립**(대형 수치): "40초 렌더 · 99.5% SLO · 142 REQ 커버 · 18 모듈" BLUE/PURPLE 교차 KPI 카드.
- **캡션**: "리드타임 수일→즉시·수분, 검수 육안→자동. 정량 개선을 숫자로 각인."
---
## E. deck-designer 작도 체크리스트
1. **색 규율**: 퍼플=AI/나노바나나 전용. 배선 3색 고정(전기 RED 실선 / 네트워크 CYAN 파선 / 급배수 TEAL 점선) — 색+선스타일 이중 인코딩(색맹 안전).
2. **라이브 배지(GREEN "이미 돈다")**: 나노바나나 파이프라인(③)·간트(⑫)·아키텍처(⑨) 외부게이트에 반드시 표기 — tech_advisory §0 실증 우위.
3. **워터마크 고지**: Before/After(④)·AI 이미지 보안 슬라이드에 RED 테두리 고지 배지 필수(REQ-S-009).
4. **캡션 1줄**: 12종 각 도식 하단에 tech_advisory 지정 캡션 그대로. MUTED 9~9.5pt.
5. **아이콘**: `assets/icons/*.svg` 20종 — 헤더 앵커는 도형 근사, 콘텐츠 아이콘은 PNG 변환 후 stroke=맥락색. 이모지 금지.
6. **가독성**: 본문 10pt 하한, 한 슬라이드 1 도식/1 메시지, 여백 확보. 도식 박스 라벨 8~10pt.
7. **배점 규율 시각화**: 간지 배점 배지(P3=PURPLE 35점 최강조), 모듈맵 P0 굵은 테두리.
8. **보안**: 시크릿·내부IP·키값 도식·라벨 미기재. 외부 API는 "승인 예외/env only"로만.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -499,6 +499,140 @@ export function IconShieldCheck(p: IconProps) {
);
}
/** ⚡ 번개 — 빠른 작업·즉시 실행. */
export function IconBolt(p: IconProps) {
return (
<Svg {...p}>
<path d="M13 2L4 14H11L10 22L20 9H13L13 2Z" {...SRJ} />
</Svg>
);
}
/** ⧗ 활동·최근(시계). */
export function IconClock(p: IconProps) {
return (
<Svg {...p}>
<circle cx="12" cy="12" r="9" {...S} />
<path d="M12 7V12L15 14.5" {...SRJ} />
</Svg>
);
}
/** ✉ 메시지·쪽지(봉투). */
export function IconMail(p: IconProps) {
return (
<Svg {...p}>
<rect x="3" y="5" width="18" height="14" rx="2" {...S} />
<path d="M4 7L12 13L20 7" {...SRJ} />
</Svg>
);
}
/** 🗨 의견·댓글(말풍선). */
export function IconChat(p: IconProps) {
return (
<Svg {...p}>
<path d="M21 11.5C21 15.6 16.97 19 12 19C10.9 19 9.85 18.83 8.9 18.53L4 20L5.4 16.1C4.53 14.77 4 13.2 4 11.5C4 7.4 8.03 4 12 4C16.97 4 21 7.4 21 11.5Z" {...SRJ} />
</Svg>
);
}
/** ⇄ 승인·결재(체크리스트). */
export function IconApproval(p: IconProps) {
return (
<Svg {...p}>
<path d="M9 5H7C5.9 5 5 5.9 5 7V19C5 20.1 5.9 21 7 21H17C18.1 21 19 20.1 19 19V7C19 5.9 18.1 5 17 5H15" {...SRJ} />
<rect x="9" y="3" width="6" height="4" rx="1" {...SRJ} />
<path d="M9 13L11 15L15 11" {...SRJ} />
</Svg>
);
}
/** ✎ 업무일지·기록(펜). */
export function IconEdit(p: IconProps) {
return (
<Svg {...p}>
<path d="M4 20H8L18.5 9.5C19.3 8.7 19.3 7.4 18.5 6.6L17.4 5.5C16.6 4.7 15.3 4.7 14.5 5.5L4 16V20Z" {...SRJ} />
<path d="M13.5 6.5L17.5 10.5" {...SR} />
</Svg>
);
}
/** ⚑ 마일스톤·깃발. */
export function IconFlag(p: IconProps) {
return (
<Svg {...p}>
<path d="M5 21V4" {...SR} />
<path d="M5 4H18L15 8L18 12H5" {...SRJ} />
</Svg>
);
}
/** ⛟ 물류·트럭. */
export function IconTruck(p: IconProps) {
return (
<Svg {...p}>
<path d="M3 6C3 5.4 3.4 5 4 5H14C14.6 5 15 5.4 15 6V16H3V6Z" {...SRJ} />
<path d="M15 9H18.5L21 12V16H15V9Z" {...SRJ} />
<circle cx="7" cy="18" r="2" {...S} />
<circle cx="17" cy="18" r="2" {...S} />
</Svg>
);
}
/** ◈ 태그·라벨. */
export function IconTag(p: IconProps) {
return (
<Svg {...p}>
<path d="M3 3H11L21 13L13 21L3 11V3Z" {...SRJ} />
<circle cx="7.5" cy="7.5" r="1.4" fill="currentColor" />
</Svg>
);
}
/** ◫ 목록·리스트. */
export function IconList(p: IconProps) {
return (
<Svg {...p}>
<path d="M8 6H21" {...SR} />
<path d="M8 12H21" {...SR} />
<path d="M8 18H21" {...SR} />
<circle cx="4" cy="6" r="1" fill="currentColor" />
<circle cx="4" cy="12" r="1" fill="currentColor" />
<circle cx="4" cy="18" r="1" fill="currentColor" />
</Svg>
);
}
/** ◔ 파이·상태분포. */
export function IconPie(p: IconProps) {
return (
<Svg {...p}>
<path d="M12 3V12H21C21 7 17 3 12 3Z" {...SRJ} />
<circle cx="12" cy="12" r="9" {...S} />
</Svg>
);
}
/** ⚲ 방문·핀(위치). */
export function IconMapPin(p: IconProps) {
return (
<Svg {...p}>
<path d="M12 22C12 22 19 15.5 19 9.5C19 5.9 15.9 3 12 3C8.1 3 5 5.9 5 9.5C5 15.5 12 22 12 22Z" {...SRJ} />
<circle cx="12" cy="9.5" r="2.5" {...S} />
</Svg>
);
}
/** ★ 별(하이라이트·추천). */
export function IconStar(p: IconProps) {
return (
<Svg {...p}>
<path d="M12 3L14.7 8.5L20.5 9.3L16.3 13.4L17.3 19.2L12 16.5L6.7 19.2L7.7 13.4L3.5 9.3L9.3 8.5L12 3Z" {...SRJ} />
</Svg>
);
}
/** ● 오프라인(연결 끊김). */
export function IconWifiOff(p: IconProps) {
return (

View File

@ -13,6 +13,7 @@ import {
} from 'recharts';
import { CHART } from '../chartColors';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconClock, IconOperations } from '../../components/ui/icons';
import { adminApi } from '../../api/endpoints';
import { ApiRequestError } from '../../api/client';
import { StatusPill } from '../work/workShared';
@ -106,7 +107,7 @@ export function AdminDashboardPage() {
<div className="kx-admin__split">
<section className="kx-card kx-admin__chart-card" aria-label={t('admin.visitorTrend')}>
<div className="kx-card__head">
<h2>{t('admin.hourlyVisitors')}</h2>
<h2><IconClock className="kx-title-ic" size={18} />{t('admin.hourlyVisitors')}</h2>
</div>
<div className="kx-admin__chart">
{data.visitorTrend.length === 0 ? (
@ -130,7 +131,7 @@ export function AdminDashboardPage() {
<aside className="kx-admin__live" aria-label={t('admin.liveEvents')}>
<div className="kx-card__head">
<h2>{t('admin.liveEvents')}</h2>
<h2><IconOperations className="kx-title-ic" size={18} />{t('admin.liveEvents')}</h2>
</div>
{data.liveEvents.length === 0 ? (
<EmptyState title={t('admin.noLiveEvents')} />

View File

@ -5,6 +5,7 @@ import { ApiRequestError } from '../../api/client';
import type { LoginSlideDto } from '../../api/types';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconImage, IconPlus } from '../../components/ui/icons';
import './admin.css';
/**
@ -125,7 +126,7 @@ export function LoginSlideAdminPage() {
{/* 새 슬라이드 추가 */}
<section className="kx-card kx-slideadm__form-card" aria-label={t('slide.addAria')}>
<div className="kx-card__head">
<h2>{t('slide.addTitle')}</h2>
<h2><IconPlus className="kx-title-ic" size={18} />{t('slide.addTitle')}</h2>
</div>
<form className="kx-slideadm__form" onSubmit={handleCreate}>
<div className="kx-slideadm__form-grid">
@ -190,6 +191,7 @@ export function LoginSlideAdminPage() {
<section className="kx-card kx-slideadm__list-card" aria-label={t('slide.listAria')}>
<div className="kx-card__head">
<h2>
<IconImage className="kx-title-ic" size={18} />
{t('slide.registered')}{' '}
{slides && (
<span className="kx-slideadm__count tnum">

View File

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from '../../components/ui/Button';
import { AiLabel } from '../../components/ui/Badge';
import { EmptyState } from '../../components/ui/States';
import { IconAiBot } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { aiConsoleApi } from './aiConsoleApi';
import type { NlQueryResult, RulesChatResult } from './aiConsoleApi';
@ -26,7 +27,7 @@ export function AiConsolePanel() {
<section className="kx-card kx-aicon" aria-label={t('ai.console.title')}>
<div className="kx-card__head">
<div>
<h2>{t('ai.console.title')}</h2>
<h2><IconAiBot className="kx-title-ic" size={18} />{t('ai.console.title')}</h2>
<p className="kx-card__hint">{t('ai.console.subtitle')}</p>
</div>
<AiLabel>{t('ai.console.badge')}</AiLabel>

View File

@ -13,6 +13,7 @@ import {
import { AiLabel } from '../../components/ui/Badge';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconAnalytics, IconPie, IconSettlement } from '../../components/ui/icons';
import { CHART } from '../chartColors';
import { analyticsApi } from '../../api/endpoints';
import { ApiRequestError } from '../../api/client';
@ -131,7 +132,7 @@ export function AnalyticsDashboardPage() {
<div className="kx-bi__split">
<section className="kx-card kx-bi__trend" aria-label={t('analytics.trendAria')}>
<div className="kx-card__head">
<h2>{t('analytics.trendTitle')}</h2>
<h2><IconAnalytics className="kx-title-ic" size={18} />{t('analytics.trendTitle')}</h2>
<AiLabel>{t('analytics.monthlyAgg')}</AiLabel>
</div>
<p className="kx-card__hint">{t('analytics.trendHint')}</p>
@ -167,7 +168,7 @@ export function AnalyticsDashboardPage() {
<section className="kx-card kx-bi__sectors" aria-label={t('analytics.sectorsAria')}>
<div className="kx-card__head">
<h2>{t('analytics.sectorsTitle')}</h2>
<h2><IconPie className="kx-title-ic" size={18} />{t('analytics.sectorsTitle')}</h2>
</div>
{data.sectors.length === 0 ? (
<EmptyState
@ -195,7 +196,7 @@ export function AnalyticsDashboardPage() {
{/* 성과 테이블(전폭) */}
<section className="kx-card kx-bi__table-card" aria-label={t('analytics.perfTable')}>
<div className="kx-card__head">
<h2>{isOperator ? t('analytics.pnlTitle') : t('analytics.exhibitorPerfTitle')}</h2>
<h2><IconSettlement className="kx-title-ic" size={18} />{isOperator ? t('analytics.pnlTitle') : t('analytics.exhibitorPerfTitle')}</h2>
{degraded && <span className="kx-bi__degraded">{t('analytics.sampleTag')}</span>}
</div>
<div className="kx-table-scroll">

View File

@ -14,6 +14,7 @@ import {
} from 'recharts';
import { AiLabel } from '../../components/ui/Badge';
import { EmptyState, Skeleton } from '../../components/ui/States';
import { IconAnalytics, IconGrid, IconSpark, IconTrendUp } from '../../components/ui/icons';
import { CHART } from '../chartColors';
import { ApiRequestError } from '../../api/client';
import {
@ -91,7 +92,7 @@ function OverviewBody({ data }: { data: AnalyticsOverview }) {
{/* 행사 간 매출 비교 */}
<section className="kx-card" aria-label={t('global.evComp')}>
<div className="kx-card__head">
<h2>{t('global.evComp')}</h2>
<h2><IconAnalytics className="kx-title-ic" size={18} />{t('global.evComp')}</h2>
<AiLabel>{t('global.top8')}</AiLabel>
</div>
<p className="kx-card__hint">{t('global.evCompHint')}</p>
@ -133,7 +134,7 @@ function OverviewBody({ data }: { data: AnalyticsOverview }) {
{/* 참가사 리텐션 */}
<section className="kx-card" aria-label={t('global.retention')}>
<div className="kx-card__head">
<h2>{t('global.retention')}</h2>
<h2><IconTrendUp className="kx-title-ic" size={18} />{t('global.retention')}</h2>
</div>
<p className="kx-card__hint">
{t('global.retentionCum')}{' '}
@ -163,7 +164,7 @@ function OverviewBody({ data }: { data: AnalyticsOverview }) {
{/* 홀 가동률 추이 */}
<section className="kx-card" aria-label={t('global.occupancy')}>
<div className="kx-card__head">
<h2>{t('global.occupancy')}</h2>
<h2><IconGrid className="kx-title-ic" size={18} />{t('global.occupancy')}</h2>
</div>
<p className="kx-card__hint">{t('global.occupancyHint')}</p>
{data.hallOccupancy.length === 0 ? (
@ -186,7 +187,7 @@ function OverviewBody({ data }: { data: AnalyticsOverview }) {
{/* 수요예측(추세) */}
<section className="kx-card" aria-label={t('global.forecast')}>
<div className="kx-card__head">
<h2>{t('global.forecastTitle')}</h2>
<h2><IconSpark className="kx-title-ic" size={18} />{t('global.forecastTitle')}</h2>
<AiLabel>{t('global.trendProjection')}</AiLabel>
</div>
<p className="kx-card__hint">{t('global.forecastHint')}</p>

View File

@ -10,7 +10,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Button } from '../../components/ui/Button';
import { AiLabel } from '../../components/ui/Badge';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconAiBot, IconCheckCircle, IconDownload, IconWarning } from '../../components/ui/icons';
import { IconAiBot, IconCheckCircle, IconDownload, IconList, IconWarning } from '../../components/ui/icons';
import { auctionApi, formatWon, type Quote } from './auctionApi';
import { errMessage, useToast } from './aucShared';
import './auction.css';
@ -133,7 +133,7 @@ export function AwardComparePage() {
{/* 비교 매트릭스 */}
<section className="kx-card" aria-label="견적 비교표">
<div className="kx-card__head">
<h2> </h2>
<h2><IconList className="kx-title-ic" size={18} /> </h2>
<AiLabel>AI </AiLabel>
</div>
<div className="kx-table-scroll">
@ -229,7 +229,7 @@ export function AwardComparePage() {
{/* AI 리스크 분석 */}
<section className="kx-card" aria-label="AI 리스크 분석">
<div className="kx-card__head">
<h2>AI · </h2>
<h2><IconAiBot className="kx-title-ic" size={18} />AI · </h2>
<AiLabel>AI </AiLabel>
</div>
<div className="kx-risk">

View File

@ -10,9 +10,12 @@ import { Button } from '../../components/ui/Button';
import { DdayChip } from '../../components/ui/Badge';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import {
IconBell,
IconCheckCircle,
IconDocument,
IconGrid,
IconList,
IconTag,
IconUsers,
IconWarning,
} from '../../components/ui/icons';
@ -78,7 +81,7 @@ export function ContractorBoothDashboardPage() {
{/* 좌 — 수주 부스 리스트 */}
<section className="kx-cbd__col" aria-label="수주 부스 리스트">
<div className="kx-card__head" style={{ marginBottom: 0 }}>
<h2 style={{ fontSize: 'var(--fs-h2)' }}> </h2>
<h2 style={{ fontSize: 'var(--fs-h2)' }}><IconList className="kx-title-ic" size={18} /> </h2>
</div>
{data.awardedBooths.length === 0 ? (
@ -101,7 +104,7 @@ export function ContractorBoothDashboardPage() {
<aside className="kx-cbd__col" aria-label="옥션·알림 위젯">
<div className="kx-cbd-widget">
<div className="kx-card__head">
<h2 style={{ fontSize: 'var(--fs-h3)' }}> </h2>
<h2 style={{ fontSize: 'var(--fs-h3)' }}><IconTag className="kx-title-ic" size={18} /> </h2>
</div>
<div className="kx-cbd-widget__rows">
{data.myAuctionBids.length === 0 ? (
@ -130,7 +133,7 @@ export function ContractorBoothDashboardPage() {
<div className="kx-cbd-widget">
<div className="kx-card__head">
<h2 style={{ fontSize: 'var(--fs-h3)' }}> </h2>
<h2 style={{ fontSize: 'var(--fs-h3)' }}><IconBell className="kx-title-ic" size={18} /> </h2>
</div>
<div className="kx-feed">
{data.feed.length === 0 ? (

View File

@ -5,7 +5,7 @@ import { useAuthStore } from '../../store/authStore';
import { AiLabel } from '../../components/ui/Badge';
import { AiImage } from '../../components/ui/AiImage';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconCheck } from '../../components/ui/icons';
import { IconBell, IconCheck, IconExhibitors, IconImage } from '../../components/ui/icons';
import { dashboardApi } from '../../api/endpoints';
import { ApiRequestError } from '../../api/client';
import type { DashboardData } from '../../api/types';
@ -133,7 +133,7 @@ export function OrganizerDashboardPage() {
<div className="kx-dash__split">
<section className="kx-dash__table-wrap" aria-label={t('dashboard.exhibitorStatus')}>
<div className="kx-dash__section-head">
<h2>{t('dashboard.exhibitorStatus')}</h2>
<h2><IconExhibitors className="kx-title-ic" size={18} />{t('dashboard.exhibitorStatus')}</h2>
{degraded && <span className="kx-dash__degraded">{t('dashboard.sampleTag')}</span>}
</div>
{data.exhibitors.length === 0 ? (
@ -167,7 +167,7 @@ export function OrganizerDashboardPage() {
<aside className="kx-dash__aside">
<section className="kx-feed" aria-label={t('dashboard.feedAria')}>
<h2 className="kx-feed__title">{t('dashboard.feedTitle')}</h2>
<h2 className="kx-feed__title"><IconBell className="kx-title-ic" size={18} />{t('dashboard.feedTitle')}</h2>
{data.feed.length === 0 ? (
<EmptyState title={t('dashboard.feedEmpty')} />
) : (
@ -185,7 +185,7 @@ export function OrganizerDashboardPage() {
<section className="kx-dash__s7" aria-label={t('dashboard.aerialAria')}>
<div className="kx-dash__s7-head">
<h2>{t('dashboard.aerialTitle')}</h2>
<h2><IconImage className="kx-title-ic" size={18} />{t('dashboard.aerialTitle')}</h2>
<AiLabel>S7</AiLabel>
</div>
<AiImage

View File

@ -186,7 +186,7 @@ export function DocsMilestonePage({ onOpenAuthoring }: { onOpenAuthoring?: (docT
<section className="kx-card kx-doc__list" aria-label="신고서류 체크리스트">
<div className="kx-doc__list-head">
<div>
<h2> </h2>
<h2><IconDocument className="kx-title-ic" size={18} /> </h2>
<p> </p>
</div>
<ChecklistProgress docs={docs} />

View File

@ -11,7 +11,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { AiLabel } from '../../components/ui/Badge';
import { Button } from '../../components/ui/Button';
import { ErrorState, Skeleton } from '../../components/ui/States';
import { IconChevronDown, IconSpark, IconWarning } from '../../components/ui/icons';
import { IconChevronDown, IconDocument, IconSpark, IconWarning } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { docsApi, type DocAction } from './docsApi';
@ -389,7 +389,7 @@ export function ReportAuthoringPage({ docType = '재해대처계획서' }: { doc
<div className="kx-rep__preview" aria-label="문서 미리보기">
<article className="kx-rep__a4">
<div className="kx-rep__a4-title">
<h2>{docType}</h2>
<h2><IconDocument className="kx-title-ic" size={18} />{docType}</h2>
</div>
<table className="kx-rep__table">
<tbody>

View File

@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { Offcanvas } from '../../components/ui/Offcanvas';
import { IconWarning } from '../../components/ui/icons';
import { IconGrid, IconSettlement, IconWarning } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import {
hallAssignApi,
@ -204,7 +204,7 @@ export function HallAssignmentPage() {
{/* ── 배정 보드 ── */}
<section className="kx-card">
<div className="kx-card__head">
<h2>{t('hallassign.board')}</h2>
<h2><IconGrid className="kx-title-ic" size={18} />{t('hallassign.board')}</h2>
<span className="kx-card__hint">{t('hallassign.boardHint')}</span>
</div>
{assignQ.isLoading || hallsQ.isLoading ? (
@ -519,7 +519,7 @@ function QuotePanel({ halls }: { halls: Hall[] }) {
return (
<section className="kx-card">
<div className="kx-card__head">
<h2>{t('hallassign.autoQuote')}</h2>
<h2><IconSettlement className="kx-title-ic" size={18} />{t('hallassign.autoQuote')}</h2>
<span className="kx-card__hint">{t('hallassign.quoteHint')}</span>
</div>
<div className="kx-hall__quoteform">

View File

@ -12,12 +12,16 @@ import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import {
IconAiBot,
IconBell,
IconBolt,
IconCalendar,
IconCheckCircle,
IconChevronLeft,
IconChevronRight,
IconClock,
IconDocument,
IconExhibitors,
IconFloorplan,
IconGrid,
IconSearch,
IconSettlement,
IconSpark,
@ -479,7 +483,10 @@ function ShowcaseRail() {
return (
<section className="kx-home__showcase" aria-label={t('home.showcase.title')}>
<div className="kx-home__section-head">
<h2 className="kx-home__section-title">{t('home.showcase.title')}</h2>
<h2 className="kx-home__section-title">
<IconGrid className="kx-title-ic" size={18} />
{t('home.showcase.title')}
</h2>
<div className="kx-seg" role="tablist" aria-label={t('home.showcase.title')}>
{SHOWCASE_TABS.map((k) => (
<button
@ -687,7 +694,10 @@ function HomeCalendar() {
>
<IconChevronLeft size={16} />
</button>
<strong className="kx-home__cal-title tnum">{monthTitle}</strong>
<strong className="kx-home__cal-title tnum">
<IconCalendar className="kx-title-ic" size={16} />
{monthTitle}
</strong>
<button
type="button"
className="kx-home__cal-nav"
@ -812,7 +822,10 @@ function HomeCalendar() {
{/* 진행 중·예정 전시 리스트 */}
<div className="kx-home__cal-list">
<h3 className="kx-home__cal-list-title">{t('home.calendar.listTitle')}</h3>
<h3 className="kx-home__cal-list-title">
<IconGrid className="kx-title-ic" size={16} />
{t('home.calendar.listTitle')}
</h3>
{listed.length === 0 ? (
<EmptyState
title={t('home.calendar.listEmpty')}
@ -928,7 +941,10 @@ function TodoWidget() {
return (
<section className="kx-card kx-home__widget" aria-label={t('home.todo.title')}>
<div className="kx-home__widget-head">
<h2>{t('home.todo.title')}</h2>
<h2>
<IconCheckCircle className="kx-title-ic" size={18} />
{t('home.todo.title')}
</h2>
{data && data.total > 0 && <span className="kx-home__count-badge tnum">{data.total}</span>}
</div>
@ -1007,7 +1023,10 @@ function NoticeWidget() {
return (
<section className="kx-card kx-home__widget" aria-label={t('home.notice.title')}>
<div className="kx-home__widget-head">
<h2>{t('home.notice.title')}</h2>
<h2>
<IconBell className="kx-title-ic" size={18} />
{t('home.notice.title')}
</h2>
</div>
{query.isLoading ? (
<div className="kx-home__widget-body">
@ -1065,7 +1084,10 @@ function QuickActions() {
return (
<section className="kx-card kx-home__widget" aria-label={t('home.quick.title')}>
<div className="kx-home__widget-head">
<h2>{t('home.quick.title')}</h2>
<h2>
<IconBolt className="kx-title-ic" size={18} />
{t('home.quick.title')}
</h2>
</div>
<div className="kx-home__quick-grid">
{actions.map((a) => (
@ -1107,7 +1129,10 @@ function ActivityWidget() {
return (
<section className="kx-card kx-home__widget" aria-label={t('home.activity.title')}>
<div className="kx-home__widget-head">
<h2>{t('home.activity.title')}</h2>
<h2>
<IconClock className="kx-title-ic" size={18} />
{t('home.activity.title')}
</h2>
</div>
{query.isLoading ? (
<div className="kx-home__widget-body">
@ -1166,7 +1191,10 @@ function WorkspaceGrid() {
return (
<section className="kx-home__workspace" aria-label={t('home.workspace.title')}>
<div className="kx-home__section-head">
<h2 className="kx-home__section-title">{t('home.workspace.title')}</h2>
<h2 className="kx-home__section-title">
<IconGrid className="kx-title-ic" size={18} />
{t('home.workspace.title')}
</h2>
</div>
{workspaces.length === 0 ? (
<div className="kx-card kx-home__ws-empty">

View File

@ -4,7 +4,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { AiLabel } from '../../components/ui/Badge';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconImage, IconPlus, IconSpark } from '../../components/ui/icons';
import { IconImage, IconMail, IconPlus, IconSpark } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { CAMPAIGN_STATUS_LABEL, CAMPAIGNS, SEGMENTS, type CampaignStatus } from './sampleMarketing';
@ -201,7 +201,7 @@ export function EdmCampaignPage() {
<aside className="kx-mkt__builder" aria-label="캠페인 빌더">
<div className="kx-card">
<div className="kx-card__head">
<h2> </h2>
<h2><IconMail className="kx-title-ic" size={18} /> </h2>
<AiLabel>AI </AiLabel>
</div>

View File

@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { Button } from '../../components/ui/Button';
import { DdayChip } from '../../components/ui/Badge';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconArrowRight, IconCheck, IconPlus } from '../../components/ui/icons';
import { IconArrowRight, IconCheck, IconCheckCircle, IconPlus, IconStar } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import {
@ -116,7 +116,7 @@ export function SponsorshipPage() {
<aside className="kx-mkt__sponsor-col" aria-label="스폰서 관리">
<div className="kx-card">
<div className="kx-card__head">
<h2> </h2>
<h2><IconStar className="kx-title-ic" size={18} /> </h2>
</div>
<ul className="kx-mkt__sponsors">
{sponsors.map((s) => (
@ -147,7 +147,7 @@ export function SponsorshipPage() {
{active && (
<div className="kx-card">
<div className="kx-card__head">
<h2>{active.name} </h2>
<h2><IconCheckCircle className="kx-title-ic" size={18} />{active.name} </h2>
</div>
<ul className="kx-mkt__fulfillment">
{active.fulfillment.map((f) => (

View File

@ -11,7 +11,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { AiLabel } from '../../components/ui/Badge';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconSpark } from '../../components/ui/icons';
import { IconSpark, IconTruck } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { logisticsApi, type Dock, type GridReservation } from './logisticsApi';
@ -274,7 +274,7 @@ function DockScheduleTab() {
{/* 우: 예약 폼 + AI */}
<aside className="kx-dock__aside">
<section className="kx-card">
<span className="kx-dock__panel-title"> </span>
<span className="kx-dock__panel-title"><IconTruck className="kx-title-ic" size={18} /> </span>
<div className="kx-dock__form">
{selected && (
<p className="kx-dock__slot-note">

View File

@ -7,6 +7,7 @@ import { useEffect, useMemo, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconList, IconTag, IconTruck } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import {
logisticsExtApi,
@ -105,7 +106,7 @@ export function EquipmentTab({ eventId }: { eventId: string | null }) {
return (
<div className="kx-logi__split">
<section className="kx-card">
<span className="kx-dock__panel-title">{t('logistics.equipment.formTitle')}</span>
<span className="kx-dock__panel-title"><IconTruck className="kx-title-ic" size={18} />{t('logistics.equipment.formTitle')}</span>
<div className="kx-dock__form">
<label className="kx-dock__field">
<span className="kx-dock__label">{t('logistics.equipment.item')}</span>
@ -148,7 +149,7 @@ export function EquipmentTab({ eventId }: { eventId: string | null }) {
</section>
<section className="kx-card kx-logi__list">
<span className="kx-dock__panel-title">{t('logistics.equipment.listTitle')}</span>
<span className="kx-dock__panel-title"><IconList className="kx-title-ic" size={18} />{t('logistics.equipment.listTitle')}</span>
{rows.length === 0 ? (
<EmptyState title={t('logistics.equipment.empty')} description={t('logistics.equipment.emptyDesc')} />
) : (
@ -256,7 +257,7 @@ export function RentalTab({ eventId }: { eventId: string | null }) {
return (
<div className="kx-logi__split">
<section className="kx-card">
<span className="kx-dock__panel-title">{t('logistics.rental.formTitle')}</span>
<span className="kx-dock__panel-title"><IconTag className="kx-title-ic" size={18} />{t('logistics.rental.formTitle')}</span>
<div className="kx-dock__form">
<label className="kx-dock__field">
<span className="kx-dock__label">{t('logistics.rental.item')}</span>
@ -299,7 +300,7 @@ export function RentalTab({ eventId }: { eventId: string | null }) {
</section>
<section className="kx-card kx-logi__list">
<span className="kx-dock__panel-title">{t('logistics.rental.listTitle')}</span>
<span className="kx-dock__panel-title"><IconList className="kx-title-ic" size={18} />{t('logistics.rental.listTitle')}</span>
{rows.length === 0 ? (
<EmptyState title={t('logistics.rental.empty')} description={t('logistics.rental.emptyDesc')} />
) : (
@ -402,7 +403,7 @@ export function InboundTab({ eventId }: { eventId: string | null }) {
return (
<div className="kx-logi__split">
<section className="kx-card">
<span className="kx-dock__panel-title">{t('logistics.inbound.formTitle')}</span>
<span className="kx-dock__panel-title"><IconTruck className="kx-title-ic" size={18} />{t('logistics.inbound.formTitle')}</span>
<div className="kx-dock__form">
<label className="kx-dock__field">
<span className="kx-dock__label">{t('logistics.inbound.trackingNo')}</span>
@ -440,7 +441,7 @@ export function InboundTab({ eventId }: { eventId: string | null }) {
</section>
<section className="kx-card kx-logi__list">
<span className="kx-dock__panel-title">{t('logistics.inbound.listTitle')}</span>
<span className="kx-dock__panel-title"><IconList className="kx-title-ic" size={18} />{t('logistics.inbound.listTitle')}</span>
{rows.length === 0 ? (
<EmptyState title={t('logistics.inbound.empty')} description={t('logistics.inbound.emptyDesc')} />
) : (

View File

@ -2,6 +2,7 @@ import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Trans, useTranslation } from 'react-i18next';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconBolt, IconClock, IconGrid, IconOperations } from '../../components/ui/icons';
import { opsApiLocal, type OpsDataExt } from './opsApi';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
@ -87,7 +88,7 @@ export function HallOperationsPage() {
{/* 히어로: 홀 혼잡(체크인 파생) */}
<section className="kx-card kx-ops__heat" aria-label={t('ops.hallStatus')}>
<div className="kx-card__head">
<h2>{t('ops.hallStatus')}</h2>
<h2><IconGrid className="kx-title-ic" size={18} />{t('ops.hallStatus')}</h2>
</div>
<span className="kx-ops__sensor-badge">
{hasPeople ? t('ops.checkinDerived') : t('ops.sensorNoneHalls')}
@ -130,7 +131,7 @@ export function HallOperationsPage() {
<aside className="kx-ops__aside">
<section className="kx-card" aria-label={t('ops.powerLoad')}>
<div className="kx-card__head">
<h2>{t('ops.powerLoad')}</h2>
<h2><IconBolt className="kx-title-ic" size={18} />{t('ops.powerLoad')}</h2>
{powerTotal != null && (
<span className="kx-ops__total"><Trans i18nKey="ops.totalKw" values={{ v: formatKw(powerTotal) }} components={[<strong className="tnum" key="s" />]} /></span>
)}
@ -161,7 +162,7 @@ export function HallOperationsPage() {
<section className="kx-card" aria-label={t('ops.hourlyCheckin')}>
<div className="kx-card__head">
<h2>{t('ops.hourlyCheckin')}</h2>
<h2><IconClock className="kx-title-ic" size={18} />{t('ops.hourlyCheckin')}</h2>
</div>
<span className="kx-ops__sensor-badge">{t('ops.hourlyAgg')}</span>
{series.length === 0 ? (
@ -190,7 +191,7 @@ export function HallOperationsPage() {
{/* 하단: 시설 센서(미연동) — 정직한 빈 상태 */}
<section className="kx-card kx-ops__parking" aria-label={t('ops.facilitySensor')}>
<div className="kx-card__head">
<h2>{t('ops.facilitySensorTitle')}</h2>
<h2><IconOperations className="kx-title-ic" size={18} />{t('ops.facilitySensorTitle')}</h2>
</div>
{!hasHvac && data.lighting.length === 0 && data.parking.length === 0 ? (
<EmptyState

View File

@ -14,6 +14,8 @@ import {
IconInfo,
IconCheck,
IconArrowRight,
IconBuyer,
IconTicket,
} from './publicIcons';
const REG_STEPS = ['apply', 'review', 'register', 'eligible'];
@ -62,7 +64,7 @@ export function AgencyTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head">
<div>
<h2 className="kxp-section__title">{t('track.agency.registryTitle')}</h2>
<h2 className="kxp-section__title"><IconBuyer className="kx-title-ic" width={22} height={22} />{t('track.agency.registryTitle')}</h2>
<p className="kxp-section__sub">{t('track.agency.registrySub')}</p>
</div>
</div>
@ -99,7 +101,7 @@ export function AgencyTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head">
<div>
<h2 className="kxp-section__title">{t('track.agency.auctionTitle')}</h2>
<h2 className="kxp-section__title"><IconTicket className="kx-title-ic" width={22} height={22} />{t('track.agency.auctionTitle')}</h2>
<p className="kxp-section__sub">{t('track.agency.auctionSub')}</p>
</div>
</div>
@ -128,7 +130,7 @@ export function AgencyTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head">
<div>
<h2 className="kxp-section__title">{t('track.agency.safetyTitle')}</h2>
<h2 className="kxp-section__title"><IconShield className="kx-title-ic" width={22} height={22} />{t('track.agency.safetyTitle')}</h2>
<p className="kxp-section__sub">{t('track.agency.safetySub')}</p>
</div>
</div>

View File

@ -10,7 +10,7 @@ import { AiLabel } from '../../components/ui/Badge';
import { publicApi, type MonthlySummary, type PublicCalendarEvent } from './publicApi';
import { formatRange, dday, errorMessage } from './publicFormat';
import { ddayClass } from './trackShared';
import { IconChevronRight, IconCalendar, IconPin } from './publicIcons';
import { IconChevronRight, IconCalendar, IconPin, IconSparkles } from './publicIcons';
type View = 'year' | 'month';
const NOW = new Date();
@ -89,7 +89,7 @@ export function AiPlanningBriefing() {
<div className="kxp-wrap">
<div className="kxp-section__head kxp-briefing__head">
<div>
<h2 className="kxp-section__title">{t('track.visitor.briefing.title')}</h2>
<h2 className="kxp-section__title"><IconSparkles className="kx-title-ic" width={22} height={22} />{t('track.visitor.briefing.title')}</h2>
<p className="kxp-section__sub">{t('track.visitor.briefing.sub')}</p>
</div>
<div className="kxp-briefing__toggle" role="tablist" aria-label={t('track.visitor.briefing.title')}>

View File

@ -11,8 +11,10 @@ import {
IconVenue,
IconBuyer,
IconCheck,
IconCheckCircle,
IconSparkles,
IconArrowRight,
IconTicket,
} from './publicIcons';
/** 주최자·참가 가치 항목. ai=true 면 보라색 AI 칩 표기. */
@ -74,7 +76,7 @@ export function BusinessTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head">
<div>
<h2 className="kxp-section__title">{t('track.business.valueTitle')}</h2>
<h2 className="kxp-section__title"><IconCheckCircle className="kx-title-ic" width={22} height={22} />{t('track.business.valueTitle')}</h2>
<p className="kxp-section__sub">{t('track.business.valueSub')}</p>
</div>
</div>
@ -116,7 +118,7 @@ export function BusinessTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head">
<div>
<h2 className="kxp-section__title">{t('track.business.processTitle')}</h2>
<h2 className="kxp-section__title"><IconArrowRight className="kx-title-ic" width={22} height={22} />{t('track.business.processTitle')}</h2>
<p className="kxp-section__sub">{t('track.business.processSub')}</p>
</div>
</div>
@ -137,7 +139,7 @@ export function BusinessTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head">
<div>
<h2 className="kxp-section__title">{t('track.business.rateTitle')}</h2>
<h2 className="kxp-section__title"><IconTicket className="kx-title-ic" width={22} height={22} />{t('track.business.rateTitle')}</h2>
<p className="kxp-section__sub">{t('track.business.rateSub')}</p>
</div>
</div>

View File

@ -37,6 +37,7 @@ import {
IconCheckCircle,
IconTicket,
IconVenue,
IconBell,
} from './publicIcons';
import type { ComponentType, SVGProps } from 'react';
@ -204,7 +205,7 @@ export function VisitorTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head pub-reveal">
<div>
<h2 className="kxp-section__title">{t('track.visitor.aiTiles.title')}</h2>
<h2 className="kxp-section__title"><IconSparkles className="kx-title-ic" width={22} height={22} />{t('track.visitor.aiTiles.title')}</h2>
<p className="kxp-section__sub">{t('track.visitor.aiTiles.sub')}</p>
</div>
</div>
@ -237,7 +238,7 @@ export function VisitorTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head pub-reveal">
<div>
<h2 className="kxp-section__title">{t('track.visitor.showcaseTitle')}</h2>
<h2 className="kxp-section__title"><IconVenue className="kx-title-ic" width={22} height={22} />{t('track.visitor.showcaseTitle')}</h2>
<p className="kxp-section__sub">{t('track.visitor.showcaseSub')}</p>
</div>
</div>
@ -251,6 +252,7 @@ export function VisitorTrackPage() {
<div className="kxp-section__head pub-reveal">
<div>
<h2 className="kxp-section__title">
<IconTarget className="kx-title-ic" width={22} height={22} />
{t('track.visitor.recommendTitle')}{' '}
<span className="kxp-inlineai">
<AiLabel>{t('track.visitor.recommendTag')}</AiLabel>
@ -281,7 +283,7 @@ export function VisitorTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head pub-reveal">
<div>
<h2 className="kxp-section__title">{t('track.visitor.guideTitle')}</h2>
<h2 className="kxp-section__title"><IconMap className="kx-title-ic" width={22} height={22} />{t('track.visitor.guideTitle')}</h2>
<p className="kxp-section__sub">{t('track.visitor.guideSub')}</p>
</div>
</div>
@ -349,7 +351,7 @@ export function VisitorTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head pub-reveal">
<div>
<h2 className="kxp-section__title">{t('track.visitor.journeyTitle')}</h2>
<h2 className="kxp-section__title"><IconArrowRight className="kx-title-ic" width={22} height={22} />{t('track.visitor.journeyTitle')}</h2>
<p className="kxp-section__sub">{t('track.visitor.journeySub')}</p>
</div>
</div>
@ -386,7 +388,7 @@ export function VisitorTrackPage() {
<div className="kxp-wrap">
<div className="kxp-section__head pub-reveal">
<div>
<h2 className="kxp-section__title">{t('track.visitor.noticeTitle')}</h2>
<h2 className="kxp-section__title"><IconBell className="kx-title-ic" width={22} height={22} />{t('track.visitor.noticeTitle')}</h2>
<p className="kxp-section__sub">{t('track.visitor.noticeSub')}</p>
</div>
</div>
@ -426,7 +428,7 @@ export function VisitorTrackPage() {
<IconVenue width={28} height={28} />
</span>
<div>
<h2 className="kxp-section__title">{t('track.visitor.venueTitle')}</h2>
<h2 className="kxp-section__title"><IconVenue className="kx-title-ic" width={22} height={22} />{t('track.visitor.venueTitle')}</h2>
<p className="kxp-section__sub">{t('track.visitor.venueSub')}</p>
</div>
</div>

View File

@ -445,7 +445,7 @@ function HallAvailability({ cursor }: { cursor: Date }) {
return (
<section className="kx-card" aria-label={t('schedule.availTitle')}>
<div className="kx-card__head">
<h2>{t('schedule.availMonth', { y: year, m: month + 1 })}</h2>
<h2><IconCalendar className="kx-title-ic" size={18} />{t('schedule.availMonth', { y: year, m: month + 1 })}</h2>
{data?.degraded && <span className="kx-bi__degraded">{t('schedule.offlineSample')}</span>}
</div>
{query.isLoading ? (
@ -501,7 +501,7 @@ function HallUtilization({ cursor }: { cursor: Date }) {
return (
<section className="kx-card" aria-label={t('schedule.hallUtil')}>
<div className="kx-card__head">
<h2>{t('schedule.hallUtilMonth', { y: year, m: month + 1 })}</h2>
<h2><IconGrid className="kx-title-ic" size={18} />{t('schedule.hallUtilMonth', { y: year, m: month + 1 })}</h2>
{degraded && <span className="kx-bi__degraded">{t('schedule.offlineSample')}</span>}
</div>
<ul className="kx-barlist">

View File

@ -5,6 +5,7 @@ import type { TFunction } from 'i18next';
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconAnalytics, IconDocument, IconPie } from '../../components/ui/icons';
import { CHART } from '../chartColors';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { useAuthStore } from '../../store/authStore';
@ -136,7 +137,7 @@ export function SettlementDashboardPage() {
{/* 상태 분포 도넛 + 범례 */}
<section className="kx-card kx-settle__dist" aria-label={t('settlement.distTitle')}>
<div className="kx-card__head">
<h2>{t('settlement.distTitle')}</h2>
<h2><IconPie className="kx-title-ic" size={18} />{t('settlement.distTitle')}</h2>
</div>
<StatusDistribution buckets={dash.statusDistribution} />
<p className="kx-settle__paynote">{dash.paymentNote}</p>
@ -147,7 +148,7 @@ export function SettlementDashboardPage() {
{/* 청구서 목록 */}
<section className="kx-card kx-settle__list-card" aria-label={t('settlement.listTitle')}>
<div className="kx-card__head kx-settle__list-head">
<h2>{t('settlement.invoices')}</h2>
<h2><IconDocument className="kx-title-ic" size={18} />{t('settlement.invoices')}</h2>
<div className="kx-seg" role="tablist" aria-label={t('settlement.statusFilter')}>
{STATUS_FILTER_VALUES.map((v) => (
<button
@ -322,7 +323,7 @@ function ReportSection({ eventId }: { eventId: string }) {
return (
<section className="kx-card kx-settle__report" aria-label={t('settlement.reportTitle')}>
<div className="kx-card__head kx-settle__report-head">
<h2>{t('settlement.reportTitle')}</h2>
<h2><IconAnalytics className="kx-title-ic" size={18} />{t('settlement.reportTitle')}</h2>
<div className="kx-settle__report-actions">
<Button variant="secondary" onClick={() => setOpen((v) => !v)}>
{open ? t('settlement.reportHide') : t('settlement.reportShow')}

View File

@ -11,7 +11,7 @@ import {
} from 'recharts';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconSearch, IconWifiOff, IconCheckCircle, IconGrid, IconUsers } from '../../components/ui/icons';
import { IconSearch, IconWifiOff, IconCheckCircle, IconClock, IconGrid, IconUsers } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { CHART } from '../chartColors';
@ -317,7 +317,7 @@ export function CheckinDeskPage() {
</div>
<div className="kx-card kx-checkin__hours">
<div className="kx-card__head"><h2> </h2></div>
<div className="kx-card__head"><h2><IconClock className="kx-title-ic" size={18} /> </h2></div>
{chartData.length === 0 ? (
<EmptyState title="집계 없음" description="체크인이 시작되면 시간대별 추이가 표시됩니다." />
) : (

View File

@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
import { AiLabel } from '../../components/ui/Badge';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconDownload, IconSpark } from '../../components/ui/icons';
import { IconClock, IconDownload, IconSpark } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { useParams } from 'react-router-dom';
@ -326,7 +326,7 @@ function LeadDetail({
<div className="kx-card">
<div className="kx-card__head">
<h2> </h2>
<h2><IconClock className="kx-title-ic" size={18} /> </h2>
</div>
<ul className="kx-vis__activity">
{lead.activity.map((a, i) => (

View File

@ -14,7 +14,7 @@ import {
import { AiLabel } from '../../components/ui/Badge';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconDownload, IconSpark } from '../../components/ui/icons';
import { IconDocument, IconDownload, IconSpark, IconTag, IconTrendUp, IconUsers } from '../../components/ui/icons';
import { ApiRequestError } from '../../api/client';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { CHART } from '../chartColors';
@ -116,7 +116,7 @@ export function VisitorRegistrationDashboardPage() {
<div className="kx-vis__split">
<section className="kx-card kx-vis__trend" aria-label="등록·체크인 추이">
<div className="kx-card__head">
<h2> · </h2>
<h2><IconTrendUp className="kx-title-ic" size={18} /> · </h2>
<ul className="kx-vis__legend" aria-hidden="true">
<li>
<span className="kx-dot" style={{ background: CHART.primary600 }} />
@ -184,7 +184,7 @@ export function VisitorRegistrationDashboardPage() {
<aside className="kx-vis__aside">
<section className="kx-card" aria-label="등록 폼 관리">
<div className="kx-card__head">
<h2> </h2>
<h2><IconDocument className="kx-title-ic" size={18} /> </h2>
</div>
<ul className="kx-vis__forms">
{summary.forms.map((f) => (
@ -210,7 +210,7 @@ export function VisitorRegistrationDashboardPage() {
<section className="kx-card" aria-label="배지 템플릿">
<div className="kx-card__head">
<h2> 릿</h2>
<h2><IconTag className="kx-title-ic" size={18} /> 릿</h2>
</div>
<div className="kx-vis__badge-preview">
<div className="kx-badgecard">
@ -235,7 +235,7 @@ export function VisitorRegistrationDashboardPage() {
{/* 등록자 명단 */}
<section className="kx-card kx-vis__table-card" aria-label="전체 신청자 명단">
<div className="kx-card__head">
<h2> </h2>
<h2><IconUsers className="kx-title-ic" size={18} /> </h2>
<span className="kx-vis__pii-note"> · </span>
</div>
<div className="kx-table-scroll">

View File

@ -12,7 +12,7 @@ import { useAuthStore } from '../../store/authStore';
import { Button } from '../../components/ui/Button';
import { RoleBadge } from '../../components/ui/Badge';
import { ErrorState, Skeleton } from '../../components/ui/States';
import { IconCheckCircle } from '../../components/ui/icons';
import { IconBell, IconCheckCircle, IconSettings, IconShieldCheck, IconUser } from '../../components/ui/icons';
import { useToast } from './workShared';
import './work.css';
@ -99,7 +99,7 @@ export function MyPage() {
{tab === 'profile' && (
<>
<div className="kx-card__head">
<h2></h2>
<h2><IconUser className="kx-title-ic" size={18} /></h2>
</div>
{meQ.isLoading ? (
<Skeleton height={80} />
@ -133,7 +133,7 @@ export function MyPage() {
{tab === 'security' && (
<>
<div className="kx-card__head">
<h2>2 (OTP)</h2>
<h2><IconShieldCheck className="kx-title-ic" size={18} />2 (OTP)</h2>
</div>
{otpQ.isLoading ? (
<Skeleton height={60} />
@ -169,7 +169,7 @@ export function MyPage() {
{tab === 'notify' && (
<>
<div className="kx-card__head">
<h2> </h2>
<h2><IconBell className="kx-title-ic" size={18} /> </h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{(
@ -199,7 +199,7 @@ export function MyPage() {
{tab === 'theme' && (
<>
<div className="kx-card__head">
<h2>·</h2>
<h2><IconSettings className="kx-title-ic" size={18} />·</h2>
</div>
<div className="kx-formgrid">
<label className="kx-field">

View File

@ -10,6 +10,7 @@ import type { OpinionDto, OpinionSaveRequest } from '../../api/types';
import { useAuthStore } from '../../store/authStore';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconChat, IconList } from '../../components/ui/icons';
import { StatusPill, errMessage, fmtDateTime, useToast } from './workShared';
import './work.css';
@ -85,7 +86,7 @@ export function OpinionPage() {
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<section className="kx-card" aria-label="의견 접수">
<div className="kx-card__head">
<h2> </h2>
<h2><IconChat className="kx-title-ic" size={18} /> </h2>
</div>
<div className="kx-formgrid">
<label className="kx-field">
@ -136,7 +137,7 @@ export function OpinionPage() {
<section className="kx-card" aria-label="접수 목록">
<div className="kx-card__head">
<h2> </h2>
<h2><IconList className="kx-title-ic" size={18} /> </h2>
<select
className="kx-select"
value={statusFilter}

View File

@ -20,7 +20,7 @@ import { reportApi, workStatsApi } from '../../api/endpoints';
import { Button } from '../../components/ui/Button';
import { CHART } from '../chartColors';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import { IconDownload } from '../../components/ui/icons';
import { IconAnalytics, IconDocument, IconDownload, IconUsers } from '../../components/ui/icons';
import { errMessage, fmtDate } from './workShared';
import './work.css';
@ -133,7 +133,7 @@ export function ReportPage() {
{/* 유형별 차트 */}
<section className="kx-card" aria-label="유형별 업무량">
<div className="kx-card__head">
<h2> </h2>
<h2><IconAnalytics className="kx-title-ic" size={18} /> </h2>
</div>
{statsQ.isLoading ? (
<Skeleton height={260} />
@ -165,7 +165,7 @@ export function ReportPage() {
<div className="kx-split" style={{ gridTemplateColumns: '1fr 1fr' }}>
<section className="kx-card" aria-label="작성자별 집계">
<div className="kx-card__head">
<h2> </h2>
<h2><IconUsers className="kx-title-ic" size={18} /> </h2>
</div>
<div className="kx-table-scroll">
<table className="kx-list-table">
@ -207,7 +207,7 @@ export function ReportPage() {
<section className="kx-card" aria-label="최근 보고서">
<div className="kx-card__head">
<h2> </h2>
<h2><IconDocument className="kx-title-ic" size={18} /> </h2>
</div>
<div className="kx-table-scroll">
<table className="kx-list-table">

View File

@ -16,6 +16,7 @@ import {
IconDocument,
IconSearch,
IconSpark,
IconTag,
} from '../../components/ui/icons';
import { fmtDateTime, errMessage } from './workShared';
import './work.css';
@ -157,7 +158,7 @@ export function SearchPage() {
{/* 파셋 */}
<aside className="kx-card" aria-label="검색 필터">
<div className="kx-card__head">
<h2></h2>
<h2><IconTag className="kx-title-ic" size={18} /></h2>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{tabs.map((t) => (

View File

@ -174,6 +174,39 @@ textarea {
font-variant-numeric: tabular-nums;
}
/*
* 중간 타이틀(섹션/카드 제목) 선행 아이콘 헬퍼 (2026-07 전수 스윕)
* - 타이틀 요소의 자식 <svg> 삽입 텍스트 컨텍스트/flex 컨텍스트 모두 대응.
* · 일반(블록/인라인) 제목: vertical-align 캡하이트 중앙 정렬 + margin 간격.
* · flex 제목(display:flex): align-items 중앙 정렬, flex:none 축소 방지.
* - 색은 토큰만(--color-primary-600 기본 · --muted neutral-500).
* - 페이지 대제목(h1) 대상 아님 중간 타이틀에만 부착한다.
* */
.kx-title-ic {
display: inline-block;
flex: none;
vertical-align: -0.16em;
margin-right: 0.42em;
color: var(--color-primary-600);
}
.kx-title-ic--muted {
color: var(--color-neutral-500);
}
/* flex 컨텍스트(제목 요소 자체가 flex)에서는 margin 대신 부모 gap 간격을 준다
* 그런 제목은 이미 gap 가지므로 아이콘 margin 이중 간격이 되지 않도록 소거. */
.kx-card__header .kx-title-ic,
.kx-card__head .kx-title-ic,
.kx-dock__panel-title .kx-title-ic {
margin-right: 0;
}
.kx-card__header h2,
.kx-card__header h3,
.kx-card__head h2 {
display: inline-flex;
align-items: center;
gap: var(--space-2);
}
.mono {
font-family: var(--font-mono);
font-size: var(--fs-mono);