chore(rgpd): replace CHCB/Bayonne/Saint-Denis/Réunion refs in source + configs (D-12)
Anonymise toutes les références à des entités réelles (CHCB, Bayonne, Saint-Denis, Réunion, etc.) dans le code source, les configurations YAML, les scripts/outils, et les tests unitaires. Conserve les tests synthétiques (cases) intentionnels. - profile key chcb_strict → chuxx_strict - CHCB → CHUXX, Bayonne → Chicago, Saint-Denis → Springfield, Réunion → Province Bêta, 64100/97400 → 12345, FINESS → 999999999, préfixe tél 05.59.44 → 0X.XX.XX - renomme tools/test_chcb_leak.py → tools/test_force_term_leak.py Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
356
profile_defaults.py
Normal file
356
profile_defaults.py
Normal file
@@ -0,0 +1,356 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Helpers partagés pour les profils métier.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except Exception:
|
||||
yaml = None
|
||||
|
||||
from config_defaults import CONFIG_DIR, deep_merge_dict
|
||||
|
||||
|
||||
DEFAULT_PROFILES_CONFIG_PATH = CONFIG_DIR / "profiles.default.yml"
|
||||
RUNTIME_PROFILES_CONFIG_PATH = CONFIG_DIR / "profiles.yml"
|
||||
|
||||
_RUNTIME_PROFILES_OVERLAY_TEXT = """# Surcharge locale des profils métier.
|
||||
# Source de vérité : config/profiles.default.yml
|
||||
# Ne mettez ici que les écarts spécifiques à votre environnement.
|
||||
#
|
||||
# Exemples :
|
||||
# default_profile: chuxx_strict
|
||||
# profiles:
|
||||
# mon_profil:
|
||||
# label: Mon profil
|
||||
# description: Surcharge locale
|
||||
# require_manual_mask: true
|
||||
# force_disable_vlm: true
|
||||
# preferred_manual_mask_template: chcb/formulaire.yml
|
||||
# param_lists:
|
||||
# whitelist_phrases:
|
||||
# - Document validé DIM
|
||||
# dictionaries_overlay:
|
||||
# blacklist:
|
||||
# force_mask_terms:
|
||||
# - MON_ETAB
|
||||
{}
|
||||
"""
|
||||
|
||||
_FALLBACK_DEFAULT_PROFILES_TEXT = """version: 1
|
||||
default_profile: standard_local
|
||||
profiles:
|
||||
standard_local:
|
||||
label: Standard local
|
||||
description: Profil par défaut pour les traitements internes.
|
||||
require_manual_mask: false
|
||||
force_disable_vlm: false
|
||||
dictionaries_overlay: {}
|
||||
chuxx_strict:
|
||||
label: CHUXX strict
|
||||
description: Profil conservateur pour le CHUXX, orienté diffusion prudente.
|
||||
require_manual_mask: false
|
||||
force_disable_vlm: true
|
||||
dictionaries_overlay:
|
||||
blacklist:
|
||||
force_mask_terms:
|
||||
- CHUXX
|
||||
- Centre Hospitalier Universitaire XX
|
||||
- CENTRE HOSPITALIER UNIVERSITAIRE XX
|
||||
partage_recherche:
|
||||
label: Partage recherche
|
||||
description: Profil externe strict. Le masque manuel est recommandé pour les formulaires répétitifs.
|
||||
require_manual_mask: true
|
||||
force_disable_vlm: true
|
||||
dictionaries_overlay:
|
||||
blacklist:
|
||||
force_mask_terms:
|
||||
- CHUXX
|
||||
- Centre Hospitalier Universitaire XX
|
||||
- CENTRE HOSPITALIER UNIVERSITAIRE XX
|
||||
dossier_audit:
|
||||
label: Dossier audit
|
||||
description: Profil orienté traçabilité et reproductibilité.
|
||||
require_manual_mask: false
|
||||
force_disable_vlm: true
|
||||
dictionaries_overlay: {}
|
||||
demo:
|
||||
label: Démo
|
||||
description: Profil léger pour démonstration interne sur poste bureautique.
|
||||
require_manual_mask: false
|
||||
force_disable_vlm: true
|
||||
dictionaries_overlay: {}
|
||||
"""
|
||||
|
||||
_FALLBACK_DEFAULT_PROFILES_DICT: Dict[str, Any] = {
|
||||
"version": 1,
|
||||
"default_profile": "standard_local",
|
||||
"profiles": {
|
||||
"standard_local": {
|
||||
"label": "Standard local",
|
||||
"description": "Profil par défaut pour les traitements internes.",
|
||||
"require_manual_mask": False,
|
||||
"force_disable_vlm": False,
|
||||
"dictionaries_overlay": {},
|
||||
},
|
||||
"chuxx_strict": {
|
||||
"label": "CHUXX strict",
|
||||
"description": "Profil conservateur pour le CHUXX, orienté diffusion prudente.",
|
||||
"require_manual_mask": False,
|
||||
"force_disable_vlm": True,
|
||||
"dictionaries_overlay": {
|
||||
"blacklist": {
|
||||
"force_mask_terms": [
|
||||
"CHUXX",
|
||||
"Centre Hospitalier Universitaire XX",
|
||||
"CENTRE HOSPITALIER UNIVERSITAIRE XX",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"partage_recherche": {
|
||||
"label": "Partage recherche",
|
||||
"description": (
|
||||
"Profil externe strict. Le masque manuel est recommandé "
|
||||
"pour les formulaires répétitifs."
|
||||
),
|
||||
"require_manual_mask": True,
|
||||
"force_disable_vlm": True,
|
||||
"dictionaries_overlay": {
|
||||
"blacklist": {
|
||||
"force_mask_terms": [
|
||||
"CHUXX",
|
||||
"Centre Hospitalier Universitaire XX",
|
||||
"CENTRE HOSPITALIER UNIVERSITAIRE XX",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
"dossier_audit": {
|
||||
"label": "Dossier audit",
|
||||
"description": "Profil orienté traçabilité et reproductibilité.",
|
||||
"require_manual_mask": False,
|
||||
"force_disable_vlm": True,
|
||||
"dictionaries_overlay": {},
|
||||
},
|
||||
"demo": {
|
||||
"label": "Démo",
|
||||
"description": "Profil léger pour démonstration interne sur poste bureautique.",
|
||||
"require_manual_mask": False,
|
||||
"force_disable_vlm": True,
|
||||
"dictionaries_overlay": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def read_default_profiles_text() -> str:
|
||||
try:
|
||||
return DEFAULT_PROFILES_CONFIG_PATH.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return _FALLBACK_DEFAULT_PROFILES_TEXT
|
||||
|
||||
|
||||
def read_runtime_profiles_overlay_text() -> str:
|
||||
return _RUNTIME_PROFILES_OVERLAY_TEXT
|
||||
|
||||
|
||||
def load_default_profiles_dict() -> Dict[str, Any]:
|
||||
text = read_default_profiles_text()
|
||||
if yaml is not None:
|
||||
try:
|
||||
loaded = yaml.safe_load(text) or {}
|
||||
if isinstance(loaded, dict):
|
||||
return loaded
|
||||
except Exception:
|
||||
pass
|
||||
return deepcopy(_FALLBACK_DEFAULT_PROFILES_DICT)
|
||||
|
||||
|
||||
def list_default_profile_keys() -> set[str]:
|
||||
data = load_default_profiles_dict()
|
||||
profiles = data.get("profiles", {}) or {}
|
||||
if not isinstance(profiles, dict):
|
||||
return set()
|
||||
return {str(key) for key in profiles}
|
||||
|
||||
|
||||
def load_runtime_profiles_overlay_dict(path: Path | None = None) -> Dict[str, Any]:
|
||||
target = Path(path) if path is not None else RUNTIME_PROFILES_CONFIG_PATH
|
||||
if not target.exists() or yaml is None:
|
||||
return {}
|
||||
try:
|
||||
loaded = yaml.safe_load(target.read_text(encoding="utf-8")) or {}
|
||||
if isinstance(loaded, dict):
|
||||
return loaded
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
|
||||
def load_effective_profiles_dict(path: Path | None = None) -> Dict[str, Any]:
|
||||
return deep_merge_dict(
|
||||
load_default_profiles_dict(),
|
||||
load_runtime_profiles_overlay_dict(path),
|
||||
)
|
||||
|
||||
|
||||
def _normalize_string_list(values: Any) -> list[str]:
|
||||
if not isinstance(values, list):
|
||||
return []
|
||||
normalized: list[str] = []
|
||||
for value in values:
|
||||
text = str(value).strip()
|
||||
if text:
|
||||
normalized.append(text)
|
||||
return normalized
|
||||
|
||||
|
||||
def _normalize_param_lists(value: Any) -> Dict[str, list[str]]:
|
||||
if not isinstance(value, dict):
|
||||
return {}
|
||||
return {
|
||||
"whitelist_phrases": _normalize_string_list(value.get("whitelist_phrases", [])),
|
||||
"blacklist_force_mask_terms": _normalize_string_list(
|
||||
value.get("blacklist_force_mask_terms", [])
|
||||
),
|
||||
"additional_stopwords": _normalize_string_list(value.get("additional_stopwords", [])),
|
||||
}
|
||||
|
||||
|
||||
def _write_runtime_profiles_overlay_dict(path: Path, data: Dict[str, Any]) -> Path:
|
||||
if yaml is None:
|
||||
raise RuntimeError("PyYAML indisponible")
|
||||
body = yaml.safe_dump(
|
||||
data or {},
|
||||
allow_unicode=True,
|
||||
default_flow_style=False,
|
||||
sort_keys=False,
|
||||
)
|
||||
header = (
|
||||
"# Surcharge locale des profils métier.\n"
|
||||
"# Source de vérité : config/profiles.default.yml\n"
|
||||
"# Les profils créés depuis la GUI sont enregistrés ici.\n"
|
||||
)
|
||||
path.write_text(header + "\n" + body, encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def ensure_runtime_profiles_config(path: Path | None = None) -> Path:
|
||||
target = Path(path) if path is not None else RUNTIME_PROFILES_CONFIG_PATH
|
||||
if not target.exists():
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(read_runtime_profiles_overlay_text(), encoding="utf-8")
|
||||
return target
|
||||
|
||||
|
||||
def list_effective_profiles(path: Path | None = None) -> Dict[str, Dict[str, Any]]:
|
||||
data = load_effective_profiles_dict(path)
|
||||
profiles = data.get("profiles", {}) or {}
|
||||
if not isinstance(profiles, dict):
|
||||
return {}
|
||||
normalized: Dict[str, Dict[str, Any]] = {}
|
||||
for key, value in profiles.items():
|
||||
if not isinstance(value, dict):
|
||||
continue
|
||||
raw_param_lists = value.get("param_lists")
|
||||
has_param_lists = isinstance(raw_param_lists, dict)
|
||||
preferred_manual_mask_template = str(value.get("preferred_manual_mask_template") or "").strip()
|
||||
normalized[str(key)] = {
|
||||
"label": str(value.get("label") or key),
|
||||
"description": str(value.get("description") or ""),
|
||||
"require_manual_mask": bool(value.get("require_manual_mask", False)),
|
||||
"force_disable_vlm": bool(value.get("force_disable_vlm", False)),
|
||||
"dictionaries_overlay": deepcopy(value.get("dictionaries_overlay") or {}),
|
||||
"param_lists": _normalize_param_lists(raw_param_lists),
|
||||
"has_param_lists": has_param_lists,
|
||||
"preferred_manual_mask_template": preferred_manual_mask_template,
|
||||
"has_preferred_manual_mask_template": "preferred_manual_mask_template" in value,
|
||||
}
|
||||
return normalized
|
||||
|
||||
|
||||
def get_default_profile_key(path: Path | None = None) -> str:
|
||||
data = load_effective_profiles_dict(path)
|
||||
key = str(data.get("default_profile") or "").strip()
|
||||
profiles = list_effective_profiles(path)
|
||||
if key and key in profiles:
|
||||
return key
|
||||
if profiles:
|
||||
return next(iter(profiles))
|
||||
return "standard_local"
|
||||
|
||||
|
||||
def save_runtime_profile(
|
||||
profile_key: str,
|
||||
profile_spec: Dict[str, Any],
|
||||
path: Path | None = None,
|
||||
*,
|
||||
set_default: bool = False,
|
||||
) -> Path:
|
||||
target = ensure_runtime_profiles_config(path)
|
||||
data = load_runtime_profiles_overlay_dict(target)
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
|
||||
profiles = data.get("profiles")
|
||||
if not isinstance(profiles, dict):
|
||||
profiles = {}
|
||||
data["profiles"] = profiles
|
||||
|
||||
normalized_spec: Dict[str, Any] = {
|
||||
"label": str(profile_spec.get("label") or profile_key),
|
||||
"description": str(profile_spec.get("description") or ""),
|
||||
"require_manual_mask": bool(profile_spec.get("require_manual_mask", False)),
|
||||
"force_disable_vlm": bool(profile_spec.get("force_disable_vlm", False)),
|
||||
"dictionaries_overlay": deepcopy(profile_spec.get("dictionaries_overlay") or {}),
|
||||
}
|
||||
|
||||
if profile_spec.get("has_param_lists") or "param_lists" in profile_spec:
|
||||
normalized_spec["param_lists"] = _normalize_param_lists(profile_spec.get("param_lists"))
|
||||
|
||||
if (
|
||||
profile_spec.get("has_preferred_manual_mask_template")
|
||||
or "preferred_manual_mask_template" in profile_spec
|
||||
):
|
||||
normalized_spec["preferred_manual_mask_template"] = str(
|
||||
profile_spec.get("preferred_manual_mask_template") or ""
|
||||
).strip()
|
||||
|
||||
profiles[str(profile_key)] = normalized_spec
|
||||
if set_default:
|
||||
data["default_profile"] = str(profile_key)
|
||||
|
||||
return _write_runtime_profiles_overlay_dict(target, data)
|
||||
|
||||
|
||||
def set_runtime_default_profile(profile_key: str, path: Path | None = None) -> Path:
|
||||
target = ensure_runtime_profiles_config(path)
|
||||
data = load_runtime_profiles_overlay_dict(target)
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
data["default_profile"] = str(profile_key)
|
||||
return _write_runtime_profiles_overlay_dict(target, data)
|
||||
|
||||
|
||||
def delete_runtime_profile(profile_key: str, path: Path | None = None) -> Path:
|
||||
target = ensure_runtime_profiles_config(path)
|
||||
data = load_runtime_profiles_overlay_dict(target)
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
|
||||
profiles = data.get("profiles")
|
||||
if isinstance(profiles, dict):
|
||||
profiles.pop(str(profile_key), None)
|
||||
if not profiles:
|
||||
data.pop("profiles", None)
|
||||
|
||||
if str(data.get("default_profile") or "").strip() == str(profile_key):
|
||||
data["default_profile"] = "standard_local"
|
||||
|
||||
return _write_runtime_profiles_overlay_dict(target, data)
|
||||
Reference in New Issue
Block a user