{
  "sha": "f9628128302d0aeb5e0f7e8d6f9c5ad0a4c6eeb2",
  "message": "阻擋簡體字進入公開中文內容 (#217)\n\n新增 OpenCC 發布前驗證、回歸測試，並修正 2026-09-22 公開文章與晨間快報。",
  "author": {
    "login": "nnneeeooo-nnneeeooo",
    "name": "",
    "email": ""
  },
  "committer": {
    "login": "web-flow",
    "name": "",
    "email": ""
  },
  "created_at": "2026-09-22T03:28:30Z",
  "files": [
    {
      "filename": "data/articles/2026-09-22T00.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 2,
      "changes": 4
    },
    {
      "filename": "data/briefings/2026-09-22-morning.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 2,
      "changes": 4
    },
    {
      "filename": "pipeline/write.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 36,
      "deletions": 0,
      "changes": 36
    },
    {
      "filename": "requirements.txt",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 0,
      "changes": 1
    },
    {
      "filename": "tests/test_write_offline.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 8,
      "deletions": 0,
      "changes": 8
    }
  ],
  "diff": "@@ -8,8 +8,8 @@\\n       \"image\": null,\\n       \"sourceImageCandidates\": [],\\n       \"zh\": {\\n-        \"title\": \"美航空公司反对中国国航增飞纽约华盛顿\",\\n-        \"summary\": \"美航空公司反对中国国航增飞纽约与华盛顿航班\",\\n+        \"title\": \"美國航空業者反對中國國際航空增飛紐約、華盛頓\",\\n+        \"summary\": \"美國航空業者反對中國國際航空增飛紐約與華盛頓航班\",\\n         \"body\": [\\n           \"美國主要航空公司正反對中國國際航空（Air China）申請增加北京往返紐約與華盛頓的航班，理由是中國航空公司可使用俄羅斯領空，形成美國競爭對手無法比擬的優勢。\",\\n           \"路透社報導指出，這些航班與中國國家主席習近平計劃訪問美國有關，屬暫時性安排，而非永久增加航班；習近平預定於2026年9月23日至25日訪問美國，與總統川普會談。\",\\n@@ -135,8 +135,8 @@\\n         \"item_type\": \"new\",\\n         \"previous_event_id\": null,\\n         \"category\": \"aviation_industry\",\\n-        \"headline\": \"美航空公司反对中国国航增飞纽约华盛顿\",\\n-        \"summary\": \"美航空公司反对中国国航增飞纽约与华盛顿航班\",\\n+        \"headline\": \"美國航空業者反對中國國際航空增飛紐約、華盛頓\",\\n+        \"summary\": \"美國航空業者反對中國國際航空增飛紐約與華盛頓航班\",\\n         \"headline_en\": \"US airlines oppose Air China request for extra New York and Washington flights\",\\n         \"summary_en\": \"US airlines oppose Air China's bid for extra New York Washington flights\",\\n         \"event_time\": null,\\n@@ -37,6 +37,8 @@\\n from difflib import SequenceMatcher\\n from pathlib import Path\\n \\n+from opencc import OpenCC\\n+\\n from common import (\\n     ARTICLES_DIR,\\n     BASE_PATH,\\n@@ -87,6 +89,37 @@\\n EN_BRIEF_MIN_WORDS = 90\\n EN_BRIEF_MAX_WORDS = 150\\n \\n+_S2T = OpenCC(\"s2t\")\\n+\\n+\\n+def simplified_chinese_error(draft: dict) -> str | None:\\n+    \"\"\"Reject reader-facing Chinese copy that still contains simplified glyphs.\"\"\"\\n+    fields: list[tuple[str, object]] = []\\n+    zh = draft.get(\"zh\")\\n+    if isinstance(zh, dict):\\n+        fields.extend([\\n+            (\"zh.title\", zh.get(\"title\")),\\n+            (\"zh.summary\", zh.get(\"summary\")),\\n+        ])\\n+        body = zh.get(\"body\")\\n+        if isinstance(body, list):\\n+            fields.extend((f\"zh.body[{index}]\", paragraph)\\n+                          for index, paragraph in enumerate(body))\\n+    flash = draft.get(\"flash\")\\n+    if isinstance(flash, dict):\\n+        fields.append((\"flash.zh\", flash.get(\"zh\")))\\n+    incident = draft.get(\"incident\")\\n+    if isinstance(incident, dict):\\n+        for key in (\"phase\", \"location\", \"desc\"):\\n+            bilingual = incident.get(key)\\n+            if isinstance(bilingual, dict):\\n+                fields.append((f\"incident.{key}.zh\", bilingual.get(\"zh\")))\\n+    for field, value in fields:\\n+        if isinstance(value, str) and _S2T.convert(value) != value:\\n+            return f\"{field} contains Simplified Chinese characters\"\\n+    return None\\n+\\n+\\n _EDITORIAL_PROCESS_RE = re.compile(\\n     r\"(?:本文|本報導|這篇(?:文章|報導)).{0,60}\"\\n     r\"(?:採用|使用|引用|補足|補充|核對|交叉確認|資料來源|來源資料)\"\\n@@ -1023,6 +1056,9 @@ def validate_draft(draft):\\n         if len(body) > MAX_BODY_PARAGRAPHS:\\n             return (f\"{lang}.body has {len(body)} paragraphs; maximum is \"\\n                     f\"{MAX_BODY_PARAGRAPHS}\")\\n+    language_error = simplified_chinese_error(draft)\\n+    if language_error:\\n+        return language_error\\n     process_error = editorial_process_error(draft)\\n     if process_error:\\n         return process_error\\n@@ -6,3 +6,4 @@ lxml>=5.0,<6\\n jinja2>=3.1,<4\\n cryptography>=45,<47\\n airportsdata==20260315\\n+opencc-python-reimplemented>=0.1.7,<1\\n@@ -533,6 +533,14 @@ def test_extract_json_and_validate_draft():\\n \\n     check(write.validate_draft(DRAFT_SAFETY) is None, \"safety draft validates\")\\n     check(write.validate_draft(DRAFT_BIZ) is None, \"biz draft validates\")\\n+    broken = json.loads(json.dumps(DRAFT_BIZ))\\n+    broken[\"zh\"][\"title\"] = \"美航空公司反对中国国航增飞纽约华盛顿\"\\n+    check(\"Simplified Chinese\" in (write.validate_draft(broken) or \"\"),\\n+          \"simplified Chinese title is rejected before publication\")\\n+    broken = json.loads(json.dumps(DRAFT_BIZ))\\n+    broken[\"flash\"][\"zh\"] = \"中国国航增飞纽约航班\"\\n+    check(\"Simplified Chinese\" in (write.validate_draft(broken) or \"\"),\\n+          \"simplified Chinese flash is rejected before publication\")\\n     check(write.zh_body_character_count(DRAFT_BIZ[\"zh\"][\"body\"])\\n           >= write.ZH_BODY_MIN_CHARS, \"zh test fixture clears body floor\")\\n     check(write.en_body_word_count(DRAFT_BIZ[\"en\"][\"body\"])"
}
