from __future__ import annotations from app.schemas.route import RouteLeg from app.schemas.selection import ( RankedHotelCandidate, ) from app.services.route_evaluation_service import ( RouteEvaluationService, ) def build_hotel( hotel_id: str, name: str, score: float, ) -> RankedHotelCandidate: return RankedHotelCandidate( hotel_id=hotel_id, score=score, reasons=[], warnings=[], hotel={ "hotel_id": hotel_id, "name": name, }, ) def test_central_hotel_ranks_first() -> None: service = RouteEvaluationService() hotels = [ build_hotel( "central", "中心酒店", 80, ), build_hotel( "remote", "远郊酒店", 82, ), ] legs_by_hotel = { "central": [ RouteLeg( origin_name="中心酒店", origin_location=( "104.0700,30.6700" ), destination_name="景点A", destination_location=( "104.0800,30.6800" ), distance_meters=2000, duration_seconds=600, ), RouteLeg( origin_name="中心酒店", origin_location=( "104.0700,30.6700" ), destination_name="景点B", destination_location=( "104.0900,30.6900" ), distance_meters=3000, duration_seconds=900, ), ], "remote": [ RouteLeg( origin_name="远郊酒店", origin_location=( "104.2000,30.8000" ), destination_name="景点A", destination_location=( "104.0800,30.6800" ), distance_meters=15000, duration_seconds=2400, ), RouteLeg( origin_name="远郊酒店", origin_location=( "104.2000,30.8000" ), destination_name="景点B", destination_location=( "104.0900,30.6900" ), distance_meters=18000, duration_seconds=3000, ), ], } result = service.rank_hotels( hotels, legs_by_hotel, { "central": None, "remote": None, }, require_near_subway=False, ) assert result[0].hotel_id == "central" assert ( result[0].average_distance_meters == 2500 ) def test_subway_distance_affects_score() -> None: service = RouteEvaluationService() hotels = [ build_hotel( "near-subway", "地铁酒店", 80, ), build_hotel( "far-subway", "远离地铁酒店", 80, ), ] same_legs = [ RouteLeg( origin_name="酒店", origin_location="104.0,30.0", destination_name="景点", destination_location=( "104.1,30.1" ), distance_meters=5000, ) ] result = service.rank_hotels( hotels, { "near-subway": same_legs, "far-subway": same_legs, }, { "near-subway": 300, "far-subway": 1800, }, require_near_subway=True, ) assert ( result[0].hotel_id == "near-subway" ) def test_haversine_fallback() -> None: service = RouteEvaluationService() leg = service.build_haversine_leg( origin_name="酒店", origin_location="104.0600,30.6700", destination_name="景点", destination_location=( "104.0700,30.6800" ), ) assert leg.distance_meters > 0 assert leg.source == "haversine_fallback" assert leg.duration_seconds is None