diff --git a/MVP/config_manager.py b/MVP/config_manager.py
--- a/MVP/config_manager.py
+++ b/MVP/config_manager.py
@@ -8,7 +8,31 @@
import os
+import sys
from pathlib import Path
-BASE_DIR = Path(__file__).resolve().parent
+def get_bundle_dir() -> Path:
+ """Get read-only bundle directory (where static assets, rules, guidance live)."""
+ if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
+ return Path(sys._MEIPASS)
+ return Path(__file__).resolve().parent
+
+def get_data_dir() -> Path:
+ """Get writable persistent data directory (where SQLite DB, logs live)."""
+ if getattr(sys, 'frozen', False):
+ exe_dir = Path(sys.executable).resolve().parent
+ test_file = exe_dir / ".write_test"
+ try:
+ test_file.touch()
+ test_file.unlink()
+ return exe_dir / "data"
+ except (PermissionError, OSError):
+ local_appdata = os.environ.get("LOCALAPPDATA")
+ if local_appdata:
+ return Path(local_appdata) / "HAD Digital" / "data"
+ return Path.home() / ".had_digital" / "data"
+ return Path(__file__).resolve().parent / "data"
+
+BUNDLE_DIR = get_bundle_dir()
+DATA_DIR = get_data_dir()
DEFAULT_CONFIG = {
"app_name": "HAD Digital MVP",
@@ -20,3 +44,3 @@
"database": {
- "path": str(BASE_DIR / "data" / "had.db"),
+ "path": str(DATA_DIR / "had.db"),
"wal_mode": True,
@@ -35,3 +59,3 @@
"ctcae": {
- "rules_path": str(BASE_DIR / "data" / "ctcae_rules.json"),
+ "rules_path": str(BUNDLE_DIR / "data" / "ctcae_rules.json"),
},
"guidance": {
- "path": str(BASE_DIR / "guidance" / "guidance.json"),
+ "path": str(BUNDLE_DIR / "guidance" / "guidance.json"),
},
diff --git a/MVP/app.py b/MVP/app.py
--- a/MVP/app.py
+++ b/MVP/app.py
@@ -22,3 +22,3 @@
-from config_manager import config
+from config_manager import config, get_bundle_dir
from database import get_db
@@ -157,3 +157,3 @@
def _serve_static(self, file_path):
- static_dir = Path(__file__).resolve().parent / "static"
+ static_dir = get_bundle_dir() / "static"
try:
@@ -199,3 +199,3 @@
- self._json_response({"message": "Login successful", "user": {k: user[k] for k in ("id", "username", "role", "display_name")}})
+ self._json_response({"ok": True, "message": "Login successful", "user": {k: user[k] for k in ("id", "username", "role", "display_name", "patient_id")}})
@@ -211,3 +211,3 @@
- self._json_response({"authenticated": False}, 401)
+ self._json_response({"authenticated": False}, 200)
return
- self._json_response({"authenticated": True, "user": user})
+ self._json_response({"authenticated": True, "user": {k: user[k] for k in ("id", "username", "role", "display_name", "patient_id")}})
@@ -258,6 +258,10 @@
data = self._read_json_body()
+ pid = data.get("patient_id") or user.get("patient_id")
+ if not pid:
+ self._json_response({"error": "Missing required field: patient_id"}, 400)
+ return
+ # Handle multi-symptom dictionary payload from app.js / API_Contract.md
+ if "symptoms" in data and isinstance(data["symptoms"], dict):
+ # Process batch symptoms
+ ...
@@ -516,3 +520,3 @@
- server = http.server.HTTPServer((host, port), HADRequestHandler)
+ server = http.server.ThreadingHTTPServer((host, port), HADRequestHandler)
diff --git a/MVP/static/app.js b/MVP/static/app.js
--- a/MVP/static/app.js
+++ b/MVP/static/app.js
@@ -67,3 +67,3 @@
- + 'Oncologist: dr.dupont / oncology2026
'
- + 'Nurse: nurse.martin / nurse2026
'
- + 'Patient: patient.marie / patient2026'
+ + 'Oncologist: dr.martin / demo123
'
+ + 'HAD Nurse: inf.moret / demo123
'
+ + 'Patient: patient.durand / demo123'
@@ -81,3 +81,3 @@
- if (data && data.ok) { currentUser = data.user; navigate('dashboard'); }
+ if (data && (data.ok || data.user)) { currentUser = data.user; navigate('dashboard'); }