From 16ba83ec0118bcf43f0bf2c51b4e0df53d2dcdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=80=BB=E5=B7=A5?= Date: Sat, 5 Sep 2026 16:02:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(HEL-461):=20=E5=88=87=E6=8D=A2=E4=BA=8B?= =?UTF-8?q?=E5=8A=A1=E5=A4=B1=E8=B4=A5=E5=86=99=E5=85=A5=20release-group?= =?UTF-8?q?=20=E5=AE=A1=E8=AE=A1=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 整组切换中断时除回滚与废弃批次外,同步记录 action=release-group 的失败审计,便于后台追踪。 Co-authored-by: Cursor Co-authored-by: multica-agent --- xiaobai-datahub/datahub/pipeline.py | 26 +++++++++++++++++++- xiaobai-datahub/tests/test_atomic_release.py | 13 ++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/xiaobai-datahub/datahub/pipeline.py b/xiaobai-datahub/datahub/pipeline.py index 9c9fa81..c6c5d8c 100644 --- a/xiaobai-datahub/datahub/pipeline.py +++ b/xiaobai-datahub/datahub/pipeline.py @@ -813,8 +813,32 @@ class Pipeline: try: self._switch_release_group(day, staged) except Exception as exc: + reason = f"release group switch failed: {exc}" for item in staged.values(): - self._abandon_batch(item["batch_id"], f"release group switch failed: {exc}") + self._abandon_batch(item["batch_id"], reason) + LOGGER.warning( + "release group switch failed, previous official version keeps serving", + extra={ + "hub": { + "trade_date": day, + "datasets": sorted(staged), + "reason": reason, + "event": "release_group_switch_failed", + } + }, + ) + self.audit( + "pipeline", "release-group", f"eod:{day}", + json.dumps( + { + "state": "failed", + "reason": reason, + "switched": [], + "force": bool(force), + }, + ensure_ascii=False, + ), + ) raise for dataset, item in staged.items(): results[dataset] = { diff --git a/xiaobai-datahub/tests/test_atomic_release.py b/xiaobai-datahub/tests/test_atomic_release.py index 8bccf7d..4274b0b 100644 --- a/xiaobai-datahub/tests/test_atomic_release.py +++ b/xiaobai-datahub/tests/test_atomic_release.py @@ -184,6 +184,13 @@ class ReleaseGroupSwitchTests(unittest.TestCase): for table in ("eod_bars", "eod_valuation", "eod_moneyflow", "eod_auction", "eod_stocks"): rows = self.db.fetchall(f"SELECT * FROM {table} WHERE trade_date = ?", (TRADE_DATE,)) self.assertEqual(rows, [], table) + audit = self.db.fetchone( + "SELECT * FROM audit_log WHERE action = 'release-group' ORDER BY id DESC" + ) + self.assertIsNotNone(audit) + detail = str(audit["detail"]) + self.assertIn("killed mid-switch", detail) + self.assertIn("failed", detail) def test_duplicate_runs_are_idempotent(self) -> None: self.pipe.run_eod_batch_a(TRADE_DATE) @@ -389,6 +396,12 @@ class ForceBoundaryEntryTests(unittest.TestCase): self.assertIn("killed mid-switch", ctx.exception.message) # previous complete A/B versions keep serving self.assertEqual(publications_map(self.db, TRADE_DATE), before) + audit = self.db.fetchone( + "SELECT * FROM audit_log WHERE action = 'release-group' ORDER BY id DESC" + ) + self.assertIsNotNone(audit) + self.assertIn("failed", str(audit["detail"])) + self.assertIn("killed mid-switch", str(audit["detail"])) if __name__ == "__main__":