diff --git a/camply/containers/gtc_api_responses.py b/camply/containers/gtc_api_responses.py index 0ba0d33f..5196910b 100644 --- a/camply/containers/gtc_api_responses.py +++ b/camply/containers/gtc_api_responses.py @@ -18,3 +18,4 @@ class ResourceLocation(CamplyModel): resource_location_id: Optional[int] resource_location_name: str region_name: str + root_map_id: Optional[int] diff --git a/camply/containers/search_model.py b/camply/containers/search_model.py index 34414e18..27709e7e 100644 --- a/camply/containers/search_model.py +++ b/camply/containers/search_model.py @@ -56,6 +56,12 @@ class YamlSearchFile(CamplyModel): equipment: ArrayOrSingleEquipment = None offline_search: bool = False offline_search_path: Optional[str] = None + attribute_filters: Optional[List[dict]] = Field( + default=None, + description="GoingToCamp site attribute filters. Format: " + "[{attributeDefinitionId: -32767, enumValues: [3]}]. " + "Common: -32767 (Electric: 0=None, 1=15A, 2=20A, 3=30A, 4=50A)", + ) @validator("provider", pre=True) def validate_provider(cls, value): diff --git a/camply/providers/going_to_camp/going_to_camp_provider.py b/camply/providers/going_to_camp/going_to_camp_provider.py index 506a70ff..0d926ef1 100644 --- a/camply/providers/going_to_camp/going_to_camp_provider.py +++ b/camply/providers/going_to_camp/going_to_camp_provider.py @@ -166,9 +166,19 @@ def get_site_details(self, rec_area_id: int, resource_id: int): ) attribute_details = self._attribute_details - site_details = self._api_request( - rec_area_id, "SITE_DETAILS", {"resourceId": resource_id} - ) + try: + site_details = self._api_request( + rec_area_id, "SITE_DETAILS", {"resourceId": resource_id} + ) + except ConnectionError: + return { + "resourceId": resource_id, + "localizedValues": [{"name": f"Site {resource_id}"}], + "minCapacity": 1, + "maxCapacity": 1, + "definedAttributes": [], + "site_attributes": {}, + } site_attributes = {} for attribute in site_details["definedAttributes"]: attribute_detail = attribute_details[ @@ -387,6 +397,7 @@ def _filter_facilities_responses( resource_categories=facil.get("resourceCategoryIds"), resource_location_id=facil.get("resourceLocationId"), resource_location_name=location_name, + root_map_id=facil.get("rootMapId"), ) except ValidationError as ve: logger.error("That doesn't look like a valid Campground Facility") @@ -424,10 +435,11 @@ def _process_facilities_responses( ------- Tuple[dict, CampgroundFacility] """ - self.campground_details[facility.resource_location_id] - facility.id = _fetch_nested_key( - self.campground_details, facility.resource_location_id, "mapId" - ) + facility.id = facility.root_map_id + if facility.id is None: + facility.id = _fetch_nested_key( + self.campground_details, facility.resource_location_id, "mapId" + ) if facility.region_name: formatted_recreation_area = ( f"{rec_area.recreation_area}, {facility.region_name}" @@ -491,6 +503,7 @@ def list_site_availability( start_date: datetime.date, end_date: datetime.date, equipment_type_id: Optional[str], + attribute_filters: Optional[List[Dict[str, Any]]] = None, ) -> List[AvailableResource]: """ Retrieve the Availability for all Sites in a Camp Area @@ -498,11 +511,38 @@ def list_site_availability( Sites are filtered on the provided date range and compatible equipment. + Parameters + ---------- + campground: CampgroundFacility + The campground to search + start_date: datetime.date + Start date of the search window + end_date: datetime.date + End date of the search window + equipment_type_id: Optional[str] + Equipment type ID to filter by + attribute_filters: Optional[List[Dict[str, Any]]] + List of attribute filters in format: + [{"attributeDefinitionId": -32767, "enumValues": [3]}] + Use `camply equipment-types` to find attribute IDs. + Common filters for WA State Parks: + - Electrical Service (-32767): 0=None, 1=15A, 2=20A, 3=30A, 4=50A + Returns ------- available_sites: List[AvailableResource] The list of available sites """ + # Build filterData with enumValues format (not values) + # Must be JSON-encoded string for the API + filter_data = [] + if attribute_filters: + for attr_filter in attribute_filters: + filter_data.append({ + "attributeDefinitionId": attr_filter.get("attributeDefinitionId"), + "enumValues": attr_filter.get("enumValues", attr_filter.get("values", [])) + }) + search_filter = { "mapId": campground.map_id, "resourceLocationId": campground.facility_id, @@ -514,7 +554,7 @@ def list_site_availability( "partySize": 1, "numEquipment": 1, "equipmentCategoryId": NON_GROUP_EQUIPMENT, - "filterData": [], + "filterData": json.dumps(filter_data), } if equipment_type_id: search_filter["subEquipmentCategoryId"] = equipment_type_id diff --git a/camply/search/search_going_to_camp.py b/camply/search/search_going_to_camp.py index cb732fdf..79211f96 100644 --- a/camply/search/search_going_to_camp.py +++ b/camply/search/search_going_to_camp.py @@ -49,6 +49,7 @@ def __init__( campgrounds: Optional[Union[List[str], str]] = None, equipment_id: Optional[int] = None, nights: int = 1, + attribute_filters: Optional[List[dict]] = None, **kwargs, ) -> None: """ @@ -69,6 +70,12 @@ def __init__( Campground ID or List of Campground IDs nights: int minimum number of consecutive nights to search per campsite,defaults to 1 + attribute_filters: Optional[List[dict]] + List of attribute filters for GoingToCamp API in format: + [{"attributeDefinitionId": -32767, "enumValues": [3]}] + Common filters: + - Electrical Service (-32767): 0=None, 1=15A, 2=20A, 3=30A, 4=50A + - Service Type (-32768): 3=Standard, 5=Electric, 6=Elec+Water, 7=Full """ self.provider = GoingToCamp super().__init__( @@ -81,6 +88,7 @@ def __init__( self._recreation_area_id = self._validate_rec_area(recreation_area) self._campgrounds = campgrounds self.weekends_only = weekends_only + self.attribute_filters = attribute_filters assert ( any( [ @@ -146,6 +154,7 @@ def get_all_campsites(self) -> List[AvailableCampsite]: start_date=current_start_date, end_date=search_window.end_date, equipment_type_id=self.equipment_id, + attribute_filters=self.attribute_filters, ) for site in sites: site_details = self.campsite_finder.get_site_details( diff --git a/camply/utils/yaml_utils.py b/camply/utils/yaml_utils.py index 9c5767df..43f7e85f 100644 --- a/camply/utils/yaml_utils.py +++ b/camply/utils/yaml_utils.py @@ -125,6 +125,7 @@ def yaml_file_to_arguments( "equipment": equipment, "offline_search": yaml_model.offline_search, "offline_search_path": yaml_model.offline_search_path, + "attribute_filters": yaml_model.attribute_filters, } search_kwargs = { "log": True,