Implantable device list - 2015 ONC MU3 a14

Hi all, if no one else is currently working on item a14 “Implantable device list” I might take a first pass at it.

To get the conversation started:
Here is the list of Meaningful Use 3 Certification Criteria and test procedures:
https://www.healthit.gov/topic/certification-ehrs/2015-edition-test-method

For A14 Specifically they say:

  • A Unique Device Identifier (UDI) is a unique numeric or alphanumeric code that consists of two parts: (1) a device identifier (DI), a mandatory, fixed portion of a UDI that identifies the labeler and the specific version or model of a device, and (2) a production identifier (PI), a conditional, variable portion of a UDI that identifies one or more of the following when included on the label of a device: the lot or batch number within which a device was manufactured; the serial number of a specific device; the expiration date of a specific device; the date a specific device was manufactured; the distinct identification code required by 21 CFR 1271.290(c) for a human cell, tissue, or cellular and tissue-based product (HCT/P) regulated as a device. 21 CFR 801.3. See also Unique Device Identification System (UDI System) | FDA
  • The health IT must be able to satisfy this criterion using the three UDI formats from the three issuing agencies. The allowable issuing agency formats and sample labels are available at: Unique Device Identification System (UDI System) | FDA
  • The National Library of Medicine has made available a number of application program interfaces (APIs) to assist with the parsing of UDIs, to access GUDID data elements, and other functions related to UDIs. These APIs are available at: AccessGUDID - Resources Home. Note that if a developer certifies to this certification criterion using any of the NLM APIs to perform the function(s) required by this certification criterion, the developer must list the(se) API(s) as relied-upon software.
  • FDA accredits organizations to be issuing agencies for assignment of UDIs through an application process. The list of FDA-accredited agencies and the dates of their accreditations are listed at: http://www.fda.gov/MedicalDevices/DeviceRegulationandGuidance/UniqueDeviceIdentification/UDIIssuingAgencies/default.htm
  • Paragraphs (a)(14)(i) and (a)(14)(ii)(A) may or may not be separate steps. The certified health IT must be able to record the UDIs for a patient. By record, we mean that the software must store the UDI in the patient’s record, whence it can later be retrieved by a user and displayed and used according to paragraphs (a)(14)(ii)-(vi). If the developer chooses to meet the recording requirement by parsing the UDI and storing it in that parsed state, then paragraph (a)(14)(ii)(A) could be demonstrated while demonstrating paragraph (a)(14)(i). By contrast, if the developer chooses to record the UDI in its unparsed state, it would have to separately demonstrate that it can parse the UDI as needed to perform the capabilities described in paragraphs (a)(14)(ii)-(vi).
  • ONC refers to FDA for best practices. For further recommendations on best practices for UDI downloads, please consult the FDA support resources for guidance: AccessGUDID - Help Home
  • Implantable Medical Devices are entered in a Procedure Activity Procedure section of the C-CDA 2.1. This section requires that a medical device is entered as an entry underneath the procedure it is related to. Thus, in the optimal case, the procedure is coded as the device is implanted or removed. In this case, ideal coding would include the detailed code that accurately reflects the procedure performed. However, for various reasons, it may not be possible at the time of the procedure or when coding a device after the actual procedure occurred to obtain a granular code reflecting the specific procedure performed. In this case, it is appropriate to either use a generic code or a null value to enter the UDI.
  • There are some examples on the C-CDA Examples Task Force webpage, available at: [Implant Without ProcedureWeb Site Disclaimers](Implant Without Procedure), that highlight for implementers how the information may be coded.

The test procedure looks like it’s a “self declaration document” reviewed by a tester.

There are 3 UDI issuing agencies as of this post.
" GS1, Health Industry Business
Communications Council (HIBCC), and International Council for Commonality in Blood Banking
Automation (ICCBBA). Each issuing agency has a unique unique device identifier (UDI) format that was
reviewed and approved by FDA as part of the its process for accrediting issuing agencies. Any changes
to the format of the UDI by an issuing agency must be approved by FDA before implementation.
"
The below linked document gives information about the format and composition by each of the three UDI agencies.
https://www.fda.gov/media/96648/download
image

The FDA maintains a databases of these UDIs called Global Unique Device Identification Database.(GUIDID)
It is updated daily and searchable here:

A database is updated daily and available for download here:

1 Like

Some more information on the GUDID database.

A new full release is created the first of every month. As of January 2020 the zip file is 227 MB with 2,398,867 device identifier records.

