Skip to content
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

[HNT-374] Add interest picker #790

Merged
merged 13 commits into from
Feb 13, 2025
Merged

[HNT-374] Add interest picker #790

merged 13 commits into from
Feb 13, 2025

Conversation

mmiermans
Copy link
Collaborator

@mmiermans mmiermans commented Feb 12, 2025

References

Description

Return an 'Interest Picker' from the curated recommendation endpoint, to allow clients to follow sections.

image

PR Review Checklist

Put an x in the boxes that apply

  • This PR conforms to the Contribution Guidelines
  • The PR title starts with the JIRA issue reference, format example [DISCO-####], and has the same title (if applicable)
  • [load test: (abort|skip|warn)] keywords are applied to the last commit message (if applicable)
  • Documentation has been updated (if applicable)
  • Functional and performance test coverage has been expanded and maintained (if applicable)

@mmiermans mmiermans requested a review from jpetto February 12, 2025 01:14
MIN_INTEREST_PICKER_COUNT = 8 # Minimum number of items in the interest picker.


def apply_interest_picker(response: CuratedRecommendationsResponse) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about passing in the result of response.feeds.get_sections() here instead of the entire response?

that would allow skipping the if not response.feeds: check, and set this function up to return a value instead of modifying the response in place. this would also make testing a bit easier, as you can test the output of different (simpler) inputs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't love passing in a complex object and modifying it in place.

The interest picker logic determines response.sections[].isInitiallyVisible and response.interestPicker. This function could accept the result of response.feeds.get_sections() and return an InterestPicker, but it would still need to modify the sections in place. Do you think that's preferable?

I could also extract 2 or 3 small functions to make apply_interest_picker more readable. Would that help?

# Skip the rank for the interest picker.
new_rank += 1
section.receivedFeedRank = new_rank
new_rank += 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm unclear on the purpose of this for block. i don't see sections used anywhere below. is there some by reference variable updating happening?

maybe this would be more clear if this function returned a value?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

section.receivedFeedRank = new_rank updates the rank of the sections, because the interest picker has been inserted between them. I can change it to return a CuratedRecommendationsFeed and InterestPicker.

if enable_interest_picker:
assert data["interestPicker"] is not None
else:
assert data.get("interestPicker") is None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious - why is one assertion using .get and the other isn't?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! This property is always present, so get is not necessary.

@mmiermans mmiermans requested a review from jpetto February 12, 2025 19:39
if len(picker_sections) < MIN_INTEREST_PICKER_COUNT:
for section, _ in sections:
section.isInitiallyVisible = True
return feed, None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as there is quite a bit of logic in this one function, my advice here is to break this out into multiple functions that have small responsibilities. something like:

  • set isInitiallyVisible on sections (basically lines 41-47 and 57-60). accepts sections and returns sections. doesn't do anything with interest picker.
  • create an InterestPicker. accepts sections and returns InterestPicker or None.
  • update receivedFeedRank for sections. accepts InterestPicker and returns sections. if InterestPicker is None, just returns sections unmodified.

an approach like this encapsulates functionality into smaller, dedicated functions that are more easily unit tested.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this really has to be out today, you could consider shipping as-is and refactoring immediately after.

@mmiermans mmiermans force-pushed the hnt-374-interest-picker branch from 8e3e811 to 443f1d6 Compare February 12, 2025 20:50
section.isInitiallyVisible = False


def _get_interest_picker_rank(sections: list[tuple]) -> int:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! i think it would be great to have unit tests on this.

section.isInitiallyVisible = True
visible_count += 1
else:
section.isInitiallyVisible = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this logic belongs here:

if len(picker_sections) < MIN_INTEREST_PICKER_COUNT:
        for section, _ in sections:
            section.isInitiallyVisible = True

but instead of checking the length of picker_sections, you check the number of sections that have isInitiallyVisible = false.

also - why can't this return sections? i'm confused why sections is always being modified by reference...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after this function completes, you can use the result to determine if you need to create an InterestPicker or not.

return random.randint(1, 3)


def _renumber_sections(sections: list[tuple], picker_rank: int) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this return a value instead of modifying sections by reference?

Copy link
Collaborator

@jpetto jpetto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one small question, but otherwise this looks good! thank you for breaking out the functions and the really good unit tests!

@@ -468,6 +469,10 @@ async def fetch(
curated_recommendations_request.sections, response.feeds
)

if curated_recommendations_request.enableInterestPicker and response.feeds:
interest_picker = create_interest_picker(response.feeds.get_sections())
response.interestPicker = interest_picker
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably a framework/app assumption i'm not familiar with, but just to check - it's okay to set response.interestPicker = None?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's fine. The default value is None.

@mmiermans mmiermans added this pull request to the merge queue Feb 13, 2025
Merged via the queue into main with commit c3c117b Feb 13, 2025
11 checks passed
@mmiermans mmiermans deleted the hnt-374-interest-picker branch February 13, 2025 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants