Source code for passengersim.config.fares

from __future__ import annotations

import re
from typing import Any

from pydantic import BaseModel, field_serializer, field_validator


[docs] class Fare(BaseModel, extra="forbid"): """A single fare rule connecting a carrier, market, and booking class. A :class:`Fare` record defines the price and conditions under which a passenger may purchase a seat in a specific booking class on a specific carrier between an origin and destination. """ carrier: str """IATA carrier code for the airline offering this fare.""" orig: str """Origin airport or location code for this fare.""" dest: str """Destination airport or location code for this fare.""" booking_class: str """Booking class (fare class) identifier, e.g. ``"Y"``, ``"B"``, ``"Q"``.""" brand: str | None = "" """Optional brand name associated with this fare (e.g. a cabin product name). Defaults to an empty string, indicating no specific brand.""" price: float """Base price of this fare in the simulation's currency units.""" advance_purchase: int """Minimum number of days before departure that this fare must be purchased.""" restrictions: list[str] = [] """Named restrictions that apply to this fare. These are typically related to refundability, changeability, and other conditions that may apply to the fare and which may be subject to random preference variation across customers. The names may not contain pipe or slash characters, as these are used as separators when parsing from strings. The names should match those used in choice models. """ category: str | None = None """Optional category label for grouping fares (e.g. by product tier).""" cabin: str | None = "Y" """Cabin code for this fare, e.g. ``"Y"`` for economy. Defaults to ``"Y"``.""" min_stay: int = 0 """Minimum number of nights the passenger must stay at the destination. A value of ``0`` indicates no minimum stay requirement.""" saturday_night_required: bool | None = False """Whether a Saturday night stay is required to purchase this fare.""" @field_validator("restrictions", mode="before") @classmethod def _allow_unrestricted(cls, v: list[str] | None) -> Any: """Coerce ``None`` or missing restriction values to an empty list. Parameters ---------- v : list of str or None The raw value supplied for the ``restrictions`` field. Returns ------- list of str An empty list when *v* is ``None``; otherwise *v* unchanged. """ if v is None: v = [] return v @field_validator("restrictions", mode="before") @classmethod def _allow_pipe_sep(cls, v: list[str] | str) -> Any: """Parse pipe- or slash-separated restriction strings into a list. Configuration files sometimes express restrictions as a single delimited string (e.g. ``"NON_REF|NON_CHG"``). This validator splits such strings on ``|`` or ``/`` and removes empty tokens, so callers may use either the list or string form. Parameters ---------- v : list of str or str The raw value supplied for the ``restrictions`` field. When it is a :class:`str`, it is split on ``|`` or ``/``; otherwise it is returned unchanged. Returns ------- list of str The parsed list of restriction name strings. """ if isinstance(v, str): v = list(filter(None, re.split(r"[|/]", v))) return v
[docs] @field_serializer("restrictions", when_used="always") def serialize_restrictions(self, value: list[str]) -> str: """Serialize the restrictions list to a pipe-separated string. Pydantic calls this serializer whenever the model is converted to a dictionary or JSON. The resulting string is compatible with the :meth:`allow_pipe_sep` validator for round-trip fidelity. Parameters ---------- value : list of str The list of restriction name strings to serialize. Returns ------- str A single string with restriction names joined by ``"|"``, or an empty string if *value* is empty. """ return "|".join(value)
@property def market_identifier(self) -> str: """Unique identifier for the origin–destination market of this fare. Returns ------- str Identifier string in the format ``"<orig>~<dest>"``. """ return f"{self.orig}~{self.dest}"