When you open the zip file you’ll find that the database is composed of .xml files. As of 1/1/20’s release there are 96 files in the latest release.

The naming convention is as follows.(Using file 31 as an example. The single digits do not use a leading zero e.g 1 vs 01)
FULLDownload_Part31_Of_96_2020-01-01.xml

Next let’s take a look at the file structure. I’m using Notepadd++ as my xml viewer.

1 Like

Instead of doing downloading the xml zip file, AccessGUDID offers “Delimited files” as well.

Available here is a zip file containing some txt files where the data is deliminated(separated) by the pipe character “|”

So you’ll want to download the zip file for the latest or next to latest release.
AccessGUDID_Delimited_Full_Release_20200101.zip

Then copy it to wherever your sql data is stored. If using xampp you’ll want to paste it in

C:/xampp/mysql/data/openemr

Using winzip or zip or similar slect extract to a folder within the openemr folder, I called mine AccessGUDID_Delimited_Full_Release_20200101 by default.

The files have the following names and column names

contacts.txt
PrimaryDI|phone|phoneExtension|email

device.txt
PrimaryDI|publicDeviceRecordKey|publicVersionStatus|deviceRecordStatus|publicVersionNumber|publicVersionDate|devicePublishDate|deviceCommDistributionEndDate|deviceCommDistributionStatus|brandName|versionModelNumber|catalogNumber|dunsNumber|companyName|deviceCount|deviceDescription|DMExempt|premarketExempt|deviceHCTP|deviceKit|deviceCombinationProduct|singleUse|lotBatch|serialNumber|manufacturingDate|expirationDate|donationIdNumber|labeledContainsNRL|labeledNoNRL|MRISafetyStatus|rx|otc|deviceSterile|sterilizationPriorToUse

deviceSizes.txt
PrimaryDI|sizeType|size (Unit)|size (Value)|sizeText

environmentalConditions.txt
PrimaryDI|storageHandlingType|storageHandlingHigh (Unit)|storageHandlingHigh (Value)|storageHandlingLow (Unit)|storageHandlingLow (Value)|storageHandlingSpecialConditionText

gmdnTerms.txt
This file is 1.3 Gigabytes, it’s too large for notepad and several other text editors to open.
PrimaryDI|gmdnPTName|gmdnPTDefinition

identifiers.txt
PrimaryDI|deviceId|deviceIdType|deviceIdIssuingAgency|containsDINumber|pkgQuantity|pkgDiscontinuedate|pkgStatus|pkgType

premarketSubmissions.txt
PrimaryDI|submissionNumber|supplementNumber

productCodes.txt
PrimaryDI|productCode|productCodeName

sterilizationMethodTypes.txt
PrimaryDI|sterilizationMethod

Luckily for us, AccesGUDID provides a some information on how to import the data into several database systems.

Let’s take a look at the MySQL load data script.

First download the import scripts.

In this zip file it contains 4 folders. Bad, data, discard, and mysql.

mysql’s folder has these scripts and a bat file to run them on a windows machine and .sh for linux.
image

The data folder contains these files.
image

They follow the same pattern as the delimited files but appear to be significantly shortened. I suppose to test that the data loader is working. You can probably delete these.

I think you want to put the mysql folder scripts into the xampp/mysql folder.

I had you put the delimited files into the data directory’s openemr folder but the webpage says to use the data directory.

Next open the “runner script” with the appropriate extension for your system using your editor of choice. It should now be in your mysql folder.
I’m using visual studio code… Chose the .bat file for windows .sh for other.

Here’s an example of parameters you might fill out.

You can find your mysql database information in xampp by clicking on MySQL, config, and selecting the file my.ini.

If you’re running openemr locally you’ll want to give the ip address 127.0.0.1. The database name is openemr.

It looks like this script is hardcoded to look for the data files to import in the folder above the one the .bat file is in, inside a folder called “data”. So I made a new folder in C:/xampp, called it “data” and copied the flat .txt files into it.

If you run into any issues, look in the log file described. The data is loaded into tables within the openemr database preceeded by the characteres “ag_”

I noticed that when it populates the ag_gmdn_terms table it’s defined as being a varchar(2000) data field but the definition appears to get cut off in the phpmyadmin view.

I notice running queries on a primary_di (product ID code) it’s pretty slow. Maybe try setting it to an index or primary key. Can be done manually in PHPmyAdmin structure tab by clicking the icon that looks like a gold key or with the following command:
“ALTER TABLE ag_gmdn_terms ADD PRIMARY KEY(primary_di);”

