diff --git a/CHANGELOG b/CHANGELOG index 9bbf41c..c45d1d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,43 @@ Changelog ========= +## [1.5.0] - 2020-12-08 +### Summary + +This version introduces several smaller maintenance-level updates. +The FTLD builder files now function similar to the UDS3 module builders, +where NACCulator will first scan the Z1X to see which forms are present +in the record rather than automatically trying to process all possible +forms. This will prevent users from having to repeatedly manually delete +empty optional forms that will never be filled out. + +NACCulator's event detection has been generalized to be more compatible +with more centers. The requirement for the initial visit packet's event +name to contain "initial_visit" has been changed to "initial", and the +requirement for the followup visit packet's event event name to contain +"followup_visit" has been changed to just "follow" (to account for variants +with either "followup" OR "follow_up"). + +The README has been updated to include more information about what data +NACCulator needs from REDCap in order to run correctly (such as the filled +Z1X form), and updated documentation on running the filters. I corrected the +example commands for running the individual filters (with an input csv AND an +output csv) so that they work properly now. All filter commands now require an +argument specifying the config file so that the "validate" function in +filters.py works properly. This makes the command structure consistent across +all filters. + +### added + * Add note about Z1X to README + * Add optional form logic to fvp builder + * Add optional form logic to ftld ivp builder + +### changed + * Fix typo in README + * Edit NACCulator capitalization to be consistent within README + * Update documentation on filters and adjust example config file to make it easier to comment/uncomment filters + * Fixed typo fukid9agd and fusib17pdx to have underscores in uds fvp builder + * Update compatible redcap_event_names to be more general (initial, follow) + ## [1.4.0] - 2020-10-21 ### Summary diff --git a/README.md b/README.md index afee354..f863219 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,22 @@ Once the project data is exported from REDCap to the CSV file `data.csv`, run: $ redcap2nacc data.txt This command will work only in the simplest case; UDS3 IVP data only. -Nacculator will automatically skip PTIDs with errors, so the output `data.txt` +NACCulator will automatically skip PTIDs with errors, so the output `data.txt` file will be ready to submit to NACC. -In order to properly filter the data in the csv, nacculator is expecting that +In order to properly filter the data in the csv, NACCulator is expecting that REDCap visits (denoted by `redcap_event_name`) contain certain keywords: - "initial_visit" for initial visit packets, - "followup_visit" for all followups, + "initial" for initial visit packets, + "follow" for all followups, "milestone" for milestone packets, "neuropath" for neuropathology packets, "telephone" for telephone followup packets +NACCulator collects data from the Z1X form first and uses that to determine the +presence of other forms in the packet. The Z1X form for that record must be +marked "Unverified" or "Complete" for NACCulator to recognize the record, and +each optional form must be marked as submitted within the Z1X for NACCulator to +find those forms. + _Note: output is written to `STDOUT`; errors are written to `STDERR`; input is expected to be from `STDIN` (the command line) unless a file is specified using the `-file` flag._ @@ -80,7 +86,7 @@ Both LBD / LBDSV and FTLD forms can have IVP or FVP arguments. **Example** - Run data through the `cleanPtid` filter: - $ redcap2nacc -f cleanPtid -meta nacculator_cfg.ini data.txt + $ redcap2nacc -f cleanPtid -meta nacculator_cfg.ini filtered_data.csv HOW TO Filter Data Using NACCulator @@ -96,12 +102,26 @@ remove the `.example` portion, and then fill in your center's information. The config file contains sections with in-code filter function name. Each of these sections contains elements necessary for the filter to run. The filters described below will discuss what is required, if anything. + +The filters can be run all at once with your REDCap API token using: + + $ nacculator_filters nacculator_cfg.ini + +You can find more details on `nacculator_filters` under the section: +HOW TO Acquire current-db-subjects.csv for the filters + +Or they can be run one at a time on a `.csv` file with the `-f` and `-meta` +flags. +For example, to run the fixHeaders filter: + + $ redcap2nacc -f fixHeaders -meta nacculator_cfg.ini filtered_output.csv + If the filter requires the config, it must be passed with the `-meta` flag like the example above shows. + * **cleanPtid** - **Filter config required** This filter requires a section in the config called `filter_clean_ptid`. This section will contain a single key `filepath` which will point to a csv (usually called `current-db-subjects.csv`) file of ptids to be removed. All @@ -117,16 +137,15 @@ the example above shows. 110003,I,001,Current 110003,F,002,Current + * **replaceDrugId** This filter replaces the first character of non empty fields of columns `drugid_1` to `drugid_30` with character "**d**". - This filter does not require any meta data file as of now. * **fixHeaders** - **Filter config required** This filter requires a section in the config called `filter_fix_headers` with as many keys as needed to replace the necessary columns. See example below. This filter fixes the column names of any column found in the filter mapping. @@ -142,6 +161,8 @@ the example above shows. fu_otherneur: fu_othneur fu_otherneurx: fu_othneurxs fu_strokedec: fu_strokdec + fukid9agd: fu_kid9agd + fusib17pdx: fu_sib17pdx * **fillDefault** @@ -154,14 +175,15 @@ the example above shows. *If field is blank, it will be updated to default value.* + * **updateField** This filter is used to update fields that already had a value in the REDCap export. Currently, only `adcid` is updated. + * **removePtid** - **Filter config required** This filter requires a section in the config called `filter_remove_ptid` with a single key called `ptid_format`. The value for that key is a regex string to match ptids that are to be kept. @@ -211,7 +233,7 @@ move it to whatever location you specified in your `nacculator_cfg.ini` file. The csv is used by the filter_clean_ptid filter to identify and cull all packets already in NACC's Current database from your input csv. It is used to -make nacculator run faster for very large databases. +make NACCulator run faster for very large databases. Example Workflow diff --git a/nacc/ftld/fvp/builder.py b/nacc/ftld/fvp/builder.py index 2c22c11..4a69ca1 100644 --- a/nacc/ftld/fvp/builder.py +++ b/nacc/ftld/fvp/builder.py @@ -1,14 +1,16 @@ ############################################################################### -# Copyright 2015-2019 University of Florida. All rights reserved. +# Copyright 2015-2020 University of Florida. All rights reserved. # This file is part of UF CTS-IT's NACCulator project. # Use of this source code is governed by the license found in the LICENSE file. ############################################################################### +import sys + from nacc.ftld.fvp import forms as ftld_fvp_forms from nacc.uds3 import packet as ftld_fvp_packet -def build_ftld_fvp_form(record: dict): +def build_ftld_fvp_form(record: dict, err=sys.stderr): ''' Converts REDCap CSV data into a packet (list of FVP Form objects) ''' packet = ftld_fvp_packet.Packet() @@ -21,6 +23,35 @@ def build_ftld_fvp_form(record: dict): and int(record['visitday']) >= 1): raise ValueError('Form date cannot precede March 1, 2015.') + add_z1x(record, packet) + # Forms B3F, B9F, C1F, C2F, C3F, E2F, and E3F are REQUIRED. + # Forms A3A, C4F, C5F, and C6F are OPTIONAL and must be specifically + # marked as present for nacculator to process them + if record['ivp_z1x_complete'] in ['1', '2']: + if record['ftda3afs'] == '1': + add_a3a(record, packet) + add_b3f(record, packet) + add_b9f(record, packet) + add_c1f(record, packet) + add_c2f(record, packet) + add_c3f(record, packet) + if record['ftdc4fs'] == '1': + add_c4f(record, packet) + if record['ftdc5fs'] == '1': + add_c5f(record, packet) + if record['ftdc6fs'] == '1': + add_c6f(record, packet) + else: + print("ptid " + str(record['ptid']) + + ": No Z1X form found.", file=err) + add_e2f(record, packet) + add_e3f(record, packet) + update_header(record, packet) + + return packet + + +def add_z1x(record, packet): Z1X = ftld_fvp_forms.FormZ1X() Z1X.LANGA1 = record['fu_langa1'] Z1X.LANGA2 = record['fu_langa2'] @@ -71,8 +102,10 @@ def build_ftld_fvp_form(record: dict): Z1X.LANGE3F = record['fu_lange3f'] Z1X.LANGCLS = record['fu_langcls'] Z1X.CLSSUB = record['fu_clssub'] - packet.append(Z1X) + packet.insert(0, Z1X) + +def add_a3a(record, packet): A3a = ftld_fvp_forms.FormA3a() A3a.FTDRELCO = record['fu_ftdrelco'] A3a.FTDSIBBY = record['fu_ftdsibby'] @@ -82,6 +115,8 @@ def build_ftld_fvp_form(record: dict): A3a.FTDCOMME = record['fu_ftdcomme'] packet.append(A3a) + +def add_b3f(record, packet): B3F = ftld_fvp_forms.FormB3F() B3F.FTDLTFAS = record['fu_ftdltfas'] B3F.FTDLIMB = record['fu_ftdlimb'] @@ -93,6 +128,8 @@ def build_ftld_fvp_form(record: dict): B3F.FTDGTYPX = record['fu_ftdgtypx'] packet.append(B3F) + +def add_b9f(record, packet): B9F = ftld_fvp_forms.FormB9F() B9F.FTDPPASL = record['fu_ftdppasl'] B9F.FTDPPAPO = record['fu_ftdppapo'] @@ -122,6 +159,8 @@ def build_ftld_fvp_form(record: dict): B9F.FTDPABVF = record['fu_ftdpabvf'] packet.append(B9F) + +def add_c1f(record, packet): C1F = ftld_fvp_forms.FormC1F() C1F.FTDWORRC = record['fu_ftdworrc'] C1F.FTDWORRS = record['fu_ftdworrs'] @@ -150,6 +189,8 @@ def build_ftld_fvp_form(record: dict): C1F.FTDREAPR = record['fu_ftdreapr'] packet.append(C1F) + +def add_c2f(record, packet): C2F = ftld_fvp_forms.FormC2F() C2F.FTDCPC2F = record['fu_ftdcpc2f'] C2F.FTDhAIRD = record['fu_ftdhaird'] @@ -180,6 +221,8 @@ def build_ftld_fvp_form(record: dict): C2F.FTDSNRAT = record['fu_ftdsnrat'] packet.append(C2F) + +def add_c3f(record, packet): C3F = ftld_fvp_forms.FormC3F() C3F.FTDSELF = record['fu_ftdself'] C3F.FTDBADLY = record['fu_ftdbadly'] @@ -235,6 +278,8 @@ def build_ftld_fvp_form(record: dict): C3F.FTDLENGT = record['fu_ftdlengt'] packet.append(C3F) + +def add_c4f(record, packet): C4F = ftld_fvp_forms.FormC4F() C4F.FTDCPC4F = record['fu_ftdcpc4f'] C4F.FTDWORKU = record['fu_ftdworku'] @@ -247,6 +292,8 @@ def build_ftld_fvp_form(record: dict): C4F.FTDBIST = record['fu_ftdbist'] packet.append(C4F) + +def add_c5f(record, packet): C5F = ftld_fvp_forms.FormC5F() C5F.FTDCPC5F = record['fu_ftdcpc5f'] C5F.FTDINSEX = record['fu_ftdinsex'] @@ -271,6 +318,8 @@ def build_ftld_fvp_form(record: dict): C5F.FTDIRIPT = record['fu_ftdiript'] packet.append(C5F) + +def add_c6f(record, packet): C6F = ftld_fvp_forms.FormC6F() C6F.FTDCPC6F = record['fu_ftdcpc6f'] C6F.FTDALTER = record['fu_ftdalter'] @@ -291,6 +340,8 @@ def build_ftld_fvp_form(record: dict): C6F.FTDRSMST = record['fu_ftdrsmst'] packet.append(C6F) + +def add_e2f(record, packet): E2F = ftld_fvp_forms.FormE2F() E2F.FTDSMRI = record['fu_ftdsmri'] E2F.FTDSMMO = record['fu_ftdsmmo'] @@ -340,6 +391,8 @@ def build_ftld_fvp_form(record: dict): E2F.FTDOTANS = record['fu_ftdotans'] packet.append(E2F) + +def add_e3f(record, packet): E3F = ftld_fvp_forms.FormE3F() E3F.FTDIDIAG = record['fu_ftdidiag'] E3F.FTDSMRIO = record['fu_ftdsmrio'] @@ -402,9 +455,6 @@ def build_ftld_fvp_form(record: dict): E3F.FTDOThIS = record['fu_ftdothis'] packet.append(E3F) - update_header(record,packet) - return packet - def update_header(record, packet): for header in packet: diff --git a/nacc/ftld/ivp/builder.py b/nacc/ftld/ivp/builder.py index 2c5abb5..b04ec96 100644 --- a/nacc/ftld/ivp/builder.py +++ b/nacc/ftld/ivp/builder.py @@ -1,14 +1,16 @@ ############################################################################### -# Copyright 2015-2019 University of Florida. All rights reserved. +# Copyright 2015-2020 University of Florida. All rights reserved. # This file is part of UF CTS-IT's NACCulator project. # Use of this source code is governed by the license found in the LICENSE file. ############################################################################### +import sys + from nacc.ftld.ivp import forms as ftld_ivp_forms from nacc.uds3 import packet as ftld_ivp_packet -def build_ftld_ivp_form(record: dict): +def build_ftld_ivp_form(record: dict, err=sys.stderr): ''' Converts REDCap CSV data into a packet (list of IVP Form objects) ''' packet = ftld_ivp_packet.Packet() @@ -21,6 +23,35 @@ def build_ftld_ivp_form(record: dict): and int(record['visitday']) >= 1): raise ValueError('Form date cannot precede March 1, 2015.') + add_z1x(record, packet) + # Forms B3F, B9F, C1F, C2F, C3F, E2F, and E3F are REQUIRED. + # Forms A3A, C4F, C5F, and C6F are OPTIONAL and must be specifically + # marked as present for nacculator to process them + if record['ivp_z1x_complete'] in ['1', '2']: + if record['ftda3afs'] == '1': + add_a3a(record, packet) + add_b3f(record, packet) + add_b9f(record, packet) + add_c1f(record, packet) + add_c2f(record, packet) + add_c3f(record, packet) + if record['ftdc4fs'] == '1': + add_c4f(record, packet) + if record['ftdc5fs'] == '1': + add_c5f(record, packet) + if record['ftdc6fs'] == '1': + add_c6f(record, packet) + else: + print("ptid " + str(record['ptid']) + + ": No Z1X form found.", file=err) + add_e2f(record, packet) + add_e3f(record, packet) + update_header(record, packet) + + return packet + + +def add_z1x(record, packet): Z1X = ftld_ivp_forms.FormZ1X() Z1X.LANGA1 = record['langa1'] Z1X.LANGA2 = record['langa2'] @@ -72,8 +103,10 @@ def build_ftld_ivp_form(record: dict): Z1X.LANGE3F = record['lange3f'] Z1X.LANGCLS = record['langcls'] Z1X.CLSSUB = record['clssub'] - packet.append(Z1X) + packet.insert(0, Z1X) + +def add_a3a(record, packet): A3a = ftld_ivp_forms.FormA3a() A3a.FTDRELCO = record['ftdrelco'] A3a.FTDSIBBY = record['ftdsibby'] @@ -83,6 +116,8 @@ def build_ftld_ivp_form(record: dict): A3a.FTDCOMME = record['ftdcomme'] packet.append(A3a) + +def add_b3f(record, packet): B3F = ftld_ivp_forms.FormB3F() B3F.FTDLTFAS = record['ftdltfas'] B3F.FTDLIMB = record['ftdlimb'] @@ -94,6 +129,8 @@ def build_ftld_ivp_form(record: dict): B3F.FTDGTYPX = record['ftdgtypx'] packet.append(B3F) + +def add_b9f(record, packet): B9F = ftld_ivp_forms.FormB9F() B9F.FTDPPASL = record['ftdppasl'] B9F.FTDPPAPO = record['ftdppapo'] @@ -123,6 +160,8 @@ def build_ftld_ivp_form(record: dict): B9F.FTDPABVF = record['ftdpabvf'] packet.append(B9F) + +def add_c1f(record, packet): C1F = ftld_ivp_forms.FormC1F() C1F.FTDWORRC = record['ftdworrc'] C1F.FTDWORRS = record['ftdworrs'] @@ -151,6 +190,8 @@ def build_ftld_ivp_form(record: dict): C1F.FTDREAPR = record['ftdreapr'] packet.append(C1F) + +def add_c2f(record, packet): C2F = ftld_ivp_forms.FormC2F() C2F.FTDCPC2F = record['ftdcpc2f'] C2F.FTDhAIRD = record['ftdhaird'] @@ -181,6 +222,8 @@ def build_ftld_ivp_form(record: dict): C2F.FTDSNRAT = record['ftdsnrat'] packet.append(C2F) + +def add_c3f(record, packet): C3F = ftld_ivp_forms.FormC3F() C3F.FTDSELF = record['ftdself'] C3F.FTDBADLY = record['ftdbadly'] @@ -236,6 +279,8 @@ def build_ftld_ivp_form(record: dict): C3F.FTDLENGT = record['ftdlengt'] packet.append(C3F) + +def add_c4f(record, packet): C4F = ftld_ivp_forms.FormC4F() C4F.FTDCPC4F = record['ftdcpc4f'] C4F.FTDWORKU = record['ftdworku'] @@ -248,6 +293,8 @@ def build_ftld_ivp_form(record: dict): C4F.FTDBIST = record['ftdbist'] packet.append(C4F) + +def add_c5f(record, packet): C5F = ftld_ivp_forms.FormC5F() C5F.FTDCPC5F = record['ftdcpc5f'] C5F.FTDINSEX = record['ftdinsex'] @@ -272,6 +319,8 @@ def build_ftld_ivp_form(record: dict): C5F.FTDIRIPT = record['ftdiript'] packet.append(C5F) + +def add_c6f(record, packet): C6F = ftld_ivp_forms.FormC6F() C6F.FTDCPC6F = record['ftdcpc6f'] C6F.FTDALTER = record['ftdalter'] @@ -292,6 +341,8 @@ def build_ftld_ivp_form(record: dict): C6F.FTDRSMST = record['ftdrsmst'] packet.append(C6F) + +def add_e2f(record, packet): E2F = ftld_ivp_forms.FormE2F() E2F.FTDSMRI = record['ftdsmri'] E2F.FTDSMMO = record['ftdsmmo'] @@ -341,6 +392,8 @@ def build_ftld_ivp_form(record: dict): E2F.FTDOTANS = record['ftdotans'] packet.append(E2F) + +def add_e3f(record, packet): E3F = ftld_ivp_forms.FormE3F() E3F.FTDIDIAG = record['ftdidiag'] E3F.FTDSMRIO = record['ftdsmrio'] @@ -403,9 +456,6 @@ def build_ftld_ivp_form(record: dict): E3F.FTDOThIS = record['ftdothis'] packet.append(E3F) - update_header(record,packet) - return packet - def update_header(record, packet): for header in packet: diff --git a/nacc/redcap2nacc.py b/nacc/redcap2nacc.py index ed29695..68c47cc 100755 --- a/nacc/redcap2nacc.py +++ b/nacc/redcap2nacc.py @@ -163,37 +163,37 @@ def check_redcap_event(options, record) -> bool: options flag """ if options.lbd and options.ivp: - event_name = 'initial_visit' + event_name = 'initial' form_match_lbd = record['lbd_ivp_b1l_complete'] if form_match_lbd in ['0', '']: return False elif options.lbd and options.fvp: - event_name = 'followup_visit' + event_name = 'follow' form_match_lbd = record['lbd_fvp_b1l_complete'] if form_match_lbd in ['0', '']: return False elif options.lbdsv and options.ivp: - event_name = 'initial_visit' + event_name = 'initial' form_match_lbd = record['lbd_ivp_b1l_complete'] if form_match_lbd in ['0', '']: return False elif options.lbdsv and options.fvp: - event_name = 'followup_visit' + event_name = 'follow' form_match_lbd = record['lbd_fvp_b1l_complete'] if form_match_lbd in ['0', '']: return False elif options.ftld and options.ivp: - event_name = 'initial_visit' + event_name = 'initial' form_match_ftld = record['ftld_present'] if form_match_ftld in ['0', '']: return False elif options.ftld and options.fvp: - event_name = 'followup_visit' + event_name = 'follow' form_match_ftld = record['fu_ftld_present'] if form_match_ftld in ['0', '']: return False elif options.ivp: - event_name = 'initial_visit' + event_name = 'initial' try: form_match_z1 = record['ivp_z1_complete'] except KeyError: @@ -202,7 +202,7 @@ def check_redcap_event(options, record) -> bool: if form_match_z1 in ['0', ''] and form_match_z1x in ['0', '']: return False elif options.fvp: - event_name = 'followup_visit' + event_name = 'follow' try: form_match_z1 = record['fvp_z1_complete'] except KeyError: diff --git a/nacc/uds3/fvp/builder.py b/nacc/uds3/fvp/builder.py index 7080aab..1e95f79 100644 --- a/nacc/uds3/fvp/builder.py +++ b/nacc/uds3/fvp/builder.py @@ -359,7 +359,7 @@ def add_a3(record, packet): a3.SIB17YOB = record['fu_sib17yob'] a3.SIB17AGD = record['fu_sib17agd'] a3.SIB17NEU = record['fu_sib17neu'] - a3.SIB17PDX = record['fusib17pdx'] + a3.SIB17PDX = record['fu_sib17pdx'] a3.SIB17MOE = record['fu_sib17moe'] a3.SIB17AGO = record['fu_sib17ago'] a3.SIB18MOB = record['fu_sib18mob'] @@ -443,7 +443,7 @@ def add_a3(record, packet): a3.KID8AGO = record['fu_kid8ago'] a3.KID9MOB = record['fu_kid9mob'] a3.KID9YOB = record['fu_kid9yob'] - a3.KID9AGD = record['fukid9agd'] + a3.KID9AGD = record['fu_kid9agd'] a3.KID9NEU = record['fu_kid9neu'] a3.KID9PDX = record['fu_kid9pdx'] a3.KID9MOE = record['fu_kid9moe'] diff --git a/nacculator_cfg.ini.example b/nacculator_cfg.ini.example index 9bfdff5..0813331 100644 --- a/nacculator_cfg.ini.example +++ b/nacculator_cfg.ini.example @@ -9,43 +9,45 @@ redcap_server: Your Redcap Server # Comment out filters that you do not intend to use with # in front of # the filter name and each line within that section. -[filter_clean_ptid] # Filters out subjects with PTIDs and visitnums that are already cleared to # NACC's "Current Database" +[filter_clean_ptid] filepath: path/to/current-db-subjects.csv -[filter_replace_drug_id] # Automatically adds the "d" prefix for each drug ID in Form A4. +[filter_replace_drug_id] present: yes -# [filter_fix_headers] # Corrects REDCap-exported headers that do not match NACC's DEDs. # Write in format: # old_header: corrected_header +# [filter_fix_headers] otherneur: othneur otherneurx: othneurx strokedec: strokdec fu_otherneur: fu_othneur fu_otherneurx: fu_othneurx fu_strokedec: fu_strokdec +fukid9agd: fu_kid9agd +fusib17pdx: fu_sib17pdx -[filter_fill_default] # Fills fields that are typically left blank in REDCap: # adcid, nogds (automatically enters 0 if the field is blank), and formver = 3) +[filter_fill_default] adcid: Your ADC ID -[filter_update_field] # Replaces the ADCID if it was previously filled in the REDCap export. +[filter_update_field] adcid: Your ADC ID -[filter_fix_visitdate] # Ensures that REDCap's 'visitnum' field is always an integer. +[filter_fix_visitdate] present: yes -[filter_remove_ptid] # Removes PTIDs that are not in NACC's "Current Database" # but still should be skipped, such as test PTIDs. # ptid_format specifies which ptids should be *kept*. +[filter_remove_ptid] ptid_format: 11\d.* # bad_ptid notes the exceptions to ptid_format that should still be removed. # enter ptid in form of ptid,ptid,ptid,ext... (no spaces) @@ -53,6 +55,6 @@ bad_ptid: # good_ptid notes the exceptions that don't fit ptid_format but should be kept. good_ptid: -[filter_eliminate_empty_date] # Removes PTIDs that are missing information in their visit date. +[filter_eliminate_empty_date] present: yes diff --git a/setup.py b/setup.py index 337ea91..8dc73df 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup, find_packages -VERSION = "1.4.1" +VERSION = "1.5.0" setup( name="nacculator", diff --git a/tests/test_ftld_blanks.py b/tests/test_ftld_blanks.py index 17f6224..7db4e10 100644 --- a/tests/test_ftld_blanks.py +++ b/tests/test_ftld_blanks.py @@ -202,7 +202,7 @@ def make_filled_form() -> dict: 'langd1': '', 'langd2': '', 'langa3a': '', - 'ftda3afs': '', + 'ftda3afs': '1', 'ftda3afr': '', 'langb3f': '', 'langb9f': '', @@ -210,18 +210,20 @@ def make_filled_form() -> dict: 'langc2f': '', 'langc3f': '', 'langc4f': '', - 'ftdc4fs': '', + 'ftdc4fs': '1', 'ftdc4fr': '', 'langc5f': '', - 'ftdc5fs': '', + 'ftdc5fs': '1', 'ftdc5fr': '', 'langc6f': '', - 'ftdc6fs': '', + 'ftdc6fs': '1', 'ftdc6fr': '', 'lange2f': '', 'lange3f': '', 'langcls': '', 'clssub': '', + 'ivp_z1x_complete': '1', + 'fvp_z1x_complete': '1', 'ftdrelco': '', 'ftdsibby': '', 'ftdchdby': '',