Repository: oom-ai/oomstore
Pull request #832 (2021-12-27 11:09:05)

apply/get meta support stream group/feature
relate #827 #825 



Patch:
diff --git a/README.md b/README.md
index 5b75c610..54d7229f 100644
--- a/README.md
+++ b/README.md
@@ -68,8 +68,9 @@ kind: Entity
 name: user
 length: 8
 description: 'user ID'
-batch-features:
-- group: account
+groups:
+- name: account
+  category: batch
   description: 'user account info'
   features:
   - name: state
@@ -80,7 +81,8 @@ batch-features:
     value-type: int64
   - name: has_2fa_installed
     value-type: bool
-- group: transaction_stats
+- name: transaction_stats
+  category: batch
   description: 'user transaction statistics'
   features:
   - name: transaction_count_7d
diff --git a/oomcli/cmd/register_batch_feature.go b/oomcli/cmd/register_feature.go
similarity index 94%
rename from oomcli/cmd/register_batch_feature.go
rename to oomcli/cmd/register_feature.go
index 2e27360b..bd817c45 100644
--- a/oomcli/cmd/register_batch_feature.go
+++ b/oomcli/cmd/register_feature.go
@@ -12,8 +12,8 @@ var registerBatchFeatureOpt types.CreateFeatureOpt
 var registerBatchFeatureValueType string
 
 var registerBatchFeatureCmd = &cobra.Command{
-	Use:   "batch-feature <feature_name>",
-	Short: "Register a new batch feature",
+	Use:   "feature <feature_name>",
+	Short: "Register a new feature",
 	Args:  cobra.ExactArgs(1),
 	PreRun: func(cmd *cobra.Command, args []string) {
 		valueType, err := types.ParseValueType(registerBatchFeatureValueType)
diff --git a/oomcli/cmd/register_group.go b/oomcli/cmd/register_group.go
index 5f234dbe..90269a45 100644
--- a/oomcli/cmd/register_group.go
+++ b/oomcli/cmd/register_group.go
@@ -4,19 +4,22 @@ import (
 	"context"
 	"log"
 
-	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 	"github.com/spf13/cobra"
+
+	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 )
 
 var registerGroupOpt types.CreateGroupOpt
-var category string
 var registerGroupCmd = &cobra.Command{
 	Use:   "group <group_name>",
 	Short: "Register a new feature group",
 	Args:  cobra.ExactArgs(1),
 	PreRun: func(cmd *cobra.Command, args []string) {
+		if registerGroupOpt.Category != types.CategoryBatch && registerGroupOpt.Category != types.CategoryStream {
+			log.Fatalf("illegal category '%s', should be either 'stream' or 'batch'", registerGroupOpt.Category)
+		}
+
 		registerGroupOpt.GroupName = args[0]
-		registerGroupOpt.Category = types.Category(category)
 	},
 	Run: func(cmd *cobra.Command, args []string) {
 		ctx := context.Background()
@@ -37,8 +40,7 @@ func init() {
 	flags.StringVarP(&registerGroupOpt.EntityName, "entity", "e", "", "entity name")
 	_ = registerGroupCmd.MarkFlagRequired("entity")
 
-	flags.StringVarP(&category, "category", "c", "", "group category")
-	_ = registerGroupCmd.MarkFlagRequired("category")
+	flags.StringVarP(&registerGroupOpt.Category, "category", "c", "batch", "group category")
 
 	flags.StringVarP(&registerGroupOpt.Description, "description", "d", "", "group description")
 }
diff --git a/oomcli/cmd/types.go b/oomcli/cmd/types.go
index b86ee879..45779bd7 100644
--- a/oomcli/cmd/types.go
+++ b/oomcli/cmd/types.go
@@ -18,10 +18,11 @@ type FlattenEntity struct {
 }
 
 type FlattenGroup struct {
-	ID          int    `oomcli:"ID"`
-	Name        string `oomcli:"NAME"`
-	Entity      string `oomcli:"ENTITY"`
-	Description string `oomcli:"DESCRIPTION,truncate"`
+	ID          int            `oomcli:"ID"`
+	Name        string         `oomcli:"NAME"`
+	Entity      string         `oomcli:"ENTITY"`
+	Category    types.Category `oomcli:"CATEGORY"`
+	Description string         `oomcli:"DESCRIPTION,truncate"`
 
 	OnlineRevisionID *int      `oomcli:"ONLINE-REVISION-ID,wide"`
 	CreateTime       time.Time `oomcli:"CREATE-TIME,wide"`
@@ -101,6 +102,7 @@ func parseTokenLists(i interface{}) (headerTokens TokenList, dataTokens []TokenL
 				ID:               e.ID,
 				Name:             e.Name,
 				Entity:           e.Entity.Name,
+				Category:         e.Category,
 				Description:      e.Description,
 				OnlineRevisionID: e.OnlineRevisionID,
 				CreateTime:       e.CreateTime,
diff --git a/pkg/oomstore/apply.go b/pkg/oomstore/apply.go
index 99017dd2..34e26014 100644
--- a/pkg/oomstore/apply.go
+++ b/pkg/oomstore/apply.go
@@ -31,7 +31,6 @@ func (s *OomStore) Apply(ctx context.Context, opt apply.ApplyOpt) error {
 
 		// apply group
 		for _, group := range stage.NewGroups {
-			group.Category = types.CategoryBatch
 			if err := s.applyGroup(c, tx, group); err != nil {
 				return err
 			}
@@ -177,8 +176,7 @@ func buildApplyStage(ctx context.Context, opt apply.ApplyOpt) (*apply.ApplyStage
 
 			stage.NewEntities = append(stage.NewEntities, buildApplyEntity(entity))
 
-			for _, group := range entity.BatchFeatures {
-				group.Name = group.Group
+			for _, group := range entity.Groups {
 				stage.NewGroups = append(stage.NewGroups, buildApplyGroup(group, entity.Name))
 
 				for _, feature := range group.Features {
@@ -191,7 +189,6 @@ func buildApplyStage(ctx context.Context, opt apply.ApplyOpt) (*apply.ApplyStage
 				return nil, err
 			}
 
-			group.Group = group.Name
 			stage.NewGroups = append(stage.NewGroups, buildApplyGroup(group, group.EntityName))
 
 			for _, feature := range group.Features {
@@ -225,7 +222,6 @@ func buildApplyStage(ctx context.Context, opt apply.ApplyOpt) (*apply.ApplyStage
 					return nil, err
 				}
 				for _, group := range groupItems.Items {
-					group.Group = group.Name
 					stage.NewGroups = append(stage.NewGroups, buildApplyGroup(group, group.EntityName))
 
 					for _, feature := range group.Features {
@@ -240,9 +236,7 @@ func buildApplyStage(ctx context.Context, opt apply.ApplyOpt) (*apply.ApplyStage
 				for _, entity := range entityItems.Items {
 					stage.NewEntities = append(stage.NewEntities, buildApplyEntity(entity))
 
-					for _, group := range entity.BatchFeatures {
-						group.Name = group.Group
-						group.Category = types.CategoryBatch
+					for _, group := range entity.Groups {
 						stage.NewGroups = append(stage.NewGroups, buildApplyGroup(group, entity.Name))
 
 						for _, feature := range group.Features {
@@ -277,7 +271,7 @@ func parseItemsKind(data map[string]interface{}) (string, error) {
 }
 
 func buildApplyEntity(entity apply.Entity) apply.Entity {
-	// We don't want entity.BatchFeature to have values.
+	// We don't want entity.Groups to have values.
 	// The whole stage should be a flat structure, not a nested structure.
 	return apply.Entity{
 		Kind:        entity.Kind,
@@ -293,7 +287,6 @@ func buildApplyGroup(group apply.Group, entityName string) apply.Group {
 	return apply.Group{
 		Kind:        "Group",
 		Name:        group.Name,
-		Group:       group.Group,
 		EntityName:  entityName,
 		Category:    group.Category,
 		Description: group.Description,
diff --git a/pkg/oomstore/types/apply/apply.go b/pkg/oomstore/types/apply/apply.go
index 801efed7..f0f73bf4 100644
--- a/pkg/oomstore/types/apply/apply.go
+++ b/pkg/oomstore/types/apply/apply.go
@@ -74,7 +74,6 @@ func FromFeatureList(features types.FeatureList) FeatureItems {
 
 type Group struct {
 	Kind        string         `mapstructure:"kind" yaml:"kind,omitempty"`
-	Group       string         `mapstructure:"group" yaml:"group,omitempty"`
 	Name        string         `mapstructure:"name" yaml:"name,omitempty"`
 	EntityName  string         `mapstructure:"entity-name" yaml:"entity-name,omitempty"`
 	Category    types.Category `mapstructure:"category" yaml:"category,omitempty"`
@@ -140,8 +139,7 @@ type Entity struct {
 	Length      int    `mapstructure:"length" yaml:"length"`
 	Description string `mapstructure:"description" yaml:"description"`
 
-	BatchFeatures  []Group   `mapstructure:"batch-features" yaml:"batch-features,omitempty"`
-	StreamFeatures []Feature `mapstructure:"stream-features" yaml:"stream-features,omitempty"`
+	Groups []Group `mapstructure:"groups" yaml:"groups,omitempty"`
 }
 
 func (e *Entity) Validate() error {
@@ -162,14 +160,11 @@ func FromEntityList(entities types.EntityList, groups *GroupItems) (items Entity
 			Name:        entity.Name,
 			Length:      entity.Length,
 			Description: entity.Description,
-			BatchFeatures: groups.Filter(func(g Group) bool {
+			Groups: groups.Filter(func(g Group) bool {
 				return g.EntityName == entity.Name
 			}).Walk(func(g Group) Group {
 				g.Kind = ""
 				g.EntityName = ""
-				g.Category = ""
-				g.Group = g.Name
-				g.Name = ""
 				return g
 			}).Items,
 		})
diff --git a/pkg/oomstore/types/types.go b/pkg/oomstore/types/types.go
index 49ab7d0a..57a4e946 100644
--- a/pkg/oomstore/types/types.go
+++ b/pkg/oomstore/types/types.go
@@ -1,17 +1,12 @@
 package types
 
-type Category string
+type Category = string
 
 const (
 	CategoryBatch  Category = "batch"
 	CategoryStream Category = "stream"
 )
 
-// In order for cast.ToString to correctly resolve Category, Category needs to implement the interface String
-func (c Category) String() string {
-	return string(c)
-}
-
 type ExportRecord []interface{}
 
 func (r ExportRecord) EntityKey() string {


Test patch:
diff --git a/oomagent/test/data/driver_stats.yaml b/oomagent/test/data/driver_stats.yaml
index afbd1f89..3eb8401e 100644
--- a/oomagent/test/data/driver_stats.yaml
+++ b/oomagent/test/data/driver_stats.yaml
@@ -2,8 +2,9 @@
 kind: Entity
 name: driver
 length: 2
-batch-features:
-  - group: driver_stats
+groups:
+  - name: driver_stats
+    category: batch
     features:
       - name: conv_rate
         value-type: float64
diff --git a/oomagent/test/data/fraud_detection.yaml b/oomagent/test/data/fraud_detection.yaml
index ba34b02d..7edcf39b 100644
--- a/oomagent/test/data/fraud_detection.yaml
+++ b/oomagent/test/data/fraud_detection.yaml
@@ -2,8 +2,9 @@ kind: Entity
 name: user
 length: 12
 description: 'user id'
-batch-features:
-- group: account
+groups:
+- name: account
+  category: batch
   description: 'user account info'
   features:
   - name: state
@@ -14,7 +15,8 @@ batch-features:
     value-type: int64
   - name: has_2fa_installed
     value-type: bool
-- group: transaction_stats
+- name: transaction_stats
+  category: batch
   description: 'user transaction statistics'
   features:
   - name: transaction_count_7d
diff --git a/oomcli/test/test_apply_entity.sh b/oomcli/test/test_apply_entity.sh
index e764f496..cfe252fb 100755
--- a/oomcli/test/test_apply_entity.sh
+++ b/oomcli/test/test_apply_entity.sh
@@ -10,8 +10,9 @@ kind: Entity
 name: user
 length: 8
 description: 'description'
-batch-features:
-- group: device
+groups:
+- name: device
+  category: batch
   description: a description
   features:
   - name: model
@@ -20,7 +21,8 @@ batch-features:
   - name: price
     value-type: int64
     description: 'description'
-- group: user
+- name: user
+  category: batch
   description: a description
   features:
   - name: age
@@ -39,12 +41,12 @@ ID,NAME,LENGTH,DESCRIPTION
     assert_eq "apply_single_complex_entity: check entity" "$(sort <<< "$entity_expected")" "$(sort <<< "$entity_actual")"
 
     group_expected='
-ID,NAME,ENTITY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
-1,device,user,a description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
-2,user,user,a description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
+1,device,user,batch,a description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+2,user,user,batch,a description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
 '
     group_actual=$(oomcli get meta group -o csv --wide)
-    filter() { cut -d ',' -f 1-4 <<<"$1"; }
+    filter() { cut -d ',' -f 1-5 <<<"$1"; }
     assert_eq "apply_single_complex_entity: check group" "$(filter "$group_expected" | sort)" "$(filter "$group_actual" | sort)"
 
     feature_expected='
@@ -66,8 +68,9 @@ kind: Entity
 name: user
 length: 8
 description: 'description'
-batch-features:
-- group: student
+groups:
+- name: student
+  category: batch
   description: student feature group
 ---
 kind: Entity
@@ -92,11 +95,11 @@ ID,NAME,LENGTH,DESCRIPTION
     assert_eq "apply_multiple_files_of_entity: oomcli get meta entity" "$entity_expected" "$entity_actual"
 
     group_expected='
-ID,NAME,ENTITY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
-1,student,user,student feature group,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
+1,student,user,batch,student feature group,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
 '
     group_actual=$(oomcli get meta group -o csv --wide)
-    filter() { cut -d ',' -f 1-4 <<<"$1"; }
+    filter() { cut -d ',' -f 1-5 <<<"$1"; }
     assert_eq "oapply_multiple_files_of_entity: check group" "$(filter "$group_expected"| sort)" "$(filter "$group_actual" | sort)"
 }
 
@@ -109,8 +112,9 @@ items:
     name: user
     length: 8
     description: user ID
-    batch-features:
-      - group: account
+    groups:
+      - name: account
+        category: batch
         description: user account info
         features:
           - name: credit_score
@@ -122,7 +126,8 @@ items:
           - name: has_2fa_installed
             value-type: bool
             description: has_2fa_installed description
-      - group: transaction_stats
+      - name: transaction_stats
+        category: batch
         description: user transaction statistics
         features:
           - name: transaction_count_7d
@@ -135,8 +140,9 @@ items:
     name: device
     length: 8
     description: device info
-    batch-features:
-      - group: phone
+    groups:
+      - name: phone
+        category: batch
         description: phone info
         features:
           - name: model
@@ -156,10 +162,10 @@ ID,NAME,LENGTH,DESCRIPTION
       assert_eq "apply_entity_items: oomcli apply mutiple entity: check entity" "$(sort <<< "$entity_expected")" "$(sort <<< "$entity_actual")"
 
     group_expected='
-ID,NAME,ENTITY,DESCRIPTION
-1,account,user,user account info
-2,transaction_stats,user,user transaction statistics
-3,phone,device,phone info
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION
+1,account,user,batch,user account info
+2,transaction_stats,user,batch,user transaction statistics
+3,phone,device,batch,phone info
 '
       group_actual=$(oomcli get meta group -o csv)
       assert_eq "apply_entity_items: oomcli apply multiple entity: check group" "$group_expected" "$group_actual"
diff --git a/oomcli/test/test_apply_group.sh b/oomcli/test/test_apply_group.sh
index f9b0415d..d73b40f4 100755
--- a/oomcli/test/test_apply_group.sh
+++ b/oomcli/test/test_apply_group.sh
@@ -31,11 +31,11 @@ features:
 EOF
 
     group_expected='
-ID,NAME,ENTITY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
-1,device,user,description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
+1,device,user,batch,description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
 '
     group_actual=$(oomcli get meta group -o csv --wide)
-    filter() { cut -d ',' -f 1-4 <<<"$1"; }
+    filter() { cut -d ',' -f 1-5 <<<"$1"; }
     assert_eq "apply_single_complex_group: check group" "$(filter "$group_expected" | sort)" "$(filter "$group_actual" | sort)"
 
     feature_expected='
@@ -71,12 +71,12 @@ description: 'description'
 EOF
 
     group_expected='
-ID,NAME,ENTITY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
-1,device,user,description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
-2,account,user,description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
+1,device,user,batch,description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+2,account,user,batch,description,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
 '
     group_actual=$(oomcli get meta group -o csv --wide)
-    filter() { cut -d ',' -f 1-4 <<<"$1"; }
+    filter() { cut -d ',' -f 1-5 <<<"$1"; }
     assert_eq "apply_multiple_files_of_group: check group" "$(filter "$group_expected" | sort)" "$(filter "$group_actual" | sort)"
 }
 
@@ -140,14 +140,14 @@ items:
 EOF
 
     group_expected='
-ID,NAME,ENTITY,DESCRIPTION
-1,account,user,user account info
-2,transaction_stats,user,user transaction statistics
-3,phone,device,phone info
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION
+1,account,user,batch,user account info
+2,transaction_stats,user,batch,user transaction statistics
+3,phone,device,batch,phone info
 '
     group_actual=$(oomcli get meta group -o csv --wide)
-    filter() { cut -d ',' -f 1-4 <<<"$1"; }
-    assert_eq "apply_single_complex_group: check features" "$(filter "$group_expected" | sort)" "$(filter "$group_actual" | sort)"
+    filter() { cut -d ',' -f 1-5 <<<"$1"; }
+    assert_eq "apply_single_complex_group: check group" "$(filter "$group_expected" | sort)" "$(filter "$group_actual" | sort)"
 
     feature_expected='
 ID,NAME,GROUP,ENTITY,CATEGORY,VALUE-TYPE,DESCRIPTION
diff --git a/oomcli/test/test_get_meta_entity.sh b/oomcli/test/test_get_meta_entity.sh
index df9184f8..ffac5278 100755
--- a/oomcli/test/test_get_meta_entity.sh
+++ b/oomcli/test/test_get_meta_entity.sh
@@ -30,8 +30,9 @@ kind: Entity
 name: device
 length: 32
 description: device
-batch-features:
-    - group: phone
+groups:
+    - name: phone
+      category: batch
       description: phone
       features:
         - name: price
@@ -52,8 +53,9 @@ items:
       name: device
       length: 32
       description: device
-      batch-features:
-        - group: phone
+      groups:
+        - name: phone
+          category: batch
           description: phone
           features:
             - name: price
@@ -66,13 +68,17 @@ items:
       name: user
       length: 64
       description: user
-      batch-features:
-        - group: student
+      groups:
+        - name: student
+          category: batch
           description: student
           features:
             - name: age
               value-type: int64
               description: age
+        - name: user-click
+          category: stream
+          description: user click post feature
 '
 
 actual=$(oomcli get meta entity -o yaml)
diff --git a/oomcli/test/test_get_meta_group.sh b/oomcli/test/test_get_meta_group.sh
index dfde839e..c4e036c3 100755
--- a/oomcli/test/test_get_meta_group.sh
+++ b/oomcli/test/test_get_meta_group.sh
@@ -7,25 +7,27 @@ init_store
 register_features
 
 case='oomcli get meta group works'
-expected='ID,NAME,ENTITY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
-1,phone,device,phone,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
-2,student,user,student
+expected='ID,NAME,ENTITY,CATEGORY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
+1,phone,device,batch,phone,<NULL>,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+2,student,user,batch,student
+3,user-click,user,stream,user click post feature
 '
 actual=$(oomcli get meta group -o csv --wide)
-ignore_time() { cut -d ',' -f 1-4 <<<"$1"; }
+ignore_time() { cut -d ',' -f 1-5 <<<"$1"; }
 assert_eq "$case" "$(ignore_time "$expected" | sort)" "$(ignore_time "$actual" | sort)"
 
 case='oomcli get simplified group works'
-expected='ID,NAME,ENTITY,DESCRIPTION
-1,phone,device,phone
-2,student,user,student
+expected='ID,NAME,ENTITY,CATEGORY,DESCRIPTION
+1,phone,device,batch,phone
+2,student,user,batch,student
+3,user-click,user,stream,user click post feature
 '
 actual=$(oomcli get meta group -o csv)
 assert_eq "$case" "$(sort <<< "$expected")" "$(sort <<< "$actual")"
 
 case='oomcli get one group works'
-expected='ID,NAME,ENTITY,DESCRIPTION
-1,phone,device,phone
+expected='ID,NAME,ENTITY,CATEGORY,DESCRIPTION
+1,phone,device,batch,phone
 '
 actual=$(oomcli get meta group phone -o csv)
 assert_eq "$case" "$(sort <<< "$expected")" "$(sort <<< "$actual")"
@@ -73,6 +75,11 @@ items:
         - name: age
           value-type: int64
           description: age
+    - kind: Group
+      name: user-click
+      entity-name: user
+      category: stream
+      description: user click post feature
 '
 actual=$(oomcli get meta group -o yaml)
 assert_eq "$case" "$expected" "$actual"
diff --git a/oomcli/test/test_update_group.sh b/oomcli/test/test_update_group.sh
index ad6202c7..df75681c 100755
--- a/oomcli/test/test_update_group.sh
+++ b/oomcli/test/test_update_group.sh
@@ -9,9 +9,9 @@ import_sample > /dev/null
 case='oomcli update group works'
 oomcli update group phone --description "new description"
 expected='
-ID,NAME,ENTITY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
-1,phone,device,new description,,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
+ID,NAME,ENTITY,CATEGORY,DESCRIPTION,ONLINE-REVISION-ID,CREATE-TIME,MODIFY-TIME
+1,phone,device,batch,new description,,2021-11-30T07:51:03Z,2021-11-30T08:19:13Z
 '
-filter() { cut -d ',' -f 1-4 <<<"$1"; }
+filter() { cut -d ',' -f 1-5 <<<"$1"; }
 actual=$(oomcli get meta group phone -o csv)
 assert_eq "$case" "$(filter "$expected")" "$(filter "$actual")"
diff --git a/oomcli/test/util.sh b/oomcli/test/util.sh
index ae973262..171f0741 100644
--- a/oomcli/test/util.sh
+++ b/oomcli/test/util.sh
@@ -49,12 +49,13 @@ register_features() {
     oomcli register entity device --length 32     --description "device"
     oomcli register entity user   --length 64     --description "user"
 
-    oomcli register group phone    --entity device  --category "batch" --description "phone"
-    oomcli register group student  --entity user    --category "batch" --description "student"
+    oomcli register group phone      --entity device --category "batch"  --description "phone"
+    oomcli register group student    --entity user   --category "batch"  --description "student"
+    oomcli register group user-click --entity user   --category "stream" --description "user click post feature"
 
-    oomcli register batch-feature price --group phone   --value-type "int64"  --description "price"
-    oomcli register batch-feature model --group phone   --value-type "string" --description "model"
-    oomcli register batch-feature age   --group student --value-type "int64"  --description "age"
+    oomcli register feature price --group phone   --value-type "int64"  --description "price"
+    oomcli register feature model --group phone   --value-type "string" --description "model"
+    oomcli register feature age   --group student --value-type "int64"  --description "age"
 }
 
 # import sample data
diff --git a/pkg/oomstore/apply_test.go b/pkg/oomstore/apply_test.go
index e4b88e6d..ca696f21 100644
--- a/pkg/oomstore/apply_test.go
+++ b/pkg/oomstore/apply_test.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/oom-ai/oomstore/pkg/oomstore/types"
 	"github.com/oom-ai/oomstore/pkg/oomstore/types/apply"
 	"github.com/stretchr/testify/require"
 )
@@ -108,7 +109,6 @@ description: 'description'
 					{
 						Kind:        "Group",
 						Name:        "account",
-						Group:       "account",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "description",
@@ -116,7 +116,6 @@ description: 'description'
 					{
 						Kind:        "Group",
 						Name:        "device",
-						Group:       "device",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "description",
@@ -163,7 +162,6 @@ features:
 					{
 						Kind:        "Group",
 						Name:        "device",
-						Group:       "device",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "description",
@@ -196,8 +194,9 @@ kind: Entity
 name: user
 length: 8
 description: 'description'
-batch-features:
-- group: device
+groups:
+- name: device
+  category: batch
   description: description
   features:
   - name: model
@@ -206,7 +205,8 @@ batch-features:
   - name: price
     value-type: int64
     description: 'description'
-- group: user
+- name: user
+  category: batch
   description: description
   features:
   - name: age
@@ -229,14 +229,14 @@ batch-features:
 					{
 						Kind:        "Group",
 						Name:        "device",
-						Group:       "device",
+						Category:    types.CategoryBatch,
 						EntityName:  "user",
 						Description: "description",
 					},
 					{
 						Kind:        "Group",
 						Name:        "user",
-						Group:       "user",
+						Category:    types.CategoryBatch,
 						EntityName:  "user",
 						Description: "description",
 					},
@@ -388,7 +388,6 @@ items:
 					{
 						Kind:        "Group",
 						Name:        "account",
-						Group:       "account",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "user account info",
@@ -396,7 +395,6 @@ items:
 					{
 						Kind:        "Group",
 						Name:        "transaction_stats",
-						Group:       "transaction_stats",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "user transaction statistics",
@@ -451,8 +449,9 @@ items:
       name: user
       length: 8
       description: user ID
-      batch-features:
-        - group: account
+      groups:
+        - name: account
+          category: batch
           description: user account info
           features:
             - name: credit_score
@@ -464,7 +463,8 @@ items:
             - name: has_2fa_installed
               value-type: bool
               description: has_2fa_installed description
-        - group: transaction_stats
+        - name: transaction_stats
+          category: batch
           description: user transaction statistics
           features:
             - name: transaction_count_7d
@@ -477,8 +477,9 @@ items:
       name: device
       length: 8
       description: device info
-      batch-features:
-        - group: phone
+      groups:
+        - name: phone
+          category: batch
           description: phone info
           features:
             - name: model
@@ -508,7 +509,6 @@ items:
 					{
 						Kind:        "Group",
 						Name:        "account",
-						Group:       "account",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "user account info",
@@ -516,7 +516,6 @@ items:
 					{
 						Kind:        "Group",
 						Name:        "transaction_stats",
-						Group:       "transaction_stats",
 						EntityName:  "user",
 						Category:    "batch",
 						Description: "user transaction statistics",
@@ -524,7 +523,6 @@ items:
 					{
 						Kind:        "Group",
 						Name:        "phone",
-						Group:       "phone",
 						EntityName:  "device",
 						Category:    "batch",
 						Description: "phone info",
diff --git a/sdk/python/src/test/test_client.py b/sdk/python/src/test/test_client.py
index 4bb44163..e828d190 100644
--- a/sdk/python/src/test/test_client.py
+++ b/sdk/python/src/test/test_client.py
@@ -27,8 +27,9 @@ class Data:
 name: user
 length: 8
 description: "user ID"
-batch-features:
-  - group: account
+groups:
+  - name: account
+    category: batch
     description: "user account info"
     features:
       - name: state
@@ -39,7 +40,8 @@ batch-features:
         value-type: int64
       - name: has_2fa_installed
         value-type: bool
-  - group: transaction_stats
+  - name: transaction_stats
+    category: batch
     description: "user transaction statistics"
     features:
       - name: transaction_count_7d