fix: use API workdays and final delivery date in bot replies

This commit is contained in:
ar51
2026-09-20 14:28:51 +08:00
parent 24469372fb
commit 1c25c9e1d9
6 changed files with 139 additions and 207 deletions

View File

@@ -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__":