Small project idea to make OpenMRS easier for AI tools

tl;dr: It would be great to have a super simple HTML page that provides context about OpenMRS key-points through a URL to any LLM. This would help expedite AI teams’ exploration of OpenMRS and prototyping using their tools with OpenMRS.

@paynejd gave me this idea, and it resonated re. how can OpenMRS make itself more interesting for AI groups/companies/etc to play with it. This is a pretty simple low-hanging fruit mini-project we (the community) can/should do.

Is anyone interested? @tendomart thought you might be, or someone on the AI squad with you and @michaelbontyes :slight_smile:

More Detail

  • The HTML page should explain our data model, so the HTML page link can be used as part of a prompt to GenAI tools. i.e. a super simple webpage that whatever is calling the LLM can curl or whatever and toss that in as the prompt instead of just hard coding a prompt in there. Similar to the ideas of “Context Injection” or “Dynamic Prompting” in LLM engineering.

  • Just an HTML page. The webpage should be a trivially simple static thing to reduce the number of tokens. Not a prompt, but context about OpenMRS (eg data model; EAV model and concepts, etc), with the total word count as low as possible to reduce token usage. Just the text we need, no additional complex HTML/CSS.

    • (Why not just write it on an OpenMRS wiki page? A single wiki page has a massively larger HTML payload than a simple HTML site. So I think we’d want a page on our wiki (something like “Using OpenMRS with AI”) that links to the simple URL with the simple HTML content, and describes how to edit/update the content in that URL, but if folks prompt their LLMs with a wiki link it could be a >100x increase in the token cost)
    • The goal is to provide the maximum useful context with the minimum number of characters (which directly relates to tokens).

@paynejd did I capture the idea correctly? I believe you are seeing folks in the LOINC community doing this?

1 Like

Hi @grace,

I’ve just onboarded; saw the thread and wanted to contribute immediately! So I have created the minimal HTML page for LLM context as requested! Here’s what I’ve included:

  • Ultra-minimal HTML structure (no CSS/complex markup)
  • ~850 words covering all essential OpenMRS concepts
  • Complete data model coverage (Person, Patient, Encounter, Observation, Concept, Orders, etc.)
  • EAV pattern explanation with practical examples
  • Critical SQL tips (voiding/retired filters)
  • REST API essentials
  • Module architecture and permissions overview

This page uses approximately 1/100th the tokens of a typical wiki page while providing comprehensive context.

I’ve attached the HTML file below. Would love feedback from the community!

HTML Preview

  <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OpenMRS Context</title>
</head>
<body>
<h1>OpenMRS Data Model Context</h1>
<p><strong>System:</strong> OpenMRS (Open Medical Record System). Open-source EMR for resource-constrained environments. Modular plugin architecture.</p>
<p><strong>Tech Stack:</strong> Java, Spring Framework, Hibernate ORM, MySQL/PostgreSQL, Maven, REST API (FHIR support).</p>

<h2>Core Architecture: EAV Model</h2>
<p>OpenMRS uses <strong>Entity-Attribute-Value (EAV)</strong> for flexible clinical data storage without schema changes.</p>
<ul>
<li><strong>Entity:</strong> Patient or Encounter</li>
<li><strong>Attribute:</strong> Concept (the question, e.g., "Temperature")</li>
<li><strong>Value:</strong> Obs (the answer, e.g., "37.5")</li>
</ul>
<p>Sparse matrix design - only non-null values stored. Also used in person_attribute, location_attribute, provider_attribute, visit_attribute for custom extensions.</p>

<h2>Key Domains & Tables</h2>

<h3>1. Person, Patient, User, Provider</h3>
<ul>
<li><strong>Person (person):</strong> Base entity. Names (person_name), addresses (person_address), demographics (birthdate, gender).</li>
<li><strong>Patient (patient):</strong> Clinical role linked to person_id. Identifiers (patient_identifier) like Medical Record Numbers.</li>
<li><strong>User (users):</strong> System account credentials. Links to person_id. Role-based privileges.</li>
<li><strong>Provider (provider):</strong> Healthcare worker. May link to full Person or simple name/ID.</li>
</ul>

