Compare commits
93 Commits
backup/win
...
012445755a
| Author | SHA1 | Date | |
|---|---|---|---|
| 012445755a | |||
| 4b825976bd | |||
| ab5a24fa68 | |||
| 6586b89b8f | |||
| 234137ec50 | |||
| 003be68ca8 | |||
| 8e43d8d1ae | |||
| f17438c2ec | |||
| 0a377bc001 | |||
| e2e2a7c8e3 | |||
| ea214db170 | |||
| aa3db69a9b | |||
| 83769f6e63 | |||
| e6f3853426 | |||
| fd95ae5f2a | |||
| 8e458c16ca | |||
| 4b5925306e | |||
| 59acf390f4 | |||
| b5058b9c4b | |||
| b23355ed23 | |||
| 51c75558bc | |||
| 2f19f7c470 | |||
| c157205751 | |||
| 4d33610655 | |||
| 2a4b9d79a1 | |||
| fb7896f88d | |||
| 22fbf1c772 | |||
| 23e19e17e4 | |||
| 219ac18854 | |||
| ac5c35ae2d | |||
| b2ee6ad835 | |||
| 898ad9d82d | |||
| 106f1fcd2e | |||
| f9fbae1f27 | |||
| dcccd60c39 | |||
| 63a4a013a2 | |||
| 437877e1c8 | |||
| 3992b43925 | |||
| d1bdfb1aca | |||
| 65a02952c5 | |||
| ad7f1ffa8a | |||
| 2731bc1ce7 | |||
| 7c05ff9aaf | |||
| 27d19ebed7 | |||
| d957e72aff | |||
| 49ff464e6e | |||
| a827d860f1 | |||
| eb14cd219d | |||
| c9572c383a | |||
| 274e2fa586 | |||
| 7a2af5c905 | |||
| 4488a1d4a0 | |||
| 19e089ea38 | |||
| 26b210607c | |||
| 6e0e8c7312 | |||
| 26ac02b0cb | |||
| 782551c1c6 | |||
| 8629a0cda0 | |||
| e967a67052 | |||
| bc2fe667a0 | |||
| f9532d5543 | |||
| 4e6fd97e84 | |||
| cede2d64d6 | |||
| 98a21d7ccc | |||
| ea761823d6 | |||
| 47a71df930 | |||
| 93617bab55 | |||
| dfa6e2957b | |||
| eb797a4761 | |||
| 85e19af655 | |||
| d6915247fe | |||
| bf30f622d9 | |||
| b46ea83900 | |||
| 5163cb1657 | |||
| 09231be5e8 | |||
| 3b1f6cdfbe | |||
| 78adb3ba70 | |||
| 63bd4ace1d | |||
| ee34042179 | |||
| 883f14ab79 | |||
| f92da4d54e | |||
| 871221ea56 | |||
| f188116bc1 | |||
| 6806aee587 | |||
| 70ff0b9e12 | |||
| dfa45041d7 | |||
| 4eba826ca5 | |||
| 0ba5424eb0 | |||
| 99b6e7f1d1 | |||
| 30a6ebcc19 | |||
| f61e767ee6 | |||
| c78f9f415d | |||
| 340348b820 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,12 +6,10 @@ __pycache__/
|
||||
*.egg
|
||||
dist/
|
||||
build/
|
||||
release/
|
||||
*.whl
|
||||
|
||||
# === Virtual environments ===
|
||||
.venv/
|
||||
.venv_build_win/
|
||||
venv/
|
||||
venv_*/
|
||||
env/
|
||||
@@ -68,9 +66,6 @@ Thumbs.db
|
||||
# === Secrets ===
|
||||
.env
|
||||
*.env
|
||||
*.pfx
|
||||
*.p12
|
||||
build_signing.local.ps1
|
||||
credentials.json
|
||||
token.pickle
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
406
admin_rules.py
406
admin_rules.py
@@ -1,406 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Helpers partagés pour les règles d'administration.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
import re
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except Exception:
|
||||
yaml = None
|
||||
|
||||
from config_defaults import CONFIG_DIR, deep_merge_dict
|
||||
|
||||
|
||||
DEFAULT_ADMIN_RULES_CONFIG_PATH = CONFIG_DIR / "admin_rules.default.yml"
|
||||
RUNTIME_ADMIN_RULES_CONFIG_PATH = CONFIG_DIR / "admin_rules.yml"
|
||||
|
||||
_RUNTIME_ADMIN_RULES_OVERLAY_TEXT = """# Surcharge locale des règles d'administration.
|
||||
# Ce fichier est optionnel. Les règles actives de config/admin_rules.default.yml
|
||||
# restent valides tant qu'aucune surcharge locale n'est définie ici.
|
||||
#
|
||||
# Exemple :
|
||||
# version: 1
|
||||
# rules:
|
||||
# - id: rule_identifier_1234567
|
||||
# status: active
|
||||
# governance:
|
||||
# approved_by: responsable_qualite
|
||||
version: 1
|
||||
rules: []
|
||||
"""
|
||||
|
||||
_FALLBACK_DEFAULT_ADMIN_RULES_DICT: dict[str, Any] = {
|
||||
"version": 1,
|
||||
"rules": [],
|
||||
}
|
||||
|
||||
|
||||
def _is_non_empty_string(value: Any) -> bool:
|
||||
return isinstance(value, str) and bool(value.strip())
|
||||
|
||||
|
||||
def read_default_admin_rules_text() -> str:
|
||||
try:
|
||||
return DEFAULT_ADMIN_RULES_CONFIG_PATH.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
return "version: 1\nrules: []\n"
|
||||
|
||||
|
||||
def read_runtime_admin_rules_overlay_text() -> str:
|
||||
return _RUNTIME_ADMIN_RULES_OVERLAY_TEXT
|
||||
|
||||
|
||||
def load_default_admin_rules_dict() -> dict[str, Any]:
|
||||
if yaml is None:
|
||||
return deepcopy(_FALLBACK_DEFAULT_ADMIN_RULES_DICT)
|
||||
try:
|
||||
loaded = yaml.safe_load(read_default_admin_rules_text()) or {}
|
||||
if isinstance(loaded, dict):
|
||||
return loaded
|
||||
except Exception:
|
||||
pass
|
||||
return deepcopy(_FALLBACK_DEFAULT_ADMIN_RULES_DICT)
|
||||
|
||||
|
||||
def load_runtime_admin_rules_overlay_dict(path: Path | None = None) -> dict[str, Any]:
|
||||
target = Path(path) if path is not None else RUNTIME_ADMIN_RULES_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 _merge_rules_by_id(base_rules: list[dict[str, Any]], overlay_rules: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
merged: list[dict[str, Any]] = [deepcopy(rule) for rule in base_rules]
|
||||
index_by_id = {
|
||||
rule.get("id"): idx
|
||||
for idx, rule in enumerate(merged)
|
||||
if isinstance(rule, dict) and _is_non_empty_string(rule.get("id"))
|
||||
}
|
||||
for overlay_rule in overlay_rules:
|
||||
if not isinstance(overlay_rule, dict):
|
||||
continue
|
||||
rule_id = overlay_rule.get("id")
|
||||
if _is_non_empty_string(rule_id) and rule_id in index_by_id:
|
||||
idx = index_by_id[rule_id]
|
||||
merged[idx] = deep_merge_dict(merged[idx], overlay_rule)
|
||||
else:
|
||||
merged.append(deepcopy(overlay_rule))
|
||||
if _is_non_empty_string(rule_id):
|
||||
index_by_id[rule_id] = len(merged) - 1
|
||||
return merged
|
||||
|
||||
|
||||
def merge_admin_rules_dict(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]:
|
||||
merged = deep_merge_dict(base, {k: v for k, v in overlay.items() if k != "rules"})
|
||||
merged["rules"] = _merge_rules_by_id(base.get("rules", []) or [], overlay.get("rules", []) or [])
|
||||
return merged
|
||||
|
||||
|
||||
def load_effective_admin_rules_dict(path: Path | None = None) -> dict[str, Any]:
|
||||
return merge_admin_rules_dict(
|
||||
load_default_admin_rules_dict(),
|
||||
load_runtime_admin_rules_overlay_dict(path),
|
||||
)
|
||||
|
||||
|
||||
def ensure_runtime_admin_rules_config(path: Path | None = None) -> Path:
|
||||
target = Path(path) if path is not None else RUNTIME_ADMIN_RULES_CONFIG_PATH
|
||||
if not target.exists():
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(read_runtime_admin_rules_overlay_text(), encoding="utf-8")
|
||||
return target
|
||||
|
||||
|
||||
def _dedupe_keep_order(values: list[str]) -> list[str]:
|
||||
seen: set[str] = set()
|
||||
output: list[str] = []
|
||||
for value in values:
|
||||
if value in seen:
|
||||
continue
|
||||
seen.add(value)
|
||||
output.append(value)
|
||||
return output
|
||||
|
||||
|
||||
def generate_rule_variants(rule: dict[str, Any], limit: int = 12) -> list[str]:
|
||||
rule_type = rule.get("type")
|
||||
match = rule.get("match") or {}
|
||||
normalization = rule.get("normalization") or {}
|
||||
variants: list[str] = []
|
||||
|
||||
if rule_type in {"exact_term", "preserve_phrase"}:
|
||||
exact_value = str(match.get("exact_value", "")).strip()
|
||||
return [exact_value] if exact_value else []
|
||||
|
||||
if rule_type == "normalized_identifier":
|
||||
canonical = str(match.get("canonical_value", "")).strip()
|
||||
prefixes = normalization.get("accepted_prefixes") or []
|
||||
separators = normalization.get("prefix_value_separators") or [" "]
|
||||
if normalization.get("allow_bare_value", False) and canonical:
|
||||
variants.append(canonical)
|
||||
for prefix in prefixes:
|
||||
for separator in separators:
|
||||
variants.append(f"{prefix}{separator}{canonical}")
|
||||
if normalization.get("multiline", False):
|
||||
variants.append(f"{prefix}\n{canonical}")
|
||||
return _dedupe_keep_order(variants)[:limit]
|
||||
|
||||
if rule_type == "contextual_identifier":
|
||||
canonical = str(match.get("canonical_value", "")).strip()
|
||||
prefixes = match.get("context_prefixes") or []
|
||||
separators = match.get("context_separators") or [": ", ":"]
|
||||
for prefix in prefixes:
|
||||
for separator in separators:
|
||||
variants.append(f"{prefix}{separator}{canonical}")
|
||||
if (rule.get("normalization") or {}).get("multiline", False):
|
||||
variants.append(f"{prefix}\n{canonical}")
|
||||
variants.append(f"{prefix} :\n{canonical}")
|
||||
return _dedupe_keep_order(variants)[:limit]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
VALID_TYPES = {
|
||||
"exact_term",
|
||||
"normalized_identifier",
|
||||
"contextual_identifier",
|
||||
"preserve_phrase",
|
||||
}
|
||||
VALID_ACTIONS = {"mask", "preserve"}
|
||||
VALID_STATUSES = {"draft", "candidate", "approved", "active", "disabled", "retired"}
|
||||
VALID_ENVIRONMENTS = {"test", "staging", "prod"}
|
||||
VALID_SECTIONS = {"narrative", "structured", "table", "header", "footer"}
|
||||
|
||||
|
||||
def validate_rules_config(data: dict[str, Any]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
|
||||
version = data.get("version")
|
||||
if not isinstance(version, int) or version < 1:
|
||||
errors.append("`version` doit etre un entier >= 1.")
|
||||
|
||||
rules = data.get("rules")
|
||||
if not isinstance(rules, list):
|
||||
errors.append("`rules` doit etre une liste.")
|
||||
return errors
|
||||
|
||||
seen_ids: set[str] = set()
|
||||
for index, rule in enumerate(rules):
|
||||
prefix = f"rules[{index}]"
|
||||
if not isinstance(rule, dict):
|
||||
errors.append(f"{prefix}: chaque regle doit etre un mapping.")
|
||||
continue
|
||||
|
||||
rule_id = rule.get("id")
|
||||
if not _is_non_empty_string(rule_id):
|
||||
errors.append(f"{prefix}: `id` est obligatoire.")
|
||||
elif rule_id in seen_ids:
|
||||
errors.append(f"{prefix}: `id` duplique `{rule_id}`.")
|
||||
else:
|
||||
seen_ids.add(rule_id)
|
||||
|
||||
if not _is_non_empty_string(rule.get("label")):
|
||||
errors.append(f"{prefix}: `label` est obligatoire.")
|
||||
|
||||
rule_type = rule.get("type")
|
||||
if rule_type not in VALID_TYPES:
|
||||
errors.append(f"{prefix}: `type` invalide.")
|
||||
|
||||
action = rule.get("action")
|
||||
if action not in VALID_ACTIONS:
|
||||
errors.append(f"{prefix}: `action` invalide.")
|
||||
|
||||
status = rule.get("status")
|
||||
if status not in VALID_STATUSES:
|
||||
errors.append(f"{prefix}: `status` invalide.")
|
||||
|
||||
if action == "mask" and not _is_non_empty_string(rule.get("placeholder")):
|
||||
errors.append(f"{prefix}: `placeholder` est obligatoire pour une regle de masquage.")
|
||||
|
||||
match = rule.get("match")
|
||||
if not isinstance(match, dict):
|
||||
errors.append(f"{prefix}: `match` doit etre un mapping.")
|
||||
match = {}
|
||||
|
||||
normalization = rule.get("normalization") or {}
|
||||
if normalization and not isinstance(normalization, dict):
|
||||
errors.append(f"{prefix}: `normalization` doit etre un mapping.")
|
||||
normalization = {}
|
||||
|
||||
scope = rule.get("scope")
|
||||
if not isinstance(scope, dict):
|
||||
errors.append(f"{prefix}: `scope` doit etre un mapping.")
|
||||
scope = {}
|
||||
|
||||
governance = rule.get("governance")
|
||||
if not isinstance(governance, dict):
|
||||
errors.append(f"{prefix}: `governance` doit etre un mapping.")
|
||||
governance = {}
|
||||
|
||||
document_families = scope.get("document_families")
|
||||
if not isinstance(document_families, list) or not document_families:
|
||||
errors.append(f"{prefix}: `scope.document_families` doit etre une liste non vide.")
|
||||
|
||||
environments = scope.get("environments")
|
||||
if not isinstance(environments, list) or not environments:
|
||||
errors.append(f"{prefix}: `scope.environments` doit etre une liste non vide.")
|
||||
else:
|
||||
invalid_envs = [value for value in environments if value not in VALID_ENVIRONMENTS]
|
||||
if invalid_envs:
|
||||
errors.append(f"{prefix}: environnements invalides: {', '.join(invalid_envs)}.")
|
||||
|
||||
sections = scope.get("sections")
|
||||
if not isinstance(sections, list) or not sections:
|
||||
errors.append(f"{prefix}: `scope.sections` doit etre une liste non vide.")
|
||||
else:
|
||||
invalid_sections = [value for value in sections if value not in VALID_SECTIONS]
|
||||
if invalid_sections:
|
||||
errors.append(f"{prefix}: sections invalides: {', '.join(invalid_sections)}.")
|
||||
|
||||
if not _is_non_empty_string(governance.get("owner")):
|
||||
errors.append(f"{prefix}: `governance.owner` est obligatoire.")
|
||||
if not _is_non_empty_string(governance.get("justification")):
|
||||
errors.append(f"{prefix}: `governance.justification` est obligatoire.")
|
||||
if not _is_non_empty_string(governance.get("created_at")):
|
||||
errors.append(f"{prefix}: `governance.created_at` est obligatoire.")
|
||||
|
||||
tests = governance.get("tests")
|
||||
if not isinstance(tests, dict):
|
||||
errors.append(f"{prefix}: `governance.tests` doit etre un mapping.")
|
||||
tests = {}
|
||||
required_case_ids = tests.get("required_case_ids")
|
||||
if not isinstance(required_case_ids, list) or not required_case_ids:
|
||||
errors.append(f"{prefix}: `governance.tests.required_case_ids` doit etre une liste non vide.")
|
||||
|
||||
if rule_type == "exact_term":
|
||||
if not _is_non_empty_string(match.get("exact_value")):
|
||||
errors.append(f"{prefix}: `match.exact_value` est obligatoire pour `exact_term`.")
|
||||
|
||||
if rule_type == "preserve_phrase":
|
||||
if action != "preserve":
|
||||
errors.append(f"{prefix}: `preserve_phrase` doit utiliser `action: preserve`.")
|
||||
if not _is_non_empty_string(match.get("exact_value")):
|
||||
errors.append(f"{prefix}: `match.exact_value` est obligatoire pour `preserve_phrase`.")
|
||||
|
||||
if rule_type == "normalized_identifier":
|
||||
if not _is_non_empty_string(match.get("canonical_value")):
|
||||
errors.append(f"{prefix}: `match.canonical_value` est obligatoire pour `normalized_identifier`.")
|
||||
|
||||
if rule_type == "contextual_identifier":
|
||||
if not _is_non_empty_string(match.get("canonical_value")):
|
||||
errors.append(f"{prefix}: `match.canonical_value` est obligatoire pour `contextual_identifier`.")
|
||||
context_prefixes = match.get("context_prefixes")
|
||||
if not isinstance(context_prefixes, list) or not context_prefixes:
|
||||
errors.append(f"{prefix}: `match.context_prefixes` doit etre une liste non vide.")
|
||||
|
||||
if status == "active" and governance.get("review_required_for_activation", False):
|
||||
if not _is_non_empty_string(governance.get("approved_by")):
|
||||
errors.append(f"{prefix}: `governance.approved_by` est obligatoire pour une regle active.")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def _placeholder_to_kind(placeholder: str) -> str:
|
||||
if isinstance(placeholder, str) and placeholder.startswith("[") and placeholder.endswith("]"):
|
||||
return placeholder[1:-1]
|
||||
return "MASK"
|
||||
|
||||
|
||||
def _literal_to_pattern(text: str, multiline: bool) -> str:
|
||||
parts: list[str] = []
|
||||
for char in text:
|
||||
if char == " ":
|
||||
parts.append(r"\s*" if multiline else r"[ \t]*")
|
||||
elif char == "\n":
|
||||
parts.append(r"\s*" if multiline else r"\n")
|
||||
else:
|
||||
parts.append(re.escape(char))
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def _compile_identifier_rule(rule: dict[str, Any]) -> dict[str, Any]:
|
||||
rule_type = rule.get("type")
|
||||
normalization = rule.get("normalization") or {}
|
||||
multiline = bool(normalization.get("multiline", False))
|
||||
flags = re.IGNORECASE if normalization.get("case_insensitive", False) else 0
|
||||
value = str((rule.get("match") or {}).get("canonical_value", "")).strip()
|
||||
value_rx = re.escape(value)
|
||||
boundary_before = r"(?<![A-Za-z0-9])"
|
||||
boundary_after = r"(?![A-Za-z0-9])"
|
||||
patterns = []
|
||||
|
||||
if rule_type == "normalized_identifier":
|
||||
if normalization.get("allow_bare_value", False):
|
||||
patterns.append(re.compile(rf"{boundary_before}({value_rx}){boundary_after}", flags | re.MULTILINE))
|
||||
prefixes = normalization.get("accepted_prefixes") or []
|
||||
separators = normalization.get("prefix_value_separators") or [" "]
|
||||
else:
|
||||
prefixes = (rule.get("match") or {}).get("context_prefixes") or []
|
||||
separators = (rule.get("match") or {}).get("context_separators") or [": ", ":"]
|
||||
|
||||
gap = r"\s*" if multiline else r"[ \t]*"
|
||||
for prefix in prefixes:
|
||||
prefix_rx = _literal_to_pattern(str(prefix), multiline)
|
||||
for separator in separators:
|
||||
separator_rx = _literal_to_pattern(str(separator), multiline)
|
||||
patterns.append(
|
||||
re.compile(
|
||||
rf"{boundary_before}{prefix_rx}{separator_rx}{gap}({value_rx}){boundary_after}",
|
||||
flags | re.MULTILINE,
|
||||
)
|
||||
)
|
||||
|
||||
return {
|
||||
"id": rule.get("id"),
|
||||
"type": rule_type,
|
||||
"kind": _placeholder_to_kind(rule.get("placeholder", "[MASK]")),
|
||||
"placeholder": rule.get("placeholder", "[MASK]"),
|
||||
"patterns": patterns,
|
||||
}
|
||||
|
||||
|
||||
def compile_active_admin_rules(data: dict[str, Any]) -> dict[str, Any]:
|
||||
compiled = {
|
||||
"force_mask_terms": [],
|
||||
"whitelist_phrases": [],
|
||||
"detection_rules": [],
|
||||
"active_rule_ids": [],
|
||||
}
|
||||
|
||||
for rule in data.get("rules", []) or []:
|
||||
if not isinstance(rule, dict):
|
||||
continue
|
||||
if rule.get("status") != "active":
|
||||
continue
|
||||
compiled["active_rule_ids"].append(rule.get("id"))
|
||||
rule_type = rule.get("type")
|
||||
action = rule.get("action")
|
||||
match = rule.get("match") or {}
|
||||
|
||||
if rule_type == "exact_term" and action == "mask":
|
||||
value = str(match.get("exact_value", "")).strip()
|
||||
if value:
|
||||
compiled["force_mask_terms"].append(value)
|
||||
elif rule_type == "preserve_phrase" and action == "preserve":
|
||||
value = str(match.get("exact_value", "")).strip()
|
||||
if value:
|
||||
compiled["whitelist_phrases"].append(value)
|
||||
elif rule_type in {"normalized_identifier", "contextual_identifier"} and action == "mask":
|
||||
if _is_non_empty_string(match.get("canonical_value")):
|
||||
compiled["detection_rules"].append(_compile_identifier_rule(rule))
|
||||
|
||||
compiled["force_mask_terms"] = _dedupe_keep_order(compiled["force_mask_terms"])
|
||||
compiled["whitelist_phrases"] = _dedupe_keep_order(compiled["whitelist_phrases"])
|
||||
return compiled
|
||||
20
ano/pdf_natif/pseudonymise/FC14.audit.jsonl
Normal file
20
ano/pdf_natif/pseudonymise/FC14.audit.jsonl
Normal file
@@ -0,0 +1,20 @@
|
||||
{"page": 0, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 0, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 1, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 1, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 2, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 2, "kind": "OGC_court", "original": "N° OGC : 14", "placeholder": "[OGC]", "bbox_hint": null}
|
||||
{"page": 2, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 3, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 3, "kind": "OGC_court", "original": "N° OGC : 14", "placeholder": "[OGC]", "bbox_hint": null}
|
||||
{"page": 3, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 0, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 0, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 1, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 1, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 2, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 2, "kind": "OGC_court", "original": "N° OGC : 14", "placeholder": "[OGC]", "bbox_hint": null}
|
||||
{"page": 2, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
{"page": 3, "kind": "FINESS", "original": "640780417", "placeholder": "[FINESS]", "bbox_hint": null}
|
||||
{"page": 3, "kind": "OGC_court", "original": "N° OGC : 14", "placeholder": "[OGC]", "bbox_hint": null}
|
||||
{"page": 3, "kind": "force_term", "original": "CENTRE HOSPITALIER COTE BASQUE", "placeholder": "[MASK]", "bbox_hint": null}
|
||||
348
ano/pdf_natif/pseudonymise/FC14.pseudonymise.txt
Normal file
348
ano/pdf_natif/pseudonymise/FC14.pseudonymise.txt
Normal file
@@ -0,0 +1,348 @@
|
||||
NNNN°°°° OOOOGGGGCCCC : ::: 11114444
|
||||
FICHE MEDICALE DE RECUEIL DU PRATICIEN CONSEIL (une fiche par RUM)
|
||||
Seul le recodage impactant la facturation est renseigné
|
||||
Etablissement : [MASK] FINESS : [FINESS] Date début contrôle : 12/05/2025
|
||||
N° champ : 1 Libellé champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Dossier manquant : 0 Dates du séjour : 09/05/2023 au 10/05/2023
|
||||
Données du
|
||||
séjour
|
||||
)sna(
|
||||
egA
|
||||
)sruoj(
|
||||
egA exeS
|
||||
.nred
|
||||
ialéD selgèr egA
|
||||
noitatseg
|
||||
sdioP
|
||||
eértne'd ed
|
||||
eéruD ruojés edoM
|
||||
eértne'd
|
||||
ecnanevorP
|
||||
edoM
|
||||
eitros
|
||||
ed
|
||||
noitanitseD secnaés
|
||||
bN
|
||||
MUR
|
||||
bN
|
||||
HXE
|
||||
j bN
|
||||
BXE
|
||||
epyT
|
||||
BXE
|
||||
j bN
|
||||
Etablissement 61 1 0 1 8 8 0 2 0 0 0
|
||||
Recodage 61 1 0 1 8 8 0 2 0 0 0
|
||||
Données du RUM Nature Nb
|
||||
Lits dédiés SP UM IGS II Durée RUM
|
||||
suppl. suppl.
|
||||
0
|
||||
N° RUM Etablissement : 1/2 0 29 C 0 0 0
|
||||
du 09/05/2023 au 09/05/2023
|
||||
0
|
||||
N° RUM Recodage : 1/2 0 29 C 0 0 0
|
||||
du 09/05/2023 au 09/05/2023
|
||||
Codage de l’Etablissement Recodage
|
||||
DP K851 PANCREATITE AIG. BIL. K801
|
||||
DR
|
||||
DAS
|
||||
Actes
|
||||
Rappel : un code CIM de DAS suivi d’un astérisque correspond à une CMA exclue par le DP
|
||||
GHM établissement : 07C131 GHS établissement : 2347 GHM après recodage : 07C141 GHS après recodage : 2351
|
||||
Praticien conseil Médecin DIM
|
||||
Recodage impactant la facturation : 1 Accord
|
||||
GHS injustifié : 0 SE FFM FSD Désaccord
|
||||
En fonction des DP/DR et actes retenus par le PC, seul le recodage d'une des CMA les plus élevées ayant une incidence sur la racine GHM
|
||||
ou sur la facturation des suppléments sera renseigné. Hors RSS injustifié avec actes externes, seuls les actes classants seront recodés
|
||||
|
||||
11114444
|
||||
N° OGC :
|
||||
FICHE MEDICALE DE RECUEIL DU PRATICIEN CONSEIL (une fiche par RUM)
|
||||
Seul le recodage impactant la facturation est renseigné
|
||||
Etablissement : [MASK] FINESS : [FINESS] Date début contrôle : 12/05/2025
|
||||
N° champ : 1 Libellé champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Dossier manquant : 0 Dates du séjour : 09/05/2023 au 10/05/2023
|
||||
Données du
|
||||
séjour
|
||||
)sna(
|
||||
egA
|
||||
)sruoj(
|
||||
egA exeS
|
||||
.nred
|
||||
ialéD selgèr egA
|
||||
noitatseg
|
||||
sdioP
|
||||
eértne'd ed
|
||||
eéruD ruojés edoM
|
||||
eértne'd
|
||||
ecnanevorP
|
||||
edoM
|
||||
eitros
|
||||
ed
|
||||
noitanitseD secnaés
|
||||
bN
|
||||
MUR
|
||||
bN
|
||||
HXE
|
||||
j bN
|
||||
BXE
|
||||
epyT
|
||||
BXE
|
||||
j bN
|
||||
Etablissement 61 1 0 1 8 8 0 2 0 0 0
|
||||
Recodage 61 1 0 1 8 8 0 2 0 0 0
|
||||
Données du RUM Nature Nb
|
||||
Lits dédiés SP UM IGS II Durée RUM
|
||||
suppl. suppl.
|
||||
1
|
||||
N° RUM Etablissement : 2/2 0 53 C 0 0 0
|
||||
du 09/05/2023 au 10/05/2023
|
||||
1
|
||||
N° RUM Recodage : 2/2 0 53 C 0 0 0
|
||||
du 09/05/2023 au 10/05/2023
|
||||
Codage de l’Etablissement Recodage
|
||||
DP K851 PANCREATITE AIG. BIL. K801
|
||||
DR
|
||||
DAS
|
||||
HMFC004 1 CHOLÉCYSTECTOMIE COELIO. HMFC004 1
|
||||
HMFC004 4 CHOLÉCYSTECTOMIE COELIO. HMFC004 4
|
||||
Actes
|
||||
Rappel : un code CIM de DAS suivi d’un astérisque correspond à une CMA exclue par le DP
|
||||
GHM établissement : 07C131 GHS établissement : 2347 GHM après recodage : 07C141 GHS après recodage : 2351
|
||||
Praticien conseil Médecin DIM
|
||||
Recodage impactant la facturation : 1 Accord
|
||||
GHS injustifié : 0 SE FFM FSD Désaccord
|
||||
En fonction des DP/DR et actes retenus par le PC, seul le recodage d'une des CMA les plus élevées ayant une incidence sur la racine GHM
|
||||
ou sur la facturation des suppléments sera renseigné. Hors RSS injustifié avec actes externes, seuls les actes classants seront recodés
|
||||
|
||||
FICHE MEDICALE DE CONCERTATION
|
||||
Etablissement : [MASK] FINESS : [FINESS] [OGC]
|
||||
N° Champ : 1 Libellé du champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Document couvert par le secret médical
|
||||
Ne peut pas être produit aux services administratifs de l’établissement et des organismes de sécurité sociale
|
||||
Nom du praticien-conseil : V VAILLENDET Nom du médecin du DIM :
|
||||
Homme de 61 ans
|
||||
Antécédent :
|
||||
Pancréatite aiguë d'origine indéterminée d'évolution favorable.
|
||||
Hospitalisation du 9 au 10/5/23
|
||||
Admis à distance de l’épisode de pancréatite pour une
|
||||
cholécystectomie par laparoscopie
|
||||
En peropératoire présence de calculs intra-vésiculaires => en
|
||||
faveur d'une origine lithiasique de cette PA.
|
||||
La cholangiographie peropératoire ne retrouvait pas de calcul
|
||||
dans la VBP.
|
||||
Codage DP :
|
||||
Cholécystectomie « à froid » suite à une pancréatite
|
||||
Le CRO mentionne une légère inflammation séquellaire de la
|
||||
pancréatite.
|
||||
Il n’y a donc pas de pancréatite aigüe sur ce séjour, c’est un
|
||||
antécédent
|
||||
Pas de notion de cholécystite aigue
|
||||
Codage retenu : K80.1 « Calcul de la vésicule biliaire avec une
|
||||
autre forme de cholécystite »
|
||||
Nb : 2 RUM
|
||||
Probable changement d’unité après chirurgie => même codage
|
||||
pour els 2 RUM
|
||||
Date de concertation :
|
||||
NOM et SIGNATURE du MEDECIN RESPONSABLE du CONTRÔLE NOM et SIGNATURE du MEDECIN du DIM
|
||||
Dr Gilles DE MONREDON Atteste avoir pris connaissance des éléments du dossier y compris
|
||||
ceux couverts par le secret médical et des arguments soutenus par
|
||||
les médecins contrôleurs et avoir eu l’opportunité d’en débattre
|
||||
contradictoirement
|
||||
NOM du ou des autres participants à la concertation
|
||||
NOM du ou des autres membres de l’équipe de contrôle ayant
|
||||
participé à la concertation
|
||||
|
||||
FICHE ADMINISTRATIVE DE CONCERTATION 1/2
|
||||
(à établir lors de la concertation avec le médecin du DIM)
|
||||
Etablissement : [MASK] FINESS : [FINESS] [OGC]
|
||||
N° Champ : 1 Libellé du champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Document susceptible d’être produit aux services administratifs de l’établissement et des organismes de sécurité sociale,
|
||||
n’inscrire aucun élément couvert par le secret médical.
|
||||
ARGUMENTAIRE DU MEDECIN CONTROLEUR
|
||||
142 : La facturation du GHS par l’établissement n’est pas conforme à l’article 1 de l’arrêté du 19 février 2015 modifié du fait d’un non-
|
||||
respect des règles de codage édictées dans l’annexe II de l’arrêté du 23 décembre 2016 modifié. En préalable, chapitre VI, paragraphe
|
||||
1.2 : « Les circonstances du diagnostic préalable n’importent pas (…) La situation de traitement est présente lorsque le diagnostic de
|
||||
l’affection est fait au moment de l’entrée du patient dans l’unité médicale et que l’admission a pour but le traitement de l’affection. »
|
||||
Le non-respect des règles porte sur le diagnostic principal (DP) codé par l’établissement dans le résumé d’unité médicale (RUM). Le DP
|
||||
n’est pas conforme aux règles de codage des diagnostics rappelées par l’annexe II, chapitre VI, paragraphe 1.2.2.1 : « Dans la situation
|
||||
de traitement unique chirurgical, le DP est en général la maladie opérée [Règle T3]. (…) Le diagnostic résultant de l’intervention peut
|
||||
être différent du diagnostic préopératoire (…). Le DP doit en effet être énoncé en connaissance de l’ensemble des informations
|
||||
acquises au cours du séjour. » Au vu des éléments présents dans le dossier du patient, alors que l’admission a été motivée par le
|
||||
traitement chirurgical d’une affection, l’établissement n’a pas retenu le code de cette affection en DP.
|
||||
|
||||
|
||||
Etablissement : [MASK] FINESS : [FINESS] Date début contrôle : 12/05/2025
|
||||
N° champ : 1 Libellé champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Données du
|
||||
séjour : )sna(
|
||||
egA )sruoj(
|
||||
egA exeS .nred
|
||||
selgèr
|
||||
ialéD noitatseg
|
||||
egA eértne'd
|
||||
sdioP ed
|
||||
eéruD ruojés eértne'd
|
||||
edoM ecnanevorP eitros
|
||||
edoM
|
||||
ed noitanitseD secnaés
|
||||
bN MUR
|
||||
bN HXE
|
||||
j
|
||||
bN BXE
|
||||
epyT BXE
|
||||
j
|
||||
bN
|
||||
Données du
|
||||
séjour
|
||||
Etablissement : 61 1 0 1 8 8 0 2 0 0 0
|
||||
Recodage : 61 1 0 1 8 8 0 2 0 0 0
|
||||
Données du RUM : Lits dédiés SP UM IGS II Durée RUM Nature
|
||||
suppl. Nb
|
||||
suppl.
|
||||
N° RUM Etablissement : 1/2 0 29 C 0 0 0 0
|
||||
du 09/05/2023 au 09/05/2023
|
||||
N° RUM Recodage : 1/2 0 29 C 0 0 0 0
|
||||
du 09/05/2023 au 09/05/2023
|
||||
Codage de l’Etablissement : Recodage
|
||||
DP : K851 PANCREATITE AIG. BIL. K801
|
||||
DR
|
||||
DAS
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Actes
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
GHM établissement : 07C131 GHS établissement : 2347 GHM après recodage : 07C141 GHS après recodage : 2351
|
||||
Praticien conseil : Médecin DIM
|
||||
Recodage impactant la facturation : 1
|
||||
GHS injustifié : 0 SE FFM FSD
|
||||
|
||||
Etablissement : [MASK] FINESS : [FINESS] Date début contrôle : 12/05/2025
|
||||
N° champ : 1 Libellé champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Données du
|
||||
séjour : )sna(
|
||||
egA )sruoj(
|
||||
egA exeS .nred
|
||||
selgèr
|
||||
ialéD noitatseg
|
||||
egA eértne'd
|
||||
sdioP ed
|
||||
eéruD ruojés eértne'd
|
||||
edoM ecnanevorP eitros
|
||||
edoM
|
||||
ed noitanitseD secnaés
|
||||
bN MUR
|
||||
bN HXE
|
||||
j
|
||||
bN BXE
|
||||
epyT BXE
|
||||
j
|
||||
bN
|
||||
Données du
|
||||
séjour
|
||||
Etablissement : 61 1 0 1 8 8 0 2 0 0 0
|
||||
Recodage : 61 1 0 1 8 8 0 2 0 0 0
|
||||
Données du RUM : Lits dédiés SP UM IGS II Durée RUM Nature
|
||||
suppl. Nb
|
||||
suppl.
|
||||
N° RUM Etablissement : 2/2 0 53 C 0 1 0 0
|
||||
du 09/05/2023 au 10/05/2023
|
||||
N° RUM Recodage : 2/2 0 53 C 0 1 0 0
|
||||
du 09/05/2023 au 10/05/2023
|
||||
Codage de l’Etablissement : Recodage
|
||||
DP : K851 PANCREATITE AIG. BIL. K801
|
||||
DR
|
||||
DAS
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Actes : HMFC004 1 CHOLÉCYSTECTOMIE COELIO. HMFC004 1
|
||||
HMFC004 : 4 CHOLÉCYSTECTOMIE COELIO. HMFC004 4
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
GHM établissement : 07C131 GHS établissement : 2347 GHM après recodage : 07C141 GHS après recodage : 2351
|
||||
Praticien conseil : Médecin DIM
|
||||
Recodage impactant la facturation : 1
|
||||
GHS injustifié : 0 SE FFM FSD
|
||||
|
||||
Etablissement : [MASK] FINESS : [FINESS] [OGC]
|
||||
N° Champ : 1 Libellé du champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Document couvert par le secret médical
|
||||
Ne peut pas être produit aux services administratifs de l’établissement et des organismes de sécurité sociale
|
||||
Nom du praticien-conseil : V VAILLENDET Nom du médecin du DIM :
|
||||
Homme de 61 ans
|
||||
Antécédent : Pancréatite aiguë d'origine indéterminée d'évolution favorable.
|
||||
Hospitalisation du 9 au 10/5/23
|
||||
Admis à distance de l’épisode de pancréatite pour une
|
||||
cholécystectomie par laparoscopie
|
||||
En peropératoire présence de calculs intra-vésiculaires => en
|
||||
faveur d'une origine lithiasique de cette PA.
|
||||
La cholangiographie peropératoire ne retrouvait pas de calcul
|
||||
dans la VBP.
|
||||
Codage DP :
|
||||
Cholécystectomie « à froid » suite à une pancréatite
|
||||
Le CRO mentionne une légère inflammation séquellaire de la
|
||||
pancréatite.
|
||||
Il n’y a donc pas de pancréatite aigüe sur ce séjour, c’est un
|
||||
antécédent
|
||||
Pas de notion de cholécystite aigue
|
||||
Codage retenu : K80.1 « Calcul de la vésicule biliaire avec une
|
||||
autre forme de cholécystite »
|
||||
Nb : 2 RUM
|
||||
Probable changement d’unité après chirurgie => même codage
|
||||
pour els 2 RUM
|
||||
NOM et SIGNATURE du MEDECIN RESPONSABLE du CONTRÔLE
|
||||
Dr Gilles DE MONREDON
|
||||
NOM du ou des autres membres de l’équipe de contrôle ayant
|
||||
participé à la concertation : NOM et SIGNATURE du MEDECIN du DIM
|
||||
Atteste avoir pris connaissance des éléments du dossier y compris
|
||||
ceux couverts par le secret médical et des arguments soutenus par
|
||||
les médecins contrôleurs et avoir eu l’opportunité d’en débattre
|
||||
contradictoirement
|
||||
NOM du ou des autres participants à la concertation
|
||||
|
||||
Etablissement : [MASK] FINESS : [FINESS] [OGC]
|
||||
N° Champ : 1 Libellé du champ de contrôle : Séjours correspondant à la racine 07C13
|
||||
Document susceptible d’être produit aux services administratifs de l’établissement et des organismes de sécurité sociale,
|
||||
n’inscrire aucun élément couvert par le secret médical.
|
||||
ARGUMENTAIRE DU MEDECIN CONTROLEUR
|
||||
142 : La facturation du GHS par l’établissement n’est pas conforme à l’article 1 de l’arrêté du 19 février 2015 modifié du fait d’un non-
|
||||
respect des règles de codage édictées dans l’annexe II de l’arrêté du 23 décembre 2016 modifié. En préalable, chapitre VI, paragraphe
|
||||
1.2 : « Les circonstances du diagnostic préalable n’importent pas (…) La situation de traitement est présente lorsque le diagnostic de
|
||||
l’affection est fait au moment de l’entrée du patient dans l’unité médicale et que l’admission a pour but le traitement de l’affection. »
|
||||
Le non-respect des règles porte sur le diagnostic principal (DP) codé par l’établissement dans le résumé d’unité médicale (RUM). Le DP
|
||||
n’est pas conforme aux règles de codage des diagnostics rappelées par l’annexe II, chapitre VI, paragraphe 1.2.2.1 : « Dans la situation
|
||||
de traitement unique chirurgical, le DP est en général la maladie opérée [Règle T3]. (…) Le diagnostic résultant de l’intervention peut
|
||||
être différent du diagnostic préopératoire (…). Le DP doit en effet être énoncé en connaissance de l’ensemble des informations
|
||||
acquises au cours du séjour. » Au vu des éléments présents dans le dossier du patient, alors que l’admission a été motivée par le
|
||||
traitement chirurgical d’une affection, l’établissement n’a pas retenu le code de cette affection en DP.
|
||||
|
||||
|
||||
| ||||