# PostgreSQL 테이블 구조 ## 작성일: 2025-08-20 ## 최종 수정일: 2025-12-04 ## 데이터베이스: main_db --- ## 1. 조직 관련 테이블 ### company | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | name | VARCHAR(255) | NO | | | url | VARCHAR(255) | YES | | | created_at | TIMESTAMPTZ | YES | | | updated_at | TIMESTAMPTZ | YES | | ### team | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | company_id | UUID | NO | FK → company | | name | VARCHAR(32) | NO | | | created_at | TIMESTAMPTZ | YES | | | updated_at | TIMESTAMPTZ | YES | | ### user | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | team_id | UUID | NO | FK → team | | email | VARCHAR(255) | NO | UNIQUE | | name | VARCHAR(255) | YES | | | picture | VARCHAR(500) | YES | | | oauth_providers | JSONB | YES | OAuth 정보 (google/slack/naverworks) | | is_active | BOOLEAN | YES | | | last_login_at | TIMESTAMPTZ | YES | | | created_at | TIMESTAMPTZ | YES | | | updated_at | TIMESTAMPTZ | YES | | | username | VARCHAR(64) | YES | | | is_admin | BOOLEAN | NO | | | metadata | JSONB | YES | 사용자 확장 정보 (nickname, position, preferences 등) | **metadata 예시**: ```json { "nickname": "joann", "position": "대표", "title": "대표님", "short_name": "고은", "department": "경영", "preferences": { "communication": "direct", "work_style": "flexible" } } ``` **oauth_providers 예시**: ```json { "google": "oauth_id_123", "slack": "U01234567", "naverworks": "nw_user_id" } ``` ### workspace_member | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | user_id | UUID | NO | FK → user | | role | user_role | NO | OWNER/MEMBER/GUEST | | is_active | BOOLEAN | YES | | | joined_at | TIMESTAMP | YES | | | updated_at | TIMESTAMP | YES | | --- ## 2. 제품 및 로빙 테이블 ### product | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | name | VARCHAR(32) | NO | | | app_id | VARCHAR(16) | YES | | | scope | JSON | YES | | | description | VARCHAR(256) | YES | | | created_at | TIMESTAMPTZ | YES | | | updated_at | TIMESTAMPTZ | YES | | ### robeing | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | product_id | UUID | NO | FK → product | | team_id | UUID | NO | FK → team | | name | VARCHAR(32) | YES | | | level | INTEGER | YES | | | experience | INTEGER | YES | | | memory | INTEGER | YES | | | compute | INTEGER | YES | | | react | INTEGER | YES | | | empathy | INTEGER | YES | | | leadership | INTEGER | YES | | | updated_at | TIMESTAMPTZ | YES | | | created_at | TIMESTAMPTZ | YES | | | robeing_container_id | VARCHAR(64) | YES | | | robeing_container_url | VARCHAR(128) | YES | | --- ## 3. Slack 통합 테이블 ### slack_workspace | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | team_id | UUID | NO | FK → team | | slack_team_id | VARCHAR(32) | YES | | | bot_token | VARCHAR(255) | YES | | | is_enterprise_install | BOOLEAN | YES | | | is_active | BOOLEAN | YES | | | installed_at | TIMESTAMPTZ | YES | | | updated_at | TIMESTAMPTZ | YES | | | created_at | TIMESTAMPTZ | YES | | ### slack_channel | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------------| | id | UUID | NO | PK | | slack_workspace_id | UUID | NO | FK → slack_workspace | | channel_id | VARCHAR(32) | NO | Slack 채널 ID (예: C09C98KK2TT) | | channel_name | VARCHAR(255) | NO | 채널명 (예: company-x-전체) | | is_private | BOOLEAN | NO | 비공개 채널 여부 | | is_archived | BOOLEAN | NO | 아카이브 여부 | | is_member | BOOLEAN | NO | robeing 봇이 멤버인지 | | robeing_can_read | BOOLEAN | NO | 읽기 가능 여부 | | robeing_can_create | BOOLEAN | NO | 전송 가능 여부 | | robeing_can_update | BOOLEAN | NO | 수정 가능 여부 | | robeing_can_delete | BOOLEAN | NO | 삭제 가능 여부 (현재 미구현) | | status | VARCHAR(32) | YES | active, inactive 등 | | metadata | JSONB | YES | 추가 메타데이터 (용도, 설명 등) | | created_at | TIMESTAMPTZ | NO | | | updated_at | TIMESTAMPTZ | NO | | **제약조건**: `UNIQUE(slack_workspace_id, channel_id)` - 워크스페이스 내 채널 ID 중복 방지 --- ## 4. 사용자 설정 및 토큰 테이블 ### user_preference | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | INTEGER | NO | PK | | user_id | UUID | NO | FK → user | | slack_user_id | VARCHAR(32) | YES | | | news_keywords | VARCHAR(128)[] | YES | | | email_filter | VARCHAR(128)[] | YES | | | briefing_enabled | BOOLEAN | YES | | | briefing_time | TIME | YES | | | updated_at | TIMESTAMPTZ | YES | | | created_at | TIMESTAMPTZ | YES | | ### gmail_token | 컬럼명 | 타입 | NULL | 설명 | |--------|------|------|------| | id | UUID | NO | PK | | user_id | UUID | NO | FK → user | | token_data | JSONB | YES | | | oauth_config | JSONB | YES | | | scopes | JSONB | YES | | | metadata | JSONB | YES | | | expiry | TIMESTAMP | YES | | | is_equipped | BOOLEAN | YES | | | equipped_to | VARCHAR(50) | YES | | | token_type | VARCHAR | YES | | | expires_at | DOUBLE PRECISION | YES | | | created_at | TIMESTAMPTZ | YES | | | updated_at | TIMESTAMPTZ | YES | | --- ## 5. 로그 및 데이터 테이블 ### conversation_log - **용도**: 대화 기록 - **Primary Key**: id (INTEGER) | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | INTEGER | NO | | PK | | user_id | UUID | NO | | FK → user | | channel_id | VARCHAR(16) | YES | | | | message | VARCHAR | YES | | | | response | VARCHAR | YES | | | | intent | VARCHAR(256) | YES | | | | confidence | DOUBLE PRECISION | YES | | | | thread_ts | VARCHAR(128) | YES | | | | timestamp | TIMESTAMPTZ | YES | | | | channel_type | VARCHAR(32) | YES | | | ### intent_* (동적 의도 레지스트리) - **용도**: 제로샷 의도 분류 설정을 DB에서 관리 - **관련 테이블** - `intents`: 의도 메타데이터 (id, name, description, active, created_at, updated_at) - `intent_prototypes`: 의도별 임베딩(pgvector) 버전/출처 기록 - `intent_thresholds`: semantic/LLM 경로별 임계값 관리 - `intent_path_stats`: fastpath/semantic/llm/clarify 경로 별 베타분포 파라미터(alpha, beta) 저장 SemanticIntentClassifier는 위 테이블에서 활성화된 의도를 불러오고, 프로토타입/임계값이 없을 경우 YAML 레지스트리로 폴백합니다. 이 구조 덕분에 신규 의도를 추가할 때 DB 레코드만 업데이트하면 서비스가 재시작 없이 최신 의도 설명을 사용할 수 있습니다. ### news - **용도**: 뉴스 데이터 - **Primary Key**: id (UUID) | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | UUID | NO | | PK | | user_id | UUID | YES | | FK → user | | data | JSONB | YES | | | | created_at | TIMESTAMPTZ | YES | CURRENT_TIMESTAMP | | | updated_at | TIMESTAMPTZ | YES | CURRENT_TIMESTAMP | | ### rb_news - **용도**: 뉴스 발행 관리 - **Primary Key**: id (UUID) | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | UUID | NO | gen_random_uuid() | PK | | title | TEXT | NO | | | | url | TEXT | NO | | UNIQUE | | summary | TEXT | YES | | | | slack_message_ts | VARCHAR(100) | YES | | | | slack_channel_id | VARCHAR(100) | YES | | | | is_published | BOOLEAN | YES | false | | | published_at | TIMESTAMPTZ | YES | | | | created_at | TIMESTAMPTZ | YES | now() | | **인덱스**: - `rb_news_pkey`: PRIMARY KEY (id) ### robeing_diary - **용도**: 로빙 일기(성장 일지) 저장 - **Primary Key**: id (INTEGER) | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | INTEGER | NO | | PK, SERIAL | | date | DATE | NO | | 일기 날짜 | | robeing_id | VARCHAR(50) | NO | | 로빙 식별자 | | summary | TEXT | YES | | 일기 요약 | | dominant_emotion | VARCHAR(50) | YES | | 지배적 감정 | | stats | JSONB | YES | '{}' | 통계 데이터 | | full_content | TEXT | NO | | 전체 일기 내용 (마크다운) | | created_at | TIMESTAMPTZ | YES | NOW() | 생성 시간 | **인덱스**: - `idx_robeing_diary_date`: (date DESC) - `idx_robeing_diary_robeing`: (robeing_id) - UNIQUE: (date, robeing_id) ### activity_log - **용도**: 로빙 활동 로그 (스킬 실행, 스케줄러 작업 등) - **Primary Key**: id (INTEGER) | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | INTEGER | NO | | PK, SERIAL | | robeing_id | VARCHAR(50) | NO | | 로빙 식별자 | | activity_type | VARCHAR(50) | NO | | 'skill', 'scheduler', 'internal' | | skill_name | VARCHAR(100) | YES | | 스킬명 (activity_type='skill'일 때) | | status | VARCHAR(20) | NO | | 'success', 'error', 'partial' | | result_summary | TEXT | YES | | 결과 요약 | | error_message | TEXT | YES | | 에러 메시지 (status='error'일 때) | | meta | JSONB | YES | '{}' | 메타데이터 | | created_at | TIMESTAMPTZ | YES | NOW() | 생성 시간 | **인덱스**: - `idx_activity_log_robeing_date`: (robeing_id, created_at DESC) - `idx_activity_log_type`: (activity_type) - `rb_news_url_key`: UNIQUE (url) - `idx_rb_news_url`: btree (url) - `idx_rb_news_slack_message_ts`: btree (slack_message_ts) - `idx_rb_news_created_at`: btree (created_at DESC) ### team_document - **용도**: 팀 문서 RAG 시스템 - **Primary Key**: id (UUID) | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | UUID | NO | gen_random_uuid() | PK | | team_id | UUID | NO | | FK → team | | filename | VARCHAR(255) | NO | | | | file_hash | VARCHAR(64) | NO | | SHA256 | | file_size | BIGINT | YES | | bytes | | mime_type | VARCHAR(100) | YES | | | | storage_path | TEXT | NO | | /mnt/hdd/uploads/{team_id}/ | | text_content | TEXT | YES | | | | chunk_count | INTEGER | YES | 0 | ChromaDB 청크 수 | | processing_status | VARCHAR(20) | YES | 'pending' | pending/completed/failed | | metadata | JSONB | YES | '{}' | uploaded_by, tags, summary 등 | | created_at | TIMESTAMPTZ | YES | CURRENT_TIMESTAMP | | | updated_at | TIMESTAMPTZ | YES | CURRENT_TIMESTAMP | | **인덱스**: - `idx_team_doc_hash`: UNIQUE (team_id, file_hash) - 팀별 파일 중복 방지 --- ### naverworks_token - **용도**: NAVER WORKS OAuth 토큰 및 Passport 정보 - **Primary Key**: id (UUID) - **Unique**: user_id - **Foreign Key**: user_id → user(id) ON DELETE CASCADE | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | id | UUID | NO | gen_random_uuid() | PK | | user_id | UUID | NO | | FK → user | | access_token | TEXT | YES | | 개별 저장용 | | refresh_token | TEXT | YES | | 개별 저장용 | | token_type | VARCHAR(50) | YES | 'Bearer' | | | expires_at | TIMESTAMP | YES | | | | scopes | JSONB | YES | | | | domain_id | VARCHAR(255) | YES | | | | service_account | VARCHAR(255) | YES | | | | metadata | JSONB | YES | | | | created_at | TIMESTAMPTZ | YES | CURRENT_TIMESTAMP | | | updated_at | TIMESTAMPTZ | YES | CURRENT_TIMESTAMP | | | username | VARCHAR(255) | YES | | | | account_id | VARCHAR(255) | YES | | | | token_data | JSONB | YES | | 통합 저장용 | | oauth_config | JSONB | YES | | | | is_equipped | BOOLEAN | YES | false | Passport 장착 여부 | **인덱스**: - idx_naverworks_token_user_id (user_id) - idx_naverworks_token_expires_at (expires_at) **트리거**: - update_naverworks_token_updated_at: updated_at 자동 갱신 ## 6. 기타 정보 ### Custom Types - **user_role**: ENUM (OWNER, MEMBER, GUEST) ### Functions - **update_column_updated_at**: 모든 테이블의 updated_at 자동 갱신 트리거 ### 외래키 관계 | 테이블 | 컬럼 | 참조 테이블 | 참조 컬럼 | |--------|------|------------|----------| | team | company_id | company | id | | user | team_id | team | id | | robeing | product_id | product | id | | robeing | team_id | team | id | | slack_workspace | team_id | team | id | | workspace_member | user_id | user | id | | conversation_log | user_id | user | id | | gmail_token | user_id | user | id | | news | user_id | user | id | | user_preference | user_id | user | id | | team_document | team_id | team | id | --- --- ## 7. 감정 분석 테이블 (robeing_metrics DB) ### emotion_readings - **용도**: 사용자 및 로빙 감정 분석 시계열 데이터 - **데이터베이스**: robeing_metrics (TimescaleDB) - **특징**: 시계열 하이퍼테이블, Top-p 기반 복합 감정 저장 | 컬럼명 | 타입 | NULL | 기본값 | 설명 | |--------|------|------|--------|------| | created_at | TIMESTAMPTZ | NO | | 파티션 키 | | user_id | UUID | YES | | user 테이블 참조 | | company_id | UUID | YES | | | | robeing_id | VARCHAR(50) | YES | | | | emotion_type | VARCHAR(20) | YES | | user/robeing | | probs | JSONB | NO | | 7개 감정 확률 분포 | | entropy | DOUBLE PRECISION | YES | | | | model_version | VARCHAR(50) | YES | | onnx-7emotions-v1 등 | | meta | JSONB | YES | | source, message_length 등 | | text_hash | VARCHAR(64) | YES | | SHA256 | | top_emotions | JSONB | YES | | [{"label", "probability"}] | | cumulative_p | DOUBLE PRECISION | YES | | Top-p 70% | **인덱스**: - `emotion_readings_created_at_idx`: btree (created_at DESC) - `idx_emotion_user_time`: btree (user_id, created_at DESC) - `idx_emotion_company_time`: btree (company_id, created_at DESC) - `idx_emotion_robeing_time`: btree (robeing_id, created_at DESC) - `idx_emotion_type`: btree (emotion_type, created_at DESC) **제약 조건**: - `emotion_readings_emotion_type_check`: emotion_type IN ('user', 'robeing') **트리거**: - `ts_insert_blocker`: TimescaleDB 하이퍼테이블 삽입 제어 **예시 데이터**: ```json { "top_emotions": [ {"label": "fear", "probability": 0.499}, {"label": "neutral", "probability": 0.335} ], "cumulative_p": 0.833, "probs": { "fear": 0.499, "neutral": 0.335, "anger": 0.056, "sadness": 0.048, "disgust": 0.032, "surprise": 0.018, "happiness": 0.012 }, "entropy": 1.234 } ``` --- ## 주의사항 ### 데이터베이스 정보 - **main_db**: 주요 서비스 데이터 (PostgreSQL) - 소유자: robeings - 테이블: user, team, robeing, conversation_log 등 - **robeing_metrics**: 시계열 메트릭 데이터 (TimescaleDB) - 테이블: emotion_readings - 특징: 시간 기반 파티셔닝, 자동 압축, retention 정책 - **타임스탬프**: TIMESTAMPTZ 사용 (WITH TIME ZONE) - **자동 갱신**: update_column_updated_at 트리거로 updated_at 자동 관리 - **UUID 기반**: 주요 테이블 모두 UUID 사용 --- **문서 끝**