AliSakr9997 commited on
Commit
d4bef91
·
verified ·
1 Parent(s): cece072

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +48 -0
  2. .gitignore +19 -0
  3. UI/safespace/.dart_tool/chrome-device/Default/Accounts/Avatar Images/100679184552698402258 +3 -0
  4. UI/safespace/.dart_tool/chrome-device/Default/Affiliation Database +3 -0
  5. UI/safespace/.dart_tool/chrome-device/Default/Google Profile Picture.png +3 -0
  6. UI/safespace/.dart_tool/chrome-device/Default/History +3 -0
  7. UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422504525250674 +3 -0
  8. UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422506231685404 +3 -0
  9. UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422658855680597 +3 -0
  10. UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422667094602974 +3 -0
  11. UI/safespace/.dart_tool/chrome-device/Default/Shared Dictionary/cache/2bfff91f8dd9b032_0 +3 -0
  12. UI/safespace/.dart_tool/chrome-device/Default/Sync Data/Nigori.bin +3 -0
  13. UI/safespace/.dart_tool/chrome-device/Default/Web Data +3 -0
  14. UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log +3 -0
  15. UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000004.log +3 -0
  16. UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000005.ldb +3 -0
  17. UI/safespace/.dart_tool/chrome-device/Default/trusted_vault.pb +3 -0
  18. UI/safespace/.dart_tool/flutter_build/694c124069dece6f3ffa00a5aabab35e/app.dill +3 -0
  19. UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill +3 -0
  20. UI/safespace/build/flutter_assets/AssetManifest.bin +3 -0
  21. UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf +3 -0
  22. UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf +3 -0
  23. UI/safespace/build/test_cache/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill +3 -0
  24. UI/safespace/build/unit_test_assets/AssetManifest.bin +3 -0
  25. UI/safespace/build/unit_test_assets/NOTICES.Z +3 -0
  26. UI/safespace/build/unit_test_assets/fonts/MaterialIcons-Regular.otf +3 -0
  27. UI/safespace/build/unit_test_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf +3 -0
  28. UI/safespace/build/web/canvaskit/chromium/canvaskit.wasm +3 -0
  29. UI/safespace/build/web/canvaskit/skwasm.wasm +3 -0
  30. UI/safespace/build/web/canvaskit/skwasm_heavy.wasm +3 -0
  31. UI/safespace/build/web/canvaskit/wimp.wasm +3 -0
  32. UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +3 -0
  33. api.py +669 -0
  34. app.py +508 -0
  35. check_db.py +23 -0
  36. clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/History +3 -0
  37. clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/Web Data +3 -0
  38. clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log +3 -0
  39. clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/trusted_vault.pb +3 -0
  40. clean_git_repo/UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill +3 -0
  41. clean_git_repo/UI/safespace/build/flutter_assets/AssetManifest.bin +3 -0
  42. clean_git_repo/UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf +3 -0
  43. clean_git_repo/UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf +3 -0
  44. clean_git_repo/UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +3 -0
  45. clean_git_repo/data.csv +3 -0
  46. clean_git_repo/mental_model.h5 +3 -0
  47. clean_git_repo/mental_xlmr_final/label_encoder.pkl +3 -0
  48. clean_git_repo/mental_xlmr_final/tokenizer.json +3 -0
  49. clean_git_repo/mental_xlmr_final/training_args.bin +3 -0
  50. clean_git_repo/model_weights.pkl +3 -0
.gitattributes CHANGED
@@ -33,3 +33,51 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png filter=lfs diff=lfs merge=lfs -text
37
+ data.csv filter=lfs diff=lfs merge=lfs -text
38
+ mental_xlmr_final/tokenizer.json filter=lfs diff=lfs merge=lfs -text
39
+ clean_git_repo/mental_xlmr_final/tokenizer.json filter=lfs diff=lfs merge=lfs -text
40
+ clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/History filter=lfs diff=lfs merge=lfs -text
41
+ clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/Web[[:space:]]Data filter=lfs diff=lfs merge=lfs -text
42
+ clean_git_repo/UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill filter=lfs diff=lfs merge=lfs -text
43
+ clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log filter=lfs diff=lfs merge=lfs -text
44
+ clean_git_repo/UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf filter=lfs diff=lfs merge=lfs -text
45
+ clean_git_repo/UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf filter=lfs diff=lfs merge=lfs -text
46
+ clean_git_repo/UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png filter=lfs diff=lfs merge=lfs -text
47
+ temp_space/mental_xlmr_final/tokenizer.json filter=lfs diff=lfs merge=lfs -text
48
+ temp_space/clean_git_repo/mental_xlmr_final/tokenizer.json filter=lfs diff=lfs merge=lfs -text
49
+ temp_space/clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/History filter=lfs diff=lfs merge=lfs -text
50
+ temp_space/clean_git_repo/UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill filter=lfs diff=lfs merge=lfs -text
51
+ temp_space/clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/Web[[:space:]]Data filter=lfs diff=lfs merge=lfs -text
52
+ temp_space/clean_git_repo/UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf filter=lfs diff=lfs merge=lfs -text
53
+ temp_space/clean_git_repo/UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf filter=lfs diff=lfs merge=lfs -text
54
+ temp_space/UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill filter=lfs diff=lfs merge=lfs -text
55
+ temp_space/UI/safespace/.dart_tool/chrome-device/Default/History filter=lfs diff=lfs merge=lfs -text
56
+ temp_space/UI/safespace/.dart_tool/chrome-device/Default/Web[[:space:]]Data filter=lfs diff=lfs merge=lfs -text
57
+ temp_space/UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log filter=lfs diff=lfs merge=lfs -text
58
+ temp_space/clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log filter=lfs diff=lfs merge=lfs -text
59
+ temp_space/clean_git_repo/UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png filter=lfs diff=lfs merge=lfs -text
60
+ temp_space/UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf filter=lfs diff=lfs merge=lfs -text
61
+ temp_space/UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf filter=lfs diff=lfs merge=lfs -text
62
+ UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill filter=lfs diff=lfs merge=lfs -text
63
+ UI/safespace/.dart_tool/chrome-device/Default/Affiliation[[:space:]]Database filter=lfs diff=lfs merge=lfs -text
64
+ UI/safespace/.dart_tool/chrome-device/Default/Google[[:space:]]Profile[[:space:]]Picture.png filter=lfs diff=lfs merge=lfs -text
65
+ UI/safespace/.dart_tool/chrome-device/Default/History filter=lfs diff=lfs merge=lfs -text
66
+ UI/safespace/.dart_tool/chrome-device/Default/Web[[:space:]]Data filter=lfs diff=lfs merge=lfs -text
67
+ temp_space/UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png filter=lfs diff=lfs merge=lfs -text
68
+ UI/safespace/.dart_tool/chrome-device/Default/Shared[[:space:]]Dictionary/cache/2bfff91f8dd9b032_0 filter=lfs diff=lfs merge=lfs -text
69
+ UI/safespace/.dart_tool/flutter_build/694c124069dece6f3ffa00a5aabab35e/app.dill filter=lfs diff=lfs merge=lfs -text
70
+ UI/safespace/build/unit_test_assets/NOTICES.Z filter=lfs diff=lfs merge=lfs -text
71
+ UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf filter=lfs diff=lfs merge=lfs -text
72
+ UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf filter=lfs diff=lfs merge=lfs -text
73
+ UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422504525250674 filter=lfs diff=lfs merge=lfs -text
74
+ UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422506231685404 filter=lfs diff=lfs merge=lfs -text
75
+ UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422658855680597 filter=lfs diff=lfs merge=lfs -text
76
+ UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422667094602974 filter=lfs diff=lfs merge=lfs -text
77
+ UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log filter=lfs diff=lfs merge=lfs -text
78
+ UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000004.log filter=lfs diff=lfs merge=lfs -text
79
+ UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000005.ldb filter=lfs diff=lfs merge=lfs -text
80
+ UI/safespace/.dart_tool/chrome-device/Default/Accounts/Avatar[[:space:]]Images/100679184552698402258 filter=lfs diff=lfs merge=lfs -text
81
+ UI/safespace/build/unit_test_assets/fonts/MaterialIcons-Regular.otf filter=lfs diff=lfs merge=lfs -text
82
+ UI/safespace/build/unit_test_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf filter=lfs diff=lfs merge=lfs -text
83
+ UI/safespace/build/test_cache/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ push_to_space.py
2
+ temp_space/
3
+ temp_hf_space/
4
+ scrub_rules.txt
5
+ .ipynb_checkpoints/
6
+ *.ipynb
7
+ repo.zip
8
+ data.csv
9
+ *.h5
10
+ *.pkl
11
+ mental_xlmr_final/
12
+ UI/safespace/build/
13
+ UI/safespace/.dart_tool/
14
+ UI/safespace/.pub-cache/
15
+ UI/safespace/.flutter-plugins
16
+ UI/safespace/.flutter-plugins-dependencies
17
+ UI/safespace/node_modules/
18
+ *.log
19
+ .DS_Store
UI/safespace/.dart_tool/chrome-device/Default/Accounts/Avatar Images/100679184552698402258 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0f2264d18025c482b6a86e40d5d048210837422c81b488022c5b77701234b7e6
3
+ size 109914
UI/safespace/.dart_tool/chrome-device/Default/Affiliation Database ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:970ee31de145a72835692447fe9f172c59443c794566dbc595ae16f0c30b6c39
3
+ size 102400
UI/safespace/.dart_tool/chrome-device/Default/Google Profile Picture.png ADDED

