File size: 8,465 Bytes
9f30ef0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
-- ============================================
-- μŒμ„± 꼬맨틀 λ°μ΄ν„°λ² μ΄μŠ€ μŠ€ν‚€λ§ˆ
-- ============================================

-- ============================================
-- 1. 퍼즐 정보 ν…Œμ΄λΈ” (λ§ˆμŠ€ν„° 데이터)
-- ============================================
CREATE TABLE puzzles (
    puzzle_number INT PRIMARY KEY,

    -- μ •λ‹΅ 정보
    answer_word VARCHAR(100) NOT NULL,

    -- 퍼즐 메타데이터
    difficulty VARCHAR(20),                     -- 'easy', 'medium', 'hard'
    category VARCHAR(50),                       -- μΉ΄ν…Œκ³ λ¦¬ (예: 'meme', 'movie', 'song')

    -- λ‚ μ§œ 정보
    puzzle_date DATE NOT NULL UNIQUE,           -- 퍼즐 λ‚ μ§œ (YYYY-MM-DD)

    -- νƒ€μž„μŠ€νƒ¬ν”„
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    -- μ œμ•½ 쑰건
    CONSTRAINT positive_puzzle_number CHECK (puzzle_number > 0)
);

CREATE INDEX idx_puzzle_date ON puzzles(puzzle_date);

COMMENT ON TABLE puzzles IS '일일 퍼즐 정보 - μ •λ‹΅ 단어';


-- ============================================
-- 2. μΆ”μΈ‘ 기둝 ν…Œμ΄λΈ” (μŒμ„± 파일 없이 λ©”νƒ€λ°μ΄ν„°λ§Œ)
-- ============================================
CREATE TABLE guess_records (
    -- κΈ°λ³Έ ν‚€
    id BIGSERIAL PRIMARY KEY,

    -- κ²Œμž„ μ»¨ν…μŠ€νŠΈ
    session_id VARCHAR(100) NOT NULL,
    puzzle_number INT NOT NULL,

    -- AI 뢄석 κ²°κ³Ό
    pitch DECIMAL(5, 2),                     -- AI μŒλ†’μ΄ 뢄석 (0-100)
    rhythm DECIMAL(5, 2),                    -- AI 리듬감 뢄석 (0-100)
    energy DECIMAL(5, 2),                    -- AI μ—λ„ˆμ§€ 뢄석 (0-100)
    pronunciation DECIMAL(5, 2),             -- AI 발음 뢄석 (0-100)
    transcript TEXT,                         -- AI 뢄석 λŒ€μ‚¬ (score, kept for compatibility)
    user_text TEXT,                          -- STT transcription from MCP
    overall DECIMAL(5, 2),                   -- AI 총점 (0-100)
    advice TEXT,                             -- AI μ‘°μ–Έ

    -- μ •λ‹΅ μ—¬λΆ€
    is_correct BOOLEAN DEFAULT FALSE,

    -- νƒ€μž„μŠ€νƒ¬ν”„
    guess_timestamp BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    -- μ œμ•½ 쑰건
    CONSTRAINT pitch_range CHECK (pitch IS NULL OR (pitch >= 0 AND pitch <= 100)),
    CONSTRAINT rhythm_range CHECK (rhythm IS NULL OR (rhythm >= 0 AND rhythm <= 100)),
    CONSTRAINT energy_range CHECK (energy IS NULL OR (energy >= 0 AND energy <= 100)),
    CONSTRAINT pronunciation_range CHECK (pronunciation IS NULL OR (pronunciation >= 0 AND pronunciation <= 100)),
    CONSTRAINT overall_range CHECK (overall IS NULL OR (overall >= 0 AND overall <= 100))
);

