| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/segmentation_platform/internal/metadata/metadata_utils.h" |
| |
| #include <inttypes.h> |
| |
| #include <algorithm> |
| |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/to_string.h" |
| #include "base/time/time.h" |
| #include "components/segmentation_platform/internal/database/signal_key.h" |
| #include "components/segmentation_platform/internal/proto/model_prediction.pb.h" |
| #include "components/segmentation_platform/public/proto/aggregation.pb.h" |
| #include "components/segmentation_platform/public/proto/model_metadata.pb.h" |
| #include "components/segmentation_platform/public/proto/output_config.pb.h" |
| #include "components/segmentation_platform/public/proto/segmentation_platform.pb.h" |
| #include "components/segmentation_platform/public/proto/types.pb.h" |
| |
| namespace segmentation_platform::metadata_utils { |
| |
| namespace { |
| uint64_t GetExpectedTensorLength(const proto::UMAFeature& feature) { |
| switch (feature.aggregation()) { |
| case proto::Aggregation::COUNT: |
| case proto::Aggregation::COUNT_BOOLEAN: |
| case proto::Aggregation::BUCKETED_COUNT_BOOLEAN_TRUE_COUNT: |
| case proto::Aggregation::SUM: |
| case proto::Aggregation::SUM_BOOLEAN: |
| case proto::Aggregation::BUCKETED_SUM_BOOLEAN_TRUE_COUNT: |
| return feature.bucket_count() == 0 ? 0 : 1; |
| case proto::Aggregation::BUCKETED_COUNT: |
| case proto::Aggregation::BUCKETED_COUNT_BOOLEAN: |
| case proto::Aggregation::BUCKETED_CUMULATIVE_COUNT: |
| case proto::Aggregation::BUCKETED_SUM: |
| case proto::Aggregation::BUCKETED_SUM_BOOLEAN: |
| case proto::Aggregation::BUCKETED_CUMULATIVE_SUM: |
| return feature.bucket_count(); |
| case proto::Aggregation::LATEST_OR_DEFAULT: |
| return 1; |
| case proto::Aggregation::UNKNOWN: |
| NOTREACHED(); |
| } |
| } |
| |
| std::string FeatureToString(const proto::UMAFeature& feature) { |
| std::string result; |
| if (feature.has_type()) |
| result = "type:" + proto::SignalType_Name(feature.type()) + ", "; |
| if (feature.has_name()) |
| result.append("name:" + feature.name() + ", "); |
| if (feature.has_name_hash()) { |
| result.append( |
| base::StringPrintf("name_hash:0x%" PRIx64 ", ", feature.name_hash())); |
| } |
| if (feature.has_bucket_count()) { |
| result.append(base::StringPrintf("bucket_count:%" PRIu64 ", ", |
| feature.bucket_count())); |
| } |
| if (feature.has_tensor_length()) { |
| result.append(base::StringPrintf("tensor_length:%" PRIu64 ", ", |
| feature.tensor_length())); |
| } |
| if (feature.has_aggregation()) { |
| result.append("aggregation:" + |
| proto::Aggregation_Name(feature.aggregation())); |
| } |
| if (base::EndsWith(result, ", ")) |
| result.resize(result.size() - 2); |
| return result; |
| } |
| |
| ValidationResult ValidateBinaryClassifier( |
| const proto::Predictor::BinaryClassifier& classifier) { |
| if (classifier.positive_label().empty() || |
| classifier.negative_label().empty()) { |
| return ValidationResult::kBinaryClassifierEmptyLabels; |
| } |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateBinnedClassifier( |
| const proto::Predictor::BinnedClassifier& classifier) { |
| float prev = std::numeric_limits<float>::lowest(); |
| for (const auto& bin : classifier.bins()) { |
| if (bin.label().empty()) { |
| return ValidationResult::kBinnedClassifierEmptyLabels; |
| } |
| if (bin.min_range() < prev) { |
| return ValidationResult::kBinnedClassifierBinsUnsorted; |
| } |
| prev = bin.min_range(); |
| } |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| } // namespace |
| |
| ValidationResult ValidateSegmentInfo(const proto::SegmentInfo& segment_info) { |
| if (segment_info.segment_id() == proto::OPTIMIZATION_TARGET_UNKNOWN) { |
| return ValidationResult::kSegmentIDNotFound; |
| } |
| |
| if (!segment_info.has_model_metadata()) |
| return ValidationResult::kMetadataNotFound; |
| |
| return ValidateMetadata(segment_info.model_metadata()); |
| } |
| |
| ValidationResult ValidateMetadata( |
| const proto::SegmentationModelMetadata& model_metadata) { |
| if (proto::CurrentVersion::METADATA_VERSION < |
| model_metadata.version_info().metadata_min_version()) { |
| return ValidationResult::kVersionNotSupported; |
| } |
| |
| if (model_metadata.time_unit() == proto::TimeUnit::UNKNOWN_TIME_UNIT) |
| return ValidationResult::kTimeUnitInvald; |
| |
| if (model_metadata.features_size() != 0 && |
| model_metadata.input_features_size() != 0) { |
| return ValidationResult::kFeatureListInvalid; |
| } |
| |
| if (model_metadata.has_output_config() && |
| model_metadata.discrete_mappings_size() > 0) { |
| return ValidationResult::kDiscreteMappingAndOutputConfigFound; |
| } |
| |
| if (model_metadata.has_output_config()) { |
| auto output_config_result = |
| ValidateOutputConfig(model_metadata.output_config()); |
| if (output_config_result != ValidationResult::kValidationSuccess) { |
| return output_config_result; |
| } |
| } |
| |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateMetadataUmaFeature(const proto::UMAFeature& feature) { |
| if (feature.type() == proto::SignalType::UNKNOWN_SIGNAL_TYPE || |
| feature.type() == proto::SignalType::UKM_EVENT) |
| return ValidationResult::kSignalTypeInvalid; |
| |
| if ((feature.type() == proto::SignalType::HISTOGRAM_ENUM || |
| feature.type() == proto::SignalType::HISTOGRAM_VALUE) && |
| feature.name().empty()) { |
| return ValidationResult::kFeatureNameNotFound; |
| } |
| |
| if (feature.name_hash() == 0) |
| return ValidationResult::kFeatureNameHashNotFound; |
| |
| if (!feature.name().empty() && |
| base::HashMetricName(feature.name()) != feature.name_hash()) { |
| return ValidationResult::kFeatureNameHashDoesNotMatchName; |
| } |
| |
| if (feature.aggregation() == proto::Aggregation::UNKNOWN) |
| return ValidationResult::kFeatureAggregationNotFound; |
| |
| if (GetExpectedTensorLength(feature) != feature.tensor_length()) |
| return ValidationResult::kFeatureTensorLengthInvalid; |
| |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateMetadataSqlFeature(const proto::SqlFeature& feature) { |
| if (feature.sql().empty()) |
| return ValidationResult::kFeatureSqlQueryEmpty; |
| |
| int total_tensor_length = 0; |
| for (int i = 0; i < feature.bind_values_size(); ++i) { |
| const auto& bind_value = feature.bind_values(i); |
| if (!bind_value.has_value() || |
| bind_value.param_type() == proto::SqlFeature::BindValue::UNKNOWN || |
| ValidateMetadataCustomInput(bind_value.value()) != |
| ValidationResult::kValidationSuccess) { |
| return ValidationResult::kFeatureBindValuesInvalid; |
| } |
| total_tensor_length += bind_value.value().tensor_length(); |
| } |
| |
| if (total_tensor_length != std::ranges::count(feature.sql(), '?')) { |
| return ValidationResult::kFeatureBindValuesInvalid; |
| } |
| |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateMetadataCustomInput( |
| const proto::CustomInput& custom_input) { |
| if (custom_input.fill_policy() == proto::CustomInput::UNKNOWN_FILL_POLICY) { |
| // If the current fill policy is not supported or not filled, we must use |
| // the given default value list, therefore the default value list must |
| // provide enough input values as specified by tensor length. |
| if (custom_input.tensor_length() > custom_input.default_value_size()) { |
| return ValidationResult::kCustomInputInvalid; |
| } |
| } else if (custom_input.default_value_size() != 0) { |
| // The default value should be longer than the tensor length. |
| if (custom_input.tensor_length() > custom_input.default_value_size()) { |
| return ValidationResult::kCustomInputInvalid; |
| } |
| } |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateMetadataAndFeatures( |
| const proto::SegmentationModelMetadata& model_metadata) { |
| auto metadata_result = ValidateMetadata(model_metadata); |
| if (metadata_result != ValidationResult::kValidationSuccess) |
| return metadata_result; |
| |
| if (model_metadata.has_output_config()) { |
| auto output_config_result = |
| ValidateOutputConfig(model_metadata.output_config()); |
| if (output_config_result != ValidationResult::kValidationSuccess) { |
| return output_config_result; |
| } |
| } |
| |
| for (int i = 0; i < model_metadata.features_size(); ++i) { |
| const auto& feature = model_metadata.features(i); |
| auto feature_result = ValidateMetadataUmaFeature(feature); |
| if (feature_result != ValidationResult::kValidationSuccess) |
| return feature_result; |
| } |
| |
| for (int i = 0; i < model_metadata.input_features_size(); ++i) { |
| const auto& feature = model_metadata.input_features(i); |
| if (feature.has_uma_feature()) { |
| auto feature_result = ValidateMetadataUmaFeature(feature.uma_feature()); |
| if (feature_result != ValidationResult::kValidationSuccess) |
| return feature_result; |
| } else if (feature.has_custom_input()) { |
| auto feature_result = ValidateMetadataCustomInput(feature.custom_input()); |
| if (feature_result != ValidationResult::kValidationSuccess) |
| return feature_result; |
| } else if (feature.has_sql_feature()) { |
| // TODO(haileywang): Fix sql validation with other requirements. |
| if (feature.sql_feature().sql().empty()) |
| return ValidationResult::kFeatureListInvalid; |
| } else { |
| return ValidationResult::kFeatureListInvalid; |
| } |
| } |
| |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateIndexedTensors( |
| const processing::IndexedTensors& tensor, |
| size_t expected_size) { |
| if (tensor.size() != expected_size) |
| return ValidationResult::kIndexedTensorsInvalid; |
| for (size_t i = 0; i < tensor.size(); ++i) { |
| if (tensor.count(i) != 1) |
| return ValidationResult::kIndexedTensorsInvalid; |
| } |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateSegmentInfoMetadataAndFeatures( |
| const proto::SegmentInfo& segment_info) { |
| auto segment_info_result = ValidateSegmentInfo(segment_info); |
| if (segment_info_result != ValidationResult::kValidationSuccess) |
| return segment_info_result; |
| |
| return ValidateMetadataAndFeatures(segment_info.model_metadata()); |
| } |
| |
| ValidationResult ValidateOutputConfig( |
| const proto::OutputConfig& output_config) { |
| if (!output_config.has_predictor()) { |
| return ValidationResult::kPredictorTypeMissing; |
| } |
| const auto& predictor = output_config.predictor(); |
| if (predictor.has_generic_predictor()) { |
| if (predictor.generic_predictor().output_labels_size() == 0) { |
| return ValidationResult::kGenericPredictorMissingLabels; |
| } |
| } |
| if (predictor.has_binary_classifier()) { |
| ValidationResult result = |
| ValidateBinaryClassifier(predictor.binary_classifier()); |
| if (result != ValidationResult::kValidationSuccess) { |
| return result; |
| } |
| } |
| if (predictor.has_binned_classifier()) { |
| ValidationResult result = |
| ValidateBinnedClassifier(predictor.binned_classifier()); |
| if (result != ValidationResult::kValidationSuccess) { |
| return result; |
| } |
| } |
| if (predictor.has_multi_class_classifier()) { |
| ValidationResult result = |
| ValidateMultiClassClassifier(predictor.multi_class_classifier()); |
| if (result != ValidationResult::kValidationSuccess) { |
| return result; |
| } |
| } |
| |
| if (output_config.has_predicted_result_ttl()) { |
| const auto& result_ttl = output_config.predicted_result_ttl(); |
| if (!result_ttl.has_default_ttl()) { |
| return ValidationResult::kDefaultTtlIsMissing; |
| } |
| if (result_ttl.time_unit() == proto::TimeUnit::UNKNOWN_TIME_UNIT) { |
| return ValidationResult::kPredictionTtlTimeUnitInvalid; |
| } |
| } |
| |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| ValidationResult ValidateMultiClassClassifier( |
| const proto::Predictor_MultiClassClassifier& multi_class_classifier) { |
| if (multi_class_classifier.class_labels_size() == 0) { |
| return ValidationResult::kMultiClassClassifierHasNoLabels; |
| } |
| |
| if (multi_class_classifier.has_threshold() && |
| multi_class_classifier.class_thresholds_size() > 0) { |
| return ValidationResult::kMultiClassClassifierUsesBothThresholdTypes; |
| } |
| |
| if (multi_class_classifier.class_thresholds_size() > 0 && |
| multi_class_classifier.class_thresholds_size() != |
| multi_class_classifier.class_labels_size()) { |
| return ValidationResult:: |
| kMultiClassClassifierClassAndThresholdCountMismatch; |
| } |
| |
| return ValidationResult::kValidationSuccess; |
| } |
| |
| void SetFeatureNameHashesFromName( |
| proto::SegmentationModelMetadata* model_metadata) { |
| for (int i = 0; i < model_metadata->features_size(); ++i) { |
| proto::UMAFeature* feature = model_metadata->mutable_features(i); |
| feature->set_name_hash(base::HashMetricName(feature->name())); |
| } |
| } |
| |
| bool HasExpiredOrUnavailableResult(const proto::SegmentInfo& segment_info, |
| const base::Time& now) { |
| if (!segment_info.has_prediction_result()) |
| return true; |
| if (segment_info.prediction_result().result_size() == 0) { |
| return true; |
| } |
| |
| base::Time last_result_timestamp = base::Time::FromDeltaSinceWindowsEpoch( |
| base::Microseconds(segment_info.prediction_result().timestamp_us())); |
| |
| base::TimeDelta result_ttl = |
| segment_info.model_metadata().result_time_to_live() * |
| GetTimeUnit(segment_info.model_metadata()); |
| |
| return last_result_timestamp + result_ttl < now; |
| } |
| |
| bool HasFreshResults(const proto::SegmentInfo& segment_info, |
| const base::Time& now) { |
| if (!segment_info.has_prediction_result()) |
| return false; |
| |
| const proto::SegmentationModelMetadata& metadata = |
| segment_info.model_metadata(); |
| |
| base::Time last_result_timestamp = base::Time::FromDeltaSinceWindowsEpoch( |
| base::Microseconds(segment_info.prediction_result().timestamp_us())); |
| base::TimeDelta result_ttl = |
| metadata.result_time_to_live() * GetTimeUnit(metadata); |
| |
| return now - last_result_timestamp < result_ttl; |
| } |
| |
| base::TimeDelta GetTimeUnit( |
| const proto::SegmentationModelMetadata& model_metadata) { |
| proto::TimeUnit time_unit = model_metadata.time_unit(); |
| return ConvertToTimeDelta(time_unit); |
| } |
| |
| base::TimeDelta ConvertToTimeDelta(proto::TimeUnit time_unit) { |
| switch (time_unit) { |
| case proto::TimeUnit::YEAR: |
| return base::Days(365); |
| case proto::TimeUnit::MONTH: |
| return base::Days(30); |
| case proto::TimeUnit::WEEK: |
| return base::Days(7); |
| case proto::TimeUnit::DAY: |
| return base::Days(1); |
| case proto::TimeUnit::HOUR: |
| return base::Hours(1); |
| case proto::TimeUnit::MINUTE: |
| return base::Minutes(1); |
| case proto::TimeUnit::SECOND: |
| return base::Seconds(1); |
| case proto::TimeUnit::UNKNOWN_TIME_UNIT: |
| [[fallthrough]]; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| SignalKey::Kind SignalTypeToSignalKind(proto::SignalType signal_type) { |
| switch (signal_type) { |
| case proto::SignalType::USER_ACTION: |
| return SignalKey::Kind::USER_ACTION; |
| case proto::SignalType::HISTOGRAM_ENUM: |
| return SignalKey::Kind::HISTOGRAM_ENUM; |
| case proto::SignalType::HISTOGRAM_VALUE: |
| return SignalKey::Kind::HISTOGRAM_VALUE; |
| case proto::SignalType::UKM_EVENT: |
| case proto::SignalType::UNKNOWN_SIGNAL_TYPE: |
| return SignalKey::Kind::UNKNOWN; |
| } |
| } |
| |
| proto::SignalType SignalKindToSignalType(SignalKey::Kind kind) { |
| switch (kind) { |
| case SignalKey::Kind::USER_ACTION: |
| return proto::SignalType::USER_ACTION; |
| case SignalKey::Kind::HISTOGRAM_ENUM: |
| return proto::SignalType::HISTOGRAM_ENUM; |
| case SignalKey::Kind::HISTOGRAM_VALUE: |
| return proto::SignalType::HISTOGRAM_VALUE; |
| case SignalKey::Kind::UNKNOWN: |
| return proto::SignalType::UNKNOWN_SIGNAL_TYPE; |
| } |
| } |
| |
| float ConvertToDiscreteScore(const std::string& mapping_key, |
| float input_score, |
| const proto::SegmentationModelMetadata& metadata) { |
| auto iter = metadata.discrete_mappings().find(mapping_key); |
| if (iter == metadata.discrete_mappings().end()) { |
| iter = |
| metadata.discrete_mappings().find(metadata.default_discrete_mapping()); |
| if (iter == metadata.discrete_mappings().end()) |
| return input_score; |
| } |
| CHECK(iter != metadata.discrete_mappings().end()); |
| |
| const auto& mapping = iter->second; |
| |
| // Iterate over the entries and find the largest entry whose min result is |
| // equal to or less than the input. |
| int discrete_result = 0; |
| float largest_score_below_input_score = std::numeric_limits<float>::min(); |
| for (int i = 0; i < mapping.entries_size(); i++) { |
| const auto& entry = mapping.entries(i); |
| if (entry.min_result() <= input_score && |
| entry.min_result() > largest_score_below_input_score) { |
| largest_score_below_input_score = entry.min_result(); |
| discrete_result = entry.rank(); |
| } |
| } |
| |
| return discrete_result; |
| } |
| |
| std::string SegmetationModelMetadataToString( |
| const proto::SegmentationModelMetadata& model_metadata) { |
| std::string result; |
| for (const auto& feature : model_metadata.features()) { |
| result.append("feature:{" + FeatureToString(feature) + "}, "); |
| } |
| if (model_metadata.has_time_unit()) { |
| result.append( |
| "time_unit:" + proto::TimeUnit_Name(model_metadata.time_unit()) + ", "); |
| } |
| if (model_metadata.has_bucket_duration()) { |
| result.append(base::StringPrintf("bucket_duration:%" PRIu64 ", ", |
| model_metadata.bucket_duration())); |
| } |
| if (model_metadata.has_signal_storage_length()) { |
| result.append(base::StringPrintf("signal_storage_length:%" PRId64 ", ", |
| model_metadata.signal_storage_length())); |
| } |
| if (model_metadata.has_min_signal_collection_length()) { |
| result.append( |
| base::StringPrintf("min_signal_collection_length:%" PRId64 ", ", |
| model_metadata.min_signal_collection_length())); |
| } |
| if (model_metadata.has_result_time_to_live()) { |
| result.append(base::StringPrintf("result_time_to_live:%" PRId64 ", ", |
| model_metadata.result_time_to_live())); |
| } |
| if (model_metadata.has_upload_tensors()) { |
| result.append(base::StringPrintf( |
| "upload_tensors: %s", base::ToString(model_metadata.upload_tensors()))); |
| } |
| |
| if (base::EndsWith(result, ", ")) |
| result.resize(result.size() - 2); |
| return result; |
| } |
| |
| std::vector<proto::UMAFeature> GetAllUmaFeatures( |
| const proto::SegmentationModelMetadata& model_metadata, |
| bool include_outputs) { |
| std::vector<proto::UMAFeature> features; |
| VisitAllUmaFeatures(model_metadata, include_outputs, |
| base::BindRepeating( |
| [](std::vector<proto::UMAFeature>* features, |
| const proto::UMAFeature& feature) { |
| features->push_back(feature); |
| }, |
| base::Unretained(&features))); |
| return features; |
| } |
| |
| using VisitUmaFeature = |
| base::RepeatingCallback<void(const proto::UMAFeature& feature)>; |
| void VisitAllUmaFeatures(const proto::SegmentationModelMetadata& model_metadata, |
| bool include_outputs, |
| VisitUmaFeature visit) { |
| std::vector<proto::UMAFeature> features; |
| for (int i = 0; i < model_metadata.features_size(); ++i) { |
| visit.Run(model_metadata.features(i)); |
| } |
| |
| // Add training/inference inputs. |
| for (int i = 0; i < model_metadata.input_features_size(); ++i) { |
| const auto& feature = model_metadata.input_features(i); |
| if (feature.has_uma_feature()) |
| visit.Run(feature.uma_feature()); |
| } |
| |
| // Add training/inference outputs. |
| if (include_outputs) { |
| for (const auto& output : model_metadata.training_outputs().outputs()) { |
| DCHECK(output.has_uma_output()) << "Currently only support UMA output."; |
| if (output.has_uma_output()) |
| visit.Run(output.uma_output().uma_feature()); |
| } |
| } |
| |
| // Add uma trigger features. |
| if (model_metadata.training_outputs().has_trigger_config()) { |
| const auto& training_config = |
| model_metadata.training_outputs().trigger_config(); |
| for (int i = 0; i < training_config.observation_trigger_size(); i++) { |
| const auto& trigger = training_config.observation_trigger(i); |
| if (trigger.has_uma_trigger() && |
| trigger.uma_trigger().has_uma_feature()) { |
| visit.Run(trigger.uma_trigger().uma_feature()); |
| } |
| } |
| } |
| } |
| |
| proto::PredictionResult CreatePredictionResult( |
| const std::vector<float>& model_scores, |
| const proto::OutputConfig& output_config, |
| base::Time timestamp, |
| int64_t model_version) { |
| proto::PredictionResult result; |
| result.mutable_result()->Add(model_scores.begin(), model_scores.end()); |
| if (output_config.has_predictor()) { |
| result.mutable_output_config()->CopyFrom(output_config); |
| } |
| result.set_timestamp_us( |
| timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds()); |
| result.set_model_version(model_version); |
| return result; |
| } |
| |
| proto::ClientResult CreateClientResultFromPredResult( |
| proto::PredictionResult pred_result, |
| base::Time timestamp) { |
| proto::ClientResult client_result; |
| client_result.mutable_client_result()->CopyFrom(pred_result); |
| client_result.set_timestamp_us( |
| timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds()); |
| return client_result; |
| } |
| |
| std::string GetInputKeyForInputContextCustomInput( |
| const proto::CustomInput& custom_input) { |
| std::string input_name = custom_input.name(); |
| DCHECK_EQ(custom_input.fill_policy(), |
| proto::CustomInput::FILL_FROM_INPUT_CONTEXT); |
| auto custom_input_iter = custom_input.additional_args().find("name"); |
| if (custom_input_iter != custom_input.additional_args().end()) { |
| input_name = custom_input_iter->second; |
| } |
| return input_name; |
| } |
| |
| std::set<std::string> GetInputKeysForMetadata( |
| const proto::SegmentationModelMetadata& metadata) { |
| std::set<std::string> input_keys; |
| for (const auto& feature : metadata.input_features()) { |
| if (feature.has_custom_input()) { |
| // This only gets keys needed for FILL_FROM_INPUT_CONTEXT, consider adding |
| // other data sources keys. |
| if (feature.custom_input().fill_policy() == |
| proto::CustomInput::FILL_FROM_INPUT_CONTEXT) { |
| input_keys.insert( |
| GetInputKeyForInputContextCustomInput(feature.custom_input())); |
| } |
| } else if (feature.has_sql_feature()) { |
| for (const auto& bind_value : feature.sql_feature().bind_values()) { |
| input_keys.insert( |
| GetInputKeyForInputContextCustomInput(bind_value.value())); |
| } |
| } |
| } |
| return input_keys; |
| } |
| |
| bool ConfigUsesLegacyOutput(const Config* config) { |
| return (config->segments.size() >= 1 && |
| SegmentUsesLegacyOutput(config->segments.begin()->first)); |
| } |
| |
| bool SegmentUsesLegacyOutput(proto::SegmentId segment_id) { |
| // List of segments ids that doesn't support multi output and uses |
| // legacy output. Please delete `SegmentId` from this list if segment is |
| // migrated to support multi output. |
| base::flat_set<SegmentId> segment_ids_use_legacy{ |
| SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB, |
| SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_SHARE, |
| SegmentId::OPTIMIZATION_TARGET_SEGMENTATION_VOICE}; |
| |
| return segment_ids_use_legacy.contains(segment_id); |
| } |
| |
| } // namespace segmentation_platform::metadata_utils |