Skip to content

Commit 3c44ec0

Browse files
committed
🐛 Fix fetching REDCap data.
1 parent 0298b90 commit 3c44ec0

6 files changed

Lines changed: 71 additions & 11 deletions

File tree

CHANGELOG.md

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

8+
## 1.12.3
9+
10+
### Fixed
11+
12+
- Bug fetching REDCap data introduced in v1.12.1.
13+
814
## 1.12.2
915

1016
### Fixed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.12.1
1+
1.12.3

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hbnmigration",
3-
"version": "1.12.1",
3+
"version": "1.12.3",
44
"private": true,
55
"description": "HBN data migration monitoring infrastructure with Python and Node.js services",
66
"workspaces": [

python_jobs/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.12.1
1+
1.12.3

python_jobs/src/hbnmigration/from_redcap/from_redcap.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -387,15 +387,11 @@ def fetch_data(
387387
filter_conditions = " OR ".join(
388388
[f"[{field}] != ''" for field in export_fields.split(",")]
389389
)
390-
else:
391-
filter_conditions = " AND ".join(
392-
[f"[{field}] != ''" for field in export_fields.split(",")]
390+
filter_logic = (
391+
f"({filter_logic}) AND ({filter_conditions})"
392+
if filter_logic
393+
else filter_conditions
393394
)
394-
filter_logic = (
395-
f"({filter_logic}) AND ({filter_conditions})"
396-
if filter_logic
397-
else filter_conditions
398-
)
399395
if filter_logic:
400396
redcap_participant_data["filterLogic"] = filter_logic
401397
for key, value in exports.items():

python_jobs/src/tests/test_redcap.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,3 +1965,61 @@ def test_string_export_fields_still_works(self, mock_fetch_api) -> None:
19651965
fetch_data("fake_token", "field1,field2")
19661966
request_data = mock_fetch_api.call_args[0][2]
19671967
assert request_data.get("fields") == "field1,field2"
1968+
1969+
1970+
class TestFetchDataRegression:
1971+
"""Ensure backward compatibility and correct filter logic construction."""
1972+
1973+
@patch("hbnmigration.from_redcap.from_redcap.fetch_api_data")
1974+
@patch("hbnmigration.from_redcap.from_redcap.redcap_variables")
1975+
def test_fetch_data_does_not_inject_mandatory_and_filters(
1976+
self, mock_vars: MagicMock, mock_fetch_api: MagicMock
1977+
) -> None:
1978+
"""
1979+
Verify that fetch_data does not automatically inject AND conditions.
1980+
1981+
v1.12.1 introduced a regression where requesting multiple fields
1982+
appended 'AND [field] != ""' for every field, causing REDCap to
1983+
return zero records (and thus NoData) if any single field was empty.
1984+
1985+
Parameters
1986+
----------
1987+
mock_vars
1988+
Mocked REDCap configuration variables.
1989+
mock_fetch_api
1990+
Mocked API execution function.
1991+
1992+
"""
1993+
# 1. Setup Mock: REDCap headers
1994+
mock_vars.headers = {"Content-Type": "application/x-www-form-urlencoded"}
1995+
1996+
# 2. Define expected behavior:
1997+
# Even if we ask for field1 and field2, if a record exists with only field1
1998+
# populated, we expect to get that row back.
1999+
mock_data = pd.DataFrame([{"record_id": "1", "field1": "val1", "field2": ""}])
2000+
mock_fetch_api.return_value = mock_data
2001+
2002+
# 3. Execution: Request multiple fields
2003+
# In the BROKEN version, this will inject:
2004+
# filterLogic: ([field1] != '') AND ([field2] != '')
2005+
# causing the REAL REDCap API to return nothing, though our mock
2006+
# specifically tests the logic construction.
2007+
2008+
try:
2009+
result = fetch_data(
2010+
"fake_token", {"fields": "field1,field2"}, all_or_any="all"
2011+
)
2012+
except Exception as e:
2013+
pytest.fail(f"fetch_data raised an exception: {e}")
2014+
2015+
# 4. Assertions on the constructed logic
2016+
# We need to inspect what was actually sent to the API mock
2017+
sent_payload = mock_fetch_api.call_args[0][2]
2018+
filter_logic = sent_payload.get("filterLogic", "")
2019+
2020+
assert "AND" not in filter_logic, (
2021+
"Regression Found: fetch_data is forcing 'AND' filters on all fields: "
2022+
f"{filter_logic}. This causes records with partial data to be excluded "
2023+
"from the fetch."
2024+
)
2025+
assert not result.empty

0 commit comments

Comments
 (0)