Skip to content

Commit

Permalink
Merge branch 'fix-readonly' of github.com:yibeichan/reproschema-py in…
Browse files Browse the repository at this point in the history
…to fix-readonly
  • Loading branch information
yibeichan committed Nov 27, 2024
2 parents b330126 + df7a66f commit efb170d
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 42 deletions.
47 changes: 31 additions & 16 deletions reproschema/redcap2reproschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import re
from pathlib import Path

import pandas as pd
import yaml
from bs4 import BeautifulSoup
import pandas as pd

from .context_url import CONTEXTFILE_URL
from .jsonldutils import get_context_version
Expand Down Expand Up @@ -137,10 +137,14 @@ def process_field_properties(data):
condition = normalize_condition(condition)
else:
condition = True

# Check Field Annotation for special flags
annotation = data.get("Field Annotation", "").upper()
if condition and ("@READONLY" in annotation or "@HIDDEN" in annotation or "@CALCTEXT" in annotation):
if condition and (
"@READONLY" in annotation
or "@HIDDEN" in annotation
or "@CALCTEXT" in annotation
):
condition = False

prop_obj = {
Expand Down Expand Up @@ -504,24 +508,30 @@ def parse_language_iso_codes(input_string):
]


def process_csv_with_pandas(csv_file, abs_folder_path, schema_context_url, protocol_name):
def process_csv_with_pandas(
csv_file, abs_folder_path, schema_context_url, protocol_name
):
datas = {}
order = {}
compute = {}
languages = []

df = pd.read_csv(csv_file, encoding="utf-8")
df = df.applymap(lambda x: x.strip() if isinstance(x, str) else x) # Clean headers
df = df.applymap(
lambda x: x.strip() if isinstance(x, str) else x
) # Clean headers

for form_name, group in df.groupby("Form Name"):
datas[form_name] = group.to_dict(orient="records")
order[form_name] = []
compute[form_name] = []
os.makedirs(f"{abs_folder_path}/activities/{form_name}/items", exist_ok=True)
os.makedirs(
f"{abs_folder_path}/activities/{form_name}/items", exist_ok=True
)

# TODO: should we bring back the language
# if not languages:
# languages = parse_language_iso_codes(row["Field Label"])
# if not languages:
# languages = parse_language_iso_codes(row["Field Label"])

for _, row in group.iterrows():
field_name = row["Variable / Field Name"]
Expand All @@ -531,26 +541,31 @@ def process_csv_with_pandas(csv_file, abs_folder_path, schema_context_url, proto
row["Choices, Calculations, OR Slider Labels"],
field_type=row["Field Type"],
)
compute[form_name].append({
"variableName": field_name,
"jsExpression": condition,
})
compute[form_name].append(
{
"variableName": field_name,
"jsExpression": condition,
}
)
elif "@CALCTEXT" in row.get("Field Annotation", "").upper():
calc_text = row["Field Annotation"]
match = re.search(r"@CALCTEXT\((.*)\)", calc_text)
if match:
js_expression = match.group(1)
js_expression = normalize_condition(js_expression)
compute[form_name].append({
"variableName": field_name,
"jsExpression": js_expression,
})
compute[form_name].append(
{
"variableName": field_name,
"jsExpression": js_expression,
}
)
else:
order[form_name].append(f"items/{field_name}")

os.makedirs(f"{abs_folder_path}/{protocol_name}", exist_ok=True)
return datas, order, compute, languages