Git LFS Details

  • SHA256: 0f2264d18025c482b6a86e40d5d048210837422c81b488022c5b77701234b7e6
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
UI/safespace/.dart_tool/chrome-device/Default/History ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:83e3f671cea193224794e68f6397cfbdcfe7299614f4927e275ba9a284c01c52
3
+ size 294912
UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422504525250674 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3190858d674bf992e4bf326536b650fddbf4b368e50e6715039c8ae1647a7187
3
+ size 145695
UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422506231685404 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3bafa05f042888995de97c0169e3cbaca763be8104f86b2aefc48a2350e4783b
3
+ size 103968
UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422658855680597 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a04b79669055015fa2ed535518c6f99625ca4be895abb7b493e77bbe38d5689
3
+ size 147567
UI/safespace/.dart_tool/chrome-device/Default/Sessions/Session_13422667094602974 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0d25e50423a6e1f42667d70bdae7d8bf81ffd7ef9d50a8444a37e5862122dd7e
3
+ size 192253
UI/safespace/.dart_tool/chrome-device/Default/Shared Dictionary/cache/2bfff91f8dd9b032_0 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:664344ef4ca53efd67808330605a1cb522258b234ec112d03ef10e3b938073bf
3
+ size 198743
UI/safespace/.dart_tool/chrome-device/Default/Sync Data/Nigori.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:303a5a630227f630b5704f2289d0469b6c415e4adec542b08e62288fcf15acea
3
+ size 990
UI/safespace/.dart_tool/chrome-device/Default/Web Data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1572acaab352554c98b7cb1765c5a92f838af0da6a3972dc196ecdc4b9fba844
3
+ size 196608
UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3632466e1e4586ea4ac9a782dbc2fe30bf25db8cd6019be0e705ddc1ef22e851
3
+ size 437109
UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000004.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c98aca056f3ce1e53e1fd16adb35fb77e9c8376ba3be69a20289942876107bb
3
+ size 352170
UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000005.ldb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a8bbd528dee536c8e86216027ae360a15d6ee05d745da3936fc3360d0429b5e8
3
+ size 133527
UI/safespace/.dart_tool/chrome-device/Default/trusted_vault.pb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b87bf305aafae1f1ec7dd947176c9529b2a9cbd4a3bf296fce25fc009e725366
3
+ size 84
UI/safespace/.dart_tool/flutter_build/694c124069dece6f3ffa00a5aabab35e/app.dill ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e86b5ab568a496d761eedcb4975ead5f277a06bd1c5d652968b2912c9d0b79ae
3
+ size 31744696
UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5a39ea79ca44e7928c9a077f63e6c575bd8d1d33efc85fa4a7d9bce7c6da0283
3
+ size 50726856
UI/safespace/build/flutter_assets/AssetManifest.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b49a2913e16c75a773c3b3952a15cb6e9ce5df0a46efa5df5f48d870e284e69
3
+ size 27031
UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d9865b671a09d683d13a863089d8825e0f61a37696ce5d7d448bc8023aa62453
3
+ size 1645184
UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67c44fe9183b002e79dde7f6977e2988661c9a3e4a3c5fce968787efdbed823c
3
+ size 257628
UI/safespace/build/test_cache/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6a2e8b59bc8fbd6dd9a3dbcae74342f7c4afcb8853b54ef3d50fc1ed821df7f3
3
+ size 48994592
UI/safespace/build/unit_test_assets/AssetManifest.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b49a2913e16c75a773c3b3952a15cb6e9ce5df0a46efa5df5f48d870e284e69
3
+ size 27031
UI/safespace/build/unit_test_assets/NOTICES.Z ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:30a3990f4698bce1c4e14242e6f53a47a356585a20d434e1f52378f61682b113
3
+ size 105255
UI/safespace/build/unit_test_assets/fonts/MaterialIcons-Regular.otf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d9865b671a09d683d13a863089d8825e0f61a37696ce5d7d448bc8023aa62453
3
+ size 1645184
UI/safespace/build/unit_test_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67c44fe9183b002e79dde7f6977e2988661c9a3e4a3c5fce968787efdbed823c
3
+ size 257628
UI/safespace/build/web/canvaskit/chromium/canvaskit.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f6c67bf7989fcb1b69a3203617882ac0d1541cc79e38f81c3f5d63ab4d73de33
3
+ size 5686836
UI/safespace/build/web/canvaskit/skwasm.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:461a759c657738e573f1c2f69e26489197e43db1bf6ec1e2c3b44c6721314cd1
3
+ size 3549758
UI/safespace/build/web/canvaskit/skwasm_heavy.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4b0028e38d865b4673ec38c0c673299dff6b00d6cd8c5e53dda9f800f7bbdda2
3
+ size 5140163
UI/safespace/build/web/canvaskit/wimp.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9e168fe6556e09414e9ea58badaf13c54b630de13a6e03f92444340b5bec2fd
3
+ size 3461867
UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png ADDED

