Compare commits
17 Commits
58aa8440d9
...
e67db2e773
| Author | SHA1 | Date | |
|---|---|---|---|
| e67db2e773 | |||
| 37aeebdd28 | |||
| 277c895b47 | |||
| 490b723a45 | |||
| 0add2cd417 | |||
| f3b800f9be | |||
| c77cba7c5b | |||
| a5a78f1ea1 | |||
| 55a3114141 | |||
| 7316cd99c5 | |||
| c595560d82 | |||
| 276293ebe7 | |||
| 3077cb04f5 | |||
| 55e27b3309 | |||
| 87f310f40a | |||
| a2b2bd9110 | |||
| 6e95ac6273 |
278
docs/deliverables/01_개발계획서.md
Normal file
278
docs/deliverables/01_개발계획서.md
Normal 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 통과 후 최신 메뉴 반영).
|
||||
BIN
docs/deliverables/01_개발계획서.pptx
Normal file
BIN
docs/deliverables/01_개발계획서.pptx
Normal file
Binary file not shown.
420
docs/deliverables/02_설계서.md
Normal file
420
docs/deliverables/02_설계서.md
Normal 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 통과 후 최신 메뉴를 반영해야 하므로 이번 범위에서 제외).
|
||||
BIN
docs/deliverables/02_설계서.pptx
Normal file
BIN
docs/deliverables/02_설계서.pptx
Normal file
Binary file not shown.
854
docs/deliverables/DA산출물/_gen/gen_da_deliverables.py
Normal file
854
docs/deliverables/DA산출물/_gen/gen_da_deliverables.py
Normal 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}", "인덱스"), ("V1–V10", "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)
|
||||
BIN
docs/deliverables/DA산출물/데이터베이스설계서.pptx
Normal file
BIN
docs/deliverables/DA산출물/데이터베이스설계서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/DA산출물/테이블정의서.xlsx
Normal file
BIN
docs/deliverables/DA산출물/테이블정의서.xlsx
Normal file
Binary file not shown.
70
docs/deliverables/_gen/_docxutil.py
Normal file
70
docs/deliverables/_gen/_docxutil.py
Normal 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
|
||||
785
docs/deliverables/_gen/gen_dev_plan_pptx.py
Normal file
785
docs/deliverables/_gen/gen_dev_plan_pptx.py
Normal 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))
|
||||
337
docs/deliverables/_gen/gen_docx.py
Normal file
337
docs/deliverables/_gen/gen_docx.py
Normal 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)
|
||||
264
docs/deliverables/_gen/sources.py
Normal file
264
docs/deliverables/_gen/sources.py
Normal 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)
|
||||
BIN
docs/deliverables/개발계획서.pptx
Normal file
BIN
docs/deliverables/개발계획서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/사업수행계획서.docx
Normal file
BIN
docs/deliverables/사업수행계획서.docx
Normal file
Binary file not shown.
BIN
docs/deliverables/제안서.pptx
Normal file
BIN
docs/deliverables/제안서.pptx
Normal file
Binary file not shown.
64
docs/deliverables/제안서/_gen/_qa_rtm.py
Normal file
64
docs/deliverables/제안서/_gen/_qa_rtm.py
Normal 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'}")
|
||||
41
docs/deliverables/제안서/_gen/_qa_struct.py
Normal file
41
docs/deliverables/제안서/_gen/_qa_struct.py
Normal 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))}')
|
||||
1942
docs/deliverables/제안서/_gen/build_deck.py
Normal file
1942
docs/deliverables/제안서/_gen/build_deck.py
Normal file
File diff suppressed because it is too large
Load Diff
242
docs/deliverables/제안서/_gen/design_system.md
Normal file
242
docs/deliverables/제안서/_gen/design_system.md
Normal 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는 "승인 예외"로만 표기.
|
||||
101
docs/deliverables/제안서/_gen/fetch_screens.py
Normal file
101
docs/deliverables/제안서/_gen/fetch_screens.py
Normal 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}")
|
||||
342
docs/deliverables/제안서/_gen/g2b_standard_toc.md
Normal file
342
docs/deliverables/제안서/_gen/g2b_standard_toc.md
Normal 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조⑤ 단서) |
|
||||
239
docs/deliverables/제안서/_gen/icon_raster.py
Normal file
239
docs/deliverables/제안서/_gen/icon_raster.py
Normal 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"))
|
||||
998
docs/deliverables/제안서/_gen/proposal_content.md
Normal file
998
docs/deliverables/제안서/_gen/proposal_content.md
Normal 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 생성 이미지는 전 슬라이드에서 워터마크·금지 고지 정책 명시.
|
||||
210
docs/deliverables/제안서/_gen/proposal_outline.md
Normal file
210
docs/deliverables/제안서/_gen/proposal_outline.md
Normal 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 등)은 압축 유지.
|
||||
117
docs/deliverables/제안서/_gen/proposal_qa.md
Normal file
117
docs/deliverables/제안서/_gen/proposal_qa.md
Normal 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 고지 존재. 실제 공고 확인 시 배점·목차·투입계획 재정렬만 반영하면 제출 가능.
|
||||
150
docs/deliverables/제안서/_gen/proposal_strategy.md
Normal file
150
docs/deliverables/제안서/_gen/proposal_strategy.md
Normal 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·미확보 데이터의 확정적 서술 금지. 미확보는 "가정/협의 전제" 라벨 필수.
|
||||
23
docs/deliverables/제안서/_gen/render_check.py
Normal file
23
docs/deliverables/제안서/_gen/render_check.py
Normal 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)
|
||||
366
docs/deliverables/제안서/_gen/rfp_analysis.md
Normal file
366
docs/deliverables/제안서/_gen/rfp_analysis.md
Normal 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>
|
||||
196
docs/deliverables/제안서/_gen/rtm.md
Normal file
196
docs/deliverables/제안서/_gen/rtm.md
Normal 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>
|
||||
374
docs/deliverables/제안서/_gen/tech_advisory.md
Normal file
374
docs/deliverables/제안서/_gen/tech_advisory.md
Normal 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`
|
||||
274
docs/deliverables/제안서/_gen/visual_concepts.md
Normal file
274
docs/deliverables/제안서/_gen/visual_concepts.md
Normal 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"로만.
|
||||
1025
docs/deliverables/지침서/_gen/gen_guides_pptx.py
Normal file
1025
docs/deliverables/지침서/_gen/gen_guides_pptx.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/deliverables/지침서/개발자지침서.pptx
Normal file
BIN
docs/deliverables/지침서/개발자지침서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/지침서/사용자지침서.pptx
Normal file
BIN
docs/deliverables/지침서/사용자지침서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/지침서/운영자지침서.pptx
Normal file
BIN
docs/deliverables/지침서/운영자지침서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/킨텍스AI_사업수행계획서.pptx
Normal file
BIN
docs/deliverables/킨텍스AI_사업수행계획서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/킨텍스AI_완료보고서.pptx
Normal file
BIN
docs/deliverables/킨텍스AI_완료보고서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/킨텍스AI_중간보고서.pptx
Normal file
BIN
docs/deliverables/킨텍스AI_중간보고서.pptx
Normal file
Binary file not shown.
BIN
docs/deliverables/킨텍스AI_착수보고서.pptx
Normal file
BIN
docs/deliverables/킨텍스AI_착수보고서.pptx
Normal file
Binary file not shown.
1115
docs/deliverables/프로그램사양서/_gen/gen_program_spec_pptx.py
Normal file
1115
docs/deliverables/프로그램사양서/_gen/gen_program_spec_pptx.py
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/deliverables/프로그램사양서/프로그램사양서.pptx
Normal file
BIN
docs/deliverables/프로그램사양서/프로그램사양서.pptx
Normal file
Binary file not shown.
@ -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 (
|
||||
|
||||
@ -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')} />
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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 ? (
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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')} />
|
||||
) : (
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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')}>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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="체크인이 시작되면 시간대별 추이가 표시됩니다." />
|
||||
) : (
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user