File size: 17,555 Bytes
d115ca5
1d86888
d115ca5
 
 
 
 
 
 
a42206d
d86e9b2
fb11e5b
43be343
c60b9b4
1d86888
 
 
 
 
 
 
d115ca5
 
 
 
 
 
 
 
 
1d86888
 
 
 
 
 
 
d115ca5
1d86888
 
 
 
 
 
d115ca5
 
43be343
d115ca5
 
 
 
a42206d
d86e9b2
fb11e5b
 
 
 
 
 
 
 
43be343
 
a42206d
 
43be343
a42206d
 
d115ca5
a42206d
d86e9b2
fb11e5b
 
 
 
 
 
 
 
43be343
 
d86e9b2
 
 
 
a42206d
d86e9b2
fb11e5b
 
 
 
 
 
 
 
43be343
 
fb11e5b
 
 
 
d86e9b2
fb11e5b
 
 
 
 
 
 
 
43be343
 
 
 
 
 
 
 
 
 
 
 
 
fb11e5b
43be343
 
 
 
 
 
 
 
 
d115ca5
 
c60b9b4
 
 
 
 
 
 
 
d115ca5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fb11e5b
d115ca5
 
 
 
 
 
fb11e5b
 
 
 
 
 
d115ca5
 
 
 
 
 
 
 
 
a42206d
 
 
 
fb11e5b
a42206d
 
 
 
 
 
 
 
 
d86e9b2
fb11e5b
d86e9b2
 
 
 
 
d115ca5
 
 
 
fb11e5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d115ca5
 
 
 
 
 
 
 
 
 
 
 
 
 
d86e9b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d115ca5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43be343
 
 
 
 
 
 
 
c60b9b4
 
 
 
 
43be343
 
 
c60b9b4
 
fb11e5b
 
 
43be343
fb11e5b
43be343
 
 
 
 
fb11e5b
 
43be343
fb11e5b
 
c60b9b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d115ca5
 
 
 
 
 
 
 
 
 
 
 
 
 
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
"""Startup compatibility patches for the Streamlit app.

This script runs before Streamlit starts in the Docker container. It applies small
idempotent source patches that keep the Hugging Face Space usable while larger UI
refactors are being staged.

Patches currently applied:
- v9: remove nested Streamlit expander usage for Woordenlijsten.
- v12.1: add a user-facing review status model to the replacement table.
- v12.2: add safe review-focus filters without changing export semantics.
- v12.3: simplify the main review table and move audit details to a technical view.
- v12.4: add clear review guidance text around the review workflow.
- v12.5: show a final review summary before download/export.
- v12.6: show advisory export sanity warnings before download/export.
"""

from pathlib import Path

APP_FILE = Path(__file__).with_name("presidio_streamlit.py")
text = APP_FILE.read_text(encoding="utf-8")


def replace_once(source: str, old: str, new: str) -> str:
    if old in source and new not in source:
        return source.replace(old, new, 1)
    return source


# v9 compatibility: Streamlit does not allow an expander inside another expander.
nested_old = '''    st_deny_allow_expander = st.expander("Woordenlijsten", expanded=False)
    with st_deny_allow_expander:
        st_allow_list = st_tags(label="Niet vervangen", text="Voer woord in en druk op Enter.")
        st.caption("Woorden in deze lijst worden niet als gevoelig gegeven behandeld.")
        st_deny_list = st_tags(label="Extra controleren", text="Voer woord in en druk op Enter.")
        st.caption("Woorden in deze lijst krijgen extra aandacht bij de herkenning.")
'''

nested_new = '''    st.markdown("**Woordenlijsten**")
    st_allow_list = st_tags(label="Niet vervangen", text="Voer woord in en druk op Enter.")
    st.caption("Woorden in deze lijst worden niet als gevoelig gegeven behandeld.")
    st_deny_list = st_tags(label="Extra controleren", text="Voer woord in en druk op Enter.")
    st.caption("Woorden in deze lijst krijgen extra aandacht bij de herkenning.")
'''

