Skip to content

Latest commit

Β 

History

History
486 lines (400 loc) Β· 15.8 KB

File metadata and controls

486 lines (400 loc) Β· 15.8 KB

μ˜¨μ‹κ΅¬ AI λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ

λ¬Έμ„œ 버전: 2026-01-10 (v2.2 - MEMBER μ—­ν•  μ„ΈλΆ„ν™” & 질문 μš°μ„ μˆœμœ„ λ„μž…)


πŸ“Š ERD

erDiagram
    FAMILY ||--o{ MEMBER : has
    FAMILY ||--o{ FAMILY_REPORT : archives
    MEMBER ||--o{ MEMBER_QUESTION : receives
    MEMBER_QUESTION ||--o{ ANSWER : has
    ANSWER ||--o{ REACTION : receives
    ANSWER ||--o{ COMMENT : has

    FAMILY {
        bigint id PK
        text name
        text lifecycle_status
        timestamptz created_at
        timestamptz updated_at
    }

    MEMBER {
        bigint id PK
        bigint family_id FK
        text nickname
        text role_type
        text role_name
        int order_in_role
        text label
        date birthday
        timestamptz created_at
        timestamptz updated_at
    }

    QUESTION {
        bigint id PK
        text content
        int level
        text category
        timestamptz created_at
    }

    MEMBER_QUESTION {
        bigint id PK
        bigint member_id FK
        text content
        int level
        int priority
        date planned_date
        text status
        int shuffle_count
        jsonb metadata
        timestamptz answered_at
    }

    ANSWER {
        bigint id PK
        bigint member_question_id FK
        text answer_type
        jsonb content
        timestamptz created_at
    }

    REACTION {
        bigint id PK
        bigint answer_id FK
        bigint member_id FK
        text emoji
        timestamptz created_at
    }

    COMMENT {
        bigint id PK
        bigint answer_id FK
        bigint commenter_member_id FK
        bigint parent_id FK
        text content
        timestamptz created_at
        timestamptz edited_at
        timestamptz deleted_at
    }

    FAMILY_REPORT {
        bigint id PK
        bigint family_id FK
        text type
        date start_date
        date end_date
        text summary
        jsonb data
        timestamptz created_at
    }
Loading

πŸ“‹ ν…Œμ΄λΈ” 상세

FAMILY

κ°€μ‘± λ‹¨μœ„μ˜ 루트 μ—”ν„°ν‹°

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK κ°€μ‘± 고유 ID
name TEXT NOT NULL κ°€μ‘± 이름
lifecycle_status TEXT κ°€μ‘± μƒνƒœ: 바쁨 | ν‰μ†Œ | 행사 | λ³€ν™”
created_at TIMESTAMPTZ DEFAULT now() 생성 μ‹œκ°
updated_at TIMESTAMPTZ DEFAULT now() μˆ˜μ • μ‹œκ°
CREATE TABLE family (
    id                  BIGSERIAL PRIMARY KEY,
    name                TEXT NOT NULL,
    lifecycle_status    TEXT,
    created_at          TIMESTAMPTZ DEFAULT now(),
    updated_at          TIMESTAMPTZ DEFAULT now()
);

MEMBER

κ°€μ‘± ꡬ성원

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK ꡬ성원 고유 ID
family_id BIGINT FK, NOT NULL μ†Œμ† κ°€μ‘±
nickname TEXT NOT NULL ν™”λ©΄ ν‘œμ‹œμš© (κ³΅μ£Όλ‹˜, μšΈμ•„λΉ  λ“±)
role_type TEXT μ‹œμŠ€ν…œ 둜직용 (PARENT, CHILD λ“±)
role_name TEXT μ •κ·œν™”λœ 호칭 (μ•„λΉ , μ—„λ§ˆ, λ”Έ λ“±)
order_in_role INT DEFAULT 1 μˆœμ„œ (첫째, λ‘˜μ§Έ λ“±)
label TEXT 관계 λ§₯락 μ••μΆ• (첫째 λ”Έ, 막내, μ„œμšΈ ν°μ—„λ§ˆ λ“±)
birthday DATE 생년월일
created_at TIMESTAMPTZ DEFAULT now() κ°€μž… μ‹œκ°
updated_at TIMESTAMPTZ DEFAULT now() μˆ˜μ • μ‹œκ°
CREATE TABLE member (
    id              BIGSERIAL PRIMARY KEY,
    family_id       BIGINT NOT NULL REFERENCES family(id) ON DELETE CASCADE,
    nickname        TEXT NOT NULL,
    role_type       TEXT,
    role_name       TEXT,
    order_in_role   INT DEFAULT 1,
    label           TEXT,
    birthday        DATE,
    created_at      TIMESTAMPTZ DEFAULT now(),
    updated_at      TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_member_family ON member(family_id);
CREATE INDEX idx_member_role_type ON member(role_type);

QUESTION

질문 ν…œν”Œλ¦Ώ (μš΄μ˜μžκ°€ λ“±λ‘ν•˜λŠ” μ‹œλ“œ 데이터, 참쑰용)

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK 질문 고유 ID
content TEXT NOT NULL 질문 본문
level INT DEFAULT 1 λ‚œμ΄λ„ 14 (12: κ°€λ²Όμš΄ 주식, 3~4: κΉŠμ€ νŠΉλ³„μ‹)
category TEXT μΉ΄ν…Œκ³ λ¦¬: 일상 | μΆ”μ–΅ | 감정 | 미래 | μ·¨λ―Έ | 관계
created_at TIMESTAMPTZ DEFAULT now() 생성 μ‹œκ°
CREATE TABLE question (
    id          BIGSERIAL PRIMARY KEY,
    content     TEXT NOT NULL,
    level       INT DEFAULT 1 CHECK (level BETWEEN 1 AND 4),
    category    TEXT,
    created_at  TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_question_level ON question(level);
CREATE INDEX idx_question_category ON question(category);

μ°Έκ³ : QUESTION은 ν…œν”Œλ¦Ώ ν’€λ‘œλ§Œ μ‚¬μš©λ©λ‹ˆλ‹€. μ‹ κ·œ 멀버 κ°€μž… μ‹œ MEMBER_QUESTION에 λ³΅μ‚¬λ©λ‹ˆλ‹€.


MEMBER_QUESTION

멀버별 μ‹€μ œ λ°œμ†‘ 질문 (QUESTIONμ—μ„œ 볡사 λ˜λŠ” AI 생성)

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK 질문 고유 ID
member_id BIGINT FK, NOT NULL μˆ˜μ‹  멀버 (주인곡)
content TEXT NOT NULL 질문 λ‚΄μš© (ν…œν”Œλ¦Ώ 볡사 λ˜λŠ” AI κ°œμΈν™”)
level INT DEFAULT 1 λ‚œμ΄λ„ 1~4
priority INT DEFAULT 1 μš°μ„ μˆœμœ„ (1:ν…œν”Œλ¦Ώ, 2:본인닡변 기반 νŒŒμƒ, 3:10:1 κ°€μ‘±λ‹΅λ³€ 기반 νŒŒμƒ)
planned_date DATE NOT NULL λ°œμ†‘ μ˜ˆμ •μΌ
status TEXT DEFAULT 'scheduled' μƒνƒœ: scheduled | sent | answered | skipped | passed
shuffle_count INT DEFAULT 0 μ…”ν”Œ 횟수 (μ΅œλŒ€ 3회)
metadata JSONB AI 생성 정보 λ“±
answered_at TIMESTAMPTZ λ‹΅λ³€ μ™„λ£Œ μ‹œκ°

metadata ꡬ쑰:

// AI 생성 질문
{"generated_by": "ai", "model": "gpt-5-nano", "confidence": 0.9}

// ν…œν”Œλ¦Ώ 볡사
{"generated_by": "template", "source_question_id": 42}

// μˆ˜λ™ μž…λ ₯
{"generated_by": "manual"}

// 1:1 νŒŒμƒ (P2)
{"logic_type": "follow_up", "parent_answer_id": 102}

// 10:1 ν•©μ„± (P3)
{"logic_type": "synthesis", "source_answer_ids": [101, 102, 108], "ai_insight": "..."}

μ‹ κ·œ 멀버 κ°€μž… μ‹œ ν…œν”Œλ¦Ώ 볡사:

INSERT INTO member_question (member_id, content, level, priority, planned_date, status, metadata)
SELECT
    :new_member_id,
    q.content,
    q.level,
    1,
    :planned_date,
    'scheduled',
    jsonb_build_object('generated_by', 'template', 'source_question_id', q.id)
FROM question q
WHERE q.level <= 2
ORDER BY RANDOM()
LIMIT 30;
CREATE TABLE member_question (
    id              BIGSERIAL PRIMARY KEY,
    member_id       BIGINT NOT NULL REFERENCES member(id) ON DELETE CASCADE,
    content         TEXT NOT NULL,
    level           INT DEFAULT 1 CHECK (level BETWEEN 1 AND 4),
    priority        INT DEFAULT 1 CHECK (priority BETWEEN 1 AND 3),
    planned_date    DATE NOT NULL,
    status          TEXT DEFAULT 'scheduled'
                    CHECK (status IN ('scheduled', 'sent', 'answered', 'skipped', 'passed')),
    shuffle_count   INT DEFAULT 0,
    metadata        JSONB,
    answered_at     TIMESTAMPTZ,

    UNIQUE(member_id, planned_date)
);

CREATE INDEX idx_member_question_member ON member_question(member_id);
CREATE INDEX idx_member_question_status ON member_question(status);

-- λ””μŠ€νŒ¨μΉ˜ μš°μ„ μˆœμœ„ 반영 인덱슀 (priority μš°μ„ , planned_date λ‹€μŒ)
CREATE INDEX idx_mq_dispatch ON member_question(priority DESC, planned_date ASC);

ANSWER

μ§ˆλ¬Έμ— λŒ€ν•œ λ‹΅λ³€ (μž‘μ„±μž = member_question.member_id)

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK λ‹΅λ³€ 고유 ID
member_question_id BIGINT FK, NOT NULL, UNIQUE 질문 ID (1:1)
answer_type TEXT NOT NULL λ‹΅λ³€ μœ ν˜•: text | image | audio | video | mixed
content JSONB NOT NULL λ‹΅λ³€ λ‚΄μš© (μ•„λž˜ ꡬ쑰 μ°Έμ‘°)
created_at TIMESTAMPTZ DEFAULT now() μž‘μ„± μ‹œκ°

content ꡬ쑰:

// ν…μŠ€νŠΈ
{"text": "ν—Œν„°ν—Œν„° μ˜€ν”„λ‹ λ“£λŠ” 쀑"}

// 이미지
{"url": "https://...", "mime": "image/jpeg"}

// ν˜Όν•©
{"text": "였늘 먹은 라면!", "url": "https://..."}
CREATE TABLE answer (
    id                  BIGSERIAL PRIMARY KEY,
    member_question_id  BIGINT NOT NULL UNIQUE REFERENCES member_question(id) ON DELETE CASCADE,
    answer_type         TEXT NOT NULL DEFAULT 'text'
                        CHECK (answer_type IN ('text', 'image', 'audio', 'video', 'mixed')),
    content             JSONB NOT NULL,
    created_at          TIMESTAMPTZ DEFAULT now()
);

μž‘μ„±μž 쑰회: JOIN member_question mq ON a.member_question_id = mq.id β†’ mq.member_idκ°€ μž‘μ„±μž


REACTION

이λͺ¨μ§€ λ¦¬μ•‘μ…˜ (μ΄ˆμ €λ§ˆμ°° 1초 λ°˜μ‘)

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK λ¦¬μ•‘μ…˜ 고유 ID
answer_id BIGINT FK, NOT NULL λŒ€μƒ λ‹΅λ³€ ID
member_id BIGINT FK, NOT NULL λ¦¬μ•‘μ…˜ 남긴 멀버
emoji TEXT NOT NULL 이λͺ¨μ§€: heart | check | smile λ“±
created_at TIMESTAMPTZ DEFAULT now() λ¦¬μ•‘μ…˜ μ‹œκ°
CREATE TABLE reaction (
    id          BIGSERIAL PRIMARY KEY,
    answer_id   BIGINT NOT NULL REFERENCES answer(id) ON DELETE CASCADE,
    member_id   BIGINT NOT NULL REFERENCES member(id) ON DELETE CASCADE,
    emoji       TEXT NOT NULL,
    created_at  TIMESTAMPTZ DEFAULT now(),

    UNIQUE(answer_id, member_id)
);

CREATE INDEX idx_reaction_answer ON reaction(answer_id);

COMMENT

닡변에 λŒ€ν•œ λŒ“κΈ€ (λŒ€λŒ“κΈ€ 지원)

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK λŒ“κΈ€ 고유 ID
answer_id BIGINT FK, NOT NULL λŒ€μƒ λ‹΅λ³€ ID
commenter_member_id BIGINT FK, NOT NULL μž‘μ„±μž ID
parent_id BIGINT FK λΆ€λͺ¨ λŒ“κΈ€ (λŒ€λŒ“κΈ€)
content TEXT NOT NULL λŒ“κΈ€ λ‚΄μš©
created_at TIMESTAMPTZ DEFAULT now() μž‘μ„± μ‹œκ°
edited_at TIMESTAMPTZ μˆ˜μ • μ‹œκ°
deleted_at TIMESTAMPTZ μ‚­μ œ μ‹œκ° (μ†Œν”„νŠΈ μ‚­μ œ)
CREATE TABLE comment (
    id                  BIGSERIAL PRIMARY KEY,
    answer_id           BIGINT NOT NULL REFERENCES answer(id) ON DELETE CASCADE,
    commenter_member_id BIGINT NOT NULL REFERENCES member(id) ON DELETE CASCADE,
    parent_id           BIGINT REFERENCES comment(id) ON DELETE CASCADE,
    content             TEXT NOT NULL,
    created_at          TIMESTAMPTZ DEFAULT now(),
    edited_at           TIMESTAMPTZ,
    deleted_at          TIMESTAMPTZ
);

CREATE INDEX idx_comment_answer ON comment(answer_id);

FAMILY_REPORT

μ£Όκ°„/μ›”κ°„ κ°€μ‘± 리포트

컬럼 νƒ€μž… μ œμ•½ μ„€λͺ…
id BIGSERIAL PK 리포트 고유 ID
family_id BIGINT FK, NOT NULL κ°€μ‘± ID
type TEXT NOT NULL μœ ν˜•: WEEKLY | MONTHLY
start_date DATE NOT NULL κΈ°κ°„ μ‹œμž‘μΌ
end_date DATE NOT NULL κΈ°κ°„ μ’…λ£ŒμΌ
summary TEXT AI 생성 μš”μ•½
data JSONB ν•˜μ΄λΌμ΄νŠΈ 데이터 (μ•„λž˜ ꡬ쑰 μ°Έμ‘°)
created_at TIMESTAMPTZ DEFAULT now() 생성 μ‹œκ°

data ꡬ쑰:

{
  "answer_ids": [101, 105, 112],
  "top_keywords": ["ν—Œν„°ν—Œν„°", "트둜트"],
  "total_answers": 7,
  "total_reactions": 23
}
CREATE TABLE family_report (
    id          BIGSERIAL PRIMARY KEY,
    family_id   BIGINT NOT NULL REFERENCES family(id) ON DELETE CASCADE,
    type        TEXT NOT NULL CHECK (type IN ('WEEKLY', 'MONTHLY')),
    start_date  DATE NOT NULL,
    end_date    DATE NOT NULL,
    summary     TEXT,
    data        JSONB,
    created_at  TIMESTAMPTZ DEFAULT now(),

    UNIQUE(family_id, type, start_date)
);

CREATE INDEX idx_family_report_family ON family_report(family_id);

πŸ—‚οΈ 데이터 흐름

FAMILY ─────────────────────────────────────────────────────────────────┐
β”‚ lifecycle_status (바쁨/ν‰μ†Œ/행사/λ³€ν™”)                                     β”‚
β”‚                                                                       β”‚
β”œβ”€β”€ MEMBER                                                              β”‚
β”‚     └─ nickname, role_type, role_name, order_in_role, label, birthday β”‚
β”‚                                                                       β”‚
β”œβ”€β”€ FAMILY_REPORT                                                       β”‚
β”‚     └─ type (WEEKLY/MONTHLY), summary, data                           β”‚
β”‚                                                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

QUESTION (ν…œν”Œλ¦Ώ ν’€, 참쑰용)
β”‚ content, level, category
β”‚
β”‚  μ‹ κ·œ 멀버 κ°€μž… μ‹œ 볡사
β”‚  ─────────────────────►
β”‚
MEMBER_QUESTION ─────────────────────────────────────────────┐
β”‚ content (질문 λ‚΄μš©)                                          β”‚
β”‚ level (λ‚œμ΄λ„ 1~4)                                           β”‚
β”‚ priority (μš°μ„ μˆœμœ„ 1~3)                                      β”‚
β”‚ planned_date, status, shuffle_count                        β”‚
β”‚ metadata (AI 생성/νŒŒμƒ/ν•©μ„± 정보)                              β”‚
β”‚                                                            β”‚
└── ANSWER ───────────────────────────────────────────────────
      β”‚ content (λ‹΅λ³€ λ‚΄μš©, μž‘μ„±μž=질문 주인곡)                     β”‚
      β”‚                                                      β”‚
      β”œβ”€β”€ REACTION (이λͺ¨μ§€)                                    β”‚
      β”‚                                                      β”‚
      └── COMMENT (λŒ“κΈ€)                                      β”‚
β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“Š ν…Œμ΄λΈ” μš”μ•½

ν…Œμ΄λΈ” 컬럼 수 μ—­ν• 
FAMILY 5 κ°€μ‘± λ‹¨μœ„
MEMBER 10 κ°€μ‘± ꡬ성원
QUESTION 5 질문 ν…œν”Œλ¦Ώ (참쑰용)
MEMBER_QUESTION 10 멀버별 μ‹€μ œ 질문
ANSWER 5 λ‹΅λ³€
REACTION 5 이λͺ¨μ§€ λ¦¬μ•‘μ…˜
COMMENT 9 λŒ“κΈ€
FAMILY_REPORT 8 μ£Όκ°„/μ›”κ°„ 리포트

πŸ”§ μ£Όμš” μ œμ•½μ‘°κ±΄

μ œμ•½ μ„€λͺ…
member_question(member_id, planned_date) UNIQUE 멀버당 ν•˜λ£¨ 1질문
member_question.priority CHECK (1~3) μš°μ„ μˆœμœ„ λ²”μœ„
reaction(answer_id, member_id) UNIQUE λ‹΅λ³€λ‹Ή 멀버 1λ¦¬μ•‘μ…˜
family_report(family_id, type, start_date) UNIQUE 리포트 쀑볡 λ°©μ§€
question.level CHECK (1~4) λ‚œμ΄λ„ λ²”μœ„
member_question.level CHECK (1~4) λ‚œμ΄λ„ λ²”μœ„
member_question.status CHECK μƒνƒœκ°’ μ œν•œ

πŸ“ λ³€κ²½ 이λ ₯

버전 λ‚ μ§œ λ³€κ²½ λ‚΄μš©
v1.0 2025-08-20 초기 μŠ€ν‚€λ§ˆ
v2.0 2026-01-02 κ°„κ²°ν™” - QUESTION을 ν…œν”Œλ¦Ώ 참쑰용으둜 λ³€κ²½, MEMBER_QUESTION이 μ‹€μ œ 질문 μ €μž₯, ANSWER_ANALYSIS μ‚­μ œ, REACTION/FAMILY_REPORT μΆ”κ°€
v2.1 2026-01-02 forbidden_keywords 보λ₯˜, MEMBER_PROFILE μ‚­μ œ, MEMBER.birthday μΆ”κ°€
v2.2 2026-01-10 MEMBER 컬럼 μ„ΈλΆ„ν™”(nickname, role_type, label λ“±) 및 MEMBER_QUESTION μš°μ„ μˆœμœ„(priority) μ •μ±… 반영