Compare commits
4 Commits
main
...
57540d5159
| Author | SHA1 | Date | |
|---|---|---|---|
| 57540d5159 | |||
| d6943faf59 | |||
| 8562c45f05 | |||
| 1cc2c754b7 |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -195,12 +195,5 @@ Endobest Reporting/
|
|||||||
jsons history/
|
jsons history/
|
||||||
nul
|
nul
|
||||||
|
|
||||||
# Ignore all json, exe, log, txt, csv and xlsx files
|
dashboard.log
|
||||||
*.json
|
*.json
|
||||||
*.exe
|
|
||||||
*.log
|
|
||||||
*.txt
|
|
||||||
*.csv
|
|
||||||
/*.xlsx
|
|
||||||
!eb_org_center_mapping.xlsx
|
|
||||||
/pyproject.toml
|
|
||||||
Binary file not shown.
BIN
config/Endobest_Dashboard_Config-new.xlsx
Normal file
BIN
config/Endobest_Dashboard_Config-new.xlsx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
config/eb_dashboard_extended_template-new.xlsx
Normal file
BIN
config/eb_dashboard_extended_template-new.xlsx
Normal file
Binary file not shown.
Binary file not shown.
40
dashboard.log
Normal file
40
dashboard.log
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
2026-02-09 17:08:47,130 - WARNING - Error in get_record_by_patient_id (Attempt 1): Server error '502 Bad Gateway' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||||
|
2026-02-09 17:08:47,130 - WARNING - Error in get_record_by_patient_id (Attempt 1): Server error '502 Bad Gateway' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||||
|
2026-02-09 17:08:47,130 - WARNING - Error in get_record_by_patient_id (Attempt 1): Server error '502 Bad Gateway' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||||
|
2026-02-09 17:09:45,882 - WARNING - Error in get_record_by_patient_id (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,883 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,882 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,883 - WARNING - Error in get_record_by_patient_id (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,889 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,890 - WARNING - Error in get_record_by_patient_id (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,890 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,892 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,890 - WARNING - Error in get_record_by_patient_id (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,892 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,891 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:09:45,892 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): [WinError 10054] Une connexion existante a d<> <20>tre ferm<72>e par l<>h<EFBFBD>te distant
|
||||||
|
2026-02-09 17:17:16,591 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): Server error '502 Bad Gateway' for url 'https://api-hcp.ziwig-connect.com/api/surveys/filter/with-answers'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||||
|
2026-02-09 17:17:16,591 - WARNING - Error in get_record_by_patient_id (Attempt 1): Server error '502 Bad Gateway' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||||
|
2026-02-09 17:17:16,592 - WARNING - Error in get_record_by_patient_id (Attempt 1): Server error '502 Bad Gateway' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
|
||||||
|
2026-02-09 17:21:04,034 - WARNING - Error in get_request_by_tube_id (Attempt 1): Client error '401 Unauthorized' for url 'https://api-lab.ziwig-connect.com/api/requests/by-tube-id/55241110000385?isAdmin=true&organization=undefined'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:04,070 - WARNING - Error in get_request_by_tube_id (Attempt 1): Client error '401 Unauthorized' for url 'https://api-lab.ziwig-connect.com/api/requests/by-tube-id/55241110001327?isAdmin=true&organization=undefined'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:05,040 - WARNING - Error in get_record_by_patient_id (Attempt 1): Client error '401 Unauthorized' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:05,043 - WARNING - Error in get_record_by_patient_id (Attempt 1): Client error '401 Unauthorized' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:05,045 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): Client error '401 Unauthorized' for url 'https://api-hcp.ziwig-connect.com/api/surveys/filter/with-answers'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:05,045 - WARNING - Error in get_all_questionnaires_by_patient (Attempt 1): Client error '401 Unauthorized' for url 'https://api-hcp.ziwig-connect.com/api/surveys/filter/with-answers'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:05,331 - WARNING - Error in get_record_by_patient_id (Attempt 1): Client error '401 Unauthorized' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
|
2026-02-09 17:21:06,089 - WARNING - Error in get_record_by_patient_id (Attempt 1): Client error '401 Unauthorized' for url 'https://api-hcp.ziwig-connect.com/api/records/byPatient'
|
||||||
|
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
|
||||||
BIN
eb_dashboard-no-6-month-visit.exe
Normal file
BIN
eb_dashboard-no-6-month-visit.exe
Normal file
Binary file not shown.
455
eb_dashboard.py
455
eb_dashboard.py
@@ -21,7 +21,7 @@
|
|||||||
# identification, and support for complex data extraction using JSON path expressions.
|
# identification, and support for complex data extraction using JSON path expressions.
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import msvcrt
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -86,10 +86,6 @@ from eb_dashboard_utils import (
|
|||||||
clear_httpx_client,
|
clear_httpx_client,
|
||||||
get_thread_position,
|
get_thread_position,
|
||||||
get_config_path,
|
get_config_path,
|
||||||
set_dashboard_config_path_override,
|
|
||||||
get_dashboard_config_path,
|
|
||||||
set_output_file_suffix,
|
|
||||||
get_output_filename,
|
|
||||||
thread_local_storage,
|
thread_local_storage,
|
||||||
run_with_context
|
run_with_context
|
||||||
)
|
)
|
||||||
@@ -107,23 +103,8 @@ from eb_dashboard_excel_export import (
|
|||||||
set_dependencies as excel_set_dependencies
|
set_dependencies as excel_set_dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
def _fatal_cli_error(message):
|
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s', filename=LOG_FILE_NAME,
|
||||||
"""Print a CLI error then wait for Enter before exiting (keeps console open on Windows Explorer launch)."""
|
filemode='w')
|
||||||
print(message)
|
|
||||||
input("Press Enter to exit...")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
# Handle --add-suffix <value> early: must precede logging.basicConfig (affects log filename)
|
|
||||||
if "--add-suffix" in sys.argv:
|
|
||||||
_idx = sys.argv.index("--add-suffix")
|
|
||||||
if _idx + 1 >= len(sys.argv):
|
|
||||||
_fatal_cli_error("Error: --add-suffix requires a value")
|
|
||||||
set_output_file_suffix(sys.argv[_idx + 1])
|
|
||||||
del sys.argv[_idx:_idx + 2]
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s',
|
|
||||||
filename=get_output_filename(LOG_FILE_NAME), filemode='w')
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -137,10 +118,6 @@ access_token = ""
|
|||||||
refresh_token = ""
|
refresh_token = ""
|
||||||
threads_list = []
|
threads_list = []
|
||||||
_token_refresh_lock = threading.Lock()
|
_token_refresh_lock = threading.Lock()
|
||||||
on_retry_exhausted = "ask" # "ask" | "ignore" | "abort" — set at startup
|
|
||||||
fetch_six_month_visit = False # Whether to fetch 6-month visit data (slow, ~5s per patient)
|
|
||||||
_stored_username = "" # Credentials stored at login for automatic re-login
|
|
||||||
_stored_password = ""
|
|
||||||
_threads_list_lock = threading.Lock()
|
_threads_list_lock = threading.Lock()
|
||||||
global_pbar = None
|
global_pbar = None
|
||||||
_global_pbar_lock = threading.Lock()
|
_global_pbar_lock = threading.Lock()
|
||||||
@@ -149,7 +126,6 @@ _user_interaction_lock = threading.Lock()
|
|||||||
# Global variables (mutable, set at runtime - not constants)
|
# Global variables (mutable, set at runtime - not constants)
|
||||||
inclusions_mapping_config = []
|
inclusions_mapping_config = []
|
||||||
organizations_mapping_config = []
|
organizations_mapping_config = []
|
||||||
_include_drafts = False # Set by --include-drafts CLI arg if provided
|
|
||||||
excel_export_config = None
|
excel_export_config = None
|
||||||
excel_export_enabled = False
|
excel_export_enabled = False
|
||||||
|
|
||||||
@@ -172,23 +148,6 @@ if "--debug" in sys.argv:
|
|||||||
sys.argv.remove("--debug")
|
sys.argv.remove("--debug")
|
||||||
enable_debug_mode()
|
enable_debug_mode()
|
||||||
|
|
||||||
# Handle --config <path> override (remove from sys.argv to preserve positional args)
|
|
||||||
if "--config" in sys.argv:
|
|
||||||
_idx = sys.argv.index("--config")
|
|
||||||
if _idx + 1 >= len(sys.argv):
|
|
||||||
_fatal_cli_error("Error: --config requires a file path argument")
|
|
||||||
_raw_config_path = sys.argv[_idx + 1]
|
|
||||||
del sys.argv[_idx:_idx + 2]
|
|
||||||
if os.path.isabs(_raw_config_path):
|
|
||||||
set_dashboard_config_path_override(_raw_config_path)
|
|
||||||
else:
|
|
||||||
set_dashboard_config_path_override(os.path.join(get_config_path(), _raw_config_path))
|
|
||||||
|
|
||||||
# Handle --include-drafts flag (remove from sys.argv to preserve positional args)
|
|
||||||
_include_drafts = "--include-drafts" in sys.argv
|
|
||||||
if _include_drafts:
|
|
||||||
sys.argv.remove("--include-drafts")
|
|
||||||
|
|
||||||
# --- Progress Bar Configuration ---
|
# --- Progress Bar Configuration ---
|
||||||
# NOTE: BAR_N_FMT_WIDTH, BAR_TOTAL_FMT_WIDTH, BAR_TIME_WIDTH, BAR_RATE_WIDTH
|
# NOTE: BAR_N_FMT_WIDTH, BAR_TOTAL_FMT_WIDTH, BAR_TIME_WIDTH, BAR_RATE_WIDTH
|
||||||
# are imported from eb_dashboard_constants.py (SINGLE SOURCE OF TRUTH)
|
# are imported from eb_dashboard_constants.py (SINGLE SOURCE OF TRUTH)
|
||||||
@@ -227,10 +186,8 @@ def new_token():
|
|||||||
finally:
|
finally:
|
||||||
if attempt < ERROR_MAX_RETRY - 1:
|
if attempt < ERROR_MAX_RETRY - 1:
|
||||||
sleep(WAIT_BEFORE_RETRY)
|
sleep(WAIT_BEFORE_RETRY)
|
||||||
# Refresh token exhausted — attempt full re-login with stored credentials
|
logging.critical("Persistent error in refresh_token")
|
||||||
logging.warning("Refresh token exhausted. Attempting re-login with stored credentials.")
|
raise httpx.RequestError(message="Persistent error in refresh_token")
|
||||||
_do_login(_stored_username, _stored_password)
|
|
||||||
logging.info("Re-login successful. New tokens acquired.")
|
|
||||||
|
|
||||||
|
|
||||||
def api_call_with_retry(func):
|
def api_call_with_retry(func):
|
||||||
@@ -255,10 +212,7 @@ def api_call_with_retry(func):
|
|||||||
|
|
||||||
if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code == 401:
|
if isinstance(exc, httpx.HTTPStatusError) and exc.response.status_code == 401:
|
||||||
logging.info(f"Token expired for {func_name}. Refreshing token.")
|
logging.info(f"Token expired for {func_name}. Refreshing token.")
|
||||||
try:
|
|
||||||
new_token()
|
new_token()
|
||||||
except (httpx.RequestError, httpx.HTTPStatusError) as token_exc:
|
|
||||||
logging.warning(f"Token refresh/re-login failed for {func_name}: {token_exc}")
|
|
||||||
|
|
||||||
if attempt < ERROR_MAX_RETRY - 1:
|
if attempt < ERROR_MAX_RETRY - 1:
|
||||||
sleep(WAIT_BEFORE_RETRY)
|
sleep(WAIT_BEFORE_RETRY)
|
||||||
@@ -271,18 +225,8 @@ def api_call_with_retry(func):
|
|||||||
sleep(WAIT_BEFORE_NEW_BATCH_OF_RETRIES)
|
sleep(WAIT_BEFORE_NEW_BATCH_OF_RETRIES)
|
||||||
break # Exit for loop to restart batch in while True
|
break # Exit for loop to restart batch in while True
|
||||||
else:
|
else:
|
||||||
# All automatic batches exhausted — apply on_retry_exhausted policy
|
# All automatic batches exhausted, ask the user
|
||||||
with _user_interaction_lock:
|
with _user_interaction_lock:
|
||||||
if on_retry_exhausted == "ignore":
|
|
||||||
ctx = getattr(thread_local_storage, "current_patient_context", {"id": "Unknown", "pseudo": "Unknown"})
|
|
||||||
logging.warning(f"[AUTO-IGNORE] Skipping {func_name} for Patient {ctx['id']} ({ctx['pseudo']}). Error: {exc}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
elif on_retry_exhausted == "abort":
|
|
||||||
logging.critical(f"[AUTO-ABORT] Stopping script after persistent error in {func_name}. Error: {exc}")
|
|
||||||
raise httpx.RequestError(message=f"Persistent error in {func_name} (auto-aborted)")
|
|
||||||
|
|
||||||
else: # "ask" — display error then interactive prompt
|
|
||||||
console.print(f"\n[bold red]Persistent error in {func_name} after {batch_count} batches ({total_attempts} attempts).[/bold red]")
|
console.print(f"\n[bold red]Persistent error in {func_name} after {batch_count} batches ({total_attempts} attempts).[/bold red]")
|
||||||
console.print(f"[red]Exception: {exc}[/red]")
|
console.print(f"[red]Exception: {exc}[/red]")
|
||||||
|
|
||||||
@@ -300,6 +244,7 @@ def api_call_with_retry(func):
|
|||||||
batch_count = 1 # Reset batch counter for the next interactive round
|
batch_count = 1 # Reset batch counter for the next interactive round
|
||||||
break # Exit for loop to restart batch in while True
|
break # Exit for loop to restart batch in while True
|
||||||
elif choice == "Ignore (return None and continue)":
|
elif choice == "Ignore (return None and continue)":
|
||||||
|
# Retrieve context if available
|
||||||
ctx = getattr(thread_local_storage, "current_patient_context", {"id": "Unknown", "pseudo": "Unknown"})
|
ctx = getattr(thread_local_storage, "current_patient_context", {"id": "Unknown", "pseudo": "Unknown"})
|
||||||
logging.warning(f"[IGNORE] User opted to skip {func_name} for Patient {ctx['id']} ({ctx['pseudo']}). Error: {exc}")
|
logging.warning(f"[IGNORE] User opted to skip {func_name} for Patient {ctx['id']} ({ctx['pseudo']}). Error: {exc}")
|
||||||
return None
|
return None
|
||||||
@@ -314,37 +259,8 @@ def api_call_with_retry(func):
|
|||||||
# BLOCK 3: AUTHENTICATION
|
# BLOCK 3: AUTHENTICATION
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def _do_login(username, password):
|
|
||||||
"""Performs the two-step authentication (IAM → RC) with the given credentials.
|
|
||||||
Updates global access_token and refresh_token on success.
|
|
||||||
Raises httpx.RequestError or httpx.HTTPStatusError on failure.
|
|
||||||
Must NOT acquire _token_refresh_lock (caller's responsibility).
|
|
||||||
"""
|
|
||||||
global access_token, refresh_token
|
|
||||||
|
|
||||||
client = get_httpx_client()
|
|
||||||
client.base_url = IAM_URL
|
|
||||||
response = client.post(API_AUTH_LOGIN_ENDPOINT,
|
|
||||||
json={"username": username, "password": password},
|
|
||||||
timeout=60)
|
|
||||||
response.raise_for_status()
|
|
||||||
master_token = response.json()["access_token"]
|
|
||||||
user_id = response.json()["userId"]
|
|
||||||
|
|
||||||
client = get_httpx_client()
|
|
||||||
client.base_url = RC_URL
|
|
||||||
response = client.post(API_AUTH_CONFIG_TOKEN_ENDPOINT,
|
|
||||||
headers={"Authorization": f"Bearer {master_token}"},
|
|
||||||
json={"userId": user_id, "clientId": RC_APP_ID,
|
|
||||||
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"},
|
|
||||||
timeout=20)
|
|
||||||
response.raise_for_status()
|
|
||||||
access_token = response.json()["access_token"]
|
|
||||||
refresh_token = response.json()["refresh_token"]
|
|
||||||
|
|
||||||
|
|
||||||
def login():
|
def login():
|
||||||
global _stored_username, _stored_password
|
global access_token, refresh_token
|
||||||
|
|
||||||
user_name = (questionary.text("login :", default=DEFAULT_USER_NAME).ask())
|
user_name = (questionary.text("login :", default=DEFAULT_USER_NAME).ask())
|
||||||
password = (questionary.password("password :", default=DEFAULT_PASSWORD).ask())
|
password = (questionary.password("password :", default=DEFAULT_PASSWORD).ask())
|
||||||
@@ -352,18 +268,42 @@ def login():
|
|||||||
return "Exit"
|
return "Exit"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_do_login(user_name, password)
|
client = get_httpx_client()
|
||||||
|
client.base_url = IAM_URL
|
||||||
|
response = client.post(API_AUTH_LOGIN_ENDPOINT, json={"username": user_name, "password": password},
|
||||||
|
timeout=20)
|
||||||
|
response.raise_for_status()
|
||||||
|
master_token = response.json()["access_token"]
|
||||||
|
user_id = response.json()["userId"]
|
||||||
except httpx.RequestError as exc:
|
except httpx.RequestError as exc:
|
||||||
print(f"Login Error : {exc}")
|
print(f"Login Error : {exc}")
|
||||||
logging.warning(f"Login Error : {exc}")
|
logging.warning(f"Login Error : {exc}")
|
||||||
return "Error"
|
return "Error"
|
||||||
except httpx.HTTPStatusError as exc:
|
except httpx.HTTPStatusError as exc:
|
||||||
print(f"Login Error : {exc.response.status_code} for Url {exc.request.url}")
|
print(f"Login Error : {exc.response.status_code} for Url {exc.request.url}")
|
||||||
logging.warning(f"Login Error : {exc.response.status_code} for Url {exc.request.url}")
|
logging.warning(
|
||||||
|
f"Login Error : {exc.response.status_code} for Url {exc.request.url}")
|
||||||
|
return "Error"
|
||||||
|
|
||||||
|
try:
|
||||||
|
client = get_httpx_client()
|
||||||
|
client.base_url = RC_URL
|
||||||
|
response = client.post(API_AUTH_CONFIG_TOKEN_ENDPOINT, headers={"Authorization": f"Bearer {master_token}"},
|
||||||
|
json={"userId": user_id, "clientId": RC_APP_ID,
|
||||||
|
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"},
|
||||||
|
timeout=20)
|
||||||
|
response.raise_for_status()
|
||||||
|
access_token = response.json()["access_token"]
|
||||||
|
refresh_token = response.json()["refresh_token"]
|
||||||
|
except httpx.RequestError as exc:
|
||||||
|
print(f"Login Error : {exc}")
|
||||||
|
logging.warning(f"Login Error : {exc}")
|
||||||
|
return "Error"
|
||||||
|
except httpx.HTTPStatusError as exc:
|
||||||
|
print(f"Login Error : {exc.response.status_code} for Url {exc.request.url}")
|
||||||
|
logging.warning(f"Login Error : {exc}")
|
||||||
return "Error"
|
return "Error"
|
||||||
|
|
||||||
_stored_username = user_name
|
|
||||||
_stored_password = password
|
|
||||||
print()
|
print()
|
||||||
print("Login Success")
|
print("Login Success")
|
||||||
return "Success"
|
return "Success"
|
||||||
@@ -373,100 +313,6 @@ def login():
|
|||||||
# BLOCK 3B: FILE UTILITIES
|
# BLOCK 3B: FILE UTILITIES
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def ask_on_retry_exhausted():
|
|
||||||
"""Asks the user what to do when all API retry batches are exhausted."""
|
|
||||||
global on_retry_exhausted
|
|
||||||
choice = questionary.select(
|
|
||||||
"On retry exhausted :",
|
|
||||||
choices=[
|
|
||||||
"Ask (interactive prompt)",
|
|
||||||
"Ignore (return None and continue)",
|
|
||||||
"Abort (stop script)"
|
|
||||||
]
|
|
||||||
).ask()
|
|
||||||
if choice is None or choice == "Ask (interactive prompt)":
|
|
||||||
on_retry_exhausted = "ask"
|
|
||||||
elif choice == "Ignore (return None and continue)":
|
|
||||||
on_retry_exhausted = "ignore"
|
|
||||||
else:
|
|
||||||
on_retry_exhausted = "abort"
|
|
||||||
|
|
||||||
|
|
||||||
def ask_fetch_six_month_visit():
|
|
||||||
"""Asks the user whether to fetch 6-month visit data (slow API call, ~5s per patient)."""
|
|
||||||
global fetch_six_month_visit
|
|
||||||
choice = questionary.select(
|
|
||||||
"Fetch 6-month visit progress data? (slow, ~5s per patient) :",
|
|
||||||
choices=[
|
|
||||||
"No (skip, faster execution)",
|
|
||||||
"Yes (fetch 6-month visit data)"
|
|
||||||
]
|
|
||||||
).ask()
|
|
||||||
fetch_six_month_visit = (choice == "Yes (fetch 6-month visit data)")
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_scheduled_launch():
|
|
||||||
"""Asks the user when to start the processing and waits if needed.
|
|
||||||
Options: Immediately / In X minutes / At HH:MM
|
|
||||||
"""
|
|
||||||
choice = questionary.select(
|
|
||||||
"When to start processing ?",
|
|
||||||
choices=["Immediately", "In X minutes", "At HH:MM"]
|
|
||||||
).ask()
|
|
||||||
|
|
||||||
if choice is None or choice == "Immediately":
|
|
||||||
return
|
|
||||||
|
|
||||||
if choice == "In X minutes":
|
|
||||||
minutes_str = questionary.text(
|
|
||||||
"Number of minutes :",
|
|
||||||
validate=lambda x: x.isdigit() and int(x) > 0
|
|
||||||
).ask()
|
|
||||||
if not minutes_str:
|
|
||||||
return
|
|
||||||
target_time = datetime.now() + timedelta(minutes=int(minutes_str))
|
|
||||||
|
|
||||||
else: # "At HH:MM"
|
|
||||||
time_str = questionary.text(
|
|
||||||
"Start time (HH:MM) :",
|
|
||||||
validate=lambda x: bool(re.match(r'^\d{2}:\d{2}$', x)) and
|
|
||||||
0 <= int(x.split(':')[0]) <= 23 and
|
|
||||||
0 <= int(x.split(':')[1]) <= 59
|
|
||||||
).ask()
|
|
||||||
if not time_str:
|
|
||||||
return
|
|
||||||
now = datetime.now()
|
|
||||||
h, m = int(time_str.split(':')[0]), int(time_str.split(':')[1])
|
|
||||||
target_time = now.replace(hour=h, minute=m, second=0, microsecond=0)
|
|
||||||
if target_time <= now:
|
|
||||||
console.print("[yellow]⚠ Specified time is already past. Starting immediately.[/yellow]")
|
|
||||||
return
|
|
||||||
|
|
||||||
print()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
remaining = target_time - datetime.now()
|
|
||||||
if remaining.total_seconds() <= 0:
|
|
||||||
break
|
|
||||||
total_secs = int(remaining.total_seconds())
|
|
||||||
h = total_secs // 3600
|
|
||||||
m = (total_secs % 3600) // 60
|
|
||||||
s = total_secs % 60
|
|
||||||
target_str = target_time.strftime('%H:%M:%S')
|
|
||||||
print(f"\r Starting in {h:02d}:{m:02d}:{s:02d}... (at {target_str}) — Ctrl+C to cancel ",
|
|
||||||
end="", flush=True)
|
|
||||||
sleep(1)
|
|
||||||
# Flush keyboard buffer to prevent stray keystrokes from polluting subsequent prompts
|
|
||||||
while msvcrt.kbhit():
|
|
||||||
msvcrt.getwch()
|
|
||||||
print()
|
|
||||||
console.print("[green]✓ Starting processing.[/green]")
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print()
|
|
||||||
console.print("[bold red]Launch cancelled by user.[/bold red]")
|
|
||||||
raise SystemExit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def load_json_file(filename):
|
def load_json_file(filename):
|
||||||
"""
|
"""
|
||||||
Load a JSON file from disk.
|
Load a JSON file from disk.
|
||||||
@@ -494,7 +340,7 @@ def load_json_file(filename):
|
|||||||
def load_inclusions_mapping_config():
|
def load_inclusions_mapping_config():
|
||||||
"""Loads and validates the inclusions mapping configuration from the Excel file."""
|
"""Loads and validates the inclusions mapping configuration from the Excel file."""
|
||||||
global inclusions_mapping_config
|
global inclusions_mapping_config
|
||||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
config_path = os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Load with data_only=True to read calculated values instead of formulas
|
# Load with data_only=True to read calculated values instead of formulas
|
||||||
@@ -596,7 +442,7 @@ def load_inclusions_mapping_config():
|
|||||||
def load_organizations_mapping_config():
|
def load_organizations_mapping_config():
|
||||||
"""Loads and validates the organizations mapping configuration from the Excel file."""
|
"""Loads and validates the organizations mapping configuration from the Excel file."""
|
||||||
global organizations_mapping_config
|
global organizations_mapping_config
|
||||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
config_path = os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Load with data_only=True to read calculated values instead of formulas
|
# Load with data_only=True to read calculated values instead of formulas
|
||||||
@@ -654,12 +500,6 @@ def _find_questionnaire_by_id(qcm_dict, qcm_id):
|
|||||||
if not isinstance(qcm_dict, dict):
|
if not isinstance(qcm_dict, dict):
|
||||||
return None
|
return None
|
||||||
qcm_data = qcm_dict.get(qcm_id)
|
qcm_data = qcm_dict.get(qcm_id)
|
||||||
if qcm_data and qcm_data.get("_count", 1) > 1:
|
|
||||||
ctx = getattr(thread_local_storage, "current_patient_context", {"id": "Unknown", "pseudo": "Unknown"})
|
|
||||||
logging.error(
|
|
||||||
f"[DUPLICATE QCM] Patient {ctx['id']} ({ctx['pseudo']}): "
|
|
||||||
f"Questionnaire id='{qcm_id}' appeared {qcm_data['_count']} times in API response — using last received copy"
|
|
||||||
)
|
|
||||||
return qcm_data.get("answers") if qcm_data else None
|
return qcm_data.get("answers") if qcm_data else None
|
||||||
|
|
||||||
|
|
||||||
@@ -667,32 +507,20 @@ def _find_questionnaire_by_name(qcm_dict, name):
|
|||||||
"""Finds a questionnaire by name (sequential search, returns first match)."""
|
"""Finds a questionnaire by name (sequential search, returns first match)."""
|
||||||
if not isinstance(qcm_dict, dict):
|
if not isinstance(qcm_dict, dict):
|
||||||
return None
|
return None
|
||||||
matches = [qcm for qcm in qcm_dict.values()
|
for qcm in qcm_dict.values():
|
||||||
if get_nested_value(qcm, ["questionnaire", "name"]) == name]
|
if get_nested_value(qcm, ["questionnaire", "name"]) == name:
|
||||||
if len(matches) > 1:
|
return qcm.get("answers")
|
||||||
ctx = getattr(thread_local_storage, "current_patient_context", {"id": "Unknown", "pseudo": "Unknown"})
|
return None
|
||||||
ids = [get_nested_value(q, ["questionnaire", "id"]) for q in matches]
|
|
||||||
logging.error(
|
|
||||||
f"[DUPLICATE QCM] Patient {ctx['id']} ({ctx['pseudo']}): "
|
|
||||||
f"Questionnaire name='{name}' matches {len(matches)} entries (ids: {ids}) — returning first match"
|
|
||||||
)
|
|
||||||
return matches[0].get("answers") if matches else None
|
|
||||||
|
|
||||||
|
|
||||||
def _find_questionnaire_by_category(qcm_dict, category):
|
def _find_questionnaire_by_category(qcm_dict, category):
|
||||||
"""Finds a questionnaire by category (sequential search, returns first match)."""
|
"""Finds a questionnaire by category (sequential search, returns first match)."""
|
||||||
if not isinstance(qcm_dict, dict):
|
if not isinstance(qcm_dict, dict):
|
||||||
return None
|
return None
|
||||||
matches = [qcm for qcm in qcm_dict.values()
|
for qcm in qcm_dict.values():
|
||||||
if get_nested_value(qcm, ["questionnaire", "category"]) == category]
|
if get_nested_value(qcm, ["questionnaire", "category"]) == category:
|
||||||
if len(matches) > 1:
|
return qcm.get("answers")
|
||||||
ctx = getattr(thread_local_storage, "current_patient_context", {"id": "Unknown", "pseudo": "Unknown"})
|
return None
|
||||||
ids = [get_nested_value(q, ["questionnaire", "id"]) for q in matches]
|
|
||||||
logging.error(
|
|
||||||
f"[DUPLICATE QCM] Patient {ctx['id']} ({ctx['pseudo']}): "
|
|
||||||
f"Questionnaire category='{category}' matches {len(matches)} entries (ids: {ids}) — returning first match"
|
|
||||||
)
|
|
||||||
return matches[0].get("answers") if matches else None
|
|
||||||
|
|
||||||
|
|
||||||
def _get_field_value_from_questionnaire(all_questionnaires, field_config):
|
def _get_field_value_from_questionnaire(all_questionnaires, field_config):
|
||||||
@@ -729,13 +557,6 @@ def get_value_from_inclusion(inclusion_dict, key):
|
|||||||
|
|
||||||
def _execute_custom_function(function_name, args, output_inclusion):
|
def _execute_custom_function(function_name, args, output_inclusion):
|
||||||
"""Executes a custom function for a calculated field."""
|
"""Executes a custom function for a calculated field."""
|
||||||
|
|
||||||
def dominant_no_value(has_undef, has_na):
|
|
||||||
"""Returns the dominant sentinel: 'undefined' > 'N/A' > None (real value present)."""
|
|
||||||
if has_undef: return "undefined"
|
|
||||||
if has_na: return "N/A"
|
|
||||||
return None
|
|
||||||
|
|
||||||
if function_name == "search_in_fields_using_regex":
|
if function_name == "search_in_fields_using_regex":
|
||||||
if not args or len(args) < 2:
|
if not args or len(args) < 2:
|
||||||
return "$$$$ Argument Error: search_in_fields_using_regex requires at least 2 arguments"
|
return "$$$$ Argument Error: search_in_fields_using_regex requires at least 2 arguments"
|
||||||
@@ -744,19 +565,16 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
field_names = args[1:]
|
field_names = args[1:]
|
||||||
|
|
||||||
field_values = []
|
field_values = []
|
||||||
has_undefined = False
|
all_undefined = True
|
||||||
has_na = False
|
|
||||||
|
|
||||||
for field_name in field_names:
|
for field_name in field_names:
|
||||||
value = get_value_from_inclusion(output_inclusion, field_name)
|
value = get_value_from_inclusion(output_inclusion, field_name)
|
||||||
field_values.append(value)
|
field_values.append(value)
|
||||||
if value is None or value == "undefined":
|
if value is not None and value != "undefined":
|
||||||
has_undefined = True
|
all_undefined = False
|
||||||
elif value == "N/A":
|
|
||||||
has_na = True
|
|
||||||
|
|
||||||
if not any(v not in (None, "undefined", "N/A") for v in field_values):
|
if all_undefined:
|
||||||
return dominant_no_value(has_undefined, has_na)
|
return "undefined"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for value in field_values:
|
for value in field_values:
|
||||||
@@ -777,8 +595,6 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
|
|
||||||
if value is None or value == "undefined":
|
if value is None or value == "undefined":
|
||||||
return "undefined"
|
return "undefined"
|
||||||
if value == "N/A":
|
|
||||||
return "N/A"
|
|
||||||
|
|
||||||
match = re.search(r'\((.*?)\)', str(value))
|
match = re.search(r'\((.*?)\)', str(value))
|
||||||
return match.group(1) if match else "undefined"
|
return match.group(1) if match else "undefined"
|
||||||
@@ -792,8 +608,6 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
|
|
||||||
if status is None or status == "undefined":
|
if status is None or status == "undefined":
|
||||||
return "undefined"
|
return "undefined"
|
||||||
if status == "N/A":
|
|
||||||
return "N/A"
|
|
||||||
|
|
||||||
if not isinstance(is_terminated, bool) or not is_terminated:
|
if not isinstance(is_terminated, bool) or not is_terminated:
|
||||||
return status
|
return status
|
||||||
@@ -803,8 +617,7 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
elif function_name == "if_then_else":
|
elif function_name == "if_then_else":
|
||||||
# Unified conditional function
|
# Unified conditional function
|
||||||
# Syntax: ["operator", arg1, arg2_optional, result_if_true, result_if_false]
|
# Syntax: ["operator", arg1, arg2_optional, result_if_true, result_if_false]
|
||||||
# Operators: "is_true", "is_false", "all_true", "any_true", "is_defined", "is_undefined", "all_defined", "==", "!=", ">", ">=", "<", "<="
|
# Operators: "is_true", "is_false", "all_true", "is_defined", "is_undefined", "all_defined", "==", "!="
|
||||||
# Sentinel propagation: "undefined" (unknown) > "N/A" (not applicable) > real value.
|
|
||||||
|
|
||||||
if not args or len(args) < 4:
|
if not args or len(args) < 4:
|
||||||
return "$$$$ Argument Error: if_then_else requires at least 4 arguments"
|
return "$$$$ Argument Error: if_then_else requires at least 4 arguments"
|
||||||
@@ -832,8 +645,6 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
value = resolve_value(args[1])
|
value = resolve_value(args[1])
|
||||||
if value is None or value == "undefined":
|
if value is None or value == "undefined":
|
||||||
return "undefined"
|
return "undefined"
|
||||||
if value == "N/A":
|
|
||||||
return "N/A"
|
|
||||||
condition = (value is True)
|
condition = (value is True)
|
||||||
result_if_true = resolve_value(args[2])
|
result_if_true = resolve_value(args[2])
|
||||||
result_if_false = resolve_value(args[3])
|
result_if_false = resolve_value(args[3])
|
||||||
@@ -844,8 +655,6 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
value = resolve_value(args[1])
|
value = resolve_value(args[1])
|
||||||
if value is None or value == "undefined":
|
if value is None or value == "undefined":
|
||||||
return "undefined"
|
return "undefined"
|
||||||
if value == "N/A":
|
|
||||||
return "N/A"
|
|
||||||
condition = (value is False)
|
condition = (value is False)
|
||||||
result_if_true = resolve_value(args[2])
|
result_if_true = resolve_value(args[2])
|
||||||
result_if_false = resolve_value(args[3])
|
result_if_false = resolve_value(args[3])
|
||||||
@@ -857,58 +666,22 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
if not isinstance(fields_arg, list):
|
if not isinstance(fields_arg, list):
|
||||||
return "$$$$ Argument Error: all_true requires arg1 to be a list of field names"
|
return "$$$$ Argument Error: all_true requires arg1 to be a list of field names"
|
||||||
|
|
||||||
has_undefined = False
|
|
||||||
has_na = False
|
|
||||||
conditions = []
|
conditions = []
|
||||||
for field_name in fields_arg:
|
for field_name in fields_arg:
|
||||||
field_value = get_value_from_inclusion(output_inclusion, field_name)
|
field_value = get_value_from_inclusion(output_inclusion, field_name)
|
||||||
if field_value is None or field_value == "undefined":
|
if field_value is None or field_value == "undefined":
|
||||||
has_undefined = True
|
return "undefined"
|
||||||
elif field_value == "N/A":
|
|
||||||
has_na = True
|
|
||||||
else:
|
|
||||||
conditions.append(field_value)
|
conditions.append(field_value)
|
||||||
|
|
||||||
status = dominant_no_value(has_undefined, has_na)
|
|
||||||
if status:
|
|
||||||
return status
|
|
||||||
|
|
||||||
condition = all(conditions)
|
condition = all(conditions)
|
||||||
result_if_true = resolve_value(args[2])
|
result_if_true = resolve_value(args[2])
|
||||||
result_if_false = resolve_value(args[3])
|
result_if_false = resolve_value(args[3])
|
||||||
|
|
||||||
elif operator == "any_true":
|
|
||||||
# OR semantics: returns "undefined" only if ALL operands are undefined
|
|
||||||
if len(args) != 4:
|
|
||||||
return "$$$$ Argument Error: any_true requires 4 arguments"
|
|
||||||
fields_arg = args[1]
|
|
||||||
if not isinstance(fields_arg, list):
|
|
||||||
return "$$$$ Argument Error: any_true requires arg1 to be a list of field names"
|
|
||||||
|
|
||||||
has_undefined = False
|
|
||||||
has_na = False
|
|
||||||
resolved = []
|
|
||||||
for field_name in fields_arg:
|
|
||||||
field_value = get_value_from_inclusion(output_inclusion, field_name)
|
|
||||||
if field_value is None or field_value == "undefined":
|
|
||||||
has_undefined = True
|
|
||||||
elif field_value == "N/A":
|
|
||||||
has_na = True
|
|
||||||
else:
|
|
||||||
resolved.append(field_value)
|
|
||||||
|
|
||||||
if not resolved:
|
|
||||||
return dominant_no_value(has_undefined, has_na)
|
|
||||||
|
|
||||||
condition = any(resolved)
|
|
||||||
result_if_true = resolve_value(args[2])
|
|
||||||
result_if_false = resolve_value(args[3])
|
|
||||||
|
|
||||||
elif operator == "is_defined":
|
elif operator == "is_defined":
|
||||||
if len(args) != 4:
|
if len(args) != 4:
|
||||||
return "$$$$ Argument Error: is_defined requires 4 arguments"
|
return "$$$$ Argument Error: is_defined requires 4 arguments"
|
||||||
value = resolve_value(args[1])
|
value = resolve_value(args[1])
|
||||||
condition = (value is not None and value != "undefined" and value != "N/A")
|
condition = (value is not None and value != "undefined")
|
||||||
result_if_true = resolve_value(args[2])
|
result_if_true = resolve_value(args[2])
|
||||||
result_if_false = resolve_value(args[3])
|
result_if_false = resolve_value(args[3])
|
||||||
|
|
||||||
@@ -929,7 +702,7 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
|
|
||||||
for field_name in fields_arg:
|
for field_name in fields_arg:
|
||||||
field_value = get_value_from_inclusion(output_inclusion, field_name)
|
field_value = get_value_from_inclusion(output_inclusion, field_name)
|
||||||
if field_value is None or field_value == "undefined" or field_value == "N/A":
|
if field_value is None or field_value == "undefined":
|
||||||
condition = False
|
condition = False
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -944,12 +717,8 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
value1 = resolve_value(args[1])
|
value1 = resolve_value(args[1])
|
||||||
value2 = resolve_value(args[2])
|
value2 = resolve_value(args[2])
|
||||||
|
|
||||||
v1_undef = value1 is None or value1 == "undefined"
|
if value1 is None or value1 == "undefined" or value2 is None or value2 == "undefined":
|
||||||
v2_undef = value2 is None or value2 == "undefined"
|
return "undefined"
|
||||||
status = dominant_no_value(v1_undef or v2_undef,
|
|
||||||
value1 == "N/A" or value2 == "N/A")
|
|
||||||
if status:
|
|
||||||
return status
|
|
||||||
|
|
||||||
condition = (value1 == value2)
|
condition = (value1 == value2)
|
||||||
result_if_true = resolve_value(args[3])
|
result_if_true = resolve_value(args[3])
|
||||||
@@ -961,58 +730,13 @@ def _execute_custom_function(function_name, args, output_inclusion):
|
|||||||
value1 = resolve_value(args[1])
|
value1 = resolve_value(args[1])
|
||||||
value2 = resolve_value(args[2])
|
value2 = resolve_value(args[2])
|
||||||
|
|
||||||
v1_undef = value1 is None or value1 == "undefined"
|
if value1 is None or value1 == "undefined" or value2 is None or value2 == "undefined":
|
||||||
v2_undef = value2 is None or value2 == "undefined"
|
return "undefined"
|
||||||
status = dominant_no_value(v1_undef or v2_undef,
|
|
||||||
value1 == "N/A" or value2 == "N/A")
|
|
||||||
if status:
|
|
||||||
return status
|
|
||||||
|
|
||||||
condition = (value1 != value2)
|
condition = (value1 != value2)
|
||||||
result_if_true = resolve_value(args[3])
|
result_if_true = resolve_value(args[3])
|
||||||
result_if_false = resolve_value(args[4])
|
result_if_false = resolve_value(args[4])
|
||||||
|
|
||||||
elif operator in (">", ">=", "<", "<="):
|
|
||||||
if len(args) != 5:
|
|
||||||
return f"$$$$ Argument Error: {operator} requires 5 arguments"
|
|
||||||
value1 = resolve_value(args[1])
|
|
||||||
value2 = resolve_value(args[2])
|
|
||||||
|
|
||||||
v1_undef = value1 is None or value1 == "undefined"
|
|
||||||
v2_undef = value2 is None or value2 == "undefined"
|
|
||||||
status = dominant_no_value(v1_undef or v2_undef,
|
|
||||||
value1 == "N/A" or value2 == "N/A")
|
|
||||||
if status:
|
|
||||||
return status
|
|
||||||
|
|
||||||
# Si l'un est numérique, tenter de convertir l'autre ; sinon comparer en string
|
|
||||||
cmp1, cmp2 = value1, value2
|
|
||||||
if isinstance(value1, (int, float)) and not isinstance(value2, (int, float)):
|
|
||||||
try:
|
|
||||||
cmp2 = float(value2)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
cmp1 = str(value1)
|
|
||||||
elif isinstance(value2, (int, float)) and not isinstance(value1, (int, float)):
|
|
||||||
try:
|
|
||||||
cmp1 = float(value1)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
cmp2 = str(value2)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if operator == ">":
|
|
||||||
condition = (cmp1 > cmp2)
|
|
||||||
elif operator == ">=":
|
|
||||||
condition = (cmp1 >= cmp2)
|
|
||||||
elif operator == "<":
|
|
||||||
condition = (cmp1 < cmp2)
|
|
||||||
else:
|
|
||||||
condition = (cmp1 <= cmp2)
|
|
||||||
except TypeError:
|
|
||||||
return f"$$$$ Comparison Error: cannot compare {type(cmp1).__name__} and {type(cmp2).__name__}"
|
|
||||||
|
|
||||||
result_if_true = resolve_value(args[3])
|
|
||||||
result_if_false = resolve_value(args[4])
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return f"$$$$ Unknown Operator: {operator}"
|
return f"$$$$ Unknown Operator: {operator}"
|
||||||
|
|
||||||
@@ -1035,8 +759,6 @@ def process_inclusions_mapping(output_inclusion, inclusion_data, record_data, re
|
|||||||
|
|
||||||
if condition_value is None or condition_value == "undefined":
|
if condition_value is None or condition_value == "undefined":
|
||||||
final_value = "undefined"
|
final_value = "undefined"
|
||||||
elif condition_value == "N/A":
|
|
||||||
final_value = "N/A"
|
|
||||||
elif not isinstance(condition_value, bool):
|
elif not isinstance(condition_value, bool):
|
||||||
final_value = "$$$$ Condition Field Error"
|
final_value = "$$$$ Condition Field Error"
|
||||||
elif not condition_value:
|
elif not condition_value:
|
||||||
@@ -1240,13 +962,6 @@ def get_all_questionnaires_by_patient(patient_id, record_data):
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
response_data = response.json()
|
response_data = response.json()
|
||||||
|
|
||||||
# First pass: count occurrences of each q_id to detect duplicates at lookup time
|
|
||||||
q_id_counts = {}
|
|
||||||
for item in response_data:
|
|
||||||
q_id = get_nested_value(item, path=["questionnaire", "id"])
|
|
||||||
if q_id:
|
|
||||||
q_id_counts[q_id] = q_id_counts.get(q_id, 0) + 1
|
|
||||||
|
|
||||||
# Build dictionary with questionnaire metadata for searching
|
# Build dictionary with questionnaire metadata for searching
|
||||||
results = {}
|
results = {}
|
||||||
for item in response_data:
|
for item in response_data:
|
||||||
@@ -1254,17 +969,6 @@ def get_all_questionnaires_by_patient(patient_id, record_data):
|
|||||||
q_name = get_nested_value(item, path=["questionnaire", "name"])
|
q_name = get_nested_value(item, path=["questionnaire", "name"])
|
||||||
q_category = get_nested_value(item, path=["questionnaire", "category"])
|
q_category = get_nested_value(item, path=["questionnaire", "category"])
|
||||||
answers = get_nested_value(item, path=["answers"], default={})
|
answers = get_nested_value(item, path=["answers"], default={})
|
||||||
|
|
||||||
# ── DRAFT ANSWERS FILTER ────────────────────────────────────────────────
|
|
||||||
# "Draft" answers have a missing or mismatched subject (patient id):
|
|
||||||
# they are not yet submitted and should not be treated as valid patient data.
|
|
||||||
# Active by default; use --include-drafts CLI flag to disable.
|
|
||||||
if not _include_drafts:
|
|
||||||
answers_patient = answers.get("subject") if isinstance(answers, dict) else None
|
|
||||||
if not answers_patient or answers_patient != patient_id:
|
|
||||||
answers = {}
|
|
||||||
# ────────────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
if q_id:
|
if q_id:
|
||||||
results[q_id] = {
|
results[q_id] = {
|
||||||
"questionnaire": {
|
"questionnaire": {
|
||||||
@@ -1272,8 +976,7 @@ def get_all_questionnaires_by_patient(patient_id, record_data):
|
|||||||
"name": q_name,
|
"name": q_name,
|
||||||
"category": q_category
|
"category": q_category
|
||||||
},
|
},
|
||||||
"answers": answers,
|
"answers": answers
|
||||||
"_count": q_id_counts[q_id]
|
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -1432,12 +1135,8 @@ def _process_inclusion_data(inclusion, organization):
|
|||||||
output_inclusion = {}
|
output_inclusion = {}
|
||||||
|
|
||||||
# --- Prepare all data sources ---
|
# --- Prepare all data sources ---
|
||||||
# 1. Launch Visit Search asynchronously (it's slow, ~5s) — only if enabled by user
|
# 1. 6-month visit loading disabled on this branch (No-6-Month-Visit)
|
||||||
# We use run_with_context to pass the patient identity to the new thread
|
# visit_future = subtasks_thread_pool.submit(run_with_context, search_visit_by_pseudo_and_order, ctx, pseudo, 2)
|
||||||
if fetch_six_month_visit:
|
|
||||||
visit_future = subtasks_thread_pool.submit(run_with_context, search_visit_by_pseudo_and_order, ctx, pseudo, 2)
|
|
||||||
else:
|
|
||||||
visit_future = None
|
|
||||||
|
|
||||||
# 2. Prepare inclusion_data: enrich inclusion with organization info
|
# 2. Prepare inclusion_data: enrich inclusion with organization info
|
||||||
inclusion_data = dict(inclusion)
|
inclusion_data = dict(inclusion)
|
||||||
@@ -1461,10 +1160,7 @@ def _process_inclusion_data(inclusion, organization):
|
|||||||
logging.error(f"Error fetching request data for patient {patient_id}: {e}")
|
logging.error(f"Error fetching request data for patient {patient_id}: {e}")
|
||||||
request_data = None
|
request_data = None
|
||||||
|
|
||||||
try:
|
# 6-month visit loading disabled on this branch (No-6-Month-Visit)
|
||||||
six_month_visit_data = visit_future.result() if visit_future is not None else {}
|
|
||||||
except Exception as e:
|
|
||||||
logging.error(f"Error searching 6-month visit for patient {pseudo}: {e}")
|
|
||||||
six_month_visit_data = None
|
six_month_visit_data = None
|
||||||
|
|
||||||
# --- Process all fields from configuration ---
|
# --- Process all fields from configuration ---
|
||||||
@@ -1519,7 +1215,7 @@ def main():
|
|||||||
load_organizations_mapping_config()
|
load_organizations_mapping_config()
|
||||||
|
|
||||||
# Completely externalized Excel-only workflow
|
# Completely externalized Excel-only workflow
|
||||||
export_excel_only(sys.argv, get_output_filename(INCLUSIONS_FILE_NAME), get_output_filename(ORGANIZATIONS_FILE_NAME),
|
export_excel_only(sys.argv, INCLUSIONS_FILE_NAME, ORGANIZATIONS_FILE_NAME,
|
||||||
inclusions_mapping_config, organizations_mapping_config)
|
inclusions_mapping_config, organizations_mapping_config)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1532,19 +1228,10 @@ def main():
|
|||||||
if login_status == "Exit":
|
if login_status == "Exit":
|
||||||
return
|
return
|
||||||
|
|
||||||
print()
|
|
||||||
ask_fetch_six_month_visit()
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
number_of_threads = int((questionary.text("Number of threads :", default="12",
|
number_of_threads = int((questionary.text("Number of threads :", default="12",
|
||||||
validate=lambda x: x.isdigit() and 0 < int(x) <= MAX_THREADS).ask()))
|
validate=lambda x: x.isdigit() and 0 < int(x) <= MAX_THREADS).ask()))
|
||||||
|
|
||||||
print()
|
|
||||||
ask_on_retry_exhausted()
|
|
||||||
|
|
||||||
print()
|
|
||||||
wait_for_scheduled_launch()
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
load_inclusions_mapping_config()
|
load_inclusions_mapping_config()
|
||||||
load_organizations_mapping_config()
|
load_organizations_mapping_config()
|
||||||
@@ -1603,14 +1290,6 @@ def main():
|
|||||||
inclusions_total_count = sum(org.get('patients_count', 0) for org in organizations_list)
|
inclusions_total_count = sum(org.get('patients_count', 0) for org in organizations_list)
|
||||||
organizations_list.sort(key=lambda org: (-org.get('patients_count', 0), org.get('name', '')))
|
organizations_list.sort(key=lambda org: (-org.get('patients_count', 0), org.get('name', '')))
|
||||||
|
|
||||||
# ╔══════════════════════════════════════════════════════════════════╗
|
|
||||||
# ║ TEST INSTRUMENTATION — REMOVE BEFORE PRODUCTION ║
|
|
||||||
# ║ Limits the table to organizations at ranks 33 and 34 ║
|
|
||||||
# ║ (indices 32 and 33, after descending sort by patients_count) ║
|
|
||||||
# ╚══════════════════════════════════════════════════════════════════╝
|
|
||||||
# organizations_list = [org for i, org in enumerate(organizations_list) if i in (32, 33)]
|
|
||||||
# ══════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
number_of_organizations = len(organizations_list)
|
number_of_organizations = len(organizations_list)
|
||||||
print(f"{inclusions_total_count} Inclusions in {number_of_organizations} Organizations...")
|
print(f"{inclusions_total_count} Inclusions in {number_of_organizations} Organizations...")
|
||||||
print()
|
print()
|
||||||
@@ -1664,7 +1343,7 @@ def main():
|
|||||||
has_coherence_critical, has_regression_critical = run_quality_checks(
|
has_coherence_critical, has_regression_critical = run_quality_checks(
|
||||||
current_inclusions=output_inclusions, # list: données en mémoire (nouvellement collectées)
|
current_inclusions=output_inclusions, # list: données en mémoire (nouvellement collectées)
|
||||||
organizations_list=organizations_list, # list: données en mémoire avec compteurs
|
organizations_list=organizations_list, # list: données en mémoire avec compteurs
|
||||||
old_inclusions_filename=get_output_filename(INCLUSIONS_FILE_NAME) # str: current file on disk (with suffix if any)
|
old_inclusions_filename=INCLUSIONS_FILE_NAME # str: "endobest_inclusions.json" (version courante sur disque)
|
||||||
)
|
)
|
||||||
|
|
||||||
# === CHECK FOR CRITICAL ISSUES AND ASK USER CONFIRMATION ===
|
# === CHECK FOR CRITICAL ISSUES AND ASK USER CONFIRMATION ===
|
||||||
@@ -1689,9 +1368,9 @@ def main():
|
|||||||
# === WRITE NEW FILES ===
|
# === WRITE NEW FILES ===
|
||||||
print("Writing files...")
|
print("Writing files...")
|
||||||
|
|
||||||
with open(get_output_filename(INCLUSIONS_FILE_NAME), 'w', encoding='utf-8') as f_json:
|
with open(INCLUSIONS_FILE_NAME, 'w', encoding='utf-8') as f_json:
|
||||||
json.dump(output_inclusions, f_json, indent=4, ensure_ascii=False)
|
json.dump(output_inclusions, f_json, indent=4, ensure_ascii=False)
|
||||||
with open(get_output_filename(ORGANIZATIONS_FILE_NAME), 'w', encoding='utf-8') as f_json:
|
with open(ORGANIZATIONS_FILE_NAME, 'w', encoding='utf-8') as f_json:
|
||||||
json.dump(organizations_list, f_json, indent=4, ensure_ascii=False)
|
json.dump(organizations_list, f_json, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
console.print("[green]✓ Data saved to JSON files[/green]")
|
console.print("[green]✓ Data saved to JSON files[/green]")
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
@echo off
|
|
||||||
eb_dashboard.exe --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix %*
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
|
||||||
python eb_dashboard.py --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix %*
|
|
||||||
@@ -34,7 +34,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
xw = None
|
xw = None
|
||||||
|
|
||||||
from eb_dashboard_utils import get_nested_value, get_config_path, get_dashboard_config_path, get_output_filename
|
from eb_dashboard_utils import get_nested_value, get_config_path
|
||||||
from eb_dashboard_constants import (
|
from eb_dashboard_constants import (
|
||||||
INCLUSIONS_FILE_NAME,
|
INCLUSIONS_FILE_NAME,
|
||||||
ORGANIZATIONS_FILE_NAME,
|
ORGANIZATIONS_FILE_NAME,
|
||||||
@@ -117,7 +117,7 @@ def load_excel_export_config(console_instance=None):
|
|||||||
if console_instance:
|
if console_instance:
|
||||||
console = console_instance
|
console = console_instance
|
||||||
|
|
||||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
config_path = os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||||
error_messages = []
|
error_messages = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -399,7 +399,7 @@ def export_to_excel(inclusions_data, organizations_data, excel_config,
|
|||||||
output_template = workbook_config.get("output_file_name_template")
|
output_template = workbook_config.get("output_file_name_template")
|
||||||
if_output_exists = workbook_config.get("if_output_exists", OUTPUT_ACTION_OVERWRITE)
|
if_output_exists = workbook_config.get("if_output_exists", OUTPUT_ACTION_OVERWRITE)
|
||||||
|
|
||||||
# Resolve output filename, then apply --add-suffix if provided
|
# Resolve output filename
|
||||||
try:
|
try:
|
||||||
output_filename = output_template.format(**template_vars)
|
output_filename = output_template.format(**template_vars)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
@@ -407,7 +407,6 @@ def export_to_excel(inclusions_data, organizations_data, excel_config,
|
|||||||
error_count += 1
|
error_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
output_filename = get_output_filename(output_filename)
|
|
||||||
output_path = os.path.join(EXCEL_OUTPUT_FOLDER, output_filename)
|
output_path = os.path.join(EXCEL_OUTPUT_FOLDER, output_filename)
|
||||||
|
|
||||||
# Log workbook processing start
|
# Log workbook processing start
|
||||||
@@ -557,7 +556,7 @@ def _prepare_template_variables():
|
|||||||
"""
|
"""
|
||||||
# Get UTC timestamp from inclusions file
|
# Get UTC timestamp from inclusions file
|
||||||
# Use constant from eb_dashboard_constants (SINGLE SOURCE OF TRUTH)
|
# Use constant from eb_dashboard_constants (SINGLE SOURCE OF TRUTH)
|
||||||
inclusions_file = get_output_filename(INCLUSIONS_FILE_NAME)
|
inclusions_file = INCLUSIONS_FILE_NAME
|
||||||
if os.path.exists(inclusions_file):
|
if os.path.exists(inclusions_file):
|
||||||
file_mtime = os.path.getmtime(inclusions_file)
|
file_mtime = os.path.getmtime(inclusions_file)
|
||||||
extract_date_time_utc = datetime.fromtimestamp(file_mtime, tz=timezone.utc)
|
extract_date_time_utc = datetime.fromtimestamp(file_mtime, tz=timezone.utc)
|
||||||
@@ -755,11 +754,7 @@ def _apply_value_replacement(value, replacements):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
for value_before, value_after in replacements:
|
for value_before, value_after in replacements:
|
||||||
# bool is a subclass of int in Python (True == 1, False == 0):
|
if value == value_before: # Strict equality
|
||||||
# reject the match if one side is bool and the other is not.
|
|
||||||
if isinstance(value, bool) != isinstance(value_before, bool):
|
|
||||||
continue
|
|
||||||
if value == value_before:
|
|
||||||
return value_after
|
return value_after
|
||||||
|
|
||||||
return value # No match, return original
|
return value # No match, return original
|
||||||
@@ -1922,9 +1917,9 @@ def export_excel_only(sys_argv,
|
|||||||
global console
|
global console
|
||||||
|
|
||||||
if not inclusions_filename:
|
if not inclusions_filename:
|
||||||
inclusions_filename = get_output_filename(INCLUSIONS_FILE_NAME)
|
inclusions_filename = INCLUSIONS_FILE_NAME
|
||||||
if not organizations_filename:
|
if not organizations_filename:
|
||||||
organizations_filename = get_output_filename(ORGANIZATIONS_FILE_NAME)
|
organizations_filename = ORGANIZATIONS_FILE_NAME
|
||||||
|
|
||||||
print()
|
print()
|
||||||
console.print("[bold cyan]═══ EXCEL ONLY MODE ═══[/bold cyan]\n")
|
console.print("[bold cyan]═══ EXCEL ONLY MODE ═══[/bold cyan]\n")
|
||||||
@@ -2039,8 +2034,8 @@ def run_normal_mode_export(excel_enabled, excel_config,
|
|||||||
try:
|
try:
|
||||||
# Load JSONs from filesystem to ensure data consistency with what was written
|
# Load JSONs from filesystem to ensure data consistency with what was written
|
||||||
# Use constants imported from eb_dashboard_constants.py (SINGLE SOURCE OF TRUTH)
|
# Use constants imported from eb_dashboard_constants.py (SINGLE SOURCE OF TRUTH)
|
||||||
inclusions_from_fs = _load_json_file_internal(get_output_filename(INCLUSIONS_FILE_NAME))
|
inclusions_from_fs = _load_json_file_internal(INCLUSIONS_FILE_NAME)
|
||||||
organizations_from_fs = _load_json_file_internal(get_output_filename(ORGANIZATIONS_FILE_NAME))
|
organizations_from_fs = _load_json_file_internal(ORGANIZATIONS_FILE_NAME)
|
||||||
|
|
||||||
if inclusions_from_fs is None or organizations_from_fs is None:
|
if inclusions_from_fs is None or organizations_from_fs is None:
|
||||||
error_msg = "Could not load data files for Excel export"
|
error_msg = "Could not load data files for Excel export"
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
eb_dashboard.exe --excel-only --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix debug %*
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
@echo off
|
|
||||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
|
||||||
python eb_dashboard.py --excel-only --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix debug %*
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ import shutil
|
|||||||
|
|
||||||
import openpyxl
|
import openpyxl
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from eb_dashboard_utils import get_nested_value, get_old_filename as _get_old_filename, get_config_path, get_dashboard_config_path, get_output_filename
|
from eb_dashboard_utils import get_nested_value, get_old_filename as _get_old_filename, get_config_path
|
||||||
from eb_dashboard_constants import (
|
from eb_dashboard_constants import (
|
||||||
INCLUSIONS_FILE_NAME,
|
INCLUSIONS_FILE_NAME,
|
||||||
ORGANIZATIONS_FILE_NAME,
|
ORGANIZATIONS_FILE_NAME,
|
||||||
@@ -93,7 +93,7 @@ def load_regression_check_config(console_instance=None):
|
|||||||
if console_instance:
|
if console_instance:
|
||||||
console = console_instance
|
console = console_instance
|
||||||
|
|
||||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
config_path = os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
workbook = openpyxl.load_workbook(config_path)
|
workbook = openpyxl.load_workbook(config_path)
|
||||||
@@ -318,10 +318,10 @@ def run_check_only_mode(sys_argv):
|
|||||||
|
|
||||||
# Run quality checks (will load all files internally)
|
# Run quality checks (will load all files internally)
|
||||||
print()
|
print()
|
||||||
old_inclusions_file = _get_old_filename(get_output_filename(INCLUSIONS_FILE_NAME), OLD_FILE_SUFFIX)
|
old_inclusions_file = _get_old_filename(INCLUSIONS_FILE_NAME, OLD_FILE_SUFFIX)
|
||||||
has_coherence_critical, has_regression_critical = run_quality_checks(
|
has_coherence_critical, has_regression_critical = run_quality_checks(
|
||||||
current_inclusions=get_output_filename(INCLUSIONS_FILE_NAME),
|
current_inclusions=INCLUSIONS_FILE_NAME,
|
||||||
organizations_list=get_output_filename(ORGANIZATIONS_FILE_NAME),
|
organizations_list=ORGANIZATIONS_FILE_NAME,
|
||||||
old_inclusions_filename=old_inclusions_file
|
old_inclusions_filename=old_inclusions_file
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -370,8 +370,8 @@ def backup_output_files():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"Could not backup {source}: {e}")
|
logging.warning(f"Could not backup {source}: {e}")
|
||||||
|
|
||||||
_backup_file_silent(get_output_filename(INCLUSIONS_FILE_NAME), _get_old_filename(get_output_filename(INCLUSIONS_FILE_NAME), OLD_FILE_SUFFIX))
|
_backup_file_silent(INCLUSIONS_FILE_NAME, _get_old_filename(INCLUSIONS_FILE_NAME, OLD_FILE_SUFFIX))
|
||||||
_backup_file_silent(get_output_filename(ORGANIZATIONS_FILE_NAME), _get_old_filename(get_output_filename(ORGANIZATIONS_FILE_NAME), OLD_FILE_SUFFIX))
|
_backup_file_silent(ORGANIZATIONS_FILE_NAME, _get_old_filename(ORGANIZATIONS_FILE_NAME, OLD_FILE_SUFFIX))
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -431,11 +431,6 @@ def coherence_check(output_inclusions, organizations_list):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
patients += 1
|
patients += 1
|
||||||
|
|
||||||
# API statistics exclude non-consented from status sub-counters only
|
|
||||||
if get_nested_value(inclusion, ["Inclusion", "Consent_Signed"]) is not True:
|
|
||||||
continue
|
|
||||||
|
|
||||||
status = get_nested_value(inclusion, ["Inclusion", "Inclusion_Status"], default="")
|
status = get_nested_value(inclusion, ["Inclusion", "Inclusion_Status"], default="")
|
||||||
|
|
||||||
if isinstance(status, str):
|
if isinstance(status, str):
|
||||||
|
|||||||
@@ -204,43 +204,6 @@ def get_config_path():
|
|||||||
return CONFIG_FOLDER_NAME
|
return CONFIG_FOLDER_NAME
|
||||||
|
|
||||||
|
|
||||||
_dashboard_config_path_override = None
|
|
||||||
|
|
||||||
|
|
||||||
def set_dashboard_config_path_override(path):
|
|
||||||
"""Sets a global override for the dashboard config file path (used by --config CLI arg)."""
|
|
||||||
global _dashboard_config_path_override
|
|
||||||
_dashboard_config_path_override = path
|
|
||||||
|
|
||||||
|
|
||||||
def get_dashboard_config_path(config_file_name):
|
|
||||||
"""Returns the dashboard config file path, respecting any --config CLI override."""
|
|
||||||
if _dashboard_config_path_override:
|
|
||||||
return _dashboard_config_path_override
|
|
||||||
return os.path.join(get_config_path(), config_file_name)
|
|
||||||
|
|
||||||
|
|
||||||
_output_file_suffix = None
|
|
||||||
|
|
||||||
|
|
||||||
def set_output_file_suffix(suffix):
|
|
||||||
"""Sets the global output file suffix (used by --add-suffix CLI arg)."""
|
|
||||||
global _output_file_suffix
|
|
||||||
_output_file_suffix = suffix
|
|
||||||
|
|
||||||
|
|
||||||
def get_output_filename(base_name):
|
|
||||||
"""Returns the output filename with the suffix inserted before the extension.
|
|
||||||
|
|
||||||
Example: get_output_filename("endobest_inclusions.json") with suffix "A"
|
|
||||||
→ "endobest_inclusions_A.json"
|
|
||||||
"""
|
|
||||||
if not _output_file_suffix:
|
|
||||||
return base_name
|
|
||||||
name, ext = os.path.splitext(base_name)
|
|
||||||
return f"{name}_{_output_file_suffix}{ext}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_old_filename(current_filename, old_suffix="_old"):
|
def get_old_filename(current_filename, old_suffix="_old"):
|
||||||
"""Generate old backup filename from current filename.
|
"""Generate old backup filename from current filename.
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
eb_dashboard.exe --include-drafts --add-suffix with_drafts %*
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
@echo off
|
|
||||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
|
||||||
python eb_dashboard.py --include-drafts --add-suffix with_drafts %*
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
eb_dashboard.exe --include-drafts --excel-only --add-suffix with_drafts %*
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
@echo off
|
|
||||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
|
||||||
python eb_dashboard.py --include-drafts --excel-only --add-suffix with_drafts %*
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@echo off
|
|
||||||
eb_dashboard.exe --include-drafts --excel-only --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix with_drafts_debug %*
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
@echo off
|
|
||||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
|
||||||
python eb_dashboard.py --include-drafts --excel-only --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix with_drafts_debug %*
|
|
||||||
|
|
||||||
Binary file not shown.
228230
endobest_inclusions.json
Normal file
228230
endobest_inclusions.json
Normal file
File diff suppressed because it is too large
Load Diff
248294
endobest_inclusions_old.json
Normal file
248294
endobest_inclusions_old.json
Normal file
File diff suppressed because it is too large
Load Diff
704
endobest_organizations.json
Normal file
704
endobest_organizations.json
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "5703b3d6-e415-40b0-90ea-b168835fd720",
|
||||||
|
"name": "HOPITAL PRIVE NATECIA",
|
||||||
|
"Center_Name": "Hôpital Privé Natécia",
|
||||||
|
"patients_count": 169,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 167,
|
||||||
|
"prematurely_terminated_count": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1de71a30-840b-4c4b-84fc-281ce1b4a5e1",
|
||||||
|
"name": "SANTE ATLANTIQUE",
|
||||||
|
"Center_Name": "Clinique Santé Atlantique",
|
||||||
|
"patients_count": 159,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 157,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5e6d7afa-6532-495f-a84a-34f644aeaa0f",
|
||||||
|
"name": "CLINIQUE BELLEDONNE",
|
||||||
|
"Center_Name": "Clinique Belledonne",
|
||||||
|
"patients_count": 158,
|
||||||
|
"preincluded_count": 4,
|
||||||
|
"included_count": 153,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "026a6d39-552f-44b9-8a2d-1ecd705f9e08",
|
||||||
|
"name": "HOPITAL AMERICAIN",
|
||||||
|
"Center_Name": "Hôpital Américain de Paris",
|
||||||
|
"patients_count": 158,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 157,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bf0f96c1-8bbc-4f2c-b360-4a5b27995a12",
|
||||||
|
"name": "SA CLINIQUE TIVOLI-DUCOS",
|
||||||
|
"Center_Name": "Clinique TIVOLI",
|
||||||
|
"patients_count": 155,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 154,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aba63d11-0dd7-40e8-a652-384c311bb358",
|
||||||
|
"name": "HOPITAL LYON SUD - HOSPICES CIVILS DE LYON",
|
||||||
|
"Center_Name": "Hôpital Lyon Sud - Hospices Civils de Lyon",
|
||||||
|
"patients_count": 127,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 127,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8488229d-f426-4bdd-923c-f638041b223c",
|
||||||
|
"name": "HOPITAL JEANNE DE FLANDRE DU CHU DE LILLE",
|
||||||
|
"Center_Name": "CHU de Lille - Hôpital Jeanne de Flandre",
|
||||||
|
"patients_count": 91,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 90,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31665f8d-0f46-44dc-931f-e3aed8879965",
|
||||||
|
"name": "HOPITAL MAISON BLANCHE CHU REIMS",
|
||||||
|
"Center_Name": "CHU de Reims - Hôpital Maison Blanche",
|
||||||
|
"patients_count": 88,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 88,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b77b301c-10fa-4eca-bb5b-51506766e158",
|
||||||
|
"name": "HOPITAL CHARLES NICOLLE CHU ROUEN",
|
||||||
|
"Center_Name": "CHU de Rouen Normandie - Hôpital Charles-Nicolle",
|
||||||
|
"patients_count": 84,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 83,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "743d0a1a-7edf-4fe6-9d65-0c14363f2153",
|
||||||
|
"name": "CENTRE HOSPITALIER UNIVERSITAIRE JEAN MINJOZ BESANCON",
|
||||||
|
"Center_Name": "CHU Jean Minjoz Besançon - Pôle Mère-Femme",
|
||||||
|
"patients_count": 81,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 81,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "855d7aab-9736-40b7-b9ff-dcc6438890ef",
|
||||||
|
"name": "HOPITAL CROIX-ROUSSE - HOSPICES CIVILS DE LYON",
|
||||||
|
"Center_Name": "Hôpital Croix-Rousse - Hospices Civils de Lyon",
|
||||||
|
"patients_count": 68,
|
||||||
|
"preincluded_count": 5,
|
||||||
|
"included_count": 62,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "74a05936-8fb6-4662-8e95-fd1c91621bf2",
|
||||||
|
"name": "GHU APHP SORBONNE UNIVERSITE SITE TENON",
|
||||||
|
"Center_Name": "GHU APHP - Hôpital Tenon",
|
||||||
|
"patients_count": 62,
|
||||||
|
"preincluded_count": 5,
|
||||||
|
"included_count": 56,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "76d2ebcd-5482-4c5d-8221-c94d1de7261e",
|
||||||
|
"name": "CENTRE HOSPITALIER ANNECY-GENEVOIS SITE ANNECY",
|
||||||
|
"Center_Name": "CH Annecy Genevois – Site d’Annecy",
|
||||||
|
"patients_count": 60,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 60,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "006740fa-696c-4ffb-8b77-7a60d0e23617",
|
||||||
|
"name": "HOPITAL PRIVÉ LE BOIS",
|
||||||
|
"Center_Name": "Hôpital Privé le Bois - Lille",
|
||||||
|
"patients_count": 60,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 59,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a4b3dfea-1220-4987-b287-f58fde1d0ee5",
|
||||||
|
"name": "CENTRE HOSPITALIER UNIVERSITAIRE COTE DE NACRE",
|
||||||
|
"Center_Name": "CHU de Caen",
|
||||||
|
"patients_count": 59,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 59,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4aef30cb-778b-409a-b6e2-898bfd91c850",
|
||||||
|
"name": "CENTRE HOSPITALIER GENERAL DE VALENCIENNES",
|
||||||
|
"Center_Name": "CH de Valenciennes",
|
||||||
|
"patients_count": 57,
|
||||||
|
"preincluded_count": 3,
|
||||||
|
"included_count": 54,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f7d3526b-18d6-43af-b52c-5ca23bac798e",
|
||||||
|
"name": "CENTRE HOSPITALIER REGIONAL D ANGERS",
|
||||||
|
"Center_Name": "CHR d’Angers",
|
||||||
|
"patients_count": 50,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 50,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1fd6c63b-d909-45d8-8c70-ecb12501c1d1",
|
||||||
|
"name": "HOPITAL DE HAUTEPIERRE",
|
||||||
|
"Center_Name": "CHRU Strasbourg - Hôpital de Hautepierre",
|
||||||
|
"patients_count": 44,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 42,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d17ddcc4-64db-4cf5-83c5-146f9a060254",
|
||||||
|
"name": "CENTRE HOSPITALIER DE CALAIS.",
|
||||||
|
"Center_Name": "CH de Calais",
|
||||||
|
"patients_count": 42,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 42,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f6665185-b1ae-4847-a378-652d35158cee",
|
||||||
|
"name": "CLINIQUE BELHARRA",
|
||||||
|
"Center_Name": "Clinique Belharra",
|
||||||
|
"patients_count": 40,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 40,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fe25553c-4894-4291-b303-c19d2a1c6f0f",
|
||||||
|
"name": "CHU SITE FELIX GUYON (SAINT DENIS)",
|
||||||
|
"Center_Name": "CHU la Réunion - Site Félix Guyon (SAINT DENIS)",
|
||||||
|
"patients_count": 39,
|
||||||
|
"preincluded_count": 6,
|
||||||
|
"included_count": 33,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "12daafee-970b-4781-9121-994d06e3a766",
|
||||||
|
"name": "CHU DE NICE HOPITAL DE L'ARCHET",
|
||||||
|
"Center_Name": "CHU Nice Archet",
|
||||||
|
"patients_count": 38,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 38,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f07a7374-d731-4fcf-86e9-2f36e5faf342",
|
||||||
|
"name": "CHU MONTPELLIER HOPITAL ARNAUD DE VILLENEUVE",
|
||||||
|
"Center_Name": "CHU Montpellier - Hôpital Arnaud de Villeneuve",
|
||||||
|
"patients_count": 38,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 38,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9c64545a-b622-4ef9-be0f-f5cc21b9cd66",
|
||||||
|
"name": "HOPITAL NORD - CHU DE GRENOBLE ALPES",
|
||||||
|
"Center_Name": "CHU de Grenoble - Hôpital Nord",
|
||||||
|
"patients_count": 36,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 35,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c03b88b5-3cd2-4336-9048-19c239baf5ec",
|
||||||
|
"name": "CHRU DE RENNES SITE HOPITAL SUD",
|
||||||
|
"Center_Name": "CHU Rennes",
|
||||||
|
"patients_count": 34,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 34,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b0ba921e-e24a-41c2-b769-a2aa108cdd58",
|
||||||
|
"name": "CENTRE HOSPITALIER LES ESCARTONS A BRIANCON",
|
||||||
|
"Center_Name": "CH des Escartons de Briançon",
|
||||||
|
"patients_count": 32,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 31,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b5f30dc5-da3f-4f8b-9a39-33f0fefb7196",
|
||||||
|
"name": "SCM RX TOULOUSE CLINIQUE PASTEUR",
|
||||||
|
"Center_Name": "Clinique Pasteur Toulouse",
|
||||||
|
"patients_count": 32,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 30,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "817429dd-0d7c-4df9-ad70-2f19c205e05a",
|
||||||
|
"name": "CENTRE HOSPITALIER - FALCONAJA - BASTIA",
|
||||||
|
"Center_Name": "CH Bastia",
|
||||||
|
"patients_count": 29,
|
||||||
|
"preincluded_count": 4,
|
||||||
|
"included_count": 25,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4f943b00-9306-418a-a853-1d97dff71172",
|
||||||
|
"name": "HOPITAL EUROPEEN",
|
||||||
|
"Center_Name": "Hôpital Européen Marseille",
|
||||||
|
"patients_count": 28,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 28,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "002312ec-69fb-4582-9e8f-7c266e5479d2",
|
||||||
|
"name": "HOPITAL NORD - CHU DE SAINT-ETIENNE",
|
||||||
|
"Center_Name": "CHU de Saint-Étienne - Hôpital Nord",
|
||||||
|
"patients_count": 27,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 25,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0ce39aac-6b5a-418b-8430-c4432e6cd78f",
|
||||||
|
"name": "HOPITAL DE RANGUEIL CHU TOULOUSE",
|
||||||
|
"Center_Name": "CHU de Toulouse - Hôpital Rangueil",
|
||||||
|
"patients_count": 24,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 23,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8ef8564e-836c-4c03-b4ec-8e28fa76c3c0",
|
||||||
|
"name": "HOPITAL ESTAING - CHU CLERMONT-FERRAND",
|
||||||
|
"Center_Name": "CHU Clermont-Ferrand - Site Estaing",
|
||||||
|
"patients_count": 23,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 22,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "546f981f-6f7d-40c8-9b97-a1a3c46e6674",
|
||||||
|
"name": "GROUPE HOSPITALIER DE LA REGION DE MULHOUSE ET SUD ALSACE",
|
||||||
|
"Center_Name": "GHR Mulhouse Sud Alsace",
|
||||||
|
"patients_count": 20,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 20,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eb9c561d-37b6-485c-b351-9e19515cb98d",
|
||||||
|
"name": "GROUPE HOSPITALIER PELLEGRIN - CHU",
|
||||||
|
"Center_Name": "CHU de Bordeaux - GH Pellegrin",
|
||||||
|
"patients_count": 20,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 20,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d1ef2ced-0206-4725-a08c-454d71e823ec",
|
||||||
|
"name": "HOPITAL DE LA MERE ET DE L'ENFANT",
|
||||||
|
"Center_Name": "CHU Limoges - Hôpital de la Mère et de l'Enfant",
|
||||||
|
"patients_count": 20,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 20,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7f43a297-b156-4fec-8ecf-d8d008b1a1d2",
|
||||||
|
"name": "HOPITAL PRIVE DIJON BOURGOGNE",
|
||||||
|
"Center_Name": "Centre Evidens - Hôpital privé Dijon Bourgogne",
|
||||||
|
"patients_count": 19,
|
||||||
|
"preincluded_count": 4,
|
||||||
|
"included_count": 15,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d767fcd0-3f4c-4cdf-9cda-b829156d2f95",
|
||||||
|
"name": "HOPITAL PRIVE SUD CORSE",
|
||||||
|
"Center_Name": "Hôpital Privé Sud Corse",
|
||||||
|
"patients_count": 18,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 18,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58d5b536-b326-4713-9137-bb6f852df0be",
|
||||||
|
"name": "CENTRE HOSPITALIER METROPOLE SAVOIE - CHAMBERY NH",
|
||||||
|
"Center_Name": "CH Métropole Savoie - Site Chambéry",
|
||||||
|
"patients_count": 16,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 16,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b4b35661-87c8-4333-bfb1-3ce4f1c77565",
|
||||||
|
"name": "CENTRE HOSPITALIER REGIONAL UNIVERSITAIRE BRETONNEAU",
|
||||||
|
"Center_Name": "CHRU Bretonneau",
|
||||||
|
"patients_count": 16,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 14,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "454313d9-c624-43de-a8e3-84989d526403",
|
||||||
|
"name": "CLINIQUE MUTUALISTE LA SAGESSE RENNES",
|
||||||
|
"Center_Name": "Clinique la Sagesse - Rennes",
|
||||||
|
"patients_count": 15,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 15,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d981c078-189e-4831-a063-778733f7e582",
|
||||||
|
"name": "HOPITAL FOCH",
|
||||||
|
"Center_Name": "Hôpital Foch",
|
||||||
|
"patients_count": 15,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 12,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "940c8425-fe53-45ac-a750-3e195df990e3",
|
||||||
|
"name": "HOPITAL PRIVE D'EURE ET LOIR",
|
||||||
|
"Center_Name": "Hôpital Privé d'Eure et Loir",
|
||||||
|
"patients_count": 14,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 13,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "98cbbefe-920a-495a-981d-02093bd2253d",
|
||||||
|
"name": "CHU LA MILETRIE",
|
||||||
|
"Center_Name": "CHU de Poitiers La Miletrie",
|
||||||
|
"patients_count": 13,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 13,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "38e9ef8a-5073-471a-a3df-14d834db2d12",
|
||||||
|
"name": "GHBS-SITE HÔPITAL DU SCORFF",
|
||||||
|
"Center_Name": "Groupe Hospitalier Bretagne Sud Lorient - GHBS - Lorient (SCORFF)",
|
||||||
|
"patients_count": 13,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 13,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6316db1a-4a90-4e45-a518-0095d63f7c36",
|
||||||
|
"name": "HOPITAL LE BOCAGE CHRU DIJON",
|
||||||
|
"Center_Name": "CHU Dijon",
|
||||||
|
"patients_count": 12,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f790c171-9523-48b6-a246-fdb26628eb6c",
|
||||||
|
"name": "CHRU NANCY - MATERNITE",
|
||||||
|
"Center_Name": "CHRU de Nancy - Maternité",
|
||||||
|
"patients_count": 11,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7b8c5362-7c7a-4d93-8b12-58b8e56020d1",
|
||||||
|
"name": "HOPITAL JACQUES MONOD CH LE HAVRE",
|
||||||
|
"Center_Name": "GH du Havre - Hôpital Jacques Monod",
|
||||||
|
"patients_count": 11,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 11,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6953af68-e30e-437f-93cf-19ed0932c350",
|
||||||
|
"name": "CHRU D'ORLEANS - HOPITAL DE LA SOURCE",
|
||||||
|
"Center_Name": "CHU d'Orléans",
|
||||||
|
"patients_count": 10,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f13560a8-3bf6-4322-bdae-332ee3bf0366",
|
||||||
|
"name": "POLYCLINIQUE JEAN VILLAR",
|
||||||
|
"Center_Name": "Polyclinique Jean Villar",
|
||||||
|
"patients_count": 10,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "da67fb6e-2ccc-4994-8fde-cc2ee2a8e9ca",
|
||||||
|
"name": "GHU AP-HP UNIVERSITE PARIS SACLAY SITE KREMLIN BICETRE",
|
||||||
|
"Center_Name": "GHU APHP - Hôpital Bicêtre",
|
||||||
|
"patients_count": 9,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 7,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56f427f0-be35-493b-8049-46b747c0105e",
|
||||||
|
"name": "CHU AMIENS SUD",
|
||||||
|
"Center_Name": "CHU Amiens Sud",
|
||||||
|
"patients_count": 7,
|
||||||
|
"preincluded_count": 3,
|
||||||
|
"included_count": 4,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f784ee6f-27b5-4afe-ba23-4f839d96535c",
|
||||||
|
"name": "APHM HOPITAL NORD",
|
||||||
|
"Center_Name": "APHM Hôpital Nord",
|
||||||
|
"patients_count": 5,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 5,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1ac60b10-08de-4639-9a49-67167f85844e",
|
||||||
|
"name": "CENTRE HOSPITALIER DE LENS",
|
||||||
|
"Center_Name": "CH de Lens",
|
||||||
|
"patients_count": 5,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9ba321be-65d0-4767-9865-a547905e647e",
|
||||||
|
"name": "HOPITAL DE LA CROIX SAINT SIMON",
|
||||||
|
"Center_Name": "GH Diaconesses Croix Saint-Simon",
|
||||||
|
"patients_count": 5,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 4,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cab5b1e9-d93a-46eb-ae35-a449ea719c65",
|
||||||
|
"name": "GRAND HOSP EST FRANCILIEN MARNE LA VALLEE SITE JOSSIGNY",
|
||||||
|
"Center_Name": "Grand Hôpital de l'Est Francilien - Site de Marne-la-Vallée",
|
||||||
|
"patients_count": 4,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 4,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "96519108-b0b2-4a5f-999f-cccc4e4059c9",
|
||||||
|
"name": "CHU DE NANTES SITE HOTEL DIEU HOPITAL MERE ENFANT",
|
||||||
|
"Center_Name": "CHU de Nantes - Site Hôtel Dieu Hôpital Mère Enfant",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fe7548b7-75a1-4e13-97a9-6dc865b04b25",
|
||||||
|
"name": "CHU SITE SUD ( SAINT PIERRE)",
|
||||||
|
"Center_Name": "CHU la Réunion - Site Sud (SAINT PIERRE)",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e68c76de-33e8-49e3-978d-c5fb408e7fd5",
|
||||||
|
"name": "CLINIQUE AXIUM",
|
||||||
|
"Center_Name": "Clinique Axium",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "af09eaa0-7e08-48e3-990c-24ca2fbc3fdc",
|
||||||
|
"name": "SCP GYNECOLOGIE RIVE GAUCHE",
|
||||||
|
"Center_Name": "SCP Gynécologie Clinique Rive Gauche",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0466558d-077c-4a51-9d25-2e15e00fabaf",
|
||||||
|
"name": "CENTRE HOSPITALIER DE CANNES SIMONE VEIL",
|
||||||
|
"Center_Name": "CH de Cannes Simone Veil",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aa3e8684-07e0-45d9-9cee-144df2e4b430",
|
||||||
|
"name": "CENTRE HOSPITALIER DE PAU",
|
||||||
|
"Center_Name": "CH de Pau",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "13306b61-7ea6-4d60-b89c-b0df2d9d1605",
|
||||||
|
"name": "CENTRE HOSPITALIER VICTOR PROVO ROUBAIX",
|
||||||
|
"Center_Name": "CH Victor Provo Roubaix",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8c27716f-edfe-4a4e-9189-09f39cc64395",
|
||||||
|
"name": "CHI DE MONT DE MARSAN ET DU PAYS DES SOURCES",
|
||||||
|
"Center_Name": "CHI Mont de Marsan",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b74c365a-fc63-4763-b57d-abb609debd3b",
|
||||||
|
"name": "CTRE HOSPITALIER INTERCOMMUNAL POISSY ST GERMAIN SITE POISSY",
|
||||||
|
"Center_Name": "CHI Poissy St Germain en Laye - Site Poissy",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4d271322-7945-42e8-801b-a3b25fc570be",
|
||||||
|
"name": "GROUPE HOSPITALIER PARIS SAINT JOSEPH",
|
||||||
|
"Center_Name": "Groupe Hospitalier Paris Saint-Joseph",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d82d64eb-a1cf-4635-a99f-23def12c7cb5",
|
||||||
|
"name": "CHRU BREST SITE HOPITAL MORVAN",
|
||||||
|
"Center_Name": "CHRU Brest - Site Hôpital Morvan",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a47dc8c0-dfea-4a4c-9bcc-1370616ff9c8",
|
||||||
|
"name": "CHU DE MARTINIQUE SITE MFME",
|
||||||
|
"Center_Name": "CHU de Martinique - Site MFME",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "384d3b28-f33e-43a6-9043-394aa88aead7",
|
||||||
|
"name": "CLINIQUE BOUCHARD",
|
||||||
|
"Center_Name": "Clinique Bouchard - Marseille",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0f78459f-b598-4faa-81a1-085a439e8c0e",
|
||||||
|
"name": "HOPITAUX PRIVES ROUENNAIS MATHILDE",
|
||||||
|
"Center_Name": "Hôpitaux Privés Rouennais - Mathilde",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "02e42e01-56ab-418d-95df-cb53a7ef17b3",
|
||||||
|
"name": "APHM HOPITAL DE LA CONCEPTION",
|
||||||
|
"Center_Name": "APHM Hôpital de la Conception",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2ba1f179-425e-4d88-97a7-88a097e639ab",
|
||||||
|
"name": "CENTRE HOSPITALIER DE CAYENNE",
|
||||||
|
"Center_Name": "CHU de Guyane - Site de Cayenne",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3e8887e1-7608-40a6-b6f5-87c6fa5a0bd0",
|
||||||
|
"name": "CENTRE HOSPITALIER DE VERSAILLES ANDRE MIGNOT",
|
||||||
|
"Center_Name": "CH de Versailles",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6af3784d-5d57-47c3-b924-0d13008ebd88",
|
||||||
|
"name": "CENTRE HOSPITALIER UNIVERSITAIRE DE POINTE-A-PITRE",
|
||||||
|
"Center_Name": "CHU de Guadeloupe - Pointe-à-Pitre",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aa6af654-2cf2-4714-885d-a0e40b6f6f0a",
|
||||||
|
"name": "CHR METZ - THIONVILLE - HOPITAL DE MERCY",
|
||||||
|
"Center_Name": "CHR Metz - Thionville - Hôpital de Mercy",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8441f339-4dd3-4c8c-bd2d-7751dc7fe43b",
|
||||||
|
"name": "CLINIQUE DU TERTRE ROUGE - POLE SANTE SUD",
|
||||||
|
"Center_Name": "Clinique du Tertre Rouge - Pôle Santé Sud",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "09c785f2-3deb-4a4a-a1a8-adbca5b767d7",
|
||||||
|
"name": "GHU APHP CENTRE-UNIVERSITE PARIS CITE SITE COCHIN PORT ROYAL",
|
||||||
|
"Center_Name": "APHP Site Cochin",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5847c4fa-4025-4303-80eb-deaa07b80c0b",
|
||||||
|
"name": "GHU APHP SORBONNE UNIVERSITE SITE PITIE SALPETRIERE",
|
||||||
|
"Center_Name": "APHP Site Pitié Salpêtriere",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dacc94bd-89b0-4cc4-b334-f684360abbe5",
|
||||||
|
"name": "HOPITAL PRIVE D ANTONY",
|
||||||
|
"Center_Name": "Hôpital Privé d'Antony",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
704
endobest_organizations_old.json
Normal file
704
endobest_organizations_old.json
Normal file
@@ -0,0 +1,704 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "5703b3d6-e415-40b0-90ea-b168835fd720",
|
||||||
|
"name": "HOPITAL PRIVE NATECIA",
|
||||||
|
"Center_Name": "Hôpital Privé Natécia",
|
||||||
|
"patients_count": 169,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 167,
|
||||||
|
"prematurely_terminated_count": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1de71a30-840b-4c4b-84fc-281ce1b4a5e1",
|
||||||
|
"name": "SANTE ATLANTIQUE",
|
||||||
|
"Center_Name": "Clinique Santé Atlantique",
|
||||||
|
"patients_count": 159,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 157,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5e6d7afa-6532-495f-a84a-34f644aeaa0f",
|
||||||
|
"name": "CLINIQUE BELLEDONNE",
|
||||||
|
"Center_Name": "Clinique Belledonne",
|
||||||
|
"patients_count": 158,
|
||||||
|
"preincluded_count": 4,
|
||||||
|
"included_count": 153,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "026a6d39-552f-44b9-8a2d-1ecd705f9e08",
|
||||||
|
"name": "HOPITAL AMERICAIN",
|
||||||
|
"Center_Name": "Hôpital Américain de Paris",
|
||||||
|
"patients_count": 158,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 157,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bf0f96c1-8bbc-4f2c-b360-4a5b27995a12",
|
||||||
|
"name": "SA CLINIQUE TIVOLI-DUCOS",
|
||||||
|
"Center_Name": "Clinique TIVOLI",
|
||||||
|
"patients_count": 155,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 154,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aba63d11-0dd7-40e8-a652-384c311bb358",
|
||||||
|
"name": "HOPITAL LYON SUD - HOSPICES CIVILS DE LYON",
|
||||||
|
"Center_Name": "Hôpital Lyon Sud - Hospices Civils de Lyon",
|
||||||
|
"patients_count": 127,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 127,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8488229d-f426-4bdd-923c-f638041b223c",
|
||||||
|
"name": "HOPITAL JEANNE DE FLANDRE DU CHU DE LILLE",
|
||||||
|
"Center_Name": "CHU de Lille - Hôpital Jeanne de Flandre",
|
||||||
|
"patients_count": 91,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 90,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "31665f8d-0f46-44dc-931f-e3aed8879965",
|
||||||
|
"name": "HOPITAL MAISON BLANCHE CHU REIMS",
|
||||||
|
"Center_Name": "CHU de Reims - Hôpital Maison Blanche",
|
||||||
|
"patients_count": 88,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 88,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b77b301c-10fa-4eca-bb5b-51506766e158",
|
||||||
|
"name": "HOPITAL CHARLES NICOLLE CHU ROUEN",
|
||||||
|
"Center_Name": "CHU de Rouen Normandie - Hôpital Charles-Nicolle",
|
||||||
|
"patients_count": 84,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 83,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "743d0a1a-7edf-4fe6-9d65-0c14363f2153",
|
||||||
|
"name": "CENTRE HOSPITALIER UNIVERSITAIRE JEAN MINJOZ BESANCON",
|
||||||
|
"Center_Name": "CHU Jean Minjoz Besançon - Pôle Mère-Femme",
|
||||||
|
"patients_count": 81,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 81,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "855d7aab-9736-40b7-b9ff-dcc6438890ef",
|
||||||
|
"name": "HOPITAL CROIX-ROUSSE - HOSPICES CIVILS DE LYON",
|
||||||
|
"Center_Name": "Hôpital Croix-Rousse - Hospices Civils de Lyon",
|
||||||
|
"patients_count": 68,
|
||||||
|
"preincluded_count": 5,
|
||||||
|
"included_count": 62,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "74a05936-8fb6-4662-8e95-fd1c91621bf2",
|
||||||
|
"name": "GHU APHP SORBONNE UNIVERSITE SITE TENON",
|
||||||
|
"Center_Name": "GHU APHP - Hôpital Tenon",
|
||||||
|
"patients_count": 62,
|
||||||
|
"preincluded_count": 5,
|
||||||
|
"included_count": 56,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "76d2ebcd-5482-4c5d-8221-c94d1de7261e",
|
||||||
|
"name": "CENTRE HOSPITALIER ANNECY-GENEVOIS SITE ANNECY",
|
||||||
|
"Center_Name": "CH Annecy Genevois – Site d’Annecy",
|
||||||
|
"patients_count": 60,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 60,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "006740fa-696c-4ffb-8b77-7a60d0e23617",
|
||||||
|
"name": "HOPITAL PRIVÉ LE BOIS",
|
||||||
|
"Center_Name": "Hôpital Privé le Bois - Lille",
|
||||||
|
"patients_count": 60,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 59,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a4b3dfea-1220-4987-b287-f58fde1d0ee5",
|
||||||
|
"name": "CENTRE HOSPITALIER UNIVERSITAIRE COTE DE NACRE",
|
||||||
|
"Center_Name": "CHU de Caen",
|
||||||
|
"patients_count": 59,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 59,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4aef30cb-778b-409a-b6e2-898bfd91c850",
|
||||||
|
"name": "CENTRE HOSPITALIER GENERAL DE VALENCIENNES",
|
||||||
|
"Center_Name": "CH de Valenciennes",
|
||||||
|
"patients_count": 57,
|
||||||
|
"preincluded_count": 3,
|
||||||
|
"included_count": 54,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f7d3526b-18d6-43af-b52c-5ca23bac798e",
|
||||||
|
"name": "CENTRE HOSPITALIER REGIONAL D ANGERS",
|
||||||
|
"Center_Name": "CHR d’Angers",
|
||||||
|
"patients_count": 50,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 50,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1fd6c63b-d909-45d8-8c70-ecb12501c1d1",
|
||||||
|
"name": "HOPITAL DE HAUTEPIERRE",
|
||||||
|
"Center_Name": "CHRU Strasbourg - Hôpital de Hautepierre",
|
||||||
|
"patients_count": 44,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 42,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d17ddcc4-64db-4cf5-83c5-146f9a060254",
|
||||||
|
"name": "CENTRE HOSPITALIER DE CALAIS.",
|
||||||
|
"Center_Name": "CH de Calais",
|
||||||
|
"patients_count": 42,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 42,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f6665185-b1ae-4847-a378-652d35158cee",
|
||||||
|
"name": "CLINIQUE BELHARRA",
|
||||||
|
"Center_Name": "Clinique Belharra",
|
||||||
|
"patients_count": 40,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 40,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fe25553c-4894-4291-b303-c19d2a1c6f0f",
|
||||||
|
"name": "CHU SITE FELIX GUYON (SAINT DENIS)",
|
||||||
|
"Center_Name": "CHU la Réunion - Site Félix Guyon (SAINT DENIS)",
|
||||||
|
"patients_count": 39,
|
||||||
|
"preincluded_count": 6,
|
||||||
|
"included_count": 33,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "12daafee-970b-4781-9121-994d06e3a766",
|
||||||
|
"name": "CHU DE NICE HOPITAL DE L'ARCHET",
|
||||||
|
"Center_Name": "CHU Nice Archet",
|
||||||
|
"patients_count": 38,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 38,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f07a7374-d731-4fcf-86e9-2f36e5faf342",
|
||||||
|
"name": "CHU MONTPELLIER HOPITAL ARNAUD DE VILLENEUVE",
|
||||||
|
"Center_Name": "CHU Montpellier - Hôpital Arnaud de Villeneuve",
|
||||||
|
"patients_count": 38,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 38,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9c64545a-b622-4ef9-be0f-f5cc21b9cd66",
|
||||||
|
"name": "HOPITAL NORD - CHU DE GRENOBLE ALPES",
|
||||||
|
"Center_Name": "CHU de Grenoble - Hôpital Nord",
|
||||||
|
"patients_count": 36,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 35,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c03b88b5-3cd2-4336-9048-19c239baf5ec",
|
||||||
|
"name": "CHRU DE RENNES SITE HOPITAL SUD",
|
||||||
|
"Center_Name": "CHU Rennes",
|
||||||
|
"patients_count": 34,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 34,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b0ba921e-e24a-41c2-b769-a2aa108cdd58",
|
||||||
|
"name": "CENTRE HOSPITALIER LES ESCARTONS A BRIANCON",
|
||||||
|
"Center_Name": "CH des Escartons de Briançon",
|
||||||
|
"patients_count": 32,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 31,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b5f30dc5-da3f-4f8b-9a39-33f0fefb7196",
|
||||||
|
"name": "SCM RX TOULOUSE CLINIQUE PASTEUR",
|
||||||
|
"Center_Name": "Clinique Pasteur Toulouse",
|
||||||
|
"patients_count": 32,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 30,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "817429dd-0d7c-4df9-ad70-2f19c205e05a",
|
||||||
|
"name": "CENTRE HOSPITALIER - FALCONAJA - BASTIA",
|
||||||
|
"Center_Name": "CH Bastia",
|
||||||
|
"patients_count": 29,
|
||||||
|
"preincluded_count": 4,
|
||||||
|
"included_count": 25,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4f943b00-9306-418a-a853-1d97dff71172",
|
||||||
|
"name": "HOPITAL EUROPEEN",
|
||||||
|
"Center_Name": "Hôpital Européen Marseille",
|
||||||
|
"patients_count": 28,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 28,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "002312ec-69fb-4582-9e8f-7c266e5479d2",
|
||||||
|
"name": "HOPITAL NORD - CHU DE SAINT-ETIENNE",
|
||||||
|
"Center_Name": "CHU de Saint-Étienne - Hôpital Nord",
|
||||||
|
"patients_count": 27,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 25,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0ce39aac-6b5a-418b-8430-c4432e6cd78f",
|
||||||
|
"name": "HOPITAL DE RANGUEIL CHU TOULOUSE",
|
||||||
|
"Center_Name": "CHU de Toulouse - Hôpital Rangueil",
|
||||||
|
"patients_count": 24,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 23,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8ef8564e-836c-4c03-b4ec-8e28fa76c3c0",
|
||||||
|
"name": "HOPITAL ESTAING - CHU CLERMONT-FERRAND",
|
||||||
|
"Center_Name": "CHU Clermont-Ferrand - Site Estaing",
|
||||||
|
"patients_count": 23,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 22,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "546f981f-6f7d-40c8-9b97-a1a3c46e6674",
|
||||||
|
"name": "GROUPE HOSPITALIER DE LA REGION DE MULHOUSE ET SUD ALSACE",
|
||||||
|
"Center_Name": "GHR Mulhouse Sud Alsace",
|
||||||
|
"patients_count": 20,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 20,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eb9c561d-37b6-485c-b351-9e19515cb98d",
|
||||||
|
"name": "GROUPE HOSPITALIER PELLEGRIN - CHU",
|
||||||
|
"Center_Name": "CHU de Bordeaux - GH Pellegrin",
|
||||||
|
"patients_count": 20,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 20,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d1ef2ced-0206-4725-a08c-454d71e823ec",
|
||||||
|
"name": "HOPITAL DE LA MERE ET DE L'ENFANT",
|
||||||
|
"Center_Name": "CHU Limoges - Hôpital de la Mère et de l'Enfant",
|
||||||
|
"patients_count": 20,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 20,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7f43a297-b156-4fec-8ecf-d8d008b1a1d2",
|
||||||
|
"name": "HOPITAL PRIVE DIJON BOURGOGNE",
|
||||||
|
"Center_Name": "Centre Evidens - Hôpital privé Dijon Bourgogne",
|
||||||
|
"patients_count": 19,
|
||||||
|
"preincluded_count": 4,
|
||||||
|
"included_count": 15,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d767fcd0-3f4c-4cdf-9cda-b829156d2f95",
|
||||||
|
"name": "HOPITAL PRIVE SUD CORSE",
|
||||||
|
"Center_Name": "Hôpital Privé Sud Corse",
|
||||||
|
"patients_count": 18,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 18,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "58d5b536-b326-4713-9137-bb6f852df0be",
|
||||||
|
"name": "CENTRE HOSPITALIER METROPOLE SAVOIE - CHAMBERY NH",
|
||||||
|
"Center_Name": "CH Métropole Savoie - Site Chambéry",
|
||||||
|
"patients_count": 16,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 16,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b4b35661-87c8-4333-bfb1-3ce4f1c77565",
|
||||||
|
"name": "CENTRE HOSPITALIER REGIONAL UNIVERSITAIRE BRETONNEAU",
|
||||||
|
"Center_Name": "CHRU Bretonneau",
|
||||||
|
"patients_count": 16,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 14,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "454313d9-c624-43de-a8e3-84989d526403",
|
||||||
|
"name": "CLINIQUE MUTUALISTE LA SAGESSE RENNES",
|
||||||
|
"Center_Name": "Clinique la Sagesse - Rennes",
|
||||||
|
"patients_count": 15,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 15,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d981c078-189e-4831-a063-778733f7e582",
|
||||||
|
"name": "HOPITAL FOCH",
|
||||||
|
"Center_Name": "Hôpital Foch",
|
||||||
|
"patients_count": 15,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 12,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "940c8425-fe53-45ac-a750-3e195df990e3",
|
||||||
|
"name": "HOPITAL PRIVE D'EURE ET LOIR",
|
||||||
|
"Center_Name": "Hôpital Privé d'Eure et Loir",
|
||||||
|
"patients_count": 14,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 13,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "98cbbefe-920a-495a-981d-02093bd2253d",
|
||||||
|
"name": "CHU LA MILETRIE",
|
||||||
|
"Center_Name": "CHU de Poitiers La Miletrie",
|
||||||
|
"patients_count": 13,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 13,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "38e9ef8a-5073-471a-a3df-14d834db2d12",
|
||||||
|
"name": "GHBS-SITE HÔPITAL DU SCORFF",
|
||||||
|
"Center_Name": "Groupe Hospitalier Bretagne Sud Lorient - GHBS - Lorient (SCORFF)",
|
||||||
|
"patients_count": 13,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 13,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6316db1a-4a90-4e45-a518-0095d63f7c36",
|
||||||
|
"name": "HOPITAL LE BOCAGE CHRU DIJON",
|
||||||
|
"Center_Name": "CHU Dijon",
|
||||||
|
"patients_count": 12,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f790c171-9523-48b6-a246-fdb26628eb6c",
|
||||||
|
"name": "CHRU NANCY - MATERNITE",
|
||||||
|
"Center_Name": "CHRU de Nancy - Maternité",
|
||||||
|
"patients_count": 11,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "7b8c5362-7c7a-4d93-8b12-58b8e56020d1",
|
||||||
|
"name": "HOPITAL JACQUES MONOD CH LE HAVRE",
|
||||||
|
"Center_Name": "GH du Havre - Hôpital Jacques Monod",
|
||||||
|
"patients_count": 11,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 11,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6953af68-e30e-437f-93cf-19ed0932c350",
|
||||||
|
"name": "CHRU D'ORLEANS - HOPITAL DE LA SOURCE",
|
||||||
|
"Center_Name": "CHU d'Orléans",
|
||||||
|
"patients_count": 10,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f13560a8-3bf6-4322-bdae-332ee3bf0366",
|
||||||
|
"name": "POLYCLINIQUE JEAN VILLAR",
|
||||||
|
"Center_Name": "Polyclinique Jean Villar",
|
||||||
|
"patients_count": 10,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 10,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "da67fb6e-2ccc-4994-8fde-cc2ee2a8e9ca",
|
||||||
|
"name": "GHU AP-HP UNIVERSITE PARIS SACLAY SITE KREMLIN BICETRE",
|
||||||
|
"Center_Name": "GHU APHP - Hôpital Bicêtre",
|
||||||
|
"patients_count": 9,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 7,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "56f427f0-be35-493b-8049-46b747c0105e",
|
||||||
|
"name": "CHU AMIENS SUD",
|
||||||
|
"Center_Name": "CHU Amiens Sud",
|
||||||
|
"patients_count": 7,
|
||||||
|
"preincluded_count": 3,
|
||||||
|
"included_count": 4,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f784ee6f-27b5-4afe-ba23-4f839d96535c",
|
||||||
|
"name": "APHM HOPITAL NORD",
|
||||||
|
"Center_Name": "APHM Hôpital Nord",
|
||||||
|
"patients_count": 5,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 5,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1ac60b10-08de-4639-9a49-67167f85844e",
|
||||||
|
"name": "CENTRE HOSPITALIER DE LENS",
|
||||||
|
"Center_Name": "CH de Lens",
|
||||||
|
"patients_count": 5,
|
||||||
|
"preincluded_count": 2,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "9ba321be-65d0-4767-9865-a547905e647e",
|
||||||
|
"name": "HOPITAL DE LA CROIX SAINT SIMON",
|
||||||
|
"Center_Name": "GH Diaconesses Croix Saint-Simon",
|
||||||
|
"patients_count": 5,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 4,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cab5b1e9-d93a-46eb-ae35-a449ea719c65",
|
||||||
|
"name": "GRAND HOSP EST FRANCILIEN MARNE LA VALLEE SITE JOSSIGNY",
|
||||||
|
"Center_Name": "Grand Hôpital de l'Est Francilien - Site de Marne-la-Vallée",
|
||||||
|
"patients_count": 4,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 4,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "96519108-b0b2-4a5f-999f-cccc4e4059c9",
|
||||||
|
"name": "CHU DE NANTES SITE HOTEL DIEU HOPITAL MERE ENFANT",
|
||||||
|
"Center_Name": "CHU de Nantes - Site Hôtel Dieu Hôpital Mère Enfant",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fe7548b7-75a1-4e13-97a9-6dc865b04b25",
|
||||||
|
"name": "CHU SITE SUD ( SAINT PIERRE)",
|
||||||
|
"Center_Name": "CHU la Réunion - Site Sud (SAINT PIERRE)",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e68c76de-33e8-49e3-978d-c5fb408e7fd5",
|
||||||
|
"name": "CLINIQUE AXIUM",
|
||||||
|
"Center_Name": "Clinique Axium",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "af09eaa0-7e08-48e3-990c-24ca2fbc3fdc",
|
||||||
|
"name": "SCP GYNECOLOGIE RIVE GAUCHE",
|
||||||
|
"Center_Name": "SCP Gynécologie Clinique Rive Gauche",
|
||||||
|
"patients_count": 3,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 3,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0466558d-077c-4a51-9d25-2e15e00fabaf",
|
||||||
|
"name": "CENTRE HOSPITALIER DE CANNES SIMONE VEIL",
|
||||||
|
"Center_Name": "CH de Cannes Simone Veil",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aa3e8684-07e0-45d9-9cee-144df2e4b430",
|
||||||
|
"name": "CENTRE HOSPITALIER DE PAU",
|
||||||
|
"Center_Name": "CH de Pau",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "13306b61-7ea6-4d60-b89c-b0df2d9d1605",
|
||||||
|
"name": "CENTRE HOSPITALIER VICTOR PROVO ROUBAIX",
|
||||||
|
"Center_Name": "CH Victor Provo Roubaix",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8c27716f-edfe-4a4e-9189-09f39cc64395",
|
||||||
|
"name": "CHI DE MONT DE MARSAN ET DU PAYS DES SOURCES",
|
||||||
|
"Center_Name": "CHI Mont de Marsan",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b74c365a-fc63-4763-b57d-abb609debd3b",
|
||||||
|
"name": "CTRE HOSPITALIER INTERCOMMUNAL POISSY ST GERMAIN SITE POISSY",
|
||||||
|
"Center_Name": "CHI Poissy St Germain en Laye - Site Poissy",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 1,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4d271322-7945-42e8-801b-a3b25fc570be",
|
||||||
|
"name": "GROUPE HOSPITALIER PARIS SAINT JOSEPH",
|
||||||
|
"Center_Name": "Groupe Hospitalier Paris Saint-Joseph",
|
||||||
|
"patients_count": 2,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 2,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d82d64eb-a1cf-4635-a99f-23def12c7cb5",
|
||||||
|
"name": "CHRU BREST SITE HOPITAL MORVAN",
|
||||||
|
"Center_Name": "CHRU Brest - Site Hôpital Morvan",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a47dc8c0-dfea-4a4c-9bcc-1370616ff9c8",
|
||||||
|
"name": "CHU DE MARTINIQUE SITE MFME",
|
||||||
|
"Center_Name": "CHU de Martinique - Site MFME",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "384d3b28-f33e-43a6-9043-394aa88aead7",
|
||||||
|
"name": "CLINIQUE BOUCHARD",
|
||||||
|
"Center_Name": "Clinique Bouchard - Marseille",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0f78459f-b598-4faa-81a1-085a439e8c0e",
|
||||||
|
"name": "HOPITAUX PRIVES ROUENNAIS MATHILDE",
|
||||||
|
"Center_Name": "Hôpitaux Privés Rouennais - Mathilde",
|
||||||
|
"patients_count": 1,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 1,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "02e42e01-56ab-418d-95df-cb53a7ef17b3",
|
||||||
|
"name": "APHM HOPITAL DE LA CONCEPTION",
|
||||||
|
"Center_Name": "APHM Hôpital de la Conception",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2ba1f179-425e-4d88-97a7-88a097e639ab",
|
||||||
|
"name": "CENTRE HOSPITALIER DE CAYENNE",
|
||||||
|
"Center_Name": "CHU de Guyane - Site de Cayenne",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3e8887e1-7608-40a6-b6f5-87c6fa5a0bd0",
|
||||||
|
"name": "CENTRE HOSPITALIER DE VERSAILLES ANDRE MIGNOT",
|
||||||
|
"Center_Name": "CH de Versailles",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6af3784d-5d57-47c3-b924-0d13008ebd88",
|
||||||
|
"name": "CENTRE HOSPITALIER UNIVERSITAIRE DE POINTE-A-PITRE",
|
||||||
|
"Center_Name": "CHU de Guadeloupe - Pointe-à-Pitre",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aa6af654-2cf2-4714-885d-a0e40b6f6f0a",
|
||||||
|
"name": "CHR METZ - THIONVILLE - HOPITAL DE MERCY",
|
||||||
|
"Center_Name": "CHR Metz - Thionville - Hôpital de Mercy",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "8441f339-4dd3-4c8c-bd2d-7751dc7fe43b",
|
||||||
|
"name": "CLINIQUE DU TERTRE ROUGE - POLE SANTE SUD",
|
||||||
|
"Center_Name": "Clinique du Tertre Rouge - Pôle Santé Sud",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "09c785f2-3deb-4a4a-a1a8-adbca5b767d7",
|
||||||
|
"name": "GHU APHP CENTRE-UNIVERSITE PARIS CITE SITE COCHIN PORT ROYAL",
|
||||||
|
"Center_Name": "APHP Site Cochin",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5847c4fa-4025-4303-80eb-deaa07b80c0b",
|
||||||
|
"name": "GHU APHP SORBONNE UNIVERSITE SITE PITIE SALPETRIERE",
|
||||||
|
"Center_Name": "APHP Site Pitié Salpêtriere",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dacc94bd-89b0-4cc4-b334-f684360abbe5",
|
||||||
|
"name": "HOPITAL PRIVE D ANTONY",
|
||||||
|
"Center_Name": "Hôpital Privé d'Antony",
|
||||||
|
"patients_count": 0,
|
||||||
|
"preincluded_count": 0,
|
||||||
|
"included_count": 0,
|
||||||
|
"prematurely_terminated_count": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user