| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- from __future__ import annotations
- """酒店查询与结果模型:HotelSearchQuery查询参数与HotelOption搜索结果。"""
- from datetime import date
- from typing import Literal
- from pydantic import field_validator,Field,model_validator
- from app.schemas.common import SourceMetadata,AppModel
- HotelSort = Literal["lowest_price","highest_price","most_reviewed"]
- HotelRatingFilter = Literal["3.5+","4.0+","4.5+"]
- HotelClass = Literal[2,3,4,5]
- class HotelSearchQuery(AppModel):
- """酒店查询参数"""
- query:str
- check_in_date:date
- check_out_date:date
- adults:int = Field(default=1, ge=1, le=9)
- # SerpApi要求提供儿童年龄,而不只是儿童人数。
- children_ages:list[int] = Field(default_factory=list)
-
- currency:str = "CNY"
- language:str = "zh-cn"
- country:str = "cn"
- # 以下全部为可选条件
- min_price:int | None = Field(default=None,gt=0)
- max_price:int | None = Field(default=None,gt=0)
- rating_filter:HotelRatingFilter | None=None
- hotel_class:list[HotelClass] = Field(default_factory=list)
- free_cancellation:bool | None=None
- sort:HotelSort | None=None
- no_cache:bool = False
- @field_validator("query")
- @classmethod
- def validate_query(cls,value:str)->str:
- if not value.strip():
- raise ValueError("酒店搜索关键词不能为空")
- return value.strip()
-
- @field_validator("currency")
- @classmethod
- def normalize_currency(cls,value:str)->str:
- return value.upper()
-
- @field_validator("children_ages")
- @classmethod
- def validate_children_ages(cls,values:list[int])->list[int]:
- for age in values:
- if age < 1 or age > 17:
- raise ValueError("儿童年龄必须在1-17岁")
- return values
-
- @model_validator(mode="after")
- def validate_dates_and_prices(self)->"HotelSearchQuery":
- if self.check_out_date <= self.check_in_date:
- raise ValueError("退房日期必须晚于入住日期")
- if (
- self.min_price is not None
- and self.max_price is not None
- and self.min_price > self.max_price
- ):
- raise ValueError("最低价格不能高于最高价格")
- return self
-
- class GeoPoint(AppModel):
- """地理坐标"""
- latitude:float
- longitude:float
- class HotelPriceOffer(AppModel):
- """某个酒店供应商给出的价格。"""
- source:str | None=None
- price_per_night:float | None=Field(default=None,gt=0)
- total_price:float | None=Field(default=None,gt=0)
- free_cancellation:bool | None=None
- class HotelOption(AppModel):
- """一个标准化酒店候选。"""
- hotel_id:str
- provider_rank:int = Field(ge=0)
- name:str
- property_type:str | None=None
- description:str | None=None
- coordinates:GeoPoint | None=None
- check_in_time:str | None=None
- check_out_time:str | None=None
- hotel_class:int | None=Field(default=None,ge=1,le=5)
- overall_rating:float | None=Field(default=None,ge=0,le=5)
- review_count:int | None=Field(default=None,ge=0)
- location_rating:float | None=Field(default=None,ge=0)
- price_per_night:float | None=Field(default=None,gt=0)
- total_price:float | None=Field(default=None,gt=0)
- currency:str = "CNY"
- amenities:list[str] = Field(default_factory=list)
- excluded_amenities:list[str] = Field(default_factory=list)
- free_cancellation:bool | None=None
- sponsored:bool = False
- eco_certified:bool | None=None
- property_token:str | None=None
- thumbnail_url:str | None=None
- price_offers:list[HotelPriceOffer] = Field(default_factory=list)
- # 新增这两个字段,用于保存接口返回的优惠信息
- deal: str | None = None
- deal_description: str | None = None
- class HotelSearchResult(AppModel):
- """酒店搜索结果"""
- query:HotelSearchQuery
- hotels:list[HotelOption] = Field(default_factory=list)
- total_count:int = Field(default=0,ge=0)
- source:SourceMetadata
- next_page_token:str | None=None
- warnings:list[str] = Field(default_factory=list)
-
|