YeongMin commited on
Commit
1f53218
ยท
1 Parent(s): 8861c85
Files changed (2) hide show
  1. .claude/settings.local.json +2 -1
  2. app.py +168 -100
.claude/settings.local.json CHANGED
@@ -4,7 +4,8 @@
4
  "Bash(del:*)",
5
  "Bash(python3:*)",
6
  "Bash(lsof:*)",
7
- "Bash(xargs kill -9)"
 
8
  ],
9
  "deny": [],
10
  "ask": []
 
4
  "Bash(del:*)",
5
  "Bash(python3:*)",
6
  "Bash(lsof:*)",
7
+ "Bash(xargs kill -9)",
8
+ "Bash(python app.py:*)"
9
  ],
10
  "deny": [],
11
  "ask": []
app.py CHANGED
@@ -359,6 +359,7 @@ class ReviewAnalyzer:
359
  def extract_evidence_from_text(self, text: str, category: str) -> str:
360
  """
361
  ํ…์ŠคํŠธ์—์„œ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ๊ทผ๊ฑฐ ๋ฌธ์žฅ ์ถ”์ถœ
 
362
 
363
  Args:
364
  text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
@@ -371,34 +372,41 @@ class ReviewAnalyzer:
371
 
372
  # ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ‚ค์›Œ๋“œ ๋งคํ•‘
373
  keywords = {
374
- "๋ฐฐ์†ก": ["๋ฐฐ์†ก", "ํƒ๋ฐฐ", "๋„์ฐฉ", "ํฌ์žฅ", "๋น ๋ฅด"],
375
- "ํ’ˆ์งˆ/๋””์ž์ธ": ["ํ’ˆ์งˆ", "์žฌ์งˆ", "ํŠผํŠผ", "๋‚ด๊ตฌ", "์™„์„ฑ๋„", "ํ„ธ๋น ์ง", "๋น ์ง", "๋””์ž์ธ", "์ƒ‰์ƒ", "์˜ˆ์˜", "์Šคํƒ€์ผ", "์™ธ๊ด€", "์ด์˜"],
376
- "์‚ฌ์ด์ฆˆ": ["์‚ฌ์ด์ฆˆ", "ํฌ๊ธฐ", "ํ•", "์น˜์ˆ˜", "๋งž"],
377
  "๊ตํ™˜/ํ™˜๋ถˆ": ["๊ตํ™˜", "ํ™˜๋ถˆ", "๋ฐ˜ํ’ˆ"],
378
  "์„œ๋น„์Šค": ["์„œ๋น„์Šค", "๊ณ ๊ฐ์„ผํ„ฐ", "์‘๋Œ€", "์นœ์ ˆ"],
379
  "๊ฐ€๊ฒฉ": ["๊ฐ€๊ฒฉ", "๊ฐ€์„ฑ๋น„", "๋น„์‹ธ", "์ €๋ ด", "ํ• ์ธ", "๋ˆ"],
380
  "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": ["๊ธฐ๋Šฅ", "์„ฑ๋Šฅ", "์ž‘๋™", "ํšจ๊ณผ", "์‚ฌ์šฉ"]
381
  }
382
 
383
- # ๋ฌธ์žฅ ๋ถ„๋ฆฌ
384
- sentences = re.split(r'[.!?~]+\s*', text)
 
 
385
 
386
- # ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์žฅ ์ฐพ๊ธฐ
387
- for sentence in sentences:
388
- sentence = sentence.strip()
389
- if category in keywords:
390
- for keyword in keywords[category]:
391
- if keyword in sentence and len(sentence) > 5:
392
- # ๋„ˆ๋ฌด ๊ธด ๋ฌธ์žฅ์€ ์ž˜๋ผ๋‚ด๊ธฐ
393
- if len(sentence) > 40:
394
- sentence = sentence[:40] + "..."
395
- return f'"{sentence}"'
 
 
 
 
396
 
397
  return "-"
398
 
399
  def analyze_sentiment_for_category(self, text: str, category: str) -> str:
400
  """
401
  ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์— ๋Œ€ํ•œ ๊ฐ์ • ๋ถ„์„
 
402
 
403
  Args:
404
  text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
