Skip to content

Commit 1493c44

Browse files
committed
Merge branch 'release/1.13.0'
2 parents 6cf424d + 9f4336b commit 1493c44

18 files changed

+360
-154
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ build/
1717
run_*/
1818
nacculator_cfg.ini
1919
.DS_STORE
20+
.env
21+
logs/*
22+
23+
# virtual environment folders .venv is standard, venv is legacy non-standard path
24+
.venv/
25+
venv/

CHANGELOG

+12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
Changelog
22
=========
3+
## [1.13.0] - 2024-03-07
4+
### Summary
5+
This release adds logging functionality (report_handler) to NACCulator's output (found in the `logs` folder), along with several minor bug fixes.
6+
7+
### Added
8+
* Added report_handler (Kshitij Sinha)
9+
10+
### Fixed
11+
* Fix filter name in nacculator_cfg.ini.example (Samantha Emerson)
12+
* Add A3NOT field to ivp and fvp builder files (Samantha Emerson)
13+
* Add FTLD fields to Z1X in builder.py for ivp and fvp (Samantha Emerson)
14+
315
## [1.12.1] - 2023-10-27
416
### Summary
517
This release fixes some blanking logic errors in the new Neuropath version 11 module.

README.md

-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,6 @@ first.
382382
_Note: execute `generator.py` from the same folder as the `corrected`
383383
folder, which should contain any "corrected" DEDs._
384384

385-
386385
### Resources
387386

388387
* UDS3 forms: https://www.alz.washington.edu/NONMEMBER/UDS/DOCS/VER3/UDS3csvded.html

nacc/lbd/fvp/forms.py

+28-28
Large diffs are not rendered by default.

nacc/lbd/ivp/forms.py

+27-27
Large diffs are not rendered by default.

nacc/lbd/v3_1/fvp/forms.py

+28-28
Large diffs are not rendered by default.

nacc/lbd/v3_1/ivp/forms.py

+27-27
Large diffs are not rendered by default.

nacc/local_filters.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import datetime
55
import configparser
66
from nacc.uds3.filters import *
7+
import logging
78

89

910
# Creating a folder which contains Intermediate files
@@ -26,59 +27,82 @@ def run_all_filters(folder_name, config, input_name):
2627
# Calling Filters
2728
try:
2829
print("--------------Removing subjects already in current--------------------", file=sys.stderr)
30+
logging.info('Removing subjects already in current')
2931
if input_name:
3032
input_path = input_name
3133
else:
3234
input_path = os.path.join(folder_name, "redcap_input.csv")
3335
output_path = os.path.join(folder_name, "clean.csv")
3436
print("Processing", file=sys.stderr)
37+
3538
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
3639
filter_clean_ptid(input_ptr, config, output_ptr)
3740

3841
print("--------------Replacing drug IDs--------------------", file=sys.stderr)
42+
logging.info('Replacing drug IDs')
43+
3944
input_path = os.path.join(folder_name, "clean.csv")
4045
output_path = os.path.join(folder_name, "drugs.csv")
4146
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
4247
filter_replace_drug_id(input_ptr, config, output_ptr)
4348

4449
print("--------------Fixing Headers--------------------", file=sys.stderr)
50+
logging.info('Fixing Headers')
51+
4552
input_path = os.path.join(folder_name, "drugs.csv")
4653
output_path = os.path.join(folder_name, "clean_headers.csv")
4754
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
4855
filter_fix_headers(input_ptr, config, output_ptr)
4956

5057
print("--------------Filling in Defaults--------------------", file=sys.stderr)
58+
logging.info('Filling in Defaults')
59+
5160
input_path = os.path.join(folder_name, "clean_headers.csv")
5261
output_path = os.path.join(folder_name, "default.csv")
5362
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
5463
filter_fill_default(input_ptr, config, output_ptr)
5564

5665
print("--------------Updating fields--------------------", file=sys.stderr)
66+
logging.info('Updating fields')
67+
5768
input_path = os.path.join(folder_name, "default.csv")
5869
output_path = os.path.join(folder_name, "update_fields.csv")
5970
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
60-
filter_update_field(input_ptr, config, output_ptr)
71+
(input_ptr, config, output_ptr)
6172

6273
print("--------------Fixing Visit Dates--------------------", file=sys.stderr)
74+
logging.info('Fixing visit dates')
75+
6376
input_path = os.path.join(folder_name, "update_fields.csv")
6477
output_path = os.path.join(folder_name, "proper_visitdate.csv")
6578
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
6679
filter_fix_visitdate(input_ptr, config, output_ptr)
6780

6881
print("--------------Removing Unnecessary Records--------------------", file=sys.stderr)
82+
logging.info('Removing Unnecessary Records')
6983
input_path = os.path.join(folder_name, "proper_visitdate.csv")
7084
output_path = os.path.join(folder_name, "CleanedPtid_Update.csv")
7185
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
7286
filter_remove_ptid(input_ptr, config, output_ptr)
7387

7488
print("--------------Removing Records without VisitDate--------------------", file=sys.stderr)
89+
logging.info('Removing Records without VisitDate')
7590
input_path = os.path.join(folder_name, "CleanedPtid_Update.csv")
7691
output_path = os.path.join(folder_name, "final_Update.csv")
7792
with open(output_path, 'w') as output_ptr, open(input_path, 'r') as input_ptr:
7893
filter_eliminate_empty_date(input_ptr, config, output_ptr)
7994

8095
except Exception as e:
8196
print("Error in Opening a file")
97+
logging.error('Error in Opening a file',
98+
extra={
99+
"report_handler": {
100+
"data": {"ptid": None,
101+
"error": f'Error in Opening a file: {e}'},
102+
"sheet": 'error'
103+
}
104+
}
105+
)
82106
print(e)
83107

84108
return

nacc/logger.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import logging
2+
import sys
3+
from report_handler.report_handler import ReportHandler
4+
5+
6+
def configure_logging(config, handlers: list[logging.Handler] = []):
7+
fmt = '%(asctime)s %(levelname)-9s %(message)s'
8+
9+
logging.basicConfig(level=logging.DEBUG, format=fmt,
10+
filename="logs.log")
11+
12+
console = logging.StreamHandler(sys.stderr)
13+
formatter = logging.Formatter(fmt)
14+
console.setFormatter(formatter)
15+
console.setLevel(logging.DEBUG)
16+
17+
# Gets the root logger and any config changes here affects logging across the code base
18+
logger = logging.getLogger()
19+
20+
logger.addHandler(console)
21+
22+
for handler in handlers:
23+
logger.addHandler(handler)

nacc/redcap2nacc.py

+108-28
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
from nacc.uds3 import filters
3838
from nacc.uds3 import packet as uds3_packet
3939
from nacc.uds3 import Field
40+
from nacc.logger import configure_logging
41+
from report_handler.report_handler import ReportHandler
42+
import logging
4043

4144

4245
def check_blanks(packet: uds3_packet.Packet, options: argparse.Namespace) \
@@ -178,7 +181,8 @@ def check_for_bad_characters(field: Field) -> typing.List:
178181
return incompatible
179182

180183

181-
def check_redcap_event(options, record, out=sys.stdout, err=sys.stderr) -> bool:
184+
def check_redcap_event(
185+
options, record, out=sys.stdout, err=sys.stderr) -> bool:
182186
"""
183187
Determines if the record's redcap_event_name and filled forms match the
184188
options flag
@@ -303,8 +307,21 @@ def check_redcap_event(options, record, out=sys.stdout, err=sys.stderr) -> bool:
303307
if followup_match in ['', '0']:
304308
return False
305309
except KeyError:
306-
print("Could not find a REDCap field for TFP Z1X form.",
307-
file=err)
310+
print(
311+
"Could not find a REDCap field for TFP Z1X form.",
312+
file=err)
313+
logging.error(
314+
"Could not find a REDCap field for TFP Z1X form",
315+
extra={
316+
"report_handler": {
317+
"data": {
318+
"ptid": record['ptid'],
319+
"error": "Could not find a REDCap field for TFP Z1X form"
320+
},
321+
"sheet": 'ERROR'
322+
}
323+
},
324+
)
308325
return False
309326
elif options.tfp3:
310327
event_name = 'tele'
@@ -419,9 +436,8 @@ def set_to_zero_if_blank(*field_names):
419436
# B8 3.
420437
try:
421438
if packet['CVDSIGNS'] == 1:
422-
set_to_zero_if_blank(
423-
'CORTDEF', 'SIVDFIND', 'CVDMOTL', 'CVDMOTR', 'CORTVISL',
424-
'CORTVISR', 'SOMATL', 'SOMATR')
439+
set_to_zero_if_blank('CORTDEF', 'SIVDFIND', 'CVDMOTL', 'CVDMOTR',
440+
'CORTVISL', 'CORTVISR', 'SOMATL', 'SOMATR')
425441
except KeyError:
426442
pass
427443

@@ -440,15 +456,15 @@ def set_to_zero_if_blank(*field_names):
440456
try:
441457
if packet['DEMENTED'] == 1:
442458
set_to_zero_if_blank(
443-
'AMNDEM', 'PCA', 'PPASYN', 'FTDSYN', 'LBDSYN', 'NAMNDEM')
459+
'AMNDEM', 'PCA', 'PPASYN', 'FTDSYN', 'LBDSYN', 'NAMNDEM')
444460
except KeyError:
445461
pass
446462

447463
# D1 5.
448464
try:
449465
if packet['DEMENTED'] == 0:
450466
set_to_zero_if_blank(
451-
'MCIAMEM', 'MCIAPLUS', 'MCINON1', 'MCINON2', 'IMPNOMCI')
467+
'MCIAMEM', 'MCIAPLUS', 'MCINON1', 'MCINON2', 'IMPNOMCI')
452468
except KeyError:
453469
pass
454470

@@ -470,6 +486,13 @@ def set_to_zero_if_blank(*field_names):
470486
except KeyError:
471487
pass
472488

489+
# NP v11 19(r, s, t)
490+
try:
491+
set_to_zero_if_blank(
492+
'NPPDXR', 'NPPDXS', 'NPPDXT')
493+
except KeyError:
494+
pass
495+
473496

474497
def convert(fp, options, out=sys.stdout, err=sys.stderr):
475498
"""
@@ -512,8 +535,9 @@ def convert(fp, options, out=sys.stdout, err=sys.stderr):
512535
if not event_match:
513536
continue
514537

515-
print("[START] ptid : " + str(record['ptid']) + " visit " +
516-
str(record['visitnum']), file=err)
538+
print("[START] ptid : " + str(record['ptid']) +
539+
" visit " + str(record['visitnum']), file=err)
540+
logging.info('[START] ptid: {}'.format(record['ptid']))
517541
try:
518542
if options.lbd and options.ivp:
519543
packet = lbd_ivp_builder.build_lbd_ivp_form(record)
@@ -548,14 +572,23 @@ def convert(fp, options, out=sys.stdout, err=sys.stderr):
548572
elif options.m:
549573
packet = m_builder.build_uds3_m_form(record)
550574

551-
except Exception:
575+
except Exception as e:
552576
if 'ptid' in record:
553-
print("[SKIP] Error for ptid : " + str(record['ptid']) +
554-
" visit " + str(record['visitnum']), file=err)
577+
print("[SKIP] Error for ptid : " + str(record['ptid']),
578+
file=err)
579+
logging.error(
580+
'[SKIP] Error for ptid : {}'.format(record['ptid']),
581+
extra={
582+
"report_handler": {
583+
"data": {"ptid": record['ptid'], "error": str(traceback.format_exc())},
584+
"sheet": "SKIP"
585+
}
586+
}
587+
)
555588
traceback.print_exc()
556589
continue
557590

558-
if not (options.np or options.np10 or options.m or options.lbd or
591+
if not (options.np10 or options.m or options.lbd or
559592
options.lbdsv or options.ftld or options.csf or options.cv):
560593
set_blanks_to_zero(packet)
561594

@@ -565,17 +598,35 @@ def convert(fp, options, out=sys.stdout, err=sys.stderr):
565598
warnings = []
566599
try:
567600
warnings += check_blanks(packet, options)
568-
except KeyError:
601+
except KeyError as e:
569602
print("[SKIP] Error for ptid : " + str(record['ptid']) +
570603
" visit " + str(record['visitnum']), file=err)
604+
logging.error(
605+
'[SKIP] Error for ptid : {}'.format(record['ptid']),
606+
extra={
607+
"report_handler": {
608+
"data": {"ptid": record['ptid'], "error": str(traceback.format_exc())},
609+
"sheet": "SKIP"
610+
}
611+
}
612+
)
571613
traceback.print_exc()
572614
continue
573615

574616
try:
575617
warnings += check_characters(packet)
576-
except KeyError:
618+
except KeyError as e:
577619
print("[SKIP] Error for ptid : " + str(record['ptid']) +
578620
" visit " + str(record['visitnum']), file=err)
621+
logging.error(
622+
'[SKIP] Error for ptid : {}'.format(record['ptid']),
623+
extra={
624+
"report_handler": {
625+
"data": {"ptid": record['ptid'], "error": str(traceback.format_exc())},
626+
"sheet": "SKIP"
627+
}
628+
}
629+
)
579630
traceback.print_exc()
580631
continue
581632

@@ -585,6 +636,15 @@ def convert(fp, options, out=sys.stdout, err=sys.stderr):
585636
warn = "\n".join(map(str, warnings))
586637
warn = warn.replace("\\", "")
587638
print(warn, file=err)
639+
logging.error(
640+
'[SKIP] Error for ptid : {}'.format(record['ptid']),
641+
extra={
642+
"report_handler": {
643+
"data": {"ptid": record['ptid'], "error": ",".join(map(str, warnings))},
644+
"sheet": "SKIP"
645+
}
646+
}
647+
)
588648
continue
589649

590650
if not options.np and not options.np10 and not options.m and not \
@@ -596,9 +656,19 @@ def convert(fp, options, out=sys.stdout, err=sys.stderr):
596656

597657
try:
598658
print(form, file=out)
599-
except AssertionError:
600-
print("[SKIP] Error for ptid : " + str(record['ptid']) +
601-
" visit " + str(record['visitnum']), file=err)
659+
except AssertionError as e:
660+
print("[SKIP] Error for ptid assertion: " +
661+
str(record['ptid']),
662+
file=err)
663+
logging.error(
664+
'[SKIP] Error for ptid : {}'.format(record['ptid']),
665+
extra={
666+
"report_handler": {
667+
"data": {"ptid": record['ptid'], "error": str(e)},
668+
"sheet": "SKIP"
669+
}
670+
}
671+
)
602672
traceback.print_exc()
603673
continue
604674

@@ -700,21 +770,31 @@ def main():
700770

701771
fp = sys.stdin if options.file is None else open(options.file, 'r')
702772

773+
report_handler = ReportHandler()
774+
configure_logging(options, [report_handler])
775+
703776
# Default option is to print out directly to the command terminal.
704777
# If you want to print the output to a specific file, then redirect
705778
# stdout to that filename.
706779
output = sys.stdout
707780

708-
if options.filter:
709-
if options.filter == "getPtid":
710-
filters.filter_extract_ptid(
711-
fp, options.ptid, options.vnum, options.vtype, output)
781+
try:
782+
if options.filter:
783+
if options.filter == "getPtid":
784+
filters.filter_extract_ptid(
785+
fp, options.ptid, options.vnum, options.vtype, output)
786+
else:
787+
filter_method = 'filter_' + filters_names[options.filter]
788+
filter_func = getattr(filters, filter_method)
789+
filter_func(fp, options.filter_meta, output)
712790
else:
713-
filter_method = 'filter_' + filters_names[options.filter]
714-
filter_func = getattr(filters, filter_method)
715-
filter_func(fp, options.filter_meta, output)
716-
else:
717-
convert(fp, options)
791+
convert(fp, options)
792+
logging.info('Nacculator Ended')
793+
except Exception as e:
794+
print(
795+
f"An exception occurred in main(): {str(e), str(e.__cause__), str(e.__context__), str(e.__traceback__), str(e.with_traceback())}")
796+
finally:
797+
report_handler.write_report("logs")
718798

719799

720800
if __name__ == '__main__':

0 commit comments

Comments
 (0)