<h3>2. Concept Dictionary (concept)</h3>
<p>Backbone of EAV model. Defines all medical terms (questions and answers).</p>
<ul>
<li><strong>Structure:</strong> concept_id, datatype (Numeric, Coded, Text, Date, Boolean), class (Diagnosis, Test, Drug, Finding).</li>
<li><strong>Coded Concepts:</strong> Answers link to other Concepts (Q: "Malaria Test Result", A: "Positive").</li>
<li><strong>Concept Names (concept_name):</strong> Multi-language support. Locale-specific names per concept.</li>
<li><strong>CIEL:</strong> Standard curated concept dictionary used in most implementations.</li>
<li><strong>Mapping:</strong> Concepts map to external terminologies (SNOMED, ICD-10, LOINC, RxNorm).</li>
</ul>

<h3>3. Clinical Data (obs, encounter, visit)</h3>
<ul>
<li><strong>Visit (visit):</strong> Top-level grouping. Entire care episode (e.g., "Outpatient Visit Nov 22"). Links to patient, location, visit_type.</li>
<li><strong>Encounter (encounter):</strong> Specific interaction within Visit (e.g., "Vitals Check", "Consultation"). Links to patient, visit, encounter_type, location, provider.</li>
<li><strong>Observation (obs):</strong> Atomic clinical data unit.
<ul>
<li>person_id: Subject</li>
<li>concept_id: What is measured</li>
<li>encounter_id: Context</li>
<li>value_*: Result columns - value_numeric, value_coded (FK to concept), value_text, value_datetime, value_boolean</li>
<li>obs_group_id: Groups related observations (e.g., blood pressure systolic/diastolic)</li>
</ul>
</li>
</ul>

<h3>4. Orders (orders)</h3>
<p>Instructions for patient care. Base table with subtypes:</p>
<ul>
<li><strong>Drug Order (drug_order):</strong> Medication prescriptions. Links to Concept or specific Drug formulation. Dose, frequency, duration.</li>
<li><strong>Test Order (test_order):</strong> Lab/diagnostic requests.</li>
<li><strong>Order Type:</strong> Categorizes orders (Drug Order, Test Order, Referral).</li>
</ul>

<h3>5. Forms & Metadata</h3>
<ul>
<li><strong>Form (form):</strong> Data entry template. Creates encounter with multiple observations on submission.</li>
<li><strong>Location (location):</strong> Physical place. Supports hierarchy (ward within clinic). Can be retired.</li>
<li><strong>Encounter Type (encounter_type):</strong> Categorizes encounters (Vitals, Consultation, Admission).</li>
<li><strong>Visit Type (visit_type):</strong> Categorizes visits (Outpatient, Inpatient).</li>
</ul>

<h2>SQL Query Logic (Critical for AI)</h2>
<ul>
<li><strong>Join Pattern:</strong> obs → concept → concept_name (for question labels). obs → concept (via value_coded) → concept_name (for coded answers).</li>
<li><strong>Voiding:</strong> Soft delete. NEVER physically delete. Sets voided = 1, voided_by, date_voided, void_reason. <strong>ALWAYS filter WHERE voided = 0</strong> in queries.</li>
<li><strong>Retired:</strong> Metadata (concepts, locations, encounter_types) uses retired = 1 instead of voided. <strong>Filter WHERE retired = 0</strong> for active metadata.</li>
<li><strong>Obs Groups:</strong> Use obs_group_id to find related observations. Parent obs has no value, only links children.</li>
<li><strong>Latest Obs:</strong> Use MAX(obs_datetime) or ORDER BY obs_datetime DESC LIMIT 1 to get most recent value.</li>
</ul>

<h2>REST API Essentials</h2>
<p>Endpoint: /openmrs/ws/rest/v1/</p>
<ul>
<li><strong>Resources:</strong> patient, encounter, obs, concept, order, provider, location, visit, form, user, role, privilege</li>
<li><strong>Representations:</strong> ref (minimal), default (standard), full (complete with audit)</li>
<li><strong>Auth:</strong> HTTP Basic</li>
<li><strong>Operations:</strong> GET (search/read), POST (create/update), DELETE (void/retire)</li>
<li><strong>Search:</strong> Query params like q=searchTerm, patient=uuid</li>
</ul>

