Spaces:
Sleeping
Sleeping
File size: 7,691 Bytes
5342e0e | 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 | """Advisory export sanity checks for Scrub Legal.
This helper prepares Dutch, user-facing warnings for the export step. It is
purely advisory: it does not block downloads, mutate review rows or change which
replacement rows are included in export.
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from review_summary import (
NEEDS_REVIEW,
_is_checked,
_lower,
_normalise_rows,
_row_status,
build_review_summary,
)
CONTROL_NEEDED_WARNING = (
"Let op: {count} regel(s) met 'Controle nodig' staan niet aangevinkt. "
"Controleer deze waarden handmatig voordat u het bestand gebruikt."
)
CANDIDATE_NOT_INCLUDED_WARNING = (
"Let op: {count} mogelijke kandidaatwaarde(n) worden niet meegenomen in de export. "
"Niet-aangevinkte kandidaten blijven mogelijk zichtbaar in de uitvoer."
)
NO_REPLACEMENTS_WARNING = (
"Er zijn geen vervangingen geselecteerd. De export kan daardoor gelijk blijven "
"aan de invoer."
)
USER_REVIEW_REQUIRED_WARNING = (
"Gebruikerscontrole blijft nodig: controleer het opgeschoonde document altijd zelf "
"vóór gebruik of delen."
)
ADVISORY_EXPORT_WARNING = (
"De export is een hulpmiddel en geen garantie op volledige anonimisering. "
"Downloaden blijft mogelijk, maar de gebruiker blijft verantwoordelijk voor de eindcontrole."
)
def _to_int(value: Any) -> int:
try:
return int(value or 0)
except (TypeError, ValueError):
return 0
def _is_export_summary(value: Any) -> bool:
return isinstance(value, Mapping) and "total_rows" in value and "include" not in value
def _is_candidate_row(row: Mapping[str, Any]) -> bool:
source = _lower(row.get("source"))
candidate_marker = _lower(row.get("candidate"))
return source == "candidate" or candidate_marker in {"1", "true", "yes", "ja"}
def _normalise_export_summary(summary: Mapping[str, Any]) -> dict[str, int | bool | str | dict[str, Any]]:
review_summary = dict(summary.get("review_summary", summary))
checked_rows = _to_int(summary.get("checked_rows_included_in_export", review_summary.get("checked_rows_included_in_export")))
total_rows = _to_int(summary.get("total_rows", review_summary.get("total_rows")))
unchecked_control_needed_rows = _to_int(
summary.get("unchecked_control_needed_rows", review_summary.get("open_candidate_rows"))
)
candidate_rows_not_included = _to_int(
summary.get("candidate_rows_not_included", review_summary.get("open_candidate_rows"))
)
normalised: dict[str, int | bool | str | dict[str, Any]] = {
"total_rows": total_rows,
"checked_rows_included_in_export": checked_rows,
"unchecked_rows_excluded_from_export": _to_int(
summary.get(
"unchecked_rows_excluded_from_export",
review_summary.get("unchecked_rows_excluded_from_export"),
)
),
"unchecked_control_needed_rows": unchecked_control_needed_rows,
"candidate_rows_not_included": candidate_rows_not_included,
"no_replacements_selected": bool(summary.get("no_replacements_selected", checked_rows == 0)),
"user_review_required": True,
"export_is_advisory": True,
"blocks_export": False,
"changes_export_semantics": False,
"review_summary": review_summary,
}
normalised["ready_label"] = export_sanity_ready_label(normalised)
return normalised
def build_export_sanity_checks(rows: Any) -> dict[str, int | bool | str | dict[str, Any]]:
"""Build advisory export sanity flags from replacement review rows.
The returned flags are intended for warning text only. They must not be used
to block downloads or to change export inclusion behavior.
"""
records = _normalise_rows(rows)
review_summary = build_review_summary(records)
unchecked_control_needed_rows = sum(
1
for row in records
if _row_status(row) == NEEDS_REVIEW and not _is_checked(row.get("include"))
)
candidate_rows_not_included = sum(
1 for row in records if _is_candidate_row(row) and not _is_checked(row.get("include"))
)
checked_rows = _to_int(review_summary.get("checked_rows_included_in_export"))
summary: dict[str, int | bool | str | dict[str, Any]] = {
"total_rows": _to_int(review_summary.get("total_rows")),
"checked_rows_included_in_export": checked_rows,
"unchecked_rows_excluded_from_export": _to_int(
review_summary.get("unchecked_rows_excluded_from_export")
),
"unchecked_control_needed_rows": unchecked_control_needed_rows,
"candidate_rows_not_included": candidate_rows_not_included,
"no_replacements_selected": checked_rows == 0,
"user_review_required": True,
"export_is_advisory": True,
"blocks_export": False,
"changes_export_semantics": False,
"review_summary": review_summary,
}
summary["ready_label"] = export_sanity_ready_label(summary)
return summary
def _as_export_sanity_summary(summary_or_rows: Any) -> dict[str, int | bool | str | dict[str, Any]]:
if _is_export_summary(summary_or_rows):
return _normalise_export_summary(summary_or_rows)
return build_export_sanity_checks(summary_or_rows)
def export_sanity_warnings(summary_or_rows: Any) -> list[str]:
"""Return Dutch user-facing warnings for the export step."""
summary = _as_export_sanity_summary(summary_or_rows)
warnings: list[str] = []
unchecked_control_needed_rows = _to_int(summary.get("unchecked_control_needed_rows"))
candidate_rows_not_included = _to_int(summary.get("candidate_rows_not_included"))
if unchecked_control_needed_rows > 0:
warnings.append(CONTROL_NEEDED_WARNING.format(count=unchecked_control_needed_rows))
if candidate_rows_not_included > 0:
warnings.append(CANDIDATE_NOT_INCLUDED_WARNING.format(count=candidate_rows_not_included))
if bool(summary.get("no_replacements_selected")):
warnings.append(NO_REPLACEMENTS_WARNING)
warnings.append(USER_REVIEW_REQUIRED_WARNING)
warnings.append(ADVISORY_EXPORT_WARNING)
return warnings
def export_sanity_ready_label(summary_or_rows: Any) -> str:
"""Return a compact Dutch readiness label for advisory display."""
if _is_export_summary(summary_or_rows):
total_rows = _to_int(summary_or_rows.get("total_rows"))
checked_rows = _to_int(summary_or_rows.get("checked_rows_included_in_export"))
unchecked_control_needed_rows = _to_int(summary_or_rows.get("unchecked_control_needed_rows"))
candidate_rows_not_included = _to_int(summary_or_rows.get("candidate_rows_not_included"))
no_replacements_selected = bool(summary_or_rows.get("no_replacements_selected", checked_rows == 0))
else:
summary = build_export_sanity_checks(summary_or_rows)
total_rows = _to_int(summary.get("total_rows"))
checked_rows = _to_int(summary.get("checked_rows_included_in_export"))
unchecked_control_needed_rows = _to_int(summary.get("unchecked_control_needed_rows"))
candidate_rows_not_included = _to_int(summary.get("candidate_rows_not_included"))
no_replacements_selected = bool(summary.get("no_replacements_selected"))
if total_rows == 0:
return "Geen vervangregels gevonden — controleer handmatig"
if no_replacements_selected or checked_rows == 0:
return "Geen vervangingen geselecteerd"
if unchecked_control_needed_rows > 0 or candidate_rows_not_included > 0:
return "Controle nodig vóór export"
return "Klaar voor export na gebruikerscontrole"
|