Skip to content

Commit 9ee4529

Browse files
committed
fix test data error
1 parent 1b3c488 commit 9ee4529

File tree

3 files changed

+142
-151
lines changed

3 files changed

+142
-151
lines changed

reproschema/reproschema2redcap.py

Lines changed: 122 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import csv
22
from pathlib import Path
3+
import logging
34

45
import requests
56

@@ -8,6 +9,8 @@
89
from .models import Activity, Item, Protocol, ResponseOption
910
from .utils import start_server, stop_server
1011

12+
logger = logging.getLogger(__name__)
13+
1114

1215
def fetch_choices_from_url(url):
1316
try:
@@ -37,6 +40,17 @@ def fetch_choices_from_url(url):
3740

3841

3942
def find_Ftype_and_colH(item, row_data, response_options):
43+
"""
44+
Determine field type and column H value.
45+
46+
Args:
47+
item: Item object containing UI information
48+
row_data: Dictionary to store field data
49+
response_options: Response options object
50+
51+
Returns:
52+
dict: Updated row_data with field type and validation info
53+
"""
4054
# Extract the input type from the item_json
4155
f_type = item.ui.inputType
4256
col_h = ""
@@ -58,16 +72,15 @@ def find_Ftype_and_colH(item, row_data, response_options):
5872
f_type = "text"
5973
col_h = "date_mdy"
6074
elif f_type == "select":
61-
multiple_choice = response_options.multipleChoice
62-
print("mult", multiple_choice)
75+
multiple_choice = getattr(response_options, 'multipleChoice', False)
76+
logger.debug(f"Multiple choice setting for {item.id}: {multiple_choice}")
6377
f_type = "checkbox" if multiple_choice else "dropdown"
6478
elif f_type == "radio":
65-
if response_options.multipleChoice:
79+
if getattr(response_options, 'multipleChoice', False):
6680
f_type = "checkbox"
67-
elif f_type.startswith("select"): # TODO: this should be reviewed
68-
# Adjusting for selectCountry, selectLanguage, selectState types
81+
elif f_type.startswith("select"):
6982
f_type = "radio"
70-
choices_url = response_options.choices
83+
choices_url = getattr(response_options, 'choices', None)
7184
if choices_url and isinstance(choices_url, str):
7285
choices_data = fetch_choices_from_url(choices_url)
7386
if choices_data:
@@ -78,7 +91,6 @@ def find_Ftype_and_colH(item, row_data, response_options):
7891
f_type = "text"
7992

8093
row_data["field_type"] = f_type.lower()
81-
8294
if col_h:
8395
row_data["val_type_OR_slider"] = col_h.lower()
8496

