Corin1998 commited on
Commit
516889a
·
verified ·
1 Parent(s): 8b06bd8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -1
app.py CHANGED
@@ -1,7 +1,261 @@
1
  # app.py
 
 
 
 
2
  import gradio as gr
3
- from es_advanced_evaluator import evaluate_entry_sheet
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
 
 
 
5
 
6
  def evaluate_es(self_pr: str, motivation: str):
7
  """
 
1
  # app.py
2
+ from typing import List, Dict, Any, Optional
3
+ import re
4
+ import json
5
+
6
  import gradio as gr
7
+ from transformers import pipeline
8
+
9
+ # =========================
10
+ # モデル定義(Hugging Face)
11
+ # =========================
12
+
13
+ # 日本語感情分析モデル
14
+ SENTIMENT_MODEL_NAME = "koheiduck/bert-japanese-finetuned-sentiment"
15
+ # 多言語ゼロショット分類モデル(STAR分類・トーン評価用)
16
+ NLI_MODEL_NAME = "joeddav/xlm-roberta-large-xnli"
17
+
18
+ # 必要に応じて device=0 (GPU) に変更してください
19
+ sentiment_classifier = pipeline(
20
+ "sentiment-analysis",
21
+ model=SENTIMENT_MODEL_NAME,
22
+ tokenizer=SENTIMENT_MODEL_NAME,
23
+ )
24
+
25
+ zero_shot_classifier = pipeline(
26
+ "zero-shot-classification",
27
+ model=NLI_MODEL_NAME,
28
+ tokenizer=NLI_MODEL_NAME,
29
+ )
30
+
31
+ # =========================
32
+ # ユーティリティ
33
+ # =========================
34
+
35
+ _SENT_SPLIT_RE = re.compile(r"[。!?\n]+")
36
+
37
+
38
+ def split_sentences(text: str) -> List[str]:
39
+ """日本語の文をざっくり分割する簡易関数"""
40
+ sentences = [s.strip() for s in _SENT_SPLIT_RE.split(text) if s.strip()]
41
+ return sentences
42
+
43
+
44
+ # =========================
45
+ # 感情・トーン分析
46
+ # =========================
47
+
48
+ def analyze_sentiment(text: str) -> Dict[str, Any]:
49
+ """
50
+ 日本語テキストのネガポジ分析。
51
+ 出力例:
52
+ {
53
+ "label": "POSITIVE",
54
+ "score": 0.98
55
+ }
56
+ """
57
+ if not text.strip():
58
+ return {"label": "NEUTRAL", "score": 0.0}
59
+
60
+ result = sentiment_classifier(text)[0]
61
+ return {
62
+ "label": result["label"],
63
+ "score": float(result["score"]),
64
+ }
65
+
66
+
67
+ def analyze_tone(text: str) -> Dict[str, Any]:
68
+ """
69
+ ゼロショット分類で「熱意・主体性・一貫性・論理性・協調性」などをスコアリング。
70
+ multi_label=True なので、1文が複数のトーンを兼ねることも許容。
71
+ 出力例:
72
+ {
73
+ "labels": {
74
+ "熱意": 0.92,
75
+ "主体性": 0.81,
76
+ "一貫性": 0.55,
77
+ "論理性": 0.73,
78
+ "協調性": 0.40
79
+ }
80
+ }
81
+ """
82
+ if not text.strip():
83
+ return {"labels": {}}
84
+
85
+ tone_labels = ["熱意", "主体性", "一貫性", "論理性", "協調性"]
86
+
87
+ result = zero_shot_classifier(
88
+ text,
89
+ candidate_labels=tone_labels,
90
+ multi_label=True,
91
+ )
92
+
93
+ label_scores = {
94
+ label: float(score)
95
+ for label, score in zip(result["labels"], result["scores"])
96
+ }
97
+
98
+ ordered = {label: label_scores.get(label, 0.0) for label in tone_labels}
99
+
100
+ return {"labels": ordered}
101
+
102
+
103
+ # =========================
104
+ # STAR 構造分析
105
+ # =========================
106
+
107
+ STAR_LABELS_JP = [
108
+ "Situation(状況)",
109
+ "Task(課題)",
110
+ "Action(行動)",
111
+ "Result(結果)",
112
+ "Other(その他)",
113
+ ]
114
+
115
+ STAR_KEY_MAP = {
116
+ "Situation(状況)": "S",
117
+ "Task(課題)": "T",
118
+ "Action(行動)": "A",
119
+ "Result(結果)": "R",
120
+ "Other(その他)": "O",
121
+ }
122
+
123
+
124
+ def classify_sentence_star(sentence: str) -> Dict[str, Any]:
125
+ """
126
+ 1文を STAR のどれに近いかゼロショット分類する。
127
+ multi_label=False とし、最も近いラベルのみ採用。
128
+ 出力例:
129
+ {
130
+ "sentence": "...",
131
+ "star_label": "S",
132
+ "raw_label": "Situation(状況)",
133
+ "score": 0.87
134
+ }
135
+ """
136
+ if not sentence.strip():
137
+ return {
138
+ "sentence": sentence,
139
+ "star_label": "O",
140
+ "raw_label": "Other(その他)",
141
+ "score": 0.0,
142
+ }
143
+
144
+ result = zero_shot_classifier(
145
+ sentence,
146
+ candidate_labels=STAR_LABELS_JP,
147
+ multi_label=False,
148
+ )
149
+ raw_label = result["labels"][0]
150
+ score = float(result["scores"][0])
151
+ star_label = STAR_KEY_MAP.get(raw_label, "O")
152
+
153
+ return {
154
+ "sentence": sentence,
155
+ "star_label": star_label,
156
+ "raw_label": raw_label,
157
+ "score": score,
158
+ }
159
+
160
+
161
+ def analyze_star_structure(text: str) -> Dict[str, Any]:
162
+ """
163
+ 自己PRテキストの STAR 構造(S/T/A/R がどの程度含まれているか)を分析。
164
+ 出力例:
165
+ {
166
+ "coverage": {
167
+ "S": true,
168
+ "T": true,
169
+ "A": true,
170
+ "R": false
171
+ },
172
+ "star_score": 0.75,
173
+ "per_sentence": [...],
174
+ "missing_elements": ["R"],
175
+ "comment": "結果(Result)の記述が弱いため、成果をより具体的に書くと良いです。"
176
+ }
177
+ """
178
+ sentences = split_sentences(text)
179
+ if not sentences:
180
+ return {
181
+ "coverage": {k: False for k in ["S", "T", "A", "R"]},
182
+ "star_score": 0.0,
183
+ "per_sentence": [],
184
+ "missing_elements": ["S", "T", "A", "R"],
185
+ "comment": "文章が空か極端に短いため、STAR 構造の判定ができません。",
186
+ }
187
+
188
+ per_sentence_results = [
189
+ classify_sentence_star(s) for s in sentences
190
+ ]
191
+
192
+ coverage = {k: False for k in ["S", "T", "A", "R"]}
193
+ for r in per_sentence_results:
194
+ key = r["star_label"]
195
+ if key in coverage:
196
+ coverage[key] = True
197
+
198
+ missing = [k for k, v in coverage.items() if not v]
199
+ star_score = sum(1 for v in coverage.values() if v) / 4.0
200
+
201
+ # コメント生成(シンプルなヒューリスティック)
202
+ if not missing:
203
+ comment = "STAR の各要素(状況・課題・行動・結果)が一通り含まれています。構成としてバランスは良好です。"
204
+ else:
205
+ parts = []
206
+ mapping_jp = {"S": "状況(Situation)", "T": "課題(Task)", "A": "行動(Action)", "R": "結果(Result)"}
207
+ for m in missing:
208
+ parts.append(mapping_jp[m])
209
+ missing_jp = "・".join(parts)
210
+ comment = (
211
+ f"{missing_jp} の要素が弱い/不足しています。"
212
+ "不足している要素を具体的に書き足すと、より論理的で説得力のある自己PRになります。"
213
+ )
214
+
215
+ return {
216
+ "coverage": coverage,
217
+ "star_score": star_score,
218
+ "per_sentence": per_sentence_results,
219
+ "missing_elements": missing,
220
+ "comment": comment,
221
+ }
222
+
223
+
224
+ # =========================
225
+ # ES 全体の評価インターフェース
226
+ # =========================
227
+
228
+ def evaluate_entry_sheet(
229
+ self_pr: str,
230
+ motivation: Optional[str] = None,
231
+ ) -> Dict[str, Any]:
232
+ """
233
+ ES の自己PR(必須)、志望動機(任意)を入力として、
234
+ 感情・トーン・STAR 構造をまとめて評価するインターフェース。
235
+ """
236
+ texts = [self_pr]
237
+ if motivation:
238
+ texts.append(motivation)
239
+ full_text = "\n".join([t for t in texts if t])
240
+
241
+ sentiment = analyze_sentiment(full_text)
242
+ tone = analyze_tone(full_text)
243
+ star = analyze_star_structure(self_pr) # STAR は主に自己PRに対して実行
244
+
245
+ return {
246
+ "input": {
247
+ "self_pr": self_pr,
248
+ "motivation": motivation,
249
+ },
250
+ "sentiment": sentiment,
251
+ "tone": tone,
252
+ "star": star,
253
+ }
254
+
255
 
256
+ # =========================
257
+ # Gradio 用ラッパー
258
+ # =========================
259
 
260
  def evaluate_es(self_pr: str, motivation: str):
261
  """