Skip to content

Commit

Permalink
Support custom granularity in group by resolution for more metric types
Browse files Browse the repository at this point in the history
This logic is only hit for certain types of metrics (like cumulative and
 derived metrics) so I didn't catch it until adding tests for those
metric types.
  • Loading branch information
courtneyholcomb committed Sep 20, 2024
1 parent a5ccfd6 commit 89f3a74
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ class SemanticManifestLookup:

def __init__(self, semantic_manifest: SemanticManifest) -> None: # noqa: D107
self._semantic_manifest = semantic_manifest
self._semantic_model_lookup = SemanticModelLookup(semantic_manifest)
self._metric_lookup = MetricLookup(self._semantic_manifest, self._semantic_model_lookup)
self._time_spine_sources = TimeSpineSource.build_standard_time_spine_sources(semantic_manifest)
self._custom_granularities = TimeSpineSource.build_custom_granularities(list(self._time_spine_sources.values()))
self._semantic_model_lookup = SemanticModelLookup(
model=semantic_manifest, custom_granularities=self._custom_granularities
)
self._metric_lookup = MetricLookup(
semantic_manifest=self._semantic_manifest,
semantic_model_lookup=self._semantic_model_lookup,
custom_granularities=self._custom_granularities,
)

@property
def semantic_manifest(self) -> SemanticManifest: # noqa: D102
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@
from metricflow_semantics.model.semantics.semantic_model_join_evaluator import MAX_JOIN_HOPS
from metricflow_semantics.model.semantics.semantic_model_lookup import SemanticModelLookup
from metricflow_semantics.specs.time_dimension_spec import TimeDimensionSpec
from metricflow_semantics.time.granularity import ExpandedTimeGranularity

logger = logging.getLogger(__name__)


class MetricLookup:
"""Tracks semantic information for metrics by linking them to semantic models."""

def __init__(self, semantic_manifest: SemanticManifest, semantic_model_lookup: SemanticModelLookup) -> None:
def __init__(
self,
semantic_manifest: SemanticManifest,
semantic_model_lookup: SemanticModelLookup,
custom_granularities: Dict[str, ExpandedTimeGranularity],
) -> None:
"""Initializer.
Args:
Expand All @@ -36,6 +42,7 @@ def __init__(self, semantic_manifest: SemanticManifest, semantic_model_lookup: S
"""
self._metrics: Dict[MetricReference, Metric] = {}
self._semantic_model_lookup = semantic_model_lookup
self._custom_granularities = custom_granularities