or this one:
“ALTER TABLE ag_gmdn_terms ADD INDEX(primary_di);”

When I tried to set primary key in ag_premarket_solutions I got the below duplicate error.
image

image

So for this table in particular, maybe try setting primary_di to index instead of primary key.

So initially I had thought to write code in PHP directly to pull out the device identifier and link people to the device page on accessgudid.nlm.nih.gov. You need only put the DI in a link like so:
AccessGUDID - DEVICE: G-aenial™ (D0470046351)

and it brings you to a nice interface that allows you to see all the device characteristics and expand and collapse aspects of the device that you do and don’t need.

Then I realized there is a REST API that can parse the fields from the larger UDI string for you.

For example if you paste the below into your broswer:
https://accessgudid.nlm.nih.gov/api/v2/parse_udi.json?udi=%3D%2FA9999XYZ100T0944%3D%2C000025%3DA99971312345600%3D>014032%3D}013032%26%2C1000000000000XYZ123

you get the below back:

Note that before sending the raw UDI to the API you’ll need to do something called percent-encoding.

In a nut shell, you need to replace reserved characters with the substitute defined in the standard.

2 Likes

Hi Rachel,
Have you decided how you’re going to record any devices in patient chart yet? I would agree that using the API’s available is the best approach. You could do the lookup on demand if needed by user as/for education or if we wanted to keep offline access as important(my recommend) one could download the device synopsis and store with the device Id as part of that record.
Still, we’d probably want to have the facility to look up device on demand as a option to a provider that may be doing research.
If help is needed with API calls and parsing let me know. I’ve been known to do this sort of thing…

Hi Jerry,

My current thinking is to add implantable devices as an “issue”.

But then again we could maybe add a field to the add/edit surgery menu.

The standard wants a device to be added and removed via the interface and associated with a “procedure”

Which is complicated by the fact that the ICCBBA doesn’t just track medical devices but things like blood transfusions.

Whatever we end up going with I was thinking that a list of common procedures/device implantation or removal could be managed with the list editor. Then in the coding column they could add a snomed or ICD procedure code or CPT if they pay the licensing fee to import.

1 Like

Yep, sounds like you’ve done your homework. I believe you’re on the right track with issues where i’d opt more towards surgeries or even a new category for devices. Here we need to be versable enough to allow recording for patient history yet allow for any encounters that may affect status. i.e a device status may not have a start/end date but when installed :slight_smile: I mean implanted/removed or even adjustments in case of pacemakers (I think they still do that) etc. Maybe I overthink this. A clinians advise would be handy here. @brady.miller @robert.down.

I believe it simpler to allow a device search to let user select appropriate device id if not known. Same for coding which I think there could be more than one procedure code appropriate for any given device depending on diagnosis and factors requiring device. This i’m pretty confident should be left to the user and the clinical setting. Trying to predict a list of canned devices appropriate/universal across many disciplines or causing an update to the list at data entry would be annoying no doubt.
The device search would be similar to a code search except we’d utilise available device API and present as a drill down to user. This way we need not maintain anything but patient device information.
For this part I know I can be of help if needed.

Otherwise, without any direction/input from clinians concerning workflow, my engineering tact would be a new devices category form handled as current issues in regard to allowing attachment to encounters if necessary. Also this is easier to maintain for reporting purposes.

Wouldn’t ‘Procedures’ set of structures be better for this? Conceptually

  1. add/import a device category (similar to adding a type of test)
  2. Define what attributes you want to be recorded (similar to test items)
  3. Optionally define allowed values (similar to test item result values)

In practice clinician will place a order for the device and throughout the life of that device enter observations (similar to multiple result value sets received from labs) in procedure_order_results(?).

Essentially each device is a special lab that gets a single specimen (installation) and keeps sending multiple result sets ending with a ‘Final’ when it is removed or ceases to perform.

ps.
Current complicated code that implements procedures can now be simplified by using json field values. Ideally couchdb will be better as the external input values do not change but the structure can change frequently.

I am assuming the clinical aspects / diagnostics etc. are left part (1) of the 1:m relationship to devices. Someone may need 3 pacemakers over their life without any changes to ‘Issue’.