<h2>Modules & Extensibility</h2>
<p>.omod files extend core via Spring plugin system. Full API access via Context singleton.</p>
<ul>
<li><strong>Common Modules:</strong> HTML Form Entry, REST Web Services, Reporting, Address Hierarchy, Metadata Mapping</li>
<li><strong>Service Layer:</strong> PatientService, EncounterService, ConceptService, ObsService provide business logic</li>
<li><strong>Permissions:</strong> Privilege-based. Roles contain privileges (can inherit). Enforced via Spring AOP.</li>
</ul>

<h2>Key Principles</h2>
<ul>
<li>Metadata-driven: Customize via concepts/forms without code changes</li>
<li>Semantic interoperability: Strong coded data via concept dictionary</li>
<li>Audit trails: Void/retire, never purge in production</li>
<li>International: Multi-language, locale support, local adaptation required</li>
</ul>

<h2>Common Use Cases</h2>
<p>Primary care EMR. HIV/TB program tracking. Lab/pharmacy systems. Mobile health data collection. Health information exchange. Clinical decision support. Cohort reporting/analytics.</p>
</body>
</html>
1 Like

Wow thank you so much @faraz! :star_struck:

This is an awesome start. @dkayiwa, @wikumc and @burke how does this look to you? My main thought is it looks a bit grounded in our recent past, missing a few things related to our current state with OpenMRS 3. But it’s a solid start. I like how it explains the EAV/metadata piece!

Then some minor nits:

  • Use cases: Would remove “Mobile health data collection” at the end because OpenMRS is generally used inside a site/facility, not so much in a portable/community-outreach setting.
  • Common modules: HTML Form Entry and Metadata Mapping are no longer part of our recommended EMR bundle.
1 Like

Thanks so much for the feedback! @grace :grinning_face_with_smiling_eyes: Really appreciate you taking the time to review this especially knowing you’ve got so much going on right now.

I agree with all the points. I’ll update the document accordingly and refine the context to better reflect the current O3 state rather than the recent past. If there are any other areas where you think we should highlight current work, I’d be happy to incorporate those as well.

Also, Grace, hope maternity leave is going wonderfully for you and your family. :yellow_heart: Thanks for still finding a moment to guide us; it means a lot.

Looking forward to polishing this so it becomes a solid reference!

Updated HTML Preview

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>OpenMRS 3 Context </title>
</head>
<body>
<h1>OpenMRS 3 (O3) Technical Context</h1>
<p><strong>System:</strong> OpenMRS is an enterprise open-source EMR. <strong>O3</strong> is a modern React-based frontend framework, runs on <strong>Platform</strong> (Java backend with APIs). O3 requires Platform v2.3+.</p>

<h2>1. Frontend Architecture</h2>
<p><strong>Framework:</strong> React, TypeScript, Single-SPA, Webpack 5 module federation, Carbon Design System.</p>
<p><strong>App Shell:</strong> Base application managing routing, auth, module loading, extensions, global state, service worker (offline mode).</p>
<p><strong>ESM (Microfrontends):</strong> Independent npm packages (e.g., <code>@openmrs/esm-patient-chart-app</code>). Loaded on-demand via <strong>Import Map</strong> JSON at <code>/openmrs/spa/importmap.json</code>. Each ESM exports activator and lifecycle functions. Loaded via Webpack module federation.</p>
<p><strong>Extension System:</strong> UI customized via named <strong>Slots</strong> and <strong>Extensions</strong> (JSON config). No code forks required.</p>
<p><strong>Configuration:</strong> Each ESM defines <code>config-schema.ts</code>. Implementer Tools UI allows in-browser configuration at runtime.</p>

<h2>2. Backend Architecture</h2>
<p><strong>Stack:</strong> Java 11+, Spring Framework, Hibernate 5+, MySQL 8+ or PostgreSQL. Modular (OMOD files are deployable plugins).</p>
<p><strong>Database:</strong> Centralized. Platform agnostic (MySQL/PostgreSQL). O3 frontend is stateless; all state persists in backend DB.</p>

