test_market_engine.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import json
  2. from pathlib import Path
  3. import unittest
  4. from app.market import MarketService
  5. from app.market.models import InquiryRequest
  6. from app.repository import RDFRepository
  7. ROOT = Path(__file__).resolve().parents[1]
  8. HAIER = "https://gesli.example/sample#Haier"
  9. HISENSE = "https://gesli.example/sample#Hisense"
  10. INDIA_RETAIL = "https://gesli.example/sample#IndiaMockRetail"
  11. class MarketEngineTest(unittest.TestCase):
  12. def setUp(self):
  13. self.service = MarketService(ROOT, RDFRepository(ROOT))
  14. def test_default_inquiry_builds_three_ranked_solutions(self):
  15. result = self.service.recommend(InquiryRequest(customer=HAIER))
  16. self.assertEqual(
  17. result["pipeline"],
  18. {
  19. "discovered": 9,
  20. "rejected": 6,
  21. "feasible": 3,
  22. "generated_solutions": 3,
  23. },
  24. )
  25. self.assertEqual(result["recommendation"]["solution_type"], "方案优选")
  26. self.assertIn("方案优选", {candidate["solution_type"] for candidate in result["candidates"]})
  27. self.assertIn("relationship", result["candidates"][0])
  28. self.assertEqual(result["weights"]["price"], 0.03)
  29. self.assertEqual(result["weights"]["spot_space"], 0.15)
  30. def test_form_only_exposes_routes_with_market_context(self):
  31. options = self.service.form_options()
  32. self.assertEqual(options["origins"], ["青岛港", "青岛胶东机场"])
  33. self.assertEqual(
  34. options["destinations"],
  35. ["印度那瓦舍瓦港", "印度金奈港", "汉堡港", "法兰克福机场", "洛杉矶港"],
  36. )
  37. self.assertEqual(options["route_types"], ["中转", "直航", "直达"])
  38. self.assertEqual(len(options["routes"]), 5)
  39. def test_customer_profile_changes_recommendation(self):
  40. haier = self.service.recommend(InquiryRequest(customer=HAIER))
  41. hisense = self.service.recommend(InquiryRequest(customer=HISENSE))
  42. self.assertEqual(haier["recommendation"]["solution_type"], "方案优选")
  43. self.assertEqual(hisense["recommendation"]["solution_type"], "方案优选")
  44. self.assertNotEqual(
  45. haier["recommendation"]["final_quote_amount"],
  46. hisense["recommendation"]["final_quote_amount"],
  47. )
  48. def test_expired_quotes_are_rejected_with_reasons(self):
  49. result = self.service.recommend(InquiryRequest(customer=HAIER))
  50. rejected = {item["quote_no"]: item for item in result["rejected_candidates"]}
  51. self.assertIn("SQ-EGL-20260710-EXPIRED", rejected)
  52. self.assertTrue(
  53. any("报价已于2026-07-18失效" in reason for reason in rejected["SQ-EGL-20260710-EXPIRED"]["reasons"])
  54. )
  55. def test_dangerous_goods_uses_only_specialized_candidates(self):
  56. inquiry = InquiryRequest(
  57. customer="https://gesli.example/sample#Sailun",
  58. dangerous_goods=True,
  59. )
  60. result = self.service.recommend(inquiry)
  61. self.assertEqual(result["pipeline"]["feasible"], 3)
  62. self.assertTrue(
  63. all("危险品" in candidate["product_name"] for candidate in result["candidates"])
  64. )
  65. def test_hamburg_route_builds_three_solutions(self):
  66. result = self.service.recommend(InquiryRequest(
  67. customer="https://gesli.example/sample#Midea",
  68. destination="汉堡港",
  69. requested_departure_date="2026-07-28",
  70. max_transit_days=36,
  71. target_amount=42500,
  72. ))
  73. self.assertEqual(result["pipeline"]["feasible"], 3)
  74. self.assertEqual(result["pipeline"]["rejected"], 1)
  75. def test_time_sensitive_air_customer_gets_express_solution(self):
  76. result = self.service.recommend(InquiryRequest(
  77. customer="https://gesli.example/sample#QingdaoMedTech",
  78. origin="青岛胶东机场",
  79. destination="法兰克福机场",
  80. requested_departure_date="2026-07-27",
  81. max_transit_days=6,
  82. target_amount=68000,
  83. preferred_route_type="直达",
  84. ))
  85. self.assertEqual(result["recommendation"]["supplier_name"], "汉莎货运青岛代理")
  86. def test_india_solution_and_relationship_outweigh_lowest_purchase_price(self):
  87. result = self.service.recommend(InquiryRequest(
  88. customer=INDIA_RETAIL,
  89. destination="印度那瓦舍瓦港",
  90. requested_departure_date="2026-07-28",
  91. max_transit_days=30,
  92. target_amount=46000,
  93. ))
  94. top = result["candidates"][0]
  95. lowest_price = max(result["candidates"], key=lambda item: item["price_score"])
  96. self.assertEqual(top["solution_type"], "方案优选")
  97. self.assertEqual(top["supplier_name"], "印度航线Mock船公司直营")
  98. self.assertLess(top["price_score"], lowest_price["price_score"])
  99. self.assertGreater(
  100. top["relationship_advantage_score"],
  101. lowest_price["relationship_advantage_score"],
  102. )
  103. self.assertEqual(top["relationship"]["tier"], "战略合作")
  104. self.assertEqual(top["relationship"]["supplier_name"], "印度航线Mock船公司直营")
  105. self.assertEqual(top["relationship"]["supplier_channel"], "船公司直营")
  106. self.assertEqual(top["spot_space"]["status"], "可立即订舱")
  107. self.assertEqual(top["spot_space"]["teu"], 18)
  108. self.assertGreater(top["spot_space_score"], lowest_price["spot_space_score"])
  109. def test_sales_response_does_not_expose_supplier_cost(self):
  110. result = self.service.recommend(InquiryRequest(customer=HAIER))
  111. serialized = json.dumps(result, ensure_ascii=False)
  112. self.assertNotIn("supplier_cost", serialized)
  113. self.assertNotIn("supplierCostAmount", serialized)
  114. self.assertIn("cost_risk_status", serialized)
  115. def test_decision_audit_is_recorded_as_rdf_assertions(self):
  116. result = self.service.recommend(InquiryRequest(customer=HAIER))
  117. audit = self.service.get_audit(result["run_id"])
  118. self.assertIsNotNone(audit)
  119. self.assertEqual(audit["candidate_count"], 3)
  120. self.assertGreaterEqual(audit["rdf_assertions"], 20)
  121. self.assertIn("business_explanation", audit)
  122. self.assertIn("现舱", audit["business_explanation"]["selection_basis"])
  123. if __name__ == "__main__":
  124. unittest.main()