diff --git a/go/internal/feast/featurestore.go b/go/internal/feast/featurestore.go index 2a652ab8d4a..c1770fe7436 100644 --- a/go/internal/feast/featurestore.go +++ b/go/internal/feast/featurestore.go @@ -186,20 +186,29 @@ func (fs *FeatureStore) GetOnlineFeatures( fullFeatureNames bool) ([]*onlineserving.FeatureVector, error) { var err error var requestedFeatureViews []*onlineserving.FeatureViewAndRefs + var requestedSortedFeatureViews []*onlineserving.SortedFeatureViewAndRefs var requestedOnDemandFeatureViews []*model.OnDemandFeatureView if featureService != nil { - requestedFeatureViews, requestedOnDemandFeatureViews, err = + requestedFeatureViews, requestedSortedFeatureViews, requestedOnDemandFeatureViews, err = onlineserving.GetFeatureViewsToUseByService(featureService, fs.registry, fs.config.Project) if err != nil { return nil, err } } else { - requestedFeatureViews, requestedOnDemandFeatureViews, err = + requestedFeatureViews, requestedSortedFeatureViews, requestedOnDemandFeatureViews, err = onlineserving.GetFeatureViewsToUseByFeatureRefs(featureRefs, fs.registry, fs.config.Project) - if err != nil { - return nil, err + } + if err != nil { + return nil, err + } + + if len(requestedSortedFeatureViews) > 0 { + sfvNames := make([]string, len(requestedSortedFeatureViews)) + for i, sfv := range requestedSortedFeatureViews { + sfvNames[i] = sfv.View.Base.Name } + return nil, errors.GrpcInvalidArgumentErrorf("GetOnlineFeatures does not support sorted feature views %v", sfvNames) } if len(requestedFeatureViews) == 0 { @@ -319,20 +328,24 @@ func (fs *FeatureStore) GetOnlineFeaturesRange( var err error var requestedSortedFeatureViews []*onlineserving.SortedFeatureViewAndRefs - + var requestedFeatureViews []*onlineserving.FeatureViewAndRefs if featureService != nil { - requestedSortedFeatureViews, err = - onlineserving.GetSortedFeatureViewsToUseByService(featureService, fs.registry, fs.config.Project) - if err != nil { - return nil, err - } - + requestedFeatureViews, requestedSortedFeatureViews, _, err = + onlineserving.GetFeatureViewsToUseByService(featureService, fs.registry, fs.config.Project) } else { - requestedSortedFeatureViews, err = onlineserving.GetSortedFeatureViewsToUseByFeatureRefs( - featureRefs, fs.registry, fs.config.Project) - if err != nil { - return nil, err + requestedFeatureViews, requestedSortedFeatureViews, _, err = + onlineserving.GetFeatureViewsToUseByFeatureRefs(featureRefs, fs.registry, fs.config.Project) + } + if err != nil { + return nil, err + } + + if len(requestedFeatureViews) > 0 { + fvNames := make([]string, len(requestedFeatureViews)) + for i, fv := range requestedFeatureViews { + fvNames[i] = fv.View.Base.Name } + return nil, errors.GrpcInvalidArgumentErrorf("GetOnlineFeaturesRange does not support standard feature views %v", fvNames) } if len(requestedSortedFeatureViews) == 0 { @@ -469,6 +482,12 @@ func (fs *FeatureStore) ParseFeatures(kind interface{}) (*Features, error) { return nil, err } return &Features{FeaturesRefs: nil, FeatureService: featureService}, nil + featureServiceRequest := kind.(*serving.GetOnlineFeaturesRangeRequest_FeatureService) + featureService, err := fs.registry.GetFeatureService(fs.config.Project, featureServiceRequest.FeatureService) + if err != nil { + return nil, err + } + return &Features{FeaturesRefs: nil, FeatureService: featureService}, nil default: return nil, errors.GrpcInvalidArgumentErrorf("cannot parse 'kind' of either a Feature Service or list of Features from request") } diff --git a/go/internal/feast/featurestore_test.go b/go/internal/feast/featurestore_test.go index 2c01265ec51..cee958dfec0 100644 --- a/go/internal/feast/featurestore_test.go +++ b/go/internal/feast/featurestore_test.go @@ -184,7 +184,7 @@ func TestGetOnlineFeaturesRange(t *testing.T) { FeatureName: "conv_rate", Values: []interface{}{0.85, 0.87, 0.89}, Statuses: []serving.FieldStatus{serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT}, - EventTimestamps: []timestamppb.Timestamp{ + EventTimestamps: []timestamp.Timestamp{ {Seconds: now.Unix() - 86400*3}, {Seconds: now.Unix() - 86400*2}, {Seconds: now.Unix() - 86400*1}, @@ -195,7 +195,7 @@ func TestGetOnlineFeaturesRange(t *testing.T) { FeatureName: "acc_rate", Values: []interface{}{0.91, 0.92, 0.94}, Statuses: []serving.FieldStatus{serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT}, - EventTimestamps: []timestamppb.Timestamp{ + EventTimestamps: []timestamp.Timestamp{ {Seconds: now.Unix() - 86400*3}, {Seconds: now.Unix() - 86400*2}, {Seconds: now.Unix() - 86400*1}, @@ -208,7 +208,7 @@ func TestGetOnlineFeaturesRange(t *testing.T) { FeatureName: "conv_rate", Values: []interface{}{0.78, 0.80}, Statuses: []serving.FieldStatus{serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT}, - EventTimestamps: []timestamppb.Timestamp{ + EventTimestamps: []timestamp.Timestamp{ {Seconds: now.Unix() - 86400*3}, {Seconds: now.Unix() - 86400*1}, }, @@ -218,7 +218,7 @@ func TestGetOnlineFeaturesRange(t *testing.T) { FeatureName: "acc_rate", Values: []interface{}{0.85, 0.88}, Statuses: []serving.FieldStatus{serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT}, - EventTimestamps: []timestamppb.Timestamp{ + EventTimestamps: []timestamp.Timestamp{ {Seconds: now.Unix() - 86400*3}, {Seconds: now.Unix() - 86400*1}, }, diff --git a/go/internal/feast/integration_tests/scylladb/feature_repo/example_repo.py b/go/internal/feast/integration_tests/scylladb/feature_repo/example_repo.py index 06a9c1331bd..7729cd5bb27 100644 --- a/go/internal/feast/integration_tests/scylladb/feature_repo/example_repo.py +++ b/go/internal/feast/integration_tests/scylladb/feature_repo/example_repo.py @@ -142,10 +142,5 @@ mlpfs_test_all_datatypes_service = FeatureService( name="test_service", - features=[mlpfs_test_all_datatypes_view], -) - -mlpfs_test_all_datatypes_sorted_service = FeatureService( - name="test_sorted_service", - features=[mlpfs_test_all_datatypes_sorted_view], + features=[mlpfs_test_all_datatypes_view, mlpfs_test_all_datatypes_sorted_view], ) diff --git a/go/internal/feast/integration_tests/scylladb/http/http_integration_test.go b/go/internal/feast/integration_tests/scylladb/http/http_integration_test.go index 8179ba32440..88b57f71d4e 100644 --- a/go/internal/feast/integration_tests/scylladb/http/http_integration_test.go +++ b/go/internal/feast/integration_tests/scylladb/http/http_integration_test.go @@ -67,14 +67,6 @@ func TestGetOnlineFeaturesRange_Http(t *testing.T) { "all_dtypes_sorted:string_val", "all_dtypes_sorted:timestamp_val", "all_dtypes_sorted:boolean_val", - "all_dtypes_sorted:array_int_val", - "all_dtypes_sorted:array_long_val", - "all_dtypes_sorted:array_float_val", - "all_dtypes_sorted:array_double_val", - "all_dtypes_sorted:array_byte_val", - "all_dtypes_sorted:array_string_val", - "all_dtypes_sorted:array_timestamp_val", - "all_dtypes_sorted:array_boolean_val", "all_dtypes_sorted:null_int_val", "all_dtypes_sorted:null_long_val", "all_dtypes_sorted:null_float_val", @@ -89,8 +81,16 @@ func TestGetOnlineFeaturesRange_Http(t *testing.T) { "all_dtypes_sorted:null_array_double_val", "all_dtypes_sorted:null_array_byte_val", "all_dtypes_sorted:null_array_string_val", - "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:null_array_boolean_val", + "all_dtypes_sorted:array_int_val", + "all_dtypes_sorted:array_long_val", + "all_dtypes_sorted:array_float_val", + "all_dtypes_sorted:array_double_val", + "all_dtypes_sorted:array_string_val", + "all_dtypes_sorted:array_boolean_val", + "all_dtypes_sorted:array_byte_val", + "all_dtypes_sorted:array_timestamp_val", + "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:event_timestamp" ], "entities": { @@ -128,14 +128,6 @@ func TestGetOnlineFeaturesRange_Http_withOnlyEqualsFilter(t *testing.T) { "all_dtypes_sorted:string_val", "all_dtypes_sorted:timestamp_val", "all_dtypes_sorted:boolean_val", - "all_dtypes_sorted:array_int_val", - "all_dtypes_sorted:array_long_val", - "all_dtypes_sorted:array_float_val", - "all_dtypes_sorted:array_double_val", - "all_dtypes_sorted:array_byte_val", - "all_dtypes_sorted:array_string_val", - "all_dtypes_sorted:array_timestamp_val", - "all_dtypes_sorted:array_boolean_val", "all_dtypes_sorted:null_int_val", "all_dtypes_sorted:null_long_val", "all_dtypes_sorted:null_float_val", @@ -150,8 +142,16 @@ func TestGetOnlineFeaturesRange_Http_withOnlyEqualsFilter(t *testing.T) { "all_dtypes_sorted:null_array_double_val", "all_dtypes_sorted:null_array_byte_val", "all_dtypes_sorted:null_array_string_val", - "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:null_array_boolean_val", + "all_dtypes_sorted:array_int_val", + "all_dtypes_sorted:array_long_val", + "all_dtypes_sorted:array_float_val", + "all_dtypes_sorted:array_double_val", + "all_dtypes_sorted:array_string_val", + "all_dtypes_sorted:array_boolean_val", + "all_dtypes_sorted:array_byte_val", + "all_dtypes_sorted:array_timestamp_val", + "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:event_timestamp" ], "entities": { @@ -187,14 +187,6 @@ func TestGetOnlineFeaturesRange_Http_forNonExistentEntityKey(t *testing.T) { "all_dtypes_sorted:string_val", "all_dtypes_sorted:timestamp_val", "all_dtypes_sorted:boolean_val", - "all_dtypes_sorted:array_int_val", - "all_dtypes_sorted:array_long_val", - "all_dtypes_sorted:array_float_val", - "all_dtypes_sorted:array_double_val", - "all_dtypes_sorted:array_byte_val", - "all_dtypes_sorted:array_string_val", - "all_dtypes_sorted:array_timestamp_val", - "all_dtypes_sorted:array_boolean_val", "all_dtypes_sorted:null_int_val", "all_dtypes_sorted:null_long_val", "all_dtypes_sorted:null_float_val", @@ -209,8 +201,16 @@ func TestGetOnlineFeaturesRange_Http_forNonExistentEntityKey(t *testing.T) { "all_dtypes_sorted:null_array_double_val", "all_dtypes_sorted:null_array_byte_val", "all_dtypes_sorted:null_array_string_val", - "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:null_array_boolean_val", + "all_dtypes_sorted:array_int_val", + "all_dtypes_sorted:array_long_val", + "all_dtypes_sorted:array_float_val", + "all_dtypes_sorted:array_double_val", + "all_dtypes_sorted:array_string_val", + "all_dtypes_sorted:array_boolean_val", + "all_dtypes_sorted:array_byte_val", + "all_dtypes_sorted:array_timestamp_val", + "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:event_timestamp" ], "entities": { @@ -278,14 +278,6 @@ func TestGetOnlineFeaturesRange_Http_withEmptySortKeyFilter(t *testing.T) { "all_dtypes_sorted:string_val", "all_dtypes_sorted:timestamp_val", "all_dtypes_sorted:boolean_val", - "all_dtypes_sorted:array_int_val", - "all_dtypes_sorted:array_long_val", - "all_dtypes_sorted:array_float_val", - "all_dtypes_sorted:array_double_val", - "all_dtypes_sorted:array_byte_val", - "all_dtypes_sorted:array_string_val", - "all_dtypes_sorted:array_timestamp_val", - "all_dtypes_sorted:array_boolean_val", "all_dtypes_sorted:null_int_val", "all_dtypes_sorted:null_long_val", "all_dtypes_sorted:null_float_val", @@ -300,8 +292,16 @@ func TestGetOnlineFeaturesRange_Http_withEmptySortKeyFilter(t *testing.T) { "all_dtypes_sorted:null_array_double_val", "all_dtypes_sorted:null_array_byte_val", "all_dtypes_sorted:null_array_string_val", - "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:null_array_boolean_val", + "all_dtypes_sorted:array_int_val", + "all_dtypes_sorted:array_long_val", + "all_dtypes_sorted:array_float_val", + "all_dtypes_sorted:array_double_val", + "all_dtypes_sorted:array_string_val", + "all_dtypes_sorted:array_boolean_val", + "all_dtypes_sorted:array_byte_val", + "all_dtypes_sorted:array_timestamp_val", + "all_dtypes_sorted:null_array_timestamp_val", "all_dtypes_sorted:event_timestamp" ], "entities": { @@ -323,7 +323,7 @@ func TestGetOnlineFeaturesRange_Http_withEmptySortKeyFilter(t *testing.T) { func TestGetOnlineFeaturesRange_Http_withFeatureService(t *testing.T) { requestJson := []byte(`{ - "feature_service": "test_sorted_service", + "feature_service": "test_service", "entities": { "index_id": [1, 2, 3] }, @@ -342,63 +342,36 @@ func TestGetOnlineFeaturesRange_Http_withFeatureService(t *testing.T) { responseRecorder := httptest.NewRecorder() getOnlineFeaturesRangeHandler.ServeHTTP(responseRecorder, request) - assert.Equal(t, responseRecorder.Code, http.StatusOK, "Expected HTTP status code 200 OK response body is: %s", responseRecorder.Body.String()) - expectedResponse, err := loadResponse("valid_response.json") - require.NoError(t, err, "Failed to load expected response from file") - assert.JSONEq(t, string(expectedResponse), responseRecorder.Body.String(), "Response body does not match expected JSON") -} - -func TestGetOnlineFeaturesRange_Http_withInvalidFeatureService(t *testing.T) { - requestJson := []byte(`{ - "feature_service": "invalid_service", - "entities": { - "index_id": [1, 2, 3] - }, - "sort_key_filters": [ - { - "sort_key_name": "event_timestamp", - "range": { - "range_start": 0 - } - } - ], - "limit": 10 - }`) - - request := httptest.NewRequest(http.MethodPost, "/get-online-features-range", bytes.NewBuffer(requestJson)) - responseRecorder := httptest.NewRecorder() - - getOnlineFeaturesRangeHandler.ServeHTTP(responseRecorder, request) - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.Contains(t, responseRecorder.Body.String(), "Error getting feature service from registry", "Response body does not contain expected error message") + assert.Equal(t, responseRecorder.Code, http.StatusBadRequest) + assert.Equal(t, `{"error":"GetOnlineFeaturesRange does not support standard feature views [all_dtypes]","status_code":400}`, responseRecorder.Body.String(), "Response body does not match expected error message") } -func TestGetOnlineFeaturesRange_Http_withInvalidSortedFeatureView(t *testing.T) { +func TestGetOnlineFeaturesRange_Http_withInvalidFeatureView(t *testing.T) { requestJson := []byte(`{ - "features": ["invalid_sorted_view:some_feature"], - "entities": { - "index_id": [1, 2, 3] - }, - "sort_key_filters": [ - { - "sort_key_name": "event_timestamp", - "range": { - "range_start": { - "unix_timestamp_val": 0 - } - } - } - ], - "limit": 10 - }`) + "features": [ + "all_dtypes:int_val" + ], + "entities": { + "index_id": [1, 2, 3] + }, + "sort_key_filters": [ + { + "sort_key_name": "event_timestamp", + "range": { + "range_start": 0 + } + } + ], + "limit": 10 + }`) request := httptest.NewRequest(http.MethodPost, "/get-online-features-range", bytes.NewBuffer(requestJson)) responseRecorder := httptest.NewRecorder() getOnlineFeaturesRangeHandler.ServeHTTP(responseRecorder, request) - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - expectedErrorMessage := `{"error":"sorted feature view invalid_sorted_view doesn't exist, please make sure that you have created the sorted feature view invalid_sorted_view and that you have registered it by running \"apply\"","status_code":400}` - assert.JSONEq(t, expectedErrorMessage, responseRecorder.Body.String(), "Response body does not match expected error message") + assert.Equal(t, responseRecorder.Code, http.StatusBadRequest, "Expected HTTP status code 400 BadRequest response body is: %s", responseRecorder.Body.String()) + expectedErrorMessage := `{"error":"GetOnlineFeaturesRange does not support standard feature views [all_dtypes]","status_code":400}` + assert.Equal(t, expectedErrorMessage, responseRecorder.Body.String(), "Response body does not match expected error message") } func TestGetOnlineFeaturesRange_Http_withInvalidSortKeyFilter(t *testing.T) { diff --git a/go/internal/feast/integration_tests/scylladb/http/valid_equals_response.json b/go/internal/feast/integration_tests/scylladb/http/valid_equals_response.json index 5f837738990..686d4b45dac 100644 --- a/go/internal/feast/integration_tests/scylladb/http/valid_equals_response.json +++ b/go/internal/feast/integration_tests/scylladb/http/valid_equals_response.json @@ -14,14 +14,6 @@ "string_val", "timestamp_val", "boolean_val", - "array_int_val", - "array_long_val", - "array_float_val", - "array_double_val", - "array_byte_val", - "array_string_val", - "array_timestamp_val", - "array_boolean_val", "null_int_val", "null_long_val", "null_float_val", @@ -36,8 +28,16 @@ "null_array_double_val", "null_array_byte_val", "null_array_string_val", - "null_array_timestamp_val", "null_array_boolean_val", + "array_int_val", + "array_long_val", + "array_float_val", + "array_double_val", + "array_string_val", + "array_boolean_val", + "array_byte_val", + "array_timestamp_val", + "null_array_timestamp_val", "event_timestamp" ] }, @@ -101,144 +101,56 @@ { "values": [ [ - [ - 641, - 229, - 465, - 968, - 325, - 543, - 806, - 587, - 700, - 641 - ] + null ] ] }, { "values": [ [ - [ - 14617, - 4647, - 97806, - 88854, - 52201, - 45481, - 20690, - 34777, - 25993, - 20199 - ] + null ] ] }, { "values": [ [ - [ - 42.384342, - 77.91697, - 69.00426, - 80.79554, - 13.054096, - 35.700005, - 20.132544, - 97.402245, - 1.021711, - 14.016888 - ] + null ] ] }, { "values": [ [ - [ - 39.619735960926164, - 80.68860017001165, - 32.64254790114928, - 43.32240835268312, - 77.46765753551638, - 42.03371290943731, - 88.171018378022, - 80.54406477799951, - 98.17662710411794, - 74.97415732322717 - ] + null ] ] }, { "values": [ [ - [ - "v3CHIwQTKOsPlg==", - "9NpAUZtBRhQQdg==", - "mFIS2ujOt3nMNw==", - "cLpMChXX44TEqQ==", - "/QYIT7SunvM/BQ==", - "Ia6pzqLJsN+i/g==", - "LtVpAouLocySHw==", - "1SqOKGMZEXm/BQ==", - "xL95J5LcB6QmFw==", - "RpbGjz9Lo64HMA==" - ] + null ] ] }, { "values": [ [ - [ - "9QECZ", - "GLPQJ", - "RWCS4", - "4J2ZQ", - "S2ZJG", - "TLS1U", - "J3ZNM", - "CPILQ", - "9QPKL", - "ZJELB" - ] + null ] ] }, { "values": [ [ - [ - "2024-06-05 02:04:31Z", - "2024-09-20 02:04:31Z", - "2024-10-16 02:04:31Z", - "2024-05-26 02:04:31Z", - "2024-08-21 02:04:31Z", - "2025-01-11 03:04:31Z", - "2025-01-12 03:04:31Z", - "2025-04-16 02:04:31Z", - "2024-10-21 02:04:31Z", - "2025-04-07 02:04:31Z" - ] + null ] ] }, { "values": [ [ - [ - false, - true, - false, - true, - false, - true, - false, - false, - true, - false - ] + null ] ] }, @@ -294,56 +206,144 @@ { "values": [ [ - null + [ + 641, + 229, + 465, + 968, + 325, + 543, + 806, + 587, + 700, + 641 + ] ] ] }, { "values": [ [ - null + [ + 14617, + 4647, + 97806, + 88854, + 52201, + 45481, + 20690, + 34777, + 25993, + 20199 + ] ] ] }, { "values": [ [ - null + [ + 42.384342, + 77.91697, + 69.00426, + 80.79554, + 13.054096, + 35.700005, + 20.132544, + 97.402245, + 1.021711, + 14.016888 + ] ] ] }, { "values": [ [ - null + [ + 39.619735960926164, + 80.68860017001165, + 32.64254790114928, + 43.32240835268312, + 77.46765753551638, + 42.03371290943731, + 88.171018378022, + 80.54406477799951, + 98.17662710411794, + 74.97415732322717 + ] ] ] }, { "values": [ [ - null + [ + "9QECZ", + "GLPQJ", + "RWCS4", + "4J2ZQ", + "S2ZJG", + "TLS1U", + "J3ZNM", + "CPILQ", + "9QPKL", + "ZJELB" + ] ] ] }, { "values": [ [ - null + [ + false, + true, + false, + true, + false, + true, + false, + false, + true, + false + ] ] ] }, { "values": [ [ - null + [ + "v3CHIwQTKOsPlg==", + "9NpAUZtBRhQQdg==", + "mFIS2ujOt3nMNw==", + "cLpMChXX44TEqQ==", + "/QYIT7SunvM/BQ==", + "Ia6pzqLJsN+i/g==", + "LtVpAouLocySHw==", + "1SqOKGMZEXm/BQ==", + "xL95J5LcB6QmFw==", + "RpbGjz9Lo64HMA==" + ] ] ] }, { "values": [ [ - null + [ + "2024-06-05 02:04:31Z", + "2024-09-20 02:04:31Z", + "2024-10-16 02:04:31Z", + "2024-05-26 02:04:31Z", + "2024-08-21 02:04:31Z", + "2025-01-11 03:04:31Z", + "2025-01-12 03:04:31Z", + "2025-04-16 02:04:31Z", + "2024-10-21 02:04:31Z", + "2025-04-07 02:04:31Z" + ] ] ] }, diff --git a/go/internal/feast/integration_tests/scylladb/http/valid_nonexistent_key_response.json b/go/internal/feast/integration_tests/scylladb/http/valid_nonexistent_key_response.json index fb31952e8c9..296832af8d7 100644 --- a/go/internal/feast/integration_tests/scylladb/http/valid_nonexistent_key_response.json +++ b/go/internal/feast/integration_tests/scylladb/http/valid_nonexistent_key_response.json @@ -14,14 +14,6 @@ "string_val", "timestamp_val", "boolean_val", - "array_int_val", - "array_long_val", - "array_float_val", - "array_double_val", - "array_byte_val", - "array_string_val", - "array_timestamp_val", - "array_boolean_val", "null_int_val", "null_long_val", "null_float_val", @@ -36,8 +28,16 @@ "null_array_double_val", "null_array_byte_val", "null_array_string_val", - "null_array_timestamp_val", "null_array_boolean_val", + "array_int_val", + "array_long_val", + "array_float_val", + "array_double_val", + "array_string_val", + "array_boolean_val", + "array_byte_val", + "array_timestamp_val", + "null_array_timestamp_val", "event_timestamp" ] }, diff --git a/go/internal/feast/integration_tests/scylladb/http/valid_response.json b/go/internal/feast/integration_tests/scylladb/http/valid_response.json index 55db0470247..cac5be1634b 100644 --- a/go/internal/feast/integration_tests/scylladb/http/valid_response.json +++ b/go/internal/feast/integration_tests/scylladb/http/valid_response.json @@ -3,7 +3,7 @@ "index_id" : [ 1, 2, 3 ] }, "metadata" : { - "feature_names" : [ "int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_byte_val", "array_string_val", "array_timestamp_val", "array_boolean_val", "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", "null_array_timestamp_val", "null_array_boolean_val", "event_timestamp" ] + "feature_names" : [ "int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", "null_array_boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_string_val", "array_boolean_val", "array_byte_val", "array_timestamp_val", "null_array_timestamp_val", "event_timestamp" ] }, "results" : [ { "values" : [ [ 729, 728, 727, 726, 725, 724, 723, 722, 721, 720 ], [ 730, 729, 728, 727, 726, 725, 724, 723, 722, 721 ], [ 730, 729, 728, 727, 726, 725, 724, 723, 722, 721 ] ] @@ -21,22 +21,6 @@ "values" : [ [ "2024-04-20 02:06:11Z", "2024-04-20 02:06:10Z", "2024-04-20 02:06:09Z", "2024-04-20 02:06:08Z", "2024-04-20 02:06:07Z", "2024-04-20 02:06:06Z", "2024-04-20 02:06:05Z", "2024-04-20 02:06:04Z", "2024-04-20 02:06:03Z", "2024-04-20 02:06:02Z" ], [ "2024-04-20 02:06:12Z", "2024-04-20 02:06:11Z", "2024-04-20 02:06:10Z", "2024-04-20 02:06:09Z", "2024-04-20 02:06:08Z", "2024-04-20 02:06:07Z", "2024-04-20 02:06:06Z", "2024-04-20 02:06:05Z", "2024-04-20 02:06:04Z", "2024-04-20 02:06:03Z" ], [ "2024-04-20 02:06:12Z", "2024-04-20 02:06:11Z", "2024-04-20 02:06:10Z", "2024-04-20 02:06:09Z", "2024-04-20 02:06:08Z", "2024-04-20 02:06:07Z", "2024-04-20 02:06:06Z", "2024-04-20 02:06:05Z", "2024-04-20 02:06:04Z", "2024-04-20 02:06:03Z" ] ] }, { "values" : [ [ true, true, true, true, true, true, true, true, true, true ], [ true, true, true, true, true, true, true, true, true, true ], [ true, true, true, true, true, true, true, true, true, true ] ] - }, { - "values" : [ [ [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ] ], [ [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ] ], [ [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ] ] ] - }, { - "values" : [ [ [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ] ], [ [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ] ], [ [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ] ] ] - }, { - "values" : [ [ [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ] ], [ [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ] ], [ [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ] ] ] - }, { - "values" : [ [ [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ] ], [ [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ] ], [ [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ] ] ] - }, { - "values" : [ [ [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ] ], [ [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ] ], [ [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ] ] ] - },{ - "values" : [ [ [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ] ], [ [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ] ], [ [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ] ] ] - }, { - "values" : [ [ [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ] ], [ [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ] ], [ [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ] ] ] - }, { - "values" : [ [ [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ] ], [ [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ] ], [ [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ] ] ] }, { "values" : [ [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ] ] }, { @@ -67,6 +51,22 @@ "values" : [ [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ] ] }, { "values" : [ [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ] ] + }, { + "values" : [ [ [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ] ], [ [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ] ], [ [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ], [ 641, 229, 465, 968, 325, 543, 806, 587, 700, 641 ] ] ] + }, { + "values" : [ [ [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ] ], [ [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ] ], [ [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ], [ 14617, 4647, 97806, 88854, 52201, 45481, 20690, 34777, 25993, 20199 ] ] ] + }, { + "values" : [ [ [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ] ], [ [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ] ], [ [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ], [ 42.384342, 77.91697, 69.00426, 80.79554, 13.054096, 35.700005, 20.132544, 97.402245, 1.021711, 14.016888 ] ] ] + }, { + "values" : [ [ [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ] ], [ [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ] ], [ [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ], [ 39.619735960926164, 80.68860017001165, 32.64254790114928, 43.32240835268312, 77.46765753551638, 42.03371290943731, 88.171018378022, 80.54406477799951, 98.17662710411794, 74.97415732322717 ] ] ] + }, { + "values" : [ [ [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ] ], [ [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ] ], [ [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ], [ "9QECZ", "GLPQJ", "RWCS4", "4J2ZQ", "S2ZJG", "TLS1U", "J3ZNM", "CPILQ", "9QPKL", "ZJELB" ] ] ] + }, { + "values" : [ [ [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ] ], [ [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ] ], [ [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ], [ false, true, false, true, false, true, false, false, true, false ] ] ] + }, { + "values" : [ [ [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ] ], [ [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ] ], [ [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ], [ "v3CHIwQTKOsPlg==", "9NpAUZtBRhQQdg==", "mFIS2ujOt3nMNw==", "cLpMChXX44TEqQ==", "/QYIT7SunvM/BQ==", "Ia6pzqLJsN+i/g==", "LtVpAouLocySHw==", "1SqOKGMZEXm/BQ==", "xL95J5LcB6QmFw==", "RpbGjz9Lo64HMA==" ] ] ] + }, { + "values" : [ [ [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ] ], [ [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ] ], [ [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ], [ "2024-06-05 02:04:31Z", "2024-09-20 02:04:31Z", "2024-10-16 02:04:31Z", "2024-05-26 02:04:31Z", "2024-08-21 02:04:31Z", "2025-01-11 03:04:31Z", "2025-01-12 03:04:31Z", "2025-04-16 02:04:31Z", "2024-10-21 02:04:31Z", "2025-04-07 02:04:31Z" ] ] ] }, { "values" : [ [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ], [ null, null, null, null, null, null, null, null, null, null ] ] }, { diff --git a/go/internal/feast/integration_tests/scylladb/scylladb_integration_test.go b/go/internal/feast/integration_tests/scylladb/scylladb_integration_test.go index d11e1d7d127..42ebb3ddc10 100644 --- a/go/internal/feast/integration_tests/scylladb/scylladb_integration_test.go +++ b/go/internal/feast/integration_tests/scylladb/scylladb_integration_test.go @@ -17,11 +17,6 @@ import ( "testing" ) -const ( - ALL_SORTED_FEATURE_NAMES = "int_val,long_val,float_val,double_val,byte_val,string_val,timestamp_val,boolean_val,array_int_val,array_long_val,array_float_val,array_double_val,array_byte_val,array_string_val,array_timestamp_val,array_boolean_val,null_int_val,null_long_val,null_float_val,null_double_val,null_byte_val,null_string_val,null_timestamp_val,null_boolean_val,null_array_int_val,null_array_long_val,null_array_float_val,null_array_double_val,null_array_byte_val,null_array_string_val,null_array_timestamp_val,null_array_boolean_val,event_timestamp" - ALL_REGULAR_FEATURE_NAMES = "int_val,long_val,float_val,double_val,byte_val,string_val,timestamp_val,boolean_val,array_int_val,array_long_val,array_float_val,array_double_val,array_byte_val,array_string_val,array_timestamp_val,array_boolean_val,null_int_val,null_long_val,null_float_val,null_double_val,null_byte_val,null_string_val,null_timestamp_val,null_boolean_val,null_array_int_val,null_array_long_val,null_array_float_val,null_array_double_val,null_array_byte_val,null_array_string_val,null_array_timestamp_val,null_array_boolean_val" -) - var client serving.ServingServiceClient var ctx context.Context @@ -67,7 +62,11 @@ func TestGetOnlineFeaturesRange(t *testing.T) { }, } - featureNames := getAllSortedFeatureNames() + featureNames := []string{"int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", + "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", + "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", + "null_array_boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_string_val", "array_boolean_val", + "array_byte_val", "array_timestamp_val", "null_array_timestamp_val", "event_timestamp"} var featureNamesWithFeatureView []string @@ -109,7 +108,11 @@ func TestGetOnlineFeaturesRange_withOnlyEqualsFilter(t *testing.T) { }, } - featureNames := getAllSortedFeatureNames() + featureNames := []string{"int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", + "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", + "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", + "null_array_boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_string_val", "array_boolean_val", + "array_byte_val", "array_timestamp_val", "null_array_timestamp_val", "event_timestamp"} var featureNamesWithFeatureView []string @@ -168,7 +171,11 @@ func TestGetOnlineFeaturesRange_forNonExistentEntityKey(t *testing.T) { }, } - featureNames := getAllRegularFeatureNames() + featureNames := []string{"int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", + "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", + "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", + "null_array_boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_string_val", "array_boolean_val", + "array_byte_val", "array_timestamp_val", "null_array_timestamp_val"} var featureNamesWithFeatureView []string @@ -267,7 +274,11 @@ func TestGetOnlineFeaturesRange_withEmptySortKeyFilter(t *testing.T) { }, } - featureNames := getAllRegularFeatureNames() + featureNames := []string{"int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", + "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", + "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", + "null_array_boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_string_val", "array_boolean_val", + "array_byte_val", "array_timestamp_val", "null_array_timestamp_val"} var featureNamesWithFeatureView []string @@ -303,7 +314,7 @@ func TestGetOnlineFeaturesRange_withFeatureService(t *testing.T) { request := &serving.GetOnlineFeaturesRangeRequest{ Kind: &serving.GetOnlineFeaturesRangeRequest_FeatureService{ - FeatureService: "test_sorted_service", + FeatureService: "test_service", }, Entities: entities, SortKeyFilters: []*serving.SortKeyFilter{ @@ -318,11 +329,9 @@ func TestGetOnlineFeaturesRange_withFeatureService(t *testing.T) { }, Limit: 10, } - response, err := client.GetOnlineFeaturesRange(ctx, request) - assert.NoError(t, err) - - featureNames := getAllSortedFeatureNames() - assertResponseData(t, response, featureNames, 3, false) + _, err := client.GetOnlineFeaturesRange(ctx, request) + require.Error(t, err, "Expected an error due to regular feature view requested for range query") + assert.Equal(t, "rpc error: code = InvalidArgument desc = GetOnlineFeaturesRange does not support standard feature views [all_dtypes]", err.Error(), "Expected error message for unsupported feature view") } func TestGetOnlineFeaturesRange_withFeatureViewThrowsError(t *testing.T) { @@ -336,7 +345,11 @@ func TestGetOnlineFeaturesRange_withFeatureViewThrowsError(t *testing.T) { }, } - featureNames := getAllRegularFeatureNames() + featureNames := []string{"int_val", "long_val", "float_val", "double_val", "byte_val", "string_val", "timestamp_val", "boolean_val", + "null_int_val", "null_long_val", "null_float_val", "null_double_val", "null_byte_val", "null_string_val", "null_timestamp_val", "null_boolean_val", + "null_array_int_val", "null_array_long_val", "null_array_float_val", "null_array_double_val", "null_array_byte_val", "null_array_string_val", + "null_array_boolean_val", "array_int_val", "array_long_val", "array_float_val", "array_double_val", "array_string_val", "array_boolean_val", + "array_byte_val", "array_timestamp_val", "null_array_timestamp_val"} var featureNamesWithFeatureView []string @@ -365,8 +378,7 @@ func TestGetOnlineFeaturesRange_withFeatureViewThrowsError(t *testing.T) { } _, err := client.GetOnlineFeaturesRange(ctx, request) require.Error(t, err, "Expected an error due to regular feature view requested for range query") - assert.Contains(t, err.Error(), "sorted feature view all_dtypes doesn't exist", - "Expected error message for non-existent sorted feature view") + assert.Equal(t, "rpc error: code = InvalidArgument desc = GetOnlineFeaturesRange does not support standard feature views [all_dtypes]", err.Error(), "Expected error message for unsupported feature view") } func assertResponseData(t *testing.T, response *serving.GetOnlineFeaturesRangeResponse, featureNames []string, entitiesRequested int, includeMetadata bool) { @@ -408,11 +420,3 @@ func assertResponseData(t *testing.T, response *serving.GetOnlineFeaturesRangeRe } } } - -func getAllSortedFeatureNames() []string { - return strings.Split(ALL_SORTED_FEATURE_NAMES, ",") -} - -func getAllRegularFeatureNames() []string { - return strings.Split(ALL_REGULAR_FEATURE_NAMES, ",") -} diff --git a/go/internal/feast/onlineserving/serving.go b/go/internal/feast/onlineserving/serving.go index 90a4cb1e5dd..d3eaa1e2b6a 100644 --- a/go/internal/feast/onlineserving/serving.go +++ b/go/internal/feast/onlineserving/serving.go @@ -141,8 +141,8 @@ func GetFeatureViewsToUseByService( return nil, nil, err } } else { - log.Error().Errs("any feature view", []error{fvErr, odFvErr}).Msgf("Feature view %s not found", featureViewName) - return nil, nil, errors.GrpcInvalidArgumentErrorf("the provided feature service %s contains a reference to a feature View"+ + log.Error().Errs("any feature view", []error{fvErr, sortedFvErr, odFvErr}).Msgf("Feature view %s not found", featureViewName) + return nil, nil, nil, errors.GrpcInvalidArgumentErrorf("the provided feature service %s contains a reference to a feature View"+ "%s which doesn't exist, please make sure that you have created the feature View"+ "%s and that you have registered it by running \"apply\"", featureService.Name, featureViewName, featureViewName) } @@ -205,6 +205,18 @@ func addFeaturesToValidationMap( } } +func addFeaturesToValidationMap( + viewName string, + fvFeatures []*model.Field, + validationMap map[string]map[string]bool) { + if _, ok := validationMap[viewName]; !ok { + validationMap[viewName] = make(map[string]bool) + for _, field := range fvFeatures { + validationMap[viewName][field.Name] = true + } + } +} + /* Return @@ -227,44 +239,66 @@ func GetFeatureViewsToUseByFeatureRefs( odFvToFeatures := make(map[string][]string) odFvToProjectWithFeatures := make(map[string]*model.OnDemandFeatureView) - for _, vf := range viewFeatures { - featureViewName := vf.ViewName - requestedFeatureNames := vf.Features - - if fv, fvErr := registry.GetFeatureView(projectName, featureViewName); fvErr == nil { - err := validateFeatures( - featureViewName, - requestedFeatureNames, - fv.Base.Features) - if err != nil { - return nil, nil, err + viewToFeaturesValidationMap := make(map[string]map[string]bool) + invalidFeatures := make([]string, 0) + for _, featureRef := range features { + featureViewName, featureName, err := ParseFeatureReference(featureRef) + if err != nil { + return nil, nil, nil, err + } + if fv, err := registry.GetFeatureView(projectName, featureViewName); err == nil { + addFeaturesToValidationMap(fv.Base.Name, fv.Base.Features, viewToFeaturesValidationMap) + if !viewToFeaturesValidationMap[fv.Base.Name][featureName] { + invalidFeatures = append(invalidFeatures, featureRef) + } else { + if viewAndRef, ok := viewNameToViewAndRefs[fv.Base.Name]; ok { + viewAndRef.FeatureRefs = addStringIfNotContains(viewAndRef.FeatureRefs, featureName) + } else { + viewNameToViewAndRefs[fv.Base.Name] = &FeatureViewAndRefs{ + View: fv, + FeatureRefs: []string{featureName}, + } + } } + } else if sortedFv, err := registry.GetSortedFeatureView(projectName, featureViewName); err == nil { + addFeaturesToValidationMap(sortedFv.Base.Name, sortedFv.Base.Features, viewToFeaturesValidationMap) + if !viewToFeaturesValidationMap[sortedFv.Base.Name][featureName] { + invalidFeatures = append(invalidFeatures, featureRef) + } else { - viewNameToViewAndRefs[fv.Base.Name] = &FeatureViewAndRefs{ - View: fv, - FeatureRefs: requestedFeatureNames, - } - } else { - odfv, odfvErr := registry.GetOnDemandFeatureView(projectName, featureViewName) - - if odfvErr == nil { - err := validateFeatures( - featureViewName, - requestedFeatureNames, - odfv.Base.Features) - if err != nil { - return nil, nil, err + if viewAndRef, ok := viewNameToSortedViewAndRefs[sortedFv.Base.Name]; ok { + viewAndRef.FeatureRefs = addStringIfNotContains(viewAndRef.FeatureRefs, featureName) + } else { + viewNameToSortedViewAndRefs[sortedFv.Base.Name] = &SortedFeatureViewAndRefs{ + View: sortedFv, + FeatureRefs: []string{featureName}, + } } + } + } else if odfv, err := registry.GetOnDemandFeatureView(projectName, featureViewName); err == nil { + addFeaturesToValidationMap(odfv.Base.Name, odfv.Base.Features, viewToFeaturesValidationMap) + if !viewToFeaturesValidationMap[odfv.Base.Name][featureName] { + invalidFeatures = append(invalidFeatures, featureRef) + } else { - odFvToFeatures[odfv.Base.Name] = requestedFeatureNames + if _, ok := odFvToFeatures[odfv.Base.Name]; !ok { + odFvToFeatures[odfv.Base.Name] = []string{featureName} + } else { + odFvToFeatures[odfv.Base.Name] = append( + odFvToFeatures[odfv.Base.Name], featureName) + } odFvToProjectWithFeatures[odfv.Base.Name] = odfv - } else { - return nil, nil, errors.GrpcInvalidArgumentErrorf("feature view %s doesn't exist, please make sure that you have created the"+ - " feature view %s and that you have registered it by running \"apply\"", featureViewName, featureViewName) } + } else { + return nil, nil, nil, errors.GrpcInvalidArgumentErrorf("feature View %s doesn't exist, please make sure that you have created the"+ + " feature View %s and that you have registered it by running \"apply\"", featureViewName, featureViewName) } } + if len(invalidFeatures) > 0 { + return nil, nil, nil, errors.GrpcInvalidArgumentErrorf("requested features are not valid: %s", strings.Join(invalidFeatures, ", ")) + } + odFvsToUse := make([]*model.OnDemandFeatureView, 0) for odFvName, featureNames := range odFvToFeatures { projectedOdFv, err := odFvToProjectWithFeatures[odFvName].ProjectWithFeatures(featureNames) @@ -897,36 +931,6 @@ func getEventTimestamp(timestamps []timestamppb.Timestamp, index int) *timestamp return ×tamppb.Timestamp{} } -func buildDeduplicatedFeatureNamesMap(features []string) ([]ViewFeatures, error) { - var result []ViewFeatures - viewIndex := make(map[string]int) - featureSet := make(map[string]map[string]bool) - - for _, featureRef := range features { - featureViewName, featureName, err := ParseFeatureReference(featureRef) - if err != nil { - return nil, err - } - - if idx, exists := viewIndex[featureViewName]; exists { - if !featureSet[featureViewName][featureName] { - result[idx].Features = append(result[idx].Features, featureName) - featureSet[featureViewName][featureName] = true - } - } else { - viewIndex[featureViewName] = len(result) - result = append(result, ViewFeatures{ - ViewName: featureViewName, - Features: []string{featureName}, - }) - featureSet[featureViewName] = make(map[string]bool) - featureSet[featureViewName][featureName] = true - } - } - - return result, nil -} - func KeepOnlyRequestedFeatures[T any]( vectors []T, requestedFeatureRefs []string, diff --git a/go/internal/feast/onlineserving/serving_test.go b/go/internal/feast/onlineserving/serving_test.go index 675c6ce79c8..4db5782f264 100644 --- a/go/internal/feast/onlineserving/serving_test.go +++ b/go/internal/feast/onlineserving/serving_test.go @@ -571,6 +571,158 @@ func TestGetSortedFeatureViewsToUseByFeatureRefs_ReturnsErrorWithInvalidFeatures assert.Contains(t, sfvErr.Error(), "featInvalid does not exist in feature view sortedViewA") } +func TestGetFeatureViewsToUseByService_returnsErrorWithInvalidFeatures(t *testing.T) { + projectName := "test_project" + testRegistry, err := createRegistry(projectName) + assert.NoError(t, err) + + featASpec := test.CreateFeature("featA", types.ValueType_INT32) + featBSpec := test.CreateFeature("featB", types.ValueType_INT32) + featCSpec := test.CreateFeature("featC", types.ValueType_INT32) + featDSpec := test.CreateFeature("featD", types.ValueType_INT32) + featESpec := test.CreateFeature("featE", types.ValueType_FLOAT) + onDemandFeature1 := test.CreateFeature("featF", types.ValueType_FLOAT) + onDemandFeature2 := test.CreateFeature("featG", types.ValueType_FLOAT) + featSSpec := test.CreateFeature("featS", types.ValueType_FLOAT) + sortKeyA := test.CreateSortKeyProto("featS", core.SortOrder_DESC, types.ValueType_FLOAT) + + entities := []*core.Entity{test.CreateEntityProto("entity", types.ValueType_INT32, "entity")} + viewA := test.CreateFeatureViewProto("viewA", entities, featASpec, featBSpec) + viewB := test.CreateFeatureViewProto("viewB", entities, featCSpec, featDSpec) + viewC := test.CreateFeatureViewProto("viewC", entities, featESpec) + viewS := test.CreateSortedFeatureViewProto("viewS", entities, []*core.SortKey{sortKeyA}, featSSpec) + onDemandView := test.CreateOnDemandFeatureViewProto( + "odfv", + map[string][]*core.FeatureSpecV2{"viewB": {featCSpec}, "viewC": {featESpec}}, + onDemandFeature1, onDemandFeature2) + + featInvalidSpec := test.CreateFeature("featInvalid", types.ValueType_INT32) + fs := test.CreateFeatureService("service", map[string][]*core.FeatureSpecV2{ + "viewA": {featASpec, featBSpec}, + "viewB": {featCSpec, featInvalidSpec}, + "odfv": {onDemandFeature2}, + "viewS": {featSSpec}, + }) + testRegistry.SetModels([]*core.FeatureService{}, []*core.Entity{}, []*core.FeatureView{viewA, viewB, viewC}, []*core.SortedFeatureView{viewS}, []*core.OnDemandFeatureView{onDemandView}) + + _, _, _, invalidFeaturesErr := GetFeatureViewsToUseByService(fs, testRegistry, projectName) + assert.EqualError(t, invalidFeaturesErr, "rpc error: code = InvalidArgument desc = the projection for viewB cannot be applied because it contains featInvalid which the FeatureView doesn't have") +} + +func TestGetFeatureViewsToUseByService_returnsErrorWithInvalidOnDemandFeatures(t *testing.T) { + projectName := "test_project" + testRegistry, err := createRegistry(projectName) + assert.NoError(t, err) + + featASpec := test.CreateFeature("featA", types.ValueType_INT32) + featBSpec := test.CreateFeature("featB", types.ValueType_INT32) + featCSpec := test.CreateFeature("featC", types.ValueType_INT32) + featDSpec := test.CreateFeature("featD", types.ValueType_INT32) + featESpec := test.CreateFeature("featE", types.ValueType_FLOAT) + onDemandFeature1 := test.CreateFeature("featF", types.ValueType_FLOAT) + onDemandFeature2 := test.CreateFeature("featG", types.ValueType_FLOAT) + featSSpec := test.CreateFeature("featS", types.ValueType_FLOAT) + sortKeyA := test.CreateSortKeyProto("featS", core.SortOrder_DESC, types.ValueType_FLOAT) + + entities := []*core.Entity{test.CreateEntityProto("entity", types.ValueType_INT32, "entity")} + viewA := test.CreateFeatureViewProto("viewA", entities, featASpec, featBSpec) + viewB := test.CreateFeatureViewProto("viewB", entities, featCSpec, featDSpec) + viewC := test.CreateFeatureViewProto("viewC", entities, featESpec) + viewS := test.CreateSortedFeatureViewProto("viewS", entities, []*core.SortKey{sortKeyA}, featSSpec) + onDemandView := test.CreateOnDemandFeatureViewProto( + "odfv", + map[string][]*core.FeatureSpecV2{"viewB": {featCSpec}, "viewC": {featESpec}}, + onDemandFeature1, onDemandFeature2) + + featInvalidSpec := test.CreateFeature("featInvalid", types.ValueType_INT32) + fs := test.CreateFeatureService("service", map[string][]*core.FeatureSpecV2{ + "viewA": {featASpec, featBSpec}, + "viewB": {featCSpec}, + "odfv": {onDemandFeature2, featInvalidSpec}, + "viewS": {featSSpec}, + }) + testRegistry.SetModels([]*core.FeatureService{}, []*core.Entity{}, []*core.FeatureView{viewA, viewB, viewC}, []*core.SortedFeatureView{viewS}, []*core.OnDemandFeatureView{onDemandView}) + + _, _, _, invalidFeaturesErr := GetFeatureViewsToUseByService(fs, testRegistry, projectName) + assert.EqualError(t, invalidFeaturesErr, "rpc error: code = InvalidArgument desc = the projection for odfv cannot be applied because it contains featInvalid which the FeatureView doesn't have") +} + +func TestGetFeatureViewsToUseByService_returnsErrorWithInvalidSortedFeatures(t *testing.T) { + projectName := "test_project" + testRegistry, err := createRegistry(projectName) + assert.NoError(t, err) + + featASpec := test.CreateFeature("featA", types.ValueType_INT32) + featBSpec := test.CreateFeature("featB", types.ValueType_INT32) + featCSpec := test.CreateFeature("featC", types.ValueType_INT32) + featDSpec := test.CreateFeature("featD", types.ValueType_INT32) + featESpec := test.CreateFeature("featE", types.ValueType_FLOAT) + onDemandFeature1 := test.CreateFeature("featF", types.ValueType_FLOAT) + onDemandFeature2 := test.CreateFeature("featG", types.ValueType_FLOAT) + featSSpec := test.CreateFeature("featS", types.ValueType_FLOAT) + sortKeyA := test.CreateSortKeyProto("featS", core.SortOrder_DESC, types.ValueType_FLOAT) + + entities := []*core.Entity{test.CreateEntityProto("entity", types.ValueType_INT32, "entity")} + viewA := test.CreateFeatureViewProto("viewA", entities, featASpec, featBSpec) + viewB := test.CreateFeatureViewProto("viewB", entities, featCSpec, featDSpec) + viewC := test.CreateFeatureViewProto("viewC", entities, featESpec) + viewS := test.CreateSortedFeatureViewProto("viewS", entities, []*core.SortKey{sortKeyA}, featSSpec) + onDemandView := test.CreateOnDemandFeatureViewProto( + "odfv", + map[string][]*core.FeatureSpecV2{"viewB": {featCSpec}, "viewC": {featESpec}}, + onDemandFeature1, onDemandFeature2) + + featInvalidSpec := test.CreateFeature("featInvalid", types.ValueType_INT32) + fs := test.CreateFeatureService("service", map[string][]*core.FeatureSpecV2{ + "viewA": {featASpec, featBSpec}, + "viewB": {featCSpec}, + "odfv": {onDemandFeature2}, + "viewS": {featSSpec, featInvalidSpec}, + }) + testRegistry.SetModels([]*core.FeatureService{}, []*core.Entity{}, []*core.FeatureView{viewA, viewB, viewC}, []*core.SortedFeatureView{viewS}, []*core.OnDemandFeatureView{onDemandView}) + + _, _, _, invalidFeaturesErr := GetFeatureViewsToUseByService(fs, testRegistry, projectName) + assert.EqualError(t, invalidFeaturesErr, "rpc error: code = InvalidArgument desc = the projection for viewS cannot be applied because it contains featInvalid which the FeatureView doesn't have") +} + +func TestGetFeatureViewsToUseByFeatureRefs_returnsErrorWithInvalidFeatures(t *testing.T) { + projectName := "test_project" + testRegistry, err := createRegistry(projectName) + assert.NoError(t, err) + + featASpec := test.CreateFeature("featA", types.ValueType_INT32) + featBSpec := test.CreateFeature("featB", types.ValueType_INT32) + featCSpec := test.CreateFeature("featC", types.ValueType_INT32) + featDSpec := test.CreateFeature("featD", types.ValueType_INT32) + featESpec := test.CreateFeature("featE", types.ValueType_FLOAT) + onDemandFeature1 := test.CreateFeature("featF", types.ValueType_FLOAT) + onDemandFeature2 := test.CreateFeature("featG", types.ValueType_FLOAT) + featSSpec := test.CreateFeature("featS", types.ValueType_FLOAT) + sortKeyA := test.CreateSortKeyProto("featS", core.SortOrder_DESC, types.ValueType_FLOAT) + + entities := []*core.Entity{test.CreateEntityProto("entity", types.ValueType_INT32, "entity")} + viewA := test.CreateFeatureViewProto("viewA", entities, featASpec, featBSpec) + viewB := test.CreateFeatureViewProto("viewB", entities, featCSpec, featDSpec) + viewC := test.CreateFeatureViewProto("viewC", entities, featESpec) + viewS := test.CreateSortedFeatureViewProto("viewS", entities, []*core.SortKey{sortKeyA}, featSSpec) + onDemandView := test.CreateOnDemandFeatureViewProto( + "odfv", + map[string][]*core.FeatureSpecV2{"viewB": {featCSpec}, "viewC": {featESpec}}, + onDemandFeature1, onDemandFeature2) + testRegistry.SetModels([]*core.FeatureService{}, []*core.Entity{}, []*core.FeatureView{viewA, viewB, viewC}, []*core.SortedFeatureView{viewS}, []*core.OnDemandFeatureView{onDemandView}) + + _, _, _, fvErr := GetFeatureViewsToUseByFeatureRefs( + []string{ + "viewA:featA", + "viewA:featB", + "viewB:featInvalid", + "odfv:odFeatInvalid", + "viewS:sortedFeatInvalid", + }, + testRegistry, projectName) + assert.EqualError(t, fvErr, "rpc error: code = InvalidArgument desc = requested features are not valid: viewB:featInvalid, odfv:odFeatInvalid, viewS:sortedFeatInvalid") +} + func TestValidateSortKeyFilters_ValidFilters(t *testing.T) { sortKey1 := test.CreateSortKeyProto("timestamp", core.SortOrder_DESC, types.ValueType_UNIX_TIMESTAMP) sortKey2 := test.CreateSortKeyProto("price", core.SortOrder_ASC, types.ValueType_DOUBLE) @@ -1304,7 +1456,7 @@ func TestTransposeRangeFeatureRowsIntoColumns(t *testing.T) { FeatureName: "f1", Values: []interface{}{42.5, 43.2}, Statuses: []serving.FieldStatus{serving.FieldStatus_PRESENT, serving.FieldStatus_PRESENT}, - EventTimestamps: []timestamppb.Timestamp{ + EventTimestamps: []timestamp.Timestamp{ {Seconds: nowTime.Unix()}, {Seconds: yesterdayTime.Unix()}, }, @@ -1316,7 +1468,7 @@ func TestTransposeRangeFeatureRowsIntoColumns(t *testing.T) { FeatureName: "f1", Values: []interface{}{99.9}, Statuses: []serving.FieldStatus{serving.FieldStatus_PRESENT}, - EventTimestamps: []timestamppb.Timestamp{ + EventTimestamps: []timestamp.Timestamp{ {Seconds: nowTime.Unix()}, }, }, diff --git a/go/internal/feast/server/http_server.go b/go/internal/feast/server/http_server.go index b1f7d0a9497..e161566cf5f 100644 --- a/go/internal/feast/server/http_server.go +++ b/go/internal/feast/server/http_server.go @@ -370,6 +370,11 @@ func (s *HttpServer) getOnlineFeatures(w http.ResponseWriter, r *http.Request) { return } + includeMetadata, err := parseIncludeMetadata(r) + if err != nil { + logSpanContext.Error().Err(err).Msg("Error parsing includeMetadata query parameter") + writeJSONError(w, fmt.Errorf("error parsing includeMetadata query parameter: %w", err), http.StatusBadRequest) + return includeMetadata, err := parseIncludeMetadata(r) if err != nil { logSpanContext.Error().Err(err).Msg("Error parsing includeMetadata query parameter") @@ -549,6 +554,11 @@ func (s *HttpServer) getOnlineFeaturesRange(w http.ResponseWriter, r *http.Reque return } + includeMetadata, err := parseIncludeMetadata(r) + if err != nil { + logSpanContext.Error().Err(err).Msg("Error parsing includeMetadata query parameter") + writeJSONError(w, fmt.Errorf("error parsing includeMetadata query parameter: %w", err), http.StatusBadRequest) + return includeMetadata, err := parseIncludeMetadata(r) if err != nil { logSpanContext.Error().Err(err).Msg("Error parsing includeMetadata query parameter") diff --git a/protos/feast/registry/RegistryServer.proto b/protos/feast/registry/RegistryServer.proto index f8841b0d7a9..40c6195ddaf 100644 --- a/protos/feast/registry/RegistryServer.proto +++ b/protos/feast/registry/RegistryServer.proto @@ -4,6 +4,7 @@ package feast.registry; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; import "feast/core/DataSource.proto"; import "feast/core/Entity.proto"; import "feast/core/FeatureService.proto"; @@ -93,6 +94,10 @@ service RegistryServer{ rpc Refresh (RefreshRequest) returns (google.protobuf.Empty) {} rpc Proto (google.protobuf.Empty) returns (feast.core.Registry) {} + // Expedia Search RPCs + rpc ExpediaSearchProjects (ExpediaSearchProjectsRequest) returns (ExpediaSearchProjectsResponse) {} + rpc ExpediaSearchFeatureViews (ExpediaSearchFeatureViewsRequest) returns (ExpediaSearchFeatureViewsResponse) {} + } message RefreshRequest { @@ -448,4 +453,41 @@ message ListProjectsResponse { message DeleteProjectRequest { string name = 1; bool commit = 2; +} + +// Expedia Search + +message ExpediaProjectAndRelatedFeatureViews { + feast.core.Project project = 1; + repeated feast.core.FeatureView feature_views = 2; +} + +message ExpediaSearchFeatureViewsRequest { + string search_text = 1; + google.protobuf.BoolValue online = 2; + string application = 3; + string team = 4; + google.protobuf.Timestamp created_at = 5; + google.protobuf.Timestamp updated_at = 6; + int32 page_size = 7; + int32 page_index = 8; +} + +message ExpediaSearchFeatureViewsResponse { + repeated feast.core.FeatureView feature_views = 1; + int32 total_feature_views = 2; + int32 total_page_indices = 3; +} + +message ExpediaSearchProjectsRequest { + string search_text = 1; + google.protobuf.Timestamp updated_at = 2; + int32 page_size = 3; + int32 page_index = 4; +} + +message ExpediaSearchProjectsResponse { + repeated ExpediaProjectAndRelatedFeatureViews projects_and_related_feature_views = 1; + int32 total_projects = 3; + int32 total_page_indices = 4; } \ No newline at end of file diff --git a/sdk/python/feast/expediagroup/search.py b/sdk/python/feast/expediagroup/search.py new file mode 100644 index 00000000000..8d317b1f7ee --- /dev/null +++ b/sdk/python/feast/expediagroup/search.py @@ -0,0 +1,491 @@ +from datetime import datetime +from typing import List, Optional + +from google.protobuf.wrappers_pb2 import BoolValue + +from feast.feature_view import FeatureView +from feast.project import Project +from feast.protos.feast.registry.RegistryServer_pb2 import ( + ExpediaProjectAndRelatedFeatureViews as ExpediaProjectAndRelatedFeatureViewsProto, +) +from feast.protos.feast.registry.RegistryServer_pb2 import ( + ExpediaSearchFeatureViewsRequest as ExpediaSearchFeatureViewsRequestProto, +) +from feast.protos.feast.registry.RegistryServer_pb2 import ( + ExpediaSearchFeatureViewsResponse as ExpediaSearchFeatureViewsResponseProto, +) +from feast.protos.feast.registry.RegistryServer_pb2 import ( + ExpediaSearchProjectsRequest as ExpediaSearchProjectsRequestProto, +) +from feast.protos.feast.registry.RegistryServer_pb2 import ( + ExpediaSearchProjectsResponse as ExpediaSearchProjectsResponseProto, +) + + +class ExpediaProjectAndRelatedFeatureViews: + """ + Container for a Project and its related FeatureViews. + + Attributes: + project: The Feast Project object. + feature_views: List of FeatureView objects associated with the project. + """ + + project: Project + feature_views: List[FeatureView] + + def __init__(self, project: Project, feature_views: List[FeatureView]): + """ + Creates an ExpediaProjectAndRelatedFeatureViews object. + + Args: + project: The Feast Project object. + feature_views: List of FeatureView objects associated with the project. + """ + self.project = project + self.feature_views = feature_views + + def __eq__(self, other): + if not isinstance(other, ExpediaProjectAndRelatedFeatureViews): + return False + return ( + self.project == other.project and self.feature_views == other.feature_views + ) + + @classmethod + def from_proto(cls, proto: ExpediaProjectAndRelatedFeatureViewsProto): + """ + Creates an ExpediaProjectAndRelatedFeatureViews object from its protobuf representation. + + Args: + proto: Protobuf representation. + + Returns: + ExpediaProjectAndRelatedFeatureViews object. + """ + return cls( + project=Project.from_proto(proto.project), + feature_views=[FeatureView.from_proto(fv) for fv in proto.feature_views], + ) + + def to_proto(self) -> ExpediaProjectAndRelatedFeatureViewsProto: + """ + Converts this object to its protobuf representation. + + Returns: + ExpediaProjectAndRelatedFeatureViewsProto protobuf. + """ + proto = ExpediaProjectAndRelatedFeatureViewsProto() + proto.project.CopyFrom(self.project.to_proto()) + # FeatureView protos support project field, but their Python class does not. + # We need to manually set the project field here. + for fv in self.feature_views: + fv_proto = fv.to_proto() + fv_proto.spec.project = self.project.name + proto.feature_views.append(fv_proto) + return proto + + +class ExpediaSearchFeatureViewsRequest: + """ + Request object for searching FeatureViews. + + Attributes: + search_text: Text to search for. + online: Whether the feature view is online. + application: Application tag. + team: Team tag. + created_at: Creation timestamp. + updated_at: Last updated timestamp. + page_size: Number of results per page. + page_index: Page index for pagination. + """ + + search_text: str + online: Optional[bool] + application: str + team: str + created_at: Optional[datetime] + updated_at: Optional[datetime] + page_size: int + page_index: int + + def __init__( + self, + search_text: str = "", + online: Optional[bool] = None, + application: str = "", + team: str = "", + created_at: Optional[datetime] = None, + updated_at: Optional[datetime] = None, + page_size: int = 10, + page_index: int = 0, + ): + """ + Creates an ExpediaSearchFeatureViewsRequest object. + + Args: + search_text: Text to search for. + online: Whether the feature view is online. + application: Application tag. + team: Team tag. + created_at: Creation timestamp. + updated_at: Last updated timestamp. + page_size: Number of results per page. + page_index: Page index for pagination. + """ + self.search_text = search_text + self.online = online + self.application = application + self.team = team + self.created_at = created_at + self.updated_at = updated_at + self.page_size = page_size + self.page_index = page_index + + def __iter__(self): + """ + Allows iteration over the attributes of the request. + """ + yield from ( + self.search_text, + self.online, + self.application, + self.team, + self.created_at, + self.updated_at, + self.page_size, + self.page_index, + ) + + @classmethod + def from_proto(cls, proto: ExpediaSearchFeatureViewsRequestProto): + """ + Creates an ExpediaSearchFeatureViewsRequest object from its protobuf representation. + + Args: + proto: Protobuf representation. + + Returns: + ExpediaSearchFeatureViewsRequest object. + """ + online = proto.online.value if proto.HasField("online") else None + created_at = ( + proto.created_at.ToDatetime() if proto.HasField("created_at") else None + ) + updated_at = ( + proto.updated_at.ToDatetime() if proto.HasField("updated_at") else None + ) + page_size = proto.page_size if proto.page_size > 0 else 10 + return cls( + search_text=proto.search_text, + online=online, + application=proto.application, + team=proto.team, + created_at=created_at, + updated_at=updated_at, + page_size=page_size, + page_index=proto.page_index, + ) + + def to_proto(self) -> ExpediaSearchFeatureViewsRequestProto: + """ + Converts this object to its protobuf representation. + + Returns: + ExpediaSearchFeatureViewsRequestProto protobuf. + """ + proto = ExpediaSearchFeatureViewsRequestProto() + proto.search_text = self.search_text + proto.application = self.application + proto.team = self.team + proto.page_size = self.page_size + proto.page_index = self.page_index + if self.online is not None: + proto.online.CopyFrom(BoolValue(value=self.online)) + if self.created_at is not None: + proto.created_at.FromDatetime(self.created_at) + if self.updated_at is not None: + proto.updated_at.FromDatetime(self.updated_at) + return proto + + def __eq__(self, other): + if not isinstance(other, ExpediaSearchFeatureViewsRequest): + return False + return ( + self.search_text == other.search_text + and self.online == other.online + and self.application == other.application + and self.team == other.team + and ( + (self.created_at is None and other.created_at is None) + or ( + self.created_at is not None + and other.created_at is not None + and self.created_at.timestamp() == other.created_at.timestamp() + ) + ) + and ( + (self.updated_at is None and other.updated_at is None) + or ( + self.updated_at is not None + and other.updated_at is not None + and self.updated_at.timestamp() == other.updated_at.timestamp() + ) + ) + and self.page_size == other.page_size + and self.page_index == other.page_index + ) + + +class ExpediaSearchFeatureViewsResponse: + """ + Response object for searching FeatureViews. + + Attributes: + feature_views: List of FeatureView objects. + total_feature_views: Total number of feature views found. + total_page_indices: Total number of pages. + """ + + feature_views: List[FeatureView] + total_feature_views: int + total_page_indices: int + + def __init__( + self, + feature_views: List[FeatureView], + total_feature_views: int, + total_page_indices: int, + ): + """ + Creates an ExpediaSearchFeatureViewsResponse object. + + Args: + feature_views: List of FeatureView objects. + total_feature_views: Total number of feature views found. + total_page_indices: Total number of pages. + """ + self.feature_views = feature_views + self.total_feature_views = total_feature_views + self.total_page_indices = total_page_indices + + @classmethod + def from_proto(cls, proto: ExpediaSearchFeatureViewsResponseProto): + """ + Creates an ExpediaSearchFeatureViewsResponse object from its protobuf representation. + + Args: + proto: Protobuf representation. + + Returns: + ExpediaSearchFeatureViewsResponse object. + """ + return cls( + feature_views=[FeatureView.from_proto(fv) for fv in proto.feature_views], + total_feature_views=proto.total_feature_views, + total_page_indices=proto.total_page_indices, + ) + + def to_proto(self) -> ExpediaSearchFeatureViewsResponseProto: + """ + Converts this object to its protobuf representation. + + Returns: + ExpediaSearchFeatureViewsResponseProto protobuf. + """ + proto = ExpediaSearchFeatureViewsResponseProto() + proto.feature_views.extend([fv.to_proto() for fv in self.feature_views]) + proto.total_feature_views = self.total_feature_views + proto.total_page_indices = self.total_page_indices + return proto + + def __eq__(self, other): + if not isinstance(other, ExpediaSearchFeatureViewsResponse): + return False + return ( + self.feature_views == other.feature_views + and self.total_feature_views == other.total_feature_views + and self.total_page_indices == other.total_page_indices + ) + + +class ExpediaSearchProjectsRequest: + """ + Request object for searching Projects. + + Attributes: + search_text: Text to search for. + updated_at: Last updated timestamp. + page_size: Number of results per page. + page_index: Page index for pagination. + """ + + search_text: str + updated_at: Optional[datetime] + page_size: int + page_index: int + + def __init__( + self, + search_text: str = "", + updated_at: Optional[datetime] = None, + page_size: int = 10, + page_index: int = 0, + ): + """ + Creates an ExpediaSearchProjectsRequest object. + + Args: + search_text: Text to search for. + updated_at: Last updated timestamp. + page_size: Number of results per page. + page_index: Page index for pagination. + """ + self.search_text = search_text + self.updated_at = updated_at + self.page_size = page_size + self.page_index = page_index + + def __iter__(self): + """ + Allows iteration over the attributes of the request. + """ + yield from ( + self.search_text, + self.updated_at, + self.page_size, + self.page_index, + ) + + @classmethod + def from_proto(cls, proto: ExpediaSearchProjectsRequestProto): + """ + Creates an ExpediaSearchProjectsRequest object from its protobuf representation. + + Args: + proto: Protobuf representation. + + Returns: + ExpediaSearchProjectsRequest object. + """ + updated_at = ( + proto.updated_at.ToDatetime() if proto.HasField("updated_at") else None + ) + page_size = proto.page_size if proto.page_size > 0 else 10 + return cls( + search_text=proto.search_text, + updated_at=updated_at, + page_size=page_size, + page_index=proto.page_index, + ) + + def to_proto(self) -> ExpediaSearchProjectsRequestProto: + """ + Converts this object to its protobuf representation. + + Returns: + ExpediaSearchProjectsRequestProto protobuf. + """ + proto = ExpediaSearchProjectsRequestProto() + proto.search_text = self.search_text + proto.page_size = self.page_size + proto.page_index = self.page_index + if self.updated_at is not None: + proto.updated_at.FromDatetime(self.updated_at) + return proto + + def __eq__(self, other): + if not isinstance(other, ExpediaSearchProjectsRequest): + return False + return ( + self.search_text == other.search_text + and ( + (self.updated_at is None and other.updated_at is None) + or ( + self.updated_at is not None + and other.updated_at is not None + and self.updated_at.timestamp() == other.updated_at.timestamp() + ) + ) + and self.page_size == other.page_size + and self.page_index == other.page_index + ) + + +class ExpediaSearchProjectsResponse: + """ + Response object for searching Projects. + + Attributes: + projects_and_related_feature_views: List of ExpediaProjectAndRelatedFeatureViews objects. + total_projects: Total number of projects found. + total_page_indices: Total number of pages. + """ + + projects_and_related_feature_views: List[ExpediaProjectAndRelatedFeatureViews] + total_projects: int + total_page_indices: int + + def __init__( + self, + projects_and_related_feature_views: List[ExpediaProjectAndRelatedFeatureViews], + total_projects: int, + total_page_indices: int, + ): + """ + Creates an ExpediaSearchProjectsResponse object. + + Args: + projects_and_related_feature_views: List of ExpediaProjectAndRelatedFeatureViews objects. + total_projects: Total number of projects found. + total_page_indices: Total number of pages. + """ + self.projects_and_related_feature_views = projects_and_related_feature_views + self.total_projects = total_projects + self.total_page_indices = total_page_indices + + @classmethod + def from_proto(cls, proto: ExpediaSearchProjectsResponseProto): + """ + Creates an ExpediaSearchProjectsResponse object from its protobuf representation. + + Args: + proto: Protobuf representation. + + Returns: + ExpediaSearchProjectsResponse object. + """ + return cls( + projects_and_related_feature_views=[ + ExpediaProjectAndRelatedFeatureViews.from_proto(p) + for p in proto.projects_and_related_feature_views + ], + total_projects=proto.total_projects, + total_page_indices=proto.total_page_indices, + ) + + def to_proto(self) -> ExpediaSearchProjectsResponseProto: + """ + Converts this object to its protobuf representation. + + Returns: + ExpediaSearchProjectsResponseProto protobuf. + """ + proto = ExpediaSearchProjectsResponseProto() + proto.projects_and_related_feature_views.extend( + [p.to_proto() for p in self.projects_and_related_feature_views] + ) + proto.total_projects = self.total_projects + proto.total_page_indices = self.total_page_indices + return proto + + def __eq__(self, other): + if not isinstance(other, ExpediaSearchProjectsResponse): + return False + return ( + self.projects_and_related_feature_views + == other.projects_and_related_feature_views + and self.total_projects == other.total_projects + and self.total_page_indices == other.total_page_indices + ) diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 3d7df683b24..071a161fad0 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -151,6 +151,12 @@ def __init__( registry_config = self.config.registry if registry_config.registry_type == "sql": self._registry = SqlRegistry(registry_config, self.config.project, None) + elif registry_config.registry_type == "sql-fallback": + from feast.infra.registry.sql_fallback import SqlFallbackRegistry + + self._registry = SqlFallbackRegistry( + registry_config, self.config.project, None + ) elif registry_config.registry_type == "http": self._registry = HttpRegistry(registry_config, self.config.project, None) elif registry_config.registry_type == "snowflake.registry": diff --git a/sdk/python/feast/infra/registry/sql.py b/sdk/python/feast/infra/registry/sql.py index 827f4579142..df43c226acd 100644 --- a/sdk/python/feast/infra/registry/sql.py +++ b/sdk/python/feast/infra/registry/sql.py @@ -16,8 +16,10 @@ MetaData, String, Table, + bindparam, create_engine, delete, + func, insert, select, update, @@ -43,6 +45,13 @@ from feast.expediagroup.pydantic_models.project_metadata_model import ( ProjectMetadataModel, ) +from feast.expediagroup.search import ( + ExpediaProjectAndRelatedFeatureViews, + ExpediaSearchFeatureViewsRequest, + ExpediaSearchFeatureViewsResponse, + ExpediaSearchProjectsRequest, + ExpediaSearchProjectsResponse, +) from feast.feature_service import FeatureService from feast.feature_view import FeatureView from feast.infra.infra_object import Infra @@ -1439,3 +1448,220 @@ def get_project_metadata( datetime.utcfromtimestamp(int(metadata_value)) ) return project_metadata_model + + def expedia_search_projects( + self, + request: ExpediaSearchProjectsRequest, + ) -> ExpediaSearchProjectsResponse: + # Unpack fields from the request object + ( + search_text, + updated_at, + page_size, + page_index, + ) = request + + # 1. Query projects table for matching projects + with self.read_engine.begin() as conn: + count_stmt = select(func.count(projects.c.project_id.distinct())) + params = {} + + if search_text: + count_stmt = count_stmt.where( + projects.c.project_id.like(bindparam("search_pattern")) + ) + params["search_pattern"] = f"%{search_text}%" + + if updated_at is not None: + updated_at_timestamp = int(updated_at.timestamp()) + count_stmt = count_stmt.where( + projects.c.last_updated_timestamp >= updated_at_timestamp + ) + + total_count = conn.execute(count_stmt, params).scalar() or 0 + total_page_indices = (total_count + page_size - 1) // page_size + + stmt = ( + select( + projects.c.project_id, + projects.c.project_proto, + ) + .order_by(projects.c.project_id) + .limit(page_size) + .offset(page_index * page_size) + ) + + if search_text: + stmt = stmt.where( + projects.c.project_id.like(bindparam("search_pattern")) + ) + params["search_pattern"] = f"%{search_text}%" + + if updated_at is not None: + updated_at_timestamp = int(updated_at.timestamp()) + stmt = stmt.where( + projects.c.last_updated_timestamp >= updated_at_timestamp + ) + + rows = conn.execute(stmt, params).all() + + project_objs: List[Project] = [] + project_ids: List[str] = [] + for row in rows: + project_id = row._mapping["project_id"] + project_proto = ProjectProto.FromString(row._mapping["project_proto"]) + project = Project.from_proto(project_proto) + project_objs.append(project) + project_ids.append(project_id) + + # 2. Fetch all feature views for these projects + with self.read_engine.begin() as conn: + feature_views_stmt = select( + feature_views.c.project_id, feature_views.c.feature_view_proto + ).where(feature_views.c.project_id.in_(project_ids)) + feature_view_rows = conn.execute(feature_views_stmt).all() + + feature_views_by_project: Dict[str, List[FeatureView]] = {} + for row in feature_view_rows: + project_id = row._mapping["project_id"] + feature_view_proto = FeatureViewProto.FromString( + row._mapping["feature_view_proto"] + ) + fv = FeatureView.from_proto(feature_view_proto) + feature_views_by_project.setdefault(project_id, []).append(fv) + + # 3. Build ExpediaProjectAndRelatedFeatureViews objects + projects_and_related_feature_views = [] + for project in project_objs: + obj = ExpediaProjectAndRelatedFeatureViews( + project=project, + feature_views=feature_views_by_project.get(project.name, []), + ) + projects_and_related_feature_views.append(obj) + + return ExpediaSearchProjectsResponse( + projects_and_related_feature_views=projects_and_related_feature_views, + total_projects=total_count, + total_page_indices=total_page_indices, + ) + + def expedia_search_feature_views( + self, + request: ExpediaSearchFeatureViewsRequest, + ) -> ExpediaSearchFeatureViewsResponse: + # Unpack fields from the request object + ( + search_text, + online, + application, + team, + created_at, + updated_at, + page_size, + page_index, + ) = request + + offset = page_index * page_size + results = [] + filtered_results = [] + in_memory_filtering_required = any( + [online is not None, application, team, created_at, updated_at] + ) + + with self.read_engine.begin() as conn: + if not in_memory_filtering_required: + stmt = ( + select(feature_views) + .where( + feature_views.c.feature_view_name.like( + bindparam("search_pattern") + ) + ) + .order_by(feature_views.c.feature_view_name) + .limit(page_size) + .offset(offset) + ) + + rows = conn.execute(stmt, {"search_pattern": f"%{search_text}%"}).all() + + for row in rows: + feature_view_proto = FeatureViewProto.FromString( + row._mapping["feature_view_proto"] + ) + feature_view_proto.spec.project = row._mapping["project_id"] + fv = FeatureView.from_proto(feature_view_proto) + results.append(fv) + + total_stmt = ( + select(func.count()) + .select_from(feature_views) + .where( + feature_views.c.feature_view_name.like( + bindparam("search_pattern") + ) + ) + ) + + total_count = ( + conn.execute( + total_stmt, {"search_pattern": f"%{search_text}%"} + ).scalar() + or 0 + ) + total_page_indices = (total_count + page_size - 1) // page_size + + return ExpediaSearchFeatureViewsResponse( + feature_views=results, + total_feature_views=total_count, + total_page_indices=total_page_indices, + ) + + stmt = select(feature_views) + if search_text: + stmt = stmt.where( + feature_views.c.feature_view_name.like(bindparam("search_pattern")) + ) + + rows = conn.execute( + stmt, {"search_pattern": f"%{search_text}%"} if search_text else {} + ).all() + + for row in rows: + feature_view_proto = FeatureViewProto.FromString( + row._mapping["feature_view_proto"] + ) + feature_view_proto.spec.project = row._mapping["project_id"] + fv = FeatureView.from_proto(feature_view_proto) + add_to_results = True + + if online is not None and fv.online != online: + add_to_results = False + if application and fv.tags.get("application") != application: + add_to_results = False + if team and fv.tags.get("team") != team: + add_to_results = False + if ( + created_at is not None + and fv.created_timestamp + and fv.created_timestamp < created_at + ): + add_to_results = False + if ( + updated_at is not None + and fv.last_updated_timestamp + and fv.last_updated_timestamp < updated_at + ): + add_to_results = False + + if add_to_results: + filtered_results.append(fv) + + total_count = len(filtered_results) + total_page_indices = (total_count + page_size - 1) // page_size + paginated_results = filtered_results[offset : offset + page_size] + + return ExpediaSearchFeatureViewsResponse( + feature_views=paginated_results, + total_feature_views=total_count, + total_page_indices=total_page_indices, + ) diff --git a/sdk/python/feast/registry_server.py b/sdk/python/feast/registry_server.py index ef307446c20..2682e616609 100644 --- a/sdk/python/feast/registry_server.py +++ b/sdk/python/feast/registry_server.py @@ -1,3 +1,4 @@ +import logging from concurrent import futures from datetime import datetime, timezone from typing import Optional, Union, cast @@ -12,6 +13,10 @@ from feast.data_source import DataSource from feast.entity import Entity from feast.errors import FeatureViewNotFoundException +from feast.expediagroup.search import ( + ExpediaSearchFeatureViewsRequest, + ExpediaSearchProjectsRequest, +) from feast.feast_object import FeastObject from feast.feature_view import FeatureView from feast.grpc_error_interceptor import ErrorInterceptor @@ -39,6 +44,9 @@ from feast.sorted_feature_view import SortedFeatureView from feast.stream_feature_view import StreamFeatureView +_logger = logging.getLogger(__name__) +_logger.setLevel(logging.INFO) + def _build_any_feature_view_proto(feature_view: BaseFeatureView): if isinstance(feature_view, SortedFeatureView): @@ -797,6 +805,24 @@ def Refresh(self, request, context): def Proto(self, request, context): return self.proxied_registry.proto() + def ExpediaSearchProjects( + self, request: RegistryServer_pb2.ExpediaSearchProjectsRequest, context + ): + # Using `type: ignore[attr-defined]` because this should only be implemented in sql registry. + response = self.proxied_registry.expedia_search_projects( # type: ignore[attr-defined] + request=ExpediaSearchProjectsRequest.from_proto(request) + ) + return response.to_proto() + + def ExpediaSearchFeatureViews( + self, request: RegistryServer_pb2.ExpediaSearchFeatureViewsRequest, context + ): + # Using `type: ignore[attr-defined]` because this should only be implemented in sql registry. + response = self.proxied_registry.expedia_search_feature_views( # type: ignore[attr-defined] + request=ExpediaSearchFeatureViewsRequest.from_proto(request) + ) + return response.to_proto() + def start_server(store: FeatureStore, port: int, wait_for_termination: bool = True): auth_manager_type = str_to_auth_manager_type(store.config.auth_config.type) @@ -828,6 +854,7 @@ def start_server(store: FeatureStore, port: int, wait_for_termination: bool = Tr server.add_insecure_port(f"[::]:{port}") server.start() + _logger.info(f"Registry server started on port {port}") if wait_for_termination: server.wait_for_termination() else: diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index da12b6340a5..b7abf593008 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -39,6 +39,7 @@ REGISTRY_CLASS_FOR_TYPE = { "file": "feast.infra.registry.registry.Registry", "sql": "feast.infra.registry.sql.SqlRegistry", + "sql-fallback": "feast.infra.registry.sql_fallback.SqlFallbackRegistry", "snowflake.registry": "feast.infra.registry.snowflake.SnowflakeRegistry", "http": "feast.infra.registry.http.HttpRegistry", "remote": "feast.infra.registry.remote.RemoteRegistry", diff --git a/sdk/python/tests/expediagroup/test_search.py b/sdk/python/tests/expediagroup/test_search.py new file mode 100644 index 00000000000..cc6c0c1f9e2 --- /dev/null +++ b/sdk/python/tests/expediagroup/test_search.py @@ -0,0 +1,125 @@ +import unittest +from datetime import datetime + +from feast.expediagroup.search import ( + ExpediaProjectAndRelatedFeatureViews, + ExpediaSearchFeatureViewsRequest, + ExpediaSearchFeatureViewsResponse, + ExpediaSearchProjectsRequest, + ExpediaSearchProjectsResponse, +) +from feast.feature_view import FeatureView +from feast.infra.offline_stores.file_source import FileSource +from feast.project import Project + + +class TestExpediaSearch(unittest.TestCase): + def setUp(self): + self.project = Project(name="test_project") + self.source = FileSource(path="test_path") + self.feature_view = FeatureView(name="test_feature_view", source=self.source) + self.created_at = datetime(2024, 1, 1, 12, 0, 0) + self.updated_at = datetime(2024, 1, 2, 13, 30, 0) + + def test_expedia_project_and_related_feature_views_init(self): + obj = ExpediaProjectAndRelatedFeatureViews( + project=self.project, + feature_views=[self.feature_view], + ) + self.assertEqual(obj.project, self.project) + self.assertEqual(obj.feature_views, [self.feature_view]) + + def test_expedia_project_and_related_feature_views_proto_roundtrip(self): + obj = ExpediaProjectAndRelatedFeatureViews( + project=self.project, + feature_views=[self.feature_view], + ) + proto = obj.to_proto() + obj2 = ExpediaProjectAndRelatedFeatureViews.from_proto(proto) + self.assertEqual(obj, obj2) + + def test_expedia_search_feature_views_request_init(self): + req = ExpediaSearchFeatureViewsRequest( + search_text="foo", + online=True, + application="app", + team="team", + created_at=self.created_at, + updated_at=self.updated_at, + page_size=5, + page_index=2, + ) + self.assertEqual(req.search_text, "foo") + self.assertEqual(req.online, True) + self.assertEqual(req.application, "app") + self.assertEqual(req.team, "team") + self.assertEqual(req.created_at, self.created_at) + self.assertEqual(req.updated_at, self.updated_at) + self.assertEqual(req.page_size, 5) + self.assertEqual(req.page_index, 2) + + def test_expedia_search_feature_views_request_proto_roundtrip(self): + req = ExpediaSearchFeatureViewsRequest( + search_text="foo", + online=False, + application="app", + team="team", + created_at=self.created_at, + updated_at=self.updated_at, + page_size=7, + page_index=3, + ) + proto = req.to_proto() + req2 = ExpediaSearchFeatureViewsRequest.from_proto(proto) + self.assertEqual(req, req2) + + def test_expedia_search_feature_views_response_proto_roundtrip(self): + resp = ExpediaSearchFeatureViewsResponse( + feature_views=[self.feature_view], + total_feature_views=1, + total_page_indices=1, + ) + proto = resp.to_proto() + resp2 = ExpediaSearchFeatureViewsResponse.from_proto(proto) + self.assertEqual(resp, resp2) + + def test_expedia_search_projects_request_init(self): + req = ExpediaSearchProjectsRequest( + search_text="bar", + updated_at=self.updated_at, + page_size=8, + page_index=4, + ) + self.assertEqual(req.search_text, "bar") + self.assertEqual(req.updated_at, self.updated_at) + self.assertEqual(req.page_size, 8) + self.assertEqual(req.page_index, 4) + + def test_expedia_search_projects_request_proto_roundtrip(self): + req = ExpediaSearchProjectsRequest( + search_text="bar", + updated_at=self.updated_at, + page_size=8, + page_index=4, + ) + proto = req.to_proto() + req2 = ExpediaSearchProjectsRequest.from_proto(proto) + self.assertEqual(req, req2) + + def test_expedia_search_projects_response_proto_roundtrip(self): + obj = ExpediaProjectAndRelatedFeatureViews( + project=self.project, + feature_views=[self.feature_view], + ) + resp = ExpediaSearchProjectsResponse( + projects_and_related_feature_views=[obj], + total_projects=1, + total_page_indices=1, + ) + proto = resp.to_proto() + resp2 = ExpediaSearchProjectsResponse.from_proto(proto) + self.assertEqual(resp, resp2) + + +if __name__ == "__main__": + unittest.main()