Correction: reject lost numbers and growth; place verifier anchors from the talkgroup name (server-26#162, server-26#160)
transcript_correction now also discards a correction that drops or changes a bare number the recogniser heard (the live "7" -> "ShotSpotter" case) or grows the transcript past max(4 words, 25%). place_verifier.verify() derives an anchor from the talkgroup's own name plus the receiving node's state when no area is configured; no state means no anchor, and a configured place without an anchor keeps the load-bearing skip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f4d78aba77
commit
7ecfca7648
@@ -116,11 +116,37 @@ def _dedupe(items: list[str]) -> list[str]:
|
||||
# mishearing this pass exists to fix (server-26#162).
|
||||
_CODE_TOKEN_RE = re.compile(r"\b\d{1,3}(?:-\d{1,3})+\b")
|
||||
|
||||
# Bare numbers (the rest of server-26#162): the same call had a bare "7"
|
||||
# turned into "ShotSpotter". A unit number, a post, a house number or a count
|
||||
# the recogniser DID hear must survive, in order. A number may still be
|
||||
# added, because a sound-alike can legitimately be one ("back to Rose" ->
|
||||
# "back to Route 9"). "4th" is not bare (it is a word).
|
||||
_BARE_NUMBER_RE = re.compile(r"(?<![\w-])\d+(?![\w-])")
|
||||
|
||||
# "NEVER add information" is the other prompt rule nothing checked. A
|
||||
# correction substitutes sound-alikes, so it may shift the word count a little
|
||||
# ("Holmes Street and 4th and Rowe" -> "Home Street and Forest Ave"); growing
|
||||
# by more than this is the model writing, not correcting.
|
||||
_MAX_ADDED_WORDS = 4
|
||||
_MAX_GROWTH_RATIO = 0.25
|
||||
|
||||
|
||||
def _code_tokens(text: str) -> list[str]:
|
||||
return _CODE_TOKEN_RE.findall(text or "")
|
||||
|
||||
|
||||
def _lost_numbers(original: str, corrected: str) -> bool:
|
||||
"""True unless every bare number of `original` is still in `corrected`, in order."""
|
||||
remaining = iter(_BARE_NUMBER_RE.findall(corrected or ""))
|
||||
return not all(any(n == m for m in remaining) for n in _BARE_NUMBER_RE.findall(original or ""))
|
||||
|
||||
|
||||
def _grew(original: str, corrected: str) -> bool:
|
||||
n_orig = len((original or "").split())
|
||||
n_new = len((corrected or "").split())
|
||||
return n_new - n_orig > max(_MAX_ADDED_WORDS, int(n_orig * _MAX_GROWTH_RATIO))
|
||||
|
||||
|
||||
def _talkgroup_entry(system_doc: dict, talkgroup_id: Optional[int]) -> dict:
|
||||
"""The config.talkgroups[] entry for this talkgroup, or {}."""
|
||||
if talkgroup_id is None:
|
||||
@@ -241,6 +267,7 @@ async def correct(
|
||||
system_id: Optional[str] = None,
|
||||
talkgroup_id: Optional[int] = None,
|
||||
talkgroup_name: Optional[str] = None,
|
||||
node_id: Optional[str] = None,
|
||||
) -> tuple[Optional[str], Optional[list[dict]], bool]:
|
||||
"""
|
||||
Second-opinion pass over a transcript.
|
||||
@@ -330,6 +357,8 @@ async def correct(
|
||||
context.get("tg_area"),
|
||||
system_id=system_id,
|
||||
talkgroup_id=talkgroup_id,
|
||||
talkgroup_name=talkgroup_name,
|
||||
node_id=node_id,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Place verification failed for call {call_id}: {e}")
|
||||
@@ -354,6 +383,18 @@ async def correct(
|
||||
f"discarding the joined correction"
|
||||
)
|
||||
corrected = None
|
||||
if corrected is not None and _lost_numbers(text, corrected):
|
||||
logger.warning(
|
||||
f"Transcript correction for call {call_id} dropped or changed a number — "
|
||||
f"discarding the joined correction"
|
||||
)
|
||||
corrected = None
|
||||
if corrected is not None and _grew(text, corrected):
|
||||
logger.warning(
|
||||
f"Transcript correction for call {call_id} grew the transcript "
|
||||
f"({len(text.split())} -> {len(corrected.split())} words) — discarding the joined correction"
|
||||
)
|
||||
corrected = None
|
||||
if corrected_segments is not None:
|
||||
for seg, orig in zip(corrected_segments, segments or []):
|
||||
if _code_tokens(seg["text"]) != _code_tokens(orig.get("text", "")):
|
||||
@@ -363,6 +404,13 @@ async def correct(
|
||||
)
|
||||
corrected_segments = None
|
||||
break
|
||||
if _lost_numbers(orig.get("text", ""), seg["text"]) or _grew(orig.get("text", ""), seg["text"]):
|
||||
logger.warning(
|
||||
f"Transcript correction for call {call_id} grew a segment or dropped "
|
||||
f"a number in one — discarding segment corrections"
|
||||
)
|
||||
corrected_segments = None
|
||||
break
|
||||
|
||||
if corrected or corrected_segments or not_speech:
|
||||
changed = raw.get("changed") or []
|
||||
|
||||
Reference in New Issue
Block a user