From ef0424a28f46a4bad1a05dff1395ca32b69311de Mon Sep 17 00:00:00 2001 From: jillion <439912596@qq.com> Date: Mon, 21 Sep 2026 18:14:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BA=8B=E4=BB=B6=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E6=8C=89=E5=B7=A5=E4=BD=9C=E6=97=A5=E7=B4=AF=E8=AE=A1=E5=B7=B2?= =?UTF-8?q?=E8=BF=87=E5=B0=8F=E6=97=B6=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- inquiry-agent/agent/policy/deal_followup.py | 25 +++++++++++++++++++++ inquiry-agent/tests/test_deal_followup.py | 17 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/inquiry-agent/agent/policy/deal_followup.py b/inquiry-agent/agent/policy/deal_followup.py index d9f9863..f7d2783 100644 --- a/inquiry-agent/agent/policy/deal_followup.py +++ b/inquiry-agent/agent/policy/deal_followup.py @@ -2,6 +2,7 @@ 成交跟进纯规则:工作日顺延、线路对负责人。 本文件职责:给私聊/群聊成交共用,不发企微、不写主账。 +也给事件异常算工作日已过小时数(非工作日整天不算;工作日当天 0–24 都算)。 禁止:在回调线程算完还同步推负责人;把六态写进本文件。 """ @@ -105,6 +106,30 @@ def next_followup_at(now: datetime | None = None) -> datetime: return datetime.combine(day, time(9, 0), tzinfo=SHANGHAI) +def workday_elapsed_hours(start: datetime, now: datetime) -> float: + """ + 起止之间折合多少工作日小时。 + + 非工作日整天不算;工作日当天 0–24 都算,不切上午晚上。 + 起点落在休息日:从下一个工作日 00:00 起算。 + 副作用:无。可在 Worker 巡检调用,禁止放进企微回调线程做 IO。 + """ + a = start.astimezone(SHANGHAI) if start.tzinfo else start.replace(tzinfo=SHANGHAI) + b = now.astimezone(SHANGHAI) if now.tzinfo else now.replace(tzinfo=SHANGHAI) + if b <= a: + return 0.0 + total = 0.0 + cur = a + while cur.date() < b.date(): + nxt = datetime.combine(cur.date() + timedelta(days=1), time(0, 0), tzinfo=SHANGHAI) + if is_workday(cur.date()): + total += (nxt - cur).total_seconds() / 3600.0 + cur = nxt + if is_workday(b.date()): + total += (b - cur).total_seconds() / 3600.0 + return total + + def _split_routes(raw: str) -> list[str]: out: list[str] = [] for part in (raw or "").replace(",", ",").replace("、", ",").split(","): diff --git a/inquiry-agent/tests/test_deal_followup.py b/inquiry-agent/tests/test_deal_followup.py index c9f55cc..2a92024 100644 --- a/inquiry-agent/tests/test_deal_followup.py +++ b/inquiry-agent/tests/test_deal_followup.py @@ -14,6 +14,7 @@ from agent.policy.deal_followup import ( leader_matches, next_followup_at, shift_workdays, + workday_elapsed_hours, ) @@ -37,6 +38,22 @@ class TestWorkdays(unittest.TestCase): self.assertEqual(when.tzinfo, SHANGHAI) self.assertEqual(when.date(), date(2026, 9, 24)) + def test_skip_weekend_keep_weekday_night(self) -> None: + start = datetime(2026, 9, 18, 17, 0, tzinfo=SHANGHAI) # 周五 + now = datetime(2026, 9, 21, 17, 0, tzinfo=SHANGHAI) # 周一 + # 周五 17–24 = 7h,周末不算,周一 0–17 = 17h + self.assertAlmostEqual(workday_elapsed_hours(start, now), 24.0, places=4) + + def test_rest_day_start_counts_from_next_workday(self) -> None: + start = datetime(2026, 9, 19, 10, 0, tzinfo=SHANGHAI) # 周六 + now = datetime(2026, 9, 21, 10, 0, tzinfo=SHANGHAI) # 周一 + self.assertAlmostEqual(workday_elapsed_hours(start, now), 10.0, places=4) + + def test_fractional_quarter_hour(self) -> None: + start = datetime(2026, 9, 18, 9, 0, tzinfo=SHANGHAI) + now = datetime(2026, 9, 18, 9, 15, tzinfo=SHANGHAI) + self.assertAlmostEqual(workday_elapsed_hours(start, now), 0.25, places=4) + class TestAirRouteKeys(unittest.TestCase): def test_pair_and_dest(self) -> None: