CDISC SDTM validation rules v.1.1 May 2020
DOMAIN = valid Domain Code published by CDISC, with exception of custom domains
3.2
3.3
ALL
Domain code '{data($name)}' is not a valid domain code for CDISC SDTM version '{data($standardversion)}'
]]>
--DY calculated as per the study day algorithm as a non-zero integer value
3.2
3.3
ALL
SD0038
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: keeping worst case scenario into account that there are two records for the same USUBJID in DM - take the first :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $dyvalue := $record/odm:ItemData[@ItemOID=$dyoid]/@Value
(: now calculate the DY value but only when RFSTDTC and DTC have a complete date part :)
let $dycalculated := (
if ($iscompleterfstdtcvalue and $iscompletedtcvalue) then
(: if there is one, we must strip off the time part :)
let $dtcvalueshort := substring($dtcvalue,1,10)
let $rfstdtcvalueshort := substring($rfstdtcvalue,1,10)
let $dycalc := (
if($dtcvalueshort castable as xs:date and $rfstdtcvalueshort castable as xs:date) then
days-from-duration(xs:date($dtcvalueshort)-xs:date($rfstdtcvalueshort))
else()
)
return $dycalc
else () (: one or both dates are not complete dates :)
)
(: for the FDA there is no day 0, DY can only be <0 or >0, first day is day 1 :)
let $dycalculatedfda := (
if($dycalculated >= 0) then
$dycalculated + 1
else (
$dycalculated
)
)
(: before comparing, check that the DY value is really an integer :)
where $dyvalue castable as xs:integer and $dycalculatedfda and $dyvalue and not($dycalculatedfda = $dyvalue)
return Invalid value for {data($dyname)}, calculated value={data($dycalculatedfda)}, observed value={data($dyvalue)}, in dataset {data($name)}
]]>
--DY calculated as per the study day algorithm as a non-zero integer value (single dataset)
3.2
3.3
ALL
SD0038
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: keeping worst case scenario into account that there are two records for the same USUBJID in DM - take the first :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $dyvalue := $record/odm:ItemData[@ItemOID=$dyoid]/@Value
(: now calculate the DY value but only when RFSTDTC and DTC have a complete date part :)
let $dycalculated := (
if ($iscompleterfstdtcvalue and $iscompletedtcvalue) then
(: if there is one, we must strip off the time part :)
let $dtcvalueshort := substring($dtcvalue,1,10)
let $rfstdtcvalueshort := substring($rfstdtcvalue,1,10)
let $dycalc := (
if($dtcvalueshort castable as xs:date and $rfstdtcvalueshort castable as xs:date) then
days-from-duration(xs:date($dtcvalueshort)-xs:date($rfstdtcvalueshort))
else()
)
return $dycalc
else () (: one or both dates are not complete dates :)
)
(: for the FDA there is no day 0, DY can only be <0 or >0, first day is day 1 :)
let $dycalculatedfda := (
if($dycalculated >= 0) then
$dycalculated + 1
else (
$dycalculated
)
)
(: before comparing, check that the DY value is really an integer :)
where $dyvalue castable as xs:integer and $dycalculatedfda and $dyvalue and not($dycalculatedfda = $dyvalue)
return Invalid value for {data($dyname)}, calculated value={data($dycalculatedfda)}, observed value={data($dyvalue)}, in dataset {data($name)}
]]>
--DY variable may only be populated when RFSTDTC is a complete date and the --DTC is at least a complete date
3.2
3.3
ALL
SD1084
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: keeping worst case scenario into account that there are two records for the same USUBJID in DM - take the first :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $dyvalue := $record/odm:ItemData[@ItemOID=$dyoid]/@Value
(: when one of RFSTDTC and xxDTC is not complete, there may NOT be a xxDY value :)
where (not($iscompletedtcvalue) or not($iscompleterfstdtcvalue)) and $dyvalue
return {data($dyname)} is present (value={data($dyvalue)}) although one of {data($dtcname)} (value={data($dtcvalue)}) or RFSTDTC (value={data($rfstdtcvalue)}) is not a complete date
]]>
--DY variable may only be populated when RFSTDTC is a complete date and the --DTC is at least a complete date - single domain or dataset
3.2
3.3
ALL
SD1084
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: keeping worst case scenario into account that there are two records for the same USUBJID in DM - take the first :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $dyvalue := $record/odm:ItemData[@ItemOID=$dyoid]/@Value
(: when one of RFSTDTC and xxDTC is not complete, there may NOT be a xxDY value :)
where (not($iscompletedtcvalue) or not($iscompleterfstdtcvalue)) and $dyvalue
return {data($dyname)} is present (value={data($dyvalue)}) although one of {data($dtcname)} (value={data($dtcvalue)}) or RFSTDTC (value={data($rfstdtcvalue)}) is not a complete date
]]>
--ELTM must be null when --TPTREF = null
3.2
3.3
ALL
SD0034
Missing value for {data($eltmname)} is not null although {data($tptrefname)} is not populated, value of {data($eltmname)}={data($eltmvalue)}
]]>
--ELTM must be null when --TPTREF = null
3.2
3.3
ALL
SD0034
Missing value for {data($eltmname)} is not null although {data($tptrefname)} is not populated, value of {data($eltmname)}={data($eltmvalue)}
]]>
EPOCH in TA.EPOCH
3.2
3.3
ALL
SD0034
Value for EPOCH={data($epochvalue)} in dataset {data($datasetname)} was not found in the TA dataset {data($tadatasetname)}
]]>
Variable Role = IG Role for domains in IG, Role = Model Role for custom domains
3.2
3.3
ALL
0) then $domain
else $name
)
(: as "Role" is already provided in the CDISC Library response at the "Class" level,
(except for e.g. timing variables)
do the call to the CDISC Library BEFORE iterating over the variables :)
(: iterate over the defined variables (by OID) and then name :)
(: but ONLY when we are in a "General Observation" class :)
(: CDISC Library query to the SDTM MODEL :)
let $cdisclibraryquerysdtmmodel := concat($cdisclibrarybase,'sdtm/',$sdtmversion,'/classes/',$class)
(: now run the CDISC Library query
as it requires basic authentication,
we need to use the EXPath 'http-client' extension :)
let $cdisclibraryresponsesdtmmodel := http:send-request(, $cdisclibraryquerysdtmmodel)
(: CDISC Library query to the SDTM-IG :)
let $cdisclibraryquerysdtmig := concat($cdisclibrarybase,'sdtmig/',$sdtmigversion,'/classes/',$class)
let $cdisclibraryresponsesdtmig := http:send-request(, $cdisclibraryquerysdtmig)
for $var in $dataset/odm:ItemRef[$class='Findings' or $class='Events' or $class='Interventions' or $class='FindingsAbout']
let $varoid := $var/@ItemOID
let $varname := $definedoc//odm:ItemDef[@OID=$varoid]/@Name
let $roledefinexml := $var/@Role (: could however be absent :)
(: we need to take generic variable names into account,
e.g. LBDTC goes into --DTC :)
let $varnamegen := (
if(starts-with($varname,$domainname))
then concat('--',substring($varname,3))
else $varname
)
let $roleexpectedsdtmmodel := $cdisclibraryresponsesdtmmodel/json//*/role[../name=$varname or ../name=$varnamegen]
(: Take care: e.g. VISITNUM can occur several times :)
let $roleexpectedsdtmig := ($cdisclibraryresponsesdtmig/json//*/role[../name=$varname or ../name=$varnamegen])[1]
(: return {data($varname)} - {data($roleexpectedsdtmig)} - {data($roleexpectedsdtmmodel)} :)
(: compare the Role from the define.xml with the expected one,
allow case-insensitive :)
let $roleexpected := (
if($roleexpectedsdtmmodel) then data($roleexpectedsdtmmodel)
else data($roleexpectedsdtmig)
)
where $roledefinexml and $roleexpected and not(upper-case($roledefinexml)=upper-case($roleexpectedsdtmmodel)) and not(upper-case($roledefinexml)=upper-case($roleexpectedsdtmig))
return Variable Role found: '{data($roledefinexml)}' - Role expected: '{$roleexpected}'
]]>
Variable = Model List of Allowed Variables for Observation Class
3.2
3.3
ALL
SD0058
, $cdisclibraryquerysdtmmodel)
(: we will also need to look into the 'GeneralObservations' itself :)
let $cdisclibraryquerysdtmgeneral := concat($cdisclibrarybase,'sdtm/',$sdtmversion,'/classes/GeneralObservations')
let $cdisclibrarysdtmgeneralresponse := http:send-request(, $cdisclibraryquerysdtmgeneral)
(: iterate over the defined variables (by OID) and then name :)
(: but ONLY when we are in a "General Observation" class :)
for $varoid in $dataset/odm:ItemRef/@ItemOID[$class='Findings' or $class='Events' or $class='Interventions' or $class='FindingsAbout']
let $varname := $definedoc//odm:ItemDef[@OID=$varoid]/@Name
(: workaround for when the Domain attribute is not populated
which we see a lot in older define.xml files :)
let $domainname := (
if(string-length($domain)>0) then $domain
else substring($name,1,2)
)
(: we need to take generic variable names into account,
e.g. LBDTC goes into --DTC :)
let $varnamegen := (
if(starts-with($varname,$domainname))
then concat('--',substring($varname,3))
else $varname
)
(: when not found, it needs to be looked up in the IG :)
where not($cdisclibrarysdtmmodelresponse/json//*[name=$varnamegen])
and not($cdisclibrarysdtmgeneralresponse/json//*[name=$varnamegen])
(: SDTMIG 3.3 "domain-specific" variables :)
and not(functx:is-value-in-sequence($varname,$domainspecific_3_3_variables) and $sdtmigversion='3.3')
(: FAOBJ, SROBJ :)
and not(functx:is-value-in-sequence($varname,$fa_domainspecific_variables) and $class='FindingsAbout')
return Variable {data($varname)} is not in the model list of allowed variables for class {data($class)}
]]>
Variable = Model List of Allowed Variables for Observation Class
3.2
3.3
ALL
SD0058
, $cdisclibraryquerysdtmmodel)
(: we will also need to look into the 'GeneralObservations' itself :)
let $cdisclibraryquerysdtmgeneral := concat($cdisclibrarybase,'sdtm/',$sdtmversion,'/classes/GeneralObservations')
let $cdisclibrarysdtmgeneralresponse := http:send-request(, $cdisclibraryquerysdtmgeneral)
(: iterate over the defined variables (by OID) and then name :)
(: but ONLY when we are in a "General Observation" class :)
for $varoid in $dataset/odm:ItemRef/@ItemOID[$class='Findings' or $class='Events' or $class='Interventions' or $class='FindingsAbout']
let $varname := $definedoc//odm:ItemDef[@OID=$varoid]/@Name
(: workaround for when the Domain attribute is not populated
which we see a lot in older define.xml files :)
let $domainname := (
if(string-length($domain)>0) then $domain
else substring($name,1,2)
)
(: we need to take generic variable names into account,
e.g. LBDTC goes into --DTC :)
let $varnamegen := (
if(starts-with($varname,$domainname))
then concat('--',substring($varname,3))
else $varname
)
(: when not found, it needs to be looked up in the IG :)
where not($cdisclibrarysdtmmodelresponse/json//*[name=$varnamegen])
and not($cdisclibrarysdtmgeneralresponse/json//*[name=$varnamegen])
(: SDTMIG 3.3 "domain-specific" variables :)
and not(functx:is-value-in-sequence($varname,$domainspecific_3_3_variables) and $sdtmigversion='3.3')
(: FAOBJ, SROBJ :)
and not(functx:is-value-in-sequence($varname,$fa_domainspecific_variables) and $class='FindingsAbout')
return Variable {data($varname)} is not in the model list of allowed variables for class {data($class)}
]]>
When Variable Core Status = Required then Variable present in dataset and "= null
3.2
3.3
ALL
No data found for required variable {data($varname)} in record number {data($recnum)} in dataset {data($datasetname)}
]]>
When Variable Core Status = Required then Variable present in dataset and "= null
3.2
3.3
ALL
, $cdisclibraryquerysdtmig)
(: make an inventory of the variables that are required using the web service for this domain and SDS version :)
let $requiredvars := $cdisclibrarysdtmigresponse/json/datasets/*[name=$domain]/datasetVariables/*[core='Req']/name/text()
(: iterate over the required variables for this dataset,
and then check whether the variable is populated for each record :)
let $datasetdoc := (
if($dsname) then doc(concat($base,$dsname))
else ()
)
(: for the current dataset, we do now have all the required variables, by name,
but we do need to the OIDs :)
let $requiredoids := (
for $a in $itemgroup/odm:ItemRef/@ItemOID
where $definedoc//odm:ItemDef[@OID=$a and functx:is-value-in-sequence(@Name,$requiredvars)]
return data($a)
)
(: iterate over all the rows, and within the row,
check whether there is a value for it :)
(: iterate over all the records in the dataset :)
for $record in $datasetdoc//odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: iterate over all required variable OIDs :)
for $varoid in $requiredoids
(: get the name of the variable :)
let $varname := $definedoc//odm:ItemDef[@OID=$varoid]/@Name
(: give an error when there is no datapoint ('ItemData') for the required variable :)
where not($record/odm:ItemData[@ItemOID=$varoid])
return No data found for required variable {data($varname)} in record number {data($recnum)} in dataset {data($datasetname)}
]]>
When Variable Core Status = Expected then Variable present in dataset
3.2
3.3
ALL
0) then $domain
else $name
)
(: In order to limit the number of calls to the CDISC Library,
we ask for all the variables for the current class,
for which the response contains the "core" which can be "Exp" (expected) :)
let $cdisclibraryquery := concat($cdisclibrarybase,'/sdtmig/',$sdtmigversion,'/classes/',$class)
let $cdisclibraryresponse := http:send-request(, $cdisclibraryquery)
(: get the CDISC library reponse limited to the current domain :)
let $cdisclibrarydomain := $cdisclibraryresponse/json/datasets/*[name=$domainname]
(: iterate over all the variables that are "expected" (core=Exp) :)
for $expectedvarname in $cdisclibrarydomain//datasetVariables/*[core='Exp']/name
(: now check whether the expected variable is in the define.xml for this domain :)
let $itemoid := (
for $a in $definedoc//odm:ItemDef[@Name=$expectedvarname]/@OID
where $a = $dataset/odm:ItemRef/@ItemOID
return $a
)
(: When not, give an error :)
where not($itemoid)
return Expected SDTM variable {data($expectedvarname)} is not found
]]>
Split dataset names length > 2 and <= 4
3.2
3.3
ALL
SD1095
2 and <= 4.
P.S.: What about splitted FAxx domains? For example, FAAE1, FAAE2 ... ? :)
(: 'Split dataset names can be up to four characters in length. For example, if splitting by --CAT, then dataset names would be the domain name plus up to two additional characters (e.g., QS36 for SF-36).
If splitting Findings About by parent domain, then the dataset name would be the domain name plus the two-character domain code describing the parent domain code (e.g., FACM).
:)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare namespace functx = "http://www.functx.com";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: iterate over all datasets in the define.xml that have a 'Domain' attribute
and that are not SUPPxx (Supplemental Qualifiers) datasets :)
for $itemgroup in $definedoc//odm:ItemGroupDef[@Domain and not(starts-with(@Name,'SUPP'))]
(: get the domain name - value of the 'Domain' attribute :)
let $domain := $itemgroup/@Domain
(: and the dataset name (@Name attribute) :)
let $name := $itemgroup/@Name
(: and get the filename :)
let $filename := (
if($defineversion='2.1') then $itemgroup/def21:leaf/@xlink:href
else $itemgroup/def:leaf/@xlink:href
)
(: get the position of the current ItemGroupDef in the series :)
let $pos := $itemgroup/position()
(: get all the ItemGroupDefs with the same value for the 'Domain' attribute,
but that are not SUPPxx datasets
If so, this means that the dataset is a 'splitted' dataset.
P.S. There currently is no other way to find out :)
let $count := count($definedoc//odm:ItemGroupDef[@Domain=$domain and not(starts-with(@Name,'SUPP'))])
(: in case there is more than one such dataset, the prefix (part before the dot)
MUST have MORE than two characters (e.g. qscs.xpt) and no more than 4 characters :)
let $numchars := string-length(substring-before($filename,'.'))
where $count > 1 and ($numchars < 3 or $numchars > 4)
return Split dataset {data($name)} has a dataset/file name '{data($filename)}' with less than 3 or more than 4 characters
]]>
Each record is unique per sponsor defined key variables as documented in the define.xml
3.2
3.3
ALL
No keys have been defined for dataset {data($name)} in the define.xml!
else
(: we now have the OIDs of the key variables, and that is exactly what we need when working with Dataset-XML :)
(: iterate over all the records in the dataset :)
for $record in $datasetdoc//odm:ItemGroupData
let $recordnum := $record/@data:ItemGroupDataSeq (: the record number :)
(: create a sequence (array) with the VALUES of the key variables :)
let $keyvalues := (
(: we need the value of the attribute, not the attribute itself, therefore we need the data() function :)
for $value in $record/odm:ItemData[functx:is-value-in-sequence(@ItemOID,$keyoids)]/@Value return data($value)
)
(: TESTING: for $item in $keyvalues return {$item} :)
(: iterate over the next records and do the same :)
for $record2 in $datasetdoc//odm:ItemGroupData[@data:ItemGroupDataSeq > $recordnum]
let $recordnum2 := $record2/@data:ItemGroupDataSeq (: the record number :)
(: create a sequence (array) with the VALUES of the key variables :)
let $keyvalues2 := (
(: we need the value of the attribute, not the attribute itself, therefore we need the data() function :)
for $value in $record2/odm:ItemData[functx:is-value-in-sequence(@ItemOID,$keyoids)]/@Value return data($value)
)
(: compare the two sequences (array), if the contents are equal, this is an error (duplicate records) :)
where functx:sequence-deep-equal($keyvalues,$keyvalues2)
return Records {data($recordnum)} and {data($recordnum2)} have the same values for the key variables as defined in the define.xml
]]>
Each coded Value must be in the associated codelist (if any) or have the value 'MULTIPLE'
3.2
3.3
ALL
0]//odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: iterate over the coded variables :)
for $codeditem in $record/odm:ItemData[functx:is-value-in-sequence(@ItemOID,$codedoids)]
(: get the OID and value :)
let $oid := $codeditem/@ItemOID
let $value := $codeditem/@Value
(: also get the variable name for reporting :)
let $varname := $definedoc//odm:ItemDef[@OID=$oid]/@Name
(: look up the allowed values for this coded item in the define.xml :)
let $codelistoid := $definedoc//odm:ItemDef[@OID=$oid]/odm:CodeListRef/@CodeListOID
(: P.S. exclude "ExternalCodeList" :)
let $allowedvalues := $definedoc//odm:CodeList[not(odm:ExternalCodeList)][@OID=$codelistoid]/odm:*/@CodedValue
(: the value must be one of the coded values OR 'MULTIPLE' :)
where count($allowedvalues) > 0 and not(functx:is-value-in-sequence($value,$allowedvalues)) and not($value='MULTIPLE')
return Value for variable {data($varname)}: value {data($value)} not found in associated codelist {data($codelistoid)} and is not 'MULTIPLE'
]]>
Each coded Value must be in the associated codelist (if any) or have the value 'MULTIPLE'
3.2
3.3
ALL
0]//odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: iterate over the coded variables :)
for $codeditem in $record/odm:ItemData[functx:is-value-in-sequence(@ItemOID,$codedoids)]
(: get the OID and value :)
let $oid := $codeditem/@ItemOID
let $value := $codeditem/@Value
(: also get the variable name for reporting :)
let $varname := $definedoc//odm:ItemDef[@OID=$oid]/@Name
(: look up the allowed values for this coded item in the define.xml :)
let $codelistoid := $definedoc//odm:ItemDef[@OID=$oid]/odm:CodeListRef/@CodeListOID
(: P.S. exclude "ExternalCodeList" :)
let $allowedvalues := $definedoc//odm:CodeList[not(odm:ExternalCodeList)][@OID=$codelistoid]/odm:*/@CodedValue
(: the value must be one of the coded values OR 'MULTIPLE' :)
where count($allowedvalues) > 0 and not(functx:is-value-in-sequence($value,$allowedvalues)) and not($value='MULTIPLE')
return Value for variable {data($varname)}: value {data($value)} not found in associated codelist {data($codelistoid)} and is not 'MULTIPLE'
]]>
when -TPTREF = null then -RFTDTC = null
3.2
3.3
ALL
Value for {data($rftdtcname)} is '{data($rftdtcvalue)}' although value for {data($tptrefname)} is null
]]>
--SCAT and --CAT must have different values
3.2
3.3
ALL
Value of {data($catname)} and {data($scatname)} are identical: '{data($catvalue)}'
]]>
Except for TSSEQ, --SEQ is a unique number per USUBJID per domain, or a unique number per POOLID per domain, including when the domain is split into multiple files
3.2
3.3
ALL
1
return The record with USUBJID = {data($usubjid)} and {data($seqname)} is not unique in the dataset {data($itemgroupname)}. The following records have the same combination of USUBJID and {data($seqname)}: records number {data($recnums)}
]]>
Except for TSSEQ, --SEQ is a unique number per USUBJID per domain, or a unique number per POOLID per domain, including when the domain is split into multiple files
3.2
3.3
ALL
1
return The record with USUBJID = {data($usubjid)} and {data($seqname)} is not unique in the dataset {data($itemgroupname)}. The following records have the same combination of USUBJID and {data($seqname)}: records number {data($recnums)}
]]>
USUBJID in DM.USUBJID - All Subjects (USUBJID) must be present in Demographics (DM) domain, except for AP-- datasets
3.2
3.3
ALL
SD0064
USUBJID {data($value)} in dataset {data($datasetname)} could not be found in DM dataset
]]>
USUBJID in DM.USUBJID - All Subjects (USUBJID) must be present in Demographics (DM) domain, except for AP-- datasets
3.2
3.3
ALL
SD0064
USUBJID {data($value)} in dataset {data($datasetname)} could not be found in DM dataset
]]>
Planned VISIT must be defined in TV.VISIT
3.2
3.3
ALL
SD1023
VISIT={data($visitvalue)}, VISITNUM={data($visitnumvalue)} in dataset {data($datasetname)} is not defined in the TV dataset
]]>
Planned VISIT must be defined in TV.VISIT
3.2
3.3
ALL
SD1023
VISIT={data($visitvalue)}, VISITNUM={data($visitnumvalue)} in dataset {data($datasetname)} is not defined in the TV dataset
]]>
When VISITNUM is in TV.VISITNUM, then VISITDY must be = TV.VISITDY
3.2
3.3
ALL
SD1018
VISITDY={data($visitdyvalue)} for VISITNUM={data($visitnumvalue)} in dataset {data($datasetname)} is not found in the TV dataset
]]>
When VISITNUM is in TV.VISITNUM, then VISITDY must be = TV.VISITDY
3.2
3.3
ALL
SD1018
VISITDY={data($visitdyvalue)} for VISITNUM={data($visitnumvalue)} in dataset {data($datasetname)} is not found in the TV dataset
]]>
When VISITNUM != null and is planned, then VISITNUM must be in TV.VISITNUM
3.2
3.3
ALL
SD1018
Planned VISITNUM={data($visitnumvalue)} in dataset {data($datasetname)} is not found in the TV dataset
]]>
When VISITNUM != null and is planned, then VISITNUM must be in TV.VISITNUM
3.2
3.3
ALL
SD1018
Planned VISITNUM={data($visitnumvalue)} in dataset {data($datasetname)} is not found in the TV dataset
]]>
when VISITNUM != null then VISITNUM must in SV.VISITNUM
3.2
3.3
ALL
SD0065
)
(: iterate over all the datasets for which there is subject data - except for SV itself :)
for $itemgroup in $definedoc//odm:ItemGroupDef[not(@Name='SV')]
let $datasetname := $itemgroup/@Name
(: get the location :)
let $datasetlocation := (
if($defineversion='2.1') then $itemgroup/def21:leaf/@xlink:href
else $itemgroup/def:leaf/@xlink:href
)
let $datasetdoc := (
if($datasetlocation) then doc(concat($base,$datasetlocation))
else ()
)
(: get the OID of USUBJID, VISITNUM, if present :)
let $usubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $itemgroup/odm:ItemRef/@ItemOID
return $a
)
let $visitnumoid := (
for $a in $definedoc//odm:ItemDef[@Name='VISITNUM']/@OID
where $a = $itemgroup/odm:ItemRef/@ItemOID
return $a
)
(: further improvement 2019-08-11: group all the records by USUBJID and VISITNUM,
so that all records in each group have the same values for USUBJID and VISITNUM.
We then only need to check the first record in the group.
If the USUBJID-VISITNUM pair is not found in SV,
generate an error for each record in that group :)
let $orderedrecords := (
for $record in $datasetdoc[$usubjidoid and $visitnumoid]//odm:ItemGroupData[odm:ItemData[@ItemOID=$usubjidoid] and odm:ItemData[@ItemOID=$visitnumoid]]
group by
$b := $record/odm:ItemData[@ItemOID=$usubjidoid]/@Value,
$c := $record/odm:ItemData[@ItemOID=$visitnumoid]/@Value
return element group {
$record
}
)
(: iterate over the groups - all records in each group have the same values for USUBJID and VISITNUM :)
for $group in $orderedrecords
let $usubjid := $group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$usubjidoid]/@Value
let $visitnum := $group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$visitnumoid]/@Value
(: there should be a record in our USUBJID-VISITNUM pairs structure :)
(: and look up in the SV USUBJID-VISITNUM structure :)
let $svrecordscount := count($visitnumpairs[@usubjid=$usubjid and @visitnum=$visitnum])
where $svrecordscount = 0 (: no USUBJID-VISITNUM pair was found in SV :)
(: iterate over all records in the group for which no USUBJID-VISITNUM pair was found in SV :)
for $record in $group/odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
return Combination of USUBJID='{data($usubjid)}' and VISITNUM='{data($visitnum)}' in dataset {data($datasetname)} is not found in the SV domain
]]>
when VISITNUM != null then VISITNUM must in SV.VISITNUM
3.2
3.3
ALL
SD0065
)
(: iterate over all the datasets for which there is subject data - except for SV itself :)
for $itemgroup in $definedoc//odm:ItemGroupDef[not(@Name='SV')][@Name=$datasetname]
let $datasetname := $itemgroup/@Name
(: get the location :)
let $datasetlocation := (
if($defineversion='2.1') then $itemgroup/def21:leaf/@xlink:href
else $itemgroup/def:leaf/@xlink:href
)
let $datasetdoc := doc(concat($base,$datasetlocation))
(: get the OID of USUBJID, VISITNUM, if present :)
let $usubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $itemgroup/odm:ItemRef/@ItemOID
return $a
)
let $visitnumoid := (
for $a in $definedoc//odm:ItemDef[@Name='VISITNUM']/@OID
where $a = $itemgroup/odm:ItemRef/@ItemOID
return $a
)
(: further improvement 2019-08-11: group all the records by USUBJID and VISITNUM,
so that all records in each group have the same values for USUBJID and VISITNUM.
We then only need to check the first record in the group.
If the USUBJID-VISITNUM pair is not found in SV,
generate an error for each record in that group :)
let $orderedrecords := (
for $record in $datasetdoc[$usubjidoid and $visitnumoid]//odm:ItemGroupData[odm:ItemData[@ItemOID=$usubjidoid] and odm:ItemData[@ItemOID=$visitnumoid]]
group by
$b := $record/odm:ItemData[@ItemOID=$usubjidoid]/@Value,
$c := $record/odm:ItemData[@ItemOID=$visitnumoid]/@Value
return element group {
$record
}
)
(: iterate over the groups - all records in each group have the same values for USUBJID and VISITNUM :)
for $group in $orderedrecords
let $usubjid := $group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$usubjidoid]/@Value
let $visitnum := $group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$visitnumoid]/@Value
(: there should be a record in our USUBJID-VISITNUM pairs structure :)
(: and look up in the SV USUBJID-VISITNUM structure :)
let $svrecordscount := count($visitnumpairs[@usubjid=$usubjid and @visitnum=$visitnum])
where $svrecordscount = 0 (: no USUBJID-VISITNUM pair was found in SV :)
(: iterate over all records in the group for which no USUBJID-VISITNUM pair was found in SV :)
for $record in $group/odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
return Combination of USUBJID='{data($usubjid)}' and VISITNUM='{data($visitnum)}' in dataset {data($datasetname)} is not found in the SV domain
]]>
VISIT and VISITNUM have a one-to-one relationship
3.2
3.3
ALL
SD0051
{data($visitnum1)} - {data($visit1)} - {data($visitnum2)} - {data($visit2)} :)
where $visitnum1 = $visitnum2 and $visit1 != $visit2
return Combination of VISIT='{data($visit2)}' and VISITNUM={data($visitnum2)} in dataset {data($name)} is inconsistent:
record {data($recnum1)}: VISITNUM={data($visitnum1)} - VISIT='{data($visit1)}',
record {data($recnum2)}: VISITNUM={data($visitnum2)} - VISIT='{data($visit2)}'
]]>
VISIT and VISITNUM (single domain or dataset) have a one-to-one relationship
3.2
3.3
ALL
SD0051
{data($visitnum1)} - {data($visit1)} - {data($visitnum2)} - {data($visit2)} :)
where $visitnum1 = $visitnum2 and $visit1 != $visit2
return Combination of VISIT='{data($visit2)}' and VISITNUM={data($visitnum2)} in dataset {data($name)} is inconsistent:
record {data($recnum1)}: VISITNUM={data($visitnum1)} - VISIT='{data($visit1)}',
record {data($recnum2)}: VISITNUM={data($visitnum2)} - VISIT='{data($visit2)}'
]]>
For Events domains (except for DS, DV, HO), --BDSYCD must be = --SOCCD
3.2
3.3
Value of {data($bdsycdname)} '{data($bdsycdvalue)}' and {data($soccdname)} '{data($soccdvalue)}' may not differ
]]>
For Events domains (except for DS, DV, HO), --BODSYS must be = --SOC
3.2
3.3
Value of {data($bodsysname)} '{data($bodsysvalue)}' and {data($socname)} '{data($socvalue)}' may not differ
]]>
AEOCCUR is not permitted because the AE domain contains only records for adverse events that actually occurred.
3.2
3.3
AE
SD1073
AEOCCUR is not allowed in AE because the AE domain contains only records for adverse events that actually occurred
]]>
When AESCAN = 'Y' or AESCONG = 'Y' or AESDISAB = 'Y' or AESDTH = 'Y' or AESHOSP = 'Y' or AESLIFE = 'Y' or AESOD = 'Y' or AESMIE = 'Y' then AESER = 'Y'
3.2
3.3
AE
SD1062
AESER is not 'Y' although one of the qualifiers AESCAN, AESCONG, AESDISAB, AESDTH, AESHOSP, AESLIFE or AESMIE has been set to 'Y' in dataset {data($datasetname)}
]]>
When AESCAN != 'Y' and AESCONG != 'Y' and AESDISAB != 'Y' and AESDTH != 'Y' and AESHOSP != 'Y' and AESLIFE != 'Y' and AESOD != 'Y' and AESMIE != 'Y', then AESER must be 'N'
3.2
3.3
AE
SD0009
No qualifiers AESCAN, AESCONG, AESDISAB, AESDTH, AESHOSP, AESLIFE or AESMIE set to 'Y', when AE is Serious in dataset {data($datasetname)}
]]>
When AESMIE='Y' then a record must present in SUPPAE where SUPPAE.QNAM=AESOSP
3.2
3.3
AE
SD1143
No SUPPAE record 'AESOSP' was found for the AE record with AESMIE='{data($aesmievalue)}'
]]>
AESTAT is not permitted because the AE domain contains only records for adverse events that actually occurred
3.2
3.3
AE
SD1073
AESTAT is not allowed in AE because the AE domain contains only records for adverse events that actually occurred
]]>
When --ENTPT != null then --ENRTPT != null
3.2
3.3
ALL
SD1291
Missing value for {data($enrtptname)}, when {data($entptname)} is populated (not null), value = {data($entptvalue)}
]]>
When --ENTPT != null then --ENRTPT != null
3.2
3.3
ALL
SD1291
Missing value for {data($enrtptname)}, when {data($entptname)} is populated (not null), value = {data($entptvalue)}
]]>
When --ENRTPT != null then --ENTPT != null
3.2
3.3
ALL
SD1101
Missing value for {data($entptname)}, when {data($enrtptname)} is populated (not null), value = {data($enrtptvalue)}
]]>
When --ENRTPT != null then --ENTPT != null
3.2
3.3
ALL
SD1101
Missing value for {data($entptname)}, when {data($enrtptname)} is populated (not null), value = {data($enrtptvalue)}
]]>
--DECOD and --PTCD have a one-to-one relationship
3.2
3.3
EVENTS
1] (: start from the second one :)
let $recnum2 := $record/@data:ItemGroupDataSeq
(: as we grouped, the value of PTCD is the same, but we need to check on -DECOD :)
let $decod2 := $record/odm:ItemData[@ItemOID=$decodoid]/@Value
(: return {data($ptcd)} - {data($decod)} - {data($decod2)} :)
(: the two -DECOD values must be identical, as they have the same value for -PTCD :)
(: we however need to exclude the case that the value of -PTCD is null or empty :)
where $ptcd and not(empty($ptcd)) and not(empty($decod1)) and not(empty($decod1)) and not($decod1 = $decod2)
return Inconsistent value for {data($decodname)} within {data($ptcdname)}: Record {data($recnum2)} has {data($decodname)}={data($decod2)} whereas recordnumber={data($recnum1)} has {data($decodname)}={data($decod1)} for the same value of {data($ptcdname)}={data($ptcd)}
]]>
--DECOD and --PTCD have a one-to-one relationship
3.2
3.3
EVENTS
1] (: start from the second one :)
let $recnum2 := $record/@data:ItemGroupDataSeq
(: as we grouped, the value of PTCD is the same, but we need to check on -DECOD :)
let $ptcd2 := $record/odm:ItemData[@ItemOID=$ptcdoid]/@Value
(: the two -PTCD values must be identical, as they have the same value for -DECOD :)
where not(empty($decod)) and not(empty($ptcd1)) and not(empty($ptcd2)) and not($ptcd1 = $ptcd2)
return Inconsistent value for {data($ptcdname)} within {data($decodname)}: Record {data($recnum2)} has {data($ptcdname)}={data($ptcd2)} whereas recordnumber={data($recnum1)} has {data($ptcdname)}={data($ptcd1)} for the same value of {data($decodname)}={data($decod)}
]]>
When --PTCD != null then --DECOD != null
3.2
3.3
EVENTS
SD1289
Missing value for {data($decodname)}, when {data($ptcdname)} is populated (not null), value = {data($ptcdvalue)} in dataset {data($name)}
]]>
When --PTCD != null then --DECOD != null
3.2
3.3
EVENTS
SD1289
Missing value for {data($decodname)}, when {data($ptcdname)} is populated (not null), value = {data($ptcdvalue)} in dataset {data($name)}
]]>
When --PRESP not present in dataset then --REASND not present in dataset (Interventions, Events, except for DS,DV,EX)
3.2
3.3
INTERVENTIONS
EVENTS
SD1293
{data($prespname)} is present although {data($reasndname)} is absent
]]>
when --PRESP not present in dataset then --STAT not present in dataset (Interventions,Events, except for DS,DV,EX)
3.2
3.3
INTERVENTIONS
EVENTS
{data($statname)} is present although {data($prespname)} is absent
]]>
When --ENTPT present in dataset present in dataset then --ENRTPT must be present in dataset
3.2
3.3
ALL
{data($enrtptname)} is not present although {data($entptname)} is present
]]>
When --ENRTPT present in dataset present in dataset then --ENTPT must be present in dataset
3.2
3.3
ALL
{data($entptname)} is not present although {data($enrtptname)} is present
]]>
When --STTPT != null then --STRTPT != null
3.2
3.3
ALL
No value for {data($strtptname)} was found although there is a value for {data($sttptname)}={data($sttptvalue)}
]]>
When --STTPT != null then --STRTPT != null
3.2
3.3
ALL
No value for {data($strtptname)} was found although there is a value for {data($sttptname)}={data($sttptvalue)}
]]>
When --STTPT present in dataset then --STRTPT present in dataset
3.2
3.3
ALL
{data($strtptname)} is not present although {data($sttptname)} is present
]]>
When --STRTPT != null then --STTPT != null
3.2
3.3
ALL
No value for {data($sttptname)} was found although there is a value for {data($strtptname)}={data($strtptvalue)}
]]>
When --STRTPT != null then --STTPT != null
3.2
3.3
ALL
No value for {data($sttptname)} was found although there is a value for {data($strtptname)}={data($strtptvalue)}
]]>
When --STRTPT present in dataset then --STTPT present in dataset
3.2
3.3
ALL
{data($sttptname)} is not present although {data($strtptname)} is present
]]>
When there are multiple events per subject where DSCAT = 'DISPOSITION EVENT', then EPOCH != null
3.2
DS
1]/odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: each such a record must have an EPOCH variable :)
where not($record/odm:ItemData[@ItemOID=$dsepochoid]/@Value)
return EPOCH is missing for multiple DSCAT='DISPOSITION EVENT' occurrence for subject {data($usubjid)}
]]>
When DSTERM='COMPLETED' then DSDECOD='COMPLETED'
3.2
3.3
DS
The value of DSDECOD is not 'COMPLETED' although the value of DSTERM='COMPLETED'. Value found for DSDECOD='{data($dsdecodvalue)}'
]]>
When DSCAT = 'PROTOCOL MILESTONE' then DSTERM = DSDECOD
3.2
DS
The value of DSDECOD is not identical to the value of DSTERM although the value of DSCAT='PROTOCOL MILESTONE'. Value found for DSDECOD='{data($dsdecodvalue)}', value found for DSTERM='{data($dstermvalue)}'
]]>
When SS.SSSTRESC = 'DEAD', then there must be a record for that subject with DSDECOD='DEATH'
3.2
3.3
DS
A record (record number={data($recnumss)}) for USUBJID='{data($ssusubjid)}' has been found for which SSSTRESC='DEAD' but no record with DSDECOD='DEATH' was found in dataset DS
]]>
When Multiple informed consents obtained and DSDECOD = 'INFORMED CONSENT OBTAINED' then earliest DSSTDTC = DM.RFICDTC
3.2
3.3
DS
For subject USUBJID='{data($usubjidvalue)}', the earliest informed consent date in DS (DSDECOD='INFORMED CONSENT OBTAINED') does not correspond to the informed consent date RFICDTC={data($dminformedconsentdate)} in DM. The earliest informed consent date found in DS is '{data($earliestinformedconsentdateasnumber)}'
]]>
When DSDECOD = 'DEATH' then DSSTDTC = DM.DTHDTC
3.2
3.3
DS
A record (record number={data($recnum)}) for USUBJID='{data($usubjid)}' has been found for which DSDECOD='DEATH' but the value of DSSTDTC={data($dsstdtc)} does not correspond to DTHDTC={data($dthdtc)} in DM
]]>
When DSCAT = 'PROTOCOL MILESTONE' then EPOCH = null
3.2
DS
DSCAT='PROTOCOL MILESTONE' but EPOCH is not null. DSEPOCH='{data($epoch)}' was found
]]>
DVSTDTC >= DM.RFICDTC
3.2
3.3
DV
= DM.RFICDTC :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: Get the DV dataset :)
let $dvitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='DV']
(: and the location of the DS dataset :)
let $dvdatasetlocation := (
if($defineversion='2.1') then $dvitemgroupdef/def21:leaf/@xlink:href
else $dvitemgroupdef/def:leaf/@xlink:href
)
let $dvdatasetdoc := (
if($dvdatasetlocation) then doc(concat($base,$dvdatasetlocation))
else ()
)
(: get DVSTDTC in DS :)
let $dvstdtcoid := (
for $a in $definedoc//odm:ItemDef[@Name='DVSTDTC']/@OID
where $a = $dvitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: and the OID of USUBJID in DV :)
let $dvusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $dvitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: get the DM dataset :)
let $dmitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='DM']
let $dmdatasetlocation := $dmitemgroupdef/xlink:href/@xlink:href
let $dmdatasetdoc := doc($dmdatasetlocation)
(: and the OID of USUBJID and RFICDTC :)
let $dmusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $dmitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $dmrficdtcoid := (
for $a in $definedoc//odm:ItemDef[@Name='RFICDTC']/@OID
where $a = $dmitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: Iterate over all DVSTDTC records in DV :)
for $record in $dvdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dvstdtcoid]]
let $recnum := $record/@data:ItemGroupDataSeq
(: get the USUBJID :)
let $usubjid := $record/odm:ItemData[@ItemOID=$dvusubjidoid]/@Value
(: and get the value of DVSTDTC :)
let $dvstdtc := $record/odm:ItemData[@ItemOID=$dvstdtcoid]/@Value
(: find the corresponding record in DM :)
let $dmrecord := $dmdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjid]]
(: get the value of RFICDTC :)
let $rficdtc := $dmrecord/odm:ItemData[@ItemOID=$dmrficdtcoid]/@Value
(: we can only check the rule when DVSTDTC and RICDTC are valid dates. When so: DVSTDTC >= DM.RFICDTC :)
where $dvstdtc castable as xs:date and $dvstdtc castable as xs:date and not(xs:date($dvstdtc) >= xs:date($rficdtc))
return Deviation date DVSTDTC={data($rficdtc)} is after informed consent data RFICDTC={data($dvstdtc)}
]]>
When MHCAT != null then MHCAT != the same value for all records
3.2
3.3
MH
Only {data($count)} distinct value '{data($mhcatdistinctvalues)}' values for MHCAT was found in the {data($name)} dataset
]]>
When MHENDTC != null then MHENDTC < DM.RFSTDTC
3.2
3.3
MH
RFSTDTC :)
where $rfstdtc castable as xs:date and $mhendtccomplete castable as xs:date and not(xs:date($mhendtccomplete) <= xs:date($rfstdtc))
return MHENDTC={data($mhendtc)} is after or on RFSTDTC={data($rfstdtc)}
]]>
When MHSTDTC != null then MHSTDTC < DM.RFSTDTC
3.2
3.3
MH
RFSTDTC :)
where $rfstdtc castable as xs:date and $mhstdtccomplete castable as xs:date and not(xs:date($mhstdtccomplete) <= xs:date($rfstdtc))
return MHSTDTC={data($mhstdtc)} is after or on RFSTDTC={data($rfstdtc)}
]]>
When --PRESP = 'Y' and --OCCUR = null then --STAT = 'NOT DONE'
3.2
3.3
EVENTS
INTERVENTIONS
Invalid value for {data($statname)}='{data($stat)}' for {data($prespname)}='Y' and {data($occurname)}=null. Value for {data($statname)} expected is 'NOT DONE'
]]>
When --PRESP = 'Y' and --OCCUR = null then --STAT = 'NOT DONE'
3.2
3.3
EVENTS
INTERVENTIONS
Invalid value for {data($statname)}='{data($stat)}' for {data($prespname)}='Y' and {data($occurname)}=null. Value for {data($statname)} expected is 'NOT DONE'
]]>
When --BDSYCD != null then --BODSYS != null
3.2
3.3
EVENTS
INTERVENTIONS
Null value found for {data($bodsysname)} where {data($bdsycdname)}='{data($bdsycd)}'
]]>
When --BDSYCD != null then --BODSYS != null
3.2
3.3
EVENTS
INTERVENTIONS
Null value found for {data($bodsysname)} where {data($bdsycdname)}='{data($bdsycd)}'
]]>
--BDSYCD and --BODSYS have a one-to-one relationship
3.2
3.3
EVENTS
INTERVENTIONS
{data($bodsys)} - {data($bdsycd)} :)
(: iterate over all other records in the same group, they have the same value for --BODSYS,
and must also have the same value for --BDSYCD :)
for $record in $group/odm:ItemGroupData[position()>1]
let $recnum2 := $record/@data:ItemGroupDataSeq
(: get the value of --BDSYCD :)
let $bdsycd2 := $record/odm:ItemData[@ItemOID=$bdsycdoid]/@Value
(: the two --BDSYCD values must be equal :)
where $bdsycd1 and $bdsycd2 and not($bdsycd1 = $bdsycd2)
return Combination of {data($bdsycdname)} and {data($bodsysname)} in dataset {data($datasetname)} is inconsistent:
record {data($recnum1)}: {data($bdsycdname)}='{data($bdsycd1)}' - {data($bodsysname)}='{data($bodsys)}',
record {data($recnum2)}: {data($bdsycdname)}='{data($bdsycd2)}' - {data($bodsysname)}='{data($bodsys)}', ,
]]>
When --TOXGR not present in dataset then --TOX not present in dataset
3.2
3.3
LB
AE
{data($toxname)} is present, although {data($toxgrname)} is absent in dataset {data($name)}
]]>
--PRESP in ('Y', null)
3.2
3.3
EVENTS
INTERVENTIONS
{data($prespname)} is only allowed to be 'Y' or null, {data($prespname)}='{data($presp)}' was found
]]>
--PRESP in ('Y', null)
3.2
3.3
EVENTS
INTERVENTIONS
{data($prespname)} is only allowed to be 'Y' or null, {data($prespname)}='{data($presp)}' was found
]]>
When --PRESP = 'Y' and --STAT = null and --OCCUR is present in dataset then --OCCUR != null
3.2
3.3
EVENTS
INTERVENTIONS
{data($occurname)}=null for record with {data($prespname)}='Y' and {data($statname)}=null
]]>
When --PRESP = 'Y' and --STAT = null and --OCCUR is present in dataset then --OCCUR != null
3.2
3.3
EVENTS
INTERVENTIONS
{data($occurname)}=null for record with {data($prespname)}='Y' and {data($statname)}=null
]]>
When --PRESP != 'Y' and --OCCUR is present in dataset or --STAT='NOT DONE' then --OCCUR = null
3.2
3.3
EVENTS
INTERVENTIONS
{data($occurname)}='{data($occur)}' for record with {data($prespname)}='{data($presp)}' and {data($statname)}='NOT DONE'. {data($occurname)} should be null
]]>
When --PRESP != 'Y' and --OCCUR is present in dataset or --STAT='NOT DONE' then --OCCUR = null
3.2
3.3
EVENTS
INTERVENTIONS
{data($occurname)}='{data($occur)}' for record with {data($prespname)}='{data($presp)}' and {data($statname)}='NOT DONE'. {data($occurname)} should be null
]]>
When --PRESP not present in dataset then --OCCUR not present in dataset
3.2
3.3
EVENTS
INTERVENTIONS
Variable {data($occurname)} is present in dataset {data($name)} although {data($prespname)} is not present in the dataset
]]>
When --OCCUR != null then --PRESP = 'Y'
3.2
3.3
EVENTS
INTERVENTIONS
{data($prespname)}='{data($presp)}' found for {data($occurname)}='{data($occur)}'. Value expected for {data($prespname)}='Y'
]]>
When --OCCUR != null then --PRESP = 'Y'
3.2
3.3
EVENTS
INTERVENTIONS
{data($prespname)}='{data($presp)}' found for {data($occurname)}='{data($occur)}'. Value expected for {data($prespname)}='Y'
]]>
When --RFTDTC present in dataset then --TPTREF present in dataset
3.2
3.3
ALL
Variable {data($rftdtcname)} is present in dataset {data($name)} but {data($tptrefname)} is not present in the dataset
]]>
When --ELTM present in dataset then --TPTREF present in dataset
3.2
3.3
ALL
Variable {data($eltmname)} is present in dataset {data($name)} but {data($tptrefname)} is not present in the dataset
]]>
When --ELTM, --TPTNUM, and --TPT not present in dataset, then --TPTREF not present in dataset
3.2
3.3
ALL
Variable {data($tptrefname)} is present in dataset {data($name)} although {data($eltmname)}, {data($tptnumname)} and {data($tptname)} are not present in the dataset
]]>
When --REASND != null then --STAT = 'NOT DONE'
3.2
3.3
ALL
Missing or invalid value for {data($statname)} when {data($reasndname)} is provided - {data($reasndname)}={data($reasndvalue)} - {data($statname)}={data($statvalue)} in dataset {data($datasetname)}
]]>
When --REASND != null (single dataset or domain) then --STAT = 'NOT DONE'
3.2
3.3
ALL
Missing or invalid value for {data($statname)} when {data($reasndname)} is provided - {data($reasndname)}={data($reasndvalue)} - {data($statname)}={data($statvalue)} in dataset {data($datasetname)}
]]>
When --LOC not present in dataset then --LAT not present in dataset
3.2
3.3
Variable {data($locname)} is absent in dataset {data($name)} but {data($latname)} is present in the dataset
]]>
When ECOCCUR != 'N' and ECSTAT is null and ECDOSTXT is null, then ECDOSE > 0
3.2
3.3
0 :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := 'LZZT_SDTM_Dataset-XML/' :)
(: let $define := 'define_2_0.xml' :)
let $definedoc := doc(concat($base,$define))
(: get the EC dataset(s) definition :)
for $itemgroupdef in $definedoc//odm:ItemGroupDef[@Domain='EC' or starts-with(@Name,'EC')]
let $name := $itemgroupdef/@Name
(: get the dataset itself :)
let $datasetlocation := (
if($defineversion='2.1') then $itemgroupdef/def21:leaf/@xlink:href
else $itemgroupdef/def:leaf/@xlink:href
)
let $datasetdoc := doc(concat($base,$datasetlocation))
(: get the OID of ECOCCUR, ECSTAT, ECDOSTXT and ECDOSE :)
let $ecoccuroid := (
for $a in $definedoc//odm:ItemDef[@Name='ECOCCUR']/@OID
where $a = $itemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ecdostxtoid := (
for $a in $definedoc//odm:ItemDef[@Name='ECDOSTXT']/@OID
where $a = $itemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ecstatoid := (
for $a in $definedoc//odm:ItemDef[@Name='ECSTAT']/@OID
where $a = $itemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ecdoseoid := (
for $a in $definedoc//odm:ItemDef[@Name='ECDOSE']/@OID
where $a = $itemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: get all the records in the dataset for which ECOCCUR != 'N' and ECSTAT is null and ECDOSTXT is null :)
for $record in $datasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$ecoccuroid and @Value!='N'] and not(odm:ItemData[@ItemOID=$ecstatoid]) and not(odm:ItemData[@ItemOID=$ecdostxtoid])]
let $recnum := $record/@data:ItemGroupDataSeq
(: get the value of ECDOSE (as text) :)
let $dose := $record/odm:ItemData[@ItemOID=$ecdoseoid]/@Value
(: ECDOSE > 0 :)
where $dose castable as xs:double and xs:double($dose)<0
return
ECDOSE is not a number or is a non-positive number where ECOCCUR != 'N' and ECDOSTXT is null. The value found for ECDOSE is '{data($dose)}'
]]>
When ECOCCUR = 'N' then ECDOSE = null or >0
3.2
3.3
EC
0 :)
where number($dose)=0 or number($dose)<0 (: dose must either be null or >0 :)
return
ECDOSE={data($dose)} is expected to be null as ECOCCUR='N'
]]>
When EXTRT = 'PLACEBO' then EXDOSE = 0
3.2
3.3
EX
{data($extrtoid)} :)
(: get all the records in the dataset for which EXTRT='PLACEBO' :)
for $record in $datasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$extrtoid and @Value='PLACEBO']]
let $recnum := $record/@data:ItemGroupDataSeq
(: get the value of EXDOSE (as text) :)
let $exdose := $record/odm:ItemData[@ItemOID=$exdoseoid]/@Value
(: When EXTRT = 'PLACEBO' then EXDOSE = 0 :)
where $exdose castable as xs:double and xs:double($exdose)!= 0
return
EXDOSE={data($exdose)} is expected to be 0 as EXTRT='PLACEBO'
]]>
When EC domain is present then EXVAMT not present in dataset
3.2
3.3
EX
EXVAMT is present in dataset although the EC domain/dataset is present
]]>
When --TRTV = null then --VAMT = null
3.2
3.3
INTERVENTIONS
{data($name)}={data($vamt)} but was expected to be null as {data($trtvname)} is null
]]>
When --TRTV = null then --VAMT = null
3.2
3.3
INTERVENTIONS
{data($name)}={data($vamt)} but was expected to be null as {data($trtvname)} is null
]]>
When EC domain is present then EXVAMTU not present in EX dataset
3.2
3.3
EX
EXVAMTU is present in dataset although the EC domain/dataset is present
]]>
When EXTRTV = null then EXVAMTU = null
3.2
3.3
EX
EXVAMTU={data($exvamtu)} but was expected to be null as EXTRTV is null
]]>
When no records in EX for Subject then DM.ACTARMCD in ('SCRNFAIL', 'NOTASSGN', 'NOTTRT')
3.2
EX
DM
No records were found for USUBJID={data($usubjid)} in EX but ACTARMCD is not one of 'SCRNFAIL', 'NOTASSGN', 'NOTTRT'. The value found for ARCTARMCD='{data($actarmcd)}'
]]>
When --DOSTXT != null then --DOSE = null
3.2
3.3
INTERVENTIONS
{data($dosename)} is populated although {data($dostxtname)} is populated
]]>
When --DOSTXT != null then --DOSE = null
3.2
3.3
INTERVENTIONS
{data($dosename)} is populated although {data($dostxtname)} is populated
]]>
When --DOSE != null then --DOSTXT = null
3.2
3.3
INTERVENTIONS
{data($dostxtname)} is populated although{data($dosename)} is populated
]]>
When --DOSE != null then --DOSTXT = null
3.2
3.3
INTERVENTIONS
{data($dostxtname)} is populated although{data($dosename)} is populated
]]>
--DOSTXT value is non-numeric
3.2
3.3
INTERVENTIONS
{data($dostxtname)} may not be a numeric value. {data($dostxtname)} = '{data($dostxtvalue)}' was found
]]>
--DOSTXT value is non-numeric
3.2
3.3
INTERVENTIONS
{data($dostxtname)} may not be a numeric value. {data($dostxtname)} = '{data($dostxtvalue)}' was found
]]>
When --DOSE != null or --DOSTOT != null or --DOSTXT != null then --DOSU != null
INTERVENTIONS
3.2
3.3
{data($dosuname)} is not populated although one of {data($dosename)}, {data($dostxtname)} or {data($dostotname)} is populated
]]>
When --DOSE != null or --DOSTOT != null or --DOSTXT != null then --DOSU != null
3.2
3.3
INTERVENTIONS
{data($dosuname)} is not populated although one of {data($dosename)}, {data($dostxtname)} or {data($dostotname)} is populated
]]>
When --LOC not present in dataset then --PORTOT not present in dataset
3.2
3.3
INTERVENTIONS
FINDINGS
{data($portotname)} is present although {data($locname)} is present in dataset {data($name)}
]]>
When --LOC not present in dataset then --DIR not present in dataset
3.2
3.3
INTERVENTIONS
FINDINGS
{data($dirname)} is present although {data($locname)} is present in dataset {data($name)}
]]>
When ACTARM not in TA.ARM, then ACTARM in ('Screen Failure', 'Not Assigned', 'Not Treated', 'Unplanned Treatment')
3.2
DM
Invalid value for ACTARM, value={data($actarmvalue)} in dataset {data($datasetlocation)}
]]>
When ACTARM not in ('Screen Failure', 'Not Assigned', 'Not Treated', 'Unplanned Treatment') then ACTARM in TA.ARM
3.2
DM
Invalid value for ACTARM, value={data($actarmvalue)} is not one of ('Screen Failure', 'Not Assigned', 'Not Treated', 'Unplanned Treatment') and is not found in TA
]]>
When ACTARM in ('Screen Failure', 'Not Assigned') then ACTARM = ARM
3.2
DM
Invalid value for ARM, value='{data($armvalue)}', expected value='{data($actarmvalue)}'
]]>
When ARM not in TA.ARM then ARM in ('Screen Failure', 'Not Assigned')
3.2
DM
Invalid value for ARM, value='{data($armvalue)}' is not in TA and is not one of ('Screen Failure', 'Not Assigned')
]]>
When ARM not in ('Screen Failure', 'Not Assigned') then ARM in TA.ARM
3.2
DM
Invalid value for ARM, value='{data($armvalue)}' is not one of ('Screen Failure', 'Not Assigned') and is not found in TA
]]>
When ARM in ('Screen Failure', 'Not Assigned') then ARM = ACTARM
3.2
DM
Invalid value for ARM, value='{data($armvalue)}' is expected to be equal to the value of ACTARM='{data($actarmvalue)}'
]]>
ACTARMCD value length <= 20
3.2
3.3
DM
20
return Invalid value for ACTARMCD in dataset {data($datasetname)}, it's length is more than 20 characters, value='{data($actarmcdvalue)}'
]]>
When ACTARMCD not in TA.ARMCD then ACTARMCD in ('SCRNFAIL', 'NOTASSGN', 'NOTTRT', 'UNPLAN')
3.2
DM
Invalid value for ACTARMCD, value={data($actarmcdvalue)} in dataset DM is not found in TA.ARMCD and is not one of ('SCRNFAIL', 'NOTASSGN', 'NOTTRT', 'UNPLAN')
]]>
When ACTARMCD not in ('SCRNFAIL', 'NOTASSGN', 'NOTTRT', 'UNPLAN') then ACTARMCD in TA.ARMCD
3.2
DM
Invalid value for ACTARMCD, value={data($actarmcdvalue)} in dataset DM is not one of ('SCRNFAIL', 'NOTASSGN', 'NOTTRT', 'UNPLAN') and is not found in TA.ARMCD
]]>
When ARMCD not in ('SCRNFAIL', 'NOTASSGN') then ARMCD in TA.ARMCD
3.2
DM
Invalid value for ARMCD, value={data($armcdvalue)} in dataset DM is not one of ('SCRNFAIL', 'NOTASSGN') and is not found in TA.ARMCD
]]>
When ACTARMCD in ('SCRNFAIL', 'NOTASSGN') then ACTARMCD = ARMCD
3.2
DM
Invalid value for ACTARMCD, value='{data($actarmcdvalue)}' is expected to be equal to the value of ARMCD='{data($armcdvalue)}'
]]>
When ARMCD not in TA.ARMCD then ARMCD in ('SCRNFAIL', 'NOTASSGN')
3.2
DM
Invalid value for ARMCD, value='{data($armcdvalue)}' is not found in TA and is not one of('SCRNFAIL', 'NOTASSGN')
]]>
When ARMCD in ('SCRNFAIL', 'NOTASSGN') then ARMCD = ACTARMCD
3.2
DM
Invalid value for ARMCD, value={data($armcdvalue)} is expected to be equal to ACTARMCD={data($actarmcdvalue)}
]]>
DTHFL in ('Y',null)
3.2
3.3
DM
Invalid value for DTHFL, value='{data($dthflvalue)}' is expected to be 'Y' or null
]]>
When SS.SSSTRESC = 'DEAD' then DTHFL = 'Y'
3.2
3.3
SS
DM
A record (record number={data($recnumss)}) for USUBJID='{data($ssusubjid)}' has been found for which SSSTRESC='DEAD' but the value of DTHFL='{data($dthfl)}' in DM is not 'Y'
]]>
When DD record present for subject then DTHFL = 'Y'
3.2
3.3
DD
DM
A record for USUBJID='{data($subject)}' has been found in the DD dataset but the value of DTHFL='{data($dthfl)}' in DM is not 'Y'
]]>
When AE.AEOUT = 'FATAL' then DTHFL = 'Y'
3.2
3.3
AE
DM
A record for USUBJID='{data($usubjid)}' (record number {data($recnum)}) has been found in the AE dataset with AEOUT='FATAL' but the value of DTHFL='{data($dthfl)}' in DM is not 'Y'
]]>
When AE.AEOUT = 'FATAL' then DTHFL = 'Y'
3.2
3.3
AE
DM
A record for USUBJID='{data($usubjid)}' (record number {data($recnum)}) has been found in the AE dataset with AESDTH='Y' but the value of DTHFL='{data($dthfl)}' in DM is not 'Y'
]]>
When DS.DSDECOD = 'DEATH' then DTHFL = 'Y'
3.2
3.3
DS
DM
A record for USUBJID='{data($usubjid)}' (record number {data($recnum)}) has been found in the DS dataset with DSDECOD='DEATH' but the value of DTHFL='{data($dthfl)}' in DM is not 'Y'
]]>
When multiple records in SUPPDM where RACE captured then RACE = 'MULTIPLE'
3.2
DM
SUPPDM
{$suppdmcountrace} :)
(: if there is more than one such record, the value of RACE in DM must be 'MULTIPLE' :)
where $suppdmcountrace > 1 and not($race='MULTIPLE')
return {data($suppdmcountrace)} 'RACE' records for USUBJID='{data($usubjid)}' have been found in SUPPDM, but the value of RACE='{data($race)}' in DM is not 'MULTIPLE'
]]>
When milestone associated with RFENDTC is end of treatment and ACTARM in ('Screen Failure' 'Not Assigned' 'Not Treated') then RFENDTC = null
3.2
DM
RFENDTC='{data($rfendtcvalue)}' expected to be null for ACTARM='{data($actarmvalue)}'
]]>
When ACTARM not in ('Screen Failure', 'Not Assigned') then RFENDTC != null
3.2
DM
RFENDTC=null for ACTARM='{data($actarmvalue)}'. RFENDTC is required for all randomized subjects.
]]>
When DS.DSTERM indicates informed consent obtained then RFICDTC = earliest DS.DSSTDTC
3.2
3.3
DS
DM
RFICDTC='{data($rficdtc)}' does not correspond to the earliest DSSDTC='{data($earliestdsstdtc)}' value
]]>
When ACTARM in ('Screen Failure' 'Not Assigned' 'Not Treated') then RFSTDTC = null
3.2
DM
RFSTDTC='{data($rfstdtcvalue)}' expected to be null for ACTARM='{data($actarmvalue)}'
]]>
When ACTARM not in ('Screen Failure' 'Not Assigned' 'Not Treated')
3.2
DM
RFSTDTC expected to be not null for ACTARM='{data($actarmvalue)}'
]]>
When EX records present for subject then RFXENDTC = latest value of EX.EXSTDTC or EX.EXENDTC
3.2
3.3
EX
DM
RFXENDTC='{data($rfxendtc)}' does not correspond to the latest exposure data EXSTDTC/EXENDTC='{data($latestexdtc)}' value
]]>
When EX dataset present in study then RFXSTDTC = earliest EX.EXSTDTC for that subject
3.2
3.3
EX
DM
RFXSTDTC='{data($rfxstdtc)}' does not correspond to the earliest exposure EXSTDTC='{data($earliestexstdtc)}' value
]]>
SETCD value length <= 8
3.2
3.3
DM
SE
8
return SETCD='{data($setcdvalue)}' has more than 8 characters
]]>
SUBJID unique within a study
3.2
3.3
DM
$recnum][odm:ItemData[@ItemOID=$subjidoid][@Value=$subjidvalue]])
where $count > 0
return Duplicate value of SUBJID={data($subjidvalue)} - {data($count)} in dataset {data($datasetname)}
]]>
When ETCD = 'UNPLAN' then ELEMENT = null
3.2
3.3
SE
Invalid value for ELEMENT={data($element)}, null was expected for ETCD='UNPLAN'
]]>
ARMCD value length <= 20
3.2
3.3
DM
TA
20
return Invalid value for ARMCD={data($armcdvalue)} in dataset {data($datasetname)} - it has more than 20 characters
]]>
ELEMENT present in dataset and ETCD present in dataset then ELEMENT and ETCD have a one-to-one relationship
3.2
3.3
SE
TA
TV
$recnum][odm:ItemData[@ItemOID=$etcdoid] and odm:ItemData[@ItemOID=$elementoid] and odm:ItemData[@ItemOID=$etcdoid]/@Value=$etcdvalue]
let $recnum2 := $record2/@data:ItemGroupDataSeq
(: and again, get the values of ETCD and ELEMENT :)
let $etcdvalue2 := $record2/odm:ItemData[@ItemOID=$etcdoid]/@Value (: should be identical to $etcdvalue :)
let $elementvalue2 := $record2/odm:ItemData[@ItemOID=$elementoid]/@Value
(: ELEMENT must be equal to ELEMENT of the the earlier record :)
where not($elementvalue=$elementvalue2)
return Non-unique combination for ELEMENT and ETCD. Record {data($recnum)} has ETCD='{data($etcdvalue)}' and and ELEMENT='{data($elementvalue)}' whereas record {data($recnum2)} has ETCD='{data($etcdvalue2)}' and has ELEMENT='{data($elementvalue2)}' in dataset {data($name)}
]]>
AP 'DOMAIN' Value length = 4 characters beginning with 'AP' and ending with 2 character SDTM domain
3.2
3.3
Length of DOMAIN value '{data($domainvalue)}' is not 4
else if(not(starts-with($domainvalue,'AP'))) then
DOMAIN value '{data($domainvalue)}' does not start with 'AP'
else ()
)
return $message
]]>
RSUBJID in APxx must be either in DM.USUBJID or in POOLDEF.POOLID
3.2
3.3
Value of RSUBJID '' cannot be found in DM.USUBJID nor in POOLDEF.POOLID
]]>
When Comment related to Domain Records and RDOMAIN not in (null, 'DM'), then IDVAR ^= null
3.2
3.3
CO
IDVAR must be null, value '{data($idvar)}' was found, as value for RDOMAIN is null or 'DM'
]]>
When RDOMAIN = null then IDVAR = null
3.2
3.3
CO
IDVAR={data($idvar)} is populated although RDOMAIN=null
]]>
When IDVARVAL != null then RDOMAIN != null
3.2
3.3
CO
IDVARVAL={data($idvarval)} but RDOMAIN=null
]]>
When IDVAR != null then CODTC = null
3.2
3.3
CO
IDVAR={data($idvar)} but a value for CODTC={data($codtc)} was found
]]>
When SSSTRESC = 'DEAD' then SSDTC >= DM.DTHDTC
3.2
3.3
SS
= DM.DTHDTC :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: Get the DM dataset :)
let $dmitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='DM']
(: and the location of the DM dataset :)
let $dmdatasetlocation := (
if($defineversion='2.1') then $dmitemgroupdef/def21:leaf/@xlink:href
else $dmitemgroupdef/def:leaf/@xlink:href
)
let $dmdatasetdoc := doc(concat($base,$dmdatasetlocation))
(: Get the SS (Subject Status) dataset and its location :)
let $ssitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='SS']
let $ssdatasetlocation := (
if($defineversion='2.1') then $ssitemgroupdef/def21:leaf/@xlink:href
else $ssitemgroupdef/def:leaf/@xlink:href
)
let $ssdatasetdoc := (
if($ssdatasetlocation) then doc(concat($base,$ssdatasetlocation))
else ()
)
(: we need the OID of DTHDTC and of USUBJID in DM :)
let $dmdthdtcoid := (
for $a in $definedoc//odm:ItemDef[@Name='DTHDTC']/@OID
where $a = $dmitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $dmusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $dmitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: we need the OID of SSSTRESC, SSDTC and of USUBJID in SS :)
let $ssusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $ssitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ssstresc := (
for $a in $definedoc//odm:ItemDef[@Name='SSSTRESC']/@OID
where $a = $ssitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ssdtcoid := (
for $a in $definedoc//odm:ItemDef[@Name='SSDTC']/@OID
where $a = $ssitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: iterate over all records in SS for which SSSTRESC='DEAD' :)
for $record in $ssdatasetdoc[$ssdatasetlocation]//odm:ItemGroupData[odm:ItemData[@ItemOID=$ssstresc and @Value='DEAD']]
let $recnumss := $record/@data:ItemGroupDataSeq
(: get the value of USUBJID for this record :)
let $ssusubjid := $record/odm:ItemData[@ItemOID=$ssusubjidoid]/@Value
(: get the value of SSDTC for this record :)
let $ssdtc := $record/odm:ItemData[@ItemOID=$ssdtcoid]/@Value
(: get the DM record for this subject :)
let $dmrecord := $dmdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$ssusubjid]]
let $dmrecnum := $dmrecord/@data:ItemGroupDataSeq
(: get the value of DTHDTC :)
let $dthdtc := $dmrecord/odm:ItemData[@ItemOID=$dmdthdtcoid]/@Value
(: SSDTC >= DM.DTHDTC :)
where $ssdtc castable as xs:date and $dthdtc castable as xs:date and not(xs:date($ssdtc) >= xs:date($dthdtc))
return SSDTC={data($ssdtc)} is not before or equal to DM.DTHDTC={data($dthdtc)} for SSSTRESC='DEAD'
]]>
When SSSTRESC = 'DEAD' then SSDTC >= DS.DSSTDTC
3.2
3.3
SS
= DS.DSSTDTC :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace functx = "http://www.functx.com";
(: sorting function - also sorts dates :)
declare function functx:sort
( $seq as item()* ) as item()* {
for $item in $seq
order by $item
return $item
} ;
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: Get the DS dataset :)
let $dsitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='DS']
(: and the location of the DS dataset :)
let $dsdatasetlocation := (
if($defineversion='2.1') then $dsitemgroupdef/def21:leaf/@xlink:href
else $dsitemgroupdef/def:leaf/@xlink:href
)
let $dsdatasetdoc := doc(concat($base,$dsdatasetlocation))
(: Get the SS (Subject Status) dataset and its location :)
let $ssitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='SS']
let $ssdatasetlocation := (
if($defineversion='2.1') then $ssitemgroupdef/def21:leaf/@xlink:href
else $ssitemgroupdef/def:leaf/@xlink:href
)
let $ssdatasetdoc := (
if($ssdatasetlocation) then doc(concat($base,$ssdatasetlocation))
else ()
)
(: we need the OID of DSSTDTC and of USUBJID in DS :)
let $dsstdtcoid := (
for $a in $definedoc//odm:ItemDef[@Name='DSSTDTC']/@OID
where $a = $dsitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $dsusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $dsitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: we need the OID of SSSTRESC, SSDTC and of USUBJID in SS :)
let $ssusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $ssitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ssstresc := (
for $a in $definedoc//odm:ItemDef[@Name='SSSTRESC']/@OID
where $a = $ssitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $ssdtcoid := (
for $a in $definedoc//odm:ItemDef[@Name='SSDTC']/@OID
where $a = $ssitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: iterate over all records in SS for which SSSTRESC='DEAD' :)
for $record in $ssdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$ssstresc and @Value='DEAD']]
let $recnumss := $record/@data:ItemGroupDataSeq
(: get the value of USUBJID for this record :)
let $ssusubjid := $record/odm:ItemData[@ItemOID=$ssusubjidoid]/@Value
(: get the value of SSDTC for this record :)
let $ssdtc := $record/odm:ItemData[@ItemOID=$ssdtcoid]/@Value
(: get all the DSSTDTC values for this subject :)
let $dsstdtcvalues := $dsdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dsusubjidoid and @Value=$ssusubjid]]/odm:ItemData[@ItemOID=$dsstdtcoid]/@Value
(: sort the DSSTDTC values and take the latest one :)
let $latestdsstdtc := functx:sort($dsstdtcvalues)[last()]
(: SSDTC >= DS.DSSTDTC :)
where $ssdtc castable as xs:date and $latestdsstdtc castable as xs:date and not(xs:date($ssdtc) >= xs:date($latestdsstdtc))
return SSDTC={data($ssdtc)} is not before or equal to latest DSSTDTC={data($ssdtc)} for SSSTRESC='DEAD'
]]>
When IECAT = 'EXCLUSION', then IEORRES = 'Y'
3.2
3.3
IE
For IECAT='EXCLUSION', IEORRES must be 'Y' but {data($ieorresvalue)} was found
]]>
When IECAT = 'INCLUSION' then IEORRES = 'N'
3.2
3.3
IE
For IECAT='INCLUSION', IEORRES must be 'N' but {data($ieorresvalue)} was found
]]>
IESTRESC = IEORRES
3.2
3.3
IE
Mismatch between IEORRES and IESTRESC values for USUBJID={data($usubjid)}. IEORRES={data($ieorres)}, whereas IESTRESC={data($iestresc)}
]]>
IETEST in TI.IETEST
3.2
3.3
IE
Value of IE.IETEST={data($ieietest)} cannot be found in the TI dataset
]]>
IETESTCD in TI.IETESTCD
3.2
3.3
IE
Value of IE.IETESTCD={data($ieietestcd)} cannot be found in the TI dataset
]]>
When Numeric scale used then LBTOXGR is a numeric value
3.2
3.3
LB
0
return Value of LBTOXGR={data($lbtoxgr)} represents a numeric scale but is not a complete number
]]>
When MS dataset present in study then MB dataset present in study
3.2
3.3
MS
MB dataset exists, but MS dataset is missing
]]>
When MBTESTCD = 'ORGANISM' and MBSTRESC ^= 'NO GROWTH' and MBMETHOD ^= 'GRAM STAIN', then MBRESCAT != null
3.2
MB
MBTESTCD='ORGANISM' and MBSTRESC!='NO GROWTH' and MBMETHOD!='GRAM STAIN' but MBRESCAT=null
]]>
When PESTRESC != null then PESTRESC = PEORRES OR a dictionary coded preferred
3.2
3.3
PE
PESTRESC='{data($pestresc)}' is not equal to PEORRES='{data($peorres)}' and PESTRESC is not coded
]]>
Within a subject each unique RELID is present on multiple RELREC records
3.2
3.3
RELREC
There is only 1 record for the combination of USUBJID={data($usubjid)} and RELID={data($relid)}
]]>
When IDVARVAL = null and USUBJID = null then IDVAR != --SEQ
3.2
3.3
RELREC
IDVAR={data($idvar)} is not allowed as USUBJID=null and IDVARVAL=null
]]>
When QORIG = 'ASSIGNED' then QEVAL != null
3.2
3.3
SUPPQUAL
QEVAL may not be null when QORIG='ASSIGNED'
]]>
When RDOMAIN != 'DM' then IDVAR != null
3.2
3.3
SUPPQUAL
IDVAR is not allowed to be null when RDOMAIN='{data($rdomain)}' is different from 'DM'
]]>
When IDVAR != null then IDVARVAL != null
3.2
3.3
SUPPQUAL
IDVARVAL is not allowed to be null when IDVAR='{data($idvar)}' is not null
]]>
Suppqual dataset names <= 8 characters
3.2
3.3
SUPPQUAL
8
return Supplemental Qualifier dataset name {data($name)} has more than 8 characters
]]>
When ETCD = 'UNPLAN' then TAETORD = null
3.2
3.3
SE
A non-null value for TAETORD={data($taetord)} was found in combination with ETCD='UNPLAN'
]]>
When Next ELEMENT present in dataset then SEENDTC = SESTDTC of next ELEMENT
SE
3.2
3.3
{data($seendtc)} - {data($sestdtcnext)} :)
(: SEENDTC of record must be equal to SESTDTC of the next record :)
where not($seendtc=$sestdtcnext)
return SEENDDTC={data($seendtc)} of ELEMENT='{data($element)}' does not correspond to SESTDTC={data($sestdtcnext)} of subsequent ELEMENT='{data($elementnext)}'
]]>
When ETCD != null then SESTDTC != null
3.2
3.3
SE
SESTDTC may not be null for ETCD={data($etcd)}
]]>
When ETCD != null and not last record (for the current subject) then SEENDTC != null
SE
3.2
3.3
SEENDDTC=null for non-last record with ETCD='{data($etcd)}'
]]>
When SEUPDES = null then ETCD != 'UNPLAN'
3.2
3.3
SE
ETCD='UNPLAN' for SEUPDES=null
]]>
When SEUPDES != null then ETCD = 'UNPLAN'
3.2
3.3
SE
When SEUPDES!=null then ETCD is expected to be 'UNPLAN'. ETCD='{data($etcd)}', SEUPDES='{data($seupdes)}' was found
]]>
When SVUPDES = null then VISITNUM in TV.VISITNUM
3.2
3.3
SV
SV.VISITNUM={data($svvisitnum)} was not found in TV for SVUPDES=null
]]>
When SVUPDES != null then VISITNUM not in TV.VISITNUM
3.2
3.3
SV
SVUPDES={data($svupdes)} but SV.VISITNUM={data($svvisitnum)} was found in TV
]]>
When SVUPDES = null then VISIT in TV.VISIT
3.2
3.3
SV
SV.VISIT='{data($svvisit)}' was not found in TV for SVUPDES=null
]]>
When SVUPDES != null then VISITDY = null
3.2
3.3
SV
SV.VISITDY='{data($svvisitdy)}' is expected to be null for SVUPDES='{data($svupdes)}'
]]>
When TAETORD present in dataset and SESTDTC <= SVSTDTC and SVSTDTC <= SEENDTC then SV.TAETORD = SE.TAETORD
3.2
3.3
SV
TAETORD is present in SV dataset and SESTDTC <= SVSTDTC and SVSTDTC <= SEENDTC, but SV.TAETORD={data($svtaetord)} is not equal to SE.TAETORD={data($setaetord)}
]]>
When EPOCH present in dataset and SESTDTC <= SVSTDTC and SVSTDTC <= SEENDTC then SV.EPOCH = SE.EPOCH
3.2
3.3
SV
TAETORD is present in SV dataset and SESTDTC <= SVSTDTC and SVSTDTC <= SEENDTC, but SV.EPOCH={data($svepoch)} is not equal to SE.EPOCH={data($seepoch)}
]]>
At least one timing variable present in dataset
3.2
3.3
INTERVENTIONS
FINDINGS
EVENTS
{data($timingvarsdomain)} :)
(: create a list of the variables for this dataset :)
let $datasetvars := (
for $a in $itemgroupdef/odm:ItemRef/@ItemOID
return $definedoc//odm:ItemDef[@OID=$a]/@Name
)
(: return {data($datasetvars)} :)
(: count the number of variables in the intersection of allowed timing variables and the variables in this dataset :)
let $intersection := distinct-values($timingvarsdomain[.=$datasetvars])
(: return {data($intersection)} :)
let $count := count($intersection)
(: if the count of items in the intersection = 0, there is no timing variable in the dataset :)
where $count=0
return No timing variables have been defined in dataset {data($name)}
]]>
At least one timing variable present in dataset
3.2
3.3
INTERVENTIONS
FINDINGS
EVENTS
{data($timingvarsdomain)} :)
(: create a list of the variables for this dataset :)
let $datasetvars := (
for $a in $itemgroupdef/odm:ItemRef/@ItemOID
return $definedoc//odm:ItemDef[@OID=$a]/@Name
)
(: return {data($datasetvars)} :)
(: count the number of variables in the intersection of allowed timing variables and the variables in this dataset :)
let $intersection := distinct-values($timingvarsdomain[.=$datasetvars])
(: return {data($intersection)} :)
let $count := count($intersection)
(: if the count of items in the intersection = 0, there is no timing variable in the dataset :)
where $count=0
return No timing variables have been defined in dataset {data($name)}
]]>
When --STDTC and DM.RFSTDTC both contain complete values in their date portion then --STDY is properly calculated per study day algorithm
3.2
3.3
ALL
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: taking the worst case scenario into account that there is more than one record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $stdyvalue := $record/odm:ItemData[@ItemOID=$stdyoid]/@Value
(: now calculate the DY value but only when RFSTDTC and STDTC have a complete date part :)
let $stdycalculated := (
if ($iscompleterfstdtcvalue and $iscompletestdtcvalue) then
(: if there is one, we must strip off the time part :)
let $stdtcvalueshort := substring($stdtcvalue,1,10)
let $rfstdtcvalueshort := substring($rfstdtcvalue,1,10)
let $stdycalc := (
if($stdtcvalueshort castable as xs:date and $rfstdtcvalueshort castable as xs:date) then
days-from-duration(xs:date($stdtcvalueshort)-xs:date($rfstdtcvalueshort))
else()
)
return $stdycalc
else () (: one or both dates are not complete dates :)
)
(: for the FDA there is no day 0, DY can only be <0 or >0, first day is day 1 :)
let $stdycalculatedfda := (
if($stdycalculated >= 0) then
$stdycalculated + 1
else (
$stdycalculated
)
)
(: before comparing, also check whether the submitted value is really an integer :)
where $stdyvalue castable as xs:integer and $stdycalculatedfda and $stdyvalue and not($stdycalculatedfda = $stdyvalue)
return Invalid value for {data($stdyname)}, calculated value={data($stdycalculatedfda)}, observed value={data($stdyvalue)}, in dataset {data($name)}
]]>
When --STDTC and DM.RFSTDTC both contain complete values in their date portion then --STDY is properly calculated per study day algorithm - single dataset or domain
3.2
3.3
ALL
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: taking the worst case scenario into account that there is more than one record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $stdyvalue := $record/odm:ItemData[@ItemOID=$stdyoid]/@Value
(: now calculate the DY value but only when RFSTDTC and STDTC have a complete date part :)
let $stdycalculated := (
if ($iscompleterfstdtcvalue and $iscompletestdtcvalue) then
(: if there is one, we must strip off the time part :)
let $stdtcvalueshort := substring($stdtcvalue,1,10)
let $rfstdtcvalueshort := substring($rfstdtcvalue,1,10)
let $stdycalc := (
if($stdtcvalueshort castable as xs:date and $rfstdtcvalueshort castable as xs:date) then
days-from-duration(xs:date($stdtcvalueshort)-xs:date($rfstdtcvalueshort))
else()
)
return $stdycalc
else () (: one or both dates are not complete dates :)
)
(: for the FDA there is no day 0, DY can only be <0 or >0, first day is day 1 :)
let $stdycalculatedfda := (
if($stdycalculated >= 0) then
$stdycalculated + 1
else (
$stdycalculated
)
)
(: before comparing, also check whether the submitted value is really an integer :)
where $stdyvalue castable as xs:integer and $stdycalculatedfda and $stdyvalue and not($stdycalculatedfda = $stdyvalue)
return Invalid value for {data($stdyname)}, calculated value={data($stdycalculatedfda)}, observed value={data($stdyvalue)}, in dataset {data($name)}
]]>
When --STDTC or DM.RFSTDTC does not contain complete values in their date portion then --STDY = null
3.2
3.3
ALL
= 10 (: boolean :)
(: get the USUBJID and get the corresponding RFSTDTC in the DM dataset :)
let $usubjidvalue := $record/odm:ItemData[@ItemOID=$usubjidoid]/@Value
(: taking the worst case scenario into account that there is more than one record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := $dmdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether RFSTDTC is a complete date :)
let $iscompleterfstdtcdate := string-length($rfstdtcvalue) >= 10
(: Get the value of the STDY variable (if any) :)
let $stdyvalue := $record/odm:ItemData[@ItemOID=$stdyoid]/@Value
(: STDY may NOT be populated when of RFSTDTC or STDTC is an incomplete date :)
where $stdyoid and (not($iscompletestdtcdate) or not($iscompleterfstdtcdate)) and $stdyvalue
return {data($stdyname)} variable value {data($stdyvalue)} is not null although one of {data($stdtcname)} (value={data($stdtcvalue)}) or RFSTDTC (value={data($rfstdtcvalue)}) is not a complete date
]]>
When --STDTC or DM.RFSTDTC does not contain complete values in their date portion then --STDY = null - Single dataset or domain
3.2
3.3
ALL
= 10 (: boolean :)
(: get the USUBJID and get the corresponding RFSTDTC in the DM dataset :)
let $usubjidvalue := $record/odm:ItemData[@ItemOID=$usubjidoid]/@Value
(: taking the worst case scenario into account that there is more than one record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := $dmdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether RFSTDTC is a complete date :)
let $iscompleterfstdtcdate := string-length($rfstdtcvalue) >= 10
(: Get the value of the STDY variable (if any) :)
let $stdyvalue := $record/odm:ItemData[@ItemOID=$stdyoid]/@Value
(: STDY may NOT be populated when of RFSTDTC or STDTC is an incomplete date :)
where $stdyoid and (not($iscompletestdtcdate) or not($iscompleterfstdtcdate)) and $stdyvalue
return {data($stdyname)} variable value {data($stdyvalue)} is not null although one of {data($stdtcname)} (value={data($stdtcvalue)}) or RFSTDTC (value={data($rfstdtcvalue)}) is not a complete date
]]>
When --ENDTC and DM.RFSTDTC both contain complete values in their date portion then --ENDY is properly calculated per study day algorithm
3.2
3.3
ALL
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: taking worst case scenario into account that there is more than 1 record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $endyvalue := $record/odm:ItemData[@ItemOID=$endyoid]/@Value
(: now calculate the DY value but only when RFSTDTC and ENDTC have a complete date part :)
let $endycalculated := (
if ($iscompleterfstdtcvalue and $iscompletestdtcvalue) then
(: if there is one, we must strip off the time part :)
let $endtcvalueshort := substring($endtcvalue,1,10)
let $rfstdtcvalueshort := substring($rfstdtcvalue,1,10)
let $endycalc := (
if($endtcvalueshort castable as xs:date and $rfstdtcvalueshort castable as xs:date) then
days-from-duration(xs:date($endtcvalueshort)-xs:date($rfstdtcvalueshort))
else()
)
return $endycalc
else () (: one or both dates are not complete dates :)
)
(: for the FDA there is no day 0, DY can only be <0 or >0, first day is day 1 :)
let $endycalculatedfda := (
if($endycalculated >= 0) then
$endycalculated + 1
else (
$endycalculated
)
)
(: before comparing, check that the submitted ENDY value is really an integer :)
where $endyvalue castable as xs:integer and $endycalculatedfda and $endyvalue and not($endycalculatedfda = $endyvalue)
return Invalid value for {data($endyname)}, calculated value={data($endycalculatedfda)}, observed value={data($endyvalue)}, in dataset {data($datasetname)}
]]>
When --ENDTC and DM.RFSTDTC both contain complete values in their date portion then --ENDY is properly calculated per study day algorithm - single dataset or domain
3.2
3.3
ALL
= 10 (: boolean :)
(: look up the record in the DM dataset and get the RFSTDTC :)
(: taking worst case scenario into account that there is more than 1 record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether also RFSTDTC is complete :)
let $iscompleterfstdtcvalue := string-length($rfstdtcvalue) >= 10 (: boolean :)
let $endyvalue := $record/odm:ItemData[@ItemOID=$endyoid]/@Value
(: now calculate the DY value but only when RFSTDTC and ENDTC have a complete date part :)
let $endycalculated := (
if ($iscompleterfstdtcvalue and $iscompletestdtcvalue) then
(: if there is one, we must strip off the time part :)
let $endtcvalueshort := substring($endtcvalue,1,10)
let $rfstdtcvalueshort := substring($rfstdtcvalue,1,10)
let $endycalc := (
if($endtcvalueshort castable as xs:date and $rfstdtcvalueshort castable as xs:date) then
days-from-duration(xs:date($endtcvalueshort)-xs:date($rfstdtcvalueshort))
else()
)
return $endycalc
else () (: one or both dates are not complete dates :)
)
(: for the FDA there is no day 0, DY can only be <0 or >0, first day is day 1 :)
let $endycalculatedfda := (
if($endycalculated >= 0) then
$endycalculated + 1
else (
$endycalculated
)
)
(: before comparing, check that the submitted ENDY value is really an integer :)
where $endyvalue castable as xs:integer and $endycalculatedfda and $endyvalue and not($endycalculatedfda = $endyvalue)
return Invalid value for {data($endyname)}, calculated value={data($endycalculatedfda)}, observed value={data($endyvalue)}, in dataset {data($datasetname)}
]]>
3.2
3.3
When --ENDTC or DM.RFSTDTC does not contain complete values in their date portion then --ENDY = null
ALL
= 10 (: boolean :)
(: get the USUBJID and get the corresponding RFSTDTC in the DM dataset :)
let $usubjidvalue := $record/odm:ItemData[@ItemOID=$usubjidoid]/@Value
(: taking worst case scenario into account that there is more than 1 record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether RFSTDTC is a complete date :)
let $iscompleterfstdtcdate := string-length($rfstdtcvalue) >= 10
(: Get the value of the STDY variable (if any) :)
let $endyvalue := $record/odm:ItemData[@ItemOID=$endyoid]/@Value
(: ENDY may NOT be populated when of RFSTDTC or ENDTC is an incomplete date :)
where $endyoid and (not($iscompletestdtcdate) or not($iscompleterfstdtcdate)) and $endyvalue
return {data($endyname)} variable value {data($endyvalue)} is not null although one of {data($endtcname)} (value={data($endtcvalue)}) or RFSTDTC (value={data($rfstdtcvalue)}) is not a complete date
]]>
3.2
3.3
When --ENDTC or DM.RFSTDTC does not contain complete values in their date portion then --ENDY = null - single dataset or collection
ALL
= 10 (: boolean :)
(: get the USUBJID and get the corresponding RFSTDTC in the DM dataset :)
let $usubjidvalue := $record/odm:ItemData[@ItemOID=$usubjidoid]/@Value
(: taking worst case scenario into account that there is more than 1 record for a USUBJID in DM - take the first one :)
let $rfstdtcvalue := doc($dmdatasetlocation)//odm:ItemGroupData[odm:ItemData[@ItemOID=$dmusubjidoid and @Value=$usubjidvalue]][1]/odm:ItemData[@ItemOID=$rfstdtcoid]/@Value
(: and check whether RFSTDTC is a complete date :)
let $iscompleterfstdtcdate := string-length($rfstdtcvalue) >= 10
(: Get the value of the STDY variable (if any) :)
let $endyvalue := $record/odm:ItemData[@ItemOID=$endyoid]/@Value
(: ENDY may NOT be populated when of RFSTDTC or ENDTC is an incomplete date :)
where $endyoid and (not($iscompletestdtcdate) or not($iscompleterfstdtcdate)) and $endyvalue
return {data($endyname)} variable value {data($endyvalue)} is not null although one of {data($endtcname)} (value={data($endtcvalue)}) or RFSTDTC (value={data($rfstdtcvalue)}) is not a complete date
]]>
When VISITNUM is NOT in TV.VISITNUM then VISITDY = null
3.2
3.3
ALL
VISITDY={data($visitdy)} is expected to be null as VISITNUM={data($visitnum)} is not present in TV
]]>
When VISITNUM is NOT in TV.VISITNUM then VISITDY = null
3.2
3.3
ALL
VISITDY={data($visitdy)} is expected to be null as VISITNUM={data($visitnum)} is not present in TV
]]>
When DM.RFSTDTC = null then --STRF = null
3.2
3.3
ALL
{data($strfname)}={data($strf)} is found although RFSTDTC in DM is null
]]>
When DM.RFENDTC = null then --ENRF = null
3.2
3.3
ALL
{data($enrfname)}={data($enrf)} is found although RFENDTC in DM is null
]]>
--TPT and --TPTNUM have a one-to-one relationship
3.2
3.3
ALL
Inconsistent value for {data($tptname)} within {data($tptnumname)} in dataset {data($datasetname)}. {data($uniquetptvaluescount)} different {data($tptname)} values were found for {data($tptnumname)}={data($tptnumuniquevalue)}
]]>
--TPT and --TPTNUM have a one-to-one relationship - single dataset or domain
3.2
3.3
ALL
Inconsistent value for {data($tptname)} within {data($tptnumname)} in dataset {data($datasetname)}. {data($uniquetptvaluescount)} different {data($tptname)} values were found for {data($tptnumname)}={data($tptnumuniquevalue)}
]]>
When --TPT != null and --TPTNUM != null and --ELTM != null then --ELTM is the same value across records with the same values of DOMAIN, VISITNUM, --TPTREF, and --TPTNUM
3.2
3.3
ALL
Inconsistent value for {data($eltmname)} within group of DOMAIN/VISITNUM/{data($tptrefname)}/{data($tptnumname)} in dataset {data($datasetname)}. {data($count)} different {data($eltmname)} values were found for this group
]]>
When --TPT != null and --TPTNUM != null and --ELTM != null then --ELTM is the same value across records with the same values of DOMAIN, VISITNUM, --TPTREF, and --TPTNUM
3.2
3.3
ALL
Inconsistent value for {data($eltmname)} within group of DOMAIN/VISITNUM/{data($tptrefname)}/{data($tptnumname)} in dataset {data($datasetname)}. {data($count)} different {data($eltmname)} values were found for this group
]]>
ARM not in ('Screen Failure', 'Not Assigned', 'Unplanned Treatment', 'Not Treated')
3.2
TA
TV
Value for planned ARM='{data($arm)}' is not allowed in dataset {data($name)}
]]>
TA,TE,SE: ETCD value length <= 8
3.2
3.3
TA
TV
SE
8
return Invalid value for ETCD dataset {data($name)} - Value {data($etcdvalue)} has more than 8 characters
]]>
TAETORD is unique within an ARM
3.2
3.3
TA
Records {data($recnum)} and {data($recnum2)} have the same value for ARM='{data($arm)}' and TAETORD={data($taetord)}
]]>
TAETORD is an integer
3.2
3.3
TA
TAETORD={data($taetord)} must be an integer
]]>
When TIVERS present in dataset then IETESTCD unique within TIVERS
3.2
3.3
TI
Records {data($recnum)} and {data($recnumnext)} have the same value for IETESTCD='{data($ietestcd)}' and TIVERS={data($tivers)}
]]>
When TIVERS not present in dataset then IETESTCD unique in dataset
3.2
3.3
TI
Records {data($recnum)} and {data($recnumnext)} have the same value for IETESTCD='{data($ietestcd)}' where TIVERS is absent
]]>
TSPARMCD value length <= 8
3.2
3.3
TS
8
return The value for TSPARMCD in dataset {data($datasetname)} has more than 8 characters: '{data($testvalue)}' was found
]]>
TSPARM value length <= 40
3.2
3.3
TS
40
return The value for TSPARM in dataset {data($datasetname)} has more than 40 characters: '{data($testvalue)}' was found
]]>
When TSVAL = null then TSVALNF != null
3.2
3.3
TS
TSVALNF=null although TSVAL=null
]]>
When TSVAL != null then TSVALNF = null
3.2
3.3
TS
TSVALNF={data($tsvalnf)} is expected to be null as TSVAL={data($tsval)}
]]>
when TSVAL1 != null then TSVAL != null
3.2
3.3
TS
0 and not($tsval or string-length($tsval)>0)
return TSVAL1='{data($tsval1)}' is populated but TSVAL=null
]]>
when TSVAL(n+1) != null then TSVALn != null
3.2
3.3
TS
1 :)
let $tsvalnoids := (
for $a in $definedoc//odm:ItemDef[starts-with(@Name,'TSVAL') and substring(@Name,6,1) castable as xs:integer
and xs:integer(substring(@Name,6,1)) > 1]/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
(: get the location of the dataset :)
let $tsdatasetlocation := (
if($defineversion='2.1') then $itemgroup/def21:leaf/@xlink:href
else $itemgroup/def:leaf/@xlink:href
)
let $datasetdoc := doc(concat($base,$tsdatasetlocation))
(: iterate over all records in the dataset :)
for $record in $datasetdoc//odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: iterate over the TSVALn values :)
for $tsvalnoid in $tsvalnoids
(: get the value of 'N' in TSVALN :)
let $tsvalnname := $definedoc//odm:ItemDef[@OID=$tsvalnoid]/@Name
let $n := xs:integer(substring($tsvalnname,6,1))
(: get the value for this TSVALn :)
let $tsvalue := $record/odm:ItemData[@ItemOID=$tsvalnoid]/@Value
(: and the value of N-1 :)
let $nmin1 := $n - 1
(: recover the name and OID of TSVALN-1 :)
let $tsvalnmin1name := concat('TSVAL',xs:string($nmin1))
let $tsvalnmin1oid := $definedoc//odm:ItemDef[@Name=$tsvalnmin1name]/@OID
(: and get the value of TSVALn-1 in the record :)
let $tsvalmin1value := $record/odm:ItemData[@ItemOID=$tsvalnmin1oid]/@Value
(: and check that the value is not null or empty :)
where not(string-length($tsvalmin1value) > 0)
return {data($tsvalnname)}='{data($tsvalue)}' but {data($tsvalnmin1name)}=null
]]>
When TSVAL != null and TSVALCD != null then TSVALCD and TSVAL have a one-to-one relationship.
3.2
3.3
TS
Inconsistent value for TSVAL within TSVALCD in dataset {data($datasetname)}. {data($uniquetsvalvaluescount)} different TSVAL values were found for TSVALCD={data($tsvalcduniquevalue)}
]]>
When TSVCDVER != null then TSVCDREF != null
3.2
3.3
TS
TSVCDVER='{data($tsvcdver)}' but TSVCDREF=null
]]>
When TSPARMCD not unique then TSSEQ is unique for each distinct value of TSPARMCD
3.2
3.3
TS
Inconsistent value for TSSEQ within TSPARMCD={data($tsparmcd)} in dataset {data($datasetname)}. Records {data($recnum)} and {data($recnumduplicate)} have the same value for TSSEQ={data($tsseq)}
]]>
When TSPARMCD = 'ADDON' and TSVAL != null then TSVAL in ('N','Y')
3.2
3.3
TS
TSVAL='{data($tsval)}' for TSPARMCD='ADDON' is expected to be in ('N','Y')
]]>
When TSPARMCD = 'AGEMAX' and TSVAL != null then TSVAL conforms to ISO 8601 duration
3.2
3.3
TS
TSVAL={data($tsval)} for TSPARMCD='AGEMAX' is expected to be an ISO-8601 duration
]]>
When TSPARMCD = 'RANDOM' and TSVAL != null then TSVAL in ('N','Y')
3.2
3.3
TS
TSVAL='{data($tsval)}' for TSPARMCD='RANDOM' is expected to be in ('N','Y')
]]>
When TSPARMCD = 'TDIGRP' and record exists where TSPARMCD = 'HLTSUBJI' and TSVAL = null or TSVAL='HEALTHY SUBJECTS'
3.2
3.3
TS
Invalid TSVAL='{data($tdigrpvalue)}' for TSPARMCD='TDIGRP', expected to be null or 'HEALTHY SUBJECTS' as a record with TSPARMCD='HLTSUBJI and TSVAL='Y' exists
]]>
When TSPARMCD = 'TDIGRP' and record exists where TSPARMCD = 'HLTSUBJI' and TSVAL = 'N', then TSVAL != null
3.2
3.3
TS
Null value for TSVAL for TSPARMCD='TDIGRP' found although a record with TSPARMCD='HLTSUBJI and TSVAL='N' exists
]]>
When TSPARMCD = 'CURTRT' and record exists where TSPARMCD = 'ADDON' and TSVAL = 'Y' then TSVAL != null
3.2
3.3
TS
Null value for TSVAL for TSPARMCD='CURTRT' found although a record with TSPARMCD='ADDON and TSVAL='Y' exists
]]>
When TSPARMCD = 'TRT' and record exists where TSPARMCD = 'STYPE' and TSVAL = 'INTERVENTIONAL' then TSVAL != null
3.2
3.3
TS
Null value for TSVAL for TSPARMCD='TRT' found although a record with TSPARMCD='STYPE and TSVAL='INTERVENTIONAL' exists
]]>
When TSPARMCD = 'INTMODEL' and record exists where TSPARMCD = 'STYPE' and TSVAL = 'INTERVENTIONAL' then TSVAL != null
3.2
3.3
TS
Null value for TSVAL for TSPARMCD='INTMODEL' found although a record with TSPARMCD='STYPE and TSVAL='INTERVENTIONAL' exists
]]>
When TSPARMCD = 'INTTYPE' and record exists where TSPARMCD = 'STYPE' and TSVAL = 'INTERVENTIONAL' then TSVAL != null
3.2
3.3
TS
Null value for TSVAL for TSPARMCD='INTTYPE' found although a record with TSPARMCD='STYPE and TSVAL='INTERVENTIONAL' exists
]]>
When TSPARMCD = 'PCLAS' and record exists where TSPARMCD = 'STYPE' and TSVAL = 'INTERVENTIONAL' and Pharmacological Class of Interventional Therapy is applicable then TSVAL ^= null
3.2
3.3
TS
Null value for TSVAL for TSPARMCD='PCLAS' found although a record with TSPARMCD='STYPE and TSVAL='INTERVENTIONAL' exists
]]>
When TSPARMCD = 'RANDQT' and TSVAL != null then TSVAL > 0 and TSVAL <= 1
3.2
3.3
TS
0 and TSVAL <= 1 :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare namespace functx = "http://www.functx.com";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: iterate over all TS datasets (there should be only one) :)
for $itemgroup in $definedoc//odm:ItemGroupDef[@Name='TS']
(: Get the OID for the TSVAL and TSPARMCD variables :)
let $tsvaloid := (
for $a in $definedoc//odm:ItemDef[@Name='TSVAL']/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
let $tsparmcdoid := (
for $a in $definedoc//odm:ItemDef[@Name='TSPARMCD']/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
(: get the dataset :)
let $datasetname := (
if($defineversion='2.1') then $itemgroup//def21:leaf/@xlink:href
else $itemgroup//def:leaf/@xlink:href
)
let $dataset := concat($base,$datasetname)
let $datasetdoc := doc($dataset)
(: look for a record with TSPARMCD='RANDQT' - we assume there is only 1 :)
for $randqtrecord in $datasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$tsparmcdoid and @Value='RANDQT']]
let $recnum := $randqtrecord/@data:ItemGroupDataSeq
(: get the value of TSVAL in this record (if any) :)
let $tsval := $randqtrecord/odm:ItemData[@ItemOID=$tsvaloid]/@Value
(: TSVAL != null then TSVAL > 0 and TSVAL <= 1 :)
where $tsval and not($tsval castable as xs:double and xs:double($tsval) > 0 and xs:double($tsval) <=1 )
return TSVAL={data($tsval)} whereas a number >0 and <=1 was expected
]]>
When TSPARMCD = ADAPT then TSVAL in ('N','Y')
3.2
3.3
TS
TSVAL='{data($tsval)}' for TSPARMCD='ADAPT' is expected to be in ('N','Y')
]]>
When TSPARMCD = DCUTDTC then TSVAL conforms to ISO 8601 date
3.2
3.3
TS
TSVAL={data($tsval)} for TSPARMCD=DCUTDTC is expected to be a valid ISO-8601 date
]]>
When TSPARMCD = NARMS then TSVAL is integer and > 0
3.2
3.3
TS
0 :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare namespace functx = "http://www.functx.com";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: iterate over all TS datasets (there should be only one) :)
for $itemgroup in $definedoc//odm:ItemGroupDef[@Name='TS']
(: Get the OID for the TSVAL and TSPARMCD variables :)
let $tsvaloid := (
for $a in $definedoc//odm:ItemDef[@Name='TSVAL']/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
let $tsparmcdoid := (
for $a in $definedoc//odm:ItemDef[@Name='TSPARMCD']/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
(: get the dataset :)
let $datasetname := (
if($defineversion='2.1') then $itemgroup//def21:leaf/@xlink:href
else $itemgroup//def:leaf/@xlink:href
)
let $dataset := concat($base,$datasetname)
let $datasetdoc := doc($dataset)
(: look for a record with TSPARMCD='NARMS' - there should be only 1 :)
for $record in $datasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$tsparmcdoid and @Value='NARMS']]
let $recnum := $record/@data:ItemGroupDataSeq
(: get the value of TSVAL in this record (if any) :)
let $tsval := $record/odm:ItemData[@ItemOID=$tsvaloid]/@Value
(: TSVAL must be an integer and > 0 :)
where $tsval and not($tsval castable as xs:integer and xs:integer($tsval) > 0)
return TSVAL={data($tsval)} for TSPARMCD=NARMS is expected to be an integer > 0
]]>
When TSPARMCD = SSTDTC then TSVAL conforms to ISO 8601 date
3.2
3.3
TS
TSVAL={data($tsval)} for TSPARMCD=SSTDTC is expected to be a valid ISO-8601 date
]]>
When TSPARMCD = SENDTC then TSVAL conforms to ISO 8601 date
3.2
3.3
TS
TSVAL={data($tsval)} for TSPARMCD=SENDTC is expected to be a valid ISO-8601 date
]]>
A set of records exist with at least one record with TSPARMCD equal to each of the required values in the list of Trial Summary Codes in Appendix C1
3.2
3.3
TS
At least one record for TSPARMCD={data($tsparmcd)} is expected in the TS dataset
]]>
When TSVCDREF = 'CDISC' then TSVALCD = valid code in the version identified in TSVCDVER
3.2
3.3
TS
The Value TSVALCD='{data($tsvalcd)}' for version TSVCDVER='{data($tsvalcd)}' for TSVCDREF='CDISC' is not a valid CDISC-CT value this version
]]>
When TSVCDREF = 'CDISC' then TSVCDVER = valid published version (date)
3.2
3.3
TS
The Value TSVCDVER='{data($tsvcdver)}' for TSVCDREF='CDISC' is not recognized as a valid CDISC-CT version from the list ({data($cdiscctversions)})
]]>
When TSVALNF = null then TSVAL not populated with values or synonyms of values in the ISO 21090 null flavor codelist (or other terms that can be represented as null flavors)
3.2
3.3
TS
As TSVALNF=null, TSVAL={data($tsval)} is not allowed to come out of the list of ISO-21090 null flavors ({data($nfvalues)}
]]>
When TSPARMCD equals one of the values in the list of Trial Summary Codes in Appendix C1 and TSVALNF is null then TSVAL != null
3.2
3.3
TS
As TSPARMCD={data($tsparmcd)} comes out of the list from SDTM-IG 3.2 Appendix C1 and TSVALNF=null, TSVAL is not allowed to be null
]]>
When ARMCD != null then ARMCD in TA.ARMCD
3.2
3.3
TV
TV.ARMCD='{data($tvarmcd)}' is not found in the list of TA.ARMCD=({data($taarmcdvalues)})
]]>
When ARM != null then ARM in TA.ARM
3.2
3.3
TV
TV.ARM='{data($tvarm)}' is not found in the list of TA.ARM=({data($taarmvalues)})
]]>
ARMCD value length <= 20
3.2
3.3
TV
20
return TV.ARMCD='{data($tvarmcd)}' has more than 20 characters
]]>
TRLOC not present in dataset
3.2
3.3
TR
TRLOC is not allowed to be present in TR
]]>
TRLAT not present in dataset
3.2
3.3
TR
TRLAT is not allowed to be present in TR
]]>
TRDIR not present in dataset
3.2
3.3
TR
TRDIR is not allowed to be present in TR
]]>
TRPORTOT not present in dataset
3.2
3.3
TR
TRPORTOT is not allowed to be present in TR
]]>
Variable Label = IG Label
3.2
ALL
Label '{data($label)}' does not correspond to the expected label from the SDTM-IG '{data($labelfromwebservice)}'
]]>
Variable Label = IG Label
3.2
ALL
Label '{data($label)}' does not correspond to the expected label from the SDTM-IG '{data($labelfromwebservice)}'
]]>
AEREASND not present in dataset
3.2
3.3
AE
AEREASND is not allowed to be present in AE
]]>
TSPARM and TSPARMCD have a one-to-one relationship
TS
3.2
3.3
Inconsistent value for TSPARM within TSPARMCD in dataset {data($datasetname)}. {data($uniquetsparmvaluescount)} different TSPARM values were found for TSPARMCD={data($tsparmcduniquevalue)}
]]>
DOMAIN value length = 2, except for AP, RELREC
ALL
3.2
3.3
The value for DOMAIN in the dataset does not consist of exactly 2 characters: DOMAIN='{data($domainvalue)}' was found
]]>
DOMAIN value length = 2, except for AP, RELREC - single dataset or domain
ALL
3.2
3.3
The value for DOMAIN in the dataset does not consist of exactly 2 characters: DOMAIN='{data($domainvalue)}' was found
]]>
DOMAIN value length = 4 for domain APxx
AP
3.2
3.3
The value for DOMAIN in the dataset does not consist of exactly 4 characters: DOMAIN='{data($domainvalue)}' was found
]]>
Variable name length <= 8
ALL
3.2
3.3
8
return The variable name '{data($varname)}' has more than 8 characters
]]>
Variable name length <= 8
3.2
3.3
ALL
8
return The variable name '{data($varname)}' has more than 8 characters
]]>
Variable label length <= 40
3.2
3.3
ALL
40
return The variable label '{data($label)}' for variable '{data($varname)}' has more than 40 characters
]]>
Variable label length <= 40
ALL
3.2
3.3
40
return The variable label '{data($label)}' for variable '{data($varname)}' has more than 40 characters
]]>
STUDYID, USUBJID or POOLID, DOMAIN, and --SEQ exist
3.2
INTERVENTIONS
FINDINGS
EVENTS
One of the required identifier variables STUDYID, USUBJID/POOLID, DOMAIN, or --SEQ is missing
]]>
STUDYID, USUBJID or POOLID, DOMAIN, and --SEQ exist
3.2
INTERVENTIONS
FINDINGS
EVENTS
One of the required identifier variables STUDYID, USUBJID/POOLID, DOMAIN, or --SEQ is missing
]]>
When SUPP--.QNAM present in dataset then Value of SUPP--.QNAM != any variable name defined in the corresponding SDTM version
3.2
3.3
ALL
SDTM 1.2
SDTM-IG 3.1.3 => SDTM 1.3
SDTM-IG 3.2 => SDTM 1.4 (default)
SDTM-IG 3.3 => SDTM 1.7 :)
(: TODO: for define.xml 2.1, pick up the version at the ItemGroupDef level :)
let $sdtmigversion := (
if($defineversion='2.1') then $definedoc//odm:MetaDataVersion/def21:Standards/def21:Standard[@Type='IG' and @Name='SDTMIG']/@Version
else $definedoc//odm:MetaDataVersion[@def:StandardName='SDTM-IG']/@def:StandardVersion
)
let $sdtmmodelversion := (
if($sdtmigversion='3.1.2') then '1.2'
else if($sdtmigversion = '3.1.3') then '1.3'
else if($sdtmigversion = '3.2') then '1.4'
else if($sdtmigversion = '3.3') then '1.7'
else '1.4'
)
(: The base of the RESTful web service :)
let $webservicebase := concat('http://www.xml4pharmaserver.com:8080/SDSService/rest/SDSVariableInfo/',$sdtmmodelversion,'/')
(: iterate over all SUPPxx datasets definitions :)
for $itemgroupdef in $definedoc//odm:ItemGroupDef[starts-with(@Name,'SUPP')]
let $name := $itemgroupdef/@Name
(: we need the OID of QNAM and of RDOMAIN :)
let $qnamoid := (
for $a in $definedoc//odm:ItemDef[@Name='QNAM']/@OID
where $a = $itemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $rdomainoid := (
for $a in $definedoc//odm:ItemDef[@Name='RDOMAIN']/@OID
where $a = $itemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: get the dataset location :)
let $datasetlocation := (
if($defineversion='2.1') then $itemgroupdef/def21:leaf/@xlink:href
else $itemgroupdef/def:leaf/@xlink:href
)
let $datasetdoc := (
if($datasetlocation) then doc(concat($base,$datasetlocation))
else ()
)
(: iterate over all the records in the datasset
ALTERNATIVE: get all unique values of QNAM in the dataset :)
for $record in $datasetdoc//odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: get the value of QNAM :)
let $qnam := $record/odm:ItemData[@ItemOID=$qnamoid]/@Value
(: get the related domain (RDOMAIN) :)
let $rdomain := $record/odm:ItemData[@ItemOID=$rdomainoid]/@Value
(: invoke the RESTful web service to see whether this variable is already known (reserved) for this domain :)
let $webservicerequest := concat($webservicebase,$qnam)
(: run the webservice and get the variable from the response. If the QNAM is allowed, the response element has no child elements,
and thus also no label :)
let $varlabelsdtm := doc($webservicerequest)/XML4PharmaServerWebServiceResponse/Response/Variable/Variable_Label
(: is there IS a variable label for this variable, then the value of QNAM is already defined in SDTM, and the rule is violated :)
where $varlabelsdtm
return The value of QNAM='{data($qnam)}' is not allowed as it is already defined in SDTM
]]>
When PP dataset present in study then PC dataset present in study
3.2
3.3
PP
PC
The PP dataset is present in the study but the PC dataset is not present in study
]]>
When Findings custom domain present in study then --TESTCD present in dataset
3.2
3.3
FINDINGS
No --TESTCD variable is present in custom Findings domain dataset {data($name)}
]]>
When custom domain present in study then custom domain is based on one of the observation classes
3.2
3.3
ALL
The custom dataset/domain {data($name)} is not based on one of the observation classes 'INTERVENTIONS', 'FINDINGS', 'EVENTS'. Class '{data($class)}' was found
]]>
The combination of TESTRL and TEENRL is unique for each ETCD
3.2
3.3
TE
Record {data($recnum2)} with ETCD='{data($etcd2)}' has the same combination of values of TESTRL='{data($testrl)}' and TEENRL='{data($teenrl)}' as record {data($recnum)} with ETCD='{data($etcd)}'
]]>
3.2
3.3
When TEDUR = null then TEENRL != null
TE
TEENRL is null although TEDUR is null
]]>
When TEENRL = null then TEDUR != null
3.2
3.3
TE
TEDUR is null although TEENRL is null
]]>
Variables are ordered with Identifiers first, followed by the Topic, Qualifier, and Timing variables
3.2
3.3
ALL
, $identifierstimingsquery)
(: we need to split identifiers and timing variable :)
let $identifiers := $identifierstimingsresponse/json//classVariables/*[role='Identifier']/name/text()
let $timings := $identifierstimingsresponse/json//classVariables/*[role='Timing']/name/text()
(: Use the CDISC Library to get all Interventions variables :)
let $interventionsquery := concat($cdisclibrarybase,'Interventions')
let $interventionsresponse := http:send-request(, $interventionsquery)
let $interventions := $interventionsresponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all Events variables :)
let $eventsquery := concat($cdisclibrarybase,'Events')
let $eventsresponse := http:send-request(, $eventsquery)
let $events := $eventsresponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all Events variables :)
let $findingsquery := concat($cdisclibrarybase,'Findings')
let $findingsesponse := http:send-request(, $findingsquery)
let $findings := $findingsesponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all FindingsAbout variables :)
let $findingsaboutquery := concat($cdisclibrarybase,'FindingsAbout')
let $findingsaboutresponse := http:send-request(, $findingsaboutquery)
let $findingsabout := $findingsaboutresponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all SpecialPurpose variables :)
(: ATTENTION: these are organized per dataset! :)
let $specialpurposequery := concat($cdisclibrarybase,'SpecialPurpose')
let $specialpurposeresponse := http:send-request(, $specialpurposequery)
let $specialpurpose := $specialpurposeresponse/json//datasets
(: Use the CDISC Library to get all TrialDesign variables :)
(: ATTENTION: these are organized per dataset! :)
let $trialdesignquery := concat($cdisclibrarybase,'TrialDesign')
let $trialdesignresponse := http:send-request(, $trialdesignquery)
(: Remark that we do NOT go into the individual datasets YET :)
let $trialdesign := $trialdesignresponse/json//datasets
(: Use the CDISC Library to get all TrialDesign variables :)
(: ATTENTION: these are organized per dataset! :)
let $relastionshipquery := concat($cdisclibrarybase,'Relationship')
let $relationshipresponse := http:send-request(, $relastionshipquery)
let $relationship := $relationshipresponse/json//datasets
(: Use the CDISC Library to get all AssociatedPersons variables :)
let $associatedpersonsquery := concat($cdisclibrarybase,'AssociatedPersons')
let $associatedpersonsresponse := http:send-request(, $associatedpersonsquery)
let $associatedpersonsclassVariables := $associatedpersonsresponse/json//classVariables/*/name/text()
let $associatedpersonsdatasetVariables := $associatedpersonsresponse/json//datasetVariables/*/name/text()
(: iterate over all dataset definitions, and get the class, name and domain :)
for $itemgroupdef in $definedoc//odm:ItemGroupDef
let $defclass := (
if($defineversion='2.1') then $itemgroupdef/def21:Class/@Name
else $itemgroupdef/@def:Class
)
let $name := $itemgroupdef/@Name
let $domain := (
if(starts-with($name,'SUPP')) then 'SUPPQUAL'
else if($itemgroupdef/@Domain) then $itemgroupdef/@Domain
else if($itemgroupdef/@Name='RELREC') then 'RELREC'
else substring($itemgroupdef/@Name,1,2)
)
(: get the variable name :)
let $variables := (
for $varoid in $itemgroupdef/odm:ItemRef/@ItemOID
return $definedoc//odm:ItemDef[@OID=$varoid]/@Name
)
(: now comes the different part: check the order :)
(: depending on the class, create an ordered array of the variables that are applicable :)
(: Read the def:Class, if incorrect, set cdisclibraryclass to 'UNKNOWN' :)
let $cdisclibraryclass := (
if(upper-case($defclass)='INTERVENTIONS') then 'Interventions'
else if (upper-case($defclass)='EVENTS') then 'Events'
else if (upper-case($defclass)='FINDINGS') then 'Findings'
else if (upper-case($defclass)='FINDINGS ABOUT' or upper-case($defclass)='FINDINGSABOUT') then 'FindingsAbout'
else if (upper-case($defclass)='SPECIAL PURPOSE' or upper-case($defclass)='SPECIALPURPOSE') then 'SpecialPurpose'
else if (upper-case($defclass)='TRIALDESIGN' or upper-case($defclass)='TRIAL DESIGN') then 'TrialDesign'
else if (upper-case($defclass)='RELATIONSHIP') then 'Relationship'
else 'UNRECOGNIZED'
)
let $orderedvariables := (
if($cdisclibraryclass = 'Interventions') then
functx:value-union(functx:value-union($identifiers,$interventions),$timings)
else if($cdisclibraryclass = 'Events') then
functx:value-union(functx:value-union($identifiers,$events),$timings)
else if($cdisclibraryclass = 'Findings') then
functx:value-union(functx:value-union($identifiers,$findings),$timings)
else if($cdisclibraryclass = 'TrialDesign') then
$trialdesign/*[./name=$domain]/datasetVariables/*/name
else if($cdisclibraryclass = 'Relationship') then
$relationship/*[./name=$domain]/datasetVariables/*/name
else if($cdisclibraryclass = 'SpecialPurpose') then
$specialpurpose/*[./name=$domain]/datasetVariables/*/name
else ()
)
(: we need to replace '--' by the domain name :)
let $orderedvars := local:replacedash($orderedvariables,$domain)
(: check whether the variables from the define.xml is a subset of the list of domain variables :)
let $issubset := local:compare($orderedvars,$variables)
where count($orderedvars)>0 and not($issubset)
return Variable order is not correct for SDTMIG version {data($sdtmigversion)} for class '{$cdisclibraryclass}': found = {data($variables)} - expected = {$orderedvars}
]]>
Variables are ordered with Identifiers first, followed by the Topic, Qualifier, and Timing variables
3.2
3.3
ALL
, $identifierstimingsquery)
(: we need to split identifiers and timing variable :)
let $identifiers := $identifierstimingsresponse/json//classVariables/*[role='Identifier']/name/text()
let $timings := $identifierstimingsresponse/json//classVariables/*[role='Timing']/name/text()
(: Use the CDISC Library to get all Interventions variables :)
let $interventionsquery := concat($cdisclibrarybase,'Interventions')
let $interventionsresponse := http:send-request(, $interventionsquery)
let $interventions := $interventionsresponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all Events variables :)
let $eventsquery := concat($cdisclibrarybase,'Events')
let $eventsresponse := http:send-request(, $eventsquery)
let $events := $eventsresponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all Events variables :)
let $findingsquery := concat($cdisclibrarybase,'Findings')
let $findingsesponse := http:send-request(, $findingsquery)
let $findings := $findingsesponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all FindingsAbout variables :)
let $findingsaboutquery := concat($cdisclibrarybase,'FindingsAbout')
let $findingsaboutresponse := http:send-request(, $findingsaboutquery)
let $findingsabout := $findingsaboutresponse/json//classVariables/*/name/text()
(: Use the CDISC Library to get all SpecialPurpose variables :)
(: ATTENTION: these are organized per dataset! :)
let $specialpurposequery := concat($cdisclibrarybase,'SpecialPurpose')
let $specialpurposeresponse := http:send-request(, $specialpurposequery)
let $specialpurpose := $specialpurposeresponse/json//datasets
(: Use the CDISC Library to get all TrialDesign variables :)
(: ATTENTION: these are organized per dataset! :)
let $trialdesignquery := concat($cdisclibrarybase,'TrialDesign')
let $trialdesignresponse := http:send-request(, $trialdesignquery)
(: Remark that we do NOT go into the individual datasets YET :)
let $trialdesign := $trialdesignresponse/json//datasets
(: Use the CDISC Library to get all TrialDesign variables :)
(: ATTENTION: these are organized per dataset! :)
let $relastionshipquery := concat($cdisclibrarybase,'Relationship')
let $relationshipresponse := http:send-request(, $relastionshipquery)
let $relationship := $relationshipresponse/json//datasets
(: Use the CDISC Library to get all AssociatedPersons variables :)
let $associatedpersonsquery := concat($cdisclibrarybase,'AssociatedPersons')
let $associatedpersonsresponse := http:send-request(, $associatedpersonsquery)
let $associatedpersonsclassVariables := $associatedpersonsresponse/json//classVariables/*/name/text()
let $associatedpersonsdatasetVariables := $associatedpersonsresponse/json//datasetVariables/*/name/text()
(: iterate over all dataset definitions, and get the class, name and domain :)
for $itemgroupdef in $definedoc//odm:ItemGroupDef[@Name=$datasetname]
let $defclass := (
if($defineversion='2.1') then $itemgroupdef/def21:Class/@Name
else $itemgroupdef/@def:Class
)
let $name := $itemgroupdef/@Name
let $domain := (
if(starts-with($name,'SUPP')) then 'SUPPQUAL'
else if($itemgroupdef/@Domain) then $itemgroupdef/@Domain
else if($itemgroupdef/@Name='RELREC') then 'RELREC'
else substring($itemgroupdef/@Name,1,2)
)
(: get the variable name :)
let $variables := (
for $varoid in $itemgroupdef/odm:ItemRef/@ItemOID
return $definedoc//odm:ItemDef[@OID=$varoid]/@Name
)
(: now comes the different part: check the order :)
(: depending on the class, create an ordered array of the variables that are applicable :)
(: Read the def:Class, if incorrect, set cdisclibraryclass to 'UNKNOWN' :)
let $cdisclibraryclass := (
if(upper-case($defclass)='INTERVENTIONS') then 'Interventions'
else if (upper-case($defclass)='EVENTS') then 'Events'
else if (upper-case($defclass)='FINDINGS') then 'Findings'
else if (upper-case($defclass)='FINDINGS ABOUT' or upper-case($defclass)='FINDINGSABOUT') then 'FindingsAbout'
else if (upper-case($defclass)='SPECIAL PURPOSE' or upper-case($defclass)='SPECIALPURPOSE') then 'SpecialPurpose'
else if (upper-case($defclass)='TRIALDESIGN' or upper-case($defclass)='TRIAL DESIGN') then 'TrialDesign'
else if (upper-case($defclass)='RELATIONSHIP') then 'Relationship'
else 'UNRECOGNIZED'
)
let $orderedvariables := (
if($cdisclibraryclass = 'Interventions') then
functx:value-union(functx:value-union($identifiers,$interventions),$timings)
else if($cdisclibraryclass = 'Events') then
functx:value-union(functx:value-union($identifiers,$events),$timings)
else if($cdisclibraryclass = 'Findings') then
functx:value-union(functx:value-union($identifiers,$findings),$timings)
else if($cdisclibraryclass = 'TrialDesign') then
$trialdesign/*[./name=$domain]/datasetVariables/*/name
else if($cdisclibraryclass = 'Relationship') then
$relationship/*[./name=$domain]/datasetVariables/*/name
else if($cdisclibraryclass = 'SpecialPurpose') then
$specialpurpose/*[./name=$domain]/datasetVariables/*/name
else ()
)
(: we need to replace '--' by the domain name :)
let $orderedvars := local:replacedash($orderedvariables,$domain)
(: check whether the variables from the define.xml is a subset of the list of domain variables :)
let $issubset := local:compare($orderedvars,$variables)
where count($orderedvars)>0 and not($issubset)
return Variable order is not correct for SDTMIG version {data($sdtmigversion)} for class '{$cdisclibraryclass}': found = {data($variables)} - expected = {$orderedvars}
]]>
When dataset name length = 3 or 4 and dataset name does not begin with 'AP' or 'FA', then the first two characters of the dataset name must equal a domain that is present in the study
3.2
3.3
ALL
First two characters of dataset name '{data($name)}' does not correspond to the domain name '{data($domain)}'
]]>
When dataset split by parent domain and dataset name length > 2 and dataset name begins with 'FA', then the characters of the dataset name after 'FA' equal a domain that present in the study
3.2
3.3
ALL
2 and dataset name begins with 'FA' then the characters of the dataset name after 'FA' equal a domain that present in the study :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
declare namespace functx = "http://www.functx.com";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdiscpilot01/' :)
(: let $define := 'define_2_0.xml' :)
let $definedoc := doc(concat($base,$define))
(: Iterate over all FAxx datasets :)
for $faitemgroupdef in $definedoc//odm:ItemGroupDef[starts-with(@Name,'FA') and string-length(@Name)>2]
let $name := $faitemgroupdef/@Name
(: get the domain about which FA is about starting from the third character :)
let $aboutdomain := substring($name,3)
(: there must be such a dataset/domain present :)
where not($definedoc//odm:ItemGroupDef[@Name=$aboutdomain or @Domain=$aboutdomain])
return No corresponding dataset or domain '{data($aboutdomain)}' was found for Findings About dataset '{data($name)}'
]]>
When Dataset name begins with 'SUPP' then value of RDOMAIN equals characters 5 and 6 of the dataset name
3.2
3.3
SUPPQUAL
Value of RDOMAIN='{data($rdomain)}' does not correspond to the 5th and 6th character of the dataset name '{data($name)}'
]]>
--CAT != Domain Name
3.2
3.3
ALL
Value of {data($catname)}='{data($cat)}' is not allowed to be equal to the name of the domain '{data($domain)}'
]]>
--CAT != Domain Name
3.2
3.3
ALL
Value of {data($catname)}='{data($cat)}' is not allowed to be equal to the name of the domain '{data($domain)}'
]]>
--CAT != --DECOD
3.2
3.3
ALL
Value of {data($catname)}='{data($cat)}' is not allowed to be equal to the value of {data($decodname)}='{data($decod)}'
]]>
--CAT != --DECOD
3.2
3.3
ALL
Value of {data($catname)}='{data($cat)}' is not allowed to be equal to the value of {data($decodname)}='{data($decod)}'
]]>
--SCAT != --DECOD
3.2
3.3
ALL
Value of {data($scatname)}='{data($scat)}' is not allowed to be equal to the value of {data($decodname)}='{data($decod)}'
]]>
--SCAT != --DECOD
3.2
3.3
ALL
Value of {data($scatname)}='{data($scat)}' is not allowed to be equal to the value of {data($decodname)}='{data($decod)}'
]]>
--CAT != --BODSYS
3.2
3.3
ALL
Value of {data($catname)}='{data($cat)}' is not allowed to be equal to the value of {data($bodsysname)}='{data($bodsys)}'
]]>
--CAT != --BODSYS
3.2
3.3
ALL
Value of {data($catname)}='{data($cat)}' is not allowed to be equal to the value of {data($bodsysname)}='{data($bodsys)}'
]]>
--SCAT != --BODSYS
3.2
3.3
ALL
Value of {data($scatname)}='{data($scat)}' is not allowed to be equal to the value of {data($bodsysname)}='{data($bodsys)}'
]]>
--SCAT != --BODSYS
3.2
3.3
ALL
Value of {data($scatname)}='{data($scat)}' is not allowed to be equal to the value of {data($bodsysname)}='{data($bodsys)}'
]]>
--TESTCD != 'OTHER'
3.2
3.3
FINDINGS
Value of {data($testcdname)}='{data($testcd)}' is not allowed to be equal to 'OTHER'
]]>
--TESTCD != 'OTHER'
FINDINGS
3.2
3.3
Value of {data($testcdname)}='{data($testcd)}' is not allowed to be equal to 'OTHER'
]]>
--TRT != 'OTHER'
3.2
3.3
INTERVENTIONS
Value of {data($trtname)}='{data($trt)}' is not allowed to be equal to 'OTHER'
]]>
--TRT != 'OTHER'
3.2
3.3
INTERVENTIONS
Value of {data($trtname)}='{data($trt)}' is not allowed to be equal to 'OTHER'
]]>
--TERM != 'OTHER'
3.2
3.3
EVENTS
Value of {data($termname)}='{data($term)}' is not allowed to be equal to 'OTHER'
]]>
--TERM != 'OTHER'
3.2
3.3
EVENTS
Value of {data($termname)}='{data($term)}' is not allowed to be equal to 'OTHER'
]]>
--TESTCD != 'MULTIPLE'
3.2
3.3
FINDINGS
Value of {data($testcdname)}='{data($testcd)}' is not allowed to be equal to 'MULTIPLE'
]]>
--TESTCD != 'MULTIPLE'
3.2
3.3
FINDINGS
Value of {data($testcdname)}='{data($testcd)}' is not allowed to be equal to 'MULTIPLE'
]]>
--TRT != 'MULTIPLE'
3.2
3.3
INTERVENTIONS
Value of {data($trtname)}='{data($trt)}' is not allowed to be equal to 'MULTIPLE'
]]>
--TRT != 'MULTIPLE'
3.2
3.3
INTERVENTIONS
Value of {data($trtname)}='{data($trt)}' is not allowed to be equal to 'MULTIPLE'
]]>
--TERM != 'MULTIPLE'
3.2
3.3
EVENTS
Value of {data($termname)}='{data($term)}' is not allowed to be equal to 'MULTIPLE'
]]>
--TERM != 'MULTIPLE'
3.2
3.3
EVENTS
Value of {data($termname)}='{data($term)}' is not allowed to be equal to 'MULTIPLE'
]]>
--ORRES != 'MULTIPLE'
3.2
3.3
FINDINGS
Value of {data($orresname)}='{data($orres)}' is not allowed to be equal to 'MULTIPLE'
]]>
--ORRES != 'MULTIPLE'
3.2
3.3
FINDINGS
Value of {data($orresname)}='{data($orres)}' is not allowed to be equal to 'MULTIPLE'
]]>
When --STAT = null or --DRVFL != 'Y' then --ORRES != null
3.2
3.3
FINDINGS
Value of {data($orresname)} may not be null when {data($statname)}=NULL or {data($drvflname)}!='Y'
]]>
When --STAT = null or --DRVFL != 'Y' then --ORRES != null
3.2
3.3
FINDINGS
Value of {data($orresname)} may not be null when {data($statname)}=NULL or {data($drvflname)}!='Y'
]]>
When Custom domain and variable name is not STUDYID, DOMAIN, USUBJID, POOLID, SPDEVID, VISIT, VISITNUM, VISITDY, ELEMENT, TAETORD, EPOCH, then Variable name begins with the 2-character domain code
3.2
3.3
ALL
Variable {data($varname)} must start with the first 2 characters of the domain code '{data($domain)}' in the custom dataset {data($name)}
]]>
--SCAT != Domain Name
3.2
3.3
ALL
Value of {data($scatname)}='{data($scat)}' may not be equal to the domain name '{data($domainname)}'
]]>
--SCAT != Domain Name
3.2
3.3
ALL
Value of {data($scatname)}='{data($scat)}' may not be equal to the domain name '{data($domainname)}'
]]>
Variables not in Model List of Allowed Variables for Observation Class are in SUPPQUAL
3.2
3.3
INTERVENTIONS
FINDINGS
EVENTS
, $cdisclibraryquerysdtmmodel)
(: we will also need to look into the 'GeneralObservations' itself :)
let $cdisclibraryquerysdtmgeneral := concat($cdisclibrarybase,'sdtm/',$sdtmversion,'/classes/GeneralObservations')
let $cdisclibrarysdtmgeneralresponse := http:send-request(, $cdisclibraryquerysdtmgeneral)
(: TODO: for SDTM-IG 3.3, there are "Domain-Specific Variables for the General Observation Classes" :)
(: MHEVDTYP, EXMETHOD, EGBEATNO, ICIMPLBL, MSAGENT, MSCONC, MSCONCU :)
let $domainspecific_3_3_variables := (
if($sdtmigversion = '3-3') then ('MHEVDTYP', 'EXMETHOD', 'EGBEATNO', 'ICIMPLBL', 'MSAGENT', 'MSCONC', 'MSCONCU')
else ()
)
(: iterate over the defined variables (by OID) and then name :)
(: but ONLY when we are in a "General Observation" class :)
for $varoid in $dataset/odm:ItemRef/@ItemOID[$class='Findings' or $class='Events' or $class='Interventions' or $class='FindingsAbout']
let $varname := $definedoc//odm:ItemDef[@OID=$varoid]/@Name
(: we need to take generic variable names into account,
e.g. LBDTC goes into --DTC :)
let $varnamegen := (
if(starts-with($varname,$domainname))
then concat('--',substring($varname,3))
else $varname
)
(: when not found, it needs to be looked up in the GeneralObservations :)
where not(functx:is-value-in-sequence($varname,$domainspecific_3_3_variables)) and not($cdisclibrarysdtmmodelresponse/json//*[name=$varnamegen]) and not($cdisclibrarysdtmgeneralresponse/json//*[name=$varnamegen])
return Variable {data($varname)} is not in the model list of allowed variables for class {data($class)} and must go into SUPPQUAL
]]>
--DTHREL not present in dataset
3.2
3.3
ALL
The use of the variable '{data($dthrelname)}' is not allowed in SDTM (it is specific to SEND)
]]>
--EXCLFL not present in dataset
3.2
3.3
ALL
The use of the variable '{data($exclflname)}' is not allowed in SDTM (it is specific to SEND)
]]>
--REASEX not present in dataset
3.2
3.3
ALL
The use of the variable '{data($reasexname)}' is not allowed in SDTM (it is specific to SEND)
]]>
--DETECT not present in dataset
3.2
3.3
ALL
The use of the variable '{data($detectname)}' is not allowed in SDTM (it is specific to SEND)
]]>
SPECIES not present in DM dataset
3.2
3.3
DM
The use of the variable 'SPECIES' is not allowed in SDTM domain 'DM' (it is specific to SEND)
]]>
STRAIN not present in DM dataset
3.2
3.3
DM
The use of the variable 'STRAIN' is not allowed in SDTM domain 'DM' (it is specific to SEND)
]]>
SBSTRAIN not present in DM dataset
3.2
3.3
DM
The use of the variable 'SBSTRAIN' is not allowed in SDTM domain 'DM' (it is specific to SEND)
]]>
When USUBJID != null then POOLID = null
3.2
3.3
RELSUB
POOLID='{data($poolid)}' must be null as USUBJID='{data($usubjid)}' is not null
]]>
When POOLID != null then USUBJID = null
3.2
3.3
RELSUB
USUBJID='{data($usubjid)}' must be null as POOLID='{data($poolid)}' is not null
]]>
When RSUBJID != null then RSUBJID != USUBJID
3.2
3.3
RELSUB
USUBJID='{data($usubjid)}' must be different from RSUBJID='{data($rsubjid)}'
]]>
When RSUBJID != null then RSUBJID != POOLID
3.2
3.3
RELSUB
POOLID='{data($poolid)}' must be different from RSUBJID='{data($rsubjid)}'
]]>
When RSUBJID != null then RDEVID = null
3.2
3.3
AP
RDEVID='{data($rdevid)}' must be null as RSUBJID='{data($rsubjid)}' is not null
]]>
When RDEVID != null then RSUBJID = null
3.2
3.3
AP
RSUBJID='{data($rsubjid)}' must be null as RDEVID='{data($rdevid)}' is not null
]]>
When RSUBJID !=null and RSUBJID != POOLID then RSUBJID = DM.USUBJID
3.2
3.3
AP
Value of RSUBJID='{data($rsubjid)}' was not found as USUBJID in DM dataset although RSUBJID != POOLID='{data($poolid)}'
]]>
DM dataset present in study
3.2
3.3
DM
Document {data($dmdatasetname)} could not be found in collection {data($base)}
]]>
When RDOMAIN != null then RDOMAIN is dataset in study
3.2
3.3
SUPPQUAL
CO
RELREC
Value of RDOMAIN='{data($rdomain)}' was not found as a dataset in the submission
]]>
When IDVAR != null then IDVAR is variable in domain = RDOMAIN
3.2
3.3
SUPPQUAL
CO
RELREC
Variable '{data($idvarvalue)}' was not found in the parent dataset of the referenced domain RDOMAIN='{data($rdomain)}'
]]>
When IDVAR != null then IDVARVAL = value of variable = IDVAR in domain = RDOMAIN
3.2
3.3
SUPPQUAL
CO
RELREC
Invalid referenced record - Dataset={data($name)}: no parent records found in RDOMAIN={data($rdomainval)} for USUBJID={data($usubjidvalue)} with IDVAR='{data($idvarvalue)}' and IDVARVAL='{data($idvarvalvalue)}'
]]>
When IDVAR != null then IDVARVAL = value of variable = IDVAR in domain = RDOMAIN
3.2
3.3
SUPPQUAL
CO
RELREC
Invalid referenced record - Dataset={data($name)}: no parent records found in RDOMAIN={data($rdomainval)} for USUBJID={data($usubjidvalue)} with IDVAR='{data($idvarvalue)}' and IDVARVAL='{data($idvarvalvalue)}'
]]>
--TESTCD <= 8 chars and contains only letters, numbers, and underscores and can not start with a number
3.2
3.3
ALL
8 or functx:is-value-in-sequence($firstchar,$numbers) or not(matches($value,'[A-Z_][A-Z0-9_]*'))
return {data($testname)}='{data($value)}' must be not more than 8 chars long and contains only letters, numbers, and underscores and can not start with a number
]]>
--TESTCD <= 8 chars and contains only letters, numbers, and underscores and can not start with a number
3.2
3.3
ALL
8 or functx:is-value-in-sequence($firstchar,$numbers) or not(matches($value,'[A-Z_][A-Z0-9_]*'))
return {data($testname)}='{data($value)}' must be not more than 8 chars long and contains only letters, numbers, and underscores and can not start with a number
]]>
When SUPP-- dataset present in study then dataset present in study with DOMAIN = SUPP--.RDOMAIN
3.2
3.3
ALL
No parent dataset/domain was found for RDOMAIN='{data($rdomain)}'
]]>
When RELREC dataset present in study then dataset present in study with DOMAIN = RELREC.RDOMAIN
3.2
3.3
ALL
No parent dataset/domain was found for RDOMAIN='{data($rdomain)}'
]]>
TDSTOFF = 0 or positive value in ISO 8601 Duration format
3.2
3.3
TD
TDSTOFF must either be '0' or a non-negative duration. TDSTOFF='{data($tdstoff)}' was found
]]>
--LLT = MedDRA lowest level term
3.2
3.3
AE
MH
CE
FDAC351
Value '{data($lltvalue)}' for {data($lltname)} was not found in the MedDRA dictionary
]]>
--LLTCD = MedDRA lowest level code
3.2
3.3
AE
MH
CE
FDAC353
Value '{data($lltcdvalue)}' for {data($lltcdname)} was not found in the MedDRA dictionary
]]>
When --DECOD != null then --DECOD = MedDRA preferred term
3.2
3.3
AE
MH
CE
Value '{data($decodvalue)}' for {data($decodname)} was not found in the MedDRA dictionary
]]>
--PTCD = MedDRA preferred term code
3.2
3.3
AE
MH
CE
Value '{data($ptcdvalue)}' for {data($ptcdname)} was not found in the MedDRA dictionary as a preferred term code
]]>
--HLT = MedDRA high level term
3.2
3.3
AE
MH
CE
Value '{data($hltvalue)}' for {data($hltname)} was not found in the MedDRA dictionary as a high level term
]]>
--HLTCD = MedDRA high level term code
3.2
3.3
AE
MH
CE
Value '{data($hltcdvalue)}' for {data($hltcdname)} was not found in the MedDRA dictionary as a high level term
]]>
--HLGT = MedDRA high level group term
3.2
3.3
AE
MH
CE
Value '{data($hlgtvalue)}' for {data($hlgtname)} was not found in the MedDRA dictionary
]]>
--HLGTCD = MedDRA high level group term code
AE
MH
CE
3.2
3.3
Value '{data($hlgtcdvalue)}' for {data($hlgtcdname)} was not found in the MedDRA dictionary as a high level term
]]>
--BODSYS = MedDRA system organ class
AE
MH
CE
3.2
3.3
Value '{data($bodsysvalue)}' for {data($bodsysname)} is not found in the MedDRA dictionary
]]>
--BDSYCD = MedDRA system organ class code
3.2
3.3
AE
MH
CE
Value '{data($bdsycdvalue)}' for {data($bdsycdname)} is not found in the MedDRA dictionary
]]>
--SER in ('Y','N')
3.2
AE
Value of AESER='{data($aeser)}' must be one of 'Y' or 'N'
]]>
--SCAN in ('Y','N')
3.2
AE
Value of AESCAN='{data($aescan)}' must be one of 'Y' or 'N'
]]>
--SCONG in ('Y','N')
3.2
AE
Value of AESCONG='{data($aescong)}' must be one of 'Y' or 'N'
]]>
--SDISAB in ('Y','N')
3.2
AE
Value of AESDISAB='{data($aesdisab)}' must be one of 'Y' or 'N'
]]>
--SDTH in ('Y','N')
3.2
AE
Value of AESDTH='{data($aesdth)}' must be one of 'Y' or 'N'
]]>
--SHOSP in ('Y','N')
3.2
AE
Value of AESHOSP='{data($aeshsop)}' must be one of 'Y' or 'N'
]]>
--SLIFE in ('Y','N')
3.2
AE
Value of AESLIFE='{data($aeslife)}' must be one of 'Y' or 'N'
]]>
--SOD in ('Y','N')
3.2
AE
Value of AESOD='{data($aesod)}' must be one of 'Y' or 'N'
]]>
--SMIE in ('Y','N')
3.2
AE
Value of AESMIE='{data($aesmie)}' must be one of 'Y' or 'N'
]]>
--CONTRT in ('Y','N')
3.2
3.3
AE
Value of AECONTRT='{data($aecontrt)}' must be one of 'Y' or 'N'
]]>
When --ORRES != null or --DRVFL = 'Y', then --STRESC != null
3.2
3.3
FINDINGS
{data($strescname)} is not populated although {data($orresname)} is populated or {data($drvflname)}='Y'
]]>
When --ORRES != null or --DRVFL = 'Y', then --STRESC != null - single dataset or domain
3.2
3.3
FINDINGS
{data($strescname)} is not populated although {data($orresname)} is populated or {data($drvflname)}='Y'
]]>
When DSCAT = 'DISPOSITION EVENT' and DSTERM != 'COMPLETED' then at most one record per subject per epoch
3.2
3.3
DS
1
return For DSCAT = 'DISPOSITION EVENT' and DSTERM = 'COMPLETED', {data($count)} records were found for USUBJID='{data($usubjid)}' and EPOCH='{data($epoch)}'. Only 1 record is allowed.
]]>
--LOINC = valid code in the version of the LOINC dictionary specified in define.xml
3.2
3.3
FINDINGS
LOINC code for {data($loincname)}='{data($loinc)}' is not recognized as a valid LOINC code by the NLM RESTful web service
else ()
return $errormessages
]]>
--LOINC = valid code in the version of the LOINC dictionary specified in define.xml
3.2
3.3
FINDINGS
{data($webserviceresponse)} :)
let $errormessages :=
if($webserviceresponse='null' or string-length($webserviceresponse)=0) then
for $record in $group/odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
return LOINC code for {data($loincname)}='{data($loinc)}' is not recognized as a valid LOINC code by the XML4Pharma RESTful web service
else ()
return $errormessages
]]>
--LOINC = valid code in the version of the LOINC dictionary specified in define.xml
3.2
3.3
FINDINGS
{data($webserviceresponse)} :)
let $errormessages :=
if($webserviceresponse='null' or string-length($webserviceresponse)=0) then
for $record in $group/odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
return LOINC code for {data($loincname)}='{data($loinc)}' is not recognized as a valid LOINC code by the XML4Pharma RESTful web service
else ()
return $errormessages
]]>
When --PRESP = 'Y' and --OCCUR = null then --STAT present in dataset (not AE, DS, DV, EX)
3.2
3.3
EVENTS
INTERVENTIONS
0 and not($statoid)
return {data($statname)} is not present in dataset, although there are records with {data($prespname)} = 'Y' and {data($occurname)} = null
]]>
When --PRESP = 'Y' and --OCCUR = null then --STAT present in dataset (not AE, DS, DV, EX)
3.2
3.3
EVENTS
INTERVENTIONS
0 and not($statoid)
return {data($statname)} is not present in dataset, although there are records with {data($prespname)} = 'Y' and {data($occurname)} = null
]]>
--TEST length <= 40 (except for IE)
3.2
3.3
FINDINGS
40
return The value of {data($testname)} has more than 40 characters. {data($testname)}='{data($test)}' was found
]]>
--TEST length <= 40 (except for IE)
3.2
3.3
FINDINGS
40
return The value of {data($testname)} has more than 40 characters. {data($testname)}='{data($test)}' was found
]]>
EX present in study
3.2
3.3
EX
No EX dataset was defined in the define.xml
else (
for $itemgroupdef in $exitemgroupdef
let $name := $itemgroupdef/@Name
(: get the dataset location and document :)
let $exdatasetlocation := (
if($defineversion='2.1') then $itemgroupdef/def21:leaf/@xlink:href
else $itemgroupdef/def:leaf/@xlink:href
)
let $exdatasetdoc := doc(concat($base,$exdatasetlocation))
(: let $exdatasetdoc := doc(concat($base,$exdatasetlocation)) :)
(: check whether the document exists :)
where not($exdatasetdoc)
(: return Dataset {data($name)} was defined in the define.xml :)
return The dataset {data($name)} was defined but was not found
)
)
return $errormessages
]]>
Dataset > 0 records
3.2
3.3
ALL
0 records :)
(: P.S only the datasets that are listed in the define.xml are tested :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace request="http://exist-db.org/xquery/request";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: Find all datasets - exclude datasets in define.xml 2.1 that have the HasNoData="Yes" attribute :)
for $itemgroup in $definedoc//odm:ItemGroupDef[not(@def21:HasNoData='Yes')]
let $datasetname := $itemgroup/@Name
let $dataset := (
if($defineversion='2.1') then $itemgroup/def21:leaf/@xlink:href
else $itemgroup/def:leaf/@xlink:href
)
let $datasetdoc := (
if($dataset) then doc(concat($base,$dataset))
else ()
)
(: iterate over each dataset, and count the number of records :)
for $numrecords in count($datasetdoc//odm:ItemGroupData)
(: and check whether there is less than 1 record :)
where $numrecords < 1
return {$numrecords} found in dataset: {data($datasetname)}
]]>
STUDYID = DM.STUDYID
3.2
3.3
ALL
STUDYID {data($d/@Value)} in dataset {data($dataset)} does not match the STUDYID {data($dmstudyid)} in the DM dataset
]]>
STUDYID = DM.STUDYID
3.2
3.3
ALL
STUDYID {data($d/@Value)} in dataset {data($dataset)} does not match the STUDYID {data($dmstudyid)} in the DM dataset
]]>
VISITNUM identifies unique record within subject in SV
3.2
3.3
SV
1
return SV.VISITNUM does not uniquely identify records within subject - combination of USUBJID={data($usubjidvalue)} and VISITNUM={data($visitnumvalue)} occurrs {data($count)} times
]]>
IDVAR, IDVARVAL, and QNAM a unique combination per parent subject record
3.2
3.3
SUPPQUAL
1
return The combination of USUBJID={data($usubjidvalue)}, IDVAR={data($idvar)}, IDVARVAL={data($idvarval)} and QNAM={data($qnam)} is not unique
]]>
When DM.ACTARMCD not in ('SCRNFAIL', 'NOTASSGN', 'NOTTRT') then Subject records > 0 in DS
3.2
DS
0 in DS :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdiscpilot01/' :)
(: let $define := 'define_2_0.xml' :)
let $definedoc := doc(concat($base,$define))
(: get the DM dataset :)
let $dmitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='DM']
let $dmdatasetlocation := (
if($defineversion='2.1') then $dmitemgroupdef/def21:leaf/@xlink:href
else $dmitemgroupdef/def:leaf/@xlink:href
)
let $dmdatasetdoc := doc(concat($base,$dmdatasetlocation))
(: get the OID of USUBJID and ACTARMCD :)
let $dmusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $dmitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
let $dmactarmcdoid := (
for $a in $definedoc//odm:ItemDef[@Name='ACTARMCD']/@OID
where $a = $dmitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: get the DS dataset :)
let $dsitemgroupdef := $definedoc//odm:ItemGroupDef[@Name='DS']
let $dsdatasetlocation := (
if($defineversion='2.1') then $dsitemgroupdef/def21:leaf/@xlink:href
else $dsitemgroupdef/def:leaf/@xlink:href
)
let $dsdatasetdoc := (
if($dsdatasetlocation) then doc(concat($base,$dsdatasetlocation))
else ()
)
(: get the OID of USUBJID in DS (can be different from the one in DM) :)
let $dsusubjidoid := (
for $a in $definedoc//odm:ItemDef[@Name='USUBJID']/@OID
where $a = $dsitemgroupdef/odm:ItemRef/@ItemOID
return $a
)
(: iterate over all records in DM :)
for $record in $dmdatasetdoc//odm:ItemGroupData
let $recnum := $record/@data:ItemGroupDataSeq
(: get the value of USUBJID and of ACTARMCD :)
let $dmusubjid := $record/odm:ItemData[@ItemOID=$dmusubjidoid]/@Value
let $actarmcd := $record/odm:ItemData[@ItemOID=$dmactarmcdoid]/@Value
(: when ACTARMCD is NOT 'SCRNFAIL', 'NOTASSGN', 'NOTTRT'
then count the number of records for this subject in DS :)
let $errormessages :=
if ($actarmcd != 'SCRNFAIL' and $actarmcd != 'NOTASSGN' and $actarmcd != 'NOTTRT') then
let $count := count($dsdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$dsusubjidoid and @Value=$dmusubjid]])
where $count = 0
return No records found for USUBJID={data($dmusubjid)} and ACTARMCD='{data($actarmcd)}' in DS
else () (: ACTARMC is one of 'SCRNFAIL', 'NOTASSGN', 'NOTTRT' - nothing to do :)
(: 'print' the error messages :)
return $errormessages
]]>
Dataset name begins with DOMAIN value
3.2
3.3
ALL
Dataset name='{data($name)}' or file name='{data($datasetfilename)}' does not start with the domain name='{data($domain)}'
]]>
When ETCD != 'UNPLAN' then ETCD = TE.ETCD
3.2
3.3
SE
TA
ETCD value='{data($etcd)}' was not found in TE
]]>
QLABEL value length <= 40
3.2
3.3
SUPPQUAL
40
return Value of QLABEL='{data($qlabel)}' has more than 40 characters
]]>
QNAM value length <=8 and cannot start with a number and cannot contain characters other than letters in upper case, numbers, or underscores
3.2
3.3
SUPPQUAL
8 or not(matches($qnamvalue,'[A-Z_][A-Z0-9_]*'))
return Invalid value for QNAM in dataset {data($datasetname)} - Label={data($qnamvalue)} has more than 8 characters or starts with a number or contains characters other than letters in upper case, numbers, or underscores
]]>
TAETORD order of elements match order in TA within ARM of the Subject
3.2
3.3
ALL
Invalid TAETORD in {data($name)} in dataset {data($datasetname)}. TAETORD={data($taetord)} was not found in dataset {data($tadatasetname)}
]]>
TAETORD order of elements match order in TA within ARM of the Subject
3.2
3.3
ALL
Invalid TAETORD in {data($name)} in dataset {data($datasetname)}. TAETORD={data($taetord)} was not found in dataset {data($tadatasetname)}
]]>
When IDVAR populated with a --SEQ value then RELTYPE = null
3.2
3.3
RELREC
IDVAR={data($idvar)} but RELTYPE is not null. RELTYPE='{data($reltype)}' is found
]]>
When --OCCUR = 'N' then --STRF = null
3.2
3.3
INTERVENTIONS
EVENTS
0
return {data($occurname)}='N' but {data($strfname)} is not null. {data($strfname)}='{data($strf)}' is found
]]>
When --OCCUR = 'N' then --STRF = null
3.2
3.3
INTERVENTIONS
EVENTS
0
return {data($occurname)}='N' but {data($strfname)} is not null. {data($strfname)}='{data($strf)}' is found
]]>
When --OCCUR = 'N' then --ENRF = null
3.2
3.3
INTERVENTIONS
EVENTS
0
return {data($occurname)}='N' but {data($enrfname)} is not null. {data($enrfname)}='{data($enrf)}' is found
]]>
When --OCCUR = 'N' then --ENRF = null
3.2
3.3
INTERVENTIONS
EVENTS
0
return {data($occurname)}='N' but {data($enrfname)} is not null. {data($enrfname)}='{data($enrf)}' is found
]]>
When --ORRES != null then --STAT = null
3.2
3.3
FINDINGS
{data($statname)} must be NULL, as ({data($orresname)})='{data($orresvalue)}' is not null. {data($statname)}='{data($statvalue)}' is found
]]>
When --ORRES != null then --STAT = null - single dataset or domain
3.2
3.3
FINDINGS
{data($statname)} must be NULL, as ({data($orresname)})='{data($orresvalue)}' is not null. {data($statname)}='{data($statvalue)}' is found
]]>
When --VAMT != null then --TRTV != null
3.2
3.3
INTERVENTIONS
{data($trtvname)} may not be NULL, as ({data($vamtname)})='{data($vamt)}' is not null
]]>
When --VAMT != null then --TRTV != null
3.2
3.3
INTERVENTIONS
{data($trtvname)} may not be NULL, as ({data($vamtname)})='{data($vamt)}' is not null
]]>
When --STRESU != null then --STRESC (in standard units) != null
3.2
3.3
FINDINGS
{data($stresuname)}='{data($stresu)}' is not NULL, but {data($strescname)}=NULL
]]>
When --STRESU != null then --STRESC (in standard units) != null
3.2
3.3
FINDINGS
{data($stresuname)}='{data($stresu)}' is not NULL, but {data($strescname)}=NULL
]]>
When --RESCAT != null then --STRESC != null
3.2
3.3
FINDINGS
{data($rescatname)}='{data($rescat)}' is not NULL, but {data($strescname)}=NULL
]]>
When --RESCAT != null then --STRESC != null
3.2
3.3
FINDINGS
{data($rescatname)}='{data($rescat)}' is not NULL, but {data($strescname)}=NULL
]]>
When --TOX != null then --TOXGR != null
3.2
3.3
AE
MH
CE
EG
LB
PC
PP
{data($toxgrname)}=null although {data($toxname)}={data($toxvalue)} is not null
]]>
When --SCAT != null then --CAT != null
3.2
3.3
ALL
{data($catname)}=null although {data($scatname)}='{data($catvalue)}' is not null
]]>
When --SCAT != null then --CAT != null
3.2
3.3
ALL
{data($catname)}=null although {data($scatname)}='{data($catvalue)}' is not null
]]>
When --SCAT present in dataset then --CAT present in dataset
3.2
3.3
ALL
{data($catname)} variable is not present, whereas {data($scatname)} variable is present
]]>
When --SCAT present in dataset then --CAT present in dataset
3.2
3.3
ALL
{data($catname)} variable is not present, whereas {data($scatname)} variable is present
]]>
When AGE != null or AGETXT != null then AGEU != null
3.2
3.3
DM
AGEU=null, although AGE or AGETXT is populated
]]>
When AGEU != null and AGETXT = null then AGE != null
3.2
3.3
DM
AGE=null where AGETXT=null and AGEU='{data($ageuvalue)}' is not null
]]>
When AGEU != null and AGE = null then AGETXT != null
3.2
3.3
DM
AGETXT=null where AGE=null and AGEU='{data($ageuvalue)}' is not null
]]>
When DTHDTC != null then DTHFL = 'Y'
3.2
3.3
DM
DTHFL='{data($dthflvalue)}' instead of 'Y', although DTHDTC='{data($dthflvalue)}' is not null
]]>
--SOC = MedDRA primary system organ class
AE
3.2
3.3
MH
CE
Value '{data($socvalue)}' for {data($socname)} is not found as a MedDRA primary system organ class in the MedDRA dictionary
]]>
--SOCCD = MedDRA primary system organ class code
3.2
3.3
AE
MH
CE
Value '{data($soccdvalue)}' for {data($soccdname)} was not found in the MedDRA dictionary
]]>
When TSPARMCD = 'AGEMIN' and TSVAL != null then TSVAL conforms to ISO 8601 time period
3.2
3.3
TS
Invalid (non-ISO8601) TSVAL value={data($tsvalvalue)} for AGEMIN in dataset TS
]]>
When TSPARMCD = 'LENGTH' and TSVAL != null then TSVAL conforms to ISO 8601 time period
3.2
3.3
TS
Invalid (non-ISO8601) TSVAL value={data($tsvalvalue)} for LENGTH in dataset TS
]]>
When TSPARMCD = 'PLANSUB' then TSVAL is integer and > 0
3.2
3.3
TS
0 :)
xquery version "3.0";
declare namespace def = "http://www.cdisc.org/ns/def/v2.0";
declare namespace def21 = "http://www.cdisc.org/ns/def/v2.1";
declare namespace odm="http://www.cdisc.org/ns/odm/v1.3";
declare namespace data="http://www.cdisc.org/ns/Dataset-XML/v1.0";
declare namespace xlink="http://www.w3.org/1999/xlink";
declare namespace xs="http://www.w3.org/2001/XMLSchema";
(: "declare variable ... external" allows to pass $base and $define from an external programm :)
declare variable $base external;
declare variable $define external;
declare variable $defineversion external;
(: let $base := '/db/fda_submissions/cdisc01/' :)
(: let $define := 'define2-0-0-example-sdtm.xml' :)
let $definedoc := doc(concat($base,$define))
(: get the TS dataset :)
let $tsdataset := $definedoc//odm:ItemGroupDef[@Name='TS']
let $tsdatasetname := (
if($defineversion='2.1') then $tsdataset/def21:leaf/@xlink:href
else $tsdataset/def:leaf/@xlink:href
)
let $tsdatasetdoc := (
if($tsdatasetname) then doc(concat($base,$tsdatasetname))
else ()
)
(: get the OID of the TSPARMCD variable and of the TSVAL variable :)
let $tsparmcdoid := (
for $a in $definedoc//odm:ItemDef[@Name='TSPARMCD']/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
let $tsvaloid := (
for $a in $definedoc//odm:ItemDef[@Name='TSVAL']/@OID
where $a = $definedoc//odm:ItemGroupDef[@Name='TS']/odm:ItemRef/@ItemOID
return $a
)
(: get the record in the TS dataset that has TSPARMCD=PLANSUB :)
let $tsageminrecord := $tsdatasetdoc//odm:ItemGroupData[odm:ItemData[@ItemOID=$tsparmcdoid and @Value='PLANSUB']]
let $tsageminrecordnumber := $tsageminrecord/@data:ItemGroupDataSeq
(: and get the TSVAL value :)
let $tsvalvalue := $tsageminrecord/odm:ItemData[@ItemOID=$tsvaloid]/@Value
(: TSVAL must be an integer and larger then 0 :)
where $tsvalvalue and not($tsvalvalue castable as xs:integer) and xs:integer($tsvalvalue) > 0
return Invalid TSVAL value for PLANSUB, value={data($tsvalvalue)} in dataset TS
]]>
When TSPARMCD = 'STOPRULE' then TSVAL != null
3.2
3.3
TS
Value for TSVAL with TSPARMCD='STOPRULE' is null in Trial Summary Parameter in dataset TS
]]>
When TSPARMCD = 'CURTRT' then TSVAL is a valid preferred term from FDA Substance Registration System (SRS)
3.2
3.3
TS
Invalid TSVAL value='{data($tsvalvalue)}' for TSPARMCD=CURTRT in dataset TS
]]>
When TSPARMCD = 'CURTRT' then TSVALCD is a valid unique ingredient identifier from FDA Substance Registration System (SRS)
3.2
3.3
TS
Invalid TSVALCD value={data($tsvalcdvalue)} for TSPARMCD='CURTRT'
]]>
When TSPARMCD in ('COMPTRT', 'CURTRT', 'TRT') then TSVCDREF = 'UNII'
3.2
3.3
TS
Invalid TSVCDREF value='{data($tsvcdrefvalue)}' for TSPARMCD={data($tsparmcdvalue)} in dataset TS. The value must be 'UNII'
]]>
When TSPARMCD = 'COMPTRT' then TSVAL is a valid preferred term from FDA Substance Registration System (SRS)
3.2
3.3
TS
Invalid TSVAL value={data($tsvalvalue)} for TSPARMCD=COMPTRT in dataset TS
]]>
when TSPARMCD = 'COMPTRT' then TSVALCD is a valid unique ingredient identifier from FDA Substance Registration System (SRS)
3.2
3.3
TS
Invalid TSVALCD value={data($tsvalcdvalue)} for TSPARMCD=COMPTRT in dataset TS
]]>
When TSPARMCD in ('INDIC', 'TDIGRP') then TSVALCD is a valid concept identifier from SNOMED CT
3.2
3.3
TS
Invalid TSVALCD, value={data($tsvalcdvalue)} for TSPARMCD=INDIC is not a valid SNOMED-CT code in dataset TS
]]>
When TSPARMCD = 'TRT' then TSVAL is a valid preferred term from FDA Substance Registration System (SRS)
3.2
3.3
TS
{data($tsval)} :)
(: now invoke the web service to check whether it is a valid UNII, e.g.:
https://dailymed.nlm.nih.gov/dailymed/services/v2/uniis.xml?active_moiety=NAFARELIN
:)
(: ONLY FOR TESTING :)
(: let $tsval := 'NAFARELINx' :)
let $webservice := concat('https://dailymed.nlm.nih.gov/dailymed/services/v2/uniis.xml?active_moiety=',$tsval)
let $webserviceresult := doc($webservice)
(: this is a document with the structure /uniis/unii/unii_code - it is an invalid code when there is no such an unii element :)
let $count := count($webserviceresult/uniis/unii/unii_code)
where $count = 0
return Invalid TSVAL value for TRT in dataset {data($tsdatasetname)}: TSVAL='{data($tsval)}' for TRT record must be a valid preferred term from FDA Substance Registration System (SRS): value '{data($tsval)}' is not a valid preferred term identifier from SRS
]]>
When TSPARMCD = 'TRT' then TSVALCD is a valid unique ingredient identifier from FDA Substance Registration System (SRS)
3.2
3.3
TS
Invalid TSVALCD value for TRT in dataset {data($tsdatasetname)}: TSVALCD for TRT record must be a valid unique ingredient identifier from FDA Substance Registration System (SRS): value '{data($tsval)}' is not a valid unique ingredient identifier from SRS
]]>
When TSPARMCD = 'PCLAS' then TSVAL is a valid term from NDF-RT
3.2
3.3
TS
Invalid TSVAL value for PCLASS in dataset {data($tsdatasetname)}: TSVAL for TSPARMCD=PCLAS record must be a valid a valid term from NDF-RT: value '{data($tsval)}' was found
]]>
When TSPARMCD = 'PCLAS' then TSVALCD is a valid code from NDF-RT
3.2
3.3
TS
Invalid TSVALCD value={data($tsvalcdvalue)} for TSPARMCD=PCLAS
]]>
When TSPARMCD = 'PCLAS' then TSVCDREF = 'NDF-RT'
3.2
3.3
TS
Invalid TSVCDREF value={data($tsvcdrefvalue)} for TSPARMCD=PCLAS. The value must be 'NDF-RT'
]]>
When TSPARMCD = 'FCNTRY' then TSVCDREF = 'ISO 3166-1 alpha-3'
3.2
3.3
TS
Invalid TSVCDREF value={data($tsvcdrefvalue)} for FCNTRY. The value must be 'ISO 3166-1 alpha-3'
]]>
When TSPARMCD = 'ACTSUB' then TSVAL is integer and > 0
3.2
3.3
TS
0 :)
where $actsubrecord and not($actsubval castable as xs:integer and xs:integer($actsubval) > 0)
return Invalid TSVAL value '{data($actsubval)}' for Trial Summary Parameter TSPARMCD=ACTSUB in dataset {data($tsdatasetname)}
]]>
When TSPARMCD in ('INDIC', 'TDIGRP') then TSVCDREF = 'SNOMED'
3.2
3.3
TS
Invalid TSVCDREF value '{data($tsvcdrefvalue)}' for Trial Summary Parameter TSPARMCD={data($tsparmcdvalue)} in dataset {data($tsdatasetname)} - A value 'SNOMED' is expected
]]>
When TSVAL not populated with values or synonyms of values in the ISO 21090 null flavor codelist (or other terms that can be represented as null flavors)
then TSVALNF = null
3.2
3.3
TS
0
return TSVALNF='{data($tsvalnf)}' must be null as TSVAL='{data($tsval)}' is not an ISO-21090 null flavor
]]>
When ECDOSE = null then ECDOSTXT != null
3.2
3.3
EC
ECDOSTXT must be populated when ECDOSE=null
]]>
When custom domain present in study then --TRT present in dataset
3.2
3.3
INTERVENTIONS
{data($varname)} is not present in FINDINGS custom domain {data($name)}
]]>
When Custom domain present in study then --TERM present in dataset
3.2
3.3
EVENTS
{data($varname)} is not present in EVENTS custom domain {data($name)}
]]>
When IDVARVAL = null then IDVARVAL = null
3.2
3.3
CO
SUPPQUAL
0
return IDVARVAL='{data($idvarval)}' must be null as IDVAR=null
]]>
--STDTC not present
3.2
3.3
FINDINGS
Variable {data($stdtcname)} is not allowed to be present in the FINDINGS dataset {data($name)}
]]>
When --TPT present in dataset then --TPTNUM present in dataset
3.2
3.3
ALL
Variable {data($tptnumname)} is absent although {data($tptname)} is present
]]>
When MIDS present in dataset, then TM must exist
3.3
ALL
No TM dataset was declared or found although dataset {data($name)} has a MIDS variable
]]>
When RELMIDS present in dataset, then TM must exist
3.3
ALL
No TM dataset was declared or found although dataset {data($name)} has a RELMIDS variable
]]>
When MIDSDTC present in dataset, then MIDS present in dataset
3.3
ALL
No MIDS variable is present although MIDSDTC is present
]]>
--USCHFL not present in dataset
3.3
ALL
Variable {data($uschflname)} is not allowed to be used in SDTM submissions
]]>
--IMPLBL not present in dataset
3.3
ALL
Variable {data($implblname)} is not allowed to be used in SDTM submissions
]]>
FETUSID not present in dataset
3.3
ALL
Variable {data($fetusidname)} is not allowed to be used in SDTM submissions
]]>
--NOMDY not present in dataset
3.3
ALL
Variable {data($nomdyname)} is not allowed to be used in SDTM submissions
]]>
--NOMDY not present in dataset
3.3
ALL
Variable {data($nomlblname)} is not allowed to be used in SDTM submissions
]]>
When ACTARMCD = null, then ARMNRS^=null
3.3
DM
ARMNRS is not populated although ACTARMCD is null
]]>
When ACTARMCD = null, then ARMNRS^=null
3.3
DM
ARMNRS is not populated although ACTARM is null
]]>
When ARMCD = null, then ARMNRS^=null
3.3
DM
ARMNRS is not populated although ARMCD is null
]]>
When ARM = null, then ARMNRS^=null
3.3
DM
ARMNRS is not populated although ARM is null
]]>
When ARMCD ^=null and ACTARMCD ^=null, then ARMNRS = null
3.3
DM
ARMNRS is populated although values for ACTARMCD '{}' and ACTARM '' are not null
]]>
When ARMCD = null, then ARM is null
3.3
DM
ARM has value '{data($armvalue)}' although ARMCD is null
]]>
When ACTARMCD = null, then ACTARM = null
3.3
DM
ACTARM has value '{data($actarmvalue)}' although ACTARMCD is null
]]>
ADSL Population flags (COMPLT; FULLSET; ITT; PPROT; and SAFETY) not in SUPPDM
3.3
SUPPDM
Supplemental qualifier '{data($qnamvalue)}' is not allowed to be used in SUPPDM
]]>
When ARM ^= null, then RFENDTC ^= null
3.3
SUPPDM
RFENDTC=null for ARM='{data($armvalue)}'. RFENDTC is required for all randomized subjects.
]]>
When ARMNRS ^= null, then RFENDTC = null
3.3
SUPPDM
RFENDTC='{data($rfendtcvalue)}' may not be populated for ARMNRS='{data($armnrsvalue)}'
]]>
When RACE = MULTIPLE, then Multiple SUPPDM.QNAM records exist for the subject
3.3
DM
SUPPDM
Only {data($suppdmcountrace)} 'RACE' records for USUBJID='{data($usubjid)}' have been found in SUPPDM for DM.RACE='{data($race)}'
]]>
RPATHCD may not be present in DM
3.3
DM
SUPPDM
Variable RPATHCD in DM is not allowed to be used in SDTM submissions
]]>
When Subject has more than one record per Epoch with DSCAT = 'DISPOSITION EVENT', then DSSCAT must be present
3.3
DS
1,
and DSSCAT is absent or not populated :)
where $epochvalue and $numercordsingroup > 1 and not($dsscatvalue)
return DSSCAT is not populated although there are {data($numercordsingroup)} records with the same value of USUBJID = '{data($usubjidvalue)}' and EPOCH='{data($epochvalue)}' and DSCAT='DISPOSITION EVENT'
]]>
When DSCAT = 'DISPOSITION EVENT' and DSSCAT is present in dataset,
then At most one record per subject per DSSCAT per epoch, including null values of DSSCAT
3.3
DS
1
return There is more than 1 record ({count($group/odm:ItemGroupData)} records found) for the combination of USUBJID='{data($group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$usubjidoid]/@Value)}', DSSCAT='{data($group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$dsscatoid]/@Value)}' and EPOCH='{data($group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$epochoid]/@Value)}'
]]>
When DSCAT = 'DISPOSITION EVENT' and DSSCAT is present in dataset,
then At most one record per subject per DSSCAT per epoch, including null values of DSSCAT
3.3
DS
1
return There is more than 1 record ({count($group/odm:ItemGroupData)} records found) for the combination of USUBJID='{data($group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$usubjidoid]/@Value)}' and EPOCH='{data($group/odm:ItemGroupData[1]/odm:ItemData[@ItemOID=$epochoid]/@Value)}'
]]>
When Subject has more than one record per Epoch with DSCAT = 'DISPOSITION EVENT',
then No more than 1 record per subject has DSSCAT = 'STUDY PARTICIPATION'
3.3
DS
1 and $countstudyparticipationrecords > 1
return There is more than 1 record ({$countstudyparticipationrecords} records were found) with DSSCAT='STUDY PARTICIPATION' although there are more than 1 records with the same value for EPOCH='{data($epochvalue)}' and DSCAT='DISPOSITION EVENT' for subject with USUBJID='{data($usubjidvalue)}'
]]>
When Subject has more than one record per Epoch with DSCAT = 'DISPOSITION EVENT',
then No more than 1 record per subject has DSSCAT = 'STUDY TREATMENT'
3.3
DS
1 and $countstudytreatmentrecords > 1
return There is more than 1 record ({$countstudytreatmentrecords} records were found) with DSSCAT='STUDY TREATMENT' although there are more than 1 records with the same value for EPOCH='{data($epochvalue)}' and DSCAT='DISPOSITION EVENT' for subject with USUBJID='{data($usubjidvalue)}'
]]>
When DM.ACTARMCD not null, then DS records present for subject
3.3
DS
No DS records were found for DM subject with USUBJID='{data($dmusubjidvalue)}' with ACTARMCD='{data($actarmcdvalue)}'
]]>
--LOBXFL Value must be 'Y' or null
3.3
FINDINGS
Value of {data($lobxflname)} is not null or 'Y' - value {data($lobxflvalue)}
]]>
--BEATNO not present in dataset
3.3
FINDINGS
Variable {data($beatnoname)} is not allowed to be used in SDTM submissions
]]>
--SEQ is in consistent chronological order
3.2
SE
PR
Records for subject with USUBJID={data($usubjidvalue)} are not in correct chronological order by {data($seqname)} and {data($stdtcname)}
]]>
--SEQ is in consistent chronological order
3.3
SE
PR
SM
Records for subject with USUBJID={data($usubjidvalue)} are not in correct chronological order by {data($seqname)} and {data($stdtcname)}
]]>
When MIDSTYPE = TM.MIDSTYPE and TM.TMRPT = 'N', then MIDSTYPE is unique per subject
3.3
SM
1 :)
where functx:is-value-in-sequence($smmidstypevalue,$tmmidstypevalues) and $count > 1
return More than 1 MIDSTYPE='{data($smmidstypevalue)}' record was found for subject with USUBJID='{data($usubjidvalue)}' although the MIDSTYPE was declared as non-repeating in TM
]]>
When MIDSTYPE = TM.MIDSTYPE and TM.TMRPT = 'Y',
then MIDS is suffixed with a sequence number in consistent chronological order
3.3
SM
Value of MIDS='{data($smmidsvalue)}' for MIDSTYPE='{data($smmidstypevalue)}' does not end with a number although the MIDSTYPE was declared as repeating in TM
]]>
SMSTDTC ^= null
3.3
SM
Value for MSSTDTC is missing
]]>
When --ORREF ^= null or --DRVFL='Y', then --STREFC ^= null
3.3
FINDINGS
{data($strefcname)} is not populated although {data($orrefname)} is populated or {data($drvflname)}='Y'
]]>
When --STREFC ^= null, then --STREFN ^= null
3.3
FINDINGS
{data($strefnname)} is not populated although {data($strefcname)} is populated
]]>
--RESLOC not present in dataset
3.3
FINDINGS
Variable {data($reslocname)} is not allowed to be used in SDTM submissions
]]>
When --LOBXFL = 'Y' and --DRVFL is null, then --ORRES ^= null
3.3
FINDINGS
{data($orresname)} is not populated although {data($lobxflname)}='Y' and {data($drvflname)} is null
]]>
When --LOBXFL = 'Y' and --DRVFL is null, then --ORRES ^= null
3.3
FINDINGS
{data($orresname)} is not populated although {data($lobxflname)}='Y' and {data($drvflname)} is null
]]>
STUDYID, DOMAIN, and --SEQ exist and at least one of USUBJID, APID, SPDEVID, or POOLID
3.3
FINDINGS
None of USUBJID, APID, SPDEVID, POOLID are present although STUDYID, DOMAIN and {data($seqname)} are present
]]>
When PEORRES = null, then PESTRESC = null
3.3
PE
PEORRES is null, but PESTRESC is not null: '{data($pestrescvalue)}' was found
]]>
--AGENT not present in dataset
3.3
FINDINGS, except MS
Variable {data($agentname)} is not allowed to be used in SDTM submissions
]]>
--CONC not present in dataset
3.3
FINDINGS, except MS
Variable {data($concname)} is not allowed to be used in SDTM submissions
]]>
--CONC not present in dataset
3.3
FINDINGS, except MS
Variable {data($concuname)} is not allowed to be used in SDTM submissions
]]>
--EVDTYP not present in dataset
3.3
EVENTS, except for MH
Variable {data($evdtypname)} is not allowed to be used in SDTM submissions
]]>
EXMETHOD not present in dataset
3.3
EX
Variable EXMETHOD is not allowed to be used in dataset {data($name)}
]]>
When --LOBXFL = 'Y', then --STRESC ^= null
3.3
FINDINGS
{data($strescname)} is not populated although {data($lobxflname)}='Y'
]]>
When --LOBXFL = 'Y', then --STRESC ^= null
3.3
FINDINGS
{data($strescname)} is not populated although {data($lobxflname)}='Y'
]]>
ARM not in ('Screen Failure', 'Not Assigned', 'Unplanned Treatment', 'Not Treated')
3.3
DM
TA
TV
Value '{data($arm)}' is not allowed for variable ARM
]]>
When VISITNUM present in dataset and --TPTREF not present in dataset,
then --TPT and --TPTNUM have a one-to-one relationship per unique values of VISITNUM
3.2
3.3
ALL
Inconsistent value for --TPT within --TPTNUM in dataset {data($name)}. {data($tptvaluescount)} different --TPT values were found for --TPTNUM={data($uniquetptnumvalue)} within VISITNUM='{data($visitnumvalue)}' and USUBJID='{data($usubjidvalue)}'
]]>
When VISITNUM present in dataset and --TPTREF not present in dataset,
then --TPT and --TPTNUM have a one-to-one relationship per unique values of VISITNUM
3.2
3.3
ALL
Inconsistent value for --TPT within --TPTNUM in dataset {data($name)}. {data($tptvaluescount)} different --TPT values were found for --TPTNUM={data($uniquetptnumvalue)} within VISITNUM='{data($visitnumvalue)}' and USUBJID='{data($usubjidvalue)}'
]]>
When VISITNUM not present in dataset and --TPTREF present in dataset,
then --TPT and --TPTNUM have a one-to-one relationship per unique values of --TPTREF
3.2
3.3ALL
Inconsistent value for --TPT within --TPTNUM in dataset {data($name)}. {data($tptvaluescount)} different --TPT values were found for --TPTNUM={data($uniquetptnumvalue)} within {data($tptrefname)}='{data($tptrefvalue)}' and USUBJID='{data($usubjidvalue)}'
]]>
When VISITNUM not present in dataset and --TPTREF present in dataset,
then --TPT and --TPTNUM have a one-to-one relationship per unique values of --TPTREF
3.2
3.3
ALL
Inconsistent value for --TPT within --TPTNUM in dataset {data($name)}. {data($tptvaluescount)} different --TPT values were found for --TPTNUM={data($uniquetptnumvalue)} within {data($tptrefname)}='{data($tptrefvalue)}' and USUBJID='{data($usubjidvalue)}'
]]>
When VISITNUM and --TPTREF present in dataset,
then --TPT and --TPTNUM have a one-to-one relationship per unique combination of VISITNUM and –TPTREF values
3.2
3.3
ALL
Inconsistent value for --TPT within --TPTNUM in dataset {data($name)}. {data($tptvaluescount)} different --TPT values were found for --TPTNUM={data($uniquetptnumvalue)} within VISITNUM='{data($visitnumvalue)}' and {data($tptrefname)}='{data($tptrefvalue)}' and USUBJID='{data($usubjidvalue)}'
]]>
When VISITNUM and --TPTREF present in dataset,
then --TPT and --TPTNUM have a one-to-one relationship per unique combination of VISITNUM and –TPTREF values
3.2
3.3
ALL
Inconsistent value for --TPT within --TPTNUM in dataset {data($name)}. {data($tptvaluescount)} different --TPT values were found for --TPTNUM={data($uniquetptnumvalue)} within VISITNUM='{data($visitnumvalue)}' and {data($tptrefname)}='{data($tptrefvalue)}' and USUBJID='{data($usubjidvalue)}'
]]>