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

Ftr/dev 10981 spending by subawards grouped endpoint #4312

Open
wants to merge 7 commits into
base: qat
Choose a base branch
from

Conversation

loreleitrimberger
Copy link
Contributor

@loreleitrimberger loreleitrimberger commented Mar 12, 2025

Description:
Creates a new API endpoint /api/v2/search/spending_by_subaward_grouped that returns a list containing award_id, award_generated_internal_id, subaward_count, and subaward_obligation. This will be used to in Award Search 2.0 for the table results when filtered on subawards

Technical details:
This endpoint accepts all of the same filters as the /api/v2/search/spending_by_award endpoint when filtering by Subawards. A new api contract was made and from line 149 to the end is identical to spending_by_award since the filter objects are the same

Requirements for PR merge:

  1. Unit & integration tests updated
  2. API documentation updated
  3. Necessary PR reviewers:
    • Backend
    • N/A Frontend
    • N/A Operations
    • N/A Domain Expert
  4. N/A Matview impact assessment completed
  5. Frontend impact assessment completed
  6. Data validation completed
  7. N/A Appropriate Operations ticket(s) created
  8. Jira Ticket DEV-10981:
    • Link to this Pull-Request
    • N/A Performance evaluation of affected (API | Script | Download)
    • N/A Before / After data comparison

Area for explaining above N/A when needed:

How many results are returned
+ `page` (optional, number)
The page number that is currently returned.
+ `sort`: Any field within the result can be sort (optional, string)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
+ `sort`: Any field within the result can be sort (optional, string)
+ `sort`: Any field within the result can be sort (optional, enum[string])

Since this has an enumerated set of members, we can use enum[string] as the type similar to the order.

+ `previous` (required, number, nullable)

## Filter Objects
### AdvancedFilterObject (object)
Copy link
Contributor

Choose a reason for hiding this comment

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

You can link to another definition of AdvancedFilterObject to avoid having to copy it here. See

The filters available are defined in [AdvancedFilterObject](./spending_by_transaction.md#advanced-filter-object). The one difference is that `keywords` is not required.
for an example.

Comment on lines 84 to 89
{
"name": "sorted",
"key": "sort",
"type": "text",
"text_type": "search",
"default": "award_id",
Copy link
Contributor

Choose a reason for hiding this comment

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

You could make this an enum type and list the sort keys in order to provide some extra validation.

 {
    "name": "sort",
    "key": "sort",
    "type": "enum",
    "enum_values": (<sort fields here>,),
    "optional": True,
    "default": "award_id",
}


# Accepts the same filters as spending_by_award
spending_by_subaward_grouped_models.extend(copy.deepcopy(AWARD_FILTER_NO_RECIPIENT_ID))
spending_by_subaward_grouped_models.extend(copy.deepcopy(PAGINATION))
Copy link
Contributor

Choose a reason for hiding this comment

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

PAGINATION includes a sort model, so you may want to exclude it to avoid conflicting with the one above.

Suggested change
spending_by_subaward_grouped_models.extend(copy.deepcopy(PAGINATION))
spending_by_subaward_grouped_models.extend(copy.deepcopy([model for model in PAGINATION if model["name"] != "sort"]))

Comment on lines 60 to 67
filter_options = {}
time_period_obj = SubawardSearchTimePeriod(
default_end_date=settings.API_MAX_DATE, default_start_date=settings.API_SEARCH_MIN_DATE
)
filter_options["time_period_obj"] = time_period_obj

query_with_filters = QueryWithFilters(QueryType.SUBAWARDS)
filter_query = query_with_filters.generate_elasticsearch_query(self.filters, **filter_options)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you are just using one filter option, you could consider skipping creating a dictionary and then unpacking it and send it as a keyword argument directly.

Suggested change
filter_options = {}
time_period_obj = SubawardSearchTimePeriod(
default_end_date=settings.API_MAX_DATE, default_start_date=settings.API_SEARCH_MIN_DATE
)
filter_options["time_period_obj"] = time_period_obj
query_with_filters = QueryWithFilters(QueryType.SUBAWARDS)
filter_query = query_with_filters.generate_elasticsearch_query(self.filters, **filter_options)
time_period_obj = SubawardSearchTimePeriod(
default_end_date=settings.API_MAX_DATE, default_start_date=settings.API_SEARCH_MIN_DATE
)
query_with_filters = QueryWithFilters(QueryType.SUBAWARDS)
filter_query = query_with_filters.generate_elasticsearch_query(self.filters, time_period_obj=time_period_obj)

logger = logging.getLogger(__name__)


class subaward_grouped_model:
Copy link
Contributor

Choose a reason for hiding this comment

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

I really like the use of a class here to represent this data structure. Could use @dataclass decorator to simplify the syntax. Also python naming best practice is to use TitleCase in class names e.g. SubawardGroupedModel

def post(self, request):
"""Return all subawards matching given awards"""

self.original_filters = request.data.get("filters")
Copy link
Contributor

Choose a reason for hiding this comment

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

In general, it is discouraged to create new properties on self outside of the __init__ method. You could add an __init__ method to this class that calls the APIView's __init__ first with super().__init__() and then adds these properties e.g.

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.original_filters: dict[str, Any] = {}
        <set initial values of other properties of self here>

Comment on lines 129 to 130
for result in response["aggregations"]["award_id"]["buckets"]:
award_generated_internal_id = response["hits"]["hits"][count]["_source"]["unique_award_key"]
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of defining a count variable and iterating it, you can use python's enumerate to get the count this way:

Suggested change
for result in response["aggregations"]["award_id"]["buckets"]:
award_generated_internal_id = response["hits"]["hits"][count]["_source"]["unique_award_key"]
for i, result in enumerate(response["aggregations"]["award_id"]["buckets"]):
award_generated_internal_id = response["hits"]["hits"][i]["_source"]["unique_award_key"]

Can we also check to make sure that the award_piid_fain in result.key matches the value on the hit to ensure that we're getting the right unique_award_key? Relying on this list of hits always being in the right order seems like it could be a little fragile. Alternatively, this could be a nested agg by adding another .metric("unique_award_key", "terms", field="unique_award_key") and referenced like result.unique_award_key.buckets[0].key

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add type hints for the parameters and return values where possible? Some of the methods in this file are missing type hints and some have them.

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