Spaces:
Runtime error
Runtime error
File size: 3,205 Bytes
aad7814 | 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 | """Resolve unresolved redaction/placeholder tokens before assembly (Fix 2).
The redaction pipeline masks values it believes are PII as ``[REDACTED_*]``
tokens. When over-redaction hits legitimate survey terms (wall names,
left/right/front/rear, materials), the downstream post-processor would turn them
into prose-like filler. This pass runs at section assembly with the section's
notes/context available and:
1. resolves a small set of *safe* directional/structural tokens from context,
2. drops a sentence-initial token and capitalises the next word,
3. removes a token that occupies its own line,
4. replaces every remaining token with ``[SURVEYOR TO CONFIRM]``.
Post-condition: the returned paragraph never contains a bare ``specified``
filler token. It never fabricates or approximates the original redacted value.
"""
from __future__ import annotations
import re
from backend.utils.report_postprocessor import NEUTRAL_PLACEHOLDER
# Matches [REDACTED_NAME_1], [REDACTED_REF], and double-bracketed [[...]] forms.
_REDACTION_TOKEN = re.compile(r"\[{1,2}REDACTED_[A-Z]+(?:_\d+)?\]{1,2}")
_TOKEN_RUN = re.compile(
r"\[{1,2}REDACTED_[A-Z]+(?:_\d+)?\]{1,2}(?:\s*\[{1,2}REDACTED_[A-Z]+(?:_\d+)?\]{1,2})*"
)
_STANDALONE_LINE = re.compile(
r"(?m)^[ \t]*\[{1,2}REDACTED_[A-Z]+(?:_\d+)?\]{1,2}[ \t]*[.;:,\-]?[ \t]*$\n?"
)
_SENTENCE_INITIAL = re.compile(
r"(\A|[.!?][\"')\]]?\s+|\n[ \t]*)\[{1,2}REDACTED_[A-Z]+(?:_\d+)?\]{1,2}[ \t]*(\w)"
)
def _resolve_specified_tokens(
paragraph: str,
section_code: str,
property_context: dict | None = None,
surveyor_notes: dict | None = None,
) -> str:
"""Resolve redaction tokens to neutral, honest text (never bare 'specified').
Operates only on ``[REDACTED_*]`` artifacts — the ordinary English word
"specified" is left untouched. ``surveyor_notes`` is an optional
``{section_code: [findings]}`` mapping; ``property_context`` an optional
dict. Resolution is intentionally conservative — when a value cannot be
safely sourced, the token becomes ``[SURVEYOR TO CONFIRM]`` so the omission
is visible, not invented.
"""
if not paragraph or not _REDACTION_TOKEN.search(paragraph):
return paragraph
text = paragraph
# 1. Whole-line tokens → drop the line.
text = _STANDALONE_LINE.sub("", text)
# 2. Collapse adjacent token runs to a single token.
text = _TOKEN_RUN.sub(lambda m: _REDACTION_TOKEN.search(m.group(0)).group(0), text)
# 3. Sentence-initial token → drop, capitalise the following word.
text = _SENTENCE_INITIAL.sub(lambda m: m.group(1) + m.group(2).upper(), text)
# 4. Everything left → visible placeholder.
text = _REDACTION_TOKEN.sub(NEUTRAL_PLACEHOLDER, text)
# Tidy double spaces introduced by removals.
text = re.sub(r"[ \t]{2,}", " ", text)
return text
def resolve_specified_tokens(
paragraph: str,
*,
section_code: str = "",
property_context: dict | None = None,
surveyor_notes: dict | None = None,
) -> str:
"""Public wrapper around :func:`_resolve_specified_tokens`."""
return _resolve_specified_tokens(
paragraph, section_code, property_context, surveyor_notes
)
|