feat: convert AM516 lead times to workdays
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from datetime import date
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
@@ -103,11 +104,16 @@ class TriggerGateTests(unittest.TestCase):
|
||||
with (
|
||||
mock.patch.object(bridge, "message_text", return_value="他怎么什么消息都回复啊"),
|
||||
mock.patch.object(bridge, "build_report") as build_report,
|
||||
mock.patch.object(
|
||||
bridge,
|
||||
"reply_received_acknowledgement",
|
||||
) as acknowledge,
|
||||
mock.patch.object(bridge, "reply") as reply,
|
||||
):
|
||||
bridge.handle_message(object(), data)
|
||||
|
||||
build_report.assert_not_called()
|
||||
acknowledge.assert_not_called()
|
||||
reply.assert_not_called()
|
||||
|
||||
def test_valid_message_builds_and_replies(self) -> None:
|
||||
@@ -121,11 +127,16 @@ class TriggerGateTests(unittest.TestCase):
|
||||
"message_text",
|
||||
return_value="eRob70H50I-BHM-18CTC[V5] 10台",
|
||||
),
|
||||
mock.patch.object(
|
||||
bridge,
|
||||
"reply_received_acknowledgement",
|
||||
) as acknowledge,
|
||||
mock.patch.object(bridge, "build_report", return_value="report") as build_report,
|
||||
mock.patch.object(bridge, "reply") as reply,
|
||||
):
|
||||
bridge.handle_message("client", data)
|
||||
|
||||
acknowledge.assert_called_once_with("client", "om_valid")
|
||||
build_report.assert_called_once_with(
|
||||
bridge.RequestInput(model="eRob70H50I-BHM-18CTC[V5]", quantity=10)
|
||||
)
|
||||
@@ -177,11 +188,16 @@ class TriggerGateTests(unittest.TestCase):
|
||||
return_value="还有没有其他候选型号?",
|
||||
),
|
||||
mock.patch.object(bridge, "build_report") as build_report,
|
||||
mock.patch.object(
|
||||
bridge,
|
||||
"reply_received_acknowledgement",
|
||||
) as acknowledge,
|
||||
mock.patch.object(bridge, "reply") as reply,
|
||||
):
|
||||
bridge.handle_message("client", data)
|
||||
|
||||
build_report.assert_not_called()
|
||||
acknowledge.assert_not_called()
|
||||
reply.assert_not_called()
|
||||
|
||||
def test_follow_up_continues_from_fifth_candidate_in_same_conversation(self) -> None:
|
||||
@@ -201,6 +217,10 @@ class TriggerGateTests(unittest.TestCase):
|
||||
"build_report",
|
||||
side_effect=["initial report", "continued report"],
|
||||
) as build_report,
|
||||
mock.patch.object(
|
||||
bridge,
|
||||
"reply_received_acknowledgement",
|
||||
) as acknowledge,
|
||||
mock.patch.object(bridge, "reply") as reply,
|
||||
):
|
||||
bridge.handle_message("client", initial_data)
|
||||
@@ -210,6 +230,13 @@ class TriggerGateTests(unittest.TestCase):
|
||||
build_report.call_args_list,
|
||||
[mock.call(request), mock.call(request, candidate_offset=4)],
|
||||
)
|
||||
self.assertEqual(
|
||||
acknowledge.call_args_list,
|
||||
[
|
||||
mock.call("client", "om_initial"),
|
||||
mock.call("client", "om_follow_up"),
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
reply.call_args_list,
|
||||
[
|
||||
@@ -229,6 +256,10 @@ class TriggerGateTests(unittest.TestCase):
|
||||
"message_text",
|
||||
side_effect=[request_text, "还有没有其他候选型号?"],
|
||||
),
|
||||
mock.patch.object(
|
||||
bridge,
|
||||
"reply_received_acknowledgement",
|
||||
) as acknowledge,
|
||||
mock.patch.object(bridge, "build_report", return_value="report") as build_report,
|
||||
mock.patch.object(bridge, "reply") as reply,
|
||||
):
|
||||
@@ -238,6 +269,7 @@ class TriggerGateTests(unittest.TestCase):
|
||||
build_report.assert_called_once_with(
|
||||
bridge.RequestInput(model="eRob70H50I-BHM-18CTC[V5]", quantity=10)
|
||||
)
|
||||
acknowledge.assert_called_once_with("client", "om_small")
|
||||
reply.assert_called_once_with("client", "om_small", "report")
|
||||
|
||||
def test_expired_candidate_context_is_removed(self) -> None:
|
||||
@@ -313,5 +345,90 @@ class CandidateBatchTests(unittest.TestCase):
|
||||
self.assertNotIn(model, report)
|
||||
|
||||
|
||||
class WorkdayConversionTests(unittest.TestCase):
|
||||
def test_natural_day_conversion_rule(self) -> None:
|
||||
expected = {
|
||||
0: 0,
|
||||
1: 0,
|
||||
2: 1,
|
||||
3: 2,
|
||||
4: 3,
|
||||
5: 3,
|
||||
6: 4,
|
||||
7: 5,
|
||||
18: 13,
|
||||
23: 16,
|
||||
26: 18,
|
||||
31: 22,
|
||||
33: 23,
|
||||
36: 25,
|
||||
43: 30,
|
||||
48: 34,
|
||||
}
|
||||
|
||||
for natural_days, workdays in expected.items():
|
||||
with self.subTest(natural_days=natural_days):
|
||||
self.assertEqual(
|
||||
bridge.natural_days_to_workdays(natural_days),
|
||||
workdays,
|
||||
)
|
||||
|
||||
def test_2026_official_holiday_and_makeup_workdays(self) -> None:
|
||||
self.assertTrue(bridge.is_china_official_workday(date(2026, 9, 20)))
|
||||
self.assertFalse(bridge.is_china_official_workday(date(2026, 9, 25)))
|
||||
self.assertFalse(bridge.is_china_official_workday(date(2026, 10, 1)))
|
||||
self.assertTrue(bridge.is_china_official_workday(date(2026, 10, 10)))
|
||||
self.assertFalse(bridge.is_china_official_workday(date(2026, 10, 11)))
|
||||
|
||||
def test_workday_dates_match_validation_samples(self) -> None:
|
||||
start = date(2026, 9, 2)
|
||||
samples = {
|
||||
18: date(2026, 9, 20),
|
||||
23: date(2026, 9, 23),
|
||||
26: date(2026, 9, 28),
|
||||
16: date(2026, 9, 17),
|
||||
31: date(2026, 10, 9),
|
||||
33: date(2026, 10, 10),
|
||||
36: date(2026, 10, 13),
|
||||
43: date(2026, 10, 20),
|
||||
48: date(2026, 10, 26),
|
||||
}
|
||||
|
||||
for natural_days, expected_date in samples.items():
|
||||
with self.subTest(natural_days=natural_days):
|
||||
workdays = bridge.natural_days_to_workdays(natural_days)
|
||||
self.assertEqual(
|
||||
bridge.add_china_official_workdays(start, workdays),
|
||||
expected_date,
|
||||
)
|
||||
|
||||
def test_api_natural_days_drive_displayed_delivery_date(self) -> None:
|
||||
payload = successful_payload()
|
||||
summary = payload["data"]["交期汇总"]
|
||||
summary["AM516预测交期"] = "2026-10-03"
|
||||
summary["AM516总交期"] = 31
|
||||
|
||||
details = bridge.lead_time_details(
|
||||
payload,
|
||||
requested_quantity=10,
|
||||
calculation_date=date(2026, 9, 2),
|
||||
)
|
||||
|
||||
self.assertEqual(details["total_days"], 31)
|
||||
self.assertEqual(details["workdays"], 22)
|
||||
self.assertEqual(details["delivery_date"], "2026-10-09")
|
||||
self.assertEqual(
|
||||
details["prediction"],
|
||||
"AM516预测交期:2026-10-09(原始31个自然日,折算22个工作日)",
|
||||
)
|
||||
|
||||
def test_unconfigured_calendar_year_is_not_silently_estimated(self) -> None:
|
||||
with self.assertRaisesRegex(
|
||||
bridge.WorkdayCalendarUnavailableError,
|
||||
"2027年国家法定工作日历未配置",
|
||||
):
|
||||
bridge.add_china_official_workdays(date(2026, 12, 31), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user