test_route_evaluation_service.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. from __future__ import annotations
  2. from app.schemas.route import RouteLeg
  3. from app.schemas.selection import (
  4. RankedHotelCandidate,
  5. )
  6. from app.services.route_evaluation_service import (
  7. RouteEvaluationService,
  8. )
  9. def build_hotel(
  10. hotel_id: str,
  11. name: str,
  12. score: float,
  13. ) -> RankedHotelCandidate:
  14. return RankedHotelCandidate(
  15. hotel_id=hotel_id,
  16. score=score,
  17. reasons=[],
  18. warnings=[],
  19. hotel={
  20. "hotel_id": hotel_id,
  21. "name": name,
  22. },
  23. )
  24. def test_central_hotel_ranks_first() -> None:
  25. service = RouteEvaluationService()
  26. hotels = [
  27. build_hotel(
  28. "central",
  29. "中心酒店",
  30. 80,
  31. ),
  32. build_hotel(
  33. "remote",
  34. "远郊酒店",
  35. 82,
  36. ),
  37. ]
  38. legs_by_hotel = {
  39. "central": [
  40. RouteLeg(
  41. origin_name="中心酒店",
  42. origin_location=(
  43. "104.0700,30.6700"
  44. ),
  45. destination_name="景点A",
  46. destination_location=(
  47. "104.0800,30.6800"
  48. ),
  49. distance_meters=2000,
  50. duration_seconds=600,
  51. ),
  52. RouteLeg(
  53. origin_name="中心酒店",
  54. origin_location=(
  55. "104.0700,30.6700"
  56. ),
  57. destination_name="景点B",
  58. destination_location=(
  59. "104.0900,30.6900"
  60. ),
  61. distance_meters=3000,
  62. duration_seconds=900,
  63. ),
  64. ],
  65. "remote": [
  66. RouteLeg(
  67. origin_name="远郊酒店",
  68. origin_location=(
  69. "104.2000,30.8000"
  70. ),
  71. destination_name="景点A",
  72. destination_location=(
  73. "104.0800,30.6800"
  74. ),
  75. distance_meters=15000,
  76. duration_seconds=2400,
  77. ),
  78. RouteLeg(
  79. origin_name="远郊酒店",
  80. origin_location=(
  81. "104.2000,30.8000"
  82. ),
  83. destination_name="景点B",
  84. destination_location=(
  85. "104.0900,30.6900"
  86. ),
  87. distance_meters=18000,
  88. duration_seconds=3000,
  89. ),
  90. ],
  91. }
  92. result = service.rank_hotels(
  93. hotels,
  94. legs_by_hotel,
  95. {
  96. "central": None,
  97. "remote": None,
  98. },
  99. require_near_subway=False,
  100. )
  101. assert result[0].hotel_id == "central"
  102. assert (
  103. result[0].average_distance_meters
  104. == 2500
  105. )
  106. def test_subway_distance_affects_score() -> None:
  107. service = RouteEvaluationService()
  108. hotels = [
  109. build_hotel(
  110. "near-subway",
  111. "地铁酒店",
  112. 80,
  113. ),
  114. build_hotel(
  115. "far-subway",
  116. "远离地铁酒店",
  117. 80,
  118. ),
  119. ]
  120. same_legs = [
  121. RouteLeg(
  122. origin_name="酒店",
  123. origin_location="104.0,30.0",
  124. destination_name="景点",
  125. destination_location=(
  126. "104.1,30.1"
  127. ),
  128. distance_meters=5000,
  129. )
  130. ]
  131. result = service.rank_hotels(
  132. hotels,
  133. {
  134. "near-subway": same_legs,
  135. "far-subway": same_legs,
  136. },
  137. {
  138. "near-subway": 300,
  139. "far-subway": 1800,
  140. },
  141. require_near_subway=True,
  142. )
  143. assert (
  144. result[0].hotel_id
  145. == "near-subway"
  146. )
  147. def test_haversine_fallback() -> None:
  148. service = RouteEvaluationService()
  149. leg = service.build_haversine_leg(
  150. origin_name="酒店",
  151. origin_location="104.0600,30.6700",
  152. destination_name="景点",
  153. destination_location=(
  154. "104.0700,30.6800"
  155. ),
  156. )
  157. assert leg.distance_meters > 0
  158. assert leg.source == "haversine_fallback"
  159. assert leg.duration_seconds is None