Skip to content

Commit

Permalink
Handle no-metric queries with sub-daily metric_time
Browse files Browse the repository at this point in the history
Use time spines to determine granularity options and default to DAY for metric_time.
  • Loading branch information
courtneyholcomb committed Jul 27, 2024
1 parent ee005fb commit 54bf85a
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
from metricflow_semantics.model.semantics.linkable_element_set import LinkableElementSet
from metricflow_semantics.model.semantics.semantic_model_join_evaluator import SemanticModelJoinEvaluator
from metricflow_semantics.specs.time_dimension_spec import DEFAULT_TIME_GRANULARITY
from metricflow_semantics.time.time_spine_source import TimeSpineSource

if TYPE_CHECKING:
from metricflow_semantics.model.semantics.semantic_model_lookup import SemanticModelLookup
Expand Down Expand Up @@ -124,6 +125,7 @@ def __init__(
# Sort semantic models by name for consistency in building derived objects.
self._semantic_models = sorted(self._semantic_manifest.semantic_models, key=lambda x: x.name)
self._join_evaluator = SemanticModelJoinEvaluator(semantic_model_lookup)
self._time_spine_sources = TimeSpineSource.create_from_manifest(self._semantic_manifest)

assert max_entity_links >= 0
self._max_entity_links = max_entity_links
Expand Down Expand Up @@ -454,6 +456,7 @@ def _get_metric_time_elements(self, measure_reference: Optional[MeasureReference
on what aggregation time dimension was used to define the measure.
"""
measure_semantic_model: Optional[SemanticModel] = None
defined_granularity: Optional[TimeGranularity] = None
if measure_reference:
measure_semantic_model = self._get_semantic_model_for_measure(measure_reference)
measure_agg_time_dimension_reference = measure_semantic_model.checked_agg_time_dimension_for_measure(
Expand All @@ -463,15 +466,20 @@ def _get_metric_time_elements(self, measure_reference: Optional[MeasureReference
semantic_model=measure_semantic_model,
time_dimension_reference=measure_agg_time_dimension_reference,
)
possible_metric_time_granularities = tuple(
time_granularity
for time_granularity in TimeGranularity
if defined_granularity.is_smaller_than_or_equal(time_granularity)
)
else:
defined_granularity = DEFAULT_TIME_GRANULARITY

# It's possible to aggregate measures to coarser time granularities (except with cumulative metrics).
possible_metric_time_granularities = tuple(
time_granularity
for time_granularity in TimeGranularity
if defined_granularity.is_smaller_than_or_equal(time_granularity)
)
# If querying metric_time without metrics, will query from time spines.
# Defaults to DAY granularity if available in time spines, else smallest available granularity.
min_time_spine_granularity = min(self._time_spine_sources.keys())
possible_metric_time_granularities = tuple(
time_granularity
for time_granularity in TimeGranularity
if min_time_spine_granularity.is_smaller_than_or_equal(time_granularity)
)

# For each of the possible time granularities, create a LinkableDimension.
path_key_to_linkable_dimensions: Dict[ElementPathKey, List[LinkableDimension]] = defaultdict(list)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from metricflow_semantics.specs.patterns.spec_pattern import SpecPattern
from metricflow_semantics.specs.spec_set import group_specs_by_type
from metricflow_semantics.specs.time_dimension_spec import (
DEFAULT_TIME_GRANULARITY,
TimeDimensionSpec,
TimeDimensionSpecComparisonKey,
TimeDimensionSpecField,
Expand Down Expand Up @@ -47,10 +48,13 @@ def __init__(self, max_metric_default_time_granularity: Optional[TimeGranularity
def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpec]:
spec_set = group_specs_by_type(candidate_specs)

# If there are no metrics or metric_time specs in the query, skip this filter.
if not (self._max_metric_default_time_granularity and spec_set.metric_time_specs):
# If there are no metric_time specs in the query, skip this filter.
if not spec_set.metric_time_specs:
return candidate_specs

# If there are metrics in the query, use max metric default. For no-metric queries, use standard default.
default_granularity = self._max_metric_default_time_granularity or DEFAULT_TIME_GRANULARITY

spec_key_to_grains: Dict[TimeDimensionSpecComparisonKey, Set[TimeGranularity]] = defaultdict(set)
spec_key_to_specs: Dict[TimeDimensionSpecComparisonKey, Tuple[TimeDimensionSpec, ...]] = defaultdict(tuple)
for metric_time_spec in spec_set.metric_time_specs:
Expand All @@ -60,10 +64,8 @@ def match(self, candidate_specs: Sequence[InstanceSpec]) -> Sequence[InstanceSpe

matched_metric_time_specs: Tuple[TimeDimensionSpec, ...] = ()
for spec_key, time_grains in spec_key_to_grains.items():
if self._max_metric_default_time_granularity in time_grains:
matched_metric_time_specs += (
spec_key_to_specs[spec_key][0].with_grain(self._max_metric_default_time_granularity),
)
if default_granularity in time_grains:
matched_metric_time_specs += (spec_key_to_specs[spec_key][0].with_grain(default_granularity),)
else:
# If default_granularity is not in the available options, then time granularity was specified in the request
# and a default is not needed here. Pass all options through for this spec key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,55 @@
"TimeDimension('metric_time', 'day', date_part_name='month')",
"TimeDimension('metric_time', 'day', date_part_name='quarter')",
"TimeDimension('metric_time', 'day', date_part_name='year')",
"TimeDimension('metric_time', 'hour')",
"TimeDimension('metric_time', 'hour', date_part_name='day')",
"TimeDimension('metric_time', 'hour', date_part_name='dow')",
"TimeDimension('metric_time', 'hour', date_part_name='doy')",
"TimeDimension('metric_time', 'hour', date_part_name='month')",
"TimeDimension('metric_time', 'hour', date_part_name='quarter')",
"TimeDimension('metric_time', 'hour', date_part_name='year')",
"TimeDimension('metric_time', 'microsecond')",
"TimeDimension('metric_time', 'microsecond', date_part_name='day')",
"TimeDimension('metric_time', 'microsecond', date_part_name='dow')",
"TimeDimension('metric_time', 'microsecond', date_part_name='doy')",
"TimeDimension('metric_time', 'microsecond', date_part_name='month')",
"TimeDimension('metric_time', 'microsecond', date_part_name='quarter')",
"TimeDimension('metric_time', 'microsecond', date_part_name='year')",
"TimeDimension('metric_time', 'millisecond')",
"TimeDimension('metric_time', 'millisecond', date_part_name='day')",
"TimeDimension('metric_time', 'millisecond', date_part_name='dow')",
"TimeDimension('metric_time', 'millisecond', date_part_name='doy')",
"TimeDimension('metric_time', 'millisecond', date_part_name='month')",
"TimeDimension('metric_time', 'millisecond', date_part_name='quarter')",
"TimeDimension('metric_time', 'millisecond', date_part_name='year')",
"TimeDimension('metric_time', 'minute')",
"TimeDimension('metric_time', 'minute', date_part_name='day')",
"TimeDimension('metric_time', 'minute', date_part_name='dow')",
"TimeDimension('metric_time', 'minute', date_part_name='doy')",
"TimeDimension('metric_time', 'minute', date_part_name='month')",
"TimeDimension('metric_time', 'minute', date_part_name='quarter')",
"TimeDimension('metric_time', 'minute', date_part_name='year')",
"TimeDimension('metric_time', 'month')",
"TimeDimension('metric_time', 'month', date_part_name='month')",
"TimeDimension('metric_time', 'month', date_part_name='quarter')",
"TimeDimension('metric_time', 'month', date_part_name='year')",
"TimeDimension('metric_time', 'nanosecond')",
"TimeDimension('metric_time', 'nanosecond', date_part_name='day')",
"TimeDimension('metric_time', 'nanosecond', date_part_name='dow')",
"TimeDimension('metric_time', 'nanosecond', date_part_name='doy')",
"TimeDimension('metric_time', 'nanosecond', date_part_name='month')",
"TimeDimension('metric_time', 'nanosecond', date_part_name='quarter')",
"TimeDimension('metric_time', 'nanosecond', date_part_name='year')",
"TimeDimension('metric_time', 'quarter')",
"TimeDimension('metric_time', 'quarter', date_part_name='quarter')",
"TimeDimension('metric_time', 'quarter', date_part_name='year')",
"TimeDimension('metric_time', 'second')",
"TimeDimension('metric_time', 'second', date_part_name='day')",
"TimeDimension('metric_time', 'second', date_part_name='dow')",
"TimeDimension('metric_time', 'second', date_part_name='doy')",
"TimeDimension('metric_time', 'second', date_part_name='month')",
"TimeDimension('metric_time', 'second', date_part_name='quarter')",
"TimeDimension('metric_time', 'second', date_part_name='year')",
"TimeDimension('metric_time', 'week')",
"TimeDimension('metric_time', 'week', date_part_name='month')",
"TimeDimension('metric_time', 'week', date_part_name='quarter')",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ GroupByItemResolution(
time_granularity=DAY,
): (
LinkableDimension(
properties=(METRIC_TIME,),
properties=(DERIVED_TIME_GRANULARITY, METRIC_TIME),
element_name='metric_time',
dimension_type=TIME,
join_path=SemanticModelJoinPath(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,6 @@
'lux_listing__listing__lux_listing__twice_bookings_fill_nulls_with_0_without_time_spine',
'lux_listing__listing__lux_listing__views',
'lux_listing__listing__lux_listing__views_times_booking_value',
'metric_time__day',
'revenue_instance__ds__day',
'revenue_instance__ds__extract_day',
'revenue_instance__ds__extract_dow',
Expand Down

0 comments on commit 54bf85a

Please sign in to comment.