-
Notifications
You must be signed in to change notification settings - Fork 897
Return full item data instead of just ids option #1193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 12 commits
eed368c
643bb16
204e8ec
87cc1fa
50dd26d
3d6f362
3d2a5a7
48790d9
4f0718b
82d19c9
e87468c
40b0bea
ce0ebdd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -539,8 +539,9 @@ func (s *RestServer) CreateWebService() { | |
| Param(ws.QueryParameter("write-back-delay", "Timestamp delay of write back feedback (format 0h0m0s)").DataType("string")). | ||
| Param(ws.QueryParameter("n", "Number of returned items").DataType("integer")). | ||
| Param(ws.QueryParameter("offset", "Offset of returned items").DataType("integer")). | ||
| Returns(http.StatusOK, "OK", []string{}). | ||
| Writes([]string{})) | ||
| Param(ws.QueryParameter("return-items", "Include full item data in response").DataType("boolean")). | ||
| Returns(http.StatusOK, "OK", RecommendResponse{}). | ||
| Writes(RecommendResponse{})) | ||
| ws.Route(ws.GET("/recommend/{user-id}/{category}").To(s.getRecommend). | ||
| Deprecate().Doc("Get recommendation for user. Set X-API-Version: 2 to return scores."). | ||
| Metadata(restfulspec.KeyOpenAPITags, []string{RecommendationAPITag}). | ||
|
|
@@ -552,8 +553,9 @@ func (s *RestServer) CreateWebService() { | |
| Param(ws.QueryParameter("write-back-delay", "Timestamp delay of write back feedback (format 0h0m0s)").DataType("string")). | ||
| Param(ws.QueryParameter("n", "Number of returned items").DataType("integer")). | ||
| Param(ws.QueryParameter("offset", "Offset of returned items").DataType("integer")). | ||
| Returns(http.StatusOK, "OK", []string{}). | ||
| Writes([]string{})) | ||
| Param(ws.QueryParameter("return-items", "Include full item data in response").DataType("boolean")). | ||
| Returns(http.StatusOK, "OK", RecommendResponse{}). | ||
| Writes(RecommendResponse{})) | ||
| ws.Route(ws.POST("/session/recommend").To(s.sessionRecommend). | ||
| Doc("Get recommendation for session."). | ||
| Metadata(restfulspec.KeyOpenAPITags, []string{RecommendationAPITag}). | ||
|
|
@@ -883,13 +885,27 @@ func (s *RestServer) getRecommend(request *restful.Request, response *restful.Re | |
| } else { | ||
| scores = []cache.Score{} | ||
| } | ||
| results := lo.Map(scores, func(item cache.Score, index int) string { | ||
| itemIds := lo.Map(scores, func(item cache.Score, index int) string { | ||
| return item.Id | ||
| }) | ||
| includeItems := request.QueryParameter("return-items") == "true" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move BatchGetItems to return item clause |
||
| var itemMap map[string]data.Item | ||
| if includeItems { | ||
| // fetch full item data only when requested | ||
| fetchedItems, err := s.DataClient.BatchGetItems(ctx, itemIds) | ||
| if err != nil { | ||
| InternalServerError(response, err) | ||
| return | ||
| } | ||
| itemMap = make(map[string]data.Item, len(fetchedItems)) | ||
| for _, item := range fetchedItems { | ||
| itemMap[item.ItemId] = item | ||
| } | ||
| } | ||
| // write back | ||
| if writeBackFeedback != "" { | ||
| startTime := time.Now() | ||
| for _, itemId := range results { | ||
| for _, itemId := range itemIds { | ||
| // insert to data store | ||
| feedback := data.Feedback{ | ||
| FeedbackKey: data.FeedbackKey{ | ||
|
|
@@ -908,10 +924,36 @@ func (s *RestServer) getRecommend(request *restful.Request, response *restful.Re | |
| } | ||
| // Send result | ||
| if apiVersion == "2" { | ||
| Ok(response, scores) | ||
| if includeItems { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change variable names as well |
||
| scoredItems := make([]ScoredItem, 0, len(scores)) | ||
| for _, s := range scores { | ||
| si := ScoredItem{ItemId: s.Id, Score: s.Score} | ||
| if item, ok := itemMap[s.Id]; ok { | ||
| si.Item = &item | ||
| } | ||
| scoredItems = append(scoredItems, si) | ||
| } | ||
| Ok(response, scoredItems) | ||
| } else { | ||
| Ok(response, scores) | ||
| } | ||
| return | ||
| } | ||
| Ok(response, results) | ||
| // Send response: include full item data only when requested | ||
| if includeItems { | ||
| var items []data.Item | ||
| for _, id := range itemIds { | ||
| if item, ok := itemMap[id]; ok { | ||
| items = append(items, item) | ||
| } | ||
| } | ||
| Ok(response, RecommendResponse{ | ||
| ItemIds: itemIds, | ||
| Items: items, | ||
| }) | ||
| } else { | ||
| Ok(response, itemIds) | ||
| } | ||
| } | ||
|
|
||
| func (s *RestServer) sessionRecommend(request *restful.Request, response *restful.Response) { | ||
|
|
@@ -1011,6 +1053,20 @@ func (s *RestServer) sessionRecommend(request *restful.Request, response *restfu | |
| Ok(response, result) | ||
| } | ||
|
|
||
| // ScoredItem is a scored item with optional full item data for X-Api-Version: 2. | ||
| type ScoredItem struct { | ||
| ItemId string `json:"ItemId"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use id to be compatible with score |
||
| Score float64 `json:"Score"` | ||
| Item *data.Item `json:"Item,omitempty"` | ||
| } | ||
|
|
||
| // RecommendResponse is the response for the recommend endpoint. | ||
| // It includes both item IDs (for backward compatibility) and full item data. | ||
| type RecommendResponse struct { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return array and remove structure to be compatible with no items situation |
||
| ItemIds []string `json:"item_ids"` | ||
| Items []data.Item `json:"items"` | ||
| } | ||
|
|
||
| // Success is the returned data structure for data insert operations. | ||
| type Success struct { | ||
| RowAffected int | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| package server | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "net/http" | ||
|
|
@@ -88,6 +89,25 @@ func (suite *ServerTestSuite) marshal(v interface{}) string { | |
| return string(s) | ||
| } | ||
|
|
||
| // marshalRecommend builds the expected JSON for the recommend endpoint. | ||
| // It fetches full item data from the test DataClient and orders them to match itemIds. | ||
| func (suite *ServerTestSuite) marshalRecommend(itemIds []string) string { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename to marshal scored items |
||
| ctx := context.Background() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use suite.T(). Context() |
||
| fetched, err := suite.DataClient.BatchGetItems(ctx, itemIds) | ||
| suite.NoError(err) | ||
| itemMap := make(map[string]data.Item, len(fetched)) | ||
| for _, item := range fetched { | ||
| itemMap[item.ItemId] = item | ||
| } | ||
| var items []data.Item | ||
| for _, id := range itemIds { | ||
| if item, ok := itemMap[id]; ok { | ||
| items = append(items, item) | ||
| } | ||
| } | ||
| return suite.marshal(RecommendResponse{ItemIds: itemIds, Items: items}) | ||
| } | ||
|
|
||
| func (suite *ServerTestSuite) TestUsers() { | ||
| t := suite.T() | ||
| users := []data.User{ | ||
|
|
@@ -1083,6 +1103,19 @@ func (suite *ServerTestSuite) TestGetRecommends() { | |
| Status(http.StatusOK). | ||
| Body(suite.marshal([]string{"6", "7", "8"})). | ||
| End() | ||
| // Test return-items=true returns full item data | ||
| apitest.New(). | ||
| Handler(suite.handler). | ||
| Get("/api/recommend/0"). | ||
| Header("X-API-Key", apiKey). | ||
| QueryParams(map[string]string{ | ||
| "n": "3", | ||
| "return-items": "true", | ||
| }). | ||
| Expect(suite.T()). | ||
| Status(http.StatusOK). | ||
| Body(suite.marshalRecommend([]string{"6", "7", "8"})). | ||
| End() | ||
| } | ||
|
|
||
| func (suite *ServerTestSuite) TestGetRecommendsMultiCategories() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not change Writes