for metric in semantic_manifest.metrics:
self._add_metric(metric)
Expand Down Expand Up @@ -185,6 +192,7 @@ def get_valid_agg_time_dimensions_for_metric(
valid_agg_time_dimension_specs = TimeDimensionSpec.generate_possible_specs_for_time_dimension(
time_dimension_reference=agg_time_dimension_reference,
entity_links=agg_time_dimension_entity_links,
custom_granularities=self._custom_granularities,
)
return valid_agg_time_dimension_specs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,21 @@
from metricflow_semantics.specs.measure_spec import MeasureSpec
from metricflow_semantics.specs.non_additive_dimension_spec import NonAdditiveDimensionSpec
from metricflow_semantics.specs.time_dimension_spec import DEFAULT_TIME_GRANULARITY, TimeDimensionSpec
from metricflow_semantics.time.granularity import ExpandedTimeGranularity

logger = logging.getLogger(__name__)


class SemanticModelLookup:
"""Tracks semantic information for semantic models held in a set of SemanticModelContainers."""

def __init__(
self,
model: SemanticManifest,
) -> None:
def __init__(self, model: SemanticManifest, custom_granularities: Dict[str, ExpandedTimeGranularity]) -> None:
"""Initializer.
Args:
model: the semantic manifest used for loading semantic model definitions
"""
self._custom_granularities = custom_granularities
self._measure_index: Dict[MeasureReference, SemanticModel] = {}
self._measure_aggs: Dict[MeasureReference, AggregationType] = {}
self._measure_agg_time_dimension: Dict[MeasureReference, TimeDimensionReference] = {}
Expand Down Expand Up @@ -371,6 +370,7 @@ def get_agg_time_dimension_specs_for_measure(
return TimeDimensionSpec.generate_possible_specs_for_time_dimension(
time_dimension_reference=agg_time_dimension,
entity_links=(entity_link,),
custom_granularities=self._custom_granularities,
)

def get_defined_time_granularity(self, time_dimension_reference: TimeDimensionReference) -> TimeGranularity:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def __init__(self, manifest_lookup: SemanticManifestLookup) -> None: # noqa: D1

self._metric_time_specs = tuple(
TimeDimensionSpec.generate_possible_specs_for_time_dimension(
time_dimension_reference=TimeDimensionReference(element_name=METRIC_TIME_ELEMENT_NAME), entity_links=()
time_dimension_reference=TimeDimensionReference(element_name=METRIC_TIME_ELEMENT_NAME),
entity_links=(),
custom_granularities=self._manifest_lookup._custom_granularities,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass
from enum import Enum
from typing import Any, List, Optional, Sequence, Tuple, Union
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union

from dbt_semantic_interfaces.naming.keywords import METRIC_TIME_ELEMENT_NAME
from dbt_semantic_interfaces.references import DimensionReference, EntityReference, TimeDimensionReference
Expand Down Expand Up @@ -193,30 +193,31 @@ def comparison_key(self, exclude_fields: Sequence[TimeDimensionSpecField] = ())

@staticmethod
def generate_possible_specs_for_time_dimension(
time_dimension_reference: TimeDimensionReference, entity_links: Tuple[EntityReference, ...]
time_dimension_reference: TimeDimensionReference,
entity_links: Tuple[EntityReference, ...],
custom_granularities: Dict[str, ExpandedTimeGranularity],
) -> List[TimeDimensionSpec]:
"""Generate a list of time dimension specs with all combinations of granularity & date part.
TODO: [custom calendar] decide whether to add support for custom granularities or rename this to indicate that
it only includes standard granularities.
"""
"""Generate a list of time dimension specs with all combinations of granularity & date part."""
time_dimension_specs: List[TimeDimensionSpec] = []
for time_granularity in TimeGranularity:
granularities = [
ExpandedTimeGranularity.from_time_granularity(time_granularity) for time_granularity in TimeGranularity
] + list(custom_granularities.values())
for time_granularity in granularities:
time_dimension_specs.append(
TimeDimensionSpec(
element_name=time_dimension_reference.element_name,
entity_links=entity_links,
time_granularity=ExpandedTimeGranularity.from_time_granularity(time_granularity),
time_granularity=time_granularity,
date_part=None,
)
)
for date_part in DatePart:
for time_granularity in date_part.compatible_granularities:
for compatible_granularity in date_part.compatible_granularities:
time_dimension_specs.append(
TimeDimensionSpec(
element_name=time_dimension_reference.element_name,
entity_links=entity_links,
time_granularity=ExpandedTimeGranularity.from_time_granularity(time_granularity),
time_granularity=ExpandedTimeGranularity.from_time_granularity(compatible_granularity),
date_part=date_part,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,38 @@
assert_linkable_element_set_snapshot_equal,
assert_object_snapshot_equal,
)
from metricflow_semantics.time.time_spine_source import TimeSpineSource

logger = logging.getLogger(__name__)


def build_semantic_model_lookup_from_manifest(semantic_manifest: SemanticManifest) -> SemanticModelLookup: # noqa: D103
time_spine_sources = TimeSpineSource.build_standard_time_spine_sources(semantic_manifest)
custom_granularities = TimeSpineSource.build_custom_granularities(list(time_spine_sources.values()))
return SemanticModelLookup(model=semantic_manifest, custom_granularities=custom_granularities)


@pytest.fixture
def semantic_model_lookup(simple_semantic_manifest: SemanticManifest) -> SemanticModelLookup: # noqa: D103
return SemanticModelLookup(
model=simple_semantic_manifest,
)
return build_semantic_model_lookup_from_manifest(simple_semantic_manifest)


@pytest.fixture
def multi_hop_semantic_model_lookup( # noqa: D103
multi_hop_join_manifest: SemanticManifest,
) -> SemanticModelLookup:
return SemanticModelLookup(model=multi_hop_join_manifest)
return build_semantic_model_lookup_from_manifest(multi_hop_join_manifest)


@pytest.fixture
def metric_lookup( # noqa: D103
simple_semantic_manifest: SemanticManifest, semantic_model_lookup: SemanticModelLookup
) -> MetricLookup:
return MetricLookup(semantic_manifest=simple_semantic_manifest, semantic_model_lookup=semantic_model_lookup)
return MetricLookup(
semantic_manifest=simple_semantic_manifest,
semantic_model_lookup=semantic_model_lookup,
custom_granularities=semantic_model_lookup._custom_granularities,
)


@pytest.fixture
Expand All @@ -46,6 +55,7 @@ def multi_hop_metric_lookup( # noqa: D103
return MetricLookup(
semantic_manifest=multi_hop_join_manifest,
semantic_model_lookup=multi_hop_semantic_model_lookup,
custom_granularities=multi_hop_semantic_model_lookup._custom_granularities,
)


Expand Down
4 changes: 1 addition & 3 deletions tests_metricflow/integration/test_configured_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,7 @@ def filter_not_supported_features(

@pytest.mark.parametrize(
"name",
# CONFIGURED_INTEGRATION_TESTS_REPOSITORY.all_test_case_names,
["itest_granularity.yaml/cumulative_metric_with_custom_granularity"],
CONFIGURED_INTEGRATION_TESTS_REPOSITORY.all_test_case_names,
ids=lambda name: f"name={name}",
)
def test_case(
Expand Down Expand Up @@ -304,7 +303,6 @@ def test_case(
)

actual = query_result.result_df
assert 0, query_result.sql

expected = sql_client.query(
jinja2.Template(
Expand Down

0 comments on commit 89f3a74

Please sign in to comment.