Git LFS Details

  • SHA256: 6232e5815af17e25e0268b2fec7aea9e068cc92ec709e9605c2b31df4ff2a313
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
api.py ADDED
@@ -0,0 +1,669 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import datetime
3
+ import hashlib
4
+ import logging
5
+ import time
6
+
7
+ import httpx
8
+ from fastapi import FastAPI, HTTPException, Depends, Request
9
+ from fastapi.responses import HTMLResponse
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+ from pydantic import BaseModel, Field
12
+ from typing import Optional
13
+
14
+ from core_ai import predict_text, predict_survey, fuse_scores
15
+ from recommendations import get_recommendations
16
+
17
+ # --- DATABASE SETUP ---
18
+ from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime, JSON, Text, Boolean, Index
19
+ from sqlalchemy.orm import declarative_base, sessionmaker, Session
20
+
21
+ DATABASE_URL = os.environ.get("DATABASE_URL")
22
+ if DATABASE_URL and DATABASE_URL.startswith("postgres://"):
23
+ DATABASE_URL = DATABASE_URL.replace("postgres://", "postgresql://", 1)
24
+
25
+ engine = create_engine(DATABASE_URL, connect_args={'connect_timeout': 5}, pool_pre_ping=True) if DATABASE_URL else None
26
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) if engine else None
27
+ Base = declarative_base()
28
+
29
+ logging.basicConfig(level=logging.INFO)
30
+ logger = logging.getLogger("safespace.api")
31
+
32
+
33
+ class DBUser(Base):
34
+ __tablename__ = "users"
35
+ id = Column(Integer, primary_key=True, index=True)
36
+ name = Column(String, nullable=True)
37
+ email = Column(String, unique=True, index=True)
38
+ password = Column(String)
39
+ created_at = Column(DateTime, default=datetime.utcnow)
40
+
41
+
42
+ class DBAnalysis(Base):
43
+ __tablename__ = "analyses"
44
+ id = Column(Integer, primary_key=True, index=True)
45
+ user_id = Column(Integer, index=True, nullable=True)
46
+ primary_condition = Column(String)
47
+ clinical_scoring = Column(JSON)
48
+ created_at = Column(DateTime, default=datetime.utcnow)
49
+ text_input = Column(Text, nullable=True)
50
+ text_input_hash = Column(Text, nullable=True)
51
+ text_scores = Column(JSON, nullable=True)
52
+ survey_scores = Column(JSON, nullable=True)
53
+ fused_scores = Column(JSON, nullable=True)
54
+ severity = Column(Text, nullable=True)
55
+ cause = Column(Text, nullable=True)
56
+ suicidal_flag = Column(Boolean, default=False)
57
+ model_version = Column(Text, nullable=True)
58
+ app_version = Column(Text, nullable=True)
59
+ locale = Column(Text, nullable=True)
60
+
61
+
62
+ Index("ix_analyses_user_id_created_at", DBAnalysis.user_id, DBAnalysis.created_at)
63
+
64
+
65
+ class DBCheckin(Base):
66
+ __tablename__ = "checkins"
67
+ id = Column(Integer, primary_key=True, index=True)
68
+ user_id = Column(Integer, index=True, nullable=False)
69
+ mood = Column(Integer, nullable=False)
70
+ sleep = Column(Integer, nullable=False)
71
+ energy = Column(Float, nullable=False)
72
+ created_at = Column(DateTime, default=datetime.utcnow)
73
+
74
+
75
+ Index("ix_checkins_user_id_created_at", DBCheckin.user_id, DBCheckin.created_at)
76
+
77
+
78
+ class DBJournalEntry(Base):
79
+ __tablename__ = "journal_entries"
80
+ id = Column(Integer, primary_key=True, index=True)
81
+ user_id = Column(Integer, index=True, nullable=True)
82
+ content = Column(Text, nullable=False)
83
+ created_at = Column(DateTime, default=datetime.utcnow)
84
+ updated_at = Column(DateTime, nullable=True)
85
+
86
+ # --- APP SETUP ---
87
+ app = FastAPI(title="SafeSpace API", version="1.0.0")
88
+
89
+
90
+ @app.middleware("http")
91
+ async def log_requests(request: Request, call_next):
92
+ start_time = time.time()
93
+ response = await call_next(request)
94
+ duration_ms = int((time.time() - start_time) * 1000)
95
+ logger.info(
96
+ "%s %s %s %sms",
97
+ request.method,
98
+ request.url.path,
99
+ response.status_code,
100
+ duration_ms,
101
+ )
102
+ return response
103
+
104
+
105
+ @app.on_event("startup")
106
+ async def startup_event():
107
+ import asyncio
108
+ if engine:
109
+ try:
110
+ await asyncio.wait_for(
111
+ asyncio.to_thread(Base.metadata.create_all, bind=engine),
112
+ timeout=8.0
113
+ )
114
+ logger.info("Database connected and tables verified.")
115
+ except asyncio.TimeoutError:
116
+ logger.warning("Database connection timed out during startup - server will start without DB verification.")
117
+ except Exception as e:
118
+ logger.exception("Database connection failed during startup: %s", e)
119
+ logger.info("Application startup complete.")
120
+
121
+
122
+ def get_db():
123
+ if not SessionLocal:
124
+ yield None
125
+ else:
126
+ db = SessionLocal()
127
+ try:
128
+ yield db
129
+ finally:
130
+ db.close()
131
+
132
+ # Add CORS so Flutter app can communicate with it
133
+ app.add_middleware(
134
+ CORSMiddleware,
135
+ allow_origins=["*"],
136
+ allow_credentials=True,
137
+ allow_methods=["*"],
138
+ allow_headers=["*"],
139
+ )
140
+
141
+ # --- Password Hashing ---
142
+ def hash_password(password: str) -> str:
143
+ return hashlib.sha256(password.encode()).hexdigest()
144
+
145
+ # --- DASS-42 Clinical Scoring ---
146
+ def calculate_dass_clinical_score(answers: list) -> dict:
147
+ dep_idx = [2, 4, 9, 12, 15, 16, 20, 23, 25, 30, 33, 36, 37, 41]
148
+ anx_idx = [1, 3, 6, 8, 14, 18, 19, 22, 24, 27, 29, 35, 39, 40]
149
+ str_idx = [0, 5, 7, 10, 11, 13, 17, 21, 26, 28, 31, 32, 34, 38]
150
+
151
+ dep_score = sum(answers[i] for i in dep_idx)
152
+ anx_score = sum(answers[i] for i in anx_idx)
153
+ str_score = sum(answers[i] for i in str_idx)
154
+
155
+ def get_severity(score, bounds):
156
+ if score <= bounds[0]: return "Normal"
157
+ if score <= bounds[1]: return "Mild"
158
+ if score <= bounds[2]: return "Moderate"
159
+ if score <= bounds[3]: return "Severe"
160
+ return "Extremely Severe"
161
+
162
+ return {
163
+ "depression": {"score": dep_score, "severity": get_severity(dep_score, [9, 13, 20, 27])},
164
+ "anxiety": {"score": anx_score, "severity": get_severity(anx_score, [7, 9, 14, 19])},
165
+ "stress": {"score": str_score, "severity": get_severity(str_score, [14, 18, 25, 33])}
166
+ }
167
+
168
+ # --- API MODELS ---
169
+ class AnalysisRequest(BaseModel):
170
+ user_id: str | int = Field(default=None, description="User identifier")
171
+ text: str = Field(..., min_length=1)
172
+ survey_answers: list[int] = Field(..., min_items=42, max_items=42)
173
+ locale: str = Field(default="en")
174
+ client_ts: str | None = None
175
+
176
+
177
+ class AnalyzeRequest(BaseModel):
178
+ text: str = Field(..., description="The user's response in text (Arabic/English)")
179
+ survey_answers: list[int] = Field(..., min_items=42, max_items=42, description="List of 42 integers (0-4) representing DASS-42 survey answers")
180
+ user_id: int | None = Field(default=None, description="Optional user ID to link analysis to a user")
181
+ locale: str = Field(default="en")
182
+ client_ts: str | None = None
183
+ app_version: str | None = None
184
+ model_version: str | None = None
185
+
186
+
187
+ class ChatRequest(BaseModel):
188
+ message: str
189
+ session_id: Optional[str] = "default"
190
+
191
+
192
+ class ChatResponse(BaseModel):
193
+ reply: str
194
+
195
+
196
+ class SignupRequest(BaseModel):
197
+ name: str = Field(..., min_length=1)
198
+ email: str = Field(..., min_length=5)
199
+ password: str = Field(..., min_length=4)
200
+
201
+
202
+ class LoginRequest(BaseModel):
203
+ email: str = Field(..., min_length=5)
204
+ password: str = Field(..., min_length=1)
205
+
206
+
207
+ class CheckinRequest(BaseModel):
208
+ mood: int = Field(..., ge=0, le=10)
209
+ sleep: int = Field(..., ge=0, le=10)
210
+ energy: float = Field(..., ge=0, le=10)
211
+ user_id: int | None = Field(default=None, description="Optional user ID to link check-in to a user")
212
+ client_ts: str | None = None
213
+
214
+
215
+ class JournalEntryRequest(BaseModel):
216
+ content: str = Field(..., min_length=1)
217
+ user_id: int | None = Field(default=None, description="Optional user ID to link journal entry to a user")
218
+ client_ts: str | None = None
219
+
220
+
221
+ class JournalEntryUpdateRequest(BaseModel):
222
+ content: str = Field(..., min_length=1)
223
+
224
+
225
+ # --- ENDPOINTS ---
226
+ @app.get("/")
227
+ def root():
228
+ return {"status": "ok", "message": "SafeSpace API"}
229
+
230
+
231
+ @app.get("/test", response_class=HTMLResponse)
232
+ def test_page():
233
+ html_path = os.path.join(os.path.dirname(__file__), "index.html")
234
+ if not os.path.exists(html_path):
235
+ raise HTTPException(status_code=404, detail="index.html not found")
236
+ with open(html_path, "r", encoding="utf-8") as f:
237
+ return f.read()
238
+
239
+
240
+ # --- AUTH ENDPOINTS ---
241
+ @app.post("/api/v1/auth/signup")
242
+ async def signup(request: SignupRequest, db: Session = Depends(get_db)):
243
+ if not db:
244
+ raise HTTPException(status_code=500, detail="Database not available")
245
+
246
+ # Check if email already exists
247
+ existing = db.query(DBUser).filter(DBUser.email == request.email).first()
248
+ if existing:
249
+ raise HTTPException(status_code=400, detail="Email already registered")
250
+
251
+ # Create new user
252
+ try:
253
+ new_user = DBUser(
254
+ name=request.name,
255
+ email=request.email,
256
+ password=hash_password(request.password),
257
+ )
258
+ db.add(new_user)
259
+ db.commit()
260
+ db.refresh(new_user)
261
+
262
+ return {
263
+ "user_id": new_user.id,
264
+ "email": new_user.email,
265
+ "name": new_user.name,
266
+ "message": "Account created successfully"
267
+ }
268
+ except Exception as e:
269
+ db.rollback()
270
+ raise HTTPException(status_code=500, detail=f"Failed to create account: {str(e)}")
271
+
272
+
273
+ @app.post("/api/v1/auth/login")
274
+ async def login(request: LoginRequest, db: Session = Depends(get_db)):
275
+ if not db:
276
+ raise HTTPException(status_code=500, detail="Database not available")
277
+
278
+ user = db.query(DBUser).filter(DBUser.email == request.email).first()
279
+ if not user:
280
+ raise HTTPException(status_code=401, detail="Email not found")
281
+
282
+ if user.password != hash_password(request.password):
283
+ # Also try plain-text match for legacy users who signed up before hashing
284
+ if user.password != request.password:
285
+ raise HTTPException(status_code=401, detail="Incorrect password")
286
+
287
+ return {
288
+ "user_id": user.id,
289
+ "email": user.email,
290
+ "name": user.name or "",
291
+ "message": "Login successful"
292
+ }
293
+
294
+
295
+ # New-style endpoint (used by index.html test page)
296
+ @app.post("/v1/analysis")
297
+ def analyze(payload: AnalysisRequest, db: Session = Depends(get_db)):
298
+ # Shift 0-3 UI scale to 1-4 for the AI model (trained on data.csv)
299
+ shifted_answers = [a + 1 for a in payload.survey_answers]
300
+ text_scores = predict_text(payload.text)
301
+ survey_scores = predict_survey(shifted_answers)
302
+
303
+ final_scores = fuse_scores(text_scores, survey_scores)
304
+ primary = max(final_scores, key=final_scores.get)
305
+ clinical = calculate_dass_clinical_score(payload.survey_answers)
306
+ rec = get_recommendations(primary, final_scores[primary], payload.text)
307
+ created_at_dt = datetime.utcnow()
308
+ if payload.client_ts:
309
+ try:
310
+ created_at_dt = datetime.fromisoformat(payload.client_ts.replace("Z", "+00:00")).replace(tzinfo=None)
311
+ except ValueError:
312
+ pass
313
+ created_at = created_at_dt.isoformat() + "Z"
314
+
315
+ # Save to PostgreSQL if DB is connected
316
+ if db:
317
+ try:
318
+ text_input_hash = hashlib.sha256(payload.text.encode()).hexdigest()
319
+ new_analysis = DBAnalysis(
320
+ user_id=payload.user_id,
321
+ primary_condition=primary,
322
+ clinical_scoring=clinical,
323
+ created_at=created_at_dt,
324
+ text_input=payload.text,
325
+ text_input_hash=text_input_hash,
326
+ text_scores=text_scores,
327
+ survey_scores=survey_scores,
328
+ fused_scores=final_scores,
329
+ severity=rec.get("severity"),
330
+ cause=rec.get("cause"),
331
+ suicidal_flag=rec.get("suicidal_flag", False),
332
+ model_version=None,
333
+ app_version=None,
334
+ locale=payload.locale,
335
+ )
336
+ db.add(new_analysis)
337
+ db.commit()
338
+ except Exception as e:
339
+ logger.exception("DB save error: %s", e)
340
+
341
+ return {
342
+ "analysis_id": None,
343
+ "primary_condition": primary,
344
+ "fused_scores": final_scores,
345
+ "text_scores": text_scores,
346
+ "survey_scores": survey_scores,
347
+ "clinical_scoring": clinical,
348
+ "severity": rec.get("severity"),
349
+ "cause": rec.get("cause"),
350
+ "recommendations": {
351
+ "tips_en": rec.get("tips_en", []),
352
+ "tips_ar": rec.get("tips_ar", []),
353
+ "resources_en": rec.get("resources_en", []),
354
+ "resources_ar": rec.get("resources_ar", []),
355
+ "referral_en": rec.get("referral_en", ""),
356
+ "referral_ar": rec.get("referral_ar", ""),
357
+ },
358
+ "suicidal_flag": rec.get("suicidal_flag", False),
359
+ "created_at": created_at,
360
+ }
361
+
362
+ # Flutter-compatible endpoint (used by api_service.dart)
363
+ @app.post("/api/v1/analyze")
364
+ async def analyze_mental_health(request: AnalyzeRequest, db: Session = Depends(get_db)):
365
+ try:
366
+ start_time = time.time()
367
+ # Shift 0-3 UI scale to 1-4 for the AI model (trained on data.csv)
368
+ shifted_answers = [a + 1 for a in request.survey_answers]
369
+ text_scores = predict_text(request.text)
370
+ survey_scores = predict_survey(shifted_answers)
371
+
372
+ final_scores = fuse_scores(text_scores, survey_scores)
373
+ primary = max(final_scores, key=final_scores.get)
374
+ clinical = calculate_dass_clinical_score(request.survey_answers)
375
+ rec = get_recommendations(primary, final_scores[primary], request.text)
376
+ created_at_dt = datetime.utcnow()
377
+ if request.client_ts:
378
+ try:
379
+ created_at_dt = datetime.fromisoformat(request.client_ts.replace("Z", "+00:00")).replace(tzinfo=None)
380
+ except ValueError:
381
+ pass
382
+ created_at = created_at_dt.isoformat() + "Z"
383
+
384
+ # Save to PostgreSQL if DB is connected
385
+ if db:
386
+ try:
387
+ text_input_hash = hashlib.sha256(request.text.encode()).hexdigest()
388
+ new_analysis = DBAnalysis(
389
+ user_id=request.user_id,
390
+ primary_condition=primary,
391
+ clinical_scoring=clinical,
392
+ created_at=created_at_dt,
393
+ text_input=request.text,
394
+ text_input_hash=text_input_hash,
395
+ text_scores=text_scores,
396
+ survey_scores=survey_scores,
397
+ fused_scores=final_scores,
398
+ severity=rec.get("severity"),
399
+ cause=rec.get("cause"),
400
+ suicidal_flag=rec.get("suicidal_flag", False),
401
+ model_version=request.model_version,
402
+ app_version=request.app_version,
403
+ locale=request.locale,
404
+ )
405
+ db.add(new_analysis)
406
+ db.commit()
407
+ except Exception as e:
408
+ logger.exception("DB save error: %s", e)
409
+
410
+ return {
411
+ "analysis_id": None,
412
+ "primary_condition": primary,
413
+ "fused_scores": final_scores,
414
+ "text_scores": text_scores,
415
+ "survey_scores": survey_scores,
416
+ "clinical_scoring": clinical,
417
+ "severity": rec.get("severity"),
418
+ "cause": rec.get("cause"),
419
+ "recommendations": {
420
+ "tips_en": rec.get("tips_en", []),
421
+ "tips_ar": rec.get("tips_ar", []),
422
+ "resources_en": rec.get("resources_en", []),
423
+ "resources_ar": rec.get("resources_ar", []),
424
+ "referral_en": rec.get("referral_en", ""),
425
+ "referral_ar": rec.get("referral_ar", ""),
426
+ },
427
+ "suicidal_flag": rec.get("suicidal_flag", False),
428
+ "created_at": created_at,
429
+ "duration_ms": int((time.time() - start_time) * 1000),
430
+ }
431
+ except Exception as e:
432
+ logger.exception("Analyze request failed: %s", e)
433
+ raise HTTPException(status_code=500, detail="Failed to analyze")
434
+
435
+ # Flutter-compatible history endpoint
436
+ @app.get("/api/v1/analyses/history")
437
+ async def get_analyses_history(user_id: int = None, db: Session = Depends(get_db)):
438
+ try:
439
+ if not db:
440
+ return []
441
+
442
+ query = db.query(DBAnalysis)
443
+
444
+ # Filter by user_id if provided
445
+ if user_id is not None:
446
+ query = query.filter(DBAnalysis.user_id == user_id)
447
+
448
+ # Get the 10 most recent analyses, sorted by created_at ascending (oldest first for graphing)
449
+ records = query.order_by(DBAnalysis.created_at.desc()).limit(10).all()
450
+
451
+ history = []
452
+ for r in reversed(records): # Reverse so oldest is first
453
+ if r.clinical_scoring:
454
+ history.append({
455
+ "id": r.id,
456
+ "date": r.created_at.strftime("%b %d"),
457
+ "depression": r.clinical_scoring.get("depression", {}).get("score", 0),
458
+ "anxiety": r.clinical_scoring.get("anxiety", {}).get("score", 0),
459
+ "stress": r.clinical_scoring.get("stress", {}).get("score", 0),
460
+ "primary": r.primary_condition
461
+ })
462
+ return history
463
+ except Exception as e:
464
+ logger.exception("Analyze request failed: %s", e)
465
+ raise HTTPException(status_code=500, detail="Failed to analyze")
466
+
467
+ @app.post("/api/v1/chat", response_model=ChatResponse)
468
+ async def chat_with_ai(request: ChatRequest):
469
+ api_url = os.environ.get("AI_API_URL")
470
+ api_key = os.environ.get("AI_API_KEY")
471
+ chatflow_id = os.environ.get("AI_CHATFLOW_ID")
472
+
473
+ if not api_url or not api_key or not chatflow_id:
474
+ raise HTTPException(status_code=500, detail="AI API credentials are not configured in Secrets.")
475
+
476
+ endpoint = f"{api_url}/api/v1/prediction/{chatflow_id}"
477
+ headers = {"Authorization": f"Bearer {api_key}"}
478
+ payload = {"question": request.message, "overrideConfig": {"sessionId": request.session_id}}
479
+
480
+ async with httpx.AsyncClient() as client:
481
+ try:
482
+ response = await client.post(endpoint, json=payload, headers=headers, timeout=30.0)
483
+ response.raise_for_status()
484
+ data = response.json()
485
+ return ChatResponse(reply=data.get("text") or data.get("answer") or str(data))
486
+ except Exception as e:
487
+ raise HTTPException(status_code=502, detail=f"Failed to communicate with AI API: {str(e)}")
488
+
489
+
490
+ @app.post("/api/v1/checkin")
491
+ async def create_checkin(request: CheckinRequest, db: Session = Depends(get_db)):
492
+ if not db:
493
+ raise HTTPException(status_code=500, detail="Database not available")
494
+
495
+ if request.user_id is None:
496
+ raise HTTPException(status_code=400, detail="user_id is required")
497
+
498
+ created_at = datetime.utcnow()
499
+ if request.client_ts:
500
+ try:
501
+ created_at = datetime.fromisoformat(request.client_ts.replace("Z", "+00:00")).replace(tzinfo=None)
502
+ except ValueError:
503
+ pass
504
+
505
+ try:
506
+ new_checkin = DBCheckin(
507
+ user_id=request.user_id,
508
+ mood=request.mood,
509
+ sleep=request.sleep,
510
+ energy=request.energy,
511
+ created_at=created_at,
512
+ )
513
+ db.add(new_checkin)
514
+ db.commit()
515
+ db.refresh(new_checkin)
516
+
517
+ return {
518
+ "id": new_checkin.id,
519
+ "user_id": new_checkin.user_id,
520
+ "mood": new_checkin.mood,
521
+ "sleep": new_checkin.sleep,
522
+ "energy": new_checkin.energy,
523
+ "created_at": new_checkin.created_at.isoformat() + "Z",
524
+ }
525
+ except Exception as e:
526
+ db.rollback()
527
+ logger.exception("Failed to save check-in: %s", e)
528
+ raise HTTPException(status_code=500, detail="Failed to save check-in")
529
+
530
+
531
+ @app.get("/api/v1/checkin/history")
532
+ async def get_checkin_history(user_id: int | None = None, db: Session = Depends(get_db)):
533
+ try:
534
+ if not db:
535
+ return []
536
+
537
+ query = db.query(DBCheckin)
538
+ if user_id is not None:
539
+ query = query.filter(DBCheckin.user_id == user_id)
540
+
541
+ records = query.order_by(DBCheckin.created_at.desc()).limit(30).all()
542
+ return [
543
+ {
544
+ "id": r.id,
545
+ "user_id": r.user_id,
546
+ "mood": r.mood,
547
+ "sleep": r.sleep,
548
+ "energy": r.energy,
549
+ "created_at": r.created_at.isoformat() + "Z",
550
+ }
551
+ for r in records
552
+ ]
553
+ except Exception as e:
554
+ logger.exception("Failed to fetch check-in history: %s", e)
555
+ raise HTTPException(status_code=500, detail="Failed to fetch history")
556
+
557
+
558
+ @app.post("/api/v1/journal")
559
+ async def create_journal_entry(request: JournalEntryRequest, db: Session = Depends(get_db)):
560
+ if not db:
561
+ raise HTTPException(status_code=500, detail="Database not available")
562
+
563
+ if request.user_id is None:
564
+ raise HTTPException(status_code=400, detail="user_id is required")
565
+
566
+ created_at = datetime.utcnow()
567
+ if request.client_ts:
568
+ try:
569
+ created_at = datetime.fromisoformat(request.client_ts.replace("Z", "+00:00")).replace(tzinfo=None)
570
+ except ValueError:
571
+ pass
572
+
573
+ try:
574
+ entry = DBJournalEntry(
575
+ user_id=request.user_id,
576
+ content=request.content,
577
+ created_at=created_at,
578
+ )
579
+ db.add(entry)
580
+ db.commit()
581
+ db.refresh(entry)
582
+
583
+ return {
584
+ "id": entry.id,
585
+ "user_id": entry.user_id,
586
+ "content": entry.content,
587
+ "created_at": entry.created_at.isoformat() + "Z",
588
+ }
589
+ except Exception as e:
590
+ db.rollback()
591
+ logger.exception("Failed to save journal entry: %s", e)
592
+ raise HTTPException(status_code=500, detail="Failed to save journal entry")
593
+
594
+
595
+ @app.get("/api/v1/journal/history")
596
+ async def get_journal_history(user_id: int | None = None, db: Session = Depends(get_db)):
597
+ try:
598
+ if not db:
599
+ return []
600
+
601
+ query = db.query(DBJournalEntry)
602
+ if user_id is not None:
603
+ query = query.filter(DBJournalEntry.user_id == user_id)
604
+
605
+ records = query.order_by(DBJournalEntry.created_at.desc()).limit(50).all()
606
+ return [
607
+ {
608
+ "id": r.id,
609
+ "user_id": r.user_id,
610
+ "content": r.content,
611
+ "created_at": r.created_at.isoformat() + "Z",
612
+ }
613
+ for r in records
614
+ ]
615
+ except Exception as e:
616
+ logger.exception("Failed to fetch journal history: %s", e)
617
+ raise HTTPException(status_code=500, detail="Failed to fetch journal history")
618
+
619
+
620
+ @app.put("/api/v1/journal/{entry_id}")
621
+ async def update_journal_entry(entry_id: int, request: JournalEntryUpdateRequest, db: Session = Depends(get_db)):
622
+ if not db:
623
+ raise HTTPException(status_code=500, detail="Database not available")
624
+
625
+ try:
626
+ entry = db.query(DBJournalEntry).filter(DBJournalEntry.id == entry_id).first()
627
+ if not entry:
628
+ raise HTTPException(status_code=404, detail="Journal entry not found")
629
+
630
+ entry.content = request.content
631
+ entry.updated_at = datetime.utcnow()
632
+ db.add(entry)
633
+ db.commit()
634
+ db.refresh(entry)
635
+
636
+ return {
637
+ "id": entry.id,
638
+ "user_id": entry.user_id,
639
+ "content": entry.content,
640
+ "created_at": entry.created_at.isoformat() + "Z",
641
+ "updated_at": entry.updated_at.isoformat() + "Z",
642
+ }
643
+ except HTTPException:
644
+ raise
645
+ except Exception as e:
646
+ db.rollback()
647
+ logger.exception("Failed to update journal entry: %s", e)
648
+ raise HTTPException(status_code=500, detail="Failed to update journal entry")
649
+
650
+
651
+ @app.delete("/api/v1/journal/{entry_id}")
652
+ async def delete_journal_entry(entry_id: int, db: Session = Depends(get_db)):
653
+ if not db:
654
+ raise HTTPException(status_code=500, detail="Database not available")
655
+
656
+ try:
657
+ entry = db.query(DBJournalEntry).filter(DBJournalEntry.id == entry_id).first()
658
+ if not entry:
659
+ raise HTTPException(status_code=404, detail="Journal entry not found")
660
+
661
+ db.delete(entry)
662
+ db.commit()
663
+ return {"status": "deleted", "id": entry_id}
664
+ except HTTPException:
665
+ raise
666
+ except Exception as e:
667
+ db.rollback()
668
+ logger.exception("Failed to delete journal entry: %s", e)
669
+ raise HTTPException(status_code=500, detail="Failed to delete journal entry")
app.py ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py
3
+ ======
4
+ Mental Health AI — Full Pipeline
5
+ Files needed:
6
+ mental_xlmr_final/ ← XLM-R model folder
7
+ mental_model.h5 ← Survey Keras model
8
+ scaler.pkl ← Survey scaler
9
+ recommendations.py ← same directory
10
+
11
+ Install:
12
+ pip install streamlit transformers torch tensorflow scikit-learn deep-translator
13
+ """
14
+ import sys, os
15
+ sys.path.append(os.path.dirname(__file__))
16
+ import re, pickle, warnings
17
+ import numpy as np
18
+ import streamlit as st
19
+ import torch
20
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
21
+ from deep_translator import GoogleTranslator
22
+ sys.path.insert(0, os.path.dirname(__file__))
23
+ from recommendations import get_recommendations
24
+
25
+ warnings.filterwarnings("ignore")
26
+
27
+ # ── PAGE CONFIG ───────────────────────────────────────────────────────────────
28
+ st.set_page_config(page_title="Mental Health AI", page_icon="🧠", layout="wide")
29
+
30
+ st.markdown("""
31
+ <style>
32
+ .stApp { background: linear-gradient(135deg, #0d1117, #161b22, #0d1117); color: #e6edf3; }
33
+ h1 { text-align: center; color: #58a6ff; font-size: 36px; margin-bottom: 4px; }
34
+ h2, h3 { color: #c9d1d9; }
35
+ .section-card {
36
+ background: rgba(22,27,34,0.9);
37
+ border: 1px solid #30363d;
38
+ border-radius: 14px;
39
+ padding: 22px 26px;
40
+ margin-bottom: 18px;
41
+ }
42
+ .result-card {
43
+ background: #161b22;
44
+ border: 1px solid #30363d;
45
+ border-radius: 12px;
46
+ padding: 20px;
47
+ text-align: center;
48
+ margin-bottom: 8px;
49
+ }
50
+ .result-card.primary { border: 2px solid #58a6ff; }
51
+ .result-label { font-size: 15px; color: #8b949e; margin-bottom: 6px; }
52
+ .result-value { font-size: 44px; font-weight: 700; }
53
+ .severity-badge {
54
+ display: inline-block;
55
+ padding: 3px 12px;
56
+ border-radius: 20px;
57
+ font-size: 12px;
58
+ font-weight: 600;
59
+ margin-top: 6px;
60
+ }
61
+ .rec-block {
62
+ background: #161b22;
63
+ border: 1px solid #30363d;
64
+ border-radius: 12px;
65
+ padding: 18px 22px;
66
+ margin-bottom: 14px;
67
+ }
68
+ .rec-title { font-size: 15px; font-weight: 700; margin-bottom: 10px; }
69
+ .rec-item { font-size: 14px; color: #c9d1d9; padding: 4px 0; border-bottom: 1px solid #21262d; }
70
+ .rec-item:last-child { border-bottom: none; }
71
+ .ar-text { font-size: 13px; color: #8b949e; margin-top: 3px; direction: rtl; }
72
+ .referral-box {
73
+ background: rgba(248,81,73,0.1);
74
+ border: 1px solid rgba(248,81,73,0.4);
75
+ border-radius: 10px;
76
+ padding: 14px 18px;
77
+ margin-top: 12px;
78
+ }
79
+ .crisis-box {
80
+ background: rgba(248,81,73,0.2);
81
+ border: 2px solid #f85149;
82
+ border-radius: 12px;
83
+ padding: 20px 24px;
84
+ margin: 16px 0;
85
+ }
86
+ div.stButton > button {
87
+ background: linear-gradient(90deg, #1f6feb, #58a6ff);
88
+ color: white; font-size: 17px; font-weight: 700;
89
+ border-radius: 10px; height: 52px; width: 100%; border: none;
90
+ }
91
+ div.stSlider > label { color: #c9d1d9 !important; font-size: 13px; }
92
+ .stTextArea textarea {
93
+ background: #0d1117 !important;
94
+ color: #e6edf3 !important;
95
+ border: 1px solid #30363d !important;
96
+ border-radius: 8px !important;
97
+ }
98
+ </style>
99
+ """, unsafe_allow_html=True)
100
+
101
+ # ── CONSTANTS ─────────────────────────────────────────────────────────────────
102
+ CLASSES = ["anxiety", "depression", "stress"]
103
+ ARABIC_LABELS = {"anxiety": "القلق", "depression": "الاكتئاب", "stress": "الضغط النفسي"}
104
+ COLORS = {"anxiety": "#ffa657", "depression": "#79c0ff", "stress": "#56d364"}
105
+ SEVERITY_AR = {
106
+ "normal": "طبيعي", "mild": "خفيف", "moderate": "متوسط",
107
+ "severe": "شديد", "extremely_severe": "شديد جداً", "crisis": "أزمة",
108
+ }
109
+ SEVERITY_COLORS = {
110
+ "normal": "#56d364", "mild": "#e3b341", "moderate": "#ffa657",
111
+ "severe": "#f85149", "extremely_severe": "#ff0000", "crisis": "#ff0000",
112
+ }
113
+
114
+ CAUSE_AR = {
115
+ "work": "ضغط العمل", "relationships": "العلاقات", "financial": "الضغط المالي",
116
+ "academic": "الضغط الأكاديمي", "health": "المخاوف الصحية", "social": "القلق الاجتماعي",
117
+ "self_worth": "الثقة بالنفس", "trauma": "الصدمة النفسية", "general": "عام",
118
+ }
119
+
120
+ # ── LOAD MODELS ───────────────────────────────────────────────────────────────
121
+ @st.cache_resource
122
+ def load_xlmr():
123
+ token = st.secrets["HF_TOKEN"]
124
+ xlmr_tokenizer = AutoTokenizer.from_pretrained(
125
+ "tasneem33355/mental-xlmr", token=token
126
+ )
127
+ model = AutoModelForSequenceClassification.from_pretrained(
128
+ "tasneem33355/mental-xlmr", token=token
129
+ )
130
+ model.eval()
131
+ # Hardcoded — LabelEncoder على ['anxiety','depression','stress'] دايماً بترتّب alphabetically
132
+ # 0=anxiety, 1=depression, 2=stress
133
+ classes = ["anxiety", "depression", "stress"]
134
+ return xlmr_tokenizer, model, classes
135
+
136
+ @st.cache_resource
137
+ def load_survey():
138
+ scaler = pickle.load(open(os.path.join(os.path.dirname(__file__), "scaler.pkl"), "rb"))
139
+ weights = pickle.load(open(os.path.join(os.path.dirname(__file__), "model_weights.pkl"), "rb"))
140
+
141
+ def predict(x):
142
+ for w in weights:
143
+ if len(w) == 2:
144
+ x = np.dot(x, w[0]) + w[1]
145
+ x = np.maximum(0, x) # ReLU
146
+ x = np.exp(x) / np.sum(np.exp(x)) # Softmax
147
+ return x
148
+
149
+ return scaler, predict
150
+
151
+ xlmr_tokenizer, xlmr_model, le = load_xlmr()
152
+ scaler, survey_predict = load_survey()
153
+
154
+ # ── HELPERS ───────────────────────────────────────────────────────────────────
155
+ def clean_text(text):
156
+ text = re.sub(r'(.)\1{2,}', r'\1\1', text)
157
+ text = re.sub(r'[^\w\s\u0600-\u06FF\[\]]', ' ', text)
158
+ return re.sub(r'\s+', ' ', text).strip()
159
+
160
+ def translate_to_en(text):
161
+ try:
162
+ return GoogleTranslator(source="auto", target="en").translate(text)
163
+ except Exception:
164
+ return ""
165
+
166
+ # ── KEYWORD OVERRIDE ─────────────────────────────────────────────────────────
167
+ DEPRESSION_KEYWORDS = [
168
+ # عربي فصيح
169
+ "اكتئاب", "مكتئب", "مكتئبة", "حزن", "حزين", "حزينة", "يأس", "يائس", "يائسة",
170
+ "فراغ", "إحساس بالفراغ", "بلا معنى", "لا معنى", "مالهاش معنى", "بلا هدف",
171
+ "لا أمل", "مفيش أمل", "تعبت من الحياة", "زهقت من الحياة",
172
+ "مش لاقي معنى", "مش لاقية معنى", "حاسس بالفراغ", "حاسة بالفراغ",
173
+ "مفيش طاقة", "مفيش رغبة", "بكاء", "عايز أبكي", "عايزة أبكي",
174
+ "وحيد", "وحيدة", "عزلة", "منعزل", "منعزلة",
175
+ "إرهاق نفسي", "إرهاق عاطفي", "مش حاسس بحاجة", "مش حاسة بحاجة",
176
+ # عامية مصرية وشامية
177
+ "زهقت", "تعبت", "مش طايق", "مش طايقة", "نفسيتي وحشة", "نفسيتي في الأرض",
178
+ "مش قادر أكمل", "مش قادرة أكمل", "مش عايش", "مش قادر أعيش",
179
+ "مش عايز أصحى", "مش عايزة أصحى", "دموع", "بدمع", "قلبي تقيل",
180
+ "مش حاسس بنفسي", "مش حاسة بنفسي", "ما بحس بشي", "ما في فايدة",
181
+ "مافي امل", "ما في امل", "حياتي خربت", "خسرت كل حاجة",
182
+ # إنجليزي
183
+ "depressed", "depression", "hopeless", "hopelessness", "empty", "emptiness",
184
+ "worthless", "meaningless", "no meaning", "no purpose", "cannot go on",
185
+ "cant go on", "no energy", "no motivation", "crying", "feel nothing",
186
+ "numb", "isolated", "lonely", "loneliness", "sad", "sadness",
187
+ "despair", "grief", "miserable", "broken", "lost all hope",
188
+ ]
189
+
190
+ ANXIETY_KEYWORDS = [
191
+ # عربي
192
+ "قلق", "قلقان", "قلقانة", "خوف", "خايف", "خايفة", "توتر", "متوتر", "متوترة",
193
+ "هلع", "مش مرتاح", "مش مرتاحة", "ذعر", "رهاب", "وسواس",
194
+ # إنجليزي
195
+ "panic", "anxious", "anxiety", "worried", "worry", "fear",
196
+ "scared", "nervous", "restless", "tense", "phobia", "ocd",
197
+ ]
198
+
199
+ STRESS_KEYWORDS = [
200
+ # عربي
201
+ "ضغط", "ضغوط", "مضغوط", "مضغوطة", "إجهاد", "مجهد", "مجهدة",
202
+ # إنجليزي
203
+ "overwhelmed", "stressed", "stress", "burnout", "exhausted", "overloaded",
204
+ ]
205
+
206
+ def keyword_boost(text: str, scores: dict) -> dict:
207
+ """
208
+ يعوّض الـ stress bias في الموديل عن طريق override قوي
209
+ لما تكون كلمات depression أو anxiety أو stress واضحة في النص.
210
+ """
211
+ text_lower = text.lower()
212
+
213
+ dep_hits = sum(1 for kw in DEPRESSION_KEYWORDS if kw.lower() in text_lower)
214
+ anx_hits = sum(1 for kw in ANXIETY_KEYWORDS if kw.lower() in text_lower)
215
+ str_hits = sum(1 for kw in STRESS_KEYWORDS if kw.lower() in text_lower)
216
+
217
+ if dep_hits == 0 and anx_hits == 0 and str_hits == 0:
218
+ return scores
219
+
220
+ s = dict(scores)
221
+
222
+ if dep_hits > 0 and dep_hits >= anx_hits and dep_hits >= str_hits:
223
+ # depression كلمات واضحة — override قوي
224
+ boost = min(0.55 + dep_hits * 0.10, 0.85)
225
+ s["depression"] = boost
226
+ remaining = 1.0 - boost
227
+ total_rest = s["anxiety"] + s["stress"]
228
+ if total_rest > 0:
229
+ s["anxiety"] = round(remaining * s["anxiety"] / total_rest, 4)
230
+ s["stress"] = round(remaining * s["stress"] / total_rest, 4)
231
+ s["depression"] = round(boost, 4)
232
+
233
+ elif anx_hits > 0 and anx_hits >= dep_hits and anx_hits >= str_hits:
234
+ # anxiety كلمات واضحة — override قوي
235
+ boost = min(0.55 + anx_hits * 0.10, 0.85)
236
+ s["anxiety"] = boost
237
+ remaining = 1.0 - boost
238
+ total_rest = s["depression"] + s["stress"]
239
+ if total_rest > 0:
240
+ s["depression"] = round(remaining * s["depression"] / total_rest, 4)
241
+ s["stress"] = round(remaining * s["stress"] / total_rest, 4)
242
+ s["anxiety"] = round(boost, 4)
243
+
244
+ elif str_hits > 0 and str_hits >= dep_hits and str_hits >= anx_hits:
245
+ # stress كلمات واضحة — override قوي
246
+ boost = min(0.55 + str_hits * 0.10, 0.85)
247
+ s["stress"] = boost
248
+ remaining = 1.0 - boost
249
+ total_rest = s["depression"] + s["anxiety"]
250
+ if total_rest > 0:
251
+ s["depression"] = round(remaining * s["depression"] / total_rest, 4)
252
+ s["anxiety"] = round(remaining * s["anxiety"] / total_rest, 4)
253
+ s["stress"] = round(boost, 4)
254
+
255
+ # normalize
256
+ total = sum(s.values())
257
+ if total > 0:
258
+ s = {k: round(v / total, 4) for k, v in s.items()}
259
+
260
+ return s
261
+
262
+
263
+ def predict_text(text: str) -> dict:
264
+ cleaned = clean_text(text)
265
+ text_en = translate_to_en(cleaned)
266
+ combined = (text_en + " [SEP] " + cleaned) if text_en else cleaned
267
+ inputs = xlmr_tokenizer(combined, return_tensors="pt",
268
+ truncation=True, max_length=192, padding=True)
269
+ with torch.no_grad():
270
+ probs = torch.softmax(xlmr_model(**inputs).logits, dim=-1).squeeze().numpy()
271
+ raw_scores = {c: round(float(p), 4) for c, p in zip(le, probs)}
272
+ # طبّق الـ keyword boost على النص الأصلي + الترجمة
273
+ boosted = keyword_boost(text + " " + text_en, raw_scores)
274
+ return boosted
275
+
276
+ def predict_survey(answers: list) -> dict:
277
+ data = scaler.transform(np.array(answers).reshape(1, -1))
278
+ pred = survey_predict(data)[0]
279
+ return {
280
+ "depression": round(float(pred[0]), 4),
281
+ "anxiety": round(float(pred[1]), 4),
282
+ "stress": round(float(pred[2]), 4),
283
+ }
284
+
285
+ def fuse_scores(text_s, survey_s, w_text=0.4, w_survey=0.6):
286
+ return {c: round(w_text * text_s[c] + w_survey * survey_s[c], 4) for c in CLASSES}
287
+
288
+ # ── SURVEY QUESTIONS ─────────────────────────────────────────────────────────
289
+ SURVEY_Q = [
290
+ ("I found it hard to wind down", "وجدت صعوبة في الاسترخاء"),
291
+ ("I was aware of dryness of my mouth", "لاحظت جفافاً في فمي"),
292
+ ("I couldn't seem to experience any positive feeling at all", "لم أستطع الشعور بأي مشاعر إيجابية"),
293
+ ("I experienced breathing difficulty", "أحسست بصعوبة في التنفس"),
294
+ ("I found it difficult to work up the initiative to do things", "وجدت صعوبة في اتخاذ المبادرة للقيام بالأشياء"),
295
+ ("I tended to over-react to situations", "كنت أبالغ في ردود أفعالي تجاه المواقف"),
296
+ ("I experienced trembling", "شعرت بالرعشة"),
297
+ ("I felt that I was using a lot of nervous energy", "شعرت أنني أستهلك الكثير من الطاقة العصبية"),
298
+ ("I was worried about situations in which I might panic", "كنت قلقاً من مواقف قد أصاب فيها بالذعر"),
299
+ ("I felt that I had nothing to look forward to", "شعرت أنه لا يوجد شيء أتطلع إليه"),
300
+ ("I found myself getting agitated", "وجدت نفسي أشعر بالانفعال"),
301
+ ("I found it difficult to relax", "وجدت صعوبة في الاسترخاء"),
302
+ ("I felt down-hearted and blue", "شعرت بالإحباط والكآبة"),
303
+ ("I was intolerant of anything that kept me from getting on", "كنت غير متسامح مع أي شيء يعيقني"),
304
+ ("I felt I was close to panic", "شعرت أنني على وشك الذعر"),
305
+ ("I was unable to become enthusiastic", "لم أستطع أن أتحمس لأي شيء"),
306
+ ("I felt I wasn't worth much as a person", "شعرت أنني لست شخصاً ذا قيمة"),
307
+ ("I felt that I was rather touchy", "شعرت أنني متقلب المزاج"),
308
+ ("I was aware of the action of my heart", "كنت واعياً لنبضات قلبي"),
309
+ ("I felt scared without any good reason", "شعرت بالخوف دون سبب واضح"),
310
+ ("I felt that life was meaningless", "شعرت أن الحياة بلا معنى"),
311
+ ("I found it hard to calm down", "وجدت صعوبة في التهدئة"),
312
+ ("I felt nervous", "شعرت بالتوتر"),
313
+ ("I felt sad and depressed", "شعرت بالحزن والاكتئاب"),
314
+ ("I found myself getting impatient", "وجدت نفسي أشعر بنفاد الصبر"),
315
+ ("I felt that I was rather emotional", "شعرت أنني عاطفي بشكل مفرط"),
316
+ ("I felt restless", "شعرت بعدم الهدوء"),
317
+ ("I had difficulty concentrating", "وجدت صعوبة في التركيز"),
318
+ ("I felt lonely", "شعرت بالوحدة"),
319
+ ("I found it difficult to relax", "وجدت صعوبة في الاسترخاء"),
320
+ ("I felt hopeless", "شعرت باليأس"),
321
+ ("I felt worried about many things", "كنت قلقاً بشأن أشياء كثيرة"),
322
+ ("I felt that I had no energy", "شعرت بعدم وجود طاقة"),
323
+ ("I felt tense", "شعرت بالتوتر والضيق"),
324
+ ("I felt tired for no reason", "شعرت بالتعب دون سبب"),
325
+ ("I felt uneasy", "شعرت بعدم الارتياح"),
326
+ ("I felt worthless", "شعرت بأنني لا قيمة لي"),
327
+ ("I felt anxious", "شعرت بالقلق"),
328
+ ("I felt discouraged", "شعرت بالإحباط"),
329
+ ("I felt stressed", "شعرت بالضغط"),
330
+ ("I felt overwhelmed", "شعرت بالإرهاق"),
331
+ ("I felt emotionally exhausted", "شعرت بالإنهاك العاطفي"),
332
+ ]
333
+
334
+ # ── UI ────────────────────────────────────────────────────────────────────────
335
+ st.title("🧠 Mental Health AI")
336
+ st.markdown(
337
+ "<p style='text-align:center;color:#8b949e;font-size:15px;'>"
338
+ "Write how you feel and answer the survey for a complete assessment"
339
+ "<br><span dir='rtl'>اكتب ما تشعر به وأجب على الأسئلة للحصول على تقييم شامل</span></p>",
340
+ unsafe_allow_html=True,
341
+ )
342
+ st.markdown("---")
343
+
344
+ # ── PART 1: TEXT ─────────────────────────────────────────────────────────────
345
+ st.markdown("<div class='section-card'>", unsafe_allow_html=True)
346
+ st.markdown("### 💬 How are you feeling? / كيف تشعر؟")
347
+ st.markdown(
348
+ "<p style='color:#8b949e;font-size:13px;'>"
349
+ "Write in any language — Arabic (any dialect), English, or both<br>"
350
+ "<span dir='rtl'>اكتب بأي لغة — عربي (أي لهجة)، إنجليزي، أو الاتنين</span></p>",
351
+ unsafe_allow_html=True,
352
+ )
353
+ user_text = st.text_area(
354
+ label="",
355
+ placeholder="e.g. I've been feeling very overwhelmed at work and can't sleep...\nمثال: أنا تعبان جداً من الشغل ومش قادر أنام...",
356
+ height=120,
357
+ label_visibility="collapsed",
358
+ )
359
+ st.markdown("</div>", unsafe_allow_html=True)
360
+
361
+ # ── PART 2: SURVEY ────────────────────────────────────────────────────────────
362
+ st.markdown("<div class='section-card'>", unsafe_allow_html=True)
363
+ st.markdown("### 📋 DASS-42 Survey / استبيان DASS-42")
364
+ st.markdown(
365
+ "<p style='color:#8b949e;font-size:13px;'>"
366
+ "0 = Never &nbsp;|&nbsp; 1 = Sometimes &nbsp;|&nbsp; 2 = Often &nbsp;|&nbsp; "
367
+ "3 = Most of the time &nbsp;|&nbsp; 4 = Always<br>"
368
+ "<span dir='rtl'>0 = لم يحدث أبداً | 1 = أحياناً | 2 = كثيراً | 3 = معظم الوقت | 4 = دائماً</span></p>",
369
+ unsafe_allow_html=True,
370
+ )
371
+
372
+ survey_answers = []
373
+ for i in range(0, len(SURVEY_Q), 2):
374
+ cols = st.columns(2)
375
+ for j, (en, ar) in enumerate(SURVEY_Q[i:i+2]):
376
+ with cols[j]:
377
+ val = st.slider(
378
+ f"{i+j+1}. {en}\n{ar}",
379
+ min_value=0, max_value=3, value=0,
380
+ key=f"q_{i+j}",
381
+ )
382
+ survey_answers.append(val)
383
+
384
+ st.markdown("</div>", unsafe_allow_html=True)
385
+
386
+ # ── PREDICT BUTTON ────────────────────────────────────────────────────────────
387
+ _, col_btn, _ = st.columns([1, 2, 1])
388
+ with col_btn:
389
+ predict_btn = st.button("🔍 Analyze / تحليل")
390
+
391
+ # ── RESULTS ──────────────────────────────────────────────────────────────────
392
+ if predict_btn:
393
+ if not user_text.strip():
394
+ st.warning("Please write how you feel first. / من فضلك اكتب ما تشعر به أولاً.")
395
+ st.stop()
396
+
397
+ with st.spinner("Analyzing... / جاري التحليل..."):
398
+ text_scores = predict_text(user_text)
399
+ survey_scores = predict_survey(survey_answers)
400
+ final_scores = fuse_scores(text_scores, survey_scores)
401
+ primary = max(final_scores, key=final_scores.get)
402
+ rec = get_recommendations(primary, final_scores[primary], user_text)
403
+
404
+ st.markdown("---")
405
+ st.markdown("## 📊 Results / النتائج")
406
+
407
+ # ── SCORE CARDS ───────────────────────────────────────────────────────────
408
+ cols = st.columns(3)
409
+ for col, cls in zip(cols, CLASSES):
410
+ pct = int(final_scores[cls] * 100)
411
+ is_primary = cls == primary
412
+ card_class = "result-card primary" if is_primary else "result-card"
413
+ sev = rec["severity"] if is_primary else ""
414
+ badge = ""
415
+ if is_primary and sev:
416
+ sev_color = SEVERITY_COLORS.get(sev, "#8b949e")
417
+ badge = (f"<div class='severity-badge' style='background:{sev_color}20;"
418
+ f"color:{sev_color};border:1px solid {sev_color};'>"
419
+ f"{sev.replace('_',' ').title()} / {SEVERITY_AR.get(sev,'')}</div>")
420
+ col.markdown(f"""
421
+ <div class='{card_class}'>
422
+ <div class='result-label'>{cls.title()} / {ARABIC_LABELS[cls]}</div>
423
+ <div class='result-value' style='color:{COLORS[cls]}'>{pct}%</div>
424
+ {badge}
425
+ </div>""", unsafe_allow_html=True)
426
+
427
+ # ── PRIMARY LABEL ─────────────────────────────────────────────────────────
428
+ if not rec["suicidal_flag"]:
429
+ cause_label = CAUSE_AR.get(rec["cause"], rec["cause"])
430
+ st.markdown(
431
+ f"<p style='text-align:center;margin-top:10px;font-size:17px;color:#8b949e;'>"
432
+ f"Primary: <strong style='color:{COLORS[primary]}'>{primary.title()} / {ARABIC_LABELS[primary]}</strong>"
433
+ f" &nbsp;|&nbsp; Cause detected / السبب المكتشف: "
434
+ f"<strong style='color:#e3b341'>{rec['cause'].replace('_',' ').title()} / {cause_label}</strong></p>",
435
+ unsafe_allow_html=True,
436
+ )
437
+
438
+ # ── CRISIS BOX ────────────────────────────────────────────────────────────
439
+ if rec["suicidal_flag"]:
440
+ st.markdown("""
441
+ <div class='crisis-box'>
442
+ <h3 style='color:#f85149;margin-top:0;'>🚨 Crisis Support Needed / مطلوب دعم أزمة</h3>
443
+ </div>""", unsafe_allow_html=True)
444
+
445
+ # ── SCORE DETAILS ─────────────────────────────────────────────────────────
446
+ with st.expander("Show score breakdown / عرض تفاصيل النتائج"):
447
+ c1, c2 = st.columns(2)
448
+ c1.markdown("**Text model / موديل النص:**")
449
+ for cls in CLASSES:
450
+ c1.markdown(f"- {cls} / {ARABIC_LABELS[cls]}: **{int(text_scores[cls]*100)}%**")
451
+ c2.markdown("**Survey model / موديل السيرفاي:**")
452
+ for cls in CLASSES:
453
+ c2.markdown(f"- {cls} / {ARABIC_LABELS[cls]}: **{int(survey_scores[cls]*100)}%**")
454
+
455
+ # ── RECOMMENDATIONS ───────────────────────────────────────────────────────
456
+ st.markdown("---")
457
+ st.markdown("## 💡 Recommendations / التوصيات")
458
+
459
+ col_tips, col_res = st.columns(2)
460
+
461
+ with col_tips:
462
+ st.markdown("<div class='rec-block'>", unsafe_allow_html=True)
463
+ st.markdown("<div class='rec-title'>✅ Practical Tips / نصائح عملية</div>",
464
+ unsafe_allow_html=True)
465
+ tips_en = rec.get("tips_en", [])
466
+ tips_ar = rec.get("tips_ar", [])
467
+ for en, ar in zip(tips_en, tips_ar):
468
+ st.markdown(
469
+ f"<div class='rec-item'>{en}"
470
+ f"<div class='ar-text' dir='rtl'>• {ar}</div></div>",
471
+ unsafe_allow_html=True,
472
+ )
473
+ st.markdown("</div>", unsafe_allow_html=True)
474
+
475
+ with col_res:
476
+ st.markdown("<div class='rec-block'>", unsafe_allow_html=True)
477
+ st.markdown("<div class='rec-title'>📚 Resources / موارد مفيدة</div>",
478
+ unsafe_allow_html=True)
479
+ res_en = rec.get("resources_en", [])
480
+ res_ar = rec.get("resources_ar", [])
481
+ for en, ar in zip(res_en, res_ar):
482
+ st.markdown(
483
+ f"<div class='rec-item'>{en}"
484
+ f"<div class='ar-text' dir='rtl'>• {ar}</div></div>",
485
+ unsafe_allow_html=True,
486
+ )
487
+ st.markdown("</div>", unsafe_allow_html=True)
488
+
489
+ # ── REFERRAL ───────────────────────────────────────────────────────────���──
490
+ ref_en = rec.get("referral_en", "")
491
+ ref_ar = rec.get("referral_ar", "")
492
+ if ref_en:
493
+ box_class = "crisis-box" if rec["suicidal_flag"] else "referral-box"
494
+ st.markdown(
495
+ f"<div class='{box_class}'>"
496
+ f"<strong>🏥 When to seek help / متى تطلب المساعدة:</strong><br>"
497
+ f"{ref_en}<br>"
498
+ f"<span dir='rtl' style='color:#f0a0a0;font-size:13px;'>{ref_ar}</span>"
499
+ f"</div>",
500
+ unsafe_allow_html=True,
501
+ )
502
+
503
+ st.markdown(
504
+ "<p style='text-align:center;color:#484f58;font-size:12px;margin-top:20px;'>"
505
+ "⚠️ This system is for awareness only and is not a substitute for professional medical diagnosis.<br>"
506
+ "هذا النظام للتوعية فقط وليس بديلاً عن التشخيص الطبي المتخصص.</p>",
507
+ unsafe_allow_html=True,
508
+ )
check_db.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from sqlalchemy import create_engine, Column, Integer, String, DateTime
3
+ from sqlalchemy.orm import declarative_base, sessionmaker
4
+
5
+ DATABASE_URL = "postgresql://safespace:AdminAdmin@postgresql-208383-0.cloudclusters.net:19712/safespace"
6
+
7
+ engine = create_engine(DATABASE_URL)
8
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
9
+ Base = declarative_base()
10
+
11
+ class DBUser(Base):
12
+ __tablename__ = "users"
13
+ id = Column(Integer, primary_key=True, index=True)
14
+ name = Column(String, nullable=True)
15
+ email = Column(String, unique=True, index=True)
16
+ password = Column(String)
17
+
18
+ db = SessionLocal()
19
+ users = db.query(DBUser).all()
20
+ print(f"Total users: {len(users)}")
21
+ for u in users:
22
+ print(f"ID: {u.id}, Email: {u.email}, Password Hash: {u.password}")
23
+ db.close()
clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/History ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:18bb7fac167630752b1cfa38fdbd37883c97701d67639db61141b814498b3388
3
+ size 163840
clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/Web Data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3743dd646541935eebc92ec44dc065a71a5beefd8165facae2a5a73eeb6ae656
3
+ size 157696
clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/shared_proto_db/000003.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:224b7af78f931fbc5e380e4970816885ce968e38da0eb46f510b4a661f7b55db
3
+ size 111699
clean_git_repo/UI/safespace/.dart_tool/chrome-device/Default/trusted_vault.pb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b87bf305aafae1f1ec7dd947176c9529b2a9cbd4a3bf296fce25fc009e725366
3
+ size 84
clean_git_repo/UI/safespace/build/8730b13ca0249799964d0a71255a8f3b.cache.dill.track.dill ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae0b1b0e267ea854918ca7afbf3c5be6a073deae0894ad6ad67ffd9f78b46c7a
3
+ size 41863640
clean_git_repo/UI/safespace/build/flutter_assets/AssetManifest.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:00af55ad3d6f21898fe77e0ff092d1a1cda52c941b6860e9928d45c8af8c095d
3
+ size 117
clean_git_repo/UI/safespace/build/flutter_assets/fonts/MaterialIcons-Regular.otf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d9865b671a09d683d13a863089d8825e0f61a37696ce5d7d448bc8023aa62453
3
+ size 1645184
clean_git_repo/UI/safespace/build/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67c44fe9183b002e79dde7f6977e2988661c9a3e4a3c5fce968787efdbed823c
3
+ size 257628
clean_git_repo/UI/safespace/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png ADDED

Git LFS Details

  • SHA256: 6232e5815af17e25e0268b2fec7aea9e068cc92ec709e9605c2b31df4ff2a313
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
clean_git_repo/data.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d6b39f44704ef71efab7fb6223ffaa68bc84af9c7c42875310c043242dac593d
3
+ size 20819959
clean_git_repo/mental_model.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d83c61efe6d2ff698925835f970cce7d65026107d6adcbe8eb31b58ed1ac4325
3
+ size 259632
clean_git_repo/mental_xlmr_final/label_encoder.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7459c4d557907ee7efd7151ef3618a1a47b74750e839c56c50d450f6a2553c20
3
+ size 275
clean_git_repo/mental_xlmr_final/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:542cda5f12d1c653dd46ac4d8c992d71e0047e65e7bf2cdda17c2d43b1d94b37
3
+ size 17781767
clean_git_repo/mental_xlmr_final/training_args.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d487b97c54aa8b5c1582f059ef3c809014c6d48eabd7a6c0be396fc96cf835f2
3
+ size 5201
clean_git_repo/model_weights.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c7f5e42ca4cda8a43af36c8b72b4ac1dfe500fbab6c7359d26c1eaa9048b37f7
3
+ size 68153