Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 24 additions & 33 deletions usaspending_api/common/query_with_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,8 @@ class _RecipientLocations(_Filter):
@classmethod
def generate_elasticsearch_query(cls, filter_values: List[dict], query_type: QueryType, **options) -> ES_Q:
recipient_locations_query = []

field_prefix = "sub_" if query_type == QueryType.SUBAWARDS else ""
for filter_value in filter_values:
location_query = []
county = filter_value.get("county")
state = filter_value.get("state")
country = filter_value.get("country")
Expand All @@ -491,21 +490,19 @@ def generate_elasticsearch_query(cls, filter_values: List[dict], query_type: Que
"zip5": filter_value.get("zip"),
}

for location_key, location_value in location_lookup.items():
if (state is None or country != "USA" or county is not None) and (
district_current is not None or district_original is not None
):
raise InvalidParameterException(INCOMPATIBLE_DISTRICT_LOCATION_PARAMETERS)
if location_value is not None:
location_value = location_value.upper()
if query_type == QueryType.SUBAWARDS:
location_query.append(
ES_Q("match", **{f"sub_recipient_location_{location_key}": location_value})
)
else:
location_query.append(ES_Q("match", **{f"recipient_location_{location_key}": location_value}))

recipient_locations_query.append(ES_Q("bool", must=location_query))
if (state is None or country != "USA" or county is not None) and (
district_current is not None or district_original is not None
):
raise InvalidParameterException(INCOMPATIBLE_DISTRICT_LOCATION_PARAMETERS)

location_query = [
ES_Q("match", **{f"{field_prefix}recipient_location_{location_key}": location_value.upper()})
for location_key, location_value in location_lookup.items()
if location_value is not None
]

if location_query:
recipient_locations_query.append(ES_Q("bool", must=location_query))

return ES_Q("bool", should=recipient_locations_query, minimum_should_match=1)

Expand Down Expand Up @@ -550,8 +547,8 @@ class _PlaceOfPerformanceLocations(_Filter):
@classmethod
def generate_elasticsearch_query(cls, filter_values: List[dict], query_type: QueryType, **options) -> ES_Q:
pop_locations_query = []
field_prefix = "sub_" if query_type == QueryType.SUBAWARDS else ""
for filter_value in filter_values:
location_query = []
county = filter_value.get("county")
state = filter_value.get("state")
country = filter_value.get("country")
Expand All @@ -564,25 +561,19 @@ def generate_elasticsearch_query(cls, filter_values: List[dict], query_type: Que
"congressional_code_current": district_current,
"congressional_code": district_original,
"city_name__keyword": filter_value.get("city"),
"zip5": filter_value.get("zip"),
}

if query_type == QueryType.SUBAWARDS:
location_lookup["zip"] = filter_value.get("zip")
else:
location_lookup["zip5"] = filter_value.get("zip")
_PlaceOfPerformanceLocations._validate_district(state, country, county, district_current, district_original)

for location_key, location_value in location_lookup.items():
_PlaceOfPerformanceLocations._validate_district(
state, country, county, district_current, district_original
)
location_query = [
ES_Q("match", **{f"{field_prefix}pop_{location_key}": location_value.upper()})
for location_key, location_value in location_lookup.items()
if location_value is not None
]

if location_value is not None:
location_value = location_value.upper()
if query_type == QueryType.SUBAWARDS:
location_query.append(ES_Q("match", **{f"sub_pop_{location_key}": location_value}))
else:
location_query.append(ES_Q("match", **{f"pop_{location_key}": location_value}))
pop_locations_query.append(ES_Q("bool", must=location_query))
if location_query:
pop_locations_query.append(ES_Q("bool", must=location_query))

return ES_Q("bool", should=pop_locations_query, minimum_should_match=1)

Expand Down
1 change: 1 addition & 0 deletions usaspending_api/database_scripts/etl/subaward_es_view.sql
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ SELECT
s.sub_place_of_perform_state_code AS sub_pop_state_code,
s.sub_place_of_perform_state_name AS sub_pop_state_name,
s.sub_place_of_perform_county_code AS sub_pop_county_code,
s.sub_place_of_perform_zip5 AS sub_pop_zip5,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I really want to correct the formatting on this file, it would greatly inflate the changeset for the PR.