-- ============================================
-- 인덱슀
-- ============================================
CREATE INDEX idx_guess_session ON guess_records(session_id);
CREATE INDEX idx_guess_puzzle ON guess_records(puzzle_number);
CREATE INDEX idx_guess_created ON guess_records(created_at);
CREATE INDEX idx_guess_correct ON guess_records(is_correct);
CREATE INDEX idx_guess_overall ON guess_records(overall);

COMMENT ON TABLE guess_records IS 'μ‚¬μš©μž μΆ”μΈ‘ 기둝 - AI 뢄석 결과만 μ €μž₯ (μŒμ„± 파일 μ—†μŒ)';


-- ============================================
-- 3. λ‚ μ§œλ³„ 전체 톡계 VIEW (λŒ€μ‹œλ³΄λ“œμš©)
-- ============================================
CREATE OR REPLACE VIEW daily_statistics AS
SELECT
    p.puzzle_date,

    -- μ°Έμ—¬μž 수 (ν•΄λ‹Ή λ‚ μ§œμ— μ‹œλ„ν•œ 고유 μ„Έμ…˜ 수)
    COUNT(DISTINCT gr.session_id) as participants,

    -- μ •λ‹΅μž 수 (ν•΄λ‹Ή λ‚ μ§œμ— μ •λ‹΅ 맞좘 고유 μ„Έμ…˜ 수)
    COUNT(DISTINCT CASE WHEN gr.is_correct = true THEN gr.session_id END) as winners,

    -- μ •λ‹΅λ₯ 
    ROUND(
        CASE
            WHEN COUNT(DISTINCT gr.session_id) > 0
            THEN (COUNT(DISTINCT CASE WHEN gr.is_correct = true THEN gr.session_id END)::NUMERIC
                  / COUNT(DISTINCT gr.session_id) * 100)
            ELSE 0
        END,
        1
    ) as success_rate,

    -- μ‹œλ„ 횟수 (ν•΄λ‹Ή λ‚ μ§œ 총 μŒμ„± μ‹œλ„ 수)
    COUNT(*) as total_attempts,

    -- 평균 μ‹œλ„ 횟수 (μ°Έμ—¬μžλ‹Ή)
    ROUND(
        CASE
            WHEN COUNT(DISTINCT gr.session_id) > 0
            THEN COUNT(*)::NUMERIC / COUNT(DISTINCT gr.session_id)
            ELSE 0
        END,
        1
    ) as avg_attempts_per_user

FROM puzzles p
LEFT JOIN guess_records gr ON p.puzzle_number = gr.puzzle_number
GROUP BY p.puzzle_date
ORDER BY p.puzzle_date DESC;

COMMENT ON VIEW daily_statistics IS 'λ‚ μ§œλ³„ 전체 κ²Œμž„ 톡계 - λŒ€μ‹œλ³΄λ“œμš©';


-- ============================================
-- 4. 전체 λˆ„μ  톡계 VIEW (λŒ€μ‹œλ³΄λ“œμš©)
-- ============================================
CREATE OR REPLACE VIEW overall_statistics AS
SELECT
    -- 전체 μ°Έμ—¬μž 수 (λͺ¨λ“  μ‹œκ°„ λ™μ•ˆ μ°Έμ—¬ν•œ 고유 μ„Έμ…˜ 수)
    COUNT(DISTINCT session_id) as total_participants,

    -- 전체 μ •λ‹΅μž 수 (ν•œ λ²ˆμ΄λΌλ„ μ •λ‹΅ 맞좘 고유 μ„Έμ…˜ 수)
    COUNT(DISTINCT CASE WHEN is_correct = true THEN session_id END) as total_winners,

    -- 전체 μ •λ‹΅λ₯  (μ •λ‹΅μž 수 / μ°Έμ—¬μž 수)
    ROUND(
        CASE
            WHEN COUNT(DISTINCT session_id) > 0
            THEN (COUNT(DISTINCT CASE WHEN is_correct = true THEN session_id END)::NUMERIC
                  / COUNT(DISTINCT session_id) * 100)
            ELSE 0
        END,
        1
    ) as overall_success_rate,

    -- 전체 μ‹œλ„ 횟수
    COUNT(*) as total_attempts,

    -- 전체 퍼즐 수
    (SELECT COUNT(*) FROM puzzles) as total_puzzles,

    -- 전체 평균 μ‹œλ„ 횟수
    ROUND(
        CASE
            WHEN COUNT(DISTINCT session_id) > 0
            THEN COUNT(*)::NUMERIC / COUNT(DISTINCT session_id)
            ELSE 0
        END,
        1
    ) as avg_attempts_per_user