<h2>3. EAV Data Model (Entity-Attribute-Value)</h2>
<p><strong>CRITICAL:</strong> Data storage is sparse. Never assume table columns exist (e.g., <code>patient.blood_pressure</code> does not exist as a column).</p>
<p><strong>Pattern:</strong> Clinical observations stored as concept-value pairs via the obs table.</p>
<ul>
<li><strong>Entity:</strong> <code>patient</code> (clinical subject), <code>encounter</code> (context/event)</li>
<li><strong>Attribute:</strong> <code>concept</code> (the Question from concept dictionary). E.g., Concept ID 5085 = "Systolic Blood Pressure".</li>
<li><strong>Value:</strong> <code>obs</code> table row (the Answer). Columns: <code>value_numeric</code>, <code>value_coded</code>, <code>value_text</code>, <code>value_datetime</code>.</li>
</ul>

<h3>Key Tables</h3>
<ul>
<li><strong>person:</strong> Base identity record. Cols: <code>person_id</code>, <code>uuid</code>, <code>birthdate</code>, <code>gender</code>, <code>dead</code>. IDs ≤ 0 = system users.</li>
<li><strong>patient:</strong> Clinical role. FK <code>person_id</code>. Patient identifiers (MRN) in <code>patient_identifier</code> table.</li>
<li><strong>visit:</strong> Episode of care. Cols: <code>visit_id</code>, <code>patient_id</code>, <code>visit_type_id</code>, <code>location_id</code>, <code>start_datetime</code>, <code>stop_datetime</code>.</li>
<li><strong>encounter:</strong> Clinical interaction within visit. Cols: <code>encounter_id</code>, <code>patient_id</code>, <code>visit_id</code>, <code>encounter_type_id</code>, <code>provider_id</code>, <code>location_id</code>, <code>encounter_datetime</code>.</li>
<li><strong>obs:</strong> Atomic clinical data. Cols: <code>obs_id</code>, <code>person_id</code>, <code>concept_id</code> (FK), <code>encounter_id</code>, <code>obs_group_id</code> (parent obs for grouped data), <code>obs_datetime</code>, <code>value_numeric</code>, <code>value_coded</code>, <code>value_text</code>, <code>value_datetime</code>, <code>voided</code>. Parent obs can group children (e.g., Systolic + Diastolic BP).</li>
<li><strong>concept:</strong> Dictionary terms. Cols: <code>concept_id</code>, <code>datatype_id</code> (Numeric, Coded, Text, Date), <code>class_id</code> (Diagnosis, Lab, Procedure, Finding, Drug).</li>
<li><strong>concept_name:</strong> Localized concept labels. Cols: <code>concept_id</code>, <code>name</code>, <code>locale</code>, <code>concept_name_type</code> (FULLY_SPECIFIED, SYNONYM, INDEX_TERM), <code>voided</code>.</li>
<li><strong>orders:</strong> Base for prescriptions/tests. Subtypes: <code>drug_order</code> (prescriptions), <code>test_order</code> (lab requests).</li>
<li><strong>location:</strong> Facilities. Cols: <code>location_id</code>, <code>name</code>, <code>parent_location_id</code> (hierarchy).</li>
<li><strong>provider:</strong> Clinicians. Cols: <code>provider_id</code>, <code>person_id</code> (FK), <code>identifier</code>.</li>
</ul>

<h3>SQL Rules</h3>
<ul>
<li><strong>Soft Deletes (MANDATORY):</strong> Always filter <code>WHERE voided = 0</code> for obs, encounter, visit, patient, person, orders. Always filter <code>WHERE retired = 0</code> for metadata (concept, location, encounter_type, visit_type, concept_name).</li>
<li><strong>Concept Names:</strong> Join <code>obs.concept_id</code> → <code>concept_name.concept_id</code> filtering <code>concept_name_type = 'FULLY_SPECIFIED'</code> and <code>locale</code> (e.g., 'en').</li>
<li><strong>Coded Answer Names:</strong> Join <code>obs.value_coded</code> → <code>concept_name.concept_id</code> using same filters.</li>
<li><strong>Latest Obs:</strong> <code>ORDER BY obs_datetime DESC LIMIT 1</code>. Never order by <code>obs_id</code>; datetime-based sorting is required.</li>
<li><strong>Obs Groups:</strong> Parent has <code>obs_group_id = NULL</code>, no <code>value_*</code>. Children reference parent via <code>obs_group_id</code>. Query children: <code>WHERE obs_group_id = {parent_obs_id}</code>.</li>
<li><strong>UUIDs:</strong> All entities have <code>uuid</code> column. APIs use UUIDs, not integer IDs.</li>
<li><strong>Datetime Fields:</strong> Stored UTC. <code>obs_datetime</code>, <code>encounter_datetime</code>, <code>start_datetime</code>, <code>stop_datetime</code> guide temporal queries.</li>
</ul>

