Spaces:
Sleeping
Sleeping
| """Pure deterministic Scrub Key reinsert helpers for SolidPrivacy Scrub. | |
| These helpers prepare the v13.3 local reinsert workflow. They deliberately avoid | |
| Streamlit UI integration, AI calls, cloud processing, document export changes, | |
| file-system persistence and any other side effects. | |
| """ | |
| from __future__ import annotations | |
| import re | |
| from collections import Counter | |
| from collections.abc import Mapping | |
| from typing import Any | |
| from scrub_key import validate_scrub_key | |
| PLACEHOLDER_PATTERN = re.compile(r"\[[A-Z][A-Z0-9_:-]*_[0-9]+\]") | |
| def _text(value: Any) -> str: | |
| return str(value or "") | |
| def _normalised_text(value: Any) -> str: | |
| return str(value or "").strip() | |
| def _items(scrub_key: Any) -> list[Mapping[str, Any]]: | |
| if not isinstance(scrub_key, Mapping): | |
| return [] | |
| raw_items = scrub_key.get("items") | |
| if not isinstance(raw_items, list): | |
| return [] | |
| return [item for item in raw_items if isinstance(item, Mapping)] | |
| def _unique_sorted(values: list[str] | set[str]) -> list[str]: | |
| return sorted({value for value in values if value}) | |
| def detect_placeholders(text: Any) -> list[str]: | |
| """Return unique placeholder-looking tokens found in text. | |
| This is intentionally conservative and aimed at Scrub placeholders such as | |
| ``[PERSOON_1]`` or ``[ZAAKNUMMER_1]``. It does not attempt to parse arbitrary | |
| bracketed text. | |
| """ | |
| return _unique_sorted(PLACEHOLDER_PATTERN.findall(_text(text))) | |
| def build_reinsert_mapping(scrub_key: Any) -> dict[str, Any]: | |
| """Build a deterministic placeholder-to-original mapping from a Scrub Key. | |
| Invalid Scrub Keys return validation issues and an empty mapping. Items with | |
| ``include_state`` other than ``included`` are ignored. Duplicate placeholders | |
| are reported and excluded from the mapping to avoid ambiguous reinsertion. | |
| """ | |
| validation_issues = validate_scrub_key(scrub_key) | |
| items = _items(scrub_key) | |
| included_items = [item for item in items if item.get("include_state") == "included"] | |
| excluded_items = [item for item in items if item.get("include_state") != "included"] | |
| if validation_issues: | |
| return { | |
| "mapping": {}, | |
| "item_count": len(items), | |
| "active_item_count": 0, | |
| "excluded_item_count": len(excluded_items), | |
| "duplicate_placeholders": [], | |
| "validation_issues": validation_issues, | |
| } | |
| placeholders = [_normalised_text(item.get("placeholder")) for item in included_items] | |
| duplicate_placeholders = _unique_sorted( | |
| [placeholder for placeholder, count in Counter(placeholders).items() if count > 1] | |
| ) | |
| duplicate_set = set(duplicate_placeholders) | |
| mapping: dict[str, str] = {} | |
| for item in included_items: | |
| placeholder = _normalised_text(item.get("placeholder")) | |
| original_value = _text(item.get("original_value")) | |
| if not placeholder or placeholder in duplicate_set: | |
| continue | |
| mapping[placeholder] = original_value | |
| return { | |
| "mapping": mapping, | |
| "item_count": len(items), | |
| "active_item_count": len(included_items), | |
| "excluded_item_count": len(excluded_items), | |
| "duplicate_placeholders": duplicate_placeholders, | |
| "validation_issues": [], | |
| } | |
| def reinsert_from_scrub_key(text: Any, scrub_key: Any) -> dict[str, Any]: | |
| """Reinsert original values into scrubbed text using a valid Scrub Key. | |
| The function is deterministic and side-effect free. It returns both the | |
| reinserted text and an audit summary. Invalid keys do not modify the text; | |
| validation issues are returned clearly. | |
| """ | |
| original_text = _text(text) | |
| mapping_result = build_reinsert_mapping(scrub_key) | |
| mapping: dict[str, str] = dict(mapping_result.get("mapping", {})) | |
| validation_issues = list(mapping_result.get("validation_issues", [])) | |
| duplicate_placeholders = list(mapping_result.get("duplicate_placeholders", [])) | |
| placeholders_in_text = detect_placeholders(original_text) | |
| known_placeholders = set(mapping.keys()) | |
| if validation_issues: | |
| return { | |
| "text": original_text, | |
| "replacement_count": 0, | |
| "item_count": mapping_result.get("item_count", 0), | |
| "active_item_count": mapping_result.get("active_item_count", 0), | |
| "excluded_item_count": mapping_result.get("excluded_item_count", 0), | |
| "placeholders_not_found": [], | |
| "unknown_placeholders": placeholders_in_text, | |
| "duplicate_placeholders": duplicate_placeholders, | |
| "validation_issues": validation_issues, | |
| "reinserted": False, | |
| "local_only": True, | |
| "ai_processing": False, | |
| "cloud_processing": False, | |
| } | |
| reinserted_text = original_text | |
| replacement_count = 0 | |
| placeholders_not_found: list[str] = [] | |
| for placeholder, original_value in mapping.items(): | |
| occurrences = reinserted_text.count(placeholder) | |
| if occurrences == 0: | |
| placeholders_not_found.append(placeholder) | |
| continue | |
| reinserted_text = reinserted_text.replace(placeholder, original_value) | |
| replacement_count += occurrences | |
| ambiguous_placeholders = set(duplicate_placeholders) | |
| unknown_placeholders = _unique_sorted( | |
| set(placeholders_in_text) - known_placeholders - ambiguous_placeholders | |
| ) | |
| return { | |
| "text": reinserted_text, | |
| "replacement_count": replacement_count, | |
| "item_count": mapping_result.get("item_count", 0), | |
| "active_item_count": mapping_result.get("active_item_count", 0), | |
| "excluded_item_count": mapping_result.get("excluded_item_count", 0), | |
| "placeholders_not_found": _unique_sorted(placeholders_not_found), | |
| "unknown_placeholders": unknown_placeholders, | |
| "duplicate_placeholders": duplicate_placeholders, | |
| "validation_issues": [], | |
| "reinserted": replacement_count > 0, | |
| "local_only": True, | |
| "ai_processing": False, | |
| "cloud_processing": False, | |
| } | |