@@ -409,11 +417,11 @@ class ReviewAnalyzer:
409
  """
410
  import re
411
 
412
- # ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์žฅ ์ฐพ๊ธฐ
413
  keywords = {
414
- "๋ฐฐ์†ก": ["๋ฐฐ์†ก", "ํƒ๋ฐฐ", "๋„์ฐฉ", "ํฌ์žฅ", "๋น ๋ฅด"],
415
- "ํ’ˆ์งˆ/๋””์ž์ธ": ["ํ’ˆ์งˆ", "์žฌ์งˆ", "ํŠผํŠผ", "๋‚ด๊ตฌ", "์™„์„ฑ๋„", "ํ„ธ๋น ์ง", "๋น ์ง", "๋””์ž์ธ", "์ƒ‰์ƒ", "์˜ˆ์˜", "์Šคํƒ€์ผ", "์™ธ๊ด€", "์ด์˜"],
416
- "์‚ฌ์ด์ฆˆ": ["์‚ฌ์ด์ฆˆ", "ํฌ๊ธฐ", "ํ•", "์น˜์ˆ˜", "๋งž"],
417
  "๊ตํ™˜/ํ™˜๋ถˆ": ["๊ตํ™˜", "ํ™˜๋ถˆ", "๋ฐ˜ํ’ˆ"],
418
  "์„œ๋น„์Šค": ["์„œ๋น„์Šค", "๊ณ ๊ฐ์„ผํ„ฐ", "์‘๋Œ€", "์นœ์ ˆ"],
419
  "๊ฐ€๊ฒฉ": ["๊ฐ€๊ฒฉ", "๊ฐ€์„ฑ๋น„", "๋น„์‹ธ", "์ €๋ ด", "ํ• ์ธ", "๋ˆ"],
@@ -421,37 +429,108 @@ class ReviewAnalyzer:
421
  }
422
 
423
  # ๊ธ์ • ํ‚ค์›Œ๋“œ (๋ช…์‹œ์  ๊ธ์ • ํ‘œํ˜„)
424
- positive_keywords = ["์ข‹", "ํ›Œ๋ฅญ", "๋งŒ์กฑ", "์ตœ๊ณ ", "์˜ˆ์˜", "์ด์˜", "๋”ฑ๋งž", "๋น ๋ฅด", "๊ดœ์ฐฎ"]
425
 
426
  # ๋ถ€์ • ํ‚ค์›Œ๋“œ
427
- negative_keywords = ["๋ณ„๋กœ", "์•„์‰ฝ", "์‹ค๋ง", "์ตœ์•…", "์งœ์ฆ", "๋ฌธ์ œ"]
428
 
429
- sentences = re.split(r'[.!?~]+\s*', text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
- # ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ๋ฌธ์žฅ์—์„œ ๊ฐ์ • ํŒ๋‹จ
432
- if category in keywords:
433
- for sentence in sentences:
434
- # ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๋ฌธ์žฅ๋งŒ ๊ฒ€์‚ฌ
435
- has_category_keyword = False
436
- for keyword in keywords[category]:
437
- if keyword in sentence:
438
- has_category_keyword = True
439
- break
440
-
441
- if has_category_keyword:
442
- # ๊ธ์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ
443
- for pos_keyword in positive_keywords:
444
- if pos_keyword in sentence:
445
- return "๊ธ์ •"
446
-
447
- # ๋ถ€์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ
448
- for neg_keyword in negative_keywords:
449
- if neg_keyword in sentence:
450
- return "๋ถ€์ •"
451
 
452
  # ๊ธฐ๋ณธ๊ฐ’์€ ์ค‘๋ฆฝ
453
  return "์ค‘๋ฆฝ"
454
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  def generate_comprehensive_analysis(self, review_text: str, analysis_result: Dict) -> Dict:
456
  """
457
  ์ข…ํ•ฉ ๋ถ„์„ ์ƒ์„ฑ - ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€ ๋ฐ ์š”์•ฝ
