From 806134d2b2c5b8d53bf2fa6963d9b04d6121e0cc Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 14:18:58 +1300 Subject: [PATCH 1/9] add beam schema --- predicthq/endpoints/v1/beam/schemas.py | 170 +++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 predicthq/endpoints/v1/beam/schemas.py diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py new file mode 100644 index 0000000..417de98 --- /dev/null +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -0,0 +1,170 @@ +from pydantic import BaseModel, Field +from datetime import date, datetime +from predicthq.endpoints.schemas import ResultSet + + +class CreateAnalysisResponse(BaseModel): + analysis_id: str + + +class GeoPoint(BaseModel): + lat: str + lon: str + + +class Location(BaseModel): + geopoint: GeoPoint + radius: float + unit: str + google_place_id: str | None = None + + +class RankLevel(BaseModel): + min: int + max: int | None = None + + +class RankLevels(BaseModel): + phq: RankLevel | None = None + local: RankLevel | None = None + + +class Rank(BaseModel): + type: str + levels: RankLevels | None = None + + +class AnalysisDateRange(BaseModel): + start: datetime + end: datetime + + +class BestPracticeChecks(BaseModel): + industry: bool = False + rank: bool = False + radius: bool = False + + +class AnalysisReadinessChecks(BaseModel): + date_range: AnalysisDateRange | None = None + error_code: str | None = None + missing_dates: list[str] | None = None + validation_response: dict | None = None + best_practice: bool + best_practice_checks: BestPracticeChecks + + +class ProcessingCompleted(BaseModel): + correlation: bool + feature_importance: bool + value_quant: bool + + +class DemandType(BaseModel): + interval: str + week_start_day: str | None = None + industry: str | None = None + unit_descriptor: str + unit_currency_multiplier: float + currency_code: str + + +class Analysis(BaseModel): + analysis_id: str + name: str + location: Location + rank: Rank + user_id: str | None = None + access_type: str + status: str + readiness_status: str | None = None + readiness_checks: AnalysisReadinessChecks + processing_completed: ProcessingCompleted + demand_type: DemandType + group_ids: list[str] | None = None + tz: str | None = None + create_dt: datetime + update_dt: datetime + processed_dt: datetime | None = None + external_id: str | None = None + label: list[str] | None = None + + +class AnalysisResultSet(ResultSet): + results: list[Analysis] = Field(alias="analyses") + + +class Address(BaseModel): + locality: str | None = None + country_code: str | None = None + formatted_address: str | None = None + postcode: str | None = None + region: str | None = None + + +class Geo(BaseModel): + address: Address | None = None + + +class Event(BaseModel): + even_id: str + category: str + geo: Geo + labels: list + title: str + timezone: str | None = None + phq_rank: int | None = None + local_rank: int | None = None + formatted_address: str | None = None + impact_patterns: list | None = None + + +class EventResultSet(BaseModel): + events: list[Event] + + +class FeatureGroup(BaseModel): + feature_group: str + features: list[str] + p_value: float + important: bool + + +class FeatureImportance(BaseModel): + feature_importance: list[FeatureGroup] + + +class Incremental(BaseModel): + forecast_uplift_pct_relative: float + forecast_uplift_pct_absolute: float + financial_uplift_annual: float + unit_uplift_annual: float + + +class HistoricalInfo(BaseModel): + anomalous_demand_pct: float + event_contribution_pct: float + event_financial_impact_annual: float + + +class Historical(BaseModel): + anomalous_demand_pct: float + event_contribution_pct: float + total_event_contribution_pct: float + incremental: HistoricalInfo | None = None + decremental: HistoricalInfo | None = None + + +class Prediction(BaseModel): + incremental: Incremental + + +class ValueQuant(BaseModel): + prediction: Prediction | None = None + historical: Historical | None = None + + +class CorrelationResultSet(ResultSet): + model_version: str + version: str + results: list[dict] = Field(alias="dates") From 9f986c008a1d62da5a33059aa12f7233dfa78fe6 Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 14:22:07 +1300 Subject: [PATCH 2/9] add beam analysis endpoint --- predicthq/endpoints/v1/beam/__init__.py | 5 + predicthq/endpoints/v1/beam/endpoint.py | 198 ++++++++++++++++++++++++ predicthq/endpoints/v1/beam/schemas.py | 2 +- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 predicthq/endpoints/v1/beam/__init__.py create mode 100644 predicthq/endpoints/v1/beam/endpoint.py diff --git a/predicthq/endpoints/v1/beam/__init__.py b/predicthq/endpoints/v1/beam/__init__.py new file mode 100644 index 0000000..d9ecec3 --- /dev/null +++ b/predicthq/endpoints/v1/beam/__init__.py @@ -0,0 +1,5 @@ +from .endpoint import BeamEndpoint +from .schemas import Analysis + + +__all__ = ["BeamEndpoint", "Analysis"] diff --git a/predicthq/endpoints/v1/beam/endpoint.py b/predicthq/endpoints/v1/beam/endpoint.py new file mode 100644 index 0000000..2153dab --- /dev/null +++ b/predicthq/endpoints/v1/beam/endpoint.py @@ -0,0 +1,198 @@ +from predicthq.endpoints.base import BaseEndpoint +from .schemas import ( + CreateAnalysisResponse, + AnalysisResultSet, + Analysis, + EventResultSet, + Event, + FeatureImportance, + ValueQuant, + CorrelationResultSet, +) +from predicthq.endpoints.decorators import accepts, returns +from typing import overload + + +class BeamEndpoint: + def __init__(self, client): + self.analysis = self.Analysis(client) + + class Analysis(BaseEndpoint): + @accepts(query_string=False) + @overload + def create( + self, + name: str, + location__geopoint: dict, + location__radius: float = None, + location__unit: str = None, + location__google_place_id: str = None, + location__geoscope_paths: list[str] = None, + rank__type: str = None, + rank__levels__phq: dict = None, + rank__levels__local: dict = None, + demand_type__industry: str = None, + demand_type__unit_descriptor: str = None, + demand_type__unit_currency_multiplier: float = None, + demand_type__currency_code: str = None, + tz: str = None, + external_id: str = None, + label: list[str] = None, + **params, + ): ... + @returns(CreateAnalysisResponse) + def create(self, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.post( + self.build_url("v1", "beam", "analysis"), + json=params, + verify=verify_ssl, + ) + + @accepts() + @overload + def search( + self, + updated__gt: str = None, + updated__gte: str = None, + updated__lt: str = None, + updated__lte: str = None, + q: str = None, + status: list[str] = None, + group_id: list[str] = None, + demand_type__interval: list[str] = None, + demand_type__industry: list[str] = None, + readiness_status: list[str] = None, + include_deleted: bool = None, + sort: list[str] = None, + offset: int = None, + limit: int = None, + external_id: list[str] = None, + label: list[str] = None, + **params, + ): ... + @returns(AnalysisResultSet) + def search(self, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam')}analyses/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(Analysis) + def get(self, analysis_id, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/", + params=params, + verify=verify_ssl, + ) + + @accepts(query_string=False) + @overload + def update( + self, + name: str, + location__geopoint: dict, + location__radius: float = None, + location__unit: str = None, + location__google_place_id: str = None, + location__geoscope_paths: list[str] = None, + rank__type: str = None, + rank__levels__phq: dict = None, + rank__levels__local: dict = None, + demand_type__industry: str = None, + demand_type__unit_descriptor: str = None, + demand_type__unit_currency_multiplier: float = None, + demand_type__currency_code: str = None, + tz: str = None, + external_id: str = None, + label: list[str] = None, + **params, + ): ... + def update(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.patch( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/", + json=params, + verify=verify_ssl, + ) + + @accepts() + def delete(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.delete( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/", + params=params, + verify=verify_ssl, + ) + + @accepts() + def refresh(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.post( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/refresh/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(EventResultSet) + def get_events(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/events/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(Event) + def get_event(self, analysis_id: str, event_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/events/{event_id}/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(CorrelationResultSet) + def get_correlation(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/correlate/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(FeatureImportance) + def get_feature_importance(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/feature-importance/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(ValueQuant) + def get_value_quant(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/value-quant/", + params=params, + verify=verify_ssl, + ) + + @accepts(query_string=False) + def upload_demand(self, analysis_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.post( + f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/sink/", + params=params, + verify=verify_ssl, + ) diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py index 417de98..49337cf 100644 --- a/predicthq/endpoints/v1/beam/schemas.py +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -1,5 +1,5 @@ from pydantic import BaseModel, Field -from datetime import date, datetime +from datetime import datetime from predicthq.endpoints.schemas import ResultSet From e8bfa0288f9b09000e495a338315e29f05db3baa Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 14:35:07 +1300 Subject: [PATCH 3/9] add BeamEndpoint import --- predicthq/endpoints/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/predicthq/endpoints/__init__.py b/predicthq/endpoints/__init__.py index 913f460..78c2a71 100644 --- a/predicthq/endpoints/__init__.py +++ b/predicthq/endpoints/__init__.py @@ -5,6 +5,7 @@ from .v1.features import FeaturesEndpoint from .v1.places import PlacesEndpoint from .v1.radius import SuggestedRadiusEndpoint +from .v1.beam import BeamEndpoint __all__ = [ @@ -15,4 +16,5 @@ "FeaturesEndpoint", "PlacesEndpoint", "SuggestedRadiusEndpoint", + "BeamEndpoint", ] From 523beb9dea11363263c8aac155c0fa6dfa05f7d9 Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 14:42:09 +1300 Subject: [PATCH 4/9] update schema for python 3.8 --- predicthq/endpoints/v1/beam/schemas.py | 67 +++++++++++++------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py index 49337cf..6b16b9c 100644 --- a/predicthq/endpoints/v1/beam/schemas.py +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -1,6 +1,7 @@ from pydantic import BaseModel, Field from datetime import datetime from predicthq.endpoints.schemas import ResultSet +from typing import Optional class CreateAnalysisResponse(BaseModel): @@ -16,22 +17,22 @@ class Location(BaseModel): geopoint: GeoPoint radius: float unit: str - google_place_id: str | None = None + google_place_id: Optional[str] = None class RankLevel(BaseModel): min: int - max: int | None = None + max: Optional[int] = None class RankLevels(BaseModel): - phq: RankLevel | None = None - local: RankLevel | None = None + phq: Optional[RankLevel] = None + local: Optional[RankLevel] = None class Rank(BaseModel): type: str - levels: RankLevels | None = None + levels: Optional[RankLevels] = None class AnalysisDateRange(BaseModel): @@ -46,10 +47,10 @@ class BestPracticeChecks(BaseModel): class AnalysisReadinessChecks(BaseModel): - date_range: AnalysisDateRange | None = None - error_code: str | None = None - missing_dates: list[str] | None = None - validation_response: dict | None = None + date_range: Optional[AnalysisDateRange] = None + error_code: Optional[str] = None + missing_dates: Optional[list[str]] = None + validation_response: Optional[dict] = None best_practice: bool best_practice_checks: BestPracticeChecks @@ -62,8 +63,8 @@ class ProcessingCompleted(BaseModel): class DemandType(BaseModel): interval: str - week_start_day: str | None = None - industry: str | None = None + week_start_day: Optional[str] = None + industry: Optional[str] = None unit_descriptor: str unit_currency_multiplier: float currency_code: str @@ -74,20 +75,20 @@ class Analysis(BaseModel): name: str location: Location rank: Rank - user_id: str | None = None + user_id: Optional[str] = None access_type: str status: str - readiness_status: str | None = None + readiness_status: Optional[str] = None readiness_checks: AnalysisReadinessChecks processing_completed: ProcessingCompleted demand_type: DemandType - group_ids: list[str] | None = None - tz: str | None = None + group_ids: Optional[list[str]] = None + tz: Optional[str] = None create_dt: datetime update_dt: datetime - processed_dt: datetime | None = None - external_id: str | None = None - label: list[str] | None = None + processed_dt: Optional[datetime] = None + external_id: Optional[str] = None + label: Optional[list[str]] = None class AnalysisResultSet(ResultSet): @@ -95,15 +96,15 @@ class AnalysisResultSet(ResultSet): class Address(BaseModel): - locality: str | None = None - country_code: str | None = None - formatted_address: str | None = None - postcode: str | None = None - region: str | None = None + locality: Optional[str] = None + country_code: Optional[str] = None + formatted_address: Optional[str] = None + postcode: Optional[str] = None + region: Optional[str] = None class Geo(BaseModel): - address: Address | None = None + address: Optional[Address] = None class Event(BaseModel): @@ -112,11 +113,11 @@ class Event(BaseModel): geo: Geo labels: list title: str - timezone: str | None = None - phq_rank: int | None = None - local_rank: int | None = None - formatted_address: str | None = None - impact_patterns: list | None = None + timezone: Optional[str] = None + phq_rank: Optional[int] = None + local_rank: Optional[int] = None + formatted_address: Optional[str] = None + impact_patterns: Optional[list] = None class EventResultSet(BaseModel): @@ -151,8 +152,8 @@ class Historical(BaseModel): anomalous_demand_pct: float event_contribution_pct: float total_event_contribution_pct: float - incremental: HistoricalInfo | None = None - decremental: HistoricalInfo | None = None + incremental: Optional[HistoricalInfo] = None + decremental: Optional[HistoricalInfo] = None class Prediction(BaseModel): @@ -160,8 +161,8 @@ class Prediction(BaseModel): class ValueQuant(BaseModel): - prediction: Prediction | None = None - historical: Historical | None = None + prediction: Optional[Prediction] = None + historical: Optional[Historical] = None class CorrelationResultSet(ResultSet): From 60fc67b9565e6022cc6d2c25c7a32b6d6e1fb816 Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 14:43:34 +1300 Subject: [PATCH 5/9] update schema for python 3.8 --- predicthq/endpoints/v1/beam/schemas.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py index 6b16b9c..9cc45b6 100644 --- a/predicthq/endpoints/v1/beam/schemas.py +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -1,7 +1,7 @@ from pydantic import BaseModel, Field from datetime import datetime from predicthq.endpoints.schemas import ResultSet -from typing import Optional +from typing import Optional, List class CreateAnalysisResponse(BaseModel): @@ -49,7 +49,7 @@ class BestPracticeChecks(BaseModel): class AnalysisReadinessChecks(BaseModel): date_range: Optional[AnalysisDateRange] = None error_code: Optional[str] = None - missing_dates: Optional[list[str]] = None + missing_dates: Optional[List[str]] = None validation_response: Optional[dict] = None best_practice: bool best_practice_checks: BestPracticeChecks @@ -82,17 +82,17 @@ class Analysis(BaseModel): readiness_checks: AnalysisReadinessChecks processing_completed: ProcessingCompleted demand_type: DemandType - group_ids: Optional[list[str]] = None + group_ids: Optional[List[str]] = None tz: Optional[str] = None create_dt: datetime update_dt: datetime processed_dt: Optional[datetime] = None external_id: Optional[str] = None - label: Optional[list[str]] = None + label: Optional[List[str]] = None class AnalysisResultSet(ResultSet): - results: list[Analysis] = Field(alias="analyses") + results: List[Analysis] = Field(alias="analyses") class Address(BaseModel): @@ -111,28 +111,28 @@ class Event(BaseModel): even_id: str category: str geo: Geo - labels: list + labels: List title: str timezone: Optional[str] = None phq_rank: Optional[int] = None local_rank: Optional[int] = None formatted_address: Optional[str] = None - impact_patterns: Optional[list] = None + impact_patterns: Optional[List] = None class EventResultSet(BaseModel): - events: list[Event] + events: List[Event] class FeatureGroup(BaseModel): feature_group: str - features: list[str] + features: List[str] p_value: float important: bool class FeatureImportance(BaseModel): - feature_importance: list[FeatureGroup] + feature_importance: List[FeatureGroup] class Incremental(BaseModel): @@ -168,4 +168,4 @@ class ValueQuant(BaseModel): class CorrelationResultSet(ResultSet): model_version: str version: str - results: list[dict] = Field(alias="dates") + results: List[dict] = Field(alias="dates") From f46b43d7cb70689a31f26c2f1c1804c1ae885481 Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 14:45:09 +1300 Subject: [PATCH 6/9] update endpoints for python 3.8 --- predicthq/endpoints/v1/beam/endpoint.py | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/predicthq/endpoints/v1/beam/endpoint.py b/predicthq/endpoints/v1/beam/endpoint.py index 2153dab..ff88c61 100644 --- a/predicthq/endpoints/v1/beam/endpoint.py +++ b/predicthq/endpoints/v1/beam/endpoint.py @@ -10,7 +10,7 @@ CorrelationResultSet, ) from predicthq.endpoints.decorators import accepts, returns -from typing import overload +from typing import overload, List class BeamEndpoint: @@ -27,7 +27,7 @@ def create( location__radius: float = None, location__unit: str = None, location__google_place_id: str = None, - location__geoscope_paths: list[str] = None, + location__geoscope_paths: List[str] = None, rank__type: str = None, rank__levels__phq: dict = None, rank__levels__local: dict = None, @@ -37,7 +37,7 @@ def create( demand_type__currency_code: str = None, tz: str = None, external_id: str = None, - label: list[str] = None, + label: List[str] = None, **params, ): ... @returns(CreateAnalysisResponse) @@ -58,17 +58,17 @@ def search( updated__lt: str = None, updated__lte: str = None, q: str = None, - status: list[str] = None, - group_id: list[str] = None, - demand_type__interval: list[str] = None, - demand_type__industry: list[str] = None, - readiness_status: list[str] = None, + status: List[str] = None, + group_id: List[str] = None, + demand_type__interval: List[str] = None, + demand_type__industry: List[str] = None, + readiness_status: List[str] = None, include_deleted: bool = None, - sort: list[str] = None, + sort: List[str] = None, offset: int = None, limit: int = None, - external_id: list[str] = None, - label: list[str] = None, + external_id: List[str] = None, + label: List[str] = None, **params, ): ... @returns(AnalysisResultSet) @@ -99,7 +99,7 @@ def update( location__radius: float = None, location__unit: str = None, location__google_place_id: str = None, - location__geoscope_paths: list[str] = None, + location__geoscope_paths: List[str] = None, rank__type: str = None, rank__levels__phq: dict = None, rank__levels__local: dict = None, @@ -109,7 +109,7 @@ def update( demand_type__currency_code: str = None, tz: str = None, external_id: str = None, - label: list[str] = None, + label: List[str] = None, **params, ): ... def update(self, analysis_id: str, **params): From 10f19b6fd66ed09bb911b54c580313ee065e1e15 Mon Sep 17 00:00:00 2001 From: Keegan Cordeiro Date: Tue, 14 Jan 2025 15:29:26 +1300 Subject: [PATCH 7/9] fix analysis endpoints and schema --- predicthq/endpoints/v1/beam/endpoint.py | 67 +++++++++++++++++++------ predicthq/endpoints/v1/beam/schemas.py | 8 +-- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/predicthq/endpoints/v1/beam/endpoint.py b/predicthq/endpoints/v1/beam/endpoint.py index ff88c61..102adbd 100644 --- a/predicthq/endpoints/v1/beam/endpoint.py +++ b/predicthq/endpoints/v1/beam/endpoint.py @@ -11,6 +11,7 @@ ) from predicthq.endpoints.decorators import accepts, returns from typing import overload, List +from datetime import date class BeamEndpoint: @@ -18,7 +19,6 @@ def __init__(self, client): self.analysis = self.Analysis(client) class Analysis(BaseEndpoint): - @accepts(query_string=False) @overload def create( self, @@ -40,16 +40,16 @@ def create( label: List[str] = None, **params, ): ... + @accepts(query_string=False) @returns(CreateAnalysisResponse) def create(self, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.post( - self.build_url("v1", "beam", "analysis"), + f"{self.build_url('v1', 'beam')}analyses/", json=params, verify=verify_ssl, ) - @accepts() @overload def search( self, @@ -71,6 +71,7 @@ def search( label: List[str] = None, **params, ): ... + @accepts() @returns(AnalysisResultSet) def search(self, **params): verify_ssl = params.pop("config.verify_ssl", True) @@ -85,12 +86,11 @@ def search(self, **params): def get(self, analysis_id, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/", params=params, verify=verify_ssl, ) - @accepts(query_string=False) @overload def update( self, @@ -112,10 +112,11 @@ def update( label: List[str] = None, **params, ): ... + @accepts(query_string=False) def update(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.patch( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/", json=params, verify=verify_ssl, ) @@ -124,7 +125,7 @@ def update(self, analysis_id: str, **params): def delete(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.delete( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/", params=params, verify=verify_ssl, ) @@ -133,17 +134,39 @@ def delete(self, analysis_id: str, **params): def refresh(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.post( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/refresh/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/refresh/", params=params, verify=verify_ssl, ) + @overload + def get_events( + self, + analysis_id: str, + start__gt: date = None, + start__gte: date = None, + start__lt: date = None, + start__lte: date = None, + end__gt: date = None, + end__gte: date = None, + end__lt: date = None, + end__lte: date = None, + active__gt: date = None, + active__gte: date = None, + active__lt: date = None, + active__lte: date = None, + impact__gt: date = None, + impact__gte: date = None, + impact__lt: date = None, + impact__lte: date = None, + **params, + ): ... @accepts() @returns(EventResultSet) - def get_events(self, analysis_id: str, **params): + def search_events(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/events/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/events/", params=params, verify=verify_ssl, ) @@ -153,17 +176,30 @@ def get_events(self, analysis_id: str, **params): def get_event(self, analysis_id: str, event_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/events/{event_id}/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/events/{event_id}/", params=params, verify=verify_ssl, ) + @overload + def get_correlation( + self, + analysis_id: str, + date__gt: date = None, + date__gte: date = None, + date__lt: date = None, + date__lte: date = None, + offset: int = None, + limit: int = None, + include_features: List[str] = None, + **params, + ): ... @accepts() @returns(CorrelationResultSet) def get_correlation(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/correlate/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/correlate/", params=params, verify=verify_ssl, ) @@ -173,7 +209,7 @@ def get_correlation(self, analysis_id: str, **params): def get_feature_importance(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/feature-importance/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/feature-importance/", params=params, verify=verify_ssl, ) @@ -183,16 +219,17 @@ def get_feature_importance(self, analysis_id: str, **params): def get_value_quant(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/value-quant/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/value-quant/", params=params, verify=verify_ssl, ) + # TODO this function needs to accept various types of demand data to upload @accepts(query_string=False) def upload_demand(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.post( - f"{self.build_url('v1', 'beam', 'analysis')}{analysis_id}/sink/", + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/sink/", params=params, verify=verify_ssl, ) diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py index 9cc45b6..73caf14 100644 --- a/predicthq/endpoints/v1/beam/schemas.py +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -108,7 +108,7 @@ class Geo(BaseModel): class Event(BaseModel): - even_id: str + event_id: str category: str geo: Geo labels: List @@ -120,8 +120,8 @@ class Event(BaseModel): impact_patterns: Optional[List] = None -class EventResultSet(BaseModel): - events: List[Event] +class EventResultSet(ResultSet): + results: List[Event] = Field(alias="events") class FeatureGroup(BaseModel): @@ -167,5 +167,5 @@ class ValueQuant(BaseModel): class CorrelationResultSet(ResultSet): model_version: str - version: str + version: int results: List[dict] = Field(alias="dates") From e8bb13f72f6dd8949e2cade51c4aa78d253d4c38 Mon Sep 17 00:00:00 2001 From: Keegan <48084902+corke2013@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:08:18 +1300 Subject: [PATCH 8/9] add analysis group endpoints (#99) * add analysis group endpoints * add analysis group import * python 3.8 compat * fix signature * use optional type hint --------- Co-authored-by: Keegan Cordeiro --- predicthq/endpoints/v1/beam/__init__.py | 4 +- predicthq/endpoints/v1/beam/endpoint.py | 263 +++++++++++++++++------- predicthq/endpoints/v1/beam/schemas.py | 46 ++++- 3 files changed, 236 insertions(+), 77 deletions(-) diff --git a/predicthq/endpoints/v1/beam/__init__.py b/predicthq/endpoints/v1/beam/__init__.py index d9ecec3..3ece7ca 100644 --- a/predicthq/endpoints/v1/beam/__init__.py +++ b/predicthq/endpoints/v1/beam/__init__.py @@ -1,5 +1,5 @@ from .endpoint import BeamEndpoint -from .schemas import Analysis +from .schemas import Analysis, AnalysisGroup -__all__ = ["BeamEndpoint", "Analysis"] +__all__ = ["BeamEndpoint", "Analysis", "AnalysisGroup"] diff --git a/predicthq/endpoints/v1/beam/endpoint.py b/predicthq/endpoints/v1/beam/endpoint.py index 102adbd..51c3234 100644 --- a/predicthq/endpoints/v1/beam/endpoint.py +++ b/predicthq/endpoints/v1/beam/endpoint.py @@ -8,36 +8,40 @@ FeatureImportance, ValueQuant, CorrelationResultSet, + CreateAnalysisGroupResponse, + AnalysisGroup, + AnalysisGroupResultSet, ) from predicthq.endpoints.decorators import accepts, returns -from typing import overload, List +from typing import overload, List, Optional from datetime import date class BeamEndpoint: def __init__(self, client): - self.analysis = self.Analysis(client) + self.analysis = self.AnalysisEndpoint(client) + self.analysis_group = self.AnalysisGroupEndpoint(client) - class Analysis(BaseEndpoint): + class AnalysisEndpoint(BaseEndpoint): @overload def create( self, name: str, location__geopoint: dict, - location__radius: float = None, - location__unit: str = None, - location__google_place_id: str = None, - location__geoscope_paths: List[str] = None, - rank__type: str = None, - rank__levels__phq: dict = None, - rank__levels__local: dict = None, - demand_type__industry: str = None, - demand_type__unit_descriptor: str = None, - demand_type__unit_currency_multiplier: float = None, - demand_type__currency_code: str = None, - tz: str = None, - external_id: str = None, - label: List[str] = None, + location__radius: Optional[float] = None, + location__unit: Optional[str] = None, + location__google_place_id: Optional[str] = None, + location__geoscope_paths: Optional[List[str]] = None, + rank__type: Optional[str] = None, + rank__levels__phq: Optional[dict] = None, + rank__levels__local: Optional[dict] = None, + demand_type__industry: Optional[str] = None, + demand_type__unit_descriptor: Optional[str] = None, + demand_type__unit_currency_multiplier: Optional[float] = None, + demand_type__currency_code: Optional[str] = None, + tz: Optional[str] = None, + external_id: Optional[str] = None, + label: Optional[List[str]] = None, **params, ): ... @accepts(query_string=False) @@ -53,22 +57,22 @@ def create(self, **params): @overload def search( self, - updated__gt: str = None, - updated__gte: str = None, - updated__lt: str = None, - updated__lte: str = None, - q: str = None, - status: List[str] = None, - group_id: List[str] = None, - demand_type__interval: List[str] = None, - demand_type__industry: List[str] = None, - readiness_status: List[str] = None, - include_deleted: bool = None, - sort: List[str] = None, - offset: int = None, - limit: int = None, - external_id: List[str] = None, - label: List[str] = None, + updated__gt: Optional[str] = None, + updated__gte: Optional[str] = None, + updated__lt: Optional[str] = None, + updated__lte: Optional[str] = None, + q: Optional[str] = None, + status: Optional[List[str]] = None, + group_id: Optional[List[str]] = None, + demand_type__interval: Optional[List[str]] = None, + demand_type__industry: Optional[List[str]] = None, + readiness_status: Optional[List[str]] = None, + include_deleted: Optional[bool] = None, + sort: Optional[List[str]] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + external_id: Optional[List[str]] = None, + label: Optional[List[str]] = None, **params, ): ... @accepts() @@ -94,22 +98,22 @@ def get(self, analysis_id, **params): @overload def update( self, - name: str, - location__geopoint: dict, - location__radius: float = None, - location__unit: str = None, - location__google_place_id: str = None, - location__geoscope_paths: List[str] = None, - rank__type: str = None, - rank__levels__phq: dict = None, - rank__levels__local: dict = None, - demand_type__industry: str = None, - demand_type__unit_descriptor: str = None, - demand_type__unit_currency_multiplier: float = None, - demand_type__currency_code: str = None, - tz: str = None, - external_id: str = None, - label: List[str] = None, + name: Optional[str] = None, + location__geopoint: Optional[dict] = None, + location__radius: Optional[float] = None, + location__unit: Optional[str] = None, + location__google_place_id: Optional[str] = None, + location__geoscope_paths: Optional[List[str]] = None, + rank__type: Optional[str] = None, + rank__levels__phq: Optional[dict] = None, + rank__levels__local: Optional[dict] = None, + demand_type__industry: Optional[str] = None, + demand_type__unit_descriptor: Optional[str] = None, + demand_type__unit_currency_multiplier: Optional[float] = None, + demand_type__currency_code: Optional[str] = None, + tz: Optional[str] = None, + external_id: Optional[str] = None, + label: Optional[List[str]] = None, **params, ): ... @accepts(query_string=False) @@ -140,25 +144,25 @@ def refresh(self, analysis_id: str, **params): ) @overload - def get_events( + def search_events( self, analysis_id: str, - start__gt: date = None, - start__gte: date = None, - start__lt: date = None, - start__lte: date = None, - end__gt: date = None, - end__gte: date = None, - end__lt: date = None, - end__lte: date = None, - active__gt: date = None, - active__gte: date = None, - active__lt: date = None, - active__lte: date = None, - impact__gt: date = None, - impact__gte: date = None, - impact__lt: date = None, - impact__lte: date = None, + start__gt: Optional[date] = None, + start__gte: Optional[date] = None, + start__lt: Optional[date] = None, + start__lte: Optional[date] = None, + end__gt: Optional[date] = None, + end__gte: Optional[date] = None, + end__lt: Optional[date] = None, + end__lte: Optional[date] = None, + active__gt: Optional[date] = None, + active__gte:Optional[ date] = None, + active__lt: Optional[date] = None, + active__lte: Optional[date] = None, + impact__gt: Optional[date] = None, + impact__gte: Optional[date] = None, + impact__lt: Optional[date] = None, + impact__lte: Optional[date] = None, **params, ): ... @accepts() @@ -185,13 +189,13 @@ def get_event(self, analysis_id: str, event_id: str, **params): def get_correlation( self, analysis_id: str, - date__gt: date = None, - date__gte: date = None, - date__lt: date = None, - date__lte: date = None, - offset: int = None, - limit: int = None, - include_features: List[str] = None, + date__gt: Optional[date] = None, + date__gte: Optional[date] = None, + date__lt: Optional[date] = None, + date__lte: Optional[date] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + include_features: Optional[List[str]] = None, **params, ): ... @accepts() @@ -233,3 +237,116 @@ def upload_demand(self, analysis_id: str, **params): params=params, verify=verify_ssl, ) + + class AnalysisGroupEndpoint(BaseEndpoint): + @overload + def create( + self, + name: str, + analysis_ids: List[str], + demand_type__unit_descriptor: Optional[str] = None, + **params, + ): ... + @accepts(query_string=False) + @returns(CreateAnalysisGroupResponse) + def create(self, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.post( + f"{self.build_url('v1', 'beam')}analysis-groups/", + json=params, + verify=verify_ssl, + ) + + @overload + def search( + self, + updated__gt: Optional[str] = None, + updated__gte: Optional[str] = None, + updated__lt: Optional[str] = None, + updated__lte: Optional[str] = None, + q: Optional[str] = None, + status: Optional[List[str]] = None, + demand_type__interval: Optional[List[str]] = None, + demand_type__industry: Optional[List[str]] = None, + readiness_status: Optional[List[str]] = None, + include_deleted: Optional[bool] = None, + sort: Optional[List[str]] = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + **params, + ): ... + @accepts() + @returns(AnalysisGroupResultSet) + def search(self, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam')}analysis-groups/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(AnalysisGroup) + def get(self, group_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/", + params=params, + verify=verify_ssl, + ) + + @overload + def update( + self, + group_id: str, + name: Optional[str] = None, + analysis_ids: Optional[List[str]] = None, + demand_type__unit_descriptor: Optional[str] = None, + **params, + ): ... + @accepts(query_string=False) + def update(self, group_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.patch( + f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/", + json=params, + verify=verify_ssl, + ) + + @accepts() + def delete(self, group_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.delete( + f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/", + params=params, + verify=verify_ssl, + ) + + @accepts() + def refresh(self, group_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.post( + f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/refresh/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(FeatureImportance) + def get_feature_importance(self, group_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/feature-importance/", + params=params, + verify=verify_ssl, + ) + + @accepts() + @returns(ValueQuant) + def get_value_quant(self, group_id: str, **params): + verify_ssl = params.pop("config.verify_ssl", True) + return self.client.get( + f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/value-quant/", + params=params, + verify=verify_ssl, + ) diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py index 73caf14..a74fdee 100644 --- a/predicthq/endpoints/v1/beam/schemas.py +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -61,7 +61,15 @@ class ProcessingCompleted(BaseModel): value_quant: bool -class DemandType(BaseModel): +class DemandTypeGroup(BaseModel): + interval: str + week_start_day: Optional[str] = None + industry: Optional[str] = None + unit_descriptor: str + currency_code: str + + +class DemandType(DemandTypeGroup): interval: str week_start_day: Optional[str] = None industry: Optional[str] = None @@ -71,7 +79,7 @@ class DemandType(BaseModel): class Analysis(BaseModel): - analysis_id: str + analysis_id: Optional[str] = None name: str location: Location rank: Rank @@ -169,3 +177,37 @@ class CorrelationResultSet(ResultSet): model_version: str version: int results: List[dict] = Field(alias="dates") + + +class CreateAnalysisGroupResponse(BaseModel): + group_id: str + + +class ExcludedAnalysis(BaseModel): + analysis_id: str + reason: str + excluded_from: List[str] + + +class ProcessingCompletedGroup(BaseModel): + feature_importance: bool + value_quant: bool + excluded_analyses: List[ExcludedAnalysis] + + +class AnalysisGroup(BaseModel): + group_id: Optional[str] = None + name: str + analysis_ids: List[str] + user_id: Optional[str] = None + processing_completed: ProcessingCompletedGroup + readiness_status: Optional[str] = None + demand_type: Optional[DemandTypeGroup] = None + status: str + create_dt: datetime + update_dt: datetime + processed_dt: Optional[datetime] = None + + +class AnalysisGroupResultSet(ResultSet): + results: List[AnalysisGroup] = Field(alias="groups") From 8f8b7e77b2db7fa06138a863ed4d9bf4ae7ddd84 Mon Sep 17 00:00:00 2001 From: Keegan <48084902+corke2013@users.noreply.github.com> Date: Fri, 17 Jan 2025 15:37:06 +1300 Subject: [PATCH 9/9] Beam sink endpoint (#100) * beam sink endpoint - allow json, csv and ndjson upload * expose beam endpoint in sdk * allow extra fields so that new fields are visible without an update to the SDK * remove undocumented endpoints * add features-api to_csv mixin * remove accidental print statement * fix typing for python 3.8 * update build url test * fix bug in features api endpoint when using iter_all * update feature api tests * update readme, add examples and missing param to beam update function * add beam endpoint tests (#102) * add beam endpoint tests * address comments - use correct fixtures --------- Co-authored-by: Keegan Cordeiro * add comment regarding features-api * Pagination (#103) * add ArgKwargResult set for custom pagination * use custom pagination on all supported beam endpoints --------- Co-authored-by: Keegan Cordeiro --------- Co-authored-by: Keegan Cordeiro --- README.md | 30 + predicthq/client.py | 3 +- predicthq/endpoints/decorators.py | 8 +- predicthq/endpoints/schemas.py | 6 +- predicthq/endpoints/v1/beam/endpoint.py | 104 +- predicthq/endpoints/v1/beam/schemas.py | 121 +- predicthq/endpoints/v1/features/endpoint.py | 7 +- predicthq/endpoints/v1/features/schemas.py | 37 +- predicthq/version.py | 2 +- tests/endpoints/v1/test_beam.py | 787 +++++++++++ tests/endpoints/v1/test_features.py | 17 + .../beam_test/test_analysis.json | 64 + .../beam_test/test_analysis_group.json | 34 + .../beam_test/test_correlation.json | 1168 +++++++++++++++++ .../beam_test/test_create.json | 3 + .../beam_test/test_create_group.json | 3 + .../beam_test/test_empty_correlation.json | 6 + .../test_empty_feature_importance.json | 100 ++ .../beam_test/test_empty_group_search.json | 4 + .../beam_test/test_empty_search.json | 4 + .../beam_test/test_feature_importance.json | 3 + .../beam_test/test_group_search.json | 308 +++++ .../beam_test/test_search.json | 673 ++++++++++ tests/test_client.py | 2 +- usecases/beam/analysis/basics.py | 73 ++ usecases/beam/analysis_group/basics.py | 46 + .../features/get_features_for_criteria.py | 10 + 27 files changed, 3461 insertions(+), 162 deletions(-) create mode 100644 tests/endpoints/v1/test_beam.py create mode 100644 tests/fixtures/requests_responses/beam_test/test_analysis.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_analysis_group.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_correlation.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_create.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_create_group.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_empty_correlation.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_empty_feature_importance.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_empty_group_search.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_empty_search.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_feature_importance.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_group_search.json create mode 100644 tests/fixtures/requests_responses/beam_test/test_search.json create mode 100644 usecases/beam/analysis/basics.py create mode 100644 usecases/beam/analysis_group/basics.py diff --git a/README.md b/README.md index 2394b98..f6abdff 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,36 @@ suggested_radius = phq.radius.search(location__origin="45.5051,-122.6750") print(suggested_radius.radius, suggested_radius.radius_unit, suggested_radius.location.model_dump(exclude_none=True)) ``` +### Beam endpoints + +Get Analysis. + +Additional examples are available in [usecases/beam/analysis](https://github.com/predicthq/sdk-py/tree/master/usecases/beam/analysis) folder. + +```Python +from predicthq import Client + +phq = Client(access_token="abc123") + + +analysis = phq.beam.analysis.get(analysis_id="abc123") +print(analysis.model_dump(exclude_none=True)) +``` + +Get Analysis Group. + +Additional examples are available in [usecases/beam/analysis_group](https://github.com/predicthq/sdk-py/tree/master/usecases/beam/analysis_group) folder. + +```Python +from predicthq import Client + +phq = Client(access_token="abc123") + + +analysis_group = phq.beam.analysis_group.get(group_id="abc123") +print(analysis_group.model_dump(exlcude_none=True)) +``` + ### Serializing search results into a dictionary All search results can be serialized into a dictionary using the `.model_dump()` method call. diff --git a/predicthq/client.py b/predicthq/client.py index dbae207..f5876d0 100644 --- a/predicthq/client.py +++ b/predicthq/client.py @@ -16,7 +16,7 @@ class Client(object): @classmethod def build_url(cls, path): result = list(urlparse(path)) - result[2] = f"/{result[2].strip('/')}/" + result[2] = f"/{result[2].strip('/')}" return urljoin(config.ENDPOINT_URL, urlunparse(result)) def __init__(self, access_token=None): @@ -35,6 +35,7 @@ def initialize_endpoints(self): self.accounts = endpoints.AccountsEndpoint(proxy(self)) self.places = endpoints.PlacesEndpoint(proxy(self)) self.radius = endpoints.SuggestedRadiusEndpoint(proxy(self)) + self.beam = endpoints.BeamEndpoint(proxy(self)) def get_headers(self, headers): _headers = { diff --git a/predicthq/endpoints/decorators.py b/predicthq/endpoints/decorators.py index 12c9a9d..0500eab 100644 --- a/predicthq/endpoints/decorators.py +++ b/predicthq/endpoints/decorators.py @@ -4,6 +4,8 @@ from predicthq.exceptions import ValidationError +from predicthq.endpoints.schemas import ArgKwargResultSet + def _kwargs_to_key_list_mapping(kwargs, separator="__"): """ @@ -41,7 +43,7 @@ def _to_url_params(key_list_mapping, glue=".", separator=",", parent_key=""): return params -def _to_json(key_list_mapping, json = None): +def _to_json(key_list_mapping, json=None): """ Converts key_list_mapping to json """ @@ -81,7 +83,6 @@ def returns(model_class): def decorator(f): @functools.wraps(f) def wrapper(endpoint, *args, **kwargs): - model = getattr(endpoint.Meta, f.__name__, {}).get("returns", model_class) data = f(endpoint, *args, **kwargs) @@ -89,6 +90,9 @@ def wrapper(endpoint, *args, **kwargs): loaded_model = model(**data) loaded_model._more = functools.partial(wrapper, endpoint) loaded_model._endpoint = endpoint + if isinstance(loaded_model, ArgKwargResultSet): + loaded_model._args = args + loaded_model._kwargs = kwargs return loaded_model except PydanticValidationError as e: raise ValidationError(e) diff --git a/predicthq/endpoints/schemas.py b/predicthq/endpoints/schemas.py index a08232d..a3066eb 100644 --- a/predicthq/endpoints/schemas.py +++ b/predicthq/endpoints/schemas.py @@ -1,4 +1,3 @@ -from urllib.parse import parse_qsl, urlparse from typing import Callable, Optional from pydantic import BaseModel, HttpUrl @@ -59,3 +58,8 @@ def iter_all(self): def __iter__(self): return self.iter_items() + + +class ArgKwargResultSet(ResultSet): + _args: Optional[dict] = None + _kwargs: Optional[dict] = None diff --git a/predicthq/endpoints/v1/beam/endpoint.py b/predicthq/endpoints/v1/beam/endpoint.py index 51c3234..9ec24ca 100644 --- a/predicthq/endpoints/v1/beam/endpoint.py +++ b/predicthq/endpoints/v1/beam/endpoint.py @@ -3,17 +3,14 @@ CreateAnalysisResponse, AnalysisResultSet, Analysis, - EventResultSet, - Event, FeatureImportance, - ValueQuant, CorrelationResultSet, CreateAnalysisGroupResponse, AnalysisGroup, AnalysisGroupResultSet, ) from predicthq.endpoints.decorators import accepts, returns -from typing import overload, List, Optional +from typing import overload, List, Optional, TextIO, Union from datetime import date @@ -98,6 +95,7 @@ def get(self, analysis_id, **params): @overload def update( self, + analysis_id: str, name: Optional[str] = None, location__geopoint: Optional[dict] = None, location__radius: Optional[float] = None, @@ -144,49 +142,7 @@ def refresh(self, analysis_id: str, **params): ) @overload - def search_events( - self, - analysis_id: str, - start__gt: Optional[date] = None, - start__gte: Optional[date] = None, - start__lt: Optional[date] = None, - start__lte: Optional[date] = None, - end__gt: Optional[date] = None, - end__gte: Optional[date] = None, - end__lt: Optional[date] = None, - end__lte: Optional[date] = None, - active__gt: Optional[date] = None, - active__gte:Optional[ date] = None, - active__lt: Optional[date] = None, - active__lte: Optional[date] = None, - impact__gt: Optional[date] = None, - impact__gte: Optional[date] = None, - impact__lt: Optional[date] = None, - impact__lte: Optional[date] = None, - **params, - ): ... - @accepts() - @returns(EventResultSet) - def search_events(self, analysis_id: str, **params): - verify_ssl = params.pop("config.verify_ssl", True) - return self.client.get( - f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/events/", - params=params, - verify=verify_ssl, - ) - - @accepts() - @returns(Event) - def get_event(self, analysis_id: str, event_id: str, **params): - verify_ssl = params.pop("config.verify_ssl", True) - return self.client.get( - f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/events/{event_id}/", - params=params, - verify=verify_ssl, - ) - - @overload - def get_correlation( + def get_correlation_results( self, analysis_id: str, date__gt: Optional[date] = None, @@ -200,7 +156,7 @@ def get_correlation( ): ... @accepts() @returns(CorrelationResultSet) - def get_correlation(self, analysis_id: str, **params): + def get_correlation_results(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) return self.client.get( f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/correlate/", @@ -218,23 +174,41 @@ def get_feature_importance(self, analysis_id: str, **params): verify=verify_ssl, ) - @accepts() - @returns(ValueQuant) - def get_value_quant(self, analysis_id: str, **params): - verify_ssl = params.pop("config.verify_ssl", True) - return self.client.get( - f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/value-quant/", - params=params, - verify=verify_ssl, - ) - - # TODO this function needs to accept various types of demand data to upload @accepts(query_string=False) + @overload + def upload_demand( + self, + analysis_id: str, + json: Optional[Union[str, TextIO]] = None, + ndjson: Optional[Union[str, TextIO]] = None, + csv: Optional[Union[str, TextIO]] = None, + **params, + ): ... def upload_demand(self, analysis_id: str, **params): verify_ssl = params.pop("config.verify_ssl", True) + if json := params.pop("json", None): + headers = {"Content-Type": "application/json"} + file = json + if ndjson := params.pop("ndjson", None): + headers = {"Content-Type": "application/x-ndjson"} + file = ndjson + if csv := params.pop("csv", None): + headers = {"Content-Type": "text/csv"} + file = csv + + if isinstance(file, str): + with open(file) as f: + return self.client.post( + f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/sink/", + data=f, + headers=headers, + verify=verify_ssl, + ) + return self.client.post( f"{self.build_url('v1', 'beam')}analyses/{analysis_id}/sink/", - params=params, + data=file, + headers=headers, verify=verify_ssl, ) @@ -340,13 +314,3 @@ def get_feature_importance(self, group_id: str, **params): params=params, verify=verify_ssl, ) - - @accepts() - @returns(ValueQuant) - def get_value_quant(self, group_id: str, **params): - verify_ssl = params.pop("config.verify_ssl", True) - return self.client.get( - f"{self.build_url('v1', 'beam')}analysis-groups/{group_id}/value-quant/", - params=params, - verify=verify_ssl, - ) diff --git a/predicthq/endpoints/v1/beam/schemas.py b/predicthq/endpoints/v1/beam/schemas.py index a74fdee..12d056d 100644 --- a/predicthq/endpoints/v1/beam/schemas.py +++ b/predicthq/endpoints/v1/beam/schemas.py @@ -1,52 +1,68 @@ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ConfigDict from datetime import datetime -from predicthq.endpoints.schemas import ResultSet +from predicthq.endpoints.schemas import ArgKwargResultSet from typing import Optional, List -class CreateAnalysisResponse(BaseModel): +class BeamPaginationResultSet(ArgKwargResultSet): + def has_next(self): + return self._kwargs.get("offset", 0) + len(self.results) < self.count + + def get_next(self): + if "offset" in self._kwargs: + self._kwargs["offset"] = self._kwargs.get("offset") + len(self.results) + else: + self._kwargs["offset"] = len(self.results) + return self._more(**self._kwargs) + + +class AllowExtra(BaseModel): + model_config: ConfigDict = ConfigDict(extra="allow") + + +class CreateAnalysisResponse(AllowExtra): analysis_id: str -class GeoPoint(BaseModel): +class GeoPoint(AllowExtra): lat: str lon: str -class Location(BaseModel): +class Location(AllowExtra): geopoint: GeoPoint radius: float unit: str google_place_id: Optional[str] = None -class RankLevel(BaseModel): +class RankLevel(AllowExtra): min: int max: Optional[int] = None -class RankLevels(BaseModel): +class RankLevels(AllowExtra): phq: Optional[RankLevel] = None local: Optional[RankLevel] = None -class Rank(BaseModel): +class Rank(AllowExtra): type: str levels: Optional[RankLevels] = None -class AnalysisDateRange(BaseModel): +class AnalysisDateRange(AllowExtra): start: datetime end: datetime -class BestPracticeChecks(BaseModel): +class BestPracticeChecks(AllowExtra): industry: bool = False rank: bool = False radius: bool = False -class AnalysisReadinessChecks(BaseModel): +class AnalysisReadinessChecks(AllowExtra): date_range: Optional[AnalysisDateRange] = None error_code: Optional[str] = None missing_dates: Optional[List[str]] = None @@ -55,13 +71,13 @@ class AnalysisReadinessChecks(BaseModel): best_practice_checks: BestPracticeChecks -class ProcessingCompleted(BaseModel): +class ProcessingCompleted(AllowExtra): correlation: bool feature_importance: bool value_quant: bool -class DemandTypeGroup(BaseModel): +class DemandTypeGroup(AllowExtra): interval: str week_start_day: Optional[str] = None industry: Optional[str] = None @@ -78,7 +94,7 @@ class DemandType(DemandTypeGroup): currency_code: str -class Analysis(BaseModel): +class Analysis(AllowExtra): analysis_id: Optional[str] = None name: str location: Location @@ -99,103 +115,44 @@ class Analysis(BaseModel): label: Optional[List[str]] = None -class AnalysisResultSet(ResultSet): +class AnalysisResultSet(BeamPaginationResultSet): results: List[Analysis] = Field(alias="analyses") -class Address(BaseModel): - locality: Optional[str] = None - country_code: Optional[str] = None - formatted_address: Optional[str] = None - postcode: Optional[str] = None - region: Optional[str] = None - - -class Geo(BaseModel): - address: Optional[Address] = None - - -class Event(BaseModel): - event_id: str - category: str - geo: Geo - labels: List - title: str - timezone: Optional[str] = None - phq_rank: Optional[int] = None - local_rank: Optional[int] = None - formatted_address: Optional[str] = None - impact_patterns: Optional[List] = None - - -class EventResultSet(ResultSet): - results: List[Event] = Field(alias="events") - - -class FeatureGroup(BaseModel): +class FeatureGroup(AllowExtra): feature_group: str features: List[str] p_value: float important: bool -class FeatureImportance(BaseModel): +class FeatureImportance(AllowExtra): feature_importance: List[FeatureGroup] -class Incremental(BaseModel): - forecast_uplift_pct_relative: float - forecast_uplift_pct_absolute: float - financial_uplift_annual: float - unit_uplift_annual: float - - -class HistoricalInfo(BaseModel): - anomalous_demand_pct: float - event_contribution_pct: float - event_financial_impact_annual: float - - -class Historical(BaseModel): - anomalous_demand_pct: float - event_contribution_pct: float - total_event_contribution_pct: float - incremental: Optional[HistoricalInfo] = None - decremental: Optional[HistoricalInfo] = None - - -class Prediction(BaseModel): - incremental: Incremental - - -class ValueQuant(BaseModel): - prediction: Optional[Prediction] = None - historical: Optional[Historical] = None - - -class CorrelationResultSet(ResultSet): +class CorrelationResultSet(BeamPaginationResultSet): model_version: str version: int results: List[dict] = Field(alias="dates") -class CreateAnalysisGroupResponse(BaseModel): +class CreateAnalysisGroupResponse(AllowExtra): group_id: str -class ExcludedAnalysis(BaseModel): +class ExcludedAnalysis(AllowExtra): analysis_id: str reason: str excluded_from: List[str] -class ProcessingCompletedGroup(BaseModel): +class ProcessingCompletedGroup(AllowExtra): feature_importance: bool value_quant: bool excluded_analyses: List[ExcludedAnalysis] -class AnalysisGroup(BaseModel): +class AnalysisGroup(AllowExtra): group_id: Optional[str] = None name: str analysis_ids: List[str] @@ -209,5 +166,5 @@ class AnalysisGroup(BaseModel): processed_dt: Optional[datetime] = None -class AnalysisGroupResultSet(ResultSet): +class AnalysisGroupResultSet(BeamPaginationResultSet): results: List[AnalysisGroup] = Field(alias="groups") diff --git a/predicthq/endpoints/v1/features/endpoint.py b/predicthq/endpoints/v1/features/endpoint.py index bae59ec..724189d 100644 --- a/predicthq/endpoints/v1/features/endpoint.py +++ b/predicthq/endpoints/v1/features/endpoint.py @@ -25,10 +25,13 @@ def mutate_bool_to_default_for_type(cls, user_request_spec): @accepts(query_string=False) @returns(FeatureResultSet) - def obtain_features(self, **request): + # This is a temporary solution to get the next page for Features API + # _params and _json are for internal use only + def obtain_features(self, _params: dict = None, _json: dict = None, **request): verify_ssl = request.pop("config", {}).get("verify_ssl", True) return self.client.post( self.build_url("v1", "features"), - json=request, + json=_json or request, verify=verify_ssl, + params=_params, ) diff --git a/predicthq/endpoints/v1/features/schemas.py b/predicthq/endpoints/v1/features/schemas.py index 6bbe42b..2ea0da0 100644 --- a/predicthq/endpoints/v1/features/schemas.py +++ b/predicthq/endpoints/v1/features/schemas.py @@ -1,9 +1,36 @@ +import csv from datetime import date from typing import Dict, List, Optional, Union from pydantic import BaseModel, RootModel -from predicthq.endpoints.schemas import ResultSet +from predicthq.endpoints.schemas import ArgKwargResultSet + + +class CsvMixin: + def _flatten_json(self, separator: str) -> Optional[List[dict]]: + def __flatten_json(d: dict, pk: str = "") -> dict: + flat_json = {} + for k, v in d.items(): + if isinstance(v, dict): + flat_json.update( + __flatten_json(v, f"{pk}{separator}{k}" if pk else k) + ) + continue + flat_json.update({f"{pk}{separator}{k}" if pk else k: v}) + return flat_json + + return [__flatten_json(d.model_dump(exclude_none=True)) for d in self.iter_all()] + + def to_csv(self, file: str, mode: str = "w+", separator: str = "_") -> None: + header = None + with open(file, mode=mode) as csv_file: + csv_writer = csv.writer(csv_file) + for d in self._flatten_json(separator): + if not header: + header = list(d.keys()) + csv_writer.writerow(header) + csv_writer.writerow([d[h] for h in header]) class FeatureRankLevel(BaseModel): @@ -31,5 +58,11 @@ def __getattr__(self, name: str) -> Union[date, FeatureStat, FeatureRankLevel]: return self.root[name] -class FeatureResultSet(ResultSet): +class FeatureResultSet(ArgKwargResultSet, CsvMixin): results: List[Optional[Feature]] + + def get_next(self): + if not self.has_next() or not hasattr(self, "_more"): + return + params = self._parse_params(self.next) + return self._more(_params=params, _json=self._kwargs.get("_json", {}) or self._kwargs) diff --git a/predicthq/version.py b/predicthq/version.py index ce1305b..7039708 100644 --- a/predicthq/version.py +++ b/predicthq/version.py @@ -1 +1 @@ -__version__ = "4.0.0" +__version__ = "4.1.0" diff --git a/tests/endpoints/v1/test_beam.py b/tests/endpoints/v1/test_beam.py new file mode 100644 index 0000000..c9010f5 --- /dev/null +++ b/tests/endpoints/v1/test_beam.py @@ -0,0 +1,787 @@ +import unittest + +from tests import load_fixture, with_mock_client +from predicthq.endpoints.v1.beam.schemas import ( + FeatureImportance, + CorrelationResultSet, + Analysis, + CreateAnalysisResponse, + CreateAnalysisGroupResponse, + AnalysisResultSet, + AnalysisGroupResultSet, + AnalysisGroup, +) + + +class BeamTest(unittest.TestCase): + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_search") + ) + def test_search(self, client): + result = client.beam.analysis.search() + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/", + params={}, + verify=True, + ) + + assert isinstance(result, AnalysisResultSet) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_empty_search") + ) + def test_search_params_underscores(self, client): + client.beam.analysis.search( + updated__gt="2016-03-01", + updated__gte="2016-03-01", + updated__lt="2016-04-01", + updated__lte="2016-04-01", + q="query", + status="active", + group_id="group_id", + demand_type__interval="week", + demand_type__industry="retail", + readiness_status=["ready", "not_ready"], + include_deleted=True, + sort=["rank", "-start"], + offset=10, + limit=10, + external_id="external_id", + label=["label1", "label2"], + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/", + params={ + "updated.gt": "2016-03-01", + "updated.gte": "2016-03-01", + "updated.lt": "2016-04-01", + "updated.lte": "2016-04-01", + "q": "query", + "status": "active", + "group_id": "group_id", + "demand_type.interval": "week", + "demand_type.industry": "retail", + "readiness_status": "ready,not_ready", + "include_deleted": 1, + "sort": "rank,-start", + "offset": 10, + "limit": 10, + "external_id": "external_id", + "label": "label1,label2", + }, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_empty_search") + ) + def test_search_params_dicts(self, client): + client.beam.analysis.search( + updated={ + "gt": "2016-03-01", + "gte": "2016-03-01", + "lt": "2016-04-01", + "lte": "2016-04-01", + }, + q="query", + status="active", + group_id="group_id", + demand_type={"interval": "week", "industry": "retail"}, + readiness_status=["ready", "not_ready"], + include_deleted=True, + sort=["rank", "-start"], + offset=10, + limit=10, + external_id="external_id", + label=["label1", "label2"], + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/", + params={ + "updated.gt": "2016-03-01", + "updated.gte": "2016-03-01", + "updated.lt": "2016-04-01", + "updated.lte": "2016-04-01", + "q": "query", + "status": "active", + "group_id": "group_id", + "demand_type.interval": "week", + "demand_type.industry": "retail", + "readiness_status": "ready,not_ready", + "include_deleted": 1, + "sort": "rank,-start", + "offset": 10, + "limit": 10, + "external_id": "external_id", + "label": "label1,label2", + }, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_create") + ) + def test_create_params_underscores(self, client): + result = client.beam.analysis.create( + name="name", + location__geopoint={"lat": 1.1, "lon": 1.2}, + location__radius=1.0, + location__unit="km", + location__google_place_id="google_place_id", + location__geoscope_paths=["geoscope_path1", "geoscope_path2"], + rank__type="type", + rank__levels__phq={"min": 1.0, "max": 2.0}, + rank__levels__local={"min": 3.0, "max": 4.0}, + demand_type__industry="industry", + demand_type__unit_descriptor="unit_descriptor", + demand_type__unit_currency_multiplier=1.4, + demand_type__currency_code="currency_code", + tz="tz", + external_id="external_id", + label=["label1", "label2"], + ) + + client.request.assert_called_once_with( + "post", + "/v1/beam/analyses/", + json={ + "name": "name", + "location": { + "geopoint": {"lat": 1.1, "lon": 1.2}, + "radius": 1.0, + "unit": "km", + "google_place_id": "google_place_id", + "geoscope_paths": ["geoscope_path1", "geoscope_path2"], + }, + "rank": { + "type": "type", + "levels": { + "phq": {"min": 1.0, "max": 2.0}, + "local": {"min": 3.0, "max": 4.0}, + }, + }, + "demand_type": { + "industry": "industry", + "unit_descriptor": "unit_descriptor", + "unit_currency_multiplier": 1.4, + "currency_code": "currency_code", + }, + "tz": "tz", + "external_id": "external_id", + "label": ["label1", "label2"], + }, + verify=True, + ) + assert isinstance(result, CreateAnalysisResponse) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_create") + ) + def test_create_params_dicts(self, client): + result = client.beam.analysis.create( + name="name", + location={ + "geopoint": {"lat": 1.1, "lon": 1.2}, + "radius": 1.0, + "unit": "km", + "google_place_id": "google_place_id", + "geoscope_paths": ["geoscope_path1", "geoscope_path2"], + }, + rank={ + "type": "type", + "levels": { + "phq": {"min": 1.0, "max": 2.0}, + "local": {"min": 3.0, "max": 4.0}, + }, + }, + demand_type={ + "industry": "industry", + "unit_descriptor": "unit_descriptor", + "unit_currency_multiplier": 1.4, + "currency_code": "currency_code", + }, + tz="tz", + external_id="external_id", + label=["label1", "label2"], + ) + + client.request.assert_called_once_with( + "post", + "/v1/beam/analyses/", + json={ + "name": "name", + "location": { + "geopoint": {"lat": 1.1, "lon": 1.2}, + "radius": 1.0, + "unit": "km", + "google_place_id": "google_place_id", + "geoscope_paths": ["geoscope_path1", "geoscope_path2"], + }, + "rank": { + "type": "type", + "levels": { + "phq": {"min": 1.0, "max": 2.0}, + "local": {"min": 3.0, "max": 4.0}, + }, + }, + "demand_type": { + "industry": "industry", + "unit_descriptor": "unit_descriptor", + "unit_currency_multiplier": 1.4, + "currency_code": "currency_code", + }, + "tz": "tz", + "external_id": "external_id", + "label": ["label1", "label2"], + }, + verify=True, + ) + + assert isinstance(result, CreateAnalysisResponse) + + @with_mock_client() + def test_update_params_underscores(self, client): + client.beam.analysis.update( + analysis_id="abc123", + name="name", + location__geopoint={"lat": 1.1, "lon": 1.2}, + location__radius=1.0, + location__unit="km", + location__google_place_id="google_place_id", + location__geoscope_paths=["geoscope_path1", "geoscope_path2"], + rank__type="type", + rank__levels__phq={"min": 1.0, "max": 2.0}, + rank__levels__local={"min": 3.0, "max": 4.0}, + demand_type__industry="industry", + demand_type__unit_descriptor="unit_descriptor", + demand_type__unit_currency_multiplier=1.4, + demand_type__currency_code="currency_code", + tz="tz", + external_id="external_id", + label=["label1", "label2"], + ) + + client.request.assert_called_once_with( + "patch", + "/v1/beam/analyses/abc123/", + json={ + "name": "name", + "location": { + "geopoint": {"lat": 1.1, "lon": 1.2}, + "radius": 1.0, + "unit": "km", + "google_place_id": "google_place_id", + "geoscope_paths": ["geoscope_path1", "geoscope_path2"], + }, + "rank": { + "type": "type", + "levels": { + "phq": {"min": 1.0, "max": 2.0}, + "local": {"min": 3.0, "max": 4.0}, + }, + }, + "demand_type": { + "industry": "industry", + "unit_descriptor": "unit_descriptor", + "unit_currency_multiplier": 1.4, + "currency_code": "currency_code", + }, + "tz": "tz", + "external_id": "external_id", + "label": ["label1", "label2"], + }, + verify=True, + ) + + @with_mock_client() + def test_update_params_dicts(self, client): + client.beam.analysis.update( + analysis_id="abc123", + name="name", + location={ + "geopoint": {"lat": 1.1, "lon": 1.2}, + "radius": 1.0, + "unit": "km", + "google_place_id": "google_place_id", + "geoscope_paths": ["geoscope_path1", "geoscope_path2"], + }, + rank={ + "type": "type", + "levels": { + "phq": {"min": 1.0, "max": 2.0}, + "local": {"min": 3.0, "max": 4.0}, + }, + }, + demand_type={ + "industry": "industry", + "unit_descriptor": "unit_descriptor", + "unit_currency_multiplier": 1.4, + "currency_code": "currency_code", + }, + tz="tz", + external_id="external_id", + label=["label1", "label2"], + ) + + client.request.assert_called_once_with( + "patch", + "/v1/beam/analyses/abc123/", + json={ + "name": "name", + "location": { + "geopoint": {"lat": 1.1, "lon": 1.2}, + "radius": 1.0, + "unit": "km", + "google_place_id": "google_place_id", + "geoscope_paths": ["geoscope_path1", "geoscope_path2"], + }, + "rank": { + "type": "type", + "levels": { + "phq": {"min": 1.0, "max": 2.0}, + "local": {"min": 3.0, "max": 4.0}, + }, + }, + "demand_type": { + "industry": "industry", + "unit_descriptor": "unit_descriptor", + "unit_currency_multiplier": 1.4, + "currency_code": "currency_code", + }, + "tz": "tz", + "external_id": "external_id", + "label": ["label1", "label2"], + }, + verify=True, + ) + + @with_mock_client() + def test_delete(self, client): + client.beam.analysis.delete(analysis_id="abc123") + + client.request.assert_called_once_with( + "delete", + "/v1/beam/analyses/abc123/", + params={}, + verify=True, + ) + + @with_mock_client() + def test_refresh(self, client): + client.beam.analysis.refresh(analysis_id="abc123") + + client.request.assert_called_once_with( + "post", + "/v1/beam/analyses/abc123/refresh/", + params={}, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_empty_correlation" + ) + ) + def test_get_correlation_results_params_underscores(self, client): + client.beam.analysis.get_correlation_results( + analysis_id="abc123", + date__gt="2024-01-01", + date__gte="2024-01-01", + date__lt="2024-01-31", + date__lte="2024-01-31", + offset=10, + limit=10, + include_features=["feature1", "feature2"], + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/abc123/correlate/", + params={ + "date.gt": "2024-01-01", + "date.gte": "2024-01-01", + "date.lt": "2024-01-31", + "date.lte": "2024-01-31", + "offset": 10, + "limit": 10, + "include_features": "feature1,feature2", + }, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_empty_correlation" + ) + ) + def test_get_correlation_results_empty_params_dicts(self, client): + client.beam.analysis.get_correlation_results( + analysis_id="abc123", + date={ + "gt": "2024-01-01", + "gte": "2024-01-01", + "lt": "2024-01-31", + "lte": "2024-01-31", + }, + offset=10, + limit=10, + include_features=["feature1", "feature2"], + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/abc123/correlate/", + params={ + "date.gt": "2024-01-01", + "date.gte": "2024-01-01", + "date.lt": "2024-01-31", + "date.lte": "2024-01-31", + "offset": 10, + "limit": 10, + "include_features": "feature1,feature2", + }, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_correlation") + ) + def test_get_correlation_results_params_dicts(self, client): + result = client.beam.analysis.get_correlation_results( + analysis_id="abc123", + date={ + "gt": "2024-01-01", + "gte": "2024-01-01", + "lt": "2024-01-31", + "lte": "2024-01-31", + }, + offset=10, + limit=10, + include_features=["feature1", "feature2"], + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/abc123/correlate/", + params={ + "date.gt": "2024-01-01", + "date.gte": "2024-01-01", + "date.lt": "2024-01-31", + "date.lte": "2024-01-31", + "offset": 10, + "limit": 10, + "include_features": "feature1,feature2", + }, + verify=True, + ) + + assert isinstance(result, CorrelationResultSet) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_empty_feature_importance" + ) + ) + def test_feature_empty_importance(self, client): + client.beam.analysis.get_feature_importance(analysis_id="abc123") + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/abc123/feature-importance/", + params={}, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_feature_importance" + ) + ) + def test_feature_importance(self, client): + result = client.beam.analysis.get_feature_importance(analysis_id="abc123") + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/abc123/feature-importance/", + params={}, + verify=True, + ) + assert isinstance(result, FeatureImportance) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_analysis") + ) + def test_get_analysis(self, client): + result = client.beam.analysis.get(analysis_id="abc123") + + client.request.assert_called_once_with( + "get", + "/v1/beam/analyses/abc123/", + params={}, + verify=True, + ) + assert isinstance(result, Analysis) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_create_group") + ) + def test_create_group_params_underscores(self, client): + result = client.beam.analysis_group.create( + name="name", + analysis_ids=["analysis_id1", "analysis_id2"], + demand_type__unit_descriptor="unit_descriptor", + ) + + client.request.assert_called_once_with( + "post", + "/v1/beam/analysis-groups/", + json={ + "name": "name", + "analysis_ids": ["analysis_id1", "analysis_id2"], + "demand_type": {"unit_descriptor": "unit_descriptor"}, + }, + verify=True, + ) + + assert isinstance(result, CreateAnalysisGroupResponse) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_create_group") + ) + def test_create_group_params_dicts(self, client): + result = client.beam.analysis_group.create( + name="name", + analysis_ids=["analysis_id1", "analysis_id2"], + demand_type={"unit_descriptor": "unit_descriptor"}, + ) + + client.request.assert_called_once_with( + "post", + "/v1/beam/analysis-groups/", + json={ + "name": "name", + "analysis_ids": ["analysis_id1", "analysis_id2"], + "demand_type": {"unit_descriptor": "unit_descriptor"}, + }, + verify=True, + ) + + assert isinstance(result, CreateAnalysisGroupResponse) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_group_search") + ) + def test_search_group(self, client): + result = client.beam.analysis_group.search() + + client.request.assert_called_once_with( + "get", + "/v1/beam/analysis-groups/", + params={}, + verify=True, + ) + + assert isinstance(result, AnalysisGroupResultSet) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_empty_group_search" + ) + ) + def test_search_group_params_underscores(self, client): + client.beam.analysis_group.search( + updated__gt="2016-03-01", + updated__gte="2016-03-01", + updated__lt="2016-04-01", + updated__lte="2016-04-01", + q="query", + status=["active", "inactive"], + demand_type__interval=["week", "month"], + demand_type__industry=["retail", "hospitality"], + readiness_status=["ready", "not_ready"], + include_deleted=True, + sort=["rank", "-start"], + offset=10, + limit=10, + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analysis-groups/", + params={ + "updated.gt": "2016-03-01", + "updated.gte": "2016-03-01", + "updated.lt": "2016-04-01", + "updated.lte": "2016-04-01", + "q": "query", + "status": "active,inactive", + "demand_type.interval": "week,month", + "demand_type.industry": "retail,hospitality", + "readiness_status": "ready,not_ready", + "include_deleted": 1, + "sort": "rank,-start", + "offset": 10, + "limit": 10, + }, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_empty_group_search" + ) + ) + def test_search_group_params_dicts(self, client): + client.beam.analysis_group.search( + updated={ + "gt": "2016-03-01", + "gte": "2016-03-01", + "lt": "2016-04-01", + "lte": "2016-04-01", + }, + q="query", + status="active,inactive", + demand_type={ + "interval": "week,month", + "industry": "retail,hospitality", + }, + readiness_status="ready,not_ready", + include_deleted=1, + sort="rank,-start", + offset=10, + limit=10, + ) + + client.request.assert_called_once_with( + "get", + "/v1/beam/analysis-groups/", + params={ + "updated.gt": "2016-03-01", + "updated.gte": "2016-03-01", + "updated.lt": "2016-04-01", + "updated.lte": "2016-04-01", + "q": "query", + "status": "active,inactive", + "demand_type.interval": "week,month", + "demand_type.industry": "retail,hospitality", + "readiness_status": "ready,not_ready", + "include_deleted": 1, + "sort": "rank,-start", + "offset": 10, + "limit": 10, + }, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture("requests_responses/beam_test/test_analysis_group") + ) + def test_get_analysis_group(self, client): + result = client.beam.analysis_group.get(group_id="abc123") + + client.request.assert_called_once_with( + "get", + "/v1/beam/analysis-groups/abc123/", + params={}, + verify=True, + ) + assert isinstance(result, AnalysisGroup) + + @with_mock_client() + def test_update_group_params_underscores(self, client): + client.beam.analysis_group.update( + group_id="abc123", + name="name", + analysis_ids=["analysis_id1", "analysis_id2"], + demand_type__unit_descriptor="unit_descriptor", + ) + + client.request.assert_called_once_with( + "patch", + "/v1/beam/analysis-groups/abc123/", + json={ + "name": "name", + "analysis_ids": ["analysis_id1", "analysis_id2"], + "demand_type": {"unit_descriptor": "unit_descriptor"}, + }, + verify=True, + ) + + @with_mock_client() + def test_update_group_params_dicts(self, client): + client.beam.analysis_group.update( + group_id="abc123", + name="name", + analysis_ids=["analysis_id1", "analysis_id2"], + demand_type={"unit_descriptor": "unit_descriptor"}, + ) + + client.request.assert_called_once_with( + "patch", + "/v1/beam/analysis-groups/abc123/", + json={ + "name": "name", + "analysis_ids": ["analysis_id1", "analysis_id2"], + "demand_type": {"unit_descriptor": "unit_descriptor"}, + }, + verify=True, + ) + + @with_mock_client() + def test_delete_group(self, client): + client.beam.analysis_group.delete(group_id="abc123") + + client.request.assert_called_once_with( + "delete", + "/v1/beam/analysis-groups/abc123/", + params={}, + verify=True, + ) + + @with_mock_client() + def test_refresh_group(self, client): + client.beam.analysis_group.refresh(group_id="abc123") + + client.request.assert_called_once_with( + "post", + "/v1/beam/analysis-groups/abc123/refresh/", + params={}, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_empty_feature_importance" + ) + ) + def test_group_feature_empty_importance(self, client): + client.beam.analysis_group.get_feature_importance(group_id="abc123") + + client.request.assert_called_once_with( + "get", + "/v1/beam/analysis-groups/abc123/feature-importance/", + params={}, + verify=True, + ) + + @with_mock_client( + request_returns=load_fixture( + "requests_responses/beam_test/test_feature_importance" + ) + ) + def test_group_feature_importance(self, client): + result = client.beam.analysis_group.get_feature_importance(group_id="abc123") + + client.request.assert_called_once_with( + "get", + "/v1/beam/analysis-groups/abc123/feature-importance/", + params={}, + verify=True, + ) + assert isinstance(result, FeatureImportance) diff --git a/tests/endpoints/v1/test_features.py b/tests/endpoints/v1/test_features.py index 1e5e11f..963defe 100644 --- a/tests/endpoints/v1/test_features.py +++ b/tests/endpoints/v1/test_features.py @@ -47,6 +47,7 @@ def test_mutate_default_criteria(self, client): "phq_spend_performing_arts_transportation": {"stats": ["sum", "count"], "phq_rank": None}, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -114,6 +115,7 @@ def test_attendance_request_params_underscores(self, client): "phq_attendance_sports_hospitality": {"stats": feature_stats}, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -130,6 +132,7 @@ def test_attendance_request_params_underscores_without_ssl_verification(self, cl "location": {"place_id": ["4671654"]}, }, verify=False, + params=None ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -196,6 +199,7 @@ def test_attendance_request_params_dicts(self, client): "phq_attendance_sports_hospitality": feature_criteria, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -214,6 +218,7 @@ def test_attendance_request_params_dicts_without_ssl_verification(self, client): "location": {"geo": {"lat": 41.75038, "lon": -71.49978, "radius": "30km"}}, }, verify=False, + params=None ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -250,6 +255,7 @@ def test_rank_request_params_underscores(self, client): "phq_rank_academic_holiday": True, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -268,6 +274,7 @@ def test_rank_request_params_underscores_without_ssl_verification(self, client): "location": {"geo": {"lat": 41.75038, "lon": -71.49978, "radius": "30km"}}, }, verify=False, + params=None ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -303,6 +310,7 @@ def test_rank_request_params_dicts(self, client): "phq_rank_academic_holiday": True, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -319,6 +327,7 @@ def test_rank_request_params_dicts_without_ssl_verification(self, client): "location": {"place_id": ["4671654"]}, }, verify=False, + params=None ) @with_client() @@ -379,6 +388,7 @@ def test_impact_severe_weather_request_params_underscores(self, client): "phq_impact_severe_weather_tropical_storm_retail": {"stats": feature_stats}, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -425,6 +435,7 @@ def test_impact_severe_weather_request_params_dicts(self, client): "phq_impact_severe_weather_tropical_storm_retail": feature_criteria, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -463,6 +474,7 @@ def test_viewership_request_params_underscores(self, client): "phq_viewership_sports_golf_pga_championship": {"stats": feature_stats}, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -481,6 +493,7 @@ def test_viewership_request_params_underscores_without_ssl_verification(self, cl "location": {"place_id": ["4671654"]}, }, verify=False, + params=None ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -517,6 +530,7 @@ def test_viewership_request_params_dicts(self, client): "phq_viewership_sports_horse_racing": feature_criteria, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -535,6 +549,7 @@ def test_viewership_request_params_dicts_without_ssl_verification(self, client): "location": {"geo": {"lat": 41.75038, "lon": -71.49978, "radius": "30km"}}, }, verify=False, + params=None ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -610,6 +625,7 @@ def test_spend_request_params_underscores(self, client): "phq_spend_performing_arts_transportation": {"stats": feature_stats}, }, verify=True, + params=None, ) @with_mock_client(request_returns=load_fixture("requests_responses/features_test/test_empty_search")) @@ -684,4 +700,5 @@ def test_spend_request_params_dicts(self, client): "phq_spend_performing_arts_transportation": feature_criteria, }, verify=True, + params=None, ) diff --git a/tests/fixtures/requests_responses/beam_test/test_analysis.json b/tests/fixtures/requests_responses/beam_test/test_analysis.json new file mode 100644 index 0000000..33a47ca --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_analysis.json @@ -0,0 +1,64 @@ +{ + "name": "SDK Test", + "location": { + "geopoint": { + "lat": "37.7749", + "lon": "-122.4194" + }, + "radius": 3345.52, + "unit": "m", + "google_place_id": null + }, + "rank": { + "type": "local", + "levels": { + "phq": null, + "local": { + "min": 35, + "max": null + } + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2019-01-01T00:00:00", + "end": "2021-09-26T00:00:00" + }, + "error_code": null, + "missing_dates": [], + "validation_response": { + "missing_data_percentage": 0.0, + "consecutive_nan": 0 + }, + "best_practice": false, + "best_practice_checks": { + "industry": false, + "rank": true, + "radius": true + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "other", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "America/Los_Angeles", + "create_dt": "2025-01-15T00:30:44Z", + "update_dt": "2025-01-15T00:53:48Z", + "processed_dt": "2025-01-15T00:53:30Z", + "external_id": null, + "label": null +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_analysis_group.json b/tests/fixtures/requests_responses/beam_test/test_analysis_group.json new file mode 100644 index 0000000..4423e72 --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_analysis_group.json @@ -0,0 +1,34 @@ +{ + "name": "test group", + "analysis_ids": [ + "2r2f23f2", + "sgds2356" + ], + "user_id": "sdf25g", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [ + { + "analysis_id": "sdgsg35", + "reason": "analysis_deleted", + "excluded_from": [ + "feature_importance", + "value_quant" + ] + } + ] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "retail", + "unit_descriptor": "Sales", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-09-13T03:59:45Z", + "update_dt": "2024-12-17T01:55:45Z", + "processed_dt": "2024-12-17T01:55:46Z" +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_correlation.json b/tests/fixtures/requests_responses/beam_test/test_correlation.json new file mode 100644 index 0000000..999ff2d --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_correlation.json @@ -0,0 +1,1168 @@ +{ + "count": 30, + "previous": null, + "next": null, + "model_version": "2.3.1", + "version": 6, + "dates": [ + { + "date": "2021-06-01", + "actual_demand": 96.0, + "baseline_demand": 106.05615230186515, + "remainder": -10.056152301865154, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_observances": { + "rank_levels": { + "2": 1, + "3": 2 + } + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 9 + }, + { + "date": "2021-06-02", + "actual_demand": 97.0, + "baseline_demand": 106.98993686596805, + "remainder": -9.989936865968048, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-03", + "actual_demand": 108.0, + "baseline_demand": 107.78619345008876, + "remainder": 0.21380654991124004, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_observances": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 7 + }, + { + "date": "2021-06-04", + "actual_demand": 104.0, + "baseline_demand": 106.09708199255878, + "remainder": -2.0970819925587847, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-05", + "actual_demand": 85.0, + "baseline_demand": 90.29705752901104, + "remainder": -5.297057529011042, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-06", + "actual_demand": 84.0, + "baseline_demand": 77.76561500510894, + "remainder": 6.234384994891059, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-07", + "actual_demand": 94.0, + "baseline_demand": 89.68251774264668, + "remainder": 4.317482257353319, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-08", + "actual_demand": 98.0, + "baseline_demand": 104.79481768246053, + "remainder": -6.794817682460533, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-09", + "actual_demand": 118.0, + "baseline_demand": 106.99744606698593, + "remainder": 11.00255393301407, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-10", + "actual_demand": 93.0, + "baseline_demand": 109.22977624427338, + "remainder": -16.22977624427338, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_concerts": { + "sum": 1154 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_concerts": { + "sum": 25444 + }, + "phq_spend_concerts_accommodation": { + "sum": 3920 + }, + "phq_spend_concerts_hospitality": { + "sum": 12188 + }, + "phq_spend_concerts_transportation": { + "sum": 9334 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 50886, + "phq_attendance_sum": 19974, + "phq_rank_count": 6 + }, + { + "date": "2021-06-11", + "actual_demand": 91.0, + "baseline_demand": 110.31791642300257, + "remainder": -19.317916423002572, + "impact_significance": "MEDIUM", + "impact_significance_score": 2, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-12", + "actual_demand": 95.0, + "baseline_demand": 95.6506657272828, + "remainder": -0.6506657272827994, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_concerts": { + "sum": 822 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_concerts": { + "sum": 20195 + }, + "phq_spend_concerts_accommodation": { + "sum": 3921 + }, + "phq_spend_concerts_hospitality": { + "sum": 9216 + }, + "phq_spend_concerts_transportation": { + "sum": 7057 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 40389, + "phq_attendance_sum": 19642, + "phq_rank_count": 6 + }, + { + "date": "2021-06-13", + "actual_demand": 76.0, + "baseline_demand": 82.95270472073167, + "remainder": -6.952704720731674, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-14", + "actual_demand": 97.0, + "baseline_demand": 93.59407396673511, + "remainder": 3.4059260332648904, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_observances": { + "rank_levels": { + "3": 1, + "4": 1 + } + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 8 + }, + { + "date": "2021-06-15", + "actual_demand": 104.0, + "baseline_demand": 105.7583549266292, + "remainder": -1.7583549266291953, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-16", + "actual_demand": 88.0, + "baseline_demand": 103.1721650429274, + "remainder": -15.172165042927404, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-17", + "actual_demand": 102.0, + "baseline_demand": 100.69169170492215, + "remainder": 1.3083082950778504, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-18", + "actual_demand": 118.0, + "baseline_demand": 98.10072896104927, + "remainder": 19.89927103895073, + "impact_significance": "MEDIUM", + "impact_significance_score": 2, + "features": { + "phq_attendance_concerts": { + "sum": 787 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_concerts": { + "sum": 19355 + }, + "phq_spend_concerts_accommodation": { + "sum": 3758 + }, + "phq_spend_concerts_hospitality": { + "sum": 8832 + }, + "phq_spend_concerts_transportation": { + "sum": 6764 + }, + "phq_rank_observances": { + "rank_levels": { + "2": 1 + } + }, + "phq_rank_public_holidays": { + "rank_levels": { + "4": 1, + "5": 1 + } + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_thunderstorm_retail": { + "sum": 56, + "max": 56 + } + }, + "phq_impact_sum": 56, + "phq_spend_sum": 38709, + "phq_attendance_sum": 19607, + "phq_rank_count": 9 + }, + { + "date": "2021-06-19", + "actual_demand": 80.0, + "baseline_demand": 79.90844533909288, + "remainder": 0.09155466090712139, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_community": { + "sum": 280 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_community": { + "sum": 2848 + }, + "phq_spend_community_hospitality": { + "sum": 1885 + }, + "phq_spend_community_transportation": { + "sum": 962 + }, + "phq_rank_observances": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_public_holidays": { + "rank_levels": { + "4": 1, + "5": 2 + } + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_thunderstorm_retail": { + "sum": 56, + "max": 56 + } + }, + "phq_impact_sum": 56, + "phq_spend_sum": 5695, + "phq_attendance_sum": 19100, + "phq_rank_count": 10 + }, + { + "date": "2021-06-20", + "actual_demand": 84.0, + "baseline_demand": 67.07823205057903, + "remainder": 16.921767949420968, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_concerts": { + "sum": 246 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_concerts": { + "sum": 3901 + }, + "phq_spend_concerts_hospitality": { + "sum": 2209 + }, + "phq_spend_concerts_transportation": { + "sum": 1692 + }, + "phq_rank_observances": { + "rank_levels": { + "2": 2, + "5": 1 + } + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_thunderstorm_retail": { + "sum": 25, + "max": 25 + } + }, + "phq_impact_sum": 25, + "phq_spend_sum": 7802, + "phq_attendance_sum": 19066, + "phq_rank_count": 9 + }, + { + "date": "2021-06-21", + "actual_demand": 91.0, + "baseline_demand": 79.1158828855306, + "remainder": 11.884117114469404, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-22", + "actual_demand": 96.0, + "baseline_demand": 93.64346589904916, + "remainder": 2.3565341009508387, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_concerts": { + "sum": 822 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_concerts": { + "sum": 20230 + }, + "phq_spend_concerts_accommodation": { + "sum": 3928 + }, + "phq_spend_concerts_hospitality": { + "sum": 9232 + }, + "phq_spend_concerts_transportation": { + "sum": 7069 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 40459, + "phq_attendance_sum": 19642, + "phq_rank_count": 6 + }, + { + "date": "2021-06-23", + "actual_demand": 88.0, + "baseline_demand": 95.74263012317203, + "remainder": -7.7426301231720345, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-24", + "actual_demand": 96.0, + "baseline_demand": 99.68731430141152, + "remainder": -3.6873143014115186, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-25", + "actual_demand": 97.0, + "baseline_demand": 102.57127927550286, + "remainder": -5.57127927550286, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-26", + "actual_demand": 75.0, + "baseline_demand": 89.9836068363395, + "remainder": -14.983606836339504, + "impact_significance": "NO_IMPACT", + "impact_significance_score": 0, + "features": { + "phq_attendance_concerts": { + "sum": 319 + }, + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_spend_concerts": { + "sum": 5064 + }, + "phq_spend_concerts_hospitality": { + "sum": 2868 + }, + "phq_spend_concerts_transportation": { + "sum": 2196 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + } + }, + "phq_impact_sum": 0, + "phq_spend_sum": 10128, + "phq_attendance_sum": 19139, + "phq_rank_count": 6 + }, + { + "date": "2021-06-27", + "actual_demand": 88.0, + "baseline_demand": 80.16344951178947, + "remainder": 7.836550488210534, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_flood_retail": { + "sum": 10, + "max": 10 + } + }, + "phq_impact_sum": 10, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-28", + "actual_demand": 105.0, + "baseline_demand": 93.81334859988509, + "remainder": 11.186651400114911, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_flood_retail": { + "sum": 21, + "max": 21 + } + }, + "phq_impact_sum": 21, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-29", + "actual_demand": 109.0, + "baseline_demand": 107.64564801761523, + "remainder": 1.3543519823847703, + "impact_significance": "WEAK", + "impact_significance_score": 1, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_flood_retail": { + "sum": 56, + "max": 56 + } + }, + "phq_impact_sum": 56, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + }, + { + "date": "2021-06-30", + "actual_demand": 86.0, + "baseline_demand": 106.41714640075705, + "remainder": -20.417146400757048, + "impact_significance": "MEDIUM", + "impact_significance_score": 2, + "features": { + "phq_attendance_school_holidays": { + "sum": 18820 + }, + "phq_rank_school_holidays": { + "rank_levels": { + "3": 3, + "4": 1 + } + }, + "phq_rank_academic_session": { + "rank_levels": { + "3": 1 + } + }, + "phq_rank_academic_holiday": { + "rank_levels": { + "4": 1 + } + }, + "phq_impact_severe_weather_flood_retail": { + "sum": 31, + "max": 31 + } + }, + "phq_impact_sum": 31, + "phq_spend_sum": 0, + "phq_attendance_sum": 18820, + "phq_rank_count": 6 + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_create.json b/tests/fixtures/requests_responses/beam_test/test_create.json new file mode 100644 index 0000000..3c7a2f4 --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_create.json @@ -0,0 +1,3 @@ +{ + "analysis_id": "abc123" +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_create_group.json b/tests/fixtures/requests_responses/beam_test/test_create_group.json new file mode 100644 index 0000000..3540f1b --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_create_group.json @@ -0,0 +1,3 @@ +{ + "group_id": "abc123" +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_empty_correlation.json b/tests/fixtures/requests_responses/beam_test/test_empty_correlation.json new file mode 100644 index 0000000..717649d --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_empty_correlation.json @@ -0,0 +1,6 @@ +{ + "model_version": "1.0", + "version": 1, + "dates": [], + "count": 0 +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_empty_feature_importance.json b/tests/fixtures/requests_responses/beam_test/test_empty_feature_importance.json new file mode 100644 index 0000000..c14f62c --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_empty_feature_importance.json @@ -0,0 +1,100 @@ +{ + "feature_importance": [ + { + "feature_group": "concerts", + "features": [ + "phq_attendance_concerts" + ], + "p_value": 0.0, + "important": true + }, + { + "feature_group": "severe-weather", + "features": [ + "phq_impact_severe_weather_heat_wave_retail" + ], + "p_value": 0.0015, + "important": true + }, + { + "feature_group": "performing-arts", + "features": [ + "phq_attendance_performing_arts" + ], + "p_value": 0.2646, + "important": false + }, + { + "feature_group": "school-holidays", + "features": [ + "phq_attendance_school_holidays" + ], + "p_value": 0.4283, + "important": false + }, + { + "feature_group": "festivals", + "features": [ + "phq_attendance_festivals" + ], + "p_value": 0.4689, + "important": false + }, + { + "feature_group": "public-holidays", + "features": [ + "phq_rank_public_holidays" + ], + "p_value": 0.5, + "important": false + }, + { + "feature_group": "expos", + "features": [ + "phq_attendance_expos" + ], + "p_value": 0.5611, + "important": false + }, + { + "feature_group": "conferences", + "features": [ + "phq_attendance_conferences" + ], + "p_value": 0.9605, + "important": false + }, + { + "feature_group": "observances", + "features": [ + "phq_rank_observances" + ], + "p_value": 0.9945, + "important": false + }, + { + "feature_group": "sports", + "features": [ + "phq_attendance_sports" + ], + "p_value": 0.9974, + "important": false + }, + { + "feature_group": "community", + "features": [ + "phq_attendance_community" + ], + "p_value": 0.998, + "important": false + }, + { + "feature_group": "academic", + "features": [ + "phq_rank_academic_holiday" + ], + "p_value": 0.999, + "important": false + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_empty_group_search.json b/tests/fixtures/requests_responses/beam_test/test_empty_group_search.json new file mode 100644 index 0000000..58111f6 --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_empty_group_search.json @@ -0,0 +1,4 @@ +{ + "count": 88, + "groups": [] +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_empty_search.json b/tests/fixtures/requests_responses/beam_test/test_empty_search.json new file mode 100644 index 0000000..9ebb62b --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_empty_search.json @@ -0,0 +1,4 @@ +{ + "count": 0, + "analyses": [] +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_feature_importance.json b/tests/fixtures/requests_responses/beam_test/test_feature_importance.json new file mode 100644 index 0000000..3681e3b --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_feature_importance.json @@ -0,0 +1,3 @@ +{ + "feature_importance": [] +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_group_search.json b/tests/fixtures/requests_responses/beam_test/test_group_search.json new file mode 100644 index 0000000..aef9fca --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_group_search.json @@ -0,0 +1,308 @@ +{ + "count": 88, + "groups": [ + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [ + { + "analysis_id": "analysis id", + "reason": "analysis_deleted", + "excluded_from": [ + "feature_importance", + "value_quant" + ] + } + ] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "retail", + "unit_descriptor": "Sales", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-09-13T03:59:45Z", + "update_dt": "2024-12-17T01:55:45Z", + "processed_dt": "2024-12-17T01:55:46Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "week", + "week_start_day": "monday", + "industry": null, + "unit_descriptor": "Sales", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-16T21:14:03.204830Z", + "update_dt": "2024-12-15T20:45:24Z", + "processed_dt": "2024-12-15T20:45:26Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": false, + "value_quant": false, + "excluded_analyses": [ + { + "analysis_id": "analysis id", + "reason": "analysis_demand_type_inconsistent", + "excluded_from": [ + "feature_importance", + "value_quant" + ] + }, + { + "analysis_id": "analysis id", + "reason": "analysis_demand_type_inconsistent", + "excluded_from": [ + "feature_importance", + "value_quant" + ] + } + ] + }, + "readiness_status": "failed", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-07T01:45:28.369118Z", + "update_dt": "2024-11-04T03:35:35Z", + "processed_dt": "2024-11-04T03:36:02Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": false, + "value_quant": false, + "excluded_analyses": [ + { + "analysis_id": "analysis id", + "reason": "analysis_demand_type_inconsistent", + "excluded_from": [ + "feature_importance", + "value_quant" + ] + }, + { + "analysis_id": "analysis id", + "reason": "analysis_demand_type_inconsistent", + "excluded_from": [ + "feature_importance", + "value_quant" + ] + } + ] + }, + "readiness_status": "failed", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-03T01:17:21.981509Z", + "update_dt": "2024-11-04T03:35:35Z", + "processed_dt": "2024-11-04T03:36:03Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "week", + "week_start_day": "monday", + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-07-17T21:27:50.392447Z", + "update_dt": "2024-10-16T21:31:59Z", + "processed_dt": "2024-10-16T21:40:03Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-06T00:10:40.455098Z", + "update_dt": "2024-10-16T21:21:02Z", + "processed_dt": "2024-10-16T21:36:26Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "week", + "week_start_day": "monday", + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-16T20:07:33.503432Z", + "update_dt": "2024-10-16T21:14:41Z", + "processed_dt": "2024-10-16T21:19:15Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-08T00:04:55.402304Z", + "update_dt": "2024-10-16T21:14:05Z", + "processed_dt": "2024-10-16T21:19:06Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-08T00:06:04.760846Z", + "update_dt": "2024-10-16T21:14:05Z", + "processed_dt": "2024-10-16T21:19:05Z" + }, + { + "group_id": "abc123", + "name": "test group name", + "analysis_ids": [ + "id1", + "id2" + ], + "user_id": "test user id", + "processing_completed": { + "feature_importance": true, + "value_quant": true, + "excluded_analyses": [] + }, + "readiness_status": "ready", + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "omitted", + "currency_code": "USD" + }, + "status": "active", + "create_dt": "2024-05-07T02:32:16.855072Z", + "update_dt": "2024-10-16T21:13:58Z", + "processed_dt": "2024-10-16T21:31:01Z" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/requests_responses/beam_test/test_search.json b/tests/fixtures/requests_responses/beam_test/test_search.json new file mode 100644 index 0000000..698e2dc --- /dev/null +++ b/tests/fixtures/requests_responses/beam_test/test_search.json @@ -0,0 +1,673 @@ +{ + "count": 1142, + "analyses": [ + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "37.7749", + "lon": "-122.4194" + }, + "radius": 3345.52, + "unit": "m", + "google_place_id": null + }, + "rank": { + "type": "local", + "levels": { + "phq": null, + "local": { + "min": 35, + "max": null + } + } + }, + "user_id": null, + "access_type": "limited", + "status": "active", + "readiness_status": null, + "readiness_checks": { + "date_range": null, + "error_code": null, + "missing_dates": null, + "validation_response": null, + "best_practice": false, + "best_practice_checks": { + "industry": false, + "rank": true, + "radius": true + } + }, + "processing_completed": { + "correlation": false, + "feature_importance": false, + "value_quant": false + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "other", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": null, + "create_dt": "2025-01-15T23:45:01Z", + "update_dt": "2025-01-15T23:45:01Z", + "processed_dt": null, + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "37.7749", + "lon": "-122.4194" + }, + "radius": 3345.52, + "unit": "m", + "google_place_id": null + }, + "rank": { + "type": "local", + "levels": { + "phq": null, + "local": { + "min": 35, + "max": null + } + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2019-01-01T00:00:00", + "end": "2021-09-26T00:00:00" + }, + "error_code": null, + "missing_dates": [], + "validation_response": { + "missing_data_percentage": 0.0, + "consecutive_nan": 0 + }, + "best_practice": false, + "best_practice_checks": { + "industry": false, + "rank": true, + "radius": true + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "other", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "America/Los_Angeles", + "create_dt": "2025-01-15T00:30:44Z", + "update_dt": "2025-01-15T00:53:48Z", + "processed_dt": "2025-01-15T00:53:30Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "38.9808979", + "lon": "-76.5400824" + }, + "radius": 9.23, + "unit": "mi", + "google_place_id": null + }, + "rank": { + "type": "phq", + "levels": { + "phq": { + "min": 35, + "max": null + }, + "local": null + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2024-01-01T00:00:00", + "end": "2024-11-12T00:00:00" + }, + "error_code": null, + "missing_dates": [], + "validation_response": { + "missing_data_percentage": 0.0, + "consecutive_nan": 0 + }, + "best_practice": true, + "best_practice_checks": { + "industry": true, + "rank": true, + "radius": true + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "accommodation", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "America/New_York", + "create_dt": "2024-12-17T02:11:22Z", + "update_dt": "2025-01-14T02:11:24Z", + "processed_dt": "2025-01-14T02:11:15Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "41.4917972", + "lon": "-72.0903866" + }, + "radius": 7.14, + "unit": "mi", + "google_place_id": null + }, + "rank": { + "type": "local", + "levels": { + "phq": null, + "local": { + "min": 50, + "max": null + } + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2019-06-01T00:00:00", + "end": "2019-12-31T00:00:00" + }, + "error_code": null, + "missing_dates": [ + "2019-11-28", + "2019-12-25" + ], + "validation_response": { + "missing_data_percentage": 0.935, + "consecutive_nan": 1 + }, + "best_practice": true, + "best_practice_checks": { + "industry": true, + "rank": true, + "radius": true + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "accommodation", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "America/New_York", + "create_dt": "2025-01-08T03:02:14Z", + "update_dt": "2025-01-08T03:03:43Z", + "processed_dt": "2025-01-08T03:03:33Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "41.657871", + "lon": "-91.534637" + }, + "radius": 1.76, + "unit": "km", + "google_place_id": null + }, + "rank": { + "type": "phq", + "levels": { + "phq": { + "min": 30, + "max": 100 + }, + "local": null + } + }, + "user_id": null, + "access_type": "full", + "status": "draft", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2021-06-01T00:00:00", + "end": "2022-07-04T00:00:00" + }, + "error_code": null, + "missing_dates": [ + "2022-01-15", + "2022-01-16" + ], + "validation_response": { + "missing_data_percentage": 0.501, + "consecutive_nan": 2 + }, + "best_practice": false, + "best_practice_checks": { + "industry": false, + "rank": false, + "radius": false + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": null, + "unit_descriptor": "", + "currency_code": "USD", + "unit_currency_multiplier": 75.0 + }, + "group_ids": null, + "tz": "America/Chicago", + "create_dt": "2022-09-15T03:21:44.436078Z", + "update_dt": "2024-12-16T23:02:47Z", + "processed_dt": "2024-12-16T23:02:30Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "25.793463", + "lon": "-80.337645" + }, + "radius": 10.0, + "unit": "km", + "google_place_id": null + }, + "rank": { + "type": "phq", + "levels": { + "phq": { + "min": 30, + "max": null + }, + "local": null + } + }, + "user_id": null, + "access_type": "full", + "status": "draft", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2017-01-02T00:00:00", + "end": "2019-12-31T00:00:00" + }, + "error_code": null, + "missing_dates": [ + "2017-04-16", + "2017-09-08", + "2017-09-09", + "2017-09-10", + "2017-09-11", + "2017-09-12", + "2017-11-23", + "2017-12-25", + "2018-04-01", + "2018-11-22", + "2018-12-25", + "2019-04-21", + "2019-09-02", + "2019-11-28", + "2019-12-25" + ], + "validation_response": { + "missing_data_percentage": 1.371, + "consecutive_nan": 5 + }, + "best_practice": false, + "best_practice_checks": { + "industry": true, + "rank": false, + "radius": false + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "retail", + "unit_descriptor": "Rooms", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": [ + "nbH6wEfXKA4" + ], + "tz": "America/New_York", + "create_dt": "2024-08-11T22:41:03.603998Z", + "update_dt": "2024-12-16T19:32:49Z", + "processed_dt": "2024-12-16T19:32:12Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "-36.84440239999999", + "lon": "174.7648646" + }, + "radius": 2.28, + "unit": "mi", + "google_place_id": null + }, + "rank": { + "type": "phq", + "levels": { + "phq": { + "min": 35, + "max": null + }, + "local": null + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2019-06-01T00:00:00", + "end": "2019-12-31T00:00:00" + }, + "error_code": null, + "missing_dates": [ + "2019-11-28", + "2019-12-25" + ], + "validation_response": { + "missing_data_percentage": 0.935, + "consecutive_nan": 1 + }, + "best_practice": false, + "best_practice_checks": { + "industry": true, + "rank": false, + "radius": false + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "parking", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "Pacific/Auckland", + "create_dt": "2024-10-06T20:42:57Z", + "update_dt": "2024-12-16T19:32:03Z", + "processed_dt": "2024-12-16T19:31:52Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "38.9808979", + "lon": "-76.5400824" + }, + "radius": 9.23, + "unit": "mi", + "google_place_id": null + }, + "rank": { + "type": "phq", + "levels": { + "phq": { + "min": 35, + "max": null + }, + "local": null + } + }, + "user_id": null, + "access_type": "limited", + "status": "active", + "readiness_status": null, + "readiness_checks": { + "date_range": null, + "error_code": null, + "missing_dates": null, + "validation_response": null, + "best_practice": true, + "best_practice_checks": { + "industry": true, + "rank": true, + "radius": true + } + }, + "processing_completed": { + "correlation": false, + "feature_importance": false, + "value_quant": false + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "accommodation", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": null, + "create_dt": "2024-12-16T02:08:29Z", + "update_dt": "2024-12-16T02:08:29Z", + "processed_dt": null, + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "-36.848448", + "lon": "174.7596161" + }, + "radius": 3418.29, + "unit": "m", + "google_place_id": null + }, + "rank": { + "type": "local", + "levels": { + "phq": null, + "local": { + "min": 30, + "max": null + } + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2024-01-01T00:00:00", + "end": "2024-07-01T00:00:00" + }, + "error_code": null, + "missing_dates": [], + "validation_response": { + "missing_data_percentage": 0.0, + "consecutive_nan": 0 + }, + "best_practice": false, + "best_practice_checks": { + "industry": true, + "rank": false, + "radius": true + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "retail", + "unit_descriptor": "omitted", + "currency_code": "USD", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "Pacific/Auckland", + "create_dt": "2024-12-16T01:58:18Z", + "update_dt": "2024-12-16T01:58:50Z", + "processed_dt": "2024-12-16T01:58:39Z", + "external_id": null, + "label": null + }, + { + "analysis_id": "abc123", + "name": "test name", + "location": { + "geopoint": { + "lat": "-36.84440239999999", + "lon": "174.7648646" + }, + "radius": 4.04, + "unit": "km", + "google_place_id": null + }, + "rank": { + "type": "phq", + "levels": { + "phq": { + "min": 35, + "max": null + }, + "local": null + } + }, + "user_id": null, + "access_type": "full", + "status": "active", + "readiness_status": "ready", + "readiness_checks": { + "date_range": { + "start": "2019-06-01T00:00:00", + "end": "2019-12-31T00:00:00" + }, + "error_code": null, + "missing_dates": [ + "2019-11-28", + "2019-12-25" + ], + "validation_response": { + "missing_data_percentage": 0.935, + "consecutive_nan": 1 + }, + "best_practice": false, + "best_practice_checks": { + "industry": true, + "rank": false, + "radius": false + } + }, + "processing_completed": { + "correlation": true, + "feature_importance": true, + "value_quant": true + }, + "demand_type": { + "interval": "day", + "week_start_day": null, + "industry": "accommodation", + "unit_descriptor": "Sales", + "currency_code": "EUR", + "unit_currency_multiplier": 1.0 + }, + "group_ids": null, + "tz": "Pacific/Auckland", + "create_dt": "2024-12-15T20:36:50Z", + "update_dt": "2024-12-16T01:46:37Z", + "processed_dt": "2024-12-16T01:46:26Z", + "external_id": null, + "label": null + } + ] +} \ No newline at end of file diff --git a/tests/test_client.py b/tests/test_client.py index 24a10a5..38c2f4c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -22,7 +22,7 @@ def tearDown(self): @with_config(ENDPOINT_URL="https://api.predicthq.com") def test_build_url(self): - assert self.client.build_url("v1") == "https://api.predicthq.com/v1/" + assert self.client.build_url("v1") == "https://api.predicthq.com/v1" def test_endpoints_initialization(self): assert isinstance(self.client.oauth2, endpoints.OAuth2Endpoint) diff --git a/usecases/beam/analysis/basics.py b/usecases/beam/analysis/basics.py new file mode 100644 index 0000000..69d4046 --- /dev/null +++ b/usecases/beam/analysis/basics.py @@ -0,0 +1,73 @@ +from predicthq import Client + +# Please copy paste your access token here +# or read our Quickstart documentation if you don't have a token yet +# https://docs.predicthq.com/guides/quickstart/ +ACCESS_TOKEN = "abc123" + +phq = Client(access_token=ACCESS_TOKEN) + +# Create a new analysis +# The created analysis id will be returned +analysis = phq.beam.analysis.create( + name="New Analysis", location__geopoint={"lat": "37.7749", "lon": "-122.4194"} +) +print(analysis.model_dump(exclude_none=True)) + + +# Upload demand data to an analysis +# The demand data can be uploaded from a CSV, NDJSON or JSON file file.abs +# The sdk accepts both a string file path or a file object.abs +# Upload demand data from a CSV file with a string file path +phq.beam.analysis.upload_demand(analysis_id="abc123", csv="./demand.csv") +# Upload demand data from a CSV file with a file object +with open("./demand.csv") as f: + phq.beam.analysis.upload_demand(analysis_id="abc123", csv=f) + +# Upload demand data from a NDJSON file with a string file path +phq.beam.analysis.upload_demand(analysis_id="abc123", ndjson="./demand.ndjson") +# Upload demand data from a NDJSON file with a file object +with open("./demand.ndjson") as f: + phq.beam.analysis.upload_demand(analysis_id="abc123", ndjson=f) + +# Upload demand data from a JSON file with a string file path +phq.beam.analysis.upload_demand(analysis_id="abc123", json="./demand.json") +# Upload demand data from a JSON file with a file object +with open("./demand.json") as f: + phq.beam.analysis.upload_demand(analysis_id="abc123", json=f) + +# Get an analysis +analysis = phq.beam.analysis.get(analysis_id="abc123") +print(analysis.model_dump(exclude_none=True)) + + +# Search for analyses +# The search() method returns an AnalysisResultSet which allows you to iterate +# over the first page of Analysis objects (10 analyses by default) +for analysis in phq.beam.analysis.search(q="My analysis").iter_all(): + print(analysis.model_dump(exclude_none=True)) + + +# Update an analysis +phq.beam.analysis.update(analysis_id="abc123", name="Updated Analysis") + + +# Delete an analysis +phq.beam.analysis.delete(analysis_id="abc123") + + +# Refresh an analysis +phq.beam.analysis.refresh(analysis_id="abc123") + + +# Get correlation results +# The get_correlation_results() method returns a CorrelationResultSet which allows you to iterate +for correlation_result in phq.beam.analysis.get_correlation_results( + analysis_id="abc123", date__gt="2024-01-01", date__lt="2024-01-31" +).iter_all(): + print(correlation_result.model_dump(exclude_none=True)) + + +# Get feature importance results +feature_importance = phq.beam.analysis.get_feature_importance(analysis_id="abc123") +print(feature_importance.model_dump(exclude_none=True)) diff --git a/usecases/beam/analysis_group/basics.py b/usecases/beam/analysis_group/basics.py new file mode 100644 index 0000000..c17a8eb --- /dev/null +++ b/usecases/beam/analysis_group/basics.py @@ -0,0 +1,46 @@ +from predicthq import Client + +# Please copy paste your access token here +# or read our Quickstart documentation if you don't have a token yet +# https://docs.predicthq.com/guides/quickstart/ +ACCESS_TOKEN = "abc123" + +phq = Client(access_token=ACCESS_TOKEN) + +# Create a new analysis group +# The created group id will be returned +group = phq.beam.analysis_group.create( + name="New Analysis Group", analysis_ids=["abc123", "def456"] +) +print(group.model_dump(exclude_none=True)) + + +# Get an analysis group +group = phq.beam.analysis_group.get(group_id="abc123") +print(group.model_dump(exclude_none=True)) + + +# Search for analysis groups +# The search() method returns an AnalysisGroupResultSet which allows you to iterate +# over the first page of AnalysisGroup objects (10 groups by default) +for group in phq.beam.analysis_group.search(q="My analyis group").iter_all(): + print(group.model_dump(exclude_none=True)) + + +# Update an analysis group +phq.beam.analysis_group.update(group_id="abc123", name="Updated Analysis Group") + + +# Delete an analysis group +phq.beam.analysis_group.delete(group_id="abc123") + + +# Refresh an analysis group +phq.beam.analysis_group.refresh(group_id="abc123") + + +# Get feature importance results +group_feature_importance = phq.beam.analysis_group.get_feature_importance( + group_id="abc123" +) +print(group_feature_importance.model_dump(exclude_none=True)) diff --git a/usecases/features/get_features_for_criteria.py b/usecases/features/get_features_for_criteria.py index 2e641d7..38775ca 100644 --- a/usecases/features/get_features_for_criteria.py +++ b/usecases/features/get_features_for_criteria.py @@ -71,3 +71,13 @@ feature.phq_viewership_sports_american_football.stats.median, feature.phq_viewership_sports_basketball_nba.stats.count, feature.phq_viewership_sports_basketball_nba.stats.median) + +# Convert the obtained features to a CSV file called features.csv in the current directory +phq.features.obtain_features( + active__gte="2019-11-28", + active__lte="2020-01-05", + location__geo={"lon": -71.49978, "lat": 41.62064, "radius": "150km"}, + phq_rank_public_holidays=True, + phq_attendance_sports__stats=["count", "median"], + phq_attendance_sports__phq_rank={"gt": 50}, +).to_csv("./features.csv", mode="w+")