FROM guess_records;

COMMENT ON VIEW overall_statistics IS '전체 λˆ„μ  κ²Œμž„ 톡계 - λŒ€μ‹œλ³΄λ“œμš©';


-- ============================================
-- 5. κ°œλ³„ μ‚¬μš©μž 톡계 VIEW
-- ============================================
CREATE OR REPLACE VIEW user_statistics AS
WITH
daily_activity AS (
    SELECT
        gr.session_id,
        p.puzzle_date,
        COUNT(*) as attempts,
        MAX(CASE WHEN gr.is_correct = true THEN 1 ELSE 0 END) as won
    FROM guess_records gr
    JOIN puzzles p ON gr.puzzle_number = p.puzzle_number
    GROUP BY gr.session_id, p.puzzle_date
),
streaks AS (
    SELECT
        session_id,
        puzzle_date,
        won,
        puzzle_date - (ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY puzzle_date))::int AS play_streak_group
    FROM daily_activity
),
current_play_streak AS (
    SELECT
        session_id,
        COUNT(*) as play_streak
    FROM streaks
    WHERE play_streak_group = (
        SELECT play_streak_group
        FROM streaks s2
        WHERE s2.session_id = streaks.session_id
        ORDER BY puzzle_date DESC
        LIMIT 1
    )
    GROUP BY session_id
),
win_streaks AS (
    SELECT
        session_id,
        COUNT(*) as win_streak
    FROM (
        SELECT
            session_id,
            puzzle_date,
            won,
            SUM(CASE WHEN won = 0 THEN 1 ELSE 0 END)
                OVER (PARTITION BY session_id ORDER BY puzzle_date DESC) as loss_count
        FROM daily_activity
    ) ws
    WHERE loss_count = 0 AND won = 1
    GROUP BY session_id
)
SELECT
    da.session_id,
    COUNT(DISTINCT da.puzzle_date) as total_plays,
    SUM(da.attempts) as total_attempts,
    SUM(da.won) as wins,
    ROUND(
        CASE
            WHEN COUNT(DISTINCT da.puzzle_date) > 0
            THEN (SUM(da.won)::NUMERIC / COUNT(DISTINCT da.puzzle_date) * 100)
            ELSE 0
        END,
        1
    ) as win_rate,
    COALESCE(cps.play_streak, 0) as play_streak,
    COALESCE(ws.win_streak, 0) as win_streak
FROM daily_activity da
LEFT JOIN current_play_streak cps ON da.session_id = cps.session_id
LEFT JOIN win_streaks ws ON da.session_id = ws.session_id
GROUP BY da.session_id, cps.play_streak, ws.win_streak;

COMMENT ON VIEW user_statistics IS 'κ°œλ³„ μ‚¬μš©μž κ²Œμž„ 톡계';


-- ============================================
-- μƒ˜ν”Œ 데이터 (ν…ŒμŠ€νŠΈμš©)
-- ============================================
INSERT INTO puzzles (puzzle_number, answer_word, puzzle_date, difficulty, category)
VALUES
(1, 'Tralalero Tralala', '2024-11-24', 'easy', 'meme'),
(2, 'Wingardium Leviosa', '2024-11-25', 'medium', 'movie'),
(3, 'Baby shark, doo doo doo doo doo doo', '2024-11-26', 'hard', 'song');