@@ -465,14 +544,20 @@ class ReviewAnalyzer:
465
  """
466
  sentiment = analysis_result['sentiment']['sentiment']
467
  sentiment_scores = analysis_result['sentiment']['scores']
468
- categories = analysis_result['categories']['main_categories']
469
  tone = analysis_result['tone']['tone']
470
 
 
 
 
471
  # ํ•ญ๋ชฉ๋ณ„ ๏ฟฝ๏ฟฝ๊ฐ€
472
  item_ratings = []
473
- for cat_info in categories:
474
- category = cat_info['category']
475
- confidence = cat_info['confidence']
 
 
 
 
476
 
477
  # ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฐ์ • ๋ถ„์„
478
  category_sentiment = self.analyze_sentiment_for_category(review_text, category)
@@ -481,66 +566,36 @@ class ReviewAnalyzer:
481
  if category_sentiment == "๋ถ€์ •":
482
  rating = 2
483
  elif category_sentiment == "๊ธ์ •":
484
- rating = self.generate_rating_from_sentiment(category, confidence, sentiment)
485
  else:
486
  rating = 3
487
 
488
- # ๊ทผ๊ฑฐ ์ถ”์ถœ
489
- evidence = self.extract_evidence_from_text(review_text, category)
490
-
491
  item_ratings.append({
492
  "category": category,
493
  "rating": rating,
494
  "evidence": evidence,
495
- "confidence": confidence
496
  })
497
 
498
- # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ ์ถ”์ •
499
- repurchase_score = 3 # ๊ธฐ๋ณธ๊ฐ’
500
- if sentiment == "๊ธ์ •":
501
- repurchase_score = 4
502
- if sentiment_scores['๊ธ์ •'] > 70:
503
- repurchase_score = 5
504
- elif sentiment == "๋ถ€์ •":
505
- repurchase_score = 2
506
- if sentiment_scores['๋ถ€์ •'] > 70:
507
- repurchase_score = 1
508
- else:
509
- repurchase_score = 3
510
-
511
- # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ ๊ทผ๊ฑฐ
512
- repurchase_keywords = ["๋˜", "๋‹ค์‹œ", "์žฌ๊ตฌ๋งค", "์ถ”์ฒœ", "ํ™˜๋ถˆ", "์ตœ์•…"]
513
- repurchase_evidence = "-"
514
- for keyword in repurchase_keywords:
515
- if keyword in review_text:
516
- import re
517
- sentences = re.split(r'[.!?~]+\s*', review_text)
518
- for sentence in sentences:
519
- if keyword in sentence and len(sentence.strip()) > 5:
520
- repurchase_evidence = f'"{sentence.strip()[:40]}"'
521
- break
522
- if repurchase_evidence != "-":
523
- break
524
-
525
  # ์ „์ฒด ํ†ค ๋น„์œจ
526
  positive_ratio = sentiment_scores.get('๊ธ์ •', 0)
527
  negative_ratio = sentiment_scores.get('๋ถ€์ •', 0)
528
  neutral_ratio = sentiment_scores.get('์ค‘๋ฆฝ', 0)
529
 
 
 
 
530
  # ์š”์•ฝ ๋ฌธ์žฅ ์ƒ์„ฑ
531
  summary = self.generate_summary_sentence(review_text, item_ratings, sentiment)
532
 
533
  return {
534
  "item_ratings": item_ratings,
535
- "repurchase": {
536
- "rating": repurchase_score,
537
- "evidence": repurchase_evidence
538
- },
539
  "tone_ratio": {
540
  "positive": round(positive_ratio),
541
  "negative": round(negative_ratio),
542
  "neutral": round(neutral_ratio)
543
  },
 
544
  "summary": summary,
545
  "overall_sentiment": sentiment
546
  }
@@ -758,28 +813,31 @@ class ReviewAnalyzer:
758
  ๋งˆํฌ๋‹ค์šด ํ˜•์‹์˜ ๋ฌธ์ž์—ด
759
  """
760
  output = "## โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„\n\n"
761
- output += "| ํ•ญ๋ชฉ | ํ‰๊ฐ€ | ๊ทผ๊ฑฐ |\n"
762
- output += "|------|------|------|\n"
763
 
764
  # ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€
765
  for item in comprehensive['item_ratings']:
766
  stars = "โญ๏ธ" * item['rating']
767
- output += f"| {item['category']} | {stars} | {item['evidence']} |\n"
768
-
769
- # ์žฌ๊ตฌ๋งค ์˜ํ–ฅ
770
- repurchase_stars = "โญ๏ธ" * comprehensive['repurchase']['rating']
771
- output += f"| ์žฌ๊ตฌ๋งค ์˜ํ–ฅ | {repurchase_stars} | {comprehensive['repurchase']['evidence']} |\n"
772
 
773
  # ์ „์ฒด ํ†ค
774
  tone_ratio = comprehensive['tone_ratio']
775
- output += f"| ์ „์ฒด ํ†ค | ๊ธ์ • {tone_ratio['positive']} : ๋ถ€์ • {tone_ratio['negative']} | "
776
 
 
777
  if tone_ratio['positive'] > tone_ratio['negative'] + 20:
778
- output += "๊ธ์ •์ด ์šฐ์„ธํ•จ |\n"
779
  elif tone_ratio['negative'] > tone_ratio['positive'] + 20:
780
- output += "๋ถ€์ •์ด ์šฐ์„ธํ•จ |\n"
781
  else:
782
- output += "๊ธ์ •๊ณผ ๋ถ€์ •์ด ํ˜ผ์žฌ๋จ |\n"
 
 
 
 
 
783
 
784
  # ์š”์•ฝ ๋ฌธ์žฅ
785
  output += f"\n## ๐Ÿ’ก ์š”์•ฝ ๋ฌธ์žฅ\n\n"
