OpenEMR System Architecture

From OpenEMR Project Wiki

OpenEMR System Architecture

Revision History of this Document

2007-06-20
Original document by Rod Roark (http://www.sunsetsystems.com/)
2007-07-10
Minor updates for the new claims table and "effective dates" in insurance_data.
2007-08-02
Updated with "restoreSession" notes (section 11) and FreeB replacement info.
2009-01-13
Updates for internal A/R and miscellaneous other improvements.
2009-12-03
Migrated this document to the OpenEMR wiki to improve collaboration. Snapshot
2009-12-04
Extensive modifications for OpenEMR 3.1.0 and greater versions.

Introduction

This is a compilation of notes that will be of interest to developers who wish to become involved with OpenEMR, or to users who are interested in technical aspects of the project. Creation of this document was generously sponsored by Sam Rajan, IPPF, and volunteer contributors.

Other Resources

The Documentation directory of the OpenEMR distribution contains the User Manual and other earlier notes, technical and otherwise:

  • OpenEMR_3.1_Users_Guide.odt is the User Guide.
  • User_Guide directory is the html version of the User Guide.
  • README.phpgacl discusses phpGACL, which OpenEMR uses for access control.
  • README.e-prescribing discusses a solution by Phyaura for e-prescribing.
  • SystemArchitecture.odt provides a link to this wiki page.
  • 3rd_Party_Form_API.txt describes the interface as originally designed for encounter forms.(outdated)
  • Database.pdf has some information about the original database design.(outdated)
  • FAQ is also quite old and doesn't cover much.(outdated)
  • Functions.pdf describes interfaces implemented by some modules in the library directory.(outdated)
  • HISTORY.txt is a brief summary of OpenEMR versions thru about 2002.(outdated)
  • modifications.txt is a brief list of the modules that must change in order to add data items to patient demographics.(outdated)
  • NoIP.txt is an obsolete list of broken links.(outdated)
  • OpenEMR_Backend_Spec.txt is a description of some interfaces implemented in the library directory.(outdated)
  • OpenEMR-Win2003-server-install-new.html briefly discusses installation of OpenEMR on Windows 2003.(outdated)
  • Package.txt has some old notes about creating an OpenEMR distribution release.(outdated)
  • Readme.txt seems to be mostly a disclaimer regarding HIPAA compliance.(outdated)

The accounting directory contains:

  • README.sql-ledger which describes setup of SQL-Ledger with OpenEMR.

The root of the distribution contains:

  • INSTALL which is general installation and upgrading instructions for OpenEMR.
  • Changelog which is not very current.(outdated)

Then there's http://www.oemr.org/ which is the home page for the project. It includes a manual, demo site, forums and a documentation wiki.

Database

OpenEMR installs many database tables. Following is a list, with brief descriptions:

  • addresses - Contains street addresses for two other tables: insurance_companies and pharmacies. So it is effectively an extension of those tables. This is inconsistent in that some other tables (e.g. facility, patient_data, users) have their addresses embedded. The "foreign_id" column equals the "id" of the foreign table, but curiously the addresses table does not identify the foreign table itself.
  • ar_activity – This is the primary repository for Accounts Receivable information. In general it contains multiple rows per encounter (visit). Each row corresponds to either a payment or an adjustment. Columns are:
  • pid, encounter: Identifies the visit.

sequence_no: A “line number” of the activity item for this visit. code, modifier: Identifies a particular service item, if any, that this payment or adjustment is for; generally useful only for insurance. payer_type: 0 = patient, 1 = primary insurance, 2 = secondary insurance, etc. post_time: A timestamp for the event posting. post_user: The ID of the user who posted the item. session_id: The ID of the associated ar_session table row, if any; generally useful only for insurance. memo: Used for the insurance adjustment reason, if applicable. pay_amount: The amount paid, if this is a payment. adj_amount: The amount of adjustment, if this is an adjustment.

  • ar_session – Also for Accounts Receivable, this table is applicable to insurance EOBs. Each row corresponds to a “posting session” in which a single payment covers a set of claims. Columns are:
    • session_id: A unique numeric identifier for the session.
    • payer_id: The ID of the payer in the insurance_companies table.
    • user_id: The ID of the user who is posting the session.
    • closed: Indicates if the session is complete (1=yes, 0=no).
    • reference: Normally a check number or EOB number.
    • check_date: The check date as provided by the payer.
    • deposit_date: The date that the receiver deposits the payment.
    • pay_total: The amount of the payment.
  • array - I don't see anything that uses this.
  • batchcom - Records messages sent to groups of patients using the "Batch Communication Tool".
  • billing - Billing items. Contains a row for every CPT4 code, HCPCS code, and ICD9 code recorded into an encounter. In addition has a row for each co-pay entered. The billing process also maintains state information in this table.
  • categories - Defines the tree of document category types. These are the category names that you see when viewing patient documents. Note that the "parent" column is a reference to the "id" column of the same table, thus the tree structure.
  • categories_seq - Holds the value of the last-used "id" for the categories table.
  • categories_to_documents - Cross-references the categories and documents tables.
  • chart_tracker – This supports the “chart tracking” feature whereby you can keep track of the location and history of paper charts. For each check-out or check-in, it records the patient ID, timestamp, user and location.
  • claims - records events related to the queueing and generation of claims. The Billing page summarizes this information for each encounter shown.
  • codes - The table of available billing codes including their modifiers, descriptions and fees. For the U.S. these will normally be CPT, HCPCS and ICD9 codes.
  • config - Not sure what this is.
  • config_seq - Presumably the last "id" used in the config table.
  • documents - Holds metadata for patient documents. The "foreign_id" column references "pid" in the patient_data table.
  • drug_inventory - Supports in-house drug sales. Each row represents a "lot" of purchased drugs.
  • drug_sales - Supports in-house drug sales. Each row represents a drug sale, i.e. an invoice line item.
  • drug_templates - Supports in-house drug sales. Each row represents a shortcut for a common dispensation of a drug.
  • drugs - Supports in-house drug sales. This is the list of available drugs.
  • employer_data - Contains patient employer information. The "pid" column references "pid" in the patient_data table. In general there is more than one of these accumulated per patient, and the current one is that with the most recent "date" value. This is inconsistent with subscriber employer information, which is included in the insurance_data table.
  • facility - Facilities are entered for treatment or billing purposes, or both. It is important that exactly one facility has its "billing_location" flag set, as this is the one used as the billing facility in generated claims.
  • fee_sheet_options – Supports a method of categorizing services in the Fee Sheet. For each category and item within the category, it identifies one or more specific services that would be added to the Fee Sheet when that item is selected.
  • form_* - Most "encounter forms" (other EHR systems call these "templates") implement a table whose name is "form_" followed by the name of the form. See the above-mentioned 3rd_Party_Form_API.txt for more about this.
  • form_encounter - This is the "encounter form" table for the most basic information about the patient encounter. Most importantly this contains the encounter date. And more recently, some A/R data was added: last payer level billed, last payer level closed, last statement date, and number of statements previously sent.
  • form_misc_billing_options - An encounter form that is a catch-all for billing information that is not always needed and is not captured elsewhere. For example, a specialist will put "prior authorization" numbers here.
  • forms - An index of all encounter form instances.
  • geo_country_reference - Maps country names to codes. I don't think this is used.
  • geo_zone_reference - Maps U.S. states, Canadian provinces and some other geographic regions to codes. I don't think this is used either.
  • groups - Used to assign one or more "group names" to each user. Supports grouping of users.
  • history_data - This maps 1:1 with the patient_data table and stores information about the patient's medical history.
  • immunization - Just a list of immunization types.
  • immunizations - Records immunizations given to patients.
  • insurance_companies - These are the payers. Most importantly it contains the payer's name and the external payer ID (cms_id). However may clearinghouses will map your payer names to the IDs, so you might not need to put in the IDs.
  • insurance_data - Contains rows of insurance information for each patient, including at least one each for primary, secondary and tertiary insurance. Holds information about each patient insurance plan, including subscriber information. An important recent improvement to this is the maintenance of the "date" column which is the effective date of the plan; thus it is possible to store multiple plans of each type, with different effective dates. "provider" here is a reference to insurance_companies.id.
  • insurance_numbers - Cross-references providers and insurance companies, and contains information specific to that relation, such as the credentialing number assigned by the insurance company to that provider. Where insurance_company_id is NULL, this indicates "default" values for the provider.
  • integration_mapping - With the need for SQL-Ledger eliminated, this table is now obsolete. It associates certain items in the OpenEMR database (MySQL) with corresponding items in the SQL-Ledger database (PostgreSQL). local_table and local_id are values for the OpenEMR table name and primary key. foreign_table and foreign_id are those for the SQL-Ledger database. The logical entities of interest are users (providers), and patients.
  • issue_encounter - Cross-references the lists table (representing "issues") with encounters (identified by patient ID and encounter ID). The idea here is to identify those encounters that address a given problem of a given patient.
  • lang_constants - Supports language translation. Assigns an ID to each translatable string.
  • lang_definitions - Supports language translation. Provides the translation string for each language and string ID.
  • lang_languages - Supports language translation. Identifies the supported languages.
  • layout_options – Defines the visual layout for patient demographics, history, referrals and some types of issues. This table is maintained via the Layouts administrative interface.
  • list_options – Defines most static lists. This table is maintained via the Lists administrative interface.
  • lists - These are patient "issues": medical problems, allergies, medications, surgeries, etc. They are similar to patient history items, but represent those items currently under treatment and thus that one might logically want to associate with encounters.
  • lists_* - A set of tables that are extensions of the “lists” table, to support data items in specific types of issues. These were created to avoid excessive bloating of the “lists” table to support issue types that many clinics will not be interested in.
  • log - A history of information access events. This table can become very large and as of yet there is no easy too for cleaning it out or archiving it. The logging feature also needs work in that some things are not logged that should be.
  • notes - I'm not sure if or how this table is used.
  • onotes - These are the "office notes" recorded and viewed in the Notes panel.
  • openemr_modules_vars - used internally by the embedded PostNuke code.
  • openemr_modules - used internally by the embedded PostNuke code.
  • openemr_postcalendar_categories - These are the definitions of the calendar event categories. You maintain these by going into Administration / Calendar / Categories in OpenEMR.
  • openemr_postcalendar_events - These are individual calendar events: appointments, In Office, Out of Office, lunch, reserved times, etc. Many of these will be repeating events, however repeating appointments are not supported.
  • openemr_postcalendar_limits - used internally by the embedded PostNuke code.
  • openemr_postcalendar_topics - used internally by the embedded PostNuke code.
  • openemr_session_info - used internally by the embedded PostNuke code.
  • patient_data - The primary repository for patient demographics.
  • payments - Records information entered via the "Payment" popup window (accessable from the dropdown in the patient menu). This is currently not integrated with recording of co-pays, but that is planned.
  • pharmacies - The list of pharmacies. This also references the "addresses" and "phone_numbers" tables via its "id" column.
  • phone_numbers - Contains telephone numbers of insurance_companies and pharmacies, and like the addresses table is effectively an extension of those tables.
  • pma_bookmark - Contains information about the canned reports available via the dropdown in the Reports menu. We try to avoid this feature because it's crude and confusing.
  • pma_column_info - Doesn't seem to be used.
  • pma_pdf_pages - Doesn't seem to be used.
  • pma_relation - Doesn't seem to be used.
  • pma_table_coords - Doesn't seem to be used.
  • pma_table_info - Doesn't seem to be used.
  • pnotes - Patient notes. This is a very useful feature where notes associated with a specific patient may be passed around among different users within the clinic, and eventually marked as closed. The assigned_to column indicates who is the current owner of the issue.
  • prescriptions - Prescriptions. In the case of in-house dispensation, drug_id will indicate the drug dispensed.
  • prices – Contains price information for products and services, and supports multiple price levels. This table obsoletes the “fee” column of the “codes” table. Note that the price levels are defined in the “list_options” table (list_id = “pricelevel”).
  • registry - Contains metadata regarding "registered" encountered forms. The Administration / Forms panel is used to register forms and maintain this information.
  • sequences - Holds the last "id" used by the integration_mapping table and for encounter IDs.
  • transactions - Records entries made in the Transactions panel.
  • users - This is a dual-purpose table. It supports the list of local users with their login names, passwords and other information; and it supports the Address Book. Non-local users are identifiable by having an empty "username" value.
  • x12_partners - These are the entities to whom electronic claims are sent. Normally there is just one, a clearinghouse. Referenced by the insurance_companies and billing tables.

Customization of OpenEMR

Most configuration happens with changes to interface/globals.php and includes/config.php. However there are some other modules where customization may be needed:

  • custom/clickoptions.txt - May be customized to contain your preferred selections for issue titles (names of medical problems, allergies, etc.). Needs to be done concurrently with library/lists.inc file.
  • custom/code_types.inc.php - If you code your encounters using methods other than CPT4, ICD9 and HCPCS then you will want to customize this and also load your custom codes into the "codes" table.
  • custom/statement.inc.php - This is your template for patient statements or collection letters. It must be customized for your practice.
  • custom/export_demographics.php - A placeholder for a script to export patient demographics to another system. export_labworks.php and export_xml.php are examples of such scripts.
  • custom/refer.php - A placeholder for a referral management system. One option is to subscribe to refercare.org and then replace this script with the provided refercare.php script.
  • interface/billing/billing_process.php - Customize X12 partner settings.
  • library/lists.inc - Customize Issue Types.
  • library/classes/class.ezpdf.php - Customize some general pdf settings.

Encounter Forms

Under current conventions, encounter forms may be "object oriented" (using Smarty) or not. Here are some notes for the object oriented case:

Given a form category "Complaint":

  • FormComplaint.class.php is an object representing the complaint data and extending ORDataObject. Includes methods persist(), populate() and a bunch of getters and setters.
  • C_FormComplaint.class.php extends Controller which extends Smarty. Instantiates FormComplaint as needed to perform its duties. Look at WellChild which does something with CPT codes here.
  • templates/Complaint/general_new.html is a Smarty template. Its <form action> invokes save.php.
  • new.php instantiates C_FormComplaint and invokes its default_action() which I think displays the Smarty template.
  • view.php instantiates C_FormComplaint and invokes its view_action($id) which also seems to display the Smarty template.
  • save.php instantiates C_FormComplaint and invokes its default_action_process($_POST) which presumably persists the data.
  • report.php displays a report from the persisted form data. This is the ugly stuff in the upper encounter page.
  • info.txt is a short 1-liner of the form's title.
  • table.sql contains any required CREATE TABLE statements.

Non-object-oriented encounter forms have all the same files except for the .class.php files.

Calendar Architecture

From some of my old notes when I was studying the PostNuke calendar design:

  • openemr_postcalendar_events is referenced in:
    • interface/main/calendar/modules/PostCalendar/pnuser.php
    • interface/main/calendar/modules/PostCalendar/pnuserapi.php
    • interface/main/calendar/modules/PostCalendar/pninit.php
    • interface/main/calendar/modules/PostCalendar/common.api.php
    • interface/main/calendar/modules/PostCalendar/pntables.php
    • interface/main/calendar/modules/PostCalendar/pnadmin.php
    • interface/main/calendar/modules/PostCalendar/pnadminapi.php
    • interface/main/calendar/modules/PostCalendar/plugins/function.pc_filter.php
    • interface/reports/appt_encounter_report.php
  • pnuser.php: function postcalendar_user_submit($args): invoked on event submission, at end writes success message, clears form vars, calls buildSubmitForm.
  • common.api.php: function postcalendar_userapi_buildSubmitForm($args,$admin=false) creates a ton of output which is then written via the submit.html template.
  • submit.html: is the template used to generate the redisplayed submit form.
  • small_navigation.html: included to generate the date selector and view buttons.
  • function.pc_date_select.php: builds date selector with jump button, should maybe enhance this to accept a specified default date, or else change the PostCalendar current working date from submit.html.
  • function.pc_url.php: builds the urls for the links in the view buttons.

OpenEMR Dependencies

This list of dependencies from installation of OpenEMR on Ubuntu 8.04 and on Debian 4.0 may be helpful. These are the packages that were added to a basic installation. Installing these will also cause many other required dependencies to be installed:

  • apache2-mpm-prefork
  • mysql-server
  • libapache2-mod-php5
  • libdate-calc-perl
  • libdbd-mysql-perl
  • libdbi-perl
  • libhtml-parser-perl
  • libtiff-tools
  • libwww-mechanize-perl
  • libxml-parser-perl
  • php5
  • php5-mysql
  • php5-cli
  • php5-gd
  • imagemagick

PHP Sessions and Browser Windows

Before August of 2007 OpenEMR worked poorly, even dangerously, when used in multiple top-level browser windows on the same machine. The underlying problem is that most web browsers cannot support separate cookie-based sessions in this case, because all windows share the same cookie storage area. There is more information about the general issue at http://aranea.zuavra.net/index.php/80/ .

Then a JavaScript-based solution was implemented as follows:

  1. interface/login/login.php was modified to delete the session cookie when a new login occurs. This ensures that a unique session ID is generated for each login (we assume you start each new browser window session with a login).
  2. A JavaScript function restoreSession() was created via the script library/restoreSession.php. This script is included in every top-level window. When called, the function restores the session cookie's value to the session ID that was supplied when the window was created.
  3. Many other scripts (about 200!) where changed to call "top.restoreSession()" wherever a server-side script is invoked. For example, "onclick='top.restoreSession()'" was added to every "<a href=...>" tag.

The effect of all this is to make the top-level window the storage area for the session cookie. So by creating multiple top-level browser windows and logging in separately to each, you can have multiple OpenEMR sessions concurrently on your desktop. This is a valuable feature for busy users who must juggle multiple tasks at once.

Naturally this impacts future development. You must include a JavaScript call to top.restoreSession() wherever you invoke a PHP script that requires current session data (which is most of them). The most common ways of doing this are by including the onclick handler as described above, and by including "onsubmit='return top.restoreSession()'" in <form> tags.