diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d9c151..0b94f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### v1.17.0 +- Added `has_pool` parameter to `property.search.retrieve` and `property_v2.search.retrieve` to filter properties by pool availability. + ### v1.16.2 - Deprecate `volatility` endpoints. diff --git a/parcllabs/__version__.py b/parcllabs/__version__.py index 0d1c3b5..b32887e 100644 --- a/parcllabs/__version__.py +++ b/parcllabs/__version__.py @@ -1 +1 @@ -VERSION = "1.16.2" +VERSION = "1.17.0" diff --git a/parcllabs/schemas/schemas.py b/parcllabs/schemas/schemas.py index ec4899d..2a27f66 100644 --- a/parcllabs/schemas/schemas.py +++ b/parcllabs/schemas/schemas.py @@ -127,6 +127,16 @@ class PropertyV2RetrieveParams(BaseModel): current_investor_owned_flag: bool | None = Field( default=None, description="Whether to filter by current investor owned flag" ) + + # Property features + has_pool: bool | None = Field( + default=None, + description=( + "Whether to filter by pool. True returns properties with a pool. " + "False returns properties where pool data is not available." + ), + ) + current_entity_owner_name: str | None = Field( default=None, description="Current entity owner name to filter by" ) diff --git a/parcllabs/services/properties/property_search.py b/parcllabs/services/properties/property_search.py index 2c73737..5722bcb 100644 --- a/parcllabs/services/properties/property_search.py +++ b/parcllabs/services/properties/property_search.py @@ -71,6 +71,7 @@ def _prepare_params( record_added_date_start: str | None = None, record_added_date_end: str | None = None, current_on_market_flag: bool | None = None, + has_pool: bool | None = None, ) -> dict: params = {} @@ -89,6 +90,7 @@ def _prepare_params( "current_owner_occupied_flag": current_owner_occupied_flag, "current_investor_owned_flag": current_investor_owned_flag, "current_on_market_flag": current_on_market_flag, + "has_pool": has_pool, } for param_name, param_value in bool_flags.items(): @@ -140,6 +142,7 @@ def retrieve( record_added_date_start: str | None = None, record_added_date_end: str | None = None, current_on_market_flag: bool | None = None, + has_pool: bool | None = None, ) -> pd.DataFrame: """ Retrieve parcl_property_id for geographic markets based on specified criteria. @@ -180,6 +183,8 @@ def retrieve( this date (YYYY-MM-DD). Defaults to None. current_on_market_flag (bool, optional): Filter properties currently on the market. Defaults to None. + has_pool (bool, optional): Filter properties by pool availability. + True returns properties with a pool. Defaults to None. Returns: pd.DataFrame: A DataFrame containing the parcl_property_id and other detail @@ -206,6 +211,7 @@ def retrieve( record_added_date_start=record_added_date_start, record_added_date_end=record_added_date_end, current_on_market_flag=current_on_market_flag, + has_pool=has_pool, ) output_data = deque() diff --git a/parcllabs/services/properties/property_v2.py b/parcllabs/services/properties/property_v2.py index 0aa720c..74877c6 100644 --- a/parcllabs/services/properties/property_v2.py +++ b/parcllabs/services/properties/property_v2.py @@ -336,6 +336,8 @@ def _build_boolean_filters(self, params: PropertyV2RetrieveParams) -> dict[str, filters["current_investor_owned_flag"] = self.simple_bool_validator( params.current_investor_owned_flag ) + if params.has_pool is not None: + filters["has_pool"] = self.simple_bool_validator(params.has_pool) return filters @@ -488,6 +490,7 @@ def retrieve( current_new_construction_flag: bool | None = None, current_owner_occupied_flag: bool | None = None, current_investor_owned_flag: bool | None = None, + has_pool: bool | None = None, current_entity_owner_name: str | None = None, include_events: bool | None = None, include_full_event_history: bool | None = None, @@ -532,6 +535,7 @@ def retrieve( current_new_construction_flag: Whether to filter by current_new_construction flag. current_owner_occupied_flag: Whether to filter by current_owner_occupied flag. current_investor_owned_flag: Whether to filter by current_investor_owned flag. + has_pool: Whether to filter by pool availability. current_entity_owner_name: Current entity owner name to filter by. include_events: Whether to include events in the response. include_full_event_history: Whether to include full event history in the response. @@ -577,6 +581,7 @@ def retrieve( current_new_construction_flag=current_new_construction_flag, current_owner_occupied_flag=current_owner_occupied_flag, current_investor_owned_flag=current_investor_owned_flag, + has_pool=has_pool, current_entity_owner_name=current_entity_owner_name, include_events=include_events, include_full_event_history=include_full_event_history, diff --git a/tests/test_property.py b/tests/test_property.py index 4e7e674..7e29141 100644 --- a/tests/test_property.py +++ b/tests/test_property.py @@ -181,3 +181,49 @@ def test_retrieve_with_on_market_flag( # Check that .json() was called on the response mock twice assert mock_response.json.call_count == 2 + + +@patch("parcllabs.services.properties.property_search.PropertySearch._get") +def test_retrieve_with_has_pool(mock_get: Mock, property_search_service: PropertySearch) -> None: + """Test retrieve method with has_pool parameter.""" + mock_response = MagicMock() + mock_response.json.return_value = json.loads(sample_search_response) + mock_get.return_value = mock_response + + parcl_ids_to_test = [5503877] + property_type_to_test = "single_family" + + # Test with flag = True + property_search_service.retrieve( + parcl_ids=parcl_ids_to_test, + property_type=property_type_to_test, + has_pool=True, + ) + + # Test with flag = False + property_search_service.retrieve( + parcl_ids=parcl_ids_to_test, + property_type=property_type_to_test, + has_pool=False, + ) + + expected_params_on = { + "property_type": property_type_to_test.upper(), + "has_pool": "true", + "parcl_id": parcl_ids_to_test[0], + } + expected_params_off = { + "property_type": property_type_to_test.upper(), + "has_pool": "false", + "parcl_id": parcl_ids_to_test[0], + } + + assert len(mock_get.call_args_list) == 2 + + # Check first call (has_pool=True) + _, call_on_kwargs = mock_get.call_args_list[0] + assert call_on_kwargs["params"] == expected_params_on + + # Check second call (has_pool=False) + _, call_off_kwargs = mock_get.call_args_list[1] + assert call_off_kwargs["params"] == expected_params_off diff --git a/tests/test_property_v2.py b/tests/test_property_v2.py index b1e46bd..b06e7d4 100644 --- a/tests/test_property_v2.py +++ b/tests/test_property_v2.py @@ -85,6 +85,7 @@ def test_build_property_filters_from_schema(property_v2_service: PropertyV2Servi include_property_details=True, min_record_added_date="2023-01-01", max_record_added_date="2023-12-31", + has_pool=True, ) filters = property_v2_service._build_property_filters(params) @@ -102,6 +103,7 @@ def test_build_property_filters_from_schema(property_v2_service: PropertyV2Servi "include_property_details": "true", "min_record_added_date": "2023-01-01", "max_record_added_date": "2023-12-31", + "has_pool": "true", } @@ -374,3 +376,21 @@ def test_retrieve_with_schema_validation_errors( parcl_ids=[123], min_event_date="2023/01/01", ) + + +def test_build_boolean_filters_has_pool(property_v2_service: PropertyV2Service) -> None: + """Test has_pool boolean filter.""" + # True + params = PropertyV2RetrieveParams(has_pool=True) + filters = property_v2_service._build_boolean_filters(params) + assert filters["has_pool"] == "true" + + # False + params = PropertyV2RetrieveParams(has_pool=False) + filters = property_v2_service._build_boolean_filters(params) + assert filters["has_pool"] == "false" + + # None (omitted) + params = PropertyV2RetrieveParams() + filters = property_v2_service._build_boolean_filters(params) + assert "has_pool" not in filters