fix: use API workdays and final delivery date in bot replies
This commit is contained in:
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from datetime import date
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
@@ -34,8 +33,9 @@ def successful_payload() -> dict[str, object]:
|
||||
"data": {
|
||||
"时间戳": "2026-08-31 12:00:00",
|
||||
"交期汇总": {
|
||||
"AM516预测交期": "2026-09-01",
|
||||
"AM516总交期": 1,
|
||||
"AM516工作日交期": 1,
|
||||
"AM516最终交货日期": "2026-09-01",
|
||||
"AM516主导因素": "现货",
|
||||
},
|
||||
"库存信息": {"可用库存(扣除了待产数量)": 100},
|
||||
@@ -437,89 +437,120 @@ 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:
|
||||
class ApiWorkdayOutputTests(unittest.TestCase):
|
||||
def test_api_workdays_and_date_override_legacy_fields(self) -> None:
|
||||
payload = successful_payload()
|
||||
summary = payload["data"]["交期汇总"]
|
||||
summary["AM516预测交期"] = "2026-10-03"
|
||||
summary["AM516总交期"] = 31
|
||||
summary["AM516工作日交期"] = 8
|
||||
summary["AM516最终交货日期"] = "2026-10-08"
|
||||
|
||||
details = bridge.lead_time_details(
|
||||
payload,
|
||||
requested_quantity=10,
|
||||
calculation_date=date(2026, 9, 2),
|
||||
)
|
||||
details = bridge.lead_time_details(payload, requested_quantity=10)
|
||||
|
||||
self.assertEqual(details["total_days"], 31)
|
||||
self.assertEqual(details["workdays"], 22)
|
||||
self.assertEqual(details["delivery_date"], "2026-10-09")
|
||||
self.assertEqual(details["workdays"], 8)
|
||||
self.assertEqual(details["delivery_date"], "2026-10-08")
|
||||
self.assertEqual(
|
||||
details["prediction"],
|
||||
"AM516预测交期:2026-10-09(原始31个自然日,折算22个工作日)",
|
||||
"AM516预测交期:8个工作日,最终交货日期:2026-10-08",
|
||||
)
|
||||
|
||||
def test_unconfigured_calendar_year_is_not_silently_estimated(self) -> None:
|
||||
with self.assertRaisesRegex(
|
||||
bridge.WorkdayCalendarUnavailableError,
|
||||
"2027年国家法定工作日历未配置",
|
||||
def test_new_fields_work_without_natural_days_or_local_calendar(self) -> None:
|
||||
payload = successful_payload()
|
||||
summary = payload["data"]["交期汇总"]
|
||||
del summary["AM516总交期"]
|
||||
summary["AM516工作日交期"] = 8
|
||||
summary["AM516最终交货日期"] = "2027-01-15"
|
||||
|
||||
details = bridge.lead_time_details(payload, requested_quantity=10)
|
||||
|
||||
self.assertTrue(details["has_lead_time"])
|
||||
self.assertEqual(details["workdays"], 8)
|
||||
self.assertEqual(details["delivery_date"], "2027-01-15")
|
||||
|
||||
def test_zero_workdays_are_preserved(self) -> None:
|
||||
payload = successful_payload()
|
||||
payload["data"]["交期汇总"]["AM516工作日交期"] = 0
|
||||
|
||||
details = bridge.lead_time_details(payload, requested_quantity=10)
|
||||
|
||||
self.assertTrue(details["has_lead_time"])
|
||||
self.assertEqual(
|
||||
details["prediction_value"], "0个工作日,最终交货日期:2026-09-01"
|
||||
)
|
||||
|
||||
def test_missing_new_fields_do_not_fall_back_to_old_fields(self) -> None:
|
||||
for field in ("AM516工作日交期", "AM516最终交货日期"):
|
||||
with self.subTest(field=field):
|
||||
payload = successful_payload()
|
||||
summary = payload["data"]["交期汇总"]
|
||||
summary["AM516预测交期"] = "2026-09-01"
|
||||
summary["总交期"] = 15
|
||||
del summary[field]
|
||||
|
||||
details = bridge.lead_time_details(payload, requested_quantity=10)
|
||||
|
||||
self.assertFalse(details["has_lead_time"])
|
||||
self.assertIsNone(details["prediction_value"])
|
||||
self.assertIn("未返回有效工作日交期和最终交货日期", details["prediction"])
|
||||
|
||||
def test_invalid_workdays_and_dates_are_not_displayed_as_valid(self) -> None:
|
||||
invalid_values = {
|
||||
"AM516工作日交期": [None, True, -1, 1.5, "8", float("nan"), float("inf")],
|
||||
"AM516最终交货日期": [None, True, 20261008, "", "2026-02-30", "20261008"],
|
||||
}
|
||||
for field, values in invalid_values.items():
|
||||
for value in values:
|
||||
with self.subTest(field=field, value=value):
|
||||
payload = successful_payload()
|
||||
payload["data"]["交期汇总"][field] = value
|
||||
|
||||
details = bridge.lead_time_details(payload, requested_quantity=10)
|
||||
|
||||
self.assertFalse(details["has_lead_time"])
|
||||
self.assertIsNone(details["prediction_value"])
|
||||
|
||||
def test_report_and_record_use_api_workdays_for_fastest_candidate(self) -> None:
|
||||
request = bridge.RequestInput(model="eRob70H50I-FS-18CN[V5]", quantity=10)
|
||||
fastest_model = bridge.generate_candidate_specs(request.model)[0]["model"]
|
||||
|
||||
def query(query_request: bridge.RequestInput) -> dict[str, object]:
|
||||
payload = successful_payload()
|
||||
summary = payload["data"]["交期汇总"]
|
||||
fastest = query_request.model == fastest_model
|
||||
# Deliberately disagree with natural-day ranking to detect old conversion.
|
||||
summary["AM516总交期"] = 31 if fastest else 1
|
||||
summary["AM516工作日交期"] = 8 if fastest else 18
|
||||
summary["AM516最终交货日期"] = "2026-10-08" if fastest else "2026-10-21"
|
||||
return payload
|
||||
|
||||
with (
|
||||
mock.patch.object(bridge, "run_controlled_query", side_effect=query),
|
||||
mock.patch.object(bridge, "write_record") as write_record,
|
||||
):
|
||||
bridge.add_china_official_workdays(date(2026, 12, 31), 1)
|
||||
report = bridge.build_report(request)
|
||||
|
||||
record = bridge.render_record(*write_record.call_args.args)
|
||||
self.assertIn(f"最快候选型号:{fastest_model}", report)
|
||||
self.assertIn("最快候选交期:8个工作日,最终交货日期:2026-10-08", report)
|
||||
for output in (report, record):
|
||||
self.assertIn("8个工作日,最终交货日期:2026-10-08", output)
|
||||
self.assertIn("18个工作日,最终交货日期:2026-10-21", output)
|
||||
self.assertNotIn("自然日", output)
|
||||
self.assertNotIn("折算", output)
|
||||
|
||||
def test_baseline_missing_new_fields_stops_before_recording(self) -> None:
|
||||
payload = successful_payload()
|
||||
del payload["data"]["交期汇总"]["AM516工作日交期"]
|
||||
request = bridge.RequestInput(model="eRob70H50I-FS-18CN[V5]", quantity=10)
|
||||
with (
|
||||
mock.patch.object(bridge, "run_controlled_query", return_value=payload),
|
||||
mock.patch.object(bridge, "write_record") as write_record,
|
||||
):
|
||||
with self.assertRaisesRegex(RuntimeError, "工作日交期和最终交货日期"):
|
||||
bridge.build_report(request)
|
||||
write_record.assert_not_called()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user