<h2>4. APIs</h2>
<p><strong>FHIR R4 (Preferred):</strong> <code>/openmrs/ws/fhir2/R4/{resource}</code>. Resources: Patient, Encounter, Observation, Condition, MedicationRequest, ServiceRequest, Practitioner, Location, Organization. Features: pagination, search filters, includes. Use for: external integrations, standards compliance, O3 frontend.</p>
<p><strong>REST API (Admin/Legacy):</strong> <code>/openmrs/ws/rest/v1/{resource}</code>. Resources: concept, location, encountertype, visittype, provider, user. Representations: <code>?v=default</code>, <code>?v=full</code>. Use for: metadata, admin config, non-FHIR resources.</p>

<h2>5. Backend Modules (Required)</h2>
<ul>
<li><strong>webservices.rest:</strong> v2.24+. Provides REST API.</li>
<li><strong>fhir2:</strong> v1.2+. FHIR R4 API. Translation layer between FHIR and OpenMRS EAV model.</li>
<li><strong>SPA:</strong> v1.0.8+. Serves O3 frontend at <code>/openmrs/spa</code>.</li>
<li><strong>Initializer (Iniz):</strong> Config-as-code. Loads metadata (Concepts, Locations, EncounterTypes, Roles, Privileges) from JSON on startup.</li>
</ul>

<h2>6. Frontend ESM Repositories</h2>
<ul>
<li><strong>openmrs-esm-core:</strong> App shell, @openmrs/esm-framework (core libs), devtools, implementer-tools, login, offline-tools.</li>
<li><strong>openmrs-esm-patient-chart:</strong> Dashboard widgets: allergies, conditions, medications, vitals, biometrics, forms, test results, notes, attachments, programs, appointments.</li>
<li><strong>openmrs-esm-patient-management:</strong> Patient search, registration, lists, appointments, service queues, active visits, ward mgmt.</li>
<li><strong>openmrs-esm-home:</strong> Home page, navigation.</li>
<li><strong>Other:</strong> Form Builder, Cohort Builder, Dispensing, Fast Data Entry, Admin Tools.</li>
</ul>

<h2>7. Forms System</h2>
<p><strong>React Form Engine:</strong> JSON schema-based (questions, pages, sections, validation, conditional rendering, field types). Form Builder UI generates/edits schema. Submits to <code>/obs</code> or FHIR APIs.</p>

<h2>8. Key Principles</h2>
<ul>
<li><strong>Sparse Data:</strong> No direct columns for clinical data; use EAV (concept/obs) always.</li>
<li><strong>Soft Deletes:</strong> Never query without voided/retired filters; data is logically deleted, not purged.</li>
<li><strong>Localization:</strong> Concept names, form labels multi-locale. Always filter by locale in queries.</li>
<li><strong>Modularity:</strong> O3 is composable ESMs + flexible backend. No monolithic UI.</li>
<li><strong>Standards:</strong> FHIR R4 for interop, REST for admin.</li>
<li><strong>UUID-Based:</strong> All APIs use UUIDs, not integer IDs.</li>
<li><strong>Audit Trails:</strong> <code>voided</code>, <code>retired</code>, <code>date_created</code>, <code>changed_by</code>, <code>date_changed</code> columns on most tables.</li>
</ul>

</body>
</html>
2 Likes

Absolutely interested, we welcome more of these

thanks for this @faraz

1 Like

Hi, @grace @tendomart Could you please help me with my Jira and Wiki edit access? Or please let me know if there’s a procedure to it. It has been over 48 hours since I submitted the request, and I still haven’t received access. I’m eager to start contributing and would appreciate any assistance.

My access request reference ID is ISD-18738.

Thank you!

@ibacher / @dkayiwa , can you please help her gain access

1 Like

Access has been granted @faraz

1 Like

@beryl @tendomart Appreciate it very much. Thankyou so much!

1 Like