solidprivacy commited on
Commit
1f00f8d
·
1 Parent(s): b4f3ef0

test(ui): add executable Premium export transition harness

Browse files
tests/streamlit_apps/premium_export_gate_flow_app.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import streamlit as st
4
+
5
+ from premium_app_shell import standard_operator_is_supported
6
+ from premium_core_flow_state import PresentationMode, Stage, Workflow
7
+ from premium_streamlit_export_gate import render_export_readiness_gate
8
+ from premium_streamlit_state import (
9
+ ANALYSIS_RESULTS_KEY,
10
+ ALLOW_LIST_KEY,
11
+ ANALYZER_PARAMS_KEY,
12
+ DENY_LIST_KEY,
13
+ ENTITIES_KEY,
14
+ OPERATOR_VALUE_KEY,
15
+ PROFILE_LABEL_KEY,
16
+ REVIEW_ROWS_KEY,
17
+ SOURCE_TEXT_KEY,
18
+ THRESHOLD_KEY,
19
+ cache_analysis_results,
20
+ cache_review_rows,
21
+ get_cached_analysis_results,
22
+ get_cached_review_rows,
23
+ get_core_flow_state,
24
+ mark_processing_complete,
25
+ mark_review_complete,
26
+ persist_processing_settings,
27
+ processing_generation,
28
+ select_stage,
29
+ stored_source_text,
30
+ synchronize_processing_generation,
31
+ synchronize_shell_choices,
32
+ )
33
+
34
+
35
+ SOURCE = "Cliënt Noor de Vries woont aan Voorbeeldstraat 10."
36
+ PROFILE = "Dutch Legal Strict"
37
+ ENTITIES = ["PERSON"]
38
+ ALLOW_LIST: list[str] = []
39
+ DENY_LIST: list[str] = []
40
+ ANALYZER_PARAMS = ("flair", "flair/ner-english-large", "", "")
41
+ INITIAL_ROWS = [
42
+ {
43
+ "include": True,
44
+ "remember": False,
45
+ "find": "Noor de Vries",
46
+ "replace_with": "[PERSOON_TEST_01]",
47
+ "entity_type": "PERSON",
48
+ "source": "auto",
49
+ }
50
+ ]
51
+
52
+
53
+ def current_operator() -> str:
54
+ return str(st.session_state.get(OPERATOR_VALUE_KEY, "replace"))
55
+
56
+
57
+ def current_threshold() -> float:
58
+ return float(st.session_state.get(THRESHOLD_KEY, 0.40))
59
+
60
+
61
+ def generation_for(*, text: str, operator: str, threshold: float) -> str:
62
+ return processing_generation(
63
+ text=text,
64
+ profile=PROFILE,
65
+ operator=operator,
66
+ threshold=threshold,
67
+ entities=ENTITIES,
68
+ allow_list=ALLOW_LIST,
69
+ deny_list=DENY_LIST,
70
+ analyzer_params=ANALYZER_PARAMS,
71
+ )
72
+
73
+
74
+ def initialize_completed_flow() -> None:
75
+ if st.session_state.get("_premium_export_gate_fixture_initialized"):
76
+ return
77
+ st.session_state[SOURCE_TEXT_KEY] = SOURCE
78
+ persist_processing_settings(
79
+ st.session_state,
80
+ profile_label=PROFILE,
81
+ operator="replace",
82
+ threshold=0.40,
83
+ entities=ENTITIES,
84
+ allow_list=ALLOW_LIST,
85
+ deny_list=DENY_LIST,
86
+ analyzer_params=ANALYZER_PARAMS,
87
+ )
88
+ generation = generation_for(text=SOURCE, operator="replace", threshold=0.40)
89
+ synchronize_processing_generation(st.session_state, generation)
90
+ cache_analysis_results(st.session_state, generation, [{"entity_type": "PERSON", "text": "Noor de Vries"}])
91
+ cache_review_rows(st.session_state, generation, INITIAL_ROWS)
92
+ mark_processing_complete(st.session_state, generation)
93
+ mark_review_complete(st.session_state)
94
+ st.session_state["_premium_export_gate_fixture_initialized"] = True
95
+
96
+
97
+ initialize_completed_flow()
98
+
99
+ presentation_choice = st.radio(
100
+ "Weergave",
101
+ ["Standaard", "Expert"],
102
+ horizontal=True,
103
+ key="fixture_presentation_mode",
104
+ )
105
+ presentation_mode = (
106
+ PresentationMode.STANDARD if presentation_choice == "Standaard" else PresentationMode.EXPERT
107
+ )
108
+ synchronize_shell_choices(
109
+ st.session_state,
110
+ workflow=Workflow.ANONYMIZE,
111
+ presentation_mode=presentation_mode,
112
+ )
113
+
114
+ source_text = stored_source_text(st.session_state)
115
+ operator = current_operator()
116
+ threshold = current_threshold()
117
+ current_generation = generation_for(text=source_text, operator=operator, threshold=threshold)
118
+
119
+ if presentation_mode is PresentationMode.EXPERT:
120
+ selected_operator = st.selectbox(
121
+ "Expert operator",
122
+ ["replace", "highlight", "synthesize"],
123
+ index=["replace", "highlight", "synthesize"].index(operator),
124
+ key="fixture_expert_operator",
125
+ )
126
+ if selected_operator != operator:
127
+ persist_processing_settings(
128
+ st.session_state,
129
+ profile_label=PROFILE,
130
+ operator=selected_operator,
131
+ threshold=threshold,
132
+ entities=ENTITIES,
133
+ allow_list=ALLOW_LIST,
134
+ deny_list=DENY_LIST,
135
+ analyzer_params=ANALYZER_PARAMS,
136
+ )
137
+ synchronize_processing_generation(
138
+ st.session_state,
139
+ generation_for(text=source_text, operator=selected_operator, threshold=threshold),
140
+ )
141
+ st.rerun()
142
+
143
+ current_rows_for_edit = get_cached_review_rows(st.session_state, current_generation)
144
+ if current_rows_for_edit is not None and st.button("Wijzig reviewbeslissing", key="fixture_edit_review"):
145
+ edited_rows = [dict(row) for row in current_rows_for_edit]
146
+ edited_rows[0]["replace_with"] = "[PERSOON_TEST_99]"
147
+ cache_review_rows(st.session_state, current_generation, edited_rows)
148
+ select_stage(st.session_state, Stage.REVIEW)
149
+ st.rerun()
150
+
151
+ if st.button("Wijzig verwerkingsinstelling", key="fixture_change_processing"):
152
+ new_threshold = 0.55 if threshold != 0.55 else 0.40
153
+ persist_processing_settings(
154
+ st.session_state,
155
+ profile_label=PROFILE,
156
+ operator=operator,
157
+ threshold=new_threshold,
158
+ entities=ENTITIES,
159
+ allow_list=ALLOW_LIST,
160
+ deny_list=DENY_LIST,
161
+ analyzer_params=ANALYZER_PARAMS,
162
+ )
163
+ synchronize_processing_generation(
164
+ st.session_state,
165
+ generation_for(text=source_text, operator=operator, threshold=new_threshold),
166
+ )
167
+ st.rerun()
168
+ else:
169
+ if not standard_operator_is_supported(operator):
170
+ st.warning("EXPERT_OPERATOR_REQUIRED")
171
+
172
+ # Recalculate after any state transition and expose deterministic executable evidence.
173
+ source_text = stored_source_text(st.session_state)
174
+ operator = current_operator()
175
+ threshold = current_threshold()
176
+ current_generation = generation_for(text=source_text, operator=operator, threshold=threshold)
177
+ state = get_core_flow_state(st.session_state)
178
+ analysis_rows = get_cached_analysis_results(st.session_state, current_generation)
179
+ review_rows = get_cached_review_rows(st.session_state, current_generation)
180
+
181
+ st.markdown(f"STATE_SOURCE: `{source_text}`")
182
+ st.markdown(f"STATE_GENERATION: `{current_generation}`")
183
+ st.markdown(f"STATE_OPERATOR: `{operator}`")
184
+ st.markdown(f"STATE_ANALYSIS_CACHE: `{'current' if analysis_rows is not None else 'missing'}`")
185
+ st.markdown(f"STATE_REVIEW_CACHE: `{'current' if review_rows is not None else 'missing'}`")
186
+ st.markdown(
187
+ f"STATE_REVIEW_ROW: `{review_rows[0].get('replace_with') if review_rows else 'missing'}`"
188
+ )
189
+ st.markdown(
190
+ f"STATE_LINEAGE: `processed={'current' if state.processed_generation == current_generation else 'stale'};"
191
+ f"reviewed={'current' if state.reviewed_generation == current_generation else 'stale'};"
192
+ f"export={'current' if state.export_generation == current_generation else 'stale'}`"
193
+ )
194
+
195
+ export_ready = render_export_readiness_gate(
196
+ st.session_state,
197
+ is_expert=presentation_mode is PresentationMode.EXPERT,
198
+ generation=current_generation,
199
+ reviewed_rows=review_rows or [],
200
+ )
201
+ if export_ready:
202
+ st.download_button(
203
+ "Download opgeschoonde tekst (.txt)",
204
+ data=b"synthetic-clean-output",
205
+ file_name="opgeschoonde_tekst.txt",
206
+ mime="text/plain",
207
+ key="fixture_download",
208
+ )
209
+ st.success("EXPORT_AVAILABLE")
210
+ else:
211
+ st.info("EXPORT_BLOCKED")