Skip to content

Commit 5806b77

Browse files
committed
Merge branch 'release/1.8.0'
2 parents 17cabf5 + d1162fc commit 5806b77

28 files changed

+1467
-388
lines changed

AUTHORS

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Contributors at the University of Florida
2525
* Naomi Braun
2626
* Philip Chase
2727
* Samantha Emerson
28+
* Melissa Moreno
2829
* Kevin S. Hanson
2930
* Matthew McConnell
3031
* Ajantha Ramineni
@@ -35,4 +36,3 @@ Contributors at the University of Florida
3536
Other Contributors
3637

3738
* L. D. Nicolas May <ldnicolasmay@gmail.com>
38-

CHANGELOG

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
Changelog
22
=========
3+
## [1.8.0] - 2021-09-13
4+
### Summary
5+
6+
### Updated
7+
* Add Z1X processing to LBD short version
8+
* Update UDS Z1X to include handling for optional LBD short version fields
9+
* Make C2T optional for telephone follow-ups to reflect NACC's DED
10+
11+
### Added
12+
* Add new CV covid module
13+
14+
315
## [1.7.1] - 2021-02-03
416
### Summary
517
This release updates the telephone follow-up packet (TFP) module to include

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2016–2020, University of Florida.
1+
Copyright (c) 2016–2021, University of Florida.
22
All rights reserved.
33

44
Redistribution and use in source and binary forms, with or without

README.md

+13-4
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ NACCulator
33

