| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- from __future__ import annotations
- """航班查询与结果模型:FlightSearchQuery查询参数与FlightOption搜索结果。"""
- from datetime import datetime,date
- from typing import Literal
- from pydantic import field_validator,Field,model_validator
- from app.schemas.common import SourceMetadata,AppModel
- FlightType = Literal["round_trip","one_way"]
- TravelClass = Literal["economy","business","first","premium_economy"]
- FlightSort = Literal["top","price","departure_time","arrival_time","duration","emissions"]
- StopsFilter = Literal["any","nonstop","one_or_fewer","two_or_fewer"]
- class FlightSearchQuery(AppModel):
- """
- 航班查询参数
- 当前所有筛选条件默认不启用。
- 后续由需求解析agent根据用户真实需求赋值
- """
- departure_airports:list[str] = Field(min_length=1)
- arrival_airports:list[str] = Field(min_length=1)
- outbound_date:date
- return_date:date | None = None
- flight_type:FlightType="one_way"
- adults:int = Field(default=1, ge=1, le=9)
- children:int = Field(default=0, ge=0, le=8)
- travel_class:TravelClass="economy"
- currency:str = "CNY"
- # 以下全部是可选条件,不设置就不限制
- sort_by:FlightSort | None=None
- stops:StopsFilter | None=None
- max_price:int | None = Field(default=None,gt=0)
- # SerpApi 时间范围格式,例如:
- # "9,20" 或 "9,20,10,22"
- outbound_times:str | None=None
- language:str = "zh-cn"
- country:str = "cn"
- no_cache:bool = False
- # 是否加载 Google Flights 的“查看更多航班”
- show_hidden: bool = False
- # 是否启用更完整但响应更慢的深度查询
- deep_search: bool = False
- @field_validator("departure_airports","arrival_airports")
- @classmethod
- def normalize_airports(cls,value:list[str])->list[str]:
- """机场代码统一转为大写并去重"""
- normalized:list[str] = []
- for item in value:
- code = item.strip().upper()
- if not code:
- continue
- if code not in normalized:
- normalized.append(code)
- if not normalized:
- raise ValueError("机场代码不能为空")
- return normalized
-
- @field_validator("currency")
- @classmethod
- def normalize_currency(cls,value:str)->str:
- return value.upper()
-
- @model_validator(mode="after")
- def validate_dates(self)->"FlightSearchQuery":
- if self.flight_type == "round_trip":
- if self.return_date is None:
- raise ValueError("往返航班必须提供return_date。")
- if self.return_date <= self.outbound_date:
- raise ValueError("返程日期必须晚于出发日期。")
- return self
-
- class FlightSegment(AppModel):
- """一个具体航段,中转航班会包含FlightSegment。"""
- flight_number:str | None=None
- airline:str | None=None
- departure_airport_code:str
- departure_airport_name:str | None=None
- departure_time:datetime
- arrival_airport_code:str
- arrival_airport_name:str | None=None
- arrival_time:datetime
- duration_minutes:int | None=Field(default=None,ge=0)
- airplane:str | None=None
- travel_class:str | None=None
- legroom:str | None=None
- overnight:bool = False
- often_delayed_by_over_30_min:bool = False
- planed_and_crew_by:str | None=None
- extensions:list[str] = Field(default_factory=list)
- ticket_also_sold_by:list[str] = Field(default_factory=list)
- class Layover(AppModel):
- """经停或中转信息。"""
- airport_code:str
- airport_name:str | None=None
- duration_minutes:int | None=Field(default=None,ge=0)
- overnight:bool = False
- class CarbonEmissions(AppModel):
- """航班碳排放数据。"""
- this_flight_grams:int | None=None
- typical_for_route_grams:int | None=None
- difference_percent:int | None=None
- # SerpApi 返回的航班分组:best_flights / other_flights 等
- FlightSourceGroup = Literal["best","other","best_flights","other_flights"]
- class FlightOption(AppModel):
- """一个完整的去程或者返程候选方案。"""
- option_id:str
- source_group:FlightSourceGroup
- provider_rank:int = Field(ge=0)
- segments:list[FlightSegment] = Field(min_length=1)
- layovers:list[Layover] = Field(default_factory=list)
- departure_airport_code:str
- final_arrival_airport_code:str
- departure_time:datetime
- arrival_time:datetime
- total_duration_minutes:int | None=Field(default=None,ge=0)
- stop_count:int = Field(default=0,ge=0)
- price:int | None=Field(default=None,gt=0)
- currency:str = "CNY"
- flight_type:str | None=None
- airlines:list[str] = Field(default_factory=list)
- is_overnight:bool = False
- carbon_emissions:CarbonEmissions | None=None
- # 往返查询选择去程后,用于继续查询返程
- departure_token:str | None=None
- # 获取预定选项是可能使用
- booking_token:str | None=None
- class FlightSearchResult(AppModel):
- """标准化后的航班查询结果。"""
-
- query:FlightSearchQuery
- flights:list[FlightOption] = Field(default_factory=list)
- total_count:int = Field(default=0,ge=0)
- source:SourceMetadata
- price_insights:dict | None=None
- warnings:list[str] = Field(default_factory=list)
-
|