kintex/docs/architecture/data.md

882 lines
59 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 킨텍스 자동전시시스템 — 데이터 아키텍처 (A-4)
> 작성: kintex-data-architect(DA) · 작성일: 2026-07-11 · 버전: **v1.0**
> 근거: [`docs/PLANNING.md`](../PLANNING.md) v2.0(§5A M15/M16·§5B 공통코드·§7 ERD·§8 아키텍처) · [`_workspace/01_backend_contracts.md`](../../_workspace/01_backend_contracts.md)(P0 API 계약·§8 매퍼 인수) · [`docs/COMMON_CODES.md`](../COMMON_CODES.md)(공통코드) · [`docs/assets/floorplans/README.md`](../assets/floorplans/README.md)(홀 실측·트렌치 CAD) · 룰셋 `rulesets/compliance-v1.json`·`rates-v1.json`
> **문서 소유권(DA 트랙)**: 본 문서는 **데이터 모델·표준·거버넌스의 단일 출처**다. 물리 스키마(DDL·PostGIS·MyBatis 매퍼 XML) **구현은 kintex-db-engineer**가 담당하며, 본 문서는 그 구현 대상(target model)·표준·검수 기준을 정의한다. DA는 설계·표준·검수만 하고 `src/backend/**/db`·매퍼는 수정하지 않는다.
> 정합 대상: A-1 app.md(AA)·A-2 system.md(SA)·A-3 tech.md(TA)·A-5 network.md(NA) — 상충 발견 시 A-6(reviewer) 티켓화.
---
## 0. 범위·계층·원칙
### 0-1. 데이터 아키텍처 스코프
| 계층 | 대상 | 저장소 |
|---|---|---|
| 공통·시스템관리 (WISE/UIWS 이식) | 사용자·역할·공통코드·메뉴·감사·업무모듈 | PostgreSQL `TB_*` |
| 도메인 (킨텍스 코어 P0) | 행사·홀·부스·설계·유틸리티·렌더잡 | PostgreSQL + **PostGIS** |
| 도메인 (v2.0 확장) | 옥션·관람객/리드·CMS·마스터데이터 | PostgreSQL |
| 마스터·룰셋 (버전 관리 데이터) | 홀·요율·유틸요금·규정 룰셋·등록업체 | 파일(룰셋 JSON) + `TB_*` 마스터 |
| BI 데이터마트 (M16) | Fact/Dim 스타 스키마 + KpiSnapshot | PostgreSQL(별도 스키마 `mart`) / 읽기 전용 복제 |
| 대용량 바이너리 | 도면·생성 이미지·서식·견적 PDF | 오브젝트 스토리지(경로만 DB) |
### 0-2. 설계 원칙 (불변)
1. **단일 공간 원천**: 부스 폴리곤·트렌치 포인트·배선 LineString은 **PostGIS 단일 지오메트리 원천**. M2 검증·M4 라우팅·M5 시각화·M13 wayfinding·M14 부하집계·M16 ㎡당 수익이 **같은 지오메트리를 재사용**(PLANNING §7-3 불변, §4 설계원칙 (1)).
2. **룰셋은 데이터**: 요율·규정은 코드가 아닌 **버전 관리 파일**(`compliance-v*.json`·`rates-v*.json`). 모든 산출물에 `rulesetVersion`·`disclaimer` 각인(감사·면책). 마스터데이터 개정은 M18 백오피스에서 무중단 반영.
3. **PII 최소수집·분리·암호화**: 관람객/리드 개인정보는 §5 분류·보존·동의·암호화 정책을 강제. 민감 컬럼은 API 응답에서 완전 제외(계약 §0-3).
4. **운영/분석 분리**: 경영 지표는 운영 DB 직조회 금지 — **BI 데이터마트(스타 스키마)** 배치 적재 또는 읽기 전용 복제로 운영 부하 회피(PLANNING §8-1).
5. **이식 우선(재설계 금지)**: 공통·시스템·인증 스키마는 `workspace/uiws` `TB_*`를 이식(멱등 DDL). 킨텍스 고유는 도메인 테이블에만.
---
## 1. 전사 데이터 모델 — 개념(Conceptual)
### 1-1. 개념 ERD (도메인 영역)
```mermaid
erDiagram
EVENT ||--o{ HALL_ASSIGNMENT : "배정"
HALL ||--o{ HALL_ASSIGNMENT : "가용"
HALL_ASSIGNMENT ||--o{ LAYOUT : "배치안(버전)"
LAYOUT ||--o{ BOOTH : "부스(폴리곤)"
HALL ||--o{ TRENCH : "트렌치 그리드"
BOOTH ||--o{ DESIGN_PLAN : "설계안(버전)"
BOOTH ||--o{ UTILITY_ORDER : "유틸리티 신청"
UTILITY_ORDER ||--o{ WIRING_PATH : "배선(LineString)"
BOOTH ||--o{ RENDER_JOB : "시각화 샷"
EVENT ||--o{ EVENT_MEMBER : "참여자(RBAC)"
USER ||--o{ EVENT_MEMBER : "소속"
COMPANY ||--o{ EVENT_MEMBER : "업체계정"
EVENT ||--o{ AUCTION : "옥션(M15)"
AUCTION ||--o{ QUOTATION : "견적서=응찰"
AUCTION ||--o| AWARD : "낙찰"
COMPANY ||--o{ QUOTATION : "응찰업체(등록검증)"
BOOTH ||--o{ AUCTION : "자료첨부"
EVENT ||--o{ REGISTRATION : "관람객 등록(M10)"
VISITOR ||--o{ REGISTRATION : "관람객"
REGISTRATION ||--o{ BADGE : "배지/QR"
BADGE ||--o{ CHECK_IN : "체크인"
BOOTH ||--o{ LEAD : "리드캡처"
VISITOR ||--o{ LEAD : "스캔대상"
EVENT ||--o{ SETTLEMENT : "정산(M9)"
EVENT ||--o{ DOCUMENT : "서류/마일스톤(M6)"
EVENT ||--o{ CONTENT : "CMS(M17)"
EVENT ||--o{ FACT_MART : "BI 집계(M16)"
```
### 1-2. 주제영역(Subject Area) 맵
| 주제영역 | 핵심 엔티티 | 소유 모듈 | 특성 |
|---|---|---|---|
| **행사·조직·권한** | Event, User, Company, EventMember, Role | §5B·M18 | 마스터·RBAC 기준축 |
| **공간·시설** | Hall, HallFeature, Trench | M2·마스터 | PostGIS 지오메트리 |
| **설계·시공(P0)** | Layout, Booth, DesignPlan, UtilityOrder, WiringPath, RenderJob | M2~M5 | 버전·공간·비동기 |
| **발주·정산** | Auction, Quotation, Award, Settlement, PaymentSchedule, Document | M6·M9·M15 | 금액·계약·감사 |
| **관람·참가** | Visitor, Registration, Badge, CheckIn, Lead, Meeting | M10·M11 | **PII 집중 영역** |
| **콘텐츠·마스터** | Content, Microsite, MasterData, Ruleset | M17·M18 | 다국어·버전 |
| **분석(BI)** | FactBooking/Settlement/Utility/Auction/Visitor, Dim*, KpiSnapshot | M16 | 스타 스키마·집계 |
| **공통·감사** | AuditLog, CodeGroup, Code, Menu, Notification | §5B | 이식·전 모듈 공유 |
---
## 2. 전사 데이터 모델 — 논리·물리(Logical/Physical)
> 물리 테이블은 kintex-db-engineer가 구현. 아래는 **표준 대상 모델**(테이블·컬럼·타입·제약). 명명 규칙은 §4. `geom` 컬럼 상세는 §3.
### 2-1. 코어 P0 물리 ERD
```mermaid
erDiagram
TB_EVENT {
uuid event_id PK
varchar event_name
date start_date
date end_date
varchar status
timestamptz created_at
}
TB_HALL {
varchar hall_id PK "H1..H10, 반홀 H1A"
varchar hall_name
numeric area_m2
numeric floor_load_t_per_m2
numeric width_m
numeric depth_m
numeric ceiling_m
varchar floor_finish "concrete_polished|carpet"
int booth_capacity
geometry footprint "Polygon,0"
}
TB_HALL_ASSIGNMENT {
uuid assignment_id PK
uuid event_id FK
varchar hall_id FK
date occupy_from
date occupy_to
}
TB_BOOTH {
uuid booth_id PK
uuid layout_id FK
varchar booth_no "A-102"
varchar booth_type "independent|assembled"
numeric width_m
numeric depth_m
numeric height_m
numeric floor_load_t_per_m2
boolean premium
uuid assigned_company_id FK "nullable"
geometry geom "Polygon,0 · 홀로컬"
}
TB_LAYOUT {
uuid layout_id PK
uuid assignment_id FK
int version
varchar name
varchar status "draft|submitted|approved|rejected"
int source_option "선택/병합 출처 안번호"
jsonb merge_provenance "병합 출처 레이어"
timestamptz updated_at
}
TB_TRENCH {
uuid trench_id PK
varchar hall_id FK
varchar supply_matrix "power,water,air,gas,network 비트/배열"
boolean assumed "가정 그리드 여부(R4)"
geometry geom "Point,0 · 탭포인트"
geometry run_geom "LineString,0 · nullable"
}
TB_DESIGN_PLAN {
uuid design_id PK
uuid booth_id FK
int version
varchar status
jsonb spec "DesignSpec"
timestamptz updated_at
}
TB_UTILITY_ORDER {
uuid order_id PK
uuid booth_id FK
varchar status "draft|submitted|relayed"
jsonb quote "UtilityQuote 스냅샷"
varchar rateset_version
varchar location_diagram_url
timestamptz created_at
}
TB_WIRING_PATH {
uuid wiring_id PK
uuid order_id FK
varchar kind "power|network|plumbing|air"
numeric kw "nullable"
numeric length_m
geometry geom "LineString,0"
}
TB_RENDER_JOB {
uuid job_id PK
uuid booth_id FK
uuid event_id FK
varchar shot_preset "S1..S7"
varchar status "QUEUED|RUNNING|DONE|FAILED"
varchar image_url
varchar schema_hash "캐시키"
varchar model_version
varchar error_message "요약만"
timestamptz created_at
}
TB_COMPANY {
uuid company_id PK
varchar company_name
varchar registration_no "사업자번호(등록검증)"
varchar category_code "14분류 CONTRACTOR_CATEGORY"
varchar region
boolean kintex_registered "미등록 응찰 차단 게이트"
}
TB_EVENT_MEMBER {
uuid member_id PK
uuid event_id FK
uuid user_id FK
uuid company_id FK "nullable"
uuid booth_id FK "nullable · 참가업체 부스 스코프"
varchar event_role "ORGANIZER|EXHIBITOR|CONTRACTOR|HALL_MANAGER"
}
TB_EVENT ||--o{ TB_HALL_ASSIGNMENT : ""
TB_HALL ||--o{ TB_HALL_ASSIGNMENT : ""
TB_HALL ||--o{ TB_TRENCH : ""
TB_HALL_ASSIGNMENT ||--o{ TB_LAYOUT : ""
TB_LAYOUT ||--o{ TB_BOOTH : ""
TB_BOOTH ||--o{ TB_DESIGN_PLAN : ""
TB_BOOTH ||--o{ TB_UTILITY_ORDER : ""
TB_UTILITY_ORDER ||--o{ TB_WIRING_PATH : ""
TB_BOOTH ||--o{ TB_RENDER_JOB : ""
TB_COMPANY ||--o{ TB_BOOTH : "배정"
TB_EVENT ||--o{ TB_EVENT_MEMBER : ""
```
**계약 정합 근거**(01_backend_contracts §8 매퍼 인수 목록):
- `BoothMapper``TB_BOOTH.geom`(ST_MakePolygon/ST_AsGeoJSON), 판매면적 `ST_Area(geom)`, 통로폭 `ST_Distance/ST_Buffer`, 비상구 `ST_Intersects(TB_HALL_FEATURE)`.
- `DesignMapper``TB_DESIGN_PLAN.spec`(jsonb), `findEventIdByBooth`(RBAC 역참조 = TB_BOOTH→TB_LAYOUT→TB_HALL_ASSIGNMENT→event_id).
- `WiringMapper``TB_TRENCH` KNN(`geom <-> :point`), `TB_WIRING_PATH.geom` 최단(ST_Length), `TB_TRENCH.assumed` 플래그.
- `RenderJobMapper``TB_RENDER_JOB` 내구 이력·쿼터 정본(`countSucceededByEvent`, Redis는 큐/실시간).
- `UserMapper``TB_USER`(§2-3) 인증행(해시 응답 제외)·`TB_EVENT_MEMBER` 역할.
### 2-2. v2.0 확장 물리 ERD (옥션·관람·CMS)
```mermaid
erDiagram
TB_AUCTION {
uuid auction_id PK
uuid event_id FK
varchar auction_type "REVERSE|RFQ|FIXED"
varchar category_code "공종 14분류"
varchar status "OPEN|BIDDING|AWARDED|CLOSED"
int round_no
timestamptz deadline_at
varchar award_criteria "LOWEST|COMPOSITE"
jsonb weight "가격/평판/납기 가중치"
jsonb attached_refs "Booth/Design/Utility/Render 참조 자료"
}
TB_QUOTATION {
uuid quotation_id PK
uuid auction_id FK
uuid company_id FK "등록업체 검증"
int version
varchar status "SUBMITTED|REVISED|AWARDED|REJECTED"
jsonb line_items "공종·자재·수량·단가·금액"
numeric subtotal
numeric vat
numeric total
date valid_until
varchar lead_time
varchar pdf_url
timestamptz submitted_at
}
TB_AWARD {
uuid award_id PK
uuid auction_id FK
uuid quotation_id FK "선정 견적서"
numeric composite_score
varchar reason
varchar contract_doc_url "M6/M9 연동"
timestamptz awarded_at
}
TB_VISITOR {
uuid visitor_id PK
varchar name_enc "PII·AES-GCM"
varchar email_enc "PII·AES-GCM"
varchar phone_enc "PII·AES-GCM"
varchar org_name "준식별"
varchar job_title
jsonb interests "관심 업종"
varchar visitor_type "VISITOR|BUYER"
timestamptz created_at
}
TB_REGISTRATION {
uuid registration_id PK
uuid event_id FK
uuid visitor_id FK
varchar reg_type
boolean consent_privacy "동의(필수)"
boolean consent_marketing "동의(선택·정보통신망법)"
timestamptz consent_at
timestamptz registered_at
}
TB_BADGE {
uuid badge_id PK
uuid registration_id FK
varchar qr_token "회전 토큰·비추측"
varchar badge_template_id "M17"
}
TB_CHECK_IN {
uuid checkin_id PK
uuid badge_id FK
timestamptz checked_at
varchar gate
}
TB_LEAD {
uuid lead_id PK
uuid event_id FK
uuid booth_id FK "참가업체 스코프"
uuid visitor_id FK
int interest_score
varchar memo_enc "PII·AES-GCM"
boolean consent_share "리드 공유 동의"
timestamptz captured_at
}
TB_CONTENT {
uuid content_id PK
uuid event_id FK "nullable"
varchar content_type
varchar locale "ko|en|zh|ja"
int version
varchar status "draft|review|published"
jsonb body
}
TB_AUCTION ||--o{ TB_QUOTATION : ""
TB_AUCTION ||--o| TB_AWARD : ""
TB_QUOTATION ||--o| TB_AWARD : "선정"
TB_VISITOR ||--o{ TB_REGISTRATION : ""
TB_REGISTRATION ||--o{ TB_BADGE : ""
TB_BADGE ||--o{ TB_CHECK_IN : ""
TB_VISITOR ||--o{ TB_LEAD : ""
```
**보조 테이블**(도메인 완결): `TB_SETTLEMENT`(정산·M9)·`TB_PAYMENT_SCHEDULE`(납부 스케줄 20/30/20/30 + 예치금)·`TB_DOCUMENT`(서류·마일스톤 D-150/30/25/7·M6)·`TB_MEETING`(비즈매칭·M11)·`TB_MICROSITE`(참가업체·M17)·`TB_MASTER_DATA`(마스터 버전·M18). 상세 컬럼은 해당 도메인 에이전트 확정 시 본 문서 갱신.
### 2-3. 공통·시스템관리 물리 모델 (WISE/UIWS 이식 — 정본 참조)
> **재설계 금지**: 아래는 `workspace/uiws` `TB_*` 정본을 **그대로 이식**(멱등 DDL·`sql.init mode=always`+continue-on-error). 킨텍스는 표준 준수만 하고 컬럼을 임의 변경하지 않는다. 상세 컬럼 정의는 UIWS 레퍼런스가 정본.
| 테이블 | 역할 | 킨텍스 접합 |
|---|---|---|
| `TB_USER` | 사용자·인증(BCrypt 해시·`otp_secret` AES) | 6역할 + 등록업체 계정 + 관람객 셀프서비스 |
| `TB_CODE_GRP` / `TB_CODE` | 공통코드 그룹/값 | §4-3 도메인 코드 적재 |
| `TB_MENU` | 메뉴 트리·권한 매핑 | 역할별 포털 IA(§2-1) |
| `TB_AUDIT_LOG` | 감사 로그 | 승인·**낙찰**·설계변경·룰셋개정·**리드 접근(PII)** 전수 |
| `TB_NOTIFICATION` | 통합 알림 | D-데이 리마인더·낙찰·결제 |
| worklog/schedule/message/notice/meeting/report 등 | 공통 업무 | §5B-2 접합점만 이식 |
- **인증 표준(§5B-3)**: JWT + TOTP(RFC6238, SHA1·30s·6자리·±1) 2차 인증 + 로그인 실패 잠금. `admin` 비번은 env `ADMIN_PASSWORD_ENC`(AES-256-GCM) + 별도 키파일 복호 → 기동 시 BCrypt 재시드. **하드코딩 시드 금지**.
- **행사 RBAC 이중 평가**: 전역 `USER_ROLE`(WISE) + 행사 스코프 `EVENT_ROLE`(TB_EVENT_MEMBER) 병행. 킨텍스 1차 권한 = `EVENT_ROLE`(COMMON_CODES §3).
---
## 3. 공간 데이터 모델 표준 (PostGIS)
> M2~M5·M13·M14·M16이 공유하는 **단일 공간 원천**. 물리 구현(ST_* 매퍼 XML)은 db-engineer, 좌표계·타입·인덱스·검증 계약은 본 절이 표준.
### 3-1. 좌표계 표준 — 홀 로컬 데카르트
| 항목 | 표준 | 근거 |
|---|---|---|
| **SRID** | **`0`(로컬 데카르트, 미터)** — 지리좌표(4326) 아님 | 부스/트렌치/배선은 홀 로컬 미터 좌표(계약 `polygon`=홀 로컬 미터, `[[0,0],[6,0]...]`) |
| 타입 | **`geometry`**(geography 아님) | 평면 미터 연산: `ST_Area`=㎡ 직접, `ST_Distance`=m 직접, `ST_Length`=m 직접 |
| 원점 | 홀별 원점(도면 좌하단) 기준, hall_id로 좌표계 분리 | 홀마다 독립 로컬 원점 |
| 단위 | 미터(m). 각도는 도(°) | 계약 `sizeM`·`heightM`·`lengthM` |
| 정밀도 | 좌표 소수 3자리(mm), 면적/길이 소수 2자리 | 시공 실무 정밀도 |
> **주의(교차 좌표계 금지)**: 홀 로컬 좌표는 홀 간 직접 공간연산 불가(각 홀 원점 상이). 홀 전경(S7)·부지 컨텍스트가 필요하면 별도 `venue` 좌표계 변환 테이블로 배치(Phase 2). Phase 1은 단일 홀 기준(홀7 권장, PLANNING §9).
### 3-2. 지오메트리 컬럼 표준
| 엔티티 | 컬럼 | PostGIS 타입 | 규칙 |
|---|---|---|---|
| **부스** `TB_BOOTH` | `geom` | `geometry(Polygon, 0)` | 닫힌 링(첫=끝 좌표), 단순(ST_IsSimple)·유효(ST_IsValid), CCW 권장 |
| **홀 외곽** `TB_HALL` | `footprint` | `geometry(Polygon, 0)` | 홀 경계 |
| **홀 시설** `TB_HALL_FEATURE` | `geom` | `geometry(Geometry, 0)` | 기둥(Point Ø2.5m 버퍼)·비상구(Point/LineString)·셔터·화장실 — `feature_type` 구분 |
| **트렌치** `TB_TRENCH` | `geom` | `geometry(Point, 0)` | 탭/액세스 포인트(KNN 대상). `run_geom geometry(LineString,0)` 옵션(트렌치 런) |
| **배선** `TB_WIRING_PATH` | `geom` | `geometry(LineString, 0)` | 트렌치→단말 경로. `kind`별 1행 |
### 3-3. 공간 연산 계약 (매퍼 XML 대상 — db-engineer 인수)
| 용도 | 연산 | 규정/계약 매핑 |
|---|---|---|
| 판매면적 | `ST_Area(geom)` (m²) | LayoutSummary `salesAreaM2`, BI ㎡당 수익 |
| 통로 폭 최소 | `ST_Distance` + `ST_Buffer`(부스 간극) | 규정 `AISLE_WIDTH_MIN`(≥3m·block) |
| 비상구 차단 | `ST_Intersects(booth, exit_access_zone)` count | 규정 `EXIT_ACCESS`(=0·block) |
| 최근접 트렌치 | KNN `geom <-> :point ORDER BY … LIMIT k` | `WiringMapper.findNearestTrenches` |
| 최단 배선 | 경로 LineString `ST_Length` (통로 횡단 최소 휴리스틱) | `WiringMapper.shortestPath`, `WiringResult.lengthM` |
| 부스 겹침 | `ST_Overlaps` / `ST_Intersects` 자기조인 | 배치 무결성(솔버 후검증) |
| 홀 이탈 | `ST_Contains(hall.footprint, booth.geom)` | 부스가 홀 경계 내 |
- **가정 트렌치(R4)**: 실측 미확보 홀은 공개 규격 기반 가정 그리드 → `TB_TRENCH.assumed=true`. `WiringResult.assumedTrench=true` → 프론트 "가정 트렌치 좌표(실측 대기)" 배지(계약 §5). CAD 트렌치 실측(`평면,트렌치.dwg`, floorplans README) 확보 시 홀 단위 교체·`assumed=false`.
- **좌표 검증 게이트**: 저장 전 `ST_IsValid`·닫힌 링·홀 내포 검증 실패 시 `VALIDATION`(400). 무효 지오메트리 저장 금지.
### 3-4. 공간 인덱스·성능 표준
-`geom` 컬럼 **GiST 인덱스**(`USING gist(geom)`) 필수. 트렌치 KNN·통로 버퍼·비상구 교차의 실시간 응답 근거.
- 부스 수 홀당 200~600(PLANNING). 배치 검증은 홀 단위 배치(bounding box 선필터 후 정밀 연산).
- 대량 좌표는 서버 산출값 권위 — 프론트 좌표는 참고, 규정 판정은 PostGIS 산출값과 대조(compliance-v1 `AISLE_WIDTH_MIN.note`).
---
## 4. 데이터 표준 (명명·코드·마스터)
### 4-1. 명명 규칙 (Naming Convention)
| 대상 | 규칙 | 예 |
|---|---|---|
| 테이블 | `TB_` + `UPPER_SNAKE`(단수) — WISE 표준 계승 | `TB_BOOTH`, `TB_AUCTION` |
| 컬럼(물리) | `snake_case`, PostgreSQL 무인용 소문자(대소문자 혼용·인용식별자 금지) | `booth_id`, `floor_load_t_per_m2` |
| PK | `<엔티티>_id`, **UUID**(도메인) / WISE 이식 테이블은 정본 PK 유지 | `event_id`, `job_id` |
| FK | 참조 PK명 동일 | `TB_BOOTH.layout_id` |
| 지오메트리 | `geom`(주 지오메트리) / `<용도>_geom` | `geom`, `footprint`, `run_geom` |
| 암호화 PII | `<필드>_enc` 접미 | `email_enc`, `otp_secret`(WISE) |
| 코드 컬럼 | `<의미>_code` 또는 상태 `status` | `category_code`, `status` |
| 불리언 | `is_`/동사 또는 `<x>_yn`(WISE 공통은 `USE_YN`) | `premium`, `assumed`, `use_yn` |
| 시각 | `*_at`(`timestamptz`), 날짜 `*_date`(`date`) | `created_at`, `deadline_at`, `start_date` |
| 금액 | `numeric`, 원(KRW) 정수 스케일, 통화 `currency` 명시 | `total`, `vat` |
| BI 마트 | 팩트 `FACT_*`, 차원 `DIM_*`, 스냅샷 `KPI_SNAPSHOT` | `FACT_BOOKING`, `DIM_HALL` |
- **DTO(camelCase) ↔ 컬럼(snake_case) 매핑**: MyBatis `mapUnderscoreToCamelCase=true` 또는 명시 `resultMap`. 계약 DTO(`boothNo`↔`booth_no`, `floorLoadTPerM2`↔`floor_load_t_per_m2`)는 01_backend_contracts를 정본으로 매핑.
- **응답 제외 컬럼(불변, 계약 §0-3)**: `*_enc`·비번 해시·`otp_secret`·내부 IP/SSH·내부 식별자는 API 응답 완전 제외. 표시는 이름·역할·번호 등 비민감 필드만.
### 4-2. 데이터 타입 표준
| 논리형 | 물리형(PostgreSQL) | 비고 |
|---|---|---|
| 식별자 | `uuid`(도메인) | `gen_random_uuid()` |
| 반정형 스펙 | `jsonb` | DesignSpec·quote·line_items·merge_provenance — GIN 인덱스 선택 |
| 공간 | `geometry(<type>, 0)` | §3 |
| 상태·코드 | `varchar` + 공통코드 검증(앱 레벨) | ENUM 물리타입 지양(룰셋/코드 유연성) |
| 금액 | `numeric(15,2)` | |
| 시각 | `timestamptz`(UTC 저장, Asia/Seoul 표시) | |
### 4-3. 공통코드 체계 (WISE 정합)
> 정본 = [`docs/COMMON_CODES.md`](../COMMON_CODES.md)(단일 출처). 적재 `TB_CODE_GRP`/`TB_CODE`, 코드값=영문 상수·코드명=한글. **DA 검수 관점**: 코드 vs 마스터 경계 준수, "확인 필요" 코드값 임의 확정 금지.
- **코드 vs 마스터 경계(핵심 표준)**: 열거 가능 소수값=**공통코드**(BOOTH_TYPE·RENDER_STATUS·SHOT_PRESET 등), 다건·CRUD·버전 대상=**마스터/룰셋**(홀·요율·규정·등록업체) — 공통코드에 넣지 않는다(COMMON_CODES §1·§4 말미).
- **확정 코드**(계약/PLANNING 근거): `EVENT_ROLE`·`PORTAL_ROLE`·`BOOTH_TYPE`·`COMPLIANCE_SEVERITY`·`RENDER_STATUS`·`SHOT_PRESET`·`USE_YN`.
- **DA 확정 대기(확인 필요)**: `AUCTION_STATUS`·`QUOTATION_STATUS`(M15)·`ZONE_TYPE` 확장·상태 전이(`LAYOUT_STATUS`/`DESIGN_STATUS`/`UTILITY_ORDER_STATUS`의 submitted 이후)·`COMPLIANCE_GROUP` 전체 — 도메인 에이전트 확정 시 **COMMON_CODES + 본 ERD 컬럼 주석 동시 갱신**(DA 검수 항목).
- **신규 도메인 코드 제안**(DA): `CONTRACTOR_CATEGORY`(등록업체 14분류: 전시디자인설치·리깅·전기시설·카펫/파이텍스·급배수/Air·가스설비·철거·운수통관·가구비품·경비용역·광고싸인물·지게차·방염·구조해석), `UTILITY_KIND`(power·network·plumbing·air·gas), `AUCTION_TYPE`(REVERSE·RFQ·FIXED), `CONTENT_LOCALE`(ko·en·zh·ja) — 확정 시 COMMON_CODES §2에 승격.
### 4-4. 마스터데이터 관리 정책 (M18 백오피스)
| 마스터 | 저장 형태 | 버전 관리 | 권한 | 개정 절차 |
|---|---|---|---|---|
| **홀 마스터** | `TB_HALL`(+`TB_HALL_FEATURE`·`footprint`) | 스키마 컬럼 `revision`·이력 테이블 | ADMIN | CAD 실측 확보 시 교체(floorplans README 대조), 제3전시장(2028) H11~H18 확장 구조 |
| **요율 룰셋** | `rulesets/rates-v*.json`(파일) | 파일 버전(`rates-v1.0`) + `TB_MASTER_DATA` 메타 | ADMIN | 연 단위 개정 → 새 파일 교체, 산출물에 `rulesetVersion` 각인, 기존 견적 스냅샷 불변 |
| **유틸리티 요금** | rates-v*.json `utility` 절 | 상동 | ADMIN | 인터넷 150,000 vs KT 80,000 정합 확인 후 확정(R8) |
| **규정 룰셋** | `rulesets/compliance-v*.json` | 파일 버전(`compliance-v1.0`) | ADMIN | 규정 개정 시 교체, 리포트에 `rulesetVersion`+`disclaimer` 각인(면책·감사) |
| **등록업체 DB** | `TB_COMPANY`(739개·14분류) | 주기 수집 + `kintex_registered` 검증 플래그 | ADMIN | 웹 공개 데이터 수집→자체 DB화, 추후 공식 피드. **미등록=옥션 응찰/초대 차단 게이트**(불변) |
- **룰셋 스냅샷 원칙**: 견적(`TB_UTILITY_ORDER.quote`·`rateset_version`)·규정 리포트는 산출 시점 룰셋 버전을 **스냅샷 각인**. 이후 룰셋 개정이 과거 산출물을 변경하지 않는다(감사·재현성).
- **버전 관리 = 룰 엔진 결합**: 룰셋은 코드가 아닌 데이터 → M18 개정이 무중단 반영(PLANNING §8-1). 개정은 `TB_AUDIT_LOG` 전수 기록.
---
## 5. 데이터 품질·거버넌스
### 5-1. 개인정보(PII) 분류 체계
> 집중 영역 = 관람·참가(M10/M11). **주민등록번호 등 고유식별정보는 수집하지 않는다**(수집 최소화).
| 등급 | 분류 | 대상 컬럼(예) | 처리 |
|---|---|---|---|
| **P1 식별정보** | 직접 식별 | `TB_VISITOR.name_enc/email_enc/phone_enc`, `TB_LEAD.memo_enc` | **컬럼 AES-256-GCM 암호화** 저장, 응답 제외/마스킹, 접근 감사 |
| **P2 준식별** | 결합 식별 | `org_name`·`job_title`·`interests`·`visitor_type` | 접근 통제, BI는 집계/익명화만 반입 |
| **P3 인증비밀** | 크리덴셜 | `TB_USER` 비번 해시(BCrypt)·`otp_secret`(AES) | 절대 응답 금지, 로그 금지 |
| **P4 공개/비식별** | 비민감 | 부스명·업체명(표시용)·행사·집계 | 일반 처리 |
- **BoothDto 준거**: `assignedCompanyName`는 "표시용, 내부 식별자·민감정보 미포함"(BoothDto Javadoc) — P4. 부스 설계(TB_DESIGN_PLAN.spec)는 영업비밀(R10) → 행사 격리·접근 제한.
### 5-2. 암호화 정책
| 데이터 | 방식 | 근거 |
|---|---|---|
| PII 컬럼(P1) | **AES-256-GCM** 컬럼 암호화, 키는 서버 env/별도 키파일(코드·DB·커밋·로그 금지) | GUARDiA 보안 불변 `os_pw_enc` 패턴 |
| 비밀번호 | BCrypt(단방향) | WISE 표준 |
| OTP 시크릿 | AES-256-GCM | §5B-3 TOTP |
| 전송 | TLS(포털·API·워커 콜백) | NA(network.md) 정합 |
| 오브젝트 스토리지 | 접근 제어 URL(서명·만료), 도면/설계 행사 격리 | R10 |
### 5-3. 동의(Consent)·수집 최소화
- **동의 분리**: `TB_REGISTRATION.consent_privacy`(개인정보 수집·이용, **필수**) / `consent_marketing`(EDM 발송, **선택**, 정보통신망법) / `TB_LEAD.consent_share`(참가업체 리드 공유). 각 `consent_at` 시각 기록.
- **목적 구속**: 마케팅 미동의자는 M12 EDM 세그먼트 제외(발송 파이프라인 게이트). 리드 공유 미동의는 참가업체 반출 차단.
- **최소 수집**: 관람객 폼은 목적 필요 최소 필드. 셀프서비스 계정은 행사 데이터 쓰기 권한 없음(PLANNING §2).
### 5-4. 보존·파기 정책
| 데이터 | 보존 | 파기 |
|---|---|---|
| 관람객 등록·배지·체크인(PII) | 행사 종료 후 정책 기간(기본 1년, 재참가 분석 목적 별도 동의 시 연장) | 기간 경과 자동 익명화/삭제 |
| 리드(참가업체 반출본) | 참가업체 자산 — 반출 시점 이후 참가업체 책임, 플랫폼 원본은 위 관람객 정책 준수 | 상동 |
| 도면·설계·생성 이미지 | 행사 종료 후 보존(참가업체 자산, PLANNING §8) — 별도 정의 | 참가업체 요청 시 삭제 |
| 견적서 PDF·낙찰(M15) | 계약·감사 목적 장기 보존 | 법정 보존기간 준수 |
| 감사로그 | 장기 보존(불변·append-only) | 미파기 |
| BI 마트·KpiSnapshot | 집계(비식별) — 장기 보존 | — |
### 5-5. 감사(Audit)·데이터 계보(Lineage)
- **감사 대상(전수, `TB_AUDIT_LOG`)**: 승인·**낙찰(M15 Award)**·설계 변경·**룰셋/마스터 개정**·**리드/PII 접근**·권한 변경·로그인/OTP. append-only, 행위자·시각·전후값·행사 스코프 기록(COMMON_CODES §2-1 audit 확장).
- **데이터 계보(원천→마트)**:
```
[운영 원천] [BI 데이터마트 M16]
TB_HALL_ASSIGNMENT / 행사일정 ──▶ FACT_BOOKING (가동률·RevPAD·㎡당수익)
TB_SETTLEMENT (M9) ──▶ FACT_SETTLEMENT (매출구성·P&L)
TB_UTILITY_ORDER (M4) ──▶ FACT_UTILITY (유틸 매출)
TB_AUCTION/QUOTATION/AWARD ──▶ FACT_AUCTION (옥션 수수료)
TB_REGISTRATION/CHECK_IN (M10) ──▶ FACT_VISITOR (관람·리텐션) ※PII 비반입, 집계만
TB_HALL/PostGIS ST_Area ──▶ DIM_HALL (면적 정규화)
─(배치/야간 적재)─▶ KPI_SNAPSHOT (경영진 KPI)
```
- **PII 격리(계보 규칙)**: BI 마트는 P1/P2 원본을 반입하지 않는다 — 관람객은 **집계·코호트·익명 키**만 반입(FACT_VISITOR는 방문 카운트·세그먼트 차원, 개인 식별자 없음). M16 리텐션/LTV는 참가사(Company) 단위이며 개인 관람객이 아님.
- **데이터 품질 규칙(DQ)**: 참조무결성(FK), 지오메트리 유효성(§3-3 게이트), 룰셋 버전 각인 누락 0, 금액 통화 명시, 상태 코드 공통코드 준수. 마트 적재 시 원천-집계 정합 체크(§6-4).
---
## 6. BI 데이터마트 (M16) — 스타 스키마
> 대상: **kintex-bi-dev**. PLANNING §5A M16-1 운영사(킨텍스) 관점 7지표 정합. 관점 격리 = ① 참가업체 ROI(자기 부스) / ② 운영사 수익성(전 행사) 별도 대시보드·권한(PLANNING §440). 적재 = 배치/야간 `KPI_SNAPSHOT` 또는 읽기 전용 복제(운영 부하 회피, §8-1).
### 6-1. 스타 스키마 ERD
```mermaid
erDiagram
DIM_DATE {
int date_key PK "YYYYMMDD"
date full_date
int year
int quarter
int month
boolean is_peak "성수기 3-5·9-11"
boolean is_offpeak "비수기 1·2·7·12"
}
DIM_HALL {
varchar hall_key PK "H1..H10/반홀"
varchar hall_name
numeric area_m2 "㎡ 정규화 기준"
varchar center "1전시장|2전시장"
numeric floor_load
varchar floor_finish
}
DIM_EVENT {
uuid event_key PK
varchar event_name
varchar event_type
date start_date
date end_date
int duration_days
}
DIM_EXHIBITOR {
uuid exhibitor_key PK "=company_id"
varchar company_name
varchar category
varchar region
int first_participation_year "코호트"
}
FACT_BOOKING {
uuid booking_id PK
int date_key FK
varchar hall_key FK
uuid event_key FK
numeric occupied_area_m2
numeric available_area_m2
int occupied_days
int available_days
numeric rental_revenue
numeric season_coeff "성수기/비수기/1전시장 계수"
}
FACT_SETTLEMENT {
uuid settlement_id PK
int date_key FK
uuid event_key FK
varchar revenue_segment "rental|utility|auction_fee|lobby|outdoor|parking"
numeric revenue
numeric direct_cost "운영·에너지·인력"
numeric contribution_margin
}
FACT_UTILITY {
uuid util_fact_id PK
int date_key FK
uuid event_key FK
uuid exhibitor_key FK
varchar utility_kind "power|network|plumbing|air"
numeric amount
}
FACT_AUCTION {
uuid auction_fact_id PK
int date_key FK
uuid event_key FK
varchar category_code
numeric awarded_amount
numeric platform_fee
int bid_count
}
FACT_VISITOR {
uuid visitor_fact_id PK
int date_key FK
uuid event_key FK
varchar visitor_segment "visitor|buyer (익명 세그먼트)"
int registered_count
int checkin_count
int lead_count "PII 없음·집계만"
}
KPI_SNAPSHOT {
uuid snapshot_id PK
int date_key FK
varchar scope "venue|center|hall|event"
varchar scope_key
varchar kpi_code "OCC|REVPAD|MARGIN|RETENTION|LTV|YIELD"
numeric kpi_value
numeric target_value
timestamptz built_at
}
DIM_DATE ||--o{ FACT_BOOKING : ""
DIM_HALL ||--o{ FACT_BOOKING : ""
DIM_EVENT ||--o{ FACT_BOOKING : ""
DIM_DATE ||--o{ FACT_SETTLEMENT : ""
DIM_EVENT ||--o{ FACT_SETTLEMENT : ""
DIM_DATE ||--o{ FACT_UTILITY : ""
DIM_EXHIBITOR ||--o{ FACT_UTILITY : ""
DIM_DATE ||--o{ FACT_AUCTION : ""
DIM_DATE ||--o{ FACT_VISITOR : ""
DIM_EVENT ||--o{ FACT_VISITOR : ""
DIM_DATE ||--o{ KPI_SNAPSHOT : ""
```
### 6-2. 팩트 그레인(Grain) 정의
| 팩트 | 그레인(1행 = ) | 가법성 |
|---|---|---|
| `FACT_BOOKING` | 행사×홀(반홀)×기간 배정 1건 | 면적·일수·매출 가법, 계수 비가법 |
| `FACT_SETTLEMENT` | 행사×매출세그먼트×정산일 | 매출·원가·공헌이익 가법 |
| `FACT_UTILITY` | 행사×참가사×유틸종류 신청 1건 | 금액 가법 |
| `FACT_AUCTION` | 옥션(낙찰) 1건 | 낙찰액·수수료 가법, bid_count 준가법 |
| `FACT_VISITOR` | 행사×관람일×세그먼트 집계 | 카운트 가법(**개인 식별자 없음**) |
### 6-3. M16-1 지표 → 마트 매핑
| # | 운영사 지표 | 산식 | 소스 팩트/차원 |
|---|---|---|---|
| ① | 홀·기간별 가동률 | Σ occupied_area×days / Σ available_area×days ×100 | FACT_BOOKING × DIM_HALL × DIM_DATE |
| ② | 매출 구성(mix) | revenue by segment | FACT_SETTLEMENT/FACT_UTILITY/FACT_AUCTION |
| ③ | 행사별 P&L·마진 | Σ revenue Σ direct_cost = 공헌이익, 마진율 | FACT_SETTLEMENT × DIM_EVENT |
| ④ | 전시장별 ROI·RevPAD·㎡당 수익 | revenue / area_m2 (㎡ 정규화) | FACT_BOOKING × DIM_HALL(area_m2) |
| ⑤ | 참가사 리텐션·LTV | 코호트 재참가율, LTV=Σ(임대+유틸+옥션)/재참가주기 | DIM_EXHIBITOR(first_year) × FACT_* 다년 |
| ⑥ | 수요예측·수율/가격 | 성수기 계수·홀별 수요, 요율 시뮬레이션 | FACT_BOOKING 이력 × rates 룰셋 |
| ⑦ | 경영진 KPI 대시보드 | ①~⑥ 요약 + 목표 대비(점유 60~75%) | KPI_SNAPSHOT |
### 6-4. 적재·품질 표준
- **적재 방식**: 야간 배치 ETL(운영→`mart` 스키마) 또는 읽기 전용 복제(운영 부하 회피). `KPI_SNAPSHOT`은 스냅샷 시점(`built_at`) 각인 → 시계열 추이·재현성.
- **㎡ 정규화 권위**: DIM_HALL.area_m2는 **PostGIS `ST_Area` 산출값 또는 홀 마스터 확정값** 단일 출처(대형홀 vs 소형홀 생산성 비교 정합, ④ RevPAD 근거).
- **정합 체크(DQ)**: 마트 매출 합 = 운영 정산 합(허용오차 0), 가동률 분모(가용 홀·일수) = 행사일정×홀 마스터, 관점 격리(참가사 대시보드는 exhibitor_key 필터 강제).
- **PII 비반입(불변)**: FACT_VISITOR는 카운트/세그먼트만 — TB_VISITOR P1/P2 컬럼 마트 유입 금지(§5-5 계보 규칙).
- **한계 각인**: LTV·리텐션 다년 데이터 필요(초기 단년 근사), 수율 최적가=시뮬레이션 참고치(최종 요율은 킨텍스 경영 결정, R8) — 대시보드 고지.
---
## 7. DA 검수 체크리스트 (Phase A 게이트)
> 본 문서를 기준으로 db-engineer 물리 구현·타 트랙 산출물을 검수하는 항목(A-6 reviewer 정합 입력).
| # | 검수 항목 | 기준 |
|---|---|---|
| 1 | 명명 규칙 준수 | `TB_`·snake_case·`_enc`·`geom`·`FACT_/DIM_` (§4-1) |
| 2 | 공간 좌표계 | SRID 0·geometry·GiST 인덱스·`ST_IsValid` 게이트 (§3) |
| 3 | 공통코드 경계 | 코드 vs 마스터 분리, "확인 필요" 임의확정 금지 (§4-3) |
| 4 | 룰셋 스냅샷 | 견적·리포트에 `rulesetVersion` 각인, 과거본 불변 (§4-4) |
| 5 | PII 암호화·응답제외 | P1 `_enc` AES-GCM, 민감컬럼 API 완전 제외 (§5-1/5-2, 계약 §0-3) |
| 6 | 동의 게이트 | marketing 미동의 EDM 제외, share 미동의 반출 차단 (§5-3) |
| 7 | 감사 전수 | 낙찰·룰셋개정·리드접근·권한변경 기록 (§5-5) |
| 8 | BI PII 비반입 | 마트에 개인식별자 유입 0, 관점 격리 (§6-4) |
| 9 | 계약 정합 | §8 매퍼 인수 테이블/컬럼 = 본 ERD (§2-1) |
---
## 8. 미결·후속 (확정 대기)
| 항목 | 상태 | 담당 |
|---|---|---|
| M15 옥션 상태 코드(`AUCTION_STATUS`·`QUOTATION_STATUS`) 확정 | 확인 필요 | bidding-dev + DA |
| 상태 전이(layout/design/utility submitted 이후) | 확인 필요 | M6 승인 워크플로 + DA |
| `TB_SETTLEMENT`·`TB_MEETING`·`TB_MICROSITE` 상세 컬럼 | 골격만 | 도메인 에이전트 + DA |
| CAD 트렌치 실측 → `TB_TRENCH.assumed=false` 교체 | 미확보(R4) | 킨텍스 협의 |
| 홀 간 venue 좌표계 변환(S7·부지) | Phase 2 | DA + M2 |
| 제3전시장(2028) H11~H18 홀 마스터 확장 | 구조 대비 | DA |
| 인터넷 요금 정합(150,000 vs 80,000) | 확인 필요(R8) | 킨텍스 + M18 |
---
## 10. FK 최소화·공통코드 관리 표준 (소유자 지시 2026-07-12)
> 소유자 원칙: **"FK는 최소화, 공통코드로 관리"**. 본 절은 §4·§5-5(DQ) 위에 **참조무결성·범주값 관리 방식의 단일 정책**을 확정한다. 이미 적용된 마이그레이션(V1~V43)은 **불변** — 본 절은 표준·감사·백로그이며 파괴적 재작성을 지시하지 않는다. 구현(신규 멱등 마이그레이션)은 kintex-db-engineer.
### 10-1. 물리 FK 제약 최소화 정책
- **기본값 = 물리 FK 미설정**: 신규 테넌트/도메인 테이블은 DB `FOREIGN KEY`/`REFERENCES`를 **두지 않는다**. 참조무결성은 **애플리케이션 레이어(서비스·매퍼 검증) + 명명 규약(`*_id` 소프트 참조)**로 보장(§4-1 FK 명명 유지, 물리 제약만 생략).
- **근거(4)**: ① **멀티테넌트 복합키 마찰** — 테넌트 루트(`event`·`hall`·`app_user`)는 `PRIMARY KEY (tenant_id, id)`로 전환됨(V31). 단일 `id` 참조 FK는 `UNIQUE(id)` 보조제약을 강제하고, V31이 실제로 **전 자식 FK를 드롭→복합PK 전환→UNIQUE(id)로 재생성**하는 동적 스윕을 수행해야 했다(마찰 실증). ② **MyBatis** — 조인·삭제 순서를 앱이 제어. ③ **마이그레이션·시드 순서 자유** — 멱등 `ON CONFLICT` 시드가 부모 선삽입에 묶이지 않음. ④ **성능·재배치** — 부스 replaceBooths·부스 교체 시 자식 재지정이 잦음(V3 utility_order·render_job는 이미 소프트 참조 채택 — 정본 사례).
- **예외 화이트리스트(FK 유지 허용)**: 아래 **강한 무결성이 필수이고 테넌트 복합키가 아닌 전역 시스템/RBAC 구성**만 물리 FK를 허용한다. 그 외 신규 FK 신설 **금지**.
| # | 자식 → 부모 | 마이그레이션 | 유지 사유 |
|---|---|---|---|
| W1 | `common_code.grp_code``common_code_group` | V7 | 코드값 고아 방지(공통코드 정합의 근간)·전역·정적 |
| W2 | `sys_menu.parent_id``sys_menu`(self) | V7 | 메뉴 트리 순환/고아 방지·전역 |
| W3 | `sys_role_permission`(role_code→`sys_role`, perm_code→`sys_permission`) | V7 | RBAC 권한 매핑 무결성(보안 임계)·전역 |
| W4 | `sys_role_menu`(role_code→`sys_role`, menu_id→`sys_menu`) | V37 | RBAC 메뉴 매핑 무결성·전역 |
### 10-2. 공통코드로 범주값 관리 정책
- **범주형 컬럼 = 공통코드**: 상태·유형·카테고리·모드·구분·심각도 등 **열거 가능 소수값**은 자유문자열/DB enum/전용 참조테이블이 아닌 **공통코드(`common_code_group`/`common_code`)로 관리**. 컬럼엔 코드값(영문 상수)만 저장, 표시명은 조인/캐시(§4-3, COMMON_CODES 정본). DB `ENUM` 물리타입은 지양(§4-2 — 룰셋/코드 유연성).
- **정본 화면 = W12 공통코드 관리**(CommonCodeAdminPage): 그룹/상세 CRUD의 단일 관리 지점. 신규 범주 컬럼 도입 시 **먼저 공통코드 그룹을 정의**하고 컬럼 주석에 `-- 공통코드(GRP)` 표기(V37 `partner_type` 사례).
- **코드 vs 마스터 경계(불변, §4-3)**: 다건·CRUD·버전 대상(홀·요율·규정 룰셋·등록업체)은 공통코드가 아니라 마스터/룰셋. 범주 컬럼만 공통코드로.
### 10-3. 테넌트 표준 정합
- 공통코드도 **테넌트 스코프 원칙 준수**([[tenant-id-pk-standard]]). 현행 `common_code_group`/`common_code`는 전역(테넌트 미부여) 이식본 — 킨텍스 단일 테넌트 운영 중엔 전역 공유가 유효하나, 멀티테넌트 확장 시 **테넌트별 코드 오버라이드**가 필요하면 `(tenant_id, grp_code, code)` 확장을 백로그로 둔다(§10-7 B4, 지금은 순증 시드만).
- 소프트 참조 인덱스는 `(tenant_id, *_id)` 복합 선두로 생성(§10-4).
### 10-4. 소프트 참조 무결성 보완책 (명문화)
물리 FK를 생략하는 대신 아래를 강제한다:
1. **앱 레이어 검증**: 부모 존재 확인은 서비스/매퍼에서 수행(삽입 전 조회 또는 조인 검증). 낙찰·정산 등 임계 트랜잭션은 명시적 존재검증 필수.
2. **삭제 시 고아 방지 규약**: 부모 삭제는 서비스가 자식 선삭제/무효화(soft-delete `use_yn='N'` 우선). 물리 CASCADE에 의존하지 않는다.
3. **논리참조 인덱스**: 조회·조인·고아 스캔 가속을 위해 소프트 FK 컬럼에 `(tenant_id, <ref>_id)` 인덱스(§10-7 B2).
4. **고아 검증 쿼리(DQ)**: 야간/배포 후 `LEFT JOIN ... WHERE parent.id IS NULL` 고아 스캔을 운영 점검 쿼리로 상비(마트 적재 전 DQ 게이트, §6-4). §5-5 DQ의 "참조무결성(FK)"은 **소프트 참조 무결성(앱+검증쿼리)**으로 해석 갱신.
---
## 11. 뷰·구체화뷰·함수 사용 지침 (소유자 지시 2026-07-12)
> 파생·집계·재사용 계산은 **결정론적 SQL 객체**로 캡슐화해 상태 백필·중복 로직·토큰 낭비를 줄인다. **실사용 근거 없는 선제 생성 금지**(남용 방지).
### 11-1. VIEW (`v_*`) — 실시간 파생·경량 조인·상태 산출
- 용도: 저장 없이 **결정론적 파생**(생명주기 상태·경량 조인·표시용 코드명 조인). 상태/구분 컬럼 백필 대신 **뷰 산출 권장**.
- 정본 사례(V43): `v_event_calendar`(날짜로 `lifecycle` UPCOMING/ONGOING/ENDED 산출 — 별도 상태 컬럼 불필요), `v_event_monthly_summary`(월별 건수 집계). 이 패턴을 신규 파생에 재사용.
- 규약: `CREATE OR REPLACE VIEW`, `tenant_id` 컬럼 노출·필터 유지, 하부 테이블 인덱스에 의존(뷰 자체 인덱스 불가), 민감 컬럼(§5-1 P1/P3) 미노출·마스킹만.
### 11-2. MATERIALIZED VIEW (`mv_*`) — 무겁고 자주 조회·실시간성 낮은 집계
- 용도: **BI 대시보드 KPI·홀 가동률·리드/ROI·월/연 통계** 등 비용 큰 집계로 실시간성이 덜 중요한 것(§6 마트 KPI_SNAPSHOT과 정합 — mview는 경량 대체/보조).
- **새로고침 전략 명시 필수**: 주기(야간 배치)·`REFRESH MATERIALIZED VIEW CONCURRENTLY`(무중단·유니크 인덱스 전제)·mview 자체 인덱스(`tenant_id` + 조회 키) 생성.
- 남용 판단 기준:
| 상황 | 선택 |
|---|---|
| 실시간성 필수·경량 | VIEW |
| 실시간성 낮음·집계 무거움·반복 조회 | MATERIALIZED VIEW |
| 경영 KPI 시계열·스냅샷 재현성 | KPI_SNAPSHOT 테이블(§6) |
| 1회성·희소 조회 | 뷰/mview 생성 안 함(온디맨드 쿼리) |
### 11-3. FUNCTION (`fn_*`) — 재사용 계산 로직 캡슐화
- 용도: 여러 쿼리·화면 공유 계산(기간→분기 산출, 요율 계산 보조, 거리/트래블타임 보조). **부수효과 없는 순수/`IMMUTABLE`·`STABLE` 우선**, PL/pgSQL은 꼭 필요할 때만(SQL 함수 우선).
- 규약: `CREATE OR REPLACE FUNCTION fn_*`, `tenant_id` 파라미터화, 룰셋 의존 계산(요율)은 스냅샷 버전 인자(§4-4)로 재현성 보장.
### 11-4. STORED PROCEDURE (`sp_*`) — 집합연산·다단계 트랜잭션
- 용도: **대량 집합 처리·다단계 트랜잭션·정기 롤업**은 앱 루프(행 단위 왕복) 대신 **DB 프로시저(PL/pgSQL) 세트기반 처리**. 예: 월마감 집계, `REFRESH MATERIALIZED VIEW`, 대량 상태전이, 정산 롤업.
- **경계(남용 금지)**: **비즈니스 로직 대부분은 앱(Service) 유지**. DB 프로시저는 **성능이 결정적일 때만**(대량·세트기반이 앱 루프 대비 확연히 유리). 검증·권한·감사·룰셋 판정 등 도메인 규칙을 프로시저로 이관하지 않는다.
- 순수/부수효과 구분: 반환값만 있는 재사용 계산 = `fn_*`(`IMMUTABLE`/`STABLE`), **부수효과(쓰기·다단계 커밋) 있는 것만 `PROCEDURE sp_*`**(`CALL`). 명명 `fn_*`/`sp_*`, 멱등 `CREATE OR REPLACE`, `tenant_id` 파라미터·스코프 준수.
### 11-5. 공통 규약
- 명명 `v_*`·`mv_*`·`fn_*`·`sp_*`. 마이그레이션 멱등(`CREATE OR REPLACE` / mview는 `IF NOT EXISTS` 가드). 테넌트 스코프 유지. 실사용 근거(화면/API/AI 답변) 있는 것만 생성.
---
## 13. 자동화 배치 카탈로그 (대상·주기·멱등 — 데이터 표준 관점)
> **무엇을 배치로 돌릴지**(대상·주기·멱등·잠금)만 데이터 표준에서 정의한다. **실행 프레임워크**(Spring `@Scheduled`/Quartz/cron·분산락)는 아키텍처(A-1 app.md/A-3 tech.md, kintex-sa/ta) 표준 영역 — 본 카탈로그를 그쪽으로 링크. 구현 인계: **스케줄러=backend-dev, 프로시저/mview=db-engineer**.
| # | 배치 작업 | 데이터 대상 | 주기(권고) | 멱등성 | 재시도·잠금 요건 |
|---|---|---|---|---|---|
| J1 | **mview 새로고침** | `mv_hall_occupancy`·`mv_lead_roi_summary`·KPI 집계(§6) | 야간 1회(+수요 트리거) | `REFRESH ... CONCURRENTLY` 자연 멱등 | 단일 실행락(중복 REFRESH 방지)·실패 시 다음 주기 |
| J2 | **KPI 스냅샷 적재** | `KPI_SNAPSHOT`(§6-4, `built_at` 각인) | 야간 배치 | 스냅샷 키(date_key,scope) UPSERT | 원천-집계 정합 DQ 통과 후 커밋 |
| J3 | **마감임박 알림·해야할일 자동생성** | D-데이(문서/마일스톤 D-150/30/25/7)·옥션 deadline·납부 스케줄 | 일 1회(+정시) | 발송/생성 대상 dedup 키(대상+일자) | 이미 발송분 스킵(중복 통지 금지)·실패 재큐 |
| J4 | **EDM/옥션 통지 발송** | 캠페인·옥션 통지(SMTP, [[smtp-email-approval]]) | 예약시각·이벤트 트리거 | mail_log 상태(sent/skipped)로 재발송 차단 | 마케팅 미동의 세그먼트 제외 게이트(§5-3)·발송락 |
| J5 | **세션/토큰·만료 데이터 정리** | 만료 비번재설정 토큰·잠금 해제·로그인이력 보존기간 | 시간별/일별 | 조건부 삭제(자연 멱등) | — |
| J6 | **소프트 참조 고아 검출(DQ)** | §10-4 논리참조 무결성(event/hall/company 참조 고아) | 야간(마트 적재 전) | 읽기 전용 스캔 | 고아 발견 시 리포트·차단(마트 적재 게이트) |
| J7 | **PII 보존·파기** | 관람객 등록·배지·체크인(행사종료+1년, §5-4) | 일 1회 | 기간 경과분 익명화/삭제(재실행 안전) | 동의 연장분 제외·감사로그 기록 |
| J8 | **행사 crawl 갱신** | 외부 행사정보(kintex-crawler 트랙) | 정기(일/주) | 소스키 UPSERT | 소스 실패 격리·부분성공 허용 |
| J9 | **정산 롤업** | `sp_settlement_rollup`(정산→FACT_SETTLEMENT) | 마감 주기 | 재실행 시 기간 재계산(UPSERT) | 정산 확정 상태만 대상·실행락 |
- **공통 요건**: 모든 배치는 ① **멱등**(재실행 안전 — UPSERT/조건부/dedup), ② **중복실행 방지 잠금**(분산락 — 실행수단은 아키텍처), ③ **실패 격리·재시도**(부분성공 허용·다음 주기 복구), ④ **감사**(J4·J7 등 통지·파기는 `audit_log` 기록), ⑤ **테넌트 스코프**(배치도 tenant 루프/필터).
- 세트기반 롤업(J2·J9)·대량 상태전이는 §11-4 `sp_*` 프로시저 후보. 알림/발송(J3·J4)의 **판정·세그먼트 규칙은 앱(Service)** — 프로시저는 대량 적재만.
---
## 12. 감사표 + kintex-db-engineer 인계 백로그
> 대조 대상: `src/backend/src/main/resources/db/migration/` V1~V43 전수. **파괴적 재작성 금지** — FK 드롭은 권고(별도 승인 후), 즉시 실행 백로그 = 공통코드 시드·논리참조 인덱스·뷰(순증 멱등). 신규 마이그레이션 번호는 **V43 이후 여유 번호(V44~) 권고**(진행 중 V43·신규분과 충돌 회피 — 실제 번호는 db-engineer가 병합 시점 최댓값+1로 확정).
### 12-1. 감사표 A — 현행 물리 FK 목록 (유지/제거 권고)
현행 물리 FK 약 40건. **유지=화이트리스트 4그룹(§10-1)**, 그 외는 소프트 참조 **제거 권고**(우선순위 P1: 테넌트 루트 참조 / P3: 아그리게잇 내부 — 무해·비복제).
| 분류 | 자식 → 부모(FK) | 마이그레이션 | 판정 |
|---|---|---|---|
| **유지(W1~W4)** | common_code.grp_code→common_code_group; sys_menu.parent_id(self); sys_role_permission(role/perm); sys_role_menu(role/menu) | V7·V37 | **유지** |
| **P1 제거권고** | hall_assignment.event_id→event, hall_id→hall; event_member.event_id→event, user_id→app_user, company_id→company | V2 | 소프트 참조(테넌트 루트·V31 마찰) |
| **P1 제거권고** | trench.hall_id→hall; hall_exit.hall_id→hall; layout.event_id→event, hall_id→hall; utility_order.event_id→event | V3 | 소프트 참조(hall/event 루트) |
| **P1 제거권고** | doc/milestone 3× event_id→event | V14 | 소프트 참조 |
| **P1 제거권고** | dock_reservation.event_id→event, hall_id→hall, dock_id→dock; booth_sale.event_id→event, hall_id→hall | V15·V27 | 소프트 참조 |
| **P1 제거권고** | auction.event_id→event; auction_invite.company_id→company; bid.company_id→company; company_reputation.company_id→company | V16 | 소프트 참조(company/event 루트) |
| **P1 제거권고** | visitor_registration.event_id→event; lead.event_id→event; campaign 3× event_id→event; sponsorship.package_id; settlement.event_id→event; logistics 3× event_id→event | V17·V18·V24·V33 | 소프트 참조 |
| **P3 잔류허용(무해·비복제)** | booth.layout_id→layout; design_plan.booth_id→booth; auction_invite/bid/award.auction_id→auction, award.bid_id→bid; content_version.content_id→cms_content; payment_schedule/refund/tax.invoice_id→invoice; approval_line/history.approval_id→approval; message_recipient/opinion_reply/meeting_attendee | V3·V8·V16·V19·V24·V29·V34 | 아그리게잇 내부 CASCADE — 유지해도 무해, **신규 테이블엔 미복제**(소프트 우선) |
> **주의**: P1 제거는 **자동 실행 대상 아님** — 기존 FK 드롭은 테넌트 복합키 정합·데이터 검증 후 **소유자 승인 별도 DROP 마이그레이션**으로만. 표준의 실질 효력은 **신규 테이블에 FK 미신설 + P1 패턴 미복제**에 있다.
### 12-2. 감사표 B — 범주형 컬럼 → 공통코드 그룹 매핑 (전환 후보)
자유문자열/주석 열거 범주 컬럼 ≈ **34개**. 기존 시드 그룹(V7·V37)과 정합, 신규 그룹(★)은 V44 순증 시드 대상. 코드값=현행 저장값 유지(스키마 불변).
| 그룹코드 | 컬럼(테이블.컬럼) | 마이그 | 코드값(현행) | 상태 |
|---|---|---|---|---|
| `USER_STATUS`★ | app_user.status | V2 | ACTIVE·(LOCKED·INACTIVE) | 신규 |
| `CONTRACTOR_CATEGORY`★ | company.category, auction.category | V2·V16 | 14분류(전시디자인설치·리깅·전기시설·카펫/파이텍스·급배수Air·가스·철거·운수통관·가구비품·경비·광고싸인·지게차·방염·구조해석) | 신규(COMMON_CODES §2 승격) |
| `HALL_FLOOR_TYPE`★ | hall.floor_type | V2 | concrete_polished·carpet·outdoor | 신규 |
| `MASTER_DATA_CATEGORY`★ | master_data.category | V2 | RATE·UTILITY_FEE·COMPLIANCE | 신규 |
| `EVENT_STATUS`★ | event.status | V2 | active·cancelled·(closed) | 신규 |
| `EVENT_CATEGORY`★ | event.category | V23 | (행사 분류 — 값 확인 필요) | 신규 |
| `LAYOUT_STATUS` | layout.status | V3 | draft·submitted·approved·rejected | COMMON_CODES 등재(시드화 필요) |
| `BOOTH_TYPE` | booth.booth_type | V3 | assembled·independent·corner·island | 등재(corner·island 추가) |
| `DESIGN_STATUS` | design_plan.status | V3 | draft·submitted·approved·rejected | 등재(시드화) |
| `UTILITY_ORDER_STATUS` | utility_order.status | V3 | draft·submitted·relayed | 등재(시드화) |
| `RENDER_STATUS` | render_job.status | V3 | QUEUED·RUNNING·DONE·FAILED | 등재(시드화) |
| `SHOT_PRESET` | render_job.shot_preset | V3 | S1~S7 | 등재(시드화) |
| `MILESTONE_TYPE`★ | milestone.milestone_type | V14 | assignment·pre_review·utility·documents·opening | 신규 |
| `DOC_TYPE`★ | document.doc_type | V14 | operation_plan·booth_layout·disaster_plan… | 신규 |
| `APPROVAL_STATUS`★ | document.status, approval.status | V14·V29 | pending·draft·submitted·approved·rejected / DRAFT… | 신규 |
| `DOCK_RESV_STATUS`★ | dock_reservation.status | V15 | (예약 상태) | 신규 |
| `AUCTION_TYPE` | auction.auction_type | V16 | reverse·rfq | 등재(COMMON_CODES §2, 소문자 정합) |
| `AWARD_CRITERIA`★ | auction.award_criteria | V16 | lowest·comprehensive | 신규 |
| `QUOTATION_STATUS` | bid.status | V16 | submitted·revised·awarded·rejected | 등재(확인 필요 → 확정) |
| `VISITOR_TYPE` | visitor_registration.visitor_type | V17 | visitor·buyer·vip | 등재(vip 추가) |
| `CHECKIN_STATE`★ | visitor_registration.checkin_state | V17 | done·waiting·cancelled | 신규 |
| `CAMPAIGN_STATUS`★ | campaign.status | V18 | draft·scheduled·sending·done | 신규 |
| `SPONSOR_CONTRACT_STATUS`★ | sponsorship.contract_status | V18 | signed·pending | 신규 |
| `CONTENT_TYPE`★ | cms_content.content_type | V19 | PAGE·POST·NOTICE·BLOCK | 신규 |
| `CONTENT_STATUS`★ | cms_content.status | V19 | draft·review·approved·published | 신규 |
| `TRANS_STATUS`★ | content_i18n.trans_status | V19 | none·ai·reviewed | 신규 |
| `INQUIRY_TYPE`★ | public inquiry.inquiry_type | V20 | shell·raw·premium·general | 신규 |
| `INQUIRY_STATUS`★ | public inquiry.status | V20 | received·in_review·closed | 신규 |
| `HALL_ASSIGN_STATUS`★ | hall_assignment.status | V25 | assigned·… | 신규 |
| `TENANT_STATUS`★ | tenant.status | V26 | active·onboarding·suspended | 신규 |
| `BOOTH_SALE_STATUS`★ | booth_sale.status | V27 | available·held·sold·blocked | 신규 |
| `INVOICE_CATEGORY`★ | invoice.category | V28 | rental·utility·auction_fee… | 신규 |
| `SETTLEMENT_STATUS`★ | settlement.status | V24 | pending·invoiced·paid·overdue | 신규 |
| `PAYMENT_METHOD`★ | payment_schedule.method | V24 | manual·transfer·card | 신규 |
| `APPROVAL_LINE_STATUS`★ | approval_line.line_status | V29 | WAIT·approved·rejected | 신규 |
| `EQUIP_CATEGORY`★·`RENTAL_STATUS`★·`RENTAL_ITEM_CATEGORY`★·`ORDER_STATUS`★·`FREIGHT_STATUS`★ | V33 logistics 5종 | V33 | forklift…/requested…/furniture…/ordered…/registered… | 신규 |
| `REFUND_STATUS`★·`TAX_INVOICE_STATUS`★ | refund.status·tax_invoice.status | V34 | recorded·settled / issued·void | 신규 |
| `WEBHOOK_EVENT_TYPE`★·`WEBHOOK_STATUS`★ | webhook 등 | V35 | lead.hot…/disabled·queued·sent·failed | 신규 |
| `MAIL_CATEGORY`★·`MAIL_STATUS`★ | mail_log.category·status | V36 | AUCTION_OPEN·EDM·PASSWORD_RESET / sent·failed·skipped | 신규 |
| `VISITOR_GUIDE_CAT`★ | visitor_guide.category | V43 | TRANSPORT·PARKING·ADMISSION·FACILITY·ACCESS·OVERVIEW | 신규 |
| `TRANSPORT_MODE`★ | (관람객 교통 안내 세부) | V43 | (지하철·버스·자가용·KTX 등 — 확인 필요) | 신규 |
> 이미 시드된 그룹(V7·V37): USE_YN·USER_ROLE·VERIFY_METHOD·PRG_TYPE·MSG_RCV_TYPE·WORK_*·SCHE_GUBUN·IMPORTANCE·NOTICE_TYPE·OPINION_STATUS·NOTI_TYPE·REPORT_TYPE·PARTNER_TYPE — 재시드 불필요. **"확인 필요" 코드값(EVENT_CATEGORY·QUOTATION_STATUS·TRANSPORT_MODE 등)은 도메인 에이전트 확정 후 값 고정**(COMMON_CODES §2 규칙).
### 12-3. db-engineer 인계 백로그 (신규 멱등 마이그레이션 — 번호 V44~ 권고)
| ID | 백로그 | 형태 | 우선순위 |
|---|---|---|---|
| **B1** | 감사표 B ★ 신규 그룹 + 상세 공통코드 **순증 시드**(`common_code_group`/`common_code`, `ON CONFLICT DO UPDATE`) + 기존 등재 그룹(LAYOUT_STATUS·DESIGN_STATUS·RENDER_STATUS·SHOT_PRESET 등) 시드화. 컬럼 스키마 불변(코드값=현행 저장값). "확인 필요" 값은 확정분만. | V44(멱등 시드) | **P1** |
| **B2** | 소프트 참조 **논리참조 인덱스**: P1 소프트 참조 컬럼에 `(tenant_id, <ref>_id)` 인덱스(`CREATE INDEX IF NOT EXISTS`). 예: event 참조 자식들의 `(tenant_id, event_id)`, hall 참조의 `(tenant_id, hall_id)`. 이미 있는 idx(idx_lead_event 등)는 스킵. | V45(멱등 인덱스) | **P2** |
| **B3** | 뷰/mview/함수 **순증 후보**(§11): ⓐ `v_booth_display`(부스+코드명 조인·표시용), ⓑ `mv_hall_occupancy`(홀·기간 가동률 — REFRESH CONCURRENTLY·`(tenant_id,hall_key)` 유니크 인덱스), ⓒ `mv_lead_roi_summary`(리드/ROI 월별), ⓓ `fn_period_to_quarter(date)`·`fn_utility_fee(...,ruleset_version)`(재사용 계산). **실사용 화면/AI 근거 확인 후** 생성. | V46(뷰/함수, CREATE OR REPLACE) | P3 |
| **B4** | (멀티테넌트 확장 시) 공통코드 테넌트 오버라이드 `(tenant_id, grp_code, code)` 확장 — 현재 단일 테넌트라 **보류**(구조 대비만). | 후속 | P4 |
| **B5** | (소유자 승인 후) P1 FK **DROP 마이그레이션** — 테넌트 복합키 정합·고아 검증 통과 후에만. 자동 실행 금지. | 승인 대기 | P4 |
| **B6** | **프로시저 후보**(§11-4·§13): `sp_settlement_rollup`(J9)·`sp_kpi_snapshot_build`(J2)·`sp_refresh_marts`(J1 mview 일괄 REFRESH). 성능 결정적일 때만·CREATE OR REPLACE. | V46(뷰와 동반) | P3 |
| **B7** | **자동화 배치**(§13 J1~J9) — 대상·주기·멱등은 본 표 확정. 실행 프레임워크(스케줄러·분산락)는 **아키텍처(kintex-sa/ta) 인계**, 스케줄러 배선 backend-dev. | 인계(아키텍처+backend) | P2 |
---
## 9. 변경 이력
| 버전 | 일자 | 작성자 | 내용 |
|---|---|---|---|
| v1.1 | 2026-07-12 | kintex-data-architect(DA) | **§10 FK 최소화·공통코드 관리 표준**(소유자 지시) — 물리 FK 기본 미설정+화이트리스트 4그룹(W1~W4 RBAC/공통코드 구성), 범주값 공통코드화 정책, 테넌트 스코프 정합, 소프트 참조 무결성 보완책 / **§11 뷰·구체화뷰·함수·프로시저 지침**(`v_*` 실시간 파생·`mv_*` 무거운 집계+REFRESH 전략·`fn_*` 재사용 계산·`sp_*` 집합연산/다단계 트랜잭션·남용 판단 기준) / **§13 자동화 배치 카탈로그**(J1~J9 대상·주기·멱등·잠금 — 실행 프레임워크는 아키텍처 인계) / **§12 감사표 A(현행 FK 약 40건 유지4/제거권고 P1·잔류P3)+B(범주 컬럼 34개→공통코드 그룹 매핑)+db-engineer 백로그 B1~B7(V44 시드·V45 인덱스·V46 뷰·프로시저·배치 인계, 번호 V44~ 권고)**. 기존 마이그레이션 불변·비파괴. |
| v1.0 | 2026-07-11 | kintex-data-architect(DA) | 최초 — 전사 데이터 모델(개념→논리→물리 ERD, PLANNING §7 확장)·공간 데이터 표준(PostGIS SRID0·부스 POLYGON·트렌치 POINT·배선 LineString)·데이터 표준(명명·타입·공통코드 WISE 정합·마스터/룰셋 관리)·품질·거버넌스(PII 분류·암호화·동의·보존·감사·계보)·**BI 데이터마트 M16 스타 스키마(FACT_BOOKING/SETTLEMENT/UTILITY/AUCTION/VISITOR + DIM_DATE/HALL/EVENT/EXHIBITOR + KPI_SNAPSHOT, M16-1 7지표 정합)** 정의. 물리 구현은 db-engineer 인수, 코드값 확인필요 항목은 §8 후속. |