Hi,
I’d rec a separate Issue category (Implantable Devices;implantable_devices), which will work nicely for clinical flow and be a nice container for the entry. In the case with 3 different pacemakers, note each will have their own UID and would be separate entries (with the 2 removed ones being set to be Final or something equivalent). Note agree the ordering/querying of the device belongs in procedures, but I think the actual existence of the device should be stored in the new Issue category.
-brady

Perhaps if it is the user/provider performing device procedure. I’m not sure how a device is selected or ordered however, the procedure would become part of an ongoing encounter thread. The procedure itself would be selected from the practices custom compendium however that compendium item is designed. It may or may not have device info and because OpenEMR doesn’t control what that procedure looks like, we would never be assured the device is correctly tracked as part of patient history unless the device is recorded elsewhere.

This I would agree is best design. Having a separate form to record device where it is readily part of patient issues/history or attached to encounter history if needed. I only differ by naming the form Medical Devices because i’m thinking there could be wearable devices that are prescribed (A1c comes to mind). Hmm, are devices ordered via a prescription?

Anyway, i’m an engineer who relies on deductive reasoning and logic and not clinical experienced so correcting me if needed is appreciated.

Also it would behoove us to research how patient devices are transmitted in such as CCDA Continuity of Care Transfer of Care or Care Summary and especially FHIR for import by other EHRs and a look see at our import. This I will take as an action item and report back.

Lastly, I feel a closer look at how this is tested in certification can provide some clarity. A repost url to testing.

Hi @RachelEllison
I hope we are of some help in your endeavor. Ultimately, it is your project and how to proceed with the design is up to you . Just know you have resources in the community to help if needed.
Jerry

Sorry missed this key requirement which is very limited to just entering device data. We have ongoing issue about storing and analyzing output data streams from some devices so went off on tangent. When a new entity comes up, we defer to hl7v3 since our requirements usually are a tiny part of their solutions. Relevant section for medical devices shows superset of attributes needed. You can use json viewers like this to copy+paste json and see specific fields with their meaning. As you see this has little or no overlap to issues/lists.

For what it is worth, we implemented this some time ago (in a very elemental form) using a ‘Devices’ transaction record leveraging the LBT code. That approach will probably meet the MU requirements(?). Today we would probably use json focused device and device-udi parent child structure still with patient transactions as entry point for users.

Best.

You mentioned CouchDB, is there some interest in migrating to that? A student is working on LOINC mapping and is having a hard time with figuring out how to map data into tables when the data has an inherent tree-like structure.

Another thing to note that of the 3 issuing agencies use different date/time formats for their dates(manufacturing/production date and expiration date). https://www.gnu.org/software/pspp/manual/html_node/Time-and-Date-Formats.html

GSI uses only [YYMMDD] e.g 200201 for February 1st 2020.

HIBCC can use [YYJJJ],[YYJJJHH],[YYYMMDD],[MMYY][MMDDYY][YYMMDDHH]

