Compare commits
11 Commits
c812091ce3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 81c9001871 | |||
| 856fffb25f | |||
| e92c941c22 | |||
| 16682e767f | |||
| f676f8a5bd | |||
| 0b9f5cc8c7 | |||
| bcea2838b5 | |||
| ad0ebb307d | |||
| 123e9d3338 | |||
| c679182707 | |||
| 0d1b362217 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -202,4 +202,5 @@ nul
|
||||
*.txt
|
||||
*.csv
|
||||
/*.xlsx
|
||||
!eb_org_center_mapping.xlsx
|
||||
!eb_org_center_mapping.xlsx
|
||||
/pyproject.toml
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -86,6 +86,10 @@ from eb_dashboard_utils import (
|
||||
clear_httpx_client,
|
||||
get_thread_position,
|
||||
get_config_path,
|
||||
set_dashboard_config_path_override,
|
||||
get_dashboard_config_path,
|
||||
set_output_file_suffix,
|
||||
get_output_filename,
|
||||
thread_local_storage,
|
||||
run_with_context
|
||||
)
|
||||
@@ -103,8 +107,23 @@ from eb_dashboard_excel_export import (
|
||||
set_dependencies as excel_set_dependencies
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.WARNING, format='%(asctime)s - %(levelname)s - %(message)s', filename=LOG_FILE_NAME,
|
||||
filemode='w')
|
||||
def _fatal_cli_error(message):
|
||||
"""Print a CLI error then wait for Enter before exiting (keeps console open on Windows Explorer launch)."""
|
||||
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')
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -130,7 +149,7 @@ _user_interaction_lock = threading.Lock()
|
||||
# Global variables (mutable, set at runtime - not constants)
|
||||
inclusions_mapping_config = []
|
||||
organizations_mapping_config = []
|
||||
_dashboard_config_path_override = None # Set by --config CLI arg if provided
|
||||
_include_drafts = False # Set by --include-drafts CLI arg if provided
|
||||
excel_export_config = None
|
||||
excel_export_enabled = False
|
||||
|
||||
@@ -157,14 +176,18 @@ if "--debug" in sys.argv:
|
||||
if "--config" in sys.argv:
|
||||
_idx = sys.argv.index("--config")
|
||||
if _idx + 1 >= len(sys.argv):
|
||||
print("Error: --config requires a file path argument")
|
||||
sys.exit(1)
|
||||
_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):
|
||||
_dashboard_config_path_override = _raw_config_path
|
||||
set_dashboard_config_path_override(_raw_config_path)
|
||||
else:
|
||||
_dashboard_config_path_override = os.path.join(get_config_path(), _raw_config_path)
|
||||
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 ---
|
||||
# NOTE: BAR_N_FMT_WIDTH, BAR_TOTAL_FMT_WIDTH, BAR_TIME_WIDTH, BAR_RATE_WIDTH
|
||||
@@ -471,7 +494,7 @@ def load_json_file(filename):
|
||||
def load_inclusions_mapping_config():
|
||||
"""Loads and validates the inclusions mapping configuration from the Excel file."""
|
||||
global inclusions_mapping_config
|
||||
config_path = _dashboard_config_path_override or os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
||||
|
||||
try:
|
||||
# Load with data_only=True to read calculated values instead of formulas
|
||||
@@ -573,7 +596,7 @@ def load_inclusions_mapping_config():
|
||||
def load_organizations_mapping_config():
|
||||
"""Loads and validates the organizations mapping configuration from the Excel file."""
|
||||
global organizations_mapping_config
|
||||
config_path = _dashboard_config_path_override or os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
||||
|
||||
try:
|
||||
# Load with data_only=True to read calculated values instead of formulas
|
||||
@@ -1232,14 +1255,14 @@ def get_all_questionnaires_by_patient(patient_id, record_data):
|
||||
q_category = get_nested_value(item, path=["questionnaire", "category"])
|
||||
answers = get_nested_value(item, path=["answers"], default={})
|
||||
|
||||
# ── UNLINKED PATIENT FILTER ─────────────────────────────────────────────
|
||||
# If answers.patient is missing, null, or does not match the requested
|
||||
# patient_id, the questionnaire is considered unlinked → clear answers
|
||||
# to prevent use of orphaned data.
|
||||
# (disable: comment out the block below)
|
||||
answers_patient = answers.get("subject") if isinstance(answers, dict) else None
|
||||
if not answers_patient or answers_patient != patient_id:
|
||||
answers = {}
|
||||
# ── 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:
|
||||
@@ -1496,7 +1519,7 @@ def main():
|
||||
load_organizations_mapping_config()
|
||||
|
||||
# Completely externalized Excel-only workflow
|
||||
export_excel_only(sys.argv, INCLUSIONS_FILE_NAME, ORGANIZATIONS_FILE_NAME,
|
||||
export_excel_only(sys.argv, get_output_filename(INCLUSIONS_FILE_NAME), get_output_filename(ORGANIZATIONS_FILE_NAME),
|
||||
inclusions_mapping_config, organizations_mapping_config)
|
||||
return
|
||||
|
||||
@@ -1641,7 +1664,7 @@ def main():
|
||||
has_coherence_critical, has_regression_critical = run_quality_checks(
|
||||
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
|
||||
old_inclusions_filename=INCLUSIONS_FILE_NAME # str: "endobest_inclusions.json" (version courante sur disque)
|
||||
old_inclusions_filename=get_output_filename(INCLUSIONS_FILE_NAME) # str: current file on disk (with suffix if any)
|
||||
)
|
||||
|
||||
# === CHECK FOR CRITICAL ISSUES AND ASK USER CONFIRMATION ===
|
||||
@@ -1666,9 +1689,9 @@ def main():
|
||||
# === WRITE NEW FILES ===
|
||||
print("Writing files...")
|
||||
|
||||
with open(INCLUSIONS_FILE_NAME, 'w', encoding='utf-8') as f_json:
|
||||
with open(get_output_filename(INCLUSIONS_FILE_NAME), 'w', encoding='utf-8') as f_json:
|
||||
json.dump(output_inclusions, f_json, indent=4, ensure_ascii=False)
|
||||
with open(ORGANIZATIONS_FILE_NAME, 'w', encoding='utf-8') as f_json:
|
||||
with open(get_output_filename(ORGANIZATIONS_FILE_NAME), 'w', encoding='utf-8') as f_json:
|
||||
json.dump(organizations_list, f_json, indent=4, ensure_ascii=False)
|
||||
|
||||
console.print("[green]✓ Data saved to JSON files[/green]")
|
||||
|
||||
2
eb_dashboard_debug_data-exe.bat
Normal file
2
eb_dashboard_debug_data-exe.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
@echo off
|
||||
eb_dashboard.exe --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix %*
|
||||
3
eb_dashboard_debug_data.bat
Normal file
3
eb_dashboard_debug_data.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@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:
|
||||
xw = None
|
||||
|
||||
from eb_dashboard_utils import get_nested_value, get_config_path
|
||||
from eb_dashboard_utils import get_nested_value, get_config_path, get_dashboard_config_path, get_output_filename
|
||||
from eb_dashboard_constants import (
|
||||
INCLUSIONS_FILE_NAME,
|
||||
ORGANIZATIONS_FILE_NAME,
|
||||
@@ -117,7 +117,7 @@ def load_excel_export_config(console_instance=None):
|
||||
if console_instance:
|
||||
console = console_instance
|
||||
|
||||
config_path = os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
||||
error_messages = []
|
||||
|
||||
try:
|
||||
@@ -399,7 +399,7 @@ def export_to_excel(inclusions_data, organizations_data, excel_config,
|
||||
output_template = workbook_config.get("output_file_name_template")
|
||||
if_output_exists = workbook_config.get("if_output_exists", OUTPUT_ACTION_OVERWRITE)
|
||||
|
||||
# Resolve output filename
|
||||
# Resolve output filename, then apply --add-suffix if provided
|
||||
try:
|
||||
output_filename = output_template.format(**template_vars)
|
||||
except KeyError as e:
|
||||
@@ -407,6 +407,7 @@ def export_to_excel(inclusions_data, organizations_data, excel_config,
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
output_filename = get_output_filename(output_filename)
|
||||
output_path = os.path.join(EXCEL_OUTPUT_FOLDER, output_filename)
|
||||
|
||||
# Log workbook processing start
|
||||
@@ -556,7 +557,7 @@ def _prepare_template_variables():
|
||||
"""
|
||||
# Get UTC timestamp from inclusions file
|
||||
# Use constant from eb_dashboard_constants (SINGLE SOURCE OF TRUTH)
|
||||
inclusions_file = INCLUSIONS_FILE_NAME
|
||||
inclusions_file = get_output_filename(INCLUSIONS_FILE_NAME)
|
||||
if os.path.exists(inclusions_file):
|
||||
file_mtime = os.path.getmtime(inclusions_file)
|
||||
extract_date_time_utc = datetime.fromtimestamp(file_mtime, tz=timezone.utc)
|
||||
@@ -1921,9 +1922,9 @@ def export_excel_only(sys_argv,
|
||||
global console
|
||||
|
||||
if not inclusions_filename:
|
||||
inclusions_filename = INCLUSIONS_FILE_NAME
|
||||
inclusions_filename = get_output_filename(INCLUSIONS_FILE_NAME)
|
||||
if not organizations_filename:
|
||||
organizations_filename = ORGANIZATIONS_FILE_NAME
|
||||
organizations_filename = get_output_filename(ORGANIZATIONS_FILE_NAME)
|
||||
|
||||
print()
|
||||
console.print("[bold cyan]═══ EXCEL ONLY MODE ═══[/bold cyan]\n")
|
||||
@@ -2038,8 +2039,8 @@ def run_normal_mode_export(excel_enabled, excel_config,
|
||||
try:
|
||||
# Load JSONs from filesystem to ensure data consistency with what was written
|
||||
# Use constants imported from eb_dashboard_constants.py (SINGLE SOURCE OF TRUTH)
|
||||
inclusions_from_fs = _load_json_file_internal(INCLUSIONS_FILE_NAME)
|
||||
organizations_from_fs = _load_json_file_internal(ORGANIZATIONS_FILE_NAME)
|
||||
inclusions_from_fs = _load_json_file_internal(get_output_filename(INCLUSIONS_FILE_NAME))
|
||||
organizations_from_fs = _load_json_file_internal(get_output_filename(ORGANIZATIONS_FILE_NAME))
|
||||
|
||||
if inclusions_from_fs is None or organizations_from_fs is None:
|
||||
error_msg = "Could not load data files for Excel export"
|
||||
|
||||
3
eb_dashboard_excel_only_debug_data-exe.bat
Normal file
3
eb_dashboard_excel_only_debug_data-exe.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
eb_dashboard.exe --excel-only --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix debug %*
|
||||
|
||||
4
eb_dashboard_excel_only_debug_data.bat
Normal file
4
eb_dashboard_excel_only_debug_data.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@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
|
||||
from rich.console import Console
|
||||
from eb_dashboard_utils import get_nested_value, get_old_filename as _get_old_filename, get_config_path
|
||||
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_constants import (
|
||||
INCLUSIONS_FILE_NAME,
|
||||
ORGANIZATIONS_FILE_NAME,
|
||||
@@ -93,7 +93,7 @@ def load_regression_check_config(console_instance=None):
|
||||
if console_instance:
|
||||
console = console_instance
|
||||
|
||||
config_path = os.path.join(get_config_path(), DASHBOARD_CONFIG_FILE_NAME)
|
||||
config_path = get_dashboard_config_path(DASHBOARD_CONFIG_FILE_NAME)
|
||||
|
||||
try:
|
||||
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)
|
||||
print()
|
||||
old_inclusions_file = _get_old_filename(INCLUSIONS_FILE_NAME, OLD_FILE_SUFFIX)
|
||||
old_inclusions_file = _get_old_filename(get_output_filename(INCLUSIONS_FILE_NAME), OLD_FILE_SUFFIX)
|
||||
has_coherence_critical, has_regression_critical = run_quality_checks(
|
||||
current_inclusions=INCLUSIONS_FILE_NAME,
|
||||
organizations_list=ORGANIZATIONS_FILE_NAME,
|
||||
current_inclusions=get_output_filename(INCLUSIONS_FILE_NAME),
|
||||
organizations_list=get_output_filename(ORGANIZATIONS_FILE_NAME),
|
||||
old_inclusions_filename=old_inclusions_file
|
||||
)
|
||||
|
||||
@@ -370,8 +370,8 @@ def backup_output_files():
|
||||
except Exception as e:
|
||||
logging.warning(f"Could not backup {source}: {e}")
|
||||
|
||||
_backup_file_silent(INCLUSIONS_FILE_NAME, _get_old_filename(INCLUSIONS_FILE_NAME, OLD_FILE_SUFFIX))
|
||||
_backup_file_silent(ORGANIZATIONS_FILE_NAME, _get_old_filename(ORGANIZATIONS_FILE_NAME, OLD_FILE_SUFFIX))
|
||||
_backup_file_silent(get_output_filename(INCLUSIONS_FILE_NAME), _get_old_filename(get_output_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))
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -204,6 +204,43 @@ def get_config_path():
|
||||
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"):
|
||||
"""Generate old backup filename from current filename.
|
||||
|
||||
|
||||
3
eb_dashboard_with_drafts-exe.bat
Normal file
3
eb_dashboard_with_drafts-exe.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
eb_dashboard.exe --include-drafts --add-suffix with_drafts %*
|
||||
|
||||
4
eb_dashboard_with_drafts.bat
Normal file
4
eb_dashboard_with_drafts.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
||||
python eb_dashboard.py --include-drafts --add-suffix with_drafts %*
|
||||
|
||||
3
eb_dashboard_with_drafts_excel_only-exe.bat
Normal file
3
eb_dashboard_with_drafts_excel_only-exe.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
eb_dashboard.exe --include-drafts --excel-only --add-suffix with_drafts %*
|
||||
|
||||
4
eb_dashboard_with_drafts_excel_only.bat
Normal file
4
eb_dashboard_with_drafts_excel_only.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
call C:\PythonProjects\.rcvenv\Scripts\activate.bat
|
||||
python eb_dashboard.py --include-drafts --excel-only --add-suffix with_drafts %*
|
||||
|
||||
3
eb_dashboard_with_drafts_excel_only_debug_data-exe.bat
Normal file
3
eb_dashboard_with_drafts_excel_only_debug_data-exe.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
eb_dashboard.exe --include-drafts --excel-only --config "Endobest_Dashboard_Config - Debug.xlsx" --add-suffix with_drafts_debug %*
|
||||
|
||||
4
eb_dashboard_with_drafts_excel_only_debug_data.bat
Normal file
4
eb_dashboard_with_drafts_excel_only_debug_data.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@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.
Reference in New Issue
Block a user