@@ -815,6 +873,7 @@ def create_gradio_app():
815
  ["๋ฐฐ์†ก์ด ์ƒ๊ฐ๋ณด๋‹ค ๋นจ๋ผ์„œ ์ข‹์•˜์–ด์š”. ํ’ˆ์งˆ๋„ ๊ดœ์ฐฎ๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค."],
816
  ["์‚ฌ์ด์ฆˆ๊ฐ€ ๋„ˆ๋ฌด ์ž‘์•„์š”. ๊ตํ™˜ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ์ ˆ์ฐจ๊ฐ€ ๋ณต์žกํ•˜๋„ค์š”."],
817
  ["๋””์ž์ธ์€ ์˜ˆ์œ๋ฐ ํ’ˆ์งˆ์ด ๊ฐ€๊ฒฉ์— ๋น„ํ•ด ๋ณ„๋กœ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ์ €๋ƒฅ์ด์—์š”."],
 
818
  ]
819
 
820
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ - ๋ชจ๋˜ ๋Œ€์‹œ๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ
@@ -935,14 +994,23 @@ def create_gradio_app():
935
  show_label=True
936
  )
937
 
938
- gr.Markdown("---")
 
 
 
939
 
940
- # ์ข…ํ•ฉ ๋ถ„์„ - ์ „์ฒด ๋„ˆ๋น„, ์•„์ฝ”๋””์–ธ ์Šคํƒ€์ผ
941
- with gr.Accordion("โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„ & ์ธ์‚ฌ์ดํŠธ", open=True):
942
- comprehensive_output = gr.Markdown(
943
- value="",
944
- show_label=False
945
- )
 
 
 
 
 
 
946
 
947
  # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
