@@ -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)
|
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]:
|
def _split_routes(raw: str) -> list[str]:
|
||||||
out: list[str] = []
|
out: list[str] = []
|
||||||
for part in (raw or "").replace(",", ",").replace("、", ",").split(","):
|
for part in (raw or "").replace(",", ",").replace("、", ",").split(","):
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from agent.policy.deal_followup import (
|
|||||||
leader_matches,
|
leader_matches,
|
||||||
next_followup_at,
|
next_followup_at,
|
||||||
shift_workdays,
|
shift_workdays,
|
||||||
|
workday_elapsed_hours,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +38,22 @@ class TestWorkdays(unittest.TestCase):
|
|||||||
self.assertEqual(when.tzinfo, SHANGHAI)
|
self.assertEqual(when.tzinfo, SHANGHAI)
|
||||||
self.assertEqual(when.date(), date(2026, 9, 24))
|
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):
|
class TestAirRouteKeys(unittest.TestCase):
|
||||||
def test_pair_and_dest(self) -> None:
|
def test_pair_and_dest(self) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user