text = replace_once(text, nested_old, nested_new)

# v12.1/v12.2/v12.3/v12.4/v12.5: import review helpers.
text = replace_once(
    text,
    'from display_labels_nl import entity_label, source_label, confidence_label\n',
    'from display_labels_nl import entity_label, source_label, confidence_label\n'
    'from review_status import review_status_for_source, review_status_label, review_status_order\n'
    'from review_filters import REVIEW_FILTER_OPTIONS, FILTER_SHOW_ALL, filter_review_dataframe\n'
    'from review_table_config import main_review_columns, technical_display_columns\n'
    'from review_guidance import (\n'
    '    REVIEW_INTRO_GUIDANCE,\n'
    '    CANDIDATE_GUIDANCE,\n'
    '    FOCUS_FILTER_GUIDANCE,\n'
    '    TECHNICAL_DETAILS_GUIDANCE,\n'
    '    AI_USAGE_GUIDANCE,\n'
    '    EXPORT_GUIDANCE,\n'
    ')\n'
    'from review_summary import build_review_summary, review_summary_markdown\n',
)

# If v12.1 already added review_status but later helpers are not present yet, extend imports only.
text = replace_once(
    text,
    'from review_status import review_status_for_source, review_status_label, review_status_order\n',
    'from review_status import review_status_for_source, review_status_label, review_status_order\n'
    'from review_filters import REVIEW_FILTER_OPTIONS, FILTER_SHOW_ALL, filter_review_dataframe\n'
    'from review_table_config import main_review_columns, technical_display_columns\n'
    'from review_guidance import (\n'
    '    REVIEW_INTRO_GUIDANCE,\n'
    '    CANDIDATE_GUIDANCE,\n'
    '    FOCUS_FILTER_GUIDANCE,\n'
    '    TECHNICAL_DETAILS_GUIDANCE,\n'
    '    AI_USAGE_GUIDANCE,\n'
    '    EXPORT_GUIDANCE,\n'
    ')\n'
    'from review_summary import build_review_summary, review_summary_markdown\n',
)

text = replace_once(
    text,
    'from review_filters import REVIEW_FILTER_OPTIONS, FILTER_SHOW_ALL, filter_review_dataframe\n',
    'from review_filters import REVIEW_FILTER_OPTIONS, FILTER_SHOW_ALL, filter_review_dataframe\n'
    'from review_table_config import main_review_columns, technical_display_columns\n'
    'from review_guidance import (\n'
    '    REVIEW_INTRO_GUIDANCE,\n'
    '    CANDIDATE_GUIDANCE,\n'
    '    FOCUS_FILTER_GUIDANCE,\n'
    '    TECHNICAL_DETAILS_GUIDANCE,\n'
    '    AI_USAGE_GUIDANCE,\n'
    '    EXPORT_GUIDANCE,\n'
    ')\n'
    'from review_summary import build_review_summary, review_summary_markdown\n',
)

text = replace_once(
    text,
    'from review_table_config import main_review_columns, technical_display_columns\n',
    'from review_table_config import main_review_columns, technical_display_columns\n'
    'from review_guidance import (\n'
    '    REVIEW_INTRO_GUIDANCE,\n'
    '    CANDIDATE_GUIDANCE,\n'
    '    FOCUS_FILTER_GUIDANCE,\n'
    '    TECHNICAL_DETAILS_GUIDANCE,\n'
    '    AI_USAGE_GUIDANCE,\n'
    '    EXPORT_GUIDANCE,\n'
    ')\n'
    'from review_summary import build_review_summary, review_summary_markdown\n',
)

text = replace_once(
    text,
    'from review_guidance import (\n'
    '    REVIEW_INTRO_GUIDANCE,\n'
    '    CANDIDATE_GUIDANCE,\n'
    '    FOCUS_FILTER_GUIDANCE,\n'
    '    TECHNICAL_DETAILS_GUIDANCE,\n'
    '    AI_USAGE_GUIDANCE,\n'
    '    EXPORT_GUIDANCE,\n'
    ')\n',
    'from review_guidance import (\n'
    '    REVIEW_INTRO_GUIDANCE,\n'
    '    CANDIDATE_GUIDANCE,\n'
    '    FOCUS_FILTER_GUIDANCE,\n'
    '    TECHNICAL_DETAILS_GUIDANCE,\n'
    '    AI_USAGE_GUIDANCE,\n'
    '    EXPORT_GUIDANCE,\n'
    ')\n'
    'from review_summary import build_review_summary, review_summary_markdown\n',
)

# v12.6: import advisory export sanity helpers without changing export behavior.
text = replace_once(
    text,
    'from review_summary import build_review_summary, review_summary_markdown\n',
    'from review_summary import build_review_summary, review_summary_markdown\n'
    'from export_sanity import build_export_sanity_checks, export_sanity_warnings\n',
)

# v12.1: add review status fields to remembered rows.
text = replace_once(
    text,
    '''                    "source_label": source_label("remembered"),
                    "source": "remembered",
                    "reason": "Opgeslagen herbruikbare vervanging",
''',
    '''                    "source_label": source_label("remembered"),
                    "source": "remembered",
                    "review_status": review_status_for_source("remembered", entity_type, None),
                    "review_status_label": review_status_label(review_status_for_source("remembered", entity_type, None)),
                    "review_order": review_status_order(review_status_for_source("remembered", entity_type, None)),
                    "reason": "Opgeslagen herbruikbare vervanging",
''',
)

# v12.1: add review status fields to automatically detected rows.
text = replace_once(
    text,
    '''                    "source_label": source_label("detected"),
                    "source": "detected",
                    "reason": "Automatisch herkend",
''',
    '''                    "source_label": source_label("detected"),
                    "source": "detected",
                    "review_status": review_status_for_source("detected", entity_type, score),
                    "review_status_label": review_status_label(review_status_for_source("detected", entity_type, score)),
                    "review_order": review_status_order(review_status_for_source("detected", entity_type, score)),
                    "reason": "Automatisch herkend",
''',
)

# v12.1: add review status fields to candidate rows.
text = replace_once(
    text,
    '''                    "source_label": source_label("candidate"),
                    "source": "candidate",
                    "reason": candidate.get("reason", "Mogelijke gemiste waarde"),
''',
    '''                    "source_label": source_label("candidate"),
                    "source": "candidate",
                    "review_status": review_status_for_source("candidate", entity_type, score),
                    "review_status_label": review_status_label(review_status_for_source("candidate", entity_type, score)),
                    "review_order": review_status_order(review_status_for_source("candidate", entity_type, score)),
                    "reason": candidate.get("reason", "Mogelijke gemiste waarde"),
''',
)

# v12.1: add review status fields to empty/manual fallback rows.
text = replace_once(
    text,
    '''                    "source_label": source_label("manual"),
                    "source": "manual",
                    "reason": "Handmatige vervangingsregel",
''',
    '''                    "source_label": source_label("manual"),
                    "source": "manual",
                    "review_status": review_status_for_source("manual", "MANUAL", None),
                    "review_status_label": review_status_label(review_status_for_source("manual", "MANUAL", None)),
                    "review_order": review_status_order(review_status_for_source("manual", "MANUAL", None)),
                    "reason": "Handmatige vervangingsregel",
''',
)