44
[![DOI](https://zenodo.org/badge/20501/ctsit/nacculator.svg)](https://zenodo.org/badge/latestdoi/20501/ctsit/nacculator)
55

6-
Converts a CSV data file exported from REDCap into the NACC's UDS3 fixed-width
7-
format.
6+
NACCulator is a Python 3-based data converter that changes REDCap .csv exported
7+
data to NACC’s fixed-width .txt format. It is configured for UDS3 forms,
8+
including FTLD and LBD (versions 3.0 and 3.1). It will perform basic data
9+
integrity checks during a run: verifying that each field is the correct type
10+
and length, verifying that there are no illegal characters in the Char fields,
11+
verifying that Num fields are within the acceptable range as defined in NACC's
12+
Data Element Dictionary for each form, and checking that no blanking rules have
13+
been violated. NACCulator outputs a .txt file that is immediately ready to
14+
submit to NACC's database.
815

916
_Note:_ NACCulator _**requires Python 3.**_
1017

@@ -30,7 +37,8 @@ REDCap visits (denoted by `redcap_event_name`) contain certain keywords:
3037
"follow" for all followups,
3138
"milestone" for milestone packets,
3239
"neuropath" for neuropathology packets,
33-
"telephone" for telephone followup packets
40+
"telephone" for telephone followup packets,
41+
"covid" for covid-related survey packets
3442

3543
NACCulator collects data from the Z1X form first and uses that to determine the
3644
presence of other forms in the packet. The Z1X form for that record must be
@@ -47,7 +55,7 @@ the `-file` flag._
4755

4856
$ redcap2nacc -h
4957
usage: redcap2nacc [-h]
50-
[-fvp | -ivp | -tfp | -np | -m | -csf | -f {cleanPtid,replaceDrugId,fixHeaders,fillDefault,updateField,removePtid,removeDateRecord,getPtid}]
58+
[-fvp | -ivp | -tfp | -np | -m | -cv | -csf | -f {cleanPtid,replaceDrugId,fixHeaders,fillDefault,updateField,removePtid,removeDateRecord,getPtid}]
5159
[-lbd | -ftld] [-file FILE] [-meta FILTER_META] [-ptid PTID]
5260
[-vnum VNUM] [-vtype VTYPE]
5361

@@ -61,6 +69,7 @@ the `-file` flag._
6169
-tfp3 Set this flag to process as TFP v3.0 (pre-2020) data
6270
-np Set this flag to process as Neuropathology data
6371
-m Set this flag to process as Milestone data
72+
-cv Set this flag to process as COVID data
6473
-csf Set this flag to process as NACC BIDSS CSF data
6574

6675
-f {cleanPtid,replaceDrugId,fixHeaders,fillDefault,updateField,removePtid,removeDateRecord,getPtid}, --filter {cleanPtid,replaceDrugId,fixHeaders,fillDefault,updateField,removePtid,removeDateRecord,getPtid}

nacc/cv/__init__.py

Whitespace-only changes.

nacc/cv/blanks.py

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
###############################################################################
2+
# Copyright 2015-2021 University of Florida. All rights reserved.
3+
# This file is part of UF CTS-IT's NACCulator project.
4+
# Use of this source code is governed by the license found in the LICENSE file.
5+
###############################################################################
6+
7+
import csv
8+
import os
9+
import re
10+
import sys
11+
12+
13+
def convert_rule_to_python(name: str, rule: str) -> bool:
14+
"""
15+
Converts the text `rule` into a python function.
16+
17+
The returned function accepts one argument of type `Packet`.
18+
19+
Example:
20+
packet["FOO"] = "I should be blank!"
21+
packet["BAR"] = 0
22+
r = convert_rule_to_python("FOO", "Blank if Question 1 BAR = 0 (No)")
23+
if packet["FOOBAR"] != "" and r(packet):
24+
raise RuleError("FOO should be blank, but is not!")
25+
26+
:param name: Canonical name of the field
27+
:param rule: Blanking rule text
28+
"""
29+
30+
special_cases = {
31+
32+
}
33+
34+
single_value = re.compile(
35+
r"Blank if( Question(s?))? *\w+ (?P<key>\w+)"
36+
r" *(?P<eq>=|ne|is|not =|!=) (?P<value>\d+)([^-]|$)")
37+
range_values = re.compile(
38+
r"Blank if( Question(s?))? *\w+ (?P<key>\w+)"
39+
r" *(?P<eq>=|ne|is|not =|!=) (?P<start>\d+)-(?P<stop>\d+)( |$)")
40+
blank_value = re.compile(
41+
r"Blank if( Question(s?))? *\w+ (?P<key>\w+) *(?P<eq>=|ne|is|not =) blank")
42+
not_answered = re.compile(
43+
r"Blank if question not answered")
44+
45+
# First, check to see if the rule is a "Special Case"
46+
if name in special_cases:
47+
return special_cases[name](rule)
48+
49+
# Then, check to see if the rule is of the within-range type
50+
m = range_values.match(rule)
51+
if m:
52+
return _blanking_rule_check_within_range(
53+
m.group('key'), m.group('eq'), m.group('start'), m.group('stop'))
54+
55+
# Next, check to see if the rule is of the single-value type
56+
m = single_value.match(rule)
57+
if m:
58+
return _blanking_rule_check_single_value(
59+
m.group('key'), m.group('eq'), m.group('value'))
60+
61+
# Next, check to see if the rule is of the "blank if _ = blank" type
62+
m = blank_value.match(rule)
63+
if m:
64+
return _blanking_rule_check_blank_value(
65+
m.group('key'), m.group('eq'))
66+
67+
# For the FTLD forms, we need to also check to see if
68+
# "Blank if question not answered" is included in the blanking rules
69+
m = not_answered.match(rule)
70+
if m:
71+
return lambda packet: False
72+
73+
# Finally, raise an error since we do not know how to handle the rule
74+
raise Exception("Could not parse Blanking rule: "+name)
75+
76+
77+
def extract_blanks(csvfile):
78+
with open(csvfile) as fp:
79+
reader = csv.DictReader(fp)
80+
blanks_fieldnames = [f for f in reader.fieldnames if 'BLANKS' in f]
81+
for row in reader:
82+
rules = '\t'.join([row[f] for f in blanks_fieldnames]).strip()
83+
if rules:
84+
yield "%s:\t%s" % (row['Data Element'], rules)
85+
86+
87+
def _blanking_rule_check_single_value(key, eq, value):
88+
def should_be_blank(packet):
89+
""" Returns True if the value should be blank according to the rule """
90+
if '=' == eq or 'is' == eq:
91+
return packet[key] == value
92+
elif 'ne' == eq or 'not =' == eq or '!=' == eq:
93+
return packet[key] != value
94+
else:
95+
raise ValueError("'eq' must be '=' or 'ne', not '%s'." % eq)
96+
97+
return should_be_blank
98+
99+
100+
def _blanking_rule_check_within_range(key, eq, start, stop):
101+
def should_be_blank(packet):
102+
""" Returns True if the value should be blank according to the rule """
103+
first = int(start)
104+
last = int(stop)+1
105+
if '=' == eq:
106+
return packet[key] in range(first, last)
107+
elif 'ne' == eq:
108+
return packet[key] not in list(range(first, last))
109+
else:
110+
raise ValueError("'eq' must be '=' or 'ne', not '%s'." % eq)
111+
112+
return should_be_blank
113+
114+
115+
def _blanking_rule_check_blank_value(key, eq, value=None):
116+
def should_be_blank(packet):
117+
""" Returns True if the value should be blank according to the rule """
118+
if '=' == eq:
119+
return packet[key] == value
120+
elif 'ne' == eq:
121+
return packet[key] != value
122+
else:
123+
raise ValueError("'eq' must be '=' or 'ne', not '%s'." % eq)
124+
125+
return should_be_blank
126+
127+
128+
def _blanking_rule_dummy(rule):
129+
return lambda packet: False
130+
131+
132+
def main():
133+
"""
134+
Extracts all blanking rules from all DED files in a specified directory.
135+
136+
Usage:
137+
python blanks.py ./ded_ivp
138+
139+
Note: this module is more useful as an imported module; see
140+
`convert_rule_to_python`.
141+
"""
142+
data_dict_path = './ded_ivp'
143+
if len(sys.argv) > 1:
144+
data_dict_path = sys.argv[1]
145+
146+
deds = [f for f in os.listdir(data_dict_path) if f.endswith('.csv')]
147+
for ded in deds:
148+
for rule in extract_blanks(os.path.join(data_dict_path, ded)):
149+
print(rule)
150+
151+
152+
if __name__ == '__main__':
153+
main()

0 commit comments

Comments
 (0)