-
Notifications
You must be signed in to change notification settings - Fork 10
Developer guide
This page introduces anyone interested in contributing to the HIS-EMR-API through the applications structure, data model, and other concepts to help you get started.
Setting up the application for development is covered in the deployment page. Please go through that first before proceeding. In addition to the requirements specified in the deployment guide, you may find the following useful:
The tools listed above provide linting and intellisense sense capabilities for most editors. If you use vscode, the following extensions work with the two tools listed above:
Postman is recommended for manually testing the API. A postman
collection containing endpoints and example requests is provided with the application. It
is located at doc/src/index.json
; import this file into postman.
The following is the directory structure of HIS-EMR-API application (with some unused directories ommited).
.
├── app
│ ├── controllers
│ ├── exceptions
│ ├── jobs
│ ├── models
│ ├── services
│ └── utils
├── bin
├── config
├── db
│ ├── data
│ ├── initial_setup
│ ├── migrate
│ └── sql
├── doc
├── lib
├── log
├── public
├── spec
├── swagger
└── vendor
The structure is that of a typical
Ruby on Rails application with slight
additions. Under the app directory are the exceptions
, services
, and utils
directories.
app/exceptions
just contains various custom exceptions used through out the application.
For example, if we need to flag an error communicating with an external service, a custom
GatewayError exeption is raised. These exceptions can then be handled at the controller
level with an appropriate error that's reported to users. You might want to see
app/controllers/concerns/exception_handler.rb
for how the exceptions are handled globally.
The app/services
directory contains Plain Old Ruby Objects (POROs) and modules that make up
the business service layer of the application. Controllers are simply used as a glue layer
between the outside world and the service layer. Controllers get requests from users, process
them and forward them to the necessary service object/module to do its thing. The reasons for
this are best explained
elsewhere but from
our experience we find this approach eases unit testing and we don't have to think too much
about where to place a method in cases where the method cuts across multiple models.
In other words, service objects provide us a context for a given operation
(for example you will find everything to do with drug dispensations under a dispensation
service not models/drug_order, models/observation, nor models/patient). More about services
is documented here.
app/utils
contains various utility methods used across the entire project. These range from
basic date processing operations to helpers for concurrency. These methods do not carry
any domain logic, they could as well do in the /lib directory. The stuff from this
directory is usually included
in service objects/modules.
Under db
are the data
, initial_setup
, and sql
directories. These contain various *.sql
files used to initialise the database. They were imported from the old ART application, most of
these files are not used but no one is yet to summon the courage to get rid of them.
doc
is supposed to contain the project's documentation and the source files for the
documentation. doc\src
contains a postman collection that can be loaded into postman.
Finally, spec
contains the applications unit tests.
The application is built on a data model inspired by OpenMRS. The model is quite large and is divided into various domains serving different roles. We only make use of a subset of these domains and have added ours to serve other requirements like stock management. And within the domains that we use, we do not use everything OpenMRS offers and where necessary we do extend the data model to fit our needs. This section details what we use and how exactly we make use of it. Any changes that we have made are also documented accordingly.
Before proceeding there are a couple of things one should know. There are some properties that all entities in the OpenMRS model share:
- Entities within OpenMRS can broadly be classified into metadata and data. Metadata mostly provides names of things and the data is information collected about patients.
- All entities have a creator and date_created.
- Some entities have an updated_by and a date_updated field. This only applies to entities that can be updated. Not everything is allowed to be updated, some entities you can only create and delete.
- Nothing is ever deleted, entities are merely soft-deleted. Soft-delete is facilitated by the voided, voided_by, and date_voided for data. For metadata retired, retired_by, and date_retired provide that functionality. The distinction between voiding and retiring is that voided items become inaccessible whilst retired items remain accessible but can not be used to create new things. So if there is a data item referencing some metadata and the metadata is retired, the reference still remains valid. However, if the data item is referencing another data item which is voided, the reference then becomes invalid (maintenance of this data integrity is within the application, in our case to make life easier for ourselves we decided not to allow voiding of individual data elements like a patient's weight but rather void the entire tree this data item is within - more of this on observations).
According to OpenMRS, "Concepts are defined and used to support strongly coded data throughout the system." In short concepts provides names of various things. They may also define questions and their answers (more on this in the Observations section).
Concepts comprise a number of entities but we make use of the following entities
only: concept
, concept_name
, and concept_set
. A concept
is also linked to
concept_datatype
and concept_class
. We mantain this structure but do not take advantage
of these additional entities.
concept
is the unit that everything else refers to. Where ever a name is required,
a reference to a particular concept is made.
concept_name
maps various names to a single concept, for example 'HIV' and
'Human Immunodeficiency Virus' are both names of the same concept (they name the same thing).
concept_set
is just a grouping of concepts under one name (another concept). For example
you can group all Antiretroviral drugs under one concept. Thus if you need to retrieve
the concepts you can simply look up Antiretroviral drugs under concept_set
.
OpenMRS defines this as "metadata regarding health care providers intervention with
a patient." An encounter is essentially a unit that groups together related data on
a patient collected by a health care provider at a given location. This domain comprises
two entities: encounter
and encounter_type
. encounter_type
provides a name for
an encounter
, every encounter
has a name. Besides a name an encounter
has a provider,
location, and an encounter datetime.
An encounter
's provider is not necessarily the same as the creator of the encounter.
A provider is more or less the source of the information being entered. In some cases
a clinician may interact with a patient and have all the information about recorded in
some other media like paper. The clinician in this case is the provider of the information
but may not be the person that enters this information into the application.
Every encounter
has a location. The location simply refers to the point of interaction
between the provider and patient. For example a provider and a patient can interact at
a clinic's reception and later on in the consultation room. In such a case then we will
have two encounters: one at the reception and the next at the consultation room.
Finally, an encounter
has an encounter_datetime. This just records the time when the
encounter occurred. As has been explained for provider above, an encounter may not always
be entered into the application at the time a provider interacts with a patient. The
entry may be done at a later time. This field helps back date to the correct time.
An observation
records a single piece of information about a patient at a given point in
time. Related observations are collected under a single encounter. For example, a patient
may have their weight, height, and temparature at the vitals station. These would be saved
as three observations under something like a vitals encounter.
An observation has a concept which can be thought of as the name of the observation or the question the observation is asking. Think of something like "what is this patient's weight?", that is a question being asked by the "Weight" observation. And to each observation is an answer which may be a number, a date, plain text, or another concept. These are stored in value_numeric, value_datetime, value_text, and value_coded fields. value_coded may be accompanied by a value_coded_name_id which specifies an exact name to look at in cases where a concept may have multiple names.
Observations may also be linked to other observations to form an observation group. This is useful for questions that have multiple answers. For example the question "what side effects is this patient experiencing from this drug?" is one that may have multiple answers. In such a case, a parent observation would be constructed that holds the drug in question and then child observations would be created, each holding one side effect, that would be linked to the parent observation.
An observation can also be linked to an order. An observation in this case acts as a sort of metadata for the order. For example, an observation may carry how much of a drug is given to a patient for a given drug order.
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
[TODO]
Releases are primarily made quarterly in line with the Department of HIV/AIDS reporting timelines. In between quarters there may be additional releases that mostly fix bugs with last major release.
A CHANGELOG detailing all the changes between version is maintained. As a developer you are expected to log all significant changes you make to this document. The document contains information to guide you on how to go about making those changes but you can also refer to keepachangelog.com. To make a release the CHANGELOG is updated with the new version then committed (with a message like 'New release v4.12.0'). An annotated tag is then made on that commit with a summary of the changes on that tag.