# v12.1/v12.2/v12.3/v12.4: sort review rows, show guidance/status summary, focus filters and technical view.
text = replace_once(
    text,
    '''        replacement_editor_df = pd.DataFrame(default_editor_rows)
        edited_replacements_df = st.data_editor(
''',
    '''        replacement_editor_df = pd.DataFrame(default_editor_rows)
        st.info(REVIEW_INTRO_GUIDANCE)
        with st.expander("Uitleg bij deze controle", expanded=False):
            st.markdown(f"- {CANDIDATE_GUIDANCE}")
            st.markdown(f"- {FOCUS_FILTER_GUIDANCE}")
            st.markdown(f"- {TECHNICAL_DETAILS_GUIDANCE}")
            st.markdown(f"- {AI_USAGE_GUIDANCE}")
        if "review_order" in replacement_editor_df.columns:
            replacement_editor_df = replacement_editor_df.sort_values(
                by=["review_order", "type_label", "find"], kind="stable"
            ).reset_index(drop=True)
        if "review_status_label" in replacement_editor_df.columns:
            status_counts = replacement_editor_df["review_status_label"].value_counts().to_dict()
            status_parts = [f"{label}: {count}" for label, count in status_counts.items()]
            if status_parts:
                st.caption("Reviewstatus: " + " 路 ".join(status_parts))
        review_filter = st.selectbox(
            "Focusfilter voor controle",
            REVIEW_FILTER_OPTIONS,
            index=REVIEW_FILTER_OPTIONS.index(FILTER_SHOW_ALL),
            help=FOCUS_FILTER_GUIDANCE,
        )
        if review_filter != FILTER_SHOW_ALL:
            focus_df = filter_review_dataframe(replacement_editor_df, review_filter)
            st.caption(f"{len(focus_df)} van {len(replacement_editor_df)} rij(en) zichtbaar in dit focusoverzicht.")
            st.dataframe(
                focus_df[[col for col in ["review_status_label", "find", "replace_with", "type_label", "confidence", "source_label"] if col in focus_df.columns]],
                use_container_width=True,
            )
            st.caption("Pas wijzigingen toe in de volledige vervangtabel hieronder; dit focusoverzicht is alleen bedoeld om sneller te controleren.")
        with st.expander("Technische details bij de vervangtabel", expanded=False):
            st.caption(TECHNICAL_DETAILS_GUIDANCE)
            technical_columns = technical_display_columns(replacement_editor_df.columns)
            if technical_columns:
                st.dataframe(replacement_editor_df[technical_columns], use_container_width=True)
            else:
                st.caption("Geen technische detailkolommen beschikbaar.")
        edited_replacements_df = st.data_editor(
''',
)

# v12.4: if v12.1/v12.2/v12.3 block is already present, add guidance to that block.
text = replace_once(
    text,
    '''        replacement_editor_df = pd.DataFrame(default_editor_rows)
        if "review_order" in replacement_editor_df.columns:
''',
    '''        replacement_editor_df = pd.DataFrame(default_editor_rows)
        st.info(REVIEW_INTRO_GUIDANCE)
        with st.expander("Uitleg bij deze controle", expanded=False):
            st.markdown(f"- {CANDIDATE_GUIDANCE}")
            st.markdown(f"- {FOCUS_FILTER_GUIDANCE}")
            st.markdown(f"- {TECHNICAL_DETAILS_GUIDANCE}")
            st.markdown(f"- {AI_USAGE_GUIDANCE}")
        if "review_order" in replacement_editor_df.columns:
''',
)

text = replace_once(
    text,
    '            help="Gebruik dit als overzichtsfilter. De volledige vervangtabel hieronder blijft leidend voor de export.",\n',
    '            help=FOCUS_FILTER_GUIDANCE,\n',
)

text = replace_once(
    text,
    '''        with st.expander("Technische details bij de vervangtabel", expanded=False):
            technical_columns = technical_display_columns(replacement_editor_df.columns)
''',
    '''        with st.expander("Technische details bij de vervangtabel", expanded=False):
            st.caption(TECHNICAL_DETAILS_GUIDANCE)
            technical_columns = technical_display_columns(replacement_editor_df.columns)
''',
)