And ICCBA does [YYYJJJ] only(where JJJ is julian date, which means what day of the year it is. e.g February 2nd 2020 is 020033.

Does OpenEMR have an already integrated datetime conversion package? I’m not looking forward figuring out how to wrangle all those different standards. Is there a preferred format for dates/times in the OpenEMR database?

Thanks,

Current codebase optionally supports couchdb only for documents as an alternative to storing those files on server filesystem. I referenced Couchdb in context of storing loosely formatted data streams emitted by various devices. Couchdb, a noSQL database, is not an general option for this SQL based project.

Good news is @sunsetsystems already took care of importing LOINC compendiums in proc orders. I have not looked at recent code but you cheat by setting up a hard coded vendor and import a file to establish the tree structure of 1000s of testing items. Several community members should be able to help in that area.

mysql requires SQL statements provide dates in YYYY-MM-DD format. There are standard php or javascript functions that map some of the formats based on user preferences for display purposes. Standard Date classes in php or Javascript would handle any other conversions.

1 Like

Looks like the below PHP function can do URL encoding.

https://www.php.net/manual/en/function.urlencode.php

2 Likes

So here is my code so far:

<?php //This allows you to access/run your code without being logged into OpenEMR. //Good for testing and development but should be set to true before releasing into the public codebase. $ignoreAuth = true; /**Globals.php is located in the openemr/interface folder. * It contains some important code and you'll need to give the path to the file in the parenthesis and double quotes below. * The ../ means go up one folder. Since we used it 3 times that tells the code to look 3 folders up for globals.php * If we made a sub folder in the current folder holding this code and moved it there, you'd need another ../ if you moved this code up a folder * you'd need one fewer ../ */ require_once("../../../globals.php"); //This tells the code to use the OpenEMR header with the menu options. use OpenEMR\Core\Header; //If your code calls another php script or redirects there you'll want to uncomment out the below and send a token in the command line. //A token can only be used once. This is used to keep OpenEMR data safe from a cross site scripting attack. /* if (!empty($_REQUEST)) { if (!verifyCsrfToken($_REQUEST["csrf_token_form"])) { csrfNotVerified(); } } */ //Collect data from page launched by. //$pid is the variable name. //It checks to see if it's set. If yes, it sets it to the value on the right. if(isset($_REQUEST['pid'])){ $pid = $_REQUEST['pid']; }else{ $pid = 'Not Set'; } //check if implantable device is associated with a procedure if(isset($_REQUEST['procedure'])){ $procedure = $_REQUEST['procedure']; }else{ $procedure = 'Not Set'; } //end PHP header ?> Implantable Device Parser
<?php
// Auto pulls in needed dependencies jquery, bootstrap, theme etc.
Header::setupHeader(['datetime-picker']);
?>

<?PHP
    $DI = $_POST['DeviceIdentifier'];
    //print($DI);
    //example DI from GS1:  (01)51022222233336(11)141231(17)150707(10)A213B1(21)1234
    $UDI= urlencode($DI);
    echo $UDI; 
?>
<FORM NAME ="form1" METHOD ="POST" ACTION = "GUDID.php">
    <INPUT TYPE = "Text" VALUE ="" NAME = "DeviceIdentifier">
    <INPUT TYPE = "Submit" Name = "Submit1" VALUE = "Parse ID">
</FORM>

So for right now what it does is collect the UDI from the text field, convert to URL encoding and display it to the user.
Our example of: (01)51022222233336(11)141231(17)150707(10)A213B1(21)1234
becomes: %2801%2951022222233336%2811%29141231%2817%29150707%2810%29A213B1%2821%291234

Up next we’ll try to use the FDA AccessGUDID Parse UDI API:
https://accessgudid.nlm.nih.gov/resources/developers/parse_udi_api

It can provide the response in either json or xml.

I think I’ll give this method a try since it gives options for both a json or xml response.

1 Like

Continuing to add to the PHP in the head block of the HTML code, let’s make an API call.

$apiCall=‘https://accessgudid.nlm.nih.gov/api/v2/parse_udi.json?udi=’ . $UDI;

echo $apiCall;

This returns:
https://accessgudid.nlm.nih.gov/api/v2/parse_udi.json?udi=(01)51022222233336(11)141231(17)150707(10)A213B1(21)1234

$response = file_get_contents($apiCall);
echo $response;

This returns:
{“udi”:"(01)51022222233336(11)141231(17)150707(10)A213B1(21)1234",“issuingAgency”:“GS1”,“di”:“51022222233336”,“manufacturingDateOriginal”:“141231”,“manufacturingDateOriginalFormat”:“YYMMDD”,“manufacturingDate”:“2014-12-31”,“expirationDateOriginal”:“150707”,“expirationDateOriginalFormat”:“YYMMDD”,“expirationDate”:“2015-07-07”,“lotNumber”:“A213B1”,“serialNumber”:“1234”}

1 Like

So let’s take a look at the json the api returns using json_decode:
https://www.php.net/manual/en/function.json-decode.php

var_dump(json_decode($response));

returns:

stringobject(stdClass)#316 (11) { [“udi”]=> string(56) “(01)51022222233336(11)141231(17)150707(10)A213B1(21)1234” [“issuingAgency”]=> string(3) “GS1” [“di”]=> string(14) “51022222233336” [“manufacturingDateOriginal”]=> string(6) “141231” [“manufacturingDateOriginalFormat”]=> string(6) “YYMMDD” [“manufacturingDate”]=> string(10) “2014-12-31” [“expirationDateOriginal”]=> string(6) “150707” [“expirationDateOriginalFormat”]=> string(6) “YYMMDD” [“expirationDate”]=> string(10) “2015-07-07” [“lotNumber”]=> string(6) “A213B1” [“serialNumber”]=> string(4) “1234” }

To assign it to a variable you’ll want to put the true argument in after the data.

$Parsed = json_decode($response, true);

Now we can index the response by field name.

echo $Parsed[“udi”];
(01)51022222233336(11)141231(17)150707(10)A213B1(21)1234

echo $Parsed[“expirationDate”];
2015-07-07

1 Like