diff --git a/metricflow-semantics/metricflow_semantics/model/semantics/linkable_spec_resolver.py b/metricflow-semantics/metricflow_semantics/model/semantics/linkable_spec_resolver.py index 7da7d635a5..3afb0ea84a 100644 --- a/metricflow-semantics/metricflow_semantics/model/semantics/linkable_spec_resolver.py +++ b/metricflow-semantics/metricflow_semantics/model/semantics/linkable_spec_resolver.py @@ -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 @@ -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 @@ -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( @@ -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) diff --git a/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py b/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py index eeae9299fc..8c3a2499f1 100644 --- a/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py +++ b/metricflow-semantics/metricflow_semantics/specs/patterns/metric_time_default_granularity.py @@ -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, @@ -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: @@ -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. diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_available_group_by_items.py/LinkableSpecSet/test_available_group_by_items__no_metrics__set0.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_available_group_by_items.py/LinkableSpecSet/test_available_group_by_items__no_metrics__set0.txt index 3f3066a685..81ebfcea27 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_available_group_by_items.py/LinkableSpecSet/test_available_group_by_items__no_metrics__set0.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_available_group_by_items.py/LinkableSpecSet/test_available_group_by_items__no_metrics__set0.txt @@ -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')", diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_matching_item_for_filters.py/GroupByItemResolution/test_ambiguous_metric_time_in_query_filter__no_metrics__result.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_matching_item_for_filters.py/GroupByItemResolution/test_ambiguous_metric_time_in_query_filter__no_metrics__result.txt index ac202ecaf3..98dd8771e6 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_matching_item_for_filters.py/GroupByItemResolution/test_ambiguous_metric_time_in_query_filter__no_metrics__result.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_matching_item_for_filters.py/GroupByItemResolution/test_ambiguous_metric_time_in_query_filter__no_metrics__result.txt @@ -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( diff --git a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_semantic_model_container.py/tuple/test_linkable_elements_for_no_metrics_query__result0.txt b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_semantic_model_container.py/tuple/test_linkable_elements_for_no_metrics_query__result0.txt index 7cf20ab18e..bb9f02050e 100644 --- a/metricflow-semantics/tests_metricflow_semantics/snapshots/test_semantic_model_container.py/tuple/test_linkable_elements_for_no_metrics_query__result0.txt +++ b/metricflow-semantics/tests_metricflow_semantics/snapshots/test_semantic_model_container.py/tuple/test_linkable_elements_for_no_metrics_query__result0.txt @@ -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',