948
  submit_btn.click(
 
359
  def extract_evidence_from_text(self, text: str, category: str) -> str:
360
  """
361
  ํ…์ŠคํŠธ์—์„œ ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ๊ทผ๊ฑฐ ๋ฌธ์žฅ ์ถ”์ถœ
362
+ ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ์กฐ๊ฐ๋งŒ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
363
 
364
  Args:
365
  text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
 
372
 
373
  # ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ‚ค์›Œ๋“œ ๋งคํ•‘
374
  keywords = {
375
+ "๋ฐฐ์†ก": ["๋ฐฐ์†ก", "ํƒ๋ฐฐ", "๋„์ฐฉ", "ํฌ์žฅ"],
376
+ "ํ’ˆ์งˆ/๋””์ž์ธ": ["ํ’ˆ์งˆ", "์žฌ์งˆ", "ํŠผํŠผ", "๋‚ด๊ตฌ", "์™„์„ฑ๋„", "ํ„ธ๋น ์ง", "๋น ์ง", "๋””์ž์ธ", "์ƒ‰์ƒ", "์Šคํƒ€์ผ", "์™ธ๊ด€"],
377
+ "์‚ฌ์ด์ฆˆ": ["์‚ฌ์ด์ฆˆ", "ํฌ๊ธฐ", "ํ•", "์น˜์ˆ˜"],
378
  "๊ตํ™˜/ํ™˜๋ถˆ": ["๊ตํ™˜", "ํ™˜๋ถˆ", "๋ฐ˜ํ’ˆ"],
379
  "์„œ๋น„์Šค": ["์„œ๋น„์Šค", "๊ณ ๊ฐ์„ผํ„ฐ", "์‘๋Œ€", "์นœ์ ˆ"],
380
  "๊ฐ€๊ฒฉ": ["๊ฐ€๊ฒฉ", "๊ฐ€์„ฑ๋น„", "๋น„์‹ธ", "์ €๋ ด", "ํ• ์ธ", "๋ˆ"],
381
  "๊ธฐ๋Šฅ/์„ฑ๋Šฅ": ["๊ธฐ๋Šฅ", "์„ฑ๋Šฅ", "์ž‘๋™", "ํšจ๊ณผ", "์‚ฌ์šฉ"]
382
  }
383
 
384
+ if category not in keywords:
385
+ return "-"
386
+
387
+ category_keywords = keywords[category]
388
 
389
+ # ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ„๊ธฐ (์‰ผํ‘œ, ๊ทธ๋ฆฌ๊ณ , ํ•˜์ง€๋งŒ ๋“ฑ์œผ๋กœ ๋ถ„๋ฆฌ)
390
+ # ์˜ˆ: "๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ณ  ํ’ˆ์งˆ๋„ ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค" -> ["๋ฐฐ์†ก๋„ ๋น ๋ฅด๊ณ ", "ํ’ˆ์งˆ๋„ ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค"]
391
+ chunks = re.split(r'[,]|\s+๊ทธ๋ฆฌ๊ณ \s+|\s+๊ทผ๋ฐ\s+|\s+ํ•˜์ง€๋งŒ\s+|\s+์ธ๋ฐ\s+', text)
392
+
393
+ for chunk in chunks:
394
+ chunk = chunk.strip()
395
+ # ์ด ์กฐ๊ฐ์— ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
396
+ for keyword in category_keywords:
397
+ if keyword in chunk and len(chunk) > 5:
398
+ # chunk๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (์ด๋ฏธ ์กฐ๊ฐ์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ)
399
+ # ๋‹จ, ๋„ˆ๋ฌด ๊ธด ๊ฒฝ์šฐ๋งŒ ์ž˜๋ผ๋‚ด๊ธฐ
400
+ if len(chunk) > 20:
401
+ chunk = chunk[:20]
402
+ return f'"{chunk}"'
403
 
404
  return "-"
405
 
406
  def analyze_sentiment_for_category(self, text: str, category: str) -> str:
407
  """
408
  ํŠน์ • ์นดํ…Œ๊ณ ๋ฆฌ์— ๋Œ€ํ•œ ๊ฐ์ • ๋ถ„์„
409
+ ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ ๊ทผ์ฒ˜์˜ ๊ฐ์ • ํ‘œํ˜„๋งŒ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.
410
 
411
  Args:
412
  text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
 
417
  """
418
  import re
419
 
420
+ # ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ จ ํ‚ค์›Œ๋“œ
421
  keywords = {
422
+ "๋ฐฐ์†ก": ["๋ฐฐ์†ก", "ํƒ๋ฐฐ", "๋„์ฐฉ", "ํฌ์žฅ"],
423
+ "ํ’ˆ์งˆ/๋””์ž์ธ": ["ํ’ˆ์งˆ", "์žฌ์งˆ", "ํŠผํŠผ", "๋‚ด๊ตฌ", "์™„์„ฑ๋„", "ํ„ธ๋น ์ง", "๋น ์ง", "๋””์ž์ธ", "์ƒ‰์ƒ", "์Šคํƒ€์ผ", "์™ธ๊ด€"],
424
+ "์‚ฌ์ด์ฆˆ": ["์‚ฌ์ด์ฆˆ", "ํฌ๊ธฐ", "ํ•", "์น˜์ˆ˜"],
425
  "๊ตํ™˜/ํ™˜๋ถˆ": ["๊ตํ™˜", "ํ™˜๋ถˆ", "๋ฐ˜ํ’ˆ"],
426
  "์„œ๋น„์Šค": ["์„œ๋น„์Šค", "๊ณ ๊ฐ์„ผํ„ฐ", "์‘๋Œ€", "์นœ์ ˆ"],
427
  "๊ฐ€๊ฒฉ": ["๊ฐ€๊ฒฉ", "๊ฐ€์„ฑ๋น„", "๋น„์‹ธ", "์ €๋ ด", "ํ• ์ธ", "๋ˆ"],
 
429
  }
430
 
431
  # ๊ธ์ • ํ‚ค์›Œ๋“œ (๋ช…์‹œ์  ๊ธ์ • ํ‘œํ˜„)
432
+ positive_keywords = ["์ข‹", "ํ›Œ๋ฅญ", "๋งŒ์กฑ", "์ตœ๊ณ ", "์˜ˆ์˜", "์ด์˜", "๋”ฑ๋งž", "๋น ๋ฅด", "๊ดœ์ฐฎ", "์™„๋ฒฝ", "๋ฉ‹์ง€", "๊ฐ์‚ฌ"]
433
 
434
  # ๋ถ€์ • ํ‚ค์›Œ๋“œ
435
+ negative_keywords = ["๋ณ„๋กœ", "์•„์‰ฝ", "์‹ค๋ง", "์ตœ์•…", "์งœ์ฆ", "๋ฌธ์ œ", "๋‚˜์˜", "ํ˜•ํŽธ์—†", "์—‰๋ง", "ํ›„ํšŒ"]
436
 
437
+ if category not in keywords:
438
+ return "์ค‘๋ฆฝ"
439
+
440
+ # ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ํฌํ•จ๋œ ๊ตฌ๊ฐ„ ์ฐพ๊ธฐ
441
+ category_keywords = keywords[category]
442
+
443
+ # ์ „์ฒด ํ…์ŠคํŠธ๋ฅผ ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ„๊ธฐ (์‰ผํ‘œ, ๊ทธ๋ฆฌ๊ณ , ํ•˜์ง€๋งŒ ๋“ฑ์œผ๋กœ ๋ถ„๋ฆฌ)
444
+ # ์˜ˆ: "๋ฐฐ์†ก์€ ๋น ๋ฅธ๋ฐ ํ’ˆ์งˆ์ด ๋ณ„๋กœ์˜ˆ์š”" -> ["๋ฐฐ์†ก์€ ๋น ๋ฅธ๋ฐ", "ํ’ˆ์งˆ์ด ๋ณ„๋กœ์˜ˆ์š”"]
445
+ chunks = re.split(r'[,]|\s+๊ทธ๋ฆฌ๊ณ \s+|\s+๊ทผ๋ฐ\s+|\s+ํ•˜์ง€๋งŒ\s+|\s+์ธ๋ฐ\s+', text)
446
+
447
+ for chunk in chunks:
448
+ # ์ด ์กฐ๊ฐ์— ์นดํ…Œ๊ณ ๋ฆฌ ํ‚ค์›Œ๋“œ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
449
+ has_category = False
450
+ for keyword in category_keywords:
451
+ if keyword in chunk:
452
+ has_category = True
453
+ break
454
 
455
+ if not has_category:
456
+ continue
457
+
458
+ # ์ด ์กฐ๊ฐ ๋‚ด์—์„œ๋งŒ ๊ฐ์ • ํŒ๋‹จ
459
+ chunk_lower = chunk.lower()
460
+
461
+ # ๊ธ์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ
462
+ for pos_keyword in positive_keywords:
463
+ if pos_keyword in chunk_lower:
464
+ return "๊ธ์ •"
465
+
466
+ # ๋ถ€์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ
467
+ for neg_keyword in negative_keywords:
468
+ if neg_keyword in chunk_lower:
469
+ return "๋ถ€์ •"
 
 
 
 
 
470
 
471
  # ๊ธฐ๋ณธ๊ฐ’์€ ์ค‘๋ฆฝ
472
  return "์ค‘๋ฆฝ"
473
 
474
+ def extract_tone_evidence(self, text: str) -> Dict[str, str]:
475
+ """
476
+ ์ „์ฒด ํ†ค์˜ ๊ธ์ •/๋ถ€์ • ๊ทผ๊ฑฐ ์ถ”์ถœ
477
+
478
+ Args:
479
+ text: ๋ฆฌ๋ทฐ ํ…์ŠคํŠธ
480
+
481
+ Returns:
482
+ {"positive": "๊ธ์ • ๊ทผ๊ฑฐ", "negative": "๋ถ€์ • ๊ทผ๊ฑฐ"}
483
+ """
484
+ import re
485
+
486
+ # ๊ธ์ • ํ‚ค์›Œ๋“œ
487
+ positive_keywords = ["์ข‹", "ํ›Œ๋ฅญ", "๋งŒ์กฑ", "์ตœ๊ณ ", "์˜ˆ์˜", "์ด์˜", "๋”ฑ๋งž", "๋น ๋ฅด", "๊ดœ์ฐฎ", "์™„๋ฒฝ", "๋ฉ‹์ง€", "๊ฐ์‚ฌ"]
488
+
489
+ # ๋ถ€์ • ํ‚ค์›Œ๋“œ
490
+ negative_keywords = ["๋ณ„๋กœ", "์•„์‰ฝ", "์‹ค๋ง", "์ตœ์•…", "์งœ์ฆ", "๋ฌธ์ œ", "๋‚˜์˜", "ํ˜•ํŽธ์—†", "์—‰๋ง", "ํ›„ํšŒ", "๋‹ค๋ฅด"]
491
+
492
+ # ํ…์ŠคํŠธ๋ฅผ ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆ„๊ธฐ
493
+ chunks = re.split(r'[,.]|\s+๊ทธ๋ฆฌ๊ณ \s+|\s+๊ทผ๋ฐ\s+|\s+ํ•˜์ง€๋งŒ\s+|\s+์ธ๋ฐ\s+', text)
494
+
495
+ positive_evidence = []
496
+ negative_evidence = []
497
+
498
+ for chunk in chunks:
499
+ chunk = chunk.strip()
500
+ if len(chunk) < 3:
501
+ continue
502
+
503
+ chunk_lower = chunk.lower()
504
+
505
+ # ๊ธ์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ - chunk ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
506
+ for keyword in positive_keywords:
507
+ if keyword in chunk_lower:
508
+ # chunk๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (์ด๋ฏธ ์กฐ๊ฐ์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ)
509
+ evidence = chunk
510
+ if len(evidence) > 20:
511
+ evidence = evidence[:20]
512
+ positive_evidence.append(f'"{evidence}"')
513
+ break
514
+
515
+ # ๋ถ€์ • ํ‚ค์›Œ๋“œ ์ฒดํฌ - chunk ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
516
+ for keyword in negative_keywords:
517
+ if keyword in chunk_lower:
518
+ # chunk๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (์ด๋ฏธ ์กฐ๊ฐ์œผ๋กœ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ)
519
+ evidence = chunk
520
+ if len(evidence) > 20:
521
+ evidence = evidence[:20]
522
+ negative_evidence.append(f'"{evidence}"')
523
+ break
524
+
525
+ # ์ตœ๋Œ€ 2๊ฐœ์”ฉ๋งŒ ํ‘œ์‹œ
526
+ positive_text = ", ".join(positive_evidence[:2]) if positive_evidence else "-"
527
+ negative_text = ", ".join(negative_evidence[:2]) if negative_evidence else "-"
528
+
529
+ return {
530
+ "positive": positive_text,
531
+ "negative": negative_text
532
+ }
533
+
534
  def generate_comprehensive_analysis(self, review_text: str, analysis_result: Dict) -> Dict:
535
  """
536
  ์ข…ํ•ฉ ๋ถ„์„ ์ƒ์„ฑ - ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€ ๋ฐ ์š”์•ฝ
 
544
  """
545
  sentiment = analysis_result['sentiment']['sentiment']
546
  sentiment_scores = analysis_result['sentiment']['scores']
 
547
  tone = analysis_result['tone']['tone']
548
 
549
+ # ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ๊ฒ€์‚ฌ (AI ๊ฒฐ๊ณผ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ)
550
+ all_possible_categories = ["๋ฐฐ์†ก", "ํ’ˆ์งˆ/๋””์ž์ธ", "์‚ฌ์ด์ฆˆ", "๊ตํ™˜/ํ™˜๋ถˆ", "์„œ๋น„์Šค", "๊ฐ€๊ฒฉ", "๊ธฐ๋Šฅ/์„ฑ๋Šฅ"]
551
+
552
  # ํ•ญ๋ชฉ๋ณ„ ๏ฟฝ๏ฟฝ๊ฐ€
553
  item_ratings = []
554
+ for category in all_possible_categories:
555
+ # ๊ทผ๊ฑฐ ์ถ”์ถœ
556
+ evidence = self.extract_evidence_from_text(review_text, category)
557
+
558
+ # ๊ทผ๊ฑฐ๊ฐ€ ์—†์œผ๋ฉด ํ•ด๋‹น ํ•ญ๋ชฉ ์ œ์™ธ
559
+ if evidence == "-":
560
+ continue
561
 
562
  # ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๊ฐ์ • ๋ถ„์„
563
  category_sentiment = self.analyze_sentiment_for_category(review_text, category)
 
566
  if category_sentiment == "๋ถ€์ •":
567
  rating = 2
568
  elif category_sentiment == "๊ธ์ •":
569
+ rating = 5
570
  else:
571
  rating = 3
572
 
 
 
 
573
  item_ratings.append({
574
  "category": category,
575
  "rating": rating,
576
  "evidence": evidence,
577
+ "sentiment": category_sentiment
578
  })
579
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  # ์ „์ฒด ํ†ค ๋น„์œจ
581
  positive_ratio = sentiment_scores.get('๊ธ์ •', 0)
582
  negative_ratio = sentiment_scores.get('๋ถ€์ •', 0)
583
  neutral_ratio = sentiment_scores.get('์ค‘๋ฆฝ', 0)
584
 
585
+ # ์ „์ฒด ํ†ค ๊ทผ๊ฑฐ ์ถ”์ถœ
586
+ tone_evidence = self.extract_tone_evidence(review_text)
587
+
588
  # ์š”์•ฝ ๋ฌธ์žฅ ์ƒ์„ฑ
589
  summary = self.generate_summary_sentence(review_text, item_ratings, sentiment)
590
 
591
  return {
592
  "item_ratings": item_ratings,
 
 
 
 
593
  "tone_ratio": {
594
  "positive": round(positive_ratio),
595
  "negative": round(negative_ratio),
596
  "neutral": round(neutral_ratio)
597
  },
598
+ "tone_evidence": tone_evidence,
599
  "summary": summary,
600
  "overall_sentiment": sentiment
601
  }
 
813
  ๋งˆํฌ๋‹ค์šด ํ˜•์‹์˜ ๋ฌธ์ž์—ด
814
  """
815
  output = "## โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„\n\n"
816
+ output += "| ํ•ญ๋ชฉ | ๊ฐ์ • | ๋งŒ์กฑ๋„ | ๊ทผ๊ฑฐ |\n"
817
+ output += "|------|------|--------|------|\n"
818
 
819
  # ํ•ญ๋ชฉ๋ณ„ ํ‰๊ฐ€
820
  for item in comprehensive['item_ratings']:
821
  stars = "โญ๏ธ" * item['rating']
822
+ sentiment = item.get('sentiment', '์ค‘๋ฆฝ')
823
+ output += f"| {item['category']} | {sentiment} | {stars} | {item['evidence']} |\n"
 
 
 
824
 
825
  # ์ „์ฒด ํ†ค
826
  tone_ratio = comprehensive['tone_ratio']
827
+ tone_evidence = comprehensive.get('tone_evidence', {"positive": "-", "negative": "-"})
828
 
829
+ tone_summary = ""
830
  if tone_ratio['positive'] > tone_ratio['negative'] + 20:
831
+ tone_summary = "๊ธ์ •์ด ์šฐ์„ธํ•จ"
832
  elif tone_ratio['negative'] > tone_ratio['positive'] + 20:
833
+ tone_summary = "๋ถ€์ •์ด ์šฐ์„ธํ•จ"
834
  else:
835
+ tone_summary = "๊ธ์ •๊ณผ ๋ถ€์ •์ด ํ˜ผ์žฌ๋จ"
836
+
837
+ # ์ „์ฒด ํ†ค ๊ทผ๊ฑฐ ํฌ๋งทํŒ…: "๊ธ์ •: xxx / ๋ถ€์ •: xxx"
838
+ tone_evidence_text = f"๊ธ์ •: {tone_evidence['positive']} / ๋ถ€์ •: {tone_evidence['negative']}"
839
+
840
+ output += f"| ์ „์ฒด ํ†ค | {tone_summary} | ๊ธ์ • {tone_ratio['positive']} : ๋ถ€์ • {tone_ratio['negative']} | {tone_evidence_text} |\n"
841
 
842
  # ์š”์•ฝ ๋ฌธ์žฅ
843
  output += f"\n## ๐Ÿ’ก ์š”์•ฝ ๋ฌธ์žฅ\n\n"
 
873
  ["๋ฐฐ์†ก์ด ์ƒ๊ฐ๋ณด๋‹ค ๋นจ๋ผ์„œ ์ข‹์•˜์–ด์š”. ํ’ˆ์งˆ๋„ ๊ดœ์ฐฎ๊ณ  ๊ฐ€๊ฒฉ๋Œ€๋น„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค."],
874
  ["์‚ฌ์ด์ฆˆ๊ฐ€ ๋„ˆ๋ฌด ์ž‘์•„์š”. ๊ตํ™˜ํ•˜๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ ์ ˆ์ฐจ๊ฐ€ ๋ณต์žกํ•˜๋„ค์š”."],
875
  ["๋””์ž์ธ์€ ์˜ˆ์œ๋ฐ ํ’ˆ์งˆ์ด ๊ฐ€๊ฒฉ์— ๋น„ํ•ด ๋ณ„๋กœ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ์ €๋ƒฅ์ด์—์š”."],
876
+ ["์„ธํŠธ ๊ฐ€๊ฒฉ ๊ฐ€์„ฑ๋น„ ์ตœ๊ณ ์˜ˆ์šฉโค๏ธ๐Ÿคใ€€๋”ฐ๋œปํ•˜๊ณ  ํญ๋‹ฅํญ๋‹ฅํ•œ ๋А๋‚Œ ๋„ˆ๋ฌด ์กฐ์•„์—ฌ!! ํ• ๋„ˆ๋ฌด ์˜ˆ๋ป์šฉ!!!"]
877
  ]
