YeongMin commited on
Commit
cbdeabc
ยท
1 Parent(s): 968f6a0
.claude/settings.local.json CHANGED
@@ -1,7 +1,10 @@
1
  {
2
  "permissions": {
3
  "allow": [
4
- "Bash(del:*)"
 
 
 
5
  ],
6
  "deny": [],
7
  "ask": []
 
1
  {
2
  "permissions": {
3
  "allow": [
4
+ "Bash(del:*)",
5
+ "Bash(python3:*)",
6
+ "Bash(lsof:*)",
7
+ "Bash(xargs kill -9)"
8
  ],
9
  "deny": [],
10
  "ask": []
IMPROVEMENTS.md ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ๊ธด ๋ฌธ์žฅ ๋ถ„์„ ์„ฑ๋Šฅ ๊ฐœ์„  ์‚ฌํ•ญ
2
+
3
+ ## ๐ŸŽฏ ๋ฌธ์ œ์ 
4
+ ๊ธฐ์กด ์‹œ์Šคํ…œ์€ ์งง์€ ๋ฌธ์žฅ์€ ์ž˜ ๋ถ„์„ํ–ˆ์ง€๋งŒ, **๋ฌธ์žฅ์ด ๊ธธ์–ด์ง€๊ฑฐ๋‚˜ ๋ณต์žกํ•ด์ง€๋ฉด ์ธ์‹๋ฅ ์ด ๋‚ฎ์•„์ง€๋Š” ๋ฌธ์ œ**๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
5
+
6
+ ## โœ… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•
7
+
8
+ ### 1. ๋ฌธ์žฅ ๋ถ„๋ฆฌ ํ›„ ๊ฐœ๋ณ„ ๋ถ„์„ + ์ง‘๊ณ„ (Sentence-level Analysis)
9
+
10
+ #### ๐Ÿ“ ๊ตฌํ˜„ ๋‚ด์šฉ
11
+ - **100์ž ์ด์ƒ์˜ ๊ธด ๋ฆฌ๋ทฐ**๋ฅผ ์ž๋™์œผ๋กœ ๋ฌธ์žฅ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ
12
+ - ๊ฐ ๋ฌธ์žฅ์„ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ถ„์„ํ•œ ํ›„ ๊ฒฐ๊ณผ๋ฅผ ์ง‘๊ณ„
13
+ - ๋ถ„์„ ๋ฐฉ๋ฒ•์ด `method: "sentence_split"`์œผ๋กœ ํ‘œ์‹œ๋จ
14
+
15
+ #### ๐Ÿ”„ ์ง‘๊ณ„ ์ „๋žต
16
+ - **๊ฐ์ • ๋ถ„์„**: ๊ฐ ๋ฌธ์žฅ์˜ ๊ฐ์ • ์ ์ˆ˜๋ฅผ **ํ‰๊ท **ํ•˜์—ฌ ์ „์ฒด ๊ฐ์ • ํŒ๋‹จ
17
+ - ์˜ˆ: "์ข‹์€๋ฐ ๋‚˜์จ" โ†’ ๊ธ์ • + ๋ถ€์ • ๋ฌธ์žฅ์˜ ํ‰๊ท  = ํ˜ผํ•ฉ ๊ฐ์ • ํŒŒ์•…
18
+
19
+ - **์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„**: ๊ฐ ๋ฌธ์žฅ์—์„œ ๋‚˜์˜จ **์ตœ๋Œ€ ์ ์ˆ˜**๋กœ ์ง‘๊ณ„
20
+ - ์˜ˆ: 5๊ฐœ ๋ฌธ์žฅ ์ค‘ 1๊ฐœ์—์„œ "๋ฐฐ์†ก" ์–ธ๊ธ‰ โ†’ ๋ฐฐ์†ก ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ์ธ์ •
21
+ - ์—ฌ๋Ÿฌ ์ฃผ์ œ๊ฐ€ ์„ž์ธ ๊ธด ๋ฆฌ๋ทฐ์—์„œ ๋ชจ๋“  ์ฃผ์ œ๋ฅผ ๋†“์น˜์ง€ ์•Š์Œ
22
+
23
+ #### ๐Ÿ’ก ํšจ๊ณผ
24
+ ```python
25
+ # ์˜ˆ์‹œ: 121์ž ๊ธด ๋ฆฌ๋ทฐ
26
+ ๋ฆฌ๋ทฐ = "ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”~~
27
+ ๊ฐ์ˆ˜ํ• ๋งŒํ•œ๋ฐ ์€๊ทผ ์งœ์ฆ๋‚ ์ˆ˜๋„? ๊ทธ๋ƒฅ ์ž…์œผ๋ฉด ๊ณ ์–‘์ด๋งˆ๋ƒฅ ํ„ธ์„ ๋ฟœ๋‚ด์š” ใ…Žใ…Ž
28
+ ๊ทธ๋ž˜๋„ ๋””์ž์ธ์€ ์ •๋ง ์˜ˆ์˜๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๊ดœ์ฐฎ์€ ๊ฒƒ ๊ฐ™์•„์š”. ๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ฒŒ ์™”๊ณ ์š”."
29
+
30
+ # ๊ฒฐ๊ณผ:
31
+ # - ๊ฐ์ •: ๊ธ์ • 58%, ๋ถ€์ • 27% (ํ˜ผํ•ฉ ๊ฐ์ • ์ •ํ™•ํžˆ ํฌ์ฐฉ)
32
+ # - ์นดํ…Œ๊ณ ๋ฆฌ: ์‚ฌ์ด์ฆˆ, ๋””์ž์ธ, ๊ฐ€๊ฒฉ, ๋ฐฐ์†ก (๋ชจ๋“  ์ฃผ์ œ ํƒ์ง€)
33
+ # - ๋ถ„์„ ๋ฐฉ๋ฒ•: sentence_split
34
+ ```
35
+
36
+ ---
37
+
38
+ ### 2. ํ”„๋กฌํ”„ํŠธ ์ตœ์ ํ™” - ๊ตฌ์ฒด์  ์˜ˆ์‹œ ์ถ”๊ฐ€
39
+
40
+ #### ๐Ÿ“ ๊ฐœ์„  ๋‚ด์šฉ
41
+ ๊ฐ ๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ์— **์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ๋ฅผ ํฌํ•จ**ํ•˜์—ฌ ๋ชจ๋ธ์˜ ์ดํ•ด๋„ ํ–ฅ์ƒ
42
+
43
+ #### Before & After
44
+
45
+ **Before (๊ธฐ์กด)**
46
+ ```python
47
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์˜ ํ’ˆ์งˆ์— ๋Œ€ํ•ด ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค"
48
+ ```
49
+
50
+ **After (๊ฐœ์„ )**
51
+ ```python
52
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ํ’ˆ์งˆ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.
53
+ ์˜ˆ: ์žฌ์งˆ, ๋‚ด๊ตฌ์„ฑ, ์™„์„ฑ๋„, ํ’ˆ์งˆ ์ข‹์Œ, ํ’ˆ์งˆ ๋‚˜์จ, ํŠผํŠผ, ์•ฝํ•จ"
54
+ ```
55
+
56
+ #### ๐Ÿ’ก ํšจ๊ณผ
57
+ - ๋ชจ๋ธ์ด **๊ตฌ์ฒด์ ์ธ ํ‚ค์›Œ๋“œ**๋ฅผ ํ†ตํ•ด ๋” ์ •ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฅ˜
58
+ - **๋ฌธ๋งฅ ์ดํ•ด๋„ ํ–ฅ์ƒ**: "ํŠผํŠผํ•˜๋‹ค" = ํ’ˆ์งˆ ๊ด€๋ จ ๋‚ด์šฉ
59
+ - **์˜ค๋ถ„๋ฅ˜ ๊ฐ์†Œ**: ์• ๋งคํ•œ ํ‘œํ˜„๋„ ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด ์ •ํ™•ํžˆ ๋ถ„๋ฅ˜
60
+
61
+ ---
62
+
63
+ ### 3. ์นดํ…Œ๊ณ ๋ฆฌ ์ž„๊ณ„๊ฐ’ ์ƒํ–ฅ ์กฐ์ •
64
+
65
+ #### ๐Ÿ“ ๋ณ€๊ฒฝ ๋‚ด์šฉ
66
+ - ๊ธฐ์กด: **10% ์ด์ƒ**์˜ ํ™•์‹ ๋„๋ฅผ ๊ฐ€์ง„ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ
67
+ - ๊ฐœ์„ : **25% ์ด์ƒ**์˜ ํ™•์‹ ๋„๋ฅผ ๊ฐ€์ง„ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ์„ ํƒ
68
+
69
+ #### ๐Ÿ’ก ํšจ๊ณผ
70
+ - **์˜คํƒ ๊ฐ์†Œ**: ์‹ค์ œ๋กœ ์–ธ๊ธ‰๋˜์ง€ ์•Š์€ ์นดํ…Œ๊ณ ๋ฆฌ ์ œ๊ฑฐ
71
+ - **์‹ ๋ขฐ๋„ ํ–ฅ์ƒ**: ํ™•์‹คํ•œ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ํ‘œ์‹œ
72
+
73
+ ```python
74
+ # Before (10% ์ž„๊ณ„๊ฐ’)
75
+ ์นดํ…Œ๊ณ ๋ฆฌ: ๊ธฐ๋Šฅ/์„ฑ๋Šฅ (97%), ๊ตํ™˜/ํ™˜๋ถˆ (93%), ํ’ˆ์งˆ (79%)
76
+ # โ†’ ๊ตํ™˜/ํ™˜๋ถˆ์€ ์‹ค์ œ๋กœ ์–ธ๊ธ‰๋˜์ง€ ์•Š์•˜๋Š”๋ฐ ํ‘œ์‹œ๋จ
77
+
78
+ # After (25% ์ž„๊ณ„๊ฐ’)
79
+ ์นดํ…Œ๊ณ ๋ฆฌ: ํ’ˆ์งˆ (79%), ๋ฐฐ์†ก (45%)
80
+ # โ†’ ์‹ค์ œ๋กœ ์–ธ๊ธ‰๋œ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ํ‘œ์‹œ
81
+ ```
82
+
83
+ ---
84
+
85
+ ## ๐Ÿ“Š ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
86
+
87
+ ### ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 1: ๊ธด ํ˜ผํ•ฉ ๊ฐ์ • ๋ฆฌ๋ทฐ (121์ž)
88
+ ```
89
+ ๋ฆฌ๋ทฐ: "ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”..."
90
+
91
+ โœ… ๊ฒฐ๊ณผ:
92
+ - ๊ฐ์ •: ๊ธ์ • 58%, ๋ถ€์ • 27% (ํ˜ผํ•ฉ ๊ฐ์ • ์ •ํ™•ํžˆ ํŒŒ์•…)
93
+ - ์นดํ…Œ๊ณ ๋ฆฌ: ์‚ฌ์ด์ฆˆ, ๊ฐ€๊ฒฉ, ๋””์ž์ธ (๋‹ค์–‘ํ•œ ์ฃผ์ œ ํƒ์ง€)
94
+ - ๋ฐฉ๋ฒ•: sentence_split โœ“
95
+ ```
96
+
97
+ ### ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 2: ๋ณต์žกํ•œ ๋ถˆ๋งŒ ๋ฆฌ๋ทฐ (126์ž)
98
+ ```
99
+ ๋ฆฌ๋ทฐ: "์‚ฌ์ง„์ด๋ž‘ ์™„์ „ ๋‹ค๋ฅด๋„ค์š”. ํ’ˆ์งˆ๋„ ๋ณ„๋กœ๊ณ  ์‚ฌ์ด์ฆˆ๋„ ์•ˆ ๋งž์•„์š”..."
100
+
101
+ โœ… ๊ฒฐ๊ณผ:
102
+ - ๊ฐ์ •: ๋ถ€์ • 48% (์ •ํ™•ํ•œ ๋ถ€์ • ์ธ์‹)
103
+ - ์นดํ…Œ๊ณ ๋ฆฌ: ํ’ˆ์งˆ, ์‚ฌ์ด์ฆˆ, ์„œ๋น„์Šค (๋ชจ๋“  ๋ถˆ๋งŒ ์‚ฌํ•ญ ํƒ์ง€)
104
+ - ํ†ค: ๋‹จ์ˆœ ๋ถˆ๋งŒ 38% (์š•์„ค์ด ์•„๋‹Œ ์ •์ƒ์  ๋ถˆ๋งŒ์œผ๋กœ ๋ถ„๋ฅ˜)
105
+ - ๋ฐฉ๋ฒ•: sentence_split โœ“
106
+ ```
107
+
108
+ ### ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 3: ๊ด‘๊ณ ์„ฑ ๋ฆฌ๋ทฐ
109
+ ```
110
+ ๋ฆฌ๋ทฐ: "ํ…”๋ ˆ๊ทธ๋žจ @seller123 ์œผ๋กœ ์—ฐ๋ฝ์ฃผ์‹œ๋ฉด ๋ฐ˜๊ฐ’์— ๋“œ๋ฆฝ๋‹ˆ๋‹ค..."
111
+
112
+ โœ… ๊ฒฐ๊ณผ:
113
+ - ํ†ค: ๊ด‘๊ณ  52% (์ •ํ™•ํ•œ ๊ด‘๊ณ  ํƒ์ง€)
114
+ - ํ‚ค์›Œ๋“œ ์ธ์‹: ํ…”๋ ˆ๊ทธ๋žจ, ์นดํ†ก ๋“ฑ ๊ด‘๊ณ  ํŒจํ„ด ์ •ํ™•ํžˆ ํŒŒ์•…
115
+ ```
116
+
117
+ ---
118
+
119
+ ## ๐ŸŽฏ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
120
+
121
+ ### ์ž๋™ ์ ์šฉ
122
+ - 100์ž ์ด์ƒ์˜ ๊ธด ๋ฆฌ๋ทฐ๋Š” **์ž๋™์œผ๋กœ** ๋ฌธ์žฅ ๋ถ„๋ฆฌ ๋ถ„์„ ์ ์šฉ
123
+ - ๋ณ„๋„ ์„ค์ • ํ•„์š” ์—†์Œ
124
+
125
+ ### ์ˆ˜๋™ ์ œ์–ด
126
+ ```python
127
+ analyzer = ReviewAnalyzer()
128
+
129
+ # ๋ฌธ์žฅ ๋ถ„๋ฆฌ ๋ถ„์„ ์‚ฌ์šฉ (๊ธฐ๋ณธ๊ฐ’)
130
+ result = analyzer.analyze_sentiment(text, use_sentence_split=True)
131
+
132
+ # ๋ฌธ์žฅ ๋ถ„๋ฆฌ ๋ถ„์„ ๋น„ํ™œ์„ฑํ™”
133
+ result = analyzer.analyze_sentiment(text, use_sentence_split=False)
134
+
135
+ # ์นดํ…Œ๊ณ ๋ฆฌ ์ž„๊ณ„๊ฐ’ ์กฐ์ •
136
+ result = analyzer.analyze_category(text, min_threshold=0.3) # 30%๋กœ ์ƒํ–ฅ
137
+ ```
138
+
139
+ ---
140
+
141
+ ## ๐Ÿ“ˆ ๊ฐœ์„  ํšจ๊ณผ ์š”์•ฝ
142
+
143
+ | ํ•ญ๋ชฉ | ๊ฐœ์„  ์ „ | ๊ฐœ์„  ํ›„ |
144
+ |------|---------|---------|
145
+ | ๊ธด ๋ฌธ์žฅ (100์ž+) ๊ฐ์ • ๋ถ„์„ | ๋ถ€์ •ํ™• | โœ… ์ •ํ™• (๋ฌธ์žฅ๋ณ„ ์ง‘๊ณ„) |
146
+ | ํ˜ผํ•ฉ ๊ฐ์ • ๊ฐ์ง€ | ์–ด๋ ค์›€ | โœ… ์ •ํ™• (ํ‰๊ท  ์ง‘๊ณ„) |
147
+ | ์—ฌ๋Ÿฌ ์ฃผ์ œ ํƒ์ง€ | ์ผ๋ถ€ ๋ˆ„๋ฝ | โœ… ๋ชจ๋‘ ํƒ์ง€ (์ตœ๋Œ€๊ฐ’ ์ง‘๊ณ„) |
148
+ | ์นดํ…Œ๊ณ ๋ฆฌ ์˜คํƒ | ๋†’์Œ (10%) | โœ… ๋‚ฎ์Œ (25% ์ž„๊ณ„๊ฐ’) |
149
+ | ํ”„๋กฌํ”„ํŠธ ๏ฟฝ๏ฟฝ๏ฟฝํ•ด๋„ | ๋ณดํ†ต | โœ… ๋†’์Œ (์˜ˆ์‹œ ํฌํ•จ) |
150
+
151
+ ---
152
+
153
+ ## ๐Ÿ”ง ์ถ”๊ฐ€ ๊ฐœ์„  ๊ฐ€๋Šฅ์„ฑ
154
+
155
+ 1. **์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ ์œ ์‚ฌ๋„ ๊ณ„์‚ฐ**: ๋ฌธ์žฅ ๊ฐ„ ์˜๋ฏธ ์œ ์‚ฌ๋„๋ฅผ ๊ณ ๋ คํ•œ ๊ฐ€์ค‘์น˜ ๋ถ€์—ฌ
156
+ 2. **ํ‚ค์›Œ๋“œ ์ถ”์ถœ**: ์ฃผ์š” ํ‚ค์›Œ๋“œ๋ฅผ ๋จผ์ € ์ถ”์ถœํ•˜์—ฌ ๋ถ„์„ ์ •ํ™•๋„ ํ–ฅ์ƒ
157
+ 3. **Attention ๋ฉ”์ปค๋‹ˆ์ฆ˜**: ์ค‘์š”ํ•œ ๋ฌธ์žฅ์— ๋” ๋†’์€ ๊ฐ€์ค‘์น˜ ๋ถ€์—ฌ
158
+ 4. **Fine-tuning**: ์‹ค์ œ ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ๋กœ ๋ชจ๋ธ ์ถ”๊ฐ€ ํ•™์Šต
159
+
160
+ ---
161
+
162
+ ## ๐Ÿ“ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋ฐฉ๋ฒ•
163
+
164
+ ```bash
165
+ # ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰
166
+ python3 test_long_reviews.py
167
+
168
+ # ๊ฒฐ๊ณผ ํ™•์ธ
169
+ cat test_results.json
170
+ ```
app.py CHANGED
@@ -1,8 +1,12 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
  ๋ฆฌ๋ทฐ ์ž๋™ ๊ฒ€์ˆ˜ ์„œ๋น„์Šค
4
- Hugging Face์˜ Zero-Shot Classification ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋ทฐ๋ฅผ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค.
5
- ๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ: ๊ธ์ •, ๋ถ€์ •, ๊ด‘๊ณ , ์š•์„ค, ๋‹จ์ˆœ ๋„๋ฐฐ
 
 
 
 
6
  """
7
 
8
  from transformers import pipeline
@@ -15,9 +19,14 @@ import gradio as gr
15
 
16
 
17
  class ReviewAnalyzer:
18
- """๋ฆฌ๋ทฐ ๋ถ„๋ฅ˜๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค"""
 
 
 
 
 
19
 
20
- def __init__(self, use_enhanced_prompt=True):
21
  """Zero-Shot Classification ํŒŒ์ดํ”„๋ผ์ธ ์ดˆ๊ธฐํ™”"""
22
  print("๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
23
  # ํ•œ๊ตญ์–ด๋ฅผ ์ž˜ ์ดํ•ดํ•˜๋Š” multilingual ๋ชจ๋ธ ์‚ฌ์šฉ
@@ -26,46 +35,61 @@ class ReviewAnalyzer:
26
  model="MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7"
27
  )
28
 
29
- self.use_enhanced_prompt = use_enhanced_prompt
30
-
31
- if use_enhanced_prompt:
32
- # ์„ฑ๋Šฅ ๊ฐœ์„ : ๋” ๊ตฌ์ฒด์ ์ด๊ณ  ์„ค๋ช…์ ์ธ ๊ฐ€์„ค ์‚ฌ์šฉ
33
- self.categories = [
34
- "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋งŒ์กฑํ•˜๋ฉฐ ์ถ”์ฒœํ•˜๊ณ  ์นญ์ฐฌํ•˜๋Š” ๊ธ์ •์ ์ธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค",
35
- "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ์‹ค๋งํ•˜๊ณ  ๋ถˆ๋งŒ์„ ํ‘œํ˜„ํ•˜๋Š” ๋ถ€์ •์ ์ธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค",
36
- "์ด ๋ฆฌ๋ทฐ๋Š” ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋‚˜ ํŒ๋งค์ž๋ฅผ ํ™๋ณดํ•˜๊ฑฐ๋‚˜ ์—ฐ๋ฝ์ฒ˜๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ด‘๊ณ ์„ฑ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค",
37
- "์ด ๋ฆฌ๋ทฐ๋Š” ๋น„์†์–ด๋‚˜ ์š•์„ค์„ ํฌํ•จํ•˜์—ฌ ๊ณต๊ฒฉ์ ์ด๊ณ  ๋ถ€์ ์ ˆํ•œ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค",
38
- "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์— ๋Œ€ํ•œ ์‹ค์ œ ์˜๊ฒฌ์ด๋‚˜ ์ •๋ณด๊ฐ€ ์ „ํ˜€ ์—†๊ณ  ๋‹จ์ˆœํžˆ ๊ฐ™์€ ๋ฌธ์ž๋‚˜ ์ด๋ชจํ‹ฐ์ฝ˜๋งŒ ๋ฐ˜๋ณตํ•˜๋Š” ์ŠคํŒธ์„ฑ ๋„๋ฐฐ์ž…๋‹ˆ๋‹ค"
39
- ]
40
-
41
- self.category_mapping = {
42
- "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋งŒ์กฑํ•˜๋ฉฐ ์ถ”์ฒœํ•˜๊ณ  ์นญ์ฐฌํ•˜๋Š” ๊ธ์ •์ ์ธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค": "๊ธ์ •",
43
- "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ์‹ค๋งํ•˜๊ณ  ๋ถˆ๋งŒ์„ ํ‘œํ˜„ํ•˜๋Š” ๋ถ€์ •์ ์ธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค": "๋ถ€์ •",
44
- "์ด ๋ฆฌ๋ทฐ๋Š” ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋‚˜ ํŒ๋งค์ž๋ฅผ ํ™๋ณดํ•˜๊ฑฐ๋‚˜ ์—ฐ๋ฝ์ฒ˜๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ด‘๊ณ ์„ฑ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค": "๊ด‘๊ณ ",
45
- "์ด ๋ฆฌ๋ทฐ๋Š” ๋น„์†์–ด๋‚˜ ์š•์„ค์„ ํฌํ•จํ•˜์—ฌ ๊ณต๊ฒฉ์ ์ด๊ณ  ๋ถ€์ ์ ˆํ•œ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค": "์š•์„ค",
46
- "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์— ๋Œ€ํ•œ ์‹ค์ œ ์˜๊ฒฌ์ด๋‚˜ ์ •๋ณด๊ฐ€ ์ „ํ˜€ ์—†๊ณ  ๋‹จ์ˆœํžˆ ๊ฐ™์€ ๋ฌธ์ž๋‚˜ ์ด๋ชจํ‹ฐ์ฝ˜๋งŒ ๋ฐ˜๋ณตํ•˜๋Š” ์ŠคํŒธ์„ฑ ๋„๋ฐฐ์ž…๋‹ˆ๋‹ค": "๋‹จ์ˆœ ๋„๋ฐฐ"
47
- }
48
- else:
49
- # ๊ธฐ๋ณธ ์งง์€ ๋ ˆ์ด๋ธ”
50
- self.categories = [
51
- "๊ธ์ •์ ์ธ ๋ฆฌ๋ทฐ",
52
- "๋ถ€์ •์ ์ธ ๋ฆฌ๋ทฐ",
53
- "๊ด‘๊ณ ์„ฑ ๋ฆฌ๋ทฐ",
54
- "์š•์„ค์ด ํฌํ•จ๋œ ๋ฆฌ๋ทฐ",
55
- "๋‹จ์ˆœ ๋„๋ฐฐ ๋ฆฌ๋ทฐ"
56
- ]
57
-
58
- self.category_mapping = {
59
- "๊ธ์ •์ ์ธ ๋ฆฌ๋ทฐ": "๊ธ์ •",
60
- "๋ถ€์ •์ ์ธ ๋ฆฌ๋ทฐ": "๋ถ€์ •",
61
- "๊ด‘๊ณ ์„ฑ ๋ฆฌ๋ทฐ": "๊ด‘๊ณ ",
62
- "์š•์„ค์ด ํฌํ•จ๋œ ๋ฆฌ๋ทฐ": "์š•์„ค",
63
- "๋‹จ์ˆœ ๋„๋ฐฐ ๋ฆฌ๋ทฐ": "๋‹จ์ˆœ ๋„๋ฐฐ"
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  print("๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ!")
67
- if use_enhanced_prompt:
68
- print("โœ“ ํ–ฅ์ƒ๋œ ํ”„๋กฌํ”„ํŠธ ๋ชจ๋“œ ํ™œ์„ฑํ™” - ๋” ๋†’์€ ์ •ํ™•๋„")
69
 
70
  def preprocess_text(self, text: str) -> str:
71
  """
@@ -86,113 +110,525 @@ class ReviewAnalyzer:
86
 
87
  return text
88
 
89
- def is_spam_review(self, text: str) -> bool:
90
  """
91
- ์‹ค์ œ ๋„๋ฐฐ์ธ๏ฟฝ๏ฟฝ๏ฟฝ ๊ทœ์น™ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒ€์ฆ
92
 
93
  Args:
94
- text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
95
 
96
  Returns:
97
- ๋„๋ฐฐ ์—ฌ๋ถ€ (True: ๋„๋ฐฐ, False: ์ •์ƒ)
98
  """
99
  import re
100
 
101
- # ํŠน์ˆ˜๋ฌธ์ž์™€ ์ด๋ชจํ‹ฐ์ฝ˜ ์ œ๊ฑฐํ•˜์—ฌ ์‹ค์ œ ํ…์ŠคํŠธ๋งŒ ์ถ”์ถœ
102
- cleaned_text = re.sub(r'[~!@#$%^&*()_+={}\[\]:;"\'<>,.?/\\|ใ…‹ใ…Žใ„ฑใ„ดใ„ทใ„นใ…ใ…‚ใ……ใ…‡ใ…ˆใ…Šใ…‹ใ…Œใ…ใ…Ž\s-]', '', text)
 
103
 
104
- # ํ•œ๊ธ€ ๋‹จ์–ด๋งŒ ์ถ”์ถœ
105
- korean_words = re.findall(r'[๊ฐ€-ํžฃ]+', cleaned_text)
106
 
107
- # ์˜๋ฏธ ์žˆ๋Š” ํ•œ๊ธ€ ๋‹จ์–ด๊ฐ€ 5๊ฐœ ์ด์ƒ์ด๋ฉด ๋„๋ฐฐ๊ฐ€ ์•„๋‹˜
108
- if len(korean_words) >= 5:
109
- return False
110
 
111
- # ๊ณ ์œ  ๋ฌธ์ž ์ˆ˜ ํ™•์ธ
112
- unique_chars = len(set(cleaned_text))
113
- total_chars = len(cleaned_text)
114
 
115
- # ํ…์ŠคํŠธ๊ฐ€ ๋„ˆ๋ฌด ์งง๊ฑฐ๋‚˜ ์—†์œผ๋ฉด ๋„๋ฐฐ
116
- if total_chars < 3:
117
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- # ๊ณ ์œ  ๋ฌธ์ž ๋น„์œจ์ด 30% ๋ฏธ๋งŒ์ด๋ฉด ๋„๋ฐฐ (๊ฐ™์€ ๋ฌธ์ž ๋ฐ˜๋ณต)
120
- if total_chars > 0 and unique_chars / total_chars < 0.3:
121
- return True
122
 
123
- # ์ „์ฒด ํ…์ŠคํŠธ ๊ธธ์ด์— ๋น„ํ•ด ์˜๋ฏธ ์žˆ๋Š” ๋‹จ์–ด๊ฐ€ ๋„ˆ๋ฌด ์ ์œผ๋ฉด ๋„๋ฐฐ
124
- if len(text) > 20 and len(korean_words) < 3:
125
- return True
 
126
 
127
- return False
 
 
 
 
 
128
 
129
- def analyze_review(self, review_text: str, confidence_threshold=0.3) -> Dict:
130
  """
131
- ๋‹จ์ผ ๋ฆฌ๋ทฐ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
132
 
133
  Args:
134
- review_text: ๋ถ„์„ํ•  ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
135
- confidence_threshold: ์ตœ์†Œ ํ™•์‹ ๋„ ์ž„๊ณ„๊ฐ’ (๊ธฐ๋ณธ 0.3)
 
 
136
 
137
  Returns:
138
- ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ๋ฅผ ํฌํ•จํ•œ ๋”•์…”๋„ˆ๋ฆฌ
139
  """
140
- # ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ
141
- processed_text = self.preprocess_text(review_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # Zero-Shot Classification ์‹คํ–‰
 
 
 
 
 
144
  result = self.classifier(
145
- processed_text,
146
- self.categories,
147
- multi_label=False # ๊ฐ€์žฅ ํ™•๋ฅ ์ด ๋†’์€ ํ•˜๋‚˜์˜ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ์„ ํƒ
148
  )
149
 
150
- # ๊ฒฐ๊ณผ ํฌ๋งทํŒ…
151
  top_category = result['labels'][0]
152
  top_score = result['scores'][0]
153
- category = self.category_mapping[top_category]
154
-
155
- # ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํ›„์ฒ˜๋ฆฌ: ๋„๋ฐฐ๋กœ ๋ถ„๋ฅ˜๋˜์—ˆ์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์˜๋ฏธ ์žˆ๋Š” ๋‚ด์šฉ์ด ์žˆ๋Š” ๊ฒฝ์šฐ
156
- if category == "๋‹จ์ˆœ ๋„๋ฐฐ":
157
- if not self.is_spam_review(review_text):
158
- # ์‹ค์ œ ๋„๋ฐฐ๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ๋‘ ๋ฒˆ์งธ๋กœ ๋†’์€ ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ
159
- second_category = result['labels'][1]
160
- second_score = result['scores'][1]
161
- category = self.category_mapping[second_category]
162
- top_score = second_score
163
- print(f"[๊ทœ์น™ ๊ธฐ๋ฐ˜ ์žฌ๋ถ„๋ฅ˜] ๋„๋ฐฐ๊ฐ€ ์•„๋‹Œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จ -> {category} (ํ™•์‹ ๋„: {second_score:.2%})")
164
-
165
- # ํ˜ผํ•ฉ ๊ฐ์ • ๊ฐ์ง€: ๊ธ์ •๊ณผ ๋ถ€์ • ์ ์ˆ˜๊ฐ€ ๋น„์Šทํ•œ ๊ฒฝ์šฐ
166
  scores_dict = {
167
- self.category_mapping[label]: score
168
  for label, score in zip(result['labels'], result['scores'])
169
  }
170
 
171
- positive_score = scores_dict.get("๊ธ์ •", 0)
172
- negative_score = scores_dict.get("๋ถ€์ •", 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- # ๊ธ์ •๊ณผ ๋ถ€์ • ์ ์ˆ˜ ์ฐจ์ด๊ฐ€ 15% ์ด๋‚ด์ด๊ณ , ๋‘˜ ๋‹ค ์ƒ์œ„๊ถŒ์ด๋ฉด ํ˜ผํ•ฉ ๊ฐ์ •
175
- if category in ["๊ธ์ •", "๋ถ€์ •"]:
176
- score_diff = abs(positive_score - negative_score)
177
- if score_diff < 0.15 and min(positive_score, negative_score) > 0.2:
178
- category = f"{category} (ํ˜ผํ•ฉ ๊ฐ์ •)"
179
- print(f"[ํ˜ผํ•ฉ ๊ฐ์ • ๊ฐ์ง€] ๊ธ์ •: {positive_score:.2%}, ๋ถ€์ •: {negative_score:.2%}")
180
 
181
- # ํ™•์‹ ๋„๊ฐ€ ์ž„๊ณ„๊ฐ’๋ณด๋‹ค ๋‚ฎ์œผ๋ฉด "๋ถˆํ™•์‹ค" ํ‘œ์‹œ ์ถ”๊ฐ€
182
- if top_score < confidence_threshold and "(ํ˜ผํ•ฉ ๊ฐ์ •)" not in category:
183
- category = f"{category} (๋ถˆํ™•์‹ค)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
  return {
186
- "review": review_text,
187
- "category": category,
188
- "confidence": round(top_score * 100, 2),
189
- "all_scores": {
190
- self.category_mapping[label]: round(score * 100, 2)
191
- for label, score in zip(result['labels'], result['scores'])
192
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  "timestamp": datetime.now().isoformat()
194
  }
195
 
 
 
 
 
 
 
196
  def analyze_reviews(self, reviews: List[str]) -> List[Dict]:
197
  """
198
  ์—ฌ๋Ÿฌ ๋ฆฌ๋ทฐ๋ฅผ ์ผ๊ด„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
@@ -213,18 +649,18 @@ class ReviewAnalyzer:
213
  def print_results(self, results: List[Dict]):
214
  """๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ ์ข‹๊ฒŒ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค."""
215
  print("\n" + "="*80)
216
- print("๋ฆฌ๋ทฐ ๋ถ„์„ ๊ฒฐ๊ณผ")
217
  print("="*80)
218
 
219
  for idx, result in enumerate(results, 1):
220
  print(f"\n[๋ฆฌ๋ทฐ #{idx}]")
221
  print(f"๋‚ด์šฉ: {result['review']}")
222
- print(f"๋ถ„๋ฅ˜: {result['category']}")
223
- print(f"ํ™•์‹ ๋„: {result['confidence']}%")
224
- print(f"\n์ „์ฒด ์ ์ˆ˜:")
225
- for category, score in result['all_scores'].items():
226
- bar = "โ–ˆ" * int(score / 5)
227
- print(f" {category:10s}: {score:5.1f}% {bar}")
228
 
229
  print("\n" + "="*80)
230
 
@@ -251,7 +687,7 @@ class ReviewAnalyzer:
251
  reviews.append(row['review_text'])
252
  return reviews
253
 
254
- def analyze_for_gradio(self, review_text: str) -> Tuple[str, str, Dict]:
255
  """
256
  Gradio UI์šฉ ๋ฆฌ๋ทฐ ๋ถ„์„ ํ•จ์ˆ˜
257
 
@@ -259,58 +695,104 @@ class ReviewAnalyzer:
259
  review_text: ๋ถ„์„ํ•  ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
260
 
261
  Returns:
262
- (๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ, ์ƒ์„ธ ์ •๋ณด, ํ™•๋ฅ  ๋ถ„ํฌ) ํŠœํ”Œ
263
  """
264
  if not review_text or review_text.strip() == "":
265
- return "โš ๏ธ ๋ฆฌ๋ทฐ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”", "", {}
266
 
267
- result = self.analyze_review(review_text)
268
 
269
- # ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ ํ…์ŠคํŠธ ์ƒ์„ฑ
270
- category = result['category']
 
271
 
272
- # ์ด๋ชจ์ง€ ์„ ํƒ (ํ˜ผํ•ฉ ๊ฐ์ •, ๋ถˆํ™•์‹ค ๋“ฑ ํŠน์ˆ˜ ์ผ€์ด์Šค ์ฒ˜๋ฆฌ)
273
- if "ํ˜ผํ•ฉ ๊ฐ์ •" in category:
274
- if "๊ธ์ •" in category:
275
- emoji = "๐Ÿ˜ (๊ธ์ • ์šฐ์„ธ)"
276
- elif "๋ถ€์ •" in category:
277
- emoji = "๐Ÿ˜ (๋ถ€์ • ์šฐ์„ธ)"
278
- else:
279
- emoji = "๐Ÿ˜"
280
- elif "๋ถˆํ™•์‹ค" in category:
281
- emoji = "โ“"
 
 
 
282
  else:
283
- category_emoji = {
284
- "๊ธ์ •": "๐Ÿ˜Š",
285
- "๋ถ€์ •": "๐Ÿ˜ž",
286
- "๊ด‘๊ณ ": "๐Ÿ“ข",
287
- "์š•์„ค": "๐Ÿšซ",
288
- "๋‹จ์ˆœ ๋„๋ฐฐ": "๐Ÿ”„"
289
- }
290
- emoji = category_emoji.get(category, "โ“")
 
 
 
 
 
 
 
291
 
292
- classification = f"{emoji} {category}"
 
 
 
 
 
 
293
 
294
- # ์ƒ์„ธ ์ •๋ณด ํ…์ŠคํŠธ
295
- details = f"""
296
- **ํ™•์‹ ๋„:** {result['confidence']}%
297
 
298
- **๋ถ„์„ ์‹œ๊ฐ„:** {datetime.fromisoformat(result['timestamp']).strftime('%Y-%m-%d %H:%M:%S')}
 
 
299
 
300
- ---
301
 
302
- ### ๐Ÿ“Š ์ „์ฒด ์นดํ…Œ๊ณ ๋ฆฌ ์ ์ˆ˜:
303
- """
304
- for category, score in result['all_scores'].items():
305
- details += f"\n- **{category}**: {score}%"
306
 
307
- # ํ™•๋ฅ  ๋ถ„ํฌ ๋”•์…”๋„ˆ๋ฆฌ (Gradio Label ์ปดํฌ๋„ŒํŠธ์šฉ)
308
- probabilities = {
309
- category: score / 100.0
310
- for category, score in result['all_scores'].items()
311
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
 
313
- return classification, details, probabilities
 
 
 
 
314
 
315
 
316
  # ์ „์—ญ ๋ถ„์„๊ธฐ ์ธ์Šคํ„ด์Šค (Gradio ์•ฑ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋“œ)
@@ -331,114 +813,190 @@ def create_gradio_app():
331
  # ๋ถ„์„๊ธฐ ์ดˆ๊ธฐํ™”
332
  review_analyzer = get_analyzer()
333
 
334
- # ์ƒ˜ํ”Œ ๋ฆฌ๋ทฐ ์˜ˆ์‹œ (๋” ๋‹ค์–‘ํ•œ ์˜ˆ์‹œ ์ถ”๊ฐ€)
335
  examples = [
336
  ["์ •๋ง ์ข‹์€ ์ œํ’ˆ์ด์—์š”! ๋ฐฐ์†ก๋„ ๏ฟฝ๏ฟฝ๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์—๋„ ๋˜ ๊ตฌ๋งคํ• ๊ฒŒ์š”!"],
337
  ["์™„์ „ ์‹ค๋ง์ด์—์š”. ์‚ฌ์ง„์ด๋ž‘ ์™„์ „ ๋‹ค๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ๋ณ„๋กœ์ž…๋‹ˆ๋‹ค. ํ™˜๋ถˆ ์‹ ์ฒญํ–ˆ์Šต๋‹ˆ๋‹ค."],
338
  ["ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”~~๊ฐ์ˆ˜ํ• ๋งŒํ•œ๋ฐ ์€๊ทผ ์งœ์ฆ๋‚ ์ˆ˜๋„? ๊ทธ๋ƒฅ ์ž…์œผ๋ฉด ๊ณ ์–‘์ด๋งˆ๋ƒฅ ํ„ธ์„ ๋ฟœ๋‚ด์š” ใ…Žใ…Ž"],
339
  ["ํ…”๋ ˆ๊ทธ๋žจ @abcd1234๋กœ ์—ฐ๋ฝ์ฃผ์‹œ๋ฉด ๋ฐ˜๊ฐ’์— ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋„๋งค๊ฐ€๋กœ ํŒ๋งค์ค‘!"],
340
- ["์ด๊ฒŒ ๋ญ์•ผ ์ง„์งœ ์™„์ „ ์“ฐ๋ ˆ๊ธฐ๋„ค์š”. ๋ˆ ์•„๊น์Šต๋‹ˆ๋‹ค."],
341
- ["ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹"],
342
  ["๋ฐฐ์†ก์ด ์ƒ๊ฐ๋ณด๋‹ค ๋นจ๋ผ์„œ ์ข‹์•˜์–ด์š”. ํ’ˆ์งˆ๋„ ๊ดœ์ฐฎ๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค."],
 
 
343
  ]
344
 
345
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ
346
- with gr.Blocks(title="๋ฆฌ๋ทฐ ์ž๋™ ๊ฒ€์ˆ˜ ์„œ๋น„์Šค (ํ–ฅ์ƒ๋œ AI)", theme=gr.themes.Soft()) as demo:
347
- gr.Markdown("""
348
- # ๐Ÿ” ๋ฆฌ๋ทฐ ์ž๋™ ๊ฒ€์ˆ˜ ์„œ๋น„์Šค (ํ–ฅ์ƒ๋œ AI ๋ชจ๋ธ)
349
-
350
- **Zero-Shot Classification**๊ณผ **ํ–ฅ์ƒ๋œ ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง**์„ ํ™œ์šฉํ•˜์—ฌ ๋ฆฌ๋ทฐ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
- **๋ถ„๋ฅ˜ ์นดํ…Œ๊ณ ๋ฆฌ:** ๊ธ์ • ๐Ÿ˜Š | ๋ถ€์ • ๐Ÿ˜ž | ๊ด‘๊ณ  ๐Ÿ“ข | ์š•์„ค ๐Ÿšซ | ๋‹จ์ˆœ ๋„๋ฐฐ ๐Ÿ”„ | ํ˜ผํ•ฉ ๊ฐ์ • ๐Ÿ˜
 
 
353
 
354
- ๐Ÿš€ **์„ฑ๋Šฅ ๊ฐœ์„  ํฌ์ธํŠธ:**
355
- - โœ… ๊ตฌ์ฒด์ ์ด๊ณ  ์„ค๋ช…์ ์ธ ๊ฐ€์„ค(hypothesis) ์‚ฌ์šฉ์œผ๋กœ ๋ถ„๋ฅ˜ ์ •ํ™•๋„ ํ–ฅ์ƒ
356
- - โœ… ๊ทœ์น™ ๊ธฐ๋ฐ˜ ํ›„์ฒ˜๋ฆฌ๋กœ ๋„๋ฐฐ ์˜ค๋ถ„๋ฅ˜ ๋ฐฉ์ง€ (์˜๋ฏธ ์žˆ๋Š” ๋‹จ์–ด ๊ฐœ์ˆ˜, ๊ณ ์œ  ๋ฌธ์ž ๋น„์œจ ์ฒดํฌ)
357
- - โœ… ํ˜ผํ•ฉ ๊ฐ์ • ๊ฐ์ง€ (๊ธ์ •๊ณผ ๋ถ€์ •์ด ๊ณต์กดํ•˜๋Š” ๋ฆฌ๋ทฐ ์ž๋™ ์ธ์‹)
358
- - โœ… ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ๋ฐ ์ •๊ทœํ™”๋กœ ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ
359
- - โœ… ํ™•์‹ ๋„ ์ž„๊ณ„๊ฐ’ ์„ค์ •์œผ๋กœ ๋ถˆํ™•์‹คํ•œ ์ผ€์ด์Šค ๊ตฌ๋ถ„
360
  """)
361
 
 
362
  with gr.Row():
 
 
 
 
 
 
 
363
  with gr.Column(scale=1):
364
- review_input = gr.Textbox(
365
- label="๋ฆฌ๋ทฐ ์ž…๋ ฅ",
366
- placeholder="๋ฆฌ๋ทฐ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”...",
367
- lines=5,
368
- max_lines=10
369
- )
370
-
371
- with gr.Row():
372
- clear_btn = gr.Button("๐Ÿ—‘๏ธ ์ง€์šฐ๊ธฐ", variant="secondary")
373
- submit_btn = gr.Button("๐Ÿ” ๋ถ„์„ํ•˜๊ธฐ", variant="primary")
374
-
375
- gr.Examples(
376
- examples=examples,
377
- inputs=review_input,
378
- label="์˜ˆ์‹œ ๋ฆฌ๋ทฐ"
379
- )
380
 
381
- with gr.Column(scale=1):
382
- classification_output = gr.Textbox(
383
- label="๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ",
384
- lines=2,
385
- interactive=False
386
- )
387
 
388
- probability_output = gr.Label(
389
- label="์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ™•๋ฅ ",
390
- num_top_classes=5
391
- )
392
 
393
- details_output = gr.Markdown(
394
- label="์ƒ์„ธ ์ •๋ณด"
395
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
  # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
398
  submit_btn.click(
399
  fn=review_analyzer.analyze_for_gradio,
400
  inputs=review_input,
401
- outputs=[classification_output, details_output, probability_output]
 
402
  )
403
 
404
  review_input.submit(
405
  fn=review_analyzer.analyze_for_gradio,
406
  inputs=review_input,
407
- outputs=[classification_output, details_output, probability_output]
 
408
  )
409
 
410
  clear_btn.click(
411
- fn=lambda: ("", "", "", {}),
412
  inputs=None,
413
- outputs=[review_input, classification_output, details_output, probability_output]
 
414
  )
415
 
416
- gr.Markdown("""
417
- ---
418
- ### ๐Ÿ“ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
419
- 1. ์œ„ ํ…์ŠคํŠธ ๋ฐ•์Šค์— ๋ฆฌ๋ทฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”
420
- 2. **๋ถ„์„ํ•˜๊ธฐ** ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ Enter๋ฅผ ๋ˆ„๋ฅด์„ธ์š”
421
- 3. AI๊ฐ€ ์ž๋™์œผ๋กœ ๋ฆฌ๋ทฐ๋ฅผ ๋ถ„๋ฅ˜ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค
422
-
423
- ### ๐Ÿค– ๊ธฐ์ˆ  ์ •๋ณด
424
- - **๋ชจ๋ธ:** MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7
425
- - **๋ฐฉ์‹:** Zero-Shot Classification (NLI)
426
- - **์ง€์› ์–ธ์–ด:** ํ•œ๊ตญ์–ด ํฌํ•จ ๋‹ค๊ตญ์–ด
427
-
428
- ### ๐ŸŽฏ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ธฐ๋ฒ•
429
- - **ํ”„๋กฌํ”„ํŠธ ์—”์ง€๋‹ˆ์–ด๋ง**: ๋‹จ์ˆœ ๋ ˆ์ด๋ธ” ๋Œ€์‹  ๊ตฌ์ฒด์ ์ด๊ณ  ์ƒ์„ธํ•œ ์„ค๋ช…์„ ๊ฐ€์„ค๋กœ ์‚ฌ์šฉ
430
- - ์˜ˆ: "๊ธ์ •" โ†’ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋งŒ์กฑํ•˜๋ฉฐ ์ถ”์ฒœํ•˜๊ณ  ์นญ์ฐฌํ•˜๋Š” ๊ธ์ •์ ์ธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค"
431
- - **๊ทœ์น™ ๊ธฐ๋ฐ˜ ํ›„์ฒ˜๋ฆฌ**: AI ์˜ˆ์ธก ํ›„ ์ถ”๊ฐ€ ๊ฒ€์ฆ ๋ ˆ์ด์–ด
432
- - ์˜๋ฏธ ์žˆ๋Š” ํ•œ๊ธ€ ๋‹จ์–ด๊ฐ€ 5๊ฐœ ์ด์ƒ์ด๋ฉด ๋„๋ฐฐ๊ฐ€ ์•„๋‹˜
433
- - ๊ณ ์œ  ๋ฌธ์ž ๋น„์œจ์ด 30% ๋ฏธ๋งŒ์ด๋ฉด ๋„๋ฐฐ (๊ฐ™์€ ๋ฌธ์ž ๋ฐ˜๋ณต)
434
- - "ใ…‹ใ…‹ใ…‹", "ใ„ฑใ„ฑใ„ฑ" ๋“ฑ๊ณผ ์‹ค์ œ ๋ฆฌ๋ทฐ ๊ตฌ๋ถ„
435
- - **ํ˜ผํ•ฉ ๊ฐ์ • ๊ฐ์ง€**: ๊ธ์ •๊ณผ ๋ถ€์ • ์ ์ˆ˜ ์ฐจ์ด๊ฐ€ 15% ์ด๋‚ด๋ฉด ํ˜ผํ•ฉ ๊ฐ์ •์œผ๋กœ ํ‘œ์‹œ
436
- - ์˜ˆ: "ํ•์€ ์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์‹ฌํ•ด์š”" โ†’ ๋ถ€์ • (ํ˜ผํ•ฉ ๊ฐ์ •)
437
- - **์ „์ฒ˜๋ฆฌ**: ๊ณต๋ฐฑ ์ •๊ทœํ™”, ๋…ธ์ด์ฆˆ ์ œ๊ฑฐ
438
- - **ํ™•์‹ ๋„ ๊ธฐ๋ฐ˜ ํŒ๋‹จ**: ๋‚ฎ์€ ํ™•์‹ ๋„(30% ๋ฏธ๋งŒ)๋Š” "๋ถˆํ™•์‹ค" ํ‘œ์‹œ
439
-
440
- ๐Ÿ’ก **TIP:** ์ด๋ชจํ‹ฐ์ฝ˜์ด๋‚˜ ํŠน์ˆ˜๋ฌธ์ž๊ฐ€ ์žˆ์–ด๋„ ์˜๋ฏธ ์žˆ๋Š” ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ์ •ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค!
441
- """)
442
 
443
  return demo
444
 
 
1
  # -*- coding: utf-8 -*-
2
  """
3
  ๋ฆฌ๋ทฐ ์ž๋™ ๊ฒ€์ˆ˜ ์„œ๋น„์Šค
4
+ Hugging Face์˜ Zero-Shot Classification ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋ทฐ๋ฅผ 3๋‹จ๊ณ„๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
5
+
6
+ ๋ถ„์„ ๋‹จ๊ณ„:
7
+ 1. ๊ฐ์ • ๋ถ„์„: ๊ธ์ • / ์ค‘๋ฆฝ / ๋ถ€์ •
8
+ 2. ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„: ๋ฐฐ์†ก / ํ’ˆ์งˆ / ์‚ฌ์ด์ฆˆ / ๊ตํ™˜ / ์„œ๋น„์Šค ๋“ฑ
9
+ 3. ๋ฆฌ๋ทฐ ํ†ค ํƒ์ง€: ๋‹จ์ˆœ ๋ถˆ๋งŒ / ์š•์„ค / ํ—ˆ์œ„ํ›„๊ธฐ / ๊ด‘๊ณ  ๋“ฑ
10
  """
11
 
12
  from transformers import pipeline
 
19
 
20
 
21
  class ReviewAnalyzer:
22
+ """๋ฆฌ๋ทฐ๋ฅผ 3๋‹จ๊ณ„๋กœ ๋ถ„์„ํ•˜๋Š” ํด๋ž˜์Šค
23
+
24
+ 1. ๊ฐ์ • ๋ถ„์„: ๊ธ์ • / ์ค‘๋ฆฝ / ๋ถ€์ •
25
+ 2. ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„: ๋ฐฐ์†ก / ํ’ˆ์งˆ / ์‚ฌ์ด์ฆˆ / ๊ตํ™˜ / ์„œ๋น„์Šค ๋“ฑ
26
+ 3. ๋ฆฌ๋ทฐ ํ†ค ํƒ์ง€: ๋‹จ์ˆœ ๋ถˆ๋งŒ / ์š•์„ค / ํ—ˆ์œ„ํ›„๊ธฐ / ๊ด‘๊ณ  ๋“ฑ
27
+ """
28
 
29
+ def __init__(self):
30
  """Zero-Shot Classification ํŒŒ์ดํ”„๋ผ์ธ ์ดˆ๊ธฐํ™”"""
31
  print("๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...")
32
  # ํ•œ๊ตญ์–ด๋ฅผ ์ž˜ ์ดํ•ดํ•˜๋Š” multilingual ๋ชจ๋ธ ์‚ฌ์šฉ
 
35
  model="MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7"
36
  )
37
 
38
+ # 1๋‹จ๊ณ„: ๊ฐ์ • ๋ถ„์„ (๊ฐœ์„ ๋œ ํ”„๋กฌํ”„ํŠธ - ๊ตฌ์ฒด์  ์˜ˆ์‹œ ํฌํ•จ)
39
+ self.sentiment_categories = [
40
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋งŒ์กฑํ•˜๋ฉฐ ์ข‹์•„ํ•˜๊ณ  ์ถ”์ฒœํ•˜๋Š” ๊ธ์ •์ ์ธ ๊ฐ์ •์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์ข‹์•„์š”, ๋งŒ์กฑ, ์ถ”์ฒœ, ํ›Œ๋ฅญ, ์ตœ๊ณ , ๊ฐ์‚ฌ, ๋งˆ์Œ์— ๋“ค์–ด์š”",
41
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ค‘๋ฆฝ์ ์ด๊ณ  ๊ฐ๊ด€์ ์œผ๋กœ ์‚ฌ์‹ค์ด๋‚˜ ์ƒํƒœ๋งŒ์„ ๋‚˜์—ดํ•˜๋ฉฐ ํŠน๋ณ„ํ•œ ๊ฐ์ • ํ‘œํ˜„์ด ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ: ๊ทธ๋ƒฅ ๊ทธ๋ž˜์š”, ๋ณดํ†ต, ๋ฌด๋‚œ, ํ‰๋ฒ”",
42
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ์‹ค๋งํ•˜๊ณ  ๋ถˆ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๋ถ€์ •์ ์ธ ๊ฐ์ •์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๋ณ„๋กœ, ์‹ค๋ง, ๋ถˆ๋งŒ์กฑ, ์ตœ์•…, ํ™”๋‚จ, ํ›„ํšŒ, ํ™˜๋ถˆ"
43
+ ]
44
+
45
+ self.sentiment_mapping = {
46
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋งŒ์กฑํ•˜๋ฉฐ ์ข‹์•„ํ•˜๊ณ  ์ถ”์ฒœํ•˜๋Š” ๊ธ์ •์ ์ธ ๊ฐ์ •์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์ข‹์•„์š”, ๋งŒ์กฑ, ์ถ”์ฒœ, ํ›Œ๋ฅญ, ์ตœ๊ณ , ๊ฐ์‚ฌ, ๋งˆ์Œ์— ๋“ค์–ด์š”": "๊ธ์ •",
47
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ค‘๋ฆฝ์ ์ด๊ณ  ๊ฐ๊ด€์ ์œผ๋กœ ์‚ฌ์‹ค์ด๋‚˜ ์ƒํƒœ๋งŒ์„ ๋‚˜์—ดํ•˜๋ฉฐ ํŠน๋ณ„ํ•œ ๊ฐ์ • ํ‘œํ˜„์ด ์—†์Šต๋‹ˆ๋‹ค. ์˜ˆ: ๊ทธ๋ƒฅ ๊ทธ๋ž˜์š”, ๋ณดํ†ต, ๋ฌด๋‚œ, ํ‰๋ฒ”": "์ค‘๋ฆฝ",
48
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ์ด๋‚˜ ์„œ๋น„์Šค์— ์‹ค๋งํ•˜๊ณ  ๋ถˆ๋งŒ์กฑ์Šค๋Ÿฌ์šด ๋ถ€์ •์ ์ธ ๊ฐ์ •์„ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๋ณ„๋กœ, ์‹ค๋ง, ๋ถˆ๋งŒ์กฑ, ์ตœ์•…, ํ™”๋‚จ, ํ›„ํšŒ, ๏ฟฝ๏ฟฝ๋ถˆ": "๋ถ€์ •"
49
+ }
50
+
51
+ # 2๋‹จ๊ณ„: ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ (๊ฐœ์„ ๋œ ํ”„๋กฌํ”„ํŠธ)
52
+ self.topic_categories = [
53
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๋ฐฐ์†ก๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๋ฐฐ์†ก ๋น ๋ฆ„, ๋ฐฐ์†ก ๋Šฆ์Œ, ํฌ์žฅ ์ƒํƒœ, ํƒ๋ฐฐ, ๋„์ฐฉ, ํŒŒ์†",
54
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ํ’ˆ์งˆ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์žฌ์งˆ, ๋‚ด๊ตฌ์„ฑ, ์™„์„ฑ๋„, ํ’ˆ์งˆ ์ข‹์Œ, ํ’ˆ์งˆ ๋‚˜์จ, ํŠผํŠผ, ์•ฝํ•จ",
55
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ์‚ฌ์ด์ฆˆ์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ํฌ๊ธฐ, ์‚ฌ์ด์ฆˆ, ํ•, ์ž‘์Œ, ํผ, ๋”ฑ ๋งž์Œ, ์น˜์ˆ˜",
56
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๊ตํ™˜/ํ™˜๋ถˆ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ตํ™˜, ํ™˜๋ถˆ, ๋ฐ˜ํ’ˆ, ํ™˜๋ถˆ ์‹ ์ฒญ, ๊ตํ™˜ ์ ˆ์ฐจ",
57
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๊ณ ๊ฐ ์„œ๋น„์Šค์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ณ ๊ฐ์„ผํ„ฐ, ์‘๋Œ€, ์ƒ๋‹ด, A/S, ์นœ์ ˆ, ๋ถˆ์นœ์ ˆ",
58
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๊ฐ€๊ฒฉ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ฐ€๊ฒฉ, ๊ฐ€์„ฑ๋น„, ๋น„์Œˆ, ์ €๋ ด, ํ• ์ธ, ๋น„์šฉ, ๋ˆ",
59
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๋””์ž์ธ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๋””์ž์ธ, ์ƒ‰์ƒ, ์™ธ๊ด€, ์˜ˆ์จ, ์Šคํƒ€์ผ, ๋ชจ์–‘, ์ƒ‰๊น”",
60
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ๊ธฐ๋Šฅ/์„ฑ๋Šฅ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ธฐ๋Šฅ, ์„ฑ๋Šฅ, ์ž‘๋™, ํšจ๊ณผ, ์‚ฌ์šฉ๊ฐ, ํŽธ๋ฆฌํ•จ"
61
+ ]
62
+
63
+ self.topic_mapping = {
64
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๋ฐฐ์†ก๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๋ฐฐ์†ก ๋น ๋ฆ„, ๋ฐฐ์†ก ๋Šฆ์Œ, ํฌ์žฅ ์ƒํƒœ, ํƒ๋ฐฐ, ๋„์ฐฉ, ํŒŒ์†": "๋ฐฐ์†ก",
65
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ํ’ˆ์งˆ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์žฌ์งˆ, ๋‚ด๊ตฌ์„ฑ, ์™„์„ฑ๋„, ํ’ˆ์งˆ ์ข‹์Œ, ํ’ˆ์งˆ ๋‚˜์จ, ํŠผํŠผ, ์•ฝํ•จ": "ํ’ˆ์งˆ",
66
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ์‚ฌ์ด์ฆˆ์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ํฌ๊ธฐ, ์‚ฌ์ด์ฆˆ, ํ•, ์ž‘์Œ, ํผ, ๋”ฑ ๋งž์Œ, ์น˜์ˆ˜": "์‚ฌ์ด์ฆˆ",
67
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๊ตํ™˜/ํ™˜๋ถˆ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ตํ™˜, ํ™˜๋ถˆ, ๋ฐ˜ํ’ˆ, ํ™˜๋ถˆ ์‹ ์ฒญ, ๊ตํ™˜ ์ ˆ์ฐจ": "๊ตํ™˜/ํ™˜๋ถˆ",
68
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๊ณ ๊ฐ ์„œ๋น„์Šค์™€ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ณ ๊ฐ์„ผํ„ฐ, ์‘๋Œ€, ์ƒ๋‹ด, A/S, ์นœ์ ˆ, ๋ถˆ์นœ์ ˆ": "์„œ๋น„์Šค",
69
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๊ฐ€๊ฒฉ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ฐ€๊ฒฉ, ๊ฐ€์„ฑ๋น„, ๋น„์Œˆ, ์ €๋ ด, ํ• ์ธ, ๋น„์šฉ, ๋ˆ": "๊ฐ€๊ฒฉ",
70
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๋””์ž์ธ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๋””์ž์ธ, ์ƒ‰์ƒ, ์™ธ๊ด€, ์˜ˆ์จ, ์Šคํƒ€์ผ, ๋ชจ์–‘, ์ƒ‰๊น”": "๋””์ž์ธ",
71
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ œํ’ˆ ๊ธฐ๋Šฅ/์„ฑ๋Šฅ๊ณผ ๊ด€๋ จ๋œ ๋‚ด์šฉ์„ ์–ธ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ๊ธฐ๋Šฅ, ์„ฑ๋Šฅ, ์ž‘๋™, ํšจ๊ณผ, ์‚ฌ์šฉ๊ฐ, ํŽธ๋ฆฌํ•จ": "๊ธฐ๋Šฅ/์„ฑ๋Šฅ"
72
+ }
73
+
74
+ # 3๋‹จ๊ณ„: ๋ฆฌ๋ทฐ ํ†ค ํƒ์ง€ (๊ฐœ์„ ๋œ ํ”„๋กฌํ”„ํŠธ)
75
+ self.tone_categories = [
76
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ •์ƒ์ ์ธ ๋ถˆ๋งŒ ํ‘œํ˜„์œผ๋กœ ๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ ์„ ์ฐจ๋ถ„ํžˆ ์ง€์ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์•„์‰ฝ๋‹ค, ๊ฐœ์„  ํ•„์š”, ๋ถˆํŽธํ•˜๋‹ค, ๋ฌธ์ œ ์žˆ์Œ",
77
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์š•์„ค์ด๋‚˜ ๋น„์†์–ด๋ฅผ ํฌํ•จํ•˜์—ฌ ๊ณต๊ฒฉ์ ์ด๊ณ  ๋ถ€์ ์ ˆํ•œ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์š•์„ค, ๋น„๋‚œ, ์ €์ฃผ, ๊ณต๊ฒฉ์  ํ‘œํ˜„",
78
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์‹ค์ œ ๊ตฌ๋งค ์—†์ด ์ž‘์„ฑ๋œ ํ—ˆ์œ„ ํ›„๊ธฐ์ด๊ฑฐ๋‚˜ ์ง€๋‚˜์น˜๊ฒŒ ๊ณผ์žฅ๋˜๊ณ  ์˜์‹ฌ์Šค๋Ÿฌ์šด ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์˜ˆ: ๋น„ํ˜„์‹ค์  ์นญ์ฐฌ, ๊ตฌ์ฒด์„ฑ ๋ถ€์กฑ, ๋ฐ˜๋ณต ๋ฆฌ๋ทฐ",
79
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋‚˜ ํŒ๋งค์ž๋ฅผ ํ™๋ณดํ•˜๊ฑฐ๋‚˜ ์—ฐ๋ฝ์ฒ˜๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ด‘๊ณ ์„ฑ ์ŠคํŒธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์˜ˆ: ํ…”๋ ˆ๊ทธ๋žจ, ์นดํ†ก, ์—ฐ๋ฝ์ฒ˜, ํ™๋ณด ๋งํฌ",
80
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ •์ƒ์ ์ธ ๊ตฌ๋งค ํ›„๊ธฐ๋กœ ์ง„์†”ํ•˜๊ฒŒ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ ํŠน๋ณ„ํ•œ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค"
81
+ ]
82
+
83
+ self.tone_mapping = {
84
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ •์ƒ์ ์ธ ๋ถˆ๋งŒ ํ‘œํ˜„์œผ๋กœ ๊ตฌ์ฒด์ ์ธ ๋ฌธ์ œ์ ์„ ์ฐจ๋ถ„ํžˆ ์ง€์ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์•„์‰ฝ๋‹ค, ๊ฐœ์„  ํ•„์š”, ๋ถˆํŽธํ•˜๋‹ค, ๋ฌธ์ œ ์žˆ์Œ": "๋‹จ์ˆœ ๋ถˆ๋งŒ",
85
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์š•์„ค์ด๋‚˜ ๋น„์†์–ด๋ฅผ ํฌํ•จํ•˜์—ฌ ๊ณต๊ฒฉ์ ์ด๊ณ  ๋ถ€์ ์ ˆํ•œ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ: ์š•์„ค, ๋น„๋‚œ, ์ €์ฃผ, ๊ณต๊ฒฉ์  ํ‘œํ˜„": "์š•์„ค",
86
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์‹ค์ œ ๊ตฌ๋งค ์—†์ด ์ž‘์„ฑ๋œ ํ—ˆ์œ„ ํ›„๊ธฐ์ด๊ฑฐ๋‚˜ ์ง€๋‚˜์น˜๊ฒŒ ๊ณผ์žฅ๋˜๊ณ  ์˜์‹ฌ์Šค๋Ÿฌ์šด ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์˜ˆ: ๋น„ํ˜„์‹ค์  ์นญ์ฐฌ, ๊ตฌ์ฒด์„ฑ ๋ถ€์กฑ, ๋ฐ˜๋ณต ๋ฆฌ๋ทฐ": "ํ—ˆ์œ„ํ›„๊ธฐ",
87
+ "์ด ๋ฆฌ๋ทฐ๋Š” ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ๋‚˜ ํŒ๋งค์ž๋ฅผ ํ™๋ณดํ•˜๊ฑฐ๋‚˜ ์—ฐ๋ฝ์ฒ˜๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ด‘๊ณ ์„ฑ ์ŠคํŒธ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. ์˜ˆ: ํ…”๋ ˆ๊ทธ๋žจ, ์นดํ†ก, ์—ฐ๋ฝ์ฒ˜, ํ™๋ณด ๋งํฌ": "๊ด‘๊ณ ",
88
+ "์ด ๋ฆฌ๋ทฐ๋Š” ์ •์ƒ์ ์ธ ๊ตฌ๋งค ํ›„๊ธฐ๋กœ ์ง„์†”ํ•˜๊ฒŒ ์ž‘์„ฑ๋˜์—ˆ์œผ๋ฉฐ ํŠน๋ณ„ํ•œ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค": "์ •์ƒ"
89
+ }
90
 
91
  print("๋ชจ๋ธ ๋กœ๋”ฉ ์™„๋ฃŒ!")
92
+ print("โœ“ 3๋‹จ๊ณ„ ๋ถ„์„ ๋ชจ๋“œ ํ™œ์„ฑํ™” (๊ฐ์ • โ†’ ์นดํ…Œ๊ณ ๋ฆฌ โ†’ ํ†ค)")
 
93
 
94
  def preprocess_text(self, text: str) -> str:
95
  """
 
110
 
111
  return text
112
 
113
+ def split_into_sentences(self, text: str) -> List[str]:
114
  """
115
+ ํ…์ŠคํŠธ๋ฅผ ๋ฌธ์žฅ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ
116
 
117
  Args:
118
+ text: ์›๋ณธ ํ…์ŠคํŠธ
119
 
120
  Returns:
121
+ ๋ฌธ์žฅ ๋ฆฌ์ŠคํŠธ
122
  """
123
  import re
124
 
125
+ # ๋ฌธ์žฅ ์ข…๊ฒฐ ๊ธฐํ˜ธ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๋ฆฌ (., !, ?, ~, ใ…Žใ…Ž, ใ…‹ใ…‹ ๋“ฑ ๊ณ ๋ ค)
126
+ # ์ด๋ชจํ‹ฐ์ฝ˜๊ณผ ํŠน์ˆ˜๋ฌธ์ž ํŒจํ„ด ๋ณด์กด
127
+ sentences = re.split(r'[.!?~]+\s*', text)
128
 
129
+ # ๋นˆ ๋ฌธ์žฅ ์ œ๊ฑฐ ๋ฐ ์ •๋ฆฌ
130
+ sentences = [s.strip() for s in sentences if s.strip() and len(s.strip()) > 2]
131
 
132
+ return sentences if sentences else [text]
 
 
133
 
134
+ def analyze_sentiment(self, text: str, use_sentence_split: bool = True) -> Dict:
135
+ """
136
+ 1๋‹จ๊ณ„: ๊ฐ์ • ๋ถ„์„ (๊ธ์ • / ์ค‘๋ฆฝ / ๋ถ€์ •)
137
 
138
+ Args:
139
+ text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
140
+ use_sentence_split: ๋ฌธ์žฅ ๋ถ„๋ฆฌ ํ›„ ๋ถ„์„ ์—ฌ๋ถ€ (๊ธด ๋ฌธ์žฅ ๊ฐœ์„ ์šฉ)
141
+
142
+ Returns:
143
+ ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ
144
+ """
145
+ # ๊ธด ๋ฌธ์žฅ(100์ž ์ด์ƒ)์ธ ๊ฒฝ์šฐ ๋ฌธ์žฅ ๋ถ„๋ฆฌ ํ›„ ๋ถ„์„
146
+ if use_sentence_split and len(text) > 100:
147
+ sentences = self.split_into_sentences(text)
148
+
149
+ if len(sentences) > 1:
150
+ # ๊ฐ ๋ฌธ์žฅ๋ณ„ ๊ฐ์ • ์ ์ˆ˜ ์ˆ˜์ง‘
151
+ all_scores = {cat: [] for cat in self.sentiment_mapping.values()}
152
+
153
+ for sentence in sentences:
154
+ result = self.classifier(
155
+ sentence,
156
+ self.sentiment_categories,
157
+ multi_label=False
158
+ )
159
+
160
+ # ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ ์ˆ˜ ์ˆ˜์ง‘
161
+ for label, score in zip(result['labels'], result['scores']):
162
+ category = self.sentiment_mapping[label]
163
+ all_scores[category].append(score)
164
+
165
+ # ํ‰๊ท  ์ ์ˆ˜ ๊ณ„์‚ฐ
166
+ avg_scores = {
167
+ cat: sum(scores) / len(scores) if scores else 0
168
+ for cat, scores in all_scores.items()
169
+ }
170
+
171
+ # ๊ฐ€์žฅ ๋†’์€ ์ ์ˆ˜์˜ ๊ฐ์ • ์„ ํƒ
172
+ top_sentiment = max(avg_scores.items(), key=lambda x: x[1])
173
+ sentiment = top_sentiment[0]
174
+ confidence = top_sentiment[1]
175
+
176
+ scores_dict = {
177
+ cat: round(score * 100, 2)
178
+ for cat, score in avg_scores.items()
179
+ }
180
+
181
+ return {
182
+ "sentiment": sentiment,
183
+ "confidence": round(confidence * 100, 2),
184
+ "scores": scores_dict,
185
+ "method": "sentence_split"
186
+ }
187
+
188
+ # ๊ธฐ๋ณธ ๋‹จ์ผ ๋ถ„์„
189
+ result = self.classifier(
190
+ text,
191
+ self.sentiment_categories,
192
+ multi_label=False
193
+ )
194
 
195
+ top_category = result['labels'][0]
196
+ top_score = result['scores'][0]
197
+ sentiment = self.sentiment_mapping[top_category]
198
 
199
+ scores_dict = {
200
+ self.sentiment_mapping[label]: round(score * 100, 2)
201
+ for label, score in zip(result['labels'], result['scores'])
202
+ }
203
 
204
+ return {
205
+ "sentiment": sentiment,
206
+ "confidence": round(top_score * 100, 2),
207
+ "scores": scores_dict,
208
+ "method": "single"
209
+ }
210
 
211
+ def analyze_category(self, text: str, top_k: int = 3, use_sentence_split: bool = True, min_threshold: float = 0.25) -> Dict:
212
  """
213
+ 2๋‹จ๊ณ„: ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ (๋ฐฐ์†ก / ํ’ˆ์งˆ / ์‚ฌ์ด์ฆˆ / ๊ตํ™˜ / ์„œ๋น„์Šค ๋“ฑ)
214
 
215
  Args:
216
+ text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
217
+ top_k: ์ƒ์œ„ ๋ช‡ ๊ฐœ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๋ฐ˜๏ฟฝ๏ฟฝํ• ์ง€ (๊ธฐ๋ณธ 3๊ฐœ)
218
+ use_sentence_split: ๋ฌธ์žฅ ๋ถ„๋ฆฌ ํ›„ ๋ถ„์„ ์—ฌ๋ถ€ (๊ธด ๋ฌธ์žฅ ๊ฐœ์„ ์šฉ)
219
+ min_threshold: ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ ์ตœ์†Œ ์ž„๊ณ„๊ฐ’ (๊ธฐ๋ณธ 0.25 = 25%)
220
 
221
  Returns:
222
+ ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ๊ฒฐ๊ณผ
223
  """
224
+ # ๊ธด ๋ฌธ์žฅ์ธ ๊ฒฝ์šฐ ๋ฌธ์žฅ๋ณ„๋กœ ๋ถ„์„ ํ›„ ์ง‘๊ณ„
225
+ if use_sentence_split and len(text) > 100:
226
+ sentences = self.split_into_sentences(text)
227
+
228
+ if len(sentences) > 1:
229
+ # ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ ์ˆ˜ ๋ˆ„์ 
230
+ accumulated_scores = {cat: [] for cat in self.topic_mapping.values()}
231
+
232
+ for sentence in sentences:
233
+ result = self.classifier(
234
+ sentence,
235
+ self.topic_categories,
236
+ multi_label=True
237
+ )
238
+
239
+ # ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์ ์ˆ˜ ์ˆ˜์ง‘
240
+ for label, score in zip(result['labels'], result['scores']):
241
+ category = self.topic_mapping[label]
242
+ accumulated_scores[category].append(score)
243
+
244
+ # ์ตœ๋Œ€ ์ ์ˆ˜๋กœ ์ง‘๊ณ„ (์–ด๋А ํ•œ ๋ฌธ์žฅ์—์„œ๋ผ๋„ ๋†’๊ฒŒ ๋‚˜์˜ค๋ฉด ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ๋กœ ์ธ์ •)
245
+ max_scores = {
246
+ cat: max(scores) if scores else 0
247
+ for cat, scores in accumulated_scores.items()
248
+ }
249
+
250
+ # ์ ์ˆ˜ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌ
251
+ sorted_categories = sorted(max_scores.items(), key=lambda x: x[1], reverse=True)
252
+
253
+ # ์ƒ์œ„ k๊ฐœ ์„ ํƒ (์ž„๊ณ„๊ฐ’ ์ด์ƒ๋งŒ)
254
+ categories = []
255
+ for cat, score in sorted_categories[:top_k]:
256
+ if score >= min_threshold:
257
+ categories.append({
258
+ "category": cat,
259
+ "confidence": round(score * 100, 2)
260
+ })
261
+
262
+ all_scores = {
263
+ cat: round(score * 100, 2)
264
+ for cat, score in sorted_categories
265
+ }
266
+
267
+ return {
268
+ "main_categories": categories,
269
+ "all_scores": all_scores,
270
+ "method": "sentence_split"
271
+ }
272
+
273
+ # ๊ธฐ๋ณธ ๋‹จ์ผ ๋ถ„์„
274
+ result = self.classifier(
275
+ text,
276
+ self.topic_categories,
277
+ multi_label=True # ์—ฌ๋Ÿฌ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ๋™์‹œ์— ํ•ด๋‹น๋  ์ˆ˜ ์žˆ์Œ
278
+ )
279
+
280
+ # ์ƒ์œ„ k๊ฐœ์˜ ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”์ถœ
281
+ categories = []
282
+ for i in range(min(top_k, len(result['labels']))):
283
+ label = result['labels'][i]
284
+ score = result['scores'][i]
285
+ # ์ž„๊ณ„๊ฐ’ ์ด์ƒ์˜ ํ™•์‹ ๋„๋ฅผ ๊ฐ€์ง„ ์นดํ…Œ๊ณ ๋ฆฌ๋งŒ ํฌํ•จ
286
+ if score >= min_threshold:
287
+ categories.append({
288
+ "category": self.topic_mapping[label],
289
+ "confidence": round(score * 100, 2)
290
+ })
291
+
292
+ all_scores = {
293
+ self.topic_mapping[label]: round(score * 100, 2)
294
+ for label, score in zip(result['labels'], result['scores'])
295
+ }
296
+
297
+ return {
298
+ "main_categories": categories,
299
+ "all_scores": all_scores,
300
+ "method": "single"
301
+ }
302
+
303
+ def analyze_tone(self, text: str) -> Dict:
304
+ """
305
+ 3๋‹จ๊ณ„: ๋ฆฌ๋ทฐ ํ†ค ํƒ์ง€ (๋‹จ์ˆœ ๋ถˆ๋งŒ / ์š•์„ค / ํ—ˆ์œ„ํ›„๊ธฐ / ๊ด‘๊ณ  ๋“ฑ)
306
 
307
+ Args:
308
+ text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
309
+
310
+ Returns:
311
+ ํ†ค ๋ถ„์„ ๊ฒฐ๊ณผ
312
+ """
313
  result = self.classifier(
314
+ text,
315
+ self.tone_categories,
316
+ multi_label=False
317
  )
318
 
 
319
  top_category = result['labels'][0]
320
  top_score = result['scores'][0]
321
+ tone = self.tone_mapping[top_category]
322
+
 
 
 
 
 
 
 
 
 
 
 
323
  scores_dict = {
324
+ self.tone_mapping[label]: round(score * 100, 2)
325
  for label, score in zip(result['labels'], result['scores'])
326
  }
327
 
328
+ return {
329
+ "tone": tone,
330
+ "confidence": round(top_score * 100, 2),
331
+ "scores": scores_dict
332
+ }
333
+
334
+ def generate_rating_from_sentiment(self, category: str, confidence: float, sentiment: str) -> int:
335
+ """
336
+ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ฐ์ •๊ณผ ํ™•์‹ ๋„๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ณ„์  ์ƒ์„ฑ
337
+
338
+ Args:
339
+ category: ์นดํ…Œ๊ณ ๋ฆฌ๋ช…
340
+ confidence: ํ™•์‹ ๋„ (0-100)
341
+ sentiment: ๊ฐ์ • (๊ธ์ •/์ค‘๋ฆฝ/๋ถ€์ •)
342
+
343
+ Returns:
344
+ ๋ณ„์  (1-5)
345
+ """
346
+ # ๊ธฐ๋ณธ ์ ์ˆ˜: ๊ฐ์ •์— ๋”ฐ๋ผ
347
+ if sentiment == "๊ธ์ •":
348
+ base_score = 4.5
349
+ elif sentiment == "์ค‘๋ฆฝ":
350
+ base_score = 3.0
351
+ else: # ๋ถ€์ •
352
+ base_score = 1.5
353
+
354
+ # ํ™•์‹ ๋„์— ๋”ฐ๋ผ ์ ์ˆ˜ ์กฐ์ •
355
+ confidence_factor = confidence / 100.0
356
+ final_score = base_score * confidence_factor + 2.5 * (1 - confidence_factor)
357
+
358
+ # 1-5 ์‚ฌ์ด๋กœ ํด๋žจํ•‘
359
+ final_score = max(1, min(5, final_score))
360
+
361
+ return round(final_score)
362
+
363
+ def extract_evidence_from_text(self, text: str, category: str) -> str:
364
+ """
365
+ ํ…์ŠคํŠธ์—์„œ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ๊ทผ๊ฑฐ ๋ฌธ์žฅ ์ถ”์ถœ
366
+
367
+ Args:
368
+ text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
369
+ category: ์นดํ…Œ๊ณ ๋ฆฌ๋ช…
370
+
371
+ Returns:
372
+ ๊ทผ๊ฑฐ ๋ฌธ์žฅ (๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ์ง„ ํ˜•ํƒœ)
373
+ """
374
+ import re
375
+
376
+ # ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ‚ค์›Œ๋“œ ๋งคํ•‘
377
+ keywords = {
378
+ "๋ฐฐ์†ก": ["๋ฐฐ์†ก", "ํƒ๋ฐฐ", "๋„์ฐฉ", "ํฌ์žฅ", "๋น ๋ฅด"],
379
+ "ํ’ˆ์งˆ": ["ํ’ˆ์งˆ", "์žฌ์งˆ", "ํŠผํŠผ", "๋‚ด๊ตฌ", "์™„์„ฑ๋„", "ํ„ธ๋น ์ง", "๋น ์ง"],
380
+ "์‚ฌ์ด์ฆˆ": ["์‚ฌ์ด์ฆˆ", "ํฌ๊ธฐ", "ํ•", "์น˜์ˆ˜", "๋งž"],
381
+ "๊ตํ™˜/ํ™˜๋ถˆ": ["๊ตํ™˜", "ํ™˜๋ถˆ", "๋ฐ˜ํ’ˆ"],
382
+ "์„œ๋น„์Šค": ["์„œ๋น„์Šค", "๊ณ ๊ฐ์„ผํ„ฐ", "์‘๋Œ€", "์นœ์ ˆ"],
383
+ "๊ฐ€๊ฒฉ": ["๊ฐ€๊ฒฉ", "๊ฐ€์„ฑ๋น„", "๋น„์‹ธ", "์ €๋ ด", "ํ• ์ธ", "๋ˆ"],
384
+ "๋””์ž์ธ": ["๋””์ž์ธ", "์ƒ‰์ƒ", "์˜ˆ์˜", "์Šคํƒ€์ผ", "์™ธ๊ด€", "์ด์˜"],
385
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": ["๊ธฐ๋Šฅ", "์„ฑ๋Šฅ", "์ž‘๋™", "ํšจ๊ณผ", "์‚ฌ์šฉ"]
386
+ }
387
+
388
+ # ๋ฌธ์žฅ ๋ถ„๋ฆฌ
389
+ sentences = re.split(r'[.!?~]+\s*', text)
390
+
391
+ # ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์žฅ ์ฐพ๊ธฐ
392
+ for sentence in sentences:
393
+ sentence = sentence.strip()
394
+ if category in keywords:
395
+ for keyword in keywords[category]:
396
+ if keyword in sentence and len(sentence) > 5:
397
+ # ๋„ˆ๋ฌด ๊ธด ๋ฌธ์žฅ์€ ์ž˜๋ผ๋‚ด๊ธฐ
398
+ if len(sentence) > 40:
399
+ sentence = sentence[:40] + "..."
400
+ return f'"{sentence}"'
401
+
402
+ return "-"
403
+
404
+ def analyze_sentiment_for_category(self, text: str, category: str) -> str:
405
+ """
406
+ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์— ๋Œ€ํ•œ ๊ฐ์ • ๋ถ„์„
407
+
408
+ Args:
409
+ text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
410
+ category: ์นดํ…Œ๊ณ ๋ฆฌ๋ช…
411
+
412
+ Returns:
413
+ ๊ฐ์ • (๊ธ์ •/์ค‘๋ฆฝ/๋ถ€์ •)
414
+ """
415
+ import re
416
+
417
+ # ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์žฅ ์ฐพ๊ธฐ
418
+ keywords = {
419
+ "๋ฐฐ์†ก": ["๋ฐฐ์†ก", "ํƒ๋ฐฐ", "๋„์ฐฉ", "ํฌ์žฅ", "๋น ๋ฅด"],
420
+ "ํ’ˆ์งˆ": ["ํ’ˆ์งˆ", "์žฌ์งˆ", "ํŠผํŠผ", "๋‚ด๊ตฌ", "์™„์„ฑ๋„", "ํ„ธ๋น ์ง", "๋น ์ง"],
421
+ "์‚ฌ์ด์ฆˆ": ["์‚ฌ์ด์ฆˆ", "ํฌ๊ธฐ", "ํ•", "์น˜์ˆ˜", "๋งž"],
422
+ "๊ตํ™˜/ํ™˜๋ถˆ": ["๊ตํ™˜", "ํ™˜๋ถˆ", "๋ฐ˜ํ’ˆ"],
423
+ "์„œ๋น„์Šค": ["์„œ๋น„์Šค", "๊ณ ๊ฐ์„ผํ„ฐ", "์‘๋Œ€", "์นœ์ ˆ"],
424
+ "๊ฐ€๊ฒฉ": ["๊ฐ€๊ฒฉ", "๊ฐ€์„ฑ๋น„", "๋น„์‹ธ", "์ €๋ ด", "ํ• ์ธ", "๋ˆ"],
425
+ "๋””์ž์ธ": ["๋””์ž์ธ", "์ƒ‰์ƒ", "์˜ˆ์˜", "์Šคํƒ€์ผ", "์™ธ๊ด€", "์ด์˜"],
426
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": ["๊ธฐ๋Šฅ", "์„ฑ๋Šฅ", "์ž‘๋™", "ํšจ๊ณผ", "์‚ฌ์šฉ"]
427
+ }
428
+
429
+ # ๊ธ์ • ํ‚ค์›Œ๋“œ (๋ช…์‹œ์  ๊ธ์ • ํ‘œํ˜„)
430
+ positive_keywords = ["์ข‹", "ํ›Œ๋ฅญ", "๋งŒ์กฑ", "์ตœ๊ณ ", "์˜ˆ์˜", "์ด์˜", "๋”ฑ๋งž", "๋น ๋ฅด", "๊ดœ์ฐฎ"]
431
+
432
+ # ๋ถ€์ • ํ‚ค์›Œ๋“œ
433
+ negative_keywords = ["๋ณ„๋กœ", "์•„์‰ฝ", "์‹ค๋ง", "์ตœ์•…", "์งœ์ฆ", "๋ฌธ์ œ"]
434
+
435
+ sentences = re.split(r'[.!?~]+\s*', text)
436
+
437
+ # ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ๋ฌธ์žฅ์—์„œ ๊ฐ์ • ํŒ๋‹จ
438
+ if category in keywords:
439
+ for sentence in sentences:
440
+ # ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์žฅ๋งŒ ๊ฒ€์‚ฌ
441
+ has_category_keyword = False
442
+ for keyword in keywords[category]:
443
+ if keyword in sentence:
444
+ has_category_keyword = True
445
+ break
446
+
447
+ if has_category_keyword:
448
+ # ๊ธ์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ
449
+ for pos_keyword in positive_keywords:
450
+ if pos_keyword in sentence:
451
+ return "๊ธ์ •"
452
+
453
+ # ๋ถ€์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ
454
+ for neg_keyword in negative_keywords:
455
+ if neg_keyword in sentence:
456
+ return "๋ถ€์ •"
457
 
458
+ # ๊ธฐ๋ณธ๊ฐ’์€ ์ค‘๋ฆฝ
459
+ return "์ค‘๋ฆฝ"
 
 
 
 
460
 
461
+ def generate_comprehensive_analysis(self, review_text: str, analysis_result: Dict) -> Dict:
462
+ """
463
+ ์ข…ํ•ฉ ๋ถ„์„ ์ƒ์„ฑ - ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€ ๋ฐ ์š”์•ฝ
464
+
465
+ Args:
466
+ review_text: ์›๋ณธ ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
467
+ analysis_result: 3๋‹จ๊ณ„ ๋ถ„์„ ๊ฒฐ๊ณผ
468
+
469
+ Returns:
470
+ ์ข…ํ•ฉ ๋ถ„์„ ๊ฒฐ๊ณผ
471
+ """
472
+ sentiment = analysis_result['sentiment']['sentiment']
473
+ sentiment_scores = analysis_result['sentiment']['scores']
474
+ categories = analysis_result['categories']['main_categories']
475
+ tone = analysis_result['tone']['tone']
476
+
477
+ # ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€
478
+ item_ratings = []
479
+ for cat_info in categories:
480
+ category = cat_info['category']
481
+ confidence = cat_info['confidence']
482
+
483
+ # ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฐ์ • ๋ถ„์„
484
+ category_sentiment = self.analyze_sentiment_for_category(review_text, category)
485
+
486
+ # ๋ณ„์  ๊ณ„์‚ฐ (์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ฐ์ • ๊ธฐ๋ฐ˜)
487
+ if category_sentiment == "๋ถ€์ •":
488
+ rating = 2
489
+ elif category_sentiment == "๊ธ์ •":
490
+ rating = self.generate_rating_from_sentiment(category, confidence, sentiment)
491
+ else:
492
+ rating = 3
493
+
494
+ # ๊ทผ๊ฑฐ ์ถ”์ถœ
495
+ evidence = self.extract_evidence_from_text(review_text, category)
496
+
497
+ item_ratings.append({
498
+ "category": category,
499
+ "rating": rating,
500
+ "evidence": evidence,
501
+ "confidence": confidence
502
+ })
503
+
504
+ # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ ์ถ”์ •
505
+ repurchase_score = 3 # ๊ธฐ๋ณธ๊ฐ’
506
+ if sentiment == "๊ธ์ •":
507
+ repurchase_score = 4
508
+ if sentiment_scores['๊ธ์ •'] > 70:
509
+ repurchase_score = 5
510
+ elif sentiment == "๋ถ€์ •":
511
+ repurchase_score = 2
512
+ if sentiment_scores['๋ถ€์ •'] > 70:
513
+ repurchase_score = 1
514
+ else:
515
+ repurchase_score = 3
516
+
517
+ # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ ๊ทผ๊ฑฐ
518
+ repurchase_keywords = ["๋˜", "๋‹ค์‹œ", "์žฌ๊ตฌ๋งค", "์ถ”์ฒœ", "ํ™˜๋ถˆ", "์ตœ์•…"]
519
+ repurchase_evidence = "-"
520
+ for keyword in repurchase_keywords:
521
+ if keyword in review_text:
522
+ import re
523
+ sentences = re.split(r'[.!?~]+\s*', review_text)
524
+ for sentence in sentences:
525
+ if keyword in sentence and len(sentence.strip()) > 5:
526
+ repurchase_evidence = f'"{sentence.strip()[:40]}"'
527
+ break
528
+ if repurchase_evidence != "-":
529
+ break
530
+
531
+ # ์ „์ฒด ํ†ค ๋น„์œจ
532
+ positive_ratio = sentiment_scores.get('๊ธ์ •', 0)
533
+ negative_ratio = sentiment_scores.get('๋ถ€์ •', 0)
534
+ neutral_ratio = sentiment_scores.get('์ค‘๋ฆฝ', 0)
535
+
536
+ # ์š”์•ฝ ๋ฌธ์žฅ ์ƒ์„ฑ
537
+ summary = self.generate_summary_sentence(review_text, item_ratings, sentiment)
538
 
539
  return {
540
+ "item_ratings": item_ratings,
541
+ "repurchase": {
542
+ "rating": repurchase_score,
543
+ "evidence": repurchase_evidence
 
 
544
  },
545
+ "tone_ratio": {
546
+ "positive": round(positive_ratio),
547
+ "negative": round(negative_ratio),
548
+ "neutral": round(neutral_ratio)
549
+ },
550
+ "summary": summary,
551
+ "overall_sentiment": sentiment
552
+ }
553
+
554
+ def generate_summary_sentence(self, review_text: str, item_ratings: List[Dict], sentiment: str) -> str:
555
+ """
556
+ ์š”์•ฝ ๋ฌธ์žฅ ์ž๋™ ์ƒ์„ฑ
557
+
558
+ Args:
559
+ review_text: ์›๋ณธ ๋ฆฌ๋ทฐ
560
+ item_ratings: ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€
561
+ sentiment: ์ „์ฒด ๊ฐ์ •
562
+
563
+ Returns:
564
+ ์š”์•ฝ ๋ฌธ์žฅ
565
+ """
566
+ # ๋†’์€ ํ‰๊ฐ€ ํ•ญ๋ชฉ๊ณผ ๋‚ฎ์€ ํ‰๊ฐ€ ํ•ญ๋ชฉ ์ฐพ๊ธฐ
567
+ high_rated = [item for item in item_ratings if item['rating'] >= 4]
568
+ low_rated = [item for item in item_ratings if item['rating'] <= 2]
569
+
570
+ if high_rated and low_rated:
571
+ # ์žฅ๋‹จ์ ์ด ๋ชจ๋‘ ์žˆ๋Š” ๊ฒฝ์šฐ
572
+ high_cats = ", ".join([item['category'] for item in high_rated[:2]])
573
+ low_cats = ", ".join([item['category'] for item in low_rated[:2]])
574
+ return f"{high_cats}์€(๋Š”) ์ข‹์ง€๋งŒ, {low_cats} ๋ถ€๋ถ„์ด ์•„์‰ฌ์šด ์ œํ’ˆ์ด์—์š”."
575
+
576
+ elif high_rated:
577
+ # ๊ธ์ •์ ์ธ ๊ฒฝ์šฐ
578
+ high_cats = ", ".join([item['category'] for item in high_rated[:3]])
579
+ return f"{high_cats} ๋ชจ๋‘ ๋งŒ์กฑ์Šค๋Ÿฌ์šด ์ œํ’ˆ์ด์—์š”."
580
+
581
+ elif low_rated:
582
+ # ๋ถ€์ •์ ์ธ ๊ฒฝ์šฐ
583
+ low_cats = ", ".join([item['category'] for item in low_rated[:3]])
584
+ return f"{low_cats} ๋ถ€๋ถ„์ด ๊ธฐ๋Œ€์— ๋ชป ๋ฏธ์น˜๋Š” ์ œํ’ˆ์ด์—์š”."
585
+
586
+ else:
587
+ # ์ค‘๋ฆฝ์ ์ธ ๊ฒฝ์šฐ
588
+ if sentiment == "๊ธ์ •":
589
+ return "์ „๋ฐ˜์ ์œผ๋กœ ๋งŒ์กฑ์Šค๋Ÿฌ์šด ์ œํ’ˆ์ด์—์š”."
590
+ elif sentiment == "๋ถ€์ •":
591
+ return "์ „๋ฐ˜์ ์œผ๋กœ ์•„์‰ฌ์›€์ด ๋‚จ๋Š” ์ œํ’ˆ์ด์—์š”."
592
+ else:
593
+ return "๋ฌด๋‚œํ•œ ์ˆ˜์ค€์˜ ์ œํ’ˆ์ด์—์š”."
594
+
595
+ def analyze_review(self, review_text: str, include_comprehensive: bool = True) -> Dict:
596
+ """
597
+ ๋‹จ์ผ ๋ฆฌ๋ทฐ๋ฅผ 3๋‹จ๊ณ„๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
598
+
599
+ Args:
600
+ review_text: ๋ถ„์„ํ•  ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
601
+ include_comprehensive: ์ข…ํ•ฉ ๋ถ„์„ ํฌํ•จ ์—ฌ๋ถ€
602
+
603
+ Returns:
604
+ 3๋‹จ๊ณ„ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ํฌํ•จํ•œ ๋”•์…”๋„ˆ๋ฆฌ
605
+ """
606
+ # ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ
607
+ processed_text = self.preprocess_text(review_text)
608
+
609
+ # 1๋‹จ๊ณ„: ๊ฐ์ • ๋ถ„์„
610
+ sentiment_result = self.analyze_sentiment(processed_text)
611
+
612
+ # 2๋‹จ๊ณ„: ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„
613
+ category_result = self.analyze_category(processed_text)
614
+
615
+ # 3๋‹จ๊ณ„: ํ†ค ๋ถ„์„
616
+ tone_result = self.analyze_tone(processed_text)
617
+
618
+ result = {
619
+ "review": review_text,
620
+ "sentiment": sentiment_result,
621
+ "categories": category_result,
622
+ "tone": tone_result,
623
  "timestamp": datetime.now().isoformat()
624
  }
625
 
626
+ # ์ข…ํ•ฉ ๋ถ„์„ ์ถ”๊ฐ€
627
+ if include_comprehensive:
628
+ result["comprehensive"] = self.generate_comprehensive_analysis(review_text, result)
629
+
630
+ return result
631
+
632
  def analyze_reviews(self, reviews: List[str]) -> List[Dict]:
633
  """
634
  ์—ฌ๋Ÿฌ ๋ฆฌ๋ทฐ๋ฅผ ์ผ๊ด„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
 
649
  def print_results(self, results: List[Dict]):
650
  """๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ธฐ ์ข‹๊ฒŒ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค."""
651
  print("\n" + "="*80)
652
+ print("๋ฆฌ๋ทฐ 3๋‹จ๊ณ„ ๋ถ„์„ ๊ฒฐ๊ณผ")
653
  print("="*80)
654
 
655
  for idx, result in enumerate(results, 1):
656
  print(f"\n[๋ฆฌ๋ทฐ #{idx}]")
657
  print(f"๋‚ด์šฉ: {result['review']}")
658
+ print(f"\n1๏ธโƒฃ ๊ฐ์ •: {result['sentiment']['sentiment']} ({result['sentiment']['confidence']}%)")
659
+
660
+ # ์นดํ…Œ๊ณ ๋ฆฌ ์ถœ๋ ฅ
661
+ categories_str = ', '.join([f"{c['category']} ({c['confidence']}%)" for c in result['categories']['main_categories']])
662
+ print(f"2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ: {categories_str}")
663
+ print(f"3๏ธโƒฃ ํ†ค: {result['tone']['tone']} ({result['tone']['confidence']}%)")
664
 
665
  print("\n" + "="*80)
666
 
 
687
  reviews.append(row['review_text'])
688
  return reviews
689
 
690
+ def analyze_for_gradio(self, review_text: str):
691
  """
692
  Gradio UI์šฉ ๋ฆฌ๋ทฐ ๋ถ„์„ ํ•จ์ˆ˜
693
 
 
695
  review_text: ๋ถ„์„ํ•  ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
696
 
697
  Returns:
698
+ (๊ฐ์ • ๊ฒฐ๊ณผ, ์นดํ…Œ๊ณ ๋ฆฌ ๊ฒฐ๊ณผ, ํ†ค ๊ฒฐ๊ณผ, ์ข…ํ•ฉ ๋ถ„์„, ๊ฐ์ • ๋ถ„ํฌ, ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„ํฌ, ํ†ค ๋ถ„ํฌ) ํŠœํ”Œ
699
  """
700
  if not review_text or review_text.strip() == "":
701
+ return "โš ๏ธ ๋ฆฌ๋ทฐ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”", "", "", "", {}, {}, {}
702
 
703
+ result = self.analyze_review(review_text, include_comprehensive=True)
704
 
705
+ # 1๋‹จ๊ณ„: ๊ฐ์ • ๋ถ„์„ ๊ฒฐ๊ณผ
706
+ sentiment = result['sentiment']['sentiment']
707
+ sentiment_conf = result['sentiment']['confidence']
708
 
709
+ sentiment_emoji = {
710
+ "๊ธ์ •": "๐Ÿ˜Š",
711
+ "์ค‘๋ฆฝ": "๐Ÿ˜",
712
+ "๋ถ€์ •": "๐Ÿ˜ž"
713
+ }
714
+ emoji = sentiment_emoji.get(sentiment, "โ“")
715
+ sentiment_output = f"{emoji} {sentiment} ({sentiment_conf}%)"
716
+
717
+ # 2๋‹จ๊ณ„: ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„ ๊ฒฐ๊ณผ
718
+ categories = result['categories']['main_categories']
719
+ if categories:
720
+ category_list = [f"โ€ข {c['category']} ({c['confidence']}%)" for c in categories]
721
+ category_output = "\n".join(category_list)
722
  else:
723
+ category_output = "ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ"
724
+
725
+ # 3๋‹จ๊ณ„: ํ†ค ๋ถ„์„ ๊ฒฐ๊ณผ
726
+ tone = result['tone']['tone']
727
+ tone_conf = result['tone']['confidence']
728
+
729
+ tone_emoji = {
730
+ "์ •์ƒ": "โœ…",
731
+ "๋‹จ์ˆœ ๋ถˆ๋งŒ": "๐Ÿ’ฌ",
732
+ "์š•์„ค": "๐Ÿšซ",
733
+ "ํ—ˆ์œ„ํ›„๊ธฐ": "โš ๏ธ",
734
+ "๊ด‘๊ณ ": "๐Ÿ“ข"
735
+ }
736
+ tone_emoji_selected = tone_emoji.get(tone, "โ“")
737
+ tone_output = f"{tone_emoji_selected} {tone} ({tone_conf}%)"
738
 
739
+ # 4๋‹จ๊ณ„: ์ข…ํ•ฉ ๋ถ„์„ ๊ฒฐ๊ณผ
740
+ comprehensive_output = self.format_comprehensive_analysis(result['comprehensive'])
741
+
742
+ # ํ™•๋ฅ  ๋ถ„ํฌ ๋”•์…”๋„ˆ๋ฆฌ๋“ค (Gradio Label ์ปดํฌ๋„ŒํŠธ์šฉ)
743
+ sentiment_probs = {
744
+ k: v / 100.0 for k, v in result['sentiment']['scores'].items()
745
+ }
746
 
747
+ category_probs = {
748
+ k: v / 100.0 for k, v in result['categories']['all_scores'].items()
749
+ }
750
 
751
+ tone_probs = {
752
+ k: v / 100.0 for k, v in result['tone']['scores'].items()
753
+ }
754
 
755
+ return sentiment_output, category_output, tone_output, comprehensive_output, sentiment_probs, category_probs, tone_probs
756
 
757
+ def format_comprehensive_analysis(self, comprehensive: Dict) -> str:
758
+ """
759
+ ์ข…ํ•ฉ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์œผ๋กœ ํฌ๋งทํŒ…
 
760
 
761
+ Args:
762
+ comprehensive: ์ข…ํ•ฉ ๋ถ„์„ ๋”•์…”๋„ˆ๋ฆฌ
763
+
764
+ Returns:
765
+ ๋งˆํฌ๋‹ค์šด ํ˜•์‹์˜ ๋ฌธ์ž์—ด
766
+ """
767
+ output = "## โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„\n\n"
768
+ output += "| ํ•ญ๋ชฉ | ํ‰๊ฐ€ | ๊ทผ๊ฑฐ |\n"
769
+ output += "|------|------|------|\n"
770
+
771
+ # ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€
772
+ for item in comprehensive['item_ratings']:
773
+ stars = "โญ๏ธ" * item['rating']
774
+ output += f"| {item['category']} | {stars} | {item['evidence']} |\n"
775
+
776
+ # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ
777
+ repurchase_stars = "โญ๏ธ" * comprehensive['repurchase']['rating']
778
+ output += f"| ์žฌ๊ตฌ๋งค ์˜ํ–ฅ | {repurchase_stars} | {comprehensive['repurchase']['evidence']} |\n"
779
+
780
+ # ์ „์ฒด ํ†ค
781
+ tone_ratio = comprehensive['tone_ratio']
782
+ output += f"| ์ „์ฒด ํ†ค | ๊ธ์ • {tone_ratio['positive']} : ๋ถ€์ • {tone_ratio['negative']} | "
783
+
784
+ if tone_ratio['positive'] > tone_ratio['negative'] + 20:
785
+ output += "๊ธ์ •์ด ์šฐ์„ธํ•จ |\n"
786
+ elif tone_ratio['negative'] > tone_ratio['positive'] + 20:
787
+ output += "๋ถ€์ •์ด ์šฐ์„ธํ•จ |\n"
788
+ else:
789
+ output += "๊ธ์ •๊ณผ ๋ถ€์ •์ด ํ˜ผ์žฌ๋จ |\n"
790
 
791
+ # ์š”์•ฝ ๋ฌธ์žฅ
792
+ output += f"\n## ๐Ÿ’ก ์š”์•ฝ ๋ฌธ์žฅ\n\n"
793
+ output += f"**\"{comprehensive['summary']}\"**\n"
794
+
795
+ return output
796
 
797
 
798
  # ์ „์—ญ ๋ถ„์„๊ธฐ ์ธ์Šคํ„ด์Šค (Gradio ์•ฑ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋“œ)
 
813
  # ๋ถ„์„๊ธฐ ์ดˆ๊ธฐํ™”
814
  review_analyzer = get_analyzer()
815
 
816
+ # ์ƒ˜ํ”Œ ๋ฆฌ๋ทฐ ์˜ˆ์‹œ
817
  examples = [
818
  ["์ •๋ง ์ข‹์€ ์ œํ’ˆ์ด์—์š”! ๋ฐฐ์†ก๋„ ๏ฟฝ๏ฟฝ๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์—๋„ ๋˜ ๊ตฌ๋งคํ• ๊ฒŒ์š”!"],
819
  ["์™„์ „ ์‹ค๋ง์ด์—์š”. ์‚ฌ์ง„์ด๋ž‘ ์™„์ „ ๋‹ค๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ๋ณ„๋กœ์ž…๋‹ˆ๋‹ค. ํ™˜๋ถˆ ์‹ ์ฒญํ–ˆ์Šต๋‹ˆ๋‹ค."],
820
  ["ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”~~๊ฐ์ˆ˜ํ• ๋งŒํ•œ๋ฐ ์€๊ทผ ์งœ์ฆ๋‚ ์ˆ˜๋„? ๊ทธ๋ƒฅ ์ž…์œผ๋ฉด ๊ณ ์–‘์ด๋งˆ๋ƒฅ ํ„ธ์„ ๋ฟœ๋‚ด์š” ใ…Žใ…Ž"],
821
  ["ํ…”๋ ˆ๊ทธ๋žจ @abcd1234๋กœ ์—ฐ๋ฝ์ฃผ์‹œ๋ฉด ๋ฐ˜๊ฐ’์— ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋„๋งค๊ฐ€๋กœ ํŒ๋งค์ค‘!"],
 
 
822
  ["๋ฐฐ์†ก์ด ์ƒ๊ฐ๋ณด๋‹ค ๋นจ๋ผ์„œ ์ข‹์•˜์–ด์š”. ํ’ˆ์งˆ๋„ ๊ดœ์ฐฎ๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค."],
823
+ ["์‚ฌ์ด์ฆˆ๊ฐ€ ๋„ˆ๋ฌด ์ž‘์•„์š”. ๊ตํ™˜ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ์ ˆ์ฐจ๊ฐ€ ๋ณต์žกํ•˜๋„ค์š”."],
824
+ ["๋””์ž์ธ์€ ์˜ˆ์œ๋ฐ ํ’ˆ์งˆ์ด ๊ฐ€๊ฒฉ์— ๋น„ํ•ด ๋ณ„๋กœ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ์ €๋ƒฅ์ด์—์š”."],
825
  ]
826
 
827
+ # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ - ๋ชจ๋˜ ๋Œ€์‹œ๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ
828
+ with gr.Blocks(
829
+ title="๋ฆฌ๋ทฐ 3๋‹จ๊ณ„ ๋ถ„์„ ์„œ๋น„์Šค",
830
+ theme=gr.themes.Default(
831
+ primary_hue="blue",
832
+ secondary_hue="slate",
833
+ neutral_hue="slate",
834
+ font=gr.themes.GoogleFont("Noto Sans KR")
835
+ ),
836
+ css="""
837
+ .card-header {
838
+ font-size: 1.2em;
839
+ font-weight: bold;
840
+ margin-bottom: 10px;
841
+ padding: 10px;
842
+ border-radius: 8px;
843
+ text-align: center;
844
+ }
845
+ .sentiment-positive { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
846
+ .sentiment-neutral { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; }
847
+ .sentiment-negative { background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); color: white; }
848
+ .metric-card {
849
+ border: 2px solid #e5e7eb;
850
+ border-radius: 12px;
851
+ padding: 20px;
852
+ background: white;
853
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
854
+ }
855
+ .big-emoji { font-size: 3em; text-align: center; margin: 10px 0; }
856
+ .big-text { font-size: 1.8em; font-weight: bold; text-align: center; margin: 5px 0; }
857
+ .confidence { font-size: 1.2em; color: #6b7280; text-align: center; }
858
+ """
859
+ ) as demo:
860
 
861
+ # ํ—ค๋”
862
+ gr.Markdown("""
863
+ # ๐Ÿ” ๋ฆฌ๋ทฐ ๋ถ„์„ ๋Œ€์‹œ๋ณด๋“œ
864
 
865
+ AI ๊ธฐ๋ฐ˜ 3๋‹จ๊ณ„ ๋ถ„์„์œผ๋กœ ๋ฆฌ๋ทฐ๋ฅผ ์ž๋™์œผ๋กœ ๊ฒ€์ˆ˜ํ•˜๊ณ  ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
 
 
 
 
 
866
  """)
867
 
868
+ # ์ž…๋ ฅ ์„น์…˜
869
  with gr.Row():
870
+ review_input = gr.Textbox(
871
+ label="๐Ÿ“ ๋ฆฌ๋ทฐ ์ž…๋ ฅ",
872
+ placeholder="๋ถ„์„ํ•  ๋ฆฌ๋ทฐ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”...",
873
+ lines=4,
874
+ max_lines=8,
875
+ scale=4
876
+ )
877
  with gr.Column(scale=1):
878
+ submit_btn = gr.Button("๐Ÿ” ๋ถ„์„ ์‹œ์ž‘", variant="primary", size="lg")
879
+ clear_btn = gr.Button("๐Ÿ—‘๏ธ ์ดˆ๊ธฐํ™”", variant="secondary", size="sm")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
880
 
881
+ gr.Examples(
882
+ examples=examples,
883
+ inputs=review_input,
884
+ label="๐Ÿ’ก ์˜ˆ์‹œ ๋ฆฌ๋ทฐ"
885
+ )
 
886
 
887
+ gr.Markdown("---")
888
+ gr.Markdown("## ๐Ÿ“Š ๋ถ„์„ ๊ฒฐ๊ณผ")
 
 
889
 
890
+ # 3๋‹จ๊ณ„ ๋ถ„์„ ๊ฒฐ๊ณผ - 3์—ด ์นด๋“œ ๋ ˆ์ด์•„์›ƒ
891
+ with gr.Row(equal_height=True):
892
+ # 1๋‹จ๊ณ„: ๊ฐ์ • ๋ถ„์„
893
+ with gr.Column(scale=1):
894
+ gr.HTML('<div class="card-header sentiment-positive">1๏ธโƒฃ ๊ฐ์ • ๋ถ„์„</div>')
895
+ with gr.Group(elem_classes="metric-card"):
896
+ sentiment_output = gr.Textbox(
897
+ label="",
898
+ lines=1,
899
+ interactive=False,
900
+ show_label=False,
901
+ container=False,
902
+ elem_classes="big-text"
903
+ )
904
+ sentiment_prob = gr.Label(
905
+ label="ํ™•๋ฅ  ๋ถ„ํฌ",
906
+ num_top_classes=3,
907
+ show_label=True
908
+ )
909
+
910
+ # 2๋‹จ๊ณ„: ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„
911
+ with gr.Column(scale=1):
912
+ gr.HTML('<div class="card-header sentiment-neutral">2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„</div>')
913
+ with gr.Group(elem_classes="metric-card"):
914
+ category_output = gr.Textbox(
915
+ label="",
916
+ lines=4,
917
+ interactive=False,
918
+ show_label=False,
919
+ container=False
920
+ )
921
+ category_prob = gr.Label(
922
+ label="ํ™•๋ฅ  ๋ถ„ํฌ",
923
+ num_top_classes=5,
924
+ show_label=True
925
+ )
926
+
927
+ # 3๋‹จ๊ณ„: ํ†ค ํƒ์ง€
928
+ with gr.Column(scale=1):
929
+ gr.HTML('<div class="card-header sentiment-negative">3๏ธโƒฃ ๋ฆฌ๋ทฐ ํ†ค ํƒ์ง€</div>')
930
+ with gr.Group(elem_classes="metric-card"):
931
+ tone_output = gr.Textbox(
932
+ label="",
933
+ lines=1,
934
+ interactive=False,
935
+ show_label=False,
936
+ container=False,
937
+ elem_classes="big-text"
938
+ )
939
+ tone_prob = gr.Label(
940
+ label="ํ™•๋ฅ  ๋ถ„ํฌ",
941
+ num_top_classes=5,
942
+ show_label=True
943
+ )
944
+
945
+ gr.Markdown("---")
946
+
947
+ # ์ข…ํ•ฉ ๋ถ„์„ - ์ „์ฒด ๋„ˆ๋น„, ์•„์ฝ”๋””์–ธ ์Šคํƒ€์ผ
948
+ with gr.Accordion("โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„ & ์ธ์‚ฌ์ดํŠธ", open=True):
949
+ comprehensive_output = gr.Markdown(
950
+ value="",
951
+ show_label=False
952
+ )
953
 
954
  # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
955
  submit_btn.click(
956
  fn=review_analyzer.analyze_for_gradio,
957
  inputs=review_input,
958
+ outputs=[sentiment_output, category_output, tone_output, comprehensive_output,
959
+ sentiment_prob, category_prob, tone_prob]
960
  )
961
 
962
  review_input.submit(
963
  fn=review_analyzer.analyze_for_gradio,
964
  inputs=review_input,
965
+ outputs=[sentiment_output, category_output, tone_output, comprehensive_output,
966
+ sentiment_prob, category_prob, tone_prob]
967
  )
968
 
969
  clear_btn.click(
970
+ fn=lambda: ("", "", "", "", "", {}, {}, {}),
971
  inputs=None,
972
+ outputs=[review_input, sentiment_output, category_output, tone_output,
973
+ comprehensive_output, sentiment_prob, category_prob, tone_prob]
974
  )
975
 
976
+ # ํ‘ธํ„ฐ - ์•„์ฝ”๋””์–ธ์œผ๋กœ ์ ‘์„ ์ˆ˜ ์žˆ๊ฒŒ
977
+ with gr.Accordion("โ„น๏ธ ์ƒ์„ธ ์ •๋ณด & ์‚ฌ์šฉ ๊ฐ€์ด๋“œ", open=False):
978
+ gr.Markdown("""
979
+ ### ๐Ÿ“ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
980
+ 1. ์ƒ๋‹จ ํ…์ŠคํŠธ ๋ฐ•์Šค์— ๋ฆฌ๋ทฐ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”
981
+ 2. **๋ถ„์„ ์‹œ์ž‘** ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ Enter๋ฅผ ๋ˆ„๋ฅด์„ธ์š”
982
+ 3. AI๊ฐ€ ์ž๋™์œผ๋กœ 3๋‹จ๊ณ„ ๋ถ„์„ ๋ฐ ์ข…ํ•ฉ ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค
983
+
984
+ ### ๐ŸŽฏ 3๋‹จ๊ณ„ ๋ถ„์„ ์„ค๋ช…
985
+ - **1๏ธโƒฃ ๊ฐ์ • ๋ถ„์„**: ๋ฆฌ๋ทฐ์˜ ์ „๋ฐ˜์ ์ธ ๊ฐ์ • (๊ธ์ •/์ค‘๋ฆฝ/๋ถ€์ •)
986
+ - **2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„์„**: ๋ฆฌ๋ทฐ๊ฐ€ ์–ธ๊ธ‰ํ•˜๋Š” ์ฃผ์ œ (๋ฐฐ์†ก/ํ’ˆ์งˆ/์‚ฌ์ด์ฆˆ/๊ตํ™˜/์„œ๋น„์Šค/๊ฐ€๊ฒฉ/๋””์ž์ธ/๊ธฐ๋Šฅ)
987
+ - **3๏ธโƒฃ ํ†ค ํƒ์ง€**: ๋ฆฌ๋ทฐ์˜ ์‹ ๋ขฐ์„ฑ ํ‰๊ฐ€ (์ •์ƒ/๋‹จ์ˆœ๋ถˆ๋งŒ/์š•์„ค/ํ—ˆ์œ„ํ›„๊ธฐ/๊ด‘๊ณ )
988
+
989
+ ### ๐Ÿค– ๊ธฐ์ˆ  ์Šคํƒ
990
+ - **๋ชจ๋ธ**: MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli-2mil7
991
+ - **๋ฐฉ์‹**: Zero-Shot Classification (NLI)
992
+ - **์ง€์› ์–ธ์–ด**: ํ•œ๊ตญ์–ด ํฌํ•จ 100+ ์–ธ์–ด
993
+
994
+ ### ๐Ÿ’ก ํ™œ์šฉ ์‚ฌ๋ก€
995
+ - ๋Œ€๋Ÿ‰ ๋ฆฌ๋ทฐ์˜ ๊ฐ์ • ํŠธ๋ Œ๋“œ ๋ถ„์„
996
+ - ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋ถˆ๋งŒ ์‚ฌํ•ญ ์ž๋™ ์ง‘๊ณ„
997
+ - ๋ถ€์ ์ ˆํ•œ ๋ฆฌ๋ทฐ ์ž๋™ ํ•„ํ„ฐ๋ง (์š•์„ค, ๊ด‘๊ณ , ํ—ˆ์œ„ํ›„๊ธฐ)
998
+ - ์ œํ’ˆ ๊ฐœ์„  ๋ฐฉํ–ฅ ๋„์ถœ์„ ์œ„ํ•œ ์ธ์‚ฌ์ดํŠธ ์ถ”์ถœ
999
+ """)
 
 
1000
 
1001
  return demo
1002
 
test_comprehensive.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ์ข…ํ•ฉ ๋ถ„์„ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ
4
+ """
5
+
6
+ from app import ReviewAnalyzer
7
+
8
+ def test_comprehensive_analysis():
9
+ """์ข…ํ•ฉ ๋ถ„์„ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ"""
10
+
11
+ print("๋ถ„์„๊ธฐ ์ดˆ๊ธฐํ™” ์ค‘...")
12
+ analyzer = ReviewAnalyzer()
13
+
14
+ # ํ…Œ์ŠคํŠธ ๋ฆฌ๋ทฐ
15
+ test_review = "ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”~~๊ฐ์ˆ˜ํ• ๋งŒํ•œ๋ฐ ์€๊ทผ ์งœ์ฆ๋‚ ์ˆ˜๋„? ๊ทธ๋ƒฅ ์ž…์œผ๋ฉด ๊ณ ์–‘์ด๋งˆ๋ƒฅ ํ„ธ์„ ๋ฟœ๋‚ด์š” ใ…Žใ…Ž ๊ทธ๋ž˜๋„ ๋””์ž์ธ์€ ์ •๋ง ์˜ˆ์˜๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๊ดœ์ฐฎ์€ ๊ฒƒ ๊ฐ™์•„์š”. ๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ฒŒ ์™”๊ณ ์š”."
16
+
17
+ print("\n" + "="*80)
18
+ print("ํ…Œ์ŠคํŠธ ๋ฆฌ๋ทฐ:")
19
+ print(test_review)
20
+ print("="*80)
21
+
22
+ # ๋ถ„์„ ์‹คํ–‰
23
+ result = analyzer.analyze_review(test_review, include_comprehensive=True)
24
+
25
+ # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
26
+ print("\n๐Ÿ“Š 3๋‹จ๊ณ„ ๋ถ„์„ ๊ฒฐ๊ณผ:")
27
+ print(f"1๏ธโƒฃ ๊ฐ์ •: {result['sentiment']['sentiment']} ({result['sentiment']['confidence']}%)")
28
+
29
+ if result['categories']['main_categories']:
30
+ categories_str = ', '.join([f"{c['category']} ({c['confidence']}%)"
31
+ for c in result['categories']['main_categories']])
32
+ print(f"2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ: {categories_str}")
33
+
34
+ print(f"3๏ธโƒฃ ํ†ค: {result['tone']['tone']} ({result['tone']['confidence']}%)")
35
+
36
+ # ์ข…ํ•ฉ ๋ถ„์„ ์ถœ๋ ฅ
37
+ print("\n" + "="*80)
38
+ print("โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„")
39
+ print("="*80)
40
+
41
+ comprehensive = result['comprehensive']
42
+
43
+ print("\nํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€:")
44
+ print("-" * 80)
45
+ print(f"{'ํ•ญ๋ชฉ':<15} {'ํ‰๊ฐ€':<20} {'๊ทผ๊ฑฐ'}")
46
+ print("-" * 80)
47
+
48
+ for item in comprehensive['item_ratings']:
49
+ stars = "โญ๏ธ" * item['rating']
50
+ print(f"{item['category']:<15} {stars:<20} {item['evidence']}")
51
+
52
+ # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ
53
+ repurchase_stars = "โญ๏ธ" * comprehensive['repurchase']['rating']
54
+ print(f"{'์žฌ๊ตฌ๋งค ์˜ํ–ฅ':<15} {repurchase_stars:<20} {comprehensive['repurchase']['evidence']}")
55
+
56
+ # ์ „์ฒด ํ†ค
57
+ tone_ratio = comprehensive['tone_ratio']
58
+ tone_desc = f"๊ธ์ • {tone_ratio['positive']} : ๋ถ€์ • {tone_ratio['negative']}"
59
+
60
+ if tone_ratio['positive'] > tone_ratio['negative'] + 20:
61
+ tone_comment = "๊ธ์ •์ด ์šฐ์„ธํ•จ"
62
+ elif tone_ratio['negative'] > tone_ratio['positive'] + 20:
63
+ tone_comment = "๋ถ€์ •์ด ์šฐ์„ธํ•จ"
64
+ else:
65
+ tone_comment = "๊ธ์ •๊ณผ ๋ถ€์ •์ด ํ˜ผ์žฌ๋จ"
66
+
67
+ print(f"{'์ „์ฒด ํ†ค':<15} {tone_desc:<20} {tone_comment}")
68
+
69
+ # ์š”์•ฝ ๋ฌธ์žฅ
70
+ print("\n" + "="*80)
71
+ print("๐Ÿ’ก ์š”์•ฝ ๋ฌธ์žฅ")
72
+ print("="*80)
73
+ print(f"\"{comprehensive['summary']}\"")
74
+ print("\n" + "="*80)
75
+
76
+ if __name__ == "__main__":
77
+ test_comprehensive_analysis()
test_long_reviews.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ๊ธด ๋ฌธ์žฅ ๋ถ„์„ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ
4
+ """
5
+
6
+ from app import ReviewAnalyzer
7
+ import json
8
+
9
+ def test_long_reviews():
10
+ """๊ธด ๋ฌธ์žฅ๊ณผ ๋ณต์žกํ•œ ๋ฆฌ๋ทฐ๋ฅผ ํ…Œ์ŠคํŠธ"""
11
+
12
+ # ๋ถ„์„๊ธฐ ์ดˆ๊ธฐํ™”
13
+ print("๋ถ„์„๊ธฐ ์ดˆ๊ธฐํ™” ์ค‘...")
14
+ analyzer = ReviewAnalyzer()
15
+
16
+ # ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค: ๊ธด ๋ฌธ์žฅ๊ณผ ๋ณต์žกํ•œ ๋‚ด์šฉ
17
+ test_reviews = [
18
+ {
19
+ "name": "์งง์€ ๊ธ์ • ๋ฆฌ๋ทฐ",
20
+ "text": "๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ์ข‹์•„์š”!"
21
+ },
22
+ {
23
+ "name": "๊ธด ํ˜ผํ•ฉ ๊ฐ์ • ๋ฆฌ๋ทฐ",
24
+ "text": "ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”~~๊ฐ์ˆ˜ํ• ๋งŒํ•œ๋ฐ ์€๊ทผ ์งœ์ฆ๋‚ ์ˆ˜๋„? ๊ทธ๋ƒฅ ์ž…์œผ๋ฉด ๊ณ ์–‘์ด๋งˆ๋ƒฅ ํ„ธ์„ ๋ฟœ๋‚ด์š” ใ…Žใ…Ž ๊ทธ๋ž˜๋„ ๋””์ž์ธ์€ ์ •๋ง ์˜ˆ์˜๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๊ดœ์ฐฎ์€ ๊ฒƒ ๊ฐ™์•„์š”. ๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ฒŒ ์™”๊ณ ์š”."
25
+ },
26
+ {
27
+ "name": "๋ณต์žกํ•œ ๋ถˆ๋งŒ ๋ฆฌ๋ทฐ",
28
+ "text": "์‚ฌ์ง„์ด๋ž‘ ์™„์ „ ๋‹ค๋ฅด๋„ค์š”. ํ’ˆ์งˆ๋„ ๋ณ„๋กœ๊ณ  ์‚ฌ์ด์ฆˆ๋„ ์•ˆ ๋งž์•„์š”. ํ™˜๋ถˆ ์‹ ์ฒญํ•˜๋ ค๊ณ  ๊ณ ๊ฐ์„ผํ„ฐ์— ์ „ํ™”ํ–ˆ๋Š”๋ฐ ์—ฐ๊ฒฐ๋„ ์•ˆ๋˜๊ณ  ์ •๋ง ์ตœ์•…์ž…๋‹ˆ๋‹ค. ๋ฐฐ์†ก์€ ๋นจ๋ž๋Š”๋ฐ ๋ฐ›์•„๋ณด๋‹ˆ ์‹ค๋ง์ด์—์š”. ๊ฐ€๊ฒฉ๋„ ๋น„์‹ผ๋ฐ ์ด ์ •๋„ ํ’ˆ์งˆ์ด๋ฉด ๋‹ค์‹œ๋Š” ์•ˆ ์‚ด ๊ฒƒ ๊ฐ™์•„์š”."
29
+ },
30
+ {
31
+ "name": "์—ฌ๋Ÿฌ ์นดํ…Œ๊ณ ๋ฆฌ ์–ธ๊ธ‰ ๋ฆฌ๋ทฐ",
32
+ "text": "๋ฐฐ์†ก์€ 3์ผ ๊ฑธ๋ ธ์–ด์š”. ํฌ์žฅ์€ ๊น”๋”ํ–ˆ๊ตฌ์š”. ์ œํ’ˆ ์—ด์–ด๋ณด๋‹ˆ๊นŒ ์ƒ๊ฐ๋ณด๋‹ค ์‚ฌ์ด์ฆˆ๊ฐ€ ์ž‘๋”๋ผ๊ตฌ์š”. ํ’ˆ์งˆ์€ ๊ทธ๋ƒฅ ๋ฌด๋‚œํ•œ ์ˆ˜์ค€์ด๊ณ  ๋””์ž์ธ์€ ์‚ฌ์ง„์ด๋ž‘ ๋น„์Šทํ•ด์š”. ๊ฐ€๊ฒฉ ์ƒ๊ฐํ•˜๋ฉด ๊ฐ€์„ฑ๋น„๋Š” ์ข‹์€ ํŽธ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค."
33
+ },
34
+ {
35
+ "name": "๊ด‘๊ณ ์„ฑ ๋ฆฌ๋ทฐ",
36
+ "text": "ํ…”๋ ˆ๊ทธ๋žจ @seller123 ์œผ๋กœ ์—ฐ๋ฝ์ฃผ์‹œ๋ฉด ๋ฐ˜๊ฐ’์— ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋„๋งค๊ฐ€๋กœ ํŒ๋งค์ค‘์ด๊ณ  ํ’ˆ์งˆ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์นดํ†ก ID๋Š” seller456 ์ž…๋‹ˆ๋‹ค."
37
+ }
38
+ ]
39
+
40
+ print("\n" + "="*80)
41
+ print("๊ธด ๋ฌธ์žฅ ๋ถ„์„ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ")
42
+ print("="*80)
43
+
44
+ results = []
45
+
46
+ for test_case in test_reviews:
47
+ print(f"\n{'='*80}")
48
+ print(f"[ํ…Œ์ŠคํŠธ: {test_case['name']}]")
49
+ print(f"๋ฆฌ๋ทฐ ๊ธธ์ด: {len(test_case['text'])}์ž")
50
+ print(f"๋‚ด์šฉ: {test_case['text']}")
51
+ print(f"{'='*80}")
52
+
53
+ # ๋ถ„์„ ์‹คํ–‰
54
+ result = analyzer.analyze_review(test_case['text'])
55
+
56
+ # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
57
+ print(f"\n๐Ÿ“Š ๋ถ„์„ ๊ฒฐ๊ณผ:")
58
+ print(f" 1๏ธโƒฃ ๊ฐ์ •: {result['sentiment']['sentiment']} ({result['sentiment']['confidence']}%)")
59
+
60
+ # ๊ฐ์ • ์ƒ์„ธ ์ ์ˆ˜
61
+ print(f" โ””โ”€ ์ƒ์„ธ ์ ์ˆ˜: {result['sentiment']['scores']}")
62
+ if 'method' in result['sentiment']:
63
+ print(f" โ””โ”€ ๋ถ„์„ ๋ฐฉ๋ฒ•: {result['sentiment']['method']}")
64
+
65
+ # ์นดํ…Œ๊ณ ๋ฆฌ
66
+ if result['categories']['main_categories']:
67
+ categories_str = ', '.join([f"{c['category']} ({c['confidence']}%)"
68
+ for c in result['categories']['main_categories']])
69
+ print(f" 2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ: {categories_str}")
70
+ else:
71
+ print(f" 2๏ธโƒฃ ์นดํ…Œ๊ณ ๋ฆฌ: ์—†์Œ")
72
+
73
+ if 'method' in result['categories']:
74
+ print(f" โ””โ”€ ๋ถ„์„ ๋ฐฉ๋ฒ•: {result['categories']['method']}")
75
+
76
+ # ํ†ค
77
+ print(f" 3๏ธโƒฃ ํ†ค: {result['tone']['tone']} ({result['tone']['confidence']}%)")
78
+ print(f" โ””โ”€ ์ƒ์„ธ ์ ์ˆ˜: {result['tone']['scores']}")
79
+
80
+ results.append({
81
+ "test_name": test_case['name'],
82
+ "review_length": len(test_case['text']),
83
+ "result": result
84
+ })
85
+
86
+ # ๊ฒฐ๊ณผ ์ €์žฅ
87
+ with open('test_results.json', 'w', encoding='utf-8') as f:
88
+ json.dump(results, f, ensure_ascii=False, indent=2)
89
+
90
+ print(f"\n{'='*80}")
91
+ print("ํ…Œ์ŠคํŠธ ์™„๋ฃŒ! ๊ฒฐ๊ณผ๊ฐ€ test_results.json์— ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
92
+ print(f"{'='*80}")
93
+
94
+ if __name__ == "__main__":
95
+ test_long_reviews()
test_results.json ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "test_name": "์งง์€ ๊ธ์ • ๋ฆฌ๋ทฐ",
4
+ "review_length": 16,
5
+ "result": {
6
+ "review": "๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ์ข‹์•„์š”!",
7
+ "sentiment": {
8
+ "sentiment": "๊ธ์ •",
9
+ "confidence": 88.32,
10
+ "scores": {
11
+ "๊ธ์ •": 88.32,
12
+ "๋ถ€์ •": 7.98,
13
+ "์ค‘๋ฆฝ": 3.7
14
+ },
15
+ "method": "single"
16
+ },
17
+ "categories": {
18
+ "main_categories": [
19
+ {
20
+ "category": "๊ธฐ๋Šฅ/์„ฑ๋Šฅ",
21
+ "confidence": 97.75
22
+ },
23
+ {
24
+ "category": "๊ตํ™˜/ํ™˜๋ถˆ",
25
+ "confidence": 93.9
26
+ },
27
+ {
28
+ "category": "ํ’ˆ์งˆ",
29
+ "confidence": 79.36
30
+ }
31
+ ],
32
+ "all_scores": {
33
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": 97.75,
34
+ "๊ตํ™˜/ํ™˜๋ถˆ": 93.9,
35
+ "ํ’ˆ์งˆ": 79.36,
36
+ "๋””์ž์ธ": 71.71,
37
+ "๊ฐ€๊ฒฉ": 65.13,
38
+ "๋ฐฐ์†ก": 62.87,
39
+ "์‚ฌ์ด์ฆˆ": 57.47,
40
+ "์„œ๋น„์Šค": 8.03
41
+ },
42
+ "method": "single"
43
+ },
44
+ "tone": {
45
+ "tone": "์š•์„ค",
46
+ "confidence": 47.82,
47
+ "scores": {
48
+ "์š•์„ค": 47.82,
49
+ "๋‹จ์ˆœ ๋ถˆ๋งŒ": 24.19,
50
+ "๊ด‘๊ณ ": 17.71,
51
+ "ํ—ˆ์œ„ํ›„๊ธฐ": 5.41,
52
+ "์ •์ƒ": 4.87
53
+ }
54
+ },
55
+ "timestamp": "2025-11-07T11:12:49.964040"
56
+ }
57
+ },
58
+ {
59
+ "test_name": "๊ธด ํ˜ผํ•ฉ ๊ฐ์ • ๋ฆฌ๋ทฐ",
60
+ "review_length": 121,
61
+ "result": {
62
+ "review": "ํ•๋„ ๋„˜์ด์˜๊ณ  ์‚ฌ์ด์ฆˆ๋„ ๋”ฑ๋งž๊ณ  ๋‹ค์ข‹์€๋ฐ ํ„ธ๋น ์ง์ด ์žฅ๋‚œ์ด ์•„๋‹ˆ์˜ˆ์š”~~๊ฐ์ˆ˜ํ• ๋งŒํ•œ๋ฐ ์€๊ทผ ์งœ์ฆ๋‚ ์ˆ˜๋„? ๊ทธ๋ƒฅ ์ž…์œผ๋ฉด ๊ณ ์–‘์ด๋งˆ๋ƒฅ ํ„ธ์„ ๋ฟœ๋‚ด์š” ใ…Žใ…Ž ๊ทธ๋ž˜๋„ ๋””์ž์ธ์€ ์ •๋ง ์˜ˆ์˜๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๊ดœ์ฐฎ์€ ๊ฒƒ ๊ฐ™์•„์š”. ๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ฒŒ ์™”๊ณ ์š”.",
63
+ "sentiment": {
64
+ "sentiment": "๊ธ์ •",
65
+ "confidence": 58.15,
66
+ "scores": {
67
+ "๊ธ์ •": 58.15,
68
+ "์ค‘๋ฆฝ": 14.88,
69
+ "๋ถ€์ •": 26.97
70
+ },
71
+ "method": "sentence_split"
72
+ },
73
+ "categories": {
74
+ "main_categories": [
75
+ {
76
+ "category": "๊ธฐ๋Šฅ/์„ฑ๋Šฅ",
77
+ "confidence": 98.28
78
+ },
79
+ {
80
+ "category": "์‚ฌ์ด์ฆˆ",
81
+ "confidence": 97.22
82
+ },
83
+ {
84
+ "category": "๊ฐ€๊ฒฉ",
85
+ "confidence": 95.54
86
+ }
87
+ ],
88
+ "all_scores": {
89
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": 98.28,
90
+ "์‚ฌ์ด์ฆˆ": 97.22,
91
+ "๊ฐ€๊ฒฉ": 95.54,
92
+ "๊ตํ™˜/ํ™˜๋ถˆ": 94.37,
93
+ "๋ฐฐ์†ก": 92.24,
94
+ "๋””์ž์ธ": 91.75,
95
+ "ํ’ˆ์งˆ": 82.43,
96
+ "์„œ๋น„์Šค": 66.15
97
+ },
98
+ "method": "sentence_split"
99
+ },
100
+ "tone": {
101
+ "tone": "๋‹จ์ˆœ ๋ถˆ๋งŒ",
102
+ "confidence": 33.84,
103
+ "scores": {
104
+ "๋‹จ์ˆœ ๋ถˆ๋งŒ": 33.84,
105
+ "๊ด‘๊ณ ": 23.01,
106
+ "์š•์„ค": 17.0,
107
+ "์ •์ƒ": 16.28,
108
+ "ํ—ˆ์œ„ํ›„๊ธฐ": 9.87
109
+ }
110
+ },
111
+ "timestamp": "2025-11-07T11:12:52.775037"
112
+ }
113
+ },
114
+ {
115
+ "test_name": "๋ณต์žกํ•œ ๋ถˆ๋งŒ ๋ฆฌ๋ทฐ",
116
+ "review_length": 126,
117
+ "result": {
118
+ "review": "์‚ฌ์ง„์ด๋ž‘ ์™„์ „ ๋‹ค๋ฅด๋„ค์š”. ํ’ˆ์งˆ๋„ ๋ณ„๋กœ๊ณ  ์‚ฌ์ด์ฆˆ๋„ ์•ˆ ๋งž์•„์š”. ํ™˜๋ถˆ ์‹ ์ฒญํ•˜๋ ค๊ณ  ๊ณ ๊ฐ์„ผํ„ฐ์— ์ „ํ™”ํ–ˆ๋Š”๋ฐ ์—ฐ๊ฒฐ๋„ ์•ˆ๋˜๊ณ  ์ •๋ง ์ตœ์•…์ž…๋‹ˆ๋‹ค. ๋ฐฐ์†ก์€ ๋นจ๋ž๋Š”๋ฐ ๋ฐ›์•„๋ณด๋‹ˆ ์‹ค๋ง์ด์—์š”. ๊ฐ€๊ฒฉ๋„ ๋น„์‹ผ๋ฐ ์ด ์ •๋„ ํ’ˆ์งˆ์ด๋ฉด ๋‹ค์‹œ๋Š” ์•ˆ ์‚ด ๊ฒƒ ๊ฐ™์•„์š”.",
119
+ "sentiment": {
120
+ "sentiment": "๋ถ€์ •",
121
+ "confidence": 47.58,
122
+ "scores": {
123
+ "๊ธ์ •": 44.98,
124
+ "์ค‘๋ฆฝ": 7.45,
125
+ "๋ถ€์ •": 47.58
126
+ },
127
+ "method": "sentence_split"
128
+ },
129
+ "categories": {
130
+ "main_categories": [
131
+ {
132
+ "category": "๊ธฐ๋Šฅ/์„ฑ๋Šฅ",
133
+ "confidence": 97.81
134
+ },
135
+ {
136
+ "category": "๊ตํ™˜/ํ™˜๋ถˆ",
137
+ "confidence": 95.48
138
+ },
139
+ {
140
+ "category": "๊ฐ€๊ฒฉ",
141
+ "confidence": 92.2
142
+ }
143
+ ],
144
+ "all_scores": {
145
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": 97.81,
146
+ "๊ตํ™˜/ํ™˜๋ถˆ": 95.48,
147
+ "๊ฐ€๊ฒฉ": 92.2,
148
+ "ํ’ˆ์งˆ": 91.25,
149
+ "๋””์ž์ธ": 89.46,
150
+ "๋ฐฐ์†ก": 87.42,
151
+ "์‚ฌ์ด์ฆˆ": 81.98,
152
+ "์„œ๋น„์Šค": 79.33
153
+ },
154
+ "method": "sentence_split"
155
+ },
156
+ "tone": {
157
+ "tone": "๋‹จ์ˆœ ๋ถˆ๋งŒ",
158
+ "confidence": 38.45,
159
+ "scores": {
160
+ "๋‹จ์ˆœ ๋ถˆ๋งŒ": 38.45,
161
+ "์š•์„ค": 24.59,
162
+ "๊ด‘๊ณ ": 24.35,
163
+ "ํ—ˆ์œ„ํ›„๊ธฐ": 12.27,
164
+ "์ •์ƒ": 0.35
165
+ }
166
+ },
167
+ "timestamp": "2025-11-07T11:12:54.949646"
168
+ }
169
+ },
170
+ {
171
+ "test_name": "์—ฌ๋Ÿฌ ์นดํ…Œ๊ณ ๋ฆฌ ์–ธ๊ธ‰ ๋ฆฌ๋ทฐ",
172
+ "review_length": 108,
173
+ "result": {
174
+ "review": "๋ฐฐ์†ก์€ 3์ผ ๊ฑธ๋ ธ์–ด์š”. ํฌ์žฅ์€ ๊น”๋”ํ–ˆ๊ตฌ์š”. ์ œํ’ˆ ์—ด์–ด๋ณด๋‹ˆ๊นŒ ์ƒ๊ฐ๋ณด๋‹ค ์‚ฌ์ด์ฆˆ๊ฐ€ ์ž‘๋”๋ผ๊ตฌ์š”. ํ’ˆ์งˆ์€ ๊ทธ๋ƒฅ ๋ฌด๋‚œํ•œ ์ˆ˜์ค€์ด๊ณ  ๋””์ž์ธ์€ ์‚ฌ์ง„์ด๋ž‘ ๋น„์Šทํ•ด์š”. ๊ฐ€๊ฒฉ ์ƒ๊ฐํ•˜๋ฉด ๊ฐ€์„ฑ๋น„๋Š” ์ข‹์€ ํŽธ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.",
175
+ "sentiment": {
176
+ "sentiment": "๊ธ์ •",
177
+ "confidence": 49.51,
178
+ "scores": {
179
+ "๊ธ์ •": 49.51,
180
+ "์ค‘๋ฆฝ": 23.29,
181
+ "๋ถ€์ •": 27.2
182
+ },
183
+ "method": "sentence_split"
184
+ },
185
+ "categories": {
186
+ "main_categories": [
187
+ {
188
+ "category": "๊ธฐ๋Šฅ/์„ฑ๋Šฅ",
189
+ "confidence": 97.49
190
+ },
191
+ {
192
+ "category": "๊ตํ™˜/ํ™˜๋ถˆ",
193
+ "confidence": 95.83
194
+ },
195
+ {
196
+ "category": "๋””์ž์ธ",
197
+ "confidence": 92.0
198
+ }
199
+ ],
200
+ "all_scores": {
201
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": 97.49,
202
+ "๊ตํ™˜/ํ™˜๋ถˆ": 95.83,
203
+ "๋””์ž์ธ": 92.0,
204
+ "๊ฐ€๊ฒฉ": 91.82,
205
+ "ํ’ˆ์งˆ": 81.67,
206
+ "์‚ฌ์ด์ฆˆ": 78.1,
207
+ "๋ฐฐ์†ก": 64.56,
208
+ "์„œ๋น„์Šค": 61.61
209
+ },
210
+ "method": "sentence_split"
211
+ },
212
+ "tone": {
213
+ "tone": "์ •์ƒ",
214
+ "confidence": 82.93,
215
+ "scores": {
216
+ "์ •์ƒ": 82.93,
217
+ "๋‹จ์ˆœ ๋ถˆ๋งŒ": 5.99,
218
+ "ํ—ˆ์œ„ํ›„๊ธฐ": 5.13,
219
+ "๊ด‘๊ณ ": 3.89,
220
+ "์š•์„ค": 2.06
221
+ }
222
+ },
223
+ "timestamp": "2025-11-07T11:12:56.919984"
224
+ }
225
+ },
226
+ {
227
+ "test_name": "๊ด‘๊ณ ์„ฑ ๋ฆฌ๋ทฐ",
228
+ "review_length": 77,
229
+ "result": {
230
+ "review": "ํ…”๋ ˆ๊ทธ๋žจ @seller123 ์œผ๋กœ ์—ฐ๋ฝ์ฃผ์‹œ๋ฉด ๋ฐ˜๊ฐ’์— ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ๋„๋งค๊ฐ€๋กœ ํŒ๋งค์ค‘์ด๊ณ  ํ’ˆ์งˆ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์นดํ†ก ID๋Š” seller456 ์ž…๋‹ˆ๋‹ค.",
231
+ "sentiment": {
232
+ "sentiment": "์ค‘๋ฆฝ",
233
+ "confidence": 44.01,
234
+ "scores": {
235
+ "์ค‘๋ฆฝ": 44.01,
236
+ "๊ธ์ •": 29.79,
237
+ "๋ถ€์ •": 26.2
238
+ },
239
+ "method": "single"
240
+ },
241
+ "categories": {
242
+ "main_categories": [
243
+ {
244
+ "category": "๊ฐ€๊ฒฉ",
245
+ "confidence": 13.69
246
+ }
247
+ ],
248
+ "all_scores": {
249
+ "๊ฐ€๊ฒฉ": 13.69,
250
+ "๊ตํ™˜/ํ™˜๋ถˆ": 3.63,
251
+ "ํ’ˆ์งˆ": 2.39,
252
+ "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": 0.67,
253
+ "๋ฐฐ์†ก": 0.64,
254
+ "์‚ฌ์ด์ฆˆ": 0.47,
255
+ "๋””์ž์ธ": 0.42,
256
+ "์„œ๋น„์Šค": 0.41
257
+ },
258
+ "method": "single"
259
+ },
260
+ "tone": {
261
+ "tone": "๊ด‘๊ณ ",
262
+ "confidence": 52.58,
263
+ "scores": {
264
+ "๊ด‘๊ณ ": 52.58,
265
+ "๋‹จ์ˆœ ๋ถˆ๋งŒ": 21.05,
266
+ "ํ—ˆ์œ„ํ›„๊ธฐ": 11.86,
267
+ "์ •์ƒ": 9.21,
268
+ "์š•์„ค": 5.3
269
+ }
270
+ },
271
+ "timestamp": "2025-11-07T11:12:57.730285"
272
+ }
273
+ }
274
+ ]