172 lines
6.9 KiB
Python
172 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
SCRIPT_DIR = Path(__file__).resolve().parents[1] / "scripts"
|
|
|
|
|
|
def load(name: str, filename: str):
|
|
spec = importlib.util.spec_from_file_location(name, SCRIPT_DIR / filename)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
pipeline = load("review_test_pipeline", "subtitle_pipeline.py")
|
|
review = load("scientific_review", "scientific_review.py")
|
|
|
|
|
|
class ScientificReviewTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.temporary = tempfile.TemporaryDirectory()
|
|
self.root = Path(self.temporary.name)
|
|
source = self.root / "source.srt"
|
|
source.write_text(
|
|
"1\n00:00:00,000 --> 00:00:01,000\nInject 50 microliters.\n\n"
|
|
"2\n00:00:01,100 --> 00:00:02,000\nExpose the superior sclera.\n\n"
|
|
"3\n00:00:02,100 --> 00:00:03,000\nAAV8-GFP expression was detected.\n",
|
|
encoding="utf-8",
|
|
)
|
|
self.manifest_path = pipeline.prepare(source, self.root / "subtitles", "en")
|
|
self.manifest = json.loads(self.manifest_path.read_text(encoding="utf-8"))
|
|
self.translations = self.root / "translations"
|
|
self.translations.mkdir()
|
|
records = [
|
|
{"id": self.manifest["segments"][0]["id"], "translation": "注射50微升"},
|
|
{"id": self.manifest["segments"][1]["id"], "translation": "暴露上方巩膜组织"},
|
|
{"id": self.manifest["segments"][2]["id"], "translation": "检测到AAV8-GFP表达"},
|
|
]
|
|
(self.translations / "batch-0001.json").write_text(
|
|
json.dumps({"translations": records}, ensure_ascii=False), encoding="utf-8"
|
|
)
|
|
self.review_dir = self.root / "subtitles" / "scientific-review"
|
|
|
|
def tearDown(self) -> None:
|
|
self.temporary.cleanup()
|
|
|
|
def write_profile(self) -> None:
|
|
self.review_dir.mkdir(parents=True, exist_ok=True)
|
|
(self.review_dir / review.PROFILE_NAME).write_text(
|
|
json.dumps(
|
|
{
|
|
"domains": [
|
|
{
|
|
"id": "experimental-animal-science",
|
|
"label": "实验动物学",
|
|
"relevance": "primary",
|
|
}
|
|
],
|
|
"review_focus": ["剂量、解剖方向和载体名称"],
|
|
"terminology": [
|
|
{
|
|
"source": "superior sclera",
|
|
"preferred": "上方巩膜",
|
|
"category": "anatomy",
|
|
"note": "保持方向信息",
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
def test_profile_review_finalize_and_bound_render(self) -> None:
|
|
first = review.next_batch(self.manifest_path, self.translations, self.review_dir)
|
|
self.assertEqual(first["stage"], "domain_profile_required")
|
|
self.assertLessEqual(len(first["profile"]["representative_samples"]), 18)
|
|
self.write_profile()
|
|
|
|
pending = review.next_batch(self.manifest_path, self.translations, self.review_dir)
|
|
self.assertEqual(pending["stage"], "scientific_review_required")
|
|
items = pending["batch"]["items"]
|
|
records = [
|
|
{
|
|
"id": items[0]["id"],
|
|
"status": "approved",
|
|
"translation": items[0]["translation"],
|
|
"severity": "none",
|
|
"category": "none",
|
|
"reason": "",
|
|
},
|
|
{
|
|
"id": items[1]["id"],
|
|
"status": "corrected",
|
|
"translation": "暴露上方巩膜",
|
|
"severity": "low",
|
|
"category": "anatomy",
|
|
"reason": "删除原文没有的“组织”",
|
|
},
|
|
{
|
|
"id": items[2]["id"],
|
|
"status": "flagged",
|
|
"translation": items[2]["translation"],
|
|
"severity": "high",
|
|
"category": "gene_protein_vector",
|
|
"reason": "载体名称需保守保留,无法仅凭字幕确认",
|
|
},
|
|
]
|
|
Path(pending["output_path"]).write_text(
|
|
json.dumps({"reviews": records}, ensure_ascii=False), encoding="utf-8"
|
|
)
|
|
complete = review.next_batch(self.manifest_path, self.translations, self.review_dir)
|
|
self.assertTrue(complete["done"])
|
|
finalized = review.finalize(self.manifest_path, self.translations, self.review_dir)
|
|
self.assertEqual(finalized["counts"]["corrected"], 1)
|
|
self.assertEqual(finalized["counts"]["unresolved_high"], 1)
|
|
|
|
validation_path = pipeline.render(
|
|
self.manifest_path,
|
|
Path(finalized["reviewed_translations_dir"]),
|
|
self.root / "rendered",
|
|
scientific_review_report=Path(finalized["report"]),
|
|
)
|
|
validation = json.loads(validation_path.read_text(encoding="utf-8"))
|
|
self.assertTrue(validation["translation_quality_reviewed"])
|
|
self.assertFalse(validation["scientific_review"]["human_expert_reviewed"])
|
|
target = (self.root / "rendered" / "zh-CN.srt").read_text(encoding="utf-8")
|
|
self.assertIn("暴露上方巩膜", target)
|
|
self.assertNotIn("暴露上方巩膜组织", target)
|
|
|
|
def test_correction_cannot_change_protected_number_or_scientific_name(self) -> None:
|
|
self.write_profile()
|
|
pending = review.next_batch(self.manifest_path, self.translations, self.review_dir)
|
|
items = pending["batch"]["items"]
|
|
records = []
|
|
for item in items:
|
|
translation = item["translation"]
|
|
status = "approved"
|
|
severity = "none"
|
|
category = "none"
|
|
reason = ""
|
|
if "50" in translation:
|
|
translation = translation.replace("50", "500")
|
|
status = "corrected"
|
|
severity = "high"
|
|
category = "number_unit"
|
|
reason = "unsafe numerical rewrite"
|
|
records.append(
|
|
{
|
|
"id": item["id"],
|
|
"status": status,
|
|
"translation": translation,
|
|
"severity": severity,
|
|
"category": category,
|
|
"reason": reason,
|
|
}
|
|
)
|
|
output = Path(pending["output_path"])
|
|
output.write_text(json.dumps({"reviews": records}, ensure_ascii=False), encoding="utf-8")
|
|
with self.assertRaisesRegex(review.ReviewError, "protected numbers"):
|
|
review.next_batch(self.manifest_path, self.translations, self.review_dir)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|