Skip to content

Commit 63cee7e

Browse files
authored
Merge pull request #337 from policy-design-lab/release-0.24.0
Release 0.24.0
2 parents 1f05f11 + ec9f079 commit 63cee7e

File tree

5 files changed

+65
-38
lines changed

5 files changed

+65
-38
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.24.0] - 2025-07-31
8+
9+
### Changed
10+
- Rename totalCounts with totalRecipientCount. [#334](https://github.com/policy-design-lab/pdl-api/issues/334)
11+
- Update Title I endpoints to include data from 2014 to 2023. [#192](https://github.com/policy-design-lab/pdl-api/issues/192)
12+
713
## [0.23.0] - 2025-06-26
814

915
### Added
@@ -241,6 +247,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
241247
### Fixed
242248
- Open API specification file. [#50](https://github.com/policy-design-lab/pdl-api/issues/50)
243249

250+
[0.24.0]: https://github.com/policy-design-lab/pdl-api/compare/0.23.0...0.24.0
244251
[0.23.0]: https://github.com/policy-design-lab/pdl-api/compare/0.22.0...0.23.0
245252
[0.22.0]: https://github.com/policy-design-lab/pdl-api/compare/0.21.0...0.22.0
246253
[0.21.0]: https://github.com/policy-design-lab/pdl-api/compare/0.20.0...0.21.0

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM python:3.9-slim
22

3-
ENV POETRY_VIRTUALENVS_CREATE false
3+
ENV POETRY_VIRTUALENVS_CREATE=false
44

55
RUN apt update \
66
&& apt upgrade -y \
@@ -29,7 +29,7 @@ ENV DB_HOST=localhost \
2929
ALL_PROGRAMS_START_YEAR=2018 \
3030
ALL_PROGRAMS_END_YEAR=2022 \
3131
TITLE_I_START_YEAR=2014 \
32-
TITLE_I_END_YEAR=2021 \
32+
TITLE_I_END_YEAR=2023 \
3333
TITLE_II_START_YEAR=2014 \
3434
TITLE_II_END_YEAR=2023 \
3535
CROP_INSURANCE_START_YEAR=2014 \

app/controllers/configs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class to list all configuration settings required for preprocessing and formatti
3434
ALL_PROGRAMS_END_YEAR = int(os.getenv('ALL_PROGRAMS_END_YEAR', '2022')) # Landing Page endpoint
3535

3636
TITLE_I_START_YEAR = int(os.getenv('TITLE_I_START_YEAR', '2014'))
37-
TITLE_I_END_YEAR = int(os.getenv('TITLE_I_END_YEAR', '2021'))
37+
TITLE_I_END_YEAR = int(os.getenv('TITLE_I_END_YEAR', '2023'))
3838

3939
TITLE_II_START_YEAR = int(os.getenv('TITLE_II_START_YEAR', '2014'))
4040
TITLE_II_END_YEAR = int(os.getenv('TITLE_II_END_YEAR', '2023'))

app/controllers/pdl.py

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,21 @@ def titles_title_i_summary_search():
214214
min_year, max_year = cfg.TITLE_I_START_YEAR, cfg.TITLE_I_END_YEAR
215215
start_year = request.args.get('start_year', type=int, default=min_year)
216216
end_year = request.args.get('end_year', type=int, default=max_year)
217-
title_id = 100
217+
218+
title_id = get_title_id(TITLE_I_NAME)
219+
if title_id is None:
220+
msg = {
221+
"reason": "No record for the given title name " + TITLE_I_NAME,
222+
"error": "Not found: " + request.url,
223+
}
224+
logging.error("Title I: " + json.dumps(msg))
225+
return rs_handlers.not_found(msg)
218226

219227
if start_year and end_year and start_year > end_year:
220228
start_year, end_year = min_year, max_year # Return all data if invalid range
221229

222230
if start_year is None:
223-
start_year = min_year # Default to earliest available year
231+
start_year = min_year # Default to the earliest available year
224232

225233
if end_year is None:
226234
end_year = max_year # Default to latest available year
@@ -234,13 +242,21 @@ def titles_title_i_state_distribution_search():
234242
min_year, max_year = cfg.TITLE_I_START_YEAR, cfg.TITLE_I_END_YEAR
235243
start_year = request.args.get('start_year', type=int, default=min_year)
236244
end_year = request.args.get('end_year', type=int, default=max_year)
237-
title_id = 100
245+
246+
title_id = get_title_id(TITLE_I_NAME)
247+
if title_id is None:
248+
msg = {
249+
"reason": "No record for the given title name " + TITLE_I_NAME,
250+
"error": "Not found: " + request.url,
251+
}
252+
logging.error("Title I: " + json.dumps(msg))
253+
return rs_handlers.not_found(msg)
238254

239255
if start_year and end_year and start_year > end_year:
240256
start_year, end_year = min_year, max_year # Reset to full range if invalid
241257

242258
if start_year is None:
243-
start_year = min_year # Default to earliest available year
259+
start_year = min_year # Default to the earliest available year
244260

245261
if end_year is None:
246262
end_year = max_year # Default to latest available year
@@ -283,7 +299,7 @@ def titles_title_i_subtitles_subtitle_a_state_distribution_search():
283299
start_year, end_year = min_year, max_year # Reset to full range if invalid
284300

285301
if start_year is None:
286-
start_year = min_year # Default to earliest available year
302+
start_year = min_year # Default to the earliest available year
287303

288304
if end_year is None:
289305
end_year = max_year # Default to latest available year
@@ -311,7 +327,7 @@ def titles_title_i_subtitles_subtitle_a_summary_search():
311327
start_year, end_year = min_year, max_year # Reset to full range if invalid
312328

313329
if start_year is None:
314-
start_year = min_year # Default to earliest available year
330+
start_year = min_year # Default to the earliest available year
315331

316332
if end_year is None:
317333
end_year = max_year # Default to latest available year
@@ -339,7 +355,7 @@ def titles_title_i_subtitles_subtitle_d_state_distribution_search():
339355
start_year, end_year = min_year, max_year # Reset to full range if invalid
340356

341357
if start_year is None:
342-
start_year = min_year # Default to earliest available year
358+
start_year = min_year # Default to the earliest available year
343359

344360
if end_year is None:
345361
end_year = max_year # Default to latest available year
@@ -367,7 +383,7 @@ def titles_title_i_subtitles_subtitle_d_summary_search():
367383
start_year, end_year = min_year, max_year # Reset to full range if invalid
368384

369385
if start_year is None:
370-
start_year = min_year # Default to earliest available year
386+
start_year = min_year # Default to the earliest available year
371387

372388
if end_year is None:
373389
end_year = max_year # Default to latest available year
@@ -395,7 +411,7 @@ def titles_title_i_subtitles_subtitle_e_state_distribution_search():
395411
start_year, end_year = min_year, max_year # Reset to full range if invalid
396412

397413
if start_year is None:
398-
start_year = min_year # Default to earliest available year
414+
start_year = min_year # Default to the earliest available year
399415

400416
if end_year is None:
401417
end_year = max_year # Default to latest available year
@@ -423,7 +439,7 @@ def titles_title_i_subtitles_subtitle_e_summary_search():
423439
start_year, end_year = min_year, max_year # Reset to full range if invalid
424440

425441
if start_year is None:
426-
start_year = min_year # Default to earliest available year
442+
start_year = min_year # Default to the earliest available year
427443

428444
if end_year is None:
429445
end_year = max_year # Default to latest available year
@@ -1441,7 +1457,7 @@ def generate_title_iv_state_distribution_response(program_id, start_year, end_ye
14411457
Program.name.label('programName'),
14421458
Payment.year.label('year'),
14431459
Payment.payment.label('totalPaymentInDollars'),
1444-
Payment.recipient_count.label('totalCounts')
1460+
Payment.recipient_count.label('totalRecipientCount')
14451461
).join(
14461462
Program, Payment.program_id == Program.id
14471463
).filter(
@@ -1575,7 +1591,7 @@ def generate_title_i_total_state_distribution_response(title_id, start_year, end
15751591
Subtitle.name.label('subtitleName'),
15761592
Payment.year.label('year'),
15771593
Payment.payment.label('totalPaymentInDollars'),
1578-
Payment.recipient_count.label('totalCounts')).join(
1594+
Payment.recipient_count.label('totalRecipientCount')).join(
15791595
Subtitle, Payment.subtitle_id == Subtitle.id).filter(
15801596
Payment.title_id == title_id,
15811597
Payment.year.between(start_year, end_year)
@@ -1586,21 +1602,21 @@ def generate_title_i_total_state_distribution_response(title_id, start_year, end
15861602

15871603
# create a nested dictionary to store data by year and state
15881604
data_by_year_and_state = defaultdict(
1589-
lambda: defaultdict(lambda: {'totalPaymentInDollars': 0, 'totalRecipients': 0}))
1590-
all_years_summary = defaultdict(lambda: {'totalPaymentInDollars': 0, 'totalRecipients': 0})
1605+
lambda: defaultdict(lambda: {'totalPaymentInDollars': 0, 'totalRecipientCount': 0}))
1606+
all_years_summary = defaultdict(lambda: {'totalPaymentInDollars': 0, 'totalRecipientCount': 0})
15911607

15921608
for record in result:
15931609
state, title_name, year, payments, recipients = record
15941610
entry = data_by_year_and_state[year][state]
15951611
entry['state'] = state
15961612
entry['totalPaymentInDollars'] += payments
1597-
entry['totalRecipients'] += recipients
1613+
entry['totalRecipientCount'] += recipients
15981614

15991615
# add to all years summary
16001616
summary = all_years_summary[state]
16011617
summary['state'] = state
16021618
summary['totalPaymentInDollars'] += payments
1603-
summary['totalRecipients'] += recipients
1619+
summary['totalRecipientCount'] += recipients
16041620

16051621
# sort by total payment
16061622
sorted_data_by_year = {}
@@ -1701,7 +1717,7 @@ def generate_title_i_total_summary_response(title_id, start_year, end_year):
17011717
Title.name.label('titleName'),
17021718
Payment.year.label('year'),
17031719
Payment.payment.label('totalPaymentInDollars'),
1704-
Payment.recipient_count.label('totalCounts'),
1720+
Payment.recipient_count.label('totalRecipientCount'),
17051721
Subtitle.name.label('subtitleName')
17061722
).join(
17071723
Title, Payment.title_id == Title.id
@@ -1718,25 +1734,25 @@ def generate_title_i_total_summary_response(title_id, start_year, end_year):
17181734
# Initialize dictionaries to store aggregated data
17191735
title_summary = defaultdict(lambda: {
17201736
'totalPaymentInDollars': 0,
1721-
'totalCounts': 0,
1737+
'totalRecipientCount': 0,
17221738
'recipients': [],
17231739
'subtitles': defaultdict(lambda: {
17241740
'totalPaymentInDollars': 0,
1725-
'totalCounts': 0,
1741+
'totalRecipientCount': 0,
17261742
'recipients': []
17271743
})
17281744
})
17291745

17301746
# Process each record in the data
17311747
for state, title_name, year, payment, recipients, subtitle in results:
17321748
title_summary[title_name]['totalPaymentInDollars'] += payment
1733-
title_summary[title_name]['totalCounts'] += recipients
1749+
title_summary[title_name]['totalRecipientCount'] += recipients
17341750
title_summary[title_name]['recipients'].append(recipients)
17351751

17361752
# Aggregate subtitle data
17371753
subtitle_dict = title_summary[title_name]['subtitles'][subtitle]
17381754
subtitle_dict['totalPaymentInDollars'] += payment
1739-
subtitle_dict['totalCounts'] += recipients
1755+
subtitle_dict['totalRecipientCount'] += recipients
17401756
subtitle_dict['recipients'].append(recipients)
17411757

17421758
# Prepare the final summary
@@ -1754,16 +1770,18 @@ def generate_title_i_total_summary_response(title_id, start_year, end_year):
17541770
subtitle_list.append({
17551771
'programName': subtitle_name,
17561772
'totalPaymentInDollars': round(subtitle_info['totalPaymentInDollars'], 2),
1757-
'totalCounts': subtitle_info['totalCounts'],
1773+
'totalRecipientCount': subtitle_info['totalRecipientCount'],
17581774
'averageRecipientCount': subtitle_avg_recipients,
17591775
'totalPaymentInPercentage': round(payment_percentage, 2)
17601776
})
17611777

17621778
title_entry = {
17631779
'titleName': title,
17641780
'totalPaymentInDollars': round(info['totalPaymentInDollars'], 2),
1765-
'totalCounts': info['totalCounts'],
1781+
'totalRecipientCount': info['totalRecipientCount'],
17661782
'averageRecipientCount': round(average_recipient_count, 2),
1783+
'startYear': start_year,
1784+
'endYear': end_year,
17671785
'subtitles': subtitle_list
17681786
}
17691787
final_summary.append(title_entry)
@@ -1847,7 +1865,7 @@ def generate_title_i_state_distribution_response(subtitle_id, start_year, end_ye
18471865
func.sum(Payment.payment).label('totalPaymentInDollars'),
18481866
func.round(func.avg(Payment.base_acres), 2).label('averageAreaInAcres'),
18491867
func.cast(func.avg(Payment.recipient_count), BigInteger).label('averageRecipientCount'),
1850-
func.cast(func.sum(Payment.recipient_count), Integer).label('totalCounts'),
1868+
func.cast(func.sum(Payment.recipient_count), Integer).label('totalRecipientCount'),
18511869
(func.cast(func.sum(Payment.recipient_count) / subtitle_subquery_recipient_count * 100, Numeric(5, 2))).label(
18521870
'averageRecipientCountInPercentageNationwide')
18531871
).join(
@@ -1872,7 +1890,7 @@ def generate_title_i_state_distribution_response(subtitle_id, start_year, end_ye
18721890
response_dict = dict(zip(column_names, row))
18731891

18741892
# Cleanup / renaming attributes
1875-
response_dict["totalCountsInPercentageNationwide"] = response_dict["averageRecipientCountInPercentageNationwide"]
1893+
response_dict["totalRecipientCountInPercentageNationwide"] = response_dict["averageRecipientCountInPercentageNationwide"]
18761894
if response_dict['averageAreaInAcres'] is None:
18771895
response_dict['averageAreaInAcres'] = 0.0
18781896
subtitle_response_dict[response_dict['state']] = response_dict
@@ -1908,7 +1926,7 @@ def generate_title_i_state_distribution_response(subtitle_id, start_year, end_ye
19081926
Payment.state_code.label('state'),
19091927
Program.name.label('programName'),
19101928
func.sum(Payment.payment).label('totalPaymentInDollars'),
1911-
func.cast(func.sum(Payment.recipient_count), Integer).label('totalCounts'),
1929+
func.cast(func.sum(Payment.recipient_count), Integer).label('totalRecipientCount'),
19121930
func.round(func.avg(Payment.base_acres), 2).label('averageAreaInAcres'),
19131931
func.cast(func.sum(Payment.recipient_count) / total_years, BigInteger).label('averageRecipientCount'),
19141932
(func.cast(func.sum(Payment.payment) / program_subquery_total_payment * 100, Numeric(5, 2))).label(
@@ -1938,7 +1956,7 @@ def generate_title_i_state_distribution_response(subtitle_id, start_year, end_ye
19381956
state = response_dict['state']
19391957

19401958
# Cleanup / renaming attributes
1941-
response_dict["totalCountsInPercentageNationwide"] = response_dict[
1959+
response_dict["totalRecipientCountInPercentageNationwide"] = response_dict[
19421960
"averageRecipientCountInPercentageNationwide"]
19431961
response_dict['subPrograms'] = []
19441962
if response_dict['averageAreaInAcres'] is None:
@@ -2020,14 +2038,14 @@ def generate_title_i_state_distribution_response(subtitle_id, start_year, end_ye
20202038
else:
20212039
program["totalPaymentInPercentageWithinState"] = 0.0
20222040

2023-
if subtitle_response_dict[state]["totalCounts"] != 0:
2024-
program["totalCountsInPercentageWithinState"] = (
2025-
round(program["totalCounts"] / subtitle_response_dict[state]["totalCounts"] * 100, 2))
2041+
if subtitle_response_dict[state]["totalRecipientCount"] != 0:
2042+
program["totalRecipientCountInPercentageWithinState"] = (
2043+
round(program["totalRecipientCount"] / subtitle_response_dict[state]["totalRecipientCount"] * 100, 2))
20262044
# TODO: Temporary fix. The below attribute may need to be calculated based on the average recipient count or removed if not needed.
2027-
program["averageRecipientCountInPercentageWithinState"] = program["totalCountsInPercentageWithinState"]
2045+
program["averageRecipientCountInPercentageWithinState"] = program["totalRecipientCountInPercentageWithinState"]
20282046
else:
20292047
program["averageRecipientCountInPercentageWithinState"] = 0.0
2030-
program["totalCountsInPercentageWithinState"] = 0.0
2048+
program["totalRecipientCountInPercentageWithinState"] = 0.0
20312049

20322050
for subprogram in program["subPrograms"]:
20332051
if subtitle_response_dict[state]["totalPaymentInDollars"] != 0.0:
@@ -2060,7 +2078,7 @@ def generate_title_i_summary_response(subtitle_id, start_year, end_year):
20602078
subtitle_query = (session.query(
20612079
Subtitle.name.label('subtitleName'),
20622080
func.sum(Payment.payment).label('totalPaymentInDollars'),
2063-
func.cast(func.sum(Payment.recipient_count), Integer).label('totalCounts'),
2081+
func.cast(func.sum(Payment.recipient_count), Integer).label('totalRecipientCount'),
20642082
func.cast(subtitle_avg_recipient_count, BigInteger).label('averageRecipientCount')
20652083
).join(
20662084
Subtitle, Payment.subtitle_id == Subtitle.id
@@ -2080,6 +2098,8 @@ def generate_title_i_summary_response(subtitle_id, start_year, end_year):
20802098
subtitle_response_dict = dict()
20812099
for row in subtitle_result:
20822100
subtitle_response_dict = dict(zip(column_names, row))
2101+
subtitle_response_dict["startYear"] = start_year
2102+
subtitle_response_dict["endYear"] = end_year
20832103
subtitle_response_dict["programs"] = []
20842104

20852105
# Find all programs under the subtitle
@@ -2111,7 +2131,7 @@ def generate_title_i_summary_response(subtitle_id, start_year, end_year):
21112131
program_query = (session.query(
21122132
Program.name.label('programName'),
21132133
func.sum(Payment.payment).label('totalPaymentInDollars'),
2114-
func.cast(func.sum(Payment.recipient_count), Integer).label('totalCounts'),
2134+
func.cast(func.sum(Payment.recipient_count), Integer).label('totalRecipientCount'),
21152135
func.cast(program_avg_recipient_count, BigInteger).label('averageRecipientCount'),
21162136
(func.cast(func.sum(Payment.payment) / subtitle_subquery * 100, Numeric(5, 2))).label(
21172137
'totalPaymentInPercentage')

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "pdl-api"
3-
version = "0.23.0"
3+
version = "0.24.0"
44
description = ""
55
authors = ["Yong Wook Kim <[email protected]>"]
66
readme = "README.md"

0 commit comments

Comments
 (0)