From 37aeebdd28a70f0ae904ad326ce5f35b70a7ffe4 Mon Sep 17 00:00:00 2001 From: zio Date: Mon, 13 Jul 2026 04:23:07 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=94=EB=A3=A8=EC=85=98=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/agents/kintex-gap-auditor.md | 50 +++ .claude/settings.json | 24 + .../kintex-completeness-orchestrator/SKILL.md | 44 ++ .../references/gap-taxonomy.md | 37 ++ CLAUDE.md | 27 ++ docs/GAP_AUDIT.md | 107 +++++ docs/analysis/nifty-design-refs.md | 2 +- docs/deliverables/01_개발계획서.md | 278 ++++++++++++ docs/deliverables/01_개발계획서.pptx | Bin 0 -> 48265 bytes docs/deliverables/02_설계서.md | 420 ++++++++++++++++++ docs/deliverables/02_설계서.pptx | Bin 0 -> 44254 bytes docs/design.md | 106 ++++- src/frontend/src/components/ui/icons.tsx | 134 ++++++ .../src/screens/admin/AdminDashboardPage.tsx | 5 +- .../src/screens/admin/LoginSlideAdminPage.tsx | 4 +- .../src/screens/analytics/AiConsolePanel.tsx | 3 +- .../analytics/AnalyticsDashboardPage.tsx | 7 +- .../analytics/GlobalOverviewSection.tsx | 9 +- .../src/screens/auction/AwardComparePage.tsx | 6 +- .../ContractorBoothDashboardPage.tsx | 9 +- .../dashboard/OrganizerDashboardPage.tsx | 8 +- .../src/screens/docs/DocsMilestonePage.tsx | 2 +- .../src/screens/docs/ReportAuthoringPage.tsx | 4 +- .../screens/hallassign/HallAssignmentPage.tsx | 6 +- src/frontend/src/screens/home/HomePage.tsx | 44 +- .../src/screens/marketing/EdmCampaignPage.tsx | 4 +- .../src/screens/marketing/SponsorshipPage.tsx | 6 +- .../screens/movein/DockReservationPage.tsx | 4 +- .../src/screens/movein/LogisticsExtTabs.tsx | 13 +- .../src/screens/ops/HallOperationsPage.tsx | 9 +- .../src/screens/public/AgencyTrackPage.tsx | 8 +- .../src/screens/public/AiPlanningBriefing.tsx | 4 +- .../src/screens/public/BusinessTrackPage.tsx | 8 +- .../src/screens/public/VisitorTrackPage.tsx | 14 +- .../schedule/ExhibitionSchedulePage.tsx | 4 +- .../settlement/SettlementDashboardPage.tsx | 7 +- .../src/screens/visitor/CheckinDeskPage.tsx | 4 +- .../src/screens/visitor/LeadScoringPage.tsx | 4 +- .../VisitorRegistrationDashboardPage.tsx | 10 +- src/frontend/src/screens/work/MyPage.tsx | 10 +- src/frontend/src/screens/work/OpinionPage.tsx | 5 +- src/frontend/src/screens/work/ReportPage.tsx | 8 +- src/frontend/src/screens/work/SearchPage.tsx | 3 +- src/frontend/src/styles/global.css | 33 ++ 44 files changed, 1400 insertions(+), 94 deletions(-) create mode 100644 .claude/agents/kintex-gap-auditor.md create mode 100644 .claude/settings.json create mode 100644 .claude/skills/kintex-completeness-orchestrator/SKILL.md create mode 100644 .claude/skills/kintex-completeness-orchestrator/references/gap-taxonomy.md create mode 100644 docs/GAP_AUDIT.md create mode 100644 docs/deliverables/01_개발계획서.md create mode 100644 docs/deliverables/01_개발계획서.pptx create mode 100644 docs/deliverables/02_설계서.md create mode 100644 docs/deliverables/02_설계서.pptx diff --git a/.claude/agents/kintex-gap-auditor.md b/.claude/agents/kintex-gap-auditor.md new file mode 100644 index 0000000..846e2bc --- /dev/null +++ b/.claude/agents/kintex-gap-auditor.md @@ -0,0 +1,50 @@ +--- +name: kintex-gap-auditor +description: 킨텍스 앱의 미개발·미완성을 전수 감사하는 에이전트. 메뉴↔라우트↔컴포넌트↔API 4자 체인을 교차 대조해 빈 화면·스텁·"준비중"/TODO·홈/대시보드 폴백·데드링크·미구현 엔드포인트·CRUD 누락·미배선 버튼을 색출하고 우선순위 백로그를 산출한다. "미개발 확인", "완성도 감사", "빈 화면 찾기", "준비중 화면", "홈으로 가는 메뉴", "미구현", 다시 실행·업데이트·보완 포함. general-purpose 타입. +tools: Read, Write, Glob, Grep, Bash +model: opus +--- + +당신은 킨텍스 AI 전시·행사시스템의 **미개발 감사관**이다. general-purpose 타입 — grep·빌드·서버 조회를 실제 실행한다. 목적은 **고치는 것이 아니라 "무엇이 아직 안 되어 있는지"를 정확·완전하게 확인**하는 것이다(수정은 구현 하네스가 담당). + +## 핵심 역할 +프론트(`src/frontend`)·백엔드(`src/backend`)를 전수 스캔해 **미개발/미완성 지점**을 근거(파일:라인)와 함께 인벤토리화하고, 심각도·우선순위를 매긴 백로그(`docs/GAP_AUDIT.md`)를 산출한다. + +## 감사 방법 (4자 체인 교차 대조 — 핵심) +킨텍스 화면은 **메뉴(AppShell GROUPS) → 라우트(App.tsx) → 컴포넌트(screens/**) → API(backend controller)** 로 이어진다. 각 연결이 끊긴 곳이 미개발이다: +1. **메뉴 인벤토리**: `components/layout/AppShell.tsx`의 `GROUPS`/`NavItem`(`to`·`scoped`) + 공개 네비(PublicShell) 전수 추출. +2. **라우트 인벤토리**: `App.tsx`의 `` 전수 추출. +3. **화면 상태**: 각 라우트 컴포넌트가 (a)실제 구현 (b)스텁/빈 (c)"준비중"/TODO/placeholder (d)홈·대시보드로 폴백/리다이렉트 인지 판정. +4. **API 배선**: 화면이 호출하는 엔드포인트가 백엔드에 실제 구현됐는지(컨트롤러 존재·스텁 아님) 대조. 미구현/스텁/501/빈배열 하드코딩 색출. + +## 색출 대상 (gap taxonomy — 상세 references/gap-taxonomy.md) +- **빈 화면/스텁**: `return null`·빈 EmptyState 고정·`TODO`·`준비중`·`coming soon`·`구현 예정`·placeholder만 렌더. +- **홈/대시보드 폴백**: 메뉴 클릭이 전용 화면 없이 `/home`·대시보드로 감(참가업체 사례). `EVENT_SCOPED_PATH` 폴백·`Navigate` 리다이렉트. +- **데드링크**: `to`/`href`가 정의된 라우트에 없음, `#`·`javascript:void`·빈 onClick. +- **미배선 버튼/액션**: onClick 없음·`disabled` 고정·`alert('미구현')`·핸들러 빈 함수. +- **미구현 API**: 프론트가 부르는 경로가 백엔드 컨트롤러에 없음, 또는 컨트롤러가 스텁(하드코딩·빈 리스트·`NOT_IMPLEMENTED`). +- **CRUD 누락**: 목록만 있고 생성/수정/삭제 미구현, 상세 없음. +- **메뉴 없는 라우트 / 라우트 없는 메뉴**: 고아 화면·죽은 메뉴. +- **모바일(mobile/)**: 화면은 있으나 API 미배선·mock 고정(선택 스캔). + +## 작업 원칙 +- **근거 필수**: 모든 지적은 `파일:라인` + 무엇이 왜 미개발인지. 추측 금지 — 실제 코드 확인. +- **오탐 억제**: "구현됐으나 데이터가 비어 보이는 것"(행사스코프 시드 미정렬 [[event-scope-seed-alignment]])과 "코드가 미개발인 것"을 구분. 데이터 이슈는 별도 표기. +- **우선순위**: P0(핵심 업무 흐름 차단)·P1(주요 화면 빈/폴백)·P2(부가·엣지). 역할별 노출도 고려(관리자 전용 등). +- **정본 대조**: `docs/PLANNING.md`(M1~M18 범위)·`docs/design.md`(SCR 인벤토리)와 대조해 "기획엔 있는데 미구현" 을 우선. + +## 입력/출력 프로토콜 +- 입력: 스캔 범위(전체/특정 영역), 이전 `docs/GAP_AUDIT.md`(있으면 델타 갱신). +- 출력: `docs/GAP_AUDIT.md` — ①요약(총 N건·심각도 분포) ②4자체인 인벤토리(메뉴·라우트·화면상태·API) ③미개발 목록(파일:라인·유형·심각도·정본근거) ④우선순위 백로그(수정 담당 하네스 매핑: 화면=kintex-frontend-dev/구현 오케스트레이터, API=kintex-backend-dev, 데이터=kintex-testdata-dev). 중간 산출 `_workspace/`. + +## 에러 핸들링 +빌드/서버 조회 실패 시 1회 재시도, 재실패면 해당 항목 "확인 불가"로 표기하고 계속. 상충(코드상 구현인데 런타임 빈)은 삭제 말고 양쪽 근거 병기. + +## 협업 +- 발견은 `docs/GAP_AUDIT.md`로 공유. **kintex-qa**가 P0/P1 지적을 실동작(경계면·라우트 렌더)으로 재검증(오탐 제거). 수정은 구현 하네스(kintex-impl-orchestrator)로 인계 — 이 에이전트는 **감사·확인만**. + +## 재호출 지침 +이전 `docs/GAP_AUDIT.md`가 있으면 읽고, 그 사이 배포로 해소된 항목은 ✅ 표기·신규 미개발만 추가(델타 감사). 특정 영역만 요청 시 그 영역만. + +## 보안 불변 +자격증명·PII·내부IP·스택트레이스 문서 미기재. 서버 조회는 존재/상태만(값 미출력). diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..2d6389f --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,24 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "C:\\Users\\ython\\AppData\\Local\\Python\\pythoncore-3.14-64\\Scripts\\graphify.EXE hook-guard search" + } + ] + }, + { + "matcher": "Read|Glob", + "hooks": [ + { + "type": "command", + "command": "C:\\Users\\ython\\AppData\\Local\\Python\\pythoncore-3.14-64\\Scripts\\graphify.EXE hook-guard read" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/.claude/skills/kintex-completeness-orchestrator/SKILL.md b/.claude/skills/kintex-completeness-orchestrator/SKILL.md new file mode 100644 index 0000000..940020d --- /dev/null +++ b/.claude/skills/kintex-completeness-orchestrator/SKILL.md @@ -0,0 +1,44 @@ +--- +name: kintex-completeness-orchestrator +description: 킨텍스 앱의 미개발·미완성을 전수 확인하는 감사 하네스. 메뉴↔라우트↔컴포넌트↔API 4자 체인을 교차 대조해 빈 화면·스텁·"준비중"/TODO·홈/대시보드 폴백·데드링크·미배선 버튼·미구현 엔드포인트·CRUD 누락을 색출하고 우선순위 백로그(docs/GAP_AUDIT.md)를 산출한다. "미개발 확인", "완성도 감사", "빈 화면/준비중 화면 찾기", "홈으로 가는 메뉴", "미구현 점검", "개발 안 된 것", 다시 실행·특정 영역만·업데이트·보완 요청 시 이 스킬을 사용하라. (수정·구현은 kintex-impl-orchestrator, UI정렬은 kintex-wise-ui-orchestrator 담당 — 이 하네스는 확인/감사 트랙.) +--- + +# 킨텍스 미개발 확인 (완성도 감사) 오케스트레이터 + +킨텍스 앱에서 **아직 개발 안 된/미완성 지점**을 정확·완전하게 확인한다. **실행 모드: 하이브리드** — Phase1 감사(단일 에이전트) → Phase2 검증(단일 QA). 모든 Agent 호출은 `model: "opus"`. + +**경계(중복 회피):** 이 하네스는 **확인/감사만** 한다. 수정·구현은 `kintex-impl-orchestrator`, UI 정렬은 `kintex-wise-ui-orchestrator`, 데이터 빈 화면은 `kintex-renewal`/testdata. 감사 결과 백로그를 그 하네스들로 인계한다. + +## 에이전트 로스터 +| 에이전트 | 역할 | +|---|---| +| **kintex-gap-auditor**(신규) | 4자 체인 교차 대조·미개발 전수 인벤토리·`docs/GAP_AUDIT.md` 산출 | +| **kintex-qa**(재사용) | P0/P1 지적을 실동작(라우트 렌더·경계면·API 200)으로 재검증 — 오탐 제거 | +| **reviewer**(재사용, 선택) | 정본(PLANNING/design) 대조 정합 확인 | + +## Phase 0: 컨텍스트 +- `docs/GAP_AUDIT.md` 존재 여부로 초기/델타 판별. 특정 영역만 요청이면 범위 한정. +- 최근 배포로 해소된 항목은 델타에서 ✅ 처리. + +## Phase 1: 미개발 감사 (kintex-gap-auditor 단일) +메뉴(AppShell GROUPS·PublicShell)·라우트(App.tsx)·화면(screens/**)·API(backend controller)를 전수 스캔·교차 대조. gap taxonomy(references/gap-taxonomy.md)로 분류. **오탐 억제**: "코드 미개발" vs "데이터만 빈(행사스코프 시드 [[event-scope-seed-alignment]])" 구분. `docs/GAP_AUDIT.md`에 요약·인벤토리·미개발목록(파일:라인·유형·심각도)·우선순위 백로그(수정 담당 하네스 매핑) 산출. + +## Phase 2: 검증 (kintex-qa 단일) +P0/P1 지적을 **실동작으로 재검증** — 해당 라우트가 정말 빈/폴백인지, API가 정말 미구현(404/스텁)인지 헤드리스/curl/grep로 확인. 오탐은 제거·확정은 CONFIRMED 표기. 결과를 GAP_AUDIT.md에 반영. + +## Phase 3: 인계 (오케스트레이터) +확정 백로그를 화면=kintex-frontend-dev/impl, API=kintex-backend-dev, 데이터=kintex-testdata-dev로 매핑해 요약 보고. **수정은 이 하네스가 하지 않는다** — 소유자 승인 후 해당 하네스로 넘긴다. + +## 데이터 전달 / 에러 +파일 기반(`docs/GAP_AUDIT.md` 정본 + `_workspace/` 중간). 조회 실패는 1회 재시도 후 "확인 불가" 표기. 상충은 양쪽 근거 병기(삭제 금지). + +## 보안 불변 +자격증명·PII·내부IP·스택트레이스 문서 미기재. 서버 조회는 존재/상태만. + +## 테스트 시나리오 +- **정상**: "미개발 확인 하네스 실행" → Phase1 감사(GAP_AUDIT.md) → Phase2 QA 재검증(오탐 제거) → Phase3 인계 보고. +- **델타**: "다시 확인" → 기존 GAP_AUDIT.md 대비 해소분 ✅·신규만 추가. +- **부분**: "옥션 화면만 미개발 확인" → 범위 한정 감사. + +## 후속 작업 +description 후속 키워드("다시 실행"·"특정 영역만"·"업데이트")로 델타/부분 감사 트리거. Phase 0이 초기/델타/부분 판별. diff --git a/.claude/skills/kintex-completeness-orchestrator/references/gap-taxonomy.md b/.claude/skills/kintex-completeness-orchestrator/references/gap-taxonomy.md new file mode 100644 index 0000000..074e67c --- /dev/null +++ b/.claude/skills/kintex-completeness-orchestrator/references/gap-taxonomy.md @@ -0,0 +1,37 @@ +# 미개발 분류 (Gap Taxonomy) — 킨텍스 + +감사 시 각 지점을 아래 유형으로 분류하고 근거(파일:라인)와 심각도를 붙인다. + +## 유형별 탐지 신호 (grep 힌트) + +| 유형 | 코드 신호(grep) | 판정 | +|------|----------------|------| +| **빈 화면/스텁** | `return null`(라우트 컴포넌트 최상위)·`준비중`·`준비 중`·`coming soon`·`구현 예정`·`TODO`·`FIXME`·`placeholder`·`더미`·`mock`(고정) | 렌더 내용이 없거나 안내문뿐 | +| **홈/대시보드 폴백** | `EVENT_SCOPED_PATH`·`Navigate to="/home"`·`navigate('/home')`·`navigate('/dashboard')`·메뉴 `scoped` 인데 전용 라우트 없음 | 메뉴 클릭이 전용 화면 없이 홈/대시보드로 감 | +| **데드링크** | `to="#"`·`href="#"`·`href="javascript:`·`to=` 값이 App.tsx 라우트에 없음 | 이동 대상이 없음 | +| **미배선 버튼/액션** | `onClick={() => {}}`·`onClick` 부재(버튼인데)·`disabled` 하드코딩·`alert(`·`console.log('TODO` | 눌러도 동작 없음 | +| **미구현 API** | 프론트 `api.get/post('/api/...')` 경로가 backend `@*Mapping`에 없음·컨트롤러가 `NOT_IMPLEMENTED`·빈 리스트/하드코딩 반환·`return List.of()` 고정 | 서버가 없거나 스텁 | +| **CRUD 누락** | 목록 화면만·`POST/PUT/DELETE` 핸들러 부재·상세 라우트/화면 없음 | 조회만 가능 | +| **고아/죽은** | 라우트 있으나 메뉴/링크 진입 없음 / 메뉴 있으나 라우트 없음 | 접근 불가 또는 죽은 메뉴 | + +## 오탐(false positive) 주의 — 미개발 아님 +- **데이터만 빈**: 코드는 구현됐는데 행사스코프 시드가 데모 해소행사에 없어 빈 것 → [[event-scope-seed-alignment]]. **미개발 아님, 데이터 이슈로 별도 표기**(담당=testdata). +- **역할 게이트로 미노출**: 권한상 안 보이는 것(관리자 전용 등)은 미개발 아님. +- **degraded 폴백**: API 실패 시 의도적 폴백(그라디언트·안내) 있는 것은 정상 처리. +- **의도적 외부/준비중**: iOS TestFlight "준비중" 등 명시적 로드맵 항목은 "계획됨"으로 표기(미개발과 구분). + +## 심각도 +- **P0**: 핵심 업무 흐름(로그인 후 주요 도메인 진입·부스/옥션/정산/관람객 핵심 CRUD) 차단. +- **P1**: 주요 화면이 빈/폴백/데드링크. 메뉴에 있으나 동작 안 함. +- **P2**: 부가·엣지·관리자 세부·모바일 mock. + +## 정본 대조 +`docs/PLANNING.md`(M1~M18·§5B 공통·역할/트랙)·`docs/design.md`(SCR 인벤토리)에 **있는데 미구현**인 것을 최우선. 기획에 없는 것은 "범위 외"로 표기. + +## 4자 체인 스캔 순서 +1. `AppShell.tsx` GROUPS/NavItem → 메뉴·`to`·`scoped` 목록 +2. `App.tsx` `` → 라우트·컴포넌트 목록 +3. 각 컴포넌트 파일 → 렌더 내용 판정(위 신호) +4. 컴포넌트가 부르는 `*Api.ts`/`api.*` 경로 → backend `@*Mapping` 대조 +5. 공개 사이트: `PublicShell`·`screens/public/*` 동일 절차 +6. (선택) `mobile/app/*` → API 배선 여부 diff --git a/CLAUDE.md b/CLAUDE.md index bc16379..d75e610 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,3 +144,30 @@ GUARDiA 표준 프레임워크(Spring Boot 3.5 + React 19 + MyBatis + PostgreSQL | 2026-07-11 | 초기 구성 — kintex-mobile-dev 신규 + 오케스트레이터(재사용: designer·backend-dev·qa·devops-dev·reviewer). WISE 모바일 레퍼런스·Stitch 디자인 경유 반영 | 전체 | 모바일 앱 트랙 전담 하네스 부재("하네스 생성" + "wise 모바일 참고" + "디자인은 스티치" 요청) | | 2026-07-11 | **앱 타깃 2개 확정 반영** — 코드베이스 1(mobile/) + 배포 타깃 2(①운영 B2B: 2FA 필수·사내 QR ②관람객 B2C: 스토어 공개·간편가입·게스트). 계정은 단일 통합+가입 트랙 분리(PLANNING v3.1) | kintex-mobile-dev·오케스트레이터 | 소유자 확정 — 회원가입 관람객 포함 질의 → 하이브리드(계정 통합·앱 분리) 채택 | | 2026-07-12 | **내정보(WISE)+생체인식(expo-local-authentication)+프로필 사진(expo-image-picker)** + **앱 위변조 방지·시큐어코딩 보안 체크리스트**(루트/탈옥·무결성·Hermes+R8·화면캡처·cleartext·권한최소, `docs/security/mobile-security-*.md`) + **다국어(react-i18next)·역할별 랜딩 패리티** 착수 | kintex-mobile-dev·kintex-qa | "모바일 내정보 WISE+생체+사진"·"앱 위변조방지·시큐어코딩·보안체크"·"모바일도 동일 로직"·"웹 4개국어인데 모바일은?" | + +## graphify + +This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships. + +Rules: +- For codebase questions, first run `graphify query ""` when graphify-out/graph.json exists. Use `graphify path "" ""` for relationships and `graphify explain ""` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output. +- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing. +- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context. +- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost). + +--- + +## 하네스: 미개발 확인 (kintex-completeness-orchestrator) + +**목표:** 킨텍스 앱의 **미개발·미완성**을 전수 확인. 메뉴↔라우트↔컴포넌트↔API 4자 체인을 교차 대조해 빈 화면·스텁·"준비중"/TODO·홈/대시보드 폴백·데드링크·미배선 버튼·미구현 엔드포인트·CRUD 누락을 색출하고 우선순위 백로그(`docs/GAP_AUDIT.md`) 산출. **확인/감사만** — 수정은 구현 하네스 인계. + +**트리거:** 미개발 확인, 완성도 감사, 빈 화면/준비중 화면 찾기, 홈으로 가는 메뉴, 미구현 점검, 개발 안 된 것, 다시 실행·특정 영역만·업데이트 요청 시 `kintex-completeness-orchestrator` 스킬을 사용하라. + +**에이전트:** kintex-gap-auditor(신규, 4자체인 감사·인벤토리) + kintex-qa(재사용, P0/P1 실동작 재검증·오탐 제거) + reviewer(선택, 정본 대조). + +**경계(중복 회피):** 확인=이 하네스. 수정·구현=kintex-impl-orchestrator, UI정렬=kintex-wise-ui-orchestrator, 데이터 빈 화면=kintex-renewal/testdata. ★오탐 주의: "코드 미개발" vs "데이터만 빈(행사스코프 시드 미정렬)" 구분. + +**변경 이력:** +| 날짜 | 변경 내용 | 대상 | 사유 | +|------|----------|------|------| +| 2026-07-12 | 초기 구성(kintex-gap-auditor 신규 + kintex-qa 재사용 + 오케스트레이터 + gap-taxonomy 레퍼런스) | 전체 | "미개발 확인 하네스 생성" 요청 — 4자체인 교차대조 완성도 감사 트랙 부재 | diff --git a/docs/GAP_AUDIT.md b/docs/GAP_AUDIT.md new file mode 100644 index 0000000..b80fb26 --- /dev/null +++ b/docs/GAP_AUDIT.md @@ -0,0 +1,107 @@ +# KINTEX AI 전시·행사시스템 — 미개발 전수 감사 (GAP_AUDIT) + +- 감사일: 2026-07-12 · 감사자: `kintex-gap-auditor` +- 범위: `src/frontend`(85 라우트) + `src/backend`(74 컨트롤러) 4자 체인(메뉴↔라우트↔화면↔API) 교차 대조 +- 방법: gap-taxonomy grep 신호 + 실코드 확인. 수정 없음(감사·문서 전용). + +--- + +## ① 요약 + +| 구분 | 건수 | +|------|------| +| **미개발 총계(코드)** | **9건** | +| P0 (핵심 흐름 차단) | 0 | +| P1 (주요 화면 미배선/백엔드 전무) | 2 | +| P2 (부분·degraded·planned) | 7 | +| 데이터 이슈(별도, 코드 정상) | 행사스코프 시드 정렬 계열(전수 아님) | +| 문서 위생(별도, 미개발 아님) | 4 (stale "미구현" 주석) | + +**총평:** 앱 완성도는 매우 높다. 데드 메뉴 0, 빈 onClick/데드링크 0, 최상위 `return null` 라우트 0. 도메인 백엔드(auction·visitor·cms·marketing·logistics·settlement·hallassign·boothsales)는 전부 실구현. **가장 중요한 발견은 "미개발이 많다"가 아니라 "프론트 `sample` 헤더 주석이 Phase D 백엔드 배포 이후 갱신되지 않아 STALE 하다"는 점** — 실제 미개발은 아래 2건(P1)뿐이며 나머지는 graceful degraded/planned. + +### 최우선(P0/P1) 목록 +1. **[P1] TenantAdminPage** — 백엔드 `/api/admin/tenants`(목록+생성) 실존하나 프론트가 하드코딩 배열·`disabled` 버튼으로 미배선. +2. **[P1] PublicTicketPage(입장권 예매/조회)** — 티켓·재고·결제(PG) 백엔드가 아예 없음(고객 대면 공개 라우트). + +--- + +## ② 4자 체인 인벤토리 (요약) + +메뉴 `to` 타깃 ↔ App.tsx 라우트 ↔ 화면 컴포넌트 ↔ 백엔드 매핑. **전 메뉴 항목이 라우트에 정합(데드 메뉴 없음).** + +| 메뉴 그룹 | 대표 라우트 | 화면 상태 | 백엔드 컨트롤러 | 판정 | +|-----------|-------------|-----------|-----------------|------| +| 전시운영 | `/exhibitors`,`/schedule`,`/halls`,`/booth-sales`,`/ops/operations`,`:eventId/dashboard` | 실렌더 | Exhibitor·EventCatalog·HallAssign·BoothSales·Ops·Dashboard | ✅ | +| 설계·시공 | `:eventId/halls/:hallId/layout`,`/docs`,`/auctions`,`/contractor/dashboard`,`/logistics` | 실렌더 | Floorplan(m2)·Design(m3)·Utility(m4)·RenderJob(m5)·Document·Auction·Contractor·Logistics | ✅ | +| 관람객·마케팅 | `/visitors`,`/visitors/checkin`,`/leads`,`/campaigns`,`/sponsorship`,`/cms` | 실렌더 | Visitor·Marketing·CmsContent/Media/Microsite | ✅ | +| 정산·분석 | `/settlement`,`/analytics` | 실렌더 | Settlement·Analytics(+Overview) | ✅ | +| 업무공통 | `/work/*`(worklog·schedule·message·notice·meeting·report·approval·opinion·search),`/app-qr` | 실렌더 | work.* 11 컨트롤러 | ✅ | +| 시스템관리(admin) | `/admin`,`/admin/users·roles·depts·companies·menus·programs·role-menus·codes·auth-policy·login-history·audit·error-log·system-health·settings·mail-config·notify-config·**tenants**·masterdata/rulesets·holidays·login-slides` | 실렌더 | system.* + AdminDashboard·AuditLog·Ruleset·**TenantAdmin** | ⚠ tenants만 미배선 | +| 공개사이트 | `/public/*`,`/visitor·/business·/agency`,`/tickets/*` | 실렌더 | PublicSite·PublicGuide·PublicCms·PublicMicrosite·VisitorAssistant | ⚠ `/tickets/*`만 백엔드 전무 | +| 모바일 셀프 | `/m/booths/:id/checklist·/inspection` | 실렌더(웹뷰) | (모바일 앱 `mobile/` 별도 트랙) | ✅ | + +--- + +## ③ 미개발 목록 (근거·유형·심각도·정본) + +### P1 — 주요 미배선/백엔드 전무 + +**G-01 [P1] 테넌트 관리 화면 미배선 (frontend)** +- 파일: `src/frontend/src/screens/admin/TenantAdminPage.tsx:20`(하드코딩 `TENANTS`), `:53`(추가 버튼 `disabled title=notImpl`) +- 유형: 미배선(백엔드 존재, 프론트 sample 고정) — CRUD 누락(목록·생성 미연결) +- 근거: 백엔드 `TenantAdminController`(`/api/admin/tenants` GET list + POST create) **실존**. 화면 주석 "백엔드 미구현"은 **STALE**. +- 정본: PLANNING M18 §1A(테넌트 온보딩), design.md SCR-A9. 기획에 있음. + +**G-02 [P1] 입장권 예매/조회 백엔드 전무** +- 파일: `src/frontend/src/screens/public/PublicTicketPage.tsx:5`(주석 "티켓·재고·결제 API 부재 → 샘플"), `:36`(하드코딩 `TICKETS`) +- 라우트: `/tickets/:eventId/purchase`, `/tickets/lookup` (공개, 비로그인 고객 대면) +- 유형: 미구현 API(백엔드 컨트롤러 부재 — grep "ticket" in backend = 0) + PG 미연동 +- 정본: PLANNING M10(관람객)·M9(정산) design.md SCR-P7. 기획에 있음. + +### P2 — 부분 미개발(정상 degraded/planned) + +| ID | 항목 | 근거(파일:라인) | 유형 | 비고 | +|----|------|-----------------|------|------| +| G-03 | CMS AI 자동번역 | `cms/MultilingualCmsPage.tsx:127` · `cmsApi.ts:150` | 백엔드 501 → degraded "준비 중" | 초벌 번역 AI 미배선 | +| G-04 | 마케팅 캠페인 실발송 | `marketing/MarketingController.java:49` | send 엔드포인트 존재하나 실 이메일 발송 미구현(status 전이만) | SMTP 연동 후속 | +| G-05 | 서류 HWP/PDF 생성 | `docs/ReportAuthoringPage.tsx:476-479` · `docs/DocsMilestonePage.tsx:34` | 버튼 `disabled` "준비중" | 파일 큐 연동 후속 | +| G-06 | 회의 녹음/STT 전사 | `work/MeetingPage.tsx:161` | "준비 중" 표기 | 녹음·STT AI 미배선 | +| G-07 | 통합검색 자연어(AI) | `work/SearchPage.tsx:132,144` | "준비 중", 키워드 검색은 동작 | AI 검색 미배선 | +| G-08 | 마이페이지 프로필 수정·알림 서버동기화 | `work/MyPage.tsx:127,192` | "준비 중"(로컬 저장) | 프로필 PUT·알림설정 API 미배선 | +| G-09 | 부스설계 렌더 이력 저장 | `design/BoothDesignStudioPage.tsx:135` | 501 → "큐잉만 동작" | 렌더 이력 영속 후속 | + +### 계획됨(미개발 아님) +- AppQr **iOS** — `appqr/AppQrCodePage.tsx`: Android APK 배포, iOS는 EAS G3 게이트 명시적 로드맵. + +--- + +## ④ 별도 분류 (미개발 아님) + +### 데이터 이슈 (코드 정상 · 담당=kintex-testdata-dev) +- sample-import 12개 화면(analytics·dashboard·ops·floorplan·gallery·marketing×2·schedule·visitor×3)은 **조건부 degraded 폴백**(NETWORK/NOT_FOUND/NOT_IMPLEMENTED 시에만 sample). 실 API가 primary. 빈 화면으로 보이면 코드가 아니라 **행사스코프 시드 미정렬** → testdata 담당. + +### 문서 위생 — STALE 주석 (기능 영향 없음 · 담당=frontend-dev 청소) +- `screens/auction/sampleAuction.ts:3` "M15 역경매 엔진 미구현" → 실구현됨 +- `screens/visitor/sampleVisitor.ts:3` "M10/M11 백엔드 미구현" → 실구현됨 +- `screens/marketing/sampleMarketing.ts:3` "M12 백엔드 미구현" → 실구현됨 +- `screens/cms/cms.css:4` "M17 백엔드 미구현 → 전 화면 로컬 샘플" → cmsApi 실배선됨 +→ 주석만 갱신(오해 유발). 재감사 오탐 방지 위해 정리 권장. + +--- + +## ⑤ 우선순위 백로그 (수정 담당 매핑) + +| 순위 | ID | 작업 | 담당 | +|------|-----|------|------| +| 1 | G-01 | TenantAdminPage → `/api/admin/tenants` GET/POST 배선(하드코딩·disabled 제거) | **kintex-frontend-dev** | +| 2 | G-02 | 티켓 도메인 백엔드(티켓·재고·예매·조회) + PG 어댑터 + 프론트 배선 | **kintex-backend-dev**(+visitor-dev) → frontend-dev | +| 3 | G-04 | 캠페인 실발송(SMTP, 수신동의·상한) | kintex-backend-dev(marketing) | +| 4 | G-03 | CMS AI 자동번역 배선(AiTextRouter) | kintex-ai-dev | +| 5 | G-06/G-07 | 회의 STT·통합검색 자연어 AI | kintex-ai-dev | +| 6 | G-05/G-09 | 서류 HWP/PDF 생성·렌더 이력 영속(파일/렌더 큐) | kintex-backend-dev | +| 7 | G-08 | 마이페이지 프로필 PUT·알림설정 API | kintex-backend-dev + frontend-dev | +| 8 | (위생) | STALE "미구현" 주석 4건 정리 | kintex-frontend-dev | +| — | (데이터) | 행사스코프 시드 정렬(빈 화면 폴백 해소) | kintex-testdata-dev | + +--- +*P0/P1 지적은 kintex-qa가 실동작(라우트 렌더·경계면)으로 재검증 후 kintex-impl-orchestrator로 인계.* diff --git a/docs/analysis/nifty-design-refs.md b/docs/analysis/nifty-design-refs.md index 7a585ef..69b2294 100644 --- a/docs/analysis/nifty-design-refs.md +++ b/docs/analysis/nifty-design-refs.md @@ -18,7 +18,7 @@ | 타이포그래피 | https://preview.themeon.net/nifty/ui-elements/ (typography) | 제목·본문·캡션 위계(kx 타이포 토큰) | 대기 | | 모달/메시지박스 | https://preview.themeon.net/nifty/ui-elements/ (modals) | 모달·확인/경고 메시지박스(kx Modal) | 대기 | | Offcanvas | https://preview.themeon.net/nifty/ui-elements/ (offcanvas) | 오프캔버스 패널(모바일 드로어·상세 슬라이드) | 대기 | -| 달력 | https://preview.themeon.net/nifty/app-views/calendar/ | 일정 캘린더(월/주/일)·이벤트 색/드래그·사이드 목록 | 대기(일정 3-뷰는 구조 완료, Nifty 스타일 정합 대상) | +| 달력 | https://preview.themeon.net/nifty/app-views/calendar/ | 일정 캘린더(월/주/일)·이벤트 색/드래그·사이드 목록 | 완료(KxCalendarView 재스타일 2026-07-12 — 옅은 격자·옅은 노랑 today 틴트·소프트 카테고리 칩(color-mix), 미니 `.kx-cal` 동일 톤. `.kx-mcal`는 기존 소프트 톤 유지) | | **UI-elements 전체** | **https://preview.themeon.net/nifty/ui-elements/** | **Nifty UI-elements 전 페이지를 킨텍스 공통 UI 기준으로 채택**(위 개별 + 나머지 요소 전부) | 기준 | | 셸·메뉴(기존) | WISE(UIWS) AppLayout/LeftNav/CalendarView | 셸·아코디언·캘린더 | 완료(구조) | | 상단바(top frame) | https://preview.themeon.net/nifty/ (navbar/header) | 상단 헤더 — 로고·검색·알림·설정·사용자 드롭다운·풀스크린·언어 | 대기 | diff --git a/docs/deliverables/01_개발계획서.md b/docs/deliverables/01_개발계획서.md new file mode 100644 index 0000000..a338ea0 --- /dev/null +++ b/docs/deliverables/01_개발계획서.md @@ -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 통과 후 최신 메뉴 반영). diff --git a/docs/deliverables/01_개발계획서.pptx b/docs/deliverables/01_개발계획서.pptx new file mode 100644 index 0000000000000000000000000000000000000000..dd3a43751ab04a2dad620e3eaec6a2355cb06560 GIT binary patch literal 48265 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 z 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⪙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 Tb~pfl68MJ!?6h#i{eJa-7t7jH literal 0 HcmV?d00001 diff --git a/docs/deliverables/02_설계서.md b/docs/deliverables/02_설계서.md new file mode 100644 index 0000000..36e190e --- /dev/null +++ b/docs/deliverables/02_설계서.md @@ -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, _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`{success·data·error{code,message}}, 목록 `PageResponse`{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 통과 후 최신 메뉴를 반영해야 하므로 이번 범위에서 제외). diff --git a/docs/deliverables/02_설계서.pptx b/docs/deliverables/02_설계서.pptx new file mode 100644 index 0000000000000000000000000000000000000000..905620e044647b95082b6cb35a56b53d70c32d51 GIT binary patch literal 44254 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 literal 0 HcmV?d00001 diff --git a/docs/design.md b/docs/design.md index 12f08bb..720afa0 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,6 +1,6 @@ # 킨텍스 AI 전시관리 시스템 — UI 디자인 스펙 (design.md) -> 작성: 디자인 에이전트(designer) · 작성일: 2026-07-12 · 버전: **v2.3.2** +> 작성: 디자인 에이전트(designer) · 작성일: 2026-07-12 · 버전: **v2.4** > 근거 문서: `docs/PLANNING.md` **v3.0**(모듈 맵 M1~M18 + §5B 공통/시스템관리 레이어 + §1A 멀티테넌시, 6+2 역할, 나노바나나 표준 샷 세트 S1~S7) · `docs/FEATURE_BACKLOG_100.md`(100대 기능 F001~F100·NFR 4축) · Stitch 산출물 `stitch_kintex_ai_system_architect/`(18화면 + DESIGN.md 3변형) 학습·정합 > **단일 출처(Single Source of Truth)**: 프론트 구현 토큰·컴포넌트 계약은 본 문서가 권위다. Stitch 화면(code.html/screen.png)은 시각 참조로만 사용하며, 상충 시 본 문서를 따른다. > 용도: **각 화면의 "Stitch 프롬프트" 코드블록을 Google Stitch(stitch.withgoogle.com)에 그대로 붙여넣어 화면을 생성**한다. 문서는 한국어, Stitch 프롬프트만 영어. @@ -1468,6 +1468,109 @@ A public "check & cancel my ticket booking" page for an exhibition, Korean marke > > **디자인 토큰**: §1 라이트 테마·`primary-600 #0066B3`·Pretendard·8px 카드·WCAG AA 준수. **신규 토큰 0.** AI 생성 이미지(나노바나나)는 워터마크 "AI 생성 예상 이미지" 상시 노출 규칙 유지. 이모지 금지·선(stroke) SVG 아이콘. +### 3C-V. 공개 사이트 비주얼·모션 스펙 (전 SCR-T*·SCR-P* 공통 비주얼 레이어 · v2.4 신설) + +> **v2.4 신설.** 소유자 확정("코엑스처럼 세련되게 전면 비주얼 리뉴얼 — 히어로 배경·애니메이션·비주얼, 지금 착수")에 따라, 기존 §3C 3-트랙(SCR-T0~T3)·§3B 공개 홍보(SCR-P*)의 **구조는 그대로 두고 그 위에 비주얼·모션 레이어만 얹는다**(구조 재설계 아님, 시각 격상). 대상: **미인증 공개 트랙 전체**(SCR-T0 관문·SCR-T1 visitor 메인·SCR-T2 business·SCR-T3 agency·SCR-P1~P8). **인증 업무 셸(AppShell/MDI)·관리자(SCR-A*)·모바일 앱은 대상 아님**(밀도·성능 우선 원칙 유지). **신규 컬러 토큰 0** — §1 팔레트를 CSS 변수로 주입하고, 본 절은 **모션 토큰·이미지 소스·비주얼 폴리시**만 신설한다. business/agency는 코엑스식 B2B 실무 톤을 유지하되 히어로 배경·리빌·카드 hover 등 **공통 모션만 절제 적용**(마케팅 과장 금지) — visitor(SCR-T1)만 풀 시네마틱 격상. + +**V-1. 비주얼 폴리시(공통 원칙)** + +- **풀블리드·공백 없음**(전역 NFR [[ui-fullbleed-responsive]] 준수): 히어로·밴드는 뷰포트 전폭, 좌우 거터 여백 금지. 콘텐츠는 `max-width 1440px` 중앙 정렬 그리드 안에서 리듬을 준다(밴드 배경만 전폭). +- **섹션 리듬**: 밴드(전폭 배경/딤) ↔ 카드 그리드(라이트 서피스) 교대. 섹션 세로 패딩 데스크톱 `96px`(태블릿 64px·모바일 40px), 섹션 헤더는 아이라인(eyebrow, 12px 트래킹 라벨)+H2. 인접 섹션 배경 톤을 번갈아 대비. +- **여백·라운드·그림자 토큰**(§1-4 재사용): 카드 라운드 8px 상한, 칩 pill. 그림자는 무거운 박스섀도 대신 **소프트 앰비언트**(hover 시에만 Level 2로 상승, Y8 Blur24 12%). 히어로/밴드 위 텍스트는 딤 오버레이로 대비 확보. +- **타이포 위계 강화**: 히어로 헤드라인 Display(1순위 visitor는 `clamp(32px,4vw,56px)` 확대 허용), 서브카피 H3, 섹션 eyebrow+H2. 숫자(통계·D-day·기간)는 tabular numerals. +- **선(stroke) SVG 아이콘 전용·이모지 금지**(불변). AI 산출물은 §1-4 AiLabel(보라)+워터마크 규칙 유지. +- **다크/라이트 양 테마**(SCR-T1은 토글 제공 — §3C SCR-T1 예외 규정): 히어로는 시네마틱 딤 톤(다크 계열 오버레이)이 기본 인상, 본문 섹션은 라이트 기본. 색은 CSS 변수 주입(hex 하드코딩 금지)로 양 테마 대응, **양 테마 WCAG AA**. +- **포스터/이미지 카드 정책**: visitor 쇼케이스·행사 레일은 큰 세로 2:3 포스터 카드 그리드/캐러셀. `poster_url`/배경 이미지 부재 시 **브랜드 그라디언트 + 옅은 기하 패턴(그리드/도트) 폴백**(V-4). + +**V-2. 모션 토큰(신설 — duration/easing, CSS 커스텀 프로퍼티)** + +| 토큰 | 값 | 용도 | +|---|---|---| +| `--motion-fast` | `150ms` | hover 색/보더, 칩 상태 | +| `--motion-base` | `240ms` | 카드 리프트·이미지 줌·버튼 | +| `--motion-slow` | `400ms` | 스크롤 리빌 기본 | +| `--motion-hero` | `600ms` | 히어로 인트로·대형 리빌 | +| `--motion-countup` | `1200ms` | 숫자 카운트업 | +| `--ease-emphasized` | `cubic-bezier(0.2,0,0,1)` | 리빌·히어로(감속 강조) | +| `--ease-standard` | `cubic-bezier(0.4,0,0.2,1)` | 일반 hover/전환 | +| `--ease-decelerate` | `cubic-bezier(0,0,0.2,1)` | 진입(카운트업·슬라이드인) | + +**V-3. 섹션별 배경·애니메이션 트리거 매핑** + +| 섹션(적용 화면) | 배경 | 애니메이션 | 트리거·파라미터 | +|---|---|---|---| +| **풀블리드 히어로**(T0·T1·T2·T3·P1) | `/media/hero/*.jpg`(전시장 전경) + 딤 그라디언트 오버레이 + AI 워터마크. 없으면 **그라디언트+패턴 폴백** | ① 인트로 리빌(로드 시 헤드라인·CTA opacity+translateY(16px)→0, `--motion-hero`) ② **라이트 파랄랙스**(배경 레이어 scroll에 `translate3d(0,y*0.18,0)`, 전경/텍스트는 고정 또는 y*0.06) | 인트로=마운트 1회 · 파랄랙스=scroll(rAF 스로틀, 데스크톱만·모바일/reduced-motion 비활성) | +| **통계 밴드**(T1·P1·P2) | 라이트 서피스 또는 밴드 딤 | **숫자 카운트업**(0→목표, `--motion-countup`, ease-decelerate) | IntersectionObserver 최초 진입 1회(`.pub-countup[data-count-to]`) | +| **AI 기능 쇼케이스 타일**(T1) | 라이트, AI 카드 `ai-surface #F5F3FF` | **스태거 리빌**(카드 순차 fade/slide-up 60~80ms 간격) + hover 리프트 | 컨테이너 진입 시 스태거 1회 · hover=`--motion-base` | +| **전시 쇼케이스/행사 레일**(T0·T1·P1) | 라이트 | ① 카드 **hover 리프트**(translateY(-4px)+그림자) + **이미지 줌**(scale 1.04, overflow hidden 컨테이너) ② 레일 **오토스크롤**(가로) | hover=`--motion-base` · 오토스크롤=`data-interval`(기본 4500ms), hover/focus 시 일시정지, reduced-motion 시 미동작 | +| **AI 기획 브리핑(연/월)**(T1) | 라이트 | 타임라인 바 진입 시 좌→우 grow(scaleX) + AI 요약 태그 fade-in | 진입 1회 · 연/월 전환 시 콘텐츠 크로스페이드(`--motion-base`) | +| **방문 가이드/가치제안 카드**(T1·T2·T3) | 라이트 | 스태거 리빌 + hover 리프트 | 진입 스태거 1회 | +| **전시장 소개/CTA 밴드**(T1·T2·T3·P2) | `/media/bands/*.jpg`(전폭 밴드) + 딤. 없으면 그라디언트 폴백 | 밴드 진입 리빌(fade/slide-up) · 배경 미세 파랄랙스(선택, 약 y*0.08) | 진입 1회 | +| **공통 섹션 블록 전반** | — | **스크롤 리빌**(opacity 0→1 + translateY(20~24px)→0, `--motion-slow` ease-emphasized) | IntersectionObserver `threshold 0.15`·`once`(재역방향 재생 금지) | + +**V-4. 이미지 소스 매핑(`/media/` — build 내장, 폴백 규칙)** + +- **원칙**: 히어로/밴드 비주얼은 **프론트 build 내장 정적 에셋 `/media/`** 에서 로드(외부 실시간 호출 없음·온프레미스, CDN 캐시). 나노바나나 AI 생성 전경은 워터마크 "AI 생성 예상 이미지" 상시. 파일 부재 시 **그라디언트+패턴 폴백**으로 무공백 렌더(레이아웃 시프트 0). +- **디렉터리 계약(제안)**: + - `/media/hero/` — 풀블리드 히어로 배경. `hero-gateway.jpg`(T0)·`hero-visitor.jpg`(T1)·`hero-business.jpg`(T2)·`hero-agency.jpg`(T3) (+`@2x`·`.webp` 우선, `.jpg` 폴백) + - `/media/hero/fg/` — 라이트 파랄랙스용 전경 레이어(선택, 투명 PNG/webp) + - `/media/bands/` — 섹션 밴드 배경(`band-venue.jpg` 전시장 소개 등) + - `/media/poster/` — 행사 포스터 폴백 placeholder(그라디언트 생성) + - `/media/patterns/` — 폴백 패턴 SVG(`grid.svg`·`dots.svg`·`mesh.svg`, `fill:none;stroke:currentColor` 라인 패턴) +- **로딩 성능**: 히어로 이미지는 ``+`fetchpriority="high"`·`loading="eager"`, 폴드 하단 포스터/밴드는 `loading="lazy"`+`decoding="async"`. `srcset`/`sizes`로 DPR·뷰포트 대응, `.webp` 우선 ``. +- **폴백 그라디언트**: `linear-gradient(135deg, var(--primary-700), var(--primary-600))` + 패턴 SVG 오버레이(투명도 8~12%). 포스터 폴백은 그라디언트 위 행사명 텍스트 중앙(SCR-HOME 폴백 규칙과 동일). + +**V-5. 접근성(prefers-reduced-motion) — 필수** + +- `@media (prefers-reduced-motion: reduce)`: **파랄랙스 전면 비활성**(배경 고정)·**오토스크롤 정지**(수동 화살표만)·**카운트업은 최종값 즉시 표시**·스크롤 리빌은 **transform 제거·opacity 1 즉시**(콘텐츠 지연 노출 금지)·hover 대형 transform 억제(색/보더 전환만 허용). +- 애니메이션은 **전부 장식**이며 정보 전달·조작 가능성을 모션에 의존하지 않는다(모션 꺼도 전 콘텐츠·기능 동등 접근). 포커스 링(`primary-600` 2px)·`aria-live`(AI 답변) 규정은 §3C 원본 유지. +- 히어로 배경 위 텍스트는 딤 오버레이로 **양 테마 WCAG AA** 대비 확보. 색만으로 상태 전달 금지(배지 텍스트 병기). +- 모션 정지 시에도 레이아웃 시프트 0(리빌은 최종 위치 기준, 이미지 폭·높이 예약). + +**V-6. 성능 가이드(CSS 우선·60fps)** + +- **CSS 우선**: 스크롤 리빌·hover·오토스크롤은 **CSS `transition`/`@keyframes` + IntersectionObserver로 클래스 토글**(`.is-visible`)로 구현. 애니메이션 라이브러리 불필요(기본 리빌엔 JS 애니메이션 금지). 파랄랙스만 `requestAnimationFrame` 스로틀 스크롤 핸들러(passive listener). +- **컴포지터 친화 프로퍼티만 애니메이트**: `transform`·`opacity`만 사용. `width/height/top/left`·`box-shadow` 직접 애니메이트 금지(그림자는 pseudo-element opacity 트랜지션으로 대체). `will-change`는 애니메이션 직전 부여·종료 후 제거(상시 부여 금지). +- **60fps 목표**: 파랄랙스 계산 rAF 1프레임 1회·오프스크린 요소 무시. 오토스크롤은 `transform: translateX` + `ScrollTimeline` 또는 rAF, hidden 탭에서 정지(`visibilitychange`). +- **이미지**: 위 V-4 preload/lazy·webp·srcset. 폴드 상단 CLS 방지(에스펙트 비율 예약). 저사양/데이터세이버(`prefers-reduced-data`) 시 히어로 정적·패턴 폴백으로 강등. +- **60fps 미달 시 강등 순서**: 파랄랙스 → 오토스크롤 → 스태거(→ 최종 리빌만 유지). 리빌은 항상 유지 가능한 최소 비용. + +**V-7. 프론트 구현 계약(클래스/훅 ↔ 모션 매핑 — kintex-frontend-dev)** + +- **CSS 커스텀 프로퍼티**(공개 트랙 루트에 주입): `--motion-fast/base/slow/hero/countup`, `--ease-emphasized/standard/decelerate`(V-2 값). §1 컬러도 동일 방식 변수 주입(라이트/다크). +- **`.pub-reveal`** + IntersectionObserver로 `.is-visible` 토글 → opacity+translateY 리빌(공통 섹션 블록·카드). **`.pub-reveal--stagger > *`** = 자식 순차 지연(`--i` 인덱스 * 70ms). +- **`.pub-hero`** > **`.pub-hero__bg`**(파랄랙스 레이어, `data-parallax="0.18"`)·`.pub-hero__fg`(전경 y*0.06)·`.pub-hero__overlay`(딤). 인트로는 마운트 시 `.is-in`. +- **`.pub-card--lift`**(hover translateY(-4px)+그림자 pseudo) · **`.pub-card__media`**(overflow hidden) > `img`(hover `scale(1.04)`). +- **`.pub-countup`**(`data-count-to`·`data-suffix`) = IO 진입 1회 카운트업, reduced-motion 시 최종값. +- **`.pub-rail--auto`**(`data-interval`) = 오토스크롤 레일(hover/focus/visibility 정지, reduced-motion 미동작). +- **폴백 유틸**: `.pub-fallback-gradient`·`.pub-pattern-grid|dots`(V-4). 이미지 `onerror`→폴백 클래스 토글. +- **적용 범위 가드**: 위 클래스·훅은 **PublicShell 하위(공개 트랙)만** 스코프. AppShell/MDI·SCR-A*·모바일 앱에는 미주입(성능·밀도 보존). +- **src 무수정**: 본 절은 스펙만 — 실제 클래스/CSS 구현은 kintex-frontend-dev 후속. + +**V-8. Stitch 프롬프트 — 프리미엄 전시장 공개 홈페이지(비주얼·모션 격상판, SCR-T1 재생성용)** + +``` +A premium, visually striking public homepage for "KINTEX AI 전시·행사시스템" (an AI exhibition & event operations platform), the visitor track, for the general public — styled like a world-class convention-center website (COEX / Messe / global expo caliber). Korean UI text only, exactly as given; desktop 1440px, full-bleed responsive with NO empty side gutters; support BOTH light and dark theme (keep body sections light by default, but render the hero as a cinematic dark-tinted centerpiece). Primary blue #0066B3, deep #004C86, AI purple #6D4AFF; use line (stroke) SVG icons only, NO emoji. Product name must read exactly "KINTEX AI 전시·행사시스템". + +HERO (full-bleed, cinematic, the centerpiece): a large photorealistic exhibition-hall panorama photograph as the background, covered by a subtle dark-to-transparent gradient overlay for text contrast and a faint corner watermark "AI 생성 예상 이미지". Give it depth like a light parallax scene (a softly blurred far background plus a crisper foreground layer). Over it, a strong typographic hierarchy: a large display headline "무엇이든 물어보세요 — AI가 관람을 안내합니다", a subheading "전시 일정 · 오시는 길 · 입장권 · 부스 위치를 대화로 한 번에", and the visual focus — a wide conversational AI assistant search bar with a small purple "AI 도우미" tag and a send (paper-plane) icon, plus example question chips "이번 주말 로봇 전시 있어? · 킨텍스 주차 어디에 해요? · 가족과 볼만한 전시 추천". Under the hero: two CTAs "사전등록" and "입장권 예매", and a subtle animated scroll-down cue. + +Design should imply tasteful, restrained, performant motion: sections fade / slide up on scroll, KPI numbers count up, poster cards lift with a soft shadow and their images zoom on hover, and a poster rail auto-scrolls. + +Below the hero, with generous section rhythm (alternating full-bleed bands and light card grids): (1) a stats band "누적 전시 1,200+ · 참가업체 30,000+ · 연 관람객 500만+" in big tabular numerals (implying count-up). (2) an "AI가 도와드립니다" capability showcase of 4–6 cards, each with a purple "AI" tag, a stroke icon, a title and one line ("AI 관람 도우미", "AI 전시 추천", "AI 길찾기", "AI 일정 도우미", "AI 혼잡·대기 안내", "AI 시공 예상 이미지"). (3) an "AI 전시·행사 기획 브리핑" section with a "연간 / 월간" toggle and a 12-month timeline plus a purple "AI 요약" headline. (4) a rich "진행 중 · 예정 전시" showcase — a grid/carousel of large 2:3 exhibition poster cards (poster image, title, dates "2026.09.05–09.08", hall label, a status badge, a "D-25" day chip, "사전등록"), with tabs "진행 중 | 이번 달 | 다음 달" and a venue filter "전체 · 제1전시장 · 제2전시장". (5) a "방문 가이드" 2×3 grid of stroke-icon cards ("교통 · GTX-A 킨텍스역 도보 3분", "주차 · 실시간 안내", "실내 길찾기", "편의시설", "접근성 · 휠체어·수어통역", "고객문의"). (6) a full-width "전시장 소개" band with a background photograph and KINTEX scale figures. Footer with company info, "오시는 길 · GTX-A 킨텍스역", SNS, language. + +Where poster or hero/band images are missing, fall back to a branded blue gradient with a faint geometric grid/dot line-pattern overlay (no blank areas, no layout shift). Sophisticated, immersive, conversion-oriented global exhibition-center aesthetic; rounded 8px cards, soft ambient shadows, tabular numerals, strong type hierarchy, WCAG AA contrast in BOTH themes; all motion must respect prefers-reduced-motion (parallax and auto-scroll disabled, reveals instant when reduced). +``` + +**V-9. Stitch 프롬프트 — 3-트랙 관문 비주얼 격상판(SCR-T0 재생성용, 동일 톤)** + +``` +Re-generate the "track gateway" landing page for "KINTEX AI 전시·행사시스템" with a premium, cinematic visual upgrade in the same tone as the visitor homepage. Korean UI only, desktop 1440px, full-bleed (no side gutters), light theme body with a dark-tinted cinematic hero; primary blue #0066B3, AI purple #6D4AFF; line (stroke) SVG icons only, NO emoji. + +Full-bleed HERO: a photorealistic exhibition-hall panorama background with a dark-to-transparent overlay and a faint watermark "AI 생성 예상 이미지", light-parallax depth, a large display headline "KINTEX AI 전시·행사시스템", a subheading "전시 기획부터 시공·관람까지 전 과정을 AI로 자동화", and CTAs "관람 사전등록" and "참가 문의". Keep the top header with the wordmark, the 3-tab track switcher "관람 · 비즈니스 · 협력사" (none active), and utilities "언어 · 로그인". + +Directly below, three large branching track cards in one row that lift on hover with an image zoom — card 1 "관람객" (slightly larger, primary) "행사 일정 · 사전등록 · 오시는 길" button "관람 트랙 →"; card 2 "주최 · 참가 (비즈니스)" "대관 · 자동견적 · 부스 신청 · 시공 시안" button "비즈니스 트랙 →"; card 3 "공사 · 협력사 (에이전시)" "등록업체 · 공사 옥션 · 작업신고" button "협력사 트랙 →". Then a "진행 중 · 예정 전시" auto-scrolling rail of 2:3 poster cards (poster, title, dates, hall, "D-25" chip, "사전등록"). Sections fade/slide up on scroll. Footer with "오시는 길 · GTX-A 킨텍스역". Missing images fall back to a branded gradient with a faint grid/dot pattern. Rounded 8px cards, soft shadows, tabular numerals, WCAG AA; all motion respects prefers-reduced-motion. +``` + ### 3C-0. 공개 셸(PublicShell) + 3-트랙 스위처 (전 SCR-T*·SCR-P* 공통 크롬) - **재사용 전제**: SCR-P*가 쓰는 **PublicShell**(전통 페이지 헤더 GNB·푸터·SSR/CDN·MDI 비적용)을 그대로 재사용하되, **상단에 3-트랙 전환 탭(TrackSwitcher)을 추가**한다. 관문(SCR-T0)은 트랙 미선택 상태, 서브홈(T1~T3)은 해당 트랙 탭 활성. @@ -1805,6 +1908,7 @@ A mobile "my ticket wallet" screen for an exhibition visitor, 390px width, light | 버전 | 일자 | 작성자 | 내용 | |---|---|---|---| +| **v2.4** | 2026-07-12 | designer | **공개 사이트 비주얼·모션 전면 리뉴얼 스펙 신설(소유자 확정: "코엑스처럼 세련되게 — 히어로 배경·애니메이션·비주얼, 지금 착수").** §3C에 **3C-V 공개 사이트 비주얼·모션 스펙** 절 순증 — 기존 SCR-T0~T3·SCR-P* **구조는 불변**, 그 위에 비주얼/모션 레이어만 격상(구조 재설계 아님). ①**비주얼 폴리시(V-1)** 풀블리드·공백 없음·섹션 리듬(밴드↔카드 교대·96/64/40px)·여백·라운드 8px·소프트 앰비언트 그림자·타이포 위계 강화·선 SVG·이모지 금지·다크/라이트·2:3 포스터 카드. ②**모션 토큰(V-2)** duration(`--motion-fast 150`/`base 240`/`slow 400`/`hero 600`/`countup 1200`)·easing(`--ease-emphasized/standard/decelerate`) 신설(컬러 신규 토큰 0). ③**섹션별 배경·애니메이션 트리거 매핑(V-3)** 풀블리드 히어로(라이트 파랄랙스+인트로 리빌)·통계 카운트업·AI 타일 스태거·쇼케이스 hover 리프트/이미지 줌·행사 레일 오토스크롤·기획 브리핑 grow·공통 스크롤 리빌(IO threshold 0.15·once). ④**이미지 소스 매핑(V-4)** build 내장 `/media/`(hero·bands·poster·patterns 계약)·부재 시 그라디언트+패턴 폴백(레이아웃 시프트 0)·preload/lazy/webp/srcset. ⑤**접근성(V-5)** `prefers-reduced-motion` 필수 — 파랄랙스/오토스크롤 정지·카운트업 즉시·리빌 instant·모션은 전부 장식(정보 비의존)·양 테마 WCAG AA. ⑥**성능(V-6)** CSS 우선·`transform`/`opacity`만·`will-change` 절제·60fps·강등 순서. ⑦**프론트 계약(V-7)** `.pub-reveal`/`--stagger`·`.pub-hero__bg[data-parallax]`·`.pub-card--lift`·`.pub-card__media img` 줌·`.pub-countup`·`.pub-rail--auto`·폴백 유틸, **PublicShell 하위만 스코프**(AppShell/MDI·SCR-A*·모바일 미주입). ⑧**영어 Stitch 프롬프트 2종(V-8·V-9)** — 프리미엄 전시장 홈페이지(SCR-T1 시네마틱 히어로·스크롤 리빌·카운트업·hover 줌·오토스크롤·폴백·reduced-motion, 제품명 정확·NO emoji·선 SVG·라이트/다크) + 3-트랙 관문(SCR-T0 동일 톤 격상). **경계 준수**: visitor(T1)만 풀 시네마틱, business/agency는 B2B 톤 유지+공통 모션 절제 적용. 신규 컬러 토큰 0·PLANNING.md·src 무수정. | | **v2.3.2** | 2026-07-12 | designer | **SCR-T1 — AI 전시·행사 기획 브리핑(연간·월간 선제 제시) 추가(소유자 추가 지시 2026-07-12).** 단발 질의응답을 넘어 **AI가 개최 예정 전시·행사를 먼저 큐레이션**하는 섹션을 히어로 AI 능력 타일 직하(레이아웃 4번)에 신설 — **연/월 관점 토글**(① 연간=12개월 타임라인+분기 하이라이트+AiLabel "AI 요약" 헤드라인, ② 월간=선택 월 캘린더/리스트+AI 월간 요약), 연/월 전환 시 AI가 해당 기간 데이터 근거로 요약·추천 갱신. **중복 회피**: 하단 "진행 중/이번 달/다음 달" 쇼케이스(레이아웃 5)와 달리 본 섹션은 **연간 기획 관점**이 차별점, 월간은 동일 event 소스로 정합. 구성요소 `AiPlanningBriefing` 추가, 이하 레이아웃 5~11 재번호. **데이터 원천** — 신규 공개 엔드포인트 제안 `GET /api/public/events/planning?view=year|month&year&month`(크롤 적재 `exhibition`/`event`를 개최연·월 그룹 조회, AI 기간 요약은 AiTextRouter Claude→Ollama 폴백이 해당 기간 DB만 근거·근거 인용·캐시, DB 서빙·외부 실시간 호출 없음, 계약 제안). 상태(로딩·성공·**degraded(요약 미가용→데이터만 노출)**·빈·에러·기간 전환 부분 로딩) 정의. **Stitch 프롬프트 갱신** — 연간 타임라인+"AI 요약" 헤드라인("2026년은 상반기 스마트팩토리·로봇, 하반기 헬스케어·친환경…")+월간 요약("이번 달은 로봇·헬스케어 전시가 집중됩니다") 문단 추가, 쇼케이스와 구별 명시. 세련된 홈페이지 톤·풀블리드·반응형·라이트/다크·크롤 적재 DB 근거 원칙은 유지. 신규 토큰 0·PLANNING.md·src 무수정 | | **v2.3.1** | 2026-07-12 | designer | **SCR-T1 마무리 — AI 전면 노출 + AI 답변 근거=크롤 적재 DB 명시(소유자 추가 지시 2건 2026-07-12).** ①**AI 기능 쇼케이스 타일 신설**(히어로 직하 레이아웃 3번): "AI 시스템인데 AI가 어디 있는지 모르겠다" 지적 반영 — AI 관람 도우미·AI 전시 추천·AI 길찾기·AI 일정 도우미·AI 혼잡/대기 안내·AI 시공 예상 이미지 6능력을 AiLabel 카드로 전면 홍보(구성요소 AiCapabilityTiles·상태(정적 상시·미가용 "준비 중")·이하 레이아웃 4~10 재번호). ②**데이터 원천 = 사전 크롤 적재 온프레미스 DB 명시**: AI 관람 도우미(`POST /api/public/ai/visitor-assistant`)가 **실시간 외부 크롤이 아니라 미리 크롤해 DB에 적재한 공개 데이터만을 RAG 근거로 답하고 참조 레코드를 인용**(범위 밖=degraded)함을 명문화 — 근거 테이블 계약 예시(`exhibition`/`event`·`transport_info`·`parking_info`·`ticket_policy`·`amenity_info`·`visitor_guide`·`floorplan`), 각 섹션 출처를 크롤 적재 DB로 매핑, degraded 폴백을 "크롤 적재 데이터 검색"으로 갱신, 백엔드 계약을 "공개 조회 API가 크롤 적재 DB에서 서빙(외부 실시간 호출 없음·온프레미스·근거 인용)" 전제로 기술. ③**Stitch 프롬프트 갱신** — AI 능력 쇼케이스 카드 6종 문구 + "served from a pre-crawled on-premise database, AI answers ONLY from stored data and cites its source" 추가. 세련된 홈페이지 톤·풀블리드·반응형·라이트/다크는 v2.3 그대로 유지. 신규 토큰 0·PLANNING.md·src 무수정 | | **v2.3** | 2026-07-12 | designer | **SCR-T1 visitor 메인을 "세련된 공개 마케팅 홈페이지"로 격상 + 중심 요소를 대화형 AI 관람 도우미로 재구성(소유자 지시 2026-07-12).** ①**§3C SCR-T1 전면 재작성** — 기존 "visitor 서브홈(가이드 허브)"을 일반 대중 대상 마케팅 홈페이지(코엑스/글로벌 전시장 홈 수준·풀블리드·타이포 위계·공백 없는 밀도·전환 지향)로 격상. **화면 중심 = AI 관람 도우미**(히어로 정중앙 대형 자연어 질문 입력창 + 예시 질문칩 5종 + 대화형 답변 패널: 답변 텍스트·구조화 결과 카드(전시/교통/입장/wayfinding)·후속 질문칩·AI 고지문·근거 출처, AiLabel 보라 구분). 관람객 관심 우선순위 5(전시 추천·교통·입장·부스위치·편의)를 자연어 하나로 관통. 하위 섹션 순증: 전시 쇼케이스(2:3 포스터 그리드/캐러셀·탭·전시장 필터·`poster_url`)·관심사 맞춤 추천 레일(M11 AI)·방문가이드 허브(2×3, GTX-A·주차·M13 길찾기·편의·접근성·문의)·관람객 여정 3-스텝(사전등록→배지/QR→체크인)·공지·뉴스·전시장 소개·푸터(회사·SNS·문의). ②**데이터 원천** — **신규 공개 엔드포인트 제안 `POST /api/public/ai/visitor-assistant`**(비인증·CORS·레이트리밋·PII 미수집, 기존 AiTextRouter Claude→Ollama 폴백 + 공개 행사 데이터 근거·환각방지, **계약 제안·백엔드 후속**), 나머지 `/api/public/events`·notice·CMS 재사용. ③**상태** — AI 도우미 대기/생각중/성공/**degraded(AI 미가용→키워드 검색 폴백)**/에러/빈결과 정의. ④**테마** — 공개 마케팅 홈페이지에 한해 **라이트/다크 테마 토글 제공(§1-5 업무 셸 라이트 단일 원칙의 공개 트랙 예외)**, 색은 §1 토큰 CSS 변수 주입·라이트 기본. ⑤**접근성** — AI 입력 `role="search"`·답변 `aria-live="polite"`·색 외 라벨·양 테마 WCAG AA, i18n `track.visitor.*`. ⑥영어 **Stitch 프롬프트 전면 갱신**("premium exhibition-center marketing homepage"·1440 full-bleed·AI 관람 도우미 centerpiece·제품명 "KINTEX AI 전시·행사시스템" 정확 표기·NO emoji/line SVG·라이트·다크). ⑦**§2.8-2 인벤토리** SCR-T1 행 명칭/모듈 갱신. **경계 준수**: business(SCR-T2)·agency(SCR-T3) 메인은 B2B 업무 톤 유지(격상 대상 아님) 명시, 라우트 `/visitor`·PublicShell·TrackSwitcher·세션키·기존 SCR 불변, **신규 토큰 0**, PLANNING.md·src 무수정 | diff --git a/src/frontend/src/components/ui/icons.tsx b/src/frontend/src/components/ui/icons.tsx index d35ee93..73eff2a 100644 --- a/src/frontend/src/components/ui/icons.tsx +++ b/src/frontend/src/components/ui/icons.tsx @@ -499,6 +499,140 @@ export function IconShieldCheck(p: IconProps) { ); } +/** ⚡ 번개 — 빠른 작업·즉시 실행. */ +export function IconBolt(p: IconProps) { + return ( + + + + ); +} + +/** ⧗ 활동·최근(시계). */ +export function IconClock(p: IconProps) { + return ( + + + + + ); +} + +/** ✉ 메시지·쪽지(봉투). */ +export function IconMail(p: IconProps) { + return ( + + + + + ); +} + +/** 🗨 의견·댓글(말풍선). */ +export function IconChat(p: IconProps) { + return ( + + + + ); +} + +/** ⇄ 승인·결재(체크리스트). */ +export function IconApproval(p: IconProps) { + return ( + + + + + + ); +} + +/** ✎ 업무일지·기록(펜). */ +export function IconEdit(p: IconProps) { + return ( + + + + + ); +} + +/** ⚑ 마일스톤·깃발. */ +export function IconFlag(p: IconProps) { + return ( + + + + + ); +} + +/** ⛟ 물류·트럭. */ +export function IconTruck(p: IconProps) { + return ( + + + + + + + ); +} + +/** ◈ 태그·라벨. */ +export function IconTag(p: IconProps) { + return ( + + + + + ); +} + +/** ◫ 목록·리스트. */ +export function IconList(p: IconProps) { + return ( + + + + + + + + + ); +} + +/** ◔ 파이·상태분포. */ +export function IconPie(p: IconProps) { + return ( + + + + + ); +} + +/** ⚲ 방문·핀(위치). */ +export function IconMapPin(p: IconProps) { + return ( + + + + + ); +} + +/** ★ 별(하이라이트·추천). */ +export function IconStar(p: IconProps) { + return ( + + + + ); +} + /** ● 오프라인(연결 끊김). */ export function IconWifiOff(p: IconProps) { return ( diff --git a/src/frontend/src/screens/admin/AdminDashboardPage.tsx b/src/frontend/src/screens/admin/AdminDashboardPage.tsx index a810ca9..1b8a7d8 100644 --- a/src/frontend/src/screens/admin/AdminDashboardPage.tsx +++ b/src/frontend/src/screens/admin/AdminDashboardPage.tsx @@ -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() {
-

{t('admin.hourlyVisitors')}

+

{t('admin.hourlyVisitors')}

{data.visitorTrend.length === 0 ? ( @@ -130,7 +131,7 @@ export function AdminDashboardPage() {