# v12.1: make status visible in the editor.
text = replace_once(
    text,
    '''                "include",
                "remember",
                "find",
''',
    '''                "include",
                "remember",
                "review_status_label",
                "find",
''',
)

# v12.3: reduce the editable table to user-facing columns only.
text = replace_once(
    text,
    '''            column_order=[
                "include",
                "remember",
                "review_status_label",
                "find",
                "replace_with",
                "type_label",
                "confidence",
                "source_label",
                "reason",
                "context",
                "entity_type",
                "score",
                "source",
            ],
''',
    '''            column_order=main_review_columns(replacement_editor_df.columns),
''',
)

text = replace_once(
    text,
    '''                "find": st.column_config.TextColumn(
                    "Gevonden tekst", help="Exacte tekst die vervangen moet worden."
                ),
''',
    '''                "review_status_label": st.column_config.TextColumn(
                    "Status", help="Reviewstatus: automatisch vervangen, controle nodig, handmatig of onthouden."
                ),
                "find": st.column_config.TextColumn(
                    "Gevonden tekst", help="Exacte tekst die vervangen moet worden."
                ),
''',
)

review_summary_block = '''        st.subheader("4. Download opgeschoonde bestanden")
        final_review_summary = build_review_summary(edited_replacements_df)
        st.markdown("**Eindcontrole v贸贸r download**")
        if final_review_summary.get("open_candidate_warning") or final_review_summary.get("checked_rows_included_in_export", 0) == 0:
            st.warning(final_review_summary.get("readiness_label", "Controle nodig voor export"))
        else:
            st.success(final_review_summary.get("readiness_label", "Klaar voor export na gebruikerscontrole"))
        st.markdown(review_summary_markdown(final_review_summary))
        export_sanity_checks = build_export_sanity_checks(edited_replacements_df)
        st.markdown("**Extra exportcontrole**")
        for export_sanity_warning in export_sanity_warnings(export_sanity_checks):
            st.warning(export_sanity_warning)
        st.caption("Deze exportcontrole is adviserend: downloads blijven beschikbaar en de exportinstellingen blijven ongewijzigd.")
        st.warning(EXPORT_GUIDANCE)
'''

# v12.5/v12.6: add final review summary and advisory export sanity warnings before downloads.
# Handle both unpatched and v12.4-patched app text.
text = replace_once(
    text,
    '''        st.subheader("4. Download opgeschoonde bestanden")
        st.warning(EXPORT_GUIDANCE)
''',
    review_summary_block,
)

text = replace_once(
    text,
    '''        st.subheader("4. Download opgeschoonde bestanden")
''',
    review_summary_block,
)

# v12.6: extend an existing v12.5 review-summary block with advisory sanity warnings.
text = replace_once(
    text,
    '''        st.markdown(review_summary_markdown(final_review_summary))
        st.warning(EXPORT_GUIDANCE)
''',
    '''        st.markdown(review_summary_markdown(final_review_summary))
        export_sanity_checks = build_export_sanity_checks(edited_replacements_df)
        st.markdown("**Extra exportcontrole**")
        for export_sanity_warning in export_sanity_warnings(export_sanity_checks):
            st.warning(export_sanity_warning)
        st.caption("Deze exportcontrole is adviserend: downloads blijven beschikbaar en de exportinstellingen blijven ongewijzigd.")
        st.warning(EXPORT_GUIDANCE)
''',
)

# v12.1: include status in the scrub report rows where downstream exporters keep it.
text = replace_once(
    text,
    '''                    "source": safe_cell(row.get("source", "")),
                    "reason": safe_cell(row.get("reason", "")),
''',
    '''                    "source": safe_cell(row.get("source", "")),
                    "review_status": safe_cell(row.get("review_status", "")),
                    "review_status_label": safe_cell(row.get("review_status_label", "")),
                    "reason": safe_cell(row.get("reason", "")),
''',
)

APP_FILE.write_text(text, encoding="utf-8")