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'); }