s.sub_place_of_performance_zip AS sub_pop_zip,
s.sub_place_of_perform_congressio AS sub_pop_congressional_code,
s.sub_place_of_performance_congressional_current AS sub_pop_congressional_code_current,
Expand Down
15 changes: 14 additions & 1 deletion usaspending_api/etl/es_subaward_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,12 @@
"type": "integer"
},
"subaward_number": {
"type": "text"
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"subaward_amount": {
"type": "scaled_float",
Expand Down Expand Up @@ -533,6 +538,14 @@
}
}
},
"sub_pop_zip5": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"sub_pop_congressional_code": {
"type": "keyword"
},
Expand Down
79 changes: 79 additions & 0 deletions usaspending_api/search/tests/integration/test_spending_by_award.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,36 @@ def award_data_fixture(db):
)


@pytest.fixture
def subaward_data(db):
baker.make(
"search.SubawardSearch",
broker_subaward_id=1,
sub_action_date="2023-01-01",
prime_award_group="grant",
prime_award_type="07",
subaward_number=99999,
action_date="2023-01-01",
sub_legal_entity_zip5="12345",
sub_legal_entity_country_code="USA",
sub_place_of_perform_zip5="23456",
sub_place_of_perform_country_co="USA",
)
baker.make(
"search.SubawardSearch",
broker_subaward_id=2,
sub_action_date="2023-01-01",
prime_award_group="procurement",
prime_award_type="08",
subaward_number=99998,
action_date="2023-01-01",
sub_legal_entity_zip5="54321",
sub_legal_entity_country_code="USA",
sub_place_of_perform_zip5="65432",
sub_place_of_perform_country_co="USA",
)


@pytest.mark.django_db
def test_spending_by_award_subaward_success(
client, monkeypatch, elasticsearch_subaward_index, spending_by_award_test_data
Expand Down Expand Up @@ -3738,3 +3768,52 @@ def test_covid_and_iija_values(client, monkeypatch, elasticsearch_award_index, a
]
assert resp.status_code == status.HTTP_200_OK
assert resp.data["results"] == expected_result


def test_spending_by_subaward_place_of_perf_zip_filter(
client, monkeypatch, elasticsearch_subaward_index, subaward_data
):
setup_elasticsearch_test(monkeypatch, elasticsearch_subaward_index)

test_payload = {
"spending_level": "subawards",
"fields": ["Sub-Award ID"],
"filters": {
"award_type_codes": ["07", "08"],
"place_of_performance_locations": [{"country": "USA", "zip": "65432"}],
},
"sort": "Sub-Award ID",
"order": "desc",
}

resp = client.post(
"/api/v2/search/spending_by_award/", content_type="application/json", data=json.dumps(test_payload)
)

assert resp.status_code == status.HTTP_200_OK
results = resp.json().get("results")
assert len(results) == 1
assert results[0]["Sub-Award ID"] == "99998"


def test_spending_by_subaward_recipient_location_zip_filter(
client, monkeypatch, elasticsearch_subaward_index, subaward_data
):
setup_elasticsearch_test(monkeypatch, elasticsearch_subaward_index)

test_payload = {
"spending_level": "subawards",
"fields": ["Sub-Award ID"],
"filters": {"award_type_codes": ["07", "08"], "recipient_locations": [{"country": "USA", "zip": "12345"}]},
"sort": "Sub-Award ID",
"order": "desc",
}

resp = client.post(
"/api/v2/search/spending_by_award/", content_type="application/json", data=json.dumps(test_payload)
)

assert resp.status_code == status.HTTP_200_OK
results = resp.json().get("results")
assert len(results) == 1
assert results[0]["Sub-Award ID"] == "99999"
6 changes: 4 additions & 2 deletions usaspending_api/search/v2/views/spending_by_award.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,10 @@ def add_award_generated_id_field(self, records):

def get_elastic_sort_by_fields(self):
match self.pagination["sort_key"]:
case "Award ID" | "Sub-Award ID":
case "Award ID":
sort_by_fields = ["display_award_id"]
case "Sub-Award ID":
sort_by_fields = ["subaward_number.keyword"]
case "NAICS":
sort_by_fields = (
[contracts_mapping["sub_naics_code"], contracts_mapping["naics_description"]]
Expand Down Expand Up @@ -669,7 +671,7 @@ def calculate_complex_fields(self, row, hit):
zip5 = hit.get("sub_pop_zip")
case _:
zip4 = None
zip5 = None
zip5 = hit.get("sub_pop_zip5")
else:
zip4 = None
zip5 = None
Expand Down