stanlee47 Claude Sonnet 4.6 commited on
Commit
37e50ac
Β·
1 Parent(s): e5ab5af

Add device-key FCM debug route + update test_fcm.py to use device key

Browse files

- New GET /api/wearable/device/fcm-debug (X-Device-Key auth) mirrors
/api/user/fcm-debug without needing JWT/email/password
- Refactored shared logic into _fcm_debug_for_user() helper
- test_fcm.py now uses device API key entirely β€” no credentials needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +22 -1
  2. test_fcm.py +83 -126
app.py CHANGED
@@ -213,7 +213,28 @@ def fcm_debug():
213
  """
214
  user = request.current_user
215
  db = get_db()
216
- fcm_token = db.get_fcm_token(user["id"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  result = {
218
  "fcm_backend_enabled": _fcm_enabled,
219
  "user_has_token": bool(fcm_token),
 
213
  """
214
  user = request.current_user
215
  db = get_db()
216
+ return _fcm_debug_for_user(user["id"], db)
217
+
218
+
219
+ @app.route("/api/wearable/device/fcm-debug", methods=["GET"])
220
+ def device_fcm_debug():
221
+ """
222
+ Same as /api/user/fcm-debug but authenticated via X-Device-Key header.
223
+ Allows testing FCM without knowing the account email/password.
224
+ Query param: ?send_test=1 to send a test notification.
225
+ """
226
+ api_key = request.headers.get("X-Device-Key")
227
+ if not api_key:
228
+ return jsonify({"error": "X-Device-Key header required"}), 401
229
+ db = get_db()
230
+ user = db.get_user_by_device_key(api_key)
231
+ if not user:
232
+ return jsonify({"error": "Invalid or revoked device key"}), 401
233
+ return _fcm_debug_for_user(user["id"], db)
234
+
235
+
236
+ def _fcm_debug_for_user(user_id, db):
237
+ fcm_token = db.get_fcm_token(user_id)
238
  result = {
239
  "fcm_backend_enabled": _fcm_enabled,
240
  "user_has_token": bool(fcm_token),
test_fcm.py CHANGED
@@ -1,70 +1,43 @@
1
  """
2
  FCM Diagnostic Test
3
  ====================
4
- Logs in as a real user, checks FCM status on backend, sends a test push,
5
- then sends real stress sensor data as that user to trigger ML β†’ FCM flow.
6
 
7
  Usage:
8
  python test_fcm.py
9
  """
10
 
11
- import json
12
  import sys
13
  import io
14
- import requests
15
- import csv
16
  import time
 
17
 
18
  # Fix Windows console encoding
19
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
20
 
21
- BASE_URL = "https://santa47-cbt-companion-api.hf.space"
 
22
 
23
- # ---------- credentials ----------
24
- EMAIL = "test@example.com" # <-- your test account email
25
- PASSWORD = "test123" # <-- your test account password
26
- # ---------------------------------
27
-
28
- DATA_DIR = "data"
29
  SEVERE_CSV = "sampled_data_DepressionLevel_Severe.csv"
30
 
31
-
32
- def login(email, password):
33
- resp = requests.post(
34
- f"{BASE_URL}/api/login",
35
- json={"email": email, "password": password},
36
- timeout=15,
37
- )
38
- if resp.status_code == 200 and resp.json().get("success"):
39
- return resp.json()["token"]
40
- raise RuntimeError(f"Login failed ({resp.status_code}): {resp.text}")
41
-
42
-
43
- def auth_headers(token):
44
- return {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
45
 
46
 
47
  def check_health():
48
- resp = requests.get(f"{BASE_URL}/health", timeout=10)
49
- return resp.json()
50
 
51
 
52
- def check_fcm_status(token):
53
- resp = requests.get(
54
- f"{BASE_URL}/api/user/fcm-debug",
55
- headers=auth_headers(token),
56
- timeout=10,
57
- )
58
- return resp.json()
59
-
60
-
61
- def send_test_push(token):
62
- resp = requests.get(
63
- f"{BASE_URL}/api/user/fcm-debug?send_test=1",
64
- headers=auth_headers(token),
65
- timeout=15,
66
- )
67
- return resp.json()
68
 
69
 
70
  def load_severe_data():
@@ -81,118 +54,102 @@ def load_severe_data():
81
  return readings
82
 
83
 
84
- def send_sensor_reading(token, reading):
85
- resp = requests.post(
86
- f"{BASE_URL}/api/wearable/data",
87
- headers=auth_headers(token),
88
  json=reading,
89
  timeout=20,
90
- )
91
- return resp.json()
92
 
93
 
94
  def main():
95
  print("\n" + "="*60)
96
  print(" CBT COMPANION β€” FCM DIAGNOSTIC TEST")
 
97
  print("="*60)
98
 
99
- # 1. Health check
100
  print("\n[1] Health check...")
101
  health = check_health()
102
  print(f" Status: {health.get('status')}")
103
  print(f" ML model loaded: {health.get('ml_model_loaded')}")
104
- fcm_enabled = health.get("fcm_enabled")
105
- print(f" FCM enabled: {fcm_enabled}")
106
- if not fcm_enabled:
107
- print("\n ❌ firebase-admin is NOT loaded on the server.")
108
- print(" β†’ Check HuggingFace Space logs. The Space may still be building,")
109
- print(" or FIREBASE_SERVICE_ACCOUNT secret may be missing/invalid.")
110
-
111
- # 2. Login
112
- print(f"\n[2] Logging in as {EMAIL}...")
113
- try:
114
- token = login(EMAIL, PASSWORD)
115
- print(" βœ… Login OK β€” JWT acquired")
116
- except RuntimeError as e:
117
- print(f" ❌ {e}")
118
  return
119
 
120
- # 3. FCM token check
121
- print("\n[3] Checking FCM token in database...")
122
- status = check_fcm_status(token)
123
  print(f" FCM backend enabled: {status.get('fcm_backend_enabled')}")
124
  print(f" User has token: {status.get('user_has_token')}")
125
  print(f" Token preview: {status.get('token_preview', 'N/A')}")
126
 
127
  if not status.get("user_has_token"):
128
  print("\n ❌ No FCM token stored for this user!")
129
- print(" β†’ Open the new APK on your phone and LOG IN.")
130
- print(" The token uploads automatically when you log in.")
131
  print(" β†’ Then run this script again.")
132
- print("\n Aborting β€” cannot test FCM without a stored token.")
133
  return
134
 
135
- # 4. Test push
136
- print("\n[4] Sending test FCM push notification...")
137
- push_result = send_test_push(token)
138
  test_push = push_result.get("test_push", "unknown")
139
  print(f" Result: {test_push}")
 
140
  if "SENT" in str(test_push):
141
- print("\n βœ… Test push SENT! Check your phone now.")
142
- print(" If you don't see a notification:")
143
- print(" β€’ Disable battery optimization for the app")
144
- print(" β€’ Make sure notifications are allowed for the app")
145
- print(" β€’ Try with app in background (not foreground)")
146
- elif "FAILED" in str(test_push):
147
- print("\n ❌ FCM send FAILED. Check HuggingFace Space logs for details.")
148
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
- time.sleep(3)
151
-
152
- # 5. Stress data trigger test
153
- print("\n[5] Sending severe stress sensor data as THIS user (ML β†’ FCM flow)...")
154
- try:
155
- readings = load_severe_data()
156
- except FileNotFoundError:
157
- print(f" ⚠️ {SEVERE_CSV} not found in {DATA_DIR}/ β€” skipping stress data step")
158
- print(" You can manually stress-test via the wearable or test_dry_run.py")
159
- print(" But make sure to use YOUR device API key (registered under this account).")
160
- print_summary()
161
- return
162
 
163
- alert_triggered = False
164
- for i, reading in enumerate(readings):
165
- result = send_sensor_reading(token, reading)
166
- ml = result.get("ml_prediction")
167
- if ml and isinstance(ml, dict) and ml.get("status") != "waiting":
168
- pred = ml.get("prediction", "?")
169
- conf = ml.get("confidence", 0)
170
- risk = ml.get("risk_level", -1)
171
- label = {"NORMAL": "[NORMAL]", "MILD_STRESS": "[MILD_STRESS]",
172
- "HIGH_STRESS": "[HIGH_STRESS] !!!"}.get(pred, f"[{pred}]")
173
- print(f" [{i+1:2d}/{len(readings)}] {label} conf={conf:.0%} risk={risk}")
174
- if risk >= 1:
175
- alert_triggered = True
176
- print(f" ↑ FCM push should fire NOW to ...{status.get('token_preview', '?')}")
177
- elif ml and isinstance(ml, dict):
178
- cnt = ml.get("readings_count", "?")
179
- print(f" [{i+1:2d}/{len(readings)}] waiting ({cnt}/25 readings)")
180
- time.sleep(0.3)
181
-
182
- print_summary(alert_triggered)
183
-
184
-
185
- def print_summary(alert_triggered=None):
186
  print("\n" + "="*60)
187
- print(" SUMMARY")
188
- print("="*60)
189
- if alert_triggered is True:
190
- print(" βœ… Stress detected. If you got no push notification:")
191
- print(" 1. Check phone notifications are allowed for the app")
192
- print(" 2. Check HuggingFace Space logs for FCM errors")
193
- print(" 3. The token may have rotated β€” log out/in on phone and retry")
194
- elif alert_triggered is False:
195
- print(" ⚠️ No stress alert triggered (not enough/wrong data)")
196
  print("="*60 + "\n")
197
 
198
 
 
1
  """
2
  FCM Diagnostic Test
3
  ====================
4
+ Uses device API key to check FCM status and send a test push notification.
5
+ No email/password needed.
6
 
7
  Usage:
8
  python test_fcm.py
9
  """
10
 
11
+ import csv
12
  import sys
13
  import io
 
 
14
  import time
15
+ import requests
16
 
17
  # Fix Windows console encoding
18
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
19
 
20
+ BASE_URL = "https://santa47-cbt-companion-api.hf.space"
21
+ DEVICE_API_KEY = "0c4358009dbe75db461b760222a6e15b"
22
 
23
+ DATA_DIR = "data"
 
 
 
 
 
24
  SEVERE_CSV = "sampled_data_DepressionLevel_Severe.csv"
25
 
26
+ DEVICE_HEADERS = {
27
+ "Content-Type": "application/json",
28
+ "X-Device-Key": DEVICE_API_KEY,
29
+ }
 
 
 
 
 
 
 
 
 
 
30
 
31
 
32
  def check_health():
33
+ return requests.get(f"{BASE_URL}/health", timeout=10).json()
 
34
 
35
 
36
+ def check_fcm_status(send_test=False):
37
+ url = f"{BASE_URL}/api/wearable/device/fcm-debug"
38
+ if send_test:
39
+ url += "?send_test=1"
40
+ return requests.get(url, headers=DEVICE_HEADERS, timeout=15).json()
 
 
 
 
 
 
 
 
 
 
 
41
 
42
 
43
  def load_severe_data():
 
54
  return readings
55
 
56
 
57
+ def send_sensor(reading):
58
+ return requests.post(
59
+ f"{BASE_URL}/api/wearable/device/data",
60
+ headers=DEVICE_HEADERS,
61
  json=reading,
62
  timeout=20,
63
+ ).json()
 
64
 
65
 
66
  def main():
67
  print("\n" + "="*60)
68
  print(" CBT COMPANION β€” FCM DIAGNOSTIC TEST")
69
+ print(f" Device key: {DEVICE_API_KEY[:8]}...")
70
  print("="*60)
71
 
72
+ # 1. Health
73
  print("\n[1] Health check...")
74
  health = check_health()
75
  print(f" Status: {health.get('status')}")
76
  print(f" ML model loaded: {health.get('ml_model_loaded')}")
77
+ print(f" FCM enabled: {health.get('fcm_enabled')}")
78
+ if not health.get("fcm_enabled"):
79
+ print("\n ❌ firebase-admin NOT loaded on server.")
80
+ print(" Check HuggingFace Space logs + FIREBASE_SERVICE_ACCOUNT secret.")
 
 
 
 
 
 
 
 
 
 
81
  return
82
 
83
+ # 2. FCM token check (no test push yet)
84
+ print("\n[2] Checking if device owner has FCM token in DB...")
85
+ status = check_fcm_status(send_test=False)
86
  print(f" FCM backend enabled: {status.get('fcm_backend_enabled')}")
87
  print(f" User has token: {status.get('user_has_token')}")
88
  print(f" Token preview: {status.get('token_preview', 'N/A')}")
89
 
90
  if not status.get("user_has_token"):
91
  print("\n ❌ No FCM token stored for this user!")
92
+ print(" β†’ Open the app on your phone and LOG IN.")
93
+ print(" The token uploads automatically on login.")
94
  print(" β†’ Then run this script again.")
 
95
  return
96
 
97
+ # 3. Test push
98
+ print("\n[3] Sending test FCM push notification...")
99
+ push_result = check_fcm_status(send_test=True)
100
  test_push = push_result.get("test_push", "unknown")
101
  print(f" Result: {test_push}")
102
+
103
  if "SENT" in str(test_push):
104
+ print("\n βœ… Test push SENT β€” check your phone NOW.")
105
+ print(" (It may take a few seconds to arrive)")
106
+ if not health.get("ml_model_loaded"):
107
+ print("\n ⚠️ ML model is NOT loaded β€” real alerts won't fire.")
108
+ print(" But the test push above confirms FCM itself is working.")
109
+ print(" The ML model file needs to be present on the server.")
110
+ else:
111
+ # 4. Real stress data
112
+ print("\n[4] Sending SEVERE stress sensor data to trigger ML β†’ FCM alert...")
113
+ try:
114
+ readings = load_severe_data()
115
+ except FileNotFoundError:
116
+ print(f" ⚠️ {SEVERE_CSV} not found in {DATA_DIR}/ β€” skipping")
117
+ print(" Run test_dry_run.py separately to trigger ML alerts.")
118
+ return
119
+
120
+ alert_seen = False
121
+ for i, reading in enumerate(readings):
122
+ result = send_sensor(reading)
123
+ ml = result.get("ml_prediction")
124
+ if ml and isinstance(ml, dict):
125
+ if ml.get("status") == "waiting":
126
+ cnt = ml.get("readings_count", "?")
127
+ print(f" [{i+1:2d}/{len(readings)}] waiting ({cnt}/25 readings)")
128
+ else:
129
+ pred = ml.get("prediction", "?")
130
+ conf = ml.get("confidence", 0)
131
+ risk = ml.get("risk_level", -1)
132
+ lbl = {"NORMAL": "[NORMAL]",
133
+ "MILD_STRESS": "[MILD_STRESS]",
134
+ "HIGH_STRESS": "[HIGH_STRESS] !!!"}.get(pred, pred)
135
+ print(f" [{i+1:2d}/{len(readings)}] {lbl} conf={conf:.0%} risk={risk}")
136
+ if risk >= 1:
137
+ alert_seen = True
138
+ print(f" ↑ FCM push should fire to ...{status.get('token_preview')}")
139
+ time.sleep(0.3)
140
+
141
+ if not alert_seen:
142
+ print("\n ⚠️ No stress detected in this data batch (model may need more readings).")
143
 
144
+ elif "FAILED" in str(test_push):
145
+ print("\n ❌ FCM send FAILED β€” check HuggingFace Space logs for the error message.")
146
+ print(" Common causes:")
147
+ print(" β€’ FIREBASE_SERVICE_ACCOUNT secret is malformed")
148
+ print(" β€’ FCM token is expired (log out and log back in on phone)")
149
+ print(" β€’ Wrong Firebase project")
 
 
 
 
 
 
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  print("\n" + "="*60)
152
+ print(" DONE")
 
 
 
 
 
 
 
 
153
  print("="*60 + "\n")
154
 
155