feat: hide flagged scientific review captions
This commit is contained in:
@@ -110,7 +110,8 @@ This produces a separate reviewed translation set and JSON/Markdown report;
|
|||||||
the initial translations remain unchanged. Evidence-backed terminology or
|
the initial translations remain unchanged. Evidence-backed terminology or
|
||||||
semantic corrections are applied, while uncertain source-caption, numeric,
|
semantic corrections are applied, while uncertain source-caption, numeric,
|
||||||
unit, drug, gene, protein, vector, strain, or model-name issues are preserved
|
unit, drug, gene, protein, vector, strain, or model-name issues are preserved
|
||||||
and flagged rather than guessed. Unresolved high-risk flags do not block the
|
and flagged rather than guessed. Flagged cue pairs stay in the review report
|
||||||
|
but are omitted from rendered subtitles. Unresolved high-risk flags do not block the
|
||||||
ordinary internal-use workflow, but must be disclosed in the final handoff.
|
ordinary internal-use workflow, but must be disclosed in the final handoff.
|
||||||
|
|
||||||
Render only the reviewed translation set and bind its exact review report:
|
Render only the reviewed translation set and bind its exact review report:
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ Use the statuses conservatively:
|
|||||||
scientific or semantic error. Do not use it for preference-only rewriting.
|
scientific or semantic error. Do not use it for preference-only rewriting.
|
||||||
- `flagged`: preserve the initial translation exactly and record a medium/high
|
- `flagged`: preserve the initial translation exactly and record a medium/high
|
||||||
unresolved concern. Never guess a correction to a suspected source-caption
|
unresolved concern. Never guess a correction to a suspected source-caption
|
||||||
error, drug, gene, protein, vector, strain, model, dose, or unit.
|
error, drug, gene, protein, vector, strain, model, dose, or unit. Flagged
|
||||||
|
cue pairs remain in the report but are excluded from the rendered bilingual
|
||||||
|
subtitles, so uncertainty markers such as “(听不清)” never appear in the MP4.
|
||||||
|
|
||||||
Numbers and scientific names are protected. The validator rejects a correction
|
Numbers and scientific names are protected. The validator rejects a correction
|
||||||
that changes them; use `flagged` when such a change may be necessary. Preserve
|
that changes them; use `flagged` when such a change may be necessary. Preserve
|
||||||
|
|||||||
@@ -392,6 +392,9 @@ def finalize(
|
|||||||
if [item["id"] for item in all_reviews] != [item["id"] for item in items]:
|
if [item["id"] for item in all_reviews] != [item["id"] for item in items]:
|
||||||
raise ReviewError("scientific reviews do not cover every subtitle segment exactly once")
|
raise ReviewError("scientific reviews do not cover every subtitle segment exactly once")
|
||||||
reviewed = {record["id"]: record["translation"] for record in all_reviews}
|
reviewed = {record["id"]: record["translation"] for record in all_reviews}
|
||||||
|
suppressed_segment_ids = [
|
||||||
|
record["id"] for record in all_reviews if record["status"] == "flagged"
|
||||||
|
]
|
||||||
reviewed_dir = review_dir / REVIEWED_DIR_NAME
|
reviewed_dir = review_dir / REVIEWED_DIR_NAME
|
||||||
reviewed_dir.mkdir(parents=True, exist_ok=True)
|
reviewed_dir.mkdir(parents=True, exist_ok=True)
|
||||||
reviewed_path = reviewed_dir / "translations.json"
|
reviewed_path = reviewed_dir / "translations.json"
|
||||||
@@ -423,6 +426,7 @@ def finalize(
|
|||||||
"initial_translation_sha256": _sha256_json(initial),
|
"initial_translation_sha256": _sha256_json(initial),
|
||||||
"reviewed_translation_sha256": _sha256_json(reviewed),
|
"reviewed_translation_sha256": _sha256_json(reviewed),
|
||||||
"reviewed_translations_dir": str(reviewed_dir.resolve()),
|
"reviewed_translations_dir": str(reviewed_dir.resolve()),
|
||||||
|
"suppressed_segment_ids": suppressed_segment_ids,
|
||||||
"profile": profile,
|
"profile": profile,
|
||||||
"counts": {
|
"counts": {
|
||||||
"total": len(all_reviews),
|
"total": len(all_reviews),
|
||||||
|
|||||||
@@ -1001,7 +1001,8 @@ def _ass_layout(manifest: dict[str, Any]) -> dict[str, int]:
|
|||||||
|
|
||||||
|
|
||||||
def _render_ass(
|
def _render_ass(
|
||||||
manifest: dict[str, Any], translations: dict[str, str], font: str
|
manifest: dict[str, Any], translations: dict[str, str], font: str,
|
||||||
|
suppressed_segment_ids: set[str] | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
font = _validate_font(font)
|
font = _validate_font(font)
|
||||||
cue_by_id = _cue_map(manifest)
|
cue_by_id = _cue_map(manifest)
|
||||||
@@ -1035,6 +1036,10 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
target_language = str(manifest.get("target_language") or DEFAULT_TARGET_LANGUAGE)
|
target_language = str(manifest.get("target_language") or DEFAULT_TARGET_LANGUAGE)
|
||||||
dialogue: list[str] = []
|
dialogue: list[str] = []
|
||||||
for segment in _render_segments(manifest):
|
for segment in _render_segments(manifest):
|
||||||
|
if suppressed_segment_ids and any(
|
||||||
|
segment_id in suppressed_segment_ids for segment_id in segment["cue_ids"]
|
||||||
|
):
|
||||||
|
continue
|
||||||
source_exact = _segment_source_text(segment, cue_by_id)
|
source_exact = _segment_source_text(segment, cue_by_id)
|
||||||
target_exact = normalize_target_caption(
|
target_exact = normalize_target_caption(
|
||||||
_display_translation(manifest, segment, translations), target_language
|
_display_translation(manifest, segment, translations), target_language
|
||||||
@@ -1070,7 +1075,8 @@ def _target_srt_name(manifest: dict[str, Any]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _expected_outputs(
|
def _expected_outputs(
|
||||||
manifest: dict[str, Any], translations: dict[str, str], font: str
|
manifest: dict[str, Any], translations: dict[str, str], font: str,
|
||||||
|
suppressed_segment_ids: set[str] | None = None,
|
||||||
) -> dict[str, bytes]:
|
) -> dict[str, bytes]:
|
||||||
cue_by_id = _cue_map(manifest)
|
cue_by_id = _cue_map(manifest)
|
||||||
layout = _ass_layout(manifest)
|
layout = _ass_layout(manifest)
|
||||||
@@ -1079,6 +1085,10 @@ def _expected_outputs(
|
|||||||
target_entries: list[tuple[int, int, str]] = []
|
target_entries: list[tuple[int, int, str]] = []
|
||||||
bilingual_entries: list[tuple[int, int, str]] = []
|
bilingual_entries: list[tuple[int, int, str]] = []
|
||||||
for segment in _render_segments(manifest):
|
for segment in _render_segments(manifest):
|
||||||
|
if suppressed_segment_ids and any(
|
||||||
|
segment_id in suppressed_segment_ids for segment_id in segment["cue_ids"]
|
||||||
|
):
|
||||||
|
continue
|
||||||
source_exact = _segment_source_text(segment, cue_by_id)
|
source_exact = _segment_source_text(segment, cue_by_id)
|
||||||
source_chunks = wrap_layout_chunks(source_exact, layout["source_columns"])
|
source_chunks = wrap_layout_chunks(source_exact, layout["source_columns"])
|
||||||
if "".join(source_chunks) != source_exact:
|
if "".join(source_chunks) != source_exact:
|
||||||
@@ -1099,7 +1109,9 @@ def _expected_outputs(
|
|||||||
"source.srt": _render_srt(source_entries).encode("utf-8"),
|
"source.srt": _render_srt(source_entries).encode("utf-8"),
|
||||||
_target_srt_name(manifest): _render_srt(target_entries).encode("utf-8"),
|
_target_srt_name(manifest): _render_srt(target_entries).encode("utf-8"),
|
||||||
"bilingual.srt": _render_srt(bilingual_entries).encode("utf-8"),
|
"bilingual.srt": _render_srt(bilingual_entries).encode("utf-8"),
|
||||||
"bilingual.ass": _render_ass(manifest, translations, font).encode("utf-8"),
|
"bilingual.ass": _render_ass(
|
||||||
|
manifest, translations, font, suppressed_segment_ids
|
||||||
|
).encode("utf-8"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1155,6 +1167,9 @@ def _scientific_review_binding(
|
|||||||
counts = value.get("counts")
|
counts = value.get("counts")
|
||||||
if not isinstance(counts, dict) or counts.get("total") != len(translations):
|
if not isinstance(counts, dict) or counts.get("total") != len(translations):
|
||||||
raise PipelineError("scientific review report segment count is invalid")
|
raise PipelineError("scientific review report segment count is invalid")
|
||||||
|
suppressed = value.get("suppressed_segment_ids", [])
|
||||||
|
if not isinstance(suppressed, list) or not all(isinstance(segment_id, str) for segment_id in suppressed):
|
||||||
|
raise PipelineError("scientific review suppression list is malformed")
|
||||||
return {
|
return {
|
||||||
"report_path": str(review_report),
|
"report_path": str(review_report),
|
||||||
"report_sha256": _sha256_bytes(review_report.read_bytes()),
|
"report_sha256": _sha256_bytes(review_report.read_bytes()),
|
||||||
@@ -1162,6 +1177,7 @@ def _scientific_review_binding(
|
|||||||
"human_expert_reviewed": False,
|
"human_expert_reviewed": False,
|
||||||
"counts": counts,
|
"counts": counts,
|
||||||
"disclosure": value.get("disclosure"),
|
"disclosure": value.get("disclosure"),
|
||||||
|
"suppressed_segment_ids": suppressed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1211,7 +1227,21 @@ def render(
|
|||||||
scientific_review = _scientific_review_binding(
|
scientific_review = _scientific_review_binding(
|
||||||
scientific_review_report, manifest_path, translations_dir, translations
|
scientific_review_report, manifest_path, translations_dir, translations
|
||||||
)
|
)
|
||||||
expected = _expected_outputs(manifest, translations, font)
|
suppressed_cue_ids = None
|
||||||
|
if scientific_review:
|
||||||
|
suppressed_review_ids = set(scientific_review.get("suppressed_segment_ids", []))
|
||||||
|
suppressed_cue_ids = {
|
||||||
|
cue_id
|
||||||
|
for translation_segment in manifest["segments"]
|
||||||
|
if translation_segment["id"] in suppressed_review_ids
|
||||||
|
for cue_id in translation_segment["cue_ids"]
|
||||||
|
}
|
||||||
|
expected = _expected_outputs(
|
||||||
|
manifest,
|
||||||
|
translations,
|
||||||
|
font,
|
||||||
|
suppressed_cue_ids,
|
||||||
|
)
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
for name, data in expected.items():
|
for name, data in expected.items():
|
||||||
_atomic_write(output_dir / name, data)
|
_atomic_write(output_dir / name, data)
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ class ScientificReviewTests(unittest.TestCase):
|
|||||||
target = (self.root / "rendered" / "zh-CN.srt").read_text(encoding="utf-8")
|
target = (self.root / "rendered" / "zh-CN.srt").read_text(encoding="utf-8")
|
||||||
self.assertIn("暴露上方巩膜", target)
|
self.assertIn("暴露上方巩膜", target)
|
||||||
self.assertNotIn("暴露上方巩膜组织", target)
|
self.assertNotIn("暴露上方巩膜组织", target)
|
||||||
|
self.assertNotIn(items[2]["translation"], target)
|
||||||
|
|
||||||
def test_correction_cannot_change_protected_number_or_scientific_name(self) -> None:
|
def test_correction_cannot_change_protected_number_or_scientific_name(self) -> None:
|
||||||
self.write_profile()
|
self.write_profile()
|
||||||
|
|||||||
Reference in New Issue
Block a user