# todo adding output path
def redcap2reproschema(
csv_file, yaml_file, output_path, schema_context_url=None
Expand Down
56 changes: 30 additions & 26 deletions reproschema/tests/test_field_property.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import csv

import pytest

from ..redcap2reproschema import process_field_properties
Expand All @@ -13,63 +14,65 @@ def test_process_field_properties_calctext():
"Variable / Field Name": "test_var",
"Required Field?": "",
"Field Annotation": "@CALCTEXT",
"Branching Logic (Show field only if...)": ""
"Branching Logic (Show field only if...)": "",
},
"expected": {
"variableName": "test_var",
"isAbout": "items/test_var",
"isVis": False
}
"isVis": False,
},
},
# Complex CALCTEXT with conditional logic
{
"input": {
"Variable / Field Name": "parkinsons_diagnosis",
"Required Field?": "",
"Field Annotation": "@CALCTEXT(if(([diagnosis_parkinsons_gsd_category_1(bradykinesia)] && ([diagnosis_parkinsons_gsd_category_1(tremor)] || [diagnosis_parkinsons_gsd_category_1(rigidity)])), 'Yes', 'No'))",
"Branching Logic (Show field only if...)": "[some_other_condition] = 1"
"Branching Logic (Show field only if...)": "[some_other_condition] = 1",
},
"expected": {
"variableName": "parkinsons_diagnosis",
"isAbout": "items/parkinsons_diagnosis",
"isVis": False
}
"isVis": False,
},
},
# CALCTEXT with numerical operations
{
"input": {
"Variable / Field Name": "bmi",
"Required Field?": "",
"Field Annotation": "@CALCTEXT([weight]/([height]*[height]))",
"Branching Logic (Show field only if...)": "[weight] > 0 and [height] > 0"
"Branching Logic (Show field only if...)": "[weight] > 0 and [height] > 0",
},
"expected": {
"variableName": "bmi",
"isAbout": "items/bmi",
"isVis": False
}
"isVis": False,
},
},
# CALCTEXT with multiple nested conditions
{
"input": {
"Variable / Field Name": "complex_score",
"Required Field?": "",
"Field Annotation": "@CALCTEXT(if([score1] > 10 && [score2] < 5, 'High', if([score1] > 5, 'Medium', 'Low')))",
"Branching Logic (Show field only if...)": ""
"Branching Logic (Show field only if...)": "",
},
"expected": {
"variableName": "complex_score",
"isAbout": "items/complex_score",
"isVis": False
}
}
"isVis": False,
},
},
]

for test_case in test_cases:
result = process_field_properties(test_case["input"])
for key, expected_value in test_case["expected"].items():
assert result[key] == expected_value, \
f"Failed for {key} in test case with annotation: {test_case['input']['Field Annotation']}"
assert (
result[key] == expected_value
), f"Failed for {key} in test case with annotation: {test_case['input']['Field Annotation']}"


def test_process_field_properties_mixed_annotations():
"""Test fields with multiple annotations"""
Expand All @@ -80,34 +83,35 @@ def test_process_field_properties_mixed_annotations():
"Variable / Field Name": "test_var",
"Required Field?": "",
"Field Annotation": "@CALCTEXT @READONLY",
"Branching Logic (Show field only if...)": ""
"Branching Logic (Show field only if...)": "",
},
"expected": {"isVis": False}
"expected": {"isVis": False},
},
# CALCTEXT with HIDDEN
{
"input": {
"Variable / Field Name": "test_var",
"Required Field?": "",
"Field Annotation": "@HIDDEN @CALCTEXT(if([var1] > 0, 1, 0))",
"Branching Logic (Show field only if...)": ""
"Branching Logic (Show field only if...)": "",
},
"expected": {"isVis": False}
"expected": {"isVis": False},
},
# Complex CALCTEXT with other annotations
{
"input": {
"Variable / Field Name": "test_var",
"Required Field?": "",
"Field Annotation": "@CALCTEXT(if(([var1] && [var2]), 'Yes', 'No')) @READONLY @HIDDEN-SURVEY",
"Branching Logic (Show field only if...)": "[condition] = 1"
"Branching Logic (Show field only if...)": "[condition] = 1",
},
"expected": {"isVis": False}
}
"expected": {"isVis": False},
},
]

for test_case in test_cases:
result = process_field_properties(test_case["input"])
for key, expected_value in test_case["expected"].items():
assert result[key] == expected_value, \
f"Failed for {key} in test case with annotation: {test_case['input']['Field Annotation']}"
assert (
result[key] == expected_value
), f"Failed for {key} in test case with annotation: {test_case['input']['Field Annotation']}"

0 comments on commit efb170d

Please sign in to comment.