Keep the row for an unverifiable paragraph, so the gap says why

A paragraph whose translation could not be verified produced no rows at
all, which is indistinguishable from one that was never translated: the
review lane showed a missing utterance with no explanation.

The project's own rule for a gap is "the row is kept, the gap is visible,
no clip is generated", and it already applies to the collapse orphans. An
unverifiable paragraph is the same kind of gap, so it now gets the same
treatment: a voiced=false row carrying the reason.

Measured on the real ch-15 window: the ten drifted paragraphs now produce
thirteen explicit not-voiced rows instead of vanishing, and the reviewer
can see that the translation was dropped because it renders another
paragraph rather than because nobody translated it.

60 tests, ruff clean.
This commit is contained in:
Umbra Tools
2026-11-06 21:22:09 -08:00
parent acab79d4ad
commit 11fd8d8060
2 changed files with 41 additions and 0 deletions

View File

@@ -880,6 +880,25 @@ def build_delivery(
for alignment in alignments:
paragraph = bundle_ids.get(alignment.paragraph_id) or {}
sources = {source.source_id: source for source in source_utterances(paragraph, bundle)}
if alignment.method == "mismatch":
# The paragraph's translation could not be trusted - it
# renders another paragraph. The utterances still get a row,
# marked as not voiced and carrying the reason, so the gap is
# visible in the review lane and says why. Silence would be
# indistinguishable from "never translated".
reason = "; ".join(alignment.problems) or "the translation could not be verified"
for source in sources.values():
if source.row_id is None:
continue
utterances.append(
{
"utterance_id": source.row_id,
"voiced": False,
"reason": reason,
"source_ref": source_ref or alignment.paragraph_id,
}
)
continue
for segment in alignment.segments:
source = sources.get(segment.source_id)
if source is None or source.row_id is None:

View File

@@ -333,6 +333,28 @@ class DeliveryTests(unittest.TestCase):
self.assertIn("absorbed", collapsed[0]["reason"])
self.assertNotIn("text", collapsed[0])
def test_an_unverifiable_paragraph_is_delivered_as_a_visible_gap(self) -> None:
"""The row is kept and says why, rather than vanishing.
A delivery that simply omits the utterances is indistinguishable
from one that was never translated. The review lane needs to see the
gap and its reason.
"""
alignment = align_paragraph(paragraph_fixture(), "这艘船很大。设计师很用心。", bundle_fixture())
alignment.method = "mismatch"
alignment.segments = []
alignment.problems = ["translation belongs to another paragraph: it renders the next one"]
delivery = build_delivery(
{"target_language": "zh"},
[({"id": "ch-1", "paragraphs": [paragraph_fixture()]}, [alignment])],
bundle_fixture(),
)
self.assertEqual(len(delivery["utterances"]), 2)
self.assertTrue(all(not u["voiced"] for u in delivery["utterances"]))
self.assertEqual([u["utterance_id"] for u in delivery["utterances"]], [100, 101])
self.assertIn("another paragraph", delivery["utterances"][0]["reason"])
self.assertNotIn("text", delivery["utterances"][0])
class CommandTests(unittest.TestCase):
def test_the_cache_directory_is_created_before_the_first_call(self) -> None: