Spaces:
Sleeping
Sleeping
File size: 10,006 Bytes
ef85832 debca42 ef85832 9481c68 ef85832 bca2667 ef85832 bca2667 ef85832 9481c68 ef85832 9481c68 bca2667 9481c68 bca2667 9481c68 bca2667 9481c68 bca2667 9481c68 bca2667 9481c68 bca2667 9481c68 bca2667 9481c68 bca2667 9481c68 ef85832 debca42 30ba196 ef85832 debca42 30ba196 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | from __future__ import annotations
from pathlib import Path
from replacement_decision import (
VALID_REVIEW_STATES,
VALID_SCOPES,
build_replacement_audit,
build_replacement_decision,
matching_occurrence_ids,
)
REPO_ROOT = Path(__file__).resolve().parents[1]
UI_PLAN = REPO_ROOT / "REPLACE_LOGIC_UI_PLAN.md"
THIS_TEST = Path(__file__)
UI_ACTION_TO_STATE = {
"Vervangen": "accepted",
"Vervanging aanpassen": "edited",
"Zichtbaar houden": "ignored",
"Handmatig gemiste waarde toevoegen": "manual_added",
"Als context behouden": "preserve_context",
"Later controleren": "unresolved",
}
UI_SCOPE_TO_HELPER_SCOPE = {
"Alleen deze plek": "this_occurrence",
"Alle exact dezelfde waarden": "all_exact",
"Alle genormaliseerde gelijke waarden": "all_normalized",
}
ALLOWED_VIEW_ONLY_SESSION_KEYS = {
"replacement_decision_selected_occurrence_id",
"replacement_decision_preview_state",
"replacement_decision_preview_scope",
"replacement_decision_preview_text",
"replacement_decision_panel_expanded",
}
FORBIDDEN_MUTATION_TARGETS = {
"replacement_editor",
"edited_replacements_df",
"review table rows",
"export state",
"scrub key state",
"reinsert state",
}
def _ui_plan_text() -> str:
return UI_PLAN.read_text(encoding="utf-8")
def _contract_text() -> str:
return _ui_plan_text().replace("`", "").lower()
def test_ui_action_contract_maps_only_to_supported_helper_states():
assert set(UI_ACTION_TO_STATE.values()).issubset(VALID_REVIEW_STATES)
for label, state in UI_ACTION_TO_STATE.items():
decision = build_replacement_decision(
occurrence_id=f"occ-{state}",
source_text="SYNTHETIC-WAARDE",
entity_type="SYNTHETIC_ENTITY",
display_label=label,
suggested_replacement="[SYNTHETIC_1]",
final_replacement="[SYNTHETIC_EDITED]" if state == "edited" else None,
review_state=state,
scope="this_occurrence",
)
assert decision.review_state == state
if state in {"ignored", "preserve_context", "unresolved"}:
assert decision.creates_mapping is False
assert decision.replacement_value is None
else:
assert decision.creates_mapping is True
assert decision.replacement_value is not None
def test_ui_scope_contract_maps_only_to_supported_helper_scopes_and_matching_rules():
assert set(UI_SCOPE_TO_HELPER_SCOPE.values()).issubset(VALID_SCOPES)
occurrences = [
{"occurrence_id": "one", "source_text": "SYNTHETIC VALUE"},
{"occurrence_id": "two", "source_text": "SYNTHETIC VALUE"},
{"occurrence_id": "three", "source_text": "synthetic value"},
{"occurrence_id": "four", "source_text": "OTHER SYNTHETIC VALUE"},
]
exact_decision = build_replacement_decision(
occurrence_id="one",
source_text="SYNTHETIC VALUE",
entity_type="SYNTHETIC_ENTITY",
display_label="Vervangen",
suggested_replacement="[SYNTHETIC_1]",
review_state="accepted",
scope="all_exact",
)
normalized_decision = build_replacement_decision(
occurrence_id="one",
source_text="SYNTHETIC VALUE",
entity_type="SYNTHETIC_ENTITY",
display_label="Vervangen",
suggested_replacement="[SYNTHETIC_1]",
review_state="accepted",
scope="all_normalized",
)
assert matching_occurrence_ids(exact_decision, occurrences) == ["one", "two"]
assert matching_occurrence_ids(normalized_decision, occurrences) == ["one", "two", "three"]
def test_ui_plan_contains_required_labels_states_scopes_and_confirmation_language():
text = _ui_plan_text()
lowered = text.lower()
for label in UI_ACTION_TO_STATE:
assert label in text
for state in UI_ACTION_TO_STATE.values():
assert f"`{state}`" in text
for label in UI_SCOPE_TO_HELPER_SCOPE:
assert label in text
for scope in UI_SCOPE_TO_HELPER_SCOPE.values():
assert f"`{scope}`" in text
assert "affected count" in lowered
assert "stronger confirmation" in lowered
assert "no fuzzy matching or guessed intent" in lowered
assert "default scope is `this_occurrence`" in lowered
def test_audit_contract_is_report_only_and_export_readiness_is_advisory():
decisions = [
build_replacement_decision(
occurrence_id="accepted-1",
source_text="SYNTHETIC-A",
entity_type="SYNTHETIC_ENTITY",
display_label="Vervangen",
suggested_replacement="[SYNTHETIC_A]",
review_state="accepted",
),
build_replacement_decision(
occurrence_id="later-1",
source_text="SYNTHETIC-B",
entity_type="SYNTHETIC_ENTITY",
display_label="Later controleren",
suggested_replacement="[SYNTHETIC_B]",
review_state="unresolved",
risk_flags=("synthetic_high_risk",),
),
]
audit = build_replacement_audit(decisions)
assert audit["report_only"] is True
assert audit["export_blocking"] is False
assert audit["export_readiness"] == "high_risk_unresolved"
assert audit["unresolved_items"] == ["later-1"]
assert audit["mapping_candidates"] == ["accepted-1"]
assert audit["risk_flags"] == ["synthetic_high_risk"]
def test_ui_contract_plan_preserves_boundaries_and_does_not_approve_ui_implementation():
text = _contract_text()
for required in [
"does not implement ui",
"audit panel is report-only",
"must not block export",
"does not approve export blocking",
"must not change scrub key behavior",
"highlight preview remains read-only and non-authoritative",
"no click-to-mark implementation is approved",
"this plan does not change",
"presidio_streamlit.py",
"serial_review_panel_ui.py",
"fix_streamlit_nested_expanders.py",
"review table behavior",
"export/download behavior",
"scrub key behavior",
"reinsert behavior",
"helper runtime behavior",
"dependencies",
"cloud processing",
"real-data fixtures",
]:
assert required in text
def test_staged_vs_applied_state_contract_is_explicit_and_non_mutating():
text = _contract_text()
for required in [
"staged decision preview only",
"staged decision state is not applied state",
"existing review table remains source of truth and fallback",
"no review table mutation",
"no replacement mutation",
"no automatic replacement",
"not write those decisions back",
"separate package with explicit coordinator approval",
]:
assert required in text
def test_session_state_contract_allows_only_view_only_keys_and_blocks_mutation_targets():
text = _ui_plan_text()
contract = _contract_text()
assert "Allowed view-only session keys" in text
assert "temporary UI selection and preview state" in text
assert "must not be treated as applied replacement state" in text
for key in ALLOWED_VIEW_ONLY_SESSION_KEYS:
assert key in text
for target in FORBIDDEN_MUTATION_TARGETS:
assert f"no mutation of {target}" in contract
def test_scrub_key_mapping_indicators_are_advisory_only():
text = _contract_text()
decisions = [
build_replacement_decision(
occurrence_id="mapping-1",
source_text="SYNTHETIC-A",
entity_type="SYNTHETIC_ENTITY",
display_label="Vervangen",
suggested_replacement="[SYNTHETIC_A]",
review_state="accepted",
)
]
audit = build_replacement_audit(decisions)
assert decisions[0].creates_mapping is True
assert audit["mapping_candidates"] == ["mapping-1"]
assert "creates_mapping is advisory only" in text
assert "does not authorize a scrub key write" in text
assert "mapping_candidates are advisory only" in text
assert "do not authorize scrub key persistence" in text
assert "writing mappings directly from a new ui panel" in text
assert "scrub key schema" in text
def test_export_download_and_reinsert_boundaries_are_explicit():
text = _contract_text()
for required in [
"export_readiness is advisory only",
"must not call export/download functions",
"change export eligibility",
"disable export buttons",
"alter download payloads",
"no export/download calls",
"no reinsert behavior change",
"no scrub key writes",
]:
assert required in text
def test_no_fuzzy_matching_no_guessed_intent_and_no_automatic_replacement_contract():
text = _contract_text()
assert "no fuzzy matching or guessed intent is allowed" in text
assert "no automatic replacement" in text
assert "auto-repairing duplicate placeholders" in text
def test_all_normalized_not_available_as_first_mutating_scope_without_approval():
text = _contract_text()
assert "all_normalized" in text
assert "not available as a first mutating ui scope without separate explicit coordinator approval" in text
assert "disabled/advisory only" in text
def test_future_ui_requires_separate_explicit_coordinator_approval():
text = _contract_text()
assert "only after separate explicit coordinator approval" in text
assert "do not start implementation automatically" in text
assert "small staged/read-only companion panel" in text
def test_contract_tests_use_synthetic_values_only():
rendered = THIS_TEST.read_text(encoding="utf-8") + _ui_plan_text()
forbidden_real_data_examples = [
"Jan " + "Jansen",
"Piet " + "de " + "Vries",
"123" + "456" + "782",
]
assert "SYNTHETIC" in rendered
for forbidden in forbidden_real_data_examples:
assert forbidden not in rendered
|