— Jozef Aerts 2008/12/31 11:36
Author: Jozef Aerts, XML4Pharma
Applicable to: ODM version 1.3
The CDISC ODM has very good support for update of data points and for audit trails. As such, CDISC ODM is fully 21 CFR Part 11 compliant.
When adding data points to the ClinicalData section in an ODM file, the default “transaction type” is “Insert”. Strictly spoken, this is only the case for “snapshot” data, as the ODM specification states that for transactional data, the “TransactionType” should be declared at the top level data element explicitely (section 2.9 of the specification). However, most implementations I have seen sofar, suppose that (even in transactional files), the data is of type “Insert”, unless stated otherwise.
This means that the following two ODM snippets are essentially equivalent:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001" TransactionType="Insert"> <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="1"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:02:00"/> <ItemData ItemOID="IT.SYSBP" Value="120"/> <ItemData ItemOID="IT.DIABP" Value="80"/> </ItemGroupData> </FormData> </StudyEventData> </SubjectData> </ClinicalData>
and:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001"> <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="1"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:02:00"/> <ItemData ItemOID="IT.SYSBP" Value="120"/> <ItemData ItemOID="IT.DIABP" Value="80"/> </ItemGroupData> </FormData> </StudyEventData> </SubjectData> </ClinicalData>
In the second snippet, “TransactionType” is not given, so “Insert” is applicable as the default.
It is important to understand that the value of “TransactionType” is inherited by the child elements of the element that contains the “TransactionType” attribute, unless overriden by another “TransactionType” attribute on one of the child elements. As such, in the snippet above, the transaction type of the StudyEventData is “Insert” (inherited), the transaction type of the FormData is “Insert” (inherited), as are the transaction types of the ItemGroup and all the ItemData elements.
We also see that in the above snippet, there is an ItemGroupRepeatKey on the ItemGroupData element, meaning that the measurements may be repeating. In such a case, in the metadata, the specific ItemGroup MUST have been defined as being repeating:
<ItemGroupDef OID="IG.VITALS" Name="Vital Signs" Repeating="Yes"> <ItemRef ... </ItemGroupDef>
An example may be:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001"> <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="1"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:02:00"/> <ItemData ItemOID="IT.SYSBP" Value="120"/> <ItemData ItemOID="IT.DIABP" Value="80"/> </ItemGroupData> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:12:00"/> <ItemData ItemOID="IT.SYSBP" Value="222"/> <ItemData ItemOID="IT.DIABP" Value="83"/> </ItemGroupData> </FormData> </StudyEventData> </SubjectData> </ClinicalData>
I.e. a first measurement was done (ItemGroupRepeatKey=“1”) at 10:02 a.m., and a second measurement was done at 10:12 a.m. (ItemGroupRepeatKey=“2”).
Remember that it is not mandatory that the value of a repeatkey is a numerical value, although it usually is.
Let us now suppose that an incorrect value was captured (already an idea which one?), and that an update must be made. Updates can only be made in “transactional” ODM files, meaning that the first line of the ODM file must explicitely state that the file is of type “transactional”, i.e.:
<ODM xmlns="http://www.cdisc.org/ns/odm/v1.3" FileType="Transactional" ...>
Now, we can add the correction to the data point in a transactional way:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001" TransactionType="Insert" <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="1"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:02:00"/> <ItemData ItemOID="IT.SYSBP" Value="120"/> <ItemData ItemOID="IT.DIABP" Value="80"/> </ItemGroupData> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:12:00"/> <ItemData ItemOID="IT.SYSBP" Value="222"/> <ItemData ItemOID="IT.DIABP" Value="83"/> </ItemGroupData> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2" TransactionType="Update"> <AuditRecord ...>...</AuditRecord> <ItemData ItemOID="IT.SYSBP" Value="112"/> </ItemGroupData> </FormData> </StudyEventData> </SubjectData> </ClinicalData>
One sees that (obvious in this case) the value for the systolic blood pressure was entered incorrectly (222), and needs to be replaced by a value “122”. It is shown how this CAN be done in the third instance of “ItemGroupData”. The latter has a “TransactionType” of “Update”, which overrides the inherited “Insert” at the level of “SubjectData”.
For the first two “ItemGroupData” (and the ItemData therein) the transaction type is “Insert” (inherited from the “SubjectData” element). The third instance has a transaction type that explicitely overrides the inherited one. Remark that for the third instance the value for the “ItemGroupRepeatKey” is “2”, as it are the data from the second measurement that need to be updated. If one would assign a new value for the “ItemGroupRepeatKey” (e.g.”3”) it would not be clear which data point needs to be updated. I.e. the “ItemGroupRepeatKey” is an identifier for the group of (repeating) records.
Some people have previously misinterpreted the statement (section 3.1.4.1.1.1.1) “This pair of values is unique within the parent form” as ItemGroupRepeatKeys must be unique within the parent FormData. This is however only the case when the transaction type is “Insert”.
So, the meaning of the snippet above is “insert two measurements with systolic blood pressures of 120 and 222 respectively into the receiving system/database” followed by “and replace the second value of 222 by the value 122”.
One may wonder why in the “update” ItemGroup, the diastolic blood pressure and the time of the measurement (which are correct and do not need to be updated) are not mentioned. The reason for this is the rule “Properties not mentioned are not modified” (section 2.9 of the specification).
Remark that there are several other ways to accomplish the same thing. For example:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001" TransactionType="Insert" <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="1"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:02:00"/> <ItemData ItemOID="IT.SYSBP" Value="120"/> <ItemData ItemOID="IT.DIABP" Value="80"/> </ItemGroupData> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:12:00"/> <ItemData ItemOID="IT.SYSBP" Value="222"/> <ItemData ItemOID="IT.DIABP" Value="83"/> </ItemGroupData> </FormData> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2" TransactionType="Update"> <AuditRecord ...>...</AuditRecord> <ItemData ItemOID="IT.SYSBP" Value="112"/> </ItemGroupData> </FormData> </StudyEventData> </SubjectData> </ClinicalData>
Do you see the difference? In the last example, the instruction to update has been put in a separate instance of “FormData”. In such a case, personally I would prefer to put the TransactionType=“Update” at the level of FormData, but there is no rule that says that this must be done so. Essentially, what is important is that at each level (SubjectData, StudyEventData, FormData, …) it is 100% clear what the transaction type is. This can be an inherited transaction type, or one that is explicitely set.
A practice we see a lot (and which makes sense) is to put updates of records in a separate “ClinicalData” section.
For example:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001" TransactionType="Insert" <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="1"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:02:00"/> <ItemData ItemOID="IT.SYSBP" Value="120"/> <ItemData ItemOID="IT.DIABP" Value="80"/> </ItemGroupData> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2"> <ItemData ItemOID="IT.MEASUREMENTTIME" Value="10:12:00"/> <ItemData ItemOID="IT.SYSBP" Value="222"/> <ItemData ItemOID="IT.DIABP" Value="83"/> </ItemGroupData> </FormData> </StudyEventData> </SubjectData> </ClinicalData> <!-- updates come here --> <ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> <SubjectData SubjectKey="SUBJ.001" TransactionType="Update" <StudyEventData StudyEventOID="SE.VISIT2"> <FormData FormOID="FO.VITALS"> <ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2"> <AuditRecord ...>...</AuditRecord> <ItemData ItemOID="IT.SYSBP" Value="112"/> </FormData> </StudyEventData> </SubjectData> </ClinicalData>
(Remark that the “Update” has been defined at the SubjectData level - it might in this case however, also have been set at the StudyEventData level, at the FormData level, at the ItemGroupData level, or even at the ItemData level)
There is no obligation however to start a new “ClinicalData” element for those records that are updates of previously inserted records. Everything depends on how the export from the system can best be organized.
If the “update” comes in a separate file (e.g. transmitted one day later), then there is of course no other choice than to start a new “ClinicalData” section, which can however e.g. also contain newly captured data points.
For archiving purposes however, my personal preference is to put updates of data points as close as possible to the original data point, as this makes inspection (e.g. using an ODM Viewer) considerably more easy.
There is one more very important point: data points that are updates MUST have an AuditRecord. This is necessary to be 21 CRF Part 11 compliant. An AuditRecord must contain at least the following information:
a) WHO changed the data point (user ID and location)?
b) WHEN (date and time stamp) was the change done?
Not imposed by the ODM standard, but surely good practice, is also WHY the data point was changed.
So a typical AuditRecord may be:
<ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2" TransactionType="Update"> <AuditRecord> <UserRef UserOID="USER.AERTS"/> <LocationRef LocationOID="LOC.SINGEN"/> <DateTimeStamp>2009-03-24T17:05:23+01:00"/> <ReasonForChange>The value of 222 for the systolic blood pressure was obviously incorrect. A query to the investigator revealed that the value must be 122.</ReasonForChange> </AuditRecord> <ItemData ItemOID="IT.SYSBP" Value="112"/> </ItemGroupData>
One may think that there is something weird about the way this is done: the auditrecord is added BEFORE the update of the datapoint and on the level of “ItemGroupData”, and not as some may expect at the level of “ItemData”. This is however not a problem, as (just like transaction types), audit records are inherited from the parent element. So the above is essentially identical to:
<ItemGroupData ItemGroupOID="IG.VITALS" ItemGroupRepeatKey="2"> <ItemData ItemOID="IT.SYSBP" Value="112" TransactionType="Update"> <AuditRecord> <UserRef UserOID="USER.AERTS"/> <LocationRef LocationOID="LOC.SINGEN"/> <DateTimeStamp>2009-03-24T17:05:23+01:00"/> <ReasonForChange>The value of 222 for the systolic blood pressure was obviously incorrect. A query to the investigator revealed that the value must be 122.</ReasonForChange> </AuditRecord> </ItemData> </ItemGroupData>
I hope this is not too confusing, but once you have understood how inheritence for “TransactionType” and “AuditRecord” works, it is “piece of cake”.
Some more information can optionally be added to audit records, and I always recommend to do so when possible:
- the “EditPoint” attribute defines the phase of data processing in which action occurred. It can have one of the following values: “Monitoring”, “DataManagement” or “DBAudit”. I presume it is clear what is meant here, so for more information, please have a look in the ODM specification (section 3.1.4.1.2). - the “UsedImputationMethod” attribute defines whether a method was used for the action for the change. This can be for example a method used during batch validation. - as of ODM 1.3, each AuditRecord can also have an “ID”. This is only used (and then mandatory) when all audit records are grouped into a single “AuditRecords” element (See “special topics” further on).
For example:
<AuditRecord EditPoint="Monitoring" UsedImputationMethod="No"> <UserRef UserOID="USER.AERTS"/> <LocationRef LocationOID="LOC.SINGEN"/> <DateTimeStamp>2009-03-24T17:05:23+01:00"/> <ReasonForChange>The value of 222 for the systolic blood pressure was obviously incorrect. A query to the investigator revealed that the value must be 122.</ReasonForChange> </AuditRecord>
TODO !!!
A new feature of ODM 1.3 (which is optional) is that audit records can be grouped:
<ClinicalData StudyOID="MyStudy" MetaDataVersionOID="MV.001"> ... <ItemDataInteger ItemOID="IT.SYSBP" TransactionType="Update" AuditRecordID="ID.123456789">122</ItemDataInteger> ... <AuditRecords> <AuditRecord ID="ID.123456789"> <UserRef UserOID="USER.AERTS"/> <LocationRef LocationOID="LOC.SINGEN"/> <DateTimeStamp>2009-03-24T17:05:23+01:00"/> <ReasonForChange>The value of 222 for the systolic blood pressure was obviously incorrect. A query to the investigator revealed that the value must be 122.</ReasonForChange> </AuditRecord> </AuditRecords> </ClinicalData>
We must remark here that this may only be used when one uses “typed” ItemData, i.e. “ItemDataString”, “ItemDataFloat”, “ItemDataDate” etc.. (see section 2.5.1.1 of the ODM specification).
Personally, I am not a great fan of “typed” ItemData, as it only moves the problem of data type correctness from one level to another. Also, I have not seen (m)any ODM 1.3 files yet using the typed ItemData mechanism, so in my own work I stick to the “classic” ItemData element, until proven that the new mechanism (which, once again, is optional) really works better.