878
 
879
  # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์ƒ์„ฑ - ๋ชจ๋˜ ๋Œ€์‹œ๋ณด๋“œ ๋ ˆ์ด์•„์›ƒ
 
994
  show_label=True
995
  )
996
 
997
+ # ์ข…ํ•ฉ ๋ถ„์„ - ์ „์ฒด ๋„ˆ๋น„
998
+ gr.HTML('<div class="card-header sentiment-positive">โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„ & ์ธ์‚ฌ์ดํŠธ</div>')
999
+ comprehensive_output = gr.Markdown(
1000
+ value="""## โš–๏ธ ์ข…ํ•ฉ ๋ถ„์„
1001
 
1002
+ | ํ•ญ๋ชฉ | ๊ฐ์ • | ๋งŒ์กฑ๋„ | ๊ทผ๊ฑฐ |
1003
+ |------|------|--------|------|
1004
+ | - | - | - | - |
1005
+
1006
+ | ์ „์ฒด ํ†ค | - | - | - |
1007
+
1008
+ ## ๐Ÿ’ก ์š”์•ฝ ๋ฌธ์žฅ
1009
+
1010
+ **"๋ฆฌ๋ทฐ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๋ถ„์„์„ ์‹œ์ž‘ํ•˜์„ธ์š”"**
1011
+ """,
1012
+ show_label=False
1013
+ )
1014
 
1015
  # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
1016
  submit_btn.click(