Hello-maker-s commited on
Commit
a388657
·
1 Parent(s): b685931
ChatBot/rules/safety.py CHANGED
@@ -1,38 +1,63 @@
1
- # safety.py
2
-
3
  import re
4
- import os
5
- import json
6
- from dotenv import load_dotenv
7
- from groq import Groq
8
-
9
- load_dotenv()
10
-
11
- client = Groq(api_key=os.getenv("GROQ_API_KEY"))
12
- MODEL = "llama-3.1-8b-instant"
13
 
14
  PUNCT_RE = re.compile(r"[^\w\s']")
15
  SPACE_RE = re.compile(r"\s+")
16
 
 
17
  def normalize(text: str) -> str:
 
18
  text = text.lower()
 
 
19
  text = text.replace("’", "'")
 
 
20
  text = text.replace("dont", "don't")
21
  text = text.replace("cant", "can't")
22
  text = text.replace("wont", "won't")
 
23
  text = PUNCT_RE.sub(" ", text)
24
  text = SPACE_RE.sub(" ", text).strip()
 
25
  return text
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  # -------------------------
28
  # Hard safety overrides
29
  # -------------------------
30
  HIGH_RISK_PATTERNS = [
 
 
31
  r"\bi will kill myself\b",
32
  r"\bi am going to kill myself\b",
33
  r"\bend my life\b",
 
 
34
  r"\bhow to commit suicide\b",
35
  r"\bhow to kill myself\b",
 
 
36
  r"\bi don't want to live anymore\b",
37
  r"\bi want to die\b",
38
  r"\bi wish i was dead\b",
@@ -41,6 +66,7 @@ HIGH_RISK_PATTERNS = [
41
  r"\bi can't go on\b",
42
  ]
43
 
 
44
  NEGATIONS = [
45
  "not suicidal",
46
  "i am not suicidal",
@@ -49,7 +75,9 @@ NEGATIONS = [
49
  "i don't want to kill myself",
50
  ]
51
 
 
52
  RECOVERY_PATTERNS = [
 
53
  r"\bi talked to a professional\b",
54
  r"\bi spoke to a therapist\b",
55
  r"\bi got help\b",
@@ -60,268 +88,91 @@ RECOVERY_PATTERNS = [
60
  r"\bi reached out\b",
61
  ]
62
 
63
- HIGH_RISK_RE = [re.compile(p) for p in HIGH_RISK_PATTERNS]
64
- RECOVERY_RE = [re.compile(p) for p in RECOVERY_PATTERNS]
 
 
 
 
 
 
 
 
 
 
65
 
66
  # -------------------------
67
  # Main detector
68
  # -------------------------
69
  def check_critical(text: str) -> bool:
 
70
  if not text or len(text.strip()) < 5:
71
  return False
72
 
73
  t = normalize(text)
74
 
75
- # 1. Recovery override
 
 
76
  for pattern in RECOVERY_RE:
 
77
  if pattern.search(t):
 
78
  print("Recovery signal detected")
 
79
  return False
80
 
81
- # 2. Negation override
 
 
82
  for neg in NEGATIONS:
 
83
  if neg in t:
 
84
  print("Negation detected")
 
85
  return False
86
 
87
- # 3. Explicit hard override
 
 
88
  for pattern in HIGH_RISK_RE:
 
89
  if pattern.search(t):
 
90
  print("Critical detected via regex")
 
 
91
  return True
92
 
93
- # 4. Advanced Clinical Safety Screening via Groq
94
- system_prompt = """
95
- You are an elite safety screening system, strictly modeled after fine-tuned DistilBERT text classification pipelines for self-harm monitoring.
96
- Your absolute priority is to draw a sharp line between general depressive/exhausted low moods (SAFE) and acute self-harm crises (CRITICAL).
97
-
98
- ### STRICT DIFFERENTIAL BOUNDARY RULES:
99
- - "LABEL_0" (SAFE): General depression, crying, feeling deeply sad, loneliness, situational stress, heavy academic/work burnout, fatigue, expressing "I have no energy", or venting about a miserable day WITHOUT active self-harm intent. Even if the text is overwhelmingly negative or sorrowful, if there is no active self-harm or suicidal structure, it MUST be classified as LABEL_0.
100
- - "LABEL_1" (CRITICAL): Active or acute suicidal ideation, explicit statements of intent to self-harm, discussing methods, execution planning, final farewell gestures, or immediate danger to their own life.
101
-
102
- ### Scoring Calibration
103
- The downstream app relies on a strict threshold where score >= 0.97 for LABEL_1 triggers an emergency crisis protocol.
104
- - Do NOT let deep sadness or heavy venting pull the model into an ambiguous high-score LABEL_1.
105
- - If a user is expressing standard low mood or intense fatigue without clear self-harm indications, you must aggressively classify it as LABEL_0 with a high score.
106
-
107
- ### Expected JSON Output Format
108
- {
109
- "clinical_distinction": {
110
- "contains_active_self_harm_or_suicidal_framework": false,
111
- "is_permissible_emotional_venting_or_depression": true
112
- },
113
- "label": "LABEL_0",
114
- "score": 0.99
115
- }
116
- """
117
 
118
  try:
119
- response = client.chat.completions.create(
120
- model=MODEL,
121
- messages=[
122
- {"role": "system", "content": system_prompt},
123
- {"role": "user", "content": text[:2000]}
124
- ],
125
- temperature=0.0, # Kept at 0.0 for objective, rigid logical evaluation
126
- response_format={"type": "json_object"}
127
- )
128
 
129
- result = json.loads(response.choices[0].message.content)
130
-
131
- label = result.get("label", "LABEL_0")
132
- score = float(result.get("score", 0.0))
133
-
134
- print(f"Groq Safety Pipeline -> Label: {label}, Score: {score:.4f}")
135
 
136
  except Exception as e:
137
- print("Groq Safety API Error:", e)
 
 
138
  return False
139
 
140
- # 5. Semantic self-harm detection threshold
 
 
 
 
 
141
  decision = (
142
  label == "LABEL_1"
143
  and score >= 0.97
144
  )
145
 
146
- print("Final Critical Decision:", decision)
147
- return decision
148
-
149
-
150
- # import re
151
- # from transformers import pipeline
152
-
153
- # PUNCT_RE = re.compile(r"[^\w\s']")
154
- # SPACE_RE = re.compile(r"\s+")
155
-
156
-
157
- # def normalize(text: str) -> str:
158
-
159
- # text = text.lower()
160
-
161
- # # normalize unicode apostrophes
162
- # text = text.replace("’", "'")
163
-
164
- # # normalize common contractions
165
- # text = text.replace("dont", "don't")
166
- # text = text.replace("cant", "can't")
167
- # text = text.replace("wont", "won't")
168
-
169
- # text = PUNCT_RE.sub(" ", text)
170
- # text = SPACE_RE.sub(" ", text).strip()
171
-
172
- # return text
173
-
174
-
175
- # # -------------------------
176
- # # Lazy-loaded singleton
177
- # # -------------------------
178
- # _model = None
179
-
180
-
181
- # def get_model():
182
-
183
- # global _model
184
-
185
- # if _model is None:
186
-
187
- # _model = pipeline(
188
- # "text-classification",
189
- # model="wcyat/distilbert-suicide-detection-hk"
190
- # )
191
-
192
- # return _model
193
-
194
-
195
- # # -------------------------
196
- # # Hard safety overrides
197
- # # -------------------------
198
- # HIGH_RISK_PATTERNS = [
199
-
200
- # # explicit intent
201
- # r"\bi will kill myself\b",
202
- # r"\bi am going to kill myself\b",
203
- # r"\bend my life\b",
204
-
205
- # # method seeking
206
- # r"\bhow to commit suicide\b",
207
- # r"\bhow to kill myself\b",
208
-
209
- # # passive suicidal ideation
210
- # r"\bi don't want to live anymore\b",
211
- # r"\bi want to die\b",
212
- # r"\bi wish i was dead\b",
213
- # r"\bbetter off dead\b",
214
- # r"\bno reason to live\b",
215
- # r"\bi can't go on\b",
216
- # ]
217
-
218
-
219
- # NEGATIONS = [
220
- # "not suicidal",
221
- # "i am not suicidal",
222
- # "i don't want to die",
223
- # "i do not want to die",
224
- # "i don't want to kill myself",
225
- # ]
226
-
227
-
228
- # RECOVERY_PATTERNS = [
229
-
230
- # r"\bi talked to a professional\b",
231
- # r"\bi spoke to a therapist\b",
232
- # r"\bi got help\b",
233
- # r"\bit helped\b",
234
- # r"\bi feel better\b",
235
- # r"\bi am feeling better\b",
236
- # r"\bthings are improving\b",
237
- # r"\bi reached out\b",
238
- # ]
239
-
240
-
241
- # HIGH_RISK_RE = [
242
- # re.compile(p)
243
- # for p in HIGH_RISK_PATTERNS
244
- # ]
245
-
246
-
247
- # RECOVERY_RE = [
248
- # re.compile(p)
249
- # for p in RECOVERY_PATTERNS
250
- # ]
251
-
252
-
253
- # # -------------------------
254
- # # Main detector
255
- # # -------------------------
256
- # def check_critical(text: str) -> bool:
257
-
258
- # if not text or len(text.strip()) < 5:
259
- # return False
260
-
261
- # t = normalize(text)
262
-
263
- # # -------------------------
264
- # # Recovery override
265
- # # -------------------------
266
- # for pattern in RECOVERY_RE:
267
-
268
- # if pattern.search(t):
269
-
270
- # print("Recovery signal detected")
271
-
272
- # return False
273
-
274
- # # -------------------------
275
- # # Negation override
276
- # # -------------------------
277
- # for neg in NEGATIONS:
278
-
279
- # if neg in t:
280
-
281
- # print("Negation detected")
282
-
283
- # return False
284
-
285
- # # -------------------------
286
- # # Explicit hard override
287
- # # -------------------------
288
- # for pattern in HIGH_RISK_RE:
289
-
290
- # if pattern.search(t):
291
-
292
- # print("Critical detected via regex")
293
- # print("Matched pattern:", pattern.pattern)
294
-
295
- # return True
296
-
297
- # # -------------------------
298
- # # Transformer inference
299
- # # -------------------------
300
- # model = get_model()
301
-
302
- # try:
303
-
304
- # result = model(text[:512])[0]
305
-
306
- # print("Safety model result:", result)
307
-
308
- # except Exception as e:
309
-
310
- # print("Safety model error:", e)
311
-
312
- # return False
313
-
314
- # label = result["label"]
315
- # score = float(result["score"])
316
-
317
- # # -------------------------
318
- # # Semantic self-harm detection
319
- # # -------------------------
320
- # decision = (
321
- # label == "LABEL_1"
322
- # and score >= 0.97
323
- # )
324
-
325
- # print("Critical decision:", decision)
326
 
327
- # return decision
 
 
 
1
  import re
2
+ from transformers import pipeline
 
 
 
 
 
 
 
 
3
 
4
  PUNCT_RE = re.compile(r"[^\w\s']")
5
  SPACE_RE = re.compile(r"\s+")
6
 
7
+
8
  def normalize(text: str) -> str:
9
+
10
  text = text.lower()
11
+
12
+ # normalize unicode apostrophes
13
  text = text.replace("’", "'")
14
+
15
+ # normalize common contractions
16
  text = text.replace("dont", "don't")
17
  text = text.replace("cant", "can't")
18
  text = text.replace("wont", "won't")
19
+
20
  text = PUNCT_RE.sub(" ", text)
21
  text = SPACE_RE.sub(" ", text).strip()
22
+
23
  return text
24
 
25
+
26
+ # -------------------------
27
+ # Lazy-loaded singleton
28
+ # -------------------------
29
+ _model = None
30
+
31
+
32
+ def get_model():
33
+
34
+ global _model
35
+
36
+ if _model is None:
37
+
38
+ _model = pipeline(
39
+ "text-classification",
40
+ model="wcyat/distilbert-suicide-detection-hk"
41
+ )
42
+
43
+ return _model
44
+
45
+
46
  # -------------------------
47
  # Hard safety overrides
48
  # -------------------------
49
  HIGH_RISK_PATTERNS = [
50
+
51
+ # explicit intent
52
  r"\bi will kill myself\b",
53
  r"\bi am going to kill myself\b",
54
  r"\bend my life\b",
55
+
56
+ # method seeking
57
  r"\bhow to commit suicide\b",
58
  r"\bhow to kill myself\b",
59
+
60
+ # passive suicidal ideation
61
  r"\bi don't want to live anymore\b",
62
  r"\bi want to die\b",
63
  r"\bi wish i was dead\b",
 
66
  r"\bi can't go on\b",
67
  ]
68
 
69
+
70
  NEGATIONS = [
71
  "not suicidal",
72
  "i am not suicidal",
 
75
  "i don't want to kill myself",
76
  ]
77
 
78
+
79
  RECOVERY_PATTERNS = [
80
+
81
  r"\bi talked to a professional\b",
82
  r"\bi spoke to a therapist\b",
83
  r"\bi got help\b",
 
88
  r"\bi reached out\b",
89
  ]
90
 
91
+
92
+ HIGH_RISK_RE = [
93
+ re.compile(p)
94
+ for p in HIGH_RISK_PATTERNS
95
+ ]
96
+
97
+
98
+ RECOVERY_RE = [
99
+ re.compile(p)
100
+ for p in RECOVERY_PATTERNS
101
+ ]
102
+
103
 
104
  # -------------------------
105
  # Main detector
106
  # -------------------------
107
  def check_critical(text: str) -> bool:
108
+
109
  if not text or len(text.strip()) < 5:
110
  return False
111
 
112
  t = normalize(text)
113
 
114
+ # -------------------------
115
+ # Recovery override
116
+ # -------------------------
117
  for pattern in RECOVERY_RE:
118
+
119
  if pattern.search(t):
120
+
121
  print("Recovery signal detected")
122
+
123
  return False
124
 
125
+ # -------------------------
126
+ # Negation override
127
+ # -------------------------
128
  for neg in NEGATIONS:
129
+
130
  if neg in t:
131
+
132
  print("Negation detected")
133
+
134
  return False
135
 
136
+ # -------------------------
137
+ # Explicit hard override
138
+ # -------------------------
139
  for pattern in HIGH_RISK_RE:
140
+
141
  if pattern.search(t):
142
+
143
  print("Critical detected via regex")
144
+ print("Matched pattern:", pattern.pattern)
145
+
146
  return True
147
 
148
+ # -------------------------
149
+ # Transformer inference
150
+ # -------------------------
151
+ model = get_model()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
  try:
 
 
 
 
 
 
 
 
 
154
 
155
+ result = model(text[:512])[0]
156
+
157
+ print("Safety model result:", result)
 
 
 
158
 
159
  except Exception as e:
160
+
161
+ print("Safety model error:", e)
162
+
163
  return False
164
 
165
+ label = result["label"]
166
+ score = float(result["score"])
167
+
168
+ # -------------------------
169
+ # Semantic self-harm detection
170
+ # -------------------------
171
  decision = (
172
  label == "LABEL_1"
173
  and score >= 0.97
174
  )
175
 
176
+ print("Critical decision:", decision)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
+ return decision
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Install dependencies
7
+ COPY requirements.txt .
8
+ RUN pip install --no-cache-dir -r requirements.txt gunicorn
9
+
10
+ # Copy project files
11
+ COPY . .
12
+
13
+ # Expose port (Hugging Face uses 7860 by default)
14
+ EXPOSE 7860
15
+
16
+ # Start the application
17
+ CMD ["gunicorn", "--bind", "0.0.0.0:7860", "mental_health.wsgi:application"]
core/sentiment.py CHANGED
@@ -1,179 +1,102 @@
1
- import os
2
- import json
3
- from dotenv import load_dotenv
4
- from groq import Groq
5
 
6
- load_dotenv()
7
 
8
- client = Groq(api_key=os.getenv("GROQ_API_KEY"))
9
- MODEL = "llama-3.1-8b-instant"
10
 
11
- def analyze_text(text: str):
12
- if not text or not text.strip():
13
- return {"label": "NEUTRAL", "score": 0.5, "mood": "neutral"}
14
-
15
- # ADVANCED CHAIN-OF-THOUGHT PROMPT
16
- system_prompt = """
17
- You are an elite psychological sentiment analyzer, modeled after DistilRoBERTa emotion classifiers.
18
- Analyze the user's text and return strictly a JSON object.
19
-
20
- ### Step 1: Ekman Emotion Analysis
21
- First, evaluate the text across these 7 core emotions (must add up to 1.0):
22
- - "joy", "sadness", "anger", "fear", "surprise", "disgust", "neutral".
23
-
24
- ### Step 2: Mapping to Internal State
25
- Based on the dominant Ekman emotions, map the result to EXACTLY ONE of these target moods:
26
- - "great" (High Joy / Positive Surprise) -> Label: POSITIVE
27
- - "good" (Mild Joy / Contentment) -> Label: POSITIVE
28
- - "neutral" (High Neutral) -> Label: NEUTRAL
29
- - "stressed" (High Fear / Anxiety) -> Label: NEGATIVE
30
- - "low" (High Sadness / Disappointment) -> Label: NEGATIVE
31
- - "overwhelmed" (High Anger / Disgust / Overload) -> Label: NEGATIVE
32
-
33
- ### Expected JSON Output Format
34
- {
35
- "ekman_scores": {
36
- "joy": 0.0,
37
- "sadness": 0.7,
38
- "anger": 0.1,
39
- "fear": 0.0,
40
- "surprise": 0.0,
41
- "disgust": 0.0,
42
- "neutral": 0.2
43
- },
44
- "label": "NEGATIVE",
45
- "mood": "low",
46
- "score": 0.75
47
- }
48
- """
49
-
50
- try:
51
- response = client.chat.completions.create(
52
- model=MODEL,
53
- messages=[
54
- {"role": "system", "content": system_prompt},
55
- {"role": "user", "content": text[:2000]}
56
- ],
57
- temperature=0.1,
58
- response_format={"type": "json_object"}
59
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- result = json.loads(response.choices[0].message.content)
62
-
63
- # We extract your specific keys, ignoring the ekman_scores which were just used for LLM reasoning
64
- return {
65
- "label": result.get("label", "NEUTRAL"),
66
- "score": float(result.get("score", 0.5)),
67
- "mood": result.get("mood", "neutral")
68
- }
69
-
70
- except Exception as e:
71
- print("Groq Sentiment API Error:", e)
72
- return {
73
- "label": "NEUTRAL",
74
- "score": 0.5,
75
- "mood": "neutral"
76
- }
77
-
78
- # # sentiment.py
79
-
80
- # from transformers import pipeline
81
-
82
- # # Lazy-loaded singleton
83
-
84
- # _model = None
85
-
86
- # def get_model():
87
- # global _model
88
- # if _model is None:
89
- # _model = pipeline(
90
- # "text-classification",
91
- # model="j-hartmann/emotion-english-distilroberta-base",
92
- # return_all_scores=True
93
- # )
94
- # return _model
95
-
96
-
97
- # # Chunking (still needed for long text)
98
- # def split_text(text, max_len=400):
99
- # sentences = text.split(". ")
100
- # chunks, current = [], ""
101
-
102
- # for s in sentences:
103
- # if len(current) + len(s) < max_len:
104
- # current += s + ". "
105
- # else:
106
- # chunks.append(current.strip())
107
- # current = s + ". "
108
-
109
- # if current:
110
- # chunks.append(current.strip())
111
-
112
- # return chunks
113
-
114
-
115
-
116
- # # Emotion → your mood mapping
117
- # def map_emotion_to_mood(emotion: str):
118
- # if emotion == "joy":
119
- # return "good"
120
- # elif emotion == "sadness":
121
- # return "low"
122
- # elif emotion == "anger":
123
- # return "overwhelmed"
124
- # elif emotion == "fear":
125
- # return "stressed"
126
- # elif emotion == "neutral":
127
- # return "neutral"
128
- # else:
129
- # return "neutral"
130
-
131
-
132
- # def analyze_text(text: str):
133
- # model = get_model()
134
- # chunks = split_text(text)
135
-
136
- # aggregated = {}
137
-
138
- # for chunk in chunks:
139
- # try:
140
- # outputs = model(chunk[:512])
141
- # except Exception:
142
- # return {
143
- # "label": "NEUTRAL",
144
- # "score": 0.5,
145
- # "mood": "neutral"
146
- # }
147
-
148
- # # Normalize output
149
- # if isinstance(outputs[0], list):
150
- # results = outputs[0] # return_all_scores=True case
151
- # else:
152
- # results = outputs # single prediction case
153
-
154
- # for r in results:
155
- # if isinstance(r, dict):
156
- # label = r["label"]
157
- # score = r["score"]
158
- # else:
159
- # # fallback if model returns string label
160
- # label = r
161
- # score = 1.0
162
-
163
- # aggregated[label] = aggregated.get(label, 0) + score
164
-
165
- # # average scores
166
- # for k in aggregated:
167
- # aggregated[k] /= len(chunks)
168
-
169
- # # pick best emotion
170
- # best_emotion = max(aggregated, key=aggregated.get)
171
- # best_score = aggregated[best_emotion]
172
-
173
- # mood = map_emotion_to_mood(best_emotion)
174
-
175
- # return {
176
- # "label": "POSITIVE" if mood in ["good"] else "NEGATIVE",
177
- # "score": float(best_score),
178
- # "mood": mood
179
- # }
 
1
+ # sentiment.py
 
 
 
2
 
3
+ from transformers import pipeline
4
 
5
+ # Lazy-loaded singleton
 
6
 
7
+ _model = None
8
+
9
+ def get_model():
10
+ global _model
11
+ if _model is None:
12
+ _model = pipeline(
13
+ "text-classification",
14
+ model="j-hartmann/emotion-english-distilroberta-base",
15
+ return_all_scores=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  )
17
+ return _model
18
+
19
+
20
+ # Chunking (still needed for long text)
21
+ def split_text(text, max_len=400):
22
+ sentences = text.split(". ")
23
+ chunks, current = [], ""
24
+
25
+ for s in sentences:
26
+ if len(current) + len(s) < max_len:
27
+ current += s + ". "
28
+ else:
29
+ chunks.append(current.strip())
30
+ current = s + ". "
31
+
32
+ if current:
33
+ chunks.append(current.strip())
34
 
35
+ return chunks
36
+
37
+
38
+
39
+ # Emotion → your mood mapping
40
+ def map_emotion_to_mood(emotion: str):
41
+ if emotion == "joy":
42
+ return "good"
43
+ elif emotion == "sadness":
44
+ return "low"
45
+ elif emotion == "anger":
46
+ return "overwhelmed"
47
+ elif emotion == "fear":
48
+ return "stressed"
49
+ elif emotion == "neutral":
50
+ return "neutral"
51
+ else:
52
+ return "neutral"
53
+
54
+
55
+ def analyze_text(text: str):
56
+ model = get_model()
57
+ chunks = split_text(text)
58
+
59
+ aggregated = {}
60
+
61
+ for chunk in chunks:
62
+ try:
63
+ outputs = model(chunk[:512])
64
+ except Exception:
65
+ return {
66
+ "label": "NEUTRAL",
67
+ "score": 0.5,
68
+ "mood": "neutral"
69
+ }
70
+
71
+ # Normalize output
72
+ if isinstance(outputs[0], list):
73
+ results = outputs[0] # return_all_scores=True case
74
+ else:
75
+ results = outputs # single prediction case
76
+
77
+ for r in results:
78
+ if isinstance(r, dict):
79
+ label = r["label"]
80
+ score = r["score"]
81
+ else:
82
+ # fallback if model returns string label
83
+ label = r
84
+ score = 1.0
85
+
86
+ aggregated[label] = aggregated.get(label, 0) + score
87
+
88
+ # average scores
89
+ for k in aggregated:
90
+ aggregated[k] /= len(chunks)
91
+
92
+ # pick best emotion
93
+ best_emotion = max(aggregated, key=aggregated.get)
94
+ best_score = aggregated[best_emotion]
95
+
96
+ mood = map_emotion_to_mood(best_emotion)
97
+
98
+ return {
99
+ "label": "POSITIVE" if mood in ["good"] else "NEGATIVE",
100
+ "score": float(best_score),
101
+ "mood": mood
102
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
mental_health/settings.py CHANGED
@@ -33,7 +33,7 @@ SECRET_KEY = env('SECRET_KEY')
33
  DEBUG = env.bool('DEBUG', default=True)
34
 
35
  ALLOWED_HOSTS = ['*']
36
-
37
 
38
  # Application definition
39
 
 
33
  DEBUG = env.bool('DEBUG', default=True)
34
 
35
  ALLOWED_HOSTS = ['*']
36
+ CSRF_TRUSTED_ORIGINS = ['https://*.hf.space']
37
 
38
  # Application definition
39
 
requirements.txt CHANGED
@@ -13,7 +13,7 @@ click==8.4.1
13
  colorama==0.4.6
14
  distro==1.9.0
15
  dj-database-url==3.1.2
16
- Django==5.2.15
17
  django-anymail==15.0
18
  django-cors-headers==4.9.0
19
  django-environ==0.13.0
@@ -22,19 +22,29 @@ django-ratelimit==4.1.0
22
  djangorestframework==3.17.1
23
  djangorestframework_simplejwt==5.5.1
24
  edge-tts==7.2.8
 
25
  frozenlist==1.8.0
 
26
  groq==1.2.0
27
  gunicorn==26.0.0
28
  h11==0.16.0
 
29
  httpcore==1.0.9
30
  httpx==0.28.1
 
31
  idna==3.17
32
  Jinja2==3.1.6
 
33
  markdown-it-py==4.0.0
34
  MarkupSafe==3.0.3
35
  mdurl==0.1.2
 
36
  multidict==6.7.1
 
 
 
37
  packaging==26.2
 
38
  pillow==12.2.0
39
  propcache==0.5.2
40
  psutil==7.2.2
@@ -48,14 +58,26 @@ python-dateutil==2.9.0.post0
48
  python-dotenv==1.2.2
49
  python-multipart==0.0.30
50
  PyYAML==6.0.3
 
51
  requests==2.34.2
52
  rich==15.0.0
 
 
 
 
53
  setuptools==81.0.0
54
  shellingham==1.5.4
55
  six==1.17.0
56
  sniffio==1.3.1
57
  sqlparse==0.5.5
 
58
  tabulate==0.10.0
 
 
 
 
 
 
59
  typer==0.25.1
60
  typing-inspection==0.4.2
61
  typing_extensions==4.15.0
 
13
  colorama==0.4.6
14
  distro==1.9.0
15
  dj-database-url==3.1.2
16
+ Django==6.0.5
17
  django-anymail==15.0
18
  django-cors-headers==4.9.0
19
  django-environ==0.13.0
 
22
  djangorestframework==3.17.1
23
  djangorestframework_simplejwt==5.5.1
24
  edge-tts==7.2.8
25
+ filelock==3.29.0
26
  frozenlist==1.8.0
27
+ fsspec==2026.4.0
28
  groq==1.2.0
29
  gunicorn==26.0.0
30
  h11==0.16.0
31
+ hf-xet==1.5.0
32
  httpcore==1.0.9
33
  httpx==0.28.1
34
+ huggingface_hub==1.17.0
35
  idna==3.17
36
  Jinja2==3.1.6
37
+ joblib==1.5.3
38
  markdown-it-py==4.0.0
39
  MarkupSafe==3.0.3
40
  mdurl==0.1.2
41
+ mpmath==1.3.0
42
  multidict==6.7.1
43
+ networkx==3.6.1
44
+ nltk==3.9.4
45
+ numpy==2.4.6
46
  packaging==26.2
47
+ pandas==3.0.3
48
  pillow==12.2.0
49
  propcache==0.5.2
50
  psutil==7.2.2
 
58
  python-dotenv==1.2.2
59
  python-multipart==0.0.30
60
  PyYAML==6.0.3
61
+ regex==2026.5.9
62
  requests==2.34.2
63
  rich==15.0.0
64
+ safetensors==0.7.0
65
+ scikit-learn==1.8.0
66
+ scipy==1.17.1
67
+ sentence-transformers==5.5.1
68
  setuptools==81.0.0
69
  shellingham==1.5.4
70
  six==1.17.0
71
  sniffio==1.3.1
72
  sqlparse==0.5.5
73
+ sympy==1.14.0
74
  tabulate==0.10.0
75
+ textblob==0.20.0
76
+ threadpoolctl==3.6.0
77
+ tokenizers==0.22.2
78
+ torch==2.12.0
79
+ tqdm==4.67.3
80
+ transformers==5.9.0
81
  typer==0.25.1
82
  typing-inspection==0.4.2
83
  typing_extensions==4.15.0