@@ -139,39 +151,67 @@ def process_item(
139151
choices = response_options.choices
140152
if choices and not isinstance(choices, str):
141153
if isinstance(choices, list):
142-
item_choices = [
143-
f"{ch.value}, {ch.name.get('en', '')}"
144-
for ch in choices
145-
if ch.value is not None
146-
]
154+
# Handle the case where choices is a list
155+
item_choices = []
156+
for ch in choices:
157+
if hasattr(ch, 'value') and ch.value is not None:
158+
name = ch.name.get('en', '') if hasattr(ch, 'name') else ''
159+
item_choices.append(f"{ch.value}, {name}")
147160
if item_choices:
148161
row_data["choices"] = " | ".join(item_choices)
149162

150163
# Add valueRequired if explicitly True
151164
if (
152165
item_properties
153-
and "valueRequired" in item_properties
154-
and item_properties["valueRequired"] is True
166+
and isinstance(item_properties, dict) # Ensure it's a dictionary
167+
and item_properties.get("valueRequired") is True
155168
):
156169
row_data["required"] = "y"
157170

158171
var_name = str(item.id).split("/")[-1] # Get the last part of the id path
172+
173+
# Handle compute items
174+
if compute_item and compute_expr:
175+
logger.debug(f"Processing compute item: {var_name}")
176+
logger.debug(f"Compute expression: {compute_expr}")
177+
row_data["choices"] = compute_expr
178+
row_data["field_type"] = "calc"
179+
# For computed fields, we may need to set visibility to false by default
180+
if any(score_type in var_name for score_type in ["_score", "_total"]):
181+
row_data["isVis_logic"] = False
182+
else:
183+
# Use find_Ftype_and_colH but only add non-empty values
184+
field_info = find_Ftype_and_colH(item, {}, response_options)
185+
if field_info.get("field_type"):
186+
row_data["field_type"] = field_info["field_type"]
187+
if field_info.get("val_type_OR_slider"):
188+
row_data["val_type_OR_slider"] = field_info["val_type_OR_slider"]
189+
190+
# Handle visibility
159191
if var_name.endswith("_total_score"):
160-
row_data["isVis_logic"] = False # This will make the field hidden
161-
# Regular isVis handling for other fields
162-
elif "isVis" in item_properties and item_properties["isVis"] is not True:
192+
row_data["isVis_logic"] = False
193+
elif (
194+
item_properties
195+
and isinstance(item_properties, dict) # Ensure it's a dictionary
196+
and "isVis" in item_properties
197+
and item_properties["isVis"] is not True
198+
):
163199
row_data["isVis_logic"] = item_properties["isVis"]
164200

165201
# Handle description
166202
if (
167-
item.description
168-
and "en" in item.description
169-
and item.description["en"]
203+
hasattr(item, 'description')
204+
and isinstance(item.description, dict)
205+
and item.description.get("en")
170206
):
171207
row_data["field_notes"] = item.description["en"]
172208

173209
# Handle preamble
174-
if item.preamble and "en" in item.preamble and item.preamble["en"]:
210+
if (
211+
hasattr(item, 'preamble')
212+
and isinstance(item.preamble, dict)
213+
and item.preamble.get("en")
214+
):
175215
row_data["preamble"] = item.preamble["en"]
176216
elif activity_preamble:
177217
row_data["preamble"] = activity_preamble
@@ -180,44 +220,23 @@ def process_item(
180220
if compute_item:
181221
question = item.description
182222
else:
183-
question = item.question
223+
question = item.question if hasattr(item, 'question') else None
184224

185-
if isinstance(question, dict) and "en" in question and question["en"]:
225+
if isinstance(question, dict) and question.get("en"):
186226
row_data["field_label"] = question["en"]
187227
elif isinstance(question, str) and question:
188228
row_data["field_label"] = question
189229

190-
# Handle compute items
191-
if compute_item and compute_expr:
192-
print(f"\nDebug - Compute Item: {var_name}")
193-
print(f"Compute Expression: {compute_expr}")
194-
row_data["choices"] = compute_expr
195-
row_data["field_type"] = "calc"
196-
# For computed fields, we may need to set visibility to false by default
197-
if any(score_type in var_name for score_type in ["_score", "_total"]):
198-
row_data["isVis_logic"] = False
199-
else:
200-
# Use find_Ftype_and_colH but only add non-empty values
201-
field_info = find_Ftype_and_colH(item, {}, response_options)
202-
if field_info.get("field_type"):
203-
row_data["field_type"] = field_info["field_type"]
204-
if field_info.get("val_type_OR_slider"):
205-
row_data["val_type_OR_slider"] = field_info["val_type_OR_slider"]
206-
207230
return row_data
208231

209232

210233
def get_csv_data(dir_path, contextfile, http_kwargs):
211234
csv_data = []
212235

213-
# Iterate over directories in dir_path
214236
for protocol_dir in dir_path.iterdir():
215237
if protocol_dir.is_dir():
216-
# Check for a _schema file in each directory
217238
schema_file = next(protocol_dir.glob("*_schema"), None)
218-
print(f"Found schema file: {schema_file}")
219239
if schema_file:
220-
# Process the found _schema file
221240
parsed_protocol_json = load_file(
222241
schema_file,
223242
started=True,
@@ -234,6 +253,7 @@ def get_csv_data(dir_path, contextfile, http_kwargs):
234253
for activity_path in activity_order:
235254
if not _is_url(activity_path):
236255
activity_path = protocol_dir / activity_path
256+
237257
parsed_activity_json = load_file(
238258
activity_path,
239259
started=True,
@@ -244,112 +264,73 @@ def get_csv_data(dir_path, contextfile, http_kwargs):
244264
)
245265
del parsed_activity_json["@context"]
246266
act = Activity(**parsed_activity_json)
247-
items_properties = {
248-
el["variableName"]: el
249-
for el in parsed_activity_json["ui"]["addProperties"]
250-
}
251267

252-
# Get activity name without adding extra _schema
268+
# Get activity name
253269
activity_name = act.id.split("/")[-1]
254270
if activity_name.endswith("_schema.jsonld"):
255-
activity_name = activity_name[
256-
:-12
257-
] # Remove _schema.jsonld
271+
activity_name = activity_name[:-12]
258272
elif activity_name.endswith(".jsonld"):
259-
activity_name = activity_name[:-7] # Remove .jsonld
260-
261-
items_properties.update(
262-
{
263-
el["isAbout"]: el
264-
for el in parsed_activity_json["ui"][
265-
"addProperties"
266-
]
273+
activity_name = activity_name[:-7]
274+
275+
# Create a map of computed items
276+
compute_map = {}
277+
if hasattr(act, 'compute'):
278+
compute_map = {
279+
comp.variableName: comp.jsExpression
280+
for comp in act.compute
267281
}
268-
)
269282

270-
if parsed_activity_json:
271-
item_order = [("ord", el) for el in act.ui.order]
272-
item_calc = [("calc", el) for el in act.compute]
283+
# Process each item defined in addProperties
284+
for item_def in parsed_activity_json["ui"]["addProperties"]:
285+
item_path = item_def["isAbout"]
286+
var_name = item_def["variableName"]
287+
288+
# Get the item file path
289+
if not _is_url(item_path):
290+
full_item_path = Path(activity_path).parent / item_path
291+
else:
292+
full_item_path = item_path
293+
294+
try:
295+
item_json = load_file(
296+
full_item_path,
297+
started=True,
298+
http_kwargs=http_kwargs,
299+
fixoldschema=True,
300+
compact=True,
301+
compact_context=contextfile,
302+
)
303+
item_json.pop("@context", "")
304+
item = Item(**item_json)
273305

274-
computed_fields = {
275-
calc_item.variableName
276-
for _, calc_item in item_calc
277-
}
306+
activity_preamble = (
307+
act.preamble.get("en", "").strip()
308+
if hasattr(act, "preamble")
309+
else ""
310+
)
278311

279-
for tp, item in item_order + item_calc:
280-
try:
281-
if tp == "calc":
282-
js_expr = item.jsExpression
283-
var_name = item.variableName
284-
285-
# Find the corresponding item properties
286-
if var_name in items_properties:
287-
item = items_properties[var_name][
288-
"isAbout"
289-
]
290-
# Ensure computed fields are marked as hidden
291-
items_properties[var_name][
292-
"isVis"
293-
] = False
294-
else:
295-
print(
296-
f"WARNING: no item properties found for computed field {var_name} in {activity_name}"
297-
)
298-
continue
299-
item_calc = True
300-
else:
301-
item_calc = False
302-
js_expr = None
303-
it_prop = items_properties.get(item)
304-
if not _is_url(item):
305-
item = Path(activity_path).parent / item
306-
307-
try:
308-
item_json = load_file(
309-
item,
310-
started=True,
311-
http_kwargs=http_kwargs,
312-
fixoldschema=True,
313-
compact=True,
314-
compact_context=contextfile,
315-
)
316-
item_json.pop("@context", "")
317-
itm = Item(**item_json)
318-
except Exception as e:
319-
print(f"Error loading item: {item}")
320-
print(f"Error details: {str(e)}")
321-
continue
322-
323-
activity_name = act.id.split("/")[-1].split(
324-
"."
325-
)[0]
326-
activity_preamble = (
327-
act.preamble.get("en", "").strip()
328-
if hasattr(act, "preamble")
329-
else ""
330-
)
331-
332-
row_data = process_item(
333-
itm,
334-
it_prop,
335-
activity_name,
336-
activity_preamble,
337-
contextfile,
338-
http_kwargs,
339-
item_calc,
340-
js_expr,
341-
)
342-
csv_data.append(row_data)
343-
344-
except Exception as e:
345-
print(
346-
f"Error processing item {item}: {str(e)}"
347-
)
348-
continue
349-
# Break after finding the first _schema file
350-
break
351-
return csv_data
312+
# Check if this is a computed item
313+
compute_expr = compute_map.get(var_name)
314+
is_computed = compute_expr is not None
315+
316+
row_data = process_item(
317+
item,
318+
item_def,
319+
activity_name,
320+
activity_preamble,
321+
contextfile,
322+
http_kwargs,
323+
is_computed,
324+
compute_expr
325+
)
326+
csv_data.append(row_data)
327+
328+
except Exception as e:
329+
print(f"Error processing item {item_path} for activity {activity_name}")
330+
print(f"Error details: {str(e)}")
331+
continue
352332

333+
return csv_data
353334

354335
def write_to_csv(csv_data, output_csv_filename):
355336
# REDCap-specific headers

reproschema/tests/data_test_nimh-minimal/nimh_minimal/nimh_minimal/nimh_minimal_schema

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"@context": [
33
"https://raw.githubusercontent.com/ReproNim/reproschema/1.0.0-rc4/contexts/generic",
44
{
5-
"activity_path": "https://raw.githubusercontent.com/ReproNim/reproschema-library/a23a13875c7262c0bd0d77bd90c1ec296c6d1116/activities/"
5+
"activity_path": "https://raw.githubusercontent.com/ReproNim/reproschema-library/main/activities/"
66
}
77
],
88
"@type": "reproschema:Protocol",

0 commit comments

Comments
 (0)