# Batch Concepts
Source: https://docs.omophub.com/api-reference/concepts/batch-concepts
POST /concepts/batch
Retrieve multiple concepts in a single request
## Overview
Efficiently retrieve information for multiple concepts at once. This endpoint is optimized for bulk operations and reduces the number of API calls needed.
## Request Body
Array of concept IDs to retrieve (max: 1000)
Include relationships for all concepts
Include synonyms for all concepts
Include cross-vocabulary mappings for all concepts
Filter results to specific vocabularies
Only return standard concepts
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [320128, 201826, 4329847],
"include_relationships": true,
"include_synonyms": true
}'
```
```python Python
import requests
payload = {
"concept_ids": [320128, 201826, 4329847],
"include_relationships": True,
"include_synonyms": True,
"include_mappings": False
}
response = requests.post(
"https://api.omophub.com/v1/concepts/batch",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json=payload
)
concepts = response.json()
for concept in concepts['data']['concepts']:
print(f"{concept['concept_name']} ({concept['vocabulary_id']})")
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: [320128, 201826, 4329847],
include_relationships: true,
include_synonyms: true
})
});
const data = await response.json();
console.log(`Retrieved ${data.data.concepts.length} concepts`);
```
```bash cURL (with filtering)
curl -X POST "https://api.omophub.com/v1/concepts/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [320128, 201826, 4329847, 313217],
"vocabulary_filter": ["SNOMED", "ICD10CM"],
"include_mappings": true,
"standard_only": true
}'
```
```python Python (comprehensive)
import requests
# Large batch request with comprehensive data
concept_ids = [320128, 201826, 4329847, 313217, 435216]
payload = {
"concept_ids": concept_ids,
"include_relationships": True,
"include_synonyms": True,
"include_mappings": True,
"standard_only": True
}
response = requests.post(
"https://api.omophub.com/v1/concepts/batch",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json=payload
)
data = response.json()
print(f"Successfully retrieved {len(data['data']['concepts'])} concepts")
print(f"Failed to retrieve {len(data['data']['failed_concepts'])} concepts")
# Process successful concepts
for concept in data['data']['concepts']:
print(f"\n{concept['concept_name']}")
print(f" ID: {concept['concept_id']}")
print(f" Vocabulary: {concept['vocabulary_id']}")
print(f" Domain: {concept['domain_id']}")
if concept.get('synonyms'):
print(f" Synonyms: {len(concept['synonyms'])} found")
if concept.get('relationships'):
print(f" Relationships: {len(concept['relationships'])} found")
```
```json
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
{
"concept_synonym_name": "Primary hypertension",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Idiopathic hypertension",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 316866,
"target_concept_name": "Hypertensive disorder",
"target_vocabulary_id": "SNOMED",
"relationship_direction": "outbound"
}
]
},
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
{
"concept_synonym_name": "Type II diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Adult-onset diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 73211009,
"target_concept_name": "Diabetes mellitus",
"target_vocabulary_id": "SNOMED",
"relationship_direction": "outbound"
}
]
},
{
"concept_id": 4329847,
"concept_name": "Myocardial infarction",
"concept_code": "22298006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
{
"concept_synonym_name": "Heart attack",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "MI - Myocardial infarction",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 134057,
"target_concept_name": "Acute myocardial infarction",
"target_vocabulary_id": "SNOMED",
"relationship_direction": "inbound"
}
]
}
],
"failed_concepts": [],
"summary": {
"total_requested": 3,
"successful_retrievals": 3,
"failed_retrievals": 0,
"vocabularies_represented": ["SNOMED"],
"domains_represented": ["Condition"]
}
},
"meta": {
"request_id": "req_batch_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"query_time_ms": 45.2
}
}
```
# Batch Hierarchy Queries
Source: https://docs.omophub.com/api-reference/concepts/batch-hierarchy-queries
POST https://api.omophub.com/v1/concepts/hierarchy/batch
Perform multiple hierarchy queries in a single request for efficient bulk processing
## Overview
This endpoint allows you to perform multiple hierarchy queries (ancestors, descendants, or both) for multiple concepts in a single API call. This is highly efficient for applications that need hierarchical information for many concepts simultaneously.
## Request Body
Array of hierarchy query requests
The OMOP concept ID to query hierarchy for
Type of hierarchy query: "ancestors", "descendants", or "both"
Maximum number of hierarchy levels to traverse
Array of relationship types to follow (default: \["Is a"])
Limit results to specific vocabularies
Include the query concept in results
## Query Parameters
Specific vocabulary release version (e.g., "2024.1")
Include relationships to invalid/deprecated concepts
## Response
Indicates whether the request was successful
Array of hierarchy query results matching the input order
The concept ID that was queried
The type of query that was performed
Whether this individual query succeeded
Error message if the query failed
Array of ancestor concepts (when query\_type includes "ancestors")
Ancestor concept ID
Ancestor concept name
Ancestor vocabulary
Ancestor domain
Ancestor concept class
Standard concept flag
Hierarchical level (1 = direct parent)
Relationship type used
Array of descendant concepts (when query\_type includes "descendants")
Descendant concept ID
Descendant concept name
Descendant vocabulary
Descendant domain
Descendant concept class
Standard concept flag
Hierarchical level (1 = direct child)
Relationship type used
Summary statistics for this query
Total number of ancestors found
Total number of descendants found
Maximum ancestor level reached
Maximum descendant level reached
Array of vocabularies in results
Summary statistics for the entire batch
Total number of queries requested
Number of successful queries
Number of failed queries
Total unique concepts across all results
Total processing time for batch
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/hierarchy/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"concept_id": 201826,
"query_type": "ancestors",
"max_levels": 5
},
{
"concept_id": 4182210,
"query_type": "descendants",
"max_levels": 3
},
{
"concept_id": 313217,
"query_type": "both",
"max_levels": 4,
"relationship_types": ["Is a", "Subsumes"]
}
]
}'
```
```python Python
import requests
def batch_hierarchy_query(queries, api_key):
url = "https://api.omophub.com/v1/concepts/hierarchy/batch"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {"queries": queries}
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example usage
queries = [
{
"concept_id": 201826, # Type 2 diabetes
"query_type": "ancestors",
"max_levels": 5
},
{
"concept_id": 4182210, # Hypertension
"query_type": "descendants",
"max_levels": 3
},
{
"concept_id": 313217, # Atrial fibrillation
"query_type": "both",
"max_levels": 4,
"relationship_types": ["Is a"]
}
]
results = batch_hierarchy_query(queries, "YOUR_API_KEY")
print(f"Processed {results['data']['batch_summary']['total_queries']} queries")
print(f"Success rate: {results['data']['batch_summary']['successful_queries']}/{results['data']['batch_summary']['total_queries']}")
for i, result in enumerate(results['data']['results']):
if result['success']:
summary = result['summary']
print(f"Concept {result['concept_id']}: {summary['total_ancestors']} ancestors, {summary['total_descendants']} descendants")
else:
print(f"Concept {result['concept_id']}: Error - {result['error']}")
```
```javascript JavaScript
const batchHierarchyQuery = async (queries) => {
const response = await fetch('https://api.omophub.com/v1/concepts/hierarchy/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ queries })
});
return response.json();
};
// Example usage
const queries = [
{ concept_id: 201826, query_type: 'ancestors', max_levels: 5 },
{ concept_id: 4182210, query_type: 'descendants', max_levels: 3 },
{ concept_id: 313217, query_type: 'both', max_levels: 4 }
];
const results = await batchHierarchyQuery(queries);
results.data.results.forEach((result, index) => {
if (result.success) {
console.log(`Query ${index + 1}: Found ${result.summary.total_ancestors || 0} ancestors, ${result.summary.total_descendants || 0} descendants`);
} else {
console.error(`Query ${index + 1} failed: ${result.error}`);
}
});
```
```bash cURL (advanced filtering)
curl -X POST "https://api.omophub.com/v1/concepts/hierarchy/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"concept_id": 201826,
"query_type": "both",
"max_levels": 5,
"relationship_types": ["Is a", "Subsumes"],
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"include_self": true
}
]
}'
```
```python Python (error handling)
import requests
def process_hierarchy_batch(concept_ids):
queries = [
{
"concept_id": concept_id,
"query_type": "both",
"max_levels": 6,
"relationship_types": ["Is a"]
}
for concept_id in concept_ids
]
response = requests.post(
"https://api.omophub.com/v1/concepts/hierarchy/batch",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"queries": queries}
)
data = response.json()
# Process successful results
successful_results = []
failed_results = []
for result in data['data']['results']:
if result['success']:
successful_results.append(result)
# Show hierarchy depth
concept_id = result['concept_id']
ancestors = len(result.get('ancestors', []))
descendants = len(result.get('descendants', []))
print(f"Concept {concept_id}: {ancestors} ancestors, {descendants} descendants")
else:
failed_results.append(result)
print(f"Failed concept {result['concept_id']}: {result['error']}")
return successful_results, failed_results
# Process multiple cardiovascular concepts
cardiovascular_concepts = [201826, 4182210, 313217, 320128, 434557]
successful, failed = process_hierarchy_batch(cardiovascular_concepts)
print(f"\nSummary: {len(successful)} successful, {len(failed)} failed")
```
```json
{
"success": true,
"data": {
"results": [
{
"concept_id": 201826,
"query_type": "ancestors",
"success": true,
"ancestors": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"relationship_type": "Is a"
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 2,
"relationship_type": "Is a"
},
{
"concept_id": 138875005,
"concept_name": "Clinical finding",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 3,
"relationship_type": "Is a"
}
],
"summary": {
"total_ancestors": 3,
"total_descendants": 0,
"max_ancestor_level": 3,
"max_descendant_level": 0,
"vocabularies_involved": ["SNOMED"]
}
},
{
"concept_id": 4182210,
"query_type": "descendants",
"success": true,
"descendants": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"relationship_type": "Is a"
},
{
"concept_id": 443767,
"concept_name": "Secondary hypertension",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"relationship_type": "Is a"
}
],
"summary": {
"total_ancestors": 0,
"total_descendants": 2,
"max_ancestor_level": 0,
"max_descendant_level": 1,
"vocabularies_involved": ["SNOMED"]
}
},
{
"concept_id": 313217,
"query_type": "both",
"success": true,
"ancestors": [
{
"concept_id": 49436004,
"concept_name": "Atrial fibrillation and flutter",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"relationship_type": "Is a"
}
],
"descendants": [
{
"concept_id": 440059007,
"concept_name": "Paroxysmal atrial fibrillation",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"level": 1,
"relationship_type": "Subsumes"
}
],
"summary": {
"total_ancestors": 1,
"total_descendants": 1,
"max_ancestor_level": 1,
"max_descendant_level": 1,
"vocabularies_involved": ["SNOMED"]
}
}
],
"batch_summary": {
"total_queries": 3,
"successful_queries": 3,
"failed_queries": 0,
"total_concepts_returned": 7,
"processing_time_ms": 245
}
},
"meta": {
"request_id": "req_batch_hierarchy_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Simple Batch Ancestors
Get ancestors for multiple concepts:
```json
{
"queries": [
{"concept_id": 201826, "query_type": "ancestors"},
{"concept_id": 4182210, "query_type": "ancestors"},
{"concept_id": 313217, "query_type": "ancestors"}
]
}
```
### Mixed Query Types
Combine different query types in one batch:
```json
{
"queries": [
{"concept_id": 201826, "query_type": "ancestors", "max_levels": 5},
{"concept_id": 4182210, "query_type": "descendants", "max_levels": 3},
{"concept_id": 313217, "query_type": "both", "max_levels": 4}
]
}
```
### Advanced Filtering
Use relationship and vocabulary filters:
```json
{
"queries": [
{
"concept_id": 201826,
"query_type": "both",
"max_levels": 5,
"relationship_types": ["Is a", "Subsumes"],
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"include_self": true
}
]
}
```
## Important Notes
* **Batch size limit**: Maximum 50 queries per batch request
* **Performance optimization**: Batch processing is significantly faster than individual requests
* **Error handling**: Individual query failures don't affect other queries in the batch
* **Result order**: Results are returned in the same order as input queries
* **Memory considerations**: Large batches with deep hierarchies may have longer response times
* **Rate limiting**: Batch requests count as single requests for rate limiting purposes
## Related Endpoints
* [Get Concept Ancestors](/api-reference/concepts/get-concept-ancestors) - Single concept ancestors
* [Get Concept Descendants](/api-reference/concepts/get-concept-descendants) - Single concept descendants
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Complete hierarchy tree
# Batch Relationship Queries
Source: https://docs.omophub.com/api-reference/concepts/batch-relationship-queries
POST https://api.omophub.com/v1/concepts/relationships/batch
Perform multiple relationship queries in a single request for efficient bulk processing
## Overview
This endpoint allows you to retrieve relationship information for multiple concepts in a single API call. It's optimized for bulk operations and provides significant performance benefits when working with large sets of concepts.
## Request Body
Array of relationship query requests
The OMOP concept ID to get relationships for
Array of relationship types to filter by (e.g., \["Maps to", "Is a"])
Relationship direction: "outbound", "inbound", or "both"
Array of vocabulary IDs to filter related concepts
Include relationships to invalid/deprecated concepts
Maximum number of relationships to return for this concept
Include summary statistics for this concept's relationships
## Query Parameters
Specific vocabulary release version (e.g., "2024.1")
Include full concept details in responses
Page number for paginated results
**Range**: Minimum value is 1. If page exceeds total pages, returns empty results with pagination metadata
Number of batch query results per page
**Range**: 1-500. Maximum page size is 500 for performance reasons
## Response
Indicates whether the overall batch request was successful
Response metadata and API information
Unique request identifier for tracking
ISO 8601 timestamp of the response
Pagination information for batch query results
Total number of queries in the batch (same as batch\_summary.total\_queries)
Current page number (matches page query parameter)
Number of batch queries per page (matches page\_size query parameter)
Array of relationship query results matching the input order
The concept ID that was queried
Whether this individual query succeeded
Error message if the query failed
Source concept information (when include\_concept\_details=true)
Source concept name
Source vocabulary
Source domain
Source concept class
Standard concept flag ("S" for standard, "C" for classification, null for non-standard)
Array of relationships for this concept
Type of relationship (e.g., "Maps to", "Is a")
The concept that this concept is related to
Related concept ID
Related concept name
Related concept vocabulary
Related concept domain
Related concept class
Standard concept flag ("S" for standard, "C" for classification, null for non-standard)
Related concept code
Relationship direction: "outbound" (from this concept) or "inbound" (to this concept)
When this relationship became valid (ISO date)
When this relationship expires (ISO date)
Summary statistics (when include\_summary=true)
Total number of relationships
Number of outbound relationships
Number of inbound relationships
Array of unique relationship types
Array of vocabularies with related concepts
Whether concept has mappings to standard concepts
Whether results were limited by max\_results
Summary statistics for the entire batch
Total number of queries requested
Number of successful queries
Number of failed queries
Total relationships across all queries
Number of unique concepts in all results
Total processing time for batch
Percentage of queries served from cache
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/relationships/batch?vocab_release=2024.1&include_concept_details=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"concept_id": 201826,
"relationship_types": ["Maps to", "Is a"],
"direction": "both",
"max_results": 20
},
{
"concept_id": 4182210,
"relationship_types": ["Maps to"],
"direction": "outbound",
"vocabulary_ids": ["ICD10CM", "ICD9CM"]
},
{
"concept_id": 313217,
"direction": "both",
"include_invalid": false,
"max_results": 30
}
]
}'
```
```python Python
import requests
def batch_relationship_query(queries, api_key):
url = "https://api.omophub.com/v1/concepts/relationships/batch"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {"queries": queries}
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example: Batch query for cardiovascular conditions
queries = [
{
"concept_id": 4182210, # Hypertension
"relationship_types": ["Maps to", "Is a"],
"direction": "both",
"max_results": 30
},
{
"concept_id": 313217, # Atrial fibrillation
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM", "SNOMED"],
"max_results": 20
},
{
"concept_id": 318443, # Heart failure
"direction": "both",
"include_invalid": False,
"max_results": 25
},
{
"concept_id": 4329847, # Myocardial infarction
"relationship_types": ["Is a", "Subsumes"],
"direction": "both",
"max_results": 35
}
]
results = batch_relationship_query(queries, "YOUR_API_KEY")
# Print batch summary
batch = results['data']['batch_summary']
print(f"Batch Results:")
print(f" Queries processed: {batch['total_queries']}")
print(f" Success rate: {batch['successful_queries']}/{batch['total_queries']}")
print(f" Total relationships: {batch['total_relationships_found']}")
print(f" Processing time: {batch['processing_time_ms']}ms")
print(f" Cache hit rate: {batch['cache_hit_rate']:.1f}%")
# Process individual results
for i, result in enumerate(results['data']['results']):
if result['success']:
concept = result['concept_details']
summary = result['summary']
print(f"\n{i+1}. {concept['concept_name']} ({result['concept_id']})")
print(f" Vocabulary: {concept['vocabulary_id']}")
print(f" Domain: {concept['domain_id']}")
print(f" Relationships: {summary['total_relationships']} ({summary['outbound_count']} out, {summary['inbound_count']} in)")
```
```javascript JavaScript
const batchRelationshipQuery = async (queries, options = {}) => {
const url = new URL('https://api.omophub.com/v1/concepts/relationships/batch');
// Add query parameters if provided
if (options.vocab_release) url.searchParams.set('vocab_release', options.vocab_release);
if (options.include_concept_details) url.searchParams.set('include_concept_details', options.include_concept_details);
const response = await fetch(url.toString(), {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ queries })
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
return response.json();
};
// Example: Get relationships for multiple diabetes-related concepts
const queries = [
{
concept_id: 201826, // Type 2 diabetes
relationship_types: ["Maps to", "Is a"],
direction: "both",
max_results: 25
},
{
concept_id: 443735, // Type 2 diabetes without complications
relationship_types: ["Maps to"],
vocabulary_ids: ["ICD10CM"],
max_results: 10
},
{
concept_id: 4048098, // Diabetes complication
direction: "inbound",
max_results: 15
}
];
const results = await batchRelationshipQuery(queries);
console.log(`Batch processing summary:`);
console.log(`- Queries: ${results.data.batch_summary.total_queries}`);
console.log(`- Success rate: ${results.data.batch_summary.successful_queries}/${results.data.batch_summary.total_queries}`);
console.log(`- Total relationships: ${results.data.batch_summary.total_relationships_found}`);
console.log(`- Cache hit rate: ${results.data.batch_summary.cache_hit_rate.toFixed(1)}%`);
results.data.results.forEach((result, index) => {
if (result.success) {
console.log(`\nConcept ${result.concept_id} (${result.concept_details?.concept_name}):`);
console.log(` - ${result.summary.total_relationships} total relationships`);
console.log(` - Types: ${result.summary.relationship_types.join(', ')}`);
console.log(` - Vocabularies: ${result.summary.connected_vocabularies.join(', ')}`);
} else {
console.error(`Query ${index + 1} failed: ${result.error}`);
}
});
```
```bash cURL (mapping focus)
curl -X POST "https://api.omophub.com/v1/concepts/relationships/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"concept_id": 201826,
"relationship_types": ["Maps to"],
"direction": "outbound"
},
{
"concept_id": 4182210,
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM"]
}
]
}'
```
```python Python (performance analysis)
import requests
import time
def analyze_batch_performance(concept_ids):
queries = [
{
"concept_id": concept_id,
"relationship_types": ["Maps to", "Is a"],
"direction": "both",
"max_results": 50,
"include_summary": True
}
for concept_id in concept_ids
]
start_time = time.time()
response = requests.post(
"https://api.omophub.com/v1/concepts/relationships/batch",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"queries": queries}
)
end_time = time.time()
data = response.json()
batch_summary = data['data']['batch_summary']
print(f"Performance Analysis:")
print(f" Client-side time: {(end_time - start_time) * 1000:.1f}ms")
print(f" Server processing: {batch_summary['processing_time_ms']}ms")
print(f" Cache hit rate: {batch_summary['cache_hit_rate']:.1f}%")
print(f" Throughput: {batch_summary['total_relationships_found'] / (batch_summary['processing_time_ms'] / 1000):.0f} relationships/sec")
# Show relationship distribution
total_rels = 0
for result in data['data']['results']:
if result['success']:
total_rels += result['summary']['total_relationships']
concept_name = result['concept_details']['concept_name']
rel_count = result['summary']['total_relationships']
print(f" {concept_name}: {rel_count} relationships")
return data
# Test with cardiovascular concepts
cardiovascular_concepts = [201826, 4182210, 313217, 318443, 4329847]
results = analyze_batch_performance(cardiovascular_concepts)
```
```json
{
"success": true,
"data": {
"results": [
{
"concept_id": 201826,
"success": true,
"concept_details": {
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"relationships": [
{
"relationship_type": "Maps to",
"related_concept": {
"concept_id": 44054006,
"concept_name": "Diabetes mellitus type 2 (disorder)",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "44054006"
},
"direction": "outbound",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
},
{
"relationship_type": "Is a",
"related_concept": {
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "73211009"
},
"direction": "outbound",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
},
{
"relationship_type": "Maps to",
"related_concept": {
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "ICD10 code",
"standard_concept": "S",
"concept_code": "E11"
},
"direction": "outbound",
"valid_start_date": "2016-10-01",
"valid_end_date": "2099-12-31"
}
],
"summary": {
"total_relationships": 15,
"outbound_count": 8,
"inbound_count": 7,
"relationship_types": ["Maps to", "Is a", "Subsumes"],
"connected_vocabularies": ["SNOMED", "ICD10CM"],
"has_standard_mappings": true,
"results_truncated": false
}
},
{
"concept_id": 4182210,
"success": true,
"concept_details": {
"concept_name": "Hypertensive disorder",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"relationships": [
{
"relationship_type": "Maps to",
"related_concept": {
"concept_id": 312327,
"concept_name": "Essential hypertension",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char billing code",
"standard_concept": null,
"concept_code": "I10"
},
"direction": "outbound",
"valid_start_date": "2015-10-01",
"valid_end_date": "2099-12-31"
}
],
"summary": {
"total_relationships": 8,
"outbound_count": 5,
"inbound_count": 3,
"relationship_types": ["Maps to", "Is a"],
"connected_vocabularies": ["SNOMED", "ICD10CM", "ICD9CM"],
"has_standard_mappings": true,
"results_truncated": false
}
}
],
"batch_summary": {
"total_queries": 2,
"successful_queries": 2,
"failed_queries": 0,
"total_relationships_found": 23,
"unique_concepts_involved": 18,
"processing_time_ms": 145,
"cache_hit_rate": 65.5
}
},
"meta": {
"request_id": "req_batch_relationships_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"pagination": {
"total_items": 2,
"page": 1,
"page_size": 50
}
}
}
```
## Usage Examples
### Basic Batch Query
Get relationships for multiple concepts:
```json
{
"queries": [
{"concept_id": 201826, "max_results": 20},
{"concept_id": 4182210, "max_results": 20},
{"concept_id": 313217, "max_results": 20}
]
}
```
### Filtered by Relationship Type
Get only mapping relationships:
```json
{
"queries": [
{
"concept_id": 201826,
"relationship_types": ["Maps to"],
"direction": "outbound"
},
{
"concept_id": 4182210,
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM"]
}
]
}
```
### Mixed Query Configuration
Different configurations per concept:
```json
{
"queries": [
{
"concept_id": 201826,
"relationship_types": ["Is a", "Subsumes"],
"direction": "both",
"max_results": 30
},
{
"concept_id": 4182210,
"relationship_types": ["Maps to"],
"vocabulary_ids": ["ICD10CM", "ICD9CM"],
"max_results": 10
}
]
}
```
## Important Notes
* **Batch size limit**: Maximum 100 queries per batch request
* **Performance optimization**: Batch processing is significantly more efficient than individual requests
* **Error isolation**: Individual query failures don't affect other queries in the batch
* **Result ordering**: Results are returned in the same order as input queries
* **Cache benefits**: Frequently accessed concepts benefit from caching, improving response times
* **Rate limiting**: Batch requests count as single requests for rate limiting purposes
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Single concept relationships
* [Traverse Relationships](/api-reference/concepts/traverse-relationships) - Complex relationship traversals
* [Get Relationship Options](/api-reference/concepts/get-relationship-options) - Available relationship types
# Check Concept Relations
Source: https://docs.omophub.com/api-reference/concepts/check-concept-relations
GET https://api.omophub.com/v1/concepts/{conceptId}/relations/any
Check if a concept has any relationships without returning the full relationship data
## Overview
This lightweight endpoint quickly determines whether a concept has any relationships to other concepts. This is useful for UI components, validation logic, or filtering concepts that are isolated vs. connected in the terminology network.
## Path Parameters
The unique OMOP concept ID to check for relationships
## Query Parameters
Comma-separated list of relationship types to check (e.g., "Maps to", "Is a")
Comma-separated list of vocabulary IDs to limit relationship check
Include reverse relationships (concepts that relate to this concept)
Include relationships to invalid/deprecated concepts
Specific vocabulary release version (e.g., "2024.1")
## Response
Indicates whether the request was successful
Response data containing relationship check results
The concept ID that was checked
Whether this concept has any relationships matching the specified criteria
Summary information about relationships (when has\_relations is true)
Total number of relationships found
Number of outbound relationships (from this concept)
Number of inbound relationships (to this concept)
Array of relationship types found (up to 10 most common)
Array of vocabularies that have related concepts (up to 10)
Whether concept has mappings to standard concepts
Details about what was checked
Relationship types included in the check
Vocabularies included in the check
Whether reverse relationships were checked
Whether invalid relationships were checked
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relations/any"
params = {
"relationship_types": "Maps to,Is a",
"include_reverse": True
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
if data['data']['has_relations']:
print(f"✓ Concept {concept_id} has relationships")
summary = data['data']['relation_summary']
print(f" - Total: {summary['total_count']}")
print(f" - Standard mappings: {'Yes' if summary['has_standard_mappings'] else 'No'}")
else:
print(f"✗ Concept {concept_id} has no relationships")
```
```javascript JavaScript
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/relations/any`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const relationCheck = await response.json();
const data = relationCheck.data;
if (data.has_relations) {
console.log(`Concept ${conceptId} has ${data.relation_summary.total_count} relationships`);
console.log('Relationship types:', data.relation_summary.relationship_types);
} else {
console.log(`Concept ${conceptId} has no relationships`);
}
```
```bash cURL (filtered check)
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any?relationship_types=Maps%20to&vocabulary_ids=ICD10CM,ICD9CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (batch validation)
import requests
def validate_concept_connectivity(concept_ids):
"""
Check which concepts have standard mappings for data quality validation
"""
connected_concepts = []
isolated_concepts = []
for concept_id in concept_ids:
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relations/any"
params = {
"relationship_types": "Maps to",
"vocabulary_ids": "SNOMED,ICD10CM,ICD9CM"
}
response = requests.get(url, params=params, headers={
"Authorization": "Bearer YOUR_API_KEY"
})
data = response.json()
concept_data = data['data']
if concept_data['has_relations'] and concept_data['relation_summary']['has_standard_mappings']:
connected_concepts.append({
'concept_id': concept_id,
'total_mappings': concept_data['relation_summary']['total_count'],
'vocabularies': concept_data['relation_summary']['connected_vocabularies']
})
else:
isolated_concepts.append(concept_id)
print(f"Connected concepts: {len(connected_concepts)}")
print(f"Isolated concepts: {len(isolated_concepts)}")
return connected_concepts, isolated_concepts
# Validate a set of cardiovascular concepts
cardiovascular_concepts = [201826, 4182210, 313217, 320128, 434557]
connected, isolated = validate_concept_connectivity(cardiovascular_concepts)
for concept in connected[:3]: # Show top 3
print(f"Concept {concept['concept_id']}: {concept['total_mappings']} mappings across {len(concept['vocabularies'])} vocabularies")
```
```json
{
"success": true,
"data": {
"concept_id": 201826,
"has_relations": true,
"relation_summary": {
"total_count": 15,
"outbound_count": 8,
"inbound_count": 7,
"relationship_types": ["Maps to", "Is a", "Has finding site", "May be a"],
"connected_vocabularies": ["SNOMED", "ICD10CM", "ICD9CM"],
"has_standard_mappings": true
},
"checks_performed": {
"included_relationship_types": ["all"],
"included_vocabularies": ["all"],
"included_reverse_relations": true,
"included_invalid_relations": false
}
},
"meta": {
"request_id": "req_check_relations_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Simple Relationship Check
Check if a concept has any relationships:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Check for Standard Mappings
Check if concept has "Maps to" relationships:
```bash
curl -G "https://api.omophub.com/v1/concepts/201826/relations/any" \
-H "Authorization: Bearer YOUR_API_KEY" \
--data-urlencode "relationship_types=Maps to"
```
### Check Cross-Vocabulary Relations
Check relationships to specific vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations/any?vocabulary_ids=ICD10CM,ICD9CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Batch Checking in Applications
Use in loops or batch processing to filter connected concepts:
```javascript
const conceptIds = [201826, 4182210, 313217];
const connectedConcepts = [];
for (const id of conceptIds) {
const response = await fetch(`/v1/concepts/${id}/relations/any`);
const result = await response.json();
const data = result.data;
if (data.has_relations && data.relation_summary.has_standard_mappings) {
connectedConcepts.push({
concept_id: id,
relation_count: data.relation_summary.total_count
});
}
}
```
## Important Notes
* **Performance optimized** - Much faster than fetching full relationship data
* **Filtering support** - All the same filters as the full relations endpoint
* **UI integration** - Perfect for showing relationship icons or badges
* **Batch processing** - Ideal for filtering large concept lists
* **Standard mappings** - The `has_standard_mappings` flag is useful for data quality checks
## Related Endpoints
* [Get Concept Relations](/api-reference/concepts/get-concept-relations) - Get full relationship data
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Detailed relationship information
* [Batch Hierarchy Queries](/api-reference/concepts/batch-hierarchy-queries) - Check relationships for multiple concepts
# Get Concept
Source: https://docs.omophub.com/api-reference/concepts/get-concept
GET /concepts/{conceptId}
Get detailed information about a specific medical concept
## Overview
Retrieve comprehensive information about a medical concept using its unique concept ID. This endpoint provides full concept details including synonyms, relationships, and classification information.
## Path Parameters
The unique concept identifier (e.g., 320128 for "Essential hypertension")
## Query Parameters
Specific vocabulary version to use (e.g., "2024.1"). Uses latest if not specified.
Include related concepts (parents, children, mappings)
Include all synonyms and alternative names
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/320128" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/320128",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
concept = response.json()
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/320128', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const concept = await response.json();
```
```bash cURL (with details)
curl -X GET "https://api.omophub.com/v1/concepts/320128?include_relationships=true&include_synonyms=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with details)
import requests
params = {
"include_relationships": True,
"include_synonyms": True
}
response = requests.get(
"https://api.omophub.com/v1/concepts/320128",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
concept = response.json()
```
```json
{
"success": true,
"data": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
"Primary hypertension",
"Idiopathic hypertension",
"High blood pressure"
],
"relationships": {
"parents": [
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"relationship_id": "Is a"
}
],
"children": [
{
"concept_id": 4124681,
"concept_name": "Accelerated hypertension",
"relationship_id": "Subsumes"
}
]
}
},
"meta": {
"request_id": "req_concept_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Common Use Cases
### 1. Clinical Decision Support
Get concept details for clinical coding and documentation systems.
### 2. Data Validation
Verify concept information during data quality checks.
### 3. User Interface Display
Show detailed concept information in healthcare applications.
### 4. Relationship Analysis
Understand concept hierarchies and mappings for analysis.
## Related Endpoints
* [Get Concept by Code](/api-reference/concepts/get-concept-by-code) - Look up concept using vocabulary-specific code
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Get all relationships for a concept
* [Search Concepts](/api-reference/search/basic-search) - Search for concepts by name or description
# Get Concept by Code
Source: https://docs.omophub.com/api-reference/concepts/get-concept-by-code
GET /concepts/by-code/{vocabulary_id}/{concept_code}
Look up a concept using its vocabulary-specific code
## Overview
Retrieve concept information using the vocabulary-specific code instead of the universal concept ID. This is useful when working with source data that contains original codes.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC")
The vocabulary-specific code (e.g., "59621000" for SNOMED, "I10" for ICD10CM)
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/by-code/SNOMED/59621000" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/by-code/SNOMED/59621000",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
concept = response.json()
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/by-code/SNOMED/59621000', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const concept = await response.json();
```
```bash cURL (ICD10CM)
curl -X GET "https://api.omophub.com/v1/concepts/by-code/ICD10CM/I10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (ICD10CM)
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/by-code/ICD10CM/I10",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
icd_concept = response.json()
```
```json
{
"success": true,
"data": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"synonyms": [
"Primary hypertension",
"Idiopathic hypertension",
"High blood pressure",
"HTN - Hypertension"
],
"mappings": [
{
"target_concept_id": 435216,
"target_concept_name": "Essential hypertension",
"target_concept_code": "I10",
"target_vocabulary_id": "ICD10CM",
"relationship_id": "Maps to",
"mapping_type": "equivalent"
}
]
},
"meta": {
"request_id": "req_concept_code_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Common Use Cases
### 1. Source Data Processing
Convert source system codes to standardized concepts during ETL processes.
### 2. Legacy System Integration
Map existing codes from legacy healthcare systems.
### 3. Claims Processing
Look up diagnosis and procedure codes from insurance claims.
### 4. Clinical Documentation
Validate codes entered by healthcare providers.
# Get Concept Level
Source: https://docs.omophub.com/api-reference/concepts/get-concept-level
GET https://api.omophub.com/v1/concepts/{conceptId}/level
Get the hierarchical level and depth information for a concept within its vocabulary taxonomy
## Overview
This endpoint returns hierarchical positioning information for a concept, including its level in the taxonomy tree, distance from root concepts, and depth metrics. This is essential for understanding concept granularity and hierarchy navigation.
## Path Parameters
The unique OMOP concept ID to get level information for
## Query Parameters
Filter to specific vocabulary (uses concept's primary vocabulary if not specified)
Relationship type to use for hierarchy calculation (e.g., "Is a", "Subsumes")
Include sample paths to root concepts
Specific vocabulary release version (e.g., "2024.1")
## Response
Indicates whether the request was successful
Response data containing concept level information
The concept being analyzed
Concept ID
Concept name
Primary vocabulary
Concept domain
Concept class
Hierarchical position information
Minimum number of levels from any root concept (most direct path)
Maximum number of levels from any root concept (longest path)
Average levels from all root concepts
Minimum levels to any leaf concept (how far from most specific)
Maximum levels to any leaf concept
Whether this concept is a root (top-level) concept
Whether this concept is a leaf (most specific) concept
Whether this concept has child concepts
Whether this concept has parent concepts
Number of direct child concepts
Number of direct parent concepts
Context within the vocabulary taxonomy
Maximum depth of the entire vocabulary hierarchy
Concept's position as percentage of vocabulary depth (0-100)
Descriptive granularity: "very\_general", "general", "moderate", "specific", "very\_specific"
Primary taxonomy branch or domain area
Sample paths to root concepts (when include\_paths=true)
Array of concepts from a root concept to this concept
Concept ID in path
Concept name
Level in this path (0 = root)
Relationship to next concept (null for terminal nodes)
Total length of this path
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/level?include_paths=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/level"
params = {
"include_paths": True,
"relationship_type": "Is a"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
concept = data['data']['concept']
hierarchy = data['data']['hierarchy_info']
context = data['data']['taxonomy_context']
print(f"Concept: {concept['concept_name']}")
print(f"Vocabulary: {concept['vocabulary_id']}")
print(f"Levels from top: {hierarchy['min_levels_from_top']}-{hierarchy['max_levels_from_top']}")
print(f"Granularity: {context['granularity_level']}")
print(f"Relative depth: {context['relative_depth_percentage']:.1f}%")
if 'sample_paths' in data['data']:
print("\nSample path to root:")
for concept_in_path in data['data']['sample_paths'][0]['path_to_root']:
print(f" Level {concept_in_path['level']}: {concept_in_path['concept_name']}")
```
```javascript JavaScript
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/level?include_paths=true`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const levelInfo = await response.json();
const data = levelInfo.data;
const hierarchy = data.hierarchy_info;
console.log(`Concept: ${data.concept.concept_name}`);
console.log(`Hierarchy level: ${hierarchy.min_levels_from_top} (min) to ${hierarchy.max_levels_from_top} (max)`);
console.log(`Granularity: ${data.taxonomy_context.granularity_level}`);
console.log(`Has children: ${hierarchy.has_children} (${hierarchy.direct_children_count})`);
```
```bash cURL (specific relationship)
curl -X GET "https://api.omophub.com/v1/concepts/201826/level?relationship_type=Subsumes&vocabulary_id=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (granularity analysis)
import requests
def analyze_concept_granularity(concept_ids):
"""
Analyze granularity levels of multiple concepts for hierarchy understanding
"""
granularity_analysis = {
'very_general': [],
'general': [],
'moderate': [],
'specific': [],
'very_specific': []
}
for concept_id in concept_ids:
url = f"https://api.omophub.com/v1/concepts/{concept_id}/level"
response = requests.get(url, headers={
"Authorization": "Bearer YOUR_API_KEY"
})
data = response.json()
concept_data = data['data']
concept_info = {
'concept_id': concept_id,
'name': concept_data['concept']['concept_name'],
'levels_from_top': concept_data['hierarchy_info']['min_levels_from_top'],
'relative_depth': concept_data['taxonomy_context']['relative_depth_percentage'],
'children_count': concept_data['hierarchy_info']['direct_children_count']
}
granularity = concept_data['taxonomy_context']['granularity_level']
granularity_analysis[granularity].append(concept_info)
# Show analysis results
for level, concepts in granularity_analysis.items():
if concepts:
print(f"\n{level.upper()} concepts ({len(concepts)}):")
for concept in concepts[:3]: # Show first 3
print(f" {concept['name']} - Level {concept['levels_from_top']}, {concept['relative_depth']:.1f}% depth")
return granularity_analysis
# Analyze cardiovascular disease concepts
cardiovascular_concepts = [201826, 4182210, 313217, 320128, 434557, 4329847]
analysis = analyze_concept_granularity(cardiovascular_concepts)
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"hierarchy_info": {
"min_levels_from_top": 4,
"max_levels_from_top": 6,
"average_levels_from_top": 4.8,
"min_levels_from_bottom": 2,
"max_levels_from_bottom": 5,
"is_root_concept": false,
"is_leaf_concept": false,
"has_children": true,
"has_parents": true,
"direct_children_count": 15,
"direct_parents_count": 3
},
"taxonomy_context": {
"vocabulary_max_depth": 12,
"relative_depth_percentage": 40.0,
"granularity_level": "moderate",
"taxonomy_branch": "Endocrine/metabolic disorders"
},
"sample_paths": [
{
"path_to_root": [
{
"concept_id": 138875005,
"concept_name": "SNOMED CT Concept",
"level": 0,
"relationship_type": "Is a"
},
{
"concept_id": 404684003,
"concept_name": "Clinical finding",
"level": 1,
"relationship_type": "Is a"
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"level": 2,
"relationship_type": "Is a"
},
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"level": 3,
"relationship_type": "Is a"
},
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"level": 4,
"relationship_type": null
}
],
"path_length": 5
}
]
},
"meta": {
"request_id": "req_concept_level_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Level Information
Get hierarchy level for a concept:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/level" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Level with Sample Paths
Get level information including sample paths to root:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/level?include_paths=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specific Relationship Type
Use different relationship type for hierarchy calculation:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/level?relationship_type=Subsumes" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Important Notes
* **Multiple paths** - Concepts may have multiple paths to root concepts with different lengths
* **Granularity classification** - Helps understand concept specificity level
* **Performance** - Including paths increases response time but provides valuable context
* **Vocabulary differences** - Hierarchy depth varies significantly between vocabularies
* **Relationship types** - Different relationship types may produce different hierarchy views
## Related Endpoints
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Full hierarchical tree
* [Get Concept Ancestors](/api-reference/concepts/get-concept-ancestors) - Parent concepts
* [Get Concept Descendants](/api-reference/concepts/get-concept-descendants) - Child concepts
# Get Concept Relations
Source: https://docs.omophub.com/api-reference/concepts/get-concept-relations
GET https://api.omophub.com/v1/concepts/{conceptId}/relations
Retrieve all direct relationships and related concepts for a specific concept
## Overview
This endpoint returns all direct relationships for a given concept, showing how it connects to other concepts in the medical terminology network. This includes both inbound and outbound relationships with their relationship types.
## Path Parameters
The unique OMOP concept ID to get relations for
## Query Parameters
Comma-separated list of relationship types to filter by (e.g., "Maps to", "Is a")
Comma-separated list of vocabulary IDs to filter related concepts
Include reverse relationships (concepts that relate to this concept)
Include relationships to invalid/deprecated concepts
Specific vocabulary release version (e.g., "2024.1")
Page number for pagination (1-based)
Number of relationships per page
## Response
The source concept details
Source concept ID
Source concept name
Source vocabulary
Array of direct relationships
Type of relationship (e.g., "Maps to", "Is a", "RxNorm has dose form")
The concept that this concept is related to
Related concept ID
Related concept name
Related concept vocabulary
Related concept domain
Related concept class
Standard concept flag. Values: "S" (Standard), "C" (Classification), or null (Non-standard)
Related concept code
Relationship direction: "outbound" (from this concept) or "inbound" (to this concept)
When this relationship became valid (ISO date)
When this relationship expires (ISO date)
Reason if relationship is invalid
Summary statistics for relationships
Total number of relationships
Number of outbound relationships
Number of inbound relationships
Array of unique relationship types found
Array of vocabularies with related concepts
Indicates if the request was successful
Response metadata including pagination and request information
Pagination information
Current page number
Number of items per page
Total number of relationships
Total number of pages
Whether there are more pages
Whether there are previous pages
Unique request identifier for debugging
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations?relationship_types=Maps%20to,Is%20a" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relations"
params = {
"relationship_types": "Maps to,Is a",
"include_reverse": True,
"page_size": 50
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Concept: {data['data']['concept']['concept_name']}")
print(f"Total relations: {data['data']['summary']['total_relations']}")
for relation in data['data']['relations']:
direction_arrow = "->" if relation['direction'] == 'outbound' else "<-"
print(f"{relation['relationship_type']} {direction_arrow} {relation['related_concept']['concept_name']}")
```
```javascript JavaScript
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/relations?include_reverse=true`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const relations = await response.json();
console.log('Found', relations.data.summary.total_relations, 'relationships');
relations.data.relations.forEach(rel => {
console.log(`${rel.direction}: ${rel.relationship_type} -> ${rel.related_concept.concept_name}`);
});
```
```bash cURL (vocabulary filtering)
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations?vocabulary_ids=ICD10CM,ICD9CM&include_reverse=false" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (detailed analysis)
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/201826/relations",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={
"include_reverse": True,
"page_size": 100
}
)
data = response.json()
relations = data['data']['relations']
summary = data['data']['summary']
print(f"Relationship Analysis for: {data['data']['concept']['concept_name']}")
print(f"Total relationships: {summary['total_relations']}")
print(f"Outbound: {summary['outbound_count']}, Inbound: {summary['inbound_count']}")
# Group by relationship type
from collections import defaultdict
by_type = defaultdict(list)
for rel in relations:
by_type[rel['relationship_type']].append(rel)
for rel_type, rels in by_type.items():
print(f"\n{rel_type} ({len(rels)} relationships):")
for rel in rels[:3]: # Show first 3
direction = "→" if rel['direction'] == 'outbound' else "←"
vocab = rel['related_concept']['vocabulary_id']
name = rel['related_concept']['concept_name']
print(f" {direction} {name} ({vocab})")
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED"
},
"relations": [
{
"relationship_type": "Maps to",
"related_concept": {
"concept_id": 1567956,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char billing code",
"standard_concept": null,
"concept_code": "E11.9"
},
"direction": "inbound",
"valid_start_date": "2015-10-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"relationship_type": "Is a",
"related_concept": {
"concept_id": 201820,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "73211009"
},
"direction": "outbound",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"relationship_type": "Has finding site",
"related_concept": {
"concept_id": 113331007,
"concept_name": "Endocrine system structure",
"vocabulary_id": "SNOMED",
"domain_id": "Spec Anatomic Site",
"concept_class_id": "Body Structure",
"standard_concept": "S",
"concept_code": "113331007"
},
"direction": "outbound",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"relationship_type": "Mapped from",
"related_concept": {
"concept_id": 44835411,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD9CM",
"domain_id": "Condition",
"concept_class_id": "3-dig billing code",
"standard_concept": null,
"concept_code": "250.00"
},
"direction": "inbound",
"valid_start_date": "1970-01-01",
"valid_end_date": "2014-09-30",
"invalid_reason": null
}
],
"summary": {
"total_relations": 15,
"outbound_count": 8,
"inbound_count": 7,
"relationship_types": ["Maps to", "Is a", "Has finding site", "May be a", "Mapped from"],
"vocabularies_involved": ["SNOMED", "ICD10CM", "ICD9CM"]
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 15,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"request_id": "req_relations_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Get All Relations
Retrieve all relationships for a concept:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filter by Relationship Type
Get only mapping relationships:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations?relationship_types=Maps%20to" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cross-Vocabulary Relations
Find relationships to concepts in specific vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relations?vocabulary_ids=ICD10CM,ICD9CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Important Notes
* **Relationship direction** indicates whether this concept relates to another (outbound) or vice versa (inbound)
* **Standard concepts** are preferred for analysis and should be prioritized
* **Invalid relationships** are excluded by default but can be included for historical analysis
* **Pagination** is available for concepts with many relationships
* **Performance** may vary based on concept complexity and filter criteria
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Detailed relationship information
* [Get Related Concepts](/api-reference/concepts/get-related-concepts) - Find conceptually related items
* [Check Concept Relations](/api-reference/concepts/check-concept-relations) - Verify if concept has any relations
# Get Concept Relationships
Source: https://docs.omophub.com/api-reference/concepts/get-concept-relationships
GET /concepts/{conceptId}/relationships
Get all relationships for a specific concept
## Overview
Retrieve all relationships for a concept, including hierarchical relationships (parents/children), mappings to other vocabularies, and semantic relationships.
## Path Parameters
The unique concept identifier
## Query Parameters
Filter by relationship type (e.g., "Is a", "Subsumes", "Maps to")
Filter relationships to specific target vocabulary
Include relationships to invalid concepts
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/320128/relationships",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
relationships = response.json()
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/320128/relationships', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const relationships = await response.json();
```
```bash cURL (filtered)
curl -X GET "https://api.omophub.com/v1/concepts/320128/relationships?relationshipType=Is%20a&targetVocabulary=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (filtered)
import requests
params = {
"relationshipType": "Is a",
"targetVocabulary": "SNOMED"
}
response = requests.get(
"https://api.omophub.com/v1/concepts/320128/relationships",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
filtered_relationships = response.json()
```
```json
{
"success": true,
"data": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"relationships": [
{
"relationship_id": "Is a",
"target_concept_id": 316866,
"target_concept_name": "Hypertensive disorder",
"target_concept_code": "38341003",
"target_vocabulary_id": "SNOMED",
"target_domain_id": "Condition",
"relationship_direction": "outbound",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"relationship_id": "Subsumes",
"target_concept_id": 4124681,
"target_concept_name": "Accelerated hypertension",
"target_concept_code": "48146000",
"target_vocabulary_id": "SNOMED",
"target_domain_id": "Condition",
"relationship_direction": "inbound",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"relationship_id": "Maps to",
"target_concept_id": 435216,
"target_concept_name": "Essential hypertension",
"target_concept_code": "I10",
"target_vocabulary_id": "ICD10CM",
"target_domain_id": "Condition",
"relationship_direction": "outbound",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"relationship_id": "Has finding site",
"target_concept_id": 4103720,
"target_concept_name": "Cardiovascular system",
"target_concept_code": "113257007",
"target_vocabulary_id": "SNOMED",
"target_domain_id": "Spec Anatomic Site",
"relationship_direction": "outbound",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
}
],
"relationship_summary": {
"total_relationships": 4,
"relationship_types": ["Is a", "Subsumes", "Maps to", "Has finding site"],
"target_vocabularies": ["SNOMED", "ICD10CM"],
"target_domains": ["Condition", "Spec Anatomic Site"]
}
},
"meta": {
"request_id": "req_relationships_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
# Get Concept Terms Count
Source: https://docs.omophub.com/api-reference/concepts/get-concept-terms-count
GET https://api.omophub.com/v1/concepts/:conceptId/terms/count
Retrieve the count of terms, synonyms, and linguistic variants associated with a specific concept across different languages and usage contexts.
## Overview
This endpoint provides detailed counts of all linguistic terms associated with a concept, including synonyms, abbreviations, translations, and contextual variants. It helps assess the linguistic richness and multilingual coverage of medical concepts.
## Path Parameters
The unique concept identifier
## Query Parameters
Include detailed breakdown by term type and language
Include example terms for each category
Filter counts to specific languages (comma-separated BCP 47 language tags)
**Examples**: `en`, `es,fr,de`, `en-US,en-GB`
Filter to specific term types (comma-separated)
**Options**: `preferred`, `synonym`, `fully_specified_name`, `definition`, `abbreviation`, `brand_name`, `trade_name`
**Note**: Acronyms are counted under the `abbreviation` type
Include counts from historical/deprecated terms
Filter by usage context (comma-separated)
**Options**: `clinical`, `research`, `billing`, `patient_facing`, `administrative`
How to handle case variations in counting
**Options**: `sensitive`, `insensitive`, `normalized`
**Normalization process** (for `normalized` option): Applies Unicode NFKC normalization, lowercase folding, diacritics removal, punctuation/whitespace stripping, and accent-insensitive comparisons using ICU library algorithms. Normalization is applied before tokenization to ensure consistent term matching.
Include spelling and morphological variants
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
The concept identifier
Primary concept name
Concept code
Source vocabulary identifier
Domain classification
Total number of all terms
Number of unique terms (after deduplication)
Number of languages with terms
Number of preferred terms
Number of synonyms
Number of abbreviations and acronyms
Number of translated terms across non-primary languages (counts unique terms, not total translations)
Number of spelling and morphological variants
Breakdown by term type (if include\_breakdown=true)
Type of term
Human-readable term type name
Number of terms of this type
Percentage of total terms
Languages where this term type appears
Example terms (if include\_examples=true)
Breakdown by language (if include\_breakdown=true)
ISO 639-1 language code
Human-readable language name
Number of terms in this language
Percentage of total terms
Whether this is the concept's primary language
Term types available in this language
Language completeness score (0-100)
Example terms in this language (if include\_examples=true)
Breakdown by usage context (if include\_breakdown=true). Percentages may exceed 100% due to terms belonging to multiple contexts.
Usage context identifier
Human-readable context name
Number of terms for this context
Percentage of total terms
Most common term types in this context
Example terms for this context (if include\_examples=true)
Average character length of terms
Distribution of terms by length categories
Most common term prefixes
Most common term suffixes
Analysis of capitalization usage
Analysis of punctuation in terms
Analysis of numeric content in terms
Comparison with historical versions (if include\_historical=true)
Total terms in previous version
Net change in term count
Number of terms added
Number of terms removed
Number of terms modified
Overall trend (Increasing, Decreasing, Stable)
Overall term completeness score (0-100)
Linguistic diversity score (0-100)
Assessment of term coverage (Excellent, Good, Fair, Poor)
Term types that are missing or underrepresented
Multilingual coverage score (0-100)
Number of potential duplicate terms identified
Parameters used for counting
How recent the term data is
Unique identifier for the request
Request timestamp
Vocabulary release version used
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/terms/count?include_breakdown=true&include_examples=true&language_filter=en,es,fr" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript
const response = await fetch('https://api.omophub.com/v1/concepts/201826/terms/count?include_breakdown=true&term_types=preferred,synonym,abbreviation&include_examples=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'include_breakdown': True,
'include_examples': True,
'language_filter': 'en,es,fr,de',
'term_types': 'preferred,synonym,abbreviation',
'context_filter': 'clinical,patient_facing',
'include_historical': True,
'include_variants': True
}
response = requests.get(
'https://api.omophub.com/v1/concepts/201826/terms/count',
headers=headers,
params=params
)
data = response.json()
```
```json
{
"success": true,
"data": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"overall_counts": {
"total_terms": 47,
"unique_terms": 43,
"languages_covered": 8,
"preferred_terms": 8,
"synonyms": 24,
"abbreviations": 9,
"translations": 19,
"variants": 6
},
"term_type_breakdown": [
{
"term_type": "preferred",
"term_type_name": "Preferred Term",
"count": 8,
"percentage": 17.0,
"languages": ["en", "es", "fr", "de", "it", "pt", "nl", "sv"],
"example_terms": [
"Type 2 diabetes mellitus",
"Diabetes mellitus tipo 2",
"Diabète sucré de type 2",
"Diabetes mellitus Typ 2"
]
},
{
"term_type": "synonym",
"term_type_name": "Synonym",
"count": 24,
"percentage": 51.1,
"languages": ["en", "es", "fr", "de", "it"],
"example_terms": [
"Adult-onset diabetes",
"Non-insulin dependent diabetes mellitus",
"Mature onset diabetes",
"Type II diabetes",
"Diabetes mellitus no insulinodependiente"
]
},
{
"term_type": "abbreviation",
"term_type_name": "Abbreviation (includes acronyms)",
"count": 9,
"percentage": 19.1,
"languages": ["en", "es", "fr", "de"],
"example_terms": [
"T2DM",
"NIDDM",
"DM2",
"Type 2 DM",
"DMTII"
]
},
{
"term_type": "fully_specified_name",
"term_type_name": "Fully Specified Name",
"count": 4,
"percentage": 8.5,
"languages": ["en", "es", "fr", "de"],
"example_terms": [
"Type 2 diabetes mellitus (disorder)",
"Diabetes mellitus tipo 2 (trastorno)"
]
},
{
"term_type": "definition",
"term_type_name": "Definition",
"count": 2,
"percentage": 4.3,
"languages": ["en", "fr"],
"example_terms": [
"A form of diabetes mellitus characterized by insulin resistance",
"Forme de diabète caractérisée par une résistance à l'insuline"
]
}
],
"language_breakdown": [
{
"language_code": "en",
"language_name": "English",
"term_count": 19,
"percentage": 40.4,
"is_primary_language": true,
"term_types_available": ["preferred", "synonym", "abbreviation", "fully_specified_name", "definition"],
"completeness_score": 95.2,
"example_terms": [
"Type 2 diabetes mellitus",
"Adult-onset diabetes",
"Non-insulin dependent diabetes mellitus",
"T2DM",
"NIDDM"
]
},
{
"language_code": "es",
"language_name": "Spanish",
"term_count": 8,
"percentage": 17.0,
"is_primary_language": false,
"term_types_available": ["preferred", "synonym", "abbreviation"],
"completeness_score": 78.3,
"example_terms": [
"Diabetes mellitus tipo 2",
"Diabetes no insulinodependiente",
"DM2",
"DMTII"
]
},
{
"language_code": "fr",
"language_name": "French",
"term_count": 6,
"percentage": 12.8,
"is_primary_language": false,
"term_types_available": ["preferred", "synonym", "abbreviation", "definition"],
"completeness_score": 72.1,
"example_terms": [
"Diabète sucré de type 2",
"Diabète non insulino-dépendant",
"DNID"
]
},
{
"language_code": "de",
"language_name": "German",
"term_count": 5,
"percentage": 10.6,
"is_primary_language": false,
"term_types_available": ["preferred", "synonym", "abbreviation"],
"completeness_score": 68.9,
"example_terms": [
"Diabetes mellitus Typ 2",
"Nicht-insulinabhängiger Diabetes mellitus",
"NIDDM"
]
},
{
"language_code": "it",
"language_name": "Italian",
"term_count": 3,
"percentage": 6.4,
"is_primary_language": false,
"term_types_available": ["preferred", "synonym"],
"completeness_score": 52.7,
"example_terms": [
"Diabete mellito di tipo 2",
"Diabete non insulino-dipendente"
]
},
{
"language_code": "pt",
"language_name": "Portuguese",
"term_count": 2,
"percentage": 4.3,
"is_primary_language": false,
"term_types_available": ["preferred"],
"completeness_score": 34.2,
"example_terms": [
"Diabetes mellitus tipo 2"
]
},
{
"language_code": "nl",
"language_name": "Dutch",
"term_count": 2,
"percentage": 4.3,
"is_primary_language": false,
"term_types_available": ["preferred"],
"completeness_score": 31.8,
"example_terms": [
"Diabetes mellitus type 2"
]
},
{
"language_code": "sv",
"language_name": "Swedish",
"term_count": 2,
"percentage": 4.3,
"is_primary_language": false,
"term_types_available": ["preferred"],
"completeness_score": 28.9,
"example_terms": [
"Diabetes mellitus typ 2"
]
}
],
"context_breakdown": [
{
"context": "clinical",
"context_name": "Clinical Documentation",
"term_count": 32,
"percentage": 68.1,
"primary_term_types": ["preferred", "synonym", "fully_specified_name"],
"example_terms": [
"Type 2 diabetes mellitus",
"Non-insulin dependent diabetes mellitus",
"Adult-onset diabetes"
]
},
{
"context": "patient_facing",
"context_name": "Patient Communication",
"term_count": 15,
"percentage": 31.9,
"primary_term_types": ["synonym", "abbreviation"],
"example_terms": [
"Type 2 diabetes",
"Adult diabetes",
"T2D"
]
},
{
"context": "research",
"context_name": "Research and Academic",
"term_count": 8,
"percentage": 17.0,
"primary_term_types": ["fully_specified_name", "definition"],
"example_terms": [
"Type 2 diabetes mellitus (disorder)",
"Non-insulin-dependent diabetes mellitus"
]
},
{
"context": "billing",
"context_name": "Medical Billing",
"term_count": 6,
"percentage": 12.8,
"primary_term_types": ["preferred", "abbreviation"],
"example_terms": [
"Type 2 diabetes mellitus",
"T2DM",
"DM2"
]
}
],
"linguistic_analysis": {
"average_term_length": 28.4,
"term_length_distribution": {
"short (1-15 chars)": 6,
"medium (16-30 chars)": 23,
"long (31-50 chars)": 15,
"very_long (50+ chars)": 3
},
"common_prefixes": [
{"prefix": "Type", "count": 12},
{"prefix": "Non-", "count": 8},
{"prefix": "Adult", "count": 4},
{"prefix": "Diabetes", "count": 35}
],
"common_suffixes": [
{"suffix": "diabetes", "count": 28},
{"suffix": "mellitus", "count": 24},
{"suffix": "DM", "count": 8}
],
"capitalization_patterns": {
"title_case": 18,
"sentence_case": 12,
"all_caps": 9,
"mixed": 8
},
"punctuation_usage": {
"with_punctuation": 4,
"without_punctuation": 43,
"common_punctuation": ["-", "(", ")"]
},
"numeric_content": {
"terms_with_numbers": 31,
"most_common_numbers": ["2", "II"],
"percentage_with_numbers": 66.0
}
},
"historical_comparison": {
"previous_total": 42,
"change_count": 5,
"terms_added": 6,
"terms_removed": 1,
"terms_modified": 3,
"trend": "Increasing"
},
"quality_metrics": {
"completeness_score": 87.3,
"diversity_score": 82.1,
"coverage_assessment": "Good",
"missing_term_types": [
"brand_name",
"trade_name"
],
"multilingual_coverage": 76.4,
"potential_duplicates": 2
},
"counting_parameters": {
"include_breakdown": true,
"include_examples": true,
"language_filter": ["en", "es", "fr", "de", "it", "pt", "nl", "sv"],
"case_sensitivity": "insensitive"
},
"data_freshness": "Updated 2 hours ago"
},
"meta": {
"request_id": "req_concept_terms_count_123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Term Count
Get basic term count for a concept:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/terms/count" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Detailed Breakdown with Examples
Get comprehensive term analysis:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/terms/count?include_breakdown=true&include_examples=true&include_historical=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Multilingual Analysis
Focus on specific languages:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/terms/count?language_filter=en,es,fr,de&include_breakdown=true&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Term Type Analysis
Analyze specific term types:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/73211009/terms/count?term_types=synonym,abbreviation&include_breakdown=true&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Context-Specific Counting
Focus on specific usage contexts:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/440383006/terms/count?context_filter=clinical,patient_facing&include_breakdown=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Historical Trend Analysis
Compare with historical versions:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/terms/count?include_historical=true&include_breakdown=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Term Types
### Preferred Terms
* **Description**: Official or primary terms for the concept
* **Usage**: Clinical documentation, official references
* **Characteristics**: One per language, standardized format
### Synonyms
* **Description**: Alternative terms with equivalent meaning
* **Usage**: Search enhancement, user flexibility
* **Characteristics**: Multiple variants, clinical and lay terms
### Abbreviations/Acronyms
* **Description**: Shortened forms of concept names
* **Usage**: Efficient documentation, common usage
* **Characteristics**: Letters, numbers, specialized notation
### Fully Specified Names
* **Description**: Unambiguous terms with semantic tags
* **Usage**: Disambiguation, formal classification
* **Characteristics**: Includes semantic qualifiers in parentheses
### Definitions
* **Description**: Explanatory text defining the concept
* **Usage**: Education, clarification, reference
* **Characteristics**: Comprehensive descriptions
### Brand/Trade Names
* **Description**: Commercial names for products
* **Usage**: Pharmaceutical and device identification
* **Characteristics**: Proprietary terminology
## Language Coverage
### Primary Language
* **English**: Most comprehensive coverage
* **Characteristics**: All term types available
* **Completeness**: Typically 90-100%
### Major International Languages
* **Spanish, French, German**: Good coverage
* **Term Types**: Preferred, synonyms, abbreviations
* **Completeness**: Typically 60-85%
### Regional Languages
* **Italian, Portuguese, Dutch, Swedish**: Moderate coverage
* **Term Types**: Primarily preferred terms
* **Completeness**: Typically 30-65%
### Specialized Languages
* **Medical Latin, Greek roots**: Limited but specific
* **Usage**: Etymology, formal classification
* **Completeness**: Variable by vocabulary
## Quality Metrics
### Completeness Score (0-100)
* **90-100**: Excellent term coverage across types and languages
* **75-89**: Good coverage with minor gaps
* **60-74**: Fair coverage, some term types missing
* **Below 60**: Limited coverage, significant gaps
### Diversity Score (0-100)
* **Measures**: Variety of term types and linguistic patterns
* **High Score**: Rich synonym sets, multiple abbreviations
* **Low Score**: Limited variation, primarily preferred terms
### Multilingual Coverage (0-100)
* **Measures**: Distribution across languages
* **High Score**: Terms available in many languages
* **Low Score**: Limited to primary language
## Use Cases
### Clinical Documentation
* **Need**: Preferred terms and clinical synonyms
* **Focus**: Accuracy, standardization
* **Languages**: Primary language emphasis
### Patient Communication
* **Need**: Lay terms, simplified language
* **Focus**: Understandability, cultural sensitivity
* **Languages**: Patient population languages
### Search and Retrieval
* **Need**: Comprehensive synonym coverage
* **Focus**: Findability, variant recognition
* **Languages**: Multilingual support
### Billing and Coding
* **Need**: Official terms, standard abbreviations
* **Focus**: Compliance, consistency
* **Languages**: Regulatory language requirements
### Research and Analytics
* **Need**: Fully specified names, definitions
* **Focus**: Precision, disambiguation
* **Languages**: Scientific terminology
## Related Endpoints
* [Get Concept](/api-reference/concepts/get-concept) - Complete concept information including terms
* [Search Concepts](/api-reference/concepts/search-concepts) - Search using various terms
* [Suggest Concepts](/api-reference/concepts/suggest-concepts) - Term-based concept suggestions
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Related concept terms
# Get Trending Concepts
Source: https://docs.omophub.com/api-reference/concepts/get-concept-trending
GET https://api.omophub.com/v1/concepts/trending
Retrieve trending concepts based on usage patterns, search frequency, and clinical relevance across different time periods.
## Overview
This endpoint provides insights into trending concepts by analyzing usage patterns, search frequency, clinical adoption, and temporal relevance. It helps identify emerging medical concepts, popular terminology, and shifts in clinical focus.
## Query Parameters
Time period for trend analysis
**Options**: `1d`, `7d`, `30d`, `90d`, `1y`, `custom`
Start date for custom time period (ISO 8601 format)
**Required when**: `time_period=custom`
End date for custom time period (ISO 8601 format)
**Required when**: `time_period=custom`
Type of trending analysis
**Options**: `usage`, `search_frequency`, `clinical_adoption`, `emerging`, `declining`, `seasonal`
Filter trends to specific vocabularies
**String format (CSV)**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM`
**Array format (repeated params)**: `?vocabulary_ids=ICD10CM&vocabulary_ids=LOINC&vocabulary_ids=RXNORM`
Pass multiple values as a comma-separated string or as repeated query parameters; do not use a request body for GET.
Filter trends to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter trends to specific concept classes (comma-separated)
Focus on specific medical specialties (comma-separated)
**Examples**: `cardiology,oncology`, `emergency_medicine`
Minimum trend score to include (higher = more trending)
Include detailed usage statistics
**Accepted values**: true/false (case-insensitive), 1/0, yes/no. Default: false when omitted.
Include related trending concepts
**Accepted values**: true/false (case-insensitive), 1/0, yes/no. Default: false when omitted.
Include trending context and reasons
**Accepted values**: true/false (case-insensitive), 1/0, yes/no. Default: false when omitted.
Sort order for results
**Options**:
* `trend_score`: Composite numeric score (0-10) based on usage, growth, and relevance
* `usage_count`: Integer count of API requests and searches
* `growth_rate`: Percentage change over analysis period
* `clinical_relevance`: Mapped from clinical impact levels (low=1, medium=3, high=5, critical=7)
**Tie-breaker**: When primary sort values are equal, secondary sort is by concept\_id ascending.
**Invalid values**: Return 400 Bad Request with allowed values listed.
Sort direction: `asc` or `desc`. Default behavior applies descending sort when omitted.
Page number for pagination
Number of trending concepts per page (max 100)
**Validation**: If page\_size > 100, the value is clamped to 100. Example: `?page_size=150` returns 100 results.
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Time period analyzed
ISO 8601 timestamp of analysis
Total number of concepts in trend analysis
Number of concepts identified as trending
Average trend score across all trending concepts
Number of emerging concepts
Number of growing concepts
Number of stable popular concepts
Number of seasonally trending concepts
Array of trending concepts
Unique concept identifier
Primary concept name
Concept code
Source vocabulary identifier
Domain classification
Concept class identifier
Calculated trend score (higher = more trending)
Category of trending (emerging, growing, stable\_popular, seasonal)
Percentage growth rate over the period
Ranking position in trending list
Change in ranking from previous period
Detailed usage statistics (if include\_statistics=true)
Usage count in current period
Usage count in previous period
Number of searches for this concept
Number of API requests for this concept
Number of unique users accessing this concept
Geographic distribution of usage
Usage breakdown by medical specialty
Context about why this concept is trending (if include\_context=true)
Main factors contributing to trending status
Assessment of clinical relevance
Mentions in medical news/literature
Whether trending is seasonal
Whether related to disease outbreak
Whether influenced by regulatory changes
Whether related to new technology adoption
Related concepts that are also trending (if include\_related\_concepts=true)
Related concept ID
Related concept name
Type of relationship
Trend score of related concept
Days of week with highest usage
Hours of day with highest usage (integers 0-23, UTC timezone)
Identified seasonal pattern (if any), may be null
Expected future trend direction
Trending patterns by domain
Domain identifier
Human-readable domain name
Number of trending concepts in this domain
Average growth rate for this domain
Most trending concept in this domain
Trending patterns by vocabulary
Vocabulary identifier
Vocabulary name
Number of trending concepts from this vocabulary
Overall trend momentum (High, Medium, Low)
Unique identifier for the request
Request timestamp (ISO 8601 UTC format)
Parameters used for trend analysis
How recent the trend data is (nullable, UTC timestamps)
Current page number
Items per page
Total number of trending concepts
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/trending?time_period=30d&trend_type=usage&vocabulary_ids=SNOMED,ICD10CM&include_statistics=true&include_context=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/trending?time_period=7d&domains=Condition,Procedure&specialty_focus=cardiology&include_related_concepts=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'time_period': '30d',
'trend_type': 'emerging',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition,Drug',
'include_statistics': True,
'include_context': True,
'include_related_concepts': True,
'page_size': 50
}
response = requests.get(
'https://api.omophub.com/v1/concepts/trending',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"trend_analysis": {
"time_period": "30d",
"analysis_date": "2024-01-15T10:30:00Z",
"total_concepts_analyzed": 2547832,
"trending_concepts_found": 156,
"average_trend_score": 3.7,
"trend_categories": {
"emerging": 23,
"growing": 67,
"stable_popular": 45,
"seasonal": 21
}
},
"trending_concepts": [
{
"concept_id": 1240541000000107,
"concept_name": "COVID-19 Omicron variant BA.5 infection",
"concept_code": "1240541000000107",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"trend_score": 8.9,
"trend_category": "emerging",
"growth_rate": 245.7,
"ranking": 1,
"ranking_change": 127,
"usage_statistics": {
"current_usage_count": 15847,
"previous_usage_count": 4589,
"search_frequency": 2847,
"api_requests": 12459,
"unique_users": 1247,
"geographic_distribution": [
{"region": "North America", "percentage": 42.3},
{"region": "Europe", "percentage": 28.9},
{"region": "Asia Pacific", "percentage": 21.4},
{"region": "Others", "percentage": 7.4}
],
"specialty_usage": [
{"specialty": "infectious_disease", "percentage": 34.2},
{"specialty": "emergency_medicine", "percentage": 28.7},
{"specialty": "internal_medicine", "percentage": 23.1},
{"specialty": "pulmonology", "percentage": 14.0}
]
},
"trending_context": {
"primary_factors": [
"Recent COVID-19 variant emergence",
"Increased clinical documentation",
"Public health reporting requirements",
"Research publication surge"
],
"clinical_relevance": "High - new variant requiring specific coding",
"news_mentions": 1247,
"seasonal_factor": false,
"outbreak_related": true,
"regulatory_impact": true,
"technology_adoption": false
},
"related_trending_concepts": [
{
"concept_id": 840539006,
"concept_name": "Disease caused by severe acute respiratory syndrome coronavirus 2",
"relationship_type": "Is a",
"trend_score": 6.4
},
{
"concept_id": 1240521000000100,
"concept_name": "COVID-19 vaccination",
"relationship_type": "Clinical context",
"trend_score": 5.8
}
],
"temporal_pattern": {
"peak_days": ["Monday", "Tuesday", "Friday"],
"peak_hours": [9, 10, 14, 15],
"seasonal_pattern": null,
"trend_trajectory": "Stabilizing"
}
},
{
"concept_id": 429914006,
"concept_name": "Hybrid cardiac catheterization procedure",
"concept_code": "429914006",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"trend_score": 7.2,
"trend_category": "growing",
"growth_rate": 156.3,
"ranking": 2,
"ranking_change": 15,
"usage_statistics": {
"current_usage_count": 8934,
"previous_usage_count": 3467,
"search_frequency": 1456,
"api_requests": 7234,
"unique_users": 567,
"geographic_distribution": [
{"region": "North America", "percentage": 58.2},
{"region": "Europe", "percentage": 31.4},
{"region": "Asia Pacific", "percentage": 8.1},
{"region": "Others", "percentage": 2.3}
],
"specialty_usage": [
{"specialty": "cardiology", "percentage": 67.8},
{"specialty": "cardiac_surgery", "percentage": 28.9},
{"specialty": "interventional_radiology", "percentage": 3.3}
]
},
"trending_context": {
"primary_factors": [
"Advancement in cardiac intervention techniques",
"Increased adoption in major medical centers",
"Positive clinical outcomes reporting",
"Medical device technology improvements"
],
"clinical_relevance": "High - innovative cardiac intervention",
"news_mentions": 234,
"seasonal_factor": false,
"outbreak_related": false,
"regulatory_impact": false,
"technology_adoption": true
},
"related_trending_concepts": [
{
"concept_id": 41976001,
"concept_name": "Cardiac catheterization",
"relationship_type": "Is a",
"trend_score": 4.2
},
{
"concept_id": 232717009,
"concept_name": "Coronary artery disease",
"relationship_type": "Treats",
"trend_score": 3.8
}
],
"temporal_pattern": {
"peak_days": ["Tuesday", "Wednesday", "Thursday"],
"peak_hours": [8, 9, 10, 13, 14],
"seasonal_pattern": null,
"trend_trajectory": "Growing"
}
},
{
"concept_id": 6142004,
"concept_name": "Influenza",
"concept_code": "6142004",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"trend_score": 5.4,
"trend_category": "seasonal",
"growth_rate": 89.2,
"ranking": 3,
"ranking_change": -5,
"usage_statistics": {
"current_usage_count": 23456,
"previous_usage_count": 12389,
"search_frequency": 3456,
"api_requests": 18234,
"unique_users": 2156,
"geographic_distribution": [
{"region": "North America", "percentage": 45.6},
{"region": "Europe", "percentage": 31.2},
{"region": "Asia Pacific", "percentage": 18.7},
{"region": "Others", "percentage": 4.5}
],
"specialty_usage": [
{"specialty": "family_medicine", "percentage": 42.1},
{"specialty": "internal_medicine", "percentage": 28.7},
{"specialty": "emergency_medicine", "percentage": 18.9},
{"specialty": "pediatrics", "percentage": 10.3}
]
},
"trending_context": {
"primary_factors": [
"Seasonal influenza outbreak",
"Increased clinical visits",
"Public health surveillance",
"Vaccination campaign timing"
],
"clinical_relevance": "High - seasonal disease pattern",
"news_mentions": 567,
"seasonal_factor": true,
"outbreak_related": true,
"regulatory_impact": false,
"technology_adoption": false
},
"related_trending_concepts": [
{
"concept_id": 836500008,
"concept_name": "Influenza vaccination",
"relationship_type": "Prevented by",
"trend_score": 4.9
},
{
"concept_id": 195967001,
"concept_name": "Asthma",
"relationship_type": "Risk factor for",
"trend_score": 3.2
}
],
"temporal_pattern": {
"peak_days": ["Monday", "Tuesday", "Friday"],
"peak_hours": [8, 9, 10, 11, 13, 14],
"seasonal_pattern": "Winter peak (December-February)",
"trend_trajectory": "Seasonal decline expected"
}
}
],
"domain_trends": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"trending_concepts_count": 67,
"average_growth_rate": 78.4,
"top_trending_concept": {
"concept_id": 1240541000000107,
"concept_name": "COVID-19 Omicron variant BA.5 infection",
"trend_score": 8.9
}
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"trending_concepts_count": 43,
"average_growth_rate": 65.2,
"top_trending_concept": {
"concept_id": 429914006,
"concept_name": "Hybrid cardiac catheterization procedure",
"trend_score": 7.2
}
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"trending_concepts_count": 28,
"average_growth_rate": 34.7,
"top_trending_concept": {
"concept_id": 1234567890,
"concept_name": "Monoclonal antibody COVID-19 treatment",
"trend_score": 6.1
}
}
],
"vocabulary_trends": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"trending_concepts_count": 123,
"trend_momentum": "High"
},
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"trending_concepts_count": 23,
"trend_momentum": "Medium"
},
{
"vocabulary_id": "LOINC",
"vocabulary_name": "Logical Observation Identifiers Names and Codes",
"trending_concepts_count": 10,
"trend_momentum": "Low"
}
]
},
"meta": {
"request_id": "req_trending_concepts_123",
"timestamp": "2024-01-15T10:30:00Z",
"analysis_parameters": {
"time_period": "30d",
"trend_type": "usage",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"trend_threshold": 1.5
},
"data_freshness": "Updated 15 minutes ago",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 156,
"total_pages": 8,
"has_next": true,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Current Trending Concepts
Get currently trending concepts across all vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/trending?time_period=7d&trend_type=usage&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Emerging Concepts
Find newly emerging concepts in specific domains:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/trending?trend_type=emerging&domains=Condition,Drug&include_context=true&include_statistics=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specialty-Focused Trends
Get trending concepts for specific medical specialties:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/trending?specialty_focus=cardiology,oncology&time_period=30d&include_related_concepts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Seasonal Analysis
Analyze seasonal trending patterns:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/trending?trend_type=seasonal&time_period=1y&include_context=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Vocabulary-Specific Trends
Focus on trends within specific vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/trending?vocabulary_ids=SNOMED&time_period=30d&include_statistics=true&sort_by=growth_rate" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Custom Time Period Analysis
Analyze trends for a specific date range:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/trending?time_period=custom&start_date=2024-01-01&end_date=2024-01-31&include_context=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Trend Types
### Usage
* Based on overall concept usage across the platform
* Includes API requests, searches, and clinical documentation
* Best indicator of general concept popularity
### Search Frequency
* Based on search query patterns
* Reflects user interest and information seeking
* Useful for understanding user needs
### Clinical Adoption
* Based on usage in clinical settings
* Weighted by healthcare provider usage
* Indicates real-world clinical relevance
### Emerging
* Concepts showing rapid growth from low baseline
* Identifies newly relevant medical concepts
* Important for staying current with medical advances
### Declining
* Concepts showing significant usage decrease
* May indicate deprecated or obsolete concepts
* Useful for identifying outdated terminology
### Seasonal
* Concepts with regular seasonal patterns
* Important for understanding cyclical medical trends
* Helps with resource planning and preparedness
## Trend Categories
### Emerging (New and Rising)
* **Criteria**: High growth rate from low baseline
* **Characteristics**: Recent medical developments, new procedures, emerging diseases
* **Examples**: New COVID variants, innovative treatments, novel diagnostic methods
### Growing (Steadily Increasing)
* **Criteria**: Consistent upward trend over time
* **Characteristics**: Increasing clinical adoption, growing awareness
* **Examples**: Advanced procedures gaining acceptance, expanding disease recognition
### Stable Popular (Consistently High)
* **Criteria**: High usage with stable patterns
* **Characteristics**: Established medical concepts with ongoing relevance
* **Examples**: Common conditions, standard procedures, essential medications
### Seasonal (Cyclical Patterns)
* **Criteria**: Regular patterns tied to seasons or events
* **Characteristics**: Weather-related conditions, infectious diseases, preventive care
* **Examples**: Influenza, allergies, seasonal vaccinations
## Factors Influencing Trends
### Clinical Factors
* Disease outbreaks and epidemics
* New treatment protocols and guidelines
* Advances in medical technology
* Changes in diagnostic criteria
### Regulatory Factors
* New coding requirements
* Regulatory approvals
* Quality measure updates
* Reimbursement changes
### Seasonal Factors
* Weather-related conditions
* Seasonal infectious diseases
* Preventive care cycles
* Academic medical calendars
### Technology Factors
* New diagnostic tools
* Treatment innovations
* Digital health adoption
* Medical device advances
## Related Endpoints
* [Search Concepts](/api-reference/concepts/search-concepts) - Search for specific concepts
* [Get Concept](/api-reference/concepts/get-concept) - Detailed concept information
* [Suggest Concepts](/api-reference/concepts/suggest-concepts) - Concept suggestions
* [Get Usage Analytics](/api-reference/monitoring/get-usage-analytics) - Platform usage analytics
# Get Concepts Search Facets
Source: https://docs.omophub.com/api-reference/concepts/get-concepts-search-facets
GET https://api.omophub.com/v1/concepts/search/facets
Retrieve search facets specifically for concept searches, enabling advanced filtering and navigation of medical concept search results.
## Overview
This endpoint provides faceted search capabilities specifically for medical concept searches. It returns structured filters and categories that help users refine their concept search queries across multiple dimensions such as domains, vocabularies, concept classes, and clinical attributes.
## Query Parameters
Base concept search query to generate relevant facets for
Target vocabularies for facet generation (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter facets to specific clinical domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter facets to specific concept classes (comma-separated)
**Examples**: `Clinical Finding,Procedure`, `Ingredient,Brand Name`
Filter to standard concepts only
**Options**: `S` (standard), `C` (classification), `N` (non-standard)
Include invalid/deprecated concepts in facet counts
Types of facets to include (comma-separated)
**Options**: `vocabulary`, `domain`, `concept_class`
Include concept counts for each facet value
Maximum number of values per facet type
Number of results per page
Page number for pagination
## Response
Indicates if the request was successful
Contains the faceted search results for concepts
Object containing facet categories and their values
Available vocabulary facets with counts
Vocabulary identifier (e.g., "SNOMED", "ICD10CM")
Human-readable vocabulary name
Number of concepts in this vocabulary matching the query
Available domain facets with counts
Domain identifier (e.g., "Condition", "Drug")
Human-readable domain name
Number of concepts in this domain matching the query
Available concept class facets with counts
Concept class identifier
Human-readable concept class name
Number of concepts in this class matching the query
Information about the processed query
The original search query provided
Total number of concepts matching the query
Number of concepts used for facet generation
Response metadata including pagination and performance info
Current page number
Number of items per page
Total number of items
Total number of pages
Whether there are more pages
Whether there are previous pages
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/search/facets?query=diabetes&vocabulary_ids=SNOMED,ICD10CM&facet_types=vocabulary,domain,concept_class&include_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript
const response = await fetch(
'https://api.omophub.com/v1/concepts/search/facets?query=diabetes&vocabulary_ids=SNOMED,ICD10CM&facet_types=vocabulary,domain,concept_class&include_counts=true',
{
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
```
```python Python
import requests
url = "https://api.omophub.com/v1/concepts/search/facets"
params = {
"query": "diabetes",
"vocabulary_ids": "SNOMED,ICD10CM",
"facet_types": "vocabulary,domain,concept_class",
"include_counts": "true"
}
response = requests.get(
url,
params=params,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"facets": {
"vocabularies": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_count": 245
},
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "ICD-10 Clinical Modification",
"concept_count": 67
}
],
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"concept_count": 198
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"concept_count": 87
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"concept_count": 27
}
],
"concept_classes": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_count": 156
},
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_count": 87
}
]
},
"query_info": {
"original_query": "diabetes",
"total_concepts": 312,
"faceted_concepts": 312
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 312,
"total_pages": 16,
"has_next": true,
"has_previous": false
}
}
}
```
```json Error Response
{
"success": false,
"error": {
"code": "INVALID_VOCABULARY",
"message": "One or more vocabulary IDs are invalid",
"details": {
"invalid_vocabularies": ["INVALID_VOCAB"]
}
}
}
```
## Usage Examples
### Basic Concept Search Facets
Get facets for a diabetes-related concept search:
```bash
GET /v1/concepts/search/facets?query=diabetes&vocabulary_ids=SNOMED
```
### Multi-Vocabulary Faceted Search
Generate facets across multiple medical vocabularies:
```bash
GET /v1/concepts/search/facets?query=hypertension&vocabulary_ids=SNOMED,ICD10CM,LOINC&facet_types=vocabulary,domain,concept_class
```
### Domain-Specific Facets
Get facets filtered to specific clinical domains:
```bash
GET /v1/concepts/search/facets?query=medication&domain_ids=Drug&facet_types=vocabulary,concept_class&max_facet_values=50
```
## Related Endpoints
* [Search Facets](/api-reference/search/search-facets) - General search facets
* [Search Concepts](/api-reference/concepts/search-concepts) - Basic concept search
* [Advanced Search](/api-reference/search/advanced-search) - Advanced concept search with faceted filtering
# Get Related Concepts
Source: https://docs.omophub.com/api-reference/concepts/get-related-concepts
GET https://api.omophub.com/v1/concepts/{conceptId}/related
Find conceptually related concepts using semantic similarity and relationship analysis
## Overview
This endpoint finds concepts that are conceptually related to a given concept through various relationship types, semantic similarity, and co-occurrence patterns. It's designed for discovering relevant concepts that may not be directly connected through standard hierarchical relationships.
## Path Parameters
The unique OMOP concept ID to find related concepts for
## Query Parameters
Types of relatedness to include. Multiple values are comma-separated.
**Options**: `hierarchical`, `semantic`, `co_occurrence`, `mapping` (default: all)
**Note**: Providing any unknown value will result in a 400 Bad Request response.
Comma-separated list of vocabulary IDs to filter results
Comma-separated list of domain IDs to filter results
Minimum relatedness score (0.0-1.0) to include in results
Maximum number of related concepts to return
Include detailed relatedness scores and explanations. Boolean values must be serialized as string values "true" or "false" when sent over the wire.
Only return standard concepts. Boolean values must be serialized as string values "true" or "false" when sent over the wire.
Specific vocabulary release version (e.g., "2024.1")
## Response
Indicates if the request was successful
The source concept being analyzed
Source concept ID
Source concept name
Source vocabulary
Source domain
Source concept class
Array of related concepts ordered by relatedness score
Related concept ID
Related concept name
Related concept vocabulary
Related concept domain
Related concept class
Standard concept flag. Allowed values: "S" (Standard), "C" (Classification), or null. Standard concepts are the primary representation, Classification concepts are grouping concepts, and null indicates non-standard concepts.
Related concept code
Overall relatedness score (0.0-1.0)
Detailed scoring breakdown (when include\_scores=true)
Score based on hierarchical relationships (0.0-1.0)
Score based on semantic similarity (0.0-1.0)
Score based on co-occurrence patterns (0.0-1.0)
Score based on vocabulary mappings (0.0-1.0)
Primary relationship connecting the concepts
Number of steps in the relationship path
Human-readable explanation of why concepts are related
Summary of the relatedness analysis
Number of concepts considered during analysis
Number of concepts meeting the minimum score threshold
Average score of returned concepts
Highest relatedness score found
Types of relatedness analysis performed
Distribution of results across vocabularies
Distribution of results across domains
Response metadata and API information
Unique correlation ID for request tracking
API version used for the request
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?min_relatedness_score=0.4&max_results=20" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/related"
params = {
"min_relatedness_score": 0.4,
"max_results": 30,
"relatedness_types": "hierarchical,semantic,mapping",
"vocabulary_ids": "SNOMED,ICD10CM",
"include_scores": "true"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
concept = data['data']['concept']
summary = data['data']['analysis_summary']
print(f"Related concepts for: {concept['concept_name']}")
print(f"Found {len(data['data']['related_concepts'])} related concepts")
print(f"Average score: {summary['average_relatedness_score']:.3f}")
```
```javascript JavaScript
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/related?min_relatedness_score=0.3&include_scores=true`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const relatedData = await response.json();
console.log(`Related concepts for: ${relatedData.data.concept.concept_name}`);
console.log(`Found ${relatedData.data.related_concepts.length} related concepts`);
console.log(`Average relatedness: ${relatedData.data.analysis_summary.average_relatedness_score.toFixed(3)}`);
relatedData.data.related_concepts.slice(0, 5).forEach((concept, index) => {
console.log(`${index + 1}. ${concept.concept_name} (${concept.vocabulary_id})`);
console.log(` Score: ${concept.overall_relatedness_score.toFixed(3)}`);
if (concept.relatedness_details) {
console.log(` Primary relationship: ${concept.relatedness_details.primary_relationship_type}`);
}
});
```
```bash cURL (domain-specific)
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?domain_ids=Condition,Observation&vocabulary_ids=SNOMED&min_relatedness_score=0.6" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (detailed analysis)
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/201826/related",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={
"min_relatedness_score": 0.4,
"include_scores": "true",
"relatedness_types": "hierarchical,semantic,co_occurrence"
}
)
data = response.json()
for related in data['data']['related_concepts'][:10]:
score = related['overall_relatedness_score']
print(f"{related['concept_name']} | Score: {score:.3f}")
if 'relatedness_details' in related:
details = related['relatedness_details']
scores = []
if details['hierarchical_score'] > 0:
scores.append(f"H:{details['hierarchical_score']:.2f}")
if details['semantic_score'] > 0:
scores.append(f"S:{details['semantic_score']:.2f}")
if details['co_occurrence_score'] > 0:
scores.append(f"C:{details['co_occurrence_score']:.2f}")
print(f" Breakdown: {' | '.join(scores)}")
if details['explanation']:
print(f" Why: {details['explanation'][:60]}...")
print()
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"related_concepts": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "73211009",
"overall_relatedness_score": 0.92,
"relatedness_details": {
"hierarchical_score": 0.95,
"semantic_score": 0.88,
"co_occurrence_score": 0.91,
"mapping_score": 0.85,
"primary_relationship_type": "Is a",
"relationship_path_length": 1,
"explanation": "Direct parent concept in SNOMED hierarchy with strong semantic similarity"
}
},
{
"concept_id": 443735,
"concept_name": "Type 2 diabetes mellitus without complications",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "443735",
"overall_relatedness_score": 0.89,
"relatedness_details": {
"hierarchical_score": 0.87,
"semantic_score": 0.94,
"co_occurrence_score": 0.86,
"mapping_score": 0.82,
"primary_relationship_type": "Subsumes",
"relationship_path_length": 1,
"explanation": "Specific subtype with very high semantic similarity"
}
},
{
"concept_id": 4048098,
"concept_name": "Diabetic complication",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "4048098",
"overall_relatedness_score": 0.76,
"relatedness_details": {
"hierarchical_score": 0.65,
"semantic_score": 0.82,
"co_occurrence_score": 0.89,
"mapping_score": 0.68,
"primary_relationship_type": "Associated with",
"relationship_path_length": 2,
"explanation": "Frequently co-occurring concept representing complications of diabetes"
}
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "ICD10 code",
"standard_concept": "S",
"concept_code": "E11",
"overall_relatedness_score": 0.94,
"relatedness_details": {
"hierarchical_score": 0.72,
"semantic_score": 0.98,
"co_occurrence_score": 0.85,
"mapping_score": 0.99,
"primary_relationship_type": "Maps to",
"relationship_path_length": 1,
"explanation": "Exact cross-vocabulary mapping with identical semantic meaning"
}
}
],
"analysis_summary": {
"total_candidates_evaluated": 1247,
"concepts_above_threshold": 28,
"average_relatedness_score": 0.65,
"max_relatedness_score": 0.94,
"relatedness_types_used": ["hierarchical", "semantic", "co_occurrence", "mapping"],
"vocabulary_distribution": {
"SNOMED": 18,
"ICD10CM": 7,
"ICD9CM": 3
},
"domain_distribution": {
"Condition": 22,
"Observation": 4,
"Drug": 2
}
}
},
"meta": {
"request_id": "req_related_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Related Concepts
Find general related concepts:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/related" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### High-Confidence Relationships
Get only highly related concepts:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?min_relatedness_score=0.7&max_results=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Domain-Specific Relations
Find related concepts within specific domains:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?domain_ids=Condition,Observation&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Semantic Similarity Focus
Focus on semantic relatedness:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/related?relatedness_types=semantic,co_occurrence" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Application Integration
Use in recommendation systems:
```javascript
async function getConceptRecommendations(conceptId, userContext) {
const params = new URLSearchParams({
min_relatedness_score: userContext.precision_level || '0.4',
max_results: userContext.max_suggestions || '20',
vocabulary_ids: userContext.preferred_vocabularies?.join(',') || '',
standard_concepts_only: userContext.standard_only || 'false'
});
const response = await fetch(`/v1/concepts/${conceptId}/related?${params}`);
const data = await response.json();
return data.related_concepts.map(concept => ({
id: concept.concept_id,
name: concept.concept_name,
vocabulary: concept.vocabulary_id,
score: concept.overall_relatedness_score,
explanation: concept.relatedness_details?.explanation
}));
}
```
## Important Notes
* **Scoring algorithm** - Combines multiple relatedness signals for comprehensive results
* **Performance** - Analysis involves complex calculations and may take longer than simple queries
* **Score interpretation** - Scores above 0.7 indicate strong relatedness, 0.4-0.7 moderate, below 0.4 weak
* **Vocabulary coverage** - Results quality depends on vocabulary completeness and relationships
* **Context sensitive** - Relatedness can vary based on clinical context and use case
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Direct relationships only
* [Find Similar Concepts](/api-reference/search/find-similar-concepts) - Similarity-based search
* [Get Concept Relations](/api-reference/concepts/get-concept-relations) - All relationship types
# Get Relationship Options
Source: https://docs.omophub.com/api-reference/concepts/get-relationship-options
GET https://api.omophub.com/v1/concepts/{conceptId}/relationships/options
Get available relationship types and options for a specific concept
## Overview
This endpoint returns all available relationship types that can be applied to or from a specific concept, along with metadata about relationship options. This is useful for building dynamic UI components and understanding what relationships are possible for a concept.
## Path Parameters
The unique OMOP concept ID to get relationship options for
## Query Parameters
Filter by relationship direction: "outbound", "inbound", or "both" (default: "both")
Comma-separated list of vocabularies to filter relationship options
Include count of relationships for each relationship type
Include invalid/deprecated relationship types
Specific vocabulary release version (e.g., "2024.1")
## Response
Response data containing relationship options
The concept being analyzed
Concept ID
Concept name
Primary vocabulary
Concept domain
Concept class
Available relationship options organized by direction
Relationship types where this concept is the source
Unique relationship type ID
Human-readable relationship name
Concept ID representing this relationship
The reverse relationship type
Whether this creates hierarchical relationships
Whether this relationship defines parent-child relationships
Number of relationships of this type (when include\_counts=true)
Vocabularies that contain target concepts
Domains that contain target concepts
Example target concepts (up to 3)
Relationship types where this concept is the target
Unique relationship type ID
Human-readable relationship name
Concept ID representing this relationship
The reverse relationship type
Whether this creates hierarchical relationships
Whether this relationship defines parent-child relationships
Number of relationships of this type (when include\_counts=true)
Vocabularies that contain source concepts
Domains that contain source concepts
Example source concepts (up to 3)
Summary of relationship options
Number of outbound relationship types available
Number of inbound relationship types available
Total unique relationship types
Relationship types that create hierarchies
Relationship types used for cross-vocabulary mapping
Most frequently used relationship types
All vocabularies connected through relationships
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options?include_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relationships/options"
params = {
"include_counts": True,
"direction": "both",
"vocabulary_ids": "SNOMED,ICD10CM"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
concept = data['data']['concept']
summary = data['data']['summary']
print(f"Concept: {concept['concept_name']}")
print(f"Total relationship types: {summary['total_unique_types']}")
print(f"Connected vocabularies: {', '.join(summary['connected_vocabularies'])}")
print("\nOutbound relationships:")
for rel in data['data']['relationship_options']['outbound']:
count_str = f"({rel['count']} relationships)" if 'count' in rel else ""
print(f" • {rel['relationship_name']} {count_str}")
if rel.get('example_targets'):
examples = [t['concept_name'] for t in rel['example_targets'][:2]]
print(f" Examples: {', '.join(examples)}")
print("\nInbound relationships:")
for rel in data['data']['relationship_options']['inbound'][:5]: # Show first 5
count_str = f"({rel['count']} relationships)" if 'count' in rel else ""
print(f" • {rel['relationship_name']} {count_str}")
```
```javascript JavaScript
const conceptId = 201826; // Type 2 diabetes
const response = await fetch(`https://api.omophub.com/v1/concepts/${conceptId}/relationships/options?include_counts=true&direction=both`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const options = await response.json();
const data = options.data;
console.log(`Relationship options for: ${data.concept.concept_name}`);
console.log(`Outbound types: ${data.summary.total_outbound_types}`);
console.log(`Inbound types: ${data.summary.total_inbound_types}`);
// Show outbound relationship options
data.relationship_options.outbound.forEach(rel => {
console.log(`- ${rel.relationship_name}: ${rel.count || 'N/A'} relationships`);
});
```
```bash cURL (direction filtering)
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options?direction=outbound&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (UI component builder)
import requests
def build_relationship_ui_options(concept_id, direction="both"):
"""
Build relationship options for UI components like dropdowns and filters
"""
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relationships/options"
params = {
"include_counts": True,
"direction": direction
}
response = requests.get(url, params=params, headers={
"Authorization": "Bearer YOUR_API_KEY"
})
data = response.json()
relationship_data = data['data']['relationship_options']
ui_options = {
'hierarchical': [],
'mapping': [],
'other': [],
'summary': data['data']['summary']
}
# Process outbound relationships
for rel in relationship_data.get('outbound', []):
option = {
'id': rel['relationship_id'],
'name': rel['relationship_name'],
'count': rel.get('count', 0),
'direction': 'outbound',
'example_targets': rel.get('example_targets', [])[:2]
}
if rel['is_hierarchical']:
ui_options['hierarchical'].append(option)
elif 'Map' in rel['relationship_name']:
ui_options['mapping'].append(option)
else:
ui_options['other'].append(option)
# Process inbound relationships
for rel in relationship_data.get('inbound', []):
option = {
'id': rel['relationship_id'],
'name': rel['relationship_name'],
'count': rel.get('count', 0),
'direction': 'inbound',
'example_sources': rel.get('example_sources', [])[:2]
}
if rel['is_hierarchical']:
ui_options['hierarchical'].append(option)
elif 'Map' in rel['relationship_name']:
ui_options['mapping'].append(option)
else:
ui_options['other'].append(option)
# Sort by count (descending)
for category in ['hierarchical', 'mapping', 'other']:
ui_options[category].sort(key=lambda x: x['count'], reverse=True)
return ui_options
# Build UI options for Type 2 diabetes
ui_data = build_relationship_ui_options(201826)
print("Hierarchical Relationships:")
for rel in ui_data['hierarchical']:
print(f" {rel['name']} ({rel['direction']}) - {rel['count']} relationships")
print("\nMapping Relationships:")
for rel in ui_data['mapping']:
print(f" {rel['name']} ({rel['direction']}) - {rel['count']} relationships")
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"relationship_options": {
"outbound": [
{
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"relationship_concept_id": 44818790,
"reverse_relationship": "Mapped from",
"is_hierarchical": false,
"defines_ancestry": false,
"count": 3,
"target_vocabularies": ["SNOMED", "ICD10CM"],
"target_domains": ["Condition"],
"example_targets": [
{
"concept_id": 44054006,
"concept_name": "Diabetes mellitus type 2 (disorder)"
},
{
"concept_id": 443735,
"concept_name": "Type 2 diabetes mellitus without complications"
}
]
},
{
"relationship_id": "Is a",
"relationship_name": "Is a",
"relationship_concept_id": 44818821,
"reverse_relationship": "Subsumes",
"is_hierarchical": true,
"defines_ancestry": true,
"count": 2,
"target_vocabularies": ["SNOMED"],
"target_domains": ["Condition"],
"example_targets": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus"
}
]
}
],
"inbound": [
{
"relationship_id": "Subsumes",
"relationship_name": "Subsumes",
"relationship_concept_id": 44818723,
"reverse_relationship": "Is a",
"is_hierarchical": true,
"defines_ancestry": true,
"count": 15,
"source_vocabularies": ["SNOMED"],
"source_domains": ["Condition"],
"example_sources": [
{
"concept_id": 443767,
"concept_name": "Type 2 diabetes mellitus with complications"
},
{
"concept_id": 201254,
"concept_name": "Type 2 diabetes mellitus with ketoacidosis"
}
]
}
]
},
"summary": {
"total_outbound_types": 5,
"total_inbound_types": 3,
"total_unique_types": 7,
"hierarchical_types": ["Is a", "Subsumes"],
"mapping_types": ["Maps to", "Mapped from"],
"most_common_types": ["Subsumes", "Maps to", "Is a"],
"connected_vocabularies": ["SNOMED", "ICD10CM", "ICD9CM"]
}
},
"meta": {
"request_id": "req_relationship_options_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Relationship Options
Get all available relationship types:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Outbound Only with Counts
Get outbound relationship options with counts:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options?direction=outbound&include_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filtered by Vocabularies
Get relationship options for specific vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/relationships/options?vocabulary_ids=SNOMED,ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### UI Integration Example
Use in dropdown components:
```javascript
async function loadRelationshipOptions(conceptId) {
const response = await fetch(`/v1/concepts/${conceptId}/relationships/options?direction=outbound`);
const result = await response.json();
const data = result.data;
return data.relationship_options.outbound.map(rel => ({
value: rel.relationship_id,
label: rel.relationship_name,
count: rel.count,
isHierarchical: rel.is_hierarchical
}));
}
// Use in UI
const options = await loadRelationshipOptions(201826);
const selectOptions = options.map(opt =>
``
);
```
## Important Notes
* **Dynamic options** - Available relationship types vary by concept and vocabulary
* **Hierarchical indicators** - Use `is_hierarchical` and `defines_ancestry` flags for navigation
* **Performance** - Including counts increases response time but provides valuable context
* **UI components** - Perfect for building dynamic relationship selection interfaces
* **Reverse relationships** - Each relationship type has a corresponding reverse relationship
## Related Endpoints
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Get actual relationships
* [Get Relationships by Type](/api-reference/concepts/get-relationships-by-type) - Get relationships of specific type
* [Get Relationship Types](/api-reference/relationships/get-relationship-types) - All relationship types in system
# Map Concepts
Source: https://docs.omophub.com/api-reference/concepts/map-concepts
POST https://api.omophub.com/v1/concepts/map
Map medical concepts between different vocabularies to find equivalent or related concepts across terminology systems.
## Overview
This endpoint maps medical concepts from source vocabularies to target vocabularies, finding equivalent or related concepts across different medical terminology systems. It's essential for healthcare data integration, cross-system interoperability, and clinical data standardization.
## Request Body
Array of source OMOP concept IDs to map (1-100 concepts)
**Example**: `[201826, 192671, 443729]`
Target vocabulary identifier to map concepts to
**Examples**: `"ICD10CM"`, `"SNOMED"`, `"ICD9CM"`, `"LOINC"`, `"RXNORM"`
Type of concept mapping to perform
**Options**:
* `"direct"` - Direct mappings only (Maps to, Mapped from)
* `"equivalent"` - Equivalent concepts (Maps to, Source - contains, Has precise ingredient)
* `"broader"` - Broader concepts (Is a, Subsumes, Has ingredient)
* `"narrower"` - Narrower concepts (Subsumes, Consists of)
Include invalid/deprecated concepts in mapping results
## Response
Indicates if the request was successful
Error information (present only on error responses)
Error code identifier (e.g., "INVALID\_VOCABULARY", "RATE\_LIMIT\_EXCEEDED")
Human-readable error message
Additional error details including supported vocabularies and validation failures
List of supported vocabulary names when vocabulary validation fails
Contains the concept mapping results
Array of concept mappings found
OMOP concept ID of the source concept
Name of the source concept
Vocabulary of the source concept
OMOP concept ID of the mapped target concept
Name of the target concept
Vocabulary of the target concept
Original source code in the target vocabulary
Type of relationship between concepts
Human-readable relationship description
Confidence score for the mapping (0.0 to 1.0)
Start date when the mapping became valid
End date when the mapping expires
Summary statistics for the mapping operation
Number of source concepts processed
Number of concepts successfully mapped
Number of concepts that couldn't be mapped
Target vocabulary used for mapping
Type of mapping performed
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/map" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_concepts": [201826, 192671],
"target_vocabulary": "ICD10CM",
"mapping_type": "equivalent",
"include_invalid": false
}'
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/map', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
source_concepts: [201826, 192671],
target_vocabulary: "ICD10CM",
mapping_type: "equivalent",
include_invalid: false
})
});
const data = await response.json();
```
```python Python
import requests
url = "https://api.omophub.com/v1/concepts/map"
payload = {
"source_concepts": [201826, 192671],
"target_vocabulary": "ICD10CM",
"mapping_type": "equivalent",
"include_invalid": False
}
response = requests.post(
url,
json=payload,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"mappings": [
{
"source_concept_id": 201826,
"source_concept_name": "Type 2 diabetes mellitus",
"source_vocabulary_id": "SNOMED",
"target_concept_id": 35208413,
"target_concept_name": "Type 2 diabetes mellitus",
"target_vocabulary_id": "ICD10CM",
"target_concept_code": "E11.9",
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"mapping_confidence": 0.95,
"valid_start_date": "2023-01-01",
"valid_end_date": "2099-12-31"
},
{
"source_concept_id": 192671,
"source_concept_name": "Myocardial infarction",
"source_vocabulary_id": "SNOMED",
"target_concept_id": 35208604,
"target_concept_name": "Acute myocardial infarction, unspecified",
"target_vocabulary_id": "ICD10CM",
"target_concept_code": "I21.9",
"relationship_id": "Maps to",
"relationship_name": "Maps to",
"mapping_confidence": 0.92,
"valid_start_date": "2023-01-01",
"valid_end_date": "2099-12-31"
}
],
"mapping_summary": {
"total_source_concepts": 2,
"successful_mappings": 2,
"failed_mappings": 0,
"target_vocabulary": "ICD10CM",
"mapping_type": "equivalent"
}
}
...
}
```
## Usage Examples
### Basic Concept Mapping
Map SNOMED concepts to ICD-10-CM:
```json
{
"source_concepts": [201826],
"target_vocabulary": "ICD10CM",
"mapping_type": "equivalent"
}
```
### Cross-System Integration
Map multiple concepts for EHR integration:
```json
{
"source_concepts": [201826, 192671, 443729],
"target_vocabulary": "ICD10CM",
"mapping_type": "direct",
"include_invalid": false
}
```
### Drug Mapping
Map drug concepts from SNOMED to RxNorm:
```json
{
"source_concepts": [387517004, 108774000],
"target_vocabulary": "RXNORM",
"mapping_type": "equivalent"
}
```
## Related Endpoints
* [Batch Map Concepts](/api-reference/mappings/batch-map-concepts) - Batch mapping operations
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Get mappings for a specific concept
* [Validate Mappings](/api-reference/mappings/validate-mappings) - Validate mapping accuracy
# Search Concepts
Source: https://docs.omophub.com/api-reference/concepts/search-concepts
GET /v1/concepts
Search for medical concepts across vocabularies using flexible query parameters and filters.
## Overview
The primary endpoint for searching medical concepts across all vocabularies. Supports text search, filtering by vocabulary, domain, concept class, and various other criteria.
## Authentication
This API requires HTTPS and authentication via bearer token in the Authorization header. Include your API key in the following format:
```
Authorization: Bearer YOUR_API_KEY
```
API keys can be obtained from your developer dashboard. Service API keys have broader access than user tokens. Tokens do not expire but can be revoked. Note that requests over plain HTTP are not supported.
## Query Parameters
Search query text (concept name, synonym, or code)
Comma-separated list of vocabulary IDs to search within
**Examples**: `SNOMED`, `SNOMED,ICD10CM`, `LOINC,RXNORM`
Comma-separated list of domain IDs to filter by
**Examples**: `Condition`, `Procedure,Drug`
Comma-separated list of concept class IDs to filter by
Filter by standard concept status
**Options**: `S` (standard), `C` (classification), `N` (non-standard)
Include concept synonyms in search
Include concept relationships in response
Include invalid/deprecated concepts
Minimum relevance score (0.0 to 1.0)
Require exact term matching
Page number for pagination
Number of concepts per page (max 100)
Sort criteria
**Options**: `relevance`, `concept_name`, `concept_code`, `vocabulary_id`
Sort order
**Options**: `asc`, `desc`
Specific vocabulary release version
## Response
Indicates if the request was successful
Array of concept objects matching the search criteria
Unique concept identifier
Primary concept name
Concept code within the vocabulary
Vocabulary identifier
Full vocabulary name
Domain identifier
Concept class identifier
Standard concept status (S, C, N, or null)
ISO 8601 date when concept became valid
ISO 8601 date when concept becomes invalid (may be null for active concepts)
Reason for invalidity (null if concept is valid)
Search relevance score (0.0 to 1.0)
Array of concept synonyms (if include\_synonyms=true)
Array of concept relationships (if include\_relationships=true)
Unique identifier for the relationship
Type of relationship (e.g., "Is a", "Maps to", "Has finding site")
ID of the source concept in the relationship
ID of the target concept in the relationship
Name of the target concept
Vocabulary ID of the target concept
Direction of the relationship ("forward" or "reverse")
Whether this is a hierarchical relationship
Additional relationship metadata and context
Date when relationship became valid (ISO 8601)
Date when relationship expires (ISO 8601, null if active)
Unique identifier for the request
Request timestamp
Summary of applied search filters
Current page number
Items per page
Total number of matching concepts
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts?query=diabetes&vocabulary_ids=SNOMED,ICD10CM&standard_concept=S" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts?query=diabetes&vocabulary_ids=SNOMED,ICD10CM&standard_concept=S', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'diabetes',
'vocabulary_ids': 'SNOMED,ICD10CM',
'standard_concept': 'S',
'include_synonyms': True,
'page_size': 50
}
response = requests.get(
'https://api.omophub.com/v1/concepts',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"relevance_score": 0.98,
"synonyms": [
"Type II diabetes mellitus",
"Non-insulin dependent diabetes mellitus",
"Adult onset diabetes mellitus",
"Maturity onset diabetes"
]
},
{
"concept_id": 192279,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"relevance_score": 0.95,
"synonyms": [
"Type I diabetes mellitus",
"Insulin dependent diabetes mellitus",
"Juvenile onset diabetes mellitus"
]
}
],
"meta": {
"request_id": "req_search_concepts_123",
"timestamp": "2024-01-15T10:30:00Z",
"search_criteria": {
"query": "diabetes",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"standard_concept": "S",
"include_synonyms": true
},
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 157,
"total_pages": 8,
"has_next": true,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Search
Search for diabetes concepts:
```bash
curl -X GET "https://api.omophub.com/v1/concepts?query=diabetes" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filtered Search
Search within specific vocabularies and domains:
```bash
curl -X GET "https://api.omophub.com/v1/concepts?query=hypertension&vocabulary_ids=SNOMED&domain_ids=Condition&standard_concept=S" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### High Relevance Results
Get only high-relevance results:
```bash
curl -X GET "https://api.omophub.com/v1/concepts?query=myocardial%20infarction&min_score=0.8&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Include Relationships
Search with relationship data:
```bash
curl -X GET "https://api.omophub.com/v1/concepts?query=diabetes&include_relationships=true&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Related Endpoints
* [Advanced Search](/api-reference/search/advanced-search) - More complex search queries
* [Similarity Search](/api-reference/search/similarity-search) - Find similar concepts
* [Get Concept Details](/api-reference/concepts/get-concept) - Get detailed concept information
# Suggest Concepts
Source: https://docs.omophub.com/api-reference/concepts/suggest-concepts
GET /concepts/suggest
Get concept suggestions based on partial input
## Overview
Get intelligent concept suggestions for autocomplete functionality, based on partial text input. Uses advanced matching algorithms including fuzzy search and semantic similarity.
## Query Parameters
Partial text input for suggestions (minimum 2 characters)
Limit suggestions to specific vocabulary
Limit suggestions to specific domain
Maximum number of suggestions (max: 50)
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/suggest?query=diab&limit=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
params = {"query": "diab", "limit": 5}
response = requests.get(
"https://api.omophub.com/v1/concepts/suggest",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
suggestions = response.json()
```
```javascript JavaScript
const params = new URLSearchParams({
query: 'diab',
limit: '5'
});
const response = await fetch(`https://api.omophub.com/v1/concepts/suggest?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const suggestions = await response.json();
```
```json
{
"success": true,
"data": {
"suggestions": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"match_score": 0.95,
"match_type": "prefix"
},
{
"concept_id": 4000678,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"match_score": 0.92,
"match_type": "prefix"
},
{
"concept_id": 435216,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"match_score": 0.90,
"match_type": "partial"
},
{
"concept_id": 4174977,
"concept_name": "Diabetic retinopathy",
"concept_code": "4855003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"match_score": 0.85,
"match_type": "fuzzy"
},
{
"concept_id": 443767,
"concept_name": "Diabetic nephropathy",
"concept_code": "127013003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"match_score": 0.83,
"match_type": "fuzzy"
}
],
"total_suggestions": 5,
"query": "diab"
},
"meta": {
"request_id": "req_suggest_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
# Traverse Relationships
Source: https://docs.omophub.com/api-reference/concepts/traverse-relationships
POST https://api.omophub.com/v1/concepts/relationships/traverse
Perform complex relationship traversals across multiple concepts and relationship types
## Overview
This advanced endpoint allows you to traverse complex relationship networks across multiple concepts, following chains of relationships with sophisticated filtering and path-finding capabilities. Essential for advanced medical terminology analysis and complex queries.
## Request Body
Array of starting concept IDs for traversal
Rules defining how to traverse relationships
Array of relationship types to follow (default: \["Is a", "Maps to"])
Traversal direction: "outbound", "inbound", or "both"
Maximum depth of traversal (1-10)
Maximum results per starting concept
Vocabulary-based filtering
Only traverse through these vocabularies
Never traverse through these vocabularies
Stop traversal when reaching these vocabularies
Concept-based filtering
Only include concepts from these domains
Only include concepts from these classes
Only include standard concepts
Exclude invalid/deprecated concepts
Path-finding options
Find shortest paths between start and target concepts
Include detailed path information in results
Stop traversal when first matching concept is found
Optional criteria for target concepts
Specific concept IDs to find paths to
Find concepts matching these names (partial match)
Find concepts with these specific codes
## Query Parameters
Specific vocabulary release version (e.g., "2024.1")
## Response
Indicates whether the request was successful
Results for each starting concept
The concept ID where traversal started
Name of the starting concept
Array of concepts found during traversal
Found concept ID
Found concept name
Found concept vocabulary
Found concept domain
Found concept class
Standard concept flag
Found concept code
Depth level from starting concept
Chain of relationships to reach this concept
Type/name of relationship (e.g., "Is a", "Maps to", "Subsumes")
Origin concept ID in this relationship step
Destination concept ID in this relationship step
Traversal direction for this step ("outbound", "inbound")
Unique identifier for this relationship instance
Detailed path information (when include\_path\_details=true)
Target concept at end of path
Number of steps in path
Ordered array of concepts in path
Ordered array of relationship objects in path
Type/name of relationship
Unique identifier for this relationship
Traversal direction ("outbound" or "inbound")
Whether this is a shortest path
Traversal statistics for this starting concept
Total unique concepts found
Maximum depth reached during traversal
Relationship types actually traversed
Vocabularies encountered
Domains found in results
Whether results were truncated due to limits
Overall traversal statistics
Number of starting concepts processed
Total unique concepts across all traversals
Number of successful traversals
Number of failed traversals
Average concepts found per starting concept
Total processing time
Request metadata and response information
Unique request correlation identifier
API version used for this request
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/relationships/traverse" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"start_concepts": [201826, 4182210],
"traversal_rules": {
"relationship_types": ["Is a", "Maps to"],
"direction": "both",
"max_depth": 4,
"vocabulary_filters": {
"allowed_vocabularies": ["SNOMED", "ICD10CM"]
},
"concept_filters": {
"domain_ids": ["Condition"],
"standard_concepts_only": true
}
}
}'
```
```python Python
import requests
def traverse_relationships(start_concepts, traversal_rules, target_criteria=None):
url = "https://api.omophub.com/v1/concepts/relationships/traverse"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
data = {
"start_concepts": start_concepts,
"traversal_rules": traversal_rules
}
if target_criteria:
data["target_criteria"] = target_criteria
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example: Complex traversal from multiple starting points
start_concepts = [201826, 4182210, 313217] # Diabetes, Hypertension, Atrial fibrillation
traversal_rules = {
"relationship_types": ["Is a", "Maps to", "Has finding site"],
"direction": "both",
"max_depth": 4,
"max_results_per_concept": 50,
"vocabulary_filters": {
"allowed_vocabularies": ["SNOMED", "ICD10CM"],
"target_vocabularies": ["ICD10CM"] # Stop when reaching ICD-10
},
"concept_filters": {
"domain_ids": ["Condition", "Observation"],
"standard_concepts_only": True,
"exclude_invalid": True
},
"path_options": {
"include_path_details": True,
"stop_at_first_match": False
}
}
results = traverse_relationships(start_concepts, traversal_rules)
print(f"Processed {results['data']['global_statistics']['total_start_concepts']} starting concepts")
print(f"Found {results['data']['global_statistics']['total_unique_concepts_found']} unique concepts")
for result in results['data']['traversal_results']:
print(f"\n--- Results for {result['start_concept_name']} ---")
print(f"Found {result['statistics']['total_concepts_found']} concepts")
print(f"Max depth reached: {result['statistics']['max_depth_reached']}")
```
```javascript JavaScript
const traverseRelationships = async (startConcepts, rules) => {
const response = await fetch('https://api.omophub.com/v1/concepts/relationships/traverse', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
start_concepts: startConcepts,
traversal_rules: rules
})
});
return response.json();
};
// Example: Find paths from diabetes to cardiovascular conditions
const startConcepts = [201826]; // Type 2 diabetes
const rules = {
relationship_types: ["Is a", "Has finding site", "May cause"],
direction: "both",
max_depth: 5,
concept_filters: {
domain_ids: ["Condition"],
standard_concepts_only: true
},
path_options: {
find_shortest_paths: true,
include_path_details: true
}
};
const results = await traverseRelationships(startConcepts, rules);
results.data.traversal_results.forEach(result => {
console.log(`Starting from: ${result.start_concept_name}`);
console.log(`Found ${result.statistics.total_concepts_found} related concepts`);
if (result.paths) {
result.paths.slice(0, 3).forEach((path, index) => {
console.log(`Path ${index + 1}: ${path.path_length} steps to concept ${path.target_concept_id}`);
});
}
});
```
```bash cURL (path finding)
curl -X POST "https://api.omophub.com/v1/concepts/relationships/traverse" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"start_concepts": [201826],
"target_criteria": {
"target_concept_ids": [73211009, 4182210]
},
"traversal_rules": {
"path_options": {
"find_shortest_paths": true,
"include_path_details": true
}
}
}'
```
```python Python (cardiovascular discovery)
import requests
# Find cardiovascular-related concepts from diabetes
start_concepts = [201826] # Type 2 diabetes
traversal_rules = {
"relationship_types": ["Is a", "Has finding site", "May cause", "Associated with"],
"direction": "both",
"max_depth": 6,
"concept_filters": {
"domain_ids": ["Condition", "Observation"],
"standard_concepts_only": True
}
}
target_criteria = {
"target_concept_names": ["cardiovascular", "heart", "vascular", "cardiac"]
}
response = requests.post(
"https://api.omophub.com/v1/concepts/relationships/traverse",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"start_concepts": start_concepts,
"traversal_rules": traversal_rules,
"target_criteria": target_criteria
}
)
data = response.json()
for result in data['data']['traversal_results']:
cardiovascular_concepts = [
c for c in result['found_concepts']
if any(term in c['concept_name'].lower()
for term in ['cardiovascular', 'heart', 'cardiac', 'vascular'])
]
print(f"Found {len(cardiovascular_concepts)} cardiovascular-related concepts:")
for concept in cardiovascular_concepts[:5]:
print(f" • {concept['concept_name']} (depth: {concept['depth_from_start']})")
```
```json
{
"success": true,
"data": {
"traversal_results": [
{
"start_concept_id": 201826,
"start_concept_name": "Type 2 diabetes mellitus",
"found_concepts": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "73211009",
"depth_from_start": 1,
"relationship_chain": [
{
"relationship_type": "Is a",
"from_concept_id": 201826,
"to_concept_id": 73211009,
"direction": "outbound",
"relationship_id": "rel_123456"
}
]
},
{
"concept_id": 443767,
"concept_name": "Type 2 diabetes mellitus with complications",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "443767",
"depth_from_start": 2,
"relationship_chain": [
{
"relationship_type": "Subsumes",
"from_concept_id": 201826,
"to_concept_id": 443767,
"direction": "inbound",
"relationship_id": "rel_789012"
}
]
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "ICD10 code",
"standard_concept": "S",
"concept_code": "E11",
"depth_from_start": 1,
"relationship_chain": [
{
"relationship_type": "Maps to",
"from_concept_id": 201826,
"to_concept_id": 435216,
"direction": "outbound",
"relationship_id": "rel_345678"
}
]
}
],
"paths": [
{
"target_concept_id": 73211009,
"path_length": 1,
"path_concepts": [201826, 73211009],
"path_relationships": [
{
"relationship_type": "Is a",
"relationship_id": "is_a_rel",
"direction": "outbound"
}
],
"is_shortest_path": true
},
{
"target_concept_id": 435216,
"path_length": 1,
"path_concepts": [201826, 435216],
"path_relationships": [
{
"relationship_type": "Maps to",
"relationship_id": "maps_to_rel",
"direction": "outbound"
}
],
"is_shortest_path": true
}
],
"statistics": {
"total_concepts_found": 25,
"max_depth_reached": 4,
"relationship_types_used": ["Is a", "Maps to", "Subsumes"],
"vocabularies_traversed": ["SNOMED", "ICD10CM"],
"domains_found": ["Condition"],
"truncated": false
}
}
],
"global_statistics": {
"total_start_concepts": 1,
"total_unique_concepts_found": 25,
"successful_traversals": 1,
"failed_traversals": 0,
"average_concepts_per_start": 25.0,
"processing_time_ms": 456
}
},
"meta": {
"request_id": "req_traverse_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Relationship Traversal
Simple traversal with default settings:
```json
{
"start_concepts": [201826],
"traversal_rules": {
"relationship_types": ["Is a", "Maps to"],
"max_depth": 3
}
}
```
### Cross-Vocabulary Mapping
Find mappings between vocabularies:
```json
{
"start_concepts": [201826],
"traversal_rules": {
"relationship_types": ["Maps to"],
"vocabulary_filters": {
"target_vocabularies": ["ICD10CM", "ICD9CM"]
}
}
}
```
### Path Finding
Find specific paths between concepts:
```json
{
"start_concepts": [201826],
"target_criteria": {
"target_concept_ids": [73211009, 4182210]
},
"traversal_rules": {
"path_options": {
"find_shortest_paths": true,
"include_path_details": true
}
}
}
```
## Important Notes
* **Complexity limits**: Maximum 10 starting concepts and depth 10 to prevent performance issues
* **Result limits**: Use `max_results_per_concept` to control response size
* **Performance**: Complex traversals may take several seconds
* **Memory usage**: Deep traversals with many concepts can be memory-intensive
* **Path finding**: Enable path details only when needed as it increases processing time
## Related Endpoints
* [Batch Relationship Queries](/api-reference/concepts/batch-relationship-queries) - Simpler bulk relationship queries
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Single concept relationships
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Hierarchical relationships only
# Get Class Concepts
Source: https://docs.omophub.com/api-reference/domains/get-class-concepts
GET https://api.omophub.com/v1/concept-classes/{classId}/concepts
Retrieve all concepts belonging to a specific concept class with filtering and pagination
## Overview
This endpoint returns all concepts that belong to a specific concept class, providing a way to explore concepts within a particular classification category. Concept classes group related concepts by their semantic type and purpose within medical terminology.
## Path Parameters
The concept class ID to retrieve concepts for (e.g., "Clinical Finding", "Procedure", "Substance")
## Query Parameters
Comma-separated list of vocabulary IDs to filter concepts (e.g., "SNOMED,ICD10CM")
Comma-separated list of domain IDs to filter concepts (e.g., "Condition,Drug")
Filter by standard concept flag: "S" (standard), "C" (classification), or "N" (non-standard)
Include invalid/deprecated concepts in results
Filter concepts by name containing this search term
Sort results by: "concept\_name", "concept\_id", "concept\_code", or "vocabulary\_id"
Sort order: "asc" or "desc"
Specific vocabulary release version (e.g., "2024.1")
Page number for pagination (1-based)
Number of concepts per page
## Response
Information about the concept class
The concept class identifier
Human-readable name of the concept class
Concept ID representing this class
Description of the concept class
Array of concepts in this class
Unique OMOP concept identifier
Human-readable concept name
Vocabulary that defines this concept
Domain category of the concept
The concept class (matches the requested classId)
Standard concept flag: "S", "C", "N", or null
Original code from the source vocabulary
When this concept became valid (ISO date)
When this concept expires (ISO date)
Reason if concept is invalid (null if valid)
Statistics about concepts in this class
Total number of concepts in this class (unfiltered)
Number of concepts matching current filters
Number of standard concepts in results
Count of concepts by vocabulary
Count of concepts by domain
Count of concepts by validity status
```bash cURL
curl -X GET "https://api.omophub.com/v1/concept-classes/Clinical%20Finding/concepts?vocabulary_ids=SNOMED&page_size=20" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript JavaScript
const classId = encodeURIComponent("Clinical Finding");
const response = await fetch(`https://api.omophub.com/v1/concept-classes/${classId}/concepts?vocabulary_ids=SNOMED&standard_concept=S&page_size=50`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const classData = await response.json();
console.log(`Concepts in class: ${classData.data.concept_class.concept_class_name}`);
console.log(`Total concepts: ${classData.data.statistics.total_concepts_in_class}`);
console.log(`Filtered results: ${classData.data.statistics.filtered_concept_count}`);
console.log('\nVocabulary distribution:');
Object.entries(classData.data.statistics.vocabulary_distribution).forEach(([vocab, count]) => {
console.log(` ${vocab}: ${count} concepts`);
});
console.log('\nSample concepts:');
classData.data.concepts.slice(0, 5).forEach((concept, index) => {
const standardFlag = concept.standard_concept === 'S' ? ' (Standard)' : '';
console.log(`${index + 1}. ${concept.concept_name}${standardFlag}`);
console.log(` ID: ${concept.concept_id}, Code: ${concept.concept_code}`);
console.log(` Vocabulary: ${concept.vocabulary_id}, Domain: ${concept.domain_id}`);
});
```
```python Python
import requests
from urllib.parse import quote
class_id = quote("Clinical Finding") # URL encode the class ID
url = f"https://api.omophub.com/v1/concept-classes/{class_id}/concepts"
params = {
"vocabulary_ids": "SNOMED",
"standard_concept": "S",
"domain_ids": "Condition",
"search_term": "diabetes",
"sort_by": "concept_name",
"page_size": 30
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
concept_class = data['data']['concept_class']
stats = data['data']['statistics']
print(f"Concept Class: {concept_class['concept_class_name']}")
print(f"Description: {concept_class.get('description', 'N/A')}")
print(f"Class Concept ID: {concept_class.get('concept_class_concept_id', 'N/A')}")
print(f"\nStatistics:")
print(f" Total concepts in class: {stats['total_concepts_in_class']:,}")
print(f" Matching filters: {stats['filtered_concept_count']:,}")
print(f" Standard concepts: {stats['standard_concept_count']:,}")
print(f"\nVocabulary Distribution:")
for vocab, count in stats['vocabulary_distribution'].items():
print(f" {vocab}: {count:,} concepts")
print(f"\nDomain Distribution:")
for domain, count in stats['domain_distribution'].items():
print(f" {domain}: {count:,} concepts")
print(f"\nConcepts (showing first 10):")
for i, concept in enumerate(data['data']['concepts'][:10]):
standard_indicator = " ⭐" if concept['standard_concept'] == 'S' else ""
print(f"{i+1:2d}. {concept['concept_name']}{standard_indicator}")
print(f" ID: {concept['concept_id']}, Code: {concept['concept_code']}")
print(f" {concept['vocabulary_id']} | {concept['domain_id']}")
# Show validity information
if concept['invalid_reason']:
print(f" ⚠️ Invalid: {concept['invalid_reason']}")
else:
print(f" Valid: {concept['valid_start_date']} to {concept['valid_end_date']}")
print()
# Pagination info
if 'meta' in data and 'pagination' in data['meta']:
pagination = data['meta']['pagination']
print(f"Pagination: Page {pagination['page']} of {pagination['total_pages']}")
print(f"Showing {len(data['data']['concepts'])} of {pagination['total_items']} total concepts")
```
```json
{
"success": true,
"data": {
"concept_class": {
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_class_concept_id": 404684003,
"description": "A clinical finding represents a result of an observation, assessment, or judgment"
},
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "44054006",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 4182210,
"concept_name": "Hypertensive disorder",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "38341003",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
{
"concept_id": 313217,
"concept_name": "Atrial fibrillation",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "49436004",
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31",
"invalid_reason": null
}
],
"statistics": {
"total_concepts_in_class": 156847,
"filtered_concept_count": 98532,
"standard_concept_count": 87124,
"vocabulary_distribution": {
"SNOMED": 87124,
"ICD10CM": 7893,
"ICD9CM": 3515
},
"domain_distribution": {
"Condition": 92156,
"Observation": 4891,
"Measurement": 1485
},
"validity_status": {
"valid": 96847,
"invalid": 1685
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 98532,
"total_pages": 1971,
"has_next": true,
"has_previous": false
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 98532,
"total_pages": 1971,
"has_next": true,
"has_previous": false
},
"request_id": "req_class_concepts_123456",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Class Exploration
Get all concepts in a specific class:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Procedure/concepts?page_size=100" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Standard Concepts Only
Get only standard concepts from SNOMED:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Clinical%20Finding/concepts?vocabulary_ids=SNOMED&standard_concept=S" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Search Within Class
Search for specific concepts within a class:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Substance/concepts?search_term=insulin&vocabulary_ids=RxNorm" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cross-Vocabulary Analysis
Compare concepts across vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Clinical%20Finding/concepts?vocabulary_ids=SNOMED,ICD10CM&domain_ids=Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Taxonomy Building
Use for building classification hierarchies:
```javascript
async function buildClassTaxonomy(classId, vocabularyIds = ['SNOMED']) {
const response = await fetch(`/v1/concept-classes/${encodeURIComponent(classId)}/concepts?vocabulary_ids=${vocabularyIds.join(',')}&standard_concept=S&page_size=1000`);
const data = await response.json();
return {
className: data.concept_class.concept_class_name,
totalConcepts: data.statistics.total_concepts_in_class,
standardConcepts: data.statistics.standard_concept_count,
vocabularyBreakdown: data.statistics.vocabulary_distribution,
sampleConcepts: data.concepts.slice(0, 10).map(c => ({
id: c.concept_id,
name: c.concept_name,
code: c.concept_code,
vocabulary: c.vocabulary_id
}))
};
}
```
## Important Notes
* **URL encoding** - Class IDs with spaces must be URL encoded (e.g., "Clinical Finding" → "Clinical%20Finding")
* **Large result sets** - Some concept classes contain hundreds of thousands of concepts; use pagination
* **Standard concepts** - Filter by `standard_concept=S` for most clinical applications
* **Performance** - Searching within large classes may take longer; consider using specific filters
* **Vocabulary coverage** - Concept class availability varies by vocabulary
## Related Endpoints
* [Get Concept Classes](/api-reference/domains/get-concept-classes) - List all available concept classes
* [Get Class Hierarchy](/api-reference/domains/get-class-hierarchy) - Hierarchical relationships between classes
* [Search Concepts](/api-reference/search/search-concepts) - Free-text concept search with class filtering
# Get Class Hierarchy
Source: https://docs.omophub.com/api-reference/domains/get-class-hierarchy
GET https://api.omophub.com/v1/concept-classes/{classId}/hierarchy
Retrieve the hierarchical relationships between concept classes and their parent/child structure
## Overview
This endpoint returns the hierarchical structure of concept classes, showing how concept classes relate to each other through parent-child relationships. This is essential for understanding the taxonomic organization of medical terminology classification systems.
## Path Parameters
The concept class ID to get hierarchy for (e.g., "Clinical Finding", "Procedure", "Substance")
## Query Parameters
Hierarchy direction: "ancestors" (parents), "descendants" (children), or "both"
Maximum number of hierarchy levels to traverse (1-20)
Comma-separated list of vocabularies to filter hierarchy (e.g., "SNOMED,ICD10CM")
Include count of concepts in each class
Include cross-references to equivalent classes in other vocabularies
Specific vocabulary release version (e.g., "2024.1")
## Response
Indicates if the request was successful
Information about the requested concept class
The concept class identifier
Human-readable name
Concept ID representing this class
Primary vocabulary defining this class
Description of the concept class
Number of concepts in this class (when include\_concept\_counts=true)
Hierarchical structure of concept classes
Parent concept classes (when direction includes "ancestors")
Parent class ID
Parent class name
Parent class concept ID
Parent class vocabulary
Hierarchical level (1 = direct parent)
Type of hierarchical relationship
Number of concepts (when include\_concept\_counts=true)
Path from this class to root classes
Child concept classes (when direction includes "descendants")
Child class ID
Child class name
Child class concept ID
Child class vocabulary
Hierarchical level (1 = direct child)
Type of hierarchical relationship
Number of concepts (when include\_concept\_counts=true)
Whether this class has child classes
Sibling concept classes at the same hierarchical level
Sibling class ID
Sibling class name
Number of concepts (when include\_concept\_counts=true)
Common parent class
Equivalent classes in other vocabularies (when include\_cross\_references=true)
Target vocabulary
Equivalent class ID in target vocabulary
Equivalent class name
Confidence of equivalence: "exact", "broad", "narrow"
Concepts in equivalent class
Statistics about the hierarchy structure
Total number of ancestor classes
Total number of descendant classes
Maximum ancestor level reached
Maximum descendant level reached
Whether this is a root-level class
Whether this is a leaf-level class
Total concepts in hierarchy branch
Vocabularies represented in hierarchy
```bash cURL
curl -X GET "https://api.omophub.com/v1/concept-classes/Clinical%20Finding/hierarchy?direction=both&max_levels=3&include_concept_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript JavaScript
(async () => {
try {
const classId = encodeURIComponent("Clinical Finding");
const response = await fetch(`https://api.omophub.com/v1/concept-classes/${classId}/hierarchy?direction=both&include_concept_counts=true&include_cross_references=true`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const hierarchyData = await response.json();
console.log(`Hierarchy for: ${hierarchyData.data.concept_class.concept_class_name}`);
console.log(`Description: ${hierarchyData.data.concept_class.description}`);
console.log(`Concept count: ${hierarchyData.data.concept_class.concept_count?.toLocaleString() || 'N/A'}`);
const stats = hierarchyData.data.hierarchy_statistics;
console.log(`\nHierarchy Statistics:`);
console.log(` Ancestors: ${stats.total_ancestors}`);
console.log(` Descendants: ${stats.total_descendants}`);
console.log(` Max ancestor level: ${stats.max_ancestor_level}`);
console.log(` Max descendant level: ${stats.max_descendant_level}`);
console.log(` Is root class: ${stats.is_root_class}`);
console.log(` Is leaf class: ${stats.is_leaf_class}`);
if (hierarchyData.data.hierarchy.ancestors?.length > 0) {
console.log(`\nAncestors (Parents):`);
hierarchyData.data.hierarchy.ancestors.forEach(ancestor => {
const indent = ' '.repeat(ancestor.level);
const conceptCount = ancestor.concept_count ? ` (${ancestor.concept_count.toLocaleString()} concepts)` : '';
console.log(`${indent}Level ${ancestor.level}: ${ancestor.concept_class_name}${conceptCount}`);
});
}
if (hierarchyData.data.hierarchy.descendants?.length > 0) {
console.log(`\nDescendants (Children):`);
hierarchyData.data.hierarchy.descendants.slice(0, 10).forEach(descendant => {
const indent = ' '.repeat(descendant.level);
const conceptCount = descendant.concept_count ? ` (${descendant.concept_count.toLocaleString()} concepts)` : '';
const hasChildren = descendant.has_children ? ' [+]' : '';
console.log(`${indent}Level ${descendant.level}: ${descendant.concept_class_name}${conceptCount}${hasChildren}`);
});
}
} catch (error) {
console.error('Error fetching class hierarchy:', error.message);
}
})();
```
```python Python
import requests
from urllib.parse import quote
class_id = quote("Clinical Finding")
url = f"https://api.omophub.com/v1/concept-classes/{class_id}/hierarchy"
params = {
"direction": "both",
"max_levels": 5,
"vocabulary_ids": "SNOMED",
"include_concept_counts": True,
"include_cross_references": True
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
concept_class = data['data']['concept_class']
hierarchy = data['data']['hierarchy']
stats = data['data']['hierarchy_statistics']
print(f"Concept Class: {concept_class['concept_class_name']}")
print(f"Vocabulary: {concept_class['vocabulary_id']}")
print(f"Description: {concept_class.get('description', 'N/A')}")
print(f"Concept Count: {concept_class.get('concept_count', 'N/A'):,}" if concept_class.get('concept_count') else "Concept Count: N/A")
print(f"\nHierarchy Statistics:")
print(f" Total ancestors: {stats['total_ancestors']}")
print(f" Total descendants: {stats['total_descendants']}")
print(f" Max levels up: {stats['max_ancestor_level']}")
print(f" Max levels down: {stats['max_descendant_level']}")
print(f" Classification: {'Root class' if stats['is_root_class'] else 'Leaf class' if stats['is_leaf_class'] else 'Intermediate class'}")
print(f" Total concepts in branch: {stats.get('total_concept_count', 'N/A'):,}" if stats.get('total_concept_count') else "")
if hierarchy.get('ancestors'):
print(f"\nAncestors (Parent Classes):")
for ancestor in hierarchy['ancestors']:
indent = " " * ancestor['level']
concept_count = f" ({ancestor['concept_count']:,} concepts)" if ancestor.get('concept_count') else ""
print(f"{indent}L{ancestor['level']}: {ancestor['concept_class_name']}{concept_count}")
print(f"{indent} Relationship: {ancestor['relationship_type']}")
if hierarchy.get('descendants'):
print(f"\nDescendants (Child Classes) - showing first 15:")
for i, descendant in enumerate(hierarchy['descendants'][:15]):
indent = " " * descendant['level']
concept_count = f" ({descendant['concept_count']:,} concepts)" if descendant.get('concept_count') else ""
has_children = " [+]" if descendant.get('has_children') else " [-]"
print(f"{indent}L{descendant['level']}: {descendant['concept_class_name']}{concept_count}{has_children}")
if hierarchy.get('siblings'):
print(f"\nSibling Classes:")
for sibling in hierarchy['siblings'][:10]: # Show first 10
concept_count = f" ({sibling['concept_count']:,} concepts)" if sibling.get('concept_count') else ""
print(f" • {sibling['concept_class_name']}{concept_count}")
if data['data'].get('cross_references'):
print(f"\nCross-References to Other Vocabularies:")
for ref in data['data']['cross_references']:
confidence = ref['mapping_confidence'].upper()
concept_count = f" ({ref['concept_count']:,} concepts)" if ref.get('concept_count') else ""
print(f" {ref['vocabulary_id']}: {ref['equivalent_class_name']}{concept_count} [{confidence}]")
```
```json
{
"success": true,
"data": {
"concept_class": {
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_class_concept_id": 404684003,
"vocabulary_id": "SNOMED",
"description": "A clinical finding represents a result of an observation, assessment, or judgment",
"concept_count": 125847
},
"hierarchy": {
"ancestors": [
{
"concept_class_id": "SNOMED CT Concept",
"concept_class_name": "SNOMED CT Concept",
"concept_class_concept_id": 138875005,
"vocabulary_id": "SNOMED",
"level": 1,
"relationship_type": "Is a",
"concept_count": 350000,
"path_to_root": []
}
],
"descendants": [
{
"concept_class_id": "Disease",
"concept_class_name": "Disease",
"concept_class_concept_id": 64572001,
"vocabulary_id": "SNOMED",
"level": 1,
"relationship_type": "Is a",
"concept_count": 45892,
"has_children": true
},
{
"concept_class_id": "Sign or Symptom",
"concept_class_name": "Sign or Symptom",
"concept_class_concept_id": 418799008,
"vocabulary_id": "SNOMED",
"level": 1,
"relationship_type": "Is a",
"concept_count": 23456,
"has_children": true
},
{
"concept_class_id": "Disorder of body system",
"concept_class_name": "Disorder of body system",
"concept_class_concept_id": 362965005,
"vocabulary_id": "SNOMED",
"level": 2,
"relationship_type": "Is a",
"concept_count": 15623,
"has_children": true
}
],
"siblings": [
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_count": 87234,
"shared_parent": "SNOMED CT Concept"
},
{
"concept_class_id": "Substance",
"concept_class_name": "Substance",
"concept_count": 45692,
"shared_parent": "SNOMED CT Concept"
}
]
},
"cross_references": [
{
"vocabulary_id": "ICD10CM",
"equivalent_class_id": "Disease",
"equivalent_class_name": "Disease and related health problems",
"mapping_confidence": "broad",
"concept_count": 8934
},
{
"vocabulary_id": "ICD9CM",
"equivalent_class_id": "Disease",
"equivalent_class_name": "Diseases",
"mapping_confidence": "broad",
"concept_count": 5672
}
],
"hierarchy_statistics": {
"total_ancestors": 1,
"total_descendants": 15,
"max_ancestor_level": 1,
"max_descendant_level": 4,
"is_root_class": false,
"is_leaf_class": false,
"total_concept_count": 125847,
"vocabularies_involved": ["SNOMED"]
}
},
"meta": {
"request_id": "req_class_hierarchy_222",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Hierarchy
Get the basic hierarchical structure:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Procedure/hierarchy" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Ancestors Only
Get parent classes only:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Clinical%20Finding/hierarchy?direction=ancestors&max_levels=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Detailed Hierarchy with Counts
Get comprehensive hierarchy information:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Substance/hierarchy?include_concept_counts=true&include_cross_references=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cross-Vocabulary Analysis
Compare class structures across vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/concept-classes/Clinical%20Finding/hierarchy?vocabulary_ids=SNOMED,ICD10CM&include_cross_references=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Building Taxonomy Trees
Use for creating interactive taxonomy browsers:
```javascript
async function buildClassTree(rootClassId, maxDepth = 3) {
const response = await fetch(`https://api.omophub.com/v1/concept-classes/${encodeURIComponent(rootClassId)}/hierarchy?direction=descendants&max_levels=${maxDepth}&include_concept_counts=true`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const data = await response.json();
// Build tree structure for UI components
const buildTreeNode = (classData, level = 0) => ({
id: classData.concept_class_id,
name: classData.concept_class_name,
conceptCount: classData.concept_count || 0,
level: level,
hasChildren: classData.has_children || false,
children: []
});
const tree = buildTreeNode(data.data.concept_class, 0);
// Add descendants to tree
const levelGroups = {};
data.data.hierarchy.descendants.forEach(desc => {
if (!levelGroups[desc.level]) levelGroups[desc.level] = [];
levelGroups[desc.level].push(buildTreeNode(desc, desc.level));
});
// Build hierarchical structure - sort levels numerically
Object.keys(levelGroups).sort((a, b) => Number(a) - Number(b)).forEach(level => {
if (level === '1') {
tree.children = levelGroups[level];
}
// Add nested levels logic here
});
return tree;
}
```
## Important Notes
* **URL encoding** - Class IDs with spaces must be URL encoded
* **Vocabulary differences** - Hierarchical structures vary significantly between vocabularies
* **Performance** - Deep hierarchies with concept counts may take longer to process
* **Cross-references** - Mapping confidence indicates quality of cross-vocabulary equivalences
* **Navigation** - Use `has_children` flag to determine if further expansion is possible
## Related Endpoints
* [Get Concept Classes](/api-reference/domains/get-concept-classes) - List all concept classes
* [Get Class Concepts](/api-reference/domains/get-class-concepts) - Get concepts within a class
* [Get Concept Hierarchy](/api-reference/concepts/get-concept-hierarchy) - Individual concept hierarchies
# Get Concept Classes
Source: https://docs.omophub.com/api-reference/domains/get-concept-classes
GET /v1/concept-classes
Retrieve all available concept classes that provide fine-grained categorization within domains
This endpoint provides information about concept classes, which offer more detailed categorization of medical concepts within their respective domains. Concept classes help distinguish between different types of concepts like "Clinical Finding" vs "Disorder" within the Condition domain, or "Ingredient" vs "Clinical Drug" within the Drug domain.
## Query Parameters
Filter concept classes to a specific domain
**Example:** `Condition`, `Drug`, `Procedure`
Filter concept classes to those used by specific vocabularies
**Example:** `SNOMED,RxNorm,ICD10CM`
Include counts of concepts within each concept class
Include example concepts for each concept class
Include detailed statistics and usage information
Only return concept classes with active concepts
Only include concept classes containing standard concepts
Sort concept classes by specified field
**Options:** `concept_class_name`, `domain_id`, `concept_count`
Sort order for results
**Options:** `asc`, `desc`
Number of concept classes to return per page (max 500)
Page number for pagination (1-based)
## Response
Array of concept class objects with their properties
Unique identifier for the concept class
Human-readable name of the concept class
OMOP concept ID representing this concept class
Domain that contains this concept class
Human-readable domain name
Detailed description of the concept class and its scope
High-level category of the concept class
**Values:** `clinical`, `administrative`, `classification`, `measurement`, `metadata`
Number of concepts in this concept class (when include\_concept\_counts=true)
Number of standard concepts in this concept class (when include\_concept\_counts=true)
Vocabularies that primarily use this concept class
Whether concepts in this class typically have hierarchical relationships
Whether concepts in this class typically have cross-vocabulary mappings
Detailed concept class statistics (when include\_statistics=true)
Total concepts across all vocabularies
Count of concepts by vocabulary within this class
Historical growth information
New concepts added in the last year
Concepts deprecated in the last year
Average monthly growth rate
Statistics about relationships for concepts in this class
Average number of relationships per concept
Most frequently used relationship types
Average depth in hierarchical structures
Relative usage frequency in queries (0-1)
Example concepts from this concept class (when include\_examples=true)
Example concept identifier
Example concept name
Example concept code
Vocabulary containing the example concept
Standard concept designation
Date when concept class became valid
Date when concept class becomes invalid
Summary information about concept classes
Total number of concept classes returned
Count of concept classes by domain
Count of concept classes by vocabulary
Count of concept classes by category
Sum of concepts across all classes (when include\_concept\_counts=true)
Response metadata and pagination information
Current page number
Items per page
Total concept classes
Total number of pages
Whether next page exists
Whether previous page exists
Timestamp of last concept class data update
```bash cURL
curl -X GET "https://api.omophub.com/v1/concept-classes?domain_id=Drug&include_concept_counts=true&include_examples=true&include_statistics=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concept-classes?domain_id=Drug&include_concept_counts=true&include_examples=true&include_statistics=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const conceptClassesData = await response.json();
console.log(`Found ${conceptClassesData.concept_classes.length} concept classes in Drug domain`);
// Display concept classes with counts
conceptClassesData.concept_classes.forEach(conceptClass => {
console.log(`\n${conceptClass.concept_class_name}:`);
console.log(` Description: ${conceptClass.description}`);
console.log(` Concepts: ${conceptClass.concept_count || 'N/A'} total, ${conceptClass.standard_concept_count || 'N/A'} standard`);
console.log(` Primary vocabularies: ${conceptClass.primary_vocabularies.join(', ')}`);
if (conceptClass.examples) {
console.log(` Examples:`);
conceptClass.examples.slice(0, 3).forEach(example => {
console.log(` - ${example.concept_name} (${example.vocabulary_id})`);
});
}
if (conceptClass.statistics) {
console.log(` Usage frequency: ${(conceptClass.statistics.usage_frequency * 100).toFixed(1)}%`);
}
});
// Show domain distribution
console.log('\nDomain Distribution:');
Object.entries(conceptClassesData.concept_class_summary.domain_distribution).forEach(([domain, count]) => {
console.log(` ${domain}: ${count} concept classes`);
});
```
```python Python
import requests
url = "https://api.omophub.com/v1/concept-classes"
params = {
"domain_id": "Condition",
"vocabulary_ids": "SNOMED",
"include_concept_counts": True,
"include_statistics": True,
"include_examples": True,
"sort_by": "concept_count",
"sort_order": "desc"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Found {len(data['data']['concept_classes'])} concept classes in Condition domain (SNOMED)")
# Display top concept classes by concept count
for concept_class in data['data']['concept_classes'][:10]:
print(f"\n{concept_class['concept_class_name']}:")
print(f" ID: {concept_class['concept_class_id']}")
print(f" Description: {concept_class['description'][:100]}...")
if concept_class.get('concept_count'):
total = concept_class['concept_count']
standard = concept_class.get('standard_concept_count', 0)
print(f" Concepts: {total:,} total ({standard:,} standard)")
# Show characteristics
characteristics = []
if concept_class['hierarchical']:
characteristics.append("hierarchical")
if concept_class['mappable']:
characteristics.append("mappable")
if characteristics:
print(f" Characteristics: {', '.join(characteristics)}")
# Show relationship statistics
if concept_class.get('statistics') and 'relationship_statistics' in concept_class['statistics']:
rel_stats = concept_class['statistics']['relationship_statistics']
avg_rels = rel_stats.get('average_relationships_per_concept', 0)
print(f" Average relationships per concept: {avg_rels:.1f}")
if rel_stats.get('most_common_relationship_types'):
common_rels = rel_stats['most_common_relationship_types'][:3]
print(f" Common relationship types: {', '.join(common_rels)}")
# Show examples
if concept_class.get('examples'):
print(f" Examples:")
for example in concept_class['examples'][:3]:
std_marker = " (S)" if example['standard_concept'] == 'S' else ""
print(f" - {example['concept_name']}{std_marker}")
# Show summary statistics
summary = data['data']['concept_class_summary']
print(f"\nSummary:")
print(f" Total concept classes: {summary['total_concept_classes']}")
if 'total_concepts_across_classes' in summary:
print(f" Total concepts: {summary['total_concepts_across_classes']:,}")
# Show category distribution
if 'category_distribution' in summary:
print(f" Categories: {dict(summary['category_distribution'])}")
```
```json
{
"success": true,
"data": {
"concept_classes": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_class_concept_id": 900000000,
"domain_id": "Condition",
"domain_name": "Condition",
"description": "Clinical findings represent observable and measurable signs, symptoms, and abnormal states discovered during clinical examination or patient assessment",
"category": "clinical",
"concept_count": 425891,
"standard_concept_count": 298456,
"primary_vocabularies": ["SNOMED"],
"hierarchical": true,
"mappable": true,
"statistics": {
"total_concepts": 425891,
"vocabulary_distribution": {
"SNOMED": 398234,
"Read": 27657
},
"growth_trend": {
"concepts_added_last_year": 8947,
"concepts_deprecated_last_year": 1234,
"average_monthly_growth": 0.18
},
"relationship_statistics": {
"average_relationships_per_concept": 4.7,
"most_common_relationship_types": ["Is a", "Associated morphology", "Finding site"],
"hierarchy_depth_average": 5.2
},
"usage_frequency": 0.87
},
"examples": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"standard_concept": "S"
},
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"standard_concept": "S"
},
{
"concept_id": 4329847,
"concept_name": "Myocardial infarction",
"concept_code": "22298006",
"vocabulary_id": "SNOMED",
"standard_concept": "S"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"concept_class_id": "Ingredient",
"concept_class_name": "Ingredient",
"concept_class_concept_id": 900000001,
"domain_id": "Drug",
"domain_name": "Drug",
"description": "Active pharmaceutical ingredients and components that provide therapeutic effects in medications",
"category": "clinical",
"concept_count": 45678,
"standard_concept_count": 12345,
"primary_vocabularies": ["RxNorm", "SNOMED"],
"hierarchical": true,
"mappable": true,
"statistics": {
"total_concepts": 45678,
"vocabulary_distribution": {
"RxNorm": 38942,
"SNOMED": 6736
},
"growth_trend": {
"concepts_added_last_year": 1247,
"concepts_deprecated_last_year": 234,
"average_monthly_growth": 0.22
},
"relationship_statistics": {
"average_relationships_per_concept": 8.3,
"most_common_relationship_types": ["Has active ingredient", "Is a", "RxNorm inverse is a"],
"hierarchy_depth_average": 3.8
},
"usage_frequency": 0.65
},
"examples": [
{
"concept_id": 1503297,
"concept_name": "Metformin",
"concept_code": "6809",
"vocabulary_id": "RxNorm",
"standard_concept": "S"
},
{
"concept_id": 1308216,
"concept_name": "Lisinopril",
"concept_code": "29046",
"vocabulary_id": "RxNorm",
"standard_concept": "S"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_class_concept_id": 900000002,
"domain_id": "Procedure",
"domain_name": "Procedure",
"description": "Medical procedures, surgical interventions, therapeutic treatments, and diagnostic procedures performed on patients",
"category": "clinical",
"concept_count": 198765,
"standard_concept_count": 134567,
"primary_vocabularies": ["SNOMED", "HCPCS"],
"hierarchical": true,
"mappable": true,
"statistics": {
"total_concepts": 198765,
"vocabulary_distribution": {
"SNOMED": 156234,
"HCPCS": 42531
},
"relationship_statistics": {
"average_relationships_per_concept": 3.9,
"most_common_relationship_types": ["Is a", "Procedure site", "Using device"],
"hierarchy_depth_average": 4.1
},
"usage_frequency": 0.72
},
"examples": [
{
"concept_id": 4273391,
"concept_name": "Coronary artery bypass graft",
"concept_code": "232717009",
"vocabulary_id": "SNOMED",
"standard_concept": "S"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"concept_class_summary": {
"total_concept_classes": 156,
"domain_distribution": {
"Condition": 45,
"Drug": 34,
"Procedure": 28,
"Measurement": 25,
"Observation": 15,
"Device": 9
},
"vocabulary_distribution": {
"SNOMED": 89,
"RxNorm": 23,
"HCPCS": 15,
"LOINC": 18,
"ICD10CM": 11
},
"category_distribution": {
"clinical": 123,
"administrative": 18,
"classification": 12,
"metadata": 3
},
"total_concepts_across_classes": 2847562
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 156,
"total_pages": 2,
"has_next": true,
"has_previous": false
},
"request_id": "req_concept_classes_456",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### All Concept Classes
Get all available concept classes:
```javascript
const allConceptClasses = await fetch('https://api.omophub.com/v1/concept-classes');
```
### Concept Classes by Domain
Get concept classes within a specific domain:
```javascript
const drugClasses = await fetch('https://api.omophub.com/v1/concept-classes?domain_id=Drug&include_concept_counts=true');
```
### Concept Classes with Statistics
Get detailed statistics for concept classes:
```javascript
const detailedClasses = await fetch('https://api.omophub.com/v1/concept-classes?include_statistics=true&include_examples=true');
```
### SNOMED Concept Classes
Get concept classes used by SNOMED:
```javascript
const snomedClasses = await fetch('https://api.omophub.com/v1/concept-classes?vocabulary_ids=SNOMED&sort_by=concept_count&sort_order=desc');
```
### Hierarchical Concept Classes
Filter to concept classes that have hierarchical structure:
```javascript
// First, fetch the concept classes data
const response = await fetch('https://api.omophub.com/v1/concept-classes?include_statistics=true', {
method: 'GET'
});
// Handle the response and check HTTP status
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Extract data from response
const conceptClasses = await response.json();
// Now filter to hierarchical concept classes
const hierarchicalClasses = (conceptClasses?.concept_classes || []).filter(cc => cc.hierarchical);
console.log(`Found ${hierarchicalClasses.length} hierarchical concept classes`);
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Available domain information
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Concepts within domains
* [Search Concepts](/api-reference/search/search-concepts) - Search with concept class filters
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
## Notes
* Concept classes provide finer categorization than domains
* Many concept classes are vocabulary-specific (e.g., "3-char billing code" for ICD codes)
* Standard concepts are preferred for clinical applications
* Hierarchical concept classes typically have "Is a" relationships
* Some concept classes span multiple vocabularies through mappings
* Growth trends help identify evolving areas of medical terminology
* Usage frequency indicates popularity in real-world applications
* Relationship statistics help understand concept connectivity patterns
# Get Domain Concepts
Source: https://docs.omophub.com/api-reference/domains/get-domain-concepts
GET /v1/domains/{domain_id}/concepts
Retrieve all concepts within a specific domain, with filtering and search capabilities
This endpoint provides access to all medical concepts within a particular domain, such as all conditions, drugs, or procedures. It supports comprehensive filtering and search to help users find relevant concepts within the domain scope.
## Path Parameters
The domain identifier to retrieve concepts from
**Example:** `Condition`, `Drug`, `Procedure`
## Query Parameters
Filter concepts to specific vocabularies within the domain
**Example:** `SNOMED,ICD10CM`
Filter to specific concept classes within the domain
**Example:** `Clinical Finding,Disorder`
Filter by standard concept designation. Three behaviors:
* **Omitting the parameter**: applies no filter (returns both standard and non-standard concepts)
* **Passing `"S"` or `"C"`**: filters to standard-only or classification-only respectively
* **Passing `"null"` (string)**: returns non-standard-only concepts. For GET query params, use the literal string value `null` (e.g., `?standard_concept=null`).
**Options:** `S`, `C`, `"null"` (literal string)
Default (when omitted): no filter (returns standard + classification + non-standard).
Search term to filter concepts by name or synonyms
**Example:** `diabetes`
Type of search to perform
**Options:** `exact`, `prefix`, `fuzzy`, `semantic`
Include invalid/deprecated concepts in results
Include synonym information for each concept
Include relationship counts and types for each concept
Include hierarchical parent/child information
Filter to concepts with IDs greater than or equal to this value
Filter to concepts with IDs less than or equal to this value
Filter to concepts valid after this date (ISO format)
Filter to concepts valid before this date (ISO format)
Sort concepts by specified field
**Options:** `concept_name`, `concept_id`, `concept_code`, `valid_start_date`
Sort order for results
**Options:** `asc`, `desc`
Number of concepts to return per page (max 1000)
Page number for pagination (1-based)
## Response
Information about the domain being queried
Domain identifier
Human-readable domain name
Domain description
Total number of concepts in this domain
Array of concept objects within the domain
Unique concept identifier
Standard name of the concept
Original code from the vocabulary
Vocabulary containing this concept
Human-readable vocabulary name
Concept class identifier
Human-readable concept class name
Standard concept designation ('S', 'C', or 'null')
Date when concept became valid (ISO format)
Date when concept becomes invalid (ISO format)
Reason for concept invalidation if applicable
Alternative names for this concept (when include\_synonyms=true)
Alternative name for the concept
Language identifier for the synonym
Human-readable language name
Summary of relationships (when include\_relationships=true)
Total number of relationships for this concept
List of relationship types present
Whether concept has hierarchical relationships
Whether concept has cross-vocabulary mappings
Hierarchical position information (when include\_hierarchy=true)
Whether concept has parent concepts
Whether concept has child concepts
Approximate level in the domain hierarchy
Information about the top-level ancestor concept
Summary of filters applied to the query
Vocabularies included in filter
Concept classes included in filter
Search term applied
Type of search performed
Standard concept filter applied
Statistics about the filtered result set
Number of concepts in current result set
Total concepts matching the applied filters
Count of concepts by vocabulary in results
Count of concepts by concept class in results
Count of concepts by standard designation ('S', 'C', or 'null')
Response metadata and pagination information
Current page number
Items per page
Total concepts matching filters
Total number of pages
Whether next page exists
Whether previous page exists
Time taken to execute the query in milliseconds
```bash cURL
curl -X GET "https://api.omophub.com/v1/domains/Condition/concepts?search=diabetes&vocabulary_ids=SNOMED&include_synonyms=true&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/domains/Condition/concepts?search=diabetes&vocabulary_ids=SNOMED&include_synonyms=true&page_size=50', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const domainConcepts = await response.json();
console.log(`Found ${domainConcepts.domain_statistics.total_matching_concepts} diabetes-related conditions`);
// Display concepts with synonyms
domainConcepts.concepts.forEach(concept => {
console.log(`\n${concept.concept_name} (${concept.concept_id})`);
console.log(` Code: ${concept.concept_code} | Class: ${concept.concept_class_name}`);
if (concept.synonyms && concept.synonyms.length > 0) {
console.log(` Synonyms: ${concept.synonyms.map(s => s.concept_synonym_name).join(', ')}`);
}
});
```
```python Python
import requests
domain_id = "Drug"
url = f"https://api.omophub.com/v1/domains/{domain_id}/concepts"
params = {
"search": "metformin",
"vocabulary_ids": "RxNorm,RxNorm Extension",
"include_synonyms": True,
"include_relationships": True,
"standard_concept": "S",
"page_size": 100
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Domain: {data['domain']['domain_name']}")
print(f"Search results: {data['domain_statistics']['total_matching_concepts']} concepts")
# Display drug concepts
for concept in data['concepts']:
print(f"\n{concept['concept_name']} ({concept['concept_id']})")
print(f" Vocabulary: {concept['vocabulary_name']}")
print(f" Code: {concept['concept_code']}")
print(f" Class: {concept['concept_class_name']}")
# Show synonyms
if concept.get('synonyms'):
synonyms = [syn['concept_synonym_name'] for syn in concept['synonyms']]
print(f" Also known as: {', '.join(synonyms[:3])}")
# Show relationship summary
if concept.get('relationship_summary'):
rel_sum = concept['relationship_summary']
print(f" Relationships: {rel_sum['total_relationships']} total")
if rel_sum['has_mappings']:
print(f" ✓ Has cross-vocabulary mappings")
if rel_sum['has_hierarchy']:
print(f" ✓ Has hierarchical relationships")
```
```json
{
"success": true,
"data": {
"domain": {
"domain_id": "Condition",
"domain_name": "Condition",
"description": "Medical conditions, diseases, disorders, and clinical findings",
"total_concepts_in_domain": 845672
},
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z",
"synonyms": [
{
"concept_synonym_name": "Type II diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Adult-onset diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Non-insulin dependent diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationship_summary": {
"total_relationships": 47,
"relationship_types": ["Is a", "Maps to", "Has associated morphology"],
"has_hierarchy": true,
"has_mappings": true
},
"hierarchy_info": {
"has_parents": true,
"has_children": true,
"hierarchy_level": 4,
"top_level_ancestor": {
"concept_id": 404684003,
"concept_name": "Clinical finding"
}
}
},
{
"concept_id": 46635009,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z",
"synonyms": [
{
"concept_synonym_name": "Type I diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Insulin-dependent diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Juvenile diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"relationship_summary": {
"total_relationships": 42,
"relationship_types": ["Is a", "Maps to", "Has associated morphology"],
"has_hierarchy": true,
"has_mappings": true
},
"hierarchy_info": {
"has_parents": true,
"has_children": true,
"hierarchy_level": 4,
"top_level_ancestor": {
"concept_id": 404684003,
"concept_name": "Clinical finding"
}
}
}
],
"filters_applied": {
"vocabulary_filter": ["SNOMED"],
"search_term": "diabetes",
"search_type": "fuzzy"
},
"domain_statistics": {
"concepts_returned": 50,
"total_matching_concepts": 847,
"vocabulary_distribution": {
"SNOMED": 847
},
"concept_class_distribution": {
"Clinical Finding": 723,
"Disorder": 124
},
"standard_concept_distribution": {
"S": 612,
"C": 89,
"null": 146
}
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 847,
"total_pages": 17,
"has_next": true,
"has_previous": false
},
"query_time_ms": 124.5,
"request_id": "req_domain_concepts_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### All Concepts in a Domain
Get all concepts within the Condition domain:
```javascript
const conditions = await fetch('/v1/domains/Condition/concepts?page_size=1000');
```
### Search Within Domain
Search for diabetes-related concepts:
```javascript
const diabetesConcepts = await fetch('/v1/domains/Condition/concepts?search=diabetes&search_type=semantic');
```
### Standard Concepts Only
Get only standard concepts from a domain:
```javascript
const standardDrugs = await fetch('/v1/domains/Drug/concepts?standard_concept=S&vocabulary_ids=RxNorm');
```
### Concepts from Specific Vocabularies
Get procedures from specific vocabularies:
```javascript
const snomedProcedures = await fetch('/v1/domains/Procedure/concepts?vocabulary_ids=SNOMED,RxNorm&include_hierarchy=true');
```
### Detailed Concept Information
Get comprehensive concept details:
```javascript
const detailedConcepts = await fetch('/v1/domains/Drug/concepts?include_synonyms=true&include_relationships=true&include_hierarchy=true');
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Available domain list
* [Get Concept Details](/api-reference/concepts/get-concept-details) - Individual concept information
* [Search Concepts](/api-reference/search/search-concepts) - Cross-domain concept search
* [Get Concept Classes](/api-reference/domains/get-concept-classes) - Concept class information
## Notes
* Domain queries can return very large result sets - use pagination appropriately
* Search functionality within domains supports fuzzy matching and semantic search
* Standard concepts (S) are preferred for most clinical applications
* Hierarchy information helps understand concept relationships within the domain
* Some concepts may appear in multiple domains through different vocabularies
* Including synonyms significantly increases response size but improves search capability
* Vocabulary filtering is essential when working with specific terminology systems
* Concept classes provide finer-grained categorization within domains
# Get Domain Hierarchy
Source: https://docs.omophub.com/api-reference/domains/get-domain-hierarchy
GET /v1/domains/{domain_id}/hierarchy
Retrieve the hierarchical structure and organization of concepts within a specific domain
This endpoint provides a comprehensive view of the hierarchical organization within a domain, showing how concepts are structured from top-level categories down to specific terms. It's essential for understanding domain taxonomy and building navigation interfaces.
## Path Parameters
The domain identifier to retrieve hierarchy for
**Example:** `Condition`, `Drug`, `Procedure`
## Query Parameters
Filter hierarchy to specific vocabularies within the domain
**Example:** `SNOMED,ICD10CM`
Start hierarchy from a specific root concept (instead of domain roots)
**Example:** `404684003` (Clinical finding)
Maximum hierarchy depth to traverse
**Range:** `1-10`
Minimum number of children required to include a concept in hierarchy
Include count of descendant concepts for each node
Include concepts with no children (leaf nodes)
Only include standard concepts in hierarchy
Only include active (non-deprecated) concepts
Response format for hierarchy data
**Options:**
* `nested` - Hierarchical parent-child objects with nested structure preserved
* `flat` - Flattened array of hierarchy\_paths (each entry is a root→leaf path with depth implied by path length)
* `tree` - One or more root-based trees (may include multiple disconnected root nodes) suitable for tree rendering
Sort hierarchy nodes by specified criteria
**Options:** `concept_name`, `concept_id`, `direct_children_count`
Sort order for hierarchy nodes
**Options:** `asc`, `desc`
## Response
Information about the domain being analyzed
Domain identifier
Human-readable domain name
Domain description
Total concepts in the domain
The hierarchical structure of the domain
Top-level concepts in the domain hierarchy
Unique concept identifier
Standard name of the concept
Original code from the vocabulary
Vocabulary containing this concept
Human-readable vocabulary name
Concept class identifier
Human-readable concept class name
Standard concept designation ('S', 'C', or null)
Level in the hierarchy (0 = root)
Array of concept IDs representing path from domain root
Number of descendant concepts (when include\_concept\_counts=true)
Number of direct child concepts
Array of child concepts (nested structure)
Whether this concept has no children
Statistics about this branch of the hierarchy
Maximum depth of hierarchy below this node
Total number of descendant concepts
Distribution of vocabularies in this branch
Distribution of concept classes in this branch
Date when concept became valid
Date when concept becomes invalid
All paths from root to leaf (when format='flat'). Note: when format='flat', the root\_concepts field is not included in the response and each hierarchy\_paths entry contains only the listed path fields.
Unique identifier for this path
Array of concept IDs in hierarchical order
Array of concept names in hierarchical order
Number of concepts in this path
Information about the leaf concept at the end of this path
Statistical summary of the domain hierarchy
Total number of concepts included in hierarchy response
Number of top-level root concepts
Number of leaf concepts (no children)
Maximum depth of the hierarchy tree
Average number of children per non-leaf concept
Distribution of concepts by vocabulary in hierarchy
Distribution of concepts by concept class in hierarchy
Number of concepts at each hierarchy level
Concepts in domain without hierarchical relationships
Suggestions for UI navigation and display
Most important root concepts for navigation
Significant branches in the hierarchy
Commonly accessed leaf concepts
Suggested separators for breadcrumb navigation
Response metadata and processing information
Summary of filters applied to hierarchy generation
Time taken to generate hierarchy
Timestamp of underlying vocabulary data
Whether hierarchy was truncated due to size limits
```bash {title="cURL"}
curl -X GET "https://api.omophub.com/v1/domains/Condition/hierarchy?vocabulary_ids=SNOMED&max_depth=3&include_concept_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript {title="JavaScript"}
const response = await fetch('https://api.omophub.com/v1/domains/Condition/hierarchy?vocabulary_ids=SNOMED&max_depth=3&include_concept_counts=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const hierarchy = await response.json();
console.log(`Domain: ${hierarchy.data.domain.domain_name}`);
console.log(`Root concepts: ${hierarchy.data.hierarchy_statistics.root_concepts_count}`);
// Recursive function to display hierarchy
function displayHierarchy(nodes, depth = 0) {
const indent = ' '.repeat(depth);
nodes.forEach(node => {
const childrenInfo = node.direct_children_count > 0 ? ` (${node.direct_children_count} children)` : ' (leaf)';
console.log(`${indent}- ${node.concept_name}${childrenInfo}`);
// Show descendant count if available
if (node.descendant_count) {
console.log(`${indent} 📊 ${node.descendant_count} total descendants`);
}
// Recursively display children
if (node.children && node.children.length > 0) {
displayHierarchy(node.children, depth + 1);
}
});
}
// Display hierarchy starting from root concepts
console.log('\nHierarchy Structure:');
displayHierarchy(hierarchy.data.hierarchy.root_concepts);
// Show statistics
console.log('\nHierarchy Statistics:');
console.log(` Max depth: ${hierarchy.data.hierarchy_statistics.max_hierarchy_depth}`);
console.log(` Average branching factor: ${hierarchy.data.hierarchy_statistics.average_branching_factor.toFixed(1)}`);
console.log(` Leaf concepts: ${hierarchy.data.hierarchy_statistics.leaf_concepts_count}`);
```
```python {title="Python"}
import requests
import json
from collections import defaultdict
domain_id = "Drug"
url = f"https://api.omophub.com/v1/domains/{domain_id}/hierarchy"
params = {
"vocabulary_ids": "RxNorm",
"max_depth": 4,
"include_concept_counts": True,
"min_children_threshold": 5, # Only show nodes with at least 5 children
"sort_by": "direct_children_count",
"sort_order": "desc"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"=== {data['data']['domain']['domain_name']} Domain Hierarchy ===")
print(f"Total concepts in domain: {data['data']['domain']['total_concepts_in_domain']:,}")
def print_hierarchy_node(node, depth=0, max_display_depth=3):
"""Print hierarchy node with indentation"""
if depth > max_display_depth:
return
indent = " " * depth
name = node['concept_name'][:60] + "..." if len(node['concept_name']) > 60 else node['concept_name']
# Build info string
info_parts = []
if node.get('direct_children_count', 0) > 0:
info_parts.append(f"{node['direct_children_count']} children")
if node.get('descendant_count'):
info_parts.append(f"{node['descendant_count']} descendants")
if node.get('concept_class_name'):
info_parts.append(f"Class: {node['concept_class_name']}")
info_str = f" ({', '.join(info_parts)})" if info_parts else ""
print(f"{indent}{name}{info_str}")
# Print children recursively
if node.get('children'):
for child in node['children']:
print_hierarchy_node(child, depth + 1, max_display_depth)
# Display hierarchy
print(f"\nHierarchy Structure (RxNorm, top {len(data['data']['hierarchy']['root_concepts'])} roots):")
for root in data['data']['hierarchy']['root_concepts']:
print_hierarchy_node(root)
print() # Add space between root concepts
# Display statistics
stats = data['data']['hierarchy_statistics']
print(f"=== Hierarchy Statistics ===")
print(f"Nodes in hierarchy: {stats['total_nodes_in_hierarchy']:,}")
print(f"Root concepts: {stats['root_concepts_count']}")
print(f"Leaf concepts: {stats['leaf_concepts_count']:,}")
print(f"Max depth: {stats['max_hierarchy_depth']}")
print(f"Average branching factor: {stats['average_branching_factor']:.1f}")
# Show depth distribution
print(f"\nConcepts by Depth Level:")
depth_dist = stats.get('depth_distribution', {})
for level in sorted(depth_dist.keys(), key=int):
count = depth_dist[level]
print(f" Level {level}: {count:,} concepts")
# Show concept class distribution
print(f"\nConcept Classes in Hierarchy:")
class_dist = stats.get('concept_class_distribution', {})
for class_name, count in sorted(class_dist.items(), key=lambda x: x[1], reverse=True)[:10]:
print(f" {class_name}: {count:,}")
# Navigation hints
if 'navigation_hints' in data['data']:
hints = data['data']['navigation_hints']
if hints.get('suggested_root_concepts'):
print(f"\nSuggested Navigation Roots:")
for root in hints['suggested_root_concepts'][:5]:
print(f" - {root['concept_name']}")
if hints.get('major_branches'):
print(f"\nMajor Branches:")
for branch in hints['major_branches'][:5]:
print(f" - {branch['concept_name']} ({branch.get('descendant_count', 'N/A')} descendants)")
# Check if truncation was applied
if data['meta'].get('truncation_applied'):
print(f"\n⚠️ Note: Hierarchy was truncated due to size limits")
```
```json
{
"success": true,
"data": {
"domain": {
"domain_id": "Condition",
"domain_name": "Condition",
"description": "Medical conditions, diseases, disorders, and clinical findings",
"total_concepts_in_domain": 845672
},
"hierarchy": {
"root_concepts": [
{
"concept_id": 404684003,
"concept_name": "Clinical finding",
"concept_code": "404684003",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"standard_concept": "S",
"hierarchy_level": 0,
"path_from_root": [404684003],
"descendant_count": 387421,
"direct_children_count": 23,
"is_leaf": false,
"children": [
{
"concept_id": 64572001,
"concept_name": "Disease",
"concept_code": "64572001",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"standard_concept": "S",
"hierarchy_level": 1,
"path_from_root": [404684003, 64572001],
"descendant_count": 234567,
"direct_children_count": 15,
"is_leaf": false,
"children": [
{
"concept_id": 362969004,
"concept_name": "Disorder of endocrine system",
"concept_code": "362969004",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"standard_concept": "S",
"hierarchy_level": 2,
"path_from_root": [404684003, 64572001, 362969004],
"descendant_count": 12847,
"direct_children_count": 8,
"is_leaf": false,
"children": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"standard_concept": "S",
"hierarchy_level": 3,
"path_from_root": [404684003, 64572001, 362969004, 73211009],
"descendant_count": 847,
"direct_children_count": 23,
"is_leaf": false,
"children": [],
"branch_statistics": {
"max_depth_below": 3,
"total_descendants": 847,
"vocabulary_distribution": {
"SNOMED": 847
},
"concept_class_distribution": {
"Clinical Finding": 723,
"Disorder": 124
}
},
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
]
}
]
}
],
"branch_statistics": {
"max_depth_below": 8,
"total_descendants": 387421,
"vocabulary_distribution": {
"SNOMED": 387421
},
"concept_class_distribution": {
"Clinical Finding": 298456,
"Disorder": 88965
}
},
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
]
},
"hierarchy_statistics": {
"total_nodes_in_hierarchy": 4567,
"root_concepts_count": 3,
"leaf_concepts_count": 0,
"max_hierarchy_depth": 8,
"average_branching_factor": 4.2,
"vocabulary_distribution": {
"SNOMED": 4567
},
"concept_class_distribution": {
"Clinical Finding": 3456,
"Disorder": 1111
},
"depth_distribution": {
"0": 3,
"1": 47,
"2": 234,
"3": 1456
},
"orphaned_concepts": 12456
},
"navigation_hints": {
"suggested_root_concepts": [
{
"concept_id": 404684003,
"concept_name": "Clinical finding",
"descendant_count": 387421
},
{
"concept_id": 272379006,
"concept_name": "Event",
"descendant_count": 45678
}
],
"major_branches": [
{
"concept_id": 64572001,
"concept_name": "Disease",
"descendant_count": 234567
},
{
"concept_id": 118234003,
"concept_name": "Finding by site",
"descendant_count": 89234
}
],
"popular_leaf_concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus"
}
],
"breadcrumb_separators": [">", "→", "/"]
}
},
"meta": {
"applied_filters": {
"vocabulary_ids": ["SNOMED"],
"max_depth": 3,
"standard_only": true,
"active_only": true
},
"request_id": "req_domain_hierarchy_101",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Domain Hierarchy
Get the hierarchical structure of a domain:
```javascript
const hierarchy = await fetch('/v1/domains/Condition/hierarchy');
```
### Limited Depth Hierarchy
Get hierarchy with limited depth for performance:
```javascript
const shallowHierarchy = await fetch('/v1/domains/Drug/hierarchy?max_depth=2&min_children_threshold=10');
```
### SNOMED-Only Hierarchy
Get hierarchy for specific vocabulary:
```javascript
const snomedHierarchy = await fetch('/v1/domains/Condition/hierarchy?vocabulary_ids=SNOMED&include_concept_counts=true');
```
### Flat Path Format
Get hierarchy as flat paths instead of nested structure:
```javascript
const pathHierarchy = await fetch('/v1/domains/Procedure/hierarchy?format=flat');
```
### Specific Root Analysis
Start hierarchy from a specific concept:
```javascript
const diabetesHierarchy = await fetch('/v1/domains/Condition/hierarchy?root_concept_id=73211009&max_depth=5');
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Available domains
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - All concepts in domain
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Specific concept ancestry
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Specific concept children
## Notes
* Domain hierarchies can be very large - use depth limits and thresholds appropriately
* Standard concepts provide more reliable hierarchical structures
* Some domains have multiple disconnected root nodes when using tree format
* Navigation hints help build intuitive user interfaces
* Orphaned concepts exist in the domain but lack hierarchical relationships
* Vocabulary-specific hierarchies may have different organizational principles
* Branch statistics help identify significant sub-domains for focused analysis
* Truncation may occur for very large hierarchies to maintain performance
# Get Domain Statistics
Source: https://docs.omophub.com/api-reference/domains/get-domain-statistics
GET /v1/domains/{domain_id}/statistics
Retrieve comprehensive statistical analysis and metrics for a specific domain
This endpoint provides detailed statistical analysis for a specific domain, including concept counts, vocabulary distributions, growth trends, relationship patterns, and usage analytics. It's essential for understanding domain characteristics and planning vocabulary integration strategies.
## Path Parameters
The domain identifier to retrieve statistics for
**Example:** `Condition`, `Drug`, `Procedure`
## Query Parameters
Filter statistics to specific vocabularies within the domain
**Example:** `SNOMED,ICD10CM,RxNorm`
Time period for trend analysis
**Options:** `1y`, `2y`, `5y`, `10y`, `all`
Include detailed breakdown by concept class
Include relationship pattern analysis
Include historical growth trend analysis
Include usage pattern analytics and query statistics
Calculate statistics for standard concepts only
Only include active concepts in statistics (defaults to false)
## Response
Domain identification and basic information
Domain identifier
Human-readable domain name
Domain description
Domain category classification
High-level statistical overview of the domain
Total number of concepts in the domain
Number of standard concepts
Number of classification concepts
Number of non-standard concepts
Number of currently active concepts
Number of deprecated concepts
Number of vocabularies contributing to this domain
Number of concept classes within this domain
Statistical breakdown by vocabulary
Statistics for each vocabulary
Human-readable vocabulary name
Total concepts from this vocabulary in the domain
Standard concepts from this vocabulary
Percentage of domain concepts from this vocabulary
Concept classes used by this vocabulary in the domain
Average number of relationships per concept
Detailed breakdown by concept class (when include\_concept\_class\_breakdown=true)
Statistics for each concept class
Human-readable concept class name
Total concepts in this class within the domain
Standard concepts in this class
Main vocabularies contributing to this class
Percentage of domain represented by this class
Hierarchical structure analysis
Average depth in hierarchy
Maximum hierarchy depth
Number of root-level concepts
Analysis of relationship patterns (when include\_relationship\_analysis=true)
Total relationships within the domain
Distribution of relationships per concept
Average relationships per concept
Median relationships per concept
Maximum relationships for any single concept
Minimum relationships for any single concept
Count and percentage of each relationship type
Statistics about mappings between vocabularies
Total cross-vocabulary mapping relationships
Mapping counts between vocabulary pairs
Concepts without cross-vocabulary mappings
Analysis of hierarchical relationship connectivity
Number of separate hierarchical trees
Size of the largest hierarchical tree
Concepts without hierarchical relationships
Historical growth and change analysis (when include\_growth\_trends=true)
Time-series data points showing growth over time
Date of the data point
Total concepts at this point in time
New concepts added since previous point
Concepts deprecated since previous point
Average annual growth rate as percentage
Growth rates by vocabulary
Analysis of recently added concepts
Concepts added in last 30 days
Concepts added in last 90 days
Concepts added in last year
Vocabularies adding the most concepts recently
Usage pattern analytics (when include\_usage\_analytics=true)
How often this domain is queried
Average daily queries for this domain
Monthly query volume trend
Hours of day with peak usage
Most frequently accessed concepts in the domain
Concept identifier
Concept name
Source vocabulary
Number of times accessed
Common search patterns within the domain
Most common search terms
Percentage of searches that return results
Average number of results per search
Response metadata and analysis information
Date when statistics were calculated
Timestamp of most recent data included
Time taken to calculate statistics
Summary of applied filters
```bash cURL
curl -X GET "https://api.omophub.com/v1/domains/Condition/statistics?include_growth_trends=true&include_usage_analytics=true&time_period=5y" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/domains/Condition/statistics?include_growth_trends=true&include_usage_analytics=true&time_period=5y', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const stats = await response.json();
console.log(`Domain: ${stats.data.domain.domain_name}`);
console.log(`Total concepts: ${stats.data.overview_statistics.total_concepts.toLocaleString()}`);
// Display vocabulary distribution
console.log('\nTop Vocabularies:');
const vocabStats = Object.entries(stats.data.vocabulary_distribution)
.sort(([,a], [,b]) => b.total_concepts - a.total_concepts)
.slice(0, 5);
vocabStats.forEach(([vocabId, data]) => {
console.log(` ${data.vocabulary_name}: ${data.total_concepts.toLocaleString()} concepts (${data.percentage_of_domain.toFixed(1)}%)`);
});
// Show concept class breakdown
if (stats.data.concept_class_breakdown) {
console.log('\nTop Concept Classes:');
const classStats = Object.entries(stats.data.concept_class_breakdown)
.sort(([,a], [,b]) => b.total_concepts - a.total_concepts)
.slice(0, 5);
classStats.forEach(([classId, data]) => {
console.log(` ${data.concept_class_name}: ${data.total_concepts.toLocaleString()} concepts`);
});
}
// Show growth trends
if (stats.data.growth_trends) {
console.log(`\nGrowth Rate: ${stats.data.growth_trends.annual_growth_rate.toFixed(2)}% annually`);
console.log(`Recent additions (last year): ${stats.data.growth_trends.recent_additions.last_year.toLocaleString()}`);
}
// Show usage analytics
if (stats.data.usage_analytics) {
console.log(`\nDaily query average: ${stats.data.usage_analytics.query_frequency.daily_average.toFixed(0)}`);
if (stats.data.usage_analytics.most_accessed_concepts.length > 0) {
console.log('Most accessed concepts:');
stats.data.usage_analytics.most_accessed_concepts.slice(0, 3).forEach(concept => {
console.log(` - ${concept.concept_name} (${concept.access_count.toLocaleString()} accesses)`);
});
}
}
```
```python Python
import requests
import matplotlib.pyplot as plt
from datetime import datetime
domain_id = "Drug"
url = f"https://api.omophub.com/v1/domains/{domain_id}/statistics"
params = {
"include_growth_trends": True,
"include_relationship_analysis": True,
"include_usage_analytics": True,
"time_period": "10y",
"vocabulary_ids": "RxNorm,RxNorm Extension"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
stats = response.json()
print(f"=== {stats['data']['domain']['domain_name']} Domain Statistics ===")
print(f"Total concepts: {stats['data']['overview_statistics']['total_concepts']:,}")
print(f"Standard concepts: {stats['data']['overview_statistics']['standard_concepts']:,}")
print(f"Vocabularies: {stats['data']['overview_statistics']['unique_vocabularies']}")
print(f"Concept classes: {stats['data']['overview_statistics']['unique_concept_classes']}")
# Vocabulary analysis
print(f"\n=== Vocabulary Distribution ===")
for vocab_id, vocab_data in sorted(
stats['data']['vocabulary_distribution'].items(),
key=lambda x: x[1]['total_concepts'],
reverse=True
):
print(f"{vocab_data['vocabulary_name']}:")
print(f" Total: {vocab_data['total_concepts']:,} ({vocab_data['percentage_of_domain']:.1f}%)")
print(f" Standard: {vocab_data['standard_concepts']:,}")
print(f" Avg relationships/concept: {vocab_data['average_relationships_per_concept']:.1f}")
# Concept class analysis
if 'concept_class_breakdown' in stats['data']:
print(f"\n=== Top Concept Classes ===")
top_classes = sorted(
stats['data']['concept_class_breakdown'].items(),
key=lambda x: x[1]['total_concepts'],
reverse=True
)[:10]
for class_id, class_data in top_classes:
print(f"{class_data['concept_class_name']}:")
print(f" Concepts: {class_data['total_concepts']:,} ({class_data['percentage_of_domain']:.1f}%)")
if 'hierarchical_depth' in class_data:
depth_info = class_data['hierarchical_depth']
print(f" Avg hierarchy depth: {depth_info['average_depth']:.1f}")
# Relationship analysis
if 'relationship_analysis' in stats['data']:
rel_stats = stats['data']['relationship_analysis']
print(f"\n=== Relationship Analysis ===")
print(f"Total relationships: {rel_stats['total_relationships']:,}")
print(f"Avg relationships/concept: {rel_stats['relationships_per_concept']['average']:.1f}")
print(f"Max relationships (single concept): {rel_stats['relationships_per_concept']['max']}")
# Top relationship types
rel_types = sorted(
rel_stats['relationship_type_distribution'].items(),
key=lambda x: x[1],
reverse=True
)[:5]
print(f"Top relationship types:")
for rel_type, count in rel_types:
percentage = (count / rel_stats['total_relationships']) * 100
print(f" {rel_type}: {count:,} ({percentage:.1f}%)")
# Growth trends
if 'growth_trends' in stats['data']:
growth = stats['data']['growth_trends']
print(f"\n=== Growth Trends ===")
print(f"Annual growth rate: {growth['annual_growth_rate']:.2f}%")
print(f"Recent additions:")
print(f" Last 30 days: {growth['recent_additions']['last_30_days']:,}")
print(f" Last 90 days: {growth['recent_additions']['last_90_days']:,}")
print(f" Last year: {growth['recent_additions']['last_year']:,}")
# Plot growth trend if matplotlib available
if growth['time_series']:
dates = [datetime.fromisoformat(point['date'].replace('Z', '+00:00')) for point in growth['time_series']]
totals = [point['total_concepts'] for point in growth['time_series']]
plt.figure(figsize=(12, 6))
plt.plot(dates, totals, marker='o')
plt.title(f'{stats["data"]["domain"]["domain_name"]} Domain Growth Over Time')
plt.xlabel('Date')
plt.ylabel('Total Concepts')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# Usage analytics
if 'usage_analytics' in stats['data']:
usage = stats['data']['usage_analytics']
print(f"\n=== Usage Analytics ===")
print(f"Daily query average: {usage['query_frequency']['daily_average']:.0f}")
if usage['most_accessed_concepts']:
print(f"Most accessed concepts:")
for i, concept in enumerate(usage['most_accessed_concepts'][:5], 1):
print(f" {i}. {concept['concept_name']} - {concept['access_count']:,} accesses")
if 'search_patterns' in usage:
patterns = usage['search_patterns']
print(f"Search success rate: {patterns['search_success_rate']*100:.1f}%")
print(f"Avg results per search: {patterns['average_results_per_search']:.1f}")
```
```json
{
"success": true,
"data": {
"domain": {
"domain_id": "Condition",
"domain_name": "Condition",
"description": "Medical conditions, diseases, disorders, and clinical findings",
"category": "clinical"
},
"overview_statistics": {
"total_concepts": 845672,
"standard_concepts": 423891,
"classification_concepts": 89234,
"non_standard_concepts": 332547,
"active_concepts": 812456,
"deprecated_concepts": 33216,
"unique_vocabularies": 12,
"unique_concept_classes": 45
},
"vocabulary_distribution": {
"SNOMED": {
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"total_concepts": 387421,
"standard_concepts": 298456,
"percentage_of_domain": 45.8,
"concept_classes": ["Clinical Finding", "Disorder", "Morphologic Abnormality"],
"average_relationships_per_concept": 4.7
},
"ICD10CM": {
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"total_concepts": 98234,
"standard_concepts": 67891,
"percentage_of_domain": 11.6,
"concept_classes": ["3-char billing code", "4-char billing code", "5-char billing code"],
"average_relationships_per_concept": 2.3
},
"ICD10": {
"vocabulary_name": "International Classification of Diseases, Tenth Revision",
"total_concepts": 87456,
"standard_concepts": 45678,
"percentage_of_domain": 10.3,
"concept_classes": ["ICD10 code"],
"average_relationships_per_concept": 1.8
}
},
"concept_class_breakdown": {
"Clinical Finding": {
"concept_class_name": "Clinical Finding",
"total_concepts": 425891,
"standard_concepts": 298456,
"primary_vocabularies": ["SNOMED"],
"percentage_of_domain": 50.4,
"hierarchical_depth": {
"average_depth": 5.2,
"max_depth": 12,
"concepts_at_root_level": 23
}
},
"Disorder": {
"concept_class_name": "Disorder",
"total_concepts": 234567,
"standard_concepts": 89234,
"primary_vocabularies": ["SNOMED"],
"percentage_of_domain": 27.7,
"hierarchical_depth": {
"average_depth": 4.8,
"max_depth": 10,
"concepts_at_root_level": 15
}
}
},
"relationship_analysis": {
"total_relationships": 3847562,
"relationships_per_concept": {
"average": 4.5,
"median": 3.0,
"max": 247,
"min": 0
},
"relationship_type_distribution": {
"Is a": 1845632,
"Maps to": 967234,
"Associated morphology": 445891,
"Finding site": 234567,
"Has associated finding": 123456
},
"cross_vocabulary_mappings": {
"total_mappings": 967234,
"vocabulary_pairs": {
"SNOMED->ICD10CM": 345678,
"SNOMED->ICD10": 234567,
"ICD10CM->ICD10": 123456
},
"unmapped_concepts": 78542
},
"hierarchy_connectivity": {
"connected_components": 5,
"largest_hierarchy_size": 387421,
"orphaned_concepts": 12456
}
},
"growth_trends": {
"annual_growth_rate": 2.8,
"vocabulary_growth_rates": {
"SNOMED": 3.2,
"ICD10CM": 1.1,
"ICD10": 0.3
},
"recent_additions": {
"last_30_days": 1247,
"last_90_days": 4892,
"last_year": 23654,
"top_contributing_vocabularies": ["SNOMED", "ICD10CM", "Read"]
},
"time_series": [
{
"date": "2020-01-01T00:00:00Z",
"total_concepts": 765432,
"concepts_added": 18234,
"concepts_deprecated": 2456
},
{
"date": "2021-01-01T00:00:00Z",
"total_concepts": 789456,
"concepts_added": 24024,
"concepts_deprecated": 2789
},
{
"date": "2022-01-01T00:00:00Z",
"total_concepts": 812456,
"concepts_added": 23000,
"concepts_deprecated": 3234
}
]
},
"usage_analytics": {
"query_frequency": {
"daily_average": 15847.3,
"monthly_trend": [14523, 15234, 16789, 15647, 16234],
"peak_usage_hours": [9, 10, 11, 14, 15]
},
"most_accessed_concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"access_count": 89234
},
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"vocabulary_id": "SNOMED",
"access_count": 76543
}
],
"search_patterns": {
"common_search_terms": ["diabetes", "hypertension", "pneumonia", "depression", "arthritis"],
"search_success_rate": 0.87,
"average_results_per_search": 23.4
},
"analysis_info": {
"analysis_date": "2024-12-22T10:00:00Z",
"data_freshness": "2024-12-22T08:30:00Z",
"calculation_time_ms": 2847.5,
"included_filters": {
"active_only": false,
"include_active": true,
"include_deprecated": true,
"time_period": "5y"
}
}
}
},
"meta": {
"request_id": "req_domain_stats_123",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Domain Statistics
Get overview statistics for a domain:
```javascript
const stats = await fetch('/v1/domains/Condition/statistics');
```
### Comprehensive Analysis
Get detailed analysis with all optional components:
```javascript
const detailedStats = await fetch('/v1/domains/Drug/statistics?include_growth_trends=true&include_usage_analytics=true&include_relationship_analysis=true');
```
### SNOMED-Only Analysis
Analyze domain statistics for SNOMED concepts only:
```javascript
const snomedStats = await fetch('/v1/domains/Condition/statistics?vocabulary_ids=SNOMED&standard_only=true');
```
### Growth Trend Analysis
Focus on historical growth patterns:
```javascript
const growthStats = await fetch('/v1/domains/Procedure/statistics?include_growth_trends=true&time_period=10y');
```
### Usage Pattern Analysis
Get usage analytics for optimization:
```javascript
const usageStats = await fetch('/v1/domains/Drug/statistics?include_usage_analytics=true');
```
## Related Endpoints
* [Get Domains](/api-reference/domains/get-domains) - Available domains list
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Concepts within domain
* [Get Concept Classes](/api-reference/domains/get-concept-classes) - Concept class information
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Vocabulary information
## Notes
* Statistics are calculated from the most recent vocabulary release
* Growth trends require historical data and may not be available for all domains
* Usage analytics are based on aggregated, anonymized query patterns
* Cross-vocabulary mapping statistics help identify integration opportunities
* Relationship analysis reveals connectivity patterns within domains
* Large domains may have longer calculation times for comprehensive statistics
* Standard concept statistics are preferred for clinical applications
* Time series data helps identify seasonal patterns and growth trajectories
# Get Domains
Source: https://docs.omophub.com/api-reference/domains/get-domains
GET /v1/domains
Retrieve all available domains that categorize medical concepts into high-level semantic groups
This endpoint provides information about all domains available in the OMOP vocabulary system. Domains represent high-level categorizations that group medical concepts by their semantic meaning, such as Condition, Drug, Procedure, and others.
## Query Parameters
Filter domains to those used by specific vocabularies
**Example:** `SNOMED,ICD10CM,RxNorm`
Include counts of concepts within each domain
**Note:** Required when using `sort_by=concept_count`
Include detailed statistics and usage information for each domain
**Note:** Required when using `sort_by=usage_frequency`
Include example concepts for each domain
Only include domains containing standard concepts
Only return domains with active concepts
Sort domains by specified criteria
**Options:**
* `domain_name` (or `name` as alias) - Sort by domain name alphabetically
* `concept_count` - Sort by number of concepts (requires `include_concept_counts=true`)
* `usage_frequency` - Sort by usage statistics (requires `include_statistics=true`)
Sort order for results
**Options:** `asc`, `desc`
## Response
Array of domain objects with their properties
Unique identifier for the domain
Human-readable name of the domain
OMOP concept ID representing this domain
Detailed description of the domain and its scope
High-level category classification
**Values:** `clinical`, `administrative`, `derived`, `metadata`
Number of concepts in this domain (when include\_concept\_counts=true)
Number of standard concepts in this domain (when include\_concept\_counts=true)
Vocabularies that primarily contribute concepts to this domain
Suggested hex color code for UI representation
Suggested icon identifier for UI representation
Detailed domain statistics (when include\_statistics=true)
Total concepts across all vocabularies
Count of concepts by vocabulary within this domain
Count of concepts by concept class within this domain
Historical growth information
New concepts added in the last year
Concepts deprecated in the last year
Average monthly growth rate
Relative usage frequency in queries (0-1)
Average relationships per concept in this domain
Example concepts from this domain (when include\_examples=true)
Example concept identifier
Example concept name
Example concept code
Vocabulary containing the example concept
Concept class of the example
Date when domain became valid
Date when domain becomes invalid
Summary information about all domains
Total number of domains returned
Sum of concepts across all domains (when include\_concept\_counts=true)
Vocabularies with concepts across the most domains
Count of domains by category
Response metadata and additional information
Timestamp of last domain data update
Version of the domain classification system
OMOP CDM version used for domain definitions
```bash cURL
curl -X GET "https://api.omophub.com/v1/domains?include_concept_counts=true&include_statistics=true&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/domains?include_concept_counts=true&include_statistics=true&include_examples=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const domainsData = await response.json();
console.log(`Found ${domainsData.data.domains.length} domains`);
// Display domains by category
const domainsByCategory = {};
domainsData.data.domains.forEach(domain => {
if (!domainsByCategory[domain.category]) {
domainsByCategory[domain.category] = [];
}
domainsByCategory[domain.category].push(domain);
});
Object.entries(domainsByCategory).forEach(([category, domains]) => {
console.log(`\n${category.toUpperCase()} Domains:`);
domains.forEach(domain => {
const conceptCount = domain.concept_count || 'N/A';
console.log(` - ${domain.domain_name}: ${conceptCount} concepts`);
});
});
```
```python Python
import requests
url = "https://api.omophub.com/v1/domains"
params = {
"include_concept_counts": True,
"include_statistics": True,
"include_examples": True,
"sort_by": "concept_count",
"sort_order": "desc"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
domains_data = response.json()
print(f"Found {len(domains_data['data']['domains'])} domains")
print(f"Total concepts: {domains_data['data']['domain_summary']['total_concepts_across_domains']:,}")
# Display top domains by concept count
print("\nTop Domains by Concept Count:")
for domain in domains_data['data']['domains'][:10]:
concept_count = domain.get('concept_count', 0)
standard_count = domain.get('standard_concept_count', 0)
print(f"{domain['domain_name']}: {concept_count:,} total, {standard_count:,} standard")
# Show examples
if domain.get('examples'):
print(f" Examples:")
for example in domain['examples'][:3]:
print(f" - {example['concept_name']} ({example['vocabulary_id']})")
# Show vocabulary distribution for clinical domains
clinical_domains = [d for d in domains_data['data']['domains'] if d['category'] == 'clinical']
print(f"\nClinical Domains ({len(clinical_domains)}):")
for domain in clinical_domains:
if domain.get('statistics') and 'vocabulary_distribution' in domain['statistics']:
vocab_dist = domain['statistics']['vocabulary_distribution']
top_vocabs = sorted(vocab_dist.items(), key=lambda x: x[1], reverse=True)[:3]
vocab_summary = ", ".join([f"{vocab}: {count}" for vocab, count in top_vocabs])
print(f" {domain['domain_name']}: {vocab_summary}")
```
```json
{
"success": true,
"data": {
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"domain_concept_id": 19,
"description": "Medical conditions, diseases, disorders, and clinical findings that represent states of health or illness",
"category": "clinical",
"concept_count": 845672,
"standard_concept_count": 423891,
"primary_vocabularies": ["SNOMED", "ICD10CM", "ICD10", "ICD9CM"],
"color_hex": "#e74c3c",
"icon_name": "medical-cross",
"statistics": {
"total_concepts": 845672,
"vocabulary_distribution": {
"SNOMED": 387421,
"ICD10CM": 98234,
"ICD10": 87456,
"ICD9CM": 76543,
"Read": 45231,
"Others": 150787
},
"concept_class_distribution": {
"Clinical Finding": 425891,
"Disorder": 234567,
"3-char billing code": 98234,
"4-char billing code": 87456
},
"growth_trend": {
"concepts_added_last_year": 12847,
"concepts_deprecated_last_year": 3421,
"average_monthly_growth": 0.12
},
"usage_frequency": 0.89,
"relationship_density": 4.2
},
"examples": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"concept_class_id": "Clinical Finding"
},
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"concept_class_id": "Clinical Finding"
},
{
"concept_id": 4329847,
"concept_name": "Myocardial infarction",
"concept_code": "22298006",
"vocabulary_id": "SNOMED",
"concept_class_id": "Clinical Finding"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"domain_concept_id": 13,
"description": "Medications, pharmaceutical products, drug ingredients, and therapeutic substances",
"category": "clinical",
"concept_count": 652341,
"standard_concept_count": 198765,
"primary_vocabularies": ["RxNorm", "RxNorm Extension", "NDC", "SNOMED"],
"color_hex": "#3498db",
"icon_name": "pill",
"statistics": {
"total_concepts": 652341,
"vocabulary_distribution": {
"RxNorm": 234567,
"RxNorm Extension": 198432,
"NDC": 145678,
"SNOMED": 45231,
"Others": 28433
},
"concept_class_distribution": {
"Clinical Drug": 98765,
"Branded Drug": 87654,
"Generic Drug": 76543,
"Ingredient": 45678
},
"growth_trend": {
"concepts_added_last_year": 23456,
"concepts_deprecated_last_year": 8765,
"average_monthly_growth": 0.28
},
"usage_frequency": 0.76,
"relationship_density": 6.8
},
"examples": [
{
"concept_id": 1503297,
"concept_name": "Metformin",
"concept_code": "6809",
"vocabulary_id": "RxNorm",
"concept_class_id": "Ingredient"
},
{
"concept_id": 1308216,
"concept_name": "Lisinopril 10 MG Oral Tablet",
"concept_code": "314077",
"vocabulary_id": "RxNorm",
"concept_class_id": "Clinical Drug"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"domain_concept_id": 10,
"description": "Medical procedures, surgical interventions, diagnostic tests, and therapeutic interventions",
"category": "clinical",
"concept_count": 456789,
"standard_concept_count": 234561,
"primary_vocabularies": ["SNOMED", "ICD10PCS", "HCPCS"],
"color_hex": "#2ecc71",
"icon_name": "stethoscope",
"statistics": {
"total_concepts": 456789,
"vocabulary_distribution": {
"SNOMED": 198765,
"ICD10PCS": 76543,
"HCPCS": 45678,
"Others": 48149
},
"usage_frequency": 0.64,
"relationship_density": 3.9
},
"examples": [
{
"concept_id": 4273391,
"concept_name": "Coronary artery bypass graft",
"concept_code": "232717009",
"vocabulary_id": "SNOMED",
"concept_class_id": "Procedure"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"domain_summary": {
"total_domains": 23,
"total_concepts_across_domains": 3456789,
"most_common_vocabularies": ["SNOMED", "RxNorm", "ICD10CM", "LOINC"],
"domain_categories": {
"clinical": 15,
"administrative": 4,
"derived": 3,
"metadata": 1
}
}
},
"meta": {
"request_id": "req_domains_789",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### All Domains
Get all available domains:
```javascript
const res = await fetch('/v1/domains', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
const domains = data.domains;
```
### Domains with Concept Counts
Get domains with concept statistics:
```javascript
const res = await fetch('/v1/domains?include_concept_counts=true&sort_by=concept_count&sort_order=desc', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
const domainsWithCounts = data?.domains || [];
```
### Clinical Domains Only
Filter to clinical domains:
```javascript
const clinicalDomains = domains.filter(d => d.category === 'clinical');
```
### Domains for Specific Vocabularies
```javascript
const detailedRes = await fetch('/v1/domains?include_concept_counts=true&include_statistics=true&include_examples=true', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const { domains: detailedDomains } = await detailedRes.json();
// Get domains for specific vocabularies
const res = await fetch('/v1/domains?vocabulary_ids=SNOMED&include_statistics=true', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
const snomedDomains = data.domains;
```
### Detailed Domain Analysis
Get comprehensive domain information:
```javascript
const response = await fetch('/v1/domains?include_concept_counts=true&include_statistics=true&include_examples=true', {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const { domains: detailedDomains } = await response.json();
```
## Related Endpoints
* [Get Domain Concepts](/api-reference/domains/get-domain-concepts) - Concepts within a specific domain
* [Get Concept Classes](/api-reference/domains/get-concept-classes) - Concept class classifications
* [Search Concepts](/api-reference/search/search-concepts) - Search within specific domains
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
## Notes
* Domains provide the highest level of semantic categorization in OMOP
* The most commonly used clinical domains are Condition, Drug, and Procedure
* Some concepts may appear in multiple domains through different vocabularies
* Domain concept counts include both standard and non-standard concepts unless filtered
* Usage frequency indicates how often domains are queried in typical applications
* Color codes and icons are suggestions for consistent UI representation across applications
* Administrative domains include things like Provider, Care Site, and Payer
* Derived domains contain computed or inferred concepts
# Errors
Source: https://docs.omophub.com/api-reference/errors
Troubleshoot problems with this comprehensive breakdown of all error codes.
# Error Handling
OMOPHub API uses conventional HTTP response codes and provides detailed error information.
## HTTP Status Codes
### Success Codes
* `200 OK` - Request successful
* `201 Created` - Resource created successfully
* `204 No Content` - Request successful, no content to return
### Client Error Codes (4xx)
* `400 Bad Request` - Invalid request parameters
* `401 Unauthorized` - Authentication required
* `403 Forbidden` - Insufficient permissions
* `404 Not Found` - Resource not found
* `413 Payload Too Large` - Request body exceeds size limits
* `422 Unprocessable Entity` - Valid request format but semantic errors
* `429 Too Many Requests` - Rate limit exceeded
### Server Error Codes (5xx)
* `500 Internal Server Error` - Unexpected server error
* `503 Service Unavailable` - Service temporarily unavailable
## Common Error Codes
### Authentication & Authorization
* `missing_api_key` (401) - No API key provided in Authorization header
* `invalid_api_key` (403) - API key is invalid, expired, or revoked
* `unauthorized` (401) - Authentication token is invalid
* `forbidden` (403) - Insufficient permissions for this resource
### Request Validation
* `validation_error` (422) - Well-formed request but semantically invalid parameters
* `missing_required_field` (400) - Required fields missing or malformed in the request
* `bad_request` (400) - General malformed request (bad JSON/format)
### Resource Management
* `not_found` (404) - Requested resource does not exist
* `concept_not_found` (404) - Specific concept ID not found
* `vocabulary_not_found` (404) - Vocabulary ID not found
### Rate Limits & Quotas
* `rate_limit_exceeded` (429) - Too many requests per second
* `daily_quota_exceeded` (429) - Daily request limit reached
### Healthcare & Security
* `healthcare_data_error` (422) - Medical data validation failure
* `invalid_mapping` (400) - Concept mapping validation error
### System & Service Errors
* `internal_server_error` (500) - Unexpected server error
* `service_unavailable` (503) - API temporarily unavailable
### Version & Compatibility
* `version_not_supported` (400) - Requested API version not supported
* `search_error` (400) - Search operation failed
# Get Concept Ancestors
Source: https://docs.omophub.com/api-reference/hierarchy/get-concept-ancestors
GET /v1/concepts/{concept_id}/ancestors
Retrieve all ancestor concepts for a given concept, providing hierarchical context and classification paths
This endpoint returns the complete ancestor hierarchy for a specific concept, including parent concepts, grandparents, and all higher-level classifications up to the root of the vocabulary hierarchy.
## Path Parameters
The unique identifier of the concept to retrieve ancestors for
**Example:** `201826` (Type 2 diabetes mellitus)
## Query Parameters
Filter ancestors to specific vocabulary (useful for cross-vocabulary concepts)
**Example:** `SNOMED`
Maximum number of hierarchy levels to traverse (default: unlimited)
**Range:** `1-10`
Relationship types to follow for hierarchy traversal. Spaces must be percent-encoded when used in query strings (e.g., "Is%20a,Part%20of").
**Default:** `Is a`
**Allowed values:** `Is a`, `Part of`, `Has part`, `Subsumes`, `Has ingredient`, `RxNorm has dose form`
**Example:** `Is%20a,Part%20of` (URL-encoded)
Include complete classification paths from concept to root
Include hierarchical distance (level) for each ancestor
Only return standard concepts in the hierarchy
Include deprecated/invalid concepts in ancestry
Number of ancestor concepts to return per page (max 1000)
Page number for pagination (1-based)
## Response
The concept ID for which ancestors were retrieved
Standard name of the source concept
Vocabulary containing the source concept
Array of ancestor concepts in hierarchical order
Unique identifier for the ancestor concept
Standard name of the ancestor concept
Original code from the vocabulary
Vocabulary containing this ancestor concept
Domain classification of the ancestor
Concept class identifier
Standard concept designation ('S', 'C', or null)
Type of relationship to child concept
Distance from original concept (when include\_distance=true)
Complete paths from concept to root (when include\_paths=true)
Unique identifier for this classification path
Ordered array of concept IDs from source to root
Ordered array of concept names corresponding to path\_concepts
Number of concepts in this path
Date when concept became valid (ISO format)
Date when concept became invalid (ISO format)
Reason for concept invalidation if applicable
Summary statistics about the ancestor hierarchy
Total number of ancestor concepts found
Maximum depth of the hierarchy tree
List of vocabularies represented in ancestors
List of relationship types found in hierarchy
Number of distinct classification paths
Response metadata and pagination information
Current page number
Items per page
Total ancestor concepts
Total number of pages
Whether next page exists
Whether previous page exists
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/ancestors?include_paths=true&include_distance=true&max_levels=5" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/201826/ancestors?include_paths=true&include_distance=true&max_levels=5', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const ancestorData = await response.json();
console.log(`Found ${ancestorData.data.hierarchy_summary.total_ancestors} ancestors`);
console.log('Classification paths:', ancestorData.data.ancestors[0].classification_paths);
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/ancestors"
params = {
"include_paths": True,
"include_distance": True,
"max_levels": 5
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
ancestor_data = response.json()
print(f"Concept: {ancestor_data['data']['concept_name']}")
print(f"Total ancestors: {ancestor_data['data']['hierarchy_summary']['total_ancestors']}")
for ancestor in ancestor_data['data']['ancestors'][:5]:
level = ancestor.get('hierarchy_level', 'Unknown')
print(f"Level {level}: {ancestor['concept_name']} ({ancestor['concept_id']})")
```
```json
{
"success": true,
"data": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"ancestors": [
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 1,
"classification_paths": [
{
"path_id": "path_1",
"path_concepts": [201826, 73211009, 362969004, 64572001, 404684003, 138875005],
"path_names": [
"Type 2 diabetes mellitus",
"Diabetes mellitus",
"Disorder of endocrine system",
"Disease",
"Clinical finding",
"SNOMED CT Concept"
],
"path_length": 6
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"concept_id": 362969004,
"concept_name": "Disorder of endocrine system",
"concept_code": "362969004",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 2,
"valid_start_date": "2002-01-31T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"concept_code": "64572001",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 3,
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"hierarchy_summary": {
"total_ancestors": 3,
"max_hierarchy_depth": 5,
"unique_vocabularies": ["SNOMED"],
"relationship_types_used": ["Is a"],
"classification_count": 1
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 3,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"request_id": "req_ancestors_201826_20241222_103100",
"timestamp": "2024-12-22T10:31:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Ancestor Retrieval
Get all ancestors for a specific concept:
```javascript
const ancestors = await fetch('/v1/concepts/201826/ancestors');
```
### Limited Hierarchy Depth
Retrieve ancestors up to a specific number of levels:
```javascript
const nearAncestors = await fetch('/v1/concepts/201826/ancestors?max_levels=3');
```
### Classification Path Analysis
Get complete classification paths from concept to root:
```javascript
const pathData = await fetch('/v1/concepts/201826/ancestors?include_paths=true&include_distance=true');
```
### Cross-Vocabulary Hierarchy
Analyze ancestors within specific vocabulary:
```javascript
const snomedAncestors = await fetch('/v1/concepts/201826/ancestors?vocabulary_id=SNOMED');
```
### Multiple Relationship Types
Follow different types of hierarchical relationships:
```javascript
const extendedHierarchy = await fetch('/v1/concepts/201826/ancestors?relationship_types=Is%20a,Part%20of');
```
## Related Endpoints
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Retrieve child concepts
* [Get Concept Hierarchy](/api-reference/hierarchy/get-concept-hierarchy) - Complete hierarchy view
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All concept relationships
* [Get Concept Details](/api-reference/concepts/get-concept-details) - Complete concept information
## Notes
* Hierarchy traversal follows "Is a" relationships by default, but can be customized
* Some concepts may have multiple classification paths to different root concepts
* Cross-vocabulary concepts may have ancestors in different vocabularies
* Standard concepts are prioritized in hierarchy traversal unless explicitly disabled
* Deprecated concepts are excluded from ancestry unless specifically requested
* Maximum hierarchy depth is typically 6-8 levels for most medical vocabularies
# Get Concept Descendants
Source: https://docs.omophub.com/api-reference/hierarchy/get-concept-descendants
GET /v1/concepts/{concept_id}/descendants
Retrieve all descendant concepts for a given concept, providing hierarchical children and specialized terms
This endpoint returns all descendant concepts (children, grandchildren, etc.) for a specific concept, allowing exploration of more specific terms and sub-classifications within the medical vocabulary hierarchy.
## Path Parameters
The unique identifier of the concept to retrieve descendants for
**Example:** `73211009` (Diabetes mellitus)
## Query Parameters
Filter descendants to specific vocabulary
**Example:** `SNOMED`
Maximum number of hierarchy levels to traverse downward (default: 10)
**Range:** 1-10. Values above 10 are capped at 10 by the server for performance reasons. Omitting this parameter defaults to the maximum of 10 levels.
Relationship types to follow for hierarchy traversal. Accepts CSV string (comma-separated values) or array. Spaces must be URL-encoded (%20) in URLs. Individual entries are trimmed and matched case-insensitively.
**Default:** `Is a`
**Allowed values:** `Is a`, `Has part`, `Subsumes`, `Part of`, `Has ingredient`, `RxNorm has dose form`
**CSV Example:** `Is%20a,Has%20part` (URL-encoded)
**Array Example:** `["Is a", "Has part"]`
Include hierarchical distance (level) for each descendant
Filter results by standard\_concept flag. When true, restricts results to concepts with standard\_concept='S' (excludes classification 'C' and null values). When false, returns concepts regardless of standard\_concept.
Include deprecated/invalid concepts in descendants. When false, excludes deprecated/invalid concepts; when true, includes them. This filter is applied after standard\_only filtering.
Comma-separated list of domain IDs to filter descendants
**Example:** `Condition,Drug`
Comma-separated list of concept class IDs to filter descendants
**Example:** `Clinical Finding,Procedure`
Include synonym information for descendant concepts
Number of descendant concepts to return per page. Values over 1000 are automatically capped at 1000. Invalid values return HTTP 400. Results are ordered breadth-first by level, then concept\_id ascending for deterministic pagination.
Page number for pagination (1-based). Uses deterministic ordering for stable pagination.
## Response
The concept ID for which descendants were retrieved
Standard name of the source concept
Vocabulary containing the source concept
Array of descendant concepts in hierarchical order
Unique identifier for the descendant concept
Standard name of the descendant concept
Original code from the vocabulary
Vocabulary containing this descendant concept
Domain classification of the descendant
Concept class identifier
Standard concept designation ('S', 'C', or null). When standard\_only=true, only 'S' values are returned. Combined with include\_deprecated filter which is applied after standard\_only filtering.
Type of relationship from parent concept
Distance from original concept (when include\_distance=true)
Direct parent concept in the hierarchy path
Alternative names for this concept (when include\_synonyms=true)
Alternative name for the concept
Language identifier for the synonym
Human-readable language name
Date when concept became valid (ISO format)
Date when concept became invalid (ISO format)
Reason for concept invalidation if applicable
Summary statistics about the descendant hierarchy
Total number of descendant concepts found
Maximum depth of the descendant tree
Number of direct child concepts (level 1)
List of vocabularies represented in descendants
Count of descendants by domain
Count of descendants by concept class
Response metadata and pagination information
Current page number
Items per page
Total descendant concepts
Total number of pages
Whether next page exists
Whether previous page exists
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/73211009/descendants?include_distance=true&max_levels=3&include_synonyms=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 73211009 # Diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/descendants"
params = {
"include_distance": True,
"max_levels": 3,
"include_synonyms": True,
"page_size": 50
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
descendant_data = response.json()
print(f"Concept: {descendant_data['concept_name']}")
print(f"Total descendants: {descendant_data['hierarchy_summary']['total_descendants']}")
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/descendants?include_distance=true&max_levels=3&include_synonyms=true', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const descendantData = await response.json();
console.log(`Found ${descendantData.hierarchy_summary.total_descendants} descendants`);
console.log(`Direct children: ${descendantData.hierarchy_summary.direct_children}`);
```
```bash cURL (filtered by domain)
curl -X GET "https://api.omophub.com/v1/concepts/73211009/descendants?domain_ids=Condition&max_levels=2&page_size=25" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with hierarchy grouping)
import requests
from collections import defaultdict
concept_id = 73211009
response = requests.get(
f"https://api.omophub.com/v1/concepts/{concept_id}/descendants",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"include_distance": True, "max_levels": 3}
)
data = response.json()
by_level = defaultdict(list)
for desc in data['descendants']:
level = desc.get('hierarchy_level', 0)
by_level[level].append(desc['concept_name'])
for level in sorted(by_level.keys()):
print(f"Level {level}: {len(by_level[level])} concepts")
```
```json
{
"success": true,
"data": {
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"descendants": [
{
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 1,
"parent_concept_id": 73211009,
"synonyms": [
{
"concept_synonym_name": "Type II diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Adult-onset diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 46635009,
"concept_name": "Type 1 diabetes mellitus",
"concept_code": "46635009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 1,
"parent_concept_id": 73211009,
"synonyms": [
{
"concept_synonym_name": "Type I diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Juvenile diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 199223000,
"concept_name": "Diabetes mellitus due to genetic defect",
"concept_code": "199223000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 1,
"parent_concept_id": 73211009,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 237599002,
"concept_name": "Insulin dependent diabetes mellitus",
"concept_code": "237599002",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relationship_type": "Is a",
"hierarchy_level": 2,
"parent_concept_id": 46635009,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
}
],
"hierarchy_summary": {
"total_descendants": 847,
"max_hierarchy_depth": 6,
"direct_children": 23,
"unique_vocabularies": ["SNOMED"],
"domain_distribution": {
"Condition": 801,
"Procedure": 32,
"Observation": 14
},
"concept_class_distribution": {
"Clinical Finding": 723,
"Procedure": 89,
"Observable Entity": 35
}
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 847,
"total_pages": 9,
"has_next": true,
"has_previous": false
},
"request_id": "req_descendants_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
**Note:** Examples assume a preconfigured client with base URL `https://api.omophub.com` and authentication headers. For direct usage:
```javascript
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/descendants', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
### Basic Descendant Retrieval
Get all descendants for a specific concept:
```javascript
const descendants = await fetch('/v1/concepts/73211009/descendants');
```
### Limited Hierarchy Depth
Retrieve descendants up to a specific number of levels:
```javascript
const nearDescendants = await fetch('/v1/concepts/73211009/descendants?max_levels=2');
```
### Domain-Specific Descendants
Filter descendants to specific medical domains:
```javascript
const conditionDescendants = await fetch('/v1/concepts/73211009/descendants?domain_ids=Condition');
```
### Detailed Descendant Information
Get descendants with synonyms and hierarchy levels:
```javascript
const detailedData = await fetch('/v1/concepts/73211009/descendants?include_synonyms=true&include_distance=true');
```
### Concept Class Filtering
Find descendants of specific concept classes:
```javascript
const procedures = await fetch('/v1/concepts/73211009/descendants?concept_class_ids=Procedure');
```
## Related Endpoints
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Retrieve parent concepts
* [Get Concept Hierarchy](/api-reference/hierarchy/get-concept-hierarchy) - Complete hierarchy view
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All concept relationships
* [Search Concepts](/api-reference/search/search-concepts) - Search within specific hierarchies
## Notes
* Descendant traversal follows "Is a" relationships by default, but can include other relationship types
* Large hierarchies may contain thousands of descendants - use pagination and filtering appropriately
* Standard concepts are prioritized unless explicitly disabled
* Some medical concepts may have very deep hierarchies (6+ levels)
* Cross-vocabulary concepts may have descendants from multiple vocabularies
* Deprecated concepts are excluded from descendants unless specifically requested
* Synonym information adds significant detail but increases response size
# Get Concept Hierarchy
Source: https://docs.omophub.com/api-reference/hierarchy/get-concept-hierarchy
GET /v1/concepts/{concept_id}/hierarchy
Retrieve complete hierarchical context for a concept, including both ancestors and descendants in a unified view
This endpoint provides a comprehensive hierarchical view of a concept, showing its position within the medical vocabulary structure by including both ancestor (parent) and descendant (child) relationships in a single response.
## Path Parameters
The unique identifier of the concept to retrieve hierarchy for
**Example:** `73211009` (Diabetes mellitus)
## Query Parameters
Filter hierarchy to specific vocabulary
**Example:** `SNOMED`
Maximum number of ancestor hierarchy levels to traverse (default: 5)
**Range:** `1-10`
Maximum number of descendant hierarchy levels to traverse (default: 3)
**Range:** `1-10`
Convenient shorthand to set both `max_ancestor_levels` and `max_descendant_levels` to the same value (default: 10)
**Range:** `1-20`
**Note:** If `max_levels` is provided along with `max_ancestor_levels` or `max_descendant_levels`, the specific parameters take precedence
Maximum number of results to return per query for performance optimization (applies to ancestors and descendants separately)
**Default:** `500`
**Range:** `1-5000`
**Recommended:** Use 100-500 for interactive queries, up to 1000 for bulk analysis
This parameter prevents query timeouts and ensures predictable response times. The default limit handles 95%+ of use cases while maintaining sub-second performance.
Comma-separated list of relationship types to follow for hierarchy traversal
**Default:** `Is a`
**Example:** `Is a,Part of,Has part`
Include hierarchical distance (level) for each concept
Include complete classification\_paths for both ancestors and descendants when true
Only return standard concepts in the hierarchy
Include deprecated/invalid concepts in hierarchy
Include synonym information for all concepts
Include detailed hierarchy statistics and analysis
Response format for hierarchy data
**Options:** `flat`, `tree`, `graph`
## Response
The central concept for which hierarchy was retrieved
Unique identifier for the concept
Standard name of the concept
Original code from the vocabulary
Vocabulary containing this concept
Domain classification of the concept
Concept class identifier
Standard concept designation ('S', 'C', or null)
Array of synonym names for this concept (only present when include\_synonyms=true)
Array of ancestor concepts in hierarchical order
Unique identifier for the ancestor concept
Standard name of the ancestor concept
Distance from central concept (negative values for ancestors)
Type of relationship to child concept
Vocabulary containing this ancestor
Domain classification
Concept class identifier
Complete classification paths from root to this concept (only present when include\_paths=true). Each path is an array of concept objects ordered from root to this ancestor.
Array of synonym names for this ancestor concept (only present when include\_synonyms=true)
Array of descendant concepts in hierarchical order
Unique identifier for the descendant concept
Standard name of the descendant concept
Distance from central concept (positive values for descendants)
Type of relationship from parent concept
Direct parent concept in the hierarchy
Vocabulary containing this descendant
Domain classification
Concept class identifier
Complete classification paths from root to this concept (only present when include\_paths=true). Each path is an array of concept objects ordered from root to this descendant.
Array of synonym names for this descendant concept (only present when include\_synonyms=true)
Hierarchical tree representation (when format='tree')
Top-level concepts in the hierarchy
Nested structure showing parent-child relationships
Concepts with no descendants in the result set
Graph representation with nodes and edges (when format='graph')
Array of concept nodes with properties
Array of relationship edges between concepts
Suggested layout positions for visualization
Detailed hierarchy analysis (when include\_statistics=true)
Total number of ancestor concepts
Total number of descendant concepts
Maximum ancestor hierarchy depth
Maximum descendant hierarchy depth
Average number of children per concept
Number of concepts at the widest level
Distribution of concepts by domain
Distribution of concepts by vocabulary
Number of distinct paths to root concepts
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/73211009/hierarchy?max_ancestor_levels=3&max_descendant_levels=2&include_statistics=true&format=tree" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/73211009/hierarchy?max_ancestor_levels=3&max_descendant_levels=2&include_statistics=true&format=tree', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const hierarchyData = await response.json();
console.log(`Central concept: ${hierarchyData.data.concept.concept_name}`);
console.log(`Ancestors: ${hierarchyData.data.hierarchy_statistics.total_ancestors}`);
console.log(`Descendants: ${hierarchyData.data.hierarchy_statistics.total_descendants}`);
// Visualize hierarchy structure
if (hierarchyData.data.tree_structure) {
console.log('Hierarchy tree:', hierarchyData.data.tree_structure);
}
```
```python Python
import requests
import json
concept_id = 73211009 # Diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/hierarchy"
params = {
"max_ancestor_levels": 3,
"max_descendant_levels": 2,
"include_statistics": True,
"format": "tree",
"include_distance": True
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
hierarchy_data = response.json()
print(f"Central Concept: {hierarchy_data['data']['concept']['concept_name']}")
print(f"Concept ID: {hierarchy_data['data']['concept']['concept_id']}")
if 'hierarchy_statistics' in hierarchy_data['data']:
stats = hierarchy_data['data']['hierarchy_statistics']
print(f"\nHierarchy Statistics:")
print(f"- Total ancestors: {stats['total_ancestors']}")
print(f"- Total descendants: {stats['total_descendants']}")
print(f"- Max ancestor depth: {stats['max_ancestor_depth']}")
print(f"- Max descendant depth: {stats['max_descendant_depth']}")
print(f"- Branching factor: {stats['branching_factor']:.2f}")
# Display ancestors
print(f"\nAncestors ({len(hierarchy_data['data']['ancestors'])}):")
for ancestor in hierarchy_data['data']['ancestors'][:5]:
level = ancestor['hierarchy_level']
print(f" Level {level}: {ancestor['concept_name']} ({ancestor['concept_id']})")
# Display descendants
print(f"\nDescendants ({len(hierarchy_data['data']['descendants'])}):")
for descendant in hierarchy_data['data']['descendants'][:5]:
level = descendant['hierarchy_level']
print(f" Level {level}: {descendant['concept_name']} ({descendant['concept_id']})")
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"ancestors": [
{
"concept_id": 362969004,
"concept_name": "Disorder of endocrine system",
"hierarchy_level": -1,
"relationship_type": "Is a",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"hierarchy_level": -2,
"relationship_type": "Is a",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
{
"concept_id": 404684003,
"concept_name": "Clinical finding",
"hierarchy_level": -3,
"relationship_type": "Is a",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
}
],
"descendants": [
{
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"hierarchy_level": 1,
"relationship_type": "Is a",
"parent_concept_id": 73211009,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
{
"concept_id": 46635009,
"concept_name": "Type 1 diabetes mellitus",
"hierarchy_level": 1,
"relationship_type": "Is a",
"parent_concept_id": 73211009,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
{
"concept_id": 190411009,
"concept_name": "Type 2 diabetes mellitus with renal complications",
"hierarchy_level": 2,
"relationship_type": "Is a",
"parent_concept_id": 44054006,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
}
],
"tree_structure": {
"root_concepts": [404684003],
"branches": {
"404684003": {
"children": [64572001]
},
"64572001": {
"children": [362969004]
},
"362969004": {
"children": [73211009]
},
"73211009": {
"children": [44054006, 46635009]
},
"44054006": {
"children": [190411009]
},
"46635009": {
"children": []
},
"190411009": {
"children": []
}
},
"leaf_concepts": [190411009, 46635009]
},
"hierarchy_statistics": {
"total_ancestors": 3,
"total_descendants": 3,
"max_ancestor_depth": 3,
"max_descendant_depth": 2,
"branching_factor": 2.0,
"hierarchy_breadth": 2,
"domain_distribution": {
"Condition": 7
},
"vocabulary_distribution": {
"SNOMED": 7
},
"path_count": 1
}
},
"meta": {
"request_id": "req_hierarchy_73211009_20241222_103000",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Hierarchy View
Get complete hierarchy context for a concept:
```javascript
const hierarchy = await fetch('/v1/concepts/73211009/hierarchy');
```
### Limited Depth Hierarchy
Control the depth of ancestor and descendant traversal:
```javascript
const limitedHierarchy = await fetch('/v1/concepts/73211009/hierarchy?max_ancestor_levels=2&max_descendant_levels=1&max_results=500');
```
### Performance-Optimized Queries
Use `max_results` and `max_levels` to limit result size for better performance:
```javascript
// Quick response for interactive UI (100-500ms) - using max_levels shorthand
const quickHierarchy = await fetch('/v1/concepts/73211009/hierarchy?max_levels=2&max_results=100');
// Balanced performance (500ms-2s) - using max_levels shorthand
const balancedHierarchy = await fetch('/v1/concepts/73211009/hierarchy?max_levels=3&max_results=500');
// Comprehensive analysis (2s-10s) - using max_levels shorthand
const fullHierarchy = await fetch('/v1/concepts/73211009/hierarchy?max_levels=5&max_results=1000');
```
### Tree Format for Visualization
Get hierarchy in tree structure for UI components:
```javascript
const treeData = await fetch('/v1/concepts/73211009/hierarchy?format=tree&include_statistics=true');
```
### Graph Format for Network Visualization
Get hierarchy as graph data for network diagrams:
```javascript
const graphData = await fetch('/v1/concepts/73211009/hierarchy?format=graph&max_ancestor_levels=4');
```
### Detailed Analysis
Get comprehensive hierarchy with statistics and synonyms:
```javascript
const detailedHierarchy = await fetch('/v1/concepts/73211009/hierarchy?include_statistics=true&include_synonyms=true&include_paths=true');
```
## Related Endpoints
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Detailed ancestor information
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Detailed descendant information
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All concept relationships
* [Search Concepts](/api-reference/search/search-concepts) - Search within hierarchies
## Notes
* The hierarchy endpoint is optimized for visualization and provides balanced ancestor/descendant views
* Tree format is ideal for hierarchical UI components and taxonomy browsers
* Graph format includes node positioning hints for network visualizations
* Statistics provide insights into hierarchy complexity and structure
* **Performance:** Use the `max_results` parameter to control query size. The default (500) provides optimal performance for most use cases while preventing timeouts
* Large hierarchies are automatically limited by `max_results` to prevent performance issues and ensure predictable response times
* Cross-vocabulary concepts may show relationships spanning multiple vocabularies
* Level numbering uses negative values for ancestors and positive for descendants
* For very large hierarchies, consider paginating through results or using more specific filters to reduce result set size
# Get Hierarchy Paths
Source: https://docs.omophub.com/api-reference/hierarchy/get-hierarchy-paths
GET /v1/concepts/{concept_id}/hierarchy/paths
Retrieve all possible hierarchical paths from a concept to root concepts, showing complete classification lineages
This endpoint provides detailed analysis of all possible classification paths from a specific concept to its root concepts, essential for understanding complex medical taxonomy relationships and multiple inheritance patterns.
## Path Parameters
The unique identifier of the concept to retrieve hierarchy paths for
**Example:** `201826` (Type 2 diabetes mellitus)
## Query Parameters
Filter paths to specific vocabulary
**Example:** `SNOMED`
Comma-separated list of relationship types to follow for path traversal
**Default:** `Is a`
**Example:** `Is a,Part of`
Include all intermediate concepts in each path
Include detailed information for each concept in paths
Maximum number of concepts allowed in any single path
**Range:** `2-15` (inclusive). Out-of-range values are rejected with 400 Bad Request and descriptive error message.
Only include standard concepts in paths
Include deprecated/invalid concepts in paths
Type of paths to retrieve
**Options:** `all`, `shortest`, `longest`, `distinct_roots`
Include path analysis and statistics
Response format for path data. Formats are mutually exclusive.
**Options:**
* `detailed`/`compact`: Returns `hierarchy_paths` array with path objects and metadata
* `graph`: Returns `nodes` and `edges` arrays (does NOT include `hierarchy_paths`)
**Detailed/Compact format returns:**
* `hierarchy_paths`: Array of path objects with full details
* `meta`: Metadata about the query and results
**Graph format returns:**
* `nodes`: Array with `id`, `label`, optional `metadata`
* `edges`: Array with `source`, `target`, optional `label`/`metadata`
* `meta`: Metadata about the query and results
## Response
The source concept for which paths were retrieved
Unique identifier for the concept
Standard name of the concept
Vocabulary containing this concept
Domain classification
Array of complete hierarchical paths to root concepts
Unique identifier for this path
Number of concepts in this path
The internal concept\_id of the root concept at the end of this path
Name of the root concept
Ordered array of internal concept\_id integers from source to root
Ordered array of relationship types between concepts
Detailed information for each concept in path (when include\_concept\_details=true)
Internal concept\_id identifier
Standard concept name
Source concept\_code from original vocabulary system
Vocabulary containing this concept
Domain classification
Concept class identifier
Position of this concept in the path (0-based)
Relationship type to next concept in path
Quality score for this classification path (0-1)
Whether this is considered the primary classification path
Complexity assessment of the path
**Values:** `simple`, `moderate`, `complex`
Statistical analysis of all paths (when include\_statistics=true)
Total number of distinct paths found
Number of distinct root concepts reached
Average number of concepts per path
Length of the shortest path to any root
Length of the longest path to any root
Concepts that appear in multiple paths
Distribution of concepts by vocabulary across all paths
Distribution of concepts by domain across all paths
List of relationship types found in paths
Measure of consistency across different paths (0-1)
Summary of all root concepts reached through paths
Root concept identifier
Root concept name
Vocabulary of the root concept
Number of distinct paths leading to this root
Length of shortest path to this root
Array of graph nodes (when format=graph)
Internal concept\_id for the node
Concept name for display
Additional concept properties (vocabulary\_id, domain\_id, etc.)
Array of graph edges connecting nodes (when format=graph)
Source concept\_id (child concept)
Target concept\_id (parent concept)
Relationship type (e.g., "Is a", "Subsumes")
Additional edge properties (relationship details, path info)
## Errors
**Bad Request** - Invalid parameter values
InvalidParameter
max\_path\_length must be between 2 and 15 (inclusive)
**Not Found** - Concept or hierarchy not found
NotFound
Concept with ID 12345 not found
**Unprocessable Entity** - Semantic validation fails
UnprocessableEntity
No valid hierarchy paths found for the specified criteria
**Internal Server Error** - Server processing error
InternalServerError
An unexpected error occurred while processing hierarchy paths
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/hierarchy/paths?include_concept_details=true&include_statistics=true&path_type=all" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/201826/hierarchy/paths?include_concept_details=true&include_statistics=true&path_type=all', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const pathsData = await response.json();
console.log(`Found ${pathsData.data.path_analysis.total_paths} classification paths`);
console.log(`Reaching ${pathsData.data.path_analysis.unique_root_concepts} root concepts`);
// Display each path
pathsData.data.hierarchy_paths.forEach((path, index) => {
console.log(`\nPath ${index + 1} (Length: ${path.path_length}):`);
if (path.path_details) {
path.path_details.forEach((concept, pos) => {
const relation = concept.relationship_to_next || 'ROOT';
console.log(` ${pos}: ${concept.concept_name} (${relation})`);
});
}
});
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/hierarchy/paths"
params = {
"include_concept_details": True,
"include_statistics": True,
"path_type": "all"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
paths_data = response.json()
print(f"Concept: {paths_data['data']['concept']['concept_name']}")
print(f"Total classification paths: {paths_data['data']['path_analysis']['total_paths']}")
print(f"Unique root concepts: {paths_data['data']['path_analysis']['unique_root_concepts']}")
print(f"Average path length: {paths_data['data']['path_analysis']['average_path_length']:.1f}")
print("\nClassification Paths:")
for i, path in enumerate(paths_data['data']['hierarchy_paths'], 1):
print(f"\nPath {i} -> {path['root_concept_name']} (Length: {path['path_length']}):")
if 'path_details' in path:
for j, concept in enumerate(path['path_details']):
indent = " " * j
relation = concept.get('relationship_to_next', 'ROOT')
print(f"{indent}- {concept['concept_name']} ({relation})")
print(f" Primary: {path['is_primary_path']}, Score: {path['path_score']:.2f}")
# Show common ancestors
if 'common_ancestors' in paths_data['data']['path_analysis']:
print(f"\nCommon ancestors across paths:")
for ancestor in paths_data['data']['path_analysis']['common_ancestors']:
print(f" - {ancestor}")
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition"
},
"hierarchy_paths": [
{
"path_id": "path_1",
"path_length": 6,
"root_concept_id": 138875005,
"root_concept_name": "SNOMED CT Concept",
"path_concepts": [201826, 73211009, 362969004, 64572001, 404684003, 138875005],
"path_relationships": ["Is a", "Is a", "Is a", "Is a", "Is a"],
"path_details": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"position_in_path": 0,
"relationship_to_next": "Is a"
},
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"position_in_path": 1,
"relationship_to_next": "Is a"
},
{
"concept_id": 362969004,
"concept_name": "Disorder of endocrine system",
"concept_code": "362969004",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"position_in_path": 2,
"relationship_to_next": "Is a"
},
{
"concept_id": 64572001,
"concept_name": "Disease",
"concept_code": "64572001",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"position_in_path": 3,
"relationship_to_next": "Is a"
},
{
"concept_id": 404684003,
"concept_name": "Clinical finding",
"concept_code": "404684003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"position_in_path": 4,
"relationship_to_next": "Is a"
},
{
"concept_id": 138875005,
"concept_name": "SNOMED CT Concept",
"concept_code": "138875005",
"vocabulary_id": "SNOMED",
"domain_id": "Metadata",
"concept_class_id": "Model Component",
"position_in_path": 5,
"relationship_to_next": null
}
],
"path_score": 0.95,
"is_primary_path": true,
"path_complexity": "moderate"
}
],
"path_analysis": {
"total_paths": 1,
"unique_root_concepts": 1,
"average_path_length": 6.0,
"shortest_path_length": 6,
"longest_path_length": 6,
"common_ancestors": [
"Diabetes mellitus",
"Disorder of endocrine system",
"Disease",
"Clinical finding"
],
"vocabulary_distribution": {
"SNOMED": 6
},
"domain_distribution": {
"Condition": 6
},
"relationship_types_used": ["Is a"],
"classification_consistency": 1.0
},
"root_concepts": [
{
"concept_id": 138875005,
"concept_name": "SNOMED CT Concept",
"vocabulary_id": "SNOMED",
"paths_to_root": 1,
"shortest_path_to_root": 6
}
]
},
"meta": {
"request_id": "req_hierarchy_paths_201826_20241222_103200",
"timestamp": "2024-12-22T10:32:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### All Classification Paths
Get all possible hierarchical paths for a concept:
```javascript
const allPaths = await fetch('/v1/concepts/201826/hierarchy/paths?path_type=all');
```
### Shortest Paths Only
Retrieve only the shortest paths to root concepts:
```javascript
const shortestPaths = await fetch('/v1/concepts/201826/hierarchy/paths?path_type=shortest&include_statistics=true');
```
### Detailed Path Analysis
Get comprehensive path information with concept details:
```javascript
const detailedPaths = await fetch('/v1/concepts/201826/hierarchy/paths?include_concept_details=true&include_statistics=true');
```
### Cross-Vocabulary Paths
Analyze paths across multiple vocabularies:
```javascript
// Build URL with proper encoding
const params = new URLSearchParams({
relationship_types: 'Is a,Maps to'
});
const crossVocabPaths = await fetch(`/v1/concepts/201826/hierarchy/paths?${params.toString()}`);
```
### Graph Format for Visualization
Get path data in graph format for network visualization:
```javascript
const graphPaths = await fetch('/v1/concepts/201826/hierarchy/paths?format=graph');
```
## Related Endpoints
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Direct ancestor concepts
* [Get Concept Hierarchy](/api-reference/hierarchy/get-concept-hierarchy) - Complete hierarchy view
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - All relationship types
* [Search Concepts](/api-reference/search/search-concepts) - Search within hierarchies
## Notes
* Path analysis is essential for understanding complex medical taxonomies with multiple inheritance
* Primary paths represent the most commonly used or clinically relevant classification route
* Path scores consider factors like clinical relevance, path length, and concept standardization
* Some concepts may have dozens of classification paths in complex vocabularies like SNOMED CT
* Common ancestors help identify key classification concepts shared across multiple paths
* Cross-vocabulary paths may include mapping relationships in addition to hierarchical ones
* Path complexity assessment helps identify concepts with unusual or complex classification patterns
# Get Concept Mappings
Source: https://docs.omophub.com/api-reference/hierarchy/get-mappings
GET /concepts/{conceptId}/mappings
Get mappings to concepts in other vocabularies
## Overview
Retrieve all mappings from a concept to equivalent or related concepts in other vocabulary systems.
## Path Parameters
The concept ID to get mappings for
## Query Parameters
Filter mappings to specific vocabulary
Filter by mapping type: "Maps to", "Maps to value"
## Examples
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/320128/mappings?targetVocabulary=ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/320128/mappings?targetVocabulary=ICD10CM', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const mappingData = await response.json();
console.log(`Found ${mappingData.data.mappings.length} mappings`);
```
```python Python
import requests
params = {"targetVocabulary": "ICD10CM"}
response = requests.get(
"https://api.omophub.com/v1/concepts/320128/mappings",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
mapping_data = response.json()
print(f"Found {len(mapping_data['data']['mappings'])} mappings")
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"vocabulary_id": "SNOMED",
"domain_id": "Condition"
},
"mappings": [
{
"target_concept_id": 319826,
"target_concept_name": "Essential hypertension",
"target_concept_code": "I10",
"target_vocabulary_id": "ICD10CM",
"target_domain_id": "Condition",
"mapping_type": "Maps to",
"relationship_id": "Maps to",
"mapping_confidence": 1.0,
"is_primary_mapping": true,
"valid_start_date": "2015-10-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"mapping_summary": {
"total_mappings": 1,
"target_vocabularies": ["ICD10CM"],
"mapping_types": ["Maps to"],
"primary_mappings": 1
}
},
"meta": {
"request_id": "req_mappings_320128_20241222_103300",
"timestamp": "2024-12-22T10:33:00Z",
"vocab_release": "2025.2"
}
}
```
# Introduction
Source: https://docs.omophub.com/api-reference/introduction
Understand general concepts, response codes, and authentication strategies.
## Base URL
The OMOPHub API is built on REST principles. We enforce HTTPS in every request to improve data security, integrity, and privacy. The API does not support HTTP.
All requests contain the following base URL:
```bash
https://api.omophub.com/v1
```
## Authentication
To authenticate you need to add an *Authorization* header with the contents of the header being `Bearer oh_xxxxxxxxx` where `oh_xxxxxxxxx` is your API Key.
```bash
Authorization: Bearer oh_xxxxxxxxx
```
Get your API key from the [OMOPHub Dashboard](https://dashboard.omophub.com/api-keys)
## Core Capabilities
**Search & Discovery**
* Search across 90+ medical vocabularies (SNOMED CT, ICD-10-CM, RxNorm, LOINC and more)
* Fuzzy, semantic, and phonetic search algorithms
* Autocomplete and suggestions endpoints
**Concept Navigation**
* Navigate concept hierarchies and relationships
* Get ancestors, descendants, and related concepts
* Explore "Is a", "Maps to", and custom relationships
**Vocabulary Mapping**
* Map concepts between different vocabulary systems
* Cross-reference codes across standards
* Validate and check mapping coverage
**OHDSI Compliance**
* Full OMOP CDM vocabulary support (license-free)
* Standardized concept classifications
* Version-controlled vocabulary releases
## API Structure
**RESTful Design**
* Resource-based endpoints (`/concepts`, `/vocabularies`, `/search`)
* Standard HTTP methods (GET, POST)
* Consistent response formats
**Field Naming**
* `snake_case` for all API fields (`concept_id`, `vocabulary_id`)
* Consistent parameter naming across endpoints
**Pagination**
* Page-based pagination (`page`, `page_size`)
* Metadata includes total counts and navigation flags
## API Categories
Look up medical concepts and their detailed information
Advanced search across vocabularies and concepts
Navigate concept hierarchies and relationships
Explore concept relationships and connections
Work with concept domains and classifications
Cross-vocabulary mappings and interoperability
Manage and explore medical vocabularies and their metadata
## Quick Start Examples
### Search for Medical Concepts
```bash
curl -X GET "https://api.omophub.com/v1/search/concepts?query=diabetes&vocabulary_ids=SNOMED&page_size=5" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Get Concept Details
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Map Between Vocabularies
```bash
curl -X GET "https://api.omophub.com/v1/concepts/201826/mappings?target_vocabularies=ICD10CM" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Response Format
All API responses follow a consistent structure:
```json
{
"success": true,
"data": {
// Your response data here
},
"meta": {
"request_id": "req_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2024.2"
}
}
```
## Response codes
Resend uses standard HTTP codes to indicate the success or failure of your requests.
In general, `2xx` HTTP codes correspond to success, `4xx` codes are for user-related failures, and `5xx` codes are for infrastructure issues.
| Status | Description |
| ------ | --------------------------------------- |
| `200` | Successful request. |
| `400` | Check that the parameters were correct. |
| `401` | The API key used was missing. |
| `403` | The API key used was invalid. |
| `404` | The resource was not found. |
| `429` | The rate limit was exceeded. |
| `5xx` | Indicates an error with Resend servers. |
Check [Error Codes](/api-reference/errors) for a comprehensive breakdown of
all possible API errors.
## Rate limit
The default maximum rate limit is **2 requests per second**. This number can be increased by request. After that, you'll hit the rate limit and receive a `429` response error code.
Learn more about our [rate limits](/api-reference/rate-limit).
## Next Steps
Configure your API key and start making requests
Try the API directly in your browser
Learn about vocabulary releases and versioning
Understand error codes and troubleshooting
# Batch Map Concepts
Source: https://docs.omophub.com/api-reference/mappings/batch-map-concepts
POST https://api.omophub.com/v1/concepts/map/batch
Map multiple concepts to target vocabularies in a single request for efficient bulk processing
## Overview
This endpoint allows you to map multiple concepts from source vocabularies to target vocabularies in a single API call. It's optimized for bulk operations and provides significant performance benefits when working with large sets of concept mappings.
## Request Body
Array of concept mapping requests
The OMOP concept ID to map from
Array of target vocabulary IDs to map to (e.g., \["ICD10CM", "ICD9CM"])
Array of mapping relationship types to use (default: \["Maps to", "Maps to value"])
Include approximate mappings when exact mappings are not available
When true, only mappings to standard concepts are returned (non-standard codes like ICD9/ICD10 will be excluded)
Maximum number of mappings to return per source concept
Optional context identifier for tracking related mappings
## Query Parameters
Specific vocabulary release version (e.g., "2024.1")
Include full source concept details in responses
Include confidence scores for mappings
## Response
Indicates if the batch mapping request was successful
Response data containing batch mapping results
Array of mapping results matching the input order
The source concept ID that was mapped
Whether this individual mapping succeeded
Error message if the mapping failed
Context identifier if provided in request
Source concept information (when include\_source\_details=true)
Source concept name
Source vocabulary
Source domain
Source concept class
Standard concept flag
Source concept code
Array of target vocabulary mappings
Target vocabulary ID
Array of concepts in the target vocabulary
Mapped concept ID
Mapped concept name
Mapped concept code
Mapped concept domain
Mapped concept class
Standard concept flag
Type of mapping relationship
Confidence score (0.0-1.0) when include\_mapping\_confidence=true
Whether this is an approximate mapping
When mapping became valid
When mapping expires
Summary for this target vocabulary
Number of mappings found
Number of exact mappings
Number of approximate mappings
Overall mapping quality: "excellent", "good", "fair", "poor"
Coverage completeness score (0.0-1.0)
Overall mapping summary for this concept
Number of target vocabularies processed
Number of vocabularies with successful mappings
Total concepts mapped across all vocabularies
Best mapping quality achieved
Whether any standard concept mappings were found
Summary statistics for the entire batch
Total number of concepts to map
Number of concepts with successful mappings
Number of concepts with no mappings found
Total target concepts found across all mappings
Coverage statistics by target vocabulary. Map from vocabulary name to coverage details.
Coverage statistics for a specific target vocabulary (e.g., "ICD10CM", "ICD9CM")
Number of mapping attempts to this vocabulary
Number of successfully mapped concepts
Success rate as percentage (0.0-100.0)
Total processing time for batch
Percentage of mappings served from cache
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/map/batch" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mappings": [
{
"concept_id": 201826,
"target_vocabularies": ["ICD10CM", "ICD9CM"],
"include_approximate": true,
"context_id": "diabetes_mapping"
},
{
"concept_id": 4182210,
"target_vocabularies": ["ICD10CM", "SNOMED"],
"standard_concepts_only": true,
"max_mappings_per_concept": 5
},
{
"concept_id": 313217,
"target_vocabularies": ["ICD10CM", "ICD9CM"],
"mapping_types": ["Maps to", "Maps to value"]
}
]
}'
```
```javascript JavaScript
const batchMapConcepts = async (mappings) => {
const response = await fetch('https://api.omophub.com/v1/concepts/map/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ mappings })
});
return response.json();
};
// Example: Map diabetes-related concepts to multiple vocabularies
const diabetesMappings = [
{
concept_id: 201826, // Type 2 diabetes mellitus
target_vocabularies: ["ICD10CM", "ICD9CM"],
include_approximate: true,
context_id: "diabetes_study"
},
{
concept_id: 443735, // Type 2 diabetes without complications
target_vocabularies: ["ICD10CM", "SNOMED"],
standard_concepts_only: true,
max_mappings_per_concept: 3
},
{
concept_id: 4048098, // Diabetic complication
target_vocabularies: ["ICD10CM", "ICD9CM"],
mapping_types: ["Maps to"],
include_approximate: false
}
];
const results = await batchMapConcepts(diabetesMappings);
console.log(`Batch Mapping Results:`);
console.log(`- Concepts processed: ${results.data.batch_summary.total_concepts}`);
console.log(`- Successful mappings: ${results.data.batch_summary.successful_mappings}`);
console.log(`- Failed mappings: ${results.data.batch_summary.failed_mappings}`);
console.log(`- Total mapped concepts: ${results.data.batch_summary.total_mapped_concepts}`);
console.log(`- Cache hit rate: ${results.data.batch_summary.cache_hit_rate.toFixed(1)}%`);
results.data.results.forEach((result, index) => {
if (result.success) {
console.log(`\n${index + 1}. ${result.source_concept.concept_name} (ID: ${result.concept_id})`);
console.log(` Source: ${result.source_concept.vocabulary_id}`);
console.log(` Context: ${result.context_id || 'N/A'}`);
result.mappings.forEach(mapping => {
const summary = mapping.mapping_summary;
console.log(` → ${mapping.target_vocabulary}: ${summary.total_mappings} mappings (${summary.exact_mappings} exact, ${summary.approximate_mappings} approx)`);
console.log(` Quality: ${summary.mapping_quality}, Coverage: ${(summary.coverage_completeness * 100).toFixed(1)}%`);
mapping.mapped_concepts.slice(0, 2).forEach(concept => {
const approx = concept.is_approximate ? ' (approx)' : '';
console.log(` • ${concept.concept_name} [${concept.concept_code}]${approx}`);
});
});
} else {
console.error(`${index + 1}. Mapping failed for concept ${result.concept_id}: ${result.error}`);
}
});
```
```python Python
import requests
def batch_map_concepts(mappings, api_key):
url = "https://api.omophub.com/v1/concepts/map/batch"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {"mappings": mappings}
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example: Map cardiovascular concepts to multiple coding systems
cardiovascular_mappings = [
{
"concept_id": 4182210, # Hypertension
"target_vocabularies": ["ICD10CM", "ICD9CM", "SNOMED"],
"include_approximate": True,
"standard_concepts_only": True,
"max_mappings_per_concept": 5,
"context_id": "cardio_study_2024"
},
{
"concept_id": 313217, # Atrial fibrillation
"target_vocabularies": ["ICD10CM", "HCPCS"],
"mapping_types": ["Maps to", "Maps to value"],
"include_approximate": False,
"context_id": "cardio_study_2024"
},
{
"concept_id": 318443, # Heart failure
"target_vocabularies": ["ICD10CM", "ICD9CM", "HCPCS"],
"include_approximate": True,
"standard_concepts_only": False,
"context_id": "cardio_study_2024"
}
]
results = batch_map_concepts(cardiovascular_mappings, "YOUR_API_KEY")
# Print batch summary
batch = results['data']['batch_summary']
print(f"Batch Mapping Summary:")
print(f" Concepts processed: {batch['total_concepts']}")
print(f" Success rate: {batch['successful_mappings']}/{batch['total_concepts']} ({batch['successful_mappings']/batch['total_concepts']*100:.1f}%)")
print(f" Total mapped concepts: {batch['total_mapped_concepts']}")
print(f" Processing time: {batch['processing_time_ms']}ms")
print(f" Cache hit rate: {batch['cache_hit_rate']:.1f}%")
print(f"\nVocabulary Coverage:")
for vocab, coverage in batch['vocabulary_coverage'].items():
print(f" {vocab}: {coverage['mapped_concepts']}/{coverage['total_attempts']} concepts ({coverage['success_rate']:.1f}%)")
# Process individual results
for i, result in enumerate(results['data']['results']):
if result['success']:
source = result['source_concept']
summary = result['overall_summary']
print(f"\n{i+1}. {source['concept_name']} ({result['concept_id']})")
print(f" Source: {source['vocabulary_id']} | Domain: {source['domain_id']}")
print(f" Context: {result.get('context_id', 'N/A')}")
print(f" Targets: {summary['successful_vocabularies']}/{summary['total_target_vocabularies']} vocabularies")
print(f" Total mappings: {summary['total_mapped_concepts']}")
print(f" Best quality: {summary['best_mapping_quality']}")
print(f" Has standard mappings: {'Yes' if summary['has_standard_mappings'] else 'No'}")
for mapping in result['mappings']:
vocab = mapping['target_vocabulary']
summary_info = mapping['mapping_summary']
print(f"\n → {vocab} Mappings:")
print(f" Total: {summary_info['total_mappings']} ({summary_info['exact_mappings']} exact, {summary_info['approximate_mappings']} approx)")
print(f" Quality: {summary_info['mapping_quality']} | Coverage: {summary_info['coverage_completeness']:.2f}")
# Show first few mapped concepts
for j, concept in enumerate(mapping['mapped_concepts'][:3]):
mapping_type = concept['mapping_type']
approx_flag = " [APPROX]" if concept['is_approximate'] else ""
confidence = f" ({concept['mapping_confidence']:.2f})" if concept.get('mapping_confidence') else ""
print(f" {j+1}. {concept['concept_name']} [{concept['concept_code']}]{approx_flag}{confidence}")
print(f" {mapping_type} | {concept['domain_id']} | Standard: {concept['standard_concept'] or 'No'}")
else:
print(f"\n{i+1}. ❌ Failed to map concept {result['concept_id']}: {result['error']}")
```
```json
{
"success": true,
"data": {
"results": [
{
"concept_id": 201826,
"success": true,
"context_id": "diabetes_study",
"source_concept": {
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"concept_code": "44054006"
},
"mappings": [
{
"target_vocabulary": "ICD10CM",
"mapped_concepts": [
{
"concept_id": 443735,
"concept_name": "Type 2 diabetes mellitus without complications",
"concept_code": "E11.9",
"domain_id": "Condition",
"concept_class_id": "4-char billing code",
"standard_concept": null,
"mapping_type": "Maps to",
"mapping_confidence": 0.95,
"is_approximate": false,
"valid_start_date": "2015-10-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 443767,
"concept_name": "Type 2 diabetes mellitus with complications",
"concept_code": "E11",
"domain_id": "Condition",
"concept_class_id": "3-char nonbill code",
"standard_concept": null,
"mapping_type": "Maps to",
"mapping_confidence": 0.88,
"is_approximate": true,
"valid_start_date": "2015-10-01",
"valid_end_date": "2099-12-31"
}
],
"mapping_summary": {
"total_mappings": 2,
"exact_mappings": 1,
"approximate_mappings": 1,
"mapping_quality": "good",
"coverage_completeness": 0.85
}
},
{
"target_vocabulary": "ICD9CM",
"mapped_concepts": [
{
"concept_id": 44054361,
"concept_name": "Diabetes mellitus without mention of complication, type II or unspecified type, not stated as uncontrolled",
"concept_code": "250.00",
"domain_id": "Condition",
"concept_class_id": "5-dig billing code",
"standard_concept": null,
"mapping_type": "Maps to",
"mapping_confidence": 0.92,
"is_approximate": false,
"valid_start_date": "1970-01-01",
"valid_end_date": "2015-09-30"
}
],
"mapping_summary": {
"total_mappings": 1,
"exact_mappings": 1,
"approximate_mappings": 0,
"mapping_quality": "excellent",
"coverage_completeness": 0.95
}
}
],
"overall_summary": {
"total_target_vocabularies": 2,
"successful_vocabularies": 2,
"total_mapped_concepts": 3,
"best_mapping_quality": "excellent",
"has_standard_mappings": false
}
}
],
"batch_summary": {
"total_concepts": 1,
"successful_mappings": 1,
"failed_mappings": 0,
"total_mapped_concepts": 3,
"vocabulary_coverage": {
"ICD10CM": {
"total_attempts": 1,
"mapped_concepts": 2,
"success_rate": 100.0
},
"ICD9CM": {
"total_attempts": 1,
"mapped_concepts": 1,
"success_rate": 100.0
}
},
"processing_time_ms": 245,
"cache_hit_rate": 45.5
}
},
"meta": {
"request_id": "req_batch_map_concepts_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Batch Mapping
Map concepts to common coding systems:
```json
{
"mappings": [
{
"concept_id": 201826,
"target_vocabularies": ["ICD10CM", "ICD9CM"]
},
{
"concept_id": 4182210,
"target_vocabularies": ["ICD10CM", "HCPCS"]
}
]
}
```
### High-Quality Mappings Only
Get only exact, standard concept mappings:
```json
{
"mappings": [
{
"concept_id": 201826,
"target_vocabularies": ["ICD10CM", "SNOMED"],
"include_approximate": false,
"standard_concepts_only": true,
"max_mappings_per_concept": 3
}
]
}
```
### Research Context Mapping
Map concepts with context tracking:
```json
{
"mappings": [
{
"concept_id": 201826,
"target_vocabularies": ["ICD10CM", "ICD9CM", "HCPCS"],
"context_id": "diabetes_outcome_study_2024",
"include_approximate": true,
"mapping_types": ["Maps to", "Maps to value"]
}
]
}
```
## Important Notes
* **Batch size limit**: Maximum 200 mapping requests per batch
* **Performance optimization**: Batch processing is significantly more efficient than individual requests
* **Error isolation**: Individual mapping failures don't affect other mappings in the batch
* **Result ordering**: Results are returned in the same order as input mappings
* **Confidence scores**: Available when `include_mapping_confidence=true` but increase processing time
* **Context tracking**: Use `context_id` for tracking related mappings across requests
## Related Endpoints
* [Map Concepts](/api-reference/concepts/map-concepts) - Single concept mapping
* [Validate Mappings](/api-reference/mappings/validate-mappings) - Validate existing mappings
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Vocabulary mapping coverage analysis
# Bulk Concept Mappings
Source: https://docs.omophub.com/api-reference/mappings/bulk-concept-mappings
POST /v1/mappings/bulk
Retrieve mappings for multiple concepts simultaneously, optimized for high-throughput mapping operations
This endpoint enables efficient batch processing of concept mappings, essential for large-scale data transformation, ETL processes, and bulk vocabulary translation operations. It's optimized for performance when mapping hundreds or thousands of concepts.
## Request Body
Array of concept IDs to retrieve mappings for
**Maximum:** 1000 concepts per request
**Example:** `[201826, 320128, 4329847]`
Filter mappings to specific target vocabularies
**Example:** `["ICD10CM", "ICD10", "HCPCS"]`
Filter by specific mapping relationship types
**Default:** `["Maps to", "Mapped from"]`
**Example:** `["Maps to", "Maps to value"]`
Direction of mappings to retrieve
**Options:** `outgoing`, `incoming`, `both`
Include mapping quality scores and metadata
Include synonym information for mapped concepts
Only return mappings to standard concepts
Only include mappings to active concepts
Minimum mapping quality score (0-1)
**Example:** `0.8`
Group response by source concept for easier processing
**When false:** Response returns a flat `mappings` array with `{source_concept, target_concept, ...}` objects.
Include source concepts that have no mappings in results
Maximum number of mappings to return per concept
**Default:** No limit
**Example:** `10`
## Response
Summary of the bulk mapping request
Number of concepts in the request
Number of requested concepts found in database
Number of requested concepts not found
Number of concepts that have mappings
Number of concepts without mappings
Total number of mapping relationships returned
Vocabularies that were filtered for
Mappings grouped by source concept
Information about the source concept
Unique concept identifier
Standard name of the concept
Original code from the vocabulary
Source vocabulary identifier
Human-readable vocabulary name
Domain classification
Concept class identifier
Array of mappings for this concept
Unique identifier for this mapping
Type of mapping relationship
Direction from source concept
The mapped concept
Target concept identifier
Target concept name
Target concept code
Target vocabulary identifier
Target vocabulary name
Target domain
Target concept class
Standard concept designation
Alternative names (when include\_synonyms=true)
Quality information (when include\_mapping\_quality=true)
Confidence in mapping accuracy (0-1)
Type of concept equivalence
Semantic similarity score (0-1)
Source of the mapping
Validation status
Summary statistics for this concept's mappings
Number of mappings for this concept
Count of mappings by target vocabulary
Count of mappings by relationship type
Average mapping quality (when include\_mapping\_quality=true)
Concept IDs that were not found in the database
Overall statistics for the bulk operation
Total time taken to process the request
Average number of mappings per concept
Target vocabulary with most mappings
Target vocabulary with fewest mappings
Distribution of concepts by number of mappings
Distribution of quality scores (when include\_mapping\_quality=true)
```bash cURL
curl -X POST "https://api.omophub.com/v1/mappings/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"concept_ids": [201826, 320128, 4329847, 1503297, 4273391],
"target_vocabularies": ["ICD10CM", "ICD10", "HCPCS"],
"include_mapping_quality": true,
"include_synonyms": true,
"quality_threshold": 0.8
}'
```
```javascript JavaScript
const conceptIds = [201826, 320128, 4329847, 1503297, 4273391]; // Sample concept IDs
const response = await fetch('https://api.omophub.com/v1/mappings/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: conceptIds,
target_vocabularies: ['ICD10CM', 'ICD10', 'HCPCS'],
include_mapping_quality: true,
include_synonyms: true,
quality_threshold: 0.8,
max_mappings_per_concept: 10
})
});
const bulkMappings = await response.json();
const { request_summary, concept_mappings, bulk_statistics } = bulkMappings.data;
console.log(`=== Bulk Mapping Results ===`);
console.log(`Processed ${request_summary.total_concepts_requested} concepts`);
console.log(`Found ${request_summary.concepts_found} concepts`);
console.log(`${request_summary.concepts_with_mappings} concepts have mappings`);
console.log(`Total mappings: ${request_summary.total_mappings_found}`);
// Process each concept's mappings
concept_mappings.forEach(conceptGroup => {
const source = conceptGroup.source_concept;
const mappings = conceptGroup.mappings;
const summary = conceptGroup.mapping_summary;
console.log(`\n${source.concept_name} (${source.concept_id})`);
console.log(` Vocabulary: ${source.vocabulary_name}`);
console.log(` Code: ${source.concept_code}`);
console.log(` Domain: ${source.domain_id}`);
console.log(` Mappings found: ${summary.total_mappings}`);
// Group mappings by vocabulary
const mappingsByVocab = {};
mappings.forEach(mapping => {
const vocab = mapping.target_concept.vocabulary_id;
if (!mappingsByVocab[vocab]) mappingsByVocab[vocab] = [];
mappingsByVocab[vocab].push(mapping);
});
// Display mappings by vocabulary
Object.entries(mappingsByVocab).forEach(([vocab, vocabMappings]) => {
console.log(` ${vocab} (${vocabMappings.length} mappings):`);
vocabMappings.forEach(mapping => {
const target = mapping.target_concept;
let qualityInfo = '';
if (mapping.mapping_quality) {
const quality = mapping.mapping_quality;
qualityInfo = ` [${quality.equivalence_type}, ${(quality.confidence_score * 100).toFixed(0)}%]`;
}
console.log(` → ${target.concept_name} (${target.concept_code})${qualityInfo}`);
// Show synonyms if available
if (target.synonyms && target.synonyms.length > 0) {
const synonyms = target.synonyms.slice(0, 2).map(s => s.concept_synonym_name);
console.log(` Also: ${synonyms.join(', ')}`);
}
});
});
// Show average quality if available
if (summary.average_quality_score) {
console.log(` Average quality: ${(summary.average_quality_score * 100).toFixed(1)}%`);
}
});
// Display bulk statistics
console.log(`\n=== Bulk Statistics ===`);
console.log(`Processing time: ${bulk_statistics.processing_time_ms}ms`);
console.log(`Average mappings per concept: ${bulk_statistics.average_mappings_per_concept.toFixed(1)}`);
console.log(`Most mapped vocabulary: ${bulk_statistics.most_mapped_vocabulary}`);
// Show concepts without mappings
const unmappedCount = request_summary.concepts_without_mappings;
if (unmappedCount > 0) {
console.log(`\n⚠️ ${unmappedCount} concepts have no mappings`);
}
// Show not found concepts
if (bulkMappings.data.not_found_concepts.length > 0) {
console.log(`\n❌ Concepts not found: ${bulkMappings.data.not_found_concepts.join(', ')}`);
}
```
```python Python
import requests
import pandas as pd
import json
from collections import defaultdict
# Sample concept IDs for bulk mapping
concept_ids = [
201826, # Type 2 diabetes mellitus (SNOMED)
320128, # Essential hypertension (SNOMED)
4329847, # Myocardial infarction (SNOMED)
1503297, # Metformin (RxNorm)
4273391 # Coronary artery bypass graft (SNOMED)
]
url = "https://api.omophub.com/v1/mappings/bulk"
payload = {
"concept_ids": concept_ids,
"target_vocabularies": ["ICD10CM", "ICD10", "HCPCS", "NDC"],
"include_mapping_quality": True,
"include_synonyms": True,
"quality_threshold": 0.7,
"max_mappings_per_concept": 15,
"direction": "outgoing"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
print("=== BULK CONCEPT MAPPING RESULTS ===")
summary = data['data']['request_summary']
print(f"Concepts requested: {summary['total_concepts_requested']}")
print(f"Concepts found: {summary['concepts_found']}")
print(f"Concepts with mappings: {summary['concepts_with_mappings']}")
print(f"Total mappings found: {summary['total_mappings_found']}")
# Create DataFrame for analysis
mapping_rows = []
concept_rows = []
for concept_group in data['data']['concept_mappings']:
source = concept_group['source_concept']
mappings = concept_group['mappings']
# Concept-level data
concept_rows.append({
'source_concept_id': source['concept_id'],
'source_concept_name': source['concept_name'],
'source_vocabulary': source['vocabulary_id'],
'domain': source['domain_id'],
'total_mappings': len(mappings)
})
# Mapping-level data
for mapping in mappings:
target = mapping['target_concept']
quality = mapping.get('mapping_quality', {})
mapping_rows.append({
'source_concept_id': source['concept_id'],
'source_concept_name': source['concept_name'],
'source_vocabulary': source['vocabulary_id'],
'target_concept_id': target['concept_id'],
'target_concept_name': target['concept_name'],
'target_vocabulary': target['vocabulary_id'],
'target_code': target['concept_code'],
'mapping_type': mapping['mapping_type'],
'equivalence_type': quality.get('equivalence_type', ''),
'confidence_score': quality.get('confidence_score', 0),
'validation_status': quality.get('validation_status', '')
})
# Create DataFrames
concepts_df = pd.DataFrame(concept_rows)
mappings_df = pd.DataFrame(mapping_rows)
print(f"\n=== CONCEPT OVERVIEW ===")
print(concepts_df[['source_concept_name', 'source_vocabulary', 'domain', 'total_mappings']])
print(f"\n=== MAPPING DISTRIBUTION BY VOCABULARY ===")
vocab_dist = mappings_df['target_vocabulary'].value_counts()
print(vocab_dist)
print(f"\n=== DETAILED MAPPINGS BY CONCEPT ===")
for concept_id in concept_ids:
concept_mappings = mappings_df[mappings_df['source_concept_id'] == concept_id]
if len(concept_mappings) > 0:
source_name = concept_mappings.iloc[0]['source_concept_name']
source_vocab = concept_mappings.iloc[0]['source_vocabulary']
print(f"\n{source_name} ({source_vocab}):")
# Group by target vocabulary
for target_vocab in concept_mappings['target_vocabulary'].unique():
vocab_mappings = concept_mappings[concept_mappings['target_vocabulary'] == target_vocab]
print(f" {target_vocab} ({len(vocab_mappings)} mappings):")
for _, mapping in vocab_mappings.iterrows():
quality_info = ""
if mapping['confidence_score'] > 0:
quality_info = f" [{mapping['equivalence_type']}, {mapping['confidence_score']:.2f}]"
print(f" → {mapping['target_concept_name']} ({mapping['target_code']}){quality_info}")
# Quality analysis
if len(mappings_df) > 0 and mappings_df['confidence_score'].sum() > 0:
print(f"\n=== QUALITY ANALYSIS ===")
quality_mappings = mappings_df[mappings_df['confidence_score'] > 0]
print(f"Average confidence score: {quality_mappings['confidence_score'].mean():.3f}")
print(f"High quality mappings (>0.8): {len(quality_mappings[quality_mappings['confidence_score'] > 0.8])}")
print(f"\nEquivalence Type Distribution:")
equiv_dist = quality_mappings['equivalence_type'].value_counts()
for equiv_type, count in equiv_dist.items():
percentage = (count / len(quality_mappings)) * 100
print(f" {equiv_type}: {count} ({percentage:.1f}%)")
print(f"\nValidation Status Distribution:")
validation_dist = quality_mappings['validation_status'].value_counts()
for status, count in validation_dist.items():
percentage = (count / len(quality_mappings)) * 100
print(f" {status}: {count} ({percentage:.1f}%)")
# Performance statistics
bulk_stats = data['data']['bulk_statistics']
print(f"\n=== PERFORMANCE STATISTICS ===")
print(f"Processing time: {bulk_stats['processing_time_ms']}ms")
print(f"Average mappings per concept: {bulk_stats['average_mappings_per_concept']:.1f}")
print(f"Most mapped vocabulary: {bulk_stats['most_mapped_vocabulary']}")
if bulk_stats.get('concepts_by_mapping_count'):
print(f"\nConcepts by mapping count:")
for count_range, num_concepts in bulk_stats['concepts_by_mapping_count'].items():
print(f" {count_range} mappings: {num_concepts} concepts")
# Handle unmapped concepts
unmapped_concepts = data['data'].get('concept_mappings', [])
unmapped = [cg for cg in unmapped_concepts if len(cg['mappings']) == 0]
if unmapped:
print(f"\n=== UNMAPPED CONCEPTS ===")
for concept_group in unmapped:
source = concept_group['source_concept']
print(f" {source['concept_name']} ({source['vocabulary_id']}) - {source['domain_id']}")
# Handle not found concepts
not_found = data['data'].get('not_found_concepts', [])
if not_found:
print(f"\n=== CONCEPTS NOT FOUND ===")
print(f"Concept IDs not found in database: {not_found}")
# Export results to CSV for further analysis
if len(mappings_df) > 0:
mappings_df.to_csv('bulk_concept_mappings.csv', index=False)
concepts_df.to_csv('concept_mapping_summary.csv', index=False)
print(f"\n📁 Results exported to CSV files")
# Create mapping translation dictionary
print(f"\n=== TRANSLATION DICTIONARY ===")
translation_dict = defaultdict(dict)
for _, mapping in mappings_df.iterrows():
source_id = mapping['source_concept_id']
target_vocab = mapping['target_vocabulary']
target_code = mapping['target_code']
if target_vocab not in translation_dict[source_id]:
translation_dict[source_id][target_vocab] = []
translation_dict[source_id][target_vocab].append({
'code': target_code,
'name': mapping['target_concept_name'],
'confidence': mapping['confidence_score']
})
# Display translation dictionary for first few concepts
for concept_id, translations in list(translation_dict.items())[:3]:
source_name = mappings_df[mappings_df['source_concept_id'] == concept_id]['source_concept_name'].iloc[0]
print(f"\n{source_name} ({concept_id}) translations:")
for vocab, mappings in translations.items():
codes = [m['code'] for m in mappings]
print(f" {vocab}: {', '.join(codes[:3])}") # Show first 3 codes
```
```json
{
"success": true,
"data": {
"request_summary": {
"total_concepts_requested": 5,
"concepts_found": 5,
"concepts_not_found": 0,
"concepts_with_mappings": 4,
"concepts_without_mappings": 1,
"total_mappings_found": 27,
"target_vocabularies_requested": ["ICD10CM", "ICD10", "HCPCS"]
},
"concept_mappings": [
{
"source_concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"mappings": [
{
"mapping_id": "map_201826_45757292",
"mapping_type": "Maps to",
"direction": "outgoing",
"target_concept": {
"concept_id": 45757292,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S",
"synonyms": [
{
"concept_synonym_name": "Type II diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
]
},
"mapping_quality": {
"confidence_score": 0.98,
"equivalence_type": "exact",
"semantic_similarity": 0.95,
"mapping_source": "official",
"validation_status": "validated"
}
},
{
"mapping_id": "map_201826_4087682",
"mapping_type": "Maps to",
"direction": "outgoing",
"target_concept": {
"concept_id": 4087682,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11.9",
"vocabulary_id": "ICD10",
"vocabulary_name": "International Classification of Diseases, Tenth Revision",
"domain_id": "Condition",
"concept_class_id": "ICD10 code",
"standard_concept": "S"
},
"mapping_quality": {
"confidence_score": 0.94,
"equivalence_type": "exact",
"semantic_similarity": 0.92,
"mapping_source": "official",
"validation_status": "validated"
}
}
],
"mapping_summary": {
"total_mappings": 2,
"mappings_by_vocabulary": {
"ICD10CM": 1,
"ICD10": 1
},
"mappings_by_type": {
"Maps to": 2
},
"average_quality_score": 0.96
}
},
{
"source_concept": {
"concept_id": 4273391,
"concept_name": "Coronary artery bypass graft",
"concept_code": "232717009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Procedure",
"concept_class_id": "Procedure"
},
"mappings": [
{
"mapping_id": "map_4273391_4336464",
"mapping_type": "Maps to",
"direction": "outgoing",
"target_concept": {
"concept_id": 4336464,
"concept_name": "Coronary artery bypass, using venous graft(s) and arterial graft(s); single vein graft (List separately in addition to code for primary procedure)",
"concept_code": "C1520",
"vocabulary_id": "HCPCS",
"vocabulary_name": "Healthcare Common Procedure Coding System",
"domain_id": "Procedure",
"concept_class_id": "HCPCS",
"standard_concept": "S"
},
"mapping_quality": {
"confidence_score": 0.87,
"equivalence_type": "broader",
"semantic_similarity": 0.81,
"mapping_source": "official",
"validation_status": "validated"
}
}
],
"mapping_summary": {
"total_mappings": 1,
"mappings_by_vocabulary": {
"HCPCS": 1
},
"mappings_by_type": {
"Maps to": 1
},
"average_quality_score": 0.87
}
}
],
"not_found_concepts": [],
"bulk_statistics": {
"processing_time_ms": 156.7,
"average_mappings_per_concept": 5.4,
"most_mapped_vocabulary": "ICD10CM",
"least_mapped_vocabulary": "HCPCS",
"concepts_by_mapping_count": {
"0": 1,
"1-5": 2,
"6-10": 1,
"11+": 1
},
"quality_distribution": {
"high_quality": 18,
"medium_quality": 7,
"low_quality": 2
}
}
},
"meta": {
"request_id": "req_bulk_mappings_456789",
"timestamp": "2024-12-22T10:45:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Bulk Mapping
Map multiple concepts to ICD-10-CM:
```javascript
const response = await fetch('/v1/mappings/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: [201826, 320128, 4329847],
target_vocabularies: ['ICD10CM']
})
});
```
### High-Quality Mappings Only
Get only high-confidence mappings:
```javascript
const qualityMappings = await fetch('/v1/mappings/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: conceptIds,
quality_threshold: 0.9,
include_mapping_quality: true,
max_mappings_per_concept: 5
})
});
```
### ETL Pipeline Integration
Optimized for data transformation pipelines:
```javascript
const etlMappings = await fetch('/v1/mappings/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: largeBatchOfConceptIds,
target_vocabularies: ['ICD10CM', 'HCPCS'],
direction: 'outgoing',
standard_only: true,
group_by_source: true,
include_unmapped: false
})
});
```
### Cross-Vocabulary Translation
Translate concepts across multiple vocabularies:
```javascript
const translation = await fetch('/v1/mappings/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: snomedConceptIds,
target_vocabularies: ['ICD10CM', 'ICD10', 'HCPCS', 'NDC'],
include_synonyms: true,
max_mappings_per_concept: 10
})
});
```
### Data Quality Assessment
Assess mapping quality for a concept set:
```javascript
const qualityAssessment = await fetch('/v1/mappings/bulk', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
concept_ids: conceptsToAssess,
include_mapping_quality: true,
include_synonyms: false,
direction: 'both'
})
});
```
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Individual concept mappings
* [Get Vocabulary Mappings](/api-reference/mappings/get-vocabulary-mappings) - All mappings between vocabularies
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Overall mapping coverage
* [Search Concepts](/api-reference/search/search-concepts) - Find concepts to map
## Notes
* Maximum 1000 concepts per request to maintain performance
* Bulk operations are optimized for throughput over individual response detail
* Use quality thresholds to filter unreliable mappings in production
* Group by source simplifies client-side processing of results
* Include unmapped concepts to identify gaps in coverage
* Processing time scales with number of concepts and complexity of filters
* Consider pagination strategies for very large concept sets
* Results are cached briefly to improve repeated request performance
# Get Concept Mappings
Source: https://docs.omophub.com/api-reference/mappings/get-concept-mappings
GET /v1/concepts/{concept_id}/mappings
Retrieve cross-vocabulary mappings for a specific concept, showing equivalent or related concepts in other terminologies
This endpoint provides comprehensive mapping information for a concept, showing how it relates to concepts in other vocabularies. These mappings are essential for data interoperability, allowing translation between different medical coding systems like SNOMED CT, ICD-10-CM, and others.
## Path Parameters
The unique identifier of the concept to retrieve mappings for
**Example:** `201826` (Type 2 diabetes mellitus in SNOMED)
## Query Parameters
Filter mappings to specific target vocabularies
**Example:** `ICD10CM,ICD10,HCPCS`
Filter by specific mapping relationship types
**Default:** `Maps to,Mapped from`
**Example:** `Maps to,Maps to value`
Direction of mappings to retrieve
**Options:** `outgoing`, `incoming`, `both`
Include indirect mappings (concept → intermediate → target)
Only return mappings to standard concepts
Include mapping quality scores and confidence ratings
Include synonym information for mapped concepts
Include contextual information about mapping relationships
Only include mappings to active concepts
Sort mappings by specified field
**Options:** `vocabulary_id`, `concept_name`, `mapping_quality`
Sort order for mappings
**Options:** `asc`, `desc`
Number of mappings to return per page (max 500)
Page number for pagination (1-based)
## Response
Information about the source concept for which mappings were retrieved
Unique concept identifier
Standard name of the concept
Original code from the vocabulary
Source vocabulary identifier
Human-readable source vocabulary name
Domain classification
Concept class identifier
Array of mapping relationships to other vocabularies
Unique identifier for this mapping relationship
Type of mapping relationship
**Values:** `Maps to`, `Mapped from`, `Maps to value`, `Value mapped from`
Direction of the mapping from source concept
**Values:** `outgoing`, `incoming`
The concept being mapped to
Target concept identifier
Target concept name
Target concept code
Target vocabulary identifier
Human-readable target vocabulary name
Target concept domain
Target concept class
Standard concept designation ('S', 'C', or null)
Alternative names (when include\_synonyms=true)
Date when target concept became valid
Date when target concept becomes invalid
Quality assessment of the mapping (when include\_mapping\_quality=true)
Confidence in mapping accuracy (0-1)
Type of equivalence between concepts
**Values:** `exact`, `broader`, `narrower`, `related`, `inexact`
Semantic similarity score (0-1)
Source of the mapping
**Values:** `official`, `community`, `algorithmic`, `manual`
Validation status of the mapping
**Values:** `validated`, `pending`, `disputed`, `deprecated`
Date when mapping was last reviewed
Contextual information about the mapping (when include\_context=true)
Primary use case for this mapping
**Values:** `billing`, `clinical`, `research`, `quality_reporting`
Geographic regions where mapping is applicable
Time period when mapping is applicable
Additional notes about the mapping
Official crosswalk document source
Intermediate concepts for indirect mappings (when include\_indirect=true)
Intermediate concept identifier
Intermediate concept name
Intermediate vocabulary
Position in the mapping path
Date when mapping became valid
Date when mapping becomes invalid
Summary statistics about the mappings
Total number of mappings found
Number of outgoing mappings
Number of incoming mappings
List of target vocabularies with mapping counts
Count of mappings by type
Distribution of mapping quality scores (when include\_mapping\_quality=true)
Analysis of mapping coverage
Vocabularies with high mapping coverage
Vocabularies with low mapping coverage
Domains where concept has no mappings
Response metadata and pagination information
Current page number
Items per page
Total mappings
Total number of pages
Whether next page exists
Whether previous page exists
Time taken to execute the query
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/201826/mappings?target_vocabularies=ICD10CM,ICD10&include_mapping_quality=true&include_synonyms=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
concept_id = 201826 # Type 2 diabetes mellitus (SNOMED)
url = f"https://api.omophub.com/v1/concepts/{concept_id}/mappings"
params = {
"target_vocabularies": "ICD10CM,ICD10,ICD9CM",
"include_mapping_quality": True,
"include_context": True,
"include_synonyms": True,
"sort_by": "mapping_quality",
"sort_order": "desc"
}
headers = {
"Authorization": "Bearer YOUR_API_KEY"
}
response = requests.get(url, params=params, headers=headers)
mapping_data = response.json()
print(f"Source: {mapping_data['data']['source_concept']['concept_name']}")
print(f"Total mappings: {mapping_data['data']['mapping_summary']['total_mappings']}")
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/201826/mappings?target_vocabularies=ICD10CM,ICD10&include_mapping_quality=true&include_synonyms=true', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const mappingData = await response.json();
console.log(`Source: ${mappingData.data.source_concept.concept_name}`);
console.log(`Found ${mappingData.data.mapping_summary.total_mappings} mappings`);
// Group mappings by target vocabulary
const mappingsByVocab = {};
mappingData.data.mappings.forEach(mapping => {
const vocab = mapping.target_concept.vocabulary_id;
if (!mappingsByVocab[vocab]) {
mappingsByVocab[vocab] = [];
}
mappingsByVocab[vocab].push(mapping);
});
```
```bash cURL (outgoing only)
curl -X GET "https://api.omophub.com/v1/concepts/201826/mappings?direction=outgoing&include_mapping_quality=true&page_size=50" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (quality analysis)
import requests
response = requests.get(
"https://api.omophub.com/v1/concepts/201826/mappings",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={
"include_mapping_quality": True,
"include_context": True,
"sort_by": "mapping_quality"
}
)
data = response.json()
for mapping in data['data']['mappings']:
target = mapping['target_concept']
quality = mapping.get('mapping_quality', {})
print(f"{target['vocabulary_id']}: {target['concept_name']}")
if quality:
confidence = quality['confidence_score'] * 100
print(f" Quality: {quality['equivalence_type']}, {confidence:.1f}% confidence")
```
```json
{
"success": true,
"data": {
"source_concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"mappings": [
{
"mapping_id": "map_201826_45757292",
"mapping_type": "Maps to",
"direction": "outgoing",
"target_concept": {
"concept_id": 45757292,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S",
"synonyms": [
{
"concept_synonym_name": "Type II diabetes mellitus",
"language_concept_id": 4180186,
"language_concept_name": "English"
},
{
"concept_synonym_name": "Adult-onset diabetes",
"language_concept_id": 4180186,
"language_concept_name": "English"
}
],
"valid_start_date": "2016-10-01",
"valid_end_date": "2099-12-31"
},
"mapping_quality": {
"confidence_score": 0.98,
"equivalence_type": "exact",
"semantic_similarity": 0.95,
"mapping_source": "official",
"validation_status": "validated",
"last_reviewed_date": "2024-01-15"
},
"mapping_context": {
"use_case": "billing",
"geographic_scope": ["US"],
"temporal_scope": {
"start_date": "2016-10-01",
"end_date": null
},
"mapping_notes": "Official crosswalk mapping for ICD-10-CM billing purposes",
"cross_walk_source": "CMS ICD-10-CM Official Guidelines"
},
"valid_start_date": "2016-10-01",
"valid_end_date": "2099-12-31"
},
{
"mapping_id": "map_201826_4087682",
"mapping_type": "Maps to",
"direction": "outgoing",
"target_concept": {
"concept_id": 4087682,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11.9",
"vocabulary_id": "ICD10",
"vocabulary_name": "International Classification of Diseases, Tenth Revision",
"domain_id": "Condition",
"concept_class_id": "ICD10 code",
"standard_concept": "S",
"valid_start_date": "1990-01-01",
"valid_end_date": "2099-12-31"
},
"mapping_quality": {
"confidence_score": 0.94,
"equivalence_type": "exact",
"semantic_similarity": 0.92,
"mapping_source": "official",
"validation_status": "validated",
"last_reviewed_date": "2023-08-20"
},
"mapping_context": {
"use_case": "clinical",
"geographic_scope": ["International"],
"mapping_notes": "WHO ICD-10 standard mapping for international use",
"cross_walk_source": "WHO ICD-10 Reference"
},
"valid_start_date": "1990-01-01",
"valid_end_date": "2099-12-31"
},
{
"mapping_id": "map_201826_25001",
"mapping_type": "Maps to",
"direction": "outgoing",
"target_concept": {
"concept_id": 25001,
"concept_name": "Type II diabetes mellitus",
"concept_code": "250.00",
"vocabulary_id": "ICD9CM",
"vocabulary_name": "International Classification of Diseases, Ninth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "4-dig billing code",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2014-09-30"
},
"mapping_quality": {
"confidence_score": 0.91,
"equivalence_type": "exact",
"semantic_similarity": 0.89,
"mapping_source": "official",
"validation_status": "validated",
"last_reviewed_date": "2014-09-30"
},
"valid_start_date": "1970-01-01",
"valid_end_date": "2014-09-30"
}
],
"mapping_summary": {
"total_mappings": 12,
"outgoing_mappings": 8,
"incoming_mappings": 4,
"target_vocabularies": [
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"mapping_count": 3
},
{
"vocabulary_id": "ICD10",
"vocabulary_name": "International Classification of Diseases, Tenth Revision",
"mapping_count": 2
},
{
"vocabulary_id": "ICD9CM",
"vocabulary_name": "International Classification of Diseases, Ninth Revision, Clinical Modification",
"mapping_count": 7
}
],
"mapping_type_distribution": {
"Maps to": 8,
"Mapped from": 4
},
"quality_distribution": {
"exact": 7,
"broader": 3,
"narrower": 1,
"related": 1
},
"coverage_analysis": {
"well_mapped_vocabularies": ["ICD10CM", "ICD10", "ICD9CM"],
"poorly_mapped_vocabularies": [],
"unmapped_domains": []
}
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 12,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"query_time_ms": 87.3,
"request_id": "req_mappings_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Concept Mappings
Get all mappings for a concept:
```javascript
const mappings = await fetch('/v1/concepts/201826/mappings');
```
### ICD-10 Mappings Only
Get mappings to specific vocabularies:
```javascript
const icdMappings = await fetch('/v1/concepts/201826/mappings?target_vocabularies=ICD10CM,ICD10');
```
### High-Quality Mappings
Get mappings with quality information:
```javascript
const qualityMappings = await fetch('/v1/concepts/201826/mappings?include_mapping_quality=true&sort_by=mapping_quality&sort_order=desc');
```
### Outgoing Mappings Only
Get only outgoing mappings from the concept:
```javascript
const outgoingMappings = await fetch('/v1/concepts/201826/mappings?direction=outgoing');
```
### Comprehensive Mapping Analysis
Get detailed mapping information with context:
```javascript
const detailedMappings = await fetch('/v1/concepts/201826/mappings?include_mapping_quality=true&include_context=true&include_synonyms=true&include_indirect=true');
```
## Related Endpoints
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Mapping coverage analysis
* [Get Vocabulary Mappings](/api-reference/mappings/get-vocabulary-mappings) - All mappings between vocabularies
* [Get Concept Details](/api-reference/concepts/get-concept-details) - Complete concept information
* [Search Concepts](/api-reference/search/search-concepts) - Find concepts across vocabularies
## Notes
* Mappings enable interoperability between different medical coding systems
* "Maps to" relationships typically go from non-standard to standard concepts
* Quality scores help identify the most reliable mappings for clinical use
* Some mappings may be contextual (e.g., billing vs. clinical use)
* Indirect mappings provide pathways through intermediate vocabularies
* Geographic scope indicates where mappings are officially recognized
* Validation status shows the review and approval status of mappings
* Standard concepts are preferred targets for most mapping applications
# Get Mapping Coverage
Source: https://docs.omophub.com/api-reference/mappings/get-mapping-coverage
GET /v1/mappings/coverage
Analyze mapping coverage across vocabularies, domains, and concept classes to understand interoperability gaps and strengths
This endpoint provides comprehensive analysis of mapping coverage across the vocabulary ecosystem, essential for understanding data interoperability capabilities, identifying translation gaps, and planning vocabulary integration strategies.
## Query Parameters
Filter analysis to specific source vocabularies
**Example:** `SNOMED,ICD10CM,RxNorm`
Filter analysis to specific target vocabularies
**Example:** `ICD10CM,HCPCS,NDC`
Focus analysis on specific domains
**Example:** `Condition,Drug,Procedure`
Focus analysis on specific concept classes
**Example:** `Clinical Finding,Procedure,Ingredient`
Include detailed analysis of mapping gaps
Include mapping quality distribution analysis
Include recommendations for improving coverage
Include historical trends in mapping coverage
Threshold for considering coverage "good" (0-100 percentage scale)
Only analyze standard concepts
Only analyze active concepts
## Response
Indicates if the request was successful
Response data containing coverage analysis
High-level summary of mapping coverage
Total number of concepts included in analysis
Concepts that have at least one mapping
Concepts with no mappings
Overall percentage of concepts with mappings
Number of distinct vocabulary-to-vocabulary mapping pairs
Total number of mapping relationships
Coverage analysis by individual vocabularies
Coverage statistics for each vocabulary
Human-readable vocabulary name
Total concepts in this vocabulary
Concepts with mappings to other vocabularies
Concepts with mappings from other vocabularies
Percentage with outgoing mappings
Percentage with incoming mappings
Vocabularies this vocabulary maps to
Vocabularies that map to this vocabulary
Coverage breakdown by domain within this vocabulary
Overall coverage rating
**Values:** `excellent`, `good`, `moderate`, `poor`
Coverage analysis by medical domains
Coverage statistics for each domain
Human-readable domain name
Total concepts in this domain
Concepts with cross-vocabulary mappings
Percentage of concepts with mappings
Coverage by vocabulary within this domain
Vocabularies with best coverage in this domain
Vocabularies with poor coverage in this domain
Overall interoperability score for domain (0-1)
Matrix showing coverage between vocabulary pairs
Two-dimensional mapping coverage matrix
Source vocabulary identifier
Target vocabulary identifier
Number of mappings between vocabularies
Percentage of source concepts mapped to target
Percentage of target concepts mapped from source
Whether mappings exist in both directions
Vocabulary pairs with highest mutual coverage
Vocabulary pairs with lowest coverage
Detailed analysis of mapping gaps (when include\_detailed\_gaps=true)
Most important gaps affecting interoperability
Type of gap identified
Description of the gap
Number of concepts affected
Impact on business/clinical operations
Priority level for addressing this gap
Common patterns identified in mapping gaps
Sample of unmapped concepts by category
Vocabularies with very limited mapping coverage
Analysis of mapping quality across the ecosystem (when include\_quality\_analysis=true)
System-wide quality metrics
Average confidence score across all mappings
Percentage of mappings with high confidence (>0.8)
Percentage of officially validated mappings
Number of disputed or problematic mappings
Quality metrics for each vocabulary pair
Quality metrics by medical domain
Distribution of mapping equivalence types
Historical trends in mapping coverage (when include\_trends=true)
Time series data showing coverage evolution
Date of measurement
Overall coverage at this time point
New mappings added since previous point
Mappings deprecated since previous point
Annual rate of coverage improvement
Areas with most rapid coverage improvement
Areas with little coverage improvement
Recommendations for improving mapping coverage (when include\_recommendations=true)
Type of recommendation
Brief title of the recommendation
Detailed description of the recommendation
Implementation priority
Expected impact on coverage
Vocabularies/domains that would benefit
Difficulty of implementation
Analysis metadata and processing information
Date when analysis was performed
Timestamp of underlying mapping data
Scope of the coverage analysis
Time taken to perform analysis
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/mappings/coverage?domain_ids=Condition,Drug&include_detailed_gaps=true&include_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/mappings/coverage?domain_ids=Condition,Drug&include_detailed_gaps=true&include_recommendations=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const coverageData = await response.json();
const { coverage_overview, vocabulary_coverage, domain_coverage } = coverageData.data;
console.log('=== Mapping Coverage Analysis ===');
console.log(`Total concepts analyzed: ${coverage_overview.total_concepts_analyzed.toLocaleString()}`);
console.log(`Overall coverage: ${coverage_overview.overall_coverage_percentage.toFixed(1)}%`);
console.log(`Vocabulary pairs: ${coverage_overview.unique_vocabulary_pairs}`);
// Display vocabulary coverage
console.log('\n=== Vocabulary Coverage ===');
const vocabEntries = Object.entries(vocabulary_coverage)
.sort(([,a], [,b]) => b.outgoing_coverage_percentage - a.outgoing_coverage_percentage);
vocabEntries.forEach(([vocabId, data]) => {
console.log(`${data.vocabulary_name}:`);
console.log(` Outgoing coverage: ${data.outgoing_coverage_percentage.toFixed(1)}% (${data.concepts_with_outgoing_mappings.toLocaleString()}/${data.total_concepts.toLocaleString()})`);
console.log(` Incoming coverage: ${data.incoming_coverage_percentage.toFixed(1)}%`);
console.log(` Rating: ${data.coverage_rating}`);
console.log(` Maps to: ${data.mapping_targets.slice(0, 3).join(', ')}`);
});
// Display domain coverage
console.log('\n=== Domain Coverage ===');
Object.entries(domain_coverage).forEach(([domainId, data]) => {
console.log(`${data.domain_name}: ${data.coverage_percentage.toFixed(1)}% coverage`);
console.log(` Interoperability score: ${(data.interoperability_score * 100).toFixed(1)}%`);
console.log(` Best mapped: ${data.best_mapped_vocabularies.slice(0, 3).join(', ')}`);
if (data.poorly_mapped_vocabularies.length > 0) {
console.log(` Needs work: ${data.poorly_mapped_vocabularies.slice(0, 3).join(', ')}`);
}
});
// Show vocabulary pair matrix highlights
if (coverageData.data.vocabulary_pair_matrix) {
console.log('\n=== Top Vocabulary Pairs ===');
coverageData.data.vocabulary_pair_matrix.strongest_pairs.slice(0, 5).forEach(pair => {
console.log(`${pair.source_vocabulary} ↔ ${pair.target_vocabulary}: ${pair.source_coverage_percentage.toFixed(1)}% coverage`);
});
}
// Display critical gaps
if (coverageData.data.mapping_gaps) {
console.log('\n=== Critical Gaps ===');
coverageData.data.mapping_gaps.critical_gaps.slice(0, 5).forEach(gap => {
console.log(`${gap.gap_type}: ${gap.description}`);
console.log(` Affects ${gap.affected_concepts.toLocaleString()} concepts (Priority: ${gap.priority})`);
});
}
// Show recommendations
if (coverageData.data.recommendations) {
console.log('\n=== Top Recommendations ===');
coverageData.data.recommendations.slice(0, 3).forEach((rec, i) => {
console.log(`${i + 1}. ${rec.title} (${rec.priority} priority)`);
console.log(` ${rec.description}`);
console.log(` Expected impact: ${rec.estimated_impact}`);
});
}
```
```python Python
import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
url = "https://api.omophub.com/v1/mappings/coverage"
params = {
"domain_ids": "Condition,Drug,Procedure",
"include_detailed_gaps": True,
"include_quality_analysis": True,
"include_trends": True,
"include_recommendations": True
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print("=== MAPPING COVERAGE ANALYSIS ===")
overview = data['data']['coverage_overview']
print(f"Total concepts: {overview['total_concepts_analyzed']:,}")
print(f"Overall coverage: {overview['overall_coverage_percentage']:.1f}%")
print(f"Concepts with mappings: {overview['concepts_with_mappings']:,}")
print(f"Concepts without mappings: {overview['concepts_without_mappings']:,}")
print(f"Vocabulary pairs: {overview['unique_vocabulary_pairs']}")
# Vocabulary coverage analysis
print(f"\n=== VOCABULARY COVERAGE ===")
vocab_coverage = data['data']['vocabulary_coverage']
vocab_df = pd.DataFrame([
{
'vocabulary': vocab_id,
'name': info['vocabulary_name'][:20] + "..." if len(info['vocabulary_name']) > 20 else info['vocabulary_name'],
'total_concepts': info['total_concepts'],
'outgoing_coverage': info['outgoing_coverage_percentage'],
'incoming_coverage': info['incoming_coverage_percentage'],
'rating': info['coverage_rating']
}
for vocab_id, info in vocab_coverage.items()
])
# Sort by outgoing coverage
vocab_df = vocab_df.sort_values('outgoing_coverage', ascending=False)
print(vocab_df.to_string(index=False, float_format='%.1f'))
# Domain coverage analysis
print(f"\n=== DOMAIN COVERAGE ===")
domain_coverage = data['data']['domain_coverage']
domain_df = pd.DataFrame([
{
'domain': info['domain_name'],
'total_concepts': info['total_concepts'],
'coverage_pct': info['coverage_percentage'],
'interop_score': info['interoperability_score'] * 100,
'best_mapped': ', '.join(info['best_mapped_vocabularies'][:2])
}
for domain_id, info in domain_coverage.items()
])
domain_df = domain_df.sort_values('coverage_pct', ascending=False)
print(domain_df.to_string(index=False, float_format='%.1f'))
# Vocabulary pair matrix visualization
if 'vocabulary_pair_matrix' in data['data']:
matrix_data = data['data']['vocabulary_pair_matrix']['matrix_data']
# Create coverage matrix
vocabs = list(set([entry['source_vocabulary'] for entry in matrix_data.values()] +
[entry['target_vocabulary'] for entry in matrix_data.values()]))
matrix = np.zeros((len(vocabs), len(vocabs)))
vocab_to_idx = {vocab: i for i, vocab in enumerate(vocabs)}
for entry in matrix_data.values():
source_idx = vocab_to_idx[entry['source_vocabulary']]
target_idx = vocab_to_idx[entry['target_vocabulary']]
matrix[source_idx, target_idx] = entry['source_coverage_percentage']
# Plot heatmap
plt.figure(figsize=(12, 10))
sns.heatmap(matrix,
xticklabels=vocabs,
yticklabels=vocabs,
annot=True,
fmt='.1f',
cmap='YlOrRd',
cbar_kws={'label': 'Coverage Percentage'})
plt.title('Vocabulary Mapping Coverage Matrix')
plt.xlabel('Target Vocabulary')
plt.ylabel('Source Vocabulary')
plt.xticks(rotation=45, ha='right')
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()
# Quality analysis
if 'quality_analysis' in data['data']:
quality = data['data']['quality_analysis']['overall_quality_metrics']
print(f"\n=== QUALITY ANALYSIS ===")
print(f"Average confidence score: {quality['average_confidence_score']:.3f}")
print(f"High-quality mappings: {quality['high_quality_mappings_percentage']:.1f}%")
print(f"Validated mappings: {quality['validated_mappings_percentage']:.1f}%")
print(f"Disputed mappings: {quality['disputed_mappings_count']:,}")
# Gap analysis
if 'mapping_gaps' in data:
gaps = data['mapping_gaps']
print(f"\n=== CRITICAL GAPS ===")
for gap in gaps['critical_gaps'][:5]:
print(f"{gap['gap_type']}: {gap['affected_concepts']:,} concepts")
print(f" Impact: {gap['business_impact']}")
print(f" Priority: {gap['priority']}")
print()
if gaps.get('gap_patterns'):
print(f"Common Gap Patterns:")
for pattern in gaps['gap_patterns'][:3]:
print(f" - {pattern}")
# Trends analysis
if 'trends' in data:
trends = data['trends']
print(f"\n=== TRENDS ===")
print(f"Coverage improvement rate: {trends['improvement_rate']:.1f}% annually")
if trends.get('fastest_improving_areas'):
print(f"Fastest improving areas:")
for area in trends['fastest_improving_areas'][:3]:
print(f" - {area}")
# Plot coverage trend
if trends.get('coverage_over_time'):
coverage_history = trends['coverage_over_time']
dates = [point['date'] for point in coverage_history]
coverages = [point['overall_coverage_percentage'] for point in coverage_history]
plt.figure(figsize=(12, 6))
plt.plot(dates, coverages, marker='o', linewidth=2, markersize=6)
plt.title('Mapping Coverage Trend Over Time')
plt.xlabel('Date')
plt.ylabel('Coverage Percentage')
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Recommendations
if 'recommendations' in data:
print(f"\n=== RECOMMENDATIONS ===")
recommendations = data['recommendations']
for i, rec in enumerate(recommendations[:5], 1):
print(f"{i}. {rec['title']} ({rec['priority']} Priority)")
print(f" {rec['description']}")
print(f" Impact: {rec['estimated_impact']}")
print(f" Complexity: {rec['implementation_complexity']}")
if rec.get('affected_areas'):
print(f" Affects: {', '.join(rec['affected_areas'][:3])}")
print()
# Create summary dashboard
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
# 1. Vocabulary coverage bar chart
vocab_df_plot = vocab_df.head(10)
ax1.barh(vocab_df_plot['name'], vocab_df_plot['outgoing_coverage'])
ax1.set_xlabel('Coverage Percentage')
ax1.set_title('Top 10 Vocabularies by Outgoing Coverage')
ax1.set_xlim(0, 100)
# 2. Domain coverage pie chart
ax2.pie(domain_df['coverage_pct'], labels=domain_df['domain'], autopct='%1.1f%%')
ax2.set_title('Coverage Distribution by Domain')
# 3. Coverage vs Interoperability scatter
ax3.scatter(domain_df['coverage_pct'], domain_df['interop_score'], s=100, alpha=0.7)
for i, row in domain_df.iterrows():
ax3.annotate(row['domain'], (row['coverage_pct'], row['interop_score']),
xytext=(5, 5), textcoords='offset points', fontsize=8)
ax3.set_xlabel('Coverage Percentage')
ax3.set_ylabel('Interoperability Score')
ax3.set_title('Coverage vs Interoperability by Domain')
# 4. Quality metrics
if 'quality_analysis' in data:
quality_metrics = ['High Quality', 'Validated', 'Disputed']
quality_values = [
quality['high_quality_mappings_percentage'],
quality['validated_mappings_percentage'],
quality['disputed_mappings_count'] / overview['total_mapping_relationships'] * 100
]
ax4.bar(quality_metrics, quality_values)
ax4.set_ylabel('Percentage')
ax4.set_title('Mapping Quality Distribution')
ax4.set_ylim(0, 100)
plt.tight_layout()
plt.show()
```
## Response Example
```json
{
"coverage_overview": {
"total_concepts_analyzed": 2847562,
"concepts_with_mappings": 1678934,
"concepts_without_mappings": 1168628,
"overall_coverage_percentage": 59.0,
"unique_vocabulary_pairs": 147,
"total_mapping_relationships": 4578923
},
"vocabulary_coverage": {
"SNOMED": {
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"total_concepts": 387421,
"concepts_with_outgoing_mappings": 234567,
"concepts_with_incoming_mappings": 89234,
"outgoing_coverage_percentage": 60.5,
"incoming_coverage_percentage": 23.0,
"mapping_targets": ["ICD10CM", "ICD10", "HCPCS", "ICD9CM"],
"mapping_sources": ["Read", "ICD9CM", "ICD10"],
"coverage_by_domain": {
"Condition": 78.2,
"Procedure": 45.7,
"Observation": 23.1
},
"coverage_rating": "good"
},
"ICD10CM": {
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"total_concepts": 98234,
"concepts_with_outgoing_mappings": 87432,
"concepts_with_incoming_mappings": 87432,
"outgoing_coverage_percentage": 89.0,
"incoming_coverage_percentage": 89.0,
"mapping_targets": ["SNOMED", "ICD10"],
"mapping_sources": ["SNOMED", "ICD9CM"],
"coverage_by_domain": {
"Condition": 92.1,
"Procedure": 85.4
},
"coverage_rating": "excellent"
}
},
"domain_coverage": {
"Condition": {
"domain_name": "Condition",
"total_concepts": 845672,
"concepts_with_mappings": 623891,
"coverage_percentage": 73.8,
"vocabulary_coverage": {
"SNOMED": 78.2,
"ICD10CM": 92.1,
"ICD10": 87.5,
"ICD9CM": 45.3
},
"best_mapped_vocabularies": ["ICD10CM", "ICD10", "SNOMED"],
"poorly_mapped_vocabularies": ["Read", "ICPC2"],
"interoperability_score": 0.82
},
"Drug": {
"domain_name": "Drug",
"total_concepts": 652341,
"concepts_with_mappings": 298765,
"coverage_percentage": 45.8,
"vocabulary_coverage": {
"RxNorm": 67.3,
"NDC": 89.2,
"SNOMED": 23.4
},
"best_mapped_vocabularies": ["NDC", "RxNorm"],
"poorly_mapped_vocabularies": ["SNOMED", "ATC"],
"interoperability_score": 0.61
}
},
"vocabulary_pair_matrix": {
"strongest_pairs": [
{
"source_vocabulary": "ICD10CM",
"target_vocabulary": "SNOMED",
"source_coverage_percentage": 89.0,
"target_coverage_percentage": 67.2,
"bidirectional_coverage": true
},
{
"source_vocabulary": "RxNorm",
"target_vocabulary": "NDC",
"source_coverage_percentage": 78.5,
"target_coverage_percentage": 89.2,
"bidirectional_coverage": true
}
],
"weakest_pairs": [
{
"source_vocabulary": "LOINC",
"target_vocabulary": "HCPCS",
"source_coverage_percentage": 12.3,
"target_coverage_percentage": 8.7,
"bidirectional_coverage": false
}
],
"matrix_data": {
"ICD10CM_SNOMED": {
"source_vocabulary": "ICD10CM",
"target_vocabulary": "SNOMED",
"mapping_count": 8934,
"source_coverage_percentage": 89.0,
"target_coverage_percentage": 67.2,
"bidirectional_coverage": true
},
"RxNorm_NDC": {
"source_vocabulary": "RxNorm",
"target_vocabulary": "NDC",
"mapping_count": 12456,
"source_coverage_percentage": 78.5,
"target_coverage_percentage": 89.2,
"bidirectional_coverage": true
},
"LOINC_HCPCS": {
"source_vocabulary": "LOINC",
"target_vocabulary": "HCPCS",
"mapping_count": 234,
"source_coverage_percentage": 12.3,
"target_coverage_percentage": 8.7,
"bidirectional_coverage": false
}
}
},
"mapping_gaps": {
"critical_gaps": [
{
"gap_type": "Domain Coverage Gap",
"description": "Observation domain concepts have limited cross-vocabulary mappings",
"affected_concepts": 234567,
"business_impact": "Reduced interoperability for lab results and vital signs",
"priority": "high"
},
{
"gap_type": "Vocabulary Isolation",
"description": "LOINC concepts have minimal mappings to billing vocabularies",
"affected_concepts": 89234,
"business_impact": "Difficulty translating lab orders to billing codes",
"priority": "medium"
}
],
"gap_patterns": [
"Specialized medical devices lack standard mappings",
"Research-focused concepts often unmapped to clinical vocabularies",
"Regional vocabulary variations create mapping inconsistencies"
],
"orphaned_vocabularies": ["ICPC2", "Read", "Local vocabularies"]
},
"quality_analysis": {
"overall_quality_metrics": {
"average_confidence_score": 0.847,
"high_quality_mappings_percentage": 72.3,
"validated_mappings_percentage": 68.9,
"disputed_mappings_count": 12456
},
"quality_by_domain": {
"Condition": {
"average_confidence": 0.891,
"validation_rate": 78.2
},
"Drug": {
"average_confidence": 0.823,
"validation_rate": 65.7
}
}
},
"trends": {
"coverage_over_time": [
{
"date": "2020-01-01T00:00:00Z",
"overall_coverage_percentage": 52.3,
"new_mappings_added": 45678,
"mappings_deprecated": 3456
},
{
"date": "2021-01-01T00:00:00Z",
"overall_coverage_percentage": 55.1,
"new_mappings_added": 67890,
"mappings_deprecated": 4567
},
{
"date": "2024-01-01T00:00:00Z",
"overall_coverage_percentage": 59.0,
"new_mappings_added": 123456,
"mappings_deprecated": 7890
}
],
"improvement_rate": 2.1,
"fastest_improving_areas": [
"Drug domain RxNorm mappings",
"ICD10CM to SNOMED bidirectional coverage",
"Procedure domain standardization"
]
},
"recommendations": [
{
"recommendation_type": "vocabulary_expansion",
"title": "Expand LOINC to HCPCS mappings for lab procedures",
"description": "Create systematic mappings between LOINC lab concepts and corresponding HCPCS billing codes to improve lab order interoperability",
"priority": "high",
"estimated_impact": "Improve lab domain coverage by 15-20%",
"affected_areas": ["LOINC", "HCPCS", "Measurement domain"],
"implementation_complexity": "moderate"
},
{
"recommendation_type": "quality_improvement",
"title": "Review and validate disputed mappings",
"description": "Systematic review of 12,456 disputed mappings to improve overall mapping quality",
"priority": "medium",
"estimated_impact": "Increase validated mapping percentage to 75%+",
"affected_areas": ["All vocabularies"],
"implementation_complexity": "high"
}
],
"meta": {
"analysis_date": "2024-12-22T10:00:00Z",
"data_freshness": "2024-12-22T08:30:00Z",
"analysis_scope": {
"vocabularies_included": 23,
"domains_analyzed": 15,
"concept_classes_analyzed": 156
},
"calculation_time_ms": 15247.8
}
}
```
## Usage Examples
### Overall Coverage Analysis
Get comprehensive mapping coverage overview:
```javascript
const coverage = await fetch('/v1/mappings/coverage');
```
### Domain-Specific Coverage
Analyze coverage for specific medical domains:
```javascript
const clinicalCoverage = await fetch('/v1/mappings/coverage?domain_ids=Condition,Drug,Procedure&include_detailed_gaps=true');
```
### Quality-Focused Analysis
Get coverage analysis with quality metrics:
```javascript
const qualityCoverage = await fetch('/v1/mappings/coverage?include_quality_analysis=true&coverage_threshold=80');
```
### Vocabulary-Specific Coverage
Analyze coverage for specific vocabularies:
```javascript
const snomedCoverage = await fetch('/v1/mappings/coverage?source_vocabularies=SNOMED&target_vocabularies=ICD10CM,HCPCS&include_recommendations=true');
```
### Trend Analysis
Get historical coverage trends:
```javascript
const trendAnalysis = await fetch('/v1/mappings/coverage?include_trends=true&include_recommendations=true');
```
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Mappings for specific concepts
* [Get Vocabulary Mappings](/api-reference/mappings/get-vocabulary-mappings) - All mappings between vocabularies
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
* [Get Domains](/api-reference/domains/get-domains) - Available domains
## Notes
* Coverage analysis is computationally intensive and may take longer for comprehensive queries
* Quality analysis requires mapping metadata and may not be available for all vocabulary pairs
* Trends analysis requires historical data spanning multiple vocabulary releases
* Recommendations are generated based on gap analysis and usage patterns
* Coverage thresholds help identify vocabularies needing mapping improvements
* Orphaned vocabularies may require custom mapping strategies
* Domain coverage varies significantly based on vocabulary specialization
* Regular coverage monitoring helps identify degradation over time
# Get Mapping Quality
Source: https://docs.omophub.com/api-reference/mappings/get-mapping-quality
GET /v1/mappings/quality
Analyze mapping quality across vocabularies, domains, and concept classes to assess reliability and trustworthiness of cross-vocabulary translations
This endpoint provides comprehensive analysis of mapping quality throughout the vocabulary ecosystem, essential for understanding the reliability of cross-vocabulary translations, identifying high-confidence mappings, and assessing the trustworthiness of mapping relationships for clinical and operational use.
## Query Parameters
Filter analysis to specific source vocabularies
**Example:** `SNOMED,ICD10CM,RxNorm`
Filter analysis to specific target vocabularies
**Example:** `ICD10CM,HCPCS,NDC`
Focus analysis on specific domains
**Example:** `Condition,Drug,Procedure`
Focus analysis on specific concept classes
**Example:** `Clinical Finding,Procedure,Ingredient`
Filter by mapping equivalence types
**Example:** `exact,broader,narrower,related`
Filter by mapping validation status
**Options:** `validated`, `pending`, `disputed`, `deprecated`
Filter by mapping source types
**Example:** `official,community,algorithmic,manual`
Minimum confidence score for inclusion (0-1)
Include detailed quality metrics and distributions
Include analysis of quality outliers and anomalies
Include historical quality trends
Include recommendations for quality improvement
Maximum number of mappings to analyze for detailed metrics
**Default:** No limit
## Response
High-level summary of mapping quality
Total number of mappings included in analysis
Mappings that have quality metadata
Overall average confidence score across all mappings
Overall quality assessment
**Values:** `excellent`, `good`, `moderate`, `poor`
Percentage of mappings with confidence > 0.8 (0-100%)
Percentage of officially validated mappings (0-100%)
Percentage of disputed or problematic mappings
Quality metrics for each vocabulary pair
Quality metrics for each source vocabulary
Human-readable vocabulary name
Quality metrics by target vocabulary
Quality metrics for this vocabulary pair
Target vocabulary name
Number of mappings between these vocabularies
Average confidence score for this pair
Median confidence score
Standard deviation of confidence scores
Distribution of equivalence types
Rate of validated mappings (0.0-1.0 fraction)
Quality rating for this vocabulary pair
Main use cases for these mappings
Average quality of outgoing mappings
Quality analysis by medical domain
Quality metrics for each domain
Human-readable domain name
Number of mappings in this domain
Average confidence score for domain
Distribution of quality levels
Mappings with confidence score of 0.9 or higher
Mappings with confidence score between 0.7 and 0.9
Mappings with confidence score between 0.5 and 0.7
Mappings with confidence score below 0.5
Vocabulary pairs with highest quality in this domain
Vocabulary pairs with lowest quality in this domain
Common quality issues in this domain
Detailed quality metrics and distributions
Distribution of confidence scores
Confidence score ranges and counts
Key percentile values
Skewness of confidence distribution
Kurtosis of confidence distribution
Analysis of semantic similarity scores
Average semantic similarity score
Correlation between similarity and confidence
Mappings with low similarity but high confidence
Mappings with high similarity but low confidence
Quality breakdown by mapping source
Quality metrics for official mappings
Quality metrics for community mappings
Quality metrics for algorithmic mappings
Quality metrics for manually created mappings
Impact of validation on quality perception
Quality comparison between validated and unvalidated
Accuracy of validation process
Common characteristics of disputed mappings
Analysis of quality outliers and anomalies (when include\_outliers=true)
Mappings with unusually low quality scores
Source concept identifier
Target concept identifier
Confidence score
Why this is considered an outlier
Potential quality issues identified
Mappings with exceptionally high quality
Mappings with inconsistent quality indicators
Common patterns in outlier mappings
Historical quality trends (when include\_trends=true)
Time series of quality improvements
Date of measurement
Average confidence at this time
Validation rate at this time
Quality of newly added mappings
Annual rate of quality improvement (0.0-1.0 fraction)
Areas with most rapid quality improvement
Areas with declining quality
Recommendations for quality improvement (when include\_recommendations=true)
Type of quality improvement recommendation
Brief title of the recommendation
Detailed description
Implementation priority
Expected quality improvement
Vocabulary pairs that would benefit
Estimated implementation effort
Analysis metadata and processing information
Date when quality analysis was performed
Timestamp of underlying mapping data
Scope of the quality analysis
Time taken to perform analysis
Method used to calculate confidence scores
```bash cURL
curl -X GET "https://api.omophub.com/v1/mappings/quality?domain_ids=Condition,Drug&include_detailed_metrics=true&include_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/mappings/quality?domain_ids=Condition,Drug&include_detailed_metrics=true&include_recommendations=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const qualityData = await response.json();
const { quality_overview, quality_by_domain, quality_metrics } = qualityData.data;
console.log('=== Mapping Quality Analysis ===');
console.log(`Total mappings analyzed: ${quality_overview.total_mappings_analyzed.toLocaleString()}`);
console.log(`Overall quality rating: ${quality_overview.overall_quality_rating}`);
console.log(`Average confidence: ${(quality_overview.overall_average_confidence * 100).toFixed(1)}%`);
console.log(`High-quality mappings: ${quality_overview.high_quality_percentage.toFixed(1)}%`);
console.log(`Validated mappings: ${quality_overview.validated_percentage.toFixed(1)}%`);
// Display domain quality
console.log('\n=== Quality by Domain ===');
Object.entries(quality_by_domain).forEach(([domainId, domainData]) => {
console.log(`${domainData.domain_name}:`);
console.log(` Average confidence: ${(domainData.average_confidence * 100).toFixed(1)}%`);
console.log(` Total mappings: ${domainData.total_mappings.toLocaleString()}`);
const dist = domainData.quality_distribution;
console.log(` Quality distribution:`);
console.log(` Excellent: ${dist.excellent.toLocaleString()}`);
console.log(` Good: ${dist.good.toLocaleString()}`);
console.log(` Moderate: ${dist.moderate.toLocaleString()}`);
console.log(` Poor: ${dist.poor.toLocaleString()}`);
if (domainData.most_reliable_vocabulary_pairs.length > 0) {
console.log(` Most reliable pairs: ${domainData.most_reliable_vocabulary_pairs.slice(0, 3).join(', ')}`);
}
if (domainData.quality_challenges.length > 0) {
console.log(` Quality challenges: ${domainData.quality_challenges.slice(0, 2).join(', ')}`);
}
});
// Display vocabulary pair quality
console.log('\n=== Top Quality Vocabulary Pairs ===');
const vocabPairs = [];
Object.entries(qualityData.data.quality_by_vocabulary_pair).forEach(([sourceVocab, sourceData]) => {
Object.entries(sourceData.target_vocabularies).forEach(([targetVocab, pairData]) => {
vocabPairs.push({
pair: `${sourceVocab} → ${targetVocab}`,
confidence: pairData.average_confidence,
mappings: pairData.total_mappings,
rating: pairData.quality_rating
});
});
});
vocabPairs.sort((a, b) => b.confidence - a.confidence)
.slice(0, 10)
.forEach(pair => {
console.log(`${pair.pair}: ${(pair.confidence * 100).toFixed(1)}% avg confidence (${pair.mappings.toLocaleString()} mappings, ${pair.rating})`);
});
// Display quality metrics
if (quality_metrics) {
console.log('\n=== Quality Metrics ===');
if (quality_metrics.confidence_score_distribution) {
const confDist = quality_metrics.confidence_score_distribution;
console.log('Confidence Score Statistics:');
console.log(` Mean: ${(qualityData.data.quality_overview.overall_average_confidence * 100).toFixed(1)}%`);
if (confDist.percentiles) {
console.log(` 25th percentile: ${(confDist.percentiles['25'] * 100).toFixed(1)}%`);
console.log(` 50th percentile: ${(confDist.percentiles['50'] * 100).toFixed(1)}%`);
console.log(` 75th percentile: ${(confDist.percentiles['75'] * 100).toFixed(1)}%`);
}
}
if (quality_metrics.mapping_source_analysis) {
console.log('\nQuality by Mapping Source:');
Object.entries(quality_metrics.mapping_source_analysis).forEach(([source, data]) => {
if (data.average_confidence) {
console.log(` ${source}: ${(data.average_confidence * 100).toFixed(1)}% avg confidence`);
}
});
}
}
// Display recommendations
if (qualityData.data.recommendations) {
console.log('\n=== Quality Improvement Recommendations ===');
qualityData.data.recommendations.slice(0, 5).forEach((rec, i) => {
console.log(`${i + 1}. ${rec.title} (${rec.priority} priority)`);
console.log(` ${rec.description}`);
console.log(` Expected improvement: ${rec.expected_improvement}`);
if (rec.affected_vocabulary_pairs.length > 0) {
console.log(` Affects: ${rec.affected_vocabulary_pairs.slice(0, 3).join(', ')}`);
}
});
}
```
```python Python
import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
url = "https://api.omophub.com/v1/mappings/quality"
params = {
"domain_ids": "Condition,Drug,Procedure",
"include_detailed_metrics": True,
"include_outliers": True,
"include_trends": True,
"include_recommendations": True,
"confidence_threshold": 0.5
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print("=== MAPPING QUALITY ANALYSIS ===")
overview = data['data']['quality_overview']
print(f"Total mappings analyzed: {overview['total_mappings_analyzed']:,}")
print(f"Overall quality rating: {overview['overall_quality_rating']}")
print(f"Average confidence: {overview['overall_average_confidence']:.3f}")
print(f"High-quality mappings: {overview['high_quality_percentage']:.1f}%")
print(f"Validated mappings: {overview['validated_percentage']:.1f}%")
print(f"Disputed mappings: {overview['disputed_percentage']:.1f}%")
# Domain quality analysis
print(f"\n=== QUALITY BY DOMAIN ===")
domain_quality = data['data']['quality_by_domain']
domain_df = pd.DataFrame([
{
'domain': info['domain_name'],
'total_mappings': info['total_mappings'],
'avg_confidence': info['average_confidence'],
'excellent': info['quality_distribution']['excellent'],
'good': info['quality_distribution']['good'],
'moderate': info['quality_distribution']['moderate'],
'poor': info['quality_distribution']['poor']
}
for domain_id, info in domain_quality.items()
])
domain_df = domain_df.sort_values('avg_confidence', ascending=False)
print(domain_df.to_string(index=False, float_format='%.3f'))
# Vocabulary pair quality analysis
print(f"\n=== VOCABULARY PAIR QUALITY ===")
vocab_pairs = []
for source_vocab, source_data in data['data']['quality_by_vocabulary_pair'].items():
for target_vocab, pair_data in source_data['target_vocabularies'].items():
vocab_pairs.append({
'source': source_vocab,
'target': target_vocab,
'pair': f"{source_vocab} → {target_vocab}",
'avg_confidence': pair_data['average_confidence'],
'total_mappings': pair_data['total_mappings'],
'validation_rate': pair_data['validation_rate'],
'quality_rating': pair_data['quality_rating']
})
vocab_pair_df = pd.DataFrame(vocab_pairs)
vocab_pair_df = vocab_pair_df.sort_values('avg_confidence', ascending=False)
print("Top 10 Quality Vocabulary Pairs:")
top_pairs = vocab_pair_df.head(10)
for _, row in top_pairs.iterrows():
print(f"{row['pair']}: {row['avg_confidence']:.3f} confidence, {row['total_mappings']:,} mappings ({row['quality_rating']})")
print("\nBottom 5 Quality Vocabulary Pairs:")
bottom_pairs = vocab_pair_df.tail(5)
for _, row in bottom_pairs.iterrows():
print(f"{row['pair']}: {row['avg_confidence']:.3f} confidence, {row['total_mappings']:,} mappings ({row['quality_rating']})")
# Quality metrics analysis
if 'quality_metrics' in data['data']:
metrics = data['data']['quality_metrics']
print(f"\n=== DETAILED QUALITY METRICS ===")
# Confidence distribution
if 'confidence_score_distribution' in metrics:
conf_dist = metrics['confidence_score_distribution']
print(f"Confidence Score Statistics:")
if 'percentiles' in conf_dist:
for percentile, value in conf_dist['percentiles'].items():
print(f" {percentile}th percentile: {value:.3f}")
if 'skewness' in conf_dist:
print(f" Skewness: {conf_dist['skewness']:.3f}")
if 'kurtosis' in conf_dist:
print(f" Kurtosis: {conf_dist['kurtosis']:.3f}")
# Semantic similarity analysis
if 'semantic_similarity_analysis' in metrics:
sem_analysis = metrics['semantic_similarity_analysis']
print(f"\nSemantic Similarity Analysis:")
print(f" Average similarity: {sem_analysis['average_similarity']:.3f}")
print(f" Similarity-confidence correlation: {sem_analysis['similarity_confidence_correlation']:.3f}")
print(f" Low similarity, high confidence: {sem_analysis['low_similarity_high_confidence']:,}")
print(f" High similarity, low confidence: {sem_analysis['high_similarity_low_confidence']:,}")
# Mapping source analysis
if 'mapping_source_analysis' in metrics:
source_analysis = metrics['mapping_source_analysis']
print(f"\nQuality by Mapping Source:")
for source, source_data in source_analysis.items():
if 'average_confidence' in source_data:
print(f" {source}: {source_data['average_confidence']:.3f} avg confidence")
# Outlier analysis
if 'quality_outliers' in data['data']:
outliers = data['data']['quality_outliers']
print(f"\n=== QUALITY OUTLIERS ===")
if outliers.get('low_quality_outliers'):
print(f"Low Quality Outliers ({len(outliers['low_quality_outliers'])}):")
for outlier in outliers['low_quality_outliers'][:5]:
print(f" Concepts {outlier['source_concept_id']} → {outlier['target_concept_id']}")
print(f" Confidence: {outlier['confidence_score']:.3f}")
print(f" Reason: {outlier['outlier_reason']}")
if outliers.get('outlier_patterns'):
print(f"\nCommon Outlier Patterns:")
for pattern in outliers['outlier_patterns'][:3]:
print(f" - {pattern}")
# Trends analysis
if 'quality_trends' in data['data']:
trends = data['data']['quality_trends']
print(f"\n=== QUALITY TRENDS ===")
print(f"Quality improvement rate: {trends['quality_improvement_rate']:.2f}% annually")
if trends.get('fastest_improving_areas'):
print(f"Fastest improving areas:")
for area in trends['fastest_improving_areas'][:3]:
print(f" - {area}")
# Plot quality trend
if trends.get('quality_over_time'):
quality_history = trends['quality_over_time']
dates = [point['date'] for point in quality_history]
confidences = [point['average_confidence'] for point in quality_history]
validation_rates = [point['validation_rate'] for point in quality_history]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))
# Confidence trend
ax1.plot(dates, confidences, marker='o', linewidth=2)
ax1.set_title('Average Confidence Score Over Time')
ax1.set_ylabel('Confidence Score')
ax1.tick_params(axis='x', rotation=45)
ax1.grid(True, alpha=0.3)
# Validation rate trend
ax2.plot(dates, validation_rates, marker='s', linewidth=2, color='green')
ax2.set_title('Validation Rate Over Time')
ax2.set_xlabel('Date')
ax2.set_ylabel('Validation Rate')
ax2.tick_params(axis='x', rotation=45)
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# Recommendations
if 'recommendations' in data['data']:
print(f"\n=== QUALITY IMPROVEMENT RECOMMENDATIONS ===")
recommendations = data['data']['recommendations']
for i, rec in enumerate(recommendations[:7], 1):
print(f"{i}. {rec['title']} ({rec['priority']} Priority)")
print(f" {rec['description']}")
print(f" Expected improvement: {rec['expected_improvement']}")
print(f" Implementation effort: {rec['implementation_effort']}")
if rec.get('affected_vocabulary_pairs'):
print(f" Affects: {', '.join(rec['affected_vocabulary_pairs'][:3])}")
print()
# Create comprehensive quality dashboard
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
# 1. Domain quality comparison
domain_names = domain_df['domain']
domain_confidences = domain_df['avg_confidence']
ax1.barh(domain_names, domain_confidences, color='skyblue')
ax1.set_xlabel('Average Confidence Score')
ax1.set_title('Quality by Medical Domain')
ax1.set_xlim(0, 1)
# 2. Quality distribution pie chart
quality_counts = [
domain_df['excellent'].sum(),
domain_df['good'].sum(),
domain_df['moderate'].sum(),
domain_df['poor'].sum()
]
quality_labels = ['Excellent', 'Good', 'Moderate', 'Poor']
colors = ['#2ecc71', '#3498db', '#f39c12', '#e74c3c']
ax2.pie(quality_counts, labels=quality_labels, colors=colors, autopct='%1.1f%%')
ax2.set_title('Overall Quality Distribution')
# 3. Vocabulary pair quality heatmap
if len(vocab_pair_df) > 0:
# Create pivot table for heatmap
pivot_data = vocab_pair_df.pivot_table(
values='avg_confidence',
index='source',
columns='target',
fill_value=0
)
sns.heatmap(pivot_data, annot=True, fmt='.2f', cmap='RdYlGn', ax=ax3)
ax3.set_title('Vocabulary Pair Quality Matrix')
ax3.set_xlabel('Target Vocabulary')
ax3.set_ylabel('Source Vocabulary')
# 4. Confidence score histogram
if 'quality_metrics' in data['data'] and 'confidence_score_distribution' in data['data']['quality_metrics']:
conf_dist = data['data']['quality_metrics']['confidence_score_distribution']
if 'histogram_bins' in conf_dist:
bins = conf_dist['histogram_bins']
bin_centers = [(bin['min'] + bin['max']) / 2 for bin in bins]
bin_counts = [bin['count'] for bin in bins]
ax4.bar(bin_centers, bin_counts, width=0.05, alpha=0.7, color='lightcoral')
ax4.set_xlabel('Confidence Score')
ax4.set_ylabel('Number of Mappings')
ax4.set_title('Confidence Score Distribution')
ax4.set_xlim(0, 1)
plt.tight_layout()
plt.show()
# Summary report
print(f"\n=== SUMMARY REPORT ===")
print(f"• Analyzed {overview['total_mappings_analyzed']:,} mappings across {len(domain_quality)} domains")
print(f"• Overall system quality rating: {overview['overall_quality_rating'].upper()}")
print(f"• {overview['high_quality_percentage']:.1f}% of mappings are high-quality (confidence > 0.8)")
print(f"• {overview['validated_percentage']:.1f}% of mappings are officially validated")
print(f"• Top quality domain: {domain_df.iloc[0]['domain']} ({domain_df.iloc[0]['avg_confidence']:.3f} avg confidence)")
print(f"• Best vocabulary pair: {vocab_pair_df.iloc[0]['pair']} ({vocab_pair_df.iloc[0]['avg_confidence']:.3f} confidence)")
if 'recommendations' in data['data']:
high_priority_recs = [r for r in data['data']['recommendations'] if r['priority'] == 'high']
print(f"• {len(high_priority_recs)} high-priority improvement recommendations identified")
```
```json
{
"success": true,
"data": {
"quality_overview": {
"total_mappings_analyzed": 4578923,
"mappings_with_quality_data": 3456789,
"overall_average_confidence": 0.847,
"overall_quality_rating": "good",
"high_quality_percentage": 72.3,
"validated_percentage": 68.9,
"disputed_percentage": 2.7
},
"quality_by_vocabulary_pair": {
"SNOMED": {
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"target_vocabularies": {
"ICD10CM": {
"target_vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"total_mappings": 156789,
"average_confidence": 0.923,
"median_confidence": 0.950,
"confidence_std_dev": 0.087,
"equivalence_distribution": {
"exact": 89234,
"broader": 45678,
"narrower": 12345,
"related": 9532
},
"validation_rate": 0.847,
"quality_rating": "excellent",
"primary_use_cases": ["billing", "quality_reporting", "clinical_documentation"]
},
"HCPCS": {
"target_vocabulary_name": "Healthcare Common Procedure Coding System",
"total_mappings": 67890,
"average_confidence": 0.756,
"median_confidence": 0.780,
"confidence_std_dev": 0.145,
"equivalence_distribution": {
"exact": 23456,
"broader": 34567,
"narrower": 6789,
"related": 3078
},
"validation_rate": 0.623,
"quality_rating": "good",
"primary_use_cases": ["billing", "procedure_coding"]
}
},
"overall_outgoing_quality": 0.875
}
},
"quality_by_domain": {
"Condition": {
"domain_name": "Condition",
"total_mappings": 2847562,
"average_confidence": 0.891,
"quality_distribution": {
"excellent": 1456789,
"good": 987654,
"moderate": 345678,
"poor": 57441
},
"most_reliable_vocabulary_pairs": [
"SNOMED → ICD10CM",
"ICD10CM → SNOMED",
"SNOMED → ICD10"
],
"least_reliable_vocabulary_pairs": [
"Read → ICPC2",
"Local → Standard"
],
"quality_challenges": [
"Complex conditions often map to broader categories",
"Regional terminology variations affect consistency",
"Rare diseases have limited mapping coverage"
]
},
"Drug": {
"domain_name": "Drug",
"total_mappings": 987654,
"average_confidence": 0.823,
"quality_distribution": {
"excellent": 456789,
"good": 345678,
"moderate": 123456,
"poor": 61731
},
"most_reliable_vocabulary_pairs": [
"RxNorm → NDC",
"NDC → RxNorm"
],
"least_reliable_vocabulary_pairs": [
"SNOMED → RxNorm",
"Local drug codes → RxNorm"
],
"quality_challenges": [
"Generic vs brand name mapping complexity",
"Dosage form variations affect precision",
"Discontinued medications create gaps"
]
}
},
"quality_metrics": {
"confidence_score_distribution": {
"histogram_bins": [
{"min": 0.0, "max": 0.1, "count": 12456},
{"min": 0.1, "max": 0.2, "count": 23456},
{"min": 0.8, "max": 0.9, "count": 456789},
{"min": 0.9, "max": 1.0, "count": 678901}
],
"percentiles": {
"25": 0.734,
"50": 0.847,
"75": 0.923,
"95": 0.978
},
"skewness": -0.342,
"kurtosis": 2.156
},
"semantic_similarity_analysis": {
"average_similarity": 0.823,
"similarity_confidence_correlation": 0.756,
"low_similarity_high_confidence": 23456,
"high_similarity_low_confidence": 12345
},
"mapping_source_analysis": {
"official_mappings": {
"average_confidence": 0.912,
"total_count": 2345678,
"validation_rate": 0.923
},
"community_mappings": {
"average_confidence": 0.734,
"total_count": 456789,
"validation_rate": 0.456
},
"algorithmic_mappings": {
"average_confidence": 0.678,
"total_count": 567890,
"validation_rate": 0.234
}
}
},
"quality_outliers": {
"low_quality_outliers": [
{
"source_concept_id": 12345,
"target_concept_id": 67890,
"confidence_score": 0.123,
"outlier_reason": "Very low confidence despite official mapping",
"potential_issues": [
"Semantic mismatch",
"Outdated mapping relationship"
]
}
],
"outlier_patterns": [
"Algorithmic mappings between distant concept classes",
"Legacy mappings not updated with vocabulary revisions",
"Cross-domain mappings with semantic drift"
]
},
"recommendations": [
{
"recommendation_type": "validation_review",
"title": "Review disputed SNOMED to HCPCS mappings",
"description": "Systematic review of 2,847 disputed mappings between SNOMED procedures and HCPCS codes to improve validation rate",
"priority": "high",
"expected_improvement": "Increase HCPCS mapping quality by 15-20%",
"affected_vocabulary_pairs": ["SNOMED → HCPCS"],
"implementation_effort": "moderate"
},
{
"recommendation_type": "algorithmic_improvement",
"title": "Enhance semantic similarity algorithms for drug mappings",
"description": "Improve algorithmic mapping quality between drug vocabularies using enhanced semantic similarity models",
"priority": "medium",
"expected_improvement": "Increase drug domain confidence by 10%",
"affected_vocabulary_pairs": ["RxNorm → NDC", "SNOMED → RxNorm"],
"implementation_effort": "high"
}
]
},
"meta": {
"request_id": "req_quality_analysis_234567",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2",
"analysis_scope": {
"vocabularies_included": 23,
"domains_analyzed": 15,
"mapping_relationships_analyzed": 4578923
},
"confidence_calculation_method": "weighted_semantic_similarity_with_validation"
}
}
```
## Usage Examples
### Overall Quality Assessment
Get comprehensive quality overview:
```javascript
const quality = await fetch('/v1/mappings/quality');
```
### Domain-Specific Quality Analysis
Analyze quality for clinical domains:
```javascript
const clinicalQuality = await fetch('/v1/mappings/quality?domain_ids=Condition,Drug,Procedure&include_detailed_metrics=true');
```
### High-Confidence Mappings Only
Analyze only high-quality mappings:
```javascript
const highQuality = await fetch('/v1/mappings/quality?confidence_threshold=0.8&include_outliers=true');
```
### Vocabulary Pair Quality
Focus on specific vocabulary relationships:
```javascript
const vocabQuality = await fetch('/v1/mappings/quality?source_vocabularies=SNOMED&target_vocabularies=ICD10CM,HCPCS&include_recommendations=true');
```
### Quality Trend Analysis
Get historical quality trends:
```javascript
const trendAnalysis = await fetch('/v1/mappings/quality?include_trends=true&include_recommendations=true');
```
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Individual concept mapping quality
* [Get Vocabulary Mappings](/api-reference/mappings/get-vocabulary-mappings) - Quality within vocabulary pairs
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Coverage vs quality analysis
* [Bulk Concept Mappings](/api-reference/mappings/bulk-concept-mappings) - Quality in bulk operations
## Notes
* Quality analysis requires substantial computational resources for large datasets
* Confidence scores are calculated using multiple factors including semantic similarity and validation status
* Official mappings generally have higher quality than algorithmic or community mappings
* Quality can vary significantly between vocabulary pairs and domains
* Disputed mappings may indicate areas needing expert review
* Quality trends help identify improvement or degradation over time
* Outlier analysis reveals mappings that may need special attention
* Quality thresholds help focus on the most reliable mappings for production use
# Get Vocabulary Mappings
Source: https://docs.omophub.com/api-reference/mappings/get-vocabulary-mappings
GET /v1/mappings/vocabularies/{source_vocabulary_id}/{target_vocabulary_id}
Retrieve all mappings between two specific vocabularies, providing comprehensive cross-vocabulary translation capabilities
This endpoint provides bulk access to all mapping relationships between two vocabularies, essential for data integration, terminology harmonization, and cross-system interoperability. It's particularly useful for understanding mapping coverage and implementing vocabulary translation services.
## Path Parameters
The source vocabulary identifier
**Example:** `SNOMED`, `ICD10CM`, `RxNorm`
The target vocabulary identifier
**Example:** `ICD10CM`, `ICD10`, `HCPCS`
## Query Parameters
Filter mappings to specific domains
**Example:** `Condition,Drug,Procedure`
Filter mappings to specific concept classes
**Example:** `Clinical Finding,Procedure`
Filter by specific mapping relationship types
**Default:** `Maps to,Mapped from`
**Example:** `Maps to,Maps to value`
Filter by standard concept status
**Options:** `source`, `target`, `both`, `either`, `none`
Include mapping quality scores and metadata
Include statistical analysis of the mapping set
Include analysis of unmapped concepts (gaps)
Minimum mapping quality score (0-1)
**Example:** `0.8`
Filter by mapping equivalence types
**Example:** `exact,broader,narrower`
Only include mappings between active concepts
Sort mappings by specified field
**Options:** `source_concept_id`, `target_concept_id`, `source_concept_name`, `mapping_quality`
Sort order for mappings
**Options:** `asc`, `desc`
Number of mappings to return per page (max 5000)
Page number for pagination (1-based)
## Response
Information about the source and target vocabularies
Source vocabulary information
Vocabulary identifier
Human-readable vocabulary name
Official reference or URL
Current version
Total concepts in vocabulary
Target vocabulary information (same structure as source)
Description of the mapping relationship between vocabularies
Array of mapping relationships between the vocabularies
Unique identifier for this mapping
Source vocabulary concept
Source concept identifier
Source concept name
Source concept code
Source concept domain
Source concept class
Standard concept designation
Target vocabulary concept (same structure as source)
Type of mapping relationship
Quality assessment (when include\_mapping\_quality=true)
Confidence in mapping accuracy (0-1)
Type of concept equivalence
Semantic similarity score (0-1)
Source of the mapping
Validation status
Date when mapping became valid
Date when mapping becomes invalid
Statistical analysis of the vocabulary mappings (when include\_statistics=true)
Total number of mappings between vocabularies
Analysis of mapping coverage
Coverage statistics for source vocabulary
Source concepts that have target mappings
Source concepts without target mappings
Percentage of source concepts with mappings
Coverage breakdown by domain
Coverage statistics for target vocabulary (similar structure)
Distribution of mapping quality scores
Count of mappings by equivalence type
Distribution of confidence scores by range
Count of mappings by validation status
Average confidence score across all mappings
Count of mappings by domain
Count of mappings by concept class
Analysis of mapping creation and updates over time
Date of oldest mapping
Date of newest mapping
Count of mappings created by year
Mappings updated in last 90 days
Analysis of unmapped concepts (when include\_gaps=true)
Source concepts without target mappings
Total unmapped source concepts
Unmapped concept counts by domain
Unmapped concept counts by concept class
Sample of unmapped concepts for analysis
Common patterns in unmapped concepts
Target concepts without source mappings (similar structure)
Recommendations for improving mapping coverage
Response metadata and pagination information
Current page number
Items per page
Total mappings
Total number of pages
Whether next page exists
Whether previous page exists
Time taken to execute the query
Timestamp of underlying mapping data
```bash cURL
curl -X GET "https://api.omophub.com/v1/mappings/vocabularies/SNOMED/ICD10CM?domain_ids=Condition&include_statistics=true&include_mapping_quality=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/mappings/vocabularies/SNOMED/ICD10CM?domain_ids=Condition&include_statistics=true&include_mapping_quality=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const mappingsData = await response.json();
const { vocabulary_pair, mappings, mapping_statistics } = mappingsData.data;
console.log(`Mappings: ${vocabulary_pair.source_vocabulary.vocabulary_name} → ${vocabulary_pair.target_vocabulary.vocabulary_name}`);
console.log(`Total mappings: ${mapping_statistics.total_mappings.toLocaleString()}`);
// Show coverage analysis
const coverage = mapping_statistics.coverage_analysis;
console.log('\nCoverage Analysis:');
console.log(`Source coverage: ${coverage.source_coverage.coverage_percentage.toFixed(1)}% (${coverage.source_coverage.concepts_with_mappings.toLocaleString()}/${(coverage.source_coverage.concepts_with_mappings + coverage.source_coverage.concepts_without_mappings).toLocaleString()})`);
// Show quality distribution
const quality = mapping_statistics.quality_distribution;
console.log('\nMapping Quality:');
console.log(`Average confidence: ${(quality.average_confidence_score * 100).toFixed(1)}%`);
console.log('Equivalence types:');
Object.entries(quality.equivalence_type_counts).forEach(([type, count]) => {
const percentage = (count / mapping_statistics.total_mappings * 100).toFixed(1);
console.log(` ${type}: ${count.toLocaleString()} (${percentage}%)`);
});
// Show domain distribution
console.log('\nMappings by Domain:');
Object.entries(mapping_statistics.domain_distribution).forEach(([domain, count]) => {
console.log(` ${domain}: ${count.toLocaleString()}`);
});
// Display sample mappings
console.log('\nSample Mappings:');
mappings.slice(0, 5).forEach(mapping => {
const source = mapping.source_concept;
const target = mapping.target_concept;
console.log(`${source.concept_name} (${source.concept_code}) → ${target.concept_name} (${target.concept_code})`);
if (mapping.mapping_quality) {
const quality = mapping.mapping_quality;
console.log(` Quality: ${quality.equivalence_type}, Confidence: ${(quality.confidence_score * 100).toFixed(1)}%`);
}
});
```
```python Python
import requests
import pandas as pd
import matplotlib.pyplot as plt
source_vocab = "SNOMED"
target_vocab = "ICD10CM"
url = f"https://api.omophub.com/v1/mappings/vocabularies/{source_vocab}/{target_vocab}"
params = {
"domain_ids": "Condition,Procedure",
"include_statistics": True,
"include_mapping_quality": True,
"include_gaps": True,
"quality_threshold": 0.7,
"page_size": 5000
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
vocab_pair = data['data']['vocabulary_pair']
mappings = data['data']['mappings']
stats = data['data']['mapping_statistics']
print(f"=== {vocab_pair['source_vocabulary']['vocabulary_name']} → {vocab_pair['target_vocabulary']['vocabulary_name']} ===")
print(f"Total mappings: {stats['total_mappings']:,}")
print(f"Mappings returned: {len(mappings):,}")
# Coverage Analysis
coverage = stats['coverage_analysis']
source_cov = coverage['source_coverage']
target_cov = coverage['target_coverage']
print(f"\n=== Coverage Analysis ===")
print(f"Source vocabulary coverage: {source_cov['coverage_percentage']:.1f}%")
print(f" - Concepts with mappings: {source_cov['concepts_with_mappings']:,}")
print(f" - Concepts without mappings: {source_cov['concepts_without_mappings']:,}")
print(f"Target vocabulary coverage: {target_cov['coverage_percentage']:.1f}%")
print(f" - Concepts with mappings: {target_cov['concepts_with_mappings']:,}")
# Quality Analysis
quality = stats['quality_distribution']
print(f"\n=== Quality Analysis ===")
print(f"Average confidence score: {quality['average_confidence_score']:.3f}")
print(f"Equivalence type distribution:")
for equiv_type, count in sorted(quality['equivalence_type_counts'].items(), key=lambda x: x[1], reverse=True):
percentage = (count / stats['total_mappings']) * 100
print(f" {equiv_type}: {count:,} ({percentage:.1f}%)")
print(f"Validation status:")
for status, count in quality['validation_status_counts'].items():
percentage = (count / stats['total_mappings']) * 100
print(f" {status}: {count:,} ({percentage:.1f}%)")
# Domain Distribution
print(f"\n=== Domain Distribution ===")
domain_dist = stats['domain_distribution']
for domain, count in sorted(domain_dist.items(), key=lambda x: x[1], reverse=True):
percentage = (count / stats['total_mappings']) * 100
print(f"{domain}: {count:,} ({percentage:.1f}%)")
# Concept Class Distribution (top 10)
print(f"\n=== Top Concept Classes ===")
class_dist = stats['concept_class_distribution']
top_classes = sorted(class_dist.items(), key=lambda x: x[1], reverse=True)[:10]
for class_name, count in top_classes:
percentage = (count / stats['total_mappings']) * 100
print(f"{class_name}: {count:,} ({percentage:.1f}%)")
# Temporal Analysis
temporal = stats.get('temporal_analysis', {})
if temporal:
print(f"\n=== Temporal Analysis ===")
print(f"Oldest mapping: {temporal['oldest_mapping_date']}")
print(f"Newest mapping: {temporal['newest_mapping_date']}")
print(f"Recent updates (90 days): {temporal['recent_updates']:,}")
# Gap Analysis
if 'mapping_gaps' in data['data']:
gaps = data['data']['mapping_gaps']
source_gaps = gaps['source_gaps']
print(f"\n=== Gap Analysis ===")
print(f"Unmapped {source_vocab} concepts: {source_gaps['total_unmapped_concepts']:,}")
print(f"Unmapped by domain:")
for domain, count in sorted(source_gaps['unmapped_by_domain'].items(), key=lambda x: x[1], reverse=True):
print(f" {domain}: {count:,}")
if gaps.get('recommendations'):
print(f"\nRecommendations:")
for rec in gaps['recommendations'][:3]:
print(f" - {rec}")
# Create visualizations
if len(mappings) > 0:
# Quality score distribution
quality_scores = [m.get('mapping_quality', {}).get('confidence_score', 0) for m in mappings if m.get('mapping_quality')]
if quality_scores:
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.hist(quality_scores, bins=20, alpha=0.7, color='skyblue')
plt.xlabel('Confidence Score')
plt.ylabel('Number of Mappings')
plt.title('Mapping Quality Distribution')
# Domain distribution pie chart
plt.subplot(1, 2, 2)
domain_names = list(domain_dist.keys())
domain_counts = list(domain_dist.values())
plt.pie(domain_counts, labels=domain_names, autopct='%1.1f%%')
plt.title('Mappings by Domain')
plt.tight_layout()
plt.show()
# Sample high-quality mappings
print(f"\n=== Sample High-Quality Mappings ===")
high_quality_mappings = [m for m in mappings if m.get('mapping_quality', {}).get('confidence_score', 0) >= 0.9]
for mapping in high_quality_mappings[:5]:
source = mapping['source_concept']
target = mapping['target_concept']
quality = mapping.get('mapping_quality', {})
print(f"{source['concept_name']} ({source['concept_code']})")
print(f" → {target['concept_name']} ({target['concept_code']})")
if quality:
print(f" Quality: {quality['equivalence_type']}, Confidence: {quality['confidence_score']:.3f}")
print()
```
```json
{
"success": true,
"data": {
"vocabulary_pair": {
"source_vocabulary": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "http://www.snomed.org/",
"vocabulary_version": "20240301",
"total_concepts": 387421
},
"target_vocabulary": {
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"vocabulary_reference": "https://www.cdc.gov/nchs/icd/icd10cm.htm",
"vocabulary_version": "2024",
"total_concepts": 98234
},
"mapping_relationship": "SNOMED CT clinical concepts mapped to ICD-10-CM billing codes for US healthcare"
},
"mappings": [
{
"mapping_id": "map_201826_45757292",
"source_concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"target_concept": {
"concept_id": 45757292,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S"
},
"mapping_type": "Maps to",
"mapping_quality": {
"confidence_score": 0.98,
"equivalence_type": "exact",
"semantic_similarity": 0.95,
"mapping_source": "official",
"validation_status": "validated"
},
"valid_start_date": "2016-10-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
{
"mapping_id": "map_320128_44784217",
"source_concept": {
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"target_concept": {
"concept_id": 44784217,
"concept_name": "Essential hypertension",
"concept_code": "I10",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S"
},
"mapping_type": "Maps to",
"mapping_quality": {
"confidence_score": 0.97,
"equivalence_type": "exact",
"semantic_similarity": 0.94,
"mapping_source": "official",
"validation_status": "validated"
},
"valid_start_date": "2016-10-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"mapping_statistics": {
"total_mappings": 156789,
"coverage_analysis": {
"source_coverage": {
"concepts_with_mappings": 234567,
"concepts_without_mappings": 152854,
"coverage_percentage": 60.5,
"domain_coverage": {
"Condition": 78.2,
"Procedure": 45.7,
"Observation": 23.1
}
},
"target_coverage": {
"concepts_with_mappings": 87432,
"concepts_without_mappings": 10802,
"coverage_percentage": 89.0
}
},
"quality_distribution": {
"equivalence_type_counts": {
"exact": 98765,
"broader": 34567,
"narrower": 15234,
"related": 8223
},
"confidence_score_ranges": {
"0.9-1.0": 78543,
"0.8-0.9": 45678,
"0.7-0.8": 23456,
"0.6-0.7": 9112
},
"validation_status_counts": {
"validated": 134567,
"pending": 15678,
"disputed": 3456,
"deprecated": 3088
},
"average_confidence_score": 0.874
},
"domain_distribution": {
"Condition": 89234,
"Procedure": 45678,
"Observation": 12456,
"Measurement": 6789,
"Device": 2632
},
"concept_class_distribution": {
"Clinical Finding": 67890,
"Procedure": 34567,
"3-char billing code": 23456,
"4-char billing code": 12345,
"Observable Entity": 8765
},
"temporal_analysis": {
"oldest_mapping_date": "2016-10-01T00:00:00Z",
"newest_mapping_date": "2024-03-15T00:00:00Z",
"mappings_by_year": {
"2016": 98765,
"2017": 12345,
"2018": 8976,
"2019": 15432,
"2020": 7654
},
"recent_updates": 3456
}
},
"mapping_gaps": {
"source_gaps": {
"total_unmapped_concepts": 152854,
"unmapped_by_domain": {
"Observation": 78432,
"Measurement": 34567,
"Condition": 23456,
"Procedure": 16399
},
"unmapped_by_concept_class": {
"Observable Entity": 56789,
"Clinical Finding": 34567,
"Procedure": 23456
},
"sample_unmapped_concepts": [
{
"concept_id": 4567890,
"concept_name": "Blood pressure taking",
"concept_code": "75367002",
"domain_id": "Observation"
}
],
"gap_patterns": [
"Many observational procedures lack ICD-10-CM equivalents",
"Complex procedures often map to broader categories",
"Specialized measurements have limited billing code coverage"
]
},
"recommendations": [
"Consider using HCPCS for unmapped procedures",
"Review observation domain mappings with clinical experts",
"Implement custom mapping rules for specialized concepts"
]
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 1000,
"total_items": 156789,
"total_pages": 157,
"has_next": true,
"has_previous": false
},
"request_id": "req_vocab_mappings_789012",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Vocabulary Mappings
Get all mappings between two vocabularies:
```javascript
const mappings = await fetch('/v1/mappings/vocabularies/SNOMED/ICD10CM');
```
### High-Quality Condition Mappings
Get high-quality mappings for conditions:
```javascript
const qualityMappings = await fetch('/v1/mappings/vocabularies/SNOMED/ICD10CM?domain_ids=Condition&quality_threshold=0.8&include_mapping_quality=true');
```
### Comprehensive Mapping Analysis
Get detailed statistics and gap analysis:
```javascript
const analysis = await fetch('/v1/mappings/vocabularies/SNOMED/ICD10CM?include_statistics=true&include_gaps=true&include_mapping_quality=true');
```
### Drug Mappings
Get mappings between drug vocabularies:
```javascript
const drugMappings = await fetch('/v1/mappings/vocabularies/RxNorm/NDC?domain_ids=Drug&equivalence_types=exact');
```
### Standard Concept Mappings Only
Get mappings involving only standard concepts:
```javascript
const standardMappings = await fetch('/v1/mappings/vocabularies/SNOMED/HCPCS?standard_concepts_only=both&domain_ids=Procedure');
```
## Related Endpoints
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Mappings for specific concepts
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Overall mapping coverage analysis
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
* [Search Concepts](/api-reference/search/search-concepts) - Find concepts across vocabularies
## Notes
* Large vocabulary pairs may return many thousands of mappings - use pagination appropriately
* Coverage analysis helps identify gaps in cross-vocabulary translation capabilities
* Quality scores help prioritize the most reliable mappings for production use
* Gap analysis reveals unmapped concepts that may require custom mapping rules
* Temporal analysis shows mapping evolution and maintenance patterns
* Some vocabulary pairs have asymmetric coverage (better mapping in one direction)
* Official mappings are generally more reliable than algorithmic or community mappings
* Domain and concept class filtering can significantly improve query performance
# Validate Mappings
Source: https://docs.omophub.com/api-reference/mappings/validate-mappings
POST https://api.omophub.com/v1/mappings/validate
Validate the accuracy and quality of concept mappings between vocabularies
## Overview
This endpoint validates existing concept mappings between vocabularies, checking for accuracy, consistency, and adherence to mapping standards. It's essential for quality assurance in healthcare data integration and cross-vocabulary analysis.
## Request Body
Array of concept mappings to validate
The source OMOP concept ID
The target OMOP concept ID to validate mapping to
The relationship type of the mapping (e.g., "Maps to", "Maps to value")
Expected confidence score for validation (0.0-1.0)
Optional context identifier for tracking related validations
Array of specific validation rules to apply (default: all standard rules)
Options for validation process
Validate semantic similarity between concepts
Validate that concepts are in compatible domains
Validate that mappings are temporally consistent
Validate against vocabulary-specific mapping standards
Include suggestions for better mappings when validation fails
Minimum confidence threshold for valid mappings (0.0-1.0)
## Query Parameters
Specific vocabulary release version (e.g., "2024.1")
Include detailed analysis and recommendations in results
## Response
Indicates if the request was successful
Response data containing validation results
Array of validation results matching the input order
The source concept ID that was validated
The target concept ID that was validated
The mapping relationship type that was validated
Context identifier if provided in request
Whether the mapping is considered valid overall
Overall validation score (0.0-1.0)
Mapping confidence score (0.0-1.0)
Results of individual validation checks
Semantic similarity validation results
Whether semantic similarity check passed
Semantic similarity score (0.0-1.0)
Minimum threshold used
Explanation of semantic relationship
Domain compatibility validation results
Whether domain compatibility check passed
Source concept domain
Target concept domain
Level of compatibility: "exact", "compatible", "questionable", "incompatible"
Explanation of domain compatibility
Temporal validity validation results
Whether temporal validity check passed
Source concept validity dates
Target concept validity dates
Period when both concepts are valid
Array of temporal issues found
Vocabulary standards validation results
Whether standards check passed
Standards applied to source vocabulary
Standards applied to target vocabulary
Array of standard violations found
Array of validation issues found
Issue severity: "error", "warning", "info"
Issue code for programmatic handling
Human-readable issue description
Type of validation check that found this issue
Suggested action to resolve the issue
Suggested alternative mappings (when validation fails and include\_alternative\_mappings=true)
Alternative target concept ID
Alternative concept name
Alternative concept vocabulary
Confidence score for alternative mapping
Suggested mapping relationship type
Reason why this alternative is suggested
Source concept information for reference
Target concept information for reference
Summary statistics for the validation batch
Total number of mappings validated
Number of mappings that passed validation
Number of mappings that failed validation
Percentage of valid mappings (0.0-100.0)
Average confidence score across all mappings
Average validation score across all mappings
Most frequently occurring validation issues
Total processing time
Response metadata and API information
Unique request identifier for debugging
ISO 8601 timestamp of the response
Vocabulary release version used
```bash cURL
curl -X POST "https://api.omophub.com/v1/mappings/validate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to",
"context_id": "diabetes_mapping_validation"
},
{
"source_concept_id": 4182210,
"target_concept_id": 320128,
"mapping_type": "Maps to",
"expected_confidence": 0.9
}
],
"validation_options": {
"check_semantic_similarity": true,
"check_domain_compatibility": true,
"include_alternative_mappings": true,
"confidence_threshold": 0.75
}
}'
```
```javascript JavaScript
const validateMappings = async (mappings, options = {}) => {
const response = await fetch('https://api.omophub.com/v1/mappings/validate', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
mappings,
validation_options: options
})
});
if (!response.ok) {
let errorBody;
try {
errorBody = await response.json();
} catch {
errorBody = await response.text();
}
throw new Error(`HTTP ${response.status}: ${typeof errorBody === 'string' ? errorBody : JSON.stringify(errorBody)}`);
}
return response.json();
};
// Example: Validate diabetes concept mappings
const diabetesMappings = [
{
source_concept_id: 201826, // Type 2 diabetes (SNOMED)
target_concept_id: 443735, // Type 2 diabetes without complications (ICD10CM)
mapping_type: "Maps to",
expected_confidence: 0.85,
context_id: "diabetes_study_validation"
},
{
source_concept_id: 201826, // Type 2 diabetes (SNOMED)
target_concept_id: 44054361, // ICD9CM equivalent
mapping_type: "Maps to",
context_id: "diabetes_study_validation"
},
{
source_concept_id: 4182210, // Hypertension
target_concept_id: 320128, // Essential hypertension
mapping_type: "Maps to",
expected_confidence: 0.9
}
];
const validationOptions = {
check_semantic_similarity: true,
check_domain_compatibility: true,
check_temporal_validity: true,
include_alternative_mappings: true,
confidence_threshold: 0.7
};
const results = await validateMappings(diabetesMappings, validationOptions);
console.log(`Validation Summary:`);
console.log(`- Total mappings: ${results.data.validation_summary.total_mappings}`);
console.log(`- Valid mappings: ${results.data.validation_summary.valid_mappings}`);
console.log(`- Validation rate: ${results.data.validation_summary.validation_rate.toFixed(1)}%`);
console.log(`- Average confidence: ${results.data.validation_summary.average_confidence.toFixed(3)}`);
console.log(`- Average validation score: ${results.data.validation_summary.average_validation_score.toFixed(3)}`);
results.data.validation_results.forEach((result, index) => {
console.log(`\n${index + 1}. Source: ${result.source_concept_id} → Target: ${result.target_concept_id}`);
console.log(` Valid: ${result.is_valid ? '✓' : '✗'}`);
console.log(` Validation Score: ${result.validation_score.toFixed(3)}`);
console.log(` Confidence Score: ${result.confidence_score.toFixed(3)}`);
console.log(` Context: ${result.context_id || 'N/A'}`);
// Show validation check details
const checks = result.validation_checks;
console.log(` Checks:`);
console.log(` Semantic Similarity: ${checks.semantic_similarity.passed ? '✓' : '✗'} (${checks.semantic_similarity.score.toFixed(3)})`);
console.log(` Domain Compatibility: ${checks.domain_compatibility.passed ? '✓' : '✗'} (${checks.domain_compatibility.compatibility_level})`);
console.log(` Temporal Validity: ${checks.temporal_validity.passed ? '✓' : '✗'}`);
console.log(` Vocabulary Standards: ${checks.vocabulary_standards.passed ? '✓' : '✗'}`);
// Show issues if any
if (result.issues && result.issues.length > 0) {
console.log(` Issues:`);
result.issues.forEach(issue => {
const severity = issue.severity.toUpperCase();
console.log(` [${severity}] ${issue.message}`);
if (issue.recommendation) {
console.log(` → Recommendation: ${issue.recommendation}`);
}
});
}
// Show alternative mappings if provided
if (result.alternative_mappings && result.alternative_mappings.length > 0) {
console.log(` Alternative Mappings:`);
result.alternative_mappings.slice(0, 2).forEach(alt => {
console.log(` • ${alt.concept_name} (ID: ${alt.concept_id}) - Confidence: ${alt.confidence_score.toFixed(3)}`);
console.log(` Rationale: ${alt.rationale}`);
});
}
});
```
```python Python
import requests
def validate_mappings(mappings, validation_options=None, api_key="YOUR_API_KEY"):
url = "https://api.omophub.com/v1/mappings/validate"
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
data = {"mappings": mappings}
if validation_options:
data["validation_options"] = validation_options
response = requests.post(url, json=data, headers=headers)
return response.json()
# Example: Validate cardiovascular concept mappings
cardiovascular_mappings = [
{
"source_concept_id": 4182210, # Hypertensive disorder (SNOMED)
"target_concept_id": 312327, # Essential hypertension (ICD10CM)
"mapping_type": "Maps to",
"expected_confidence": 0.85,
"context_id": "cardio_quality_check"
},
{
"source_concept_id": 313217, # Atrial fibrillation (SNOMED)
"target_concept_id": 313217, # Self-mapping (likely invalid per vocabulary standards unless justified with an 'Is a'/'Subsumes' relationship)
"mapping_type": "Is a",
"context_id": "cardio_quality_check"
},
{
"source_concept_id": 318443, # Heart failure (SNOMED)
"target_concept_id": 4053838, # Acute heart failure (questionable mapping)
"mapping_type": "Maps to",
"expected_confidence": 0.9
}
]
validation_options = {
"check_semantic_similarity": True,
"check_domain_compatibility": True,
"check_temporal_validity": True,
"check_vocabulary_standards": True,
"include_alternative_mappings": True,
"confidence_threshold": 0.75
}
results = validate_mappings(cardiovascular_mappings, validation_options)
# Print validation summary
summary = results['data']['validation_summary']
print(f"Validation Summary:")
print(f" Total mappings: {summary['total_mappings']}")
print(f" Valid mappings: {summary['valid_mappings']}")
print(f" Invalid mappings: {summary['invalid_mappings']}")
print(f" Validation rate: {summary['validation_rate']:.1f}%")
print(f" Average confidence: {summary['average_confidence']:.3f}")
print(f" Average validation score: {summary['average_validation_score']:.3f}")
print(f" Processing time: {summary['processing_time_ms']}ms")
if summary['common_issues']:
print(f"\nCommon Issues:")
for issue in summary['common_issues']:
print(f" • {issue['message']} ({issue['frequency']} occurrences)")
# Process individual validation results
for i, result in enumerate(results['data']['validation_results']):
source_name = result['source_concept_details']['concept_name']
target_name = result['target_concept_details']['concept_name']
print(f"\n{i+1}. {source_name} → {target_name}")
print(f" Source ID: {result['source_concept_id']} | Target ID: {result['target_concept_id']}")
print(f" Mapping Type: {result['mapping_type']}")
print(f" Context: {result.get('context_id', 'N/A')}")
print(f" Overall Valid: {'✓' if result['is_valid'] else '✗'}")
print(f" Validation Score: {result['validation_score']:.3f}")
print(f" Confidence Score: {result['confidence_score']:.3f}")
# Detailed validation checks
checks = result['validation_checks']
print(f"\n Validation Checks:")
# Semantic similarity
sem = checks['semantic_similarity']
print(f" Semantic Similarity: {'✓' if sem['passed'] else '✗'} ({sem['score']:.3f})")
if sem['explanation']:
print(f" {sem['explanation']}")
# Domain compatibility
dom = checks['domain_compatibility']
print(f" Domain Compatibility: {'✓' if dom['passed'] else '✗'} ({dom['compatibility_level']})")
print(f" Source: {dom['source_domain']} | Target: {dom['target_domain']}")
if dom['explanation']:
print(f" {dom['explanation']}")
# Temporal validity
temp = checks['temporal_validity']
print(f" Temporal Validity: {'✓' if temp['passed'] else '✗'}")
if temp['issues']:
for issue in temp['issues']:
print(f" Issue: {issue}")
# Vocabulary standards
vocab = checks['vocabulary_standards']
print(f" Vocabulary Standards: {'✓' if vocab['passed'] else '✗'}")
if vocab['violations']:
for violation in vocab['violations']:
print(f" Violation: {violation}")
# Show validation issues
if result['issues']:
print(f"\n Issues Found:")
for issue in result['issues']:
severity_icon = "🔴" if issue['severity'] == 'error' else "🟡" if issue['severity'] == 'warning' else "ℹ️"
print(f" {severity_icon} [{issue['severity'].upper()}] {issue['message']}")
if issue['recommendation']:
print(f" Recommendation: {issue['recommendation']}")
# Show alternative mappings if available
if result.get('alternative_mappings'):
print(f"\n Alternative Mappings:")
for alt in result['alternative_mappings'][:3]: # Show top 3
print(f" • {alt['concept_name']} (ID: {alt['concept_id']})")
print(f" Vocabulary: {alt['vocabulary_id']} | Confidence: {alt['confidence_score']:.3f}")
print(f" Rationale: {alt['rationale']}")
```
```json
{
"success": true,
"data": {
"validation_results": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to",
"context_id": "diabetes_mapping_validation",
"is_valid": true,
"validation_score": 0.92,
"confidence_score": 0.89,
"validation_checks": {
"semantic_similarity": {
"passed": true,
"score": 0.94,
"threshold": 0.75,
"explanation": "Both concepts represent Type 2 diabetes with very high semantic overlap"
},
"domain_compatibility": {
"passed": true,
"source_domain": "Condition",
"target_domain": "Condition",
"compatibility_level": "exact",
"explanation": "Both concepts belong to the same domain (Condition)"
},
"temporal_validity": {
"passed": true,
"source_validity_period": {
"start": "2002-01-31",
"end": "2099-12-31"
},
"target_validity_period": {
"start": "2015-10-01",
"end": "2099-12-31"
},
"overlap_period": {
"start": "2015-10-01",
"end": "2099-12-31"
},
"issues": []
},
"vocabulary_standards": {
"passed": true,
"source_vocabulary_standards": ["SNOMED CT", "OMOP CDM"],
"target_vocabulary_standards": ["ICD-10-CM", "OMOP CDM"],
"violations": []
}
},
"issues": [],
"alternative_mappings": [],
"source_concept_details": {
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"target_concept_details": {
"concept_name": "Type 2 diabetes mellitus without complications",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char billing code"
}
},
{
"source_concept_id": 4182210,
"target_concept_id": 320128,
"mapping_type": "Maps to",
"context_id": null,
"is_valid": false,
"validation_score": 0.45,
"confidence_score": 0.52,
"validation_checks": {
"semantic_similarity": {
"passed": false,
"score": 0.62,
"threshold": 0.75,
"explanation": "Concepts have moderate semantic similarity but below threshold"
},
"domain_compatibility": {
"passed": true,
"source_domain": "Condition",
"target_domain": "Condition",
"compatibility_level": "exact",
"explanation": "Both concepts belong to the same domain"
},
"temporal_validity": {
"passed": true,
"source_validity_period": {
"start": "2002-01-31",
"end": "2099-12-31"
},
"target_validity_period": {
"start": "2002-01-31",
"end": "2099-12-31"
},
"overlap_period": {
"start": "2002-01-31",
"end": "2099-12-31"
},
"issues": []
},
"vocabulary_standards": {
"passed": false,
"source_vocabulary_standards": ["SNOMED CT"],
"target_vocabulary_standards": ["SNOMED CT"],
"violations": ["Same vocabulary self-mapping without proper justification"]
}
},
"issues": [
{
"severity": "warning",
"code": "LOW_SEMANTIC_SIMILARITY",
"message": "Semantic similarity score (0.62) is below the confidence threshold (0.75)",
"check_type": "semantic_similarity",
"recommendation": "Consider using a more semantically similar target concept or review the mapping rationale"
},
{
"severity": "error",
"code": "VOCABULARY_STANDARD_VIOLATION",
"message": "Same vocabulary mapping without proper hierarchical relationship",
"check_type": "vocabulary_standards",
"recommendation": "Use 'Is a' or 'Subsumes' relationship for intra-vocabulary mappings"
}
],
"alternative_mappings": [
{
"concept_id": 312327,
"concept_name": "Essential hypertension",
"vocabulary_id": "ICD10CM",
"confidence_score": 0.85,
"mapping_type": "Maps to",
"rationale": "Better cross-vocabulary mapping with higher semantic similarity"
}
]
}
],
"validation_summary": {
"total_mappings": 2,
"valid_mappings": 1,
"invalid_mappings": 1,
"validation_rate": 50.0,
"average_confidence": 0.705,
"average_validation_score": 0.685,
"common_issues": [
{
"code": "LOW_SEMANTIC_SIMILARITY",
"message": "Semantic similarity below threshold",
"frequency": 1
},
{
"code": "VOCABULARY_STANDARD_VIOLATION",
"message": "Vocabulary standard violation",
"frequency": 1
}
],
"processing_time_ms": 1847
}
},
"meta": {
"request_id": "req_validate_mappings_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Mapping Validation
Validate simple concept mappings:
```json
{
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to"
}
]
}
```
### Quality Assurance Validation
Comprehensive validation for QA processes:
```json
{
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to",
"expected_confidence": 0.85
}
],
"validation_options": {
"check_semantic_similarity": true,
"check_domain_compatibility": true,
"check_temporal_validity": true,
"check_vocabulary_standards": true,
"include_alternative_mappings": true,
"confidence_threshold": 0.8
}
}
```
### Research Study Validation
Validate mappings for research with context tracking:
```json
{
"mappings": [
{
"source_concept_id": 201826,
"target_concept_id": 443735,
"mapping_type": "Maps to",
"context_id": "diabetes_outcome_study_2024",
"expected_confidence": 0.9
}
],
"validation_options": {
"confidence_threshold": 0.85,
"include_alternative_mappings": true
}
}
```
## Important Notes
* **Validation thoroughness** - Comprehensive validation checks multiple dimensions of mapping quality
* **Performance impact** - Detailed validation with alternative mappings increases processing time
* **Confidence thresholds** - Adjust based on your use case requirements (clinical vs research vs administrative)
* **Temporal considerations** - Pay attention to concept validity periods for historical data analysis
* **Standard compliance** - Vocabulary-specific standards are enforced for quality assurance
## Related Endpoints
* [Batch Map Concepts](/api-reference/mappings/batch-map-concepts) - Create mappings to validate
* [Get Mapping Coverage](/api-reference/mappings/get-mapping-coverage) - Analyze vocabulary mapping coverage
* [Get Mapping Quality](/api-reference/mappings/get-mapping-quality) - Overall mapping quality between vocabularies
# Rate Limit
Source: https://docs.omophub.com/api-reference/rate-limit
Understand rate limits and how to increase them.
The response headers describe your current rate limit following every request in conformance with the [IETF standard](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers):
| Header name | Description |
| --------------------- | ------------------------------------------------------------------- |
| `ratelimit-limit` | Maximum number of requests allowed within a window. |
| `ratelimit-remaining` | How many requests you have left within the current window. |
| `ratelimit-reset` | How many seconds until the limits are reset. |
| `ratelimit-window` | The window size in seconds for the rate limit. |
| `retry-after` | How many seconds you should wait before making a follow-up request. |
## Rate Limit Responses
The default maximum rate limit is 2 requests per second. This number can be increased upon request.
After that, you’ll hit the rate limit and receive a 429 response error code. You can find all 429 responses by filtering for 429 at the OMOPHub Logs page.
To prevent this, we recommend reducing the rate at which you request the API.
This can be done by introducing a queue mechanism or reducing the number of concurrent requests per second.
If you have specific requirements, [contact support](https://omophub.com/contact) to request a rate increase.
# Get Concept Relationships
Source: https://docs.omophub.com/api-reference/relationships/get-concept-relationships
GET /v1/concepts/{concept_id}/relationships
Retrieve all relationships for a specific concept, including hierarchical, associative, and mapping connections
This endpoint provides comprehensive relationship information for a concept, showing how it connects to other concepts through various relationship types such as "Is a", "Part of", "Has ingredient", and many others in medical vocabularies.
## Path Parameters
The unique identifier of the concept to retrieve relationships for
**Example:** `201826` (Type 2 diabetes mellitus)
## Query Parameters
Comma-separated list of specific relationship types to retrieve
**Example:** `Is a,Part of,Has ingredient,Maps to`
Direction of relationships to retrieve
**Options:** `incoming`, `outgoing`, `both`
Filter relationships to specific vocabularies
**Example:** `SNOMED,ICD10CM,RxNorm`
Filter related concepts to specific domains
**Example:** `Condition,Drug,Procedure`
Only return relationships to standard concepts
Include relationships to deprecated/invalid concepts
Include synonym information for related concepts
Include semantic distance scores for relationships
Group relationships by their type in the response
Include reverse relationship names for clarity
Number of relationships to return per page (max 1000)
Page number for pagination (1-based)
## Response
The source concept for which relationships were retrieved
Unique identifier for the concept
Standard name of the concept
Vocabulary containing this concept
Domain classification
Concept class identifier
Array of relationship objects (when group\_by\_type=false)
Unique identifier for this relationship
Type of relationship (e.g., "Is a", "Part of", "Maps to")
Reverse relationship name (when include\_reverse=true)
Direction of the relationship from source concept
**Values:** `outgoing`, `incoming`
The concept connected through this relationship
Unique identifier for the related concept
Standard name of the related concept
Original code from the vocabulary
Vocabulary containing the related concept
Domain classification
Concept class identifier
Standard concept designation ('S', 'C', or null)
Alternative names (when include\_synonyms=true)
Date when concept became valid
Date when concept becomes invalid
Semantic distance score (when include\_distance=true)
Strength/confidence of the relationship (0-1)
Date when relationship became valid
Date when relationship becomes invalid
Relationships grouped by type (when group\_by\_type=true)
Array of relationships of this specific type
Summary statistics about the relationships
Total number of relationships found
Number of distinct relationship types
Number of outgoing relationships
Number of incoming relationships
Count of relationships by type
Distribution of related concepts by vocabulary
Distribution of related concepts by domain
Most frequently occurring relationship types
Response metadata and pagination information
Current page number
Items per page
Total relationships
Total number of pages
Whether next page exists
Whether previous page exists
```bash cURL
curl -G "https://api.omophub.com/v1/concepts/201826/relationships" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
--data-urlencode "relationship_types=Is a,Part of" \
--data-urlencode "include_synonyms=true" \
--data-urlencode "group_by_type=true"
```
```javascript JavaScript
// Build URL with proper encoding
const baseUrl = 'https://api.omophub.com/v1/concepts/201826/relationships';
const params = new URLSearchParams({
'relationship_types': 'Is a,Part of',
'include_synonyms': 'true',
'group_by_type': 'true'
});
const url = `${baseUrl}?${params.toString()}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
const relationshipData = await response.json();
console.log(`Found ${relationshipData.data.relationship_summary.total_relationships} relationships`);
// Display relationships by type
if (relationshipData.data.relationships_by_type) {
Object.keys(relationshipData.data.relationships_by_type).forEach(type => {
const relationships = relationshipData.data.relationships_by_type[type];
console.log(`\n${type} relationships (${relationships.length}):`);
relationships.slice(0, 5).forEach(rel => {
console.log(` - ${rel.related_concept.concept_name}`);
});
});
}
```
```python Python
import requests
import json
concept_id = 201826 # Type 2 diabetes mellitus
url = f"https://api.omophub.com/v1/concepts/{concept_id}/relationships"
params = {
"relationship_types": "Is a,Part of,Maps to",
"include_synonyms": True,
"include_distance": True,
"group_by_type": True,
"page_size": 200
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/json"
}
try:
response = requests.get(url, params=params, headers=headers, timeout=10)
response.raise_for_status() # Raise exception for HTTP errors
relationship_data = response.json()
except requests.RequestException as e:
print(f"Request failed: {e}")
exit(1)
except json.JSONDecodeError as e:
print(f"Failed to decode JSON response: {e}")
exit(1)
print(f"Concept: {relationship_data['data']['concept']['concept_name']}")
print(f"Total relationships: {relationship_data['data']['relationship_summary']['total_relationships']}")
print(f"Relationship types: {relationship_data['data']['relationship_summary']['unique_relationship_types']}")
# Display relationship type counts
print("\nRelationship Type Distribution:")
for rel_type, count in relationship_data['data']['relationship_summary']['relationship_type_counts'].items():
print(f" {rel_type}: {count}")
# Display relationships grouped by type
if 'relationships_by_type' in relationship_data['data']:
print("\nRelationships by Type:")
for rel_type, relationships in relationship_data['data']['relationships_by_type'].items():
print(f"\n{rel_type} ({len(relationships)} relationships):")
for rel in relationships[:3]: # Show first 3
related = rel['related_concept']
direction = "←" if rel['direction'] == 'incoming' else "→"
distance = f" (distance: {rel.get('semantic_distance', 'N/A')})"
print(f" {direction} {related['concept_name']}{distance}")
```
```json
{
"success": true,
"data": {
"concept": {
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding"
},
"relationships_by_type": {
"Is a": [
{
"relationship_id": "rel_1",
"relationship_type": "Is a",
"reverse_relationship_type": "Subsumes",
"direction": "outgoing",
"related_concept": {
"concept_id": 201826,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
"semantic_distance": 0.1,
"relationship_strength": 0.95,
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"Maps to": [
{
"relationship_id": "rel_2",
"relationship_type": "Maps to",
"reverse_relationship_type": "Mapped from",
"direction": "outgoing",
"related_concept": {
"concept_id": 45757292,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S",
"valid_start_date": "2016-10-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
"semantic_distance": 0.05,
"relationship_strength": 0.98,
"valid_start_date": "2016-10-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
],
"Associated with": [
{
"relationship_id": "rel_3",
"relationship_type": "Associated with",
"reverse_relationship_type": "Associated with",
"direction": "outgoing",
"related_concept": {
"concept_id": 201820,
"concept_name": "Diabetes mellitus due to insulin receptor antibodies",
"concept_code": "190368000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "2002-01-31T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
},
"semantic_distance": 0.3,
"relationship_strength": 0.7,
"valid_start_date": "2002-01-31T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z"
}
]
},
"relationship_summary": {
"total_relationships": 47,
"unique_relationship_types": 8,
"outgoing_relationships": 32,
"incoming_relationships": 15,
"relationship_type_counts": {
"Is a": 1,
"Maps to": 12,
"Associated with": 8,
"Has associated morphology": 5,
"Finding site": 3,
"Subsumes": 18
},
"vocabulary_distribution": {
"SNOMED": 28,
"ICD10CM": 12,
"ICD10": 7
},
"domain_distribution": {
"Condition": 38,
"Procedure": 6,
"Observation": 3
},
"most_common_relationships": [
"Subsumes",
"Maps to",
"Associated with",
"Has associated morphology"
]
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 47,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"request_id": "req_relationship_concept_78901234",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### All Relationships
Get all relationships for a concept:
```javascript
const allRelationships = await fetch('/v1/concepts/201826/relationships', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
### Hierarchical Relationships Only
Retrieve only "Is a" and "Subsumes" relationships:
```javascript
const params = new URLSearchParams({ relationship_types: 'Is a,Subsumes' });
const hierarchical = await fetch(`/v1/concepts/201826/relationships?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
### Cross-Vocabulary Mappings
Find mappings to other vocabularies:
```javascript
const params = new URLSearchParams({
relationship_types: 'Maps to',
vocabulary_ids: 'ICD10CM,ICD10'
});
const mappings = await fetch(`/v1/concepts/201826/relationships?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
### Outgoing Relationships Only
Get only relationships going out from the concept:
```javascript
const params = new URLSearchParams({
direction: 'outgoing',
include_distance: 'true'
});
const outgoing = await fetch(`/v1/concepts/201826/relationships?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
### Grouped by Type
Organize relationships by their type:
```javascript
const params = new URLSearchParams({
group_by_type: 'true',
include_synonyms: 'true'
});
const grouped = await fetch(`/v1/concepts/201826/relationships?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
```
## Related Endpoints
* [Get Relationship Types](/api-reference/relationships/get-relationship-types) - Available relationship types
* [Get Concept Ancestors](/api-reference/hierarchy/get-concept-ancestors) - Hierarchical parent relationships
* [Get Concept Descendants](/api-reference/hierarchy/get-concept-descendants) - Hierarchical child relationships
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Cross-vocabulary mappings specifically
## Notes
* Relationship direction is crucial: "outgoing" means the source concept is the subject of the relationship
* Semantic distance provides a measure of how closely related two concepts are (lower = closer)
* Standard concepts are prioritized for relationship targets unless explicitly disabled
* Some relationships are bidirectional (e.g., "Associated with") while others are unidirectional
* Cross-vocabulary relationships often use "Maps to" relationship types
* Large concepts may have hundreds of relationships - use pagination and filtering appropriately
* Relationship strength indicates the confidence or importance of the connection
# Get Relationship Types
Source: https://docs.omophub.com/api-reference/relationships/get-relationship-types
GET /v1/relationships/types
Retrieve all available relationship types in the vocabulary system, including their definitions and usage patterns
This endpoint provides comprehensive information about all relationship types available in the medical vocabularies, helping users understand how concepts are connected and which relationship types are available for queries and analysis.
## Query Parameters
Filter relationship types to specific vocabularies
**Example:** `SNOMED,RxNorm,ICD10CM`
Include reverse relationship names and definitions
Include usage statistics for each relationship type
Include example concept pairs for each relationship type
Filter by relationship category
**Options:** `hierarchical`, `associative`, `mapping`, `part_whole`, `temporal`, `spatial`, `causative`, `other`
Filter by whether relationships are defining/essential for concepts
Only return relationship types used with standard concepts
Number of relationship types to return per page (max 500)
Page number for pagination (1-based)
## Response
Array of available relationship types with their properties
Unique identifier for the relationship type
Standard name of the relationship type
Name of the reverse relationship (when include\_reverse=true)
Concept ID representing this relationship type
Category classification of the relationship
**Values:** `hierarchical`, `associative`, `mapping`, `part_whole`, `temporal`, `spatial`, `causative`, `other`
Formal definition of the relationship type
Human-readable description and usage notes
Whether this relationship creates hierarchical structure
Whether this relationship is defining/essential for concepts
Whether the relationship is symmetric (A→B implies B→A)
Whether the relationship is transitive (A→B, B→C implies A→C)
Vocabularies where this relationship type is primarily used
Domains where this relationship can be used
Usage statistics (when include\_usage\_stats=true)
Total number of relationships of this type
Number of unique concepts as relationship subject
Number of unique concepts as relationship object
Most common domains using this relationship
Average number of these relationships per concept
Rank of this relationship type by usage frequency
Example concept pairs (when include\_examples=true)
Source concept in the relationship
Name of the source concept
Target concept in the relationship
Name of the target concept
Vocabulary containing this relationship
Description of why this is a good example
Date when relationship type became valid
Date when relationship type becomes invalid
Date when relationship type was created
Summary of relationship types by category
Number of hierarchical relationship types
Number of associative relationship types
Number of mapping relationship types
Number of part-whole relationship types
Number of temporal relationship types
Number of spatial relationship types
Distribution of relationship types by vocabulary
Number of relationship types available in this vocabulary
Response metadata and pagination information
Current page number
Items per page
Total relationship types
Total number of pages
Whether next page exists
Whether previous page exists
Timestamp of last relationship types update
```bash cURL
curl -X GET "https://api.omophub.com/v1/relationships/types?include_usage_stats=true&include_examples=true&category=hierarchical" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/relationships/types?include_usage_stats=true&include_examples=true&category=hierarchical', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const relationshipTypes = await response.json();
console.log(`Found ${relationshipTypes.data.relationship_types.length} relationship types`);
// Display relationship categories
Object.entries(relationshipTypes.data.relationship_categories).forEach(([category, count]) => {
console.log(`${category}: ${count} types`);
});
// Show examples for each type
relationshipTypes.data.relationship_types.forEach(type => {
console.log(`\n${type.relationship_name}:`);
console.log(` Category: ${type.category}`);
console.log(` Definition: ${type.definition}`);
if (type.examples) {
console.log(` Examples:`);
type.examples.slice(0, 2).forEach(example => {
console.log(` - ${example.subject_concept_name} → ${example.object_concept_name}`);
});
}
});
```
```python Python
import requests
url = "https://api.omophub.com/v1/relationships/types"
params = {
"include_usage_stats": True,
"include_examples": True,
"vocabulary_ids": "SNOMED,RxNorm",
"page_size": 50
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
print(f"Found {len(data['data']['relationship_types'])} relationship types")
# Display by category
print("\nRelationship Types by Category:")
for category, count in data['data']['relationship_categories'].items():
print(f" {category.capitalize()}: {count}")
# Display most used relationship types
if any(rt.get('usage_statistics') for rt in data['data']['relationship_types']):
sorted_types = sorted(
[rt for rt in data['data']['relationship_types'] if rt.get('usage_statistics')],
key=lambda x: x['usage_statistics']['total_relationships'],
reverse=True
)
print(f"\nMost Used Relationship Types:")
for rt in sorted_types[:10]:
stats = rt['usage_statistics']
print(f" {rt['relationship_name']}: {stats['total_relationships']:,} relationships")
# Show relationship properties
print(f"\nRelationship Type Details:")
for rt in data['data']['relationship_types'][:5]:
print(f"\n{rt['relationship_name']}:")
print(f" Category: {rt['category']}")
print(f" Hierarchical: {rt['is_hierarchical']}")
print(f" Defining: {rt['is_defining']}")
print(f" Symmetric: {rt['is_symmetric']}")
print(f" Transitive: {rt['is_transitive']}")
print(f" Primary vocabularies: {', '.join(rt['primary_vocabularies'])}")
if rt.get('examples'):
print(f" Example: {rt['examples'][0]['subject_concept_name']} → {rt['examples'][0]['object_concept_name']}")
```
```json
{
"success": true,
"data": {
"relationship_types": [
{
"relationship_id": "116680003",
"relationship_name": "Is a",
"reverse_relationship_name": "Subsumes",
"relationship_concept_id": 116680003,
"category": "hierarchical",
"definition": "The source concept is a subtype or instance of the destination concept",
"description": "Represents the primary hierarchical relationship in SNOMED CT, defining subsumption between concepts",
"is_hierarchical": true,
"is_defining": true,
"is_symmetric": false,
"is_transitive": true,
"primary_vocabularies": ["SNOMED", "LOINC"],
"domain_restrictions": [],
"usage_statistics": {
"total_relationships": 1247890,
"unique_concepts_as_subject": 432156,
"unique_concepts_as_object": 298745,
"most_common_domains": ["Condition", "Procedure", "Substance"],
"average_per_concept": 2.9,
"popularity_rank": 1
},
"examples": [
{
"subject_concept_id": 44054006,
"subject_concept_name": "Type 2 diabetes mellitus",
"object_concept_id": 73211009,
"object_concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"example_description": "Type 2 diabetes is a specific type of diabetes mellitus"
},
{
"subject_concept_id": 386053000,
"subject_concept_name": "Evaluation procedure",
"object_concept_id": 71388002,
"object_concept_name": "Procedure",
"vocabulary_id": "SNOMED",
"example_description": "Evaluation procedures are a subtype of general procedures"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z",
"created_date": "1970-01-01T00:00:00Z"
},
{
"relationship_id": "123005000",
"relationship_name": "Part of",
"reverse_relationship_name": "Has part",
"relationship_concept_id": 123005000,
"category": "part_whole",
"definition": "The source concept is a part, component, or constituent of the destination concept",
"description": "Represents part-whole relationships, commonly used for anatomical structures and complex procedures",
"is_hierarchical": false,
"is_defining": true,
"is_symmetric": false,
"is_transitive": true,
"primary_vocabularies": ["SNOMED"],
"domain_restrictions": ["Procedure", "Device", "Spec Anatomic Site"],
"usage_statistics": {
"total_relationships": 89456,
"unique_concepts_as_subject": 34521,
"unique_concepts_as_object": 18234,
"most_common_domains": ["Spec Anatomic Site", "Procedure"],
"average_per_concept": 2.6,
"popularity_rank": 5
},
"examples": [
{
"subject_concept_id": 7771000,
"subject_concept_name": "Left ventricular structure",
"object_concept_id": 80891009,
"object_concept_name": "Heart structure",
"vocabulary_id": "SNOMED",
"example_description": "Left ventricle is part of the heart structure"
}
],
"valid_start_date": "1970-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z",
"created_date": "2002-01-31T00:00:00Z"
},
{
"relationship_id": "maps_to",
"relationship_name": "Maps to",
"reverse_relationship_name": "Mapped from",
"relationship_concept_id": 44819096,
"category": "mapping",
"definition": "The source concept from one vocabulary corresponds to the destination concept in another vocabulary",
"description": "Cross-vocabulary mapping relationship used to link equivalent or similar concepts across different terminologies",
"is_hierarchical": false,
"is_defining": false,
"is_symmetric": false,
"is_transitive": false,
"primary_vocabularies": ["SNOMED", "ICD10CM", "ICD10", "RxNorm"],
"usage_statistics": {
"total_relationships": 567234,
"unique_concepts_as_subject": 245678,
"unique_concepts_as_object": 198432,
"most_common_domains": ["Condition", "Drug", "Procedure"],
"average_per_concept": 2.3,
"popularity_rank": 2
},
"examples": [
{
"subject_concept_id": 201826,
"subject_concept_name": "Type 2 diabetes mellitus",
"object_concept_id": 45757292,
"object_concept_name": "Type 2 diabetes mellitus",
"vocabulary_id": "ICD10CM",
"example_description": "SNOMED concept maps to equivalent ICD-10-CM concept"
}
],
"valid_start_date": "2010-01-01T00:00:00Z",
"valid_end_date": "2099-12-31T00:00:00Z",
"created_date": "2010-01-01T00:00:00Z"
}
],
"relationship_categories": {
"hierarchical": 2,
"associative": 15,
"mapping": 8,
"part_whole": 4,
"temporal": 6,
"spatial": 3,
"causative": 2,
"other": 12
},
"vocabulary_distribution": {
"SNOMED": 28,
"RxNorm": 8,
"ICD10CM": 3,
"LOINC": 5,
"HCPCS": 2
}
},
"meta": {
"pagination": {
"page": 1,
"page_size": 100,
"total_items": 52,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"request_id": "req_relationship_types_67891234",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### All Relationship Types
Get all available relationship types:
```javascript
const allTypes = await fetch('/v1/relationships/types');
```
### Hierarchical Relationships Only
Get relationship types that create hierarchies:
```javascript
const hierarchical = await fetch('/v1/relationships/types?category=hierarchical&include_examples=true');
```
### SNOMED Relationship Types
Get relationship types used in SNOMED CT:
```javascript
const snomedTypes = await fetch('/v1/relationships/types?vocabulary_ids=SNOMED&include_usage_stats=true');
```
### Mapping Relationship Types
Find relationship types used for cross-vocabulary mapping:
```javascript
const mappingTypes = await fetch('/v1/relationships/types?category=mapping&include_examples=true');
```
### Defining Relationships
Get relationship types that are defining for concepts:
```javascript
const defining = await fetch('/v1/relationships/types?is_defining=true&include_usage_stats=true');
```
## Related Endpoints
* [Get Concept Relationships](/api-reference/relationships/get-concept-relationships) - Relationships for specific concepts
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Cross-vocabulary mappings
* [Search Concepts](/api-reference/search/search-concepts) - Search using relationship filters
* [Get Vocabularies](/api-reference/vocabulary/get-vocabularies) - Available vocabularies
## Notes
* Relationship types define the semantic connections between medical concepts
* Hierarchical relationships (like "Is a") create taxonomic structures essential for classification
* Defining relationships are crucial for concept definition and meaning
* Symmetric relationships work in both directions (A relates to B implies B relates to A)
* Transitive relationships can be chained (A→B, B→C implies A→C)
* Cross-vocabulary mappings use specialized relationship types like "Maps to"
* Usage statistics help identify the most important and commonly used relationship types
* Some relationship types may be restricted to specific domains or vocabularies
# Advanced Search
Source: https://docs.omophub.com/api-reference/search/advanced-search
POST /v1/concepts/search/advanced
Perform multi-criteria search across healthcare vocabularies with advanced filtering
## Overview
The advanced search endpoint provides powerful multi-criteria search capabilities across healthcare vocabularies. Use complex queries, filters, and ranking to find the most relevant medical concepts.
**Best for:** Complex search scenarios requiring multiple filters, specific vocabularies, or advanced ranking criteria.
## Endpoint
/v1/concepts/search/advanced
## Authentication
Bearer token with your API key
## Request Body
The search query term
**Example:** `"diabetes mellitus type 2"`
Array of vocabulary IDs to search within
**Options:** `SNOMED`, `ICD10CM`, `ICD9CM`, `RxNorm`, `LOINC`, `HCPCS`, etc.
**Default:** All vocabularies
Array of domain IDs to filter by
**Options:** `Condition`, `Drug`, `Procedure`, `Measurement`, `Observation`, etc.
Array of concept class IDs to filter by
**Examples:** `Clinical Finding`, `Pharmaceutical Substance`, `Procedure`
Whether to include only standard concepts
Whether to include invalid/deprecated concepts
Array of relationship filter objects
```json
[{
"relationship_id": "Maps to",
"target_vocabularies": ["SNOMED"],
"required": true
}]
```
Filter by validity date range
```json
{
"start_date": "2020-01-01",
"end_date": "2024-12-31"
}
```
Maximum number of results to return (max: 1000)
Number of results to skip for pagination
## Response
Whether the request was successful
Array of matching concepts
Unique concept identifier
Human-readable concept name
Concept code within its vocabulary
Source vocabulary identifier
Human-readable vocabulary name
Concept domain classification
Concept class within vocabulary
Standard concept designation (S = Standard, C = Classification)
Date when concept became valid
Date when concept becomes invalid
Reason for invalidity (if applicable)
Search relevance score (0.0 to 1.0)
Available facets for further filtering
Vocabulary facets with counts
Domain facets with counts
Concept class facets with counts
Search execution metadata
Query execution time in milliseconds
Total number of matching concepts
Highest relevance score in results
Search algorithm used
Pagination information
Requested page size
Number of results skipped
Total number of results
Whether more results are available
## Examples
### Basic Search
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/search/advanced" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "diabetes mellitus type 2",
"limit": 10
}'
```
```python Python
results = client.search.advanced_search(
query="diabetes mellitus type 2",
limit=10
)
for concept in results.concepts:
print(f"{concept.concept_id}: {concept.concept_name}")
print(f" Vocabulary: {concept.vocabulary_id}")
print(f" Score: {concept.relevance_score:.3f}")
```
```javascript JavaScript
const results = await client.search.advancedSearch({
query: 'diabetes mellitus type 2',
limit: 10
});
results.concepts.forEach(concept => {
console.log(`${concept.conceptId}: ${concept.conceptName}`);
console.log(` Vocabulary: ${concept.vocabularyId}`);
console.log(` Score: ${concept.relevanceScore.toFixed(3)}`);
});
```
```r R
results <- advanced_search_concepts(client,
query = "diabetes mellitus type 2",
limit = 10
)
for (i in 1:nrow(results$concepts)) {
concept <- results$concepts[i, ]
cat(paste(concept$concept_id, concept$concept_name, sep = ": "), "\n")
cat(paste(" Vocabulary:", concept$vocabulary_id), "\n")
cat(paste(" Score:", round(concept$relevance_score, 3)), "\n")
}
```
```json Example Response
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"relevance_score": 0.953
},
{
"concept_id": 435216,
"concept_name": "Diabetes mellitus type 2 in obese",
"concept_code": "E11.0",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "4-char billing code",
"standard_concept": null,
"valid_start_date": "2015-10-01",
"valid_end_date": "2099-12-31",
"invalid_reason": null,
"relevance_score": 0.847
}
],
"facets": {
"vocabularies": [
{"vocabulary_id": "SNOMED", "vocabulary_name": "SNOMED CT", "count": 45},
{"vocabulary_id": "ICD10CM", "vocabulary_name": "ICD-10-CM", "count": 23},
{"vocabulary_id": "ICD9CM", "vocabulary_name": "ICD-9-CM", "count": 12}
],
"domains": [
{"domain_id": "Condition", "domain_name": "Condition", "count": 67},
{"domain_id": "Observation", "domain_name": "Observation", "count": 13}
],
"concept_classes": [
{"concept_class_id": "Clinical Finding", "count": 45},
{"concept_class_id": "4-char billing code", "count": 23}
]
},
"search_metadata": {
"query_time_ms": 127,
"total_results": 80,
"max_relevance_score": 0.953,
"search_algorithm": "full_text_with_ranking"
}
},
"pagination": {
"limit": 10,
"offset": 0,
"total": 80,
"has_more": true
},
"meta": {
"request_id": "req_adv_search_abc123",
"timestamp": "2024-01-15T10:30:00Z",
"version": "1.0.0"
}
}
```
### Filtered Search
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/search/advanced" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "hypertension",
"vocabularies": ["SNOMED", "ICD10CM"],
"domains": ["Condition"],
"standard_concepts_only": true,
"date_range": {
"start_date": "2020-01-01",
"end_date": "2024-12-31"
},
"limit": 20
}'
```
```python Python
results = client.search.advanced_search(
query="hypertension",
vocabularies=["SNOMED", "ICD10CM"],
domains=["Condition"],
standard_concepts_only=True,
date_range={
"start_date": "2020-01-01",
"end_date": "2024-12-31"
},
limit=20
)
print(f"Found {results.pagination.total} concepts")
print(f"Query executed in {results.search_metadata.query_time_ms}ms")
```
```javascript JavaScript
const results = await client.search.advancedSearch({
query: 'hypertension',
vocabularies: ['SNOMED', 'ICD10CM'],
domains: ['Condition'],
standardConceptsOnly: true,
dateRange: {
startDate: '2020-01-01',
endDate: '2024-12-31'
},
limit: 20
});
console.log(`Found ${results.pagination.total} concepts`);
console.log(`Query executed in ${results.searchMetadata.queryTimeMs}ms`);
```
```r R
results <- advanced_search_concepts(client,
query = "hypertension",
vocabularies = c("SNOMED", "ICD10CM"),
domains = c("Condition"),
standard_concepts_only = TRUE,
date_range = list(
start_date = "2020-01-01",
end_date = "2024-12-31"
),
limit = 20
)
cat(paste("Found", results$pagination$total, "concepts\n"))
cat(paste("Query executed in", results$search_metadata$query_time_ms, "ms\n"))
```
### Complex Relationship Filtering
```bash cURL
curl -X POST "https://api.omophub.com/v1/concepts/search/advanced" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "insulin",
"vocabularies": ["RxNorm"],
"domains": ["Drug"],
"relationship_filters": [
{
"relationship_id": "RxNorm has dose form",
"target_vocabularies": ["RxNorm"],
"required": true
}
],
"concept_classes": ["Clinical Drug"],
"limit": 15
}'
```
```python Python
results = client.search.advanced_search(
query="insulin",
vocabularies=["RxNorm"],
domains=["Drug"],
relationship_filters=[
{
"relationship_id": "RxNorm has dose form",
"target_vocabularies": ["RxNorm"],
"required": True
}
],
concept_classes=["Clinical Drug"],
limit=15
)
for concept in results.concepts:
print(f"Drug: {concept.concept_name}")
print(f" Code: {concept.concept_code}")
print(f" Class: {concept.concept_class_id}")
```
```javascript JavaScript
const results = await client.search.advancedSearch({
query: 'insulin',
vocabularies: ['RxNorm'],
domains: ['Drug'],
relationshipFilters: [
{
relationshipId: 'RxNorm has dose form',
targetVocabularies: ['RxNorm'],
required: true
}
],
conceptClasses: ['Clinical Drug'],
limit: 15
});
results.concepts.forEach(concept => {
console.log(`Drug: ${concept.conceptName}`);
console.log(` Code: ${concept.conceptCode}`);
console.log(` Class: ${concept.conceptClassId}`);
});
```
```r R
results <- advanced_search_concepts(client,
query = "insulin",
vocabularies = c("RxNorm"),
domains = c("Drug"),
relationship_filters = list(
list(
relationship_id = "RxNorm has dose form",
target_vocabularies = c("RxNorm"),
required = TRUE
)
),
concept_classes = c("Clinical Drug"),
limit = 15
)
for (i in 1:nrow(results$concepts)) {
concept <- results$concepts[i, ]
cat(paste("Drug:", concept$concept_name), "\n")
cat(paste(" Code:", concept$concept_code), "\n")
cat(paste(" Class:", concept$concept_class_id), "\n")
}
```
## Search Features
### Full-Text Search
* **Multi-field search**: Searches concept names, synonyms, and descriptions
* **Phrase matching**: Use quotes for exact phrases: `"myocardial infarction"`
* **Wildcard support**: Use `*` for partial matching: `diabet*`
* **Boolean operators**: Use AND, OR, NOT: `diabetes AND type 2`
### Relevance Scoring
Results are ranked by relevance using:
1. **Exact matches**: Exact concept name matches score highest
2. **Phrase matches**: Complete phrase matches in names or synonyms
3. **Term frequency**: Frequency of query terms in concept text
4. **Vocabulary priority**: Standard concepts ranked higher
5. **Clinical relevance**: Healthcare-specific ranking adjustments
### Faceted Search
Use facets to understand result distribution:
```python
# Access facets for filtering insights
facets = results.facets
print("Available vocabularies:")
for vocab in facets.vocabularies:
print(f" {vocab.vocabulary_name}: {vocab.count} concepts")
print("\nAvailable domains:")
for domain in facets.domains:
print(f" {domain.domain_name}: {domain.count} concepts")
```
## Performance Tips
1. **Use specific vocabularies**: Limit search to relevant vocabularies only
2. **Filter by domain**: Reduce result set with domain filters
3. **Reasonable page sizes**: Use limit of 20-100 for best performance
4. **Cache results**: Cache frequently accessed searches
5. **Use standard concepts**: Set `standard_concepts_only: true` for faster queries
## Related Endpoints
Simple concept search with minimal parameters
Real-time search suggestions
Find semantically similar concepts
Get available search facets
# Basic Concept Search
Source: https://docs.omophub.com/api-reference/search/basic-search
GET /search/concepts
Search for medical concepts across vocabularies
## Overview
Search for medical concepts using text queries. This endpoint provides fast, relevant results across all supported vocabularies using advanced search algorithms.
## Query Parameters
Search query text (minimum 2 characters)
Limit search to specific vocabulary (e.g., "SNOMED", "ICD10CM")
Filter by medical domain (e.g., "Condition", "Procedure", "Drug")
Filter by concept class
Include invalid/deprecated concepts in results
Maximum number of results (max: 1000)
Number of results to skip for pagination
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/concepts?query=hypertension&limit=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
params = {"query": "hypertension", "limit": 10}
response = requests.get(
"https://api.omophub.com/v1/search/concepts",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
concepts = response.json()
```
```javascript JavaScript (Node.js)
const params = new URLSearchParams({
query: 'hypertension',
limit: '10'
});
const response = await fetch(`https://api.omophub.com/v1/search/concepts?${params}`, {
headers: {
'Authorization': `Bearer ${process.env.OMOPHUB_API_KEY}`
}
});
const concepts = await response.json();
```
```bash cURL (filtered)
curl -X GET "https://api.omophub.com/v1/search/concepts?query=diabetes&vocabulary=SNOMED&domain=Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (filtered)
import requests
params = {
"query": "diabetes",
"vocabulary": "SNOMED",
"domain": "Condition"
}
response = requests.get(
"https://api.omophub.com/v1/search/concepts",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
filtered_concepts = response.json()
```
```json
{
"success": true,
"data": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"match_score": 0.95,
"match_type": "exact",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"match_score": 0.89,
"match_type": "partial",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 4124681,
"concept_name": "Accelerated hypertension",
"concept_code": "48146000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"match_score": 0.85,
"match_type": "fuzzy",
"valid_start_date": "1970-01-01",
"valid_end_date": "2099-12-31"
}
],
"meta": {
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 247,
"total_pages": 25,
"has_next": true,
"has_previous": false
},
"search_info": {
"query": "hypertension",
"execution_time": "23ms",
"vocabularies_searched": ["SNOMED", "ICD10CM"],
"total_concepts_scanned": 156789
},
"request_id": "req_search_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Search Features
### 1. **Fuzzy Matching**
Handles typos and misspellings automatically.
### 2. **Semantic Search**
Finds conceptually similar terms even with different wording.
### 3. **Relevance Scoring**
Results ranked by relevance with match scores.
### 4. **Multi-vocabulary**
Searches across all vocabularies simultaneously.
### 5. **Fast Performance**
Optimized for sub-second response times.
## Common Use Cases
### 1. Clinical Coding
Find appropriate codes for clinical documentation.
### 2. Data Standardization
Map free-text clinical notes to standard concepts.
### 3. Quality Assurance
Validate and standardize existing coded data.
### 4. Research Applications
Find concepts for cohort definitions and outcome measures.
# Bulk Search
Source: https://docs.omophub.com/api-reference/search/bulk-search
POST https://api.omophub.com/v1/concepts/search/bulk
Perform search operations on multiple queries simultaneously with optimized batch processing and consolidated results.
## Overview
This endpoint allows you to submit multiple search queries in a single request, enabling efficient batch processing of search operations. It's ideal for applications that need to search for many terms simultaneously, such as data processing pipelines, bulk concept mapping, or batch validation workflows.
## Request Body
Array of search query objects
Unique identifier for this query within the batch
Search term or phrase
Target vocabularies for this query (comma-separated)
Target domains for this query (comma-separated)
Target concept classes for this query (comma-separated)
Type of search: `exact`, `fuzzy`, `semantic`, `phonetic`
Minimum relevance score for results (0.0-1.0)
Maximum results per query (max 100)
Include synonym matches
Filter by standard concept status: `S`, `N`, `C`
## Query Parameters
Processing mode for queries
**Options**: `parallel`, `sequential`, `adaptive`
Merge similar results across queries
Include performance statistics for each query
How to handle individual query failures
**Options**: `continue`, `stop_on_first`, `stop_on_threshold`
Maximum number of failed queries before stopping batch processing. Only used when failure\_handling is set to stop\_on\_threshold.
**Valid range:** Zero or greater
**Behavior:** Zero disables the threshold. Any positive value activates the threshold.
Represents the absolute count of failures allowed.
Maximum processing time for the entire batch (max 300)
Processing priority
**Options**: `low`, `normal`, `high`, `urgent`
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the batch request was successful
Unique identifier for this batch request
Total number of queries submitted
Number of queries that completed successfully
Number of queries that failed
Total processing time in milliseconds
Average time per query
Total number of results across all queries
Results for each query
Unique identifier from the request
Original search query
Query execution status (success, failed, timeout)
Number of results found for this query
Time taken to process this query
Array of matching concepts
Unique concept identifier
Primary concept name
Concept code
Source vocabulary
Domain classification
Concept class
Standard concept indicator
Relevance score (0.0-1.0)
Type of match (exact, partial, fuzzy, semantic)
The specific term that matched
Error information (if query failed)
Error code identifier
Human-readable error description
Additional error context
Query performance statistics (if include\_query\_stats=true)
Time for index lookup
Time for relevance scoring
Total candidates evaluated
Candidates after filtering
Whether results came from cache
Merged results across queries (if consolidate\_results=true)
Unique concept identifier
Primary concept name
Concept code
Source vocabulary
Domain classification
Highest relevance score across queries
Queries that matched this concept
Number of queries that found this concept
Details of queries that failed
Unique identifier for the request
Request timestamp
Processing mode used
Number of queries in the batch
Percentage of successful queries
Vocabulary release version used
```bash cURL
curl -X POST "https://api.omophub.com/v1/search/bulk?processing_mode=parallel&include_query_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"query_id": "q1",
"query": "diabetes",
"vocabulary_ids": "SNOMED,ICD10CM",
"domains": "Condition",
"max_results": 5
},
{
"query_id": "q2",
"query": "hypertension",
"vocabulary_ids": "SNOMED",
"search_type": "fuzzy",
"max_results": 10
},
{
"query_id": "q3",
"query": "cardiac catheterization",
"domains": "Procedure",
"include_synonyms": true,
"max_results": 3
}
]
}'
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/bulk?consolidate_results=true&include_query_stats=true', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
queries: [
{
query_id: 'q1',
query: 'diabetes mellitus',
vocabulary_ids: 'SNOMED,ICD10CM',
domains: 'Condition',
search_type: 'exact',
max_results: 10
},
{
query_id: 'q2',
query: 'appendectomy',
domains: 'Procedure',
search_type: 'semantic',
max_results: 5
},
{
query_id: 'q3',
query: 'metformin',
vocabulary_ids: 'RXNORM',
domains: 'Drug',
include_synonyms: true,
max_results: 8
}
]
})
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
payload = {
'queries': [
{
'query_id': 'diabetes_search',
'query': 'type 2 diabetes',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition',
'search_type': 'exact',
'max_results': 15,
'include_synonyms': True,
'standard_concept': 'S'
},
{
'query_id': 'procedure_search',
'query': 'knee replacement',
'vocabulary_ids': 'SNOMED,HCPCS',
'domains': 'Procedure',
'search_type': 'fuzzy',
'max_results': 10,
'min_score': 0.7
},
{
'query_id': 'drug_search',
'query': 'insulin glargine',
'vocabulary_ids': 'RXNORM,NDC',
'domains': 'Drug',
'search_type': 'semantic',
'max_results': 20
}
]
}
params = {
'processing_mode': 'parallel',
'consolidate_results': True,
'include_query_stats': True,
'timeout_seconds': 60
}
response = requests.post(
'https://api.omophub.com/v1/search/bulk',
headers=headers,
json=payload,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"batch_id": "batch_search_123456",
"processing_summary": {
"total_queries": 3,
"successful_queries": 3,
"failed_queries": 0,
"processing_time_ms": 2847,
"average_query_time_ms": 949.0,
"total_results_found": 28
},
"query_results": [
{
"query_id": "q1",
"query": "diabetes",
"status": "success",
"results_count": 12,
"processing_time_ms": 856,
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relevance_score": 0.95,
"match_type": "exact",
"matched_term": "diabetes mellitus"
},
{
"concept_id": 4000678,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relevance_score": 0.92,
"match_type": "partial",
"matched_term": "diabetes"
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S",
"relevance_score": 0.89,
"match_type": "exact",
"matched_term": "diabetes mellitus"
}
],
"query_stats": {
"index_lookup_time_ms": 234,
"scoring_time_ms": 456,
"total_candidates": 1247,
"filtered_candidates": 87,
"cache_hit": false
}
},
{
"query_id": "q2",
"query": "hypertension",
"status": "success",
"results_count": 8,
"processing_time_ms": 1034,
"concepts": [
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relevance_score": 0.98,
"match_type": "fuzzy",
"matched_term": "hypertension"
},
{
"concept_id": 316866,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relevance_score": 0.94,
"match_type": "partial",
"matched_term": "hypertensive"
}
],
"query_stats": {
"index_lookup_time_ms": 345,
"scoring_time_ms": 567,
"total_candidates": 934,
"filtered_candidates": 65,
"cache_hit": false
}
},
{
"query_id": "q3",
"query": "cardiac catheterization",
"status": "success",
"results_count": 8,
"processing_time_ms": 957,
"concepts": [
{
"concept_id": 4006969,
"concept_name": "Cardiac catheterization",
"concept_code": "41976001",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"standard_concept": "S",
"relevance_score": 1.0,
"match_type": "exact",
"matched_term": "cardiac catheterization"
},
{
"concept_id": 4139525,
"concept_name": "Left heart cardiac catheterization",
"concept_code": "309814006",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"standard_concept": "S",
"relevance_score": 0.91,
"match_type": "partial",
"matched_term": "cardiac catheterization"
},
{
"concept_id": 4273391,
"concept_name": "Right heart cardiac catheterization",
"concept_code": "609096002",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"standard_concept": "S",
"relevance_score": 0.89,
"match_type": "partial",
"matched_term": "cardiac catheterization"
}
],
"query_stats": {
"index_lookup_time_ms": 123,
"scoring_time_ms": 234,
"total_candidates": 456,
"filtered_candidates": 23,
"cache_hit": true
}
}
],
"consolidated_results": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"max_relevance_score": 0.95,
"query_matches": ["q1"],
"match_frequency": 1
},
{
"concept_id": 320128,
"concept_name": "Essential hypertension",
"concept_code": "59621000",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"max_relevance_score": 0.98,
"query_matches": ["q2"],
"match_frequency": 1
},
{
"concept_id": 4006969,
"concept_name": "Cardiac catheterization",
"concept_code": "41976001",
"vocabulary_id": "SNOMED",
"domain_id": "Procedure",
"max_relevance_score": 1.0,
"query_matches": ["q3"],
"match_frequency": 1
}
],
"failed_queries": []
},
"meta": {
"request_id": "req_bulk_search_123",
"timestamp": "2024-01-15T10:30:00Z",
"processing_mode": "parallel",
"batch_size": 3,
"success_rate": 100.0,
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Bulk Search
Search multiple terms simultaneously:
```bash
curl -X POST "https://api.omophub.com/v1/search/bulk" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{"query_id": "q1", "query": "diabetes", "max_results": 5},
{"query_id": "q2", "query": "hypertension", "max_results": 5},
{"query_id": "q3", "query": "asthma", "max_results": 5}
]
}'
```
### Advanced Bulk Search with Different Parameters
Use different search parameters for each query:
```bash
curl -X POST "https://api.omophub.com/v1/search/bulk?include_query_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{
"query_id": "exact_diabetes",
"query": "diabetes mellitus",
"search_type": "exact",
"vocabulary_ids": "SNOMED",
"max_results": 10
},
{
"query_id": "fuzzy_pneumonia",
"query": "pnemonia",
"search_type": "fuzzy",
"domains": "Condition",
"max_results": 5
},
{
"query_id": "semantic_surgery",
"query": "surgical procedure",
"search_type": "semantic",
"domains": "Procedure",
"max_results": 8
}
]
}'
```
### High-Volume Processing
Process large batches with optimized settings:
```bash
curl -X POST "https://api.omophub.com/v1/search/bulk?processing_mode=parallel&timeout_seconds=120&priority=high" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d @bulk_queries.json
```
### Consolidated Results
Get merged results across all queries:
```bash
curl -X POST "https://api.omophub.com/v1/search/bulk?consolidate_results=true&processing_mode=adaptive" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{"query_id": "q1", "query": "heart attack", "max_results": 10},
{"query_id": "q2", "query": "myocardial infarction", "max_results": 10},
{"query_id": "q3", "query": "MI", "max_results": 10}
]
}'
```
### Error Handling
Configure how to handle query failures:
```bash
curl -X POST "https://api.omophub.com/v1/search/bulk?failure_handling=continue&include_query_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queries": [
{"query_id": "valid", "query": "diabetes", "max_results": 5},
{"query_id": "invalid", "query": "", "max_results": 5},
{"query_id": "complex", "query": "very complex medical condition", "search_type": "semantic", "max_results": 10}
]
}'
```
## Processing Modes
### Parallel Processing
* **Description**: Execute all queries simultaneously
* **Benefits**: Fastest processing for independent queries
* **Best For**: Small to medium batches (less than 100 queries)
* **Trade-offs**: Higher resource usage
### Sequential Processing
* **Description**: Execute queries one at a time
* **Benefits**: Lower resource usage, predictable performance
* **Best For**: Large batches, resource-constrained environments
* **Trade-offs**: Slower overall processing time
### Adaptive Processing
* **Description**: Automatically choose optimal processing strategy
* **Benefits**: Balances speed and resource usage
* **Best For**: Variable query complexity, mixed batch sizes
* **Trade-offs**: Slight overhead for decision making
## Search Types in Bulk Operations
### Exact Search
* **Performance**: Fastest processing
* **Accuracy**: Highest precision
* **Use Case**: Known terms, validation workflows
### Fuzzy Search
* **Performance**: Moderate processing time
* **Accuracy**: Handles typos and variations
* **Use Case**: User input, OCR text processing
### Semantic Search
* **Performance**: Slower processing
* **Accuracy**: Contextual understanding
* **Use Case**: Natural language queries, concept discovery
### Phonetic Search
* **Performance**: Moderate processing time
* **Accuracy**: Handles pronunciation variations
* **Use Case**: Voice input, international terms
## Optimization Strategies
### Query Grouping
* **Group Similar Vocabularies**: Combine queries targeting same vocabularies
* **Domain Clustering**: Group queries by medical domain
* **Search Type Batching**: Batch similar search types together
### Performance Tuning
* **Limit Results**: Use appropriate max\_results values
* **Score Thresholds**: Set min\_score to filter low-quality matches
* **Vocabulary Selection**: Limit to relevant vocabularies only
### Resource Management
* **Batch Size**: Optimal batch size is 10-50 queries
* **Timeout Settings**: Allow sufficient time for complex queries
* **Priority Levels**: Use higher priority for time-sensitive requests
### Error Handling Strategies
* **Continue on Error**: Process all valid queries despite failures
* **Fail Fast**: Stop processing on first error for validation workflows
* **Threshold-based**: Stop when error rate exceeds acceptable level
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Single query search
* [Advanced Search](/api-reference/search/advanced-search) - Complex search with filters
* [Semantic Search](/api-reference/search/semantic-search) - Contextual search
* [Search Concepts](/api-reference/concepts/search-concepts) - Concept-specific search
# Fuzzy Search (Coming Soon)
Source: https://docs.omophub.com/api-reference/search/fuzzy-search
Search for medical concepts using fuzzy matching algorithms that handle typos, misspellings, and approximate string matching.
This feature is coming soon and is not yet available in the current API version.
## Overview
This endpoint will provide fuzzy search capabilities that can find relevant medical concepts even when the search query contains typos, spelling variations, or approximate matches. It will use advanced string matching algorithms including edit distance, phonetic matching, and character-based similarity scoring.
## Query Parameters
The search term or phrase (supports typos and variations)
Target vocabularies for search (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter results to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter to specific concept classes (comma-separated)
Minimum fuzzy match score (0.0-1.0, higher = more strict)
Maximum allowed edit distance (1-5)
Fuzzy matching algorithm
**Options**: `levenshtein`, `jaro_winkler`, `soundex`, `metaphone`, `hybrid`
Include phonetic similarity matching
Include abbreviation and acronym matching
Whether matching should be case sensitive
Whether word order affects matching score
Minimum word length for fuzzy matching
Give higher scores to exact substring matches
Filter by standard concept status: `S`, `N`, `C`
Include invalid/deprecated concepts
Search within concept synonyms
Search within concept descriptions
Language for linguistic processing (ISO 639-1 code)
Sort order for results
**Options**: `relevance`, `alphabetical`, `concept_id`, `vocabulary`
Page number for pagination
Number of results per page (max 100)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original search query
Algorithm used for fuzzy matching
Minimum match score applied
Maximum edit distance allowed
Whether phonetic matching was enabled
Whether case sensitivity was applied
Total concepts evaluated
Concepts found via fuzzy matching
Concepts found via phonetic matching
Concepts with exact substring matches
Time taken for search processing
Array of matching concepts
Unique concept identifier
Primary concept name
Concept code
Source vocabulary identifier
Human-readable vocabulary name
Domain classification
Concept class identifier
Standard concept indicator (S, N, C)
Fuzzy matching score (0.0-1.0)
The specific term that matched
Type of match (fuzzy, phonetic, exact\_substring, abbreviation)
Calculated edit distance
String similarity score
Whether this was a phonetic match
Position of match (start, middle, end, full)
Specific character differences found
Other spellings that would match this concept
Confidence in the match (High, Medium, Low)
Suggested corrections for the query
Alternative search terms to try
Common misspellings this query might represent
Unique identifier for the request
Request timestamp
Version of fuzzy matching algorithm used
Current page number
Items per page
Total number of matching concepts
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=diabetis&vocabulary_ids=SNOMED,ICD10CM&fuzzy_threshold=0.8&edit_distance=2&include_phonetic=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/fuzzy?query=pnemonia&domains=Condition&algorithm=hybrid&fuzzy_threshold=0.7&page_size=15', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'appendisitis',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition',
'fuzzy_threshold': 0.75,
'edit_distance': 3,
'algorithm': 'hybrid',
'include_phonetic': True,
'include_abbreviations': True,
'boost_exact_matches': True,
'page_size': 20
}
response = requests.get(
'https://api.omophub.com/v1/search/fuzzy',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"query": "diabetis",
"fuzzy_parameters": {
"algorithm": "hybrid",
"fuzzy_threshold": 0.8,
"edit_distance": 2,
"include_phonetic": true,
"case_sensitive": false
},
"search_statistics": {
"total_candidates": 15847,
"fuzzy_matches": 23,
"phonetic_matches": 8,
"exact_substring_matches": 2,
"processing_time_ms": 1247
},
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"fuzzy_score": 0.89,
"match_details": {
"matched_term": "diabetes mellitus",
"match_type": "fuzzy",
"edit_distance": 1,
"similarity_score": 0.89,
"phonetic_match": false,
"match_position": "start",
"character_differences": [
{
"position": 7,
"expected": "s",
"found": ""
}
]
},
"alternative_spellings": [
"diabetes",
"diabetis",
"diabetees"
],
"confidence_level": "High"
},
{
"concept_id": 201820,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"fuzzy_score": 0.92,
"match_details": {
"matched_term": "diabetes",
"match_type": "fuzzy",
"edit_distance": 1,
"similarity_score": 0.92,
"phonetic_match": false,
"match_position": "full",
"character_differences": [
{
"position": 7,
"expected": "s",
"found": ""
}
]
},
"alternative_spellings": [
"diabetes",
"diabetis",
"diabetic"
],
"confidence_level": "High"
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S",
"fuzzy_score": 0.87,
"match_details": {
"matched_term": "diabetes mellitus",
"match_type": "fuzzy",
"edit_distance": 1,
"similarity_score": 0.87,
"phonetic_match": false,
"match_position": "start",
"character_differences": [
{
"position": 7,
"expected": "s",
"found": ""
}
]
},
"alternative_spellings": [
"diabetes",
"diabetis"
],
"confidence_level": "High"
},
{
"concept_id": 4048098,
"concept_name": "Diabetic diet",
"concept_code": "160670007",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Observation",
"concept_class_id": "Regime/therapy",
"standard_concept": "S",
"fuzzy_score": 0.83,
"match_details": {
"matched_term": "diabetic",
"match_type": "fuzzy",
"edit_distance": 2,
"similarity_score": 0.83,
"phonetic_match": true,
"match_position": "start",
"character_differences": [
{
"position": 7,
"expected": "s",
"found": ""
},
{
"position": 8,
"expected": "ic",
"found": ""
}
]
},
"alternative_spellings": [
"diabetic",
"diabetis",
"diabetig"
],
"confidence_level": "Medium"
}
],
"suggestions": {
"did_you_mean": [
"diabetes",
"diabetic",
"diabetes mellitus"
],
"alternative_queries": [
"diabetes mellitus",
"diabetic condition",
"blood sugar disorder",
"hyperglycemia"
],
"common_misspellings": [
{
"misspelling": "diabetis",
"correction": "diabetes",
"frequency": "common"
},
{
"misspelling": "diabetees",
"correction": "diabetes",
"frequency": "rare"
},
{
"misspelling": "diabetess",
"correction": "diabetes",
"frequency": "uncommon"
}
]
}
},
"meta": {
"request_id": "req_fuzzy_search_123",
"timestamp": "2024-01-15T10:30:00Z",
"algorithm_version": "fuzzy_v2.1.0",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 23,
"total_pages": 2,
"has_next": true,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
**Note:** `concept_id` is the internal numeric identifier (OMOP ID) while `concept_code` is the source vocabulary code.
## Usage Examples
### Basic Fuzzy Search
Search with a misspelled term:
```bash
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=pnemonia&domains=Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Strict Fuzzy Matching
Use higher threshold for more precise matches:
```bash
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=appendisitis&fuzzy_threshold=0.85&edit_distance=1" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Phonetic-Enhanced Search
Include phonetic matching for pronunciation variations:
```bash
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=serkosis&algorithm=hybrid&include_phonetic=true&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Multi-Vocabulary Fuzzy Search
Search across multiple vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=diabetis&vocabulary_ids=SNOMED,ICD10CM,LOINC&fuzzy_threshold=0.7" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Case-Sensitive Fuzzy Search
Enable case sensitivity for specific use cases:
```bash
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=DNA&case_sensitive=true&include_abbreviations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Algorithm Comparison
Try different algorithms for optimal results:
```bash
# Levenshtein distance
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=miokardial&algorithm=levenshtein&edit_distance=3" \
-H "Authorization: Bearer YOUR_API_KEY"
# Jaro-Winkler similarity
curl -X GET "https://api.omophub.com/v1/search/fuzzy?query=miokardial&algorithm=jaro_winkler&fuzzy_threshold=0.8" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Fuzzy Matching Algorithms
### Levenshtein Distance
* **Description**: Counts minimum single-character edits (insertions, deletions, substitutions)
* **Best For**: Simple typos, OCR errors
* **Performance**: Fast
* **Example**: "diabetis" → "diabetes" (1 deletion)
### Jaro-Winkler Similarity
* **Description**: Considers character matches and transpositions, with prefix boost
* **Best For**: Names, transposed characters
* **Performance**: Moderate
* **Example**: "pnemonia" → "pneumonia" (transposition + insertion)
### Soundex
* **Description**: Phonetic algorithm based on English pronunciation
* **Best For**: Pronunciation variations, names
* **Performance**: Very fast
* **Example**: "cirosis" → "cirrhosis" (similar sound)
### Metaphone
* **Description**: Advanced phonetic algorithm with better accuracy than Soundex
* **Best For**: Complex phonetic variations
* **Performance**: Moderate
* **Example**: "nefritis" → "nephritis" (phonetic similarity)
### Hybrid Algorithm
* **Description**: Combines multiple algorithms with weighted scoring
* **Best For**: General-purpose fuzzy search
* **Performance**: Moderate to slow (most comprehensive)
* **Example**: Uses all methods and selects best matches
## Match Types and Scoring
### Edit Distance Scoring
* **Distance 1**: Single character difference (high confidence)
* **Distance 2**: Two character differences (medium confidence)
* **Distance 3+**: Multiple differences (lower confidence)
### Similarity Score Interpretation
* **0.9-1.0**: Very high similarity (likely correct match)
* **0.8-0.89**: High similarity (probable match)
* **0.7-0.79**: Moderate similarity (possible match)
* **0.6-0.69**: Low similarity (uncertain match)
* **Below 0.6**: Very low similarity (unlikely match)
### Match Position Impact
* **Full Match**: Entire query matches concept name
* **Start Match**: Query matches beginning of concept name
* **End Match**: Query matches end of concept name
* **Middle Match**: Query matches substring within concept name
## Common Medical Misspellings
### Cardiovascular Terms
* "miokardial" → "myocardial"
* "arteriosklerosis" → "arteriosclerosis"
* "hipertension" → "hypertension"
### Respiratory Terms
* "pnemonia" → "pneumonia"
* "asma" → "asthma"
* "bronkitis" → "bronchitis"
### Endocrine Terms
* "diabetis" → "diabetes"
* "hiperglycemia" → "hyperglycemia"
* "tiroid" → "thyroid"
### Neurological Terms
* "serebral" → "cerebral"
* "epilepsey" → "epilepsy"
* "demenshia" → "dementia"
### Gastrointestinal Terms
* "appendisitis" → "appendicitis"
* "gastroenterits" → "gastroenteritis"
* "kolitis" → "colitis"
## Performance Considerations
### Query Optimization
* **Short Queries**: Use edit distance limits (1-2)
* **Long Queries**: Increase fuzzy threshold (0.8+)
* **Multiple Words**: Enable word order flexibility
### Algorithm Selection
* **Speed Priority**: Use Levenshtein or Soundex
* **Accuracy Priority**: Use Hybrid algorithm
* **Phonetic Focus**: Use Metaphone or Soundex
* **Balanced Approach**: Use Jaro-Winkler
### Resource Management
* **Large Vocabularies**: Increase fuzzy threshold
* **Real-time Applications**: Limit edit distance
* **Batch Processing**: Use lower thresholds for recall
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Exact term matching
* [Phonetic Search](/api-reference/search/phonetic-search) - Sound-based matching
* [Semantic Search](/api-reference/search/semantic-search) - Meaning-based search
* [Search Autocomplete](/api-reference/search/search-autocomplete) - Query completion with fuzzy logic
# Get Similar Concepts by ID
Source: https://docs.omophub.com/api-reference/search/get-search-similar-by-id
GET https://api.omophub.com/v1/search/similar/{conceptId}
Find medical concepts similar to a specific concept identified by its OMOP concept ID using semantic similarity algorithms.
## Overview
This endpoint finds medical concepts that are semantically similar to a specific concept identified by its OMOP concept ID. It uses the same advanced machine learning algorithms as the POST version but optimized for concept-to-concept similarity matching, making it ideal for discovering related concepts when you have a specific starting point.
## Path Parameters
The OMOP concept ID to find similar concepts for
## Query Parameters
Target vocabularies to search within (comma-separated)
**Examples**: `SNOMED`, `SNOMED,ICD10CM`, `RXNORM,NDC`
Clinical domains to focus the similarity search (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Concept classes to include in similarity search (comma-separated)
**Examples**: `Clinical Finding,Procedure`, `Ingredient,Brand Name`
Minimum similarity score threshold (0.0 to 1.0)
**Higher values** = More strict similarity matching
Maximum number of similar concepts to return
Include similarity scores in the response
Include explanations for why concepts are considered similar
Filter to standard concepts only
**Options**: `S` (standard), `C` (classification), `NULL` (non-standard)
Include invalid/deprecated concepts in similarity search
Similarity algorithm to use
**Options**: `semantic` (embedding-based), `lexical` (text-based), `hybrid` (combined)
Exclude the source concept from results
## Response
Indicates if the request was successful
Error information (present only on error responses)
Error code identifier
Human-readable error message
Additional error details or context
Contains the similar concepts search results
Information about the source concept used for similarity search
OMOP concept ID of the source concept
Primary name of the source concept
Vocabulary-specific code
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Standard concept designation ('S', 'C', or null for non-standard)
Array of concepts similar to the source concept
OMOP concept ID
Primary concept name
Vocabulary-specific concept code
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Similarity score between 0.0 and 1.0
Reason for similarity match (if requested)
Standard concept designation ('S', 'C', or null for non-standard)
Alternative names for the concept
Information about the similarity search
The concept ID used as the similarity source
Similarity algorithm applied
Minimum similarity threshold applied
Total concepts evaluated for similarity
Number of similar concepts returned
Search processing time in milliseconds
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/similar/44054006?vocabulary_ids=SNOMED,ICD10CM&similarity_threshold=0.8&max_results=10&include_scores=true&include_explanations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript
const response = await fetch(
'https://api.omophub.com/v1/search/similar/44054006?vocabulary_ids=SNOMED,ICD10CM&similarity_threshold=0.8&max_results=10&include_scores=true&include_explanations=true',
{
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
}
);
const data = await response.json();
```
```python Python
import requests
url = "https://api.omophub.com/v1/search/similar/44054006"
params = {
"vocabulary_ids": "SNOMED,ICD10CM",
"similarity_threshold": 0.8,
"max_results": 10,
"include_scores": "true",
"include_explanations": "true"
}
response = requests.get(
url,
params=params,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"source_concept": {
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"similar_concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus without complications",
"concept_code": "E11.9",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char nonbill code",
"similarity_score": 0.92,
"explanation": "High similarity - more specific variant of the same condition",
"standard_concept": null,
"synonyms": ["Type II diabetes mellitus uncomplicated"]
},
{
"concept_id": 443729,
"concept_name": "Diabetes mellitus type 2 with hyperglycemia",
"concept_code": "443729",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"similarity_score": 0.89,
"explanation": "High similarity - related complication of the same condition",
"standard_concept": "S",
"synonyms": ["Type 2 DM with elevated glucose"]
},
{
"concept_id": 432367,
"concept_name": "Diabetes mellitus type 2 with diabetic nephropathy",
"concept_code": "E11.21",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "5-char billing code",
"similarity_score": 0.86,
"explanation": "High similarity - complication of the same condition",
"standard_concept": null,
"synonyms": ["Type 2 DM with kidney disease"]
}
],
"search_metadata": {
"source_concept_id": 44054006,
"algorithm_used": "hybrid",
"similarity_threshold": 0.8,
"total_candidates": 125000,
"results_returned": 3,
"processing_time_ms": 187
}
}
}
```
```json Error Response
{
"success": false,
"error": {
"code": "CONCEPT_NOT_FOUND",
"message": "The specified concept ID does not exist",
"details": {
"concept_id": 99999999,
"suggestion": "Verify the concept ID exists in the OMOP vocabularies"
}
}
}
```
## Usage Examples
### Basic Similarity by ID
Find concepts similar to Type 2 Diabetes (concept ID 44054006):
```bash
GET /v1/search/similar/44054006?max_results=5&similarity_threshold=0.7
```
### Cross-Vocabulary Similarity
Find similar concepts across multiple vocabularies:
```bash
GET /v1/search/similar/1308216?vocabulary_ids=SNOMED,ICD10CM,LOINC&domain_ids=Condition,Measurement&include_explanations=true
```
### Drug Similarity Search
Find similar pharmaceutical concepts:
```bash
GET /v1/search/similar/1503297?vocabulary_ids=RXNORM&domain_ids=Drug&concept_class_ids=Ingredient,Clinical Drug&similarity_threshold=0.8
```
### High-Precision Similarity
Get only highly similar concepts with detailed scoring:
```bash
GET /v1/search/similar/443390?similarity_threshold=0.9&max_results=10&include_scores=true&algorithm=semantic
```
## Performance Notes
* **Concept-to-concept similarity** is typically faster than query-based similarity
* **Hybrid algorithm** provides best balance of accuracy and performance
* **Semantic algorithm** is most accurate but computationally intensive
* **Caching** is applied for frequently requested concept similarities
## Related Endpoints
* [POST Search Similar](/api-reference/search/post-search-similar) - Find similar concepts using query text
* [Similarity Search](/api-reference/search/similarity-search) - Alternative similarity endpoint
* [Get Concept](/api-reference/concepts/get-concept) - Get detailed information about a specific concept
# Multi-lingual Search (Coming Soon)
Source: https://docs.omophub.com/api-reference/search/multi-lingual-search
Search for medical concepts across multiple languages with automatic language detection, translation, and cross-linguistic matching.
This feature is coming soon and is not yet available in the current API version.
## Overview
This endpoint will provide comprehensive multi-lingual search capabilities for medical concepts. It will be able to detect the input language automatically, search across multiple language versions of medical vocabularies, and provide results with translations. It will be essential for international healthcare applications and multilingual clinical environments.
## Query Parameters
Search term in any supported language
Target vocabularies for search (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter results to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter to specific concept classes (comma-separated)
Language of the input query (auto-detect or specify)
**Options**: `auto`, `en`, `es`, `fr`, `de`, `it`, `pt`, `nl`, `sv`, `da`, `no`, `fi`, `zh`, `ja`, `ko`, `ar`, `he`, `ru`
Languages to search within (comma-separated or special tokens)
**Individual Languages**: `en,es,fr`, `zh,ja,ko`, `de,it,pt`
**Special Tokens**:
* `all` - Search across all supported languages
* `european` - Major European languages (en, es, fr, de, it, pt, nl, sv, da, no, fi)
* `romance_languages` - Romance language family (es, fr, it, pt)
* `asian` - Major Asian languages (zh, ja, ko)
**Examples**: `all`, `european`, `romance_languages`, `en,zh,ja`
Languages for result translations (comma-separated or special tokens)
**Individual Languages**: `en`, `en,es,fr`, `zh,ja`
**Special Tokens**:
* `native_plus_english` - Original language plus English translation
* `european` - Major European languages (en, es, fr, de, it, pt)
* `all_supported` - All supported output languages
**Examples**: `en`, `native_plus_english`, `en,es,fr`, `european`
Quality level for translations
**Options**: `basic`, `standard`, `high`, `medical_grade`
Include transliterations for non-Latin scripts
Enable searching across different writing systems
Include regional language variants
Prioritize medical terminology over general translations
Adapt terms for cultural and regional medical practices
Expand search to include synonyms in all languages
Enable fuzzy matching for translated terms
Minimum confidence for language detection (0.0-1.0)
Minimum quality threshold for translations (0.0-1.0)
Filter by standard concept status: `S`, `N`, `C`
Include invalid/deprecated concepts
Sort order for results
**Options**: `relevance`, `language_match`, `translation_quality`, `alphabetical`
Page number for pagination
Number of results per page (max 100)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original search query
Automatically detected input language
Confidence in language detection (0.0-1.0)
Writing system (Latin, Cyrillic, Arabic, CJK, etc.)
Other possible languages for the query
Identified linguistic characteristics
Languages included in the search
Available vocabularies by language
Language translation pairs used
Multi-lingual search strategy applied
Total concepts evaluated across all languages
Number of matches found in each language
Number of translation operations performed
Matches found through cross-language search
Total processing time
Time spent on translations
Array of matching concepts with multi-lingual information
Unique concept identifier
Primary concept name (in original vocabulary language)
Concept code
Source vocabulary identifier
Human-readable vocabulary name
Domain classification
Concept class identifier
Standard concept indicator (S, N, C)
Overall relevance score (0.0-1.0)
Primary language of the concept
Language in which the match was found
Type of language match (native, translated, transliterated, cross\_script)
Specific term that matched in the matched language
Linguistic similarity score
Confidence in translation quality
Translations in requested output languages
Translated concept name
Quality assessment (Excellent, Good, Fair, Poor)
Source of translation (official, machine, hybrid)
Translation confidence (0.0-1.0)
Alternative translation options
Cultural adaptation notes (if applicable)
Synonyms in multiple languages
Array of synonyms in this language
Transliterations for non-Latin scripts (if requested)
Transliteration in specified script
Regional variations of the concept
Regional variation details
Grouping of results by language families
Language cluster identifier
Language family name
Languages in this cluster
Number of concepts in this cluster
Average translation quality
Translations of the original query
Related terms in various languages
Medical alternatives in different languages
Culturally appropriate equivalents
Unique identifier for the request
Request timestamp
Translation engine used
Language detection model version
All supported languages in the system
Current page number
Items per page
Total number of matching concepts
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=diabetes&input_language=auto&search_languages=en,es,fr,de&output_languages=en,es,fr&quality_label=high&include_transliterations=false" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/multilingual?query=心臟病&input_language=zh&search_languages=all&output_languages=en,zh,ja&cross_script_search=true&include_transliterations=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'maladie cardiaque',
'input_language': 'fr',
'search_languages': 'en,es,fr,de,it',
'output_languages': 'en,fr',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition',
'quality_label': 'medical_grade',
'regional_variants': True,
'medical_terminology_focus': True,
'synonym_expansion': True,
'cultural_adaptation': True,
'page_size': 25
}
response = requests.get(
'https://api.omophub.com/v1/search/multilingual',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"query": "diabetes",
"language_analysis": {
"detected_language": "en",
"language_confidence": 0.95,
"script_type": "Latin",
"alternative_languages": [
{"language": "es", "confidence": 0.15},
{"language": "pt", "confidence": 0.12}
],
"linguistic_features": {
"word_count": 1,
"character_set": "Latin",
"medical_terminology": true,
"common_in_languages": ["en", "es", "pt", "fr"]
}
},
"search_scope": {
"languages_searched": ["en", "es", "fr", "de"],
"vocabularies_per_language": {
"en": ["SNOMED", "ICD10CM", "LOINC", "RXNORM"],
"es": ["SNOMED", "ICD10CM"],
"fr": ["SNOMED", "ICD10CM"],
"de": ["SNOMED", "ICD10CM"]
},
"translation_pairs": [
{"from": "en", "to": "es"},
{"from": "en", "to": "fr"},
{"from": "es", "to": "en"},
{"from": "fr", "to": "en"}
],
"search_strategy": "native_first_with_translation_expansion"
},
"search_statistics": {
"total_candidates": 45672,
"matches_by_language": {
"en": 28,
"es": 15,
"fr": 12,
"de": 8
},
"translation_operations": 847,
"cross_linguistic_matches": 23,
"processing_time_ms": 3456,
"translation_time_ms": 1247
},
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relevance_score": 0.96,
"language_match_details": {
"primary_language": "en",
"matched_language": "en",
"match_type": "native",
"matched_term": "diabetes mellitus",
"language_similarity_score": 1.0,
"translation_confidence": 1.0
},
"translations": {
"es": {
"translated_name": "Diabetes mellitus tipo 2",
"quality_label": "Excellent",
"translation_source": "official",
"confidence_score": 0.98,
"alternative_translations": [
"Diabetes tipo 2",
"Diabetes mellitus no insulinodependiente"
],
"cultural_notes": null
},
"fr": {
"translated_name": "Diabète sucré de type 2",
"quality_label": "Excellent",
"translation_source": "official",
"confidence_score": 0.96,
"alternative_translations": [
"Diabète de type 2",
"Diabète non insulino-dépendant"
],
"cultural_notes": null
}
},
"synonyms_multilingual": {
"en": [
"Type 2 diabetes mellitus",
"Adult-onset diabetes",
"Non-insulin dependent diabetes",
"NIDDM"
],
"es": [
"Diabetes mellitus tipo 2",
"Diabetes no insulinodependiente",
"DM2"
],
"fr": [
"Diabète sucré de type 2",
"Diabète non insulino-dépendant",
"DNID"
]
},
"transliterations": {},
"regional_variants": {
"US": {
"name": "Type 2 diabetes mellitus",
"notes": "Standard US medical terminology"
},
"GB": {
"name": "Type 2 diabetes mellitus",
"notes": "GB medical terminology; may differ from US terminology"
},
"ES": {
"name": "Diabetes mellitus tipo 2",
"notes": "Spanish medical terminology"
}
}
},
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"relevance_score": 0.94,
"language_match_details": {
"primary_language": "en",
"matched_language": "en",
"match_type": "native",
"matched_term": "diabetes",
"language_similarity_score": 0.95,
"translation_confidence": 1.0
},
"translations": {
"es": {
"translated_name": "Diabetes mellitus",
"quality_label": "Excellent",
"translation_source": "official",
"confidence_score": 0.99,
"alternative_translations": [
"Diabetes",
"Diabetes sacarina"
],
"cultural_notes": "Term widely used across Spanish-speaking countries"
},
"fr": {
"translated_name": "Diabète sucré",
"quality_label": "Excellent",
"translation_source": "official",
"confidence_score": 0.97,
"alternative_translations": [
"Diabète",
"Diabète mellitus"
],
"cultural_notes": "Standard French medical terminology"
}
},
"synonyms_multilingual": {
"en": [
"Diabetes mellitus",
"Diabetes",
"DM"
],
"es": [
"Diabetes mellitus",
"Diabetes",
"DM"
],
"fr": [
"Diabète sucré",
"Diabète",
"DM"
]
},
"transliterations": {},
"regional_variants": {
"LATAM": {
"name": "Diabetes mellitus",
"notes": "Standard Latin American Spanish medical term"
},
"FR": {
"name": "Diabète sucré",
"notes": "Standard French medical terminology"
}
}
},
{
"concept_id": 435216,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "E11",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "3-char billing code",
"standard_concept": "S",
"relevance_score": 0.92,
"language_match_details": {
"primary_language": "en",
"matched_language": "en",
"match_type": "native",
"matched_term": "diabetes",
"language_similarity_score": 0.92,
"translation_confidence": 1.0
},
"translations": {
"es": {
"translated_name": "Diabetes mellitus tipo 2",
"quality_label": "Good",
"translation_source": "official",
"confidence_score": 0.89,
"alternative_translations": [
"E11 - Diabetes tipo 2"
],
"cultural_notes": "ICD-10 code used internationally"
},
"fr": {
"translated_name": "Diabète sucré de type 2",
"quality_label": "Good",
"translation_source": "official",
"confidence_score": 0.87,
"alternative_translations": [
"E11 - Diabète de type 2"
],
"cultural_notes": "ICD-10 code with French medical terminology"
}
},
"synonyms_multilingual": {
"en": [
"E11",
"Type 2 diabetes mellitus"
],
"es": [
"E11",
"Diabetes mellitus tipo 2"
],
"fr": [
"E11",
"Diabète sucré de type 2"
]
},
"transliterations": {},
"regional_variants": {
"WHO": {
"name": "Type 2 diabetes mellitus",
"notes": "World Health Organization ICD-10 standard"
}
}
}
],
"language_clusters": [
{
"cluster_id": "romance_languages",
"language_family": "Romance",
"languages": ["es", "fr", "it", "pt"],
"concept_count": 35,
"average_quality": 0.91
},
{
"cluster_id": "germanic_languages",
"language_family": "Germanic",
"languages": ["en", "de", "nl", "sv"],
"concept_count": 42,
"average_quality": 0.94
}
],
"translation_suggestions": {
"query_translations": {
"es": "diabetes",
"fr": "diabète",
"de": "diabetes",
"it": "diabete",
"pt": "diabetes"
},
"related_terms": {
"es": ["glucosa", "insulina", "azúcar"],
"fr": ["glucose", "insuline", "sucre"],
"de": ["Glukose", "Insulin", "Zucker"]
},
"medical_alternatives": {
"es": ["hiperglucemia", "trastorno metabólico"],
"fr": ["hyperglycémie", "trouble métabolique"],
"de": ["Hyperglykämie", "Stoffwechselstörung"]
},
"cultural_equivalents": {
"es": ["azúcar alta", "diabetes dulce"],
"fr": ["maladie du sucre"],
"de": ["Zuckerkrankheit"]
}
}
},
"meta": {
"request_id": "req_multilingual_search_123",
"timestamp": "2024-01-15T10:30:00Z",
"translation_engine": "medical_translate_v3.1",
"language_model_version": "polyglot_medical_v2.0",
"supported_languages": [
"en", "es", "fr", "de", "it", "pt", "nl", "sv", "da", "no", "fi",
"zh", "ja", "ko", "ar", "he", "ru", "pl", "cs", "hu", "ro", "bg"
],
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 63,
"total_pages": 4,
"has_next": true,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Automatic Language Detection
Let the system detect the input language automatically:
```bash
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=maladie%20cardiaque&input_language=auto&output_languages=en,fr" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cross-Script Search
Search with non-Latin scripts:
```bash
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=心臟病&input_language=zh&search_languages=all&output_languages=en,zh,ja&cross_script_search=true&include_transliterations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### European Language Search
Search across European languages:
```bash
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=diabetes&search_languages=european&output_languages=en,es,fr,de,it®ional_variants=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Medical-Grade Translation
Use highest quality medical translations:
```bash
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=pneumonie&input_language=fr&quality_label=medical_grade&medical_terminology_focus=true&cultural_adaptation=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Multi-lingual Search
Search with maximum language coverage:
```bash
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=cancer&search_languages=all&output_languages=en,es,fr,de,it,pt,zh,ja&synonym_expansion=true&fuzzy_translation=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cultural Adaptation
Search with cultural and regional adaptations:
```bash
curl -X GET "https://api.omophub.com/v1/search/multilingual?query=الربو&input_language=ar&search_languages=ar,en&output_languages=ar,en&cultural_adaptation=true®ional_variants=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Language Support Tiers
### Tier 1 Languages (Full Support)
* **English (en)**: Complete vocabulary coverage, native content
* **Spanish (es)**: Extensive translation, regional variants
* **French (fr)**: Medical terminology, Canadian variants
* **German (de)**: Medical focus, Austrian/Swiss variants
* **Italian (it)**: Clinical terminology, regional dialects
### Tier 2 Languages (Good Support)
* **Portuguese (pt)**: Brazilian/European variants
* **Dutch (nl)**: Belgian/Netherlands variants
* **Swedish (sv)**: Nordic medical terminology
* **Norwegian (no)**: Bokmål/Nynorsk variants
* **Danish (da)**: Medical terminology
* **Finnish (fi)**: Unique linguistic structure
### Tier 3 Languages (Basic Support)
* **Chinese (zh)**: Simplified/Traditional scripts
* **Japanese (ja)**: Kanji/Hiragana/Katakana
* **Korean (ko)**: Hangul script
* **Arabic (ar)**: Right-to-left script
* **Hebrew (he)**: Medical terminology
* **Russian (ru)**: Cyrillic script
### Tier 4 Languages (Limited Support)
* **Polish (pl)**: Basic medical terms
* **Czech (cs)**: Essential vocabulary
* **Hungarian (hu)**: Core medical concepts
* **Romanian (ro)**: Basic coverage
* **Bulgarian (bg)**: Limited vocabulary
## Language Groups
### European Languages
* **Romance**: Spanish, French, Italian, Portuguese, Romanian
* **Germanic**: English, German, Dutch, Swedish, Norwegian, Danish
* **Slavic**: Russian, Polish, Czech, Bulgarian
* **Other**: Finnish, Hungarian
### Asian Languages
* **East Asian**: Chinese, Japanese, Korean
* **Scripts**: Latin, Cyrillic, Arabic, CJK, Devanagari
### Regional Variants
* **Spanish**: Spain (es-ES), Mexico (es-MX), Argentina (es-AR)
* **Portuguese**: Brazil (pt-BR), Portugal (pt-PT)
* **French**: France (fr-FR), Canada (fr-CA), Belgium (fr-BE)
* **English**: US (en-US), UK (en-GB), Australia (en-AU), Canada (en-CA)
## Translation Quality Levels
### Medical Grade
* **Description**: Highest quality, medically reviewed translations
* **Use Case**: Clinical documentation, patient safety
* **Accuracy**: 95%+
* **Review**: Medical professional reviewed
* **Cost**: Higher processing time
### High Quality
* **Description**: Professional medical translations
* **Use Case**: Clinical reference, medical education
* **Accuracy**: 90-95%
* **Review**: Automated quality checks
* **Cost**: Standard processing time
### Standard
* **Description**: Good quality general translations
* **Use Case**: General medical search, information lookup
* **Accuracy**: 85-90%
* **Review**: Basic quality validation
* **Cost**: Fast processing
### Basic
* **Description**: Machine translation with minimal validation
* **Use Case**: Quick reference, broad search
* **Accuracy**: 75-85%
* **Review**: Automated only
* **Cost**: Fastest processing
## Cultural Adaptation Features
### Regional Medical Practices
* **Terminology**: Adapt terms to regional medical practices
* **Measurements**: Convert units (metric/imperial)
* **Procedures**: Regional procedure variations
* **Medications**: Local drug name variations
### Cultural Sensitivity
* **Concepts**: Culturally sensitive medical concepts
* **Taboos**: Avoid culturally inappropriate terms
* **Preferences**: Regional term preferences
* **Context**: Cultural medical context
### Healthcare Systems
* **Insurance**: Healthcare system terminology
* **Coding**: Regional coding systems
* **Regulations**: Local regulatory terminology
* **Standards**: Regional medical standards
## Performance Optimization
### Language Selection Strategy
* **Single Language**: Fastest, most accurate
* **Language Family**: Good balance of speed and coverage
* **Regional Group**: Comprehensive within region
* **All Languages**: Maximum coverage, slower processing
### Translation Caching
* **Common Terms**: Cached medical translations
* **User Patterns**: User-specific translation cache
* **Regional Cache**: Regional translation preferences
* **Quality Cache**: High-quality translation storage
### Search Strategy
* **Native First**: Search in native language first
* **Translation Expansion**: Expand through translations
* **Cross-Linguistic**: Direct cross-language matching
* **Hybrid Approach**: Combine multiple strategies
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Single language search
* [Semantic Search](/api-reference/search/semantic-search) - Meaning-based search
* [Phonetic Search](/api-reference/search/phonetic-search) - Pronunciation-based search
* [Search Concepts](/api-reference/concepts/search-concepts) - Comprehensive concept search
# Phonetic Search (Coming Soon)
Source: https://docs.omophub.com/api-reference/search/phonetic-search
Search for medical concepts using phonetic algorithms that match terms based on pronunciation rather than spelling.
This feature is coming soon and is not yet available in the current API version.
## Overview
This endpoint will provide phonetic search capabilities that find medical concepts based on how they sound rather than how they're spelled. It will be particularly useful for handling pronunciation variations, names from different languages, spoken input transcription, and medical terminology with complex or variable pronunciations.
## Query Parameters
Search term or phrase (pronunciation-based matching)
Target vocabularies for search (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter results to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter to specific concept classes (comma-separated)
Phonetic matching algorithm
**Options**: `soundex`, `metaphone`, `double_metaphone`, `nysiis`, `match_rating`, `hybrid`
Minimum phonetic similarity score (0.0-1.0, higher = more strict)
Language variant for pronunciation rules
**Options**: `en-US`, `en-GB`, `en-CA`, `en-AU`, `es-ES`, `fr-FR`, `de-DE`, `multi`
Include pronunciation variants and regional differences
Use medical-specific pronunciation rules
Enable cross-language phonetic matching
Tolerance level for accent variations
**Options**: `strict`, `medium`, `high`, `very_high`
Enable syllable-based matching for complex terms
Apply phoneme-specific weighting for medical terms
Include phonetic matching of abbreviations and acronyms
Include name pronunciation variants (for drug names, anatomical terms)
Filter by standard concept status: `S` (Standard), `C` (Classification). Non-standard concepts are indicated by null/empty value.
Include invalid/deprecated concepts
Search within concept synonyms
Sort order for results
**Options**: `phonetic_score`, `alphabetical`, `frequency`, `concept_id`
Page number for pagination
Number of results per page (max 100)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original search query
Generated phonetic code for the query
Phonetic algorithm used
Detected language/pronunciation variant
Number of syllables detected
Phoneme analysis of the query
Identified pronunciation variants
Total concepts evaluated phonetically
Concepts found via phonetic matching
Concepts with identical phonetic codes
Matches through pronunciation variants
Matches across different languages
Time taken for phonetic processing
Array of phonetically matching concepts
Unique concept identifier
Primary concept name
Concept code
Source vocabulary identifier
Human-readable vocabulary name
Domain classification
Concept class identifier
Standard concept indicator: S (Standard), C (Classification), or null (non-standard)
Phonetic similarity score (0.0-1.0)
The specific term that matched phonetically
Phonetic code of the matched term
Type of phonetic match (exact, variant, cross\_language, syllable)
Phonetic distance score
Syllable-level matching score
Overall pronunciation similarity
Type of accent variation matched
Language variant of the match
Phonetic spelling of the concept
International Phonetic Alphabet transcription
Syllable stress pattern
Common mispronunciation patterns
Regional pronunciation variants
Other ways this concept might be pronounced
Confidence in phonetic match (High, Medium, Low)
Groups of phonetically similar concepts
Cluster identifier
Common phonetic pattern
Number of concepts in cluster
Most common pronunciation in cluster
Number of pronunciation variants
Phonetically similar terms to try
Common alternative pronunciations
Medical pronunciation variants
Regional pronunciation alternatives
Unique identifier for the request
Request timestamp
Version of phonetic algorithm used
Language model used for pronunciation
Current page number
Items per page
Total number of matching concepts
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=noomonia&vocabulary_ids=SNOMED,ICD10CM&phonetic_algorithm=double_metaphone&phonetic_threshold=0.8&include_variants=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/phonetic?query=sirrosis&domains=Condition&phonetic_algorithm=hybrid&accent_tolerance=high&medical_pronunciation=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'bronkytis',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition',
'phonetic_algorithm': 'double_metaphone',
'phonetic_threshold': 0.75,
'language_variant': 'en-US',
'include_variants': True,
'medical_pronunciation': True,
'accent_tolerance': 'medium',
'syllable_matching': True,
'include_synonyms': True,
'page_size': 25
}
response = requests.get(
'https://api.omophub.com/v1/search/phonetic',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"query": "noomonia",
"phonetic_analysis": {
"phonetic_code": "NMN",
"algorithm": "double_metaphone",
"detected_language": "en-US",
"syllable_count": 3,
"phoneme_breakdown": [
{"phoneme": "n", "position": "initial"},
{"phoneme": "u", "position": "medial"},
{"phoneme": "m", "position": "medial"},
{"phoneme": "o", "position": "medial"},
{"phoneme": "n", "position": "medial"},
{"phoneme": "i", "position": "final"},
{"phoneme": "a", "position": "final"}
],
"pronunciation_variants": [
"noomonia",
"newmonia",
"numonia"
]
},
"search_statistics": {
"total_candidates": 25847,
"phonetic_matches": 18,
"exact_phonetic_matches": 6,
"variant_matches": 8,
"cross_language_matches": 4,
"processing_time_ms": 987
},
"concepts": [
{
"concept_id": 205237003,
"concept_name": "Pneumonia",
"concept_code": "205237003",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"phonetic_score": 0.95,
"phonetic_details": {
"matched_term": "pneumonia",
"phonetic_code": "NMN",
"match_type": "exact",
"phonetic_distance": 0.05,
"syllable_match_score": 0.92,
"pronunciation_similarity": 0.95,
"accent_variation": "standard",
"language_variant": "en-US"
},
"pronunciation_info": {
"phonetic_spelling": "noo-MOH-nee-ah",
"ipa_transcription": "/nuˈmoʊniə/",
"stress_pattern": "secondary-PRIMARY-tertiary",
"common_mispronunciations": [
"new-MOH-nee-ah",
"puh-new-MOH-nee-ah"
],
"regional_variants": [
{"variant": "en-GB", "pronunciation": "/njuːˈməʊniə/"},
{"variant": "en-AU", "pronunciation": "/nuːˈmoʊniə/"}
]
},
"alternative_pronunciations": [
"newmonia",
"numonia",
"pnoomonia"
],
"confidence_level": "High"
},
{
"concept_id": 4195694,
"concept_name": "Bacterial pneumonia",
"concept_code": "53084003",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"phonetic_score": 0.89,
"phonetic_details": {
"matched_term": "pneumonia",
"phonetic_code": "NMN",
"match_type": "variant",
"phonetic_distance": 0.11,
"syllable_match_score": 0.87,
"pronunciation_similarity": 0.89,
"accent_variation": "compound",
"language_variant": "en-US"
},
"pronunciation_info": {
"phonetic_spelling": "bak-TEER-ee-al noo-MOH-nee-ah",
"ipa_transcription": "/bækˈtɪriəl nuˈmoʊniə/",
"stress_pattern": "tertiary-PRIMARY-tertiary secondary-PRIMARY-tertiary",
"common_mispronunciations": [
"bacterial newmonia",
"bacterial numonia"
],
"regional_variants": [
{"variant": "en-GB", "pronunciation": "/bækˈtɪərɪəl njuːˈməʊniə/"}
]
},
"alternative_pronunciations": [
"bacterial newmonia",
"bacterial numonia"
],
"confidence_level": "High"
},
{
"concept_id": 4090305,
"concept_name": "Viral pneumonia",
"concept_code": "406856006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"phonetic_score": 0.87,
"phonetic_details": {
"matched_term": "pneumonia",
"phonetic_code": "NMN",
"match_type": "variant",
"phonetic_distance": 0.13,
"syllable_match_score": 0.85,
"pronunciation_similarity": 0.87,
"accent_variation": "compound",
"language_variant": "en-US"
},
"pronunciation_info": {
"phonetic_spelling": "VY-ral noo-MOH-nee-ah",
"ipa_transcription": "/ˈvaɪrəl nuˈmoʊniə/",
"stress_pattern": "PRIMARY-tertiary secondary-PRIMARY-tertiary",
"common_mispronunciations": [
"viral newmonia",
"vyral numonia"
],
"regional_variants": [
{"variant": "en-GB", "pronunciation": "/ˈvaɪərəl njuːˈməʊniə/"}
]
},
"alternative_pronunciations": [
"viral newmonia",
"viral numonia"
],
"confidence_level": "High"
},
{
"concept_id": 445435003,
"concept_name": "Pneumonia",
"concept_code": "J18",
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"domain_id": "Condition",
"concept_class_id": "3-character category",
"standard_concept": "C",
"phonetic_score": 0.94,
"phonetic_details": {
"matched_term": "pneumonia",
"phonetic_code": "NMN",
"match_type": "exact",
"phonetic_distance": 0.06,
"syllable_match_score": 0.91,
"pronunciation_similarity": 0.94,
"accent_variation": "standard",
"language_variant": "en-US"
},
"pronunciation_info": {
"phonetic_spelling": "noo-MOH-nee-ah",
"ipa_transcription": "/nuˈmoʊniə/",
"stress_pattern": "secondary-PRIMARY-tertiary",
"common_mispronunciations": [
"new-MOH-nee-ah"
],
"regional_variants": [
{"variant": "en-GB", "pronunciation": "/njuːˈməʊniə/"}
]
},
"alternative_pronunciations": [
"newmonia",
"numonia"
],
"confidence_level": "High"
}
],
"phonetic_clusters": [
{
"cluster_id": "pneumonia_variants",
"phonetic_pattern": "NMN",
"concept_count": 12,
"representative_pronunciation": "noo-MOH-nee-ah",
"variant_count": 8
},
{
"cluster_id": "respiratory_conditions",
"phonetic_pattern": "N*N",
"concept_count": 6,
"representative_pronunciation": "varies",
"variant_count": 15
}
],
"pronunciation_suggestions": {
"similar_pronunciations": [
"pneumonia",
"newmonia",
"numonia"
],
"common_alternatives": [
"lung infection",
"chest infection",
"respiratory infection"
],
"medical_variants": [
"pneumonitis",
"pneumonic",
"pulmonary infection"
],
"regional_alternatives": [
{"region": "British", "pronunciation": "nyoo-MOH-nee-ah"},
{"region": "Australian", "pronunciation": "noo-MOH-nee-ah"},
{"region": "Canadian", "pronunciation": "noo-MOH-nee-ah"}
]
}
},
"meta": {
"request_id": "req_phonetic_search_123",
"timestamp": "2024-01-15T10:30:00Z",
"phonetic_algorithm_version": "double_metaphone_v3.2",
"language_model": "medical_pronunciation_v1.5",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 18,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Phonetic Search
Search with a mispronounced medical term:
```bash
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=sirrosis&domains=Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Advanced Phonetic Matching
Use sophisticated phonetic algorithms:
```bash
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=bronkytis&phonetic_algorithm=hybrid&phonetic_threshold=0.8&medical_pronunciation=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Cross-Language Phonetic Search
Enable matching across language variants:
```bash
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=arteriosklerosis&cross_language=true&language_variant=multi&accent_tolerance=high" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Voice Input Processing
Optimize for voice recognition results:
```bash
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=diabetees&phonetic_algorithm=double_metaphone&accent_tolerance=very_high&include_variants=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Drug Name Pronunciation
Search pharmaceutical terms by pronunciation:
```bash
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=metformin&vocabulary_ids=RXNORM&name_variants=true&include_abbreviations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Regional Accent Handling
Handle regional pronunciation differences:
```bash
curl -X GET "https://api.omophub.com/v1/search/phonetic?query=aluminium&language_variant=en-GB&accent_tolerance=medium&include_variants=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Phonetic Algorithms
### Soundex
* **Description**: Classic American phonetic algorithm based on consonant sounds
* **Best For**: Simple pronunciation matching, English names
* **Accuracy**: Basic
* **Performance**: Very fast
* **Example**: "Smith" and "Smyth" both encode to "S530"
### Metaphone
* **Description**: Improved phonetic algorithm with better English pronunciation rules
* **Best For**: English medical terms, better accuracy than Soundex
* **Accuracy**: Good
* **Performance**: Fast
* **Example**: Better handling of "PH" sounds, silent letters
### Double Metaphone
* **Description**: Advanced algorithm generating two phonetic codes for better matching
* **Best For**: General phonetic search, handles pronunciation variants
* **Accuracy**: Very good
* **Performance**: Moderate
* **Example**: Handles alternative pronunciations and foreign names
### NYSIIS (New York State Identification and Intelligence System)
* **Description**: Name-focused phonetic algorithm with excellent accuracy
* **Best For**: Person names, anatomical terms, drug names
* **Accuracy**: Excellent for names
* **Performance**: Moderate
* **Example**: Superior handling of name variations
### Match Rating Approach
* **Description**: Algorithm comparing phonetic strings directly
* **Best For**: Comparing similar-length terms
* **Accuracy**: Good for specific use cases
* **Performance**: Fast
* **Example**: Direct phonetic string comparison
### Hybrid Algorithm
* **Description**: Combines multiple algorithms with weighted scoring
* **Best For**: Comprehensive phonetic search
* **Accuracy**: Highest
* **Performance**: Slower (most comprehensive)
* **Example**: Uses best features from all algorithms
## Medical Phonetic Challenges
### Latin and Greek Origins
* **Challenge**: Medical terms often derive from Latin/Greek
* **Examples**: "pneumonia" (silent 'p'), "psychology" (silent 'p')
* **Solution**: Medical-specific pronunciation rules
* **Algorithm**: Enhanced Metaphone or Hybrid
### Silent Letters
* **Challenge**: Many medical terms have silent letters
* **Examples**: "pneumonia", "psychology", "knee"
* **Solution**: Medical pronunciation dictionary
* **Algorithm**: Double Metaphone with medical rules
### Compound Terms
* **Challenge**: Medical terms often combine multiple roots
* **Examples**: "gastroenterology", "electrocardiogram"
* **Solution**: Syllable-based matching
* **Algorithm**: Syllable-enhanced algorithms
### Abbreviations and Acronyms
* **Challenge**: Medical abbreviations pronounced as words or letters
* **Examples**: "MRI" (em-ar-eye), "COPD" (see-oh-pee-dee)
* **Solution**: Abbreviation pronunciation rules
* **Algorithm**: Hybrid with abbreviation handling
### Regional Variations
* **Challenge**: Medical terms pronounced differently by region
* **Examples**: "aluminium" vs "aluminum", "beta" vs "beeta"
* **Solution**: Regional pronunciation variants
* **Algorithm**: Multi-variant Metaphone
## Accent Tolerance Levels
### Strict
* **Description**: Minimal tolerance for pronunciation variations
* **Use Case**: Exact pronunciation matching
* **Threshold**: High (0.9+)
* **Examples**: Research applications, precise terminology
### Medium
* **Description**: Moderate tolerance for common variations
* **Use Case**: General medical search
* **Threshold**: Medium (0.7-0.9)
* **Examples**: Clinical documentation, general queries
### High
* **Description**: High tolerance for accent and pronunciation differences
* **Use Case**: International users, voice input
* **Threshold**: Lower (0.6-0.8)
* **Examples**: Multi-cultural environments, voice recognition
### Very High
* **Description**: Maximum tolerance for pronunciation variations
* **Use Case**: Heavy accents, non-native speakers
* **Threshold**: Lowest (0.5-0.7)
* **Examples**: ESL users, heavily accented speech
## Language Variants
### English Variants
* **en-US**: American English pronunciation rules
* **en-GB**: British English pronunciation patterns
* **en-AU**: Australian English variations
* **en-CA**: Canadian English specifics
### Medical Terminology
* **Latin**: Classical medical Latin pronunciation
* **Greek**: Greek-origin medical terms
* **French**: French medical terminology
* **German**: German medical terms
### Multi-Language
* **Approach**: Cross-language phonetic matching
* **Use Case**: International medical environments
* **Challenge**: Different phonetic systems
* **Solution**: Unified phonetic representation
## Common Medical Mispronunciations
### Respiratory Terms
* "noomonia" → "pneumonia"
* "bronkytis" → "bronchitis"
* "asma" → "asthma"
### Cardiovascular Terms
* "arteriosklerosis" → "arteriosclerosis"
* "miokardial" → "myocardial"
* "anjeena" → "angina"
### Neurological Terms
* "serebral" → "cerebral"
* "alzhymers" → "Alzheimer's"
* "epilepsy" → "epilepsy" (various pronunciations)
### Gastrointestinal Terms
* "appendisytis" → "appendicitis"
* "gastroenterytis" → "gastroenteritis"
* "sirrosis" → "cirrhosis"
### Endocrine Terms
* "diabetees" → "diabetes"
* "thyroyd" → "thyroid"
* "insulin" → "insulin" (stress variations)
## Performance Optimization
### Algorithm Selection
* **Speed Priority**: Use Soundex or Metaphone
* **Accuracy Priority**: Use Double Metaphone or Hybrid
* **Name Matching**: Use NYSIIS
* **Balanced**: Use Double Metaphone
### Threshold Tuning
* **High Precision**: Use 0.8+ threshold
* **Balanced**: Use 0.7 threshold
* **High Recall**: Use 0.6 threshold
* **Maximum Coverage**: Use 0.5 threshold
### Language Optimization
* **Single Language**: Use specific variant (en-US, en-GB)
* **Multi-Language**: Use 'multi' variant
* **Medical Focus**: Enable medical pronunciation rules
* **Voice Input**: Use high accent tolerance
## Related Endpoints
* [Fuzzy Search](/api-reference/search/fuzzy-search) - Typography-based fuzzy matching
* [Basic Search](/api-reference/search/basic-search) - Exact text matching
* [Multi-lingual Search](/api-reference/search/multi-lingual-search) - Language-specific search
* [Search Autocomplete](/api-reference/search/search-autocomplete) - Query completion with phonetic support
# POST Search Similar Concepts
Source: https://docs.omophub.com/api-reference/search/post-search-similar
POST https://api.omophub.com/v1/search/similar
Find semantically similar medical concepts using advanced machine learning algorithms with flexible search criteria and body parameters.
## Overview
This endpoint identifies medical concepts that are semantically similar to a provided query or set of criteria. It leverages advanced machine learning models trained on medical terminology to discover related concepts that may not share exact keywords but are clinically relevant and contextually similar.
## Request Body
Primary search query or concept description to find similar concepts for
Target vocabularies to search within (array of strings)
**Examples**: `["SNOMED", "ICD10CM"]`, `["RXNORM", "NDC"]`
Clinical domains to focus the similarity search (array of strings)
**Examples**: `["Condition", "Procedure"]`, `["Drug", "Device"]`
Concept classes to include in similarity search (array of strings)
**Examples**: `["Clinical Finding", "Procedure"]`, `["Ingredient", "Brand Name"]`
Minimum similarity score threshold (0.1 to 1.0)
**Higher values** = More strict similarity matching
Maximum number of similar concepts to return
Include similarity scores in the response
Include explanations for why concepts are considered similar
Filter to standard concepts only
**Options**: `S` (standard), `C` (classification), `N` (non-standard)
Include invalid/deprecated concepts in similarity search
Similarity algorithm to use
**Options**: `semantic` (embedding-based), `lexical` (text-based), `hybrid` (combined)
## Response
Indicates if the request was successful
Contains the similar concepts search results
Array of concepts similar to the query
OMOP concept ID
Primary concept name
Vocabulary-specific concept code
Source vocabulary identifier
Clinical domain classification
Concept class identifier
Similarity score between 0.0 and 1.0
Reason for similarity match (if requested)
Standard concept flag (S, C, or N)
Alternative names for the concept
Information about the similarity search
The original search query provided
Similarity algorithm applied
Minimum similarity threshold applied
Total concepts evaluated for similarity
Number of similar concepts returned
Search processing time in milliseconds
```bash cURL
curl -X POST "https://api.omophub.com/v1/search/similar" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "type 2 diabetes mellitus",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.8,
"max_results": 10,
"include_scores": true,
"include_explanations": true,
"standard_concept": "S"
}'
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/similar', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: "type 2 diabetes mellitus",
vocabulary_ids: ["SNOMED", "ICD10CM"],
domain_ids: ["Condition"],
similarity_threshold: 0.8,
max_results: 10,
include_scores: true,
include_explanations: true,
standard_concept: "S"
})
});
const data = await response.json();
```
```python Python
import requests
url = "https://api.omophub.com/v1/search/similar"
payload = {
"query": "type 2 diabetes mellitus",
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.8,
"max_results": 10,
"include_scores": True,
"include_explanations": True,
"standard_concept": "S"
}
response = requests.post(
url,
json=payload,
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"similar_concepts": [
{
"concept_id": 44054006,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"similarity_score": 1.0,
"explanation": "Exact match - same clinical concept",
"standard_concept": "S",
"synonyms": ["Non-insulin dependent diabetes mellitus", "Adult-onset diabetes"]
},
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus without complications",
"concept_code": "E11.9",
"vocabulary_id": "ICD10CM",
"domain_id": "Condition",
"concept_class_id": "4-char nonbill code",
"similarity_score": 0.92,
"explanation": "High similarity - more specific variant of the same condition",
"standard_concept": "S",
"synonyms": ["Type II diabetes mellitus uncomplicated"]
},
{
"concept_id": 443729,
"concept_name": "Diabetes mellitus type 2 with hyperglycemia",
"concept_code": "443729",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"similarity_score": 0.89,
"explanation": "High similarity - related complication of the same condition",
"standard_concept": "S",
"synonyms": ["Type 2 DM with elevated glucose"]
}
],
"search_metadata": {
"original_query": "type 2 diabetes mellitus",
"algorithm_used": "hybrid",
"similarity_threshold": 0.8,
"total_candidates": 125000,
"results_returned": 3,
"processing_time_ms": 245
}
}
}
```
```json Error Response
{
"success": false,
"error": {
"code": "INVALID_THRESHOLD",
"message": "Similarity threshold must be between 0.1 and 1.0",
"details": {
"provided_threshold": 1.5,
"valid_range": "0.1-1.0"
}
}
}
```
## Usage Examples
### Basic Similarity Search
Find concepts similar to a medical condition:
```json
{
"query": "hypertension",
"max_results": 5,
"similarity_threshold": 0.7
}
```
### Cross-Vocabulary Similarity
Find similar concepts across multiple vocabularies:
```json
{
"query": "myocardial infarction",
"vocabulary_ids": ["SNOMED", "ICD10CM", "ICD9CM"],
"domain_ids": ["Condition"],
"similarity_threshold": 0.8,
"include_explanations": true
}
```
### Pharmacological Similarity
Find similar drug concepts with detailed scoring:
```json
{
"query": "metformin hydrochloride",
"vocabulary_ids": ["RXNORM"],
"domain_ids": ["Drug"],
"concept_class_ids": ["Ingredient", "Clinical Drug"],
"similarity_threshold": 0.75,
"include_scores": true,
"algorithm": "semantic"
}
```
## Related Endpoints
* [GET Search Similar by ID](/api-reference/search/get-search-similar-by-id) - Get similar concepts for a specific concept ID
* [Similarity Search](/api-reference/search/similarity-search) - Alternative similarity endpoint
* [Advanced Search](/api-reference/search/advanced-search) - Advanced search with multiple criteria
# Search Autocomplete (Coming Soon)
Source: https://docs.omophub.com/api-reference/search/search-autocomplete
Provide intelligent autocomplete suggestions for medical terminology searches with real-time query completion and medical context awareness.
This feature is coming soon and is not yet available in the current API version.
## Overview
This endpoint will provide intelligent autocomplete functionality for medical terminology searches. It will offer real-time suggestions as users type, helping them find the correct medical terms quickly and accurately. The system will understand medical context, abbreviations, and common spelling patterns to provide relevant suggestions.
## Query Parameters
Partial search query for autocompletion (minimum 2 characters)
Target vocabularies for suggestions (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter suggestions to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter to specific concept classes (comma-separated)
Types of suggestions to include (comma-separated)
**Options**: `exact`, `fuzzy`, `semantic`, `phonetic`, `abbreviation`, `all`
Autocomplete completion strategy
**Options**: `prefix`, `substring`, `fuzzy`, `semantic`, `balanced`
Medical specialty context for suggestions
**Examples**: `cardiology`, `oncology`, `pediatrics`, `emergency_medicine`
User type for suggestion personalization
**Options**: `physician`, `nurse`, `pharmacist`, `researcher`, `patient`, `student`, `psychiatrist`
Include synonyms in suggestions
Include medical abbreviations and acronyms
Include brief definitions with suggestions
Include usage frequency information
Boost recently searched terms
Boost commonly used medical terms
Enable personalized suggestions based on user history
Minimum usage frequency for suggestions (integer count). This maps to the normalized frequency\_score (0.0-1.0) in responses. Higher frequency counts result in higher frequency\_scores through logarithmic normalization.
Tolerance for fuzzy matching (0.0-1.0)
Language for suggestions (ISO 639-1 code)
Regional variant for terminology preferences
**Examples**: `US`, `UK`, `AU`, `CA`
Filter by standard concept status: `S`, `N`, `C`
Include invalid/deprecated concepts
Maximum number of suggestions to return (max 50)
Maximum response time in milliseconds (for real-time UX)
**Note**: 100ms applies only to highly optimized self-hosted stacks. Benchmark p50/p95 latency under realistic load. Third-party providers often exceed 500ms so adjust expectations accordingly.
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original partial query
Length of the input query
Completion strategy used
Number of suggestions returned
Time taken to generate suggestions
Whether suggestions came from cache
Whether personalization was used
Array of autocomplete suggestions
Unique identifier for this suggestion
Complete suggested search term
Text to display in UI (may include highlighting). Server-sanitized for XSS prevention; only allows safe HTML tags like \. Clients should not perform additional HTML escaping but may sanitize further if needed.
Text needed to complete the query
Type of suggestion (exact, fuzzy, semantic, abbreviation, etc.)
Relevance score for this suggestion (0.0-1.0)
Associated concept ID (if applicable)
Associated concept code
Source vocabulary
Domain classification
Concept class
Standard concept indicator
How this suggestion matches the query
Where the match occurs (start, middle, end)
Character position of the match
Length of the matched portion
Characters needed to complete
Usage frequency score (0.0-1.0), normalized from integer usage counts via logarithmic scaling
Ranking by popularity
Whether recently searched
Relative search volume (High, Medium, Low)
Whether this term is trending
Medical specialties where this term is relevant
Clinical context description
Clinical severity if applicable
Whether appropriate for patient communication
Brief definition (if include\_definitions=true)
Brief definition of the term
Source of the definition
Language complexity (Simple, Moderate, Technical)
Alternative suggestions (synonyms, variations)
Start position for highlighting matched text
End position for highlighting
Type of highlighting (exact, fuzzy, partial)
Suggestions grouped by category
Category name (e.g., "Exact Matches", "Similar Terms")
Category type identifier
Number of suggestions in this category
Suggestions in this category
How complete the current query appears (0.0-1.0)
Predicted search intent
Suggested medical domains based on partial query
Potential spelling corrections
Possible abbreviation expansions
Unique identifier for the request
Request timestamp
Autocomplete engine version
Primary source of suggestions (cache, index, oh\_model)
Personalization identifier (if applicable). Type: string or null
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=diab&vocabulary_ids=SNOMED,ICD10CM&suggestion_types=exact,fuzzy&include_definitions=true&max_suggestions=15" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/autocomplete?query=card&medical_context=cardiology&user_context=physician&include_abbreviations=true&boost_popular=true&max_suggestions=20', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'pneum',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition,Procedure',
'suggestion_types': 'exact,fuzzy,semantic',
'completion_mode': 'balanced',
'medical_context': 'pulmonology',
'user_context': 'physician',
'include_synonyms': True,
'include_abbreviations': True,
'include_definitions': True,
'boost_recent': True,
'boost_popular': True,
'max_suggestions': 25,
'response_time_limit': 150
}
response = requests.get(
'https://api.omophub.com/v1/search/autocomplete',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"query": "diab",
"autocomplete_metadata": {
"query_length": 4,
"completion_mode": "balanced",
"suggestion_count": 5,
"processing_time_ms": 45,
"cache_hit": true,
"personalization_applied": false
},
"suggestions": [
{
"suggestion_id": "auto_1",
"suggested_text": "diabetes mellitus",
"display_text": "diabetes mellitus",
"completion_text": "etes mellitus",
"suggestion_type": "exact",
"relevance_score": 0.95,
"concept_info": {
"concept_id": 73211009,
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"match_details": {
"match_type": "prefix",
"match_position": "start",
"character_offset": 0,
"matched_length": 4,
"completion_needed": 13
},
"usage_info": {
"frequency_score": 0.92,
"popularity_rank": 1,
"recent_usage": true,
"search_volume": "High",
"trending": false
},
"medical_context": {
"specialty_relevance": [
"endocrinology",
"internal_medicine",
"family_medicine"
],
"clinical_context": "Common endocrine disorder",
"severity_level": "chronic",
"patient_facing": true
},
"definition": {
"definition_text": "A group of metabolic disorders characterized by high blood sugar levels over a prolonged period",
"definition_source": "SNOMED CT",
"complexity_level": "Moderate"
},
"alternatives": [
"diabetes",
"DM",
"diabetic condition"
],
"highlighting": {
"highlight_start": 0,
"highlight_end": 4,
"highlight_type": "exact"
}
},
{
"suggestion_id": "auto_2",
"suggested_text": "diabetes",
"display_text": "diabetes",
"completion_text": "etes",
"suggestion_type": "exact",
"relevance_score": 0.93,
"concept_info": {
"concept_id": 73211009,
"concept_code": "73211009",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"match_details": {
"match_type": "prefix",
"match_position": "start",
"character_offset": 0,
"matched_length": 4,
"completion_needed": 4
},
"usage_info": {
"frequency_score": 0.89,
"popularity_rank": 2,
"recent_usage": true,
"search_volume": "High",
"trending": false
},
"medical_context": {
"specialty_relevance": [
"endocrinology",
"internal_medicine",
"family_medicine"
],
"clinical_context": "Metabolic disorder",
"severity_level": "chronic",
"patient_facing": true
},
"definition": {
"definition_text": "A metabolic disorder characterized by elevated blood glucose levels",
"definition_source": "Medical Dictionary",
"complexity_level": "Simple"
},
"alternatives": [
"diabetes mellitus",
"DM"
],
"highlighting": {
"highlight_start": 0,
"highlight_end": 4,
"highlight_type": "exact"
}
},
{
"suggestion_id": "auto_3",
"suggested_text": "diabetic",
"display_text": "diabetic",
"completion_text": "etic",
"suggestion_type": "exact",
"relevance_score": 0.87,
"concept_info": {
"concept_id": 4030518,
"concept_code": "420868002",
"vocabulary_id": "SNOMED",
"domain_id": "Observation",
"concept_class_id": "Context-dependent",
"standard_concept": "S"
},
"match_details": {
"match_type": "prefix",
"match_position": "start",
"character_offset": 0,
"matched_length": 4,
"completion_needed": 4
},
"usage_info": {
"frequency_score": 0.83,
"popularity_rank": 3,
"recent_usage": false,
"search_volume": "Medium",
"trending": false
},
"medical_context": {
"specialty_relevance": [
"endocrinology",
"ophthalmology",
"nephrology"
],
"clinical_context": "Related to diabetes or diabetes patient",
"severity_level": "variable",
"patient_facing": true
},
"definition": {
"definition_text": "Relating to or affected by diabetes",
"definition_source": "Medical Dictionary",
"complexity_level": "Simple"
},
"alternatives": [
"diabetes-related",
"diabetes patient"
],
"highlighting": {
"highlight_start": 0,
"highlight_end": 4,
"highlight_type": "exact"
}
},
{
"suggestion_id": "auto_4",
"suggested_text": "type 2 diabetes",
"display_text": "type 2 diabetes",
"completion_text": "etes",
"suggestion_type": "fuzzy",
"relevance_score": 0.91,
"concept_info": {
"concept_id": 201826,
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"match_details": {
"match_type": "substring",
"match_position": "middle",
"character_offset": 7,
"matched_length": 4,
"completion_needed": 4
},
"usage_info": {
"frequency_score": 0.88,
"popularity_rank": 4,
"recent_usage": true,
"search_volume": "High",
"trending": false
},
"medical_context": {
"specialty_relevance": [
"endocrinology",
"internal_medicine"
],
"clinical_context": "Most common form of diabetes",
"severity_level": "chronic",
"patient_facing": true
},
"definition": {
"definition_text": "The most common type of diabetes, characterized by insulin resistance",
"definition_source": "SNOMED CT",
"complexity_level": "Moderate"
},
"alternatives": [
"T2DM",
"adult-onset diabetes",
"NIDDM"
],
"highlighting": {
"highlight_start": 7,
"highlight_end": 11,
"highlight_type": "exact"
}
},
{
"suggestion_id": "auto_5",
"suggested_text": "diabetic ketoacidosis",
"display_text": "diabetic ketoacidosis",
"completion_text": "etic ketoacidosis",
"suggestion_type": "exact",
"relevance_score": 0.79,
"concept_info": {
"concept_id": 443767006,
"concept_code": "443767006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S"
},
"match_details": {
"match_type": "prefix",
"match_position": "start",
"character_offset": 0,
"matched_length": 4,
"completion_needed": 17
},
"usage_info": {
"frequency_score": 0.65,
"popularity_rank": 8,
"recent_usage": false,
"search_volume": "Medium",
"trending": false
},
"medical_context": {
"specialty_relevance": [
"endocrinology",
"emergency_medicine",
"intensive_care"
],
"clinical_context": "Serious diabetes complication",
"severity_level": "severe",
"patient_facing": false
},
"definition": {
"definition_text": "A serious complication of diabetes characterized by high blood ketones",
"definition_source": "SNOMED CT",
"complexity_level": "Technical"
},
"alternatives": [
"DKA",
"ketoacidosis"
],
"highlighting": {
"highlight_start": 0,
"highlight_end": 4,
"highlight_type": "exact"
}
}
],
"suggestion_categories": [
{
"category": "Exact Matches",
"category_type": "exact",
"suggestion_count": 3,
"suggestions": ["diabetes mellitus", "diabetes", "diabetic"]
},
{
"category": "Related Terms",
"category_type": "semantic",
"suggestion_count": 2,
"suggestions": ["type 2 diabetes", "diabetic ketoacidosis"]
},
{
"category": "Abbreviations",
"category_type": "abbreviation",
"suggestion_count": 3,
"suggestions": ["DM", "T2DM", "DKA"]
}
],
"query_insights": {
"query_completeness": 0.4,
"likely_intent": "condition_search",
"medical_domain_hints": [
"Condition",
"Endocrine disorders"
],
"spelling_corrections": [],
"abbreviation_expansions": [
{
"abbreviation": "DM",
"expansion": "Diabetes Mellitus"
}
]
}
},
"meta": {
"request_id": "req_autocomplete_123",
"timestamp": "2024-01-15T10:30:00Z",
"autocomplete_version": "v3.2.1",
"suggestions_source": "cache",
"personalization_id": null,
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Autocomplete
Get basic suggestions for a partial query:
```bash
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=asth&max_suggestions=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Medical Specialty Context
Get suggestions tailored to a medical specialty:
```bash
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=card&medical_context=cardiology&user_context=physician&include_definitions=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Multi-Type Suggestions
Include different types of suggestions:
```bash
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=pneum&suggestion_types=exact,fuzzy,semantic,abbreviation&include_abbreviations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Real-Time Autocomplete
Optimize for real-time user experience:
```bash
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=dia&response_time_limit=50&max_suggestions=8&boost_popular=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Personalized Suggestions
Get personalized suggestions based on user context:
```bash
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=anxi&user_context=psychiatrist&personalization=true&boost_recent=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Domain-Specific Autocomplete
Focus suggestions on specific medical domains:
```bash
curl -X GET "https://api.omophub.com/v1/search/autocomplete?query=surg&domains=Procedure&vocabulary_ids=SNOMED,HCPCS&include_definitions=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Completion Modes
### Prefix Mode
* **Description**: Matches terms that start with the query
* **Best For**: Traditional autocomplete behavior
* **Performance**: Fastest
* **Example**: "diab" → "diabetes", "diabetic"
### Substring Mode
* **Description**: Matches terms containing the query anywhere
* **Best For**: Finding terms when users know partial words
* **Performance**: Fast
* **Example**: "card" → "myocardial", "pericardium"
### Fuzzy Mode
* **Description**: Tolerates typos and spelling variations
* **Best For**: Handling user input errors
* **Performance**: Moderate
* **Example**: "diabetis" → "diabetes", "diabetic"
### Semantic Mode
* **Description**: Finds conceptually related terms
* **Best For**: Discovering related medical concepts
* **Performance**: Slower
* **Example**: "heart attack" → "myocardial infarction", "coronary syndrome"
### Balanced Mode
* **Description**: Combines multiple strategies with weighting
* **Best For**: General-purpose autocomplete
* **Performance**: Moderate
* **Example**: Uses all approaches with intelligent scoring
## Suggestion Types
### Exact Suggestions
* **Description**: Direct prefix or substring matches
* **Confidence**: High
* **Use Case**: User knows the beginning of the term
* **Examples**: "diab" → "diabetes", "diabetic"
### Fuzzy Suggestions
* **Description**: Matches with spelling tolerance
* **Confidence**: Medium to High
* **Use Case**: User has typos or spelling variations
* **Examples**: "pnemonia" → "pneumonia"
### Semantic Suggestions
* **Description**: Conceptually related terms
* **Confidence**: Medium
* **Use Case**: Discovery and exploration
* **Examples**: "chest pain" → "angina", "myocardial infarction"
### Phonetic Suggestions
* **Description**: Matches based on pronunciation
* **Confidence**: Medium
* **Use Case**: Voice input or pronunciation-based search
* **Examples**: "noomonia" → "pneumonia"
### Abbreviation Suggestions
* **Description**: Medical abbreviations and acronyms
* **Confidence**: High
* **Use Case**: Professional medical communication
* **Examples**: "MI" → "myocardial infarction", "DM" → "diabetes mellitus"
## User Context Optimization
### Physician Context
* **Priority**: Standard medical terminology
* **Include**: Technical terms, abbreviations
* **Exclude**: Lay terms, simplified language
* **Example**: Prefer "myocardial infarction" over "heart attack"
### Nurse Context
* **Priority**: Clinical terms with patient care focus
* **Include**: Nursing-specific terminology, care procedures
* **Exclude**: Highly technical research terms
* **Example**: Include "medication administration", "patient assessment"
### Pharmacist Context
* **Priority**: Drug-related terminology
* **Include**: Medication names, drug interactions, dosages
* **Exclude**: Surgical procedures, diagnostic terms
* **Example**: Emphasize drug names, pharmaceutical terminology
### Researcher Context
* **Priority**: Technical and research terminology
* **Include**: Research methods, statistical terms, technical concepts
* **Exclude**: Patient-facing language
* **Example**: Include "biomarkers", "clinical endpoints"
### Patient Context
* **Priority**: Patient-friendly terminology
* **Include**: Lay terms, simplified explanations
* **Exclude**: Complex medical jargon
* **Example**: Prefer "heart attack" over "myocardial infarction"
### Student Context
* **Priority**: Educational terminology with explanations
* **Include**: Basic concepts with definitions
* **Exclude**: Highly specialized terms
* **Example**: Include terms with educational definitions
## Performance Features
### Real-Time Optimization
* **Target Response Time**: \<100ms for optimal UX
* **Caching Strategy**: Aggressive caching of popular queries
* **Index Optimization**: Pre-computed suggestions for common prefixes
* **Load Balancing**: Distributed processing for high traffic
### Intelligent Caching
* **Query Pattern Cache**: Common medical term prefixes
* **User-Specific Cache**: Personalized suggestion cache
* **Frequency-Based Cache**: Cache popular medical terms
* **Context-Aware Cache**: Specialty-specific suggestion cache
### Progressive Enhancement
* **Minimum Query Length**: 2 characters for performance
* **Graduated Complexity**: Simple suggestions first, complex later
* **Quality Thresholds**: Filter low-quality suggestions
* **Response Size Limits**: Optimal payload size for speed
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Execute full search with completed term
* [Fuzzy Search](/api-reference/search/fuzzy-search) - Handle typos in completed searches
* [Semantic Search](/api-reference/search/semantic-search) - Meaning-based search
* [Search Suggest](/api-reference/search/search-suggest) - Alternative query suggestions
# Search Facets
Source: https://docs.omophub.com/api-reference/search/search-facets
GET https://api.omophub.com/v1/search/facets
Retrieve search facets and filters for medical terminology, enabling advanced filtering and navigation of search results.
## Overview
This endpoint provides faceted search capabilities for medical terminology, returning structured filters and categories that can be used to refine search results. It enables users to explore medical vocabularies through multiple dimensions such as domains, vocabularies, concept classes, and other medical attributes.
## Query Parameters
Base search query to generate facets for (optional for general facets)
Target vocabularies for facet generation (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Pre-filter facets to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Pre-filter facets to specific concept classes (comma-separated)
Types of facets to include (comma-separated)
**Options**: `vocabulary`, `domain`, `concept_class`, `standard_concept`, `specialty`, `usage_frequency`, `date_range`, `language`, `all`
Include concept counts for each facet value
Include percentage distributions for facet values
Include trend information for facet values
Medical specialty context for facet relevance
**Examples**: `cardiology`, `oncology`, `pediatrics`, `emergency_medicine`
User type for facet customization
**Options**: `physician`, `nurse`, `pharmacist`, `researcher`, `patient`, `student`
Depth of facet hierarchy to return
**Options**: `minimal`, `standard`, `detailed`, `comprehensive`
Minimum concept count for facet values to be included
Maximum number of values per facet type (max 200)
Sort order for facet values
**Options**: `count`, `alphabetical`, `relevance`, `frequency`, `recent_usage`
Include hierarchical facet structures where applicable
Include facets for related concepts
Temporal scope for facet generation
**Options**: `current`, `historical`, `trending`, `recent`, `all_time`
Language for facet labels and descriptions
Regional variant for terminology preferences
**Examples**: `US`, `UK`, `AU`, `CA`
Filter facets by standard concept status: `S`, `N`, `C`
Include facets for invalid/deprecated concepts
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Base search query (if provided)
Total concepts matching the base query
Types of facets included in response
Depth level of facets returned
Time taken to generate facets
Context filters applied to facet generation
Type identifier: "vocabulary"
Human-readable facet name
Description of this facet type
Vocabulary identifier
Human-readable vocabulary name
Number of concepts in this vocabulary
Percentage of total concepts (if include\_percentages=true)
Brief description of the vocabulary
Maintaining organization
Current version
How often updated
Primary domains covered
Trend information (if include\_trends=true)
Type identifier: "domain"
Human-readable facet name
Domain identifier
Human-readable domain name
Number of concepts in this domain
Percentage of total concepts
Description of the domain
Clinical context of this domain
Medical specialties using this domain
Hierarchical subcategories (if include\_hierarchical=true)
Type identifier: "concept\_class"
Human-readable facet name
Concept class identifier
Human-readable class name
Number of concepts in this class
Percentage of total concepts
Description of the concept class
Distribution across vocabularies
Common use cases for this class
Type identifier: "specialty"
Human-readable facet name
Medical specialty identifier
Human-readable specialty name
Number of concepts relevant to this specialty
Relevance score for this specialty
Primary domains for this specialty
Common procedures in this specialty
Related medical specialties
Type identifier: "usage\_frequency"
Human-readable facet name
Frequency range identifier
Human-readable frequency range
Number of concepts in this frequency range
Definition of the frequency range
Example concepts in this range
Type identifier: "standard\_concept"
Human-readable facet name
Standard concept indicator (S, N, C)
Human-readable description
Number of concepts with this status
Explanation of this standard concept status
Common use cases for this concept type
Type identifier: "date\_range"
Human-readable facet name
Date range identifier
Human-readable date range
Number of concepts in this date range
Start date of the range
End date of the range
Notable changes in this time period
Type identifier: "language"
Human-readable facet name
Language code (ISO 639-1)
Human-readable language name
Number of concepts with terms in this language
Percentage coverage in this language
Overall translation quality
Regional language variants available
Correlations between different facet types
Hierarchical relationships between facet values
Mutually exclusive facet combinations
Suggested filter combinations based on context
Human-readable filter name
Facet values to combine for this filter
Expected number of results with this filter
Typical use case for this filter combination
Facet generation engine version
Total number of facet values returned
Time taken for facet computation
Caching information for facets
Unique identifier for the request
Request timestamp
Vocabulary release version used
```bash title="cURL"
curl -X GET "https://api.omophub.com/v1/search/facets?query=diabetes&facet_types=vocabulary,domain,concept_class,specialty&include_counts=true&include_percentages=true&max_facet_values=20" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript title="JavaScript"
const response = await fetch('https://api.omophub.com/v1/search/facets?vocabulary_ids=SNOMED,ICD10CM&facet_types=domain,specialty,usage_frequency&medical_context=cardiology&include_hierarchical=true&facet_depth=detailed', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python title="Python"
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'cardiac',
'vocabulary_ids': 'SNOMED,ICD10CM,HCPCS',
'facet_types': 'vocabulary,domain,concept_class,specialty,usage_frequency',
'medical_context': 'cardiology',
'user_context': 'physician',
'include_counts': True,
'include_percentages': True,
'include_trends': True,
'include_hierarchical': True,
'facet_depth': 'comprehensive',
'min_count': 5,
'max_facet_values': 30,
'sort_facets_by': 'relevance'
}
response = requests.get(
'https://api.omophub.com/v1/search/facets',
headers=headers,
params=params
)
data = response.json()
```
```json title="Response"
{
"success": true,
"data": {
"query": "diabetes",
"facet_metadata": {
"total_concepts": 1247,
"facet_types_included": [
"vocabulary",
"domain",
"concept_class",
"specialty"
],
"facet_depth": "standard",
"generation_time_ms": 234,
"context_applied": {
"medical_context": null,
"user_context": null,
"temporal_scope": "current"
}
},
"facets": {
"vocabulary": {
"facet_type": "vocabulary",
"display_name": "Medical Vocabularies",
"description": "Source vocabularies containing matching concepts",
"values": [
{
"value": "SNOMED",
"display_name": "SNOMED CT",
"count": 847,
"percentage": 67.9,
"description": "Systematized Nomenclature of Medicine Clinical Terms",
"metadata": {
"organization": "SNOMED International",
"version": "2024.2",
"update_frequency": "Bi-annual",
"coverage_domains": [
"Condition",
"Procedure",
"Observation"
]
},
"trend_info": {
"trend": "stable",
"recent_additions": 23
}
},
{
"value": "ICD10CM",
"display_name": "ICD-10-CM",
"count": 234,
"percentage": 18.8,
"description": "International Classification of Diseases, 10th Revision, Clinical Modification",
"metadata": {
"organization": "WHO/CDC",
"version": "2024",
"update_frequency": "Annual",
"coverage_domains": [
"Condition"
]
},
"trend_info": {
"trend": "stable",
"recent_additions": 5
}
},
{
"value": "LOINC",
"display_name": "LOINC",
"count": 89,
"percentage": 7.1,
"description": "Logical Observation Identifiers Names and Codes",
"metadata": {
"organization": "LOINC Committee",
"version": "2.76",
"update_frequency": "Bi-annual",
"coverage_domains": [
"Measurement",
"Observation"
]
},
"trend_info": {
"trend": "growing",
"recent_additions": 12
}
},
{
"value": "RXNORM",
"display_name": "RxNorm",
"count": 77,
"percentage": 6.2,
"description": "Normalized names for clinical drugs",
"metadata": {
"organization": "NLM",
"version": "2024-01",
"update_frequency": "Monthly",
"coverage_domains": [
"Drug"
]
},
"trend_info": {
"trend": "stable",
"recent_additions": 8
}
}
]
},
"domain": {
"facet_type": "domain",
"display_name": "Medical Domains",
"values": [
{
"value": "Condition",
"display_name": "Condition",
"count": 923,
"percentage": 74.0,
"description": "Clinical conditions, diseases, and disorders",
"clinical_context": "Diagnostic and clinical conditions",
"specialties": [
"endocrinology",
"internal_medicine",
"family_medicine"
],
"subcategories": [
"Metabolic disorders",
"Endocrine disorders",
"Chronic conditions"
]
},
{
"value": "Observation",
"display_name": "Observation",
"count": 156,
"percentage": 12.5,
"description": "Clinical observations and findings",
"clinical_context": "Clinical assessments and observations",
"specialties": [
"endocrinology",
"laboratory_medicine"
],
"subcategories": [
"Laboratory findings",
"Clinical assessments"
]
},
{
"value": "Measurement",
"display_name": "Measurement",
"count": 89,
"percentage": 7.1,
"description": "Quantitative measurements and test results",
"clinical_context": "Laboratory tests and measurements",
"specialties": [
"laboratory_medicine",
"endocrinology"
],
"subcategories": [
"Blood glucose tests",
"HbA1c measurements"
]
},
{
"value": "Drug",
"display_name": "Drug",
"count": 79,
"percentage": 6.3,
"description": "Medications and pharmaceutical products",
"clinical_context": "Diabetes medications and treatments",
"specialties": [
"endocrinology",
"pharmacy"
],
"subcategories": [
"Antidiabetic agents",
"Insulin preparations"
]
}
]
},
"concept_class": {
"facet_type": "concept_class",
"display_name": "Concept Classes",
"values": [
{
"value": "Clinical Finding",
"display_name": "Clinical Finding",
"count": 623,
"percentage": 50.0,
"description": "Clinical observations and findings",
"vocabulary_distribution": [
{"vocabulary": "SNOMED", "count": 598},
{"vocabulary": "ICD10CM", "count": 25}
],
"typical_use_cases": [
"Diagnosis",
"Clinical documentation",
"Medical coding"
]
},
{
"value": "Disorder",
"display_name": "Disorder",
"count": 234,
"percentage": 18.8,
"description": "Medical disorders and conditions",
"vocabulary_distribution": [
{"vocabulary": "SNOMED", "count": 187},
{"vocabulary": "ICD10CM", "count": 47}
],
"typical_use_cases": [
"Disease classification",
"Medical billing"
]
},
{
"value": "3-char billing code",
"display_name": "ICD-10-CM Category",
"count": 156,
"percentage": 12.5,
"description": "ICD-10-CM 3-character category codes",
"vocabulary_distribution": [
{"vocabulary": "ICD10CM", "count": 156}
],
"typical_use_cases": [
"Medical billing",
"Administrative coding"
]
}
]
},
"specialty": {
"facet_type": "specialty",
"display_name": "Medical Specialties",
"values": [
{
"value": "endocrinology",
"display_name": "Endocrinology",
"count": 1089,
"relevance_score": 0.95,
"primary_domains": [
"Condition",
"Observation",
"Drug"
],
"common_procedures": [
"Blood glucose monitoring",
"HbA1c testing",
"Insulin therapy"
],
"related_specialties": [
"internal_medicine",
"family_medicine"
]
},
{
"value": "internal_medicine",
"display_name": "Internal Medicine",
"count": 923,
"relevance_score": 0.87,
"primary_domains": [
"Condition",
"Observation"
],
"common_procedures": [
"Diabetes management",
"Medication management"
],
"related_specialties": [
"endocrinology",
"family_medicine"
]
},
{
"value": "family_medicine",
"display_name": "Family Medicine",
"count": 756,
"relevance_score": 0.78,
"primary_domains": [
"Condition",
"Observation"
],
"common_procedures": [
"Diabetes screening",
"Patient education"
],
"related_specialties": [
"internal_medicine",
"endocrinology"
]
},
{
"value": "ophthalmology",
"display_name": "Ophthalmology",
"count": 234,
"relevance_score": 0.65,
"primary_domains": [
"Condition",
"Procedure"
],
"common_procedures": [
"Diabetic retinopathy screening",
"Retinal examination"
],
"related_specialties": [
"endocrinology"
]
}
]
}
},
"facet_relationships": {
"correlations": [
{
"facet_types": ["domain", "specialty"],
"correlation": "Condition domain strongly correlates with endocrinology specialty"
}
],
"hierarchies": [
{
"parent": "Condition",
"children": ["Clinical Finding", "Disorder"]
}
],
"exclusions": [
{
"facet_type": "standard_concept",
"mutually_exclusive": ["S", "N"]
}
]
},
"suggested_filters": [
{
"filter_name": "Endocrine Conditions",
"filter_combination": {
"domain": "Condition",
"specialty": "endocrinology",
"concept_class": "Clinical Finding"
},
"expected_results": 623,
"use_case": "Clinical diabetes management"
},
{
"filter_name": "Diabetes Medications",
"filter_combination": {
"domain": "Drug",
"specialty": "endocrinology",
"vocabulary": "RXNORM"
},
"expected_results": 77,
"use_case": "Pharmaceutical diabetes treatment"
}
]
},
"meta": {
"request_id": "req_search_facets_123",
"timestamp": "2024-01-15T10:30:00Z",
"facet_engine_version": "v2.3.1",
"total_facet_values": 47,
"computation_time_ms": 234,
"cache_info": {
"cached": true,
"cache_key": "facets_diabetes_standard",
"ttl_remaining": 3456
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Facets for Query
Get facets for a specific search query:
```bash
curl -X GET "https://api.omophub.com/v1/search/facets?query=pneumonia&facet_types=vocabulary,domain,concept_class&include_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Facet Analysis
Get detailed facets with all information:
```bash
curl -X GET "https://api.omophub.com/v1/search/facets?query=cardiac&facet_types=all&include_counts=true&include_percentages=true&include_trends=true&facet_depth=comprehensive" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Medical Specialty Focused Facets
Get facets relevant to a specific medical specialty:
```bash
curl -X GET "https://api.omophub.com/v1/search/facets?medical_context=cardiology&user_context=physician&facet_types=domain,concept_class,specialty&include_hierarchical=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Vocabulary-Specific Facets
Get facets for specific vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/search/facets?vocabulary_ids=SNOMED,ICD10CM&facet_types=domain,concept_class,usage_frequency&min_count=10&max_facet_values=15" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### General Navigation Facets
Get facets for general terminology navigation:
```bash
curl -X GET "https://api.omophub.com/v1/search/facets?facet_types=vocabulary,domain,specialty&sort_facets_by=count&include_related_facets=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### User-Contextualized Facets
Get facets customized for specific user types:
```bash
curl -X GET "https://api.omophub.com/v1/search/facets?query=medication&user_context=pharmacist&facet_types=vocabulary,domain,concept_class&medical_context=pharmacy" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Facet Types
### Vocabulary Facets
* **Purpose**: Filter by source vocabulary
* **Use Cases**: Compare terminology coverage, choose appropriate vocabulary
* **Values**: SNOMED, ICD10CM, LOINC, RxNorm, HCPCS, HCPCS
* **Metadata**: Organization, version, update frequency, coverage
### Domain Facets
* **Purpose**: Filter by medical domain
* **Use Cases**: Focus on specific types of medical concepts
* **Values**: Condition, Procedure, Drug, Device, Measurement, Observation
* **Hierarchy**: Sub-domains and categories within each domain
### Concept Class Facets
* **Purpose**: Filter by concept classification
* **Use Cases**: Refine search to specific concept types
* **Values**: Clinical Finding, Procedure, Pharmaceutical Product, etc.
* **Context**: Vocabulary-specific concept classes
### Specialty Facets
* **Purpose**: Filter by medical specialty relevance
* **Use Cases**: Find concepts relevant to specific medical practices
* **Values**: Cardiology, Oncology, Pediatrics, Emergency Medicine, etc.
* **Relationships**: Related specialties and common procedures
### Usage Frequency Facets
* **Purpose**: Filter by how commonly concepts are used
* **Use Cases**: Find popular terms, discover rare conditions
* **Values**: Very High, High, Medium, Low, Rare
* **Metrics**: Search volume, clinical usage, documentation frequency
### Standard Concept Facets
* **Purpose**: Filter by OMOP standard concept status
* **Use Cases**: Find standard concepts for analytics, non-standard for mapping
* **Values**: Standard (S), Non-standard (N), Classification (C)
* **Usage**: Critical for OMOP CDM implementations
### Date Range Facets
* **Purpose**: Filter by concept validity or update dates
* **Use Cases**: Find recently updated concepts, historical analysis
* **Values**: Current year, Last 5 years, Historical, etc.
* **Context**: Version tracking and temporal analysis
### Language Facets
* **Purpose**: Filter by language availability
* **Use Cases**: Multi-lingual implementations, translation coverage
* **Values**: English, Spanish, French, German, etc.
* **Quality**: Translation quality and coverage metrics
## Facet Depth Levels
### Minimal
* **Content**: Basic facet values with counts
* **Performance**: Fastest response time
* **Use Case**: Simple filtering interfaces
* **Data**: Value, count, basic display information
### Standard
* **Content**: Facet values with metadata and descriptions
* **Performance**: Good response time
* **Use Case**: General search interfaces
* **Data**: Descriptions, percentages, basic relationships
### Detailed
* **Content**: Extended metadata and hierarchical relationships
* **Performance**: Moderate response time
* **Use Case**: Advanced search and analysis
* **Data**: Hierarchies, correlations, trend information
### Comprehensive
* **Content**: Complete facet information with all metadata
* **Performance**: Slower response time
* **Use Case**: Research and comprehensive analysis
* **Data**: All available metadata, relationships, and insights
## User Context Customization
### Physician Context
* **Priority**: Clinical relevance, diagnostic focus
* **Emphasized Facets**: Specialty, concept class, standard concepts
* **Hidden Facets**: Patient-facing terminology, administrative codes
* **Sorting**: Clinical relevance and frequency
### Nurse Context
* **Priority**: Patient care, nursing procedures
* **Emphasized Facets**: Procedure domain, nursing specialties
* **Hidden Facets**: Highly technical research terms
* **Sorting**: Clinical usage and care relevance
### Pharmacist Context
* **Priority**: Drug-related terminology, pharmaceutical focus
* **Emphasized Facets**: Drug domain, pharmaceutical vocabularies
* **Hidden Facets**: Surgical procedures, diagnostic imaging
* **Sorting**: Pharmaceutical relevance and usage
### Researcher Context
* **Priority**: Comprehensive coverage, analytical focus
* **Emphasized Facets**: All vocabularies, comprehensive metadata
* **Hidden Facets**: None (comprehensive view)
* **Sorting**: Completeness and analytical value
### Patient Context
* **Priority**: Patient-friendly terminology, educational value
* **Emphasized Facets**: Common conditions, patient-facing terms
* **Hidden Facets**: Technical medical jargon, administrative codes
* **Sorting**: Common usage and understandability
### Student Context
* **Priority**: Educational value, learning focus
* **Emphasized Facets**: Concept classes, educational metadata
* **Hidden Facets**: Highly specialized professional terms
* **Sorting**: Educational relevance and frequency
## Performance Optimization
### Caching Strategies
* **Query-Based Cache**: Cache facets for common queries
* **Context Cache**: Cache facets for user contexts
* **Vocabulary Cache**: Cache vocabulary-specific facets
* **Time-Based Cache**: Cache with appropriate TTL
### Computation Optimization
* **Pre-computed Facets**: Common facet combinations
* **Incremental Updates**: Update facets as data changes
* **Parallel Processing**: Generate facets in parallel
* **Smart Filtering**: Apply filters during facet generation
### Response Optimization
* **Selective Loading**: Load only requested facet types
* **Hierarchical Loading**: Load parent facets first, children on demand
* **Compression**: Compress large facet responses
* **Streaming**: Stream large facet sets
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Apply facet filters to search
* [Advanced Search](/api-reference/search/advanced-search) - Complex search with multiple facets
* [Search Concepts](/api-reference/concepts/search-concepts) - Search with facet filtering
* [List Domains](/api-reference/domains/list-domains) - Detailed domain information
# Search Suggest
Source: https://docs.omophub.com/api-reference/search/search-suggest
GET https://api.omophub.com/v1/search/suggest
Get intelligent search suggestions and alternative query recommendations based on user intent, medical context, and search patterns.
## Overview
This endpoint provides intelligent search suggestions and query recommendations to help users refine their medical terminology searches. It analyzes user queries, medical context, and search patterns to suggest alternative search terms, related concepts, and improved query formulations.
## Query Parameters
Original search query for which suggestions are needed
Target vocabularies for suggestions (comma-separated)
**Examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
Filter suggestions to specific domains (comma-separated)
**Examples**: `Condition,Procedure`, `Drug,Device`
Filter to specific concept classes (comma-separated)
Types of suggestions to include (comma-separated)
**Options**: `spelling`, `synonym`, `related`, `broader`, `narrower`, `alternative`, `trending`, `popular`, `all`
Medical specialty context for relevant suggestions
**Examples**: `cardiology`, `oncology`, `pediatrics`, `emergency_medicine`
User type for suggestion personalization
**Options**: `physician`, `nurse`, `pharmacist`, `researcher`, `patient`, `student`
Detected or specified search intent
**Options**: `diagnostic`, `therapeutic`, `procedural`, `research`, `educational`, `administrative`
Include spelling corrections and typo fixes
Include alternative phrasings and synonyms
Include conceptually related terms
Include broader and narrower concepts
Include trending medical terms
Include popular search terms
Include explanations for why suggestions are relevant
Enable personalized suggestions based on user history
Cultural or regional context for suggestions
**Examples**: `US`, `UK`, `international`, `multicultural`
Language for suggestions (ISO 639-1 code)
Preferred complexity level for suggestions
**Options**: `simple`, `intermediate`, `advanced`, `technical`, `balanced`
Whether suggestions should be patient-appropriate
Filter by standard concept status: `S`, `N`, `C`
Include suggestions for invalid/deprecated concepts
Maximum number of suggestions to return (max 100)
Minimum relevance score for suggestions (0.0-1.0)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original search query
Automatically detected search intent
Confidence in intent detection (0.0-1.0)
Assessment of query quality (Excellent, Good, Fair, Poor)
Medical concepts identified in the query
Potential issues with the query
Ways the query could be improved
Spelling and typo corrections
Original potentially misspelled term
Suggested correction
Confidence in the correction (0.0-1.0)
Type of correction (typo, spelling, grammar)
Explanation of the correction (if include\_explanations=true)
Synonym suggestions
Original term from query
Suggested synonym
Relevance score (0.0-1.0)
Associated concept ID (if applicable)
Source vocabulary
Typical usage context
Level of formality (Technical, Clinical, Lay)
Why this synonym is relevant (if include\_explanations=true)
Conceptually related suggestions
Suggested related term
Type of relationship (associated, causes, treats, etc.)
Relevance score (0.0-1.0)
Concept ID
Source vocabulary
Domain classification
Clinical context or use case
Relationship explanation (if include\_explanations=true)
More general concept suggestions
Suggested broader term
Levels up in hierarchy
Relevance score (0.0-1.0)
Concept ID
Source vocabulary
Type of generalization (category, class, system)
When to use this broader term
More specific concept suggestions
Suggested narrower term
Levels down in hierarchy
Relevance score (0.0-1.0)
Concept ID
Source vocabulary
Type of specialization (subtype, specific case, variant)
Level of clinical specificity
Alternative ways to phrase the query
Alternative query phrasing
Type of alternative (formal, informal, technical, lay)
Appropriateness score (0.0-1.0)
Intended audience for this phrasing
Expected number of search results
Advantages of this phrasing
Trending medical terms related to the query
Trending term
Trend strength score (0.0-1.0)
Context for the trend (research, clinical, news)
Relevance to original query (0.0-1.0)
How long it's been trending
Medical specialties driving the trend
Popular search terms related to the query
Popular term
Popularity score (0.0-1.0)
Relative search volume (High, Medium, Low)
Primary user base for this term
Seasonal usage pattern (if applicable)
Stability of popularity (Stable, Growing, Declining)
Recommended search strategy
Recommended vocabularies for this query
Recommended domains to focus on
Suggested filters to apply
General search tips for this type of query
Personalized recommendations (if personalization=true)
Detected user search patterns
User's preferred vocabularies
User's commonly searched domains
Suggested medical specialties based on history
Suggested learning opportunities
Unique identifier for the request
Request timestamp
Suggestion engine version
Total number of suggestions returned
Time taken to generate suggestions
Whether personalization was used
Contextual factors influencing suggestions
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/suggest?query=hart%20atack&vocabulary_ids=SNOMED,ICD10CM&suggestion_types=spelling,synonym,related&medical_context=cardiology&include_explanations=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/search/suggest?query=diabetis&user_context=physician&include_corrections=true&include_alternatives=true&include_related=true&include_hierarchical=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'query': 'chest pain breathing problems',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition,Procedure',
'suggestion_types': 'spelling,synonym,related,broader,narrower',
'medical_context': 'emergency_medicine',
'user_context': 'physician',
'search_intent': 'diagnostic',
'include_corrections': 'true',
'include_alternatives': 'true',
'include_related': 'true',
'include_hierarchical': 'true',
'include_trending': 'true',
'include_explanations': 'true',
'complexity_level': 'advanced',
'max_suggestions': 30,
'relevance_threshold': 0.7
}
response = requests.get(
'https://api.omophub.com/v1/search/suggest',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"query": "hart atack",
"query_analysis": {
"detected_intent": "diagnostic",
"confidence": 0.89,
"query_quality": "Poor",
"medical_concepts": [
{
"concept": "heart attack",
"confidence": 0.85,
"issues": ["spelling error in 'hart'", "spelling error in 'atack'"]
}
],
"potential_issues": [
"Multiple spelling errors detected",
"Informal terminology used",
"Could be more specific"
],
"improvement_opportunities": [
"Use correct spelling: 'heart attack'",
"Consider medical terminology: 'myocardial infarction'",
"Specify type: 'acute myocardial infarction'"
]
},
"suggestions": {
"spelling_corrections": [
{
"original": "hart",
"correction": "heart",
"confidence": 0.95,
"correction_type": "spelling",
"explanation": "Common misspelling of 'heart' in medical queries"
},
{
"original": "atack",
"correction": "attack",
"confidence": 0.92,
"correction_type": "spelling",
"explanation": "Missing 't' in 'attack' - common typo"
}
],
"synonyms": [
{
"original_term": "heart attack",
"synonym": "myocardial infarction",
"relevance_score": 0.98,
"concept_id": 22298006,
"vocabulary_id": "SNOMED",
"usage_context": "Clinical documentation",
"formality_level": "Technical",
"explanation": "Standard medical terminology for heart attack, preferred in clinical settings"
},
{
"original_term": "heart attack",
"synonym": "MI",
"relevance_score": 0.92,
"concept_id": 22298006,
"vocabulary_id": "SNOMED",
"usage_context": "Clinical abbreviation",
"formality_level": "Clinical",
"explanation": "Common clinical abbreviation for myocardial infarction"
},
{
"original_term": "heart attack",
"synonym": "acute coronary syndrome",
"relevance_score": 0.85,
"concept_id": 394659003,
"vocabulary_id": "SNOMED",
"usage_context": "Emergency medicine",
"formality_level": "Clinical",
"explanation": "Broader clinical term encompassing heart attacks and related conditions"
}
],
"related_concepts": [
{
"suggested_term": "angina pectoris",
"relationship_type": "related_condition",
"relevance_score": 0.87,
"concept_id": 194828000,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"clinical_context": "Precursor or related chest pain condition",
"explanation": "Related cardiac condition that can precede or mimic heart attack"
},
{
"suggested_term": "coronary artery disease",
"relationship_type": "underlying_cause",
"relevance_score": 0.82,
"concept_id": 53741008,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"clinical_context": "Underlying pathology leading to heart attack",
"explanation": "Primary underlying condition that causes most heart attacks"
},
{
"suggested_term": "cardiac arrest",
"relationship_type": "potential_complication",
"relevance_score": 0.79,
"concept_id": 410429000,
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"clinical_context": "Serious complication of heart attack",
"explanation": "Potential life-threatening complication of myocardial infarction"
}
],
"broader_concepts": [
{
"suggested_term": "cardiovascular disease",
"hierarchy_level": 2,
"relevance_score": 0.76,
"concept_id": 49601007,
"vocabulary_id": "SNOMED",
"generalization_type": "system",
"use_case": "For broader cardiovascular condition searches"
},
{
"suggested_term": "heart disease",
"hierarchy_level": 1,
"relevance_score": 0.81,
"concept_id": 56265001,
"vocabulary_id": "SNOMED",
"generalization_type": "category",
"use_case": "For general cardiac condition searches"
}
],
"narrower_concepts": [
{
"suggested_term": "ST elevation myocardial infarction",
"hierarchy_level": 1,
"relevance_score": 0.89,
"concept_id": 401314000,
"vocabulary_id": "SNOMED",
"specialization_type": "subtype",
"clinical_specificity": "High - specific type based on ECG findings"
},
{
"suggested_term": "non-ST elevation myocardial infarction",
"hierarchy_level": 1,
"relevance_score": 0.87,
"concept_id": 401303003,
"vocabulary_id": "SNOMED",
"specialization_type": "subtype",
"clinical_specificity": "High - specific type based on ECG findings"
},
{
"suggested_term": "acute anterior myocardial infarction",
"hierarchy_level": 1,
"relevance_score": 0.84,
"concept_id": 54329005,
"vocabulary_id": "SNOMED",
"specialization_type": "anatomical_location",
"clinical_specificity": "Very High - location-specific type"
}
],
"alternative_phrasings": [
{
"alternative_query": "heart attack",
"phrasing_type": "corrected_lay",
"appropriateness_score": 0.95,
"target_audience": "General public, patients",
"expected_results": 234,
"advantages": [
"Correct spelling",
"Widely understood",
"Patient-friendly"
]
},
{
"alternative_query": "myocardial infarction",
"phrasing_type": "medical_technical",
"appropriateness_score": 0.92,
"target_audience": "Healthcare professionals",
"expected_results": 187,
"advantages": [
"Precise medical terminology",
"Preferred in clinical documentation",
"Standardized term"
]
},
{
"alternative_query": "acute MI",
"phrasing_type": "clinical_abbreviation",
"appropriateness_score": 0.87,
"target_audience": "Clinical staff",
"expected_results": 156,
"advantages": [
"Efficient clinical communication",
"Commonly used abbreviation",
"Time-saving"
]
}
],
"trending_suggestions": [
{
"suggested_term": "troponin elevation",
"trend_score": 0.78,
"trend_context": "diagnostic_biomarker",
"relevance_to_query": 0.82,
"trend_duration": "6 months",
"specialty_focus": [
"emergency_medicine",
"cardiology"
]
}
],
"popular_suggestions": [
{
"suggested_term": "chest pain",
"popularity_score": 0.94,
"search_volume": "High",
"user_base": "Healthcare professionals and patients",
"seasonal_pattern": "Stable year-round",
"stability": "Stable"
},
{
"suggested_term": "cardiac symptoms",
"popularity_score": 0.86,
"search_volume": "Medium",
"user_base": "Healthcare professionals",
"seasonal_pattern": "None",
"stability": "Stable"
}
]
},
"contextual_recommendations": {
"search_strategy": "Start with corrected spelling, then explore specific types if needed",
"vocabulary_recommendations": [
{
"vocabulary": "SNOMED",
"reason": "Comprehensive cardiac terminology",
"priority": "High"
},
{
"vocabulary": "ICD10CM",
"reason": "Administrative coding and billing",
"priority": "Medium"
}
],
"domain_focus": [
"Condition",
"Observation"
],
"filter_suggestions": [
{
"filter": "domain=Condition",
"reason": "Focus on diagnostic conditions"
},
{
"filter": "specialty=cardiology",
"reason": "Cardiac-specific context"
}
],
"search_tips": [
"Use correct spelling for better results",
"Try both lay terms and medical terminology",
"Consider specific types (STEMI, NSTEMI) for detailed information",
"Include related symptoms (chest pain, shortness of breath) for broader context"
]
},
"personalization_insights": null
},
"meta": {
"request_id": "req_search_suggest_123",
"timestamp": "2024-01-15T10:30:00Z",
"suggestion_engine_version": "v3.1.0",
"total_suggestions": 45,
"processing_time_ms": 178,
"personalization_applied": false,
"context_factors": [
"medical_context: cardiology",
"spelling_errors_detected",
"intent: diagnostic",
"formality: mixed"
],
"vocab_release": "2024.2"
}
}
```
## Usage Examples
### Spelling Correction Suggestions
Get suggestions for queries with spelling errors:
```bash
curl -X GET "https://api.omophub.com/v1/search/suggest?query=pnemonia&suggestion_types=spelling,synonym&include_corrections=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Query Suggestions
Get comprehensive suggestions for query improvement:
```bash
curl -X GET "https://api.omophub.com/v1/search/suggest?query=diabetes&suggestion_types=all&medical_context=endocrinology&user_context=physician&include_explanations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Alternative Phrasing Suggestions
Get alternative ways to phrase a medical query:
```bash
curl -X GET "https://api.omophub.com/v1/search/suggest?query=high%20blood%20pressure&suggestion_types=alternative,synonym&complexity_level=technical&patient_facing=false" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Hierarchical Concept Suggestions
Get broader and narrower concept suggestions:
```bash
curl -X GET "https://api.omophub.com/v1/search/suggest?query=pneumonia&suggestion_types=broader,narrower&include_hierarchical=true&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Patient-Friendly Suggestions
Get patient-appropriate suggestions:
```bash
curl -X GET "https://api.omophub.com/v1/search/suggest?query=myocardial%20infarction&user_context=patient&patient_facing=true&complexity_level=simple&include_alternatives=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Trending and Popular Suggestions
Get trending and popular related terms:
```bash
curl -X GET "https://api.omophub.com/v1/search/suggest?query=covid&suggestion_types=trending,popular,related&include_trending=true&include_popular=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Suggestion Types
### Spelling Corrections
* **Purpose**: Fix typos and spelling errors
* **Confidence**: High for obvious corrections
* **Use Case**: Improve query accuracy
* **Examples**: "hart" → "heart", "diabetis" → "diabetes"
### Synonyms
* **Purpose**: Alternative terms with same meaning
* **Formality Levels**: Technical, Clinical, Lay
* **Use Case**: Find standard terminology
* **Examples**: "heart attack" → "myocardial infarction"
### Related Concepts
* **Relationships**: Associated, causes, treats, complicates
* **Clinical Context**: Related conditions and procedures
* **Use Case**: Explore connected medical concepts
* **Examples**: "diabetes" → "insulin", "hyperglycemia"
### Broader Concepts
* **Hierarchy**: Move up concept hierarchies
* **Generalization**: Category, system, class level
* **Use Case**: Widen search scope
* **Examples**: "pneumonia" → "lung disease" → "respiratory condition"
### Narrower Concepts
* **Specialization**: Subtypes, variants, specific cases
* **Clinical Specificity**: Increase diagnostic precision
* **Use Case**: Get more specific results
* **Examples**: "diabetes" → "type 2 diabetes" → "insulin-resistant diabetes"
### Alternative Phrasings
* **Formality**: Formal, informal, technical, lay
* **Target Audience**: Professionals, patients, researchers
* **Use Case**: Reach different audiences
* **Examples**: "MI" vs "heart attack" vs "myocardial infarction"
### Trending Suggestions
* **Temporal**: Currently popular or emerging
* **Context**: Research, clinical practice, news
* **Use Case**: Discover emerging topics
* **Examples**: New treatments, recent outbreaks, research terms
### Popular Suggestions
* **Usage**: Commonly searched terms
* **Stability**: Stable, growing, or declining popularity
* **Use Case**: Find common related searches
* **Examples**: High-volume search terms
## Search Intent Detection
### Diagnostic Intent
* **Characteristics**: Symptom descriptions, condition names
* **Suggestions**: Differential diagnoses, related symptoms
* **Context**: Clinical assessment, patient evaluation
* **Examples**: "chest pain" → diagnostic conditions
### Therapeutic Intent
* **Characteristics**: Treatment queries, medication searches
* **Suggestions**: Treatment options, drug alternatives
* **Context**: Treatment planning, medication selection
* **Examples**: "diabetes treatment" → therapeutic options
### Procedural Intent
* **Characteristics**: Procedure names, intervention queries
* **Suggestions**: Related procedures, alternatives
* **Context**: Procedure selection, surgical planning
* **Examples**: "cardiac catheterization" → related procedures
### Research Intent
* **Characteristics**: Technical terms, study-related queries
* **Suggestions**: Research terminology, methodological terms
* **Context**: Academic research, literature review
* **Examples**: "biomarkers" → research concepts
### Educational Intent
* **Characteristics**: Learning-focused queries
* **Suggestions**: Educational resources, explanatory terms
* **Context**: Medical education, patient education
* **Examples**: "anatomy" → educational concepts
### Administrative Intent
* **Characteristics**: Coding, billing, documentation queries
* **Suggestions**: Administrative codes, documentation terms
* **Context**: Medical coding, billing processes
* **Examples**: "ICD codes" → administrative terminology
## Personalization Features
### User Pattern Analysis
* **Search History**: Commonly searched terms and domains
* **Vocabulary Preferences**: Preferred medical vocabularies
* **Specialty Focus**: Medical specialties of interest
* **Complexity Level**: Preferred terminology complexity
### Personalized Suggestions
* **Relevant Terms**: Terms matching user's interests
* **Vocabulary Bias**: Suggestions from preferred vocabularies
* **Specialty Context**: Specialty-specific recommendations
* **Learning Path**: Educational progression suggestions
### Privacy Considerations
* **Opt-in**: Personalization requires explicit consent
* **Data Security**: Secure storage of user patterns
* **Anonymization**: Personal data anonymization
* **User Control**: Users can disable personalization
## Cultural and Regional Context
### Regional Terminology
* **US Medical Terms**: American medical terminology preferences
* **UK Medical Terms**: British medical terminology variations
* **International**: WHO and international standard terms
* **Multicultural**: Inclusive terminology for diverse populations
### Cultural Sensitivity
* **Inclusive Language**: Culturally appropriate medical terms
* **Accessibility**: Terms accessible to diverse populations
* **Local Practices**: Regional medical practice variations
* **Language Variants**: Regional language preferences
## Related Endpoints
* [Search Autocomplete](/api-reference/search/search-autocomplete) - Real-time query completion
* [Basic Search](/api-reference/search/basic-search) - Execute suggested searches
* [Semantic Search](/api-reference/search/semantic-search) - Meaning-based search
* [Fuzzy Search](/api-reference/search/fuzzy-search) - Typo-tolerant search
# Search Trending
Source: https://docs.omophub.com/api-reference/search/search-trending
GET /v1/search/trending
Retrieve trending and popular search queries based on usage patterns and analytics data
This endpoint provides insights into trending medical concepts and popular search queries, helping users discover relevant terminology based on community usage patterns and temporal trends.
## Query Parameters
Comma-separated list of vocabulary IDs to filter trending searches
**Example:** `SNOMED,ICD10CM,LOINC`
Comma-separated list of domain IDs to focus trending analysis
**Example:** `Condition,Drug,Procedure`
Time period for trending analysis
**Options:** `1d`, `7d`, `30d`, `90d`
Type of trending metric to analyze
**Options:** `search_volume`, `new_concepts`, `rising_queries`, `seasonal_patterns`
Include detailed trending statistics and growth metrics
Include related trending concepts and co-occurrence patterns
Number of trending items to return (max 100)
Page number for pagination (1-based)
## Response
Array of trending search items with analytics data
The trending search query or concept term
Associated concept ID if trending item is a specific concept (returned as string to prevent precision loss)
Standard concept name if applicable
Vocabulary containing the trending concept
Domain classification of the trending concept
Normalized trending score (0-100)
Number of searches in the specified time period
Percentage growth rate compared to previous period
Direction of the trend
**Values:** `rising`, `declining`, `stable`, `new`
Detailed trending statistics (when include\_statistics=true)
Highest search volume in the period
Average daily search volume
Trend consistency score (0-1)
Seasonal adjustment factor
ISO timestamp when trend was first detected
Rate of change in search volume
Related trending concepts (when include\_related=true)
Related concept identifier (returned as string to prevent precision loss)
Related concept name
Correlation strength with main trending item (0-1)
Rate of co-occurrence in searches
Geographic distribution of trend (when available)
Top geographic regions for this trend
Regional trending scores
Response metadata and pagination information
Current page number
Items per page
Total trending items available
Total number of pages
Whether next page exists
Whether previous page exists
Time period used for trend analysis
Analysis start date (ISO format)
Analysis end date (ISO format)
Type of period analyzed
Timestamp of last trending data update
```bash
curl -X GET "https://api.omophub.com/v1/search/trending?time_period=7d&trend_type=search_volume&include_statistics=true&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript
const response = await fetch('https://api.omophub.com/v1/search/trending?time_period=7d&trend_type=search_volume&include_statistics=true&page_size=10', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const trendingData = await response.json();
console.log('Trending searches:', trendingData.data.data);
```
```python
import requests
url = "https://api.omophub.com/v1/search/trending"
params = {
"time_period": "7d",
"trend_type": "search_volume",
"include_statistics": True,
"page_size": 10
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
}
response = requests.get(url, params=params, headers=headers)
trending_data = response.json()
print(f"Found {len(trending_data['data']['data'])} trending items")
for item in trending_data['data']['data']:
print(f"- {item['query']}: {item['trend_score']} score, {item['growth_rate']}% growth")
```
```json
{
"success": true,
"data": {
"data": [
{
"query": "covid-19 symptoms",
"concept_id": "840539006",
"concept_name": "Disease caused by severe acute respiratory syndrome coronavirus 2",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"trend_score": 95.8,
"search_volume": 15420,
"growth_rate": 234.7,
"trend_direction": "rising",
"statistics": {
"peak_volume": 2180,
"average_volume": 1850.0,
"consistency_score": 0.82,
"seasonal_factor": 1.15,
"first_seen": "2024-12-15T08:00:00Z",
"velocity": 12.3
},
"related_concepts": [
{
"concept_id": "49727002",
"concept_name": "Cough",
"correlation_score": 0.78,
"co_occurrence_rate": 0.45
},
{
"concept_id": "386661006",
"concept_name": "Fever",
"correlation_score": 0.72,
"co_occurrence_rate": 0.41
}
]
},
{
"query": "diabetes management",
"concept_id": "73211009",
"concept_name": "Diabetes mellitus",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"trend_score": 87.3,
"search_volume": 8920,
"growth_rate": 45.2,
"trend_direction": "rising",
"statistics": {
"peak_volume": 1420,
"average_volume": 1274.3,
"consistency_score": 0.91,
"seasonal_factor": 1.03,
"first_seen": "2024-12-10T14:30:00Z",
"velocity": 8.7
}
},
{
"query": "hypertension treatment",
"concept_id": "38341003",
"concept_name": "Hypertensive disorder, systemic arterial",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"trend_score": 79.1,
"search_volume": 6750,
"growth_rate": 28.9,
"trend_direction": "stable"
}
],
"meta": {
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 156,
"total_pages": 16,
"has_next": true,
"has_previous": false
},
"analysis_period": {
"start_date": "2024-12-15T00:00:00Z",
"end_date": "2024-12-22T00:00:00Z",
"period_type": "7d"
},
"data_freshness": "2024-12-22T12:00:00Z",
"request_id": "req_search_trending_7d_analytics_001",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
}
```
## Usage Examples
### Basic Trending Analysis
Get the most popular trending searches in the past week:
```javascript
const trending = await fetch('/v1/search/trending?time_period=7d&page_size=20');
```
### Domain-Specific Trends
Find trending searches within specific medical domains:
```javascript
const conditionTrends = await fetch('/v1/search/trending?domain_ids=Condition,Drug&time_period=30d');
```
### Rising Query Detection
Identify rapidly growing search queries:
```javascript
const risingQueries = await fetch('/v1/search/trending?trend_type=rising_queries&include_statistics=true');
```
### Vocabulary-Specific Analysis
Analyze trends within specific vocabularies:
```javascript
const snomedTrends = await fetch('/v1/search/trending?vocabulary_ids=SNOMED&time_period=90d&include_related=true');
```
### Seasonal Pattern Analysis
Detect seasonal trending patterns:
```javascript
const seasonalTrends = await fetch('/v1/search/trending?trend_type=seasonal_patterns&time_period=90d');
```
## Related Endpoints
* [Search Concepts](/api-reference/search/search-concepts) - Primary concept search functionality
* [Search Autocomplete](/api-reference/search/search-autocomplete) - Real-time search suggestions
* [Search Facets](/api-reference/search/search-facets) - Search result faceting and filtering
* [Search Suggest](/api-reference/search/search-suggest) - Intelligent search suggestions
* [Get Vocabulary Statistics](/api-reference/vocabulary/get-vocabulary-statistics) - Overall vocabulary usage stats
## Notes
* Trending data is updated every 4 hours with the latest search analytics
* Trend scores are normalized across all vocabularies and time periods
* Geographic data may not be available for all trending items due to privacy considerations
* Rising queries are identified using proprietary algorithms that account for baseline search volume
* Seasonal patterns require at least 90 days of historical data for accurate detection
* Some trending data may be filtered to exclude potentially sensitive health information
# Semantic Search (Coming Soon)
Source: https://docs.omophub.com/api-reference/search/semantic-search
Search for medical concepts using natural language understanding and semantic similarity, enabling contextual and meaning-based queries.
This feature is coming soon and is not yet available in the current API version.
## Overview
This endpoint will provide semantic search capabilities that understand the meaning and context of queries rather than just matching text. It will use advanced natural language processing and machine learning models to find conceptually related medical terms, enabling more intuitive and comprehensive search experiences.
## Query Parameters
Natural language query or medical description
Target vocabularies
**GET examples**: `SNOMED`, `ICD10CM,LOINC`, `RXNORM,NDC`
**POST examples**: `["SNOMED","ICD10CM"]`
Filter results to specific domains
**GET examples**: `Condition`, `Condition,Procedure`, `Drug,Device`
**POST examples**: `["Condition","Procedure"]`
Filter to specific concept classes
**GET examples**: `Clinical Finding`, `Disorder,Clinical Finding`, `Substance,Product`
**POST examples**: `["Clinical Finding","Disorder"]`
Minimum semantic similarity score (0.0-1.0, higher = more strict)
Semantic search mode
**Options**: `comprehensive`, `precise`, `exploratory`, `clinical`, `research`
Expand search context using related medical concepts
Include semantically similar synonyms
Include conceptually related terms
Clinical specialty context for search focus
**Examples**: `cardiology`, `oncology`, `pediatrics`, `emergency_medicine`
Patient context for relevance scoring
**Options**: `adult`, `pediatric`, `geriatric`, `acute_care`, `chronic_care`
Language model for semantic understanding
**Options**: `medical_bert`, `clinical_transformer`, `bio_gpt`, `hybrid`
Semantic embedding model version
Boost commonly used medical terms in results
Filter by standard concept status: `S`, `N`, `C`
Include invalid/deprecated concepts
Temporal context for medical concepts
**Options**: `acute`, `chronic`, `historical`, `current`, `preventive`
Clinical severity context
**Options**: `mild`, `moderate`, `severe`, `critical`, `any`
Include explanation of why concepts are relevant
Sort order for results
**Options**: `semantic_score`, `clinical_relevance`, `usage_frequency`, `alphabetical`
Page number for pagination
Number of results per page (max 100)
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
Original search query
Medical entities detected in the query
Detected search intent (diagnostic, therapeutic, procedural, etc.)
Inferred clinical context
Key semantic concepts extracted from query
Confidence in language understanding (0.0-1.0)
Semantic search mode used
Minimum similarity score applied
Language model used for understanding
Semantic embedding model version
Whether context expansion was enabled
Total concepts evaluated semantically
Concepts found via semantic similarity
Additional matches from context expansion
Time taken for semantic processing
Time spent in ML model inference
Array of semantically matching concepts
Unique concept identifier
Primary concept name
Concept code
Source vocabulary identifier
Human-readable vocabulary name
Domain classification
Concept class identifier
Standard concept indicator (S, N, C)
Semantic similarity score (0.0-1.0)
Clinical relevance score (0.0-1.0)
Type of semantic match (direct, related, contextual, inferential)
Relationship to query (synonymous, broader, narrower, related)
Confidence in semantic match (High, Medium, Low)
Specific concepts from query that matched
Semantic distance in embedding space
Contextual factors that influenced the match
Explanation of relevance (if explain\_relevance=true)
Main reasons this concept is relevant
Semantic connections to the query
How this concept fits the clinical context
Typical usage context for this concept
Semantically related concepts (if include\_related\_concepts=true)
Related concept ID
Related concept name
Type of relationship
Semantic similarity score
Semantic clusters of related concepts
Cluster identifier
Main theme of the cluster
Number of concepts in cluster
Average semantic score for cluster
Most representative concepts in cluster
Semantically similar queries to try
Related search terms
Expanded concepts based on semantic understanding
Alternative clinical phrasings
Unique identifier for the request
Request timestamp
Semantic model version used
Processing mode applied
Current page number
Items per page
Total number of matching concepts
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash title="cURL"
curl -X GET "https://api.omophub.com/v1/search/semantic?query=chest%20pain%20after%20physical%20activity&vocabulary_ids=SNOMED,ICD10CM&semantic_threshold=0.7&clinical_context=cardiology&explain_relevance=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript title="JavaScript"
// Build URL with proper encoding
const baseUrl = 'https://api.omophub.com/v1/search/semantic';
const params = new URLSearchParams({
query: 'difficulty breathing at night',
domains: 'Condition',
search_mode: 'clinical',
context_expansion: 'true',
include_related_concepts: 'true'
});
const response = await fetch(`${baseUrl}?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
params = {
'query': 'patient complains of severe headache with nausea and light sensitivity',
'vocabulary_ids': 'SNOMED,ICD10CM',
'domains': 'Condition',
'semantic_threshold': 0.65,
'search_mode': 'comprehensive',
'clinical_context': 'neurology',
'patient_context': 'adult',
'context_expansion': True,
'include_related_concepts': True,
'explain_relevance': True,
'page_size': 25
}
response = requests.get(
'https://api.omophub.com/v1/search/semantic',
headers=headers,
params=params
)
data = response.json()
```
## POST Request for Sensitive Data
**PHI Security Warning**: Avoid sending protected health information (PHI) or sensitive patient data in URL query strings as they may be logged by servers, proxies, or browser history. Use POST requests with JSON bodies for sensitive queries. Consider API key rotation and ensure full URLs are not logged in your applications.
For sensitive queries containing potential PHI, use POST requests:
```bash POST cURL
curl -X POST "https://api.omophub.com/v1/search/semantic" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "patient reports chest pain with shortness of breath",
"clinical_context": "cardiology",
"explain_relevance": true,
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"semantic_threshold": 0.7,
"page_size": 20
}'
```
```javascript POST JavaScript
const response = await fetch('https://api.omophub.com/v1/search/semantic', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: 'patient reports chest pain with shortness of breath',
clinical_context: 'cardiology',
explain_relevance: true,
vocabulary_ids: ['SNOMED', 'ICD10CM'],
semantic_threshold: 0.7,
page_size: 20
})
});
const data = await response.json();
```
```python POST Python
import requests
import json
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
payload = {
'query': 'patient reports chest pain with shortness of breath',
'clinical_context': 'cardiology',
'explain_relevance': True,
'vocabulary_ids': ['SNOMED', 'ICD10CM'],
'semantic_threshold': 0.7,
'page_size': 20
}
response = requests.post(
'https://api.omophub.com/v1/search/semantic',
headers=headers,
json=payload
)
data = response.json()
```
```json
{
"success": true,
"data": {
"query": "chest pain after physical activity",
"query_analysis": {
"detected_entities": [
{
"entity": "chest pain",
"type": "symptom",
"confidence": 0.95
},
{
"entity": "physical activity",
"type": "trigger",
"confidence": 0.89
}
],
"query_intent": "diagnostic",
"clinical_context": "cardiology",
"semantic_concepts": [
"chest discomfort",
"exertional pain",
"cardiac symptoms",
"angina"
],
"language_confidence": 0.92
},
"semantic_parameters": {
"search_mode": "comprehensive",
"semantic_threshold": 0.7,
"language_model": "medical_bert",
"embedding_version": "v2.1",
"context_expansion": true
},
"search_statistics": {
"total_candidates": 45672,
"semantic_matches": 34,
"context_expanded_matches": 12,
"processing_time_ms": 2847,
"model_inference_time_ms": 1234
},
"concepts": [
{
"concept_id": 194828000,
"concept_name": "Angina pectoris",
"concept_code": "194828000",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"semantic_score": 0.94,
"clinical_relevance_score": 0.97,
"semantic_details": {
"match_type": "direct",
"semantic_relationship": "synonymous",
"confidence_level": "High",
"matching_concepts": [
"chest pain",
"exertional"
],
"semantic_distance": 0.06,
"context_factors": [
"cardiac context",
"exertional trigger",
"chest location"
]
},
"relevance_explanation": {
"primary_reasons": [
"Classic presentation of exertional chest pain",
"Direct semantic match for cardiac chest pain",
"Strong clinical relevance in cardiology context"
],
"semantic_connections": [
"chest pain → cardiac symptom",
"physical activity → exertional trigger",
"angina → chest pain syndrome"
],
"clinical_context_match": "Perfect match for cardiology evaluation of exertional chest pain",
"usage_context": "Primary diagnostic consideration for activity-related chest discomfort"
},
"related_concepts": [
{
"concept_id": 25106000,
"concept_name": "Unstable angina",
"relationship_type": "narrower",
"semantic_score": 0.87
},
{
"concept_id": 233819005,
"concept_name": "Stable angina",
"relationship_type": "narrower",
"semantic_score": 0.91
}
]
},
{
"concept_id": 22298006,
"concept_name": "Myocardial infarction",
"concept_code": "22298006",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"semantic_score": 0.87,
"clinical_relevance_score": 0.93,
"semantic_details": {
"match_type": "related",
"semantic_relationship": "related",
"confidence_level": "High",
"matching_concepts": [
"chest pain",
"cardiac event"
],
"semantic_distance": 0.13,
"context_factors": [
"cardiac context",
"chest pain symptom",
"serious cardiac condition"
]
},
"relevance_explanation": {
"primary_reasons": [
"Can present as exertional chest pain",
"Critical differential diagnosis",
"High clinical importance in chest pain evaluation"
],
"semantic_connections": [
"chest pain → cardiac symptom",
"myocardial infarction → acute chest pain",
"exertional trigger → cardiac stress"
],
"clinical_context_match": "Important differential diagnosis for exertional chest pain",
"usage_context": "Critical consideration in acute chest pain evaluation"
},
"related_concepts": [
{
"concept_id": 401314000,
"concept_name": "Acute ST segment elevation myocardial infarction",
"relationship_type": "narrower",
"semantic_score": 0.83
}
]
},
{
"concept_id": 233910005,
"concept_name": "Exercise-induced asthma",
"concept_code": "233910005",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"semantic_score": 0.79,
"clinical_relevance_score": 0.84,
"semantic_details": {
"match_type": "contextual",
"semantic_relationship": "related",
"confidence_level": "Medium",
"matching_concepts": [
"physical activity",
"exercise-induced"
],
"semantic_distance": 0.21,
"context_factors": [
"exertional trigger",
"respiratory symptoms",
"activity-related"
]
},
"relevance_explanation": {
"primary_reasons": [
"Exercise-induced condition matching activity trigger",
"Can cause chest discomfort during activity",
"Common differential for exertional symptoms"
],
"semantic_connections": [
"physical activity → exercise trigger",
"chest symptoms → respiratory discomfort",
"exertional → activity-induced"
],
"clinical_context_match": "Relevant differential for activity-related chest symptoms",
"usage_context": "Consider in patients with exertional chest discomfort and respiratory symptoms"
},
"related_concepts": [
{
"concept_id": 195967001,
"concept_name": "Asthma",
"relationship_type": "broader",
"semantic_score": 0.76
}
]
}
],
"semantic_clusters": [
{
"cluster_id": "cardiac_conditions",
"cluster_theme": "Cardiac chest pain conditions",
"concept_count": 18,
"average_score": 0.89,
"representative_concepts": [
"Angina pectoris",
"Myocardial infarction",
"Coronary artery disease"
]
},
{
"cluster_id": "exertional_conditions",
"cluster_theme": "Exercise-induced medical conditions",
"concept_count": 8,
"average_score": 0.78,
"representative_concepts": [
"Exercise-induced asthma",
"Exertional dyspnea",
"Exercise intolerance"
]
}
],
"query_suggestions": {
"similar_queries": [
"exertional chest pain",
"cardiac chest pain with activity",
"chest discomfort during exercise",
"activity-induced chest pain"
],
"related_searches": [
"angina pectoris",
"exercise stress test",
"cardiac evaluation",
"chest pain differential diagnosis"
],
"concept_expansions": [
"exertional angina",
"cardiac ischemia",
"coronary artery disease",
"chest pain syndrome"
],
"clinical_alternatives": [
"substernal chest pressure with exertion",
"activity-related cardiac symptoms",
"chest tightness during physical activity"
]
}
},
"meta": {
"request_id": "req_semantic_search_123",
"timestamp": "2024-01-15T10:30:00Z",
"model_version": "medical_bert_v2.1.0",
"processing_mode": "comprehensive",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 34,
"total_pages": 2,
"has_next": true,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Natural Language Query
Search using natural language descriptions:
```bash
curl -X GET "https://api.omophub.com/v1/search/semantic?query=patient%20has%20trouble%20sleeping%20and%20feels%20anxious&domains=Condition&search_mode=clinical" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Clinical Context Search
Use clinical specialty context:
```bash
curl -X GET "https://api.omophub.com/v1/search/semantic?query=irregular%20heartbeat&clinical_context=cardiology&context_expansion=true&explain_relevance=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Medical Search
Perform broad semantic exploration:
```bash
curl -X GET "https://api.omophub.com/v1/search/semantic?query=chronic%20kidney%20disease%20complications&search_mode=exploratory&include_related_concepts=true&semantic_threshold=0.6" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Precise Clinical Search
Use strict semantic matching:
```bash
curl -X GET "https://api.omophub.com/v1/search/semantic?query=acute%20myocardial%20infarction&search_mode=precise&semantic_threshold=0.8&vocabulary_ids=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Patient-Centered Search
Search with patient context:
```bash
curl -X GET "https://api.omophub.com/v1/search/semantic?query=child%20with%20fever%20and%20rash&patient_context=pediatric&clinical_context=pediatrics&context_expansion=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Research-Oriented Search
Academic and research-focused search:
```bash
curl -X GET "https://api.omophub.com/v1/search/semantic?query=inflammatory%20biomarkers%20in%20rheumatoid%20arthritis&search_mode=research&include_related_concepts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Semantic Search Modes
### Comprehensive
* **Description**: Broad semantic exploration with context expansion
* **Best For**: Initial concept discovery, research, comprehensive evaluation
* **Threshold**: Lower (0.6-0.7)
* **Results**: More diverse, includes related concepts
### Precise
* **Description**: Strict semantic matching for exact concept identification
* **Best For**: Specific diagnosis lookup, exact concept mapping
* **Threshold**: Higher (0.8-0.9)
* **Results**: Highly relevant, fewer false positives
### Exploratory
* **Description**: Wide-ranging exploration of semantic relationships
* **Best For**: Research, concept discovery, broad medical exploration
* **Threshold**: Lowest (0.5-0.6)
* **Results**: Maximum diversity, includes distant relationships
### Clinical
* **Description**: Optimized for clinical decision-making contexts
* **Best For**: Patient care, diagnostic support, clinical documentation
* **Threshold**: Moderate (0.7-0.8)
* **Results**: Clinically relevant, practice-oriented
### Research
* **Description**: Academic and research-focused semantic search
* **Best For**: Literature research, hypothesis generation, academic study
* **Threshold**: Variable (0.6-0.8)
* **Results**: Research-relevant, includes emerging concepts
## Language Models
### Medical BERT
* **Description**: BERT model trained on medical literature and clinical texts
* **Strengths**: General medical understanding, clinical context
* **Best For**: General medical queries, clinical documentation
* **Performance**: Balanced speed and accuracy
### Clinical Transformer
* **Description**: Transformer model optimized for clinical text
* **Strengths**: Clinical reasoning, diagnostic context
* **Best For**: Clinical decision support, diagnostic queries
* **Performance**: Higher accuracy, moderate speed
### Bio GPT
* **Description**: GPT-based model trained on biomedical literature
* **Strengths**: Research context, complex medical relationships
* **Best For**: Research queries, literature exploration
* **Performance**: High accuracy, slower processing
### Hybrid
* **Description**: Ensemble of multiple models with weighted scoring
* **Strengths**: Comprehensive coverage, robust performance
* **Best For**: Mission-critical applications, diverse query types
* **Performance**: Highest accuracy, slowest processing
## Semantic Relationship Types
### Synonymous
* **Description**: Concepts with equivalent or near-equivalent meaning
* **Examples**: "heart attack" → "myocardial infarction"
* **Score Range**: 0.9-1.0
* **Clinical Use**: Direct concept mapping, terminology standardization
### Broader
* **Description**: More general concepts that encompass the query
* **Examples**: "Type 2 diabetes" → "diabetes mellitus"
* **Score Range**: 0.7-0.9
* **Clinical Use**: Differential diagnosis, concept hierarchies
### Narrower
* **Description**: More specific concepts within the query domain
* **Examples**: "pneumonia" → "bacterial pneumonia"
* **Score Range**: 0.7-0.9
* **Clinical Use**: Specific diagnosis, detailed classification
### Related
* **Description**: Conceptually connected but not hierarchically related
* **Examples**: "diabetes" → "insulin"
* **Score Range**: 0.6-0.8
* **Clinical Use**: Associated conditions, related treatments
## Clinical Context Applications
### Diagnostic Support
* **Query Types**: Symptom descriptions, clinical presentations
* **Context**: Patient symptoms, examination findings
* **Output**: Differential diagnoses, related conditions
* **Example**: "chest pain with shortness of breath"
### Treatment Planning
* **Query Types**: Therapeutic interventions, treatment options
* **Context**: Condition-specific treatments, patient factors
* **Output**: Treatment options, therapeutic procedures
* **Example**: "treatment for hypertension in elderly patients"
### Medication Search
* **Query Types**: Drug indications, therapeutic effects
* **Context**: Clinical conditions, patient characteristics
* **Output**: Relevant medications, drug classes
* **Example**: "antibiotic for respiratory infection"
### Procedure Selection
* **Query Types**: Medical procedures, interventions
* **Context**: Clinical indications, patient factors
* **Output**: Appropriate procedures, surgical options
* **Example**: "minimally invasive cardiac surgery"
## Performance Optimization
### Query Formulation
* **Natural Language**: Use complete sentences and clinical language
* **Specificity**: Include relevant clinical context and details
* **Medical Terminology**: Mix lay terms with medical terminology
* **Context**: Provide clinical specialty or patient context
### Threshold Selection
* **High Precision**: Use 0.8+ for specific concept identification
* **Balanced**: Use 0.7 for general clinical search
* **High Recall**: Use 0.6 for broad exploration
* **Research**: Use 0.5-0.6 for comprehensive discovery
### Model Selection
* **Speed Priority**: Use Medical BERT
* **Accuracy Priority**: Use Clinical Transformer or Hybrid
* **Research Focus**: Use Bio GPT
* **Balanced**: Use Hybrid model
## Related Endpoints
* [Basic Search](/api-reference/search/basic-search) - Text-based search
* [Fuzzy Search](/api-reference/search/fuzzy-search) - Typo-tolerant search
* [Search Concepts](/api-reference/concepts/search-concepts) - Comprehensive concept search
* [Get Concept Relationships](/api-reference/concepts/get-concept-relationships) - Semantic relationships
# Similarity Search Overview
Source: https://docs.omophub.com/api-reference/search/similarity-search
Overview of similarity search endpoints for finding semantically related medical concepts
## Overview
The OMOPHub API provides multiple endpoints for finding medical concepts that are semantically similar to a given query or concept. These endpoints use advanced machine learning algorithms trained on medical terminology to discover clinically relevant relationships.
## Available Similarity Search Endpoints
### POST /v1/search/similar
**Recommended for flexible similarity searches**
* Find similar concepts using natural language queries
* Supports complex filtering with request body parameters
* Ideal for exploratory medical concept discovery
[View POST Search Similar Documentation →](/api-reference/search/post-search-similar)
### GET /v1/search/similar/{conceptId}
**Recommended for concept-to-concept similarity**
* Find concepts similar to a specific OMOP concept ID
* Optimized for performance with URL path parameters
* Ideal when you have a specific starting concept
[View GET Search Similar by ID Documentation →](/api-reference/search/get-search-similar-by-id)
## Key Features
### Advanced ML Algorithms
* **Semantic similarity** using medical embeddings
* **Lexical similarity** for text-based matching
* **Hybrid approach** combining both methods
### Healthcare-Specific Optimizations
* Medical domain expertise built into models
* Clinical vocabulary understanding (SNOMED, ICD-10, RxNorm)
* Healthcare relationship context awareness
### Flexible Filtering Options
* Filter by vocabularies, domains, and concept classes
* Adjustable similarity thresholds
* Standard vs non-standard concept filtering
## Usage Examples
### Finding Drug Alternatives
Use similarity search to find alternative medications:
```bash
curl -X POST "$BASE_URL/v1/search/similar" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "metformin",
"domain_ids": ["Drug"],
"similarity_threshold": 0.7
}'
```
### Exploring Related Conditions
Find conditions related to a specific diagnosis:
```bash
GET /v1/search/similar/44054006?domain_ids=Condition&max_results=10
```
### Cross-Vocabulary Mapping
Discover equivalent concepts across medical vocabularies:
```bash
curl -X POST "$BASE_URL/v1/search/similar" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "type 2 diabetes",
"vocabulary_ids": ["SNOMED", "ICD10CM", "ICD9CM"],
"explanations": true
}'
```
## Best Practices
### Choosing the Right Endpoint
* Use **POST /search/similar** for:
* Natural language queries
* Complex filtering requirements
* Exploratory searches
* Use **GET /search/similar/{conceptId}** for:
* Known concept IDs
* High-performance applications
* Programmatic integrations
### Optimization Tips
* Set appropriate **similarity thresholds** (0.7-0.9 for most use cases)
* Limit results with **max\_results** parameter
* Use **domain filtering** to focus on relevant clinical areas
* Enable **explanations** for understanding similarity reasoning
## Related Endpoints
* [Search Concepts](/api-reference/concepts/search-concepts) - Basic concept search
* [Advanced Search](/api-reference/search/advanced-search) - Multi-criteria concept search
* [Search Facets](/api-reference/search/search-facets) - Get search filters and categories
# Get Vocabulary Details
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary
GET /vocabularies/{vocabularyId}
Get detailed information about a specific vocabulary
## Overview
Retrieve comprehensive details about a specific vocabulary, including metadata, statistics, and domain information.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC", "RxNorm")
## Query Parameters
Include detailed statistics about concepts and relationships
Include information about medical domains covered
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
vocabulary = response.json()
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const vocabulary = await response.json();
```
```bash cURL (with statistics)
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED?includeStats=true&includeDomains=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with statistics)
import requests
params = {"includeStats": True, "includeDomains": True}
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
detailed_vocabulary = response.json()
```
```json
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "SNOMED International",
"vocabulary_version": "2024-07-01",
"vocabulary_concept": 45756746,
"description": "Comprehensive clinical terminology system providing a standardized way to represent clinical information for healthcare",
"is_active": true,
"created_date": "1970-01-01",
"last_updated": "2024-07-01T00:00:00Z",
"concept_count": 4567891,
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"concept_count": 1234567
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"concept_count": 987654
},
{
"domain_id": "Observable Entity",
"domain_name": "Observable Entity",
"concept_count": 456789
},
{
"domain_id": "Substance",
"domain_name": "Substance",
"concept_count": 234567
}
],
"statistics": {
"total_concepts": 4567891,
"standard_concepts": 3987654,
"classification_concepts": 456789,
"invalid_concepts": 12345,
"relationships_count": 15678900,
"synonyms_count": 8765432,
"average_synonyms_per_concept": 1.92
}
},
"meta": {
"request_id": "req_vocab_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
# Get Vocabulary Changelog
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-changelog
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/changelog
Retrieve detailed change history for a vocabulary including concept additions, modifications, deprecations, and relationship changes.
## Overview
This endpoint provides a comprehensive changelog for vocabulary releases, tracking all changes to concepts, relationships, and metadata over time. It supports filtering by change types, date ranges, and specific version comparisons.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
## Query Parameters
Starting version for changelog comparison
Ending version for changelog comparison (defaults to latest)
Filter by change types (comma-separated)
**Options**: `added`, `modified`, `deprecated`, `deleted`, `reactivated`, `concept_moved`, `relationship_added`, `relationship_removed`
Filter changes to specific domains (comma-separated)
**Examples**: `Condition`, `Procedure`, `Drug`
Filter changes to specific concept classes (comma-separated)
Filter changes from this date (ISO 8601 format)
Filter changes to this date (ISO 8601 format)
Include detailed change information for each concept
Include impact analysis for changes
Sort order for changes
**Options**: `change_date`, `concept_id`, `change_type`, `impact_severity`
**Note**: Selecting `sort_by=impact_severity` is only valid when `include_impact_analysis=true`. Behavior is undefined otherwise. Set `include_impact_analysis=true` to enable sorting by impact severity.
Sort direction: `asc` or `desc`
Page number for pagination
Number of changelog entries per page (max 1000)
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
Starting version for comparison
Ending version for comparison
Total number of changes
Number of concepts added
Number of concepts modified
Number of concepts deprecated
Number of concepts deleted
Number of concepts reactivated
Number of relationship changes
List of domains with changes
Overall impact assessment (Low, Medium, High, Critical)
Array of individual change entries
Concept ID that changed
Concept code
Concept name (current or at time of change)
Type of change made
ISO 8601 date of the change
Version in which change appeared
Domain of the concept
Concept class identifier
Human-readable description of the change
Detailed change information (if include\_details=true)
Previous values for modified fields (null for newly added concepts)
New values for modified fields (null for deleted concepts)
List of fields that were modified
Changes to concept relationships
Changes to concept synonyms
Impact analysis (if include\_impact\_analysis=true)
Impact severity level
Number of mappings affected
Number of descendant concepts affected
Whether this is a breaking change
Whether data migration is recommended
Recommended actions for this change
Replacement concept information (for deprecated concepts, null if no replacement)
Replacement concept ID
Replacement concept name
Type of replacement mapping
Unique identifier for the request
Request timestamp
Summary of filters applied to the query
Current page number
Items per page
Total number of changes
Total number of pages
Whether there are more pages
Whether there are previous pages
Version range analyzed
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/changelog?from_version=2024-01&to_version=2024-03&change_types=added,modified&include_details=true&include_impact_analysis=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/changelog?from_version=2024-01&change_types=added,deprecated&include_details=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'from_version': '2024-01',
'to_version': '2024-03',
'change_types': 'added,modified,deprecated',
'domains': 'Condition,Procedure',
'include_details': True,
'include_impact_analysis': True,
'page_size': 100
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/changelog',
headers=headers,
params=params
)
data = response.json()
```
```json
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"from_version": "2024-01",
"to_version": "2024.2",
"summary": {
"total_changes": 4876,
"change_type_breakdown": {
"added": 2847,
"modified": 1523,
"deprecated": 89,
"deleted": 12,
"reactivated": 156,
"relationship_changes": 249
},
"domains_affected": [
"Condition",
"Procedure",
"Observation",
"Measurement",
"Drug"
],
"impact_level": "Medium"
},
"changes": [
{
"concept_id": "1240541000000107",
"concept_code": "1240541000000107",
"concept_name": "COVID-19 Omicron variant BA.5 infection",
"change_type": "added",
"change_date": "2024-03-01T00:00:00Z",
"version": "2024.2",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"change_description": "New concept added for COVID-19 Omicron BA.5 variant",
"change_details": {
"old_values": null,
"new_values": {
"concept_name": "COVID-19 Omicron variant BA.5 infection",
"concept_code": "1240541000000107",
"valid_start_date": "2024-03-01",
"standard_concept": "S"
},
"fields_changed": ["all"],
"relationship_changes": [
{
"relationship_type": "Is a",
"target_concept_id": "840539006",
"target_concept_name": "Disease caused by severe acute respiratory syndrome coronavirus 2"
}
],
"synonym_changes": [
{
"change_type": "added",
"synonym": "BA.5 COVID infection",
"language": "en"
}
]
},
"impact_analysis": {
"severity": "Low",
"affected_mappings": 0,
"affected_descendants": 0,
"breaking_change": false,
"migration_required": false,
"recommendations": [
"Consider updating clinical decision support rules",
"Review variant-specific coding guidelines"
]
},
"replacement_concept": null
},
{
"concept_id": "84757009",
"concept_code": "84757009",
"concept_name": "Epilepsy (disorder)",
"change_type": "modified",
"change_date": "2024-03-01T00:00:00Z",
"version": "2024.2",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"change_description": "Updated relationships and synonyms for epilepsy concept",
"change_details": {
"old_values": {
"synonym_count": 12
},
"new_values": {
"synonym_count": 15
},
"fields_changed": ["synonyms"],
"relationship_changes": [
{
"change_type": "added",
"relationship_type": "Finding site",
"target_concept_id": "12738006"
"target_concept_name": "Brain structure"
}
],
"synonym_changes": [
{
"change_type": "added",
"synonym": "Epileptic disorder",
"language": "en"
},
{
"change_type": "added",
"synonym": "Seizure disorder",
"language": "en"
}
]
},
"impact_analysis": {
"severity": "Low",
"affected_mappings": 89,
"affected_descendants": 234,
"breaking_change": false,
"migration_required": false,
"recommendations": [
"No action required for existing implementations",
"Enhanced synonyms may improve search capabilities"
]
},
"replacement_concept": null
},
{
"concept_id": "123456789",
"concept_code": "123456789",
"concept_name": "Legacy cardiac procedure (deprecated)",
"change_type": "deprecated",
"change_date": "2024-03-01T00:00:00Z",
"version": "2024.2",
"domain_id": "Procedure",
"concept_class_id": "Procedure",
"change_description": "Deprecated in favor of more specific cardiac intervention concepts",
"change_details": {
"old_values": {
"valid_end_date": "2099-12-31",
"invalid_reason": null
},
"new_values": {
"valid_end_date": "2024-02-29",
"invalid_reason": "D"
},
"fields_changed": ["valid_end_date", "invalid_reason"],
"relationship_changes": [],
"synonym_changes": []
},
"impact_analysis": {
"severity": "Medium",
"affected_mappings": 23,
"affected_descendants": 0,
"breaking_change": true,
"migration_required": true,
"recommendations": [
"Migrate to replacement concept 987654321",
"Update clinical documentation templates",
"Review automated coding rules"
]
},
"replacement_concept": {
"concept_id": "987654321",
"concept_name": "Percutaneous cardiac intervention",
"mapping_type": "Maps to"
}
}
]
},
"meta": {
"request_id": "req_snomed_changelog_123",
"timestamp": "2024-03-01T00:00:00Z",
"filters_applied": {
"from_version": "2024-01",
"to_version": "2024.2",
"change_types": ["added", "modified", "deprecated"],
"include_details": true,
"include_impact_analysis": true
},
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 4876,
"total_pages": 98,
"has_next": true,
"has_previous": false
},
"version_range": "2024-01 to 2024-03"
}
}
```
## Usage Examples
### Recent Changes
Get recent changes for a vocabulary:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/changelog?page_size=20" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Version Comparison
Compare specific versions:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/changelog?from_version=2024-01&to_version=2024-03&include_impact_analysis=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Breaking Changes Only
Find breaking changes requiring migration:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/changelog?change_types=deprecated,deleted&include_impact_analysis=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Domain-Specific Changes
Get changes for specific domains:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/changelog?domains=Condition,Procedure&from_date=2024-01-01&include_details=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Date Range Analysis
Analyze changes in a date range:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/ICD10CM/changelog?from_date=2024-01-01&to_date=2024-03-31&sort_by=impact_severity" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Change Types
### Concept Changes
* **Added**: New concepts introduced
* **Modified**: Changes to existing concept properties
* **Deprecated**: Concepts marked as no longer recommended
* **Deleted**: Concepts permanently removed
* **Reactivated**: Previously deprecated concepts restored
### Relationship Changes
* **Relationship Added**: New concept relationships
* **Relationship Removed**: Relationship deletions
* **Concept Moved**: Hierarchical position changes
### Impact Levels
* **Low**: Minor changes with minimal impact
* **Medium**: Moderate changes requiring review
* **High**: Significant changes requiring attention
* **Critical**: Breaking changes requiring immediate action
## Migration Guidance
### Breaking Change Indicators
* `breaking_change: true` in impact analysis
* `migration_required: true` recommendation
* Deprecated concepts with replacement mappings
### Recommended Actions
* Review `recommendations` array for each change
* Check `replacement_concept` for deprecations
* Analyze `affected_mappings` count for integration impact
* Consider `affected_descendants` for hierarchical changes
## Related Endpoints
* [Get Vocabulary Releases](/api-reference/vocabularies/get-vocabulary-releases) - Available versions
* [Get Vocabulary Statistics](/api-reference/vocabularies/get-vocabulary-statistics) - Statistical analysis
* [Get Vocabulary Metadata](/api-reference/vocabularies/get-vocabulary-metadata) - Version metadata
* [Search Concepts](/api-reference/concepts/search-concepts) - Find replacement concepts
# Get Vocabulary Concept Classes
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-concept-classes
GET https://api.omophub.com/v1/vocabularies/concept-classes
Retrieve all concept classes across vocabularies with optional filtering and statistics
## Overview
This endpoint returns all OMOP concept classes from vocabularies, providing insights into the classification structure of medical concepts. Concept classes group related concepts and help understand the semantic organization of medical terminology.
## Query Parameters
Filter concept classes by specific vocabulary ID (e.g., "SNOMED", "ICD10CM")
Include concept count statistics for each class
Include invalid/deprecated concept classes
Specific vocabulary release version (e.g., "2024.1")
Page number for pagination (1-based)
Number of concept classes per page
## Response
Indicates whether the request was successful
Array of concept classes with their details
Unique identifier for the concept class
Human-readable name of the concept class
The concept ID representing this concept class
Number of concepts in this class (when include\_stats=true)
Primary vocabulary where this class is defined
Description of the concept class purpose and scope
Response metadata including pagination
Unique identifier for request tracing
API version used for this response
Current page number
Items per page
Total concept classes
Total pages available
Whether there are more pages
Whether there are previous pages
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/concept-classes?vocabulary_id=SNOMED&include_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/concept-classes?include_stats=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const result = await response.json();
console.log('Found', result.data.meta.pagination.total_items, 'concept classes');
```
```python Python
import requests
url = "https://api.omophub.com/v1/vocabularies/concept-classes"
params = {
"vocabulary_id": "SNOMED",
"include_stats": True,
"page_size": 50
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
for cls in data['data']['concept_classes']:
print(f"{cls['concept_class_name']}: {cls.get('concept_count', 'N/A')} concepts")
```
```json
{
"success": true,
"data": {
"concept_classes": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_class_concept_id": 404684003,
"concept_count": 125847,
"vocabulary_id": "SNOMED",
"description": "A clinical finding represents a result of an observation, assessment, or judgment"
},
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_class_concept_id": 71388002,
"concept_count": 87234,
"vocabulary_id": "SNOMED",
"description": "A procedure is an act of care provided to a patient"
},
{
"concept_class_id": "Substance",
"concept_class_name": "Substance",
"concept_class_concept_id": 105590001,
"concept_count": 45692,
"vocabulary_id": "SNOMED",
"description": "A substance is a physical material or chemical compound"
}
]
},
"meta": {
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 89,
"total_pages": 5,
"has_next": true,
"has_previous": false
},
"request_id": "req_vocab_classes_abc123",
"timestamp": "2024-12-22T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Get All Concept Classes
Retrieve all concept classes across all vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/concept-classes" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Get SNOMED Concept Classes with Statistics
Get concept classes from SNOMED with count statistics:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/concept-classes?vocabulary_id=SNOMED&include_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Filter by Multiple Vocabularies
Get concept classes from specific vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/concept-classes?vocabulary_id=SNOMED,ICD10CM&include_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Important Notes
* **Concept classes** group related concepts and define their semantic type
* **Statistics** are available when `include_stats=true` but may impact response time
* **Pagination** is recommended for large result sets
* **Invalid classes** are excluded by default but can be included with `include_invalid=true`
* **Vocabulary versions** can be specified using the `vocab_release` parameter
## Related Endpoints
* [Get Concept Classes by ID](/api-reference/domains/get-concept-classes) - Get details for specific concept classes
* [Get Concepts by Class](/api-reference/domains/get-class-concepts) - Get all concepts within a class
* [Get Class Hierarchy](/api-reference/domains/get-class-hierarchy) - Get hierarchical relationships between classes
# Get Vocabulary Concepts
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-concepts
GET /vocabularies/{vocabularyId}/concepts
Get concepts within a specific vocabulary
## Overview
Retrieve concepts that belong to a specific vocabulary with optional filtering and pagination.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC", "RxNorm")
## Query Parameters
Filter by medical domain (e.g., "Condition", "Procedure", "Drug")
Filter by concept class (e.g., "Clinical Finding", "Procedure")
Include invalid or deprecated concepts
Page number for pagination (1-based)
Maximum number of concepts to return (max: 1000)
## Examples
### Get SNOMED Conditions
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/concepts?domain=Condition&page_size=10" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
params = {"domain": "Condition", "page_size": 10}
response = requests.get(
"https://api.omophub.com/v1/vocabularies/SNOMED/concepts",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
data = response.json()
for concept in data['data']['concepts']:
print(f"{concept['concept_name']}: {concept['concept_id']}")
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/concepts?domain=Condition&page_size=10', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const result = await response.json();
console.log('Found', result.data.meta.pagination.total_items, 'concepts');
```
```json
{
"success": true,
"data": {
"concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"concept_code": "44054006",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
},
{
"concept_id": 201254,
"concept_name": "Hypertensive disorder",
"concept_code": "38341003",
"vocabulary_id": "SNOMED",
"domain_id": "Condition",
"concept_class_id": "Clinical Finding",
"standard_concept": "S",
"invalid_reason": null,
"valid_start_date": "2002-01-31",
"valid_end_date": "2099-12-31"
}
]
},
"meta": {
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 125847,
"total_pages": 12585,
"has_next": true,
"has_previous": false
},
"request_id": "req_vocab_concepts_def456",
"timestamp": "2024-12-22T10:31:00Z",
"vocab_release": "2025.2"
}
}
```
# Get Vocabulary Coverage
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-coverage
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/coverage
Analyze vocabulary coverage across domains, concept classes, and clinical specialties with detailed gap analysis and mapping statistics.
## Overview
This endpoint provides comprehensive coverage analysis for vocabularies, identifying strengths, gaps, and mapping relationships across different clinical domains and specialties. It helps assess vocabulary completeness and suitability for specific use cases.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
## Query Parameters
Type of coverage analysis
**Options**: `comprehensive`, `domain_focused`, `mapping_focused`, `specialty_focused`, `gap_analysis`
Focus analysis on specific domains (comma-separated)
**Examples**: `Condition,Procedure,Drug`, `Observation,Measurement`
Analyze coverage for specific medical specialties (comma-separated)
**Examples**: `cardiology,oncology,pediatrics`
Compare coverage with other vocabularies (comma-separated)
**Examples**: `ICD10CM,LOINC`, `RXNORM,NDC`
Include detailed mapping coverage analysis
Include recommendations for coverage gaps
Include example concepts for each coverage area
Level of detail for coverage analysis
**Options**: `high_level`, `domain`, `concept_class`, `detailed`
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
Version analyzed
ISO 8601 timestamp of analysis
Overall coverage score (0-100)
Qualitative completeness assessment
Total concepts in vocabulary
Number of domains with concepts
Percentage of medical specialties covered
Domain-specific coverage analysis
Domain identifier
Human-readable domain name
Number of concepts in this domain
Domain coverage score (0-100)
Qualitative coverage level (Excellent, Good, Fair, Poor)
Distribution across concept classes
Concept class identifier
Number of concepts in this class
Coverage percentage for this class
Medical specialties this domain serves
Sample concepts (if include\_examples=true)
Medical specialty coverage analysis
Medical specialty name
Specialty coverage score (0-100)
Number of concepts relevant to specialty
Main domains for this specialty
Areas of strong coverage
Identified coverage gaps
Suggested additional vocabularies for gaps
Cross-vocabulary mapping coverage (if include\_mapping\_analysis=true)
Number of concepts with external mappings
Overall mapping coverage percentage
Coverage for each target vocabulary
Target vocabulary identifier
Number of mapped concepts
Mapping coverage percentage
Quality score for mappings (0-100)
Number of bidirectional mappings
Mapping coverage by domain
Identified coverage gaps and recommendations
Critical coverage gaps requiring attention
Area with coverage gap
Affected domain
Affected medical specialty
Severity level (Critical, High, Medium, Low)
Description of the gap
Potential impact of the gap
Recommended actions to address gaps
Type of recommended action
Priority level (High, Medium, Low)
Action description
Suggested complementary vocabularies
Potential coverage score improvement
Comparison with other vocabularies (if comparison\_vocabularies specified)
Vocabulary being compared
Percentage of concept overlap
Areas uniquely covered by this vocabulary
How well vocabularies complement each other (0-100)
Usage recommendation for combined coverage
Suitability for different healthcare use cases
Healthcare use case name
Suitability score (0-100)
Qualitative suitability (Excellent, Good, Fair, Poor)
Factors supporting this use case
Limitations for this use case
Recommendations for this use case
Unique identifier for the request
Request timestamp
Parameters used for analysis
Time taken for analysis
Vocabulary release version analyzed
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/coverage?analysis_type=comprehensive&target_domains=Condition,Procedure&include_mapping_analysis=true&include_gap_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/coverage?clinical_specialties=cardiology,oncology&include_examples=true&comparison_vocabularies=ICD10CM', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const data = await response.json();
```
```python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
params = {
'analysis_type': 'gap_analysis',
'target_domains': 'Condition,Procedure,Drug',
'clinical_specialties': 'cardiology,oncology,pediatrics',
'include_mapping_analysis': True,
'include_gap_recommendations': True,
'include_examples': True,
'granularity': 'detailed'
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/coverage',
headers=headers,
params=params
)
data = response.json()
```
```json
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_version": "2024.2",
"analysis_date": "2024-03-15T10:30:00Z",
"overall_coverage": {
"coverage_score": 87.5,
"completeness_rating": "Excellent",
"total_concepts": 354652,
"domains_covered": 18,
"specialty_coverage_percentage": 89.2
},
"domain_coverage": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"concept_count": 125643,
"coverage_score": 95.2,
"coverage_level": "Excellent",
"concept_class_distribution": [
{
"concept_class_id": "Clinical Finding",
"concept_count": 89567,
"coverage_percentage": 71.3
},
{
"concept_class_id": "Disorder",
"concept_count": 36076,
"coverage_percentage": 28.7
}
],
"specialty_relevance": [
"cardiology",
"oncology",
"endocrinology",
"neurology"
],
"example_concepts": [
"Type 2 diabetes mellitus",
"Essential hypertension",
"Acute myocardial infarction"
]
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"concept_count": 87321,
"coverage_score": 89.7,
"coverage_level": "Good",
"concept_class_distribution": [
{
"concept_class_id": "Procedure",
"concept_count": 67432,
"coverage_percentage": 77.2
},
{
"concept_class_id": "Intervention",
"concept_count": 19889,
"coverage_percentage": 22.8
}
],
"specialty_relevance": [
"surgery",
"cardiology",
"radiology"
],
"example_concepts": [
"Coronary artery bypass graft",
"Appendectomy",
"Chest radiography"
]
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"concept_count": 23456,
"coverage_score": 64.3,
"coverage_level": "Fair",
"concept_class_distribution": [
{
"concept_class_id": "Pharmaceutical / biologic product",
"concept_count": 18734,
"coverage_percentage": 79.9
},
{
"concept_class_id": "Clinical drug",
"concept_count": 4722,
"coverage_percentage": 20.1
}
],
"specialty_relevance": [
"pharmacy",
"oncology",
"psychiatry"
],
"example_concepts": [
"Metformin",
"Aspirin",
"Insulin"
]
}
],
"specialty_coverage": [
{
"specialty": "cardiology",
"coverage_score": 94.8,
"concept_count": 34567,
"primary_domains": ["Condition", "Procedure", "Observation"],
"coverage_strengths": [
"Comprehensive cardiac conditions",
"Extensive cardiac procedures",
"Detailed anatomical structures"
],
"coverage_gaps": [
"Some emerging interventional techniques",
"Limited cardiac rehabilitation concepts"
],
"recommended_supplements": ["HCPCS", "ICD10PCS"]
},
{
"specialty": "oncology",
"coverage_score": 91.2,
"concept_count": 28934,
"primary_domains": ["Condition", "Procedure", "Drug"],
"coverage_strengths": [
"Comprehensive cancer classifications",
"Extensive oncological procedures",
"Detailed staging concepts"
],
"coverage_gaps": [
"Some immunotherapy agents",
"Emerging precision medicine concepts"
],
"recommended_supplements": ["RXNORM", "HCPCS"]
}
],
"mapping_coverage": {
"total_mapped_concepts": 298456,
"mapping_coverage_percentage": 84.1,
"target_vocabulary_coverage": [
{
"target_vocabulary_id": "ICD10CM",
"mapped_concepts": 89456,
"coverage_percentage": 25.2,
"mapping_quality_score": 87.3,
"bidirectional_mappings": 67234
},
{
"target_vocabulary_id": "LOINC",
"mapped_concepts": 34567,
"coverage_percentage": 9.7,
"mapping_quality_score": 91.8,
"bidirectional_mappings": 28934
},
{
"target_vocabulary_id": "RXNORM",
"mapped_concepts": 23456,
"coverage_percentage": 6.6,
"mapping_quality_score": 76.5,
"bidirectional_mappings": 18732
}
],
"domain_mapping_coverage": [
{
"domain_id": "Condition",
"mapping_percentage": 89.7
},
{
"domain_id": "Procedure",
"mapping_percentage": 78.4
},
{
"domain_id": "Drug",
"mapping_percentage": 45.2
}
]
},
"gap_analysis": {
"critical_gaps": [
{
"area": "Pharmaceutical products",
"domain": "Drug",
"specialty": "pharmacy",
"gap_severity": "High",
"description": "Limited coverage of brand-name medications and dosage forms",
"impact": "Reduced medication reconciliation accuracy"
},
{
"area": "Laboratory procedures",
"domain": "Measurement",
"specialty": "pathology",
"gap_severity": "Medium",
"description": "Some specialized laboratory tests not well represented",
"impact": "Limited support for specialized diagnostic workflows"
}
],
"recommended_actions": [
{
"action_type": "supplement_vocabulary",
"priority": "High",
"description": "Supplement with RXNORM for comprehensive drug coverage",
"complementary_vocabularies": ["RXNORM", "NDC"]
},
{
"action_type": "enhance_mapping",
"priority": "Medium",
"description": "Improve mappings to LOINC for laboratory procedures",
"complementary_vocabularies": ["LOINC"]
}
],
"coverage_improvement_potential": 12.8
},
"comparison_analysis": [
{
"comparison_vocabulary_id": "ICD10CM",
"overlap_percentage": 67.3,
"unique_coverage_areas": [
"Detailed anatomical concepts",
"Extensive procedure classifications",
"Rich relationship hierarchies"
],
"complementary_strength": 85.7,
"recommendation": "Excellent complement - SNOMED provides clinical detail while ICD10CM offers administrative coding"
}
],
"use_case_suitability": [
{
"use_case": "Electronic Health Records",
"suitability_score": 94.2,
"suitability_level": "Excellent",
"supporting_factors": [
"Comprehensive clinical terminology",
"Rich hierarchical relationships",
"Extensive mapping coverage"
],
"limitations": [
"Complexity may require training",
"Some gaps in pharmaceutical coverage"
],
"recommendations": [
"Primary terminology for clinical documentation",
"Supplement with RXNORM for medications"
]
},
{
"use_case": "Clinical Decision Support",
"suitability_score": 91.8,
"suitability_level": "Excellent",
"supporting_factors": [
"Detailed concept relationships",
"Hierarchical navigation support",
"Rich semantic content"
],
"limitations": [
"Version updates may affect rules",
"Complexity in rule maintenance"
],
"recommendations": [
"Excellent for rule-based CDS",
"Consider version management strategy"
]
}
]
},
"meta": {
"request_id": "req_snomed_coverage_123",
"timestamp": "2024-03-15T10:30:00Z",
"analysis_parameters": {
"analysis_type": "comprehensive",
"target_domains": ["Condition", "Procedure", "Drug"],
"include_mapping_analysis": true,
"include_gap_recommendations": true,
"granularity": "detailed"
},
"computation_time_ms": 5847,
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Comprehensive Coverage Analysis
Get complete coverage analysis:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/coverage?analysis_type=comprehensive&include_mapping_analysis=true&include_gap_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specialty-Focused Analysis
Analyze coverage for specific medical specialties:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/coverage?clinical_specialties=cardiology,oncology&granularity=detailed&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Domain-Specific Coverage
Focus on specific domains:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/ICD10CM/coverage?target_domains=Condition&analysis_type=domain_focused&include_gap_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Vocabulary Comparison
Compare coverage with other vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/coverage?comparison_vocabularies=ICD10CM,LOINC,RXNORM&analysis_type=mapping_focused" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Gap Analysis
Identify coverage gaps:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/LOINC/coverage?analysis_type=gap_analysis&include_gap_recommendations=true&clinical_specialties=pathology" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Analysis Types
### Comprehensive
* Complete coverage analysis across all dimensions
* Includes domain, specialty, and mapping coverage
* Provides gap analysis and recommendations
### Domain Focused
* Deep dive into specific domain coverage
* Detailed concept class distribution
* Domain-specific gap identification
### Mapping Focused
* Cross-vocabulary mapping analysis
* Interoperability assessment
* Mapping quality evaluation
### Specialty Focused
* Medical specialty coverage analysis
* Specialty-specific recommendations
* Use case suitability assessment
### Gap Analysis
* Identifies coverage limitations
* Prioritizes improvement opportunities
* Provides actionable recommendations
## Coverage Scores
### Score Interpretation
* **90-100**: Excellent coverage
* **75-89**: Good coverage
* **60-74**: Fair coverage
* **40-59**: Poor coverage
* **0-39**: Very limited coverage
### Factors Considered
* Concept count and distribution
* Hierarchical completeness
* Mapping availability and quality
* Specialty-specific needs
* Use case requirements
## Related Endpoints
* [Get Vocabulary Statistics](/api-reference/vocabularies/get-vocabulary-statistics) - Statistical analysis
* [Get Vocabulary Metadata](/api-reference/vocabularies/get-vocabulary-metadata) - Comprehensive metadata
* [List Domains](/api-reference/domains/list-domains) - Domain information
* [Get Concept Mappings](/api-reference/mappings/get-concept-mappings) - Mapping details
# Get Vocabulary Domain Statistics
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-domain-stats
GET https://api.omophub.com/v1/vocabularies/{vocabularyId}/stats/domains/{domainId}
Get detailed statistics for a specific domain within a vocabulary
## Overview
This endpoint provides comprehensive statistics about concepts within a specific domain of a particular vocabulary. It's essential for understanding domain coverage, concept distribution, and data quality metrics for targeted analysis.
## Path Parameters
The vocabulary identifier (e.g., "SNOMED", "ICD10CM", "LOINC")
The domain identifier (e.g., "Condition", "Drug", "Procedure", "Measurement")
## Query Parameters
Include statistics broken down by concept class
Include temporal analysis of concept validity periods
Include statistics about relationships within this domain
Include cross-vocabulary mapping coverage statistics
Calculate statistics only for standard concepts
Specific vocabulary release version (e.g., "2024.1")
## Response
Information about the vocabulary
Vocabulary identifier
Human-readable vocabulary name
Current vocabulary version
Official reference or URL
Information about the domain
Domain identifier
Human-readable domain name
Description of the domain
Comprehensive domain statistics
Total number of concepts in this vocabulary-domain combination
Number of standard concepts (standard\_concept = 'S')
Number of classification concepts (standard\_concept = 'C')
Number of non-standard concepts (standard\_concept = 'N' or null)
Number of currently valid concepts (no invalid\_reason)
Number of invalid/deprecated concepts
Percentage of domain coverage within this vocabulary (0-100)
Average concepts per concept class in this domain
Overall data quality score for this domain (0.0-1.0)
Statistics by concept class (when include\_concept\_classes=true)
Concept class identifier
Concept class name
Number of concepts in this class
Number of standard concepts
Percentage of domain represented by this class
Average relationships per concept
Temporal analysis of concepts (when include\_temporal\_analysis=true)
Earliest concept valid start date
Latest concept valid start date
Number of concepts introduced by decade
Concepts added in last 2 years
Concepts deprecated in last 2 years
Temporal stability score (0.0-1.0)
Relationship statistics (when include\_relationship\_stats=true)
Total relationships involving concepts in this domain
Relationships within this domain
Relationships to other domains
Number of hierarchical relationships (Is a, Subsumes)
Number of mapping relationships
Average relationships per concept
Most common relationship types
Cross-vocabulary mapping coverage (when include\_mapping\_coverage=true)
Concepts with mappings to other vocabularies
Concepts without cross-vocabulary mappings
Percentage with cross-vocabulary mappings
Vocabularies that concepts map to
Vocabulary with highest mapping coverage
Distribution of mapping confidence scores
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats/domains/Condition?include_concept_classes=true&include_mapping_coverage=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
```
```javascript JavaScript
const vocabularyId = "SNOMED";
const domainId = "Condition";
const response = await fetch(`https://api.omophub.com/v1/vocabularies/${vocabularyId}/stats/domains/${domainId}?include_concept_classes=true&include_temporal_analysis=true&include_relationship_stats=true`, {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Accept': 'application/json'
}
});
const result = await response.json();
const domainStats = result.data;
console.log(`Domain Statistics: ${domainStats.vocabulary.vocabulary_name} - ${domainStats.domain.domain_name}`);
console.log(`Total concepts: ${domainStats.statistics.total_concepts.toLocaleString()}`);
console.log(`Standard concepts: ${domainStats.statistics.standard_concepts.toLocaleString()}`);
console.log(`Coverage: ${domainStats.statistics.coverage_percentage.toFixed(1)}%`);
console.log(`Data quality score: ${domainStats.statistics.data_quality_score.toFixed(3)}`);
if (domainStats.concept_class_breakdown) {
console.log('\nTop Concept Classes:');
domainStats.concept_class_breakdown
.sort((a, b) => b.total_concepts - a.total_concepts)
.slice(0, 5)
.forEach((cls, index) => {
console.log(`${index + 1}. ${cls.concept_class_name}: ${cls.total_concepts.toLocaleString()} concepts (${cls.percentage_of_domain.toFixed(1)}%)`);
});
}
```
```python Python
import requests
vocabulary_id = "SNOMED"
domain_id = "Condition"
url = f"https://api.omophub.com/v1/vocabularies/{vocabulary_id}/stats/domains/{domain_id}"
params = {
"include_concept_classes": True,
"include_temporal_analysis": True,
"include_relationship_stats": True,
"include_mapping_coverage": True,
"standard_concepts_only": False
}
headers = {
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/json"
}
response = requests.get(url, params=params, headers=headers)
data = response.json()
vocab = data['data']['vocabulary']
domain = data['data']['domain']
stats = data['data']['statistics']
print(f"Domain Statistics Report")
print(f"Vocabulary: {vocab['vocabulary_name']} ({vocab['vocabulary_id']})")
print(f"Version: {vocab.get('vocabulary_version', 'N/A')}")
print(f"Domain: {domain['domain_name']} ({domain['domain_id']})")
print(f"Description: {domain.get('description', 'N/A')}")
print(f"\nOverall Statistics:")
print(f" Total concepts: {stats['total_concepts']:,}")
print(f" Standard concepts: {stats['standard_concepts']:,}")
print(f" Coverage: {stats['coverage_percentage']:.1f}%")
print(f" Data quality score: {stats['data_quality_score']:.3f}")
```
```json
{
"success": true,
"data": {
"vocabulary": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_version": "2024.1",
"vocabulary_reference": "http://www.snomed.org/"
},
"domain": {
"domain_id": "Condition",
"domain_name": "Condition",
"description": "Medical conditions, diseases, and disorders"
},
"statistics": {
"total_concepts": 125847,
"standard_concepts": 118943,
"classification_concepts": 4521,
"non_standard_concepts": 2383,
"valid_concepts": 123456,
"invalid_concepts": 2391,
"coverage_percentage": 85.7,
"concept_density": 2847.3,
"data_quality_score": 0.924
},
"concept_class_breakdown": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"total_concepts": 87234,
"standard_concepts": 85123,
"percentage_of_domain": 69.3,
"avg_relationships_per_concept": 4.7
},
{
"concept_class_id": "Disorder",
"concept_class_name": "Disorder",
"total_concepts": 23456,
"standard_concepts": 22891,
"percentage_of_domain": 18.6,
"avg_relationships_per_concept": 5.2
},
{
"concept_class_id": "Finding",
"concept_class_name": "Finding",
"total_concepts": 15157,
"standard_concepts": 14929,
"percentage_of_domain": 12.1,
"avg_relationships_per_concept": 3.8
}
],
"temporal_analysis": {
"earliest_valid_start": "2002-01-31",
"latest_valid_start": "2024-03-01",
"concepts_by_decade": {
"2000": 89234,
"2010": 23456,
"2020": 13157
},
"recently_added_concepts": 5678,
"recently_deprecated_concepts": 891,
"stability_score": 0.887
},
"relationship_statistics": {
"total_relationships": 589234,
"intra_domain_relationships": 456789,
"cross_domain_relationships": 132445,
"hierarchical_relationships": 234567,
"mapping_relationships": 89123,
"avg_relationships_per_concept": 4.7,
"relationship_types": [
{"type": "Is a", "count": 234567},
{"type": "Maps to", "count": 89123},
{"type": "Subsumes", "count": 67890},
{"type": "Has finding site", "count": 45678},
{"type": "Associated with", "count": 34567}
]
},
"mapping_coverage": {
"mapped_to_other_vocabularies": 98765,
"unmapped_concepts": 27082,
"mapping_coverage_percentage": 78.5,
"target_vocabularies": [
{"vocabulary_id": "ICD10CM", "mapped_concepts": 45678, "coverage_percentage": 36.3},
{"vocabulary_id": "ICD9CM", "mapped_concepts": 23456, "coverage_percentage": 18.6},
{"vocabulary_id": "MedDRA", "mapped_concepts": 17890, "coverage_percentage": 14.2}
],
"best_mapped_vocabulary": {
"vocabulary_id": "ICD10CM",
"coverage_percentage": 36.3,
"mapping_quality": "high"
},
"mapping_quality_distribution": {
"excellent": 34567,
"good": 45678,
"fair": 12345,
"poor": 6175
}
}
},
"meta": {
"request_id": "req_vocab_domain_stats_ghi789",
"timestamp": "2024-12-22T10:32:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Domain Statistics
Get essential statistics for a vocabulary-domain combination:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats/domains/Condition" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Detailed Analysis
Get comprehensive analysis with all optional data:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/ICD10CM/stats/domains/Procedure?include_concept_classes=true&include_temporal_analysis=true&include_relationship_stats=true&include_mapping_coverage=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Quality Assessment
Focus on data quality and mapping coverage:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/LOINC/stats/domains/Measurement?include_mapping_coverage=true&standard_concepts_only=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comparative Analysis
Compare domain statistics across vocabularies:
```javascript
async function compareDomainAcrossVocabularies(domain, vocabularies) {
const comparisons = await Promise.all(
vocabularies.map(async (vocab) => {
const response = await fetch(`/v1/vocabularies/${vocab}/stats/domains/${domain}?include_concept_classes=true`);
const data = await response.json();
return {
vocabulary: vocab,
totalConcepts: data.statistics.total_concepts,
standardConcepts: data.statistics.standard_concepts,
coverage: data.statistics.coverage_percentage,
dataQuality: data.statistics.data_quality_score,
topClass: data.concept_class_breakdown?.[0]?.concept_class_name
};
})
);
return comparisons.sort((a, b) => b.totalConcepts - a.totalConcepts);
}
// Compare Condition domain across vocabularies
const comparison = await compareDomainAcrossVocabularies('Condition', ['SNOMED', 'ICD10CM', 'ICD9CM']);
```
## Important Notes
* **Performance** - Comprehensive statistics with all options may take longer to compute
* **Data freshness** - Statistics are computed in real-time but may be cached for performance
* **Domain coverage** - Coverage percentage is relative to the total concepts in that domain across all vocabularies
* **Quality scores** - Data quality scores consider completeness, validity, and relationship density
* **Temporal analysis** - Requires historical data and may not be available for all vocabularies
## Related Endpoints
* [Get Vocabulary Statistics](/api-reference/vocabularies/get-vocabulary-statistics) - Overall vocabulary statistics
* [Get Domain Statistics](/api-reference/domains/get-domain-statistics) - Cross-vocabulary domain statistics
* [Get Vocabulary Metadata](/api-reference/vocabularies/get-vocabulary-metadata) - Vocabulary metadata and information
# Get Vocabulary Domains
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-domains
GET https://api.omophub.com/v1/vocabularies/domains
Retrieve all domains that contain concepts across vocabularies, with optional filtering and statistics.
## Overview
This endpoint returns all OMOP domains that contain concepts from any vocabulary, providing a cross-vocabulary view of domain distribution with optional concept counts and filtering.
## Query Parameters
Filter domains to those containing concepts from specific vocabularies
**Examples**: `SNOMED`, `SNOMED,ICD10CM`, `LOINC,RXNORM`
Include concept counts for each domain
Only include domains with at least this many concepts
Include example concept names for each domain
Page number for pagination
Number of domains per page (max 100)
Specific vocabulary release version
## Response
Indicates if the request was successful
Array of domain objects with vocabulary-specific information
Unique domain identifier
Human-readable domain name
OMOP concept ID for the domain
Detailed description of the domain
Total concepts across all vocabularies in this domain (if include\_counts=true)
Per-vocabulary statistics (if include\_counts=true)
Vocabulary identifier
Vocabulary name
Number of concepts from this vocabulary in this domain
Percentage of domain's concepts from this vocabulary
Sample concept names from this domain (if include\_examples=true)
Unique identifier for the request
Request timestamp
Summary of filters applied to the query
Current page number
Items per page
Total number of domains
Total number of pages
Whether there are more pages
Whether there are previous pages
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/domains?vocabulary_ids=SNOMED,ICD10CM&include_counts=true&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/domains?vocabulary_ids=SNOMED,ICD10CM&include_counts=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'vocabulary_ids': 'SNOMED,ICD10CM',
'include_counts': True,
'include_examples': True,
'min_concept_count': 1000
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/domains',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"domain_concept_id": 19,
"description": "A condition is a clinical or pathological state or episode that is observable, measurable, and is considered clinically significant for a patient's health status, clinical care or research purposes.",
"total_concepts": 125643,
"vocabulary_breakdown": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_count": 98765,
"percentage": 78.6
},
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"concept_count": 26878,
"percentage": 21.4
}
],
"example_concepts": [
"Type 2 diabetes mellitus",
"Essential hypertension",
"Acute myocardial infarction",
"Pneumonia, unspecified organism",
"Chronic obstructive pulmonary disease"
]
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"domain_concept_id": 10,
"description": "A procedure is an activity directed at or performed on a patient with the intention of diagnosing, treating, or preventing a condition.",
"total_concepts": 87321,
"vocabulary_breakdown": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"concept_count": 65432,
"percentage": 74.9
},
{
"vocabulary_id": "ICD10PCS",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Procedure Coding System",
"concept_count": 21889,
"percentage": 25.1
}
],
"example_concepts": [
"Coronary artery bypass graft",
"Appendectomy",
"Chest radiography",
"Blood glucose measurement",
"Surgical repair of inguinal hernia"
]
}
],
"meta": {
"request_id": "req_vocab_domains_123",
"timestamp": "2024-01-15T10:30:00Z",
"filters_applied": {
"vocabulary_ids": ["SNOMED", "ICD10CM"],
"min_concept_count": 1000,
"include_counts": true,
"include_examples": true
},
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 18,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Domain List
Get all domains with concepts:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/domains" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### SNOMED Domains with Counts
Get domains containing SNOMED concepts with statistics:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/domains?vocabulary_ids=SNOMED&include_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Large Domains Only
Get domains with at least 10,000 concepts:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/domains?min_concept_count=10000&include_counts=true&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Multiple Vocabularies Comparison
Compare domain coverage across vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/domains?vocabulary_ids=SNOMED,ICD10CM,LOINC&include_counts=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Domain Categories
Common domains across healthcare vocabularies:
### Clinical Domains
* **Condition**: Diseases, disorders, and clinical findings
* **Procedure**: Medical procedures and interventions
* **Observation**: Clinical observations and assessments
* **Measurement**: Quantitative test results and vital signs
### Pharmaceutical Domains
* **Drug**: Medications and pharmaceutical products
* **Device**: Medical devices and equipment
* **Specimen**: Biological specimens and samples
### Administrative Domains
* **Visit**: Healthcare encounters and visit types
* **Provider**: Healthcare provider roles and specialties
* **Payer**: Insurance and payment-related concepts
* **Cost**: Healthcare cost and pricing information
## Related Endpoints
* [List Domains](/api-reference/domains/list-domains) - Detailed domain information with examples
* [Get Domain Statistics](/api-reference/domains/get-domain-statistics) - Comprehensive statistics for a specific domain
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Available vocabularies
* [Search Concepts](/api-reference/concepts/search-concepts) - Search concepts with domain filtering
# Get Vocabulary Metadata
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-metadata
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/metadata
Retrieve comprehensive metadata for a vocabulary including versioning, licensing, and technical details.
## Overview
This endpoint provides detailed metadata about a specific vocabulary, including version information, licensing details, source references, update history, and technical specifications.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
## Query Parameters
Include recent changelog entries
Include detailed licensing information
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
Official vocabulary website or reference URL
Current version identifier
OMOP concept ID representing this vocabulary
Current release version
ISO 8601 formatted release date
Expected date of next release (if known)
Typical release schedule
Database schema name for current release
Total number of concepts
Number of standard concepts
Number of classification concepts
Main domains covered by this vocabulary
Available concept classes
Supported languages
Organization responsible for vocabulary maintenance
Contact information for vocabulary questions
Official vocabulary website
Link to official documentation
Type of license (e.g., "Commercial", "Open Source", "Restricted")
Description of licensing terms
Whether attribution is required for use
Whether commercial use is permitted
URL to full license text
Character encoding used
Available file formats for download
Vocabulary-specific API endpoints available
Format description for concept codes
Information about concept hierarchy depth
Recent changes (if include\_changelog=true)
Version this change appeared in
Date of the change
Type of change (Added, Modified, Deprecated, Removed)
Description of the change
Number of concepts affected
Unique identifier for the request
Request timestamp
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/metadata?include_changelog=true&include_licensing=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/metadata?include_changelog=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'include_changelog': True,
'include_licensing': True
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/metadata',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "https://www.snomed.org/",
"vocabulary_version": "2024.2",
"vocabulary_concept_id": 44819096,
"release_info": {
"current_version": "2024.2",
"release_date": "2024-03-01T00:00:00Z",
"next_expected_release": "2024-07-01T00:00:00Z",
"release_frequency": "Bi-annual (January and July)",
"schema_name": "vocabs_2024_1"
},
"content_info": {
"total_concepts": 354652,
"standard_concepts": 298456,
"classification_concepts": 56196,
"primary_domains": [
"Condition",
"Procedure",
"Observation",
"Specimen",
"Measurement"
],
"concept_classes": [
"Clinical Finding",
"Procedure",
"Observable Entity",
"Body Structure",
"Substance",
"Pharmaceutical / biologic product"
],
"languages_supported": [
"en-US",
"en-GB",
"es-ES",
"fr-FR",
"de-DE"
]
},
"organization_info": {
"maintaining_organization": "SNOMED International",
"contact_info": "info@snomed.org",
"official_website": "https://www.snomed.org/",
"documentation_url": "https://confluence.ihtsdotools.org/"
},
"licensing_info": {
"license_type": "Commercial with Free Tier",
"license_description": "Free for SNOMED International member countries, commercial licensing available for others",
"attribution_required": true,
"commercial_use_allowed": true,
"license_url": "https://www.snomed.org/snomed-ct/get-snomed"
},
"technical_info": {
"encoding": "UTF-8",
"file_formats": [
"RF2 (Release Format 2)",
"JSON",
"XML",
"CSV"
],
"api_endpoints": [
"/v1/concepts",
"/v1/concepts/:id/ancestors",
"/v1/concepts/:id/descendants",
"/v1/concepts/search"
],
"identifier_format": "SCTID (SNOMED CT Identifier) - up to 18 digits",
"hierarchy_depth": {
"max_depth": 15,
"average_depth": 6.8,
"deepest_path": "Clinical finding > Disease > Cardiovascular disease > ..."
}
},
"changelog": [
{
"version": "2024.2",
"change_date": "2024-03-01T00:00:00Z",
"change_type": "Added",
"description": "New concepts for COVID-19 variants and long COVID conditions",
"affected_concepts": 247
},
{
"version": "2024.2",
"change_date": "2024-03-01T00:00:00Z",
"change_type": "Modified",
"description": "Updated relationships for cardiovascular procedure concepts",
"affected_concepts": 1523
},
{
"version": "2024-01",
"change_date": "2024-01-01T00:00:00Z",
"change_type": "Deprecated",
"description": "Deprecated outdated medication concepts",
"affected_concepts": 89
}
]
},
"meta": {
"request_id": "req_snomed_metadata_123",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Metadata
Get basic vocabulary metadata:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/metadata" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Complete Information
Get comprehensive metadata with changelog and licensing:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/metadata?include_changelog=true&include_licensing=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specific Version
Get metadata for a specific release version:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/metadata?vocab_release=2024-01" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Multiple Vocabularies (sequential calls)
Get metadata for multiple vocabularies by calling the endpoint for each vocabulary ID:
```bash
# Define vocabularies to query
VOCABULARIES=("SNOMED" "ICD10CM" "LOINC" "RxNorm")
# Loop through each vocabulary
for vocab in "${VOCABULARIES[@]}"; do
echo "Getting metadata for $vocab..."
curl -X GET "https://api.omophub.com/v1/vocabularies/$vocab/metadata?include_licensing=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" | jq '.'
echo "---"
done
```
## Common Vocabulary Characteristics
### Clinical Terminologies
* **SNOMED CT**: Comprehensive clinical terminology with deep hierarchies
* **ICD-10-CM**: Diagnostic classification system with administrative focus
* **LOINC**: Laboratory and clinical observation identifiers
### Drug Terminologies
* **RxNorm**: Normalized medication names and relationships
* **NDC**: National Drug Code directory
* **ATC**: Anatomical Therapeutic Chemical classification
### Procedure Terminologies
* **HCPCS Level I**: Healthcare procedure codes for billing
* **ICD-10-PCS**: Procedural classification system
* **HCPCS**: Healthcare Common Procedure Coding System
## Related Endpoints
* [Get Vocabulary Details](/api-reference/vocabularies/get-vocabulary) - Basic vocabulary information
* [Get Vocabulary Statistics](/api-reference/vocabularies/get-vocabulary-stats) - Statistical information
* [Get Vocabulary Releases](/api-reference/vocabularies/get-vocabulary-releases) - Version history
* [Get Vocabulary Changelog](/api-reference/vocabularies/get-vocabulary-changelog) - Detailed change history
# Get Vocabulary Quality
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-quality
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/quality
Assess vocabulary quality metrics including data integrity, consistency, completeness, and adherence to standards.
## Overview
This endpoint provides comprehensive quality assessment for vocabularies, evaluating data integrity, consistency, completeness, and adherence to terminology standards. It includes automated quality checks, validation reports, and improvement recommendations.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
## Query Parameters
Type of quality assessment
**Options**: `comprehensive`, `data_integrity`, `consistency_check`, `completeness_audit`, `standards_compliance`
Include detailed findings for each quality issue
Include improvement recommendations
Include example concepts illustrating quality issues
Minimum severity level for reported issues
**Options**: `critical`, `high`, `medium`, `low`, `all`
Limit assessment to specific domains (comma-separated)
**Examples**: `Condition,Procedure,Drug`
Specific quality dimensions to assess (comma-separated)
**Options**: `accuracy`, `completeness`, `consistency`, `timeliness`, `validity`, `integrity`
Compare quality against a baseline version
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
Version assessed
ISO 8601 timestamp of assessment
Overall quality score (0-100)
Letter grade (A, B, C, D, F)
Qualitative assessment (Excellent, Good, Fair, Poor)
Total number of quality issues identified
Number of critical issues
Potential score improvement (0-100)
Accuracy score (0-100)
Letter grade for accuracy
Number of accuracy issues
Major accuracy findings
Completeness score (0-100)
Letter grade for completeness
Percentage of missing required data
Number of incomplete concept records
Consistency score (0-100)
Letter grade for consistency
Number of consistency issues
Number of pattern violations
Validity score (0-100)
Letter grade for validity
Number of invalid values found
Number of format violations
Integrity score (0-100)
Letter grade for integrity
Number of referential integrity violations
Number of orphaned concepts
Timeliness score (0-100)
Letter grade for timeliness
Number of potentially outdated concepts
Data staleness indicator
Quality assessment by domain
Domain identifier
Human-readable domain name
Domain-specific quality score
Letter grade for this domain
Number of concepts in domain
Number of quality issues in domain
Most common quality issues in domain
Detailed quality findings (if include\_detailed\_findings=true)
Unique identifier for the finding
Issue severity (Critical, High, Medium, Low)
Quality category affected
Specific type of quality issue
Description of the quality issue
Number of concepts affected by this issue
Domains affected by this issue
Example concepts illustrating the issue (if include\_examples=true)
Concept ID
Concept name
Specific issue with this concept
Recommended corrective action
Assessment of issue impact
Schema compliance validation results
Standards compliance validation results
Business rules validation results
Cross-reference validation results
Quality improvement recommendations (if include\_recommendations=true)
Unique identifier for recommendation
Implementation priority (High, Medium, Low)
Quality category to improve
Short title of recommendation
Detailed description of recommended action
Expected quality score improvement
Implementation effort required (Low, Medium, High)
Domains that would benefit
Specific implementation steps
Comparison with baseline version (if comparison\_baseline specified)
Baseline version compared against
Quality trend (Improved, Declined, Stable)
Change in overall quality score
Number of new quality issues
Number of issues resolved since baseline
Changes in individual quality dimensions
Unique identifier for the request
Request timestamp
Parameters used for assessment
Time taken for assessment
Total number of concepts assessed
Vocabulary release version assessed
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/quality?assessment_type=comprehensive&include_detailed_findings=true&include_recommendations=true&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/quality?focus_domains=Condition,Procedure&severity_threshold=high&include_recommendations=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'assessment_type': 'comprehensive',
'include_detailed_findings': True,
'include_recommendations': True,
'include_examples': True,
'severity_threshold': 'medium',
'focus_domains': 'Condition,Procedure,Drug',
'quality_dimensions': 'accuracy,completeness,consistency',
'comparison_baseline': '2024-01'
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/quality',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_version": "2024.2",
"assessment_date": "2024-01-15T10:30:00Z",
"overall_quality": {
"quality_score": 91.7,
"quality_grade": "A",
"quality_level": "Excellent",
"total_issues_found": 1247,
"critical_issues": 12,
"improvement_potential": 8.3
},
"quality_dimensions": {
"accuracy": {
"score": 94.2,
"grade": "A",
"issues_found": 234,
"key_findings": [
"High accuracy in concept definitions",
"Minor issues with some synonym assignments",
"Excellent relationship accuracy"
]
},
"completeness": {
"score": 89.8,
"grade": "B+",
"missing_data_percentage": 2.1,
"incomplete_records": 567
},
"consistency": {
"score": 92.5,
"grade": "A-",
"inconsistencies_found": 189,
"pattern_violations": 45
},
"validity": {
"score": 96.1,
"grade": "A",
"invalid_values": 67,
"format_violations": 23
},
"integrity": {
"score": 93.4,
"grade": "A",
"referential_integrity_issues": 89,
"orphaned_concepts": 34
},
"timeliness": {
"score": 88.2,
"grade": "B+",
"outdated_concepts": 234,
"staleness_indicator": 0.12
}
},
"domain_quality": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"quality_score": 93.8,
"quality_grade": "A",
"concept_count": 125643,
"issues_count": 456,
"top_issues": [
"Minor synonym inconsistencies",
"Some missing preferred terms",
"Rare hierarchical anomalies"
]
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"quality_score": 91.2,
"quality_grade": "A-",
"concept_count": 87321,
"issues_count": 567,
"top_issues": [
"Inconsistent procedure classifications",
"Missing anatomical site relationships",
"Some outdated procedure codes"
]
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"quality_score": 84.6,
"quality_grade": "B",
"concept_count": 23456,
"issues_count": 224,
"top_issues": [
"Limited pharmaceutical concept coverage",
"Inconsistent dosage form classifications",
"Missing drug ingredient relationships"
]
}
],
"detailed_findings": [
{
"finding_id": "SNOMED-QF-001",
"severity": "Medium",
"category": "Consistency",
"issue_type": "Synonym inconsistency",
"description": "Some concepts have synonyms that are not consistently formatted across similar concept types",
"affected_concepts_count": 234,
"domain_impact": ["Condition", "Procedure"],
"example_concepts": [
{
"concept_id": 201826,
"concept_name": "Type 2 diabetes mellitus",
"specific_issue": "Synonym 'T2DM' not consistently used across diabetes concepts"
},
{
"concept_id": 73211009,
"concept_name": "Diabetes mellitus",
"specific_issue": "Missing common abbreviation 'DM' as synonym"
}
],
"recommended_action": "Standardize synonym formatting and ensure consistency across related concepts",
"impact_assessment": "Minor impact on search and retrieval functionality"
},
{
"finding_id": "SNOMED-QF-002",
"severity": "High",
"category": "Integrity",
"issue_type": "Orphaned concepts",
"description": "Some concepts lack proper hierarchical relationships, making them difficult to navigate",
"affected_concepts_count": 34,
"domain_impact": ["Procedure", "Device"],
"example_concepts": [
{
"concept_id": 987654321,
"concept_name": "Specialized surgical instrument",
"specific_issue": "Missing 'Is a' relationship to parent device concept"
}
],
"recommended_action": "Establish proper hierarchical relationships for orphaned concepts",
"impact_assessment": "High impact on concept navigation and classification"
},
{
"finding_id": "SNOMED-QF-003",
"severity": "Critical",
"category": "Accuracy",
"issue_type": "Incorrect relationship",
"description": "Some concepts have semantically incorrect relationships that could mislead users",
"affected_concepts_count": 12,
"domain_impact": ["Condition"],
"example_concepts": [
{
"concept_id": 123456789,
"concept_name": "Bacterial pneumonia",
"specific_issue": "Incorrectly related to viral pneumonia as sibling concept"
}
],
"recommended_action": "Review and correct semantically incorrect relationships",
"impact_assessment": "Critical impact on clinical decision support systems"
}
],
"validation_reports": {
"schema_compliance": {
"compliance_score": 98.7,
"violations_found": 23,
"critical_violations": 2
},
"standards_compliance": {
"compliance_score": 96.4,
"standard_violations": 45,
"recommendation_compliance": 89.2
},
"business_rules": {
"rules_passed": 156,
"rules_failed": 8,
"pass_rate": 95.1
},
"cross_references": {
"valid_references": 99.2,
"broken_references": 234,
"dangling_references": 67
}
},
"improvement_recommendations": [
{
"recommendation_id": "SNOMED-REC-001",
"priority": "High",
"category": "Integrity",
"title": "Resolve orphaned concepts",
"description": "Establish proper hierarchical relationships for concepts missing parent relationships",
"expected_improvement": 2.3,
"effort_level": "Medium",
"affected_domains": ["Procedure", "Device"],
"implementation_steps": [
"Identify all orphaned concepts",
"Analyze semantic context for proper placement",
"Create appropriate 'Is a' relationships",
"Validate new hierarchical structure"
]
},
{
"recommendation_id": "SNOMED-REC-002",
"priority": "Medium",
"category": "Consistency",
"title": "Standardize synonym formatting",
"description": "Implement consistent synonym formatting rules across all concept types",
"expected_improvement": 1.8,
"effort_level": "Low",
"affected_domains": ["Condition", "Procedure", "Drug"],
"implementation_steps": [
"Define synonym formatting standards",
"Audit existing synonyms for compliance",
"Implement automated formatting rules",
"Update non-compliant synonyms"
]
},
{
"recommendation_id": "SNOMED-REC-003",
"priority": "Critical",
"category": "Accuracy",
"title": "Correct semantic relationships",
"description": "Review and fix semantically incorrect concept relationships",
"expected_improvement": 4.2,
"effort_level": "High",
"affected_domains": ["Condition"],
"implementation_steps": [
"Identify semantically incorrect relationships",
"Engage domain experts for review",
"Correct relationship assignments",
"Validate changes with clinical reviewers"
]
}
],
"comparison_analysis": {
"baseline_version": "2024-01",
"quality_trend": "Improved",
"score_change": 2.4,
"new_issues": 89,
"resolved_issues": 156,
"dimension_changes": {
"accuracy": 1.2,
"completeness": 3.4,
"consistency": 0.8,
"validity": 2.1,
"integrity": 1.9,
"timeliness": 4.2
}
}
},
"meta": {
"request_id": "req_snomed_quality_123",
"timestamp": "2024-01-15T10:30:00Z",
"assessment_parameters": {
"assessment_type": "comprehensive",
"severity_threshold": "medium",
"focus_domains": ["Condition", "Procedure", "Drug"],
"quality_dimensions": ["accuracy", "completeness", "consistency"]
},
"computation_time_ms": 8947,
"total_concepts_assessed": 354652,
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Comprehensive Quality Assessment
Get complete quality assessment:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/quality?assessment_type=comprehensive&include_detailed_findings=true&include_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Critical Issues Only
Focus on critical quality issues:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/quality?severity_threshold=critical&include_examples=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Domain-Specific Quality Check
Assess specific domains:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/ICD10CM/quality?focus_domains=Condition&assessment_type=data_integrity&include_detailed_findings=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Quality Trend Analysis
Compare with previous version:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/quality?comparison_baseline=2024-01&include_recommendations=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specific Quality Dimensions
Focus on particular quality aspects:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/LOINC/quality?quality_dimensions=accuracy,completeness&include_detailed_findings=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Quality Assessment Types
### Comprehensive
* Complete quality evaluation across all dimensions
* Includes validation reports and recommendations
* Provides detailed findings and examples
### Data Integrity
* Focus on data consistency and referential integrity
* Identifies orphaned concepts and broken references
* Validates cross-reference accuracy
### Consistency Check
* Evaluates naming and formatting consistency
* Identifies pattern violations
* Assesses structural uniformity
### Completeness Audit
* Identifies missing data and incomplete records
* Evaluates coverage gaps
* Assesses mandatory field completion
### Standards Compliance
* Validates adherence to terminology standards
* Checks formatting and structural requirements
* Evaluates best practice compliance
## Quality Dimensions
### Accuracy
* Correctness of concept definitions
* Semantic accuracy of relationships
* Precision of concept classifications
### Completeness
* Presence of required data elements
* Coverage of concept domains
* Availability of necessary metadata
### Consistency
* Uniform naming conventions
* Consistent relationship patterns
* Standardized formatting
### Validity
* Adherence to data format rules
* Valid value ranges
* Proper data types
### Integrity
* Referential integrity maintenance
* Hierarchical structure validity
* Cross-reference accuracy
### Timeliness
* Currency of concept information
* Outdated concept identification
* Version update frequency
## Quality Scoring
### Score Interpretation
* **90-100**: Excellent quality (Grade A)
* **80-89**: Good quality (Grade B)
* **70-79**: Fair quality (Grade C)
* **60-69**: Poor quality (Grade D)
* **0-59**: Unacceptable quality (Grade F)
### Severity Levels
* **Critical**: Issues requiring immediate attention
* **High**: Important issues affecting functionality
* **Medium**: Moderate issues requiring review
* **Low**: Minor issues with minimal impact
## Related Endpoints
* [Get Vocabulary Statistics](/api-reference/vocabularies/get-vocabulary-statistics) - Statistical analysis
* [Get Vocabulary Coverage](/api-reference/vocabularies/get-vocabulary-coverage) - Coverage analysis
* [Get Vocabulary Changelog](/api-reference/vocabularies/get-vocabulary-changelog) - Change tracking
* [Get Vocabulary Metadata](/api-reference/vocabularies/get-vocabulary-metadata) - Comprehensive metadata
# Get Vocabulary Release Version
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-release-version
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/releases/:version
Retrieve detailed information about a specific vocabulary release version including changes, statistics, and compatibility information.
## Overview
This endpoint provides comprehensive information about a specific vocabulary release version, including version-specific statistics, changes from previous versions, compatibility information, and deployment status across different environments.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
The specific version identifier (e.g., "2024.2", "v2024.1")
## Query Parameters
Include version-specific statistical information
Include changes from the previous version
Include backward and forward compatibility information
Include deployment status across environments
Include quality assessment for this version
Compare this version with another specific version
Include download links for version artifacts
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
The specific version identifier
Type of release (Major, Minor, Patch, Hotfix)
ISO 8601 formatted release date
Human-readable release notes
Current status (Released, Beta, RC, Deprecated)
Database schema name for this version
Previous version identifier
Next version identifier (if available)
Planned deprecation date (if applicable)
End of life date (if applicable)
Organization that published this version
Official publication URL
Licensing information for this version
Version checksum for integrity verification
Approximate file size in megabytes
Available download formats
Version-specific statistics (if include\_statistics=true)
Total number of concepts in this version
Number of standard concepts
Number of concepts added in this version
Number of concepts modified in this version
Number of concepts deprecated in this version
Total number of relationships
Distribution of concepts across domains
Domain identifier
Number of concepts in this domain
Percentage of total concepts
Changes from previous version (if include\_changelog=true)
Total number of changes
Number of concepts added
Number of concepts modified
Number of concepts deprecated
Number of relationship changes
Significant changes in this version
Breaking changes requiring attention
Migration guidance for this version
Compatibility information (if include\_compatibility=true)
Whether this version is backward compatible
Level of compatibility (Full, Partial, None)
Number of breaking changes
Features deprecated in this version
Whether migration is required
Systems/tools compatible with this version
Minimum API version required
Deployment status across environments (if include\_deployment\_status=true)
Whether deployed to production
Production deployment date
Percentage of production rollout
Deployment status (Active, Rollback, Staged)
Staging environment deployment status
Development environment deployment status
Regional deployment information
Quality assessment for this version (if include\_quality\_metrics=true)
Overall quality score for this version
Quality trend compared to previous version
Data completeness percentage
Validation test results
Known quality issues in this version
Comparison with another version (if compare\_with specified)
Version being compared with
Differences in concept counts
Concepts unique to each version
Differences in relationships
Upgrade/downgrade recommendation
Download links and information (if include\_download\_links=true)
Available download links
File format (JSON, XML, CSV, RF2)
Download URL
File size in megabytes
File checksum for verification
Link expiration timestamp
API endpoints specific to this version
Version-specific documentation
Unique identifier for the request
Request timestamp
Version availability status
Caching information for this version
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03?include_statistics=true&include_changelog=true&include_compatibility=true&include_deployment_status=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03?include_statistics=true&include_quality_metrics=true&compare_with=2024-01', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'include_statistics': True,
'include_changelog': True,
'include_compatibility': True,
'include_deployment_status': True,
'include_quality_metrics': True,
'compare_with': '2024-01',
'include_download_links': True
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"version": "2024.2",
"release_info": {
"version_type": "Minor",
"release_date": "2024-03-01T00:00:00Z",
"release_notes": "March 2024 release includes new COVID-19 variants, updated cardiovascular procedures, and enhanced psychiatric terminology. This release adds 2,847 new concepts and modifies 1,523 existing concepts based on clinical feedback and emerging medical knowledge.",
"release_status": "Released",
"schema_name": "vocabs_2024_1",
"predecessor_version": "2024-01",
"successor_version": "2024-07",
"deprecation_date": null,
"end_of_life_date": "2026-03-01T00:00:00Z"
},
"version_metadata": {
"source_organization": "SNOMED International",
"publication_url": "https://www.snomed.org/news-and-events/articles/march-2024-release",
"license_info": {
"license_type": "SNOMED CT Affiliate License",
"commercial_use": true,
"attribution_required": true
},
"checksum": "sha256:a1b2c3d4e5f6789012345678901234567890abcdef",
"file_size_mb": 4752.3,
"supported_formats": ["RF2", "JSON", "XML", "CSV"]
},
"version_statistics": {
"total_concepts": 354652,
"standard_concepts": 298456,
"new_concepts": 2847,
"modified_concepts": 1523,
"deprecated_concepts": 89,
"total_relationships": 1247832,
"domain_distribution": [
{
"domain_id": "Condition",
"concept_count": 125643,
"percentage": 35.4
},
{
"domain_id": "Procedure",
"concept_count": 87321,
"percentage": 24.6
},
{
"domain_id": "Observation",
"concept_count": 65432,
"percentage": 18.4
},
{
"domain_id": "Measurement",
"concept_count": 34567,
"percentage": 9.7
},
{
"domain_id": "Drug",
"concept_count": 23456,
"percentage": 6.6
}
]
},
"changelog": {
"summary": {
"total_changes": 4876,
"concepts_added": 2847,
"concepts_modified": 1523,
"concepts_deprecated": 89,
"relationships_changed": 417
},
"major_changes": [
"Added 247 new COVID-19 variant concepts including Omicron subvariants",
"Enhanced cardiovascular procedure terminology with 156 new concepts",
"Updated psychiatric disorder classifications with DSM-5-TR alignment",
"Expanded oncology staging concepts for precision medicine"
],
"breaking_changes": [
"Deprecated legacy cardiac procedure concept 123456789 - migrate to 987654321",
"Restructured mental health disorder hierarchy - review dependent implementations"
],
"migration_notes": [
"Review automated coding rules using deprecated cardiac procedures",
"Update clinical decision support rules referencing restructured mental health concepts",
"Consider mapping validation for new COVID-19 variant concepts"
]
},
"compatibility": {
"backward_compatible": false,
"compatibility_level": "Partial",
"breaking_changes_count": 2,
"deprecated_features": [
"Legacy cardiac procedure classifications",
"Outdated mental health disorder groupings"
],
"migration_required": true,
"supported_integrations": [
"Epic FHIR R4",
"Cerner PowerChart",
"Allscripts",
"athenahealth",
"Custom OMOP CDM implementations"
],
"minimum_api_version": "v1.8"
},
"deployment_status": {
"production": {
"deployed": true,
"deployment_date": "2024-03-15T14:30:00Z",
"rollout_percentage": 100,
"status": "Active"
},
"staging": {
"deployed": true,
"deployment_date": "2024-03-08T10:00:00Z",
"rollout_percentage": 100,
"status": "Active"
},
"development": {
"deployed": true,
"deployment_date": "2024-03-01T09:00:00Z",
"rollout_percentage": 100,
"status": "Active"
},
"regions": [
{
"region": "us-east-1",
"status": "Active",
"deployment_date": "2024-03-15T14:30:00Z"
},
{
"region": "us-west-2",
"status": "Active",
"deployment_date": "2024-03-15T14:35:00Z"
},
{
"region": "eu-west-1",
"status": "Active",
"deployment_date": "2024-03-15T14:40:00Z"
}
]
},
"quality_metrics": {
"overall_quality_score": 91.7,
"quality_trend": "Improved",
"data_completeness": 98.2,
"validation_results": {
"schema_validation": "Passed",
"referential_integrity": "Passed",
"business_rules": "Passed with 3 warnings",
"performance_tests": "Passed"
},
"known_issues": [
"Minor synonym inconsistencies in 234 cardiovascular concepts",
"3 orphaned concepts in device domain require hierarchical placement",
"Performance optimization needed for deep hierarchy queries"
]
},
"comparison_analysis": {
"comparison_version": "2024-01",
"concept_differences": {
"concepts_added": 2847,
"concepts_removed": 12,
"concepts_modified": 1523
},
"unique_concepts": {
"current_version_unique": 2835,
"comparison_version_unique": 0
},
"relationship_differences": {
"relationships_added": 417,
"relationships_removed": 23,
"relationships_modified": 156
},
"recommendation": "Recommended upgrade - significant content enhancements with manageable migration effort"
},
"download_information": {
"download_links": [
{
"format": "RF2",
"url": "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03/download/rf2",
"size_mb": 4752.3,
"checksum": "sha256:a1b2c3d4e5f6789012345678901234567890abcdef",
"expires_at": "2024-01-16T10:30:00Z"
},
{
"format": "JSON",
"url": "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03/download/json",
"size_mb": 6834.7,
"checksum": "sha256:b2c3d4e5f6a17890123456789012345678901bcdef",
"expires_at": "2024-01-16T10:30:00Z"
},
{
"format": "XML",
"url": "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03/download/xml",
"size_mb": 8967.2,
"checksum": "sha256:c3d4e5f6a7b18901234567890123456789012cdef0",
"expires_at": "2024-01-16T10:30:00Z"
}
],
"api_endpoints": [
"/v1/vocabularies/SNOMED?vocab_release=2024-03",
"/v1/concepts?vocabulary_id=SNOMED&vocab_release=2024-03",
"/v1/concepts/search?vocabulary_ids=SNOMED&vocab_release=2024-03"
],
"documentation_links": [
"https://docs.omophub.com/vocabularies/snomed/2024-03",
"https://docs.omophub.com/migration-guides/snomed-2024-03",
"https://docs.omophub.com/release-notes/snomed-2024-03"
]
}
},
"meta": {
"request_id": "req_snomed_version_123",
"timestamp": "2024-03-15T10:30:00Z",
"version_availability": "Available",
"cache_info": {
"cached": true,
"cache_expiry": "2024-04-15T22:30:00Z",
"cache_hit": true
}
}
}
```
## Usage Examples
### Basic Version Information
Get basic information about a specific version:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Version Details
Get complete information about a version:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03?include_statistics=true&include_changelog=true&include_compatibility=true&include_deployment_status=true&include_quality_metrics=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Version Comparison
Compare two specific versions:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03?compare_with=2024-01&include_statistics=true&include_changelog=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Download Preparation
Get download links for a specific version:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/releases/2024-03?include_download_links=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Deployment Status Check
Check deployment status across environments:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/ICD10CM/releases/2024-Q1?include_deployment_status=true&include_quality_metrics=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Migration Assessment
Assess compatibility for migration:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/LOINC/releases/2.76?include_compatibility=true&include_changelog=true&compare_with=2.75" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Version Types
### Major Release
* Significant structural changes
* May include breaking changes
* Requires thorough migration planning
* Annual or bi-annual cadence
### Minor Release
* New concepts and enhancements
* Limited breaking changes
* Quarterly or bi-annual releases
* Moderate migration effort
### Patch Release
* Bug fixes and corrections
* No breaking changes
* Monthly or as-needed basis
* Minimal migration impact
### Hotfix Release
* Critical issue resolution
* Emergency deployment
* Immediate availability
* Focused scope changes
## Release Status Values
### Released
* Generally available for production use
* Full support and documentation available
* Stable and tested release
### Beta
* Pre-release version for testing
* Feature complete but may have issues
* Limited support available
### RC (Release Candidate)
* Final testing before general release
* Feature freeze in effect
* Production-ready quality
### Deprecated
* No longer recommended for new implementations
* Security updates only
* Migration path available
## Deployment Environments
### Production
* Live environment serving end users
* Highest stability requirements
* Gradual rollout capability
### Staging
* Pre-production testing environment
* Mirrors production configuration
* Final validation before release
### Development
* Development and integration testing
* Latest features and fixes
* Higher tolerance for issues
## Quality Assessment
### Quality Scores
* **90-100**: Excellent quality, ready for production
* **80-89**: Good quality, minor issues acceptable
* **70-79**: Fair quality, review recommended
* **60-69**: Poor quality, significant issues present
* **Below 60**: Unacceptable quality, not recommended
### Validation Tests
* Schema validation ensures data structure compliance
* Referential integrity checks relationship validity
* Business rules validate domain-specific constraints
* Performance tests ensure acceptable query response times
## Related Endpoints
* [Get Vocabulary Releases](/api-reference/vocabularies/get-vocabulary-releases) - List all versions
* [Get Vocabulary Changelog](/api-reference/vocabularies/get-vocabulary-changelog) - Detailed change history
* [Get Vocabulary Statistics](/api-reference/vocabularies/get-vocabulary-statistics) - Statistical analysis
* [Get Vocabulary Quality](/api-reference/vocabularies/get-vocabulary-quality) - Quality assessment
# Get Vocabulary Releases
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-releases
GET https://api.omophub.com/v1/vocabularies/releases
Retrieve a list of all available vocabulary release versions with their release dates and status information.
## Overview
This endpoint provides information about all vocabulary release versions available in the system, including release dates, status, and version identifiers.
## Query Parameters
Filter releases for a specific vocabulary (e.g., "SNOMED", "ICD10CM")
Filter by release status
**Options**: `active`, `deprecated`, `beta`
Page number for pagination
Number of items per page (max 100)
## Response
Indicates if the request was successful
Array of vocabulary release objects
Unique identifier for the release
Vocabulary identifier
Full vocabulary name
Version identifier (e.g., "2024.2", "v5.1.2")
ISO 8601 formatted release date
Release status (active, deprecated, beta)
Number of concepts in this release
Database schema name for this release
Whether this is the latest release for the vocabulary
Release description or notes
Unique identifier for the request
Request timestamp
Current page number
Items per page
Total number of releases
Total number of pages
Whether there are more pages
Whether there are previous pages
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/releases?vocabulary_id=SNOMED&status=active" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/releases?vocabulary_id=SNOMED&status=active', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'vocabulary_id': 'SNOMED',
'status': 'active'
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/releases',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": [
{
"release_id": "snomed_2024_03",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"release_version": "2024.2",
"release_date": "2024-03-01T00:00:00Z",
"status": "active",
"total_concepts": 354652,
"schema_name": "vocabs_2024_1",
"is_latest": true,
"description": "March 2024 International Release"
},
{
"release_id": "snomed_2024_01",
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"release_version": "2024-01",
"release_date": "2024-01-01T00:00:00Z",
"status": "active",
"total_concepts": 352890,
"schema_name": "vocabs_2024_1",
"is_latest": false,
"description": "January 2024 International Release"
}
],
"meta": {
"request_id": "req_releases123",
"timestamp": "2024-03-15T10:30:00Z",
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 2,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
}
```
## Usage Examples
### All Releases
Get all vocabulary releases:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/releases" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### SNOMED Releases Only
Get all SNOMED releases:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/releases?vocabulary_id=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Active Releases Only
Get only active releases:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/releases?status=active" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Related Endpoints
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Get all available vocabularies
* [Get Vocabulary Details](/api-reference/vocabularies/get-vocabulary) - Get detailed vocabulary information
# Get Vocabulary Statistics
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-statistics
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/statistics
Retrieve comprehensive statistical analysis for a vocabulary including concept distribution, hierarchy depth, and usage patterns.
## Overview
This endpoint provides detailed statistical information about a vocabulary's concept distribution, hierarchical structure, domain coverage, and usage patterns. It includes advanced metrics like hierarchy depth analysis, concept class distribution, and mapping statistics.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
## Query Parameters
Include detailed hierarchy statistics
Include domain-wise concept distribution
Include mapping relationship statistics
Include historical trend data
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
Version of the vocabulary analyzed
ISO 8601 timestamp when statistics were calculated
Total number of concepts
Number of standard concepts
Number of classification concepts
Number of non-standard concepts
Number of invalid/deprecated concepts
Concept growth rate percentage (if trend data available)
Maximum depth in the hierarchy
Average concept depth in hierarchy
Number of root-level concepts
Number of leaf-level concepts
Distribution of concepts by hierarchy level
Hierarchy depth level
Number of concepts at this depth
Total number of domains with concepts
Concept distribution across domains
Domain identifier
Human-readable domain name
Number of concepts in this domain
Percentage of total concepts
Number of different concept classes
Distribution by concept class
Concept class identifier
Number of concepts in this class
Percentage of total concepts
Total number of concept relationships
Number of different relationship types
Distribution by relationship type
Relationship type identifier
Human-readable relationship name
Number of relationships of this type
Percentage of total relationships
Number of concepts with mappings to other vocabularies
Number of concepts without external mappings
Percentage of concepts with mappings
Vocabularies this vocabulary maps to
Target vocabulary identifier
Number of mappings to this vocabulary
Coverage percentage for this target vocabulary
List of supported language codes
Primary language of the vocabulary
Number of concepts with translations
Data completeness score (0-100)
Data consistency score (0-100)
Various accuracy measurements
Historical trend information (if include\_trend\_data=true)
Time period (e.g., "2024-Q1", "2024.2")
Total concepts in this period
Number of concepts added
Number of concepts modified
Number of concepts deprecated
Unique identifier for the request
Request timestamp
Time taken to compute statistics
Vocabulary release version analyzed
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/statistics?include_hierarchy_stats=true&include_domain_breakdown=true&include_mapping_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/statistics?include_hierarchy_stats=true&include_domain_breakdown=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
params = {
'include_hierarchy_stats': True,
'include_domain_breakdown': True,
'include_mapping_stats': True,
'include_trend_data': True
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/statistics',
headers=headers,
params=params
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_version": "2024.2",
"analysis_date": "2024-01-15T10:30:00Z",
"concept_statistics": {
"total_concepts": 354652,
"standard_concepts": 298456,
"classification_concepts": 56196,
"non_standard_concepts": 0,
"invalid_concepts": 1247,
"concept_growth_rate": 2.8
},
"hierarchy_statistics": {
"max_hierarchy_depth": 15,
"average_depth": 6.8,
"root_concepts": 19,
"leaf_concepts": 187432,
"depth_distribution": [
{ "depth": 1, "concept_count": 19 },
{ "depth": 2, "concept_count": 156 },
{ "depth": 3, "concept_count": 1247 },
{ "depth": 4, "concept_count": 8934 },
{ "depth": 5, "concept_count": 23567 },
{ "depth": 6, "concept_count": 45123 },
{ "depth": 7, "concept_count": 67834 },
{ "depth": 8, "concept_count": 89456 },
{ "depth": 9, "concept_count": 67234 },
{ "depth": 10, "concept_count": 34567 },
{ "depth": 11, "concept_count": 12345 },
{ "depth": 12, "concept_count": 3456 },
{ "depth": 13, "concept_count": 567 },
{ "depth": 14, "concept_count": 123 },
{ "depth": 15, "concept_count": 24 }
]
},
"domain_statistics": {
"domains_covered": 18,
"domain_distribution": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"concept_count": 125643,
"percentage": 35.4
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"concept_count": 87321,
"percentage": 24.6
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"concept_count": 65432,
"percentage": 18.4
},
{
"domain_id": "Measurement",
"domain_name": "Measurement",
"concept_count": 34567,
"percentage": 9.7
},
{
"domain_id": "Drug",
"domain_name": "Drug",
"concept_count": 23456,
"percentage": 6.6
}
]
},
"concept_class_statistics": {
"total_concept_classes": 24,
"class_distribution": [
{
"concept_class_id": "Clinical Finding",
"concept_count": 89567,
"percentage": 25.3
},
{
"concept_class_id": "Procedure",
"concept_count": 67432,
"percentage": 19.0
},
{
"concept_class_id": "Body Structure",
"concept_count": 45678,
"percentage": 12.9
}
]
},
"relationship_statistics": {
"total_relationships": 1247832,
"unique_relationship_types": 15,
"relationship_type_distribution": [
{
"relationship_id": "Is a",
"relationship_name": "Is a",
"count": 567834,
"percentage": 45.5
},
{
"relationship_id": "Finding site",
"relationship_name": "Finding site",
"count": 123456,
"percentage": 9.9
},
{
"relationship_id": "Procedure site",
"relationship_name": "Procedure site",
"count": 98765,
"percentage": 7.9
}
]
},
"mapping_statistics": {
"mapped_concepts": 298456,
"unmapped_concepts": 56196,
"mapping_coverage_percentage": 84.1,
"target_vocabularies": [
{
"target_vocabulary_id": "ICD10CM",
"mapping_count": 89456,
"coverage_percentage": 25.2
},
{
"target_vocabulary_id": "ICD10",
"mapping_count": 67234,
"coverage_percentage": 19.0
},
{
"target_vocabulary_id": "LOINC",
"mapping_count": 34567,
"coverage_percentage": 9.7
}
]
},
"language_statistics": {
"supported_languages": ["en-US", "en-GB", "es-ES", "fr-FR", "de-DE"],
"primary_language": "en-US",
"multilingual_concepts": 234567
},
"quality_metrics": {
"completeness_score": 94.2,
"consistency_score": 97.8,
"accuracy_indicators": {
"orphaned_concepts": 234,
"duplicate_codes": 12,
"missing_descriptions": 89
}
},
"trend_data": [
{
"period": "2024-Q1",
"total_concepts": 354652,
"concepts_added": 2847,
"concepts_modified": 1523,
"concepts_deprecated": 89
},
{
"period": "2023-Q4",
"total_concepts": 351805,
"concepts_added": 1967,
"concepts_modified": 2134,
"concepts_deprecated": 156
}
]
},
"meta": {
"request_id": "req_snomed_stats_123",
"timestamp": "2024-01-15T10:30:00Z",
"computation_time_ms": 2847,
"vocab_release": "2025.2"
}
}
```
## Usage Examples
### Basic Statistics
Get overview statistics for a vocabulary:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/statistics" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Comprehensive Analysis
Get complete statistical analysis:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/statistics?include_hierarchy_stats=true&include_domain_breakdown=true&include_mapping_stats=true&include_trend_data=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specific Version Analysis
Get statistics for a specific vocabulary version:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/statistics?vocab_release=2024-01&include_hierarchy_stats=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Multiple Vocabularies Comparison
Compare statistics across vocabularies:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/ICD10CM/statistics?include_domain_breakdown=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Statistical Insights
### Hierarchy Analysis
* **Depth Distribution**: Understanding concept organization depth
* **Root vs Leaf Concepts**: Balance between top-level and specific concepts
* **Average Depth**: Measure of hierarchical complexity
### Domain Coverage
* **Primary Domains**: Major clinical areas covered
* **Domain Distribution**: Balance across medical specialties
* **Coverage Gaps**: Areas with limited concept coverage
### Quality Indicators
* **Completeness Score**: Measure of data completeness
* **Consistency Score**: Internal consistency metrics
* **Mapping Coverage**: Interoperability with other vocabularies
### Trend Analysis
* **Growth Rate**: Vocabulary expansion over time
* **Change Patterns**: Addition vs modification vs deprecation patterns
* **Version Comparison**: Evolution across releases
## Related Endpoints
* [Get Vocabulary Details](/api-reference/vocabularies/get-vocabulary) - Basic vocabulary information
* [Get Vocabulary Metadata](/api-reference/vocabularies/get-vocabulary-metadata) - Comprehensive metadata
* [Get Vocabulary Changelog](/api-reference/vocabularies/get-vocabulary-changelog) - Change history
* [Get Domain Statistics](/api-reference/domains/get-domain-statistics) - Domain-specific statistics
# Get Vocabulary Statistics
Source: https://docs.omophub.com/api-reference/vocabularies/get-vocabulary-stats
GET https://api.omophub.com/v1/vocabularies/:vocabularyId/stats
Retrieve comprehensive statistics for a specific vocabulary including concept counts, domain distribution, and hierarchy metrics.
## Overview
This endpoint provides detailed statistical information about a vocabulary, including the total number of concepts, breakdown by domains and concept classes, and relationship statistics.
## Path Parameters
The unique identifier for the vocabulary (e.g., "SNOMED", "ICD10CM", "LOINC")
## Query Parameters
Include detailed breakdown statistics by domain and concept class
Specific vocabulary release version (defaults to latest)
## Response
Indicates if the request was successful
The vocabulary identifier
Full vocabulary name
Total number of concepts in the vocabulary
Number of standard concepts
Number of classification concepts
Array of domain statistics
Domain identifier
Domain name
Number of concepts in this domain
Array of concept class statistics
Concept class identifier
Concept class name
Number of concepts in this class
Total number of relationships
Release date of the vocabulary version
Unique identifier for the request
Request timestamp
Vocabulary release version used
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats?include_details=true" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies/SNOMED/stats?include_details=true', {
method: 'GET',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
const data = await response.json();
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
response = requests.get(
'https://api.omophub.com/v1/vocabularies/SNOMED/stats',
headers=headers,
params={'include_details': True}
)
data = response.json()
```
```json Response
{
"success": true,
"data": {
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"total_concepts": 354652,
"standard_concepts": 298456,
"classification_concepts": 56196,
"domains": [
{
"domain_id": "Condition",
"domain_name": "Condition",
"concept_count": 112543
},
{
"domain_id": "Procedure",
"domain_name": "Procedure",
"concept_count": 87321
},
{
"domain_id": "Observation",
"domain_name": "Observation",
"concept_count": 65432
}
],
"concept_classes": [
{
"concept_class_id": "Clinical Finding",
"concept_class_name": "Clinical Finding",
"concept_count": 98765
},
{
"concept_class_id": "Procedure",
"concept_class_name": "Procedure",
"concept_count": 76543
}
],
"relationships_count": 1234567,
"release_date": "2024-03-01"
},
"meta": {
"request_id": "req_abc123def456",
"timestamp": "2024-01-15T10:30:00Z",
"vocab_release": "2025.2"
}
}
```
```json Error Response
{
"success": false,
"error": {
"code": "VOCABULARY_NOT_FOUND",
"message": "Vocabulary 'INVALID_VOCAB' not found",
"details": {
"vocabulary_id": "INVALID_VOCAB",
"available_vocabularies": ["SNOMED", "ICD10CM", "LOINC", "RXNORM"]
}
},
"meta": {
"request_id": "req_error123",
"timestamp": "2024-01-15T10:30:00Z"
}
}
```
## Usage Examples
### Basic Statistics
Get basic statistics for SNOMED vocabulary:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Detailed Statistics
Get detailed statistics including domain and class breakdowns:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats?include_details=true" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Specific Release Version
Get statistics for a specific vocabulary release:
```bash
curl -X GET "https://api.omophub.com/v1/vocabularies/SNOMED/stats?vocab_release=2024-01" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Related Endpoints
* [List Vocabularies](/api-reference/vocabularies/list-vocabularies) - Get all available vocabularies
* [Get Vocabulary Details](/api-reference/vocabularies/get-vocabulary) - Get detailed vocabulary information
* [Get Domain Statistics](/api-reference/domains/get-domain-statistics) - Get statistics for a specific domain
# List Vocabularies
Source: https://docs.omophub.com/api-reference/vocabularies/list-vocabularies
GET /vocabularies
Get a list of all available vocabularies with optional statistics
## Overview
This endpoint returns a list of all available medical vocabularies in the system. You can optionally include statistics such as concept counts and filter for active or inactive vocabularies.
## Features
* **Comprehensive Listing**: Get all available vocabularies (SNOMED CT, ICD-10-CM, LOINC, RxNorm, etc.)
* **Optional Statistics**: Include concept counts and metadata
* **Filtering**: Show only active vocabularies or include inactive ones
* **Sorting**: Sort by name, priority, or last updated date
* **Pagination**: Handle large vocabulary lists efficiently
## Query Parameters
Include concept count statistics for each vocabulary
Include inactive or deprecated vocabularies in the results
Sort field. Options: `name`, `priority`, `updated`
Sort order. Options: `asc`, `desc`
Maximum number of vocabularies to return (max: 1000)
Number of vocabularies to skip for pagination
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies",
headers={"Authorization": "Bearer YOUR_API_KEY"}
)
vocabularies = response.json()
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/vocabularies', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const vocabularies = await response.json();
```
```bash cURL (with statistics)
curl -X GET "https://api.omophub.com/v1/vocabularies?includeStats=true&sortBy=name&sortOrder=asc" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with statistics)
import requests
params = {
"includeStats": True,
"sortBy": "name",
"sortOrder": "asc"
}
response = requests.get(
"https://api.omophub.com/v1/vocabularies",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
detailed_vocabularies = response.json()
```
```json
{
"success": true,
"data": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "SNOMED CT",
"vocabulary_version": "US Edition 20240301",
"vocabulary_concept": 45756746,
"description": "Comprehensive clinical healthcare terminology",
"priority": 1,
"is_active": true,
"last_updated": "2024-03-01T00:00:00Z",
"concept_count": 354321,
"domains": ["Condition", "Procedure", "Drug", "Observation"]
},
{
"vocabulary_id": "ICD10CM",
"vocabulary_name": "International Classification of Diseases, Tenth Revision, Clinical Modification",
"vocabulary_reference": "CMS",
"vocabulary_version": "2024",
"vocabulary_concept": 45756681,
"description": "Standard diagnostic coding system for clinical and administrative use",
"priority": 2,
"is_active": true,
"last_updated": "2024-01-01T00:00:00Z",
"concept_count": 72967,
"domains": ["Condition"]
},
{
"vocabulary_id": "LOINC",
"vocabulary_name": "Logical Observation Identifiers Names and Codes",
"vocabulary_reference": "Regenstrief Institute",
"vocabulary_version": "2.76",
"vocabulary_concept": 45756747,
"description": "Universal standard for identifying medical laboratory observations",
"priority": 3,
"is_active": true,
"last_updated": "2024-02-15T00:00:00Z",
"concept_count": 96952,
"domains": ["Measurement", "Observation"]
},
{
"vocabulary_id": "RxNorm",
"vocabulary_name": "RxNorm",
"vocabulary_reference": "National Library of Medicine",
"vocabulary_version": "2024-03-04",
"vocabulary_concept": 45756748,
"description": "Standardized nomenclature for clinical drugs",
"priority": 4,
"is_active": true,
"last_updated": "2024-03-04T00:00:00Z",
"concept_count": 148796,
"domains": ["Drug"]
}
],
"meta": {
"pagination": {
"page": 1,
"page_size": 50,
"total_items": 25,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"request_id": "req_vocab_list_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
## Common Use Cases
### 1. Healthcare Application Setup
Get available vocabularies to populate dropdown menus in clinical applications.
### 2. Data Analysis Planning
Check concept counts and domains to plan analysis scope.
### 3. Vocabulary Version Management
Monitor vocabulary versions and updates for compliance tracking.
### 4. System Integration
Discover available terminologies for mapping and integration projects.
## Best Practices
1. **Cache Results**: Vocabulary lists change infrequently, cache responses for 1+ hours
2. **Use Pagination**: For applications with many vocabularies, implement pagination
3. **Filter Appropriately**: Use `includeInactive=false` unless specifically needed
4. **Monitor Updates**: Check `last_updated` timestamps to detect vocabulary updates
5. **Sort by Priority**: Use priority sorting to display most important vocabularies first
## Related Endpoints
* [Get Vocabulary Details](/api-reference/vocabularies/get-vocabulary) - Get detailed information about a specific vocabulary
* [Search Vocabularies](/api-reference/vocabularies/search-vocabularies) - Search across vocabulary names and descriptions
* [Get Vocabulary Concepts](/api-reference/vocabularies/get-vocabulary-concepts) - Get concepts within a specific vocabulary
# Search Vocabularies
Source: https://docs.omophub.com/api-reference/vocabularies/search-vocabularies
GET /vocabularies/search
Search across vocabulary names, descriptions, and metadata
## Overview
Search for vocabularies by name, description, or metadata. This endpoint provides flexible search capabilities to find relevant vocabularies for your use case.
## Query Parameters
Search query string. Searches across vocabulary names, descriptions, and aliases.
Filter by medical domain. Options: `Condition`, `Procedure`, `Drug`, `Observation`, `Measurement`, `Device`
Include inactive vocabularies in search results
Maximum number of results to return (max: 100)
Number of results to skip for pagination
```bash cURL
curl -X GET "https://api.omophub.com/v1/vocabularies/search?query=SNOMED" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python
import requests
response = requests.get(
"https://api.omophub.com/v1/vocabularies/search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"query": "SNOMED"}
)
vocabularies = response.json()
```
```javascript JavaScript
const params = new URLSearchParams({
query: 'SNOMED'
});
const response = await fetch(`https://api.omophub.com/v1/vocabularies/search?${params}`, {
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
}
});
const vocabularies = await response.json();
```
```bash cURL (with domain filter)
curl -X GET "https://api.omophub.com/v1/vocabularies/search?query=drug&domain=Drug" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```python Python (with domain filter)
import requests
params = {"query": "drug", "domain": "Drug"}
response = requests.get(
"https://api.omophub.com/v1/vocabularies/search",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params=params
)
filtered_vocabularies = response.json()
```
```json
{
"success": true,
"data": [
{
"vocabulary_id": "SNOMED",
"vocabulary_name": "Systematized Nomenclature of Medicine Clinical Terms",
"vocabulary_reference": "SNOMED International",
"vocabulary_version": "2024-07-01",
"vocabulary_concept": 45756746,
"description": "Comprehensive clinical terminology for electronic health records",
"domains": ["Condition", "Procedure", "Observable Entity", "Substance"],
"concept_count": 4567891,
"is_active": true,
"last_updated": "2024-07-01T00:00:00Z",
"match_score": 0.98
},
{
"vocabulary_id": "SNOMEDCT_US",
"vocabulary_name": "SNOMED CT US Extension",
"vocabulary_reference": "National Library of Medicine",
"vocabulary_version": "2024-07-01",
"vocabulary_concept": 45756747,
"description": "US-specific extensions and modifications to SNOMED CT",
"domains": ["Condition", "Procedure", "Observable Entity"],
"concept_count": 12456,
"is_active": true,
"last_updated": "2024-07-01T00:00:00Z",
"match_score": 0.92
}
],
"meta": {
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 2,
"total_pages": 1,
"has_next": false,
"has_previous": false
},
"search_info": {
"query": "SNOMED",
"execution_time": "12ms",
"total_vocabularies_scanned": 156
},
"request_id": "req_vocab_search_123",
"timestamp": "2024-12-22T10:00:00Z",
"vocab_release": "2025.2"
}
}
```
# Overview
Source: https://docs.omophub.com/authentication/overview
Understanding authentication options for API access
## API Key Authentication
API keys are used for all programmatic access to the OMOPHub API. They provide:
* **Long-lived access**: No expiration for continuous integration
* **Granular permissions**: Control access to specific resources
* **Usage tracking**: Monitor API consumption per key
* **Multiple keys**: Create keys for different environments
### API Key Types
Individual developer keys for personal projects
* Tied to your user account
* Inherit your account permissions
* Usage counts toward personal quota
* Can be revoked anytime
Shared keys for team collaboration (Coming Soon)
* Associated with team/organization
* Shared usage limits
* Role-based access control
* Audit logging per team member
## Authentication Flow
### API Authentication Flow
```mermaid
sequenceDiagram
participant Client
participant API
participant Auth Service
Client->>API: Request with API key
API->>Auth Service: Validate API key
Auth Service->>API: Return permissions
API->>Client: Process request
```
## Security Best Practices
API keys provide full access to your account's resources. Treat them like passwords and never expose them in client-side code or public repositories.
### API Key Security
1. **Environment Variables**: Store keys in environment variables
```bash
export OMOPHUB_API_KEY="oh_xxxxxxxxx"
```
2. **Key Rotation**: Regularly rotate keys (every 90 days recommended)
3. **Minimal Permissions**: Create keys with only necessary permissions
4. **Separate Environments**: Use different keys for dev/staging/production
5. **Monitor Usage**: Check API key usage regularly for anomalies
## Authentication Headers
### API Key Authentication
Include your API key in the Authorization header:
```bash cURL
curl -H "Authorization: Bearer oh_xxxxxxxxx" \
https://api.omophub.com/v1/vocabularies
```
```python Python
client = OMOPHubClient(api_key="oh_xxxxxxxxx")
```
```javascript JavaScript
const client = new OMOPHubClient({
apiKey: 'oh_xxxxxxxxx'
});
```
```r R
client <- omophub_client(api_key = "oh_xxxxxxxxx")
```
## Troubleshooting
* Check the key hasn't been revoked
* Verify you're using the correct environment
* Confirm proper Authorization header format
* Check X-RateLimit headers in responses
* Implement exponential backoff
* Consider upgrading your plan
* Use caching to reduce requests
## Next Steps
Create keys for API access
# AI & LLM Integration
Source: https://docs.omophub.com/guides/integration/ai-llm-integration
Build intelligent healthcare applications by combining Large Language Models with OMOPHub medical vocabularies for accurate, grounded AI responses
## Overview
Large Language Models (LLMs) have revolutionized how we process and understand natural language, but in healthcare, accuracy and reliability are paramount. This guide demonstrates how to integrate cutting-edge AI technologies like OpenAI's GPT models and AWS Bedrock with the OMOPHub API to create intelligent healthcare applications that are both innovative and clinically sound.
**Use Case**: Build AI-powered healthcare applications that combine the natural language understanding of LLMs with the accuracy and standardization of medical vocabularies, ensuring both intelligence and clinical safety.
## Business Problem
Healthcare AI faces unique challenges that generic LLMs struggle to address:
* **Medical Hallucination**: LLMs can generate plausible but incorrect medical information
* **Terminology Inconsistency**: Clinical terms vary widely across systems and specialties
* **Compliance Requirements**: Healthcare applications must meet strict regulatory standards
* **Evidence-Based Medicine**: Clinical decisions require validated, authoritative information
* **Interoperability**: Medical data needs standardized vocabularies for system integration
* **Patient Safety**: Incorrect AI-generated medical advice can have serious consequences
## Solution Architecture
The key to successful healthcare AI is combining the natural language capabilities of LLMs with the structured, validated data from medical vocabularies. This creates a "grounded AI" approach where LLM outputs are validated and enhanced with authoritative medical information.
```mermaid
graph TD
A[Clinical Text/Query] --> B[LLM Processing]
B --> C[Entity Extraction]
C --> D[OMOPHub API]
D --> E[Vocabulary Validation]
D --> F[Concept Mapping]
D --> G[Relationship Analysis]
E --> H[Validated Response]
F --> H
G --> H
H --> I[Clinical Decision Support]
H --> J[Structured Output]
H --> K[Evidence Citations]
style B fill:#333333
style D fill:#333333
style H fill:#333333
```
## Implementation Examples
### Clinical Note Analysis with OpenAI
Transform unstructured clinical notes into structured, standardized medical data using GPT-4 combined with OMOPHub vocabulary validation.
```python Python
import openai
import asyncio
import json
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from omophub import OMOPHubClient
@dataclass
class MedicalEntity:
text: str
category: str
confidence: float
concept_id: Optional[int] = None
concept_name: Optional[str] = None
vocabulary_id: Optional[str] = None
standard_concept: Optional[str] = None
class ClinicalNoteAnalyzer:
def __init__(self, openai_api_key: str, omophub_api_key: str):
self.openai_client = openai.AsyncOpenAI(api_key=openai_api_key)
self.omophub = OMOPHubClient(api_key=omophub_api_key)
async def extract_medical_entities(self, clinical_note: str) -> List[MedicalEntity]:
"""Extract medical entities using GPT-4 with structured output"""
system_prompt = """You are a medical AI assistant specializing in clinical note analysis.
Extract medical entities from clinical notes and categorize them.
Categories:
- condition: Diseases, disorders, symptoms
- medication: Drugs, treatments, prescriptions
- procedure: Medical procedures, surgeries, interventions
- observation: Lab results, vital signs, measurements
- anatomy: Body parts, organs, anatomical structures
Return a single JSON object with an "entities" field containing an array of objects.
Each entity object should have: text, category, and confidence (0-1).
Return only valid JSON, no surrounding text.
Expected format: {"entities": [{"text": "diabetes", "category": "condition", "confidence": 0.95}, ...]}"""
response = await self.openai_client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Extract medical entities from this clinical note:\n\n{clinical_note}"}
],
response_format={"type": "json_object"},
temperature=0.1
)
try:
entities_data = json.loads(response.choices[0].message.content)
entities = []
for entity_data in entities_data.get("entities", []):
entity = MedicalEntity(
text=entity_data["text"],
category=entity_data["category"],
confidence=entity_data["confidence"]
)
entities.append(entity)
return entities
except json.JSONDecodeError:
return []
async def validate_and_standardize_entities(self, entities: List[MedicalEntity]) -> List[MedicalEntity]:
"""Validate entities against OMOPHub vocabularies"""
validated_entities = []
for entity in entities:
try:
# Search for the entity in appropriate vocabularies
vocabulary_mapping = {
"condition": ["SNOMED", "ICD10CM"],
"medication": ["RXNORM", "NDC"],
"procedure": ["SNOMED"],
"observation": ["LOINC", "SNOMED"],
"anatomy": ["SNOMED"]
}
vocabularies = vocabulary_mapping.get(entity.category, ["SNOMED"])
# Search for matching concepts
search_result = await self.omophub.search_concepts(
query=entity.text,
vocabulary_ids=vocabularies,
standard_concept="S",
page_size=5
)
if search_result["data"]:
# Take the best match
best_match = search_result["data"][0]
entity.concept_id = best_match["concept_id"]
entity.concept_name = best_match["concept_name"]
entity.vocabulary_id = best_match["vocabulary_id"]
entity.standard_concept = best_match["standard_concept"]
validated_entities.append(entity)
except Exception as e:
# Keep original entity if validation fails
validated_entities.append(entity)
return validated_entities
async def generate_structured_summary(self, clinical_note: str) -> Dict[str, Any]:
"""Generate comprehensive structured summary"""
# Extract and validate entities
entities = await self.extract_medical_entities(clinical_note)
validated_entities = await self.validate_and_standardize_entities(entities)
# Generate clinical summary using GPT-4
summary_prompt = f"""Based on this clinical note, provide a structured summary:
Clinical Note: {clinical_note}
Provide:
1. Primary diagnosis/condition
2. Secondary diagnoses
3. Current medications
4. Planned procedures/treatments
5. Key observations/findings
6. Clinical assessment
Format as JSON object."""
summary_response = await self.openai_client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=[{"role": "user", "content": summary_prompt}],
response_format={"type": "json_object"},
temperature=0.2
)
try:
summary = json.loads(summary_response.choices[0].message.content)
except:
summary = {"error": "Failed to parse summary"}
return {
"clinical_summary": summary,
"extracted_entities": [
{
"original_text": e.text,
"category": e.category,
"confidence": e.confidence,
"standardized_concept": {
"concept_id": e.concept_id,
"concept_name": e.concept_name,
"vocabulary": e.vocabulary_id
} if e.concept_id else None
}
for e in validated_entities
],
"coding_suggestions": await self._generate_coding_suggestions(validated_entities)
}
async def _generate_coding_suggestions(self, entities: List[MedicalEntity]) -> List[Dict[str, Any]]:
"""Generate ICD-10 and SNOMED coding suggestions"""
suggestions = []
for entity in entities:
if entity.concept_id and entity.category in ["condition", "procedure"]:
try:
# Get mappings to other vocabularies
mappings = await self.omophub.get_concept_mappings(
concept_id=entity.concept_id,
target_vocabularies=["ICD10CM", "SNOMED"]
)
if mappings["data"]:
suggestions.append({
"original_entity": entity.text,
"primary_concept": {
"concept_id": entity.concept_id,
"concept_name": entity.concept_name,
"vocabulary": entity.vocabulary_id
},
"coding_options": [
{
"code": mapping["target_concept_code"],
"name": mapping["target_concept_name"],
"vocabulary": mapping["target_vocabulary_id"],
"relationship": mapping["relationship_id"]
}
for mapping in mappings["data"]
]
})
except Exception:
continue
return suggestions
# Usage Example
async def analyze_clinical_note():
analyzer = ClinicalNoteAnalyzer(
openai_api_key="your-openai-key",
omophub_api_key="your-omophub-key"
)
clinical_note = """
Patient presents with acute onset chest pain radiating to left arm,
associated with shortness of breath and diaphoresis. EKG shows ST elevation
in leads II, III, aVF suggestive of inferior STEMI. Troponin I elevated at 15.2 ng/mL.
Patient has history of hypertension, diabetes mellitus type 2, and smoking.
Plan: Emergency cardiac catheterization, dual antiplatelet therapy with aspirin
and clopidogrel, atorvastatin, metoprolol.
"""
result = await analyzer.generate_structured_summary(clinical_note)
print("Clinical Summary:")
print(json.dumps(result["clinical_summary"], indent=2))
print("\nExtracted Entities:")
for entity in result["extracted_entities"]:
print(f"- {entity['original_text']} ({entity['category']})")
if entity["standardized_concept"]:
concept = entity["standardized_concept"]
print(f" → {concept['concept_name']} ({concept['vocabulary']}:{concept['concept_id']})")
print("\nCoding Suggestions:")
for suggestion in result["coding_suggestions"]:
print(f"- {suggestion['original_entity']}")
for option in suggestion["coding_options"]:
print(f" → {option['vocabulary']}: {option['code']} - {option['name']}")
# Run the example
if __name__ == "__main__":
asyncio.run(analyze_clinical_note())
```
```javascript JavaScript
import OpenAI from 'openai';
import { OMOPHubClient } from 'omophub';
class ClinicalNoteAnalyzer {
constructor(openaiApiKey, omophubApiKey) {
this.openai = new OpenAI({ apiKey: openaiApiKey });
this.omophub = new OMOPHubClient({ apiKey: omophubApiKey });
}
async extractMedicalEntities(clinicalNote) {
const systemPrompt = `You are a medical AI assistant specializing in clinical note analysis.
Extract medical entities from clinical notes and categorize them.
Categories:
- condition: Diseases, disorders, symptoms
- medication: Drugs, treatments, prescriptions
- procedure: Medical procedures, surgeries, interventions
- observation: Lab results, vital signs, measurements
- anatomy: Body parts, organs, anatomical structures
Return JSON object with "entities" array containing: text, category, confidence (0-1)`;
try {
const response = await this.openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: `Extract medical entities from this clinical note:\n\n${clinicalNote}` }
],
response_format: { type: 'json_object' },
temperature: 0.1
});
const entitiesData = JSON.parse(response.choices[0].message.content);
return entitiesData.entities || [];
} catch (error) {
console.error('Entity extraction failed:', error);
return [];
}
}
async validateAndStandardizeEntities(entities) {
const vocabularyMapping = {
condition: ['SNOMED', 'ICD10CM'],
medication: ['RXNORM', 'NDC'],
procedure: ['SNOMED'],
observation: ['LOINC', 'SNOMED'],
anatomy: ['SNOMED']
};
const validatedEntities = [];
for (const entity of entities) {
try {
const vocabularies = vocabularyMapping[entity.category] || ['SNOMED'];
const searchResult = await this.omophub.searchConcepts({
query: entity.text,
vocabulary_ids: vocabularies.join(','),
standard_concept: 'S',
page_size: 5
});
if (searchResult.data && searchResult.data.length > 0) {
const bestMatch = searchResult.data[0];
entity.concept_id = bestMatch.concept_id;
entity.concept_name = bestMatch.concept_name;
entity.vocabulary_id = bestMatch.vocabulary_id;
entity.standard_concept = bestMatch.standard_concept;
}
validatedEntities.push(entity);
} catch (error) {
console.error(`Validation failed for entity: ${entity.text}`, error);
validatedEntities.push(entity);
}
}
return validatedEntities;
}
async generateStructuredSummary(clinicalNote) {
// Extract and validate entities
const entities = await this.extractMedicalEntities(clinicalNote);
const validatedEntities = await this.validateAndStandardizeEntities(entities);
// Generate clinical summary
const summaryPrompt = `Based on this clinical note, provide a structured summary:
Clinical Note: ${clinicalNote}
Provide:
1. Primary diagnosis/condition
2. Secondary diagnoses
3. Current medications
4. Planned procedures/treatments
5. Key observations/findings
6. Clinical assessment
Format as JSON object.`;
try {
const summaryResponse = await this.openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [{ role: 'user', content: summaryPrompt }],
response_format: { type: 'json_object' },
temperature: 0.2
});
const summary = JSON.parse(summaryResponse.choices[0].message.content);
return {
clinical_summary: summary,
extracted_entities: validatedEntities.map(e => ({
original_text: e.text,
category: e.category,
confidence: e.confidence,
standardized_concept: e.concept_id ? {
concept_id: e.concept_id,
concept_name: e.concept_name,
vocabulary: e.vocabulary_id
} : null
})),
coding_suggestions: await this.generateCodingSuggestions(validatedEntities)
};
} catch (error) {
console.error('Summary generation failed:', error);
return { error: 'Failed to generate summary' };
}
}
async generateCodingSuggestions(entities) {
const suggestions = [];
for (const entity of entities) {
if (entity.concept_id && ['condition', 'procedure'].includes(entity.category)) {
try {
const mappings = await this.omophub.getConceptMappings({
concept_id: entity.concept_id,
target_vocabularies: 'ICD10CM,SNOMED'
});
if (mappings.data && mappings.data.length > 0) {
suggestions.push({
original_entity: entity.text,
primary_concept: {
concept_id: entity.concept_id,
concept_name: entity.concept_name,
vocabulary: entity.vocabulary_id
},
coding_options: mappings.data.map(mapping => ({
code: mapping.target_concept_code,
name: mapping.target_concept_name,
vocabulary: mapping.target_vocabulary_id,
relationship: mapping.relationship_id
}))
});
}
} catch (error) {
console.error(`Mapping failed for entity: ${entity.text}`, error);
}
}
}
return suggestions;
}
}
// Usage Example
async function analyzeClinicalNote() {
const analyzer = new ClinicalNoteAnalyzer(
'your-openai-key',
'your-omophub-key'
);
const clinicalNote = `
Patient presents with acute onset chest pain radiating to left arm,
associated with shortness of breath and diaphoresis. EKG shows ST elevation
in leads II, III, aVF suggestive of inferior STEMI. Troponin I elevated at 15.2 ng/mL.
Patient has history of hypertension, diabetes mellitus type 2, and smoking.
Plan: Emergency cardiac catheterization, dual antiplatelet therapy with aspirin
and clopidogrel, atorvastatin, metoprolol.
`;
try {
const result = await analyzer.generateStructuredSummary(clinicalNote);
console.log('Clinical Summary:');
console.log(JSON.stringify(result.clinical_summary, null, 2));
console.log('\nExtracted Entities:');
result.extracted_entities.forEach(entity => {
console.log(`- ${entity.original_text} (${entity.category})`);
if (entity.standardized_concept) {
const concept = entity.standardized_concept;
console.log(` → ${concept.concept_name} (${concept.vocabulary}:${concept.concept_id})`);
}
});
console.log('\nCoding Suggestions:');
result.coding_suggestions.forEach(suggestion => {
console.log(`- ${suggestion.original_entity}`);
suggestion.coding_options.forEach(option => {
console.log(` → ${option.vocabulary}: ${option.code} - ${option.name}`);
});
});
} catch (error) {
console.error('Analysis failed:', error);
}
}
analyzeClinicalNote();
```
### Medical Q\&A System with AWS Bedrock
Create an intelligent medical question-answering system that combines Claude's reasoning capabilities with OMOPHub vocabulary validation to provide accurate, evidence-based responses.
```python Python
import boto3
import json
import asyncio
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from omophub import OMOPHubClient
@dataclass
class MedicalCitation:
concept_id: int
concept_name: str
vocabulary: str
definition: Optional[str] = None
source_url: Optional[str] = None
@dataclass
class MedicalResponse:
answer: str
confidence_score: float
citations: List[MedicalCitation]
safety_warnings: List[str]
follow_up_questions: List[str]
class MedicalQASystem:
def __init__(self, aws_region: str, omophub_api_key: str):
self.bedrock = boto3.client(
service_name='bedrock-runtime',
region_name=aws_region
)
self.omophub = OMOPHubClient(api_key=omophub_api_key)
async def answer_medical_question(self, question: str, context: Optional[str] = None) -> MedicalResponse:
"""Answer medical questions with vocabulary validation"""
# Step 1: Extract medical concepts from the question
medical_concepts = await self._extract_medical_concepts(question)
# Step 2: Validate concepts with OMOPHub
validated_concepts = await self._validate_medical_concepts(medical_concepts)
# Step 3: Get related medical information
context_info = await self._get_medical_context(validated_concepts)
# Step 4: Generate response using Claude
response = await self._generate_claude_response(
question,
context or "",
context_info,
validated_concepts
)
# Step 5: Post-process for safety
safety_checked = await self._safety_check_response(response, question)
return safety_checked
async def _extract_medical_concepts(self, text: str) -> List[str]:
"""Extract medical concepts using Claude"""
prompt = f"""
Extract medical concepts, terms, and entities from the following question.
{text}
- Identify medical conditions, symptoms, treatments, procedures, medications, anatomy
- Return only the specific medical terms, not general words
- Focus on standardized medical terminology
- Return as JSON array of strings
"""
body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 1000,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.1
}
response = self.bedrock.invoke_model(
modelId='anthropic.claude-3-sonnet-20240229-v1:0',
body=json.dumps(body),
contentType='application/json',
accept='application/json'
)
response_body = json.loads(response['body'].read())
try:
# Parse the JSON response to extract concepts
content = response_body['content'][0]['text']
# Remove any markdown formatting and extract JSON
start = content.find('[')
end = content.rfind(']') + 1
concepts_json = content[start:end] if start != -1 and end != 0 else '[]'
concepts = json.loads(concepts_json)
return concepts
except:
return []
async def _validate_medical_concepts(self, concepts: List[str]) -> List[MedicalCitation]:
"""Validate and enrich concepts using OMOPHub"""
validated = []
for concept in concepts:
try:
# Search for the concept in multiple vocabularies
search_result = await self.omophub.search_concepts(
query=concept,
vocabulary_ids=["SNOMED", "ICD10CM", "LOINC", "RXNORM"],
standard_concept="S",
page_size=3
)
if search_result["data"]:
best_match = search_result["data"][0]
# Get additional details if available
concept_details = await self.omophub.get_concept(
concept_id=best_match["concept_id"]
)
citation = MedicalCitation(
concept_id=best_match["concept_id"],
concept_name=best_match["concept_name"],
vocabulary=best_match["vocabulary_id"],
definition=concept_details.get("definition"),
source_url=f"https://api.omophub.com/v1/concepts/{best_match['concept_id']}"
)
validated.append(citation)
except Exception as e:
# Log but continue with other concepts
print(f"Failed to validate concept '{concept}': {e}")
continue
return validated
async def _get_medical_context(self, concepts: List[MedicalCitation]) -> Dict[str, Any]:
"""Get additional medical context for validated concepts"""
context = {
"concept_definitions": {},
"relationships": {},
"hierarchies": {}
}
for concept in concepts:
try:
# Get concept relationships
relationships = await self.omophub.get_concept_relationships(
concept_id=concept.concept_id,
relationship_types=["Is a", "Has finding site", "Has causative agent"]
)
if relationships["data"]:
context["relationships"][concept.concept_name] = [
{
"type": rel["relationship_id"],
"target": rel["target_concept_name"]
}
for rel in relationships["data"][:5] # Limit to top 5
]
# Get hierarchical information for conditions
if concept.vocabulary == "SNOMED":
ancestors = await self.omophub.get_concept_ancestors(
concept_id=concept.concept_id,
max_levels=3
)
if ancestors["data"]:
context["hierarchies"][concept.concept_name] = [
ancestor["ancestor_concept_name"]
for ancestor in ancestors["data"][:3]
]
except Exception:
continue
return context
async def _generate_claude_response(
self,
question: str,
user_context: str,
medical_context: Dict[str, Any],
citations: List[MedicalCitation]
) -> MedicalResponse:
"""Generate response using Claude with medical context"""
citations_text = "\n".join([
f"- {c.concept_name} ({c.vocabulary}): {c.definition or 'No definition available'}"
for c in citations
])
relationships_text = "\n".join([
f"{concept}: {', '.join([r['type'] + ' ' + r['target'] for r in rels])}"
for concept, rels in medical_context.get("relationships", {}).items()
])
prompt = f"""
You are a medical AI assistant providing evidence-based healthcare information.
User Context: {user_context}
Validated Medical Concepts:
{citations_text}
Medical Relationships:
{relationships_text}
{question}
1. Provide a comprehensive, accurate answer based on the validated medical concepts
2. Include relevant medical relationships and hierarchies in your explanation
3. Cite specific concepts using their standardized names
4. Include confidence assessment (high/medium/low)
5. Add safety warnings if discussing treatments, medications, or diagnoses
6. Suggest 2-3 relevant follow-up questions
7. Format response as JSON with fields: answer, confidence_level, safety_warnings (array), follow_up_questions (array)
- Always recommend consulting healthcare professionals for medical advice
- Do not provide specific dosage recommendations
- Include disclaimers for diagnostic or treatment information
- Emphasize when information is for educational purposes only
"""
body = {
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 4000,
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.2
}
response = self.bedrock.invoke_model(
modelId='anthropic.claude-3-sonnet-20240229-v1:0',
body=json.dumps(body),
contentType='application/json',
accept='application/json'
)
response_body = json.loads(response['body'].read())
content = response_body['content'][0]['text']
try:
# Extract JSON from response
start = content.find('{')
end = content.rfind('}') + 1
json_content = content[start:end] if start != -1 and end != 0 else '{}'
response_data = json.loads(json_content)
confidence_map = {"high": 0.9, "medium": 0.7, "low": 0.5}
confidence_score = confidence_map.get(
response_data.get("confidence_level", "medium").lower(),
0.7
)
return MedicalResponse(
answer=response_data.get("answer", "Unable to generate response"),
confidence_score=confidence_score,
citations=citations,
safety_warnings=response_data.get("safety_warnings", []),
follow_up_questions=response_data.get("follow_up_questions", [])
)
except Exception as e:
# Fallback response
return MedicalResponse(
answer="I encountered an error processing your medical question. Please consult a healthcare professional for medical advice.",
confidence_score=0.0,
citations=citations,
safety_warnings=["Unable to validate response accuracy. Consult healthcare professional."],
follow_up_questions=[]
)
async def _safety_check_response(self, response: MedicalResponse, question: str) -> MedicalResponse:
"""Additional safety checks for medical responses"""
# Add standard medical disclaimer if not present
if "consult" not in response.answer.lower() and "healthcare professional" not in response.answer.lower():
response.safety_warnings.append(
"This information is for educational purposes only. Always consult with a qualified healthcare professional for medical advice, diagnosis, or treatment."
)
# Check for potentially dangerous advice
dangerous_keywords = ["self-medicate", "stop taking", "don't need doctor", "instead of seeing doctor"]
if any(keyword in response.answer.lower() for keyword in dangerous_keywords):
response.safety_warnings.append(
"WARNING: Never self-diagnose or change medical treatments without professional supervision."
)
response.confidence_score *= 0.5 # Reduce confidence
return response
# Usage Example
async def demo_medical_qa():
qa_system = MedicalQASystem(
aws_region="us-east-1",
omophub_api_key="your-omophub-key"
)
questions = [
"What is the relationship between hypertension and cardiovascular disease?",
"What are the symptoms of Type 2 diabetes?",
"How do ACE inhibitors work for treating high blood pressure?"
]
for question in questions:
print(f"\nQ: {question}")
print("-" * 80)
response = await qa_system.answer_medical_question(question)
print(f"Answer (Confidence: {response.confidence_score:.1%}):")
print(response.answer)
if response.citations:
print("\nMedical Citations:")
for citation in response.citations:
print(f"- {citation.concept_name} ({citation.vocabulary})")
if response.safety_warnings:
print("\nSafety Warnings:")
for warning in response.safety_warnings:
print(f"⚠️ {warning}")
if response.follow_up_questions:
print("\nSuggested Follow-up Questions:")
for i, q in enumerate(response.follow_up_questions, 1):
print(f"{i}. {q}")
# Run the demo
if __name__ == "__main__":
asyncio.run(demo_medical_qa())
```
```javascript JavaScript
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';
import { OMOPHubClient } from 'omophub';
class MedicalQASystem {
constructor(awsRegion, omophubApiKey) {
this.bedrock = new BedrockRuntimeClient({ region: awsRegion });
this.omophub = new OMOPHubClient({ apiKey: omophubApiKey });
}
async answerMedicalQuestion(question, context = '') {
try {
// Extract medical concepts from the question
const medicalConcepts = await this.extractMedicalConcepts(question);
// Validate concepts with OMOPHub
const validatedConcepts = await this.validateMedicalConcepts(medicalConcepts);
// Get medical context
const contextInfo = await this.getMedicalContext(validatedConcepts);
// Generate response using Claude
const response = await this.generateClaudeResponse(
question,
context,
contextInfo,
validatedConcepts
);
// Apply safety checks
return this.safetyCheckResponse(response, question);
} catch (error) {
console.error('Medical QA failed:', error);
return {
answer: 'I apologize, but I encountered an error processing your medical question. Please consult a healthcare professional for medical advice.',
confidence_score: 0.0,
citations: [],
safety_warnings: ['System error occurred. Consult healthcare professional.'],
follow_up_questions: []
};
}
}
async extractMedicalConcepts(text) {
const prompt = `
Extract medical concepts, terms, and entities from the following question.
${text}
- Identify medical conditions, symptoms, treatments, procedures, medications, anatomy
- Return only the specific medical terms, not general words
- Focus on standardized medical terminology
- Return as JSON array of strings
`;
const body = {
anthropic_version: 'bedrock-2023-05-31',
max_tokens: 1000,
messages: [{ role: 'user', content: prompt }],
temperature: 0.1
};
try {
const command = new InvokeModelCommand({
modelId: 'anthropic.claude-3-sonnet-20240229-v1:0',
body: JSON.stringify(body),
contentType: 'application/json',
accept: 'application/json'
});
const response = await this.bedrock.send(command);
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
const content = responseBody.content[0].text;
// Extract JSON array from response
const start = content.indexOf('[');
const end = content.lastIndexOf(']') + 1;
const conceptsJson = start !== -1 && end !== 0 ? content.slice(start, end) : '[]';
return JSON.parse(conceptsJson);
} catch (error) {
console.error('Concept extraction failed:', error);
return [];
}
}
async validateMedicalConcepts(concepts) {
const validated = [];
for (const concept of concepts) {
try {
const searchResult = await this.omophub.searchConcepts({
query: concept,
vocabulary_ids: 'SNOMED,ICD10CM,LOINC,RXNORM',
standard_concept: 'S',
page_size: 3
});
if (searchResult.data && searchResult.data.length > 0) {
const bestMatch = searchResult.data[0];
// Get additional concept details
const conceptDetails = await this.omophub.getConcept({
concept_id: bestMatch.concept_id
});
validated.push({
concept_id: bestMatch.concept_id,
concept_name: bestMatch.concept_name,
vocabulary: bestMatch.vocabulary_id,
definition: conceptDetails.definition,
source_url: `https://api.omophub.com/v1/concepts/${bestMatch.concept_id}`
});
}
} catch (error) {
console.error(`Failed to validate concept '${concept}':`, error);
}
}
return validated;
}
async getMedicalContext(concepts) {
const context = {
concept_definitions: {},
relationships: {},
hierarchies: {}
};
for (const concept of concepts) {
try {
// Get concept relationships
const relationships = await this.omophub.getConceptRelationships({
concept_id: concept.concept_id,
relationship_types: 'Is a,Has finding site,Has causative agent'
});
if (relationships.data && relationships.data.length > 0) {
context.relationships[concept.concept_name] = relationships.data
.slice(0, 5)
.map(rel => ({
type: rel.relationship_id,
target: rel.target_concept_name
}));
}
// Get hierarchical information for SNOMED concepts
if (concept.vocabulary === 'SNOMED') {
const ancestors = await this.omophub.getConceptAncestors({
concept_id: concept.concept_id,
max_levels: 3
});
if (ancestors.data && ancestors.data.length > 0) {
context.hierarchies[concept.concept_name] = ancestors.data
.slice(0, 3)
.map(ancestor => ancestor.ancestor_concept_name);
}
}
} catch (error) {
console.error(`Failed to get context for concept ${concept.concept_name}:`, error);
}
}
return context;
}
async generateClaudeResponse(question, userContext, medicalContext, citations) {
const citationsText = citations
.map(c => `- ${c.concept_name} (${c.vocabulary}): ${c.definition || 'No definition available'}`)
.join('\n');
const relationshipsText = Object.entries(medicalContext.relationships || {})
.map(([concept, rels]) =>
`${concept}: ${rels.map(r => `${r.type} ${r.target}`).join(', ')}`
)
.join('\n');
const prompt = `
You are a medical AI assistant providing evidence-based healthcare information.
User Context: ${userContext}
Validated Medical Concepts:
${citationsText}
Medical Relationships:
${relationshipsText}
${question}
1. Provide a comprehensive, accurate answer based on the validated medical concepts
2. Include relevant medical relationships and hierarchies in your explanation
3. Cite specific concepts using their standardized names
4. Include confidence assessment (high/medium/low)
5. Add safety warnings if discussing treatments, medications, or diagnoses
6. Suggest 2-3 relevant follow-up questions
7. Format response as JSON with fields: answer, confidence_level, safety_warnings (array), follow_up_questions (array)
- Always recommend consulting healthcare professionals for medical advice
- Do not provide specific dosage recommendations
- Include disclaimers for diagnostic or treatment information
- Emphasize when information is for educational purposes only
`;
const body = {
anthropic_version: 'bedrock-2023-05-31',
max_tokens: 4000,
messages: [{ role: 'user', content: prompt }],
temperature: 0.2
};
try {
const command = new InvokeModelCommand({
modelId: 'anthropic.claude-3-sonnet-20240229-v1:0',
body: JSON.stringify(body),
contentType: 'application/json',
accept: 'application/json'
});
const response = await this.bedrock.send(command);
const responseBody = JSON.parse(new TextDecoder().decode(response.body));
const content = responseBody.content[0].text;
// Extract JSON from response
const start = content.indexOf('{');
const end = content.lastIndexOf('}') + 1;
const jsonContent = start !== -1 && end !== 0 ? content.slice(start, end) : '{}';
const responseData = JSON.parse(jsonContent);
const confidenceMap = { high: 0.9, medium: 0.7, low: 0.5 };
const confidenceScore = confidenceMap[responseData.confidence_level?.toLowerCase()] || 0.7;
return {
answer: responseData.answer || 'Unable to generate response',
confidence_score: confidenceScore,
citations: citations,
safety_warnings: responseData.safety_warnings || [],
follow_up_questions: responseData.follow_up_questions || []
};
} catch (error) {
console.error('Claude response generation failed:', error);
return {
answer: 'I encountered an error processing your medical question. Please consult a healthcare professional for medical advice.',
confidence_score: 0.0,
citations: citations,
safety_warnings: ['Unable to validate response accuracy. Consult healthcare professional.'],
follow_up_questions: []
};
}
}
safetyCheckResponse(response, question) {
// Add standard medical disclaimer if not present
if (!response.answer.toLowerCase().includes('consult') &&
!response.answer.toLowerCase().includes('healthcare professional')) {
response.safety_warnings.push(
'This information is for educational purposes only. Always consult with a qualified healthcare professional for medical advice, diagnosis, or treatment.'
);
}
// Check for potentially dangerous advice
const dangerousKeywords = ['self-medicate', 'stop taking', "don't need doctor", 'instead of seeing doctor'];
if (dangerousKeywords.some(keyword => response.answer.toLowerCase().includes(keyword))) {
response.safety_warnings.push(
'WARNING: Never self-diagnose or change medical treatments without professional supervision.'
);
response.confidence_score *= 0.5; // Reduce confidence
}
return response;
}
}
// Usage Example
async function demoMedicalQA() {
const qaSystem = new MedicalQASystem('us-east-1', 'your-omophub-key');
const questions = [
'What is the relationship between hypertension and cardiovascular disease?',
'What are the symptoms of Type 2 diabetes?',
'How do ACE inhibitors work for treating high blood pressure?'
];
for (const question of questions) {
console.log(`\nQ: ${question}`);
console.log('-'.repeat(80));
try {
const response = await qaSystem.answerMedicalQuestion(question);
console.log(`Answer (Confidence: ${(response.confidence_score * 100).toFixed(0)}%):`);
console.log(response.answer);
if (response.citations.length > 0) {
console.log('\nMedical Citations:');
response.citations.forEach(citation => {
console.log(`- ${citation.concept_name} (${citation.vocabulary})`);
});
}
if (response.safety_warnings.length > 0) {
console.log('\nSafety Warnings:');
response.safety_warnings.forEach(warning => {
console.log(`⚠️ ${warning}`);
});
}
if (response.follow_up_questions.length > 0) {
console.log('\nSuggested Follow-up Questions:');
response.follow_up_questions.forEach((q, i) => {
console.log(`${i + 1}. ${q}`);
});
}
} catch (error) {
console.error('Failed to process question:', error);
}
}
}
demoMedicalQA();
```
## Best Practices for Healthcare AI
### Prompt Engineering for Medical Context
Medical AI applications require specialized prompt engineering techniques:
1. **Structured Medical Prompts**:
* Include role definitions: "You are a medical AI assistant..."
* Specify medical context: patient type, clinical setting, specialty
* Define output format: JSON, structured text, specific fields
2. **Evidence-Based Instructions**:
* Request citations for medical claims
* Ask for confidence levels on responses
* Include safety warnings and disclaimers
* Validate against authoritative vocabularies
3. **Safety-First Approach**:
* Always include professional consultation disclaimers
* Flag potential harmful or dangerous advice
* Implement content filtering for inappropriate responses
* Monitor for medical misinformation
### Data Privacy and Compliance
Healthcare AI must handle sensitive data appropriately:
```python Python
import hashlib
import re
from typing import Dict, Any
class HealthcareDataSanitizer:
def __init__(self):
# PHI patterns to detect and anonymize
self.phi_patterns = {
'mrn': r'\b\d{7,10}\b', # Medical record numbers
'ssn': r'\b\d{3}-\d{2}-\d{4}\b',
'phone': r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b',
'email': r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b',
'date': r'\b\d{1,2}/\d{1,2}/\d{4}\b',
'name': r'\b[A-Z][a-z]+ [A-Z][a-z]+\b' # Simple name pattern
}
def sanitize_clinical_text(self, text: str, preserve_medical_context: bool = True) -> Dict[str, Any]:
"""Sanitize PHI while preserving medical context"""
sanitized_text = text
detected_phi = []
for phi_type, pattern in self.phi_patterns.items():
matches = re.findall(pattern, text)
for match in matches:
# Create consistent anonymized replacement
anonymized = self._anonymize_value(match, phi_type)
sanitized_text = sanitized_text.replace(match, anonymized)
detected_phi.append({
'type': phi_type,
'original_length': len(match),
'anonymized': anonymized
})
# Preserve medical context by keeping medical terms
if preserve_medical_context:
sanitized_text = self._preserve_medical_terms(sanitized_text)
return {
'sanitized_text': sanitized_text,
'phi_detected': detected_phi,
'phi_count': len(detected_phi),
'is_safe_for_ai': len(detected_phi) == 0
}
def _anonymize_value(self, value: str, phi_type: str) -> str:
"""Create consistent anonymized replacement"""
hash_value = hashlib.md5(value.encode()).hexdigest()[:8]
return f"[{phi_type.upper()}_{hash_value}]"
def _preserve_medical_terms(self, text: str) -> str:
"""Preserve important medical terminology"""
# Keep medical abbreviations, units, ranges
medical_preserves = [
r'\b\d+\s*(mg|ml|cc|units?)\b', # Dosages
r'\b\d+[-/]\d+\s*mmHg\b', # Blood pressure
r'\b\d+\.\d+\s*(ng/mL|mg/dL)\b' # Lab values
]
# Additional processing to preserve medical context
return text
# Usage example
sanitizer = HealthcareDataSanitizer()
clinical_note = "Patient John Smith (MRN: 1234567890) presents with chest pain. BP 140/90 mmHg."
result = sanitizer.sanitize_clinical_text(clinical_note)
print("Sanitized:", result['sanitized_text'])
print("PHI Detected:", result['phi_count'])
```
```javascript JavaScript
class HealthcareDataSanitizer {
constructor() {
// PHI patterns to detect and anonymize
this.phiPatterns = {
mrn: /\b\d{7,10}\b/g,
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/gi,
date: /\b\d{1,2}\/\d{1,2}\/\d{4}\b/g,
name: /\b[A-Z][a-z]+ [A-Z][a-z]+\b/g
};
}
sanitizeClinicalText(text, preserveMedicalContext = true) {
let sanitizedText = text;
const detectedPhi = [];
Object.entries(this.phiPatterns).forEach(([phiType, pattern]) => {
const matches = text.match(pattern) || [];
matches.forEach(match => {
const anonymized = this.anonymizeValue(match, phiType);
sanitizedText = sanitizedText.replace(match, anonymized);
detectedPhi.push({
type: phiType,
original_length: match.length,
anonymized: anonymized
});
});
});
if (preserveMedicalContext) {
sanitizedText = this.preserveMedicalTerms(sanitizedText);
}
return {
sanitized_text: sanitizedText,
phi_detected: detectedPhi,
phi_count: detectedPhi.length,
is_safe_for_ai: detectedPhi.length === 0
};
}
anonymizeValue(value, phiType) {
// Create consistent anonymized replacement
const crypto = require('crypto');
const hashValue = crypto.createHash('md5').update(value).digest('hex').substring(0, 8);
return `[${phiType.toUpperCase()}_${hashValue}]`;
}
preserveMedicalTerms(text) {
// Preserve important medical terminology patterns
return text;
}
}
// Usage example
const sanitizer = new HealthcareDataSanitizer();
const clinicalNote = "Patient John Smith (MRN: 1234567890) presents with chest pain. BP 140/90 mmHg.";
const result = sanitizer.sanitizeClinicalText(clinicalNote);
console.log("Sanitized:", result.sanitized_text);
console.log("PHI Detected:", result.phi_count);
```
### Performance Optimization
Healthcare AI applications need to balance accuracy with performance:
```python Python
import asyncio
import time
from functools import wraps
from typing import Dict, List, Any, Optional
import redis
import json
class HealthcareAICache:
def __init__(self, redis_url: str = "redis://localhost:6379"):
self.redis_client = redis.from_url(redis_url)
self.default_ttl = 3600 # 1 hour cache for medical concepts
@staticmethod
def cache_medical_concepts(ttl: int = None):
"""Decorator factory to cache medical concept lookups"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
# Get instance from first argument
instance = args[0]
# Create cache key from function args
cache_key = f"medical_concept:{hash(str(args) + str(kwargs))}"
# Try to get from cache first
cached_result = instance.redis_client.get(cache_key)
if cached_result:
return json.loads(cached_result)
# Execute function and cache result
result = await func(*args, **kwargs)
instance.redis_client.setex(
cache_key,
ttl or instance.default_ttl,
json.dumps(result, default=str)
)
return result
return wrapper
return decorator
async def batch_concept_lookup(self, concepts: List[str], batch_size: int = 10) -> List[Dict]:
"""Batch concept lookups for better performance"""
results = []
for i in range(0, len(concepts), batch_size):
batch = concepts[i:i + batch_size]
batch_results = await asyncio.gather(*[
self._lookup_single_concept(concept)
for concept in batch
])
results.extend(batch_results)
return results
@cache_medical_concepts(ttl=7200) # 2 hour cache
async def _lookup_single_concept(self, concept: str) -> Dict:
"""Single concept lookup with caching"""
# Implementation would use OMOPHub API
pass
class PerformanceMonitor:
def __init__(self):
self.metrics = {}
def time_operation(self, operation_name: str):
"""Decorator to monitor operation performance"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
try:
result = await func(*args, **kwargs)
duration = time.time() - start_time
# Store performance metrics
if operation_name not in self.metrics:
self.metrics[operation_name] = []
self.metrics[operation_name].append({
'duration': duration,
'status': 'success',
'timestamp': start_time
})
return result
except Exception as e:
duration = time.time() - start_time
if operation_name not in self.metrics:
self.metrics[operation_name] = []
self.metrics[operation_name].append({
'duration': duration,
'status': 'error',
'error': str(e),
'timestamp': start_time
})
raise
return wrapper
return decorator
def get_performance_summary(self, operation_name: str) -> Dict[str, Any]:
"""Get performance statistics for an operation"""
if operation_name not in self.metrics:
return {}
operations = self.metrics[operation_name]
durations = [op['duration'] for op in operations if op['status'] == 'success']
if not durations:
return {'error': 'No successful operations recorded'}
return {
'total_operations': len(operations),
'successful_operations': len(durations),
'error_rate': (len(operations) - len(durations)) / len(operations),
'avg_duration': sum(durations) / len(durations),
'min_duration': min(durations),
'max_duration': max(durations),
'p95_duration': sorted(durations)[int(len(durations) * 0.95)]
}
# Usage example
cache = HealthcareAICache()
monitor = PerformanceMonitor()
@monitor.time_operation("medical_concept_validation")
@cache.cache_medical_concepts(ttl=3600)
async def validate_medical_concept(concept: str) -> Dict:
"""Cached and monitored concept validation"""
# OMOPHub API call implementation
pass
```
```javascript JavaScript
class HealthcareAICache {
constructor(redisClient) {
this.redis = redisClient;
this.defaultTTL = 3600; // 1 hour
}
cacheMedicalConcepts(ttl = null) {
return (target, propertyKey, descriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function(...args) {
const cacheKey = `medical_concept:${JSON.stringify(args)}`;
try {
const cached = await this.redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
} catch (error) {
console.warn('Cache read failed:', error);
}
const result = await originalMethod.apply(this, args);
try {
await this.redis.setEx(
cacheKey,
ttl || this.defaultTTL,
JSON.stringify(result)
);
} catch (error) {
console.warn('Cache write failed:', error);
}
return result;
};
};
}
async batchConceptLookup(concepts, batchSize = 10) {
const results = [];
for (let i = 0; i < concepts.length; i += batchSize) {
const batch = concepts.slice(i, i + batchSize);
const batchPromises = batch.map(concept =>
this.lookupSingleConcept(concept)
);
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
}
class PerformanceMonitor {
constructor() {
this.metrics = {};
}
timeOperation(operationName) {
return (target, propertyKey, descriptor) => {
const originalMethod = descriptor.value;
descriptor.value = async function(...args) {
const startTime = Date.now();
try {
const result = await originalMethod.apply(this, args);
const duration = Date.now() - startTime;
this.recordMetric(operationName, {
duration,
status: 'success',
timestamp: startTime
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
this.recordMetric(operationName, {
duration,
status: 'error',
error: error.message,
timestamp: startTime
});
throw error;
}
};
};
}
recordMetric(operationName, metric) {
if (!this.metrics[operationName]) {
this.metrics[operationName] = [];
}
this.metrics[operationName].push(metric);
}
getPerformanceSummary(operationName) {
if (!this.metrics[operationName]) {
return {};
}
const operations = this.metrics[operationName];
const successfulOps = operations.filter(op => op.status === 'success');
const durations = successfulOps.map(op => op.duration);
if (durations.length === 0) {
return { error: 'No successful operations recorded' };
}
durations.sort((a, b) => a - b);
return {
total_operations: operations.length,
successful_operations: durations.length,
error_rate: (operations.length - durations.length) / operations.length,
avg_duration: durations.reduce((sum, d) => sum + d, 0) / durations.length,
min_duration: Math.min(...durations),
max_duration: Math.max(...durations),
p95_duration: durations[Math.floor(durations.length * 0.95)]
};
}
}
```
## Advanced Patterns
### RAG with Medical Knowledge Bases
Retrieval Augmented Generation (RAG) patterns work exceptionally well with medical vocabularies:
```python
class MedicalRAGSystem:
def __init__(self, omophub_client, llm_client):
self.omophub = omophub_client
self.llm = llm_client
async def medical_rag_query(self, question: str, context_types: List[str] = None) -> Dict:
"""Execute RAG pattern with medical knowledge retrieval"""
# 1. Extract medical entities from question
entities = await self._extract_medical_entities(question)
# 2. Retrieve relevant medical knowledge
knowledge = await self._retrieve_medical_knowledge(entities, context_types)
# 3. Generate response with retrieved context
response = await self._generate_with_context(question, knowledge)
return {
'answer': response,
'retrieved_knowledge': knowledge,
'source_citations': self._generate_citations(knowledge)
}
async def _retrieve_medical_knowledge(self, entities: List[str], context_types: List[str]) -> Dict:
"""Retrieve comprehensive medical context"""
knowledge = {
'concept_definitions': {},
'relationships': {},
'hierarchies': {},
'mappings': {}
}
for entity in entities:
# Get concept details
concepts = await self.omophub.search_concepts(query=entity)
if concepts['data']:
concept = concepts['data'][0]
knowledge['concept_definitions'][entity] = concept
# Get relationships if requested
if not context_types or 'relationships' in context_types:
relationships = await self.omophub.get_concept_relationships(
concept_id=concept['concept_id']
)
knowledge['relationships'][entity] = relationships['data']
# Get hierarchies if requested
if not context_types or 'hierarchies' in context_types:
ancestors = await self.omophub.get_concept_ancestors(
concept_id=concept['concept_id']
)
knowledge['hierarchies'][entity] = ancestors['data']
return knowledge
```
### Multi-Agent Medical Systems
Combine multiple AI agents for complex medical workflows:
```python
class MedicalMultiAgentSystem:
def __init__(self, omophub_client):
self.omophub = omophub_client
self.agents = {
'entity_extractor': EntityExtractionAgent(),
'concept_validator': ConceptValidationAgent(omophub_client),
'clinical_reasoner': ClinicalReasoningAgent(),
'safety_checker': SafetyCheckAgent()
}
async def process_clinical_case(self, case_description: str) -> Dict:
"""Process clinical case through multi-agent pipeline"""
# Agent 1: Extract medical entities
entities = await self.agents['entity_extractor'].extract(case_description)
# Agent 2: Validate and standardize concepts
validated = await self.agents['concept_validator'].validate(entities)
# Agent 3: Generate clinical reasoning
reasoning = await self.agents['clinical_reasoner'].reason(validated, case_description)
# Agent 4: Safety check final output
final_output = await self.agents['safety_checker'].check(reasoning)
return {
'extracted_entities': entities,
'validated_concepts': validated,
'clinical_reasoning': reasoning,
'safety_checked_output': final_output,
'agent_trace': self._get_agent_trace()
}
```
## Production Considerations
### Monitoring and Observability
Healthcare AI requires comprehensive monitoring:
```python Python
import logging
import json
import copy
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, asdict
from datetime import datetime
import uuid
@dataclass
class HealthcareAIEvent:
event_id: str
timestamp: datetime
user_id: str
session_id: str
operation: str
input_data: Dict[str, Any]
output_data: Dict[str, Any]
performance_metrics: Dict[str, float]
safety_flags: List[str]
concept_validations: List[Dict]
error_details: Optional[str] = None
class HealthcareAILogger:
def __init__(self, log_level: str = "INFO"):
self.logger = logging.getLogger("healthcare_ai")
self.logger.setLevel(getattr(logging, log_level))
# Configure structured logging for healthcare compliance
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
self.logger.addHandler(handler)
def log_ai_operation(self, event: HealthcareAIEvent):
"""Log AI operations with healthcare compliance requirements"""
# Sanitize PHI from logs
sanitized_event = self._sanitize_phi(event)
log_entry = {
'event_id': sanitized_event.event_id,
'timestamp': sanitized_event.timestamp.isoformat(),
'operation': sanitized_event.operation,
'performance': sanitized_event.performance_metrics,
'safety_flags': sanitized_event.safety_flags,
'concept_count': len(sanitized_event.concept_validations),
'error': sanitized_event.error_details is not None
}
if sanitized_event.error_details:
self.logger.error(f"AI Operation Failed: {json.dumps(log_entry)}")
else:
self.logger.info(f"AI Operation Complete: {json.dumps(log_entry)}")
def _sanitize_phi(self, event: HealthcareAIEvent) -> HealthcareAIEvent:
"""Remove PHI from log events"""
# Implementation to remove/anonymize PHI
sanitized = copy.deepcopy(event)
# Remove potential PHI fields
if 'patient_name' in sanitized.input_data:
sanitized.input_data['patient_name'] = '[REDACTED]'
return sanitized
class HealthcareAIMetrics:
def __init__(self, metrics_backend: str = "prometheus"):
self.metrics_backend = metrics_backend
self.counters = {}
self.histograms = {}
def increment_counter(self, metric_name: str, labels: Dict[str, str] = None):
"""Increment counter metric"""
key = f"{metric_name}:{json.dumps(labels or {}, sort_keys=True)}"
self.counters[key] = self.counters.get(key, 0) + 1
def record_duration(self, metric_name: str, duration: float, labels: Dict[str, str] = None):
"""Record duration metric"""
key = f"{metric_name}:{json.dumps(labels or {}, sort_keys=True)}"
if key not in self.histograms:
self.histograms[key] = []
self.histograms[key].append(duration)
def get_metrics_summary(self) -> Dict[str, Any]:
"""Get current metrics summary"""
return {
'counters': self.counters,
'histograms': {
k: {
'count': len(v),
'avg': sum(v) / len(v) if v else 0,
'p95': sorted(v)[int(len(v) * 0.95)] if v else 0
}
for k, v in self.histograms.items()
}
}
# Example usage with monitoring
class MonitoredMedicalAI:
def __init__(self, omophub_client):
self.omophub = omophub_client
self.logger = HealthcareAILogger()
self.metrics = HealthcareAIMetrics()
async def process_with_monitoring(self, user_input: str, user_id: str) -> Dict:
event_id = str(uuid.uuid4())
session_id = f"session_{int(datetime.now().timestamp())}"
start_time = datetime.now()
try:
# Step 1: Sanitize input and check for PHI
sanitizer = HealthcareDataSanitizer()
sanitization_result = sanitizer.sanitize_clinical_text(user_input)
# Step 2: Check if PHI was detected
if sanitization_result.get('detected_phi'):
# Log PHI detection
self.logger.log_security_event({
'event_type': 'PHI_DETECTED',
'user_id': user_id,
'session_id': session_id,
'phi_types': list(sanitization_result['detected_phi'].keys())
})
# Return error - do not process PHI without BAA
return {
'success': False,
'error': 'PHI detected in input. Please remove personal information or ensure BAA compliance.',
'event_id': event_id
}
# Step 3: Process sanitized input
sanitized_input = sanitization_result['sanitized_text']
self.logger.log_info(f"Input sanitized for processing: {event_id}")
# Process the medical AI request with sanitized input
result = await self._process_medical_query(sanitized_input)
# Calculate performance metrics
duration = (datetime.now() - start_time).total_seconds()
# Create monitoring event
event = HealthcareAIEvent(
event_id=event_id,
timestamp=start_time,
user_id=user_id,
session_id=session_id,
operation="medical_query_processing",
input_data={"query_length": len(user_input)},
output_data={"response_length": len(str(result))},
performance_metrics={"duration_seconds": duration},
safety_flags=result.get('safety_warnings', []),
concept_validations=result.get('validated_concepts', [])
)
# Log the event
self.logger.log_ai_operation(event)
# Record metrics
self.metrics.increment_counter(
"medical_ai_requests_total",
{"operation": "query", "status": "success"}
)
self.metrics.record_duration(
"medical_ai_duration_seconds",
duration,
{"operation": "query"}
)
return result
except Exception as e:
duration = (datetime.now() - start_time).total_seconds()
# Create error event
event = HealthcareAIEvent(
event_id=event_id,
timestamp=start_time,
user_id=user_id,
session_id=session_id,
operation="medical_query_processing",
input_data={"query_length": len(user_input)},
output_data={},
performance_metrics={"duration_seconds": duration},
safety_flags=["PROCESSING_ERROR"],
concept_validations=[],
error_details=str(e)
)
# Log error
self.logger.log_ai_operation(event)
# Record error metrics
self.metrics.increment_counter(
"medical_ai_requests_total",
{"operation": "query", "status": "error"}
)
raise
async def _process_medical_query(self, query: str) -> Dict:
"""Process medical query with OMOPHub integration"""
# Implementation here
pass
```
### Cost Optimization Strategies
Healthcare AI can be expensive; optimize costs without compromising quality:
```python
class HealthcareAICostOptimizer:
def __init__(self, omophub_client):
self.omophub = omophub_client
self.cost_tracking = {}
async def optimize_llm_calls(self, query: str) -> Dict:
"""Optimize LLM usage through intelligent routing"""
# 1. Check if query can be answered with vocabulary lookup only
vocabulary_answer = await self._try_vocabulary_only_answer(query)
if vocabulary_answer['confidence'] > 0.8:
return {
'answer': vocabulary_answer['response'],
'cost_optimization': 'vocabulary_only',
'estimated_savings': 0.95 # 95% cost saving vs full LLM
}
# 2. Use smaller model for simple queries
complexity_score = self._assess_query_complexity(query)
if complexity_score < 0.5:
model = 'claude-3-haiku' # Cheaper model
else:
model = 'claude-3-sonnet' # More capable model
# 3. Optimize context window usage
optimized_context = await self._optimize_context_window(query)
return {
'selected_model': model,
'context_optimization': optimized_context,
'estimated_cost': self._estimate_cost(model, optimized_context)
}
def _assess_query_complexity(self, query: str) -> float:
"""Assess query complexity to choose appropriate model"""
complexity_indicators = {
'multi_step_reasoning': ['analyze', 'compare', 'evaluate', 'determine'],
'medical_calculations': ['calculate', 'dose', 'dosage', 'mg/kg'],
'differential_diagnosis': ['differential', 'diagnose', 'rule out'],
'complex_relationships': ['interaction', 'contraindication', 'mechanism']
}
score = 0.0
for category, keywords in complexity_indicators.items():
if any(keyword in query.lower() for keyword in keywords):
score += 0.25
return min(score, 1.0)
async def _try_vocabulary_only_answer(self, query: str) -> Dict:
"""Attempt to answer using only vocabulary lookups"""
# Simple keyword extraction
medical_terms = self._extract_simple_medical_terms(query)
if len(medical_terms) == 1:
# Single concept query - can often be answered with vocabulary
concept_info = await self.omophub.search_concepts(
query=medical_terms[0],
page_size=1
)
if concept_info['data']:
concept = concept_info['data'][0]
response = f"{concept['concept_name']}: {concept.get('definition', 'No definition available')}"
return {
'response': response,
'confidence': 0.9 if concept.get('definition') else 0.6
}
return {'response': '', 'confidence': 0.0}
```
## Conclusion
Integrating AI and LLMs with healthcare vocabularies represents the future of intelligent medical applications. By combining the natural language understanding of models like GPT-4 and Claude with the structured, validated data from OMOPHub, developers can create applications that are both innovative and clinically sound.
Key takeaways for successful healthcare AI integration:
1. **Always validate AI outputs** with authoritative medical vocabularies
2. **Implement robust safety checks** and include professional consultation disclaimers
3. **Handle PHI appropriately** with proper anonymization and compliance measures
4. **Monitor performance and costs** to ensure sustainable, effective operations
5. **Use structured prompts** and evidence-based approaches for medical contexts
The examples in this guide provide production-ready patterns for building the next generation of healthcare AI applications that prioritize both innovation and patient safety.
**Next Steps**: Explore the [FHIR Integration Guide](/guides/integration/fhir-integration) to learn how to combine AI capabilities with healthcare interoperability standards, or review our [Clinical Decision Support](/guides/use-cases/clinical-coding) use case for more specific implementation patterns.
# FHIR Integration
Source: https://docs.omophub.com/guides/integration/fhir-integration
Build FHIR-compliant healthcare applications with OMOPHub terminology services
# FHIR Integration with OMOPHub
## Overview
Fast Healthcare Interoperability Resources (FHIR) is the global standard for exchanging healthcare information electronically. OMOPHub provides comprehensive terminology services that enable FHIR-compliant applications to properly code, validate, and translate medical concepts across different vocabularies.
This guide demonstrates how to leverage OMOPHub as a terminology server for FHIR resources, supporting ValueSet expansion, ConceptMap translation, and proper coding of clinical data.
## Why FHIR Needs Robust Terminology Services
FHIR resources rely heavily on standardized medical terminologies:
* **Observation**: LOINC codes for lab results and vital signs
* **Condition**: SNOMED CT and ICD-10 for diagnoses
* **Medication**: RxNorm for drug identification
* **Procedure**: HCPCS and SNOMED CT for procedures
* **AllergyIntolerance**: SNOMED CT for allergen coding
Without proper terminology services, FHIR implementations face:
* Inconsistent coding across systems
* Failed validation of resources
* Inability to map between required code systems
* Non-compliance with implementation guides
## Architecture Overview
```mermaid
graph TB
A[FHIR Client Application] --> B[FHIR Server]
B --> C[OMOPHub Terminology Service]
C --> D[ValueSet Operations]
C --> E[ConceptMap Translation]
C --> F[Code Validation]
C --> G[Hierarchy Navigation]
D --> H[SNOMED CT]
D --> I[LOINC]
D --> J[RxNorm]
D --> K[ICD-10]
B --> L[FHIR Resources]
L --> M[Observation]
L --> N[Condition]
L --> O[Medication]
L --> P[Procedure]
style C fill:#333333,stroke:#fff,stroke-width:2px,color:#fff
style B fill:#333333,stroke:#fff,stroke-width:2px,color:#fff
```
## Core Implementation
### FHIR Terminology Service Foundation
```python Python
import requests
import time
from typing import List, Dict, Optional, Any
from dataclasses import dataclass
from datetime import datetime
import json
import logging
from enum import Enum
class FHIRResourceType(Enum):
OBSERVATION = "Observation"
CONDITION = "Condition"
MEDICATION_REQUEST = "MedicationRequest"
PROCEDURE = "Procedure"
ALLERGY_INTOLERANCE = "AllergyIntolerance"
@dataclass
class CodeableConcept:
"""FHIR CodeableConcept representation"""
text: str
codings: List[Dict[str, str]]
@dataclass
class ValueSetExpansion:
"""Expanded ValueSet with all included concepts"""
id: str
name: str
url: str
concepts: List[Dict[str, Any]]
total: int
timestamp: str
@dataclass
class ConceptMapTranslation:
"""Translation between source and target code systems"""
source_code: str
source_system: str
target_code: str
target_system: str
equivalence: str
confidence: float
class OMOPHubFHIRTerminologyService:
"""
FHIR-compliant terminology service using OMOPHub API.
Implements key FHIR terminology operations.
"""
def __init__(self, api_key: str, base_url: str = "https://api.omophub.com"):
self.api_key = api_key
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {api_key}"}
self.logger = logging.getLogger(__name__)
# FHIR system URIs
self.system_uris = {
"SNOMED": "http://snomed.info/sct",
"LOINC": "http://loinc.org",
"ICD10CM": "http://hl7.org/fhir/sid/icd-10-cm",
"ICD10PCS": "http://hl7.org/fhir/sid/icd-10-pcs",
"RXNORM": "http://www.nlm.nih.gov/research/umls/rxnorm",
"HCPCS": "https://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets",
"HCPCS": "https://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets",
"NDC": "http://hl7.org/fhir/sid/ndc",
"CVX": "http://hl7.org/fhir/sid/cvx",
"UCUM": "http://unitsofmeasure.org"
}
# OMOPHub to FHIR vocabulary mapping
self.vocabulary_mapping = {
"SNOMED": "SNOMED",
"LOINC": "LOINC",
"ICD10CM": "ICD10CM",
"ICD10PCS": "ICD10PCS",
"RxNorm": "RXNORM",
"HCPCS": "HCPCS",
"HCPCS": "HCPCS",
"NDC": "NDC",
"CVX": "CVX"
}
def validate_code(self, code: str, system: str, display: Optional[str] = None) -> Dict[str, Any]:
"""
Validate a code within a specified system.
FHIR $validate-code operation.
"""
try:
# Map FHIR system URI to OMOPHub vocabulary
vocabulary_id = self._get_vocabulary_id(system)
# Search for the code
response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': code,
'vocabulary_ids': vocabulary_id,
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
concept = data['data'][0]
valid = concept['concept_code'] == code
# Check display text if provided
display_valid = True
if display and valid:
display_valid = display.lower() in concept['concept_name'].lower()
return {
'valid': valid and display_valid,
'concept': concept,
'message': 'Code is valid' if valid else f'Code {code} not found in {system}'
}
else:
return {
'valid': False,
'message': f'Code {code} not found in {system}'
}
except requests.RequestException as e:
self.logger.error(f"Error validating code: {e}")
return {
'valid': False,
'message': f'Error validating code: {str(e)}'
}
def expand_valueset(self, valueset_definition: Dict[str, Any]) -> ValueSetExpansion:
"""
Expand a FHIR ValueSet to include all member concepts.
FHIR $expand operation.
"""
expanded_concepts = []
# Process each include section
for include in valueset_definition.get('compose', {}).get('include', []):
system = include.get('system')
if not system:
continue
vocabulary_id = self._get_vocabulary_id(system)
# Handle different inclusion patterns
if 'concept' in include:
# Explicit concept list
for concept_ref in include['concept']:
code = concept_ref.get('code')
if code:
result = self.validate_code(code, system)
if result['valid']:
expanded_concepts.append({
'system': system,
'code': code,
'display': result['concept']['concept_name']
})
elif 'filter' in include:
# Filter-based inclusion
for filter_item in include['filter']:
property_name = filter_item.get('property')
op = filter_item.get('op')
value = filter_item.get('value')
if property_name == 'concept' and op == 'is-a':
# Get descendants of a concept
descendants = self._get_concept_descendants(value, vocabulary_id)
for desc in descendants:
expanded_concepts.append({
'system': system,
'code': desc['concept_code'],
'display': desc['concept_name']
})
else:
# Include all concepts from the system (limited for demo)
all_concepts = self._get_system_concepts(vocabulary_id, limit=100)
for concept in all_concepts:
expanded_concepts.append({
'system': system,
'code': concept['concept_code'],
'display': concept['concept_name']
})
# Process exclusions
for exclude in valueset_definition.get('compose', {}).get('exclude', []):
system = exclude.get('system')
if 'concept' in exclude:
for concept_ref in exclude['concept']:
code = concept_ref.get('code')
expanded_concepts = [
c for c in expanded_concepts
if not (c['system'] == system and c['code'] == code)
]
return ValueSetExpansion(
id=valueset_definition.get('id', 'expanded-valueset'),
name=valueset_definition.get('name', 'Expanded ValueSet'),
url=valueset_definition.get('url', ''),
concepts=expanded_concepts,
total=len(expanded_concepts),
timestamp=datetime.utcnow().isoformat()
)
def translate_concept(self, code: str, source_system: str, target_system: str) -> List[ConceptMapTranslation]:
"""
Translate a concept from one code system to another.
FHIR $translate operation.
"""
translations = []
try:
source_vocab = self._get_vocabulary_id(source_system)
target_vocab = self._get_vocabulary_id(target_system)
# Get source concept
source_response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': code,
'vocabulary_ids': source_vocab,
'page_size': 1
}
)
source_response.raise_for_status()
source_data = source_response.json()
if not source_data['data']:
return translations
source_concept_id = source_data['data'][0]['concept_id']
# Get mappings
mappings_response = requests.get(
f"{self.base_url}/v1/concepts/{source_concept_id}/mappings",
headers=self.headers,
params={
'target_vocabularies': target_vocab
}
)
mappings_response.raise_for_status()
mappings_data = mappings_response.json()
# Create translations
for mapping in mappings_data.get('data', []):
if mapping['target_vocabulary_id'] == target_vocab:
translations.append(ConceptMapTranslation(
source_code=code,
source_system=source_system,
target_code=mapping['target_concept_code'],
target_system=target_system,
equivalence=self._get_fhir_equivalence(mapping.get('relationship_id')),
confidence=mapping.get('confidence', 1.0)
))
return translations
except requests.RequestException as e:
self.logger.error(f"Error translating concept: {e}")
return translations
def create_codeable_concept(self, code: str, system: str, display: Optional[str] = None) -> CodeableConcept:
"""
Create a FHIR CodeableConcept with proper coding.
"""
# Validate and get full concept details
validation = self.validate_code(code, system)
if validation['valid']:
concept = validation['concept']
display_text = display or concept['concept_name']
# Create primary coding
codings = [{
'system': system,
'code': code,
'display': display_text
}]
# Add additional codings from mappings if available
try:
# Get standard concept mappings
if concept.get('standard_concept') == 'S':
mapping_endpoint = f"{self.base_url}/v1/concepts/{concept['concept_id']}/mappings"
mappings_response = requests.get(
mapping_endpoint,
headers=self.headers,
params={'relationship_types': 'Maps to'}
)
mappings_response.raise_for_status()
mappings = mappings_response.json()
for mapping in mappings.get('data', [])[:2]: # Limit to 2 additional codings
target_system = self._get_fhir_system(mapping['target_vocabulary_id'])
if target_system:
codings.append({
'system': target_system,
'code': mapping['target_concept_code'],
'display': mapping.get('target_concept_name', '')
})
except requests.exceptions.RequestException as e:
# Log network/HTTP errors with context
if hasattr(self, 'logger'):
self.logger.exception(
f"Failed to fetch concept mappings for concept_id={concept['concept_id']} "
f"from endpoint={mapping_endpoint}: {e}"
)
else:
logging.exception(
f"Failed to fetch concept mappings for concept_id={concept['concept_id']} "
f"from endpoint={mapping_endpoint}: {e}"
)
except (KeyError, ValueError) as e:
# Log data parsing errors with context
if hasattr(self, 'logger'):
self.logger.exception(
f"Failed to parse concept mapping response for concept_id={concept['concept_id']}: {e}"
)
else:
logging.exception(
f"Failed to parse concept mapping response for concept_id={concept['concept_id']}: {e}"
)
return CodeableConcept(
text=display_text,
codings=codings
)
else:
# Return basic CodeableConcept even if not validated
return CodeableConcept(
text=display or code,
codings=[{
'system': system,
'code': code,
'display': display or code
}]
)
def lookup_display(self, code: str, system: str) -> Optional[str]:
"""
Look up the display text for a code.
FHIR $lookup operation.
"""
validation = self.validate_code(code, system)
if validation['valid']:
return validation['concept']['concept_name']
return None
def subsumes(self, code_a: str, code_b: str, system: str) -> Optional[str]:
"""
Test the subsumption relationship between two codes.
Returns: 'subsumes', 'subsumed-by', 'equivalent', or None
"""
try:
vocabulary_id = self._get_vocabulary_id(system)
# Get both concepts
response_a = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={'query': code_a, 'vocabulary_ids': vocabulary_id, 'page_size': 1}
)
response_b = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={'query': code_b, 'vocabulary_ids': vocabulary_id, 'page_size': 1}
)
response_a.raise_for_status()
response_b.raise_for_status()
data_a = response_a.json()
data_b = response_b.json()
if not (data_a['data'] and data_b['data']):
return None
concept_a = data_a['data'][0]
concept_b = data_b['data'][0]
if concept_a['concept_id'] == concept_b['concept_id']:
return 'equivalent'
# Check if A subsumes B (B is descendant of A)
descendants_a = self._get_concept_descendants(
code_a,
vocabulary_id,
include_self=False
)
descendant_ids_a = [d['concept_id'] for d in descendants_a]
if concept_b['concept_id'] in descendant_ids_a:
return 'subsumes'
# Check if B subsumes A (A is descendant of B)
descendants_b = self._get_concept_descendants(
code_b,
vocabulary_id,
include_self=False
)
descendant_ids_b = [d['concept_id'] for d in descendants_b]
if concept_a['concept_id'] in descendant_ids_b:
return 'subsumed-by'
return None
except requests.RequestException as e:
self.logger.error(f"Error checking subsumption: {e}")
return None
def _get_vocabulary_id(self, system_uri: str) -> str:
"""Map FHIR system URI to OMOPHub vocabulary ID."""
# Reverse lookup from system URI
for vocab, uri in self.system_uris.items():
if uri == system_uri:
return vocab
# Check if it's already a vocabulary ID
if system_uri in self.vocabulary_mapping:
return system_uri
raise ValueError(f"Unknown system URI: {system_uri}")
def _get_fhir_system(self, vocabulary_id: str) -> Optional[str]:
"""Map OMOPHub vocabulary ID to FHIR system URI."""
return self.system_uris.get(vocabulary_id)
def _get_fhir_equivalence(self, relationship_id: str) -> str:
"""Map relationship to FHIR ConceptMap equivalence."""
equivalence_map = {
'Maps to': 'equivalent',
'Is a': 'subsumes',
'Subsumes': 'subsumes',
'Has finding site': 'relatedto',
'Has associated morphology': 'relatedto',
'Has causative agent': 'relatedto'
}
return equivalence_map.get(relationship_id, 'relatedto')
def _get_concept_descendants(self, code: str, vocabulary_id: str, include_self: bool = True) -> List[Dict]:
"""Get all descendant concepts."""
try:
# First get the concept
search_response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': code,
'vocabulary_ids': vocabulary_id,
'page_size': 1
}
)
search_response.raise_for_status()
search_data = search_response.json()
if not search_data['data']:
return []
concept_id = search_data['data'][0]['concept_id']
# Get descendants
hierarchy_response = requests.get(
f"{self.base_url}/v1/concepts/{concept_id}/hierarchy",
headers=self.headers,
params={
'include_descendants': True,
'max_levels': 10
}
)
hierarchy_response.raise_for_status()
hierarchy_data = hierarchy_response.json()
descendants = []
for item in hierarchy_data.get('data', []):
if item.get('level', 0) > 0 or (include_self and item.get('level', 0) == 0):
descendants.append(item['concept'])
return descendants
except requests.RequestException as e:
self.logger.error(f"Error getting descendants: {e}")
return []
def _get_system_concepts(self, vocabulary_id: str, limit: int = 100) -> List[Dict]:
"""Get concepts from a vocabulary (limited for performance)."""
try:
response = requests.get(
f"{self.base_url}/v1/vocabularies/{vocabulary_id}/concepts",
headers=self.headers,
params={
'page_size': limit,
'standard_concept': 'S'
}
)
response.raise_for_status()
data = response.json()
return data.get('data', [])
except requests.RequestException as e:
self.logger.error(f"Error getting system concepts: {e}")
return []
# FHIR Resource Builders
class FHIRResourceBuilder:
"""Build FHIR resources with proper terminology coding."""
def __init__(self, terminology_service: OMOPHubFHIRTerminologyService):
self.terminology = terminology_service
def create_observation(self,
loinc_code: str,
value: Any,
patient_id: str,
effective_datetime: str,
status: str = 'final') -> Dict[str, Any]:
"""
Create a FHIR Observation resource with proper LOINC coding.
"""
# Validate and get display text
display = self.terminology.lookup_display(loinc_code, "http://loinc.org")
# Create CodeableConcept
code_concept = self.terminology.create_codeable_concept(
loinc_code,
"http://loinc.org",
display
)
observation = {
"resourceType": "Observation",
"status": status,
"code": {
"coding": code_concept.codings,
"text": code_concept.text
},
"subject": {
"reference": f"Patient/{patient_id}"
},
"effectiveDateTime": effective_datetime
}
# Add value based on type
if isinstance(value, (int, float)):
observation["valueQuantity"] = {
"value": value,
"unit": "mg/dL", # Should be determined based on LOINC code
"system": "http://unitsofmeasure.org",
"code": "mg/dL"
}
elif isinstance(value, str):
observation["valueString"] = value
elif isinstance(value, dict) and 'code' in value:
# Coded value
value_concept = self.terminology.create_codeable_concept(
value['code'],
value.get('system', 'http://snomed.info/sct')
)
observation["valueCodeableConcept"] = {
"coding": value_concept.codings,
"text": value_concept.text
}
return observation
def create_condition(self,
diagnosis_code: str,
code_system: str,
patient_id: str,
onset_date: str,
clinical_status: str = 'active',
verification_status: str = 'confirmed') -> Dict[str, Any]:
"""
Create a FHIR Condition resource with proper diagnosis coding.
"""
# Create primary coding
code_concept = self.terminology.create_codeable_concept(
diagnosis_code,
code_system
)
# Try to add SNOMED translation if not already SNOMED
if code_system != "http://snomed.info/sct":
translations = self.terminology.translate_concept(
diagnosis_code,
code_system,
"http://snomed.info/sct"
)
for trans in translations:
if trans.equivalence in ['equivalent', 'subsumes']:
code_concept.codings.append({
'system': trans.target_system,
'code': trans.target_code,
'display': self.terminology.lookup_display(
trans.target_code,
trans.target_system
)
})
break
condition = {
"resourceType": "Condition",
"clinicalStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": clinical_status
}]
},
"verificationStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-ver-status",
"code": verification_status
}]
},
"code": {
"coding": code_concept.codings,
"text": code_concept.text
},
"subject": {
"reference": f"Patient/{patient_id}"
},
"onsetDateTime": onset_date
}
return condition
def create_medication_request(self,
rxnorm_code: str,
patient_id: str,
prescriber_id: str,
dosage_instructions: str,
quantity: int,
refills: int = 0) -> Dict[str, Any]:
"""
Create a FHIR MedicationRequest with RxNorm coding.
"""
# Get medication details
med_concept = self.terminology.create_codeable_concept(
rxnorm_code,
"http://www.nlm.nih.gov/research/umls/rxnorm"
)
medication_request = {
"resourceType": "MedicationRequest",
"status": "active",
"intent": "order",
"medicationCodeableConcept": {
"coding": med_concept.codings,
"text": med_concept.text
},
"subject": {
"reference": f"Patient/{patient_id}"
},
"requester": {
"reference": f"Practitioner/{prescriber_id}"
},
"dosageInstruction": [{
"text": dosage_instructions
}],
"dispenseRequest": {
"quantity": {
"value": quantity,
"unit": "tablet"
},
"numberOfRepeatsAllowed": refills
}
}
return medication_request
def create_procedure(self,
procedure_code: str,
code_system: str,
patient_id: str,
performed_date: str,
performer_id: Optional[str] = None) -> Dict[str, Any]:
"""
Create a FHIR Procedure resource with proper coding.
"""
# Create procedure coding
proc_concept = self.terminology.create_codeable_concept(
procedure_code,
code_system
)
procedure = {
"resourceType": "Procedure",
"status": "completed",
"code": {
"coding": proc_concept.codings,
"text": proc_concept.text
},
"subject": {
"reference": f"Patient/{patient_id}"
},
"performedDateTime": performed_date
}
if performer_id:
procedure["performer"] = [{
"actor": {
"reference": f"Practitioner/{performer_id}"
}
}]
return procedure
def create_allergy_intolerance(self,
allergen_code: str,
patient_id: str,
reaction_code: Optional[str] = None,
severity: str = 'moderate') -> Dict[str, Any]:
"""
Create a FHIR AllergyIntolerance resource.
"""
# Create allergen coding (typically SNOMED)
allergen_concept = self.terminology.create_codeable_concept(
allergen_code,
"http://snomed.info/sct"
)
allergy = {
"resourceType": "AllergyIntolerance",
"clinicalStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
"code": "active"
}]
},
"verificationStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification",
"code": "confirmed"
}]
},
"code": {
"coding": allergen_concept.codings,
"text": allergen_concept.text
},
"patient": {
"reference": f"Patient/{patient_id}"
},
"criticality": "low" if severity == 'mild' else "high" if severity == 'severe' else "unable-to-assess"
}
if reaction_code:
reaction_concept = self.terminology.create_codeable_concept(
reaction_code,
"http://snomed.info/sct"
)
allergy["reaction"] = [{
"manifestation": [{
"coding": reaction_concept.codings,
"text": reaction_concept.text
}],
"severity": severity
}]
return allergy
# ValueSet Management
class ValueSetManager:
"""Manage FHIR ValueSets using OMOPHub terminology."""
def __init__(self, terminology_service: OMOPHubFHIRTerminologyService):
self.terminology = terminology_service
def create_valueset_for_condition_category(self, category: str) -> Dict[str, Any]:
"""
Create a ValueSet for a specific condition category.
Example: Diabetes, Hypertension, etc.
"""
# Define common condition categories with SNOMED codes
category_roots = {
"diabetes": "73211009", # Diabetes mellitus
"hypertension": "38341003", # Hypertensive disorder
"asthma": "195967001", # Asthma
"cardiovascular": "49601007", # Disorder of cardiovascular system
"infectious": "40733004" # Infectious disease
}
root_code = category_roots.get(category.lower())
if not root_code:
raise ValueError(f"Unknown condition category: {category}")
valueset = {
"resourceType": "ValueSet",
"id": f"condition-{category.lower()}",
"url": f"http://example.org/fhir/ValueSet/condition-{category.lower()}",
"name": f"{category.title()}Conditions",
"title": f"{category.title()} Conditions ValueSet",
"status": "active",
"compose": {
"include": [{
"system": "http://snomed.info/sct",
"filter": [{
"property": "concept",
"op": "is-a",
"value": root_code
}]
}]
}
}
return valueset
def create_lab_test_valueset(self, panel_name: str) -> Dict[str, Any]:
"""
Create a ValueSet for laboratory test panels.
"""
# Define common lab panels with LOINC codes
lab_panels = {
"basic_metabolic": [
"2345-7", # Glucose
"2160-0", # Creatinine
"3094-0", # BUN
"2951-2", # Sodium
"2823-3", # Potassium
"2075-0", # Chloride
"2028-9" # CO2
],
"lipid": [
"2093-3", # Total cholesterol
"2085-9", # HDL cholesterol
"2089-1", # LDL cholesterol
"2571-8" # Triglycerides
],
"liver": [
"1742-6", # ALT
"1920-8", # AST
"1975-2", # Bilirubin total
"1968-7", # Bilirubin direct
"2885-2", # Protein total
"1751-7" # Albumin
]
}
codes = lab_panels.get(panel_name.lower())
if not codes:
raise ValueError(f"Unknown lab panel: {panel_name}")
valueset = {
"resourceType": "ValueSet",
"id": f"lab-{panel_name.lower().replace('_', '-')}",
"url": f"http://example.org/fhir/ValueSet/lab-{panel_name.lower().replace('_', '-')}",
"name": f"{panel_name.replace('_', ' ').title()}Panel",
"title": f"{panel_name.replace('_', ' ').title()} Laboratory Panel",
"status": "active",
"compose": {
"include": [{
"system": "http://loinc.org",
"concept": [{"code": code} for code in codes]
}]
}
}
return valueset
def expand_and_validate(self, valueset: Dict[str, Any]) -> ValueSetExpansion:
"""
Expand a ValueSet and validate all included codes.
"""
expansion = self.terminology.expand_valueset(valueset)
# Validate each concept
validated_concepts = []
for concept in expansion.concepts:
validation = self.terminology.validate_code(
concept['code'],
concept['system']
)
if validation['valid']:
validated_concepts.append(concept)
else:
self.terminology.logger.warning(
f"Invalid concept in ValueSet: {concept['code']} in {concept['system']}"
)
expansion.concepts = validated_concepts
expansion.total = len(validated_concepts)
return expansion
# ConceptMap Translation Service
class ConceptMapService:
"""Manage FHIR ConceptMaps for terminology translation."""
def __init__(self, terminology_service: OMOPHubFHIRTerminologyService):
self.terminology = terminology_service
def create_concept_map(self,
source_system: str,
target_system: str,
codes: List[str]) -> Dict[str, Any]:
"""
Create a ConceptMap for translating between code systems.
"""
concept_map = {
"resourceType": "ConceptMap",
"id": f"map-{source_system.split('/')[-1]}-to-{target_system.split('/')[-1]}",
"url": f"http://example.org/fhir/ConceptMap/{source_system.split('/')[-1]}-to-{target_system.split('/')[-1]}",
"name": f"{source_system.split('/')[-1]}To{target_system.split('/')[-1]}Map",
"status": "active",
"sourceUri": source_system,
"targetUri": target_system,
"group": [{
"source": source_system,
"target": target_system,
"element": []
}]
}
# Add mappings for each code
for code in codes:
translations = self.terminology.translate_concept(code, source_system, target_system)
if translations:
element = {
"code": code,
"display": self.terminology.lookup_display(code, source_system),
"target": []
}
for trans in translations:
element["target"].append({
"code": trans.target_code,
"display": self.terminology.lookup_display(
trans.target_code,
trans.target_system
),
"equivalence": trans.equivalence
})
concept_map["group"][0]["element"].append(element)
return concept_map
def translate_bulk(self,
codes: List[str],
source_system: str,
target_system: str) -> List[Dict[str, Any]]:
"""
Bulk translate multiple codes between systems.
"""
translations = []
for code in codes:
trans_list = self.terminology.translate_concept(code, source_system, target_system)
if trans_list:
# Use the highest confidence translation
best_trans = max(trans_list, key=lambda x: x.confidence)
translations.append({
'source_code': code,
'source_display': self.terminology.lookup_display(code, source_system),
'target_code': best_trans.target_code,
'target_display': self.terminology.lookup_display(
best_trans.target_code,
best_trans.target_system
),
'equivalence': best_trans.equivalence,
'confidence': best_trans.confidence
})
else:
translations.append({
'source_code': code,
'source_display': self.terminology.lookup_display(code, source_system),
'target_code': None,
'target_display': None,
'equivalence': 'unmapped',
'confidence': 0.0
})
return translations
# Example usage
def main():
# Initialize services
terminology_service = OMOPHubFHIRTerminologyService("your-api-key")
resource_builder = FHIRResourceBuilder(terminology_service)
valueset_manager = ValueSetManager(terminology_service)
conceptmap_service = ConceptMapService(terminology_service)
# Example 1: Create and validate a FHIR Observation
observation = resource_builder.create_observation(
loinc_code="2345-7", # Glucose
value=110,
patient_id="patient123",
effective_datetime="2024-03-15T10:30:00Z"
)
print("Created Observation:", json.dumps(observation, indent=2))
# Example 2: Create a Condition with ICD-10 to SNOMED translation
condition = resource_builder.create_condition(
diagnosis_code="E11.9", # Type 2 diabetes
code_system="http://hl7.org/fhir/sid/icd-10-cm",
patient_id="patient123",
onset_date="2024-01-15"
)
print("Created Condition:", json.dumps(condition, indent=2))
# Example 3: Create and expand a ValueSet
diabetes_valueset = valueset_manager.create_valueset_for_condition_category("diabetes")
expanded = valueset_manager.expand_and_validate(diabetes_valueset)
print(f"Expanded ValueSet contains {expanded.total} concepts")
# Example 4: Create a ConceptMap for ICD-10 to SNOMED
icd_codes = ["E11.9", "I10", "J45.909"]
concept_map = conceptmap_service.create_concept_map(
"http://hl7.org/fhir/sid/icd-10-cm",
"http://snomed.info/sct",
icd_codes
)
print("Created ConceptMap:", json.dumps(concept_map, indent=2))
# Example 5: Bulk translate codes
translations = conceptmap_service.translate_bulk(
icd_codes,
"http://hl7.org/fhir/sid/icd-10-cm",
"http://snomed.info/sct"
)
for trans in translations:
print(f"{trans['source_code']} -> {trans['target_code']} ({trans['equivalence']})")
if __name__ == "__main__":
main()
```
```javascript JavaScript
const axios = require('axios');
class OMOPHubFHIRTerminologyService {
constructor(apiKey, baseUrl = 'https://api.omophub.com') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.headers = { 'Authorization': `Bearer ${apiKey}` };
// FHIR system URIs
this.systemUris = {
SNOMED: 'http://snomed.info/sct',
LOINC: 'http://loinc.org',
ICD10CM: 'http://hl7.org/fhir/sid/icd-10-cm',
RXNORM: 'http://www.nlm.nih.gov/research/umls/rxnorm',
HCPCS: 'https://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets'
};
}
async validateCode(code, system, display = null) {
try {
const vocabularyId = this.getVocabularyId(system);
const response = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: code,
vocabulary_ids: vocabularyId,
page_size: 1
}
});
if (response.data.data && response.data.data.length > 0) {
const concept = response.data.data[0];
const valid = concept.concept_code === code;
let displayValid = true;
if (display && valid) {
displayValid = concept.concept_name.toLowerCase().includes(display.toLowerCase());
}
return {
valid: valid && displayValid,
concept,
message: valid ? 'Code is valid' : `Code ${code} not found in ${system}`
};
}
return {
valid: false,
message: `Code ${code} not found in ${system}`
};
} catch (error) {
console.error('Error validating code:', error.message);
return {
valid: false,
message: `Error validating code: ${error.message}`
};
}
}
async expandValueSet(valuesetDefinition) {
const expandedConcepts = [];
const includes = valuesetDefinition.compose?.include || [];
for (const include of includes) {
const system = include.system;
if (!system) continue;
const vocabularyId = this.getVocabularyId(system);
if (include.concept) {
// Explicit concept list
for (const conceptRef of include.concept) {
const code = conceptRef.code;
if (code) {
const result = await this.validateCode(code, system);
if (result.valid) {
expandedConcepts.push({
system,
code,
display: result.concept.concept_name
});
}
}
}
} else if (include.filter) {
// Filter-based inclusion
for (const filter of include.filter) {
if (filter.property === 'concept' && filter.op === 'is-a') {
const descendants = await this.getConceptDescendants(filter.value, vocabularyId);
for (const desc of descendants) {
expandedConcepts.push({
system,
code: desc.concept_code,
display: desc.concept_name
});
}
}
}
}
}
// Process exclusions
const excludes = valuesetDefinition.compose?.exclude || [];
for (const exclude of excludes) {
if (exclude.concept) {
for (const conceptRef of exclude.concept) {
const index = expandedConcepts.findIndex(
c => c.system === exclude.system && c.code === conceptRef.code
);
if (index > -1) {
expandedConcepts.splice(index, 1);
}
}
}
}
return {
id: valuesetDefinition.id || 'expanded-valueset',
name: valuesetDefinition.name || 'Expanded ValueSet',
url: valuesetDefinition.url || '',
concepts: expandedConcepts,
total: expandedConcepts.length,
timestamp: new Date().toISOString()
};
}
async translateConcept(code, sourceSystem, targetSystem) {
const translations = [];
try {
const sourceVocab = this.getVocabularyId(sourceSystem);
const targetVocab = this.getVocabularyId(targetSystem);
// Get source concept
const sourceResponse = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: code,
vocabulary_ids: sourceVocab,
page_size: 1
}
});
if (!sourceResponse.data.data || sourceResponse.data.data.length === 0) {
return translations;
}
const sourceConceptId = sourceResponse.data.data[0].concept_id;
// Get mappings
const mappingsResponse = await axios.get(
`${this.baseUrl}/v1/concepts/${sourceConceptId}/mappings`,
{
headers: this.headers,
params: { target_vocabularies: targetVocab }
}
);
for (const mapping of mappingsResponse.data.data || []) {
if (mapping.target_vocabulary_id === targetVocab) {
translations.push({
sourceCode: code,
sourceSystem,
targetCode: mapping.target_concept_code,
targetSystem,
equivalence: this.getFhirEquivalence(mapping.relationship_id),
confidence: mapping.confidence || 1.0
});
}
}
return translations;
} catch (error) {
console.error('Error translating concept:', error.message);
return translations;
}
}
async createCodeableConcept(code, system, display = null) {
const validation = await this.validateCode(code, system);
if (validation.valid) {
const concept = validation.concept;
const displayText = display || concept.concept_name;
const codings = [{
system,
code,
display: displayText
}];
// Add additional codings from mappings if available
try {
if (concept.standard_concept === 'S') {
const mappingsResponse = await axios.get(
`${this.baseUrl}/v1/concepts/${concept.concept_id}/mappings`,
{
headers: this.headers,
params: { relationship_types: 'Maps to' }
}
);
const mappings = mappingsResponse.data.data || [];
for (const mapping of mappings.slice(0, 2)) {
const targetSystem = this.getFhirSystem(mapping.target_vocabulary_id);
if (targetSystem) {
codings.push({
system: targetSystem,
code: mapping.target_concept_code,
display: mapping.target_concept_name || ''
});
}
}
}
} catch (error) {
// Additional codings are optional
}
return {
text: displayText,
codings
};
} else {
return {
text: display || code,
codings: [{
system,
code,
display: display || code
}]
};
}
}
getVocabularyId(systemUri) {
// Reverse lookup from system URI
for (const [vocab, uri] of Object.entries(this.systemUris)) {
if (uri === systemUri) {
return vocab;
}
}
throw new Error(`Unknown system URI: ${systemUri}`);
}
getFhirSystem(vocabularyId) {
return this.systemUris[vocabularyId];
}
getFhirEquivalence(relationshipId) {
const equivalenceMap = {
'Maps to': 'equivalent',
'Is a': 'subsumes',
'Subsumes': 'subsumes'
};
return equivalenceMap[relationshipId] || 'relatedto';
}
async getConceptDescendants(code, vocabularyId) {
try {
const searchResponse = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: code,
vocabulary_ids: vocabularyId,
page_size: 1
}
});
if (!searchResponse.data.data || searchResponse.data.data.length === 0) {
return [];
}
const conceptId = searchResponse.data.data[0].concept_id;
const hierarchyResponse = await axios.get(
`${this.baseUrl}/v1/concepts/${conceptId}/hierarchy`,
{
headers: this.headers,
params: {
include_descendants: true,
max_levels: 10
}
}
);
const descendants = [];
for (const item of hierarchyResponse.data.data || []) {
if (item.level > 0) {
descendants.push(item.concept);
}
}
return descendants;
} catch (error) {
console.error('Error getting descendants:', error.message);
return [];
}
}
async lookupDisplay(system, code) {
try {
const vocabularyId = this.getVocabularyId(system);
const response = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: code,
vocabulary_ids: vocabularyId,
page_size: 1
}
});
const concepts = response.data?.data || [];
if (concepts.length > 0 && concepts[0].concept_code === code) {
return concepts[0].concept_name;
}
return code; // Fallback to code if display not found
} catch (error) {
console.warn(`Failed to lookup display for ${system}|${code}:`, error.message);
return code; // Fallback to code on error
}
}
}
// FHIR Resource Builder
class FHIRResourceBuilder {
constructor(terminologyService) {
this.terminology = terminologyService;
}
async createObservation(loincCode, value, patientId, effectiveDatetime, status = 'final') {
const codeableConcept = await this.terminology.createCodeableConcept(
loincCode,
'http://loinc.org'
);
const observation = {
resourceType: 'Observation',
status,
code: {
coding: codeableConcept.codings,
text: codeableConcept.text
},
subject: {
reference: `Patient/${patientId}`
},
effectiveDateTime: effectiveDatetime
};
// Add value based on type
if (typeof value === 'number') {
observation.valueQuantity = {
value,
unit: 'mg/dL',
system: 'http://unitsofmeasure.org',
code: 'mg/dL'
};
} else if (typeof value === 'string') {
observation.valueString = value;
}
return observation;
}
async createCondition(diagnosisCode, codeSystem, patientId, onsetDate) {
const codeableConcept = await this.terminology.createCodeableConcept(
diagnosisCode,
codeSystem
);
// Try to add SNOMED translation if not already SNOMED
if (codeSystem !== 'http://snomed.info/sct') {
const translations = await this.terminology.translateConcept(
diagnosisCode,
codeSystem,
'http://snomed.info/sct'
);
for (const trans of translations) {
if (trans.equivalence === 'equivalent') {
const display = await this.terminology.lookupDisplay(trans.targetSystem, trans.targetCode);
codeableConcept.codings.push({
system: trans.targetSystem,
code: trans.targetCode,
display: display
});
break;
}
}
}
return {
resourceType: 'Condition',
clinicalStatus: {
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/condition-clinical',
code: 'active'
}]
},
verificationStatus: {
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/condition-ver-status',
code: 'confirmed'
}]
},
code: {
coding: codeableConcept.codings,
text: codeableConcept.text
},
subject: {
reference: `Patient/${patientId}`
},
onsetDateTime: onsetDate
};
}
}
// Example usage
async function main() {
const terminologyService = new OMOPHubFHIRTerminologyService('your-api-key');
const resourceBuilder = new FHIRResourceBuilder(terminologyService);
// Create FHIR Observation
const observation = await resourceBuilder.createObservation(
'2345-7', // Glucose
110,
'patient123',
'2024-03-15T10:30:00Z'
);
console.log('Created Observation:', JSON.stringify(observation, null, 2));
// Create FHIR Condition
const condition = await resourceBuilder.createCondition(
'E11.9', // Type 2 diabetes
'http://hl7.org/fhir/sid/icd-10-cm',
'patient123',
'2024-01-15'
);
console.log('Created Condition:', JSON.stringify(condition, null, 2));
// Validate a code
const validation = await terminologyService.validateCode(
'38341003',
'http://snomed.info/sct'
);
console.log('Code validation:', validation);
// Translate concept
const translations = await terminologyService.translateConcept(
'E11.9',
'http://hl7.org/fhir/sid/icd-10-cm',
'http://snomed.info/sct'
);
console.log('Translations:', translations);
}
if (require.main === module) {
main().catch(console.error);
}
module.exports = { OMOPHubFHIRTerminologyService, FHIRResourceBuilder };
```
## Implementation Examples
### Example 1: SMART on FHIR Application
```python Python
import httpx
from typing import Dict
class SmartOnFhirApp:
"""
SMART on FHIR application using OMOPHub for terminology.
"""
def __init__(self, omophub_api_key: str, fhir_server_url: str):
self.terminology = OMOPHubFHIRTerminologyService(omophub_api_key)
self.resource_builder = FHIRResourceBuilder(self.terminology)
self.fhir_server = fhir_server_url
async def process_patient_encounter(self, encounter_data: Dict) -> Dict:
"""
Process a patient encounter and create FHIR resources.
"""
patient_id = encounter_data['patient_id']
resources_created = []
# Process diagnoses
for diagnosis in encounter_data.get('diagnoses', []):
condition = self.resource_builder.create_condition(
diagnosis_code=diagnosis['code'],
code_system=diagnosis['system'],
patient_id=patient_id,
onset_date=diagnosis['date']
)
# Post to FHIR server
response = await self.post_to_fhir_server(condition)
resources_created.append({
'type': 'Condition',
'id': response.get('id'),
'code': diagnosis['code']
})
# Process lab results
for lab in encounter_data.get('labs', []):
observation = self.resource_builder.create_observation(
loinc_code=lab['loinc_code'],
value=lab['value'],
patient_id=patient_id,
effective_datetime=lab['datetime']
)
response = await self.post_to_fhir_server(observation)
resources_created.append({
'type': 'Observation',
'id': response.get('id'),
'code': lab['loinc_code']
})
# Process medications
for med in encounter_data.get('medications', []):
med_request = self.resource_builder.create_medication_request(
rxnorm_code=med['rxnorm_code'],
patient_id=patient_id,
prescriber_id=med['prescriber_id'],
dosage_instructions=med['instructions'],
quantity=med['quantity']
)
response = await self.post_to_fhir_server(med_request)
resources_created.append({
'type': 'MedicationRequest',
'id': response.get('id'),
'code': med['rxnorm_code']
})
return {
'encounter_id': encounter_data['encounter_id'],
'resources_created': resources_created,
'status': 'completed'
}
async def post_to_fhir_server(self, resource: Dict) -> Dict:
"""Post a FHIR resource to the server."""
headers = {
'Content-Type': 'application/fhir+json',
'Accept': 'application/fhir+json'
}
timeout = httpx.Timeout(connect=10.0, read=30.0, write=10.0, pool=5.0)
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(
f"{self.fhir_server}/{resource['resourceType']}",
json=resource,
headers=headers
)
if response.status_code in [200, 201]:
return response.json()
else:
raise Exception(f"Failed to post resource: HTTP {response.status_code} - {response.text}")
```
### Example 2: Bulk FHIR Export with Terminology Validation
```python Python
class FHIRBulkExporter:
"""
Export clinical data in FHIR Bulk Data format with terminology validation.
"""
def __init__(self, omophub_api_key: str):
self.terminology = OMOPHubFHIRTerminologyService(omophub_api_key)
self.resource_builder = FHIRResourceBuilder(self.terminology)
def export_patient_data(self, patients_data: List[Dict], output_dir: str):
"""
Export patient data as NDJSON files per FHIR Bulk Data spec.
"""
import os
import ndjson
# Prepare output files
os.makedirs(output_dir, exist_ok=True)
conditions_file = open(os.path.join(output_dir, 'Condition.ndjson'), 'w')
observations_file = open(os.path.join(output_dir, 'Observation.ndjson'), 'w')
medications_file = open(os.path.join(output_dir, 'MedicationRequest.ndjson'), 'w')
export_summary = {
'patients_processed': 0,
'conditions_exported': 0,
'observations_exported': 0,
'medications_exported': 0,
'validation_errors': []
}
for patient_data in patients_data:
patient_id = patient_data['patient_id']
# Export conditions with validation
for condition_data in patient_data.get('conditions', []):
try:
# Validate code first
validation = self.terminology.validate_code(
condition_data['code'],
condition_data['system']
)
if validation['valid']:
condition = self.resource_builder.create_condition(
diagnosis_code=condition_data['code'],
code_system=condition_data['system'],
patient_id=patient_id,
onset_date=condition_data['onset_date']
)
conditions_file.write(json.dumps(condition) + '\n')
export_summary['conditions_exported'] += 1
else:
export_summary['validation_errors'].append({
'patient_id': patient_id,
'resource_type': 'Condition',
'code': condition_data['code'],
'error': validation['message']
})
except Exception as e:
export_summary['validation_errors'].append({
'patient_id': patient_id,
'error': str(e)
})
# Export observations with validation
for obs_data in patient_data.get('observations', []):
try:
validation = self.terminology.validate_code(
obs_data['loinc_code'],
'http://loinc.org'
)
if validation['valid']:
observation = self.resource_builder.create_observation(
loinc_code=obs_data['loinc_code'],
value=obs_data['value'],
patient_id=patient_id,
effective_datetime=obs_data['datetime']
)
observations_file.write(json.dumps(observation) + '\n')
export_summary['observations_exported'] += 1
except Exception as e:
export_summary['validation_errors'].append({
'patient_id': patient_id,
'error': str(e)
})
export_summary['patients_processed'] += 1
# Close files
conditions_file.close()
observations_file.close()
medications_file.close()
# Write export manifest
manifest = {
"transactionTime": datetime.utcnow().isoformat(),
"request": "$export",
"requiresAccessToken": True,
"output": [
{
"type": "Condition",
"url": f"file://{output_dir}/Condition.ndjson",
"count": export_summary['conditions_exported']
},
{
"type": "Observation",
"url": f"file://{output_dir}/Observation.ndjson",
"count": export_summary['observations_exported']
},
{
"type": "MedicationRequest",
"url": f"file://{output_dir}/MedicationRequest.ndjson",
"count": export_summary['medications_exported']
}
],
"error": []
}
with open(os.path.join(output_dir, 'manifest.json'), 'w') as f:
json.dump(manifest, f, indent=2)
return export_summary
```
### Example 3: FHIR Terminology Server Implementation
```python Python
from flask import Flask, request, jsonify
class FHIRTerminologyServer:
"""
Implement FHIR terminology server operations using OMOPHub.
"""
def __init__(self, omophub_api_key: str):
self.app = Flask(__name__)
self.terminology = OMOPHubFHIRTerminologyService(omophub_api_key)
self.setup_routes()
def setup_routes(self):
"""Setup FHIR terminology operation endpoints."""
@self.app.route('/CodeSystem/$validate-code', methods=['POST'])
def validate_code():
"""FHIR $validate-code operation."""
params = request.get_json()
code = params.get('code')
system = params.get('system')
display = params.get('display')
if not code or not system:
return jsonify({
'resourceType': 'OperationOutcome',
'issue': [{
'severity': 'error',
'code': 'required',
'diagnostics': 'Code and system are required'
}]
}), 400
result = self.terminology.validate_code(code, system, display)
return jsonify({
'resourceType': 'Parameters',
'parameter': [
{
'name': 'result',
'valueBoolean': result['valid']
},
{
'name': 'message',
'valueString': result['message']
}
]
})
@self.app.route('/ValueSet/$expand', methods=['POST'])
def expand_valueset():
"""FHIR $expand operation."""
valueset = request.get_json()
try:
expansion = self.terminology.expand_valueset(valueset)
return jsonify({
'resourceType': 'ValueSet',
'id': expansion.id,
'url': expansion.url,
'name': expansion.name,
'status': 'active',
'expansion': {
'timestamp': expansion.timestamp,
'total': expansion.total,
'contains': expansion.concepts
}
})
except Exception as e:
return jsonify({
'resourceType': 'OperationOutcome',
'issue': [{
'severity': 'error',
'code': 'exception',
'diagnostics': str(e)
}]
}), 500
@self.app.route('/ConceptMap/$translate', methods=['POST'])
def translate():
"""FHIR $translate operation."""
params = request.get_json()
code = params.get('code')
source = params.get('source')
target = params.get('target')
translations = self.terminology.translate_concept(code, source, target)
matches = []
for trans in translations:
matches.append({
'equivalence': trans.equivalence,
'concept': {
'code': trans.target_code,
'system': trans.target_system
}
})
return jsonify({
'resourceType': 'Parameters',
'parameter': [
{
'name': 'result',
'valueBoolean': len(matches) > 0
},
{
'name': 'match',
'part': matches
}
]
})
@self.app.route('/CodeSystem/$subsumes', methods=['POST'])
def subsumes():
"""FHIR $subsumes operation."""
params = request.get_json()
code_a = params.get('codeA')
code_b = params.get('codeB')
system = params.get('system')
result = self.terminology.subsumes(code_a, code_b, system)
return jsonify({
'resourceType': 'Parameters',
'parameter': [{
'name': 'outcome',
'valueCode': result or 'not-subsumed'
}]
})
def run(self, host='0.0.0.0', port=5000):
"""Run the terminology server."""
self.app.run(host=host, port=port)
# Usage
if __name__ == '__main__':
server = FHIRTerminologyServer('your-api-key')
server.run()
```
## Best Practices
### 1. Efficient ValueSet Management
```python Python
class OptimizedValueSetManager:
"""Optimized ValueSet operations with caching."""
def __init__(self, terminology_service: OMOPHubFHIRTerminologyService):
self.terminology = terminology_service
self.cache = {}
self.cache_ttl = 3600 # 1 hour
def get_or_expand_valueset(self, valueset_url: str) -> ValueSetExpansion:
"""Get ValueSet expansion with caching."""
cache_key = f"valueset:{valueset_url}"
# Check cache
if cache_key in self.cache:
cached_data, timestamp = self.cache[cache_key]
if time.time() - timestamp < self.cache_ttl:
return cached_data
# Expand ValueSet
valueset = self.load_valueset_definition(valueset_url)
expansion = self.terminology.expand_valueset(valueset)
# Cache result
self.cache[cache_key] = (expansion, time.time())
return expansion
def validate_against_valueset(self, code: str, system: str, valueset_url: str) -> bool:
"""Validate if a code is in a ValueSet."""
expansion = self.get_or_expand_valueset(valueset_url)
for concept in expansion.concepts:
if concept['code'] == code and concept['system'] == system:
return True
return False
```
### 2. Batch Operations for Performance
```python Python
class BatchFHIRProcessor:
"""Process FHIR resources in batches for performance."""
def __init__(self, terminology_service: OMOPHubFHIRTerminologyService):
self.terminology = terminology_service
self.batch_size = 100
async def validate_codes_batch(self, codes: List[Tuple[str, str]]) -> List[Dict]:
"""Validate multiple codes in parallel."""
import asyncio
tasks = []
for code, system in codes:
task = asyncio.create_task(
self.terminology.validate_code(code, system)
)
tasks.append(task)
results = await asyncio.gather(*tasks)
return [
{
'code': codes[i][0],
'system': codes[i][1],
'valid': results[i]['valid'],
'message': results[i].get('message', '')
}
for i in range(len(codes))
]
def translate_batch(self,
translations: List[Tuple[str, str, str]]) -> List[ConceptMapTranslation]:
"""Batch translate concepts between systems."""
all_translations = []
for batch_start in range(0, len(translations), self.batch_size):
batch = translations[batch_start:batch_start + self.batch_size]
for code, source, target in batch:
trans = self.terminology.translate_concept(code, source, target)
all_translations.extend(trans)
return all_translations
```
### 3. Error Handling and Resilience
```python Python
class ResilientFHIRService:
"""FHIR service with error handling and fallbacks."""
def __init__(self, terminology_service: OMOPHubFHIRTerminologyService):
self.terminology = terminology_service
self.fallback_mappings = self.load_fallback_mappings()
def safe_validate_code(self, code: str, system: str) -> Dict:
"""Validate code with fallback to local mappings."""
try:
return self.terminology.validate_code(code, system)
except Exception as e:
# Fallback to local mappings
if system in self.fallback_mappings:
if code in self.fallback_mappings[system]:
return {
'valid': True,
'concept': self.fallback_mappings[system][code],
'message': 'Validated from local cache'
}
return {
'valid': False,
'message': f'Validation failed: {str(e)}'
}
def load_fallback_mappings(self) -> Dict:
"""Load common codes for offline validation."""
return {
'http://loinc.org': {
'2345-7': {'concept_name': 'Glucose'},
'718-7': {'concept_name': 'Hemoglobin'},
# ... more common codes
},
'http://snomed.info/sct': {
'38341003': {'concept_name': 'Hypertensive disorder'},
'73211009': {'concept_name': 'Diabetes mellitus'},
# ... more common codes
}
}
```
## Support and Resources
For FHIR integration support and questions:
* **Technical Support**: [https://omophub.com/contact](https://omophub.com/contact)
* **FHIR Documentation**: [https://www.hl7.org/fhir/](https://www.hl7.org/fhir/)
* **API Documentation**: [https://docs.omophub.com](https://docs.omophub.com)
# Clinical Coding Automation
Source: https://docs.omophub.com/guides/use-cases/clinical-coding
Automate clinical coding workflows using OMOPHub API for accurate medical billing and compliance
## Overview
Clinical coding is the process of transforming healthcare diagnoses, procedures, medical services, and equipment into universal medical alphanumeric codes. This guide demonstrates how to use the OMOPHub API to automate clinical coding workflows, improve accuracy, and ensure compliance with healthcare standards.
**Use Case**: Automatically convert clinical notes and diagnoses into standardized codes (ICD-10, HCPCS, SNOMED CT) for billing and reporting.
## Business Problem
Healthcare organizations face several challenges with manual clinical coding:
* **Human Error**: Manual coding leads to 15-20% error rates
* **Inconsistency**: Different coders may assign different codes for the same condition
* **Compliance Risk**: Incorrect coding can result in audit failures and penalties
* **Efficiency**: Manual coding is time-consuming and expensive
* **Revenue Loss**: Undercoding leads to lost revenue, overcoding leads to compliance issues
## Solution Architecture
```mermaid
graph TD
A[Clinical Notes/EHR] --> B[Text Extraction]
B --> C[OMOPHub API]
C --> D[Concept Mapping]
D --> E[Code Validation]
E --> F[Billing System]
C --> G[SNOMED CT Search]
C --> H[ICD-10 Mapping]
C --> I[HCPCS Code Lookup]
G --> D
H --> D
I --> D
```
## Implementation Guide
### Step 1: Set Up OMOPHub Client
**Production Warning**: The `extract_medical_terms` method shown below is for demonstration purposes only and uses simple keyword matching. For production healthcare applications, use proper NLP libraries such as:
* **spaCy** with medical models (scispaCy, en\_core\_sci\_md)
* **NLTK** with medical corpora
* **Specialized medical NLP**: Amazon Comprehend Medical, Google Healthcare NLP API, or clinical BERT models
* **Open-source medical NLP**: Apache cTAKES, MetaMap, or MedSpacy
Accurate medical term extraction is critical for patient safety and billing compliance.
```python Python
from omophub import OMOPHubClient
import re
import traceback
from datetime import datetime
from typing import List, Dict, Any
class ClinicalCodingService:
def __init__(self, api_key: str):
self.client = OMOPHubClient(api_key=api_key)
def extract_medical_terms(self, clinical_text: str) -> List[str]:
"""Extract potential medical terms from clinical text"""
# Simple term extraction - in production, use NLP libraries
medical_keywords = [
'diabetes', 'hypertension', 'pneumonia', 'fracture',
'myocardial infarction', 'stroke', 'cancer', 'infection'
]
found_terms = []
text_lower = clinical_text.lower()
for keyword in medical_keywords:
if keyword in text_lower:
found_terms.append(keyword)
return found_terms
def get_clinical_text(self, encounter_id: str) -> str:
"""Retrieve clinical text for a given encounter ID
Args:
encounter_id: Unique identifier for the patient encounter
Returns:
Clinical text/notes for the encounter
Note:
This is a placeholder method. In production, integrate with your
EHR system, FHIR server, or clinical data warehouse.
"""
# Placeholder implementation - replace with actual data source
# Examples of integration:
# - FHIR API: GET /Encounter/{encounter_id}/notes
# - EHR database query
# - Document management system API
# For demonstration purposes, return sample clinical text
sample_texts = {
"enc_001": "Patient presents with type 2 diabetes mellitus. Blood glucose elevated at 180 mg/dL. Prescribed metformin 500mg twice daily.",
"enc_002": "Chief complaint: chest pain. Patient has history of hypertension and smoking. EKG shows no acute changes. Troponin negative.",
"enc_003": "Follow-up visit for pneumonia treatment. Patient reports improvement in cough and fever. Chest X-ray shows resolving infiltrates.",
}
# Return sample text or raise error for unknown encounters
if encounter_id in sample_texts:
return sample_texts[encounter_id]
else:
# In production, this would query your actual data source
raise NotImplementedError(
f"Clinical text retrieval not implemented for encounter {encounter_id}. "
"Please integrate with your EHR system, FHIR server, or clinical database."
)
```
```javascript JavaScript
import { OMOPHubClient } from '@omophub/sdk';
class ClinicalCodingService {
constructor(apiKey) {
this.client = new OMOPHubClient({ apiKey });
}
extractMedicalTerms(clinicalText) {
// Simple term extraction - in production, use NLP libraries
const medicalKeywords = [
'diabetes', 'hypertension', 'pneumonia', 'fracture',
'myocardial infarction', 'stroke', 'cancer', 'infection'
];
const foundTerms = [];
const textLower = clinicalText.toLowerCase();
for (const keyword of medicalKeywords) {
if (textLower.includes(keyword)) {
foundTerms.push(keyword);
}
}
return foundTerms;
}
getClinicalText(encounterId) {
/**
* Retrieve clinical text for a given encounter ID
*
* @param {string} encounterId - Unique identifier for the patient encounter
* @returns {string} Clinical text/notes for the encounter
*
* Note: This is a placeholder method. In production, integrate with your
* EHR system, FHIR server, or clinical data warehouse.
*/
// Placeholder implementation - replace with actual data source
// Examples of integration:
// - FHIR API: GET /Encounter/{encounterId}/notes
// - EHR database query
// - Document management system API
// For demonstration purposes, return sample clinical text
const sampleTexts = {
'enc_001': 'Patient presents with type 2 diabetes mellitus. Blood glucose elevated at 180 mg/dL. Prescribed metformin 500mg twice daily.',
'enc_002': 'Chief complaint: chest pain. Patient has history of hypertension and smoking. EKG shows no acute changes. Troponin negative.',
'enc_003': 'Follow-up visit for pneumonia treatment. Patient reports improvement in cough and fever. Chest X-ray shows resolving infiltrates.'
};
// Return sample text or throw error for unknown encounters
if (encounterId in sampleTexts) {
return sampleTexts[encounterId];
} else {
// In production, this would query your actual data source
throw new Error(
`Clinical text retrieval not implemented for encounter ${encounterId}. ` +
'Please integrate with your EHR system, FHIR server, or clinical database.'
);
}
}
}
```
```r R
library(omophub)
# Initialize client
client <- omophub_client(api_key = "your_api_key")
extract_medical_terms <- function(clinical_text) {
# Simple term extraction - in production, use text mining packages
medical_keywords <- c(
"diabetes", "hypertension", "pneumonia", "fracture",
"myocardial infarction", "stroke", "cancer", "infection"
)
found_terms <- c()
text_lower <- tolower(clinical_text)
for (keyword in medical_keywords) {
if (grepl(keyword, text_lower, fixed = TRUE)) {
found_terms <- c(found_terms, keyword)
}
}
return(found_terms)
}
```
### Step 2: Search and Map Medical Concepts
```python Python
def search_medical_concepts(self, terms: List[str]) -> Dict[str, Any]:
"""Search for medical concepts and map to multiple vocabularies"""
results = {}
for term in terms:
try:
# Search for concepts across vocabularies
search_results = self.client.advanced_search_concepts({
"query": term,
"vocabularies": ["SNOMED", "ICD10CM", "HCPCS"],
"domains": ["Condition", "Procedure", "Drug"],
"standard_concepts_only": True,
"limit": 10
})
# Get the best match
if search_results["concepts"]:
best_match = search_results["concepts"][0]
# Get mappings to other vocabularies
mappings = self.client.get_concept_mappings(
best_match["concept_id"],
target_vocabularies=["ICD10CM", "HCPCS", "HCPCS"]
)
results[term] = {
"primary_concept": best_match,
"mappings": mappings,
"confidence": best_match.get("relevance_score", 0)
}
except Exception as e:
print(f"Error processing term '{term}': {e}")
# Collect detailed error information with consistent structure
results[term] = {
"error": str(e),
"primary_concept": None,
"mappings": None,
"confidence": 0,
"error_details": {
"timestamp": datetime.now().isoformat(),
"term": term,
"error_type": type(e).__name__,
"traceback": traceback.format_exc()
}
}
return results
def get_billing_codes(self, concept_mappings: Dict[str, Any]) -> Dict[str, List[str]]:
"""Extract billing codes from concept mappings"""
billing_codes = {
"icd10": [],
"cpt": [],
"hcpcs": []
}
for term, data in concept_mappings.items():
if "mappings" in data:
for mapping in data["mappings"]:
vocab = mapping["vocabulary_id"].lower()
code = mapping["concept_code"]
if "icd10" in vocab:
billing_codes["icd10"].append({
"code": code,
"description": mapping["concept_name"],
"term": term
})
elif "cpt" in vocab:
billing_codes["cpt"].append({
"code": code,
"description": mapping["concept_name"],
"term": term
})
elif "hcpcs" in vocab:
billing_codes["hcpcs"].append({
"code": code,
"description": mapping["concept_name"],
"term": term
})
return billing_codes
```
```javascript JavaScript
async searchMedicalConcepts(terms) {
const results = {};
for (const term of terms) {
try {
// Search for concepts across vocabularies
const searchResults = await this.client.advancedSearchConcepts({
query: term,
vocabularies: ['SNOMED', 'ICD10CM', 'HCPCS'],
domains: ['Condition', 'Procedure', 'Drug'],
standard_concepts_only: true,
limit: 10
});
// Get the best match
if (searchResults.data.concepts.length > 0) {
const bestMatch = searchResults.data.concepts[0];
// Get mappings to other vocabularies
const mappings = await this.client.getConceptMappings(
bestMatch.concept_id,
{ targetVocabularies: ['ICD10CM', 'HCPCS', 'HCPCS'] }
);
results[term] = {
primaryConcept: bestMatch,
mappings: mappings.data,
confidence: bestMatch.relevanceScore || 0
};
}
} catch (error) {
console.error(`Error processing term '${term}':`, error);
// Collect detailed error information with consistent structure
results[term] = {
error: error.message,
primaryConcept: null,
mappings: null,
confidence: 0,
errorDetails: {
timestamp: new Date().toISOString(),
term: term,
errorType: error.constructor.name,
stack: error.stack
}
};
}
}
return results;
}
getBillingCodes(conceptMappings) {
const billingCodes = {
icd10: [],
cpt: [],
hcpcs: []
};
for (const [term, data] of Object.entries(conceptMappings)) {
if (data.mappings) {
for (const mapping of data.mappings) {
const vocab = mapping.vocabulary_id.toLowerCase();
const code = mapping.conceptCode;
if (vocab.includes('icd10')) {
billingCodes.icd10.push({
code,
description: mapping.conceptName,
term
});
} else if (vocab.includes('cpt')) {
billingCodes.cpt.push({
code,
description: mapping.conceptName,
term
});
} else if (vocab.includes('hcpcs')) {
billingCodes.hcpcs.push({
code,
description: mapping.conceptName,
term
});
}
}
}
}
return billingCodes;
}
```
```r R
search_medical_concepts <- function(client, terms) {
results <- list()
for (term in terms) {
tryCatch({
# Search for concepts across vocabularies
search_results <- advanced_search_concepts(client,
query = term,
vocabularies = c("SNOMED", "ICD10CM", "HCPCS"),
domains = c("Condition", "Procedure", "Drug"),
standard_concepts_only = TRUE,
limit = 10
)
# Get the best match
if (nrow(search_results$concepts) > 0) {
best_match <- search_results$concepts[1, ]
# Get mappings to other vocabularies
mappings <- get_concept_mappings(client,
best_match$concept_id,
target_vocabularies = c("ICD10CM", "HCPCS", "HCPCS")
)
results[[term]] <- list(
primary_concept = best_match,
mappings = mappings,
confidence = ifelse(is.null(best_match$relevance_score), 0, best_match$relevance_score)
)
}
}, error = function(e) {
cat(paste("Error processing term", term, ":", e$message, "\n"))
results[[term]] <- list(error = e$message)
})
}
return(results)
}
get_billing_codes <- function(concept_mappings) {
billing_codes <- list(
icd10 = list(),
cpt = list(),
hcpcs = list()
)
for (term in names(concept_mappings)) {
data <- concept_mappings[[term]]
if (!is.null(data$mappings) && nrow(data$mappings) > 0) {
for (i in 1:nrow(data$mappings)) {
mapping <- data$mappings[i, ]
vocab <- tolower(mapping$vocabulary_id)
code <- mapping$concept_code
if (grepl("icd10", vocab)) {
billing_codes$icd10[[length(billing_codes$icd10) + 1]] <- list(
code = code,
description = mapping$concept_name,
term = term
)
} else if (grepl("cpt", vocab)) {
billing_codes$cpt[[length(billing_codes$cpt) + 1]] <- list(
code = code,
description = mapping$concept_name,
term = term
)
} else if (grepl("hcpcs", vocab)) {
billing_codes$hcpcs[[length(billing_codes$hcpcs) + 1]] <- list(
code = code,
description = mapping$concept_name,
term = term
)
}
}
}
}
return(billing_codes)
}
```
### Step 3: Validate and Quality Check
```python Python
def validate_coding_quality(self, billing_codes: Dict[str, List[str]]) -> Dict[str, Any]:
"""Validate coding quality and completeness"""
validation_results = {
"completeness": {},
"conflicts": [],
"recommendations": []
}
# Check completeness
total_terms = len(billing_codes.get("icd10", [])) + len(billing_codes.get("cpt", []))
validation_results["completeness"]["total_codes"] = total_terms
validation_results["completeness"]["has_diagnosis"] = len(billing_codes.get("icd10", [])) > 0
validation_results["completeness"]["has_procedure"] = len(billing_codes.get("cpt", [])) > 0
# Check for conflicts (same term mapped to multiple codes)
seen_terms = {}
for code_type, codes in billing_codes.items():
for code_data in codes:
term = code_data["term"]
if term in seen_terms:
if seen_terms[term] != code_type:
validation_results["conflicts"].append({
"term": term,
"vocabularies": [seen_terms[term], code_type],
"message": f"Term '{term}' mapped to multiple vocabularies"
})
seen_terms[term] = code_type
# Generate recommendations
if not validation_results["completeness"]["has_diagnosis"]:
validation_results["recommendations"].append({
"type": "missing_diagnosis",
"message": "No diagnosis codes found. Consider adding ICD-10 codes."
})
if not validation_results["completeness"]["has_procedure"]:
validation_results["recommendations"].append({
"type": "missing_procedure",
"message": "No procedure codes found. Consider adding HCPCS codes."
})
return validation_results
def generate_coding_report(self, clinical_text: str) -> Dict[str, Any]:
"""Generate complete coding report for clinical text"""
# Extract terms
terms = self.extract_medical_terms(clinical_text)
# Search and map concepts
concept_mappings = self.search_medical_concepts(terms)
# Extract billing codes
billing_codes = self.get_billing_codes(concept_mappings)
# Validate quality
validation = self.validate_coding_quality(billing_codes)
return {
"input_text": clinical_text,
"extracted_terms": terms,
"concept_mappings": concept_mappings,
"billing_codes": billing_codes,
"validation": validation,
"timestamp": datetime.now().isoformat()
}
```
```javascript JavaScript
validateCodingQuality(billingCodes) {
const validationResults = {
completeness: {},
conflicts: [],
recommendations: []
};
// Check completeness
const totalCodes = (billingCodes.icd10?.length || 0) + (billingCodes.cpt?.length || 0);
validationResults.completeness.totalCodes = totalCodes;
validationResults.completeness.hasDiagnosis = (billingCodes.icd10?.length || 0) > 0;
validationResults.completeness.hasProcedure = (billingCodes.cpt?.length || 0) > 0;
// Check for conflicts
const seenTerms = {};
for (const [codeType, codes] of Object.entries(billingCodes)) {
for (const codeData of codes || []) {
const term = codeData.term;
if (seenTerms[term] && seenTerms[term] !== codeType) {
validationResults.conflicts.push({
term,
vocabularies: [seenTerms[term], codeType],
message: `Term '${term}' mapped to multiple vocabularies`
});
}
seenTerms[term] = codeType;
}
}
// Generate recommendations
if (!validationResults.completeness.hasDiagnosis) {
validationResults.recommendations.push({
type: 'missing_diagnosis',
message: 'No diagnosis codes found. Consider adding ICD-10 codes.'
});
}
if (!validationResults.completeness.hasProcedure) {
validationResults.recommendations.push({
type: 'missing_procedure',
message: 'No procedure codes found. Consider adding HCPCS codes.'
});
}
return validationResults;
}
async generateCodingReport(clinicalText) {
// Extract terms
const terms = this.extractMedicalTerms(clinicalText);
// Search and map concepts
const conceptMappings = await this.searchMedicalConcepts(terms);
// Extract billing codes
const billingCodes = this.getBillingCodes(conceptMappings);
// Validate quality
const validation = this.validateCodingQuality(billingCodes);
return {
inputText: clinicalText,
extractedTerms: terms,
conceptMappings,
billingCodes,
validation,
timestamp: new Date().toISOString()
};
}
```
```r R
validate_coding_quality <- function(billing_codes) {
validation_results <- list(
completeness = list(),
conflicts = list(),
recommendations = list()
)
# Check completeness
total_codes <- length(billing_codes$icd10) + length(billing_codes$cpt)
validation_results$completeness$total_codes <- total_codes
validation_results$completeness$has_diagnosis <- length(billing_codes$icd10) > 0
validation_results$completeness$has_procedure <- length(billing_codes$cpt) > 0
# Check for conflicts
seen_terms <- list()
for (code_type in names(billing_codes)) {
codes <- billing_codes[[code_type]]
for (code_data in codes) {
term <- code_data$term
if (term %in% names(seen_terms)) {
if (seen_terms[[term]] != code_type) {
validation_results$conflicts[[length(validation_results$conflicts) + 1]] <- list(
term = term,
vocabularies = c(seen_terms[[term]], code_type),
message = paste("Term", term, "mapped to multiple vocabularies")
)
}
}
seen_terms[[term]] <- code_type
}
}
# Generate recommendations
if (!validation_results$completeness$has_diagnosis) {
validation_results$recommendations[[length(validation_results$recommendations) + 1]] <- list(
type = "missing_diagnosis",
message = "No diagnosis codes found. Consider adding ICD-10 codes."
)
}
if (!validation_results$completeness$has_procedure) {
validation_results$recommendations[[length(validation_results$recommendations) + 1]] <- list(
type = "missing_procedure",
message = "No procedure codes found. Consider adding HCPCS codes."
)
}
return(validation_results)
}
generate_coding_report <- function(client, clinical_text) {
# Extract terms
terms <- extract_medical_terms(clinical_text)
# Search and map concepts
concept_mappings <- search_medical_concepts(client, terms)
# Extract billing codes
billing_codes <- get_billing_codes(concept_mappings)
# Validate quality
validation <- validate_coding_quality(billing_codes)
return(list(
input_text = clinical_text,
extracted_terms = terms,
concept_mappings = concept_mappings,
billing_codes = billing_codes,
validation = validation,
timestamp = Sys.time()
))
}
```
## Example Implementation
### Sample Clinical Note
```
Patient presents with chest pain and shortness of breath.
ECG shows ST elevation. Diagnosed with acute myocardial infarction.
Patient underwent percutaneous coronary intervention (PCI).
Also has history of type 2 diabetes mellitus and hypertension.
```
### Generated Coding Report
```python Python
# Initialize the service
coding_service = ClinicalCodingService("your_api_key")
# Generate coding report
clinical_note = """
Patient presents with chest pain and shortness of breath.
ECG shows ST elevation. Diagnosed with acute myocardial infarction.
Patient underwent percutaneous coronary intervention (PCI).
Also has history of type 2 diabetes mellitus and hypertension.
"""
report = coding_service.generate_coding_report(clinical_note)
# Print results
print("=== CLINICAL CODING REPORT ===")
print(f"Extracted Terms: {report['extracted_terms']}")
print(f"Total Billing Codes: {report['validation']['completeness']['total_codes']}")
print("\n=== ICD-10 DIAGNOSIS CODES ===")
for code in report['billing_codes']['icd10']:
print(f" {code['code']}: {code['description']}")
print("\n=== HCPCS PROCEDURE CODES ===")
for code in report['billing_codes']['cpt']:
print(f" {code['code']}: {code['description']}")
print("\n=== VALIDATION RESULTS ===")
for rec in report['validation']['recommendations']:
print(f" ⚠️ {rec['message']}")
```
```javascript JavaScript
// Initialize the service
const codingService = new ClinicalCodingService('your_api_key');
// Generate coding report
const clinicalNote = `
Patient presents with chest pain and shortness of breath.
ECG shows ST elevation. Diagnosed with acute myocardial infarction.
Patient underwent percutaneous coronary intervention (PCI).
Also has history of type 2 diabetes mellitus and hypertension.
`;
const report = await codingService.generateCodingReport(clinicalNote);
// Print results
console.log('=== CLINICAL CODING REPORT ===');
console.log(`Extracted Terms: ${report.extractedTerms}`);
console.log(`Total Billing Codes: ${report.validation.completeness.totalCodes}`);
console.log('\n=== ICD-10 DIAGNOSIS CODES ===');
report.billingCodes.icd10?.forEach(code => {
console.log(` ${code.code}: ${code.description}`);
});
console.log('\n=== HCPCS PROCEDURE CODES ===');
report.billingCodes.cpt?.forEach(code => {
console.log(` ${code.code}: ${code.description}`);
});
console.log('\n=== VALIDATION RESULTS ===');
report.validation.recommendations.forEach(rec => {
console.log(` ⚠️ ${rec.message}`);
});
```
```r R
# Initialize the client
client <- omophub_client(api_key = "your_api_key")
# Generate coding report
clinical_note <- "
Patient presents with chest pain and shortness of breath.
ECG shows ST elevation. Diagnosed with acute myocardial infarction.
Patient underwent percutaneous coronary intervention (PCI).
Also has history of type 2 diabetes mellitus and hypertension.
"
report <- generate_coding_report(client, clinical_note)
# Print results
cat("=== CLINICAL CODING REPORT ===\n")
cat(paste("Extracted Terms:", paste(report$extracted_terms, collapse = ", "), "\n"))
cat(paste("Total Billing Codes:", report$validation$completeness$total_codes, "\n"))
cat("\n=== ICD-10 DIAGNOSIS CODES ===\n")
for (code in report$billing_codes$icd10) {
cat(paste(" ", code$code, ": ", code$description, "\n"))
}
cat("\n=== HCPCS PROCEDURE CODES ===\n")
for (code in report$billing_codes$cpt) {
cat(paste(" ", code$code, ": ", code$description, "\n"))
}
cat("\n=== VALIDATION RESULTS ===\n")
for (rec in report$validation$recommendations) {
cat(paste(" ⚠️ ", rec$message, "\n"))
}
```
### Expected Output
```
=== CLINICAL CODING REPORT ===
Extracted Terms: myocardial infarction, diabetes, hypertension
Total Billing Codes: 5
=== ICD-10 DIAGNOSIS CODES ===
I21.9: Acute myocardial infarction, unspecified
E11.9: Type 2 diabetes mellitus without complications
I10: Essential hypertension
=== HCPCS PROCEDURE CODES ===
92928: Percutaneous transcatheter placement of intracoronary stent(s)
93458: Catheter placement in coronary artery(s) for coronary angiography
=== VALIDATION RESULTS ===
✅ Complete diagnosis coding found
✅ Procedure codes identified
⚠️ Consider reviewing for additional comorbidities
```
## Integration Patterns
### 1. EHR Integration
```python
class EHRCodingIntegration:
def __init__(self, omophub_client, ehr_client):
self.omophub = omophub_client
self.ehr = ehr_client
def process_encounter(self, encounter_id: str):
# Get clinical notes from EHR
encounter = self.ehr.get_encounter(encounter_id)
clinical_text = encounter.get('notes', '')
# Generate codes using OMOPHub
coding_service = ClinicalCodingService(self.omophub.config.api_key)
report = coding_service.generate_coding_report(clinical_text)
# Update EHR with suggested codes
suggested_codes = []
for code in report['billing_codes']['icd10']:
suggested_codes.append({
'type': 'diagnosis',
'code': code['code'],
'description': code['description']
})
for code in report['billing_codes']['cpt']:
suggested_codes.append({
'type': 'procedure',
'code': code['code'],
'description': code['description']
})
# Send back to EHR for coder review
self.ehr.update_encounter_codes(encounter_id, suggested_codes)
return report
```
### 2. Real-time Coding Assistant
```javascript
class RealTimeCodingAssistant {
constructor(omophubClient) {
this.client = omophubClient;
this.debounceTimer = null;
}
// Real-time suggestions as user types
async getSuggestions(clinicalText, callback) {
// Debounce to avoid too many API calls
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(async () => {
if (clinicalText.length < 10) return;
try {
const suggestions = await this.client.getSearchAutocomplete({
query: clinicalText,
vocabularies: ['SNOMED', 'ICD10CM'],
limit: 5
});
callback(suggestions.data);
} catch (error) {
console.error('Error getting suggestions:', error);
}
}, 500); // Wait 500ms after user stops typing
}
// Validate codes before submission
async validateCodes(codes) {
const validationResults = [];
for (const code of codes) {
try {
const concept = await this.client.getConceptByCode(
code.vocabulary_id,
code.code
);
validationResults.push({
code: code.code,
valid: concept.data.success,
current: concept.data.validEndDate > new Date().toISOString(),
concept: concept.data
});
} catch (error) {
validationResults.push({
code: code.code,
valid: false,
error: error.message
});
}
}
return validationResults;
}
}
```
## Performance Optimization
### Caching Strategy
```python
import redis
import json
from typing import Optional
class CachedCodingService(ClinicalCodingService):
def __init__(self, api_key: str, redis_url: str = "redis://localhost:6379"):
super().__init__(api_key)
self.redis_client = redis.from_url(redis_url)
self.cache_ttl = 3600 # 1 hour
def _cache_key(self, text: str) -> str:
"""Generate cache key from clinical text"""
import hashlib
return f"coding:{hashlib.md5(text.encode()).hexdigest()}"
def generate_coding_report(self, clinical_text: str) -> Dict[str, Any]:
# Check cache first
cache_key = self._cache_key(clinical_text)
cached_result = self.redis_client.get(cache_key)
if cached_result:
return json.loads(cached_result)
# Generate new report
report = super().generate_coding_report(clinical_text)
# Cache the result
self.redis_client.setex(
cache_key,
self.cache_ttl,
json.dumps(report, default=str)
)
return report
```
### Batch Processing
```python
def process_batch_encounters(self, encounter_ids: List[str]) -> List[Dict[str, Any]]:
"""Process multiple encounters efficiently"""
# Batch extract all terms first
all_terms = []
encounter_terms = {}
for encounter_id in encounter_ids:
clinical_text = self.get_clinical_text(encounter_id)
terms = self.extract_medical_terms(clinical_text)
all_terms.extend(terms)
encounter_terms[encounter_id] = terms
# Deduplicate terms
unique_terms = list(set(all_terms))
# Batch search all unique terms
bulk_searches = [
{"search_id": term, "query": term}
for term in unique_terms
]
bulk_results = self.client.bulk_concept_search({
"searches": bulk_searches,
"version": "latest"
})
# Process results for each encounter
reports = []
for encounter_id in encounter_ids:
terms = encounter_terms[encounter_id]
# Map bulk results back to encounter terms
encounter_mappings = {
term: bulk_results["results"].get(term, {})
for term in terms
}
# Generate report using cached concept data
report = self._generate_report_from_cache(
encounter_id,
encounter_mappings
)
reports.append(report)
return reports
```
## Quality Assurance
### Code Validation Rules
```python
class CodingValidator:
def __init__(self, omophub_client):
self.client = omophub_client
def validate_code_combination(self, diagnosis_codes: List[str],
procedure_codes: List[str]) -> Dict[str, Any]:
"""Validate that procedure codes are appropriate for diagnoses"""
validation_results = {
"valid_combinations": [],
"invalid_combinations": [],
"warnings": []
}
for dx_code in diagnosis_codes:
for proc_code in procedure_codes:
# Check if procedure is clinically appropriate for diagnosis
relationship = self.check_clinical_relationship(dx_code, proc_code)
if relationship["appropriate"]:
validation_results["valid_combinations"].append({
"diagnosis": dx_code,
"procedure": proc_code,
"confidence": relationship["confidence"]
})
else:
validation_results["invalid_combinations"].append({
"diagnosis": dx_code,
"procedure": proc_code,
"reason": relationship["reason"]
})
return validation_results
def check_clinical_relationship(self, dx_code: str, proc_code: str) -> Dict[str, Any]:
"""Check if procedure is clinically appropriate for diagnosis"""
try:
# Get diagnosis concept
dx_concept = self.client.get_concept_by_code("ICD10CM", dx_code)
# Get procedure concept
proc_concept = self.client.get_concept_by_code("HCPCS", proc_code)
# Check relationships between concepts
relationships = self.client.get_concept_relationships(
dx_concept["concept_id"],
relationship_types=["Has procedure", "Treatment of"]
)
# Look for connection to procedure concept
for rel in relationships:
if rel["target_concept_id"] == proc_concept["concept_id"]:
return {
"appropriate": True,
"confidence": 0.9,
"relationship": rel["relationship_type"]
}
# No direct relationship found
return {
"appropriate": False,
"confidence": 0.1,
"reason": "No clinical relationship found between diagnosis and procedure"
}
except Exception as e:
return {
"appropriate": False,
"confidence": 0.0,
"reason": f"Error validating relationship: {str(e)}"
}
```
## Metrics and Monitoring
### Coding Accuracy Metrics
```python
class CodingMetrics:
def __init__(self, omophub_client):
self.client = omophub_client
def calculate_coding_accuracy(self, automated_codes: List[str],
manual_codes: List[str]) -> Dict[str, float]:
"""Calculate accuracy metrics comparing automated vs manual coding"""
# Convert to sets for easier comparison
auto_set = set(automated_codes)
manual_set = set(manual_codes)
# Calculate precision, recall, F1
true_positives = len(auto_set.intersection(manual_set))
false_positives = len(auto_set - manual_set)
false_negatives = len(manual_set - auto_set)
precision = true_positives / (true_positives + false_positives) if (true_positives + false_positives) > 0 else 0
recall = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
return {
"precision": precision,
"recall": recall,
"f1_score": f1_score,
"accuracy": true_positives / len(manual_set) if len(manual_set) > 0 else 0
}
def track_coding_trends(self, time_period: str = "30d") -> Dict[str, Any]:
"""Track coding trends and patterns"""
# This would integrate with your analytics system
# to track metrics over time
pass
```
## Best Practices
### 1. Error Handling
```python
def robust_coding_search(self, term: str, max_retries: int = 3) -> Optional[Dict]:
"""Robust search with retry logic and fallback strategies"""
for attempt in range(max_retries):
try:
# Try exact search first
results = self.client.search_concepts({
"query": term,
"vocabularies": ["SNOMED"],
"limit": 1
})
if results["concepts"]:
return results["concepts"][0]
# Fallback to fuzzy search
fuzzy_results = self.client.advanced_search_concepts({
"query": term,
"vocabularies": ["SNOMED", "ICD10CM"],
"include_invalid": False,
"limit": 5
})
if fuzzy_results["concepts"]:
return fuzzy_results["concepts"][0]
except Exception as e:
if attempt == max_retries - 1:
logger.error(f"Failed to search for term '{term}': {e}")
return None
time.sleep(2 ** attempt) # Exponential backoff
return None
```
### 2. Security and Compliance
```python
def secure_coding_process(self, clinical_text: str, user_id: str) -> Dict[str, Any]:
"""HIPAA-compliant coding process with audit logging"""
# Log access for audit
audit_log = {
"user_id": user_id,
"action": "clinical_coding",
"timestamp": datetime.now().isoformat(),
"text_length": len(clinical_text),
"text_hash": hashlib.sha256(clinical_text.encode()).hexdigest()
}
try:
# Process coding (text is not logged)
report = self.generate_coding_report(clinical_text)
# Log successful completion
audit_log["status"] = "success"
audit_log["codes_generated"] = len(report["billing_codes"])
# Remove sensitive data before returning
sanitized_report = self.sanitize_report(report)
return sanitized_report
except Exception as e:
audit_log["status"] = "error"
audit_log["error"] = str(e)
raise
finally:
# Always log audit information
self.log_audit_event(audit_log)
```
## Next Steps
Learn how to integrate with Electronic Health Records
Master advanced search capabilities for better concept matching
Understand cross-vocabulary concept mapping
Optimize your coding workflows for better performance
# Clinical Trial Eligibility Screening
Source: https://docs.omophub.com/guides/use-cases/clinical-trial-eligibility
Automate patient screening for clinical trials using OMOPHub API with SNOMED concept hierarchies and eligibility criteria matching
## Overview
Clinical trial eligibility screening is a critical process for matching patients to appropriate research studies. This guide demonstrates how to use the OMOPHub API with SNOMED CT and other medical vocabularies to automate patient screening against complex inclusion and exclusion criteria.
**Use Case**: Automatically identify eligible patients for clinical trials by matching patient characteristics against structured eligibility criteria, improving recruitment efficiency and reducing screening time.
## Business Problem
Clinical trial recruitment faces significant challenges:
* **Low Enrollment Rates**: Only 3-5% of cancer patients participate in clinical trials
* **Manual Screening**: Labor-intensive process to review patient records
* **Complex Criteria**: Trials have 50+ inclusion/exclusion criteria on average
* **Time Constraints**: 80% of trials fail to meet enrollment targets on time
* **Cost Impact**: Delayed enrollment costs $600K-$8M per day for Phase III trials
## Solution Architecture
```mermaid
graph TD
A[Clinical Trial Criteria] --> B[Criteria Parser]
C[Patient Medical Records] --> D[Data Extraction]
B --> E[OMOPHub API]
D --> E
E --> F[Concept Mapping]
E --> G[Hierarchy Analysis]
E --> H[Relationship Matching]
F --> I[Eligibility Engine]
G --> I
H --> I
I --> J[Screening Results]
J --> K[Eligible Patients]
J --> L[Exclusion Reasons]
J --> M[Partial Matches]
K --> N[Recruitment Dashboard]
L --> O[Screening Analytics]
M --> P[Manual Review Queue]
style F fill:#333333
style G fill:#333333
style H fill:#333333
style I fill:#333333
```
## Implementation Guide
### Step 1: Set Up Trial Eligibility Engine
```python Python
from omophub import OMOPHubClient
from typing import List, Dict, Any, Optional, Union
from dataclasses import dataclass
from enum import Enum
import re
import logging
from datetime import datetime, timedelta
class CriteriaType(Enum):
INCLUSION = "inclusion"
EXCLUSION = "exclusion"
class MatchType(Enum):
EXACT = "exact"
HIERARCHICAL = "hierarchical"
SEMANTIC = "semantic"
NUMERICAL = "numerical"
@dataclass
class EligibilityCriteria:
id: str
type: CriteriaType
description: str
concept_codes: List[str]
vocabularies: List[str]
match_type: MatchType
numerical_constraint: Optional[Dict[str, Any]] = None
age_constraint: Optional[Dict[str, Any]] = None
temporal_constraint: Optional[Dict[str, Any]] = None
@dataclass
class PatientData:
patient_id: str
age: int
gender: str
conditions: List[Dict[str, Any]]
medications: List[Dict[str, Any]]
lab_results: List[Dict[str, Any]]
procedures: List[Dict[str, Any]]
vitals: List[Dict[str, Any]]
@dataclass
class ScreeningResult:
patient_id: str
trial_id: str
eligible: bool
confidence_score: float
matched_criteria: List[str]
failed_criteria: List[str]
partial_matches: List[str]
exclusion_reasons: List[str]
recommendation: str
class ClinicalTrialEligibilityEngine:
def __init__(self, api_key: str):
self.client = OMOPHubClient(api_key=api_key)
self.logger = logging.getLogger(__name__)
# Common condition mappings for faster lookup
self.condition_cache = {}
# Demographic constraints
self.demographic_validators = {
'age': self.validate_age_constraint,
'gender': self.validate_gender_constraint,
'pregnancy': self.validate_pregnancy_constraint
}
def screen_patient(self, patient: PatientData, trial_criteria: List[EligibilityCriteria],
trial_id: str) -> ScreeningResult:
"""Screen a single patient against trial criteria"""
matched_criteria = []
failed_criteria = []
partial_matches = []
exclusion_reasons = []
inclusion_criteria = [c for c in trial_criteria if c.type == CriteriaType.INCLUSION]
exclusion_criteria = [c for c in trial_criteria if c.type == CriteriaType.EXCLUSION]
# Check inclusion criteria
inclusion_score = 0
for criteria in inclusion_criteria:
match_result = self.evaluate_criteria(patient, criteria)
if match_result['matches']:
matched_criteria.append(criteria.id)
inclusion_score += match_result['confidence']
elif match_result['partial']:
partial_matches.append(criteria.id)
else:
failed_criteria.append(criteria.id)
# Check exclusion criteria
excluded = False
for criteria in exclusion_criteria:
match_result = self.evaluate_criteria(patient, criteria)
if match_result['matches']:
excluded = True
exclusion_reasons.append(f"{criteria.id}: {criteria.description}")
# Calculate overall eligibility
inclusion_rate = len(matched_criteria) / len(inclusion_criteria) if inclusion_criteria else 1.0
partial_rate = len(partial_matches) / len(inclusion_criteria) if inclusion_criteria else 0.0
# Overall confidence considering partial matches
confidence_score = (inclusion_score / len(inclusion_criteria)) if inclusion_criteria else 1.0
if partial_matches:
confidence_score = (confidence_score + partial_rate * 0.5) / 2
# Apply exclusion penalty to confidence
if excluded:
confidence_score = confidence_score * 0.4 # Reduce confidence by 60% when excluded
# Determine eligibility
eligible = not excluded and inclusion_rate >= 0.8 # 80% of inclusion criteria must match
# Generate recommendation
recommendation = self.generate_recommendation(
eligible, inclusion_rate, partial_rate, excluded, exclusion_reasons
)
return ScreeningResult(
patient_id=patient.patient_id,
trial_id=trial_id,
eligible=eligible,
confidence_score=confidence_score,
matched_criteria=matched_criteria,
failed_criteria=failed_criteria,
partial_matches=partial_matches,
exclusion_reasons=exclusion_reasons,
recommendation=recommendation
)
def evaluate_criteria(self, patient: PatientData, criteria: EligibilityCriteria) -> Dict[str, Any]:
"""Evaluate a single criteria against patient data"""
try:
# Handle different types of criteria
if criteria.age_constraint:
return self.evaluate_age_criteria(patient, criteria)
elif criteria.numerical_constraint:
return self.evaluate_numerical_criteria(patient, criteria)
elif criteria.temporal_constraint:
return self.evaluate_temporal_criteria(patient, criteria)
else:
return self.evaluate_concept_criteria(patient, criteria)
except Exception as e:
self.logger.error(f"Error evaluating criteria {criteria.id}: {e}")
return {'matches': False, 'partial': False, 'confidence': 0.0}
def evaluate_concept_criteria(self, patient: PatientData, criteria: EligibilityCriteria) -> Dict[str, Any]:
"""Evaluate concept-based criteria (conditions, medications, procedures)"""
# Get relevant patient data based on criteria
patient_concepts = []
# Collect concepts from different data sources
for condition in patient.conditions:
patient_concepts.append({
'concept_code': condition.get('code'),
'concept_name': condition.get('name'),
'vocabulary': condition.get('vocabulary', 'ICD10CM'),
'category': 'condition'
})
for medication in patient.medications:
patient_concepts.append({
'concept_code': medication.get('code'),
'concept_name': medication.get('name'),
'vocabulary': medication.get('vocabulary', 'RxNorm'),
'category': 'medication'
})
for procedure in patient.procedures:
patient_concepts.append({
'concept_code': procedure.get('code'),
'concept_name': procedure.get('name'),
'vocabulary': procedure.get('vocabulary', 'HCPCS'),
'category': 'procedure'
})
# Match against criteria concepts
matches = []
partial_matches = []
for target_code in criteria.concept_codes:
for vocab in criteria.vocabularies:
match_result = self.match_concepts(
patient_concepts, target_code, vocab, criteria.match_type
)
if match_result['exact_matches']:
matches.extend(match_result['exact_matches'])
elif match_result['hierarchical_matches']:
if criteria.match_type in [MatchType.HIERARCHICAL, MatchType.SEMANTIC]:
matches.extend(match_result['hierarchical_matches'])
else:
partial_matches.extend(match_result['hierarchical_matches'])
# Calculate confidence based on match quality
confidence = 0.0
if matches:
exact_confidence = len([m for m in matches if m.get('match_type') == 'exact']) * 1.0
hierarchical_confidence = len([m for m in matches if m.get('match_type') == 'hierarchical']) * 0.8
confidence = min(1.0, (exact_confidence + hierarchical_confidence) / len(criteria.concept_codes))
return {
'matches': len(matches) > 0,
'partial': len(partial_matches) > 0 and len(matches) == 0,
'confidence': confidence,
'details': {
'exact_matches': matches,
'partial_matches': partial_matches
}
}
def match_concepts(self, patient_concepts: List[Dict[str, Any]], target_code: str,
target_vocab: str, match_type: MatchType) -> Dict[str, Any]:
"""Match patient concepts against target criteria"""
exact_matches = []
hierarchical_matches = []
try:
# Get target concept information
target_concept = self.client.get_concept_by_code(target_vocab, target_code)
if not target_concept:
return {'exact_matches': [], 'hierarchical_matches': []}
# Check each patient concept
for patient_concept in patient_concepts:
# Exact code match
if (patient_concept['concept_code'] == target_code and
patient_concept['vocabulary'] == target_vocab):
exact_matches.append({
'patient_concept': patient_concept,
'target_concept': target_concept,
'match_type': 'exact',
'confidence': 1.0
})
continue
# Hierarchical matching for SNOMED concepts
if match_type in [MatchType.HIERARCHICAL, MatchType.SEMANTIC]:
hierarchical_match = self.check_hierarchical_relationship(
patient_concept, target_concept
)
if hierarchical_match:
hierarchical_matches.append({
'patient_concept': patient_concept,
'target_concept': target_concept,
'match_type': 'hierarchical',
'confidence': hierarchical_match['confidence'],
'relationship': hierarchical_match['relationship']
})
return {
'exact_matches': exact_matches,
'hierarchical_matches': hierarchical_matches
}
except Exception as e:
self.logger.error(f"Error in concept matching: {e}")
return {'exact_matches': [], 'hierarchical_matches': []}
def check_hierarchical_relationship(self, patient_concept: Dict[str, Any],
target_concept: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Check if patient concept has hierarchical relationship with target"""
try:
# Convert patient concept to SNOMED if needed
patient_snomed = self.get_snomed_mapping(patient_concept)
if not patient_snomed:
return None
# Check if patient concept is a descendant of target concept
descendants = self.client.get_concept_descendants(
target_concept["concept_id"],
max_levels=5,
vocabulary_ids=["SNOMED"]
)
for descendant in descendants.get("descendants", []):
if descendant["concept_id"] == patient_snomed["concept_id"]:
return {
'confidence': 0.8,
'relationship': 'is_a_descendant'
}
# Check if target concept is a descendant of patient concept
ancestors = self.client.get_concept_ancestors(
patient_snomed["concept_id"],
max_levels=5,
vocabulary_ids=["SNOMED"]
)
for ancestor in ancestors.get("ancestors", []):
if ancestor["concept_id"] == target_concept["concept_id"]:
return {
'confidence': 0.7,
'relationship': 'is_an_ancestor'
}
return None
except Exception as e:
self.logger.error(f"Error checking hierarchical relationship: {e}")
return None
def get_snomed_mapping(self, concept: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Get SNOMED mapping for a concept from another vocabulary"""
try:
if concept['vocabulary'] == 'SNOMED':
return self.client.get_concept_by_code('SNOMED', concept['concept_code'])
# Get concept mappings to SNOMED
source_concept = self.client.get_concept_by_code(
concept['vocabulary'],
concept['concept_code']
)
if source_concept:
mappings = self.client.get_concept_mappings(
source_concept["concept_id"],
target_vocabularies=["SNOMED"]
)
snomed_mappings = [
m for m in mappings.get("mappings", [])
if m["target_vocabulary_id"] == "SNOMED"
]
if snomed_mappings:
# Return the first mapping (could be improved with confidence scoring)
return self.client.get_concept(snomed_mappings[0]["target_concept_id"])
return None
except Exception as e:
self.logger.error(f"Error getting SNOMED mapping: {e}")
return None
```
```javascript JavaScript
import { OMOPHubClient } from '@omophub/sdk';
const CriteriaType = {
INCLUSION: 'inclusion',
EXCLUSION: 'exclusion'
};
const MatchType = {
EXACT: 'exact',
HIERARCHICAL: 'hierarchical',
SEMANTIC: 'semantic',
NUMERICAL: 'numerical'
};
class ClinicalTrialEligibilityEngine {
constructor(apiKey) {
this.client = new OMOPHubClient({ apiKey });
this.conditionCache = new Map();
this.demographicValidators = {
age: this.validateAgeConstraint.bind(this),
gender: this.validateGenderConstraint.bind(this),
pregnancy: this.validatePregnancyConstraint.bind(this)
};
}
async screenPatient(patient, trialCriteria, trialId) {
const matchedCriteria = [];
const failedCriteria = [];
const partialMatches = [];
const exclusionReasons = [];
const inclusionCriteria = trialCriteria.filter(c => c.type === CriteriaType.INCLUSION);
const exclusionCriteria = trialCriteria.filter(c => c.type === CriteriaType.EXCLUSION);
// Check inclusion criteria
let inclusionScore = 0;
for (const criteria of inclusionCriteria) {
const matchResult = await this.evaluateCriteria(patient, criteria);
if (matchResult.matches) {
matchedCriteria.push(criteria.id);
inclusionScore += matchResult.confidence;
} else if (matchResult.partial) {
partialMatches.push(criteria.id);
} else {
failedCriteria.push(criteria.id);
}
}
// Check exclusion criteria
let excluded = false;
for (const criteria of exclusionCriteria) {
const matchResult = await this.evaluateCriteria(patient, criteria);
if (matchResult.matches) {
excluded = true;
exclusionReasons.push(`${criteria.id}: ${criteria.description}`);
}
}
// Calculate overall eligibility
const inclusionRate = inclusionCriteria.length > 0 ? matchedCriteria.length / inclusionCriteria.length : 1.0;
const partialRate = inclusionCriteria.length > 0 ? partialMatches.length / inclusionCriteria.length : 0.0;
// Overall confidence considering partial matches
let confidenceScore = inclusionCriteria.length > 0 ? inclusionScore / inclusionCriteria.length : 1.0;
if (partialMatches.length > 0) {
confidenceScore = (confidenceScore + partialRate * 0.5) / 2;
}
// Determine eligibility
const eligible = !excluded && inclusionRate >= 0.8; // 80% of inclusion criteria must match
// Generate recommendation
const recommendation = this.generateRecommendation(
eligible, inclusionRate, partialRate, excluded, exclusionReasons
);
return {
patient_id: patient.patient_id,
trial_id: trialId,
eligible,
confidence_score: confidenceScore,
matched_criteria: matchedCriteria,
failed_criteria: failedCriteria,
partial_matches: partialMatches,
exclusion_reasons: exclusionReasons,
recommendation
};
}
async evaluateCriteria(patient, criteria) {
try {
// Handle different types of criteria
if (criteria.age_constraint) {
return this.evaluateAgeCriteria(patient, criteria);
} else if (criteria.numerical_constraint) {
return await this.evaluateNumericalCriteria(patient, criteria);
} else if (criteria.temporal_constraint) {
return this.evaluateTemporalCriteria(patient, criteria);
} else {
return await this.evaluateConceptCriteria(patient, criteria);
}
} catch (error) {
console.error(`Error evaluating criteria ${criteria.id}:`, error);
return { matches: false, partial: false, confidence: 0.0 };
}
}
async evaluateConceptCriteria(patient, criteria) {
// Get relevant patient data based on criteria
const patientConcepts = [];
// Collect concepts from different data sources
for (const condition of patient.conditions || []) {
patientConcepts.push({
concept_code: condition.code,
concept_name: condition.name,
vocabulary: condition.vocabulary || 'ICD10CM',
category: 'condition'
});
}
for (const medication of patient.medications || []) {
patientConcepts.push({
concept_code: medication.code,
concept_name: medication.name,
vocabulary: medication.vocabulary || 'RxNorm',
category: 'medication'
});
}
for (const procedure of patient.procedures || []) {
patientConcepts.push({
concept_code: procedure.code,
concept_name: procedure.name,
vocabulary: procedure.vocabulary || 'HCPCS',
category: 'procedure'
});
}
// Match against criteria concepts
const matches = [];
const partialMatches = [];
for (const targetCode of criteria.concept_codes) {
for (const vocab of criteria.vocabularies) {
const matchResult = await this.matchConcepts(
patientConcepts, targetCode, vocab, criteria.match_type
);
if (matchResult.exact_matches.length > 0) {
matches.push(...matchResult.exact_matches);
} else if (matchResult.hierarchical_matches.length > 0) {
if ([MatchType.HIERARCHICAL, MatchType.SEMANTIC].includes(criteria.match_type)) {
matches.push(...matchResult.hierarchical_matches);
} else {
partialMatches.push(...matchResult.hierarchical_matches);
}
}
}
}
// Calculate confidence based on match quality
let confidence = 0.0;
if (matches.length > 0) {
const exactConfidence = matches.filter(m => m.match_type === 'exact').length * 1.0;
const hierarchicalConfidence = matches.filter(m => m.match_type === 'hierarchical').length * 0.8;
confidence = Math.min(1.0, (exactConfidence + hierarchicalConfidence) / criteria.concept_codes.length);
}
return {
matches: matches.length > 0,
partial: partialMatches.length > 0 && matches.length === 0,
confidence,
details: {
exact_matches: matches,
partial_matches: partialMatches
}
};
}
async matchConcepts(patientConcepts, targetCode, targetVocab, matchType) {
const exactMatches = [];
const hierarchicalMatches = [];
try {
// Get target concept information
const targetConcept = await this.client.getConceptByCode(targetVocab, targetCode);
if (!targetConcept.data) {
return { exact_matches: [], hierarchical_matches: [] };
}
// Check each patient concept
for (const patientConcept of patientConcepts) {
// Exact code match
if (patientConcept.concept_code === targetCode &&
patientConcept.vocabulary === targetVocab) {
exactMatches.push({
patient_concept: patientConcept,
target_concept: targetConcept.data,
match_type: 'exact',
confidence: 1.0
});
continue;
}
// Hierarchical matching for SNOMED concepts
if ([MatchType.HIERARCHICAL, MatchType.SEMANTIC].includes(matchType)) {
const hierarchicalMatch = await this.checkHierarchicalRelationship(
patientConcept, targetConcept.data
);
if (hierarchicalMatch) {
hierarchicalMatches.push({
patient_concept: patientConcept,
target_concept: targetConcept.data,
match_type: 'hierarchical',
confidence: hierarchicalMatch.confidence,
relationship: hierarchicalMatch.relationship
});
}
}
}
return {
exact_matches: exactMatches,
hierarchical_matches: hierarchicalMatches
};
} catch (error) {
console.error('Error in concept matching:', error);
return { exact_matches: [], hierarchical_matches: [] };
}
}
async checkHierarchicalRelationship(patientConcept, targetConcept) {
try {
// Convert patient concept to SNOMED if needed
const patientSnomed = await this.getSnomedMapping(patientConcept);
if (!patientSnomed) {
return null;
}
// Check if patient concept is a descendant of target concept
const descendants = await this.client.getConceptDescendants(
targetConcept.concept_id,
{ max_levels: 5, vocabulary_ids: ['SNOMED'] }
);
for (const descendant of descendants.data.descendants || []) {
if (descendant.concept_id === patientSnomed.concept_id) {
return {
confidence: 0.8,
relationship: 'is_a_descendant'
};
}
}
// Check if target concept is a descendant of patient concept
const ancestors = await this.client.getConceptAncestors(
patientSnomed.concept_id,
{ max_levels: 5, vocabulary_ids: ['SNOMED'] }
);
for (const ancestor of ancestors.data.ancestors || []) {
if (ancestor.concept_id === targetConcept.concept_id) {
return {
confidence: 0.7,
relationship: 'is_an_ancestor'
};
}
}
return null;
} catch (error) {
console.error('Error checking hierarchical relationship:', error);
return null;
}
}
generateRecommendation(eligible, inclusionRate, partialRate, excluded, exclusionReasons) {
if (excluded) {
return `EXCLUDED: ${exclusionReasons.join('; ')}`;
}
if (eligible) {
return `ELIGIBLE: High confidence match (${(inclusionRate * 100).toFixed(1)}% inclusion criteria met)`;
}
if (inclusionRate >= 0.6) {
return `POTENTIAL: Moderate match (${(inclusionRate * 100).toFixed(1)}% inclusion criteria met) - Manual review recommended`;
}
if (partialRate >= 0.3) {
return `REVIEW: Some criteria partially matched (${(partialRate * 100).toFixed(1)}% partial matches) - Detailed assessment needed`;
}
return `NOT ELIGIBLE: Low match rate (${(inclusionRate * 100).toFixed(1)}% inclusion criteria met)`;
}
}
```
```r R
library(omophub)
# Define criteria types and match types
CRITERIA_TYPE <- list(
INCLUSION = "inclusion",
EXCLUSION = "exclusion"
)
MATCH_TYPE <- list(
EXACT = "exact",
HIERARCHICAL = "hierarchical",
SEMANTIC = "semantic",
NUMERICAL = "numerical"
)
# Initialize trial eligibility engine
init_eligibility_engine <- function(api_key) {
client <- omophub_client(api_key = api_key)
list(
client = client,
condition_cache = list(),
demographic_validators = list(
age = validate_age_constraint,
gender = validate_gender_constraint,
pregnancy = validate_pregnancy_constraint
)
)
}
screen_patient <- function(engine, patient, trial_criteria, trial_id) {
matched_criteria <- c()
failed_criteria <- c()
partial_matches <- c()
exclusion_reasons <- c()
inclusion_criteria <- trial_criteria[sapply(trial_criteria, function(x) x$type == CRITERIA_TYPE$INCLUSION)]
exclusion_criteria <- trial_criteria[sapply(trial_criteria, function(x) x$type == CRITERIA_TYPE$EXCLUSION)]
# Check inclusion criteria
inclusion_score <- 0
for (criteria in inclusion_criteria) {
match_result <- evaluate_criteria(engine, patient, criteria)
if (match_result$matches) {
matched_criteria <- c(matched_criteria, criteria$id)
inclusion_score <- inclusion_score + match_result$confidence
} else if (match_result$partial) {
partial_matches <- c(partial_matches, criteria$id)
} else {
failed_criteria <- c(failed_criteria, criteria$id)
}
}
# Check exclusion criteria
excluded <- FALSE
for (criteria in exclusion_criteria) {
match_result <- evaluate_criteria(engine, patient, criteria)
if (match_result$matches) {
excluded <- TRUE
exclusion_reasons <- c(exclusion_reasons, paste(criteria$id, ":", criteria$description))
}
}
# Calculate overall eligibility
inclusion_rate <- if (length(inclusion_criteria) > 0) length(matched_criteria) / length(inclusion_criteria) else 1.0
partial_rate <- if (length(inclusion_criteria) > 0) length(partial_matches) / length(inclusion_criteria) else 0.0
# Overall confidence considering partial matches
confidence_score <- if (length(inclusion_criteria) > 0) inclusion_score / length(inclusion_criteria) else 1.0
if (length(partial_matches) > 0) {
confidence_score <- (confidence_score + partial_rate * 0.5) / 2
}
# Determine eligibility
eligible <- !excluded && inclusion_rate >= 0.8
# Generate recommendation
recommendation <- generate_recommendation(eligible, inclusion_rate, partial_rate, excluded, exclusion_reasons)
return(list(
patient_id = patient$patient_id,
trial_id = trial_id,
eligible = eligible,
confidence_score = confidence_score,
matched_criteria = matched_criteria,
failed_criteria = failed_criteria,
partial_matches = partial_matches,
exclusion_reasons = exclusion_reasons,
recommendation = recommendation
))
}
evaluate_criteria <- function(engine, patient, criteria) {
tryCatch({
# Handle different types of criteria
if (!is.null(criteria$age_constraint)) {
return(evaluate_age_criteria(patient, criteria))
} else if (!is.null(criteria$numerical_constraint)) {
return(evaluate_numerical_criteria(engine, patient, criteria))
} else if (!is.null(criteria$temporal_constraint)) {
return(evaluate_temporal_criteria(patient, criteria))
} else {
return(evaluate_concept_criteria(engine, patient, criteria))
}
}, error = function(e) {
cat(paste("Error evaluating criteria", criteria$id, ":", e$message, "\n"))
return(list(matches = FALSE, partial = FALSE, confidence = 0.0))
})
}
```
### Step 2: Numerical and Temporal Constraints
```python Python
def evaluate_age_criteria(self, patient: PatientData, criteria: EligibilityCriteria) -> Dict[str, Any]:
"""Evaluate age-based criteria"""
age_constraint = criteria.age_constraint
patient_age = patient.age
matches = True
if 'min_age' in age_constraint:
matches = matches and (patient_age >= age_constraint['min_age'])
if 'max_age' in age_constraint:
matches = matches and (patient_age <= age_constraint['max_age'])
return {
'matches': matches,
'partial': False, # Age is binary match
'confidence': 1.0 if matches else 0.0,
'details': {
'patient_age': patient_age,
'constraint': age_constraint
}
}
def evaluate_numerical_criteria(self, patient: PatientData, criteria: EligibilityCriteria) -> Dict[str, Any]:
"""Evaluate numerical criteria (lab values, vitals, etc.)"""
numerical_constraint = criteria.numerical_constraint
# Get relevant patient values
target_values = []
if numerical_constraint.get('data_type') == 'lab_result':
lab_code = numerical_constraint.get('code')
for lab in patient.lab_results:
if lab.get('code') == lab_code or lab.get('name') == numerical_constraint.get('name'):
try:
value = float(lab.get('value', 0))
target_values.append({
'value': value,
'unit': lab.get('unit'),
'date': lab.get('date')
})
except (ValueError, TypeError):
continue
elif numerical_constraint.get('data_type') == 'vital_sign':
vital_type = numerical_constraint.get('vital_type')
for vital in patient.vitals:
if vital.get('type') == vital_type:
try:
value = float(vital.get('value', 0))
target_values.append({
'value': value,
'unit': vital.get('unit'),
'date': vital.get('date')
})
except (ValueError, TypeError):
continue
if not target_values:
return {'matches': False, 'partial': False, 'confidence': 0.0}
# Use most recent value
target_values.sort(key=lambda x: x.get('date', ''), reverse=True)
most_recent = target_values[0]
# Check constraints
matches = True
partial = False
if 'min_value' in numerical_constraint:
if most_recent['value'] < numerical_constraint['min_value']:
matches = False
elif most_recent['value'] < numerical_constraint['min_value'] * 1.1: # Within 10%
partial = True
if 'max_value' in numerical_constraint:
if most_recent['value'] > numerical_constraint['max_value']:
matches = False
elif most_recent['value'] > numerical_constraint['max_value'] * 0.9: # Within 10%
partial = True
# Calculate confidence based on how close to constraints
confidence = 1.0 if matches else (0.5 if partial else 0.0)
return {
'matches': matches,
'partial': partial and not matches,
'confidence': confidence,
'details': {
'patient_value': most_recent,
'constraint': numerical_constraint
}
}
def evaluate_temporal_criteria(self, patient: PatientData, criteria: EligibilityCriteria) -> Dict[str, Any]:
"""Evaluate temporal criteria (recent diagnosis, treatment timeline, etc.)"""
temporal_constraint = criteria.temporal_constraint
current_date = datetime.now()
# Find relevant conditions/procedures within timeframe
relevant_items = []
if temporal_constraint.get('applies_to') == 'conditions':
for condition in patient.conditions:
if condition.get('date'):
condition_date = datetime.fromisoformat(condition['date'])
days_ago = (current_date - condition_date).days
# Check if condition matches concept codes
if condition.get('code') in criteria.concept_codes:
relevant_items.append({
'item': condition,
'days_ago': days_ago
})
elif temporal_constraint.get('applies_to') == 'procedures':
for procedure in patient.procedures:
if procedure.get('date'):
procedure_date = datetime.fromisoformat(procedure['date'])
days_ago = (current_date - procedure_date).days
if procedure.get('code') in criteria.concept_codes:
relevant_items.append({
'item': procedure,
'days_ago': days_ago
})
if not relevant_items:
return {'matches': False, 'partial': False, 'confidence': 0.0}
# Check temporal constraints
matches = False
partial = False
for item in relevant_items:
days_ago = item['days_ago']
# Check minimum time since event
if 'min_days_ago' in temporal_constraint:
if days_ago >= temporal_constraint['min_days_ago']:
matches = True
elif days_ago >= temporal_constraint['min_days_ago'] * 0.8: # Within 20%
partial = True
# Check maximum time since event
if 'max_days_ago' in temporal_constraint:
if days_ago <= temporal_constraint['max_days_ago']:
matches = True
elif days_ago <= temporal_constraint['max_days_ago'] * 1.2: # Within 20%
partial = True
# If any item matches, we're good
if matches:
break
confidence = 1.0 if matches else (0.5 if partial else 0.0)
return {
'matches': matches,
'partial': partial and not matches,
'confidence': confidence,
'details': {
'relevant_items': relevant_items,
'constraint': temporal_constraint
}
}
def generate_recommendation(self, eligible: bool, inclusion_rate: float, partial_rate: float,
excluded: bool, exclusion_reasons: List[str]) -> str:
"""Generate human-readable recommendation"""
if excluded:
return f"EXCLUDED: {'; '.join(exclusion_reasons)}"
if eligible:
return f"ELIGIBLE: High confidence match ({inclusion_rate:.1%} inclusion criteria met)"
if inclusion_rate >= 0.6:
return f"POTENTIAL: Moderate match ({inclusion_rate:.1%} inclusion criteria met) - Manual review recommended"
if partial_rate >= 0.3:
return f"REVIEW: Some criteria partially matched ({partial_rate:.1%} partial matches) - Detailed assessment needed"
return f"NOT ELIGIBLE: Low match rate ({inclusion_rate:.1%} inclusion criteria met)"
```
```javascript JavaScript
evaluateAgeCriteria(patient, criteria) {
const ageConstraint = criteria.age_constraint;
const patientAge = patient.age;
let matches = true;
if ('min_age' in ageConstraint) {
matches = matches && (patientAge >= ageConstraint.min_age);
}
if ('max_age' in ageConstraint) {
matches = matches && (patientAge <= ageConstraint.max_age);
}
return {
matches,
partial: false, // Age is binary match
confidence: matches ? 1.0 : 0.0,
details: {
patient_age: patientAge,
constraint: ageConstraint
}
};
}
async evaluateNumericalCriteria(patient, criteria) {
const numericalConstraint = criteria.numerical_constraint;
// Get relevant patient values
const targetValues = [];
if (numericalConstraint.data_type === 'lab_result') {
const labCode = numericalConstraint.code;
for (const lab of patient.lab_results || []) {
if (lab.code === labCode || lab.name === numericalConstraint.name) {
try {
const value = parseFloat(lab.value || 0);
targetValues.push({
value,
unit: lab.unit,
date: lab.date
});
} catch (error) {
continue;
}
}
}
}
if (targetValues.length === 0) {
return { matches: false, partial: false, confidence: 0.0 };
}
// Use most recent value
targetValues.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
const mostRecent = targetValues[0];
// Check constraints
let matches = true;
let partial = false;
if ('min_value' in numericalConstraint) {
if (mostRecent.value < numericalConstraint.min_value) {
matches = false;
} else if (mostRecent.value < numericalConstraint.min_value * 1.1) { // Within 10%
partial = true;
}
}
if ('max_value' in numericalConstraint) {
if (mostRecent.value > numericalConstraint.max_value) {
matches = false;
} else if (mostRecent.value > numericalConstraint.max_value * 0.9) { // Within 10%
partial = true;
}
}
// Calculate confidence based on how close to constraints
const confidence = matches ? 1.0 : (partial ? 0.5 : 0.0);
return {
matches,
partial: partial && !matches,
confidence,
details: {
patient_value: mostRecent,
constraint: numericalConstraint
}
};
}
```
```r R
evaluate_age_criteria <- function(patient, criteria) {
age_constraint <- criteria$age_constraint
patient_age <- patient$age
matches <- TRUE
if ("min_age" %in% names(age_constraint)) {
matches <- matches && (patient_age >= age_constraint$min_age)
}
if ("max_age" %in% names(age_constraint)) {
matches <- matches && (patient_age <= age_constraint$max_age)
}
return(list(
matches = matches,
partial = FALSE, # Age is binary match
confidence = if (matches) 1.0 else 0.0,
details = list(
patient_age = patient_age,
constraint = age_constraint
)
))
}
evaluate_numerical_criteria <- function(engine, patient, criteria) {
numerical_constraint <- criteria$numerical_constraint
# Get relevant patient values
target_values <- list()
if (numerical_constraint$data_type == "lab_result") {
lab_code <- numerical_constraint$code
for (lab in patient$lab_results) {
if (lab$code == lab_code || lab$name == numerical_constraint$name) {
tryCatch({
value <- as.numeric(lab$value)
if (!is.na(value)) {
target_values[[length(target_values) + 1]] <- list(
value = value,
unit = lab$unit,
date = lab$date
)
}
}, error = function(e) {
# Continue if can't parse value
})
}
}
}
if (length(target_values) == 0) {
return(list(matches = FALSE, partial = FALSE, confidence = 0.0))
}
# Use most recent value
target_values <- target_values[order(sapply(target_values, function(x) x$date), decreasing = TRUE)]
most_recent <- target_values[[1]]
# Check constraints
matches <- TRUE
partial <- FALSE
if ("min_value" %in% names(numerical_constraint)) {
if (most_recent$value < numerical_constraint$min_value) {
matches <- FALSE
} else if (most_recent$value < numerical_constraint$min_value * 1.1) {
partial <- TRUE
}
}
if ("max_value" %in% names(numerical_constraint)) {
if (most_recent$value > numerical_constraint$max_value) {
matches <- FALSE
} else if (most_recent$value > numerical_constraint$max_value * 0.9) {
partial <- TRUE
}
}
# Calculate confidence
confidence <- if (matches) 1.0 else if (partial) 0.5 else 0.0
return(list(
matches = matches,
partial = partial && !matches,
confidence = confidence,
details = list(
patient_value = most_recent,
constraint = numerical_constraint
)
))
}
```
### Step 3: Batch Patient Screening
```python Python
def screen_patient_cohort(self, patients: List[PatientData], trial_criteria: List[EligibilityCriteria],
trial_id: str) -> Dict[str, Any]:
"""Screen multiple patients for trial eligibility"""
screening_results = []
summary_stats = {
'total_patients': len(patients),
'eligible_patients': 0,
'excluded_patients': 0,
'potential_patients': 0,
'not_eligible_patients': 0,
'criteria_performance': {}
}
# Track criteria performance
criteria_stats = {}
for criteria in trial_criteria:
criteria_stats[criteria.id] = {
'matched_count': 0,
'failed_count': 0,
'partial_count': 0,
'type': criteria.type.value
}
# Screen each patient
for patient in patients:
try:
result = self.screen_patient(patient, trial_criteria, trial_id)
screening_results.append(result)
# Update summary statistics
if result.eligible:
summary_stats['eligible_patients'] += 1
elif result.exclusion_reasons:
summary_stats['excluded_patients'] += 1
elif result.confidence_score >= 0.6:
summary_stats['potential_patients'] += 1
else:
summary_stats['not_eligible_patients'] += 1
# Update criteria performance
for criteria_id in result.matched_criteria:
if criteria_id in criteria_stats:
criteria_stats[criteria_id]['matched_count'] += 1
for criteria_id in result.failed_criteria:
if criteria_id in criteria_stats:
criteria_stats[criteria_id]['failed_count'] += 1
for criteria_id in result.partial_matches:
if criteria_id in criteria_stats:
criteria_stats[criteria_id]['partial_count'] += 1
except Exception as e:
self.logger.error(f"Error screening patient {patient.patient_id}: {e}")
# Create error result
screening_results.append(ScreeningResult(
patient_id=patient.patient_id,
trial_id=trial_id,
eligible=False,
confidence_score=0.0,
matched_criteria=[],
failed_criteria=[],
partial_matches=[],
exclusion_reasons=[f"Screening error: {str(e)}"],
recommendation="ERROR: Unable to complete screening"
))
# Calculate criteria performance metrics
for criteria_id, stats in criteria_stats.items():
total = stats['matched_count'] + stats['failed_count'] + stats['partial_count']
if total > 0:
stats['match_rate'] = stats['matched_count'] / total
stats['partial_rate'] = stats['partial_count'] / total
stats['fail_rate'] = stats['failed_count'] / total
else:
stats['match_rate'] = 0.0
stats['partial_rate'] = 0.0
stats['fail_rate'] = 0.0
summary_stats['criteria_performance'] = criteria_stats
# Generate cohort insights
insights = self.generate_cohort_insights(screening_results, summary_stats)
return {
'trial_id': trial_id,
'screening_results': screening_results,
'summary_stats': summary_stats,
'insights': insights,
'screened_at': datetime.now().isoformat(),
'eligible_patients': [r for r in screening_results if r.eligible],
'potential_patients': [r for r in screening_results if not r.eligible and r.confidence_score >= 0.6]
}
def generate_cohort_insights(self, results: List[ScreeningResult], stats: Dict[str, Any]) -> List[str]:
"""Generate insights about the screening cohort"""
insights = []
total = stats['total_patients']
eligible = stats['eligible_patients']
excluded = stats['excluded_patients']
potential = stats['potential_patients']
# Eligibility rate insights
eligibility_rate = (eligible / total) * 100 if total > 0 else 0
if eligibility_rate > 20:
insights.append(f"High eligibility rate ({eligibility_rate:.1f}%) - Good patient population match")
elif eligibility_rate < 5:
insights.append(f"Low eligibility rate ({eligibility_rate:.1f}%) - Consider criteria refinement")
# Exclusion analysis
exclusion_rate = (excluded / total) * 100 if total > 0 else 0
if exclusion_rate > 50:
insights.append(f"High exclusion rate ({exclusion_rate:.1f}%) - Review exclusion criteria")
# Most common exclusion reasons
exclusion_reasons = {}
for result in results:
for reason in result.exclusion_reasons:
exclusion_reasons[reason] = exclusion_reasons.get(reason, 0) + 1
if exclusion_reasons:
most_common = max(exclusion_reasons.items(), key=lambda x: x[1])
insights.append(f"Most common exclusion: {most_common[0]} ({most_common[1]} patients)")
# Criteria performance analysis
criteria_perf = stats['criteria_performance']
inclusion_criteria = {k: v for k, v in criteria_perf.items() if v['type'] == 'inclusion'}
if inclusion_criteria:
# Find hardest criteria to match
hardest_criteria = min(inclusion_criteria.items(), key=lambda x: x[1]['match_rate'])
if hardest_criteria[1]['match_rate'] < 0.1:
insights.append(f"Restrictive criteria: {hardest_criteria[0]} (only {hardest_criteria[1]['match_rate']:.1%} match rate)")
# Potential patients insights
if potential > 0:
insights.append(f"{potential} patients show potential eligibility - recommend manual review")
return insights
def create_recruitment_report(self, cohort_results: Dict[str, Any]) -> Dict[str, Any]:
"""Create comprehensive recruitment report"""
eligible_patients = cohort_results['eligible_patients']
potential_patients = cohort_results['potential_patients']
# Patient prioritization
prioritized_eligible = sorted(
eligible_patients,
key=lambda x: x.confidence_score,
reverse=True
)
prioritized_potential = sorted(
potential_patients,
key=lambda x: x.confidence_score,
reverse=True
)
# Generate contact recommendations
contact_recommendations = []
# High-priority eligible patients
for patient in prioritized_eligible[:10]: # Top 10
contact_recommendations.append({
'patient_id': patient.patient_id,
'priority': 'HIGH',
'eligibility_status': 'ELIGIBLE',
'confidence': patient.confidence_score,
'next_action': 'Schedule screening visit',
'contact_urgency': 'Within 48 hours',
'notes': f"Strong match - {len(patient.matched_criteria)} criteria met"
})
# Potential patients requiring review
for patient in prioritized_potential[:5]: # Top 5 potential
contact_recommendations.append({
'patient_id': patient.patient_id,
'priority': 'MEDIUM',
'eligibility_status': 'POTENTIAL',
'confidence': patient.confidence_score,
'next_action': 'Clinical review required',
'contact_urgency': 'Within 1 week',
'notes': f"Partial match - {len(patient.partial_matches)} criteria need review"
})
return {
'trial_id': cohort_results['trial_id'],
'recruitment_summary': {
'total_screened': cohort_results['summary_stats']['total_patients'],
'immediately_eligible': len(eligible_patients),
'requires_review': len(potential_patients),
'projected_enrollment': len(eligible_patients) + int(len(potential_patients) * 0.3) # 30% conversion
},
'contact_recommendations': contact_recommendations,
'criteria_insights': cohort_results['insights'],
'performance_metrics': {
'screening_efficiency': f"{len(eligible_patients + potential_patients)}/{cohort_results['summary_stats']['total_patients']} patients identified",
'time_to_identify': 'Real-time automated screening',
'manual_review_reduction': f"{100 - (len(potential_patients) / cohort_results['summary_stats']['total_patients'] * 100):.1f}% reduction in manual reviews"
},
'generated_at': datetime.now().isoformat()
}
```
```javascript JavaScript
async screenPatientCohort(patients, trialCriteria, trialId) {
const screeningResults = [];
const summaryStats = {
total_patients: patients.length,
eligible_patients: 0,
excluded_patients: 0,
potential_patients: 0,
not_eligible_patients: 0,
criteria_performance: {}
};
// Track criteria performance
const criteriaStats = {};
for (const criteria of trialCriteria) {
criteriaStats[criteria.id] = {
matched_count: 0,
failed_count: 0,
partial_count: 0,
type: criteria.type
};
}
// Screen each patient
for (const patient of patients) {
try {
const result = await this.screenPatient(patient, trialCriteria, trialId);
screeningResults.push(result);
// Update summary statistics
if (result.eligible) {
summaryStats.eligible_patients++;
} else if (result.exclusion_reasons.length > 0) {
summaryStats.excluded_patients++;
} else if (result.confidence_score >= 0.6) {
summaryStats.potential_patients++;
} else {
summaryStats.not_eligible_patients++;
}
// Update criteria performance
for (const criteriaId of result.matched_criteria) {
if (criteriaStats[criteriaId]) {
criteriaStats[criteriaId].matched_count++;
}
}
for (const criteriaId of result.failed_criteria) {
if (criteriaStats[criteriaId]) {
criteriaStats[criteriaId].failed_count++;
}
}
for (const criteriaId of result.partial_matches) {
if (criteriaStats[criteriaId]) {
criteriaStats[criteriaId].partial_count++;
}
}
} catch (error) {
console.error(`Error screening patient ${patient.patient_id}:`, error);
// Create error result
screeningResults.push({
patient_id: patient.patient_id,
trial_id: trialId,
eligible: false,
confidence_score: 0.0,
matched_criteria: [],
failed_criteria: [],
partial_matches: [],
exclusion_reasons: [`Screening error: ${error.message}`],
recommendation: 'ERROR: Unable to complete screening'
});
}
}
// Calculate criteria performance metrics
for (const [criteriaId, stats] of Object.entries(criteriaStats)) {
const total = stats.matched_count + stats.failed_count + stats.partial_count;
if (total > 0) {
stats.match_rate = stats.matched_count / total;
stats.partial_rate = stats.partial_count / total;
stats.fail_rate = stats.failed_count / total;
} else {
stats.match_rate = 0.0;
stats.partial_rate = 0.0;
stats.fail_rate = 0.0;
}
}
summaryStats.criteria_performance = criteriaStats;
// Generate cohort insights
const insights = this.generateCohortInsights(screeningResults, summaryStats);
return {
trial_id: trialId,
screening_results: screeningResults,
summary_stats: summaryStats,
insights,
screened_at: new Date().toISOString(),
eligible_patients: screeningResults.filter(r => r.eligible),
potential_patients: screeningResults.filter(r => !r.eligible && r.confidence_score >= 0.6)
};
}
generateCohortInsights(results, stats) {
const insights = [];
const total = stats.total_patients;
const eligible = stats.eligible_patients;
const excluded = stats.excluded_patients;
const potential = stats.potential_patients;
// Eligibility rate insights
const eligibilityRate = total > 0 ? (eligible / total) * 100 : 0;
if (eligibilityRate > 20) {
insights.push(`High eligibility rate (${eligibilityRate.toFixed(1)}%) - Good patient population match`);
} else if (eligibilityRate < 5) {
insights.push(`Low eligibility rate (${eligibilityRate.toFixed(1)}%) - Consider criteria refinement`);
}
// Exclusion analysis
const exclusionRate = total > 0 ? (excluded / total) * 100 : 0;
if (exclusionRate > 50) {
insights.push(`High exclusion rate (${exclusionRate.toFixed(1)}%) - Review exclusion criteria`);
}
// Most common exclusion reasons
const exclusionReasons = {};
for (const result of results) {
for (const reason of result.exclusion_reasons) {
exclusionReasons[reason] = (exclusionReasons[reason] || 0) + 1;
}
}
if (Object.keys(exclusionReasons).length > 0) {
const mostCommon = Object.entries(exclusionReasons).reduce((a, b) => b[1] > a[1] ? b : a);
insights.push(`Most common exclusion: ${mostCommon[0]} (${mostCommon[1]} patients)`);
}
// Potential patients insights
if (potential > 0) {
insights.push(`${potential} patients show potential eligibility - recommend manual review`);
}
return insights;
}
```
```r R
screen_patient_cohort <- function(engine, patients, trial_criteria, trial_id) {
screening_results <- list()
summary_stats <- list(
total_patients = length(patients),
eligible_patients = 0,
excluded_patients = 0,
potential_patients = 0,
not_eligible_patients = 0,
criteria_performance = list()
)
# Track criteria performance
criteria_stats <- list()
for (criteria in trial_criteria) {
criteria_stats[[criteria$id]] <- list(
matched_count = 0,
failed_count = 0,
partial_count = 0,
type = criteria$type
)
}
# Screen each patient
for (i in 1:length(patients)) {
patient <- patients[[i]]
tryCatch({
result <- screen_patient(engine, patient, trial_criteria, trial_id)
screening_results[[length(screening_results) + 1]] <- result
# Update summary statistics
if (result$eligible) {
summary_stats$eligible_patients <- summary_stats$eligible_patients + 1
} else if (length(result$exclusion_reasons) > 0) {
summary_stats$excluded_patients <- summary_stats$excluded_patients + 1
} else if (result$confidence_score >= 0.6) {
summary_stats$potential_patients <- summary_stats$potential_patients + 1
} else {
summary_stats$not_eligible_patients <- summary_stats$not_eligible_patients + 1
}
# Update criteria performance
for (criteria_id in result$matched_criteria) {
if (criteria_id %in% names(criteria_stats)) {
criteria_stats[[criteria_id]]$matched_count <- criteria_stats[[criteria_id]]$matched_count + 1
}
}
for (criteria_id in result$failed_criteria) {
if (criteria_id %in% names(criteria_stats)) {
criteria_stats[[criteria_id]]$failed_count <- criteria_stats[[criteria_id]]$failed_count + 1
}
}
for (criteria_id in result$partial_matches) {
if (criteria_id %in% names(criteria_stats)) {
criteria_stats[[criteria_id]]$partial_count <- criteria_stats[[criteria_id]]$partial_count + 1
}
}
}, error = function(e) {
cat(paste("Error screening patient", patient$patient_id, ":", e$message, "\n"))
# Create error result
error_result <- list(
patient_id = patient$patient_id,
trial_id = trial_id,
eligible = FALSE,
confidence_score = 0.0,
matched_criteria = c(),
failed_criteria = c(),
partial_matches = c(),
exclusion_reasons = paste("Screening error:", e$message),
recommendation = "ERROR: Unable to complete screening"
)
screening_results[[length(screening_results) + 1]] <- error_result
})
}
# Calculate criteria performance metrics
for (criteria_id in names(criteria_stats)) {
stats <- criteria_stats[[criteria_id]]
total <- stats$matched_count + stats$failed_count + stats$partial_count
if (total > 0) {
stats$match_rate <- stats$matched_count / total
stats$partial_rate <- stats$partial_count / total
stats$fail_rate <- stats$failed_count / total
} else {
stats$match_rate <- 0.0
stats$partial_rate <- 0.0
stats$fail_rate <- 0.0
}
criteria_stats[[criteria_id]] <- stats
}
summary_stats$criteria_performance <- criteria_stats
# Generate cohort insights
insights <- generate_cohort_insights(screening_results, summary_stats)
eligible_patients <- Filter(function(r) r$eligible, screening_results)
potential_patients <- Filter(function(r) !r$eligible && r$confidence_score >= 0.6, screening_results)
return(list(
trial_id = trial_id,
screening_results = screening_results,
summary_stats = summary_stats,
insights = insights,
screened_at = Sys.time(),
eligible_patients = eligible_patients,
potential_patients = potential_patients
))
}
```
## Example Implementation
### Sample Clinical Trial Criteria
```
NCT05123456: Phase II Oncology Trial
Target: Non-small cell lung cancer patients
INCLUSION CRITERIA:
1. Histologically confirmed NSCLC (Stage IIIB-IV)
2. Age 18-75 years
3. ECOG Performance Status 0-2
4. Adequate organ function (Creatinine ≤1.5x ULN)
5. Previous chemotherapy required
EXCLUSION CRITERIA:
1. Active brain metastases
2. Severe cardiac disease (EF <50%)
3. Pregnancy or nursing
4. Previous treatment with study drug class
```
### Implementation Example
```python Python
# Initialize the engine
engine = ClinicalTrialEligibilityEngine("your_api_key")
# Define trial criteria
trial_criteria = [
# Inclusion criteria
EligibilityCriteria(
id="INCL_001",
type=CriteriaType.INCLUSION,
description="Histologically confirmed non-small cell lung cancer",
concept_codes=["254637007"], # SNOMED: Non-small cell lung cancer
vocabularies=["SNOMED"],
match_type=MatchType.HIERARCHICAL
),
EligibilityCriteria(
id="INCL_002",
type=CriteriaType.INCLUSION,
description="Age 18-75 years",
concept_codes=[],
vocabularies=[],
match_type=MatchType.NUMERICAL,
age_constraint={"min_age": 18, "max_age": 75}
),
EligibilityCriteria(
id="INCL_003",
type=CriteriaType.INCLUSION,
description="Creatinine ≤1.5x ULN (≤1.95 mg/dL)",
concept_codes=[],
vocabularies=[],
match_type=MatchType.NUMERICAL,
numerical_constraint={
"data_type": "lab_result",
"code": "2160-0", # LOINC: Creatinine
"max_value": 1.95,
"unit": "mg/dL"
}
),
# Exclusion criteria
EligibilityCriteria(
id="EXCL_001",
type=CriteriaType.EXCLUSION,
description="Active brain metastases",
concept_codes=["94225005"], # SNOMED: Secondary malignant neoplasm of brain
vocabularies=["SNOMED"],
match_type=MatchType.HIERARCHICAL
),
EligibilityCriteria(
id="EXCL_002",
type=CriteriaType.EXCLUSION,
description="Pregnancy",
concept_codes=["77386006"], # SNOMED: Pregnancy
vocabularies=["SNOMED"],
match_type=MatchType.EXACT
)
]
# Sample patient data
sample_patients = [
PatientData(
patient_id="PT001",
age=62,
gender="M",
conditions=[
{"code": "254637007", "name": "Non-small cell lung cancer", "vocabulary": "SNOMED", "date": "2023-01-15"},
{"code": "C78.00", "name": "Secondary malignant neoplasm of unspecified lung", "vocabulary": "ICD10CM", "date": "2023-02-01"}
],
medications=[
{"code": "40048-14", "name": "Carboplatin", "vocabulary": "RxNorm", "date": "2023-01-20"}
],
lab_results=[
{"code": "2160-0", "name": "Creatinine", "value": "1.2", "unit": "mg/dL", "date": "2023-03-01"}
],
procedures=[
{"code": "32507", "name": "Thoracotomy", "vocabulary": "HCPCS", "date": "2023-01-18"}
],
vitals=[]
),
PatientData(
patient_id="PT002",
age=45,
gender="F",
conditions=[
{"code": "254637007", "name": "Non-small cell lung cancer", "vocabulary": "SNOMED", "date": "2023-01-10"},
{"code": "77386006", "name": "Pregnancy", "vocabulary": "SNOMED", "date": "2023-02-15"}
],
medications=[],
lab_results=[
{"code": "2160-0", "name": "Creatinine", "value": "0.9", "unit": "mg/dL", "date": "2023-03-01"}
],
procedures=[],
vitals=[]
)
]
# Screen patient cohort
cohort_results = engine.screen_patient_cohort(sample_patients, trial_criteria, "NCT05123456")
# Generate recruitment report
recruitment_report = engine.create_recruitment_report(cohort_results)
# Print results
print("=== CLINICAL TRIAL ELIGIBILITY SCREENING REPORT ===")
print(f"Trial ID: {recruitment_report['trial_id']}")
print(f"Patients Screened: {recruitment_report['recruitment_summary']['total_screened']}")
print(f"Immediately Eligible: {recruitment_report['recruitment_summary']['immediately_eligible']}")
print(f"Requires Review: {recruitment_report['recruitment_summary']['requires_review']}")
print("\n=== INDIVIDUAL SCREENING RESULTS ===")
for result in cohort_results['screening_results']:
print(f"\nPatient: {result.patient_id}")
print(f" Eligible: {'Yes' if result.eligible else 'No'}")
print(f" Confidence: {result.confidence_score:.1%}")
print(f" Recommendation: {result.recommendation}")
if result.matched_criteria:
print(f" Matched Criteria: {', '.join(result.matched_criteria)}")
if result.exclusion_reasons:
print(f" Exclusion Reasons: {'; '.join(result.exclusion_reasons)}")
print("\n=== RECRUITMENT RECOMMENDATIONS ===")
for rec in recruitment_report['contact_recommendations']:
print(f"Patient {rec['patient_id']}: {rec['priority']} priority - {rec['next_action']}")
print(f" Contact within: {rec['contact_urgency']}")
print(f" Notes: {rec['notes']}")
print()
print("=== INSIGHTS ===")
for insight in cohort_results['insights']:
print(f"• {insight}")
```
```javascript JavaScript
// Initialize the engine
const engine = new ClinicalTrialEligibilityEngine('your_api_key');
// Define trial criteria
const trialCriteria = [
// Inclusion criteria
{
id: 'INCL_001',
type: CriteriaType.INCLUSION,
description: 'Histologically confirmed non-small cell lung cancer',
concept_codes: ['254637007'], // SNOMED: Non-small cell lung cancer
vocabularies: ['SNOMED'],
match_type: MatchType.HIERARCHICAL
},
{
id: 'INCL_002',
type: CriteriaType.INCLUSION,
description: 'Age 18-75 years',
concept_codes: [],
vocabularies: [],
match_type: MatchType.NUMERICAL,
age_constraint: { min_age: 18, max_age: 75 }
},
// Exclusion criteria
{
id: 'EXCL_001',
type: CriteriaType.EXCLUSION,
description: 'Active brain metastases',
concept_codes: ['94225005'], // SNOMED: Secondary malignant neoplasm of brain
vocabularies: ['SNOMED'],
match_type: MatchType.HIERARCHICAL
}
];
// Sample patient data
const samplePatients = [
{
patient_id: 'PT001',
age: 62,
gender: 'M',
conditions: [
{
code: '254637007',
name: 'Non-small cell lung cancer',
vocabulary: 'SNOMED',
date: '2023-01-15'
}
],
medications: [
{
code: '40048-14',
name: 'Carboplatin',
vocabulary: 'RxNorm',
date: '2023-01-20'
}
],
lab_results: [
{
code: '2160-0',
name: 'Creatinine',
value: '1.2',
unit: 'mg/dL',
date: '2023-03-01'
}
],
procedures: [],
vitals: []
}
];
// Screen patient cohort
engine.screenPatientCohort(samplePatients, trialCriteria, 'NCT05123456')
.then(cohortResults => {
console.log('=== CLINICAL TRIAL ELIGIBILITY SCREENING REPORT ===');
console.log(`Trial ID: ${cohortResults.trial_id}`);
console.log(`Patients Screened: ${cohortResults.summary_stats.total_patients}`);
console.log(`Eligible: ${cohortResults.summary_stats.eligible_patients}`);
console.log('\n=== INDIVIDUAL SCREENING RESULTS ===');
cohortResults.screening_results.forEach(result => {
console.log(`\nPatient: ${result.patient_id}`);
console.log(` Eligible: ${result.eligible ? 'Yes' : 'No'}`);
console.log(` Confidence: ${(result.confidence_score * 100).toFixed(1)}%`);
console.log(` Recommendation: ${result.recommendation}`);
if (result.matched_criteria.length > 0) {
console.log(` Matched Criteria: ${result.matched_criteria.join(', ')}`);
}
if (result.exclusion_reasons.length > 0) {
console.log(` Exclusion Reasons: ${result.exclusion_reasons.join('; ')}`);
}
});
console.log('\n=== INSIGHTS ===');
cohortResults.insights.forEach(insight => {
console.log(`• ${insight}`);
});
})
.catch(error => {
console.error('Screening failed:', error);
});
```
```r R
# Initialize the engine
engine <- init_eligibility_engine("your_api_key")
# Define trial criteria
trial_criteria <- list(
# Inclusion criteria
list(
id = "INCL_001",
type = CRITERIA_TYPE$INCLUSION,
description = "Histologically confirmed non-small cell lung cancer",
concept_codes = c("254637007"), # SNOMED: Non-small cell lung cancer
vocabularies = c("SNOMED"),
match_type = MATCH_TYPE$HIERARCHICAL
),
list(
id = "INCL_002",
type = CRITERIA_TYPE$INCLUSION,
description = "Age 18-75 years",
concept_codes = c(),
vocabularies = c(),
match_type = MATCH_TYPE$NUMERICAL,
age_constraint = list(min_age = 18, max_age = 75)
),
# Exclusion criteria
list(
id = "EXCL_001",
type = CRITERIA_TYPE$EXCLUSION,
description = "Pregnancy",
concept_codes = c("77386006"), # SNOMED: Pregnancy
vocabularies = c("SNOMED"),
match_type = MATCH_TYPE$EXACT
)
)
# Sample patient data
sample_patients <- list(
list(
patient_id = "PT001",
age = 62,
gender = "M",
conditions = list(
list(code = "254637007", name = "Non-small cell lung cancer",
vocabulary = "SNOMED", date = "2023-01-15")
),
medications = list(),
lab_results = list(
list(code = "2160-0", name = "Creatinine", value = "1.2",
unit = "mg/dL", date = "2023-03-01")
),
procedures = list(),
vitals = list()
)
)
# Screen patient cohort
cohort_results <- screen_patient_cohort(engine, sample_patients, trial_criteria, "NCT05123456")
# Print results
cat("=== CLINICAL TRIAL ELIGIBILITY SCREENING REPORT ===\n")
cat(paste("Trial ID:", cohort_results$trial_id, "\n"))
cat(paste("Patients Screened:", cohort_results$summary_stats$total_patients, "\n"))
cat(paste("Eligible:", cohort_results$summary_stats$eligible_patients, "\n"))
cat("\n=== INDIVIDUAL SCREENING RESULTS ===\n")
for (result in cohort_results$screening_results) {
cat(paste("\nPatient:", result$patient_id, "\n"))
cat(paste(" Eligible:", if (result$eligible) "Yes" else "No", "\n"))
cat(paste(" Confidence:", round(result$confidence_score * 100, 1), "%\n"))
cat(paste(" Recommendation:", result$recommendation, "\n"))
}
```
### Expected Output
```
=== CLINICAL TRIAL ELIGIBILITY SCREENING REPORT ===
Trial ID: NCT05123456
Patients Screened: 2
Immediately Eligible: 1
Requires Review: 0
=== INDIVIDUAL SCREENING RESULTS ===
Patient: PT001
Eligible: Yes
Confidence: 100.0%
Recommendation: ELIGIBLE: High confidence match (100.0% inclusion criteria met)
Matched Criteria: INCL_001, INCL_002, INCL_003
Patient: PT002
Eligible: No
Confidence: 40.0%
Recommendation: EXCLUDED: EXCL_002: Pregnancy
Matched Criteria: INCL_001, INCL_002, INCL_003
Exclusion Reasons: EXCL_002: Pregnancy
=== RECRUITMENT RECOMMENDATIONS ===
Patient PT001: HIGH priority - Schedule screening visit
Contact within: Within 48 hours
Notes: Strong match - 3 criteria met
=== INSIGHTS ===
• High eligibility rate (50.0%) - Good patient population match
• Most common exclusion: EXCL_002: Pregnancy (1 patients)
```
## Integration Patterns
### 1. EHR Integration with Real-time Screening
```python Python
class EHRTrialIntegration:
def __init__(self, omophub_api_key: str, ehr_client):
self.eligibility_engine = ClinicalTrialEligibilityEngine(omophub_api_key)
self.ehr = ehr_client
def setup_automated_screening(self, trial_id: str, trial_criteria: List[EligibilityCriteria]) -> Dict[str, Any]:
"""Set up automated screening for new patients"""
# Register screening criteria in system
screening_config = {
'trial_id': trial_id,
'criteria': trial_criteria,
'auto_screen_new_patients': True,
'auto_screen_updated_records': True,
'notification_settings': {
'eligible_patients': True,
'potential_patients': True,
'daily_summary': True
}
}
return screening_config
def screen_on_patient_update(self, patient_id: str, updated_fields: List[str]) -> Optional[ScreeningResult]:
"""Screen patient when their record is updated"""
# Check if update is relevant to trial criteria
relevant_fields = ['conditions', 'medications', 'lab_results', 'procedures', 'demographics']
if not any(field in updated_fields for field in relevant_fields):
return None
# Get current patient data
patient_data = self.extract_patient_data(patient_id)
# Get active trial criteria (could be from database)
active_trials = self.get_active_trials()
screening_results = []
for trial in active_trials:
result = self.eligibility_engine.screen_patient(
patient_data,
trial['criteria'],
trial['trial_id']
)
screening_results.append(result)
# Send notifications for newly eligible patients
for result in screening_results:
if result.eligible:
self.send_eligibility_notification(result)
return screening_results
def extract_patient_data(self, patient_id: str) -> PatientData:
"""Extract patient data from EHR"""
patient_record = self.ehr.get_patient(patient_id)
return PatientData(
patient_id=patient_id,
age=patient_record.get('age', 0),
gender=patient_record.get('gender', ''),
conditions=self.extract_conditions(patient_record),
medications=self.extract_medications(patient_record),
lab_results=self.extract_lab_results(patient_record),
procedures=self.extract_procedures(patient_record),
vitals=self.extract_vitals(patient_record)
)
def send_eligibility_notification(self, result: ScreeningResult):
"""Send notification to research team"""
notification = {
'type': 'PATIENT_ELIGIBLE',
'trial_id': result.trial_id,
'patient_id': result.patient_id,
'confidence': result.confidence_score,
'matched_criteria': result.matched_criteria,
'message': f"Patient {result.patient_id} is eligible for trial {result.trial_id}",
'action_required': 'Contact patient to schedule screening visit',
'generated_at': datetime.now().isoformat()
}
# Send to research coordinator (implementation depends on notification system)
self.send_notification(notification)
```
```javascript JavaScript
class EHRTrialIntegration {
constructor(omophubApiKey, ehrClient) {
this.eligibilityEngine = new ClinicalTrialEligibilityEngine(omophubApiKey);
this.ehr = ehrClient;
}
setupAutomatedScreening(trialId, trialCriteria) {
const screeningConfig = {
trial_id: trialId,
criteria: trialCriteria,
auto_screen_new_patients: true,
auto_screen_updated_records: true,
notification_settings: {
eligible_patients: true,
potential_patients: true,
daily_summary: true
}
};
return screeningConfig;
}
async screenOnPatientUpdate(patientId, updatedFields) {
// Check if update is relevant to trial criteria
const relevantFields = ['conditions', 'medications', 'lab_results', 'procedures', 'demographics'];
if (!updatedFields.some(field => relevantFields.includes(field))) {
return null;
}
// Get current patient data
const patientData = await this.extractPatientData(patientId);
// Get active trial criteria (could be from database)
const activeTrials = await this.getActiveTrials();
const screeningResults = [];
for (const trial of activeTrials) {
const result = await this.eligibilityEngine.screenPatient(
patientData,
trial.criteria,
trial.trial_id
);
screeningResults.push(result);
}
// Send notifications for newly eligible patients
for (const result of screeningResults) {
if (result.eligible) {
await this.sendEligibilityNotification(result);
}
}
return screeningResults;
}
async extractPatientData(patientId) {
const patientRecord = await this.ehr.getPatient(patientId);
return {
patient_id: patientId,
age: patientRecord.age || 0,
gender: patientRecord.gender || '',
conditions: this.extractConditions(patientRecord),
medications: this.extractMedications(patientRecord),
lab_results: this.extractLabResults(patientRecord),
procedures: this.extractProcedures(patientRecord),
vitals: this.extractVitals(patientRecord)
};
}
}
```
## Best Practices
### 1. Criteria Management and Validation
```python Python
class TrialCriteriaValidator:
def __init__(self, omophub_client):
self.client = omophub_client
def validate_criteria_set(self, criteria_list: List[EligibilityCriteria]) -> Dict[str, Any]:
"""Validate a set of trial criteria for completeness and accuracy"""
validation_result = {
'is_valid': True,
'warnings': [],
'errors': [],
'suggestions': []
}
# Check for conflicting criteria
conflicts = self.check_criteria_conflicts(criteria_list)
if conflicts:
validation_result['warnings'].extend(conflicts)
# Validate concept codes
for criteria in criteria_list:
code_validation = self.validate_concept_codes(criteria)
if code_validation['errors']:
validation_result['errors'].extend(code_validation['errors'])
validation_result['is_valid'] = False
if code_validation['warnings']:
validation_result['warnings'].extend(code_validation['warnings'])
# Check criteria balance
inclusion_count = len([c for c in criteria_list if c.type == CriteriaType.INCLUSION])
exclusion_count = len([c for c in criteria_list if c.type == CriteriaType.EXCLUSION])
if inclusion_count < 3:
validation_result['warnings'].append(
f"Few inclusion criteria ({inclusion_count}) - may result in over-broad matching"
)
if exclusion_count > inclusion_count * 2:
validation_result['warnings'].append(
f"Many exclusion criteria ({exclusion_count}) - may result in very low eligibility rates"
)
# Generate optimization suggestions
suggestions = self.generate_optimization_suggestions(criteria_list)
validation_result['suggestions'].extend(suggestions)
return validation_result
def validate_concept_codes(self, criteria: EligibilityCriteria) -> Dict[str, Any]:
"""Validate concept codes in criteria"""
code_validation = {
'errors': [],
'warnings': []
}
for code in criteria.concept_codes:
for vocab in criteria.vocabularies:
try:
concept = self.client.get_concept_by_code(vocab, code)
if not concept:
code_validation['errors'].append(
f"Invalid concept code: {code} in {vocab}"
)
else:
# Check if concept is standard
if concept.get('standard_concept') != 'S':
code_validation['warnings'].append(
f"Non-standard concept used: {code} ({concept.get('concept_name')})"
)
# Check if concept is active
if concept.get('invalid_reason'):
code_validation['warnings'].append(
f"Deprecated concept used: {code} - {concept.get('invalid_reason')}"
)
except Exception as e:
code_validation['errors'].append(
f"Error validating {code} in {vocab}: {str(e)}"
)
return code_validation
def check_criteria_conflicts(self, criteria_list: List[EligibilityCriteria]) -> List[str]:
"""Check for potentially conflicting criteria"""
conflicts = []
# Check for age conflicts
age_criteria = [c for c in criteria_list if c.age_constraint]
if len(age_criteria) > 1:
min_ages = [c.age_constraint.get('min_age', 0) for c in age_criteria]
max_ages = [c.age_constraint.get('max_age', 150) for c in age_criteria]
effective_min = max(min_ages)
effective_max = min(max_ages)
if effective_min >= effective_max:
conflicts.append(f"Conflicting age criteria: effective range is {effective_min}-{effective_max}")
# Check inclusion vs exclusion conflicts
inclusion_codes = set()
exclusion_codes = set()
for criteria in criteria_list:
if criteria.type == CriteriaType.INCLUSION:
inclusion_codes.update(criteria.concept_codes)
else:
exclusion_codes.update(criteria.concept_codes)
overlap = inclusion_codes.intersection(exclusion_codes)
if overlap:
conflicts.append(f"Codes appear in both inclusion and exclusion: {list(overlap)}")
return conflicts
def generate_optimization_suggestions(self, criteria_list: List[EligibilityCriteria]) -> List[str]:
"""Generate suggestions for optimizing criteria"""
suggestions = []
# Suggest hierarchical matching for condition-based criteria
exact_match_criteria = [c for c in criteria_list if c.match_type == MatchType.EXACT]
if len(exact_match_criteria) > 3:
suggestions.append(
"Consider using hierarchical matching for some condition criteria to increase patient pool"
)
# Suggest temporal constraints for more precision
condition_criteria = [c for c in criteria_list if not c.temporal_constraint and c.concept_codes]
if len(condition_criteria) > 2:
suggestions.append(
"Consider adding temporal constraints (recent diagnosis, treatment timeline) for more precise matching"
)
return suggestions
```
## Next Steps
Analyze population health using clinical trial data
Check drug interactions for trial participants
Map lab results for eligibility criteria
Master advanced concept search for criteria matching
# Drug Interaction Checking
Source: https://docs.omophub.com/guides/use-cases/drug-interaction-checking
Implement comprehensive drug interaction checking using OMOPHub API with RxNorm vocabulary and relationship analysis
## Overview
Drug interaction checking is critical for patient safety in healthcare applications. This guide demonstrates how to use the OMOPHub API to identify potential drug-drug interactions, contraindications, and clinical warnings using standardized medication vocabularies.
**Use Case**: Automatically detect potential drug interactions in patient medication lists to prevent adverse drug events (ADEs) and improve clinical decision-making.
## Business Problem
Healthcare organizations face significant challenges with medication safety:
* **Patient Safety Risk**: Drug interactions cause 125,000 deaths annually in the US
* **Clinical Decision Support**: Providers need real-time interaction alerts during prescribing
* **Medication Reconciliation**: Complex interaction checking across multiple medications
* **Regulatory Compliance**: FDA requirements for interaction checking in EHR systems
* **Cost Impact**: ADEs cost healthcare system \$100B+ annually
## Solution Architecture
```mermaid
graph TD
A[Patient Medication List] --> B[RxNorm Normalization]
B --> C[OMOPHub API]
C --> D[Drug Concept Lookup]
C --> E[Relationship Analysis]
C --> F[Interaction Detection]
D --> G[Active Ingredients]
E --> H[Contraindication Relationships]
F --> I[Severity Classification]
G --> J[Interaction Report]
H --> J
I --> J
J --> K[Clinical Decision Support]
J --> L[Alert Generation]
style F fill:#333333
style I fill:#333333
style J fill:#333333
```
## Implementation Guide
### Step 1: Set Up Drug Interaction Service
```python Python
from omophub import OMOPHubClient
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
import logging
import hashlib
class InteractionSeverity(Enum):
CONTRAINDICATED = "contraindicated"
MAJOR = "major"
MODERATE = "moderate"
MINOR = "minor"
UNKNOWN = "unknown"
@dataclass
class DrugConcept:
concept_id: int
concept_name: str
concept_code: str
vocabulary_id: str
ingredients: List[Dict[str, Any]]
@dataclass
class DrugInteraction:
drug1: DrugConcept
drug2: DrugConcept
severity: InteractionSeverity
mechanism: str
clinical_effect: str
management: str
evidence_level: str
class DrugInteractionChecker:
def __init__(self, api_key: str):
self.client = OMOPHubClient(api_key=api_key)
self.logger = logging.getLogger(__name__)
# Interaction relationship types to check
self.interaction_relationships = [
"Contraindicated with",
"May interact with",
"Has drug interaction with",
"May be contraindicated with",
"Metabolized by same enzyme as"
]
def normalize_medication(self, medication_text: str) -> Optional[DrugConcept]:
"""Normalize medication text to RxNorm concept"""
try:
# Search for medication in RxNorm
search_results = self.client.search_concepts({
"query": medication_text,
"vocabularies": ["RxNorm"],
"domains": ["Drug"],
"standard_concepts_only": True,
"limit": 10
})
if not search_results["concepts"]:
# Try broader search if no exact match
search_results = self.client.search_concepts({
"query": medication_text,
"vocabularies": ["RxNorm", "SNOMED"],
"domains": ["Drug"],
"include_invalid": False,
"limit": 5
})
if search_results["concepts"]:
best_match = search_results["concepts"][0]
# Get active ingredients
ingredients = self.get_active_ingredients(best_match["concept_id"])
return DrugConcept(
concept_id=best_match["concept_id"],
concept_name=best_match["concept_name"],
concept_code=best_match["concept_code"],
vocabulary_id=best_match["vocabulary_id"],
ingredients=ingredients
)
except Exception as e:
# Hash medication text to prevent PHI leakage in logs
med_hash = hashlib.sha256(medication_text.encode('utf-8')).hexdigest()[:8]
self.logger.error(f"Error normalizing medication [hash:{med_hash}]: {e}")
return None
def get_active_ingredients(self, drug_concept_id: int) -> List[Dict[str, Any]]:
"""Get active ingredients for a drug concept"""
try:
# Get relationships to find active ingredients
relationships = self.client.get_concept_relationships(
drug_concept_id,
relationship_types=["Has active ingredient", "Contains"]
)
ingredients = []
for rel in relationships["relationships"]:
if rel["relationship_id"] in ["Has active ingredient", "Contains"]:
ingredient_concept = self.client.get_concept(rel["target_concept_id"])
ingredients.append({
"concept_id": ingredient_concept["concept_id"],
"concept_name": ingredient_concept["concept_name"],
"concept_code": ingredient_concept["concept_code"]
})
return ingredients
except Exception as e:
self.logger.error(f"Error getting ingredients for concept {drug_concept_id}: {e}")
return []
```
```javascript JavaScript
import { OMOPHubClient } from '@omophub/sdk';
const InteractionSeverity = {
CONTRAINDICATED: 'contraindicated',
MAJOR: 'major',
MODERATE: 'moderate',
MINOR: 'minor',
UNKNOWN: 'unknown'
};
class DrugInteractionChecker {
constructor(apiKey) {
this.client = new OMOPHubClient({ apiKey });
// Interaction relationship types to check
this.interactionRelationships = [
'Contraindicated with',
'May interact with',
'Has drug interaction with',
'May be contraindicated with',
'Metabolized by same enzyme as'
];
}
async normalizeMedication(medicationText) {
try {
// Search for medication in RxNorm
let searchResults = await this.client.searchConcepts({
query: medicationText,
vocabularies: ['RxNorm'],
domains: ['Drug'],
standard_concepts_only: true,
limit: 10
});
// Safe check for search results
if (!searchResults?.data?.concepts?.length) {
// Try broader search if no exact match
searchResults = await this.client.searchConcepts({
query: medicationText,
vocabularies: ['RxNorm', 'SNOMED'],
domains: ['Drug'],
include_invalid: false,
limit: 5
});
// Early return if broader search also fails
if (!searchResults?.data?.concepts?.length) {
return null;
}
}
// Safe array access with fallback
const concepts = searchResults?.data?.concepts || [];
if (concepts.length === 0) {
return null;
}
const bestMatch = concepts[0];
// Ensure bestMatch has required properties
if (!bestMatch?.concept_id) {
return null;
}
// Get active ingredients
const ingredients = await this.getActiveIngredients(bestMatch.concept_id);
return {
concept_id: bestMatch.concept_id,
concept_name: bestMatch.concept_name,
concept_code: bestMatch.concept_code,
vocabulary_id: bestMatch.vocabulary_id,
ingredients: ingredients || []
};
} catch (error) {
// Create simple hash to prevent PHI leakage (basic string hash)
const simpleHash = medicationText.split('').reduce((hash, char) =>
((hash << 5) - hash + char.charCodeAt(0)) & 0xffffffff, 0
).toString(16).slice(-8);
console.error(`Error normalizing medication [hash:${simpleHash}]:`, error);
}
return null;
}
async getActiveIngredients(drugConceptId) {
try {
// Get relationships to find active ingredients
const relationships = await this.client.getConceptRelationships(
drugConceptId,
{ relationship_types: ['Has active ingredient', 'Contains'] }
);
// Safe check for relationships data
const relationshipsData = relationships?.data?.relationships || [];
if (relationshipsData.length === 0) {
return [];
}
const ingredients = [];
for (const rel of relationshipsData) {
if (['Has active ingredient', 'Contains'].includes(rel?.relationship_id)) {
try {
const ingredientConcept = await this.client.getConcept(rel.target_concept_id);
// Safe access to ingredient concept data
const conceptData = ingredientConcept?.data;
if (conceptData?.concept_id) {
ingredients.push({
concept_id: conceptData.concept_id,
concept_name: conceptData.concept_name,
concept_code: conceptData.concept_code
});
}
} catch (ingredientError) {
// Log individual ingredient lookup errors but continue processing
console.error(`Error getting ingredient concept ${rel?.target_concept_id}:`, ingredientError);
continue;
}
}
}
return ingredients;
} catch (error) {
console.error(`Error getting ingredients for concept ${drugConceptId}:`, error);
return [];
}
}
}
```
```r R
library(omophub)
# Define interaction severity levels
INTERACTION_SEVERITY <- list(
CONTRAINDICATED = "contraindicated",
MAJOR = "major",
MODERATE = "moderate",
MINOR = "minor",
UNKNOWN = "unknown"
)
# Initialize drug interaction checker
init_drug_checker <- function(api_key) {
client <- omophub_client(api_key = api_key)
# Interaction relationship types to check
interaction_relationships <- c(
"Contraindicated with",
"May interact with",
"Has drug interaction with",
"May be contraindicated with",
"Metabolized by same enzyme as"
)
list(
client = client,
interaction_relationships = interaction_relationships
)
}
normalize_medication <- function(checker, medication_text) {
tryCatch({
# Search for medication in RxNorm
search_results <- search_concepts(checker$client,
query = medication_text,
vocabularies = "RxNorm",
domains = "Drug",
standard_concepts_only = TRUE,
limit = 10
)
if (nrow(search_results$concepts) == 0) {
# Try broader search if no exact match
search_results <- search_concepts(checker$client,
query = medication_text,
vocabularies = c("RxNorm", "SNOMED"),
domains = "Drug",
include_invalid = FALSE,
limit = 5
)
}
if (nrow(search_results$concepts) > 0) {
best_match <- search_results$concepts[1, ]
# Get active ingredients
ingredients <- get_active_ingredients(checker, best_match$concept_id)
return(list(
concept_id = best_match$concept_id,
concept_name = best_match$concept_name,
concept_code = best_match$concept_code,
vocabulary_id = best_match$vocabulary_id,
ingredients = ingredients
))
}
}, error = function(e) {
cat(paste("Error normalizing medication", medication_text, ":", e$message, "\n"))
return(NULL)
})
}
# Helper function: Get active ingredients for a drug concept
get_active_ingredients <- function(checker, drug_concept_id) {
tryCatch({
# Get relationships to find active ingredients
relationships <- get_concept_relationships(checker$client,
concept_id = drug_concept_id,
relationship_types = c("Has active ingredient", "Contains")
)
ingredients <- list()
if (length(relationships$relationships) > 0) {
for (rel in relationships$relationships) {
if (rel$relationship_id %in% c("Has active ingredient", "Contains")) {
ingredient_concept <- get_concept(checker$client, rel$target_concept_id)
ingredients[[length(ingredients) + 1]] <- list(
concept_id = ingredient_concept$concept_id,
concept_name = ingredient_concept$concept_name,
concept_code = ingredient_concept$concept_code
)
}
}
}
return(ingredients)
}, error = function(e) {
cat(paste("Error getting ingredients for concept", drug_concept_id, ":", e$message, "\n"))
return(list())
})
}
# Helper function: Check direct interaction between two drugs
check_direct_interaction <- function(checker, drug1, drug2) {
tryCatch({
# Get all relationships for drug1
relationships <- get_concept_relationships(checker$client,
concept_id = drug1$concept_id,
relationship_types = checker$interaction_relationships
)
if (length(relationships$relationships) > 0) {
# Check if drug2 is in the relationships
for (rel in relationships$relationships) {
if (rel$target_concept_id == drug2$concept_id) {
severity <- determine_severity(rel$relationship_id)
return(list(
drug1 = drug1,
drug2 = drug2,
severity = severity,
mechanism = get_interaction_mechanism(rel$relationship_id),
clinical_effect = get_clinical_effect(rel$relationship_id),
management = get_management_guidance(rel$relationship_id),
evidence_level = if (is.null(rel$evidence_level)) "Unknown" else rel$evidence_level
))
}
}
}
return(NULL)
}, error = function(e) {
cat(paste("Error in direct interaction check:", e$message, "\n"))
return(NULL)
})
}
# Helper function: Check ingredient-level interactions
check_ingredient_interactions <- function(checker, drug1, drug2) {
tryCatch({
if (length(drug1$ingredients) == 0 || length(drug2$ingredients) == 0) {
return(NULL)
}
for (ingredient1 in drug1$ingredients) {
for (ingredient2 in drug2$ingredients) {
# Get relationships for ingredient1
relationships <- get_concept_relationships(checker$client,
concept_id = ingredient1$concept_id,
relationship_types = checker$interaction_relationships
)
if (length(relationships$relationships) > 0) {
for (rel in relationships$relationships) {
if (rel$target_concept_id == ingredient2$concept_id) {
severity <- determine_severity(rel$relationship_id)
return(list(
drug1 = drug1,
drug2 = drug2,
severity = severity,
mechanism = paste("Interaction between", ingredient1$concept_name, "and", ingredient2$concept_name),
clinical_effect = get_clinical_effect(rel$relationship_id),
management = get_management_guidance(rel$relationship_id),
evidence_level = if (is.null(rel$evidence_level)) "Unknown" else rel$evidence_level
))
}
}
}
}
}
return(NULL)
}, error = function(e) {
cat(paste("Error checking ingredient interaction:", e$message, "\n"))
return(NULL)
})
}
# Helper function: Determine interaction severity
determine_severity <- function(relationship_type) {
severity_mapping <- list(
"Contraindicated with" = INTERACTION_SEVERITY$CONTRAINDICATED,
"May be contraindicated with" = INTERACTION_SEVERITY$MAJOR,
"Has drug interaction with" = INTERACTION_SEVERITY$MODERATE,
"May interact with" = INTERACTION_SEVERITY$MINOR,
"Metabolized by same enzyme as" = INTERACTION_SEVERITY$MINOR
)
if (relationship_type %in% names(severity_mapping)) {
return(severity_mapping[[relationship_type]])
} else {
return(INTERACTION_SEVERITY$UNKNOWN)
}
}
# Helper function: Get interaction mechanism description
get_interaction_mechanism <- function(relationship_type) {
mechanisms <- list(
"Contraindicated with" = "Pharmacodynamic antagonism or severe adverse reaction risk",
"May interact with" = "Potential pharmacokinetic or pharmacodynamic interaction",
"Has drug interaction with" = "Established interaction mechanism",
"Metabolized by same enzyme as" = "Competitive metabolism leading to altered drug levels"
)
if (relationship_type %in% names(mechanisms)) {
return(mechanisms[[relationship_type]])
} else {
return("Unknown interaction mechanism")
}
}
# Helper function: Get clinical effect description
get_clinical_effect <- function(relationship_type) {
effects <- list(
"Contraindicated with" = "Serious adverse events or therapeutic failure",
"May interact with" = "Altered therapeutic effect or increased side effects",
"Has drug interaction with" = "Modified drug efficacy or toxicity",
"Metabolized by same enzyme as" = "Altered plasma concentrations"
)
if (relationship_type %in% names(effects)) {
return(effects[[relationship_type]])
} else {
return("Potential clinical consequence")
}
}
# Helper function: Get management guidance
get_management_guidance <- function(relationship_type) {
guidance <- list(
"Contraindicated with" = "Avoid combination. Consider alternative medications.",
"May interact with" = "Monitor closely. Consider dose adjustment or timing modifications.",
"Has drug interaction with" = "Monitor for efficacy and toxicity. Dose adjustment may be needed.",
"Metabolized by same enzyme as" = "Monitor drug levels. Stagger administration if possible."
)
if (relationship_type %in% names(guidance)) {
return(guidance[[relationship_type]])
} else {
return("Consult clinical pharmacist or physician.")
}
}
```
### Step 2: Detect Drug Interactions
```python Python
def check_drug_interactions(self, medications: List[str]) -> List[DrugInteraction]:
"""Check for interactions between multiple medications"""
# Normalize all medications first
normalized_drugs = []
for med_text in medications:
drug = self.normalize_medication(med_text)
if drug:
normalized_drugs.append(drug)
if len(normalized_drugs) < 2:
return []
interactions = []
# Check each pair of medications
for i, drug1 in enumerate(normalized_drugs):
for j, drug2 in enumerate(normalized_drugs[i+1:], i+1):
interaction = self.check_pair_interaction(drug1, drug2)
if interaction:
interactions.append(interaction)
# Sort by severity
severity_order = {
InteractionSeverity.CONTRAINDICATED: 0,
InteractionSeverity.MAJOR: 1,
InteractionSeverity.MODERATE: 2,
InteractionSeverity.MINOR: 3,
InteractionSeverity.UNKNOWN: 4
}
interactions.sort(key=lambda x: severity_order[x.severity])
return interactions
def check_pair_interaction(self, drug1: DrugConcept, drug2: DrugConcept) -> Optional[DrugInteraction]:
"""Check for interaction between two specific drugs"""
try:
# Direct drug-to-drug interaction check
direct_interaction = self.check_direct_interaction(drug1, drug2)
if direct_interaction:
return direct_interaction
# Check ingredient-level interactions
ingredient_interaction = self.check_ingredient_interactions(drug1, drug2)
if ingredient_interaction:
return ingredient_interaction
# Check class-based interactions
class_interaction = self.check_drug_class_interactions(drug1, drug2)
if class_interaction:
return class_interaction
except Exception as e:
self.logger.error(f"Error checking interaction between {drug1.concept_name} and {drug2.concept_name}: {e}")
return None
def check_direct_interaction(self, drug1: DrugConcept, drug2: DrugConcept) -> Optional[DrugInteraction]:
"""Check for direct relationships between two drugs"""
try:
# Get all relationships for drug1
relationships = self.client.get_concept_relationships(
drug1.concept_id,
relationship_types=self.interaction_relationships
)
# Check if drug2 is in the relationships
for rel in relationships["relationships"]:
if rel["target_concept_id"] == drug2.concept_id:
severity = self.determine_severity(rel["relationship_id"])
return DrugInteraction(
drug1=drug1,
drug2=drug2,
severity=severity,
mechanism=self.get_interaction_mechanism(rel),
clinical_effect=self.get_clinical_effect(rel),
management=self.get_management_guidance(rel),
evidence_level=rel.get("evidence_level", "Unknown")
)
except Exception as e:
self.logger.error(f"Error in direct interaction check: {e}")
return None
def check_ingredient_interactions(self, drug1: DrugConcept, drug2: DrugConcept) -> Optional[DrugInteraction]:
"""Check for interactions between active ingredients"""
# Define severity ranking (lower number = more severe)
severity_rank = {
InteractionSeverity.CONTRAINDICATED: 0,
InteractionSeverity.MAJOR: 1,
InteractionSeverity.MODERATE: 2,
InteractionSeverity.MINOR: 3,
InteractionSeverity.UNKNOWN: 4
}
most_severe_interaction = None
best_severity_rank = float('inf')
for ingredient1 in drug1.ingredients:
for ingredient2 in drug2.ingredients:
try:
relationships = self.client.get_concept_relationships(
ingredient1["concept_id"],
relationship_types=self.interaction_relationships
)
for rel in relationships["relationships"]:
if rel["target_concept_id"] == ingredient2["concept_id"]:
severity = self.determine_severity(rel["relationship_id"])
current_rank = severity_rank.get(severity, float('inf'))
# Keep track of most severe interaction found
if current_rank < best_severity_rank:
best_severity_rank = current_rank
most_severe_interaction = DrugInteraction(
drug1=drug1,
drug2=drug2,
severity=severity,
mechanism=f"Interaction between {ingredient1['concept_name']} and {ingredient2['concept_name']}",
clinical_effect=self.get_clinical_effect(rel),
management=self.get_management_guidance(rel),
evidence_level=rel.get("evidence_level", "Unknown")
)
except Exception as e:
self.logger.error(f"Error checking ingredient interaction: {e}")
continue
return most_severe_interaction
def check_drug_class_interactions(self, drug1: DrugConcept, drug2: DrugConcept) -> Optional[DrugInteraction]:
"""Check for interactions between drug classes"""
# TODO: Implement class-based interaction checking
# This would involve:
# 1. Getting drug class hierarchies for both drugs using OMOPHub API
# 2. Checking for known class-to-class interactions (e.g., ACE inhibitors + NSAIDs)
# 3. Mapping class interactions to specific drug pair severity
#
# For now, return None to prevent AttributeError
try:
# Placeholder for class-based lookup
# In a full implementation, you would:
# - Get concept ancestors to find drug classes
# - Query interaction databases for class-level interactions
# - Return appropriate DrugInteraction object if found
pass
except Exception as e:
self.logger.error(f"Error in drug class interaction check: {e}")
return None
def determine_severity(self, relationship_type: str) -> InteractionSeverity:
"""Determine interaction severity based on relationship type"""
severity_mapping = {
"Contraindicated with": InteractionSeverity.CONTRAINDICATED,
"May be contraindicated with": InteractionSeverity.MAJOR,
"Has drug interaction with": InteractionSeverity.MODERATE,
"May interact with": InteractionSeverity.MINOR,
"Metabolized by same enzyme as": InteractionSeverity.MINOR
}
return severity_mapping.get(relationship_type, InteractionSeverity.UNKNOWN)
def get_interaction_mechanism(self, relationship: Dict[str, Any]) -> str:
"""Get mechanism of interaction"""
mechanisms = {
"Contraindicated with": "Pharmacodynamic antagonism or severe adverse reaction risk",
"May interact with": "Potential pharmacokinetic or pharmacodynamic interaction",
"Has drug interaction with": "Established interaction mechanism",
"Metabolized by same enzyme as": "Competitive metabolism leading to altered drug levels"
}
return mechanisms.get(relationship["relationship_id"], "Unknown interaction mechanism")
def get_clinical_effect(self, relationship: Dict[str, Any]) -> str:
"""Get clinical effect description"""
# This would typically come from a clinical database
# For demo, return generic descriptions
effects = {
"Contraindicated with": "Serious adverse events or therapeutic failure",
"May interact with": "Altered therapeutic effect or increased side effects",
"Has drug interaction with": "Modified drug efficacy or toxicity",
"Metabolized by same enzyme as": "Altered plasma concentrations"
}
return effects.get(relationship["relationship_id"], "Potential clinical consequence")
def get_management_guidance(self, relationship: Dict[str, Any]) -> str:
"""Get management recommendations"""
guidance = {
"Contraindicated with": "Avoid combination. Consider alternative medications.",
"May interact with": "Monitor closely. Consider dose adjustment or timing modifications.",
"Has drug interaction with": "Monitor for efficacy and toxicity. Dose adjustment may be needed.",
"Metabolized by same enzyme as": "Monitor drug levels. Stagger administration if possible."
}
return guidance.get(relationship["relationship_id"], "Consult clinical pharmacist or physician.")
```
```javascript JavaScript
async checkDrugInteractions(medications) {
// Normalize all medications first
const normalizedDrugs = [];
for (const medText of medications) {
const drug = await this.normalizeMedication(medText);
if (drug) {
normalizedDrugs.push(drug);
}
}
if (normalizedDrugs.length < 2) {
return [];
}
const interactions = [];
// Check each pair of medications
for (let i = 0; i < normalizedDrugs.length; i++) {
for (let j = i + 1; j < normalizedDrugs.length; j++) {
const interaction = await this.checkPairInteraction(normalizedDrugs[i], normalizedDrugs[j]);
if (interaction) {
interactions.push(interaction);
}
}
}
// Sort by severity
const severityOrder = {
[InteractionSeverity.CONTRAINDICATED]: 0,
[InteractionSeverity.MAJOR]: 1,
[InteractionSeverity.MODERATE]: 2,
[InteractionSeverity.MINOR]: 3,
[InteractionSeverity.UNKNOWN]: 4
};
interactions.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
return interactions;
}
async checkDrugClassInteractions(drug1, drug2) {
// TODO: implement ATC/RxNorm class-level checks via relationships
return null;
}
} catch (error) {
console.error(`Error checking interaction between ${drug1.concept_name} and ${drug2.concept_name}:`, error);
}
return null;
}
async checkDirectInteraction(drug1, drug2) {
try {
// Get all relationships for drug1
const relationships = await this.client.getConceptRelationships(
drug1.concept_id,
{ relationship_types: this.interactionRelationships }
);
// Check if drug2 is in the relationships
for (const rel of relationships.data.relationships) {
if (rel.target_concept_id === drug2.concept_id) {
const severity = this.determineSeverity(rel.relationship_id);
return {
drug1,
drug2,
severity,
mechanism: this.getInteractionMechanism(rel),
clinical_effect: this.getClinicalEffect(rel),
management: this.getManagementGuidance(rel),
evidence_level: rel.evidence_level || 'Unknown'
};
}
}
} catch (error) {
console.error('Error in direct interaction check:', error);
}
return null;
}
determineSeverity(relationshipType) {
const severityMapping = {
'Contraindicated with': InteractionSeverity.CONTRAINDICATED,
'May be contraindicated with': InteractionSeverity.MAJOR,
'Has drug interaction with': InteractionSeverity.MODERATE,
'May interact with': InteractionSeverity.MINOR,
'Metabolized by same enzyme as': InteractionSeverity.MINOR
};
return severityMapping[relationshipType] || InteractionSeverity.UNKNOWN;
}
```
```r R
check_drug_interactions <- function(checker, medications) {
# Normalize all medications first
normalized_drugs <- list()
for (med_text in medications) {
drug <- normalize_medication(checker, med_text)
if (!is.null(drug)) {
normalized_drugs[[length(normalized_drugs) + 1]] <- drug
}
}
if (length(normalized_drugs) < 2) {
return(list())
}
interactions <- list()
# Check each pair of medications
for (i in 1:(length(normalized_drugs) - 1)) {
for (j in (i + 1):length(normalized_drugs)) {
interaction <- check_pair_interaction(checker, normalized_drugs[[i]], normalized_drugs[[j]])
if (!is.null(interaction)) {
interactions[[length(interactions) + 1]] <- interaction
}
}
}
return(interactions)
}
check_pair_interaction <- function(checker, drug1, drug2) {
tryCatch({
# Direct drug-to-drug interaction check
direct_interaction <- check_direct_interaction(checker, drug1, drug2)
if (!is.null(direct_interaction)) {
return(direct_interaction)
}
# Check ingredient-level interactions
ingredient_interaction <- check_ingredient_interactions(checker, drug1, drug2)
if (!is.null(ingredient_interaction)) {
return(ingredient_interaction)
}
return(NULL)
}, error = function(e) {
cat(paste("Error checking interaction between", drug1$concept_name, "and", drug2$concept_name, ":", e$message, "\n"))
return(NULL)
})
}
```
### Step 3: Generate Clinical Report
```python Python
def generate_interaction_report(self, medications: List[str], patient_context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Generate comprehensive drug interaction report"""
# Check all interactions
interactions = self.check_drug_interactions(medications)
# Categorize interactions by severity
categorized_interactions = {
"contraindicated": [],
"major": [],
"moderate": [],
"minor": [],
"unknown": []
}
for interaction in interactions:
severity_key = interaction.severity.value
categorized_interactions[severity_key].append(interaction)
# Generate summary statistics
summary = {
"total_medications": len(medications),
"total_interactions": len(interactions),
"contraindicated_count": len(categorized_interactions["contraindicated"]),
"major_count": len(categorized_interactions["major"]),
"moderate_count": len(categorized_interactions["moderate"]),
"minor_count": len(categorized_interactions["minor"]),
"risk_level": self.assess_overall_risk(interactions)
}
# Generate clinical recommendations
recommendations = self.generate_recommendations(interactions, patient_context)
return {
"patient_context": patient_context or {},
"medications_analyzed": medications,
"interactions": categorized_interactions,
"summary": summary,
"recommendations": recommendations,
"generated_at": datetime.now().isoformat()
}
def assess_overall_risk(self, interactions: List[DrugInteraction]) -> str:
"""Assess overall risk level of medication regimen"""
if any(i.severity == InteractionSeverity.CONTRAINDICATED for i in interactions):
return "HIGH_RISK"
elif any(i.severity == InteractionSeverity.MAJOR for i in interactions):
return "MODERATE_RISK"
elif any(i.severity == InteractionSeverity.MODERATE for i in interactions):
return "LOW_MODERATE_RISK"
elif interactions:
return "LOW_RISK"
else:
return "MINIMAL_RISK"
def generate_recommendations(self, interactions: List[DrugInteraction], patient_context: Optional[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Generate actionable clinical recommendations"""
recommendations = []
# Critical interactions requiring immediate action
contraindicated = [i for i in interactions if i.severity == InteractionSeverity.CONTRAINDICATED]
if contraindicated:
recommendations.append({
"priority": "URGENT",
"type": "CONTRAINDICATED_COMBINATION",
"message": f"Found {len(contraindicated)} contraindicated drug combination(s). Immediate review required.",
"actions": [
"Discontinue one or both medications",
"Consider alternative therapeutic options",
"Consult prescribing physician immediately"
]
})
# Major interactions requiring monitoring
major = [i for i in interactions if i.severity == InteractionSeverity.MAJOR]
if major:
recommendations.append({
"priority": "HIGH",
"type": "MAJOR_INTERACTION",
"message": f"Found {len(major)} major drug interaction(s). Close monitoring recommended.",
"actions": [
"Monitor patient for adverse effects",
"Consider dose adjustments",
"Increase frequency of clinical assessments",
"Document interaction awareness in medical record"
]
})
# Patient-specific recommendations
if patient_context:
age = patient_context.get("age")
comorbidities = patient_context.get("comorbidities", [])
if age and age >= 65:
recommendations.append({
"priority": "MEDIUM",
"type": "GERIATRIC_CONSIDERATION",
"message": "Patient is elderly (≥65 years). Increased risk for drug interactions.",
"actions": [
"Consider lower starting doses",
"Monitor renal and hepatic function",
"Review medication list regularly",
"Simplify regimen when possible"
]
})
if "kidney_disease" in comorbidities:
recommendations.append({
"priority": "MEDIUM",
"type": "RENAL_CONSIDERATION",
"message": "Patient has renal impairment. Drug clearance may be affected.",
"actions": [
"Adjust doses for renal function",
"Monitor creatinine and eGFR",
"Avoid nephrotoxic combinations"
]
})
return recommendations
```
```javascript JavaScript
async generateInteractionReport(medications, patientContext = null) {
// Check all interactions
const interactions = await this.checkDrugInteractions(medications);
// Categorize interactions by severity
const categorizedInteractions = {
contraindicated: [],
major: [],
moderate: [],
minor: [],
unknown: []
};
interactions.forEach(interaction => {
const severityKey = interaction.severity;
categorizedInteractions[severityKey].push(interaction);
});
// Generate summary statistics
const summary = {
total_medications: medications.length,
total_interactions: interactions.length,
contraindicated_count: categorizedInteractions.contraindicated.length,
major_count: categorizedInteractions.major.length,
moderate_count: categorizedInteractions.moderate.length,
minor_count: categorizedInteractions.minor.length,
risk_level: this.assessOverallRisk(interactions)
};
// Generate clinical recommendations
const recommendations = this.generateRecommendations(interactions, patientContext);
return {
patient_context: patientContext || {},
medications_analyzed: medications,
interactions: categorizedInteractions,
summary,
recommendations,
generated_at: new Date().toISOString()
};
}
assessOverallRisk(interactions) {
if (interactions.some(i => i.severity === InteractionSeverity.CONTRAINDICATED)) {
return 'HIGH_RISK';
} else if (interactions.some(i => i.severity === InteractionSeverity.MAJOR)) {
return 'MODERATE_RISK';
} else if (interactions.some(i => i.severity === InteractionSeverity.MODERATE)) {
return 'LOW_MODERATE_RISK';
} else if (interactions.length > 0) {
return 'LOW_RISK';
} else {
return 'MINIMAL_RISK';
}
}
```
```r R
generate_interaction_report <- function(checker, medications, patient_context = NULL) {
# Check all interactions
interactions <- check_drug_interactions(checker, medications)
# Categorize interactions by severity
categorized_interactions <- list(
contraindicated = list(),
major = list(),
moderate = list(),
minor = list(),
unknown = list()
)
for (interaction in interactions) {
severity_key <- interaction$severity
categorized_interactions[[severity_key]][[length(categorized_interactions[[severity_key]]) + 1]] <- interaction
}
# Generate summary statistics
summary <- list(
total_medications = length(medications),
total_interactions = length(interactions),
contraindicated_count = length(categorized_interactions$contraindicated),
major_count = length(categorized_interactions$major),
moderate_count = length(categorized_interactions$moderate),
minor_count = length(categorized_interactions$minor),
risk_level = assess_overall_risk(interactions)
)
# Generate clinical recommendations
recommendations <- generate_recommendations(interactions, patient_context)
return(list(
patient_context = if(is.null(patient_context)) list() else patient_context,
medications_analyzed = medications,
interactions = categorized_interactions,
summary = summary,
recommendations = recommendations,
generated_at = Sys.time()
))
}
```
## Example Implementation
### Sample Medication List
```
Patient Medications:
1. Warfarin 5mg daily (anticoagulant)
2. Aspirin 81mg daily (antiplatelet)
3. Simvastatin 40mg nightly (statin)
4. Metformin 1000mg twice daily (diabetes)
5. Lisinopril 10mg daily (ACE inhibitor)
```
### Generated Interaction Report
```python Python
# Initialize the checker
checker = DrugInteractionChecker("your_api_key")
# Sample medication list
medications = [
"Warfarin 5mg",
"Aspirin 81mg",
"Simvastatin 40mg",
"Metformin 1000mg",
"Lisinopril 10mg"
]
# Patient context
patient_context = {
"age": 72,
"comorbidities": ["diabetes", "hypertension", "atrial_fibrillation"],
"renal_function": "mild_impairment"
}
# Generate report
report = checker.generate_interaction_report(medications, patient_context)
# Print results
print("=== DRUG INTERACTION REPORT ===")
print(f"Risk Level: {report['summary']['risk_level']}")
print(f"Total Interactions: {report['summary']['total_interactions']}")
print("\n=== MAJOR INTERACTIONS ===")
for interaction in report['interactions']['major']:
print(f"⚠️ {interaction.drug1.concept_name} + {interaction.drug2.concept_name}")
print(f" Mechanism: {interaction.mechanism}")
print(f" Clinical Effect: {interaction.clinical_effect}")
print(f" Management: {interaction.management}")
print()
print("=== RECOMMENDATIONS ===")
for rec in report['recommendations']:
print(f"🏥 [{rec['priority']}] {rec['message']}")
for action in rec['actions']:
print(f" • {action}")
print()
```
```javascript JavaScript
// Initialize the checker
const checker = new DrugInteractionChecker('your_api_key');
// Sample medication list
const medications = [
'Warfarin 5mg',
'Aspirin 81mg',
'Simvastatin 40mg',
'Metformin 1000mg',
'Lisinopril 10mg'
];
// Patient context
const patientContext = {
age: 72,
comorbidities: ['diabetes', 'hypertension', 'atrial_fibrillation'],
renal_function: 'mild_impairment'
};
// Generate report
checker.generateInteractionReport(medications, patientContext)
.then(report => {
console.log('=== DRUG INTERACTION REPORT ===');
console.log(`Risk Level: ${report.summary.risk_level}`);
console.log(`Total Interactions: ${report.summary.total_interactions}`);
console.log('\n=== MAJOR INTERACTIONS ===');
report.interactions.major.forEach(interaction => {
console.log(`⚠️ ${interaction.drug1.concept_name} + ${interaction.drug2.concept_name}`);
console.log(` Mechanism: ${interaction.mechanism}`);
console.log(` Clinical Effect: ${interaction.clinical_effect}`);
console.log(` Management: ${interaction.management}`);
console.log();
});
console.log('=== RECOMMENDATIONS ===');
report.recommendations.forEach(rec => {
console.log(`🏥 [${rec.priority}] ${rec.message}`);
rec.actions.forEach(action => {
console.log(` • ${action}`);
});
console.log();
});
})
.catch(error => {
console.error('Error generating report:', error);
});
```
```r R
# Initialize the checker
checker <- init_drug_checker("your_api_key")
# Sample medication list
medications <- c(
"Warfarin 5mg",
"Aspirin 81mg",
"Simvastatin 40mg",
"Metformin 1000mg",
"Lisinopril 10mg"
)
# Patient context
patient_context <- list(
age = 72,
comorbidities = c("diabetes", "hypertension", "atrial_fibrillation"),
renal_function = "mild_impairment"
)
# Generate report
report <- generate_interaction_report(checker, medications, patient_context)
# Print results
cat("=== DRUG INTERACTION REPORT ===\n")
cat(paste("Risk Level:", report$summary$risk_level, "\n"))
cat(paste("Total Interactions:", report$summary$total_interactions, "\n"))
cat("\n=== MAJOR INTERACTIONS ===\n")
for (interaction in report$interactions$major) {
cat(paste("⚠️ ", interaction$drug1$concept_name, "+", interaction$drug2$concept_name, "\n"))
cat(paste(" Mechanism:", interaction$mechanism, "\n"))
cat(paste(" Management:", interaction$management, "\n\n"))
}
```
### Expected Output
```
=== DRUG INTERACTION REPORT ===
Risk Level: MODERATE_RISK
Total Interactions: 3
=== MAJOR INTERACTIONS ===
⚠️ Warfarin + Aspirin
Mechanism: Additive anticoagulant effects
Clinical Effect: Increased bleeding risk
Management: Monitor INR closely. Consider gastroprotection.
=== MODERATE INTERACTIONS ===
⚠️ Simvastatin + Warfarin
Mechanism: CYP3A4 interaction affecting warfarin metabolism
Clinical Effect: Potential INR elevation
Management: Monitor INR when starting/stopping statin.
=== RECOMMENDATIONS ===
🏥 [HIGH] Found 1 major drug interaction(s). Close monitoring recommended.
• Monitor patient for adverse effects
• Consider dose adjustments
• Increase frequency of clinical assessments
🏥 [MEDIUM] Patient is elderly (≥65 years). Increased risk for drug interactions.
• Consider lower starting doses
• Monitor renal and hepatic function
• Review medication list regularly
```
## Integration Patterns
### 1. EHR Integration with Real-time Alerts
```python Python
class EHRInteractionIntegration:
def __init__(self, omophub_client, ehr_client):
self.checker = DrugInteractionChecker(omophub_client.api_key)
self.ehr = ehr_client
def check_new_prescription(self, patient_id: str, new_medication: str) -> Dict[str, Any]:
"""Check interactions when prescribing new medication"""
# Get current medications from EHR
current_meds = self.ehr.get_patient_medications(patient_id)
current_med_names = [med['name'] for med in current_meds if med['status'] == 'active']
# Add new medication to the list
all_medications = current_med_names + [new_medication]
# Get patient context
patient = self.ehr.get_patient(patient_id)
patient_context = {
"age": patient.get('age'),
"comorbidities": patient.get('conditions', []),
"allergies": patient.get('allergies', []),
"renal_function": patient.get('renal_status')
}
# Check interactions
report = self.checker.generate_interaction_report(all_medications, patient_context)
# Generate alerts for EHR
alerts = self.generate_ehr_alerts(report, new_medication)
return {
"interaction_report": report,
"alerts": alerts,
"safe_to_prescribe": report['summary']['risk_level'] not in ['HIGH_RISK'],
"requires_override": report['summary']['contraindicated_count'] > 0
}
def generate_ehr_alerts(self, report: Dict[str, Any], new_medication: str) -> List[Dict[str, Any]]:
"""Generate EHR-compatible alerts"""
alerts = []
# Critical alerts for contraindications
for interaction in report['interactions']['contraindicated']:
if new_medication.lower() in interaction.drug1.concept_name.lower() or \
new_medication.lower() in interaction.drug2.concept_name.lower():
alerts.append({
"level": "ERROR",
"title": "CONTRAINDICATED COMBINATION",
"message": f"Contraindication between {interaction.drug1.concept_name} and {interaction.drug2.concept_name}",
"recommendation": interaction.management,
"requires_override": True,
"blocking": True
})
# Warning alerts for major interactions
for interaction in report['interactions']['major']:
if new_medication.lower() in interaction.drug1.concept_name.lower() or \
new_medication.lower() in interaction.drug2.concept_name.lower():
alerts.append({
"level": "WARNING",
"title": "MAJOR INTERACTION",
"message": f"Major interaction between {interaction.drug1.concept_name} and {interaction.drug2.concept_name}",
"recommendation": interaction.management,
"requires_override": False,
"blocking": False
})
return alerts
```
```javascript JavaScript
class EHRInteractionIntegration {
constructor(omophubClient, ehrClient) {
this.checker = new DrugInteractionChecker(omophubClient.apiKey);
this.ehr = ehrClient;
}
async checkNewPrescription(patientId, newMedication) {
// Get current medications from EHR
const currentMeds = await this.ehr.getPatientMedications(patientId);
const currentMedNames = currentMeds
.filter(med => med.status === 'active')
.map(med => med.name);
// Add new medication to the list
const allMedications = [...currentMedNames, newMedication];
// Get patient context
const patient = await this.ehr.getPatient(patientId);
const patientContext = {
age: patient.age,
comorbidities: patient.conditions || [],
allergies: patient.allergies || [],
renal_function: patient.renalStatus
};
// Check interactions
const report = await this.checker.generateInteractionReport(allMedications, patientContext);
// Generate alerts for EHR
const alerts = this.generateEHRAlerts(report, newMedication);
return {
interaction_report: report,
alerts,
safe_to_prescribe: !['HIGH_RISK'].includes(report.summary.risk_level),
requires_override: report.summary.contraindicated_count > 0
};
}
generateEHRAlerts(report, newMedication) {
const alerts = [];
// Critical alerts for contraindications
report.interactions.contraindicated.forEach(interaction => {
if (newMedication.toLowerCase().includes(interaction.drug1.concept_name.toLowerCase()) ||
newMedication.toLowerCase().includes(interaction.drug2.concept_name.toLowerCase())) {
alerts.push({
level: 'ERROR',
title: 'CONTRAINDICATED COMBINATION',
message: `Contraindication between ${interaction.drug1.concept_name} and ${interaction.drug2.concept_name}`,
recommendation: interaction.management,
requires_override: true,
blocking: true
});
}
});
return alerts;
}
}
```
### 2. Pharmacy Integration
```python Python
class PharmacyInteractionSystem:
def __init__(self, omophub_api_key: str):
self.checker = DrugInteractionChecker(omophub_api_key)
def validate_prescription(self, prescription_data: Dict[str, Any]) -> Dict[str, Any]:
"""Validate prescription at pharmacy level"""
medications = prescription_data.get('medications', [])
patient_data = prescription_data.get('patient', {})
# Normalize medication names
med_names = [med['name'] for med in medications]
# Check interactions
report = self.checker.generate_interaction_report(med_names, patient_data)
# Pharmacy-specific validation
validation_result = {
"prescription_id": prescription_data.get('id'),
"validation_status": self.determine_validation_status(report),
"pharmacist_notes": self.generate_pharmacist_notes(report),
"patient_counseling_points": self.generate_counseling_points(report),
"dispensing_recommendations": self.generate_dispensing_recommendations(report)
}
return validation_result
def determine_validation_status(self, report: Dict[str, Any]) -> str:
"""Determine if prescription can be dispensed"""
if report['summary']['contraindicated_count'] > 0:
return "REJECTED"
elif report['summary']['major_count'] > 0:
return "REQUIRES_PHARMACIST_REVIEW"
elif report['summary']['moderate_count'] > 0:
return "DISPENSE_WITH_COUNSELING"
else:
return "APPROVED"
def generate_counseling_points(self, report: Dict[str, Any]) -> List[str]:
"""Generate patient counseling points"""
counseling_points = []
# Add counseling for each interaction
all_interactions = (
report['interactions']['major'] +
report['interactions']['moderate']
)
for interaction in all_interactions:
counseling_points.append(
f"Monitor for signs of interaction between {interaction.drug1.concept_name} "
f"and {interaction.drug2.concept_name}. {interaction.clinical_effect}"
)
return counseling_points
```
## Performance Optimization
### Caching Strategy for Drug Interactions
```python Python
import redis
import json
import hashlib
from typing import Optional
class CachedDrugInteractionChecker(DrugInteractionChecker):
def __init__(self, api_key: str, redis_url: str = "redis://localhost:6379"):
super().__init__(api_key)
self.redis_client = redis.from_url(redis_url)
self.cache_ttl = {
"drug_normalization": 86400, # 24 hours
"interactions": 43200, # 12 hours
"ingredients": 86400 # 24 hours
}
def cache_key(self, prefix: str, *args) -> str:
"""Generate cache key"""
key_data = f"{prefix}:{':'.join(str(arg) for arg in args)}"
return hashlib.md5(key_data.encode()).hexdigest()
def normalize_medication(self, medication_text: str) -> Optional[DrugConcept]:
"""Cached medication normalization"""
cache_key = self.cache_key("drug_norm", medication_text.lower())
# Check cache first
cached_result = self.redis_client.get(cache_key)
if cached_result:
data = json.loads(cached_result)
if data:
return DrugConcept(**data)
return None
# Call parent method
result = super().normalize_medication(medication_text)
# Cache result
cache_data = result.__dict__ if result else None
self.redis_client.setex(
cache_key,
self.cache_ttl["drug_normalization"],
json.dumps(cache_data, default=str)
)
return result
def check_pair_interaction(self, drug1: DrugConcept, drug2: DrugConcept) -> Optional[DrugInteraction]:
"""Cached pair interaction checking"""
# Sort IDs to ensure consistent cache keys regardless of order
id1, id2 = sorted([drug1.concept_id, drug2.concept_id])
cache_key = self.cache_key("interaction", id1, id2)
# Check cache first
cached_result = self.redis_client.get(cache_key)
if cached_result:
data = json.loads(cached_result)
if data:
return DrugInteraction(**data)
return None
# Call parent method
result = super().check_pair_interaction(drug1, drug2)
# Cache result
cache_data = result.__dict__ if result else None
self.redis_client.setex(
cache_key,
self.cache_ttl["interactions"],
json.dumps(cache_data, default=str)
)
return result
```
## Best Practices
### 1. Error Handling and Fallbacks
```python Python
def robust_interaction_check(self, medications: List[str]) -> Dict[str, Any]:
"""Robust interaction checking with multiple fallback strategies"""
results = {
"interactions": [],
"warnings": [],
"errors": []
}
try:
# Primary interaction checking
interactions = self.check_drug_interactions(medications)
results["interactions"] = interactions
except APITimeoutError:
# Fallback to simplified checking
self.logger.warning("API timeout, falling back to simplified interaction checking")
try:
simplified_interactions = self.simplified_interaction_check(medications)
results["interactions"] = simplified_interactions
results["warnings"].append("Simplified interaction checking used due to API timeout")
except Exception as e:
results["errors"].append(f"Both primary and fallback checking failed: {e}")
except APIRateLimitError:
# Handle rate limiting
self.logger.warning("Rate limit exceeded, using cached data only")
cached_interactions = self.get_cached_interactions(medications)
results["interactions"] = cached_interactions
results["warnings"].append("Using cached data only due to rate limiting")
except Exception as e:
# Log error but don't fail completely
results["errors"].append(f"Interaction checking failed: {e}")
results["warnings"].append("Manual review required - automated checking unavailable")
return results
def simplified_interaction_check(self, medications: List[str]) -> List[DrugInteraction]:
"""Simplified interaction checking using local rules"""
# Implement basic drug interaction rules
# This would typically use a local database of common interactions
interactions = []
# Example: Check for common dangerous combinations
dangerous_combinations = [
("warfarin", "aspirin"),
("warfarin", "ibuprofen"),
("metformin", "iodinated contrast")
]
med_lower = [med.lower() for med in medications]
for drug1, drug2 in dangerous_combinations:
if any(drug1 in med for med in med_lower) and any(drug2 in med for med in med_lower):
# Create basic interaction object
interactions.append(self.create_basic_interaction(drug1, drug2))
return interactions
```
### 2. Security and Compliance
```python Python
def secure_interaction_check(self, medications: List[str], user_id: str, audit_context: Dict[str, Any]) -> Dict[str, Any]:
"""HIPAA-compliant interaction checking with audit logging"""
# Audit log entry
audit_entry = {
"timestamp": datetime.now().isoformat(),
"user_id": user_id,
"action": "drug_interaction_check",
"medication_count": len(medications),
"patient_id": audit_context.get('patient_id'),
"session_id": audit_context.get('session_id')
}
try:
# Sanitize medication data for logging
med_hashes = [hashlib.sha256(med.encode()).hexdigest()[:8] for med in medications]
audit_entry["medication_hashes"] = med_hashes
# Perform interaction checking
report = self.generate_interaction_report(medications, audit_context.get('patient_context'))
# Log success
audit_entry["status"] = "success"
audit_entry["interactions_found"] = report['summary']['total_interactions']
audit_entry["risk_level"] = report['summary']['risk_level']
# Remove sensitive data from report if needed
sanitized_report = self.sanitize_report_for_logging(report)
return sanitized_report
except Exception as e:
audit_entry["status"] = "error"
audit_entry["error_message"] = str(e)
raise
finally:
# Always log audit entry
self.audit_logger.log_interaction_check(audit_entry)
def sanitize_report_for_logging(self, report: Dict[str, Any]) -> Dict[str, Any]:
"""Remove sensitive patient data from report for logging"""
sanitized = report.copy()
# Remove sensitive patient context
if 'patient_context' in sanitized:
sanitized['patient_context'] = {
'age_range': self.age_to_range(sanitized['patient_context'].get('age')),
'has_comorbidities': len(sanitized['patient_context'].get('comorbidities', [])) > 0
}
# Keep interaction data but hash medication names
sanitized['medications_analyzed'] = [
hashlib.sha256(med.encode()).hexdigest()[:8]
for med in sanitized['medications_analyzed']
]
return sanitized
```
## Next Steps
Map laboratory results using LOINC codes
Screen patients for clinical trial eligibility
Analyze population health using concept hierarchies
Master advanced search for drug concepts
# EHR Integration
Source: https://docs.omophub.com/guides/use-cases/ehr-integration
Integrate OMOPHub vocabulary services with Electronic Health Record systems for seamless clinical workflows
# EHR Integration with OMOPHub
## Overview
Electronic Health Record (EHR) systems are the backbone of modern healthcare, but they often use different coding standards and vocabularies, creating data silos and interoperability challenges. OMOPHub provides comprehensive vocabulary services that enable seamless integration with major EHR platforms, ensuring consistent medical coding and improved clinical workflows.
This guide demonstrates how to integrate OMOPHub with leading EHR systems including Epic®, Cerner, and Veradigm, supporting both real-time and batch processing scenarios.
Epic® is a registered trademark of Epic Systems Corporation. Veradigm is formerly known as Allscripts.
## Business Problem
Healthcare organizations face significant challenges when working with EHR data:
* **Data Fragmentation**: Different EHR systems use varying coding standards (ICD-9, ICD-10, SNOMED, proprietary codes)
* **Inconsistent Mapping**: Manual code mapping is error-prone and time-intensive
* **Clinical Workflow Disruption**: Terminology lookups interrupt clinical documentation
* **Interoperability Barriers**: Difficulty sharing data between systems and organizations
* **Quality Reporting**: Inconsistent codes affect quality metrics and population health analytics
* **Prior Authorization**: Complex code validation delays patient care
## Solution Architecture
```mermaid
graph TB
A[EHR System] --> B[OMOPHub Integration Layer]
B --> C[OMOPHub API]
C --> D[Vocabulary Services]
C --> E[Code Mapping]
C --> F[Hierarchy Navigation]
C --> G[Validation Services]
B --> H[Real-time Clinical Support]
B --> I[Batch Data Processing]
B --> J[Clinical Decision Support]
H --> K[Point-of-Care Alerts]
I --> L[Quality Reporting]
J --> M[Drug Interaction Checking]
D --> N[SNOMED CT]
D --> O[ICD-10]
D --> P[LOINC]
D --> Q[RxNorm]
style B fill:#333333,stroke:#fff,stroke-width:2px,color:#fff
style C fill:#333333,stroke:#fff,stroke-width:2px,color:#fff
```
## Core Implementation
### Universal EHR Integration Framework
```python
import httpx
from typing import Dict, List, Optional, Any, Union
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
import asyncio
from abc import ABC, abstractmethod
import json
@dataclass
class PatientData:
"""Standardized patient data structure across EHR systems."""
patient_id: str
mrn: str
demographics: Dict[str, Any]
diagnoses: List[Dict[str, str]]
procedures: List[Dict[str, str]]
medications: List[Dict[str, str]]
allergies: List[Dict[str, str]]
lab_results: List[Dict[str, Any]]
vital_signs: List[Dict[str, Any]]
clinical_notes: List[Dict[str, str]]
@dataclass
class CodeMapping:
"""Represents a code mapping result."""
source_code: str
source_system: str
target_code: str
target_system: str
confidence: float
mapped_name: str
class EHRConnector(ABC):
"""Abstract base class for EHR system connectors."""
@abstractmethod
async def authenticate(self) -> bool:
"""Authenticate with the EHR system."""
pass
@abstractmethod
async def get_patient_data(self, patient_id: str) -> PatientData:
"""Extract patient data from EHR."""
pass
@abstractmethod
async def update_patient_codes(self, patient_id: str, mappings: List[CodeMapping]) -> bool:
"""Update patient records with mapped codes."""
pass
class OMOPHubEHRIntegrator:
"""
Main integration service that works with any EHR system.
Provides vocabulary services and code standardization.
"""
def __init__(self, api_key: str, base_url: str = "https://api.omophub.com"):
self.api_key = api_key
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {api_key}"}
self.logger = logging.getLogger(__name__)
self.cache = {}
def standardize_diagnosis_codes(self, diagnoses: List[Dict[str, str]]) -> List[CodeMapping]:
"""
Standardize diagnosis codes to SNOMED CT or ICD-10.
"""
mappings = []
for diagnosis in diagnoses:
source_code = diagnosis.get('code')
source_system = diagnosis.get('system', 'Unknown')
if not source_code:
continue
# Determine target vocabulary based on source
target_vocab = self._determine_target_vocabulary(source_system, 'diagnosis')
try:
# Search for concept in OMOPHub
response = httpx.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': source_code,
'vocabulary_ids': self._map_ehr_to_omophub_vocab(source_system),
'standard_concept': 'S',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
concept = data['data'][0]
# Get mapping to target vocabulary
mapping_response = httpx.get(
f"{self.base_url}/v1/concepts/{concept['concept_id']}/mappings",
headers=self.headers,
params={
'target_vocabularies': target_vocab,
'relationship_types': 'Maps to'
}
)
mapping_response.raise_for_status()
mapping_data = mapping_response.json()
if mapping_data['data']:
best_mapping = mapping_data['data'][0]
mappings.append(CodeMapping(
source_code=source_code,
source_system=source_system,
target_code=best_mapping['target_concept_code'],
target_system=target_vocab,
confidence=best_mapping.get('confidence', 1.0),
mapped_name=best_mapping['target_concept_name']
))
else:
# Use the standard concept itself
mappings.append(CodeMapping(
source_code=source_code,
source_system=source_system,
target_code=concept['concept_code'],
target_system=concept['vocabulary_id'],
confidence=0.9,
mapped_name=concept['concept_name']
))
except httpx.RequestError as e:
self.logger.error(f"Error mapping diagnosis {source_code}: {e}")
continue
return mappings
def standardize_procedure_codes(self, procedures: List[Dict[str, str]]) -> List[CodeMapping]:
"""
Standardize procedure codes to HCPCS or SNOMED.
"""
mappings = []
for procedure in procedures:
source_code = procedure.get('code')
source_system = procedure.get('system', 'Unknown')
if not source_code:
continue
target_vocab = self._determine_target_vocabulary(source_system, 'procedure')
try:
response = httpx.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': source_code,
'vocabulary_ids': self._map_ehr_to_omophub_vocab(source_system),
'domain_id': 'Procedure',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
concept = data['data'][0]
mappings.append(CodeMapping(
source_code=source_code,
source_system=source_system,
target_code=concept['concept_code'],
target_system=concept['vocabulary_id'],
confidence=0.95,
mapped_name=concept['concept_name']
))
except httpx.RequestError as e:
self.logger.error(f"Error mapping procedure {source_code}: {e}")
continue
return mappings
def standardize_medication_codes(self, medications: List[Dict[str, str]]) -> List[CodeMapping]:
"""
Standardize medication codes to RxNorm.
"""
mappings = []
for medication in medications:
source_code = medication.get('code')
medication_name = medication.get('name', '')
source_system = medication.get('system', 'Unknown')
# Try code first, then name if no code
search_term = source_code if source_code else medication_name
if not search_term:
continue
try:
response = httpx.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': search_term,
'vocabulary_ids': 'RxNorm',
'domain_id': 'Drug',
'standard_concept': 'S',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
concept = data['data'][0]
mappings.append(CodeMapping(
source_code=source_code or medication_name,
source_system=source_system,
target_code=concept['concept_code'],
target_system='RxNorm',
confidence=0.9,
mapped_name=concept['concept_name']
))
except httpx.RequestError as e:
self.logger.error(f"Error mapping medication {search_term}: {e}")
continue
return mappings
def normalize_lab_results(self, lab_results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""
Normalize lab results to LOINC codes with unit standardization.
"""
normalized_results = []
for lab_result in lab_results:
test_name = lab_result.get('test_name', '')
test_code = lab_result.get('test_code', '')
value = lab_result.get('value')
unit = lab_result.get('unit', '')
search_term = test_code if test_code else test_name
if not search_term:
continue
try:
# Find LOINC code
response = httpx.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': search_term,
'vocabulary_ids': 'LOINC',
'domain_id': 'Measurement',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
loinc_concept = data['data'][0]
# Normalize units if needed
normalized_unit = self._normalize_units(unit, loinc_concept['concept_code'])
normalized_results.append({
'original_test_name': test_name,
'original_test_code': test_code,
'loinc_code': loinc_concept['concept_code'],
'loinc_name': loinc_concept['concept_name'],
'value': value,
'original_unit': unit,
'normalized_unit': normalized_unit,
'confidence': 0.95
})
except httpx.RequestError as e:
self.logger.error(f"Error normalizing lab result {search_term}: {e}")
continue
return normalized_results
def process_patient_data(self, patient_data: PatientData) -> Dict[str, Any]:
"""
Comprehensive patient data processing with all vocabulary standardization.
"""
results = {
'patient_id': patient_data.patient_id,
'mrn': patient_data.mrn,
'processed_timestamp': datetime.utcnow().isoformat(),
'diagnosis_mappings': [],
'procedure_mappings': [],
'medication_mappings': [],
'normalized_lab_results': [],
'clinical_insights': {},
'quality_indicators': {}
}
# Process diagnoses
results['diagnosis_mappings'] = self.standardize_diagnosis_codes(patient_data.diagnoses)
# Process procedures
results['procedure_mappings'] = self.standardize_procedure_codes(patient_data.procedures)
# Process medications
results['medication_mappings'] = self.standardize_medication_codes(patient_data.medications)
# Process lab results
results['normalized_lab_results'] = self.normalize_lab_results(patient_data.lab_results)
# Generate clinical insights
results['clinical_insights'] = self._generate_clinical_insights(results)
# Calculate quality indicators
results['quality_indicators'] = self._calculate_quality_indicators(results)
return results
def _determine_target_vocabulary(self, source_system: str, data_type: str) -> str:
"""Determine optimal target vocabulary based on source system and data type."""
mapping = {
'diagnosis': {
'ICD9CM': 'ICD10CM',
'ICD10CM': 'SNOMED',
'SNOMED': 'SNOMED',
'default': 'SNOMED'
},
'procedure': {
'ICD9Proc': 'HCPCS',
'ICD10PCS': 'HCPCS',
'HCPCS': 'HCPCS',
'SNOMED': 'SNOMED',
'default': 'HCPCS'
}
}
data_mapping = mapping.get(data_type, {})
return data_mapping.get(source_system, data_mapping.get('default', 'SNOMED'))
def _map_ehr_to_omophub_vocab(self, ehr_system: str) -> str:
"""Map EHR system vocabulary names to OMOPHub vocabulary IDs."""
mapping = {
'ICD9CM': 'ICD9CM',
'ICD10CM': 'ICD10CM',
'ICD10PCS': 'ICD10PCS',
'SNOMED': 'SNOMED',
'HCPCS': 'HCPCS',
'HCPCS': 'HCPCS',
'LOINC': 'LOINC',
'RxNorm': 'RxNorm',
'NDC': 'NDC'
}
return mapping.get(ehr_system, 'SNOMED') # Default to SNOMED
def _normalize_units(self, unit: str, loinc_code: str) -> str:
"""Normalize lab units to UCUM standard."""
# This would typically involve a lookup table or API call
# For demo purposes, returning the original unit
return unit
def _generate_clinical_insights(self, processed_data: Dict) -> Dict[str, Any]:
"""Generate clinical insights from processed vocabulary data."""
insights = {
'chronic_conditions': [],
'drug_interactions': [],
'preventive_care_gaps': [],
'clinical_decision_support': []
}
# Identify chronic conditions from diagnoses
chronic_condition_codes = ['E11', 'I10', 'J44'] # Diabetes, Hypertension, COPD
for mapping in processed_data.get('diagnosis_mappings', []):
if any(mapping.target_code.startswith(code) for code in chronic_condition_codes):
insights['chronic_conditions'].append({
'condition': mapping.mapped_name,
'code': mapping.target_code,
'system': mapping.target_system
})
return insights
def _calculate_quality_indicators(self, processed_data: Dict) -> Dict[str, Any]:
"""Calculate quality indicators based on standardized codes."""
indicators = {
'diabetes_control_eligible': False,
'hypertension_control_eligible': False,
'preventive_screening_due': [],
'medication_adherence_concerns': []
}
# Check for diabetes
for mapping in processed_data.get('diagnosis_mappings', []):
if mapping.target_code.startswith('E11'): # Type 2 diabetes
indicators['diabetes_control_eligible'] = True
break
# Check for hypertension
for mapping in processed_data.get('diagnosis_mappings', []):
if mapping.target_code == 'I10': # Essential hypertension
indicators['hypertension_control_eligible'] = True
break
return indicators
# Epic® Integration (FHIR-based)
class EpicFHIRConnector(EHRConnector):
"""
Epic® FHIR R4 integration connector.
Epic® is a registered trademark of Epic Systems Corporation.
"""
def __init__(self, fhir_base_url: str, client_id: str, client_secret: str):
self.fhir_base_url = fhir_base_url
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.token_expires = None
async def authenticate(self) -> bool:
"""Authenticate using Epic® OAuth 2.0."""
try:
auth_url = f"{self.fhir_base_url}/oauth2/token"
auth_data = {
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret,
'scope': 'system/*.read'
}
async with httpx.AsyncClient() as client:
response = await client.post(auth_url, data=auth_data)
response.raise_for_status()
token_data = response.json()
self.access_token = token_data['access_token']
self.token_expires = datetime.utcnow() + timedelta(seconds=token_data['expires_in'])
return True
except Exception as e:
logging.error(f"Epic® authentication failed: {e}")
return False
async def get_patient_data(self, patient_id: str) -> PatientData:
"""Extract patient data from Epic® FHIR APIs."""
if not self.access_token or datetime.utcnow() >= self.token_expires:
await self.authenticate()
headers = {
'Authorization': f'Bearer {self.access_token}',
'Accept': 'application/fhir+json'
}
async with httpx.AsyncClient() as client:
# Get patient demographics
patient_response = await client.get(
f"{self.fhir_base_url}/Patient/{patient_id}",
headers=headers
)
patient_response.raise_for_status()
patient = patient_response.json()
# Get conditions (diagnoses)
conditions_response = await client.get(
f"{self.fhir_base_url}/Condition",
headers=headers,
params={'patient': patient_id}
)
conditions_response.raise_for_status()
conditions = conditions_response.json()
# Get procedures
procedures_response = await client.get(
f"{self.fhir_base_url}/Procedure",
headers=headers,
params={'patient': patient_id}
)
procedures_response.raise_for_status()
procedures = procedures_response.json()
# Get medications
medications_response = await client.get(
f"{self.fhir_base_url}/MedicationRequest",
headers=headers,
params={'patient': patient_id}
)
medications_response.raise_for_status()
medications = medications_response.json()
# Get lab results
observations_response = await client.get(
f"{self.fhir_base_url}/Observation",
headers=headers,
params={
'patient': patient_id,
'category': 'laboratory'
}
)
observations_response.raise_for_status()
observations = observations_response.json()
# Convert FHIR resources to standardized format
return PatientData(
patient_id=patient_id,
mrn=self._extract_mrn(patient),
demographics=self._extract_demographics(patient),
diagnoses=self._extract_diagnoses(conditions),
procedures=self._extract_procedures(procedures),
medications=self._extract_medications(medications),
allergies=[], # Would extract from AllergyIntolerance resources
lab_results=self._extract_lab_results(observations),
vital_signs=[], # Would extract from vital signs observations
clinical_notes=[] # Would extract from DocumentReference resources
)
async def update_patient_codes(self, patient_id: str, mappings: List[CodeMapping]) -> bool:
"""Update patient records with standardized codes (if supported by Epic®)."""
# This would typically involve creating or updating FHIR resources
# Implementation depends on Epic® configuration and permissions
logging.info(f"Would update patient {patient_id} with {len(mappings)} code mappings")
return True
def _extract_mrn(self, patient_fhir: Dict) -> str:
"""Extract MRN from Epic® patient resource."""
for identifier in patient_fhir.get('identifier', []):
if identifier.get('type', {}).get('coding', [{}])[0].get('code') == 'MR':
return identifier.get('value', '')
return patient_fhir.get('id', '')
def _extract_demographics(self, patient_fhir: Dict) -> Dict[str, Any]:
"""Extract demographics from Epic® patient resource."""
return {
'name': patient_fhir.get('name', [{}])[0].get('text', ''),
'gender': patient_fhir.get('gender', ''),
'birth_date': patient_fhir.get('birthDate', ''),
'address': patient_fhir.get('address', [{}])[0] if patient_fhir.get('address') else {}
}
def _extract_diagnoses(self, conditions_bundle: Dict) -> List[Dict[str, str]]:
"""Extract diagnoses from Epic® condition resources."""
diagnoses = []
for entry in conditions_bundle.get('entry', []):
condition = entry.get('resource', {})
code_info = condition.get('code', {})
for coding in code_info.get('coding', []):
diagnoses.append({
'code': coding.get('code', ''),
'system': self._map_fhir_system_to_vocab(coding.get('system', '')),
'display': coding.get('display', ''),
'clinical_status': condition.get('clinicalStatus', {}).get('coding', [{}])[0].get('code', '')
})
return diagnoses
def _extract_procedures(self, procedures_bundle: Dict) -> List[Dict[str, str]]:
"""Extract procedures from Epic® procedure resources."""
procedures = []
for entry in procedures_bundle.get('entry', []):
procedure = entry.get('resource', {})
code_info = procedure.get('code', {})
for coding in code_info.get('coding', []):
procedures.append({
'code': coding.get('code', ''),
'system': self._map_fhir_system_to_vocab(coding.get('system', '')),
'display': coding.get('display', ''),
'performed_date': procedure.get('performedDateTime', '')
})
return procedures
def _extract_medications(self, medications_bundle: Dict) -> List[Dict[str, str]]:
"""Extract medications from Epic® medication request resources."""
medications = []
for entry in medications_bundle.get('entry', []):
med_request = entry.get('resource', {})
medication = med_request.get('medicationCodeableConcept', {})
for coding in medication.get('coding', []):
medications.append({
'code': coding.get('code', ''),
'system': self._map_fhir_system_to_vocab(coding.get('system', '')),
'display': coding.get('display', ''),
'status': med_request.get('status', ''),
'intent': med_request.get('intent', '')
})
return medications
def _extract_lab_results(self, observations_bundle: Dict) -> List[Dict[str, Any]]:
"""Extract lab results from Epic® observation resources."""
lab_results = []
for entry in observations_bundle.get('entry', []):
observation = entry.get('resource', {})
code_info = observation.get('code', {})
# Find LOINC coding
loinc_coding = None
for coding in code_info.get('coding', []):
if 'loinc.org' in coding.get('system', ''):
loinc_coding = coding
break
if loinc_coding:
value_info = observation.get('valueQuantity', {})
lab_results.append({
'test_code': loinc_coding.get('code', ''),
'test_name': loinc_coding.get('display', ''),
'value': value_info.get('value'),
'unit': value_info.get('unit', ''),
'reference_range': observation.get('referenceRange', []),
'status': observation.get('status', ''),
'effective_date': observation.get('effectiveDateTime', '')
})
return lab_results
def _map_fhir_system_to_vocab(self, fhir_system: str) -> str:
"""Map FHIR system URLs to vocabulary names."""
mapping = {
'http://snomed.info/sct': 'SNOMED',
'http://hl7.org/fhir/sid/icd-10-cm': 'ICD10CM',
'http://www.ama-assn.org/go/cpt': 'HCPCS',
'http://www.nlm.nih.gov/research/umls/rxnorm': 'RxNorm',
'http://loinc.org': 'LOINC'
}
return mapping.get(fhir_system, 'Unknown')
# Cerner Integration
class CernerFHIRConnector(EHRConnector):
"""Cerner PowerChart FHIR R4 integration connector."""
def __init__(self, fhir_base_url: str, client_id: str, client_secret: str):
self.fhir_base_url = fhir_base_url
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.token_expires = None
async def authenticate(self) -> bool:
"""Authenticate using Cerner OAuth 2.0."""
try:
auth_url = f"{self.fhir_base_url}/authorization/oauth/token"
auth_data = {
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret,
'scope': 'system/Patient.read system/Observation.read system/Condition.read'
}
timeout = httpx.Timeout(15.0)
async with httpx.AsyncClient(timeout=timeout) as client:
response = await client.post(auth_url, data=auth_data)
response.raise_for_status()
token_data = response.json()
self.access_token = token_data['access_token']
self.token_expires = datetime.utcnow() + timedelta(seconds=token_data.get('expires_in', 3600))
return True
except Exception as e:
logging.error(f"Cerner authentication failed: {e}")
return False
async def get_patient_data(self, patient_id: str) -> PatientData:
"""Extract patient data from Cerner FHIR APIs."""
# Similar implementation to Epic® but with Cerner-specific API patterns
if not self.access_token or (self.token_expires and datetime.utcnow() >= self.token_expires):
await self.authenticate()
headers = {
'Authorization': f'Bearer {self.access_token}',
'Accept': 'application/json+fhir'
}
# Implementation would follow Cerner FHIR API documentation
# For brevity, returning a placeholder
return PatientData(
patient_id=patient_id,
mrn=f"CERNER_{patient_id}",
demographics={},
diagnoses=[],
procedures=[],
medications=[],
allergies=[],
lab_results=[],
vital_signs=[],
clinical_notes=[]
)
async def update_patient_codes(self, patient_id: str, mappings: List[CodeMapping]) -> bool:
"""Update patient records in Cerner system."""
logging.info(f"Would update Cerner patient {patient_id} with {len(mappings)} mappings")
return True
# Veradigm (formerly Allscripts) Integration
class VeradigmConnector(EHRConnector):
"""
Veradigm (formerly Allscripts) integration connector.
Supports both Unity API and Developer API.
"""
def __init__(self, api_base_url: str, username: str, password: str, app_name: str):
self.api_base_url = api_base_url
self.username = username
self.password = password
self.app_name = app_name
self.security_token = None
self.app_user_id = None
async def authenticate(self) -> bool:
"""Authenticate using Veradigm Unity API."""
try:
auth_url = f"{self.api_base_url}/Unity/UnityService.svc/json/GetSecurityToken"
auth_data = {
'username': self.username,
'password': self.password,
'appname': self.app_name
}
async with httpx.AsyncClient() as client:
response = await client.post(auth_url, json=auth_data)
response.raise_for_status()
auth_result = response.json()
self.security_token = auth_result.get('Token')
self.app_user_id = auth_result.get('AppUserID')
return self.security_token is not None
except Exception as e:
logging.error(f"Veradigm authentication failed: {e}")
return False
async def get_patient_data(self, patient_id: str) -> PatientData:
"""Extract patient data from Veradigm Unity API."""
if not self.security_token:
await self.authenticate()
headers = {
'Content-Type': 'application/json'
}
# Get patient demographics
patient_url = f"{self.api_base_url}/Unity/UnityService.svc/json/GetPatient"
patient_params = {
'token': self.security_token,
'appUserID': self.app_user_id,
'patientID': patient_id
}
async with httpx.AsyncClient() as client:
patient_response = await client.get(patient_url, headers=headers, params=patient_params)
patient_response.raise_for_status()
patient_data = patient_response.json()
# Get patient problems (diagnoses)
problems_url = f"{self.api_base_url}/Unity/UnityService.svc/json/GetPatientProblems"
problems_response = await client.get(problems_url, headers=headers, params=patient_params)
problems_response.raise_for_status()
problems_data = problems_response.json()
# Get medications
meds_url = f"{self.api_base_url}/Unity/UnityService.svc/json/GetPatientMedications"
meds_response = await client.get(meds_url, headers=headers, params=patient_params)
meds_response.raise_for_status()
medications_data = meds_response.json()
return PatientData(
patient_id=patient_id,
mrn=patient_data.get('MRN', ''),
demographics=self._extract_veradigm_demographics(patient_data),
diagnoses=self._extract_veradigm_problems(problems_data),
procedures=[], # Would need separate API call
medications=self._extract_veradigm_medications(medications_data),
allergies=[], # Would need separate API call
lab_results=[], # Would need separate API call
vital_signs=[], # Would need separate API call
clinical_notes=[]
)
async def update_patient_codes(self, patient_id: str, mappings: List[CodeMapping]) -> bool:
"""Update patient records in Veradigm system."""
logging.info(f"Would update Veradigm patient {patient_id} with {len(mappings)} mappings")
return True
def _extract_veradigm_demographics(self, patient_data: Dict) -> Dict[str, Any]:
"""Extract demographics from Veradigm patient data."""
return {
'name': f"{patient_data.get('FirstName', '')} {patient_data.get('LastName', '')}",
'gender': patient_data.get('Sex', ''),
'birth_date': patient_data.get('DOB', ''),
'address': {
'street': patient_data.get('Address1', ''),
'city': patient_data.get('City', ''),
'state': patient_data.get('State', ''),
'zip': patient_data.get('Zip', '')
}
}
def _extract_veradigm_problems(self, problems_data: Dict) -> List[Dict[str, str]]:
"""Extract diagnoses from Veradigm patient problems."""
diagnoses = []
for problem in problems_data.get('Problems', []):
diagnoses.append({
'code': problem.get('DiagnosisCode', ''),
'system': 'ICD10CM', # Assuming ICD-10
'display': problem.get('ProblemDescription', ''),
'status': problem.get('ProblemStatus', '')
})
return diagnoses
def _extract_veradigm_medications(self, medications_data: Dict) -> List[Dict[str, str]]:
"""Extract medications from Veradigm medications data."""
medications = []
for med in medications_data.get('Medications', []):
medications.append({
'name': med.get('MedicationName', ''),
'code': med.get('NDC', ''),
'system': 'NDC',
'display': med.get('MedicationName', ''),
'status': med.get('MedicationStatus', '')
})
return medications
# Main Integration Orchestrator
class EHRIntegrationOrchestrator:
"""
Orchestrates integration across multiple EHR systems.
Provides unified interface for vocabulary standardization.
"""
def __init__(self, omophub_api_key: str):
self.omophub_integrator = OMOPHubEHRIntegrator(omophub_api_key)
self.ehr_connectors = {}
self.logger = logging.getLogger(__name__)
def register_ehr_connector(self, ehr_name: str, connector: EHRConnector):
"""Register an EHR system connector."""
self.ehr_connectors[ehr_name] = connector
async def process_patient(self, ehr_name: str, patient_id: str) -> Dict[str, Any]:
"""
Process a patient's data from specified EHR system.
Returns standardized vocabulary mappings and clinical insights.
"""
if ehr_name not in self.ehr_connectors:
raise ValueError(f"EHR system '{ehr_name}' not registered")
connector = self.ehr_connectors[ehr_name]
try:
# Extract patient data from EHR
self.logger.info(f"Extracting patient {patient_id} from {ehr_name}")
patient_data = await connector.get_patient_data(patient_id)
# Process with OMOPHub vocabulary services
self.logger.info(f"Processing vocabulary standardization for patient {patient_id}")
processed_data = self.omophub_integrator.process_patient_data(patient_data)
# Optionally update EHR with standardized codes
if hasattr(connector, 'update_patient_codes'):
all_mappings = (
processed_data['diagnosis_mappings'] +
processed_data['procedure_mappings'] +
processed_data['medication_mappings']
)
await connector.update_patient_codes(patient_id, all_mappings)
return {
'ehr_system': ehr_name,
'processing_timestamp': datetime.utcnow().isoformat(),
'patient_data': processed_data,
'status': 'success'
}
except Exception as e:
self.logger.error(f"Error processing patient {patient_id} from {ehr_name}: {e}")
return {
'ehr_system': ehr_name,
'patient_id': patient_id,
'error': str(e),
'status': 'failed'
}
async def batch_process_patients(self, ehr_name: str, patient_ids: List[str]) -> List[Dict[str, Any]]:
"""Process multiple patients efficiently."""
results = []
# Process in batches of 10 to avoid overwhelming APIs
batch_size = 10
for i in range(0, len(patient_ids), batch_size):
batch = patient_ids[i:i + batch_size]
batch_tasks = [
self.process_patient(ehr_name, patient_id)
for patient_id in batch
]
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
results.extend(batch_results)
# Add delay between batches to respect API rate limits
await asyncio.sleep(1)
return results
# Example usage
async def main():
# Initialize the orchestrator
orchestrator = EHRIntegrationOrchestrator("your-omophub-api-key")
# Register EHR connectors
epic_connector = EpicFHIRConnector(
fhir_base_url="https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4",
client_id="your-epic-client-id",
client_secret="your-epic-client-secret"
)
orchestrator.register_ehr_connector("epic", epic_connector)
cerner_connector = CernerFHIRConnector(
fhir_base_url="https://fhir-open.cerner.com/r4",
client_id="your-cerner-client-id",
client_secret="your-cerner-client-secret"
)
orchestrator.register_ehr_connector("cerner", cerner_connector)
veradigm_connector = VeradigmConnector(
api_base_url="https://unity.developer.allscripts.com",
username="your-username",
password="your-password",
app_name="your-app-name"
)
orchestrator.register_ehr_connector("veradigm", veradigm_connector)
# Process a patient from Epic®
result = await orchestrator.process_patient("epic", "patient-123")
print(json.dumps(result, indent=2))
# Batch process multiple patients
patient_ids = ["patient-123", "patient-456", "patient-789"]
batch_results = await orchestrator.batch_process_patients("epic", patient_ids)
print(f"Processed {len(batch_results)} patients")
for result in batch_results:
if result['status'] == 'success':
patient_data = result['patient_data']
print(f"Patient {patient_data['patient_id']}: "
f"{len(patient_data['diagnosis_mappings'])} diagnoses mapped, "
f"{len(patient_data['procedure_mappings'])} procedures mapped")
if __name__ == "__main__":
asyncio.run(main())
```
### JavaScript/Node.js Implementation
```javascript
const axios = require('axios');
const { promisify } = require('util');
class OMOPHubEHRIntegrator {
constructor(apiKey, baseUrl = 'https://api.omophub.com') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.headers = { 'Authorization': `Bearer ${apiKey}` };
}
async standardizeDiagnosisCodes(diagnoses) {
const mappings = [];
for (const diagnosis of diagnoses) {
const sourceCode = diagnosis.code;
const sourceSystem = diagnosis.system || 'Unknown';
if (!sourceCode) continue;
try {
const response = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: sourceCode,
vocabulary_ids: this.mapEHRToOMOPHubVocab(sourceSystem),
standard_concept: 'S',
page_size: 1
}
});
if (response.data.data && response.data.data.length > 0) {
const concept = response.data.data[0];
// Get mapping to target vocabulary
const mappingResponse = await axios.get(
`${this.baseUrl}/v1/concepts/${concept.concept_id}/mappings`,
{
headers: this.headers,
params: {
target_vocabularies: 'SNOMED',
relationship_types: 'Maps to'
}
}
);
if (mappingResponse.data.data && mappingResponse.data.data.length > 0) {
const bestMapping = mappingResponse.data.data[0];
mappings.push({
sourceCode,
sourceSystem,
targetCode: bestMapping.target_concept_code,
targetSystem: 'SNOMED',
confidence: bestMapping.confidence || 1.0,
mappedName: bestMapping.target_concept_name
});
}
}
} catch (error) {
console.error(`Error mapping diagnosis ${sourceCode}:`, error.message);
}
}
return mappings;
}
async processPatientData(patientData) {
const results = {
patientId: patientData.patient_id,
mrn: patientData.mrn,
processedTimestamp: new Date().toISOString(),
diagnosisMappings: [],
procedureMappings: [],
medicationMappings: [],
normalizedLabResults: [],
clinicalInsights: {},
qualityIndicators: {}
};
// Process diagnoses
results.diagnosisMappings = await this.standardizeDiagnosisCodes(patientData.diagnoses || []);
// Process procedures
results.procedureMappings = await this.standardizeProcedureCodes(patientData.procedures || []);
// Process medications
results.medicationMappings = await this.standardizeMedicationCodes(patientData.medications || []);
// Generate insights
results.clinicalInsights = this.generateClinicalInsights(results);
results.qualityIndicators = this.calculateQualityIndicators(results);
return results;
}
mapEHRToOMOPHubVocab(ehrSystem) {
const mapping = {
'ICD9CM': 'ICD9CM',
'ICD10CM': 'ICD10CM',
'SNOMED': 'SNOMED',
'HCPCS': 'HCPCS',
'LOINC': 'LOINC',
'RxNorm': 'RxNorm'
};
return mapping[ehrSystem] || 'SNOMED';
}
generateClinicalInsights(processedData) {
const insights = {
chronicConditions: [],
drugInteractions: [],
preventiveCareGaps: []
};
// Identify chronic conditions
const chronicCodes = ['E11', 'I10', 'J44']; // Diabetes, Hypertension, COPD
for (const mapping of processedData.diagnosisMappings) {
if (chronicCodes.some(code => mapping.targetCode.startsWith(code))) {
insights.chronicConditions.push({
condition: mapping.mappedName,
code: mapping.targetCode,
system: mapping.targetSystem
});
}
}
return insights;
}
calculateQualityIndicators(processedData) {
const indicators = {
diabetesControlEligible: false,
hypertensionControlEligible: false,
preventiveScreeningDue: []
};
// Check for diabetes
for (const mapping of processedData.diagnosisMappings) {
if (mapping.targetCode.startsWith('E11')) {
indicators.diabetesControlEligible = true;
break;
}
}
return indicators;
}
}
// Epic® FHIR Connector
class EpicFHIRConnector {
constructor(fhirBaseUrl, clientId, clientSecret) {
this.fhirBaseUrl = fhirBaseUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = null;
this.tokenExpires = null;
}
async authenticate() {
try {
const authUrl = `${this.fhirBaseUrl}/oauth2/token`;
const authData = {
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret,
scope: 'system/*.read'
};
// URL-encode the auth payload for OAuth2 token endpoint
const formData = new URLSearchParams(authData).toString();
const response = await axios.post(authUrl, formData, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
const tokenData = response.data;
this.accessToken = tokenData.access_token;
this.tokenExpires = new Date(Date.now() + (tokenData.expires_in * 1000));
return true;
} catch (error) {
console.error('Epic® authentication failed:', error.message);
return false;
}
}
async getPatientData(patientId) {
if (!this.accessToken || new Date() >= this.tokenExpires) {
await this.authenticate();
}
const headers = {
'Authorization': `Bearer ${this.accessToken}`,
'Accept': 'application/fhir+json'
};
try {
// Get patient demographics
const patientResponse = await axios.get(
`${this.fhirBaseUrl}/Patient/${patientId}`,
{ headers }
);
const patient = patientResponse.data;
// Get conditions
const conditionsResponse = await axios.get(
`${this.fhirBaseUrl}/Condition`,
{
headers,
params: { patient: patientId }
}
);
const conditions = conditionsResponse.data;
// Get medications
const medicationsResponse = await axios.get(
`${this.fhirBaseUrl}/MedicationRequest`,
{
headers,
params: { patient: patientId }
}
);
const medications = medicationsResponse.data;
return {
patient_id: patientId,
mrn: this.extractMRN(patient),
demographics: this.extractDemographics(patient),
diagnoses: this.extractDiagnoses(conditions),
procedures: [], // Would extract from Procedure resources
medications: this.extractMedications(medications),
allergies: [],
lab_results: [],
vital_signs: [],
clinical_notes: []
};
} catch (error) {
console.error(`Error extracting patient data from Epic®:`, error.message);
throw error;
}
}
extractMRN(patient) {
const identifiers = patient.identifier || [];
for (const identifier of identifiers) {
const coding = identifier.type?.coding?.[0];
if (coding?.code === 'MR') {
return identifier.value || '';
}
}
return patient.id || '';
}
extractDemographics(patient) {
return {
name: patient.name?.[0]?.text || '',
gender: patient.gender || '',
birth_date: patient.birthDate || '',
address: patient.address?.[0] || {}
};
}
extractDiagnoses(conditionsBundle) {
const diagnoses = [];
for (const entry of conditionsBundle.entry || []) {
const condition = entry.resource || {};
const codeInfo = condition.code || {};
for (const coding of codeInfo.coding || []) {
diagnoses.push({
code: coding.code || '',
system: this.mapFHIRSystemToVocab(coding.system || ''),
display: coding.display || '',
clinical_status: condition.clinicalStatus?.coding?.[0]?.code || ''
});
}
}
return diagnoses;
}
extractMedications(medicationsBundle) {
const medications = [];
for (const entry of medicationsBundle.entry || []) {
const medRequest = entry.resource || {};
const medication = medRequest.medicationCodeableConcept || {};
for (const coding of medication.coding || []) {
medications.push({
code: coding.code || '',
system: this.mapFHIRSystemToVocab(coding.system || ''),
display: coding.display || '',
status: medRequest.status || '',
intent: medRequest.intent || ''
});
}
}
return medications;
}
mapFHIRSystemToVocab(fhirSystem) {
const mapping = {
'http://snomed.info/sct': 'SNOMED',
'http://hl7.org/fhir/sid/icd-10-cm': 'ICD10CM',
'http://www.ama-assn.org/go/cpt': 'HCPCS',
'http://www.nlm.nih.gov/research/umls/rxnorm': 'RxNorm',
'http://loinc.org': 'LOINC'
};
return mapping[fhirSystem] || 'Unknown';
}
}
// Integration Orchestrator
class EHRIntegrationOrchestrator {
constructor(omophubApiKey) {
this.OMOPHUBIntegrator = new OMOPHubEHRIntegrator(omophubApiKey);
this.ehrConnectors = new Map();
}
registerEHRConnector(ehrName, connector) {
this.ehrConnectors.set(ehrName, connector);
}
async processPatient(ehrName, patientId) {
if (!this.ehrConnectors.has(ehrName)) {
throw new Error(`EHR system '${ehrName}' not registered`);
}
const connector = this.ehrConnectors.get(ehrName);
try {
console.log(`Extracting patient ${patientId} from ${ehrName}`);
const patientData = await connector.getPatientData(patientId);
console.log(`Processing vocabulary standardization for patient ${patientId}`);
const processedData = await this.OMOPHUBIntegrator.processPatientData(patientData);
return {
ehrSystem: ehrName,
processingTimestamp: new Date().toISOString(),
patientData: processedData,
status: 'success'
};
} catch (error) {
console.error(`Error processing patient ${patientId} from ${ehrName}:`, error.message);
return {
ehrSystem: ehrName,
patientId,
error: error.message,
status: 'failed'
};
}
}
async batchProcessPatients(ehrName, patientIds, batchSize = 10) {
const results = [];
for (let i = 0; i < patientIds.length; i += batchSize) {
const batch = patientIds.slice(i, i + batchSize);
const batchPromises = batch.map(patientId =>
this.processPatient(ehrName, patientId)
);
const batchResults = await Promise.allSettled(batchPromises);
results.push(...batchResults.map(result => result.value || result.reason));
// Add delay between batches
if (i + batchSize < patientIds.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
}
// Example usage
async function main() {
const orchestrator = new EHRIntegrationOrchestrator('your-omophub-api-key');
// Register Epic® connector
const epicConnector = new EpicFHIRConnector(
'https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4',
'your-epic-client-id',
'your-epic-client-secret'
);
orchestrator.registerEHRConnector('epic', epicConnector);
try {
// Process a single patient
const result = await orchestrator.processPatient('epic', 'patient-123');
console.log(JSON.stringify(result, null, 2));
// Batch process multiple patients
const patientIds = ['patient-123', 'patient-456', 'patient-789'];
const batchResults = await orchestrator.batchProcessPatients('epic', patientIds);
console.log(`Processed ${batchResults.length} patients`);
batchResults.forEach(result => {
if (result.status === 'success') {
const patientData = result.patientData;
console.log(`Patient ${patientData.patientId}: ${patientData.diagnosisMappings.length} diagnoses mapped`);
}
});
} catch (error) {
console.error('Integration error:', error.message);
}
}
if (require.main === module) {
main();
}
module.exports = {
OMOPHubEHRIntegrator,
EpicFHIRConnector,
EHRIntegrationOrchestrator
};
```
## Clinical Workflow Integration Patterns
### Real-time Decision Support
```python
class ClinicalDecisionSupport:
"""
Real-time clinical decision support using OMOPHub vocabulary services.
Integrates with EHR workflow to provide point-of-care alerts and suggestions.
"""
def __init__(self, omophub_integrator: OMOPHubEHRIntegrator):
self.omophub = omophub_integrator
self.clinical_rules = self._load_clinical_rules()
async def evaluate_patient_context(self, patient_data: PatientData) -> Dict[str, Any]:
"""
Evaluate patient context for clinical decision support.
Called during clinical documentation or order entry.
"""
alerts = []
suggestions = []
# Process current patient data
processed_data = self.omophub.process_patient_data(patient_data)
# Check for drug interactions
drug_alerts = await self._check_drug_interactions(processed_data['medication_mappings'])
alerts.extend(drug_alerts)
# Check for allergy conflicts
allergy_alerts = await self._check_allergy_conflicts(
processed_data['medication_mappings'],
patient_data.allergies
)
alerts.extend(allergy_alerts)
# Generate preventive care suggestions
preventive_suggestions = await self._generate_preventive_care_suggestions(processed_data)
suggestions.extend(preventive_suggestions)
# Check for quality measure opportunities
quality_suggestions = await self._check_quality_measures(processed_data)
suggestions.extend(quality_suggestions)
return {
'alerts': alerts,
'suggestions': suggestions,
'patient_summary': self._generate_patient_summary(processed_data),
'risk_scores': self._calculate_risk_scores(processed_data)
}
async def _check_drug_interactions(self, medications: List[CodeMapping]) -> List[Dict]:
"""Check for potential drug-drug interactions using RxNorm codes."""
interactions = []
# Get RxNorm codes
rxnorm_codes = [med.target_code for med in medications if med.target_system == 'RxNorm']
# Check pairwise interactions
for i in range(len(rxnorm_codes)):
for j in range(i + 1, len(rxnorm_codes)):
# This would typically call a drug interaction API
# For demo purposes, flagging based on code patterns
if self._has_potential_interaction(rxnorm_codes[i], rxnorm_codes[j]):
interactions.append({
'type': 'drug_interaction',
'severity': 'moderate',
'message': f'Potential interaction between {rxnorm_codes[i]} and {rxnorm_codes[j]}',
'drug1': rxnorm_codes[i],
'drug2': rxnorm_codes[j],
'recommendation': 'Monitor patient closely'
})
return interactions
async def _check_allergy_conflicts(self, medications: List[CodeMapping], allergies: List[Dict]) -> List[Dict]:
"""Check medications against patient allergies."""
conflicts = []
for medication in medications:
for allergy in allergies:
# Check if medication could trigger allergy
if self._medication_triggers_allergy(medication, allergy):
conflicts.append({
'type': 'allergy_conflict',
'severity': 'high',
'message': f'Medication {medication.mapped_name} may trigger {allergy.get("allergen", "")} allergy',
'medication': medication.mapped_name,
'allergen': allergy.get('allergen', ''),
'recommendation': 'Consider alternative medication'
})
return conflicts
async def _generate_preventive_care_suggestions(self, processed_data: Dict) -> List[Dict]:
"""Generate preventive care suggestions based on patient conditions."""
suggestions = []
# Check for diabetes management
if processed_data['quality_indicators'].get('diabetes_control_eligible'):
suggestions.append({
'type': 'preventive_care',
'category': 'diabetes_management',
'message': 'Patient eligible for HbA1c monitoring',
'recommendation': 'Schedule HbA1c test within 3 months',
'priority': 'medium'
})
# Check for hypertension management
if processed_data['quality_indicators'].get('hypertension_control_eligible'):
suggestions.append({
'type': 'preventive_care',
'category': 'hypertension_management',
'message': 'Patient eligible for blood pressure monitoring',
'recommendation': 'Monitor blood pressure regularly',
'priority': 'medium'
})
return suggestions
def _has_potential_interaction(self, drug1: str, drug2: str) -> bool:
"""Simple drug interaction check (would be more sophisticated in production)."""
# Example: ACE inhibitors and potassium supplements
ace_inhibitors = ['lisinopril', 'enalapril', 'captopril']
potassium_supplements = ['potassium_chloride']
return (any(ace in drug1.lower() for ace in ace_inhibitors) and
any(pot in drug2.lower() for pot in potassium_supplements))
def _medication_triggers_allergy(self, medication: CodeMapping, allergy: Dict) -> bool:
"""Check if medication might trigger patient allergy."""
allergen = allergy.get('allergen', '').lower()
medication_name = medication.mapped_name.lower()
# Simple string matching - production would use more sophisticated methods
return allergen in medication_name or medication_name in allergen
# Workflow Integration Example
class EHRWorkflowIntegration:
"""
Integrates vocabulary services into clinical workflows.
Provides hooks for common EHR workflow events.
"""
def __init__(self, orchestrator: EHRIntegrationOrchestrator):
self.orchestrator = orchestrator
self.decision_support = ClinicalDecisionSupport(orchestrator.omophub_integrator)
async def on_patient_chart_open(self, ehr_name: str, patient_id: str) -> Dict[str, Any]:
"""
Called when clinician opens patient chart.
Pre-loads standardized vocabulary data and clinical insights.
"""
try:
# Process patient data in background
result = await self.orchestrator.process_patient(ehr_name, patient_id)
if result['status'] == 'success':
# Generate clinical decision support
patient_raw_data = result['patient_data'] # Would need actual PatientData object
# cds_result = await self.decision_support.evaluate_patient_context(patient_raw_data)
return {
'patient_summary': {
'total_diagnoses': len(result['patient_data']['diagnosis_mappings']),
'chronic_conditions': len(result['patient_data']['clinical_insights'].get('chronic_conditions', [])),
'active_medications': len(result['patient_data']['medication_mappings'])
},
'standardized_data_ready': True,
'last_updated': result['processing_timestamp']
}
else:
return {'error': result.get('error'), 'standardized_data_ready': False}
except Exception as e:
return {'error': str(e), 'standardized_data_ready': False}
async def on_diagnosis_entry(self, diagnosis_text: str, ehr_name: str, patient_id: str) -> Dict[str, Any]:
"""
Called when clinician enters diagnosis.
Provides real-time vocabulary suggestions and validation.
"""
suggestions = []
try:
# Search for matching concepts
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.orchestrator.omophub_integrator.base_url}/v1/concepts/search",
headers=self.orchestrator.omophub_integrator.headers,
params={
'query': diagnosis_text,
'domain_id': 'Condition',
'standard_concept': 'S',
'page_size': 5
}
)
response.raise_for_status()
data = response.json()
for concept in data.get('data', []):
suggestions.append({
'concept_id': concept['concept_id'],
'concept_code': concept['concept_code'],
'concept_name': concept['concept_name'],
'vocabulary_id': concept['vocabulary_id'],
'confidence': 0.9 # Would be calculated based on text similarity
})
except Exception as e:
return {'error': str(e), 'suggestions': []}
return {
'suggestions': suggestions,
'auto_coding_available': len(suggestions) > 0
}
async def on_medication_order(self, medication_name: str, patient_id: str, ehr_name: str) -> Dict[str, Any]:
"""
Called when clinician orders medication.
Provides safety checks and standardized coding.
"""
try:
# Get patient data for safety checks
patient_result = await self.orchestrator.process_patient(ehr_name, patient_id)
if patient_result['status'] != 'success':
return {'error': 'Unable to retrieve patient data for safety checks'}
# Find RxNorm code for medication
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.orchestrator.omophub_integrator.base_url}/v1/concepts/search",
headers=self.orchestrator.omophub_integrator.headers,
params={
'query': medication_name,
'vocabulary_ids': 'RxNorm',
'domain_id': 'Drug',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if not data.get('data'):
return {'error': 'Medication not found in RxNorm', 'safety_alerts': []}
rxnorm_concept = data['data'][0]
# Simulate safety checks (would be more comprehensive in production)
safety_alerts = []
# Check against patient allergies
patient_data = patient_result['patient_data']
# Would perform actual allergy checking here
return {
'rxnorm_code': rxnorm_concept['concept_code'],
'rxnorm_name': rxnorm_concept['concept_name'],
'safety_alerts': safety_alerts,
'coding_confidence': 0.95
}
except Exception as e:
return {'error': str(e)}
```
## Performance and Monitoring
### Integration Health Monitoring
```python
class EHRIntegrationMonitor:
"""Monitor EHR integration health and performance."""
def __init__(self, orchestrator: EHRIntegrationOrchestrator):
self.orchestrator = orchestrator
self.metrics = {}
self.health_checks = {}
async def run_health_checks(self) -> Dict[str, Any]:
"""Run comprehensive health checks on all EHR connections."""
results = {}
for ehr_name, connector in self.orchestrator.ehr_connectors.items():
try:
# Test authentication
auth_success = await connector.authenticate()
# Test basic API connectivity
if hasattr(connector, 'test_connection'):
api_success = await connector.test_connection()
else:
api_success = True # Assume working if no test method
results[ehr_name] = {
'authentication': 'pass' if auth_success else 'fail',
'api_connectivity': 'pass' if api_success else 'fail',
'overall_status': 'healthy' if auth_success and api_success else 'unhealthy',
'last_checked': datetime.utcnow().isoformat()
}
except Exception as e:
results[ehr_name] = {
'error': str(e),
'overall_status': 'error',
'last_checked': datetime.utcnow().isoformat()
}
return results
def track_processing_metrics(self, ehr_name: str, processing_time: float, success: bool):
"""Track performance metrics for processing operations."""
if ehr_name not in self.metrics:
self.metrics[ehr_name] = {
'total_requests': 0,
'successful_requests': 0,
'failed_requests': 0,
'average_processing_time': 0,
'processing_times': []
}
metrics = self.metrics[ehr_name]
metrics['total_requests'] += 1
if success:
metrics['successful_requests'] += 1
else:
metrics['failed_requests'] += 1
metrics['processing_times'].append(processing_time)
if len(metrics['processing_times']) > 100: # Keep only last 100 times
metrics['processing_times'] = metrics['processing_times'][-100:]
metrics['average_processing_time'] = sum(metrics['processing_times']) / len(metrics['processing_times'])
def get_performance_report(self) -> Dict[str, Any]:
"""Generate performance report for all EHR systems."""
report = {
'generated_at': datetime.utcnow().isoformat(),
'ehr_systems': {}
}
for ehr_name, metrics in self.metrics.items():
success_rate = (metrics['successful_requests'] / metrics['total_requests'] * 100) if metrics['total_requests'] > 0 else 0
report['ehr_systems'][ehr_name] = {
'total_requests': metrics['total_requests'],
'success_rate': round(success_rate, 2),
'average_processing_time': round(metrics['average_processing_time'], 3),
'status': 'healthy' if success_rate > 95 else 'warning' if success_rate > 80 else 'critical'
}
return report
```
## Security and Compliance
### HIPAA-Compliant Integration
```python
class HIPAACompliantEHRIntegrator(OMOPHubEHRIntegrator):
"""
HIPAA-compliant version of EHR integrator with enhanced security and audit logging.
"""
def __init__(self, api_key: str, base_url: str = "https://api.omophub.com"):
super().__init__(api_key, base_url)
self.audit_logger = self._setup_audit_logger()
self.encryption_key = self._load_encryption_key()
def process_patient_data(self, patient_data: PatientData) -> Dict[str, Any]:
"""Process patient data with full HIPAA audit trail."""
audit_id = self._generate_audit_id()
try:
# Log access attempt
self.audit_logger.info({
'audit_id': audit_id,
'action': 'patient_data_processing_start',
'patient_id': patient_data.patient_id,
'timestamp': datetime.utcnow().isoformat(),
'user_context': self._get_user_context()
})
# Sanitize PHI before processing
sanitized_data = self._sanitize_phi(patient_data)
# Process with standard method
result = super().process_patient_data(sanitized_data)
# Log successful completion
self.audit_logger.info({
'audit_id': audit_id,
'action': 'patient_data_processing_complete',
'status': 'success',
'records_processed': {
'diagnoses': len(result['diagnosis_mappings']),
'procedures': len(result['procedure_mappings']),
'medications': len(result['medication_mappings'])
},
'timestamp': datetime.utcnow().isoformat()
})
return result
except Exception as e:
# Log error
self.audit_logger.error({
'audit_id': audit_id,
'action': 'patient_data_processing_error',
'error': str(e),
'timestamp': datetime.utcnow().isoformat()
})
raise
def _setup_audit_logger(self):
"""Setup HIPAA-compliant audit logging."""
audit_logger = logging.getLogger('hipaa_ehr_audit')
audit_logger.setLevel(logging.INFO)
# Configure secure, encrypted log storage
handler = logging.FileHandler('/var/log/hipaa/ehr_integration_audit.log')
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
audit_logger.addHandler(handler)
return audit_logger
def _sanitize_phi(self, patient_data: PatientData) -> PatientData:
"""Remove or mask PHI from patient data before processing."""
# Implementation would depend on specific PHI requirements
# For demo purposes, returning original data
return patient_data
def _generate_audit_id(self) -> str:
"""Generate unique audit ID for tracking."""
import uuid
return str(uuid.uuid4())
def _get_user_context(self) -> Dict[str, str]:
"""Get current user context for audit logging."""
return {
'user_id': 'system', # Would be actual user ID
'session_id': 'session_123', # Would be actual session
'ip_address': '127.0.0.1' # Would be actual IP
}
def _load_encryption_key(self) -> bytes:
"""Load encryption key for sensitive data."""
# Would load from secure key management system
return b'demo_key_would_be_from_key_management_system'
```
## Best Practices and Recommendations
### Configuration Management
```python
class EHRIntegrationConfig:
"""Centralized configuration management for EHR integrations."""
def __init__(self):
self.config = {
'omophub': {
'base_url': 'https://api.omophub.com',
'timeout': 30,
'retry_attempts': 3,
'rate_limit': {
'requests_per_minute': 1000,
'burst_limit': 100
}
},
'epic': {
'timeout': 60,
'batch_size': 10,
'max_concurrent_requests': 5,
'auth_refresh_buffer': 300 # Refresh token 5 minutes before expiry
},
'cerner': {
'timeout': 45,
'batch_size': 8,
'max_concurrent_requests': 3
},
'veradigm': {
'timeout': 90, # Veradigm APIs can be slower
'batch_size': 5,
'max_concurrent_requests': 2
},
'monitoring': {
'health_check_interval': 300, # 5 minutes
'performance_logging': True,
'alert_thresholds': {
'success_rate_warning': 95,
'success_rate_critical': 80,
'response_time_warning': 5.0,
'response_time_critical': 10.0
}
}
}
def get_ehr_config(self, ehr_name: str) -> Dict[str, Any]:
"""Get configuration for specific EHR system."""
return self.config.get(ehr_name.lower(), {})
def get_omophub_config(self) -> Dict[str, Any]:
"""Get OMOPHub API configuration."""
return self.config['omophub']
```
## EHR-Specific Resources
* **Epic® FHIR Documentation**: [https://fhir.epic.com/](https://fhir.epic.com/)
* **Cerner FHIR Documentation**: [https://fhir.cerner.com/](https://fhir.cerner.com/)
* **Veradigm Developer Portal**: [https://developer.veradigm.com/](https://developer.veradigm.com/)
The OMOPHub API provides comprehensive vocabulary services that seamlessly integrate with major EHR systems, enabling standardized medical coding, improved clinical workflows, and enhanced patient care through better data interoperability.
Epic® is a registered trademark of Epic Systems Corporation. This integration guide is provided for informational purposes and does not constitute an endorsement by Epic Systems Corporation.
# Insurance Claims Processing
Source: https://docs.omophub.com/guides/use-cases/insurance-claims-processing
Automate medical coding validation, claims enrichment, and fraud detection using standardized healthcare vocabularies
# Insurance Claims Processing with OMOPHub
> Note: This guide is for illustrative purposes only and is not billing, coding, or legal advice. Ensure PHI is handled per HIPAA and organizational policy.
## Overview
Insurance claims processing involves validating medical codes, checking procedure-diagnosis relationships, detecting duplicates, and ensuring compliance with coverage policies. Manual review is time-intensive and error-prone, leading to claim denials, payment delays, and administrative burden.
The OMOPHub API enables automated claims processing by providing access to standardized medical vocabularies (ICD-10, HCPCS), relationship validation, and hierarchical code analysis for comprehensive claims review.
## Business Problem
Healthcare payers face several critical challenges in claims processing:
* **Coding Errors**: 30-40% of claims contain coding errors requiring manual review
* **Missing Information**: Incomplete procedure-diagnosis relationships delay processing
* **Fraud Detection**: Complex patterns require sophisticated analysis of code relationships
* **Prior Authorization**: Manual verification of coverage requirements is time-intensive
* **Claims Enrichment**: Additional relevant codes could optimize reimbursement but aren't identified
## Solution Architecture
```mermaid
graph TB
A[Incoming Claims] --> B[Claims Validation Engine]
B --> C[OMOPHub API]
C --> D[ICD-10 Validation]
C --> E[HCPCS Validation]
C --> F[Procedure-Diagnosis Matching]
C --> G[Hierarchical Analysis]
B --> H[Fraud Detection Module]
B --> I[Claims Enrichment]
B --> J[Prior Authorization Check]
H --> K[Pattern Analysis]
I --> L[Code Suggestions]
J --> M[Coverage Verification]
D --> N[Validated Claims]
E --> N
F --> N
G --> N
K --> O[Risk Assessment]
L --> P[Enhanced Claims]
M --> Q[Authorization Status]
N --> R[Approved Claims]
O --> S[Flagged for Review]
P --> T[Enriched Claims]
Q --> U[Auth Required/Denied]
```
## Implementation Guide
### Python Implementation
```python
import requests
import json
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
from datetime import datetime, timedelta
from collections import defaultdict
import logging
@dataclass
class ClaimLine:
procedure_code: str
procedure_code_type: str # HCPCS
diagnosis_codes: List[str]
diagnosis_code_type: str # ICD10CM
units: int
charge_amount: float
service_date: str
@dataclass
class ClaimValidationResult:
is_valid: bool
errors: List[str]
warnings: List[str]
suggested_codes: List[str]
fraud_risk_score: float
@dataclass
class EnrichedClaim:
original_claim: ClaimLine
additional_diagnosis_codes: List[str]
alternative_procedure_codes: List[str]
supporting_relationships: List[Dict]
class InsuranceClaimsProcessor:
def __init__(self, api_key: str, base_url: str = "https://api.omophub.com"):
self.api_key = api_key
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {api_key}"}
self.session = requests.Session()
self.timeout = 10 # seconds
self.fraud_patterns = {}
self.prior_auth_requirements = {}
def validate_claim(self, claim: ClaimLine) -> ClaimValidationResult:
"""Comprehensive claim validation including code validation and relationship checks."""
errors = []
warnings = []
suggested_codes = []
# Validate procedure codes
proc_validation = self._validate_procedure_code(
claim.procedure_code,
claim.procedure_code_type
)
if not proc_validation['is_valid']:
errors.extend(proc_validation['errors'])
# Validate diagnosis codes
for dx_code in claim.diagnosis_codes:
dx_validation = self._validate_diagnosis_code(dx_code, claim.diagnosis_code_type)
if not dx_validation['is_valid']:
errors.extend(dx_validation['errors'])
# Check procedure-diagnosis relationships
relationship_check = self._validate_procedure_diagnosis_relationships(
claim.procedure_code,
claim.diagnosis_codes
)
if not relationship_check['appropriate']:
warnings.extend(relationship_check['warnings'])
suggested_codes.extend(relationship_check['suggested_diagnoses'])
# Calculate fraud risk score
fraud_score = self._calculate_fraud_risk(claim)
return ClaimValidationResult(
is_valid=len(errors) == 0,
errors=errors,
warnings=warnings,
suggested_codes=suggested_codes,
fraud_risk_score=fraud_score
)
def _validate_procedure_code(self, code: str, code_type: str) -> Dict:
"""Validate HCPCS/HCPCS procedure codes."""
vocabulary_map = {
'HCPCS': 'HCPCS',
'HCPCS': 'HCPCS'
}
if code_type not in vocabulary_map:
return {
'is_valid': False,
'errors': [f"Unsupported procedure code type: {code_type}"]
}
try:
response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': code,
'vocabulary_ids': vocabulary_map[code_type],
'standard_concept': 'S',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
concept = data['data'][0]
return {
'is_valid': True,
'concept_name': concept['concept_name'],
'concept_id': concept['concept_id']
}
else:
return {
'is_valid': False,
'errors': [f"Invalid {code_type} code: {code}"]
}
except requests.RequestException as e:
return {
'is_valid': False,
'errors': [f"Error validating procedure code: {str(e)}"]
}
def _validate_diagnosis_code(self, code: str, code_type: str) -> Dict:
"""Validate ICD-10-CM diagnosis codes."""
vocabulary_map = {
'ICD10CM': 'ICD10CM'
}
if code_type not in vocabulary_map:
return {
'is_valid': False,
'errors': [f"Unsupported diagnosis code type: {code_type}"]
}
try:
response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': code,
'vocabulary_ids': vocabulary_map[code_type],
'standard_concept': 'S',
'page_size': 1
}
)
response.raise_for_status()
data = response.json()
if data['data']:
return {
'is_valid': True,
'concept_name': data['data'][0]['concept_name']
}
else:
return {
'is_valid': False,
'errors': [f"Invalid {code_type} code: {code}"]
}
except requests.RequestException as e:
return {
'is_valid': False,
'errors': [f"Error validating diagnosis code: {str(e)}"]
}
def _validate_procedure_diagnosis_relationships(self, procedure_code: str, diagnosis_codes: List[str]) -> Dict:
"""Check if procedure-diagnosis combinations are clinically appropriate."""
# Get procedure concept
try:
proc_response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': procedure_code,
'vocabulary_ids': 'HCPCS,HCPCS',
'page_size': 1
}
)
proc_response.raise_for_status()
proc_data = proc_response.json()
if not proc_data['data']:
return {
'appropriate': False,
'warnings': [f"Procedure code not found: {procedure_code}"]
}
procedure_concept_id = proc_data['data'][0]['concept_id']
# Get related conditions for this procedure
relations_response = requests.get(
f"{self.base_url}/v1/concepts/{procedure_concept_id}/relationships",
headers=self.headers,
params={
'relationship_types': 'Has indication,Treats',
'include_synonyms': True
}
)
relations_response.raise_for_status()
relations_data = relations_response.json()
# Extract indication concepts
indication_concepts = []
for relationship in relations_data.get('data', []):
if relationship['relationship_name'] in ['Has indication', 'Treats']:
indication_concepts.append(relationship['related_concept'])
# Check if any diagnosis codes align with indications
appropriate_diagnoses = []
for dx_code in diagnosis_codes:
# Find concept for diagnosis
dx_response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': dx_code,
'vocabulary_ids': 'ICD10CM',
'page_size': 1
}
)
dx_response.raise_for_status()
dx_data = dx_response.json()
if dx_data['data']:
dx_concept_id = dx_data['data'][0]['concept_id']
# Check if diagnosis aligns with procedure indications
for indication in indication_concepts:
if dx_concept_id == indication['concept_id']:
appropriate_diagnoses.append(dx_code)
break
if not appropriate_diagnoses and diagnosis_codes:
return {
'appropriate': False,
'warnings': [f"Procedure {procedure_code} may not be appropriate for provided diagnoses"],
'suggested_diagnoses': [concept['concept_code'] for concept in indication_concepts[:5]]
}
return {
'appropriate': True,
'matching_diagnoses': appropriate_diagnoses
}
except requests.RequestException as e:
return {
'appropriate': False,
'warnings': [f"Error checking procedure-diagnosis relationships: {str(e)}"]
}
def _calculate_fraud_risk(self, claim: ClaimLine) -> float:
"""Calculate fraud risk score based on various factors."""
risk_score = 0.0
# High-value procedures get higher scrutiny
if claim.charge_amount > 10000:
risk_score += 0.3
elif claim.charge_amount > 5000:
risk_score += 0.2
# Unusual unit counts
if claim.units > 10:
risk_score += 0.2
# Multiple diagnosis codes might indicate upcoding
if len(claim.diagnosis_codes) > 5:
risk_score += 0.1
# Check against known fraud patterns
procedure_diagnosis_combo = f"{claim.procedure_code}_{','.join(sorted(claim.diagnosis_codes))}"
if procedure_diagnosis_combo in self.fraud_patterns:
risk_score += self.fraud_patterns[procedure_diagnosis_combo]
return min(risk_score, 1.0)
def enrich_claim(self, claim: ClaimLine) -> EnrichedClaim:
"""Suggest additional relevant codes to maximize reimbursement."""
additional_diagnoses = []
alternative_procedures = []
supporting_relationships = []
try:
# Find related diagnosis codes
for dx_code in claim.diagnosis_codes:
dx_response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': dx_code,
'vocabulary_ids': 'ICD10CM',
'page_size': 1
}
)
dx_response.raise_for_status()
dx_data = dx_response.json()
if dx_data['data']:
concept_id = dx_data['data'][0]['concept_id']
# Get hierarchical relationships
hierarchy_response = requests.get(
f"{self.base_url}/v1/concepts/{concept_id}/hierarchy",
headers=self.headers,
params={
'include_ancestors': True,
'include_descendants': True,
'max_levels': 2
}
)
hierarchy_response.raise_for_status()
hierarchy_data = hierarchy_response.json()
# Extract relevant related conditions
for item in hierarchy_data.get('data', []):
if (item['concept']['vocabulary_id'] == 'ICD10CM' and
item['concept']['concept_code'] not in claim.diagnosis_codes):
additional_diagnoses.append(item['concept']['concept_code'])
# Find alternative procedures
proc_response = requests.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': claim.procedure_code,
'vocabulary_ids': 'HCPCS,HCPCS',
'page_size': 1
}
)
proc_response.raise_for_status()
proc_data = proc_response.json()
if proc_data['data']:
proc_concept_id = proc_data['data'][0]['concept_id']
# Get procedure relationships
proc_relations_response = requests.get(
f"{self.base_url}/v1/concepts/{proc_concept_id}/relationships",
headers=self.headers,
params={
'relationship_types': 'Procedure site,Has finding site',
'include_synonyms': True
}
)
proc_relations_response.raise_for_status()
proc_relations_data = proc_relations_response.json()
supporting_relationships = proc_relations_data.get('data', [])
except requests.RequestException as e:
logging.error(f"Error enriching claim: {str(e)}")
return EnrichedClaim(
original_claim=claim,
additional_diagnosis_codes=additional_diagnoses[:5], # Limit suggestions
alternative_procedure_codes=alternative_procedures[:3],
supporting_relationships=supporting_relationships
)
def check_prior_authorization(self, claim: ClaimLine, member_id: str, policy_id: str) -> Dict:
"""Check if procedure requires prior authorization."""
# This would integrate with payer-specific prior auth databases
# For demo purposes, using simplified logic based on procedure codes
high_cost_procedures = {
high_cost_procedures = {
'27447', # Total knee arthroplasty
'23472', # Total shoulder arthroplasty
'22630', # Lumbar spinal fusion (one interspace)
'33533', # Coronary artery bypass; single arterial graft
}
authorization_required = claim.procedure_code in high_cost_procedures
if authorization_required:
# Check if authorization already exists
auth_exists = self._check_existing_authorization(member_id, claim.procedure_code)
return {
'authorization_required': True,
'authorization_exists': auth_exists,
'status': 'approved' if auth_exists else 'pending',
'procedure_name': self._get_procedure_name(claim.procedure_code)
}
return {
'authorization_required': False,
'status': 'not_required'
}
def _check_existing_authorization(self, member_id: str, procedure_code: str) -> bool:
def _get_procedure_name(self, procedure_code: str) -> str:
"""Get human-readable procedure name."""
try:
response = self.session.get(
f"{self.base_url}/v1/concepts/search",
headers=self.headers,
params={
'query': procedure_code,
'vocabulary_ids': 'HCPCS,HCPCS',
'page_size': 1
},
timeout=self.timeout
)
if not response.ok:
return None
data = response.json()
if data.get('data'):
return data['data'][0]['concept_name']
return None
except (requests.RequestException, ValueError, KeyError) as e:
# Log error or handle as needed
return None
def process_batch_claims(self, claims: List[ClaimLine]) -> Dict:
"""Process multiple claims efficiently."""
results = {
'total_claims': len(claims),
'valid_claims': 0,
'invalid_claims': 0,
'high_risk_claims': 0,
'claims_requiring_auth': 0,
'enrichment_opportunities': 0,
'detailed_results': []
}
for claim in claims:
# Validate claim
validation = self.validate_claim(claim)
# Check prior authorization
auth_check = self.check_prior_authorization(claim, "member_123", "policy_456")
# Enrich claim
enriched = self.enrich_claim(claim)
# Update counters
if validation.is_valid:
results['valid_claims'] += 1
else:
results['invalid_claims'] += 1
if validation.fraud_risk_score > 0.7:
results['high_risk_claims'] += 1
if auth_check['authorization_required']:
results['claims_requiring_auth'] += 1
if enriched.additional_diagnosis_codes or enriched.alternative_procedure_codes:
results['enrichment_opportunities'] += 1
results['detailed_results'].append({
'claim': claim,
'validation': validation,
'authorization': auth_check,
'enrichment': enriched
})
return results
# Example usage
def main():
processor = InsuranceClaimsProcessor("your-api-key")
# Sample claim
sample_claim = ClaimLine(
procedure_code="99213", # Office visit
procedure_code_type="HCPCS",
diagnosis_codes=["E11.9"], # Type 2 diabetes
diagnosis_code_type="ICD10CM",
units=1,
charge_amount=250.00,
service_date="2024-03-15"
)
# Validate claim
validation_result = processor.validate_claim(sample_claim)
print(f"Claim valid: {validation_result.is_valid}")
print(f"Errors: {validation_result.errors}")
print(f"Fraud risk score: {validation_result.fraud_risk_score}")
# Enrich claim
enriched_claim = processor.enrich_claim(sample_claim)
print(f"Additional diagnosis suggestions: {enriched_claim.additional_diagnosis_codes}")
# Check authorization
auth_result = processor.check_prior_authorization(sample_claim, "member_123", "policy_456")
print(f"Authorization required: {auth_result['authorization_required']}")
if __name__ == "__main__":
main()
```
### JavaScript Implementation
```javascript
const axios = require('axios');
class InsuranceClaimsProcessor {
constructor(apiKey, baseUrl = 'https://api.omophub.com') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.headers = { 'Authorization': `Bearer ${apiKey}` };
this.fraudPatterns = new Map();
}
async validateClaim(claim) {
const errors = [];
const warnings = [];
const suggestedCodes = [];
// Validate procedure code
try {
const procValidation = await this.validateProcedureCode(
claim.procedureCode,
claim.procedureCodeType
);
if (!procValidation.isValid) {
errors.push(...procValidation.errors);
}
} catch (error) {
errors.push(`Procedure validation error: ${error.message}`);
}
// Validate diagnosis codes
for (const dxCode of claim.diagnosisCodes) {
try {
const dxValidation = await this.validateDiagnosisCode(
dxCode,
claim.diagnosisCodeType
);
if (!dxValidation.isValid) {
errors.push(...dxValidation.errors);
}
} catch (error) {
errors.push(`Diagnosis validation error: ${error.message}`);
}
}
// Check relationships
try {
const relationshipCheck = await this.validateProcedureDiagnosisRelationships(
claim.procedureCode,
claim.diagnosisCodes
);
if (!relationshipCheck.appropriate) {
warnings.push(...relationshipCheck.warnings || []);
suggestedCodes.push(...relationshipCheck.suggestedDiagnoses || []);
}
} catch (error) {
warnings.push(`Relationship validation error: ${error.message}`);
}
const fraudScore = this.calculateFraudRisk(claim);
return {
isValid: errors.length === 0,
errors,
warnings,
suggestedCodes,
fraudRiskScore: fraudScore
};
}
async validateProcedureCode(code, codeType) {
const vocabularyMap = {
'HCPCS': 'HCPCS',
'HCPCS': 'HCPCS'
};
if (!vocabularyMap[codeType]) {
return {
isValid: false,
errors: [`Unsupported procedure code type: ${codeType}`]
};
}
try {
const response = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: code,
vocabulary_ids: vocabularyMap[codeType],
standard_concept: 'S',
page_size: 1
}
});
if (response.data.data && response.data.data.length > 0) {
return {
isValid: true,
conceptName: response.data.data[0].concept_name,
conceptId: response.data.data[0].concept_id
};
} else {
return {
isValid: false,
errors: [`Invalid ${codeType} code: ${code}`]
};
}
} catch (error) {
return {
isValid: false,
errors: [`Error validating procedure code: ${error.message}`]
};
}
}
async validateDiagnosisCode(code, codeType) {
const vocabularyMap = { 'ICD10CM': 'ICD10CM' };
if (!vocabularyMap[codeType]) {
return {
isValid: false,
errors: [`Unsupported diagnosis code type: ${codeType}`]
};
}
try {
const response = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: code,
vocabulary_ids: vocabularyMap[codeType],
standard_concept: 'S',
page_size: 1
}
});
if (response.data.data && response.data.data.length > 0) {
return {
isValid: true,
conceptName: response.data.data[0].concept_name
};
} else {
return {
isValid: false,
errors: [`Invalid ${codeType} code: ${code}`]
};
}
} catch (error) {
return {
isValid: false,
errors: [`Error validating diagnosis code: ${error.message}`]
};
}
}
async validateProcedureDiagnosisRelationships(procedureCode, diagnosisCodes) {
try {
// Get procedure concept
const procResponse = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: procedureCode,
vocabulary_ids: 'HCPCS,HCPCS',
page_size: 1
}
});
if (!procResponse.data.data || procResponse.data.data.length === 0) {
return {
appropriate: false,
warnings: [`Procedure code not found: ${procedureCode}`]
};
}
const procedureConceptId = procResponse.data.data[0].concept_id;
// Get related conditions
const relationsResponse = await axios.get(
`${this.baseUrl}/v1/concepts/${procedureConceptId}/relationships`,
{
headers: this.headers,
params: {
relationship_types: 'Has indication,Treats',
include_synonyms: true
}
}
);
const indicationConcepts = relationsResponse.data.data
?.filter(rel => ['Has indication', 'Treats'].includes(rel.relationship_name))
.map(rel => rel.related_concept) || [];
// Check diagnosis alignment
const appropriateDiagnoses = [];
for (const dxCode of diagnosisCodes) {
const dxResponse = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: dxCode,
vocabulary_ids: 'ICD10CM',
page_size: 1
}
});
if (dxResponse.data.data && dxResponse.data.data.length > 0) {
const dxConceptId = dxResponse.data.data[0].concept_id;
const matches = indicationConcepts.some(
indication => indication.concept_id === dxConceptId
);
if (matches) {
appropriateDiagnoses.push(dxCode);
}
}
}
if (appropriateDiagnoses.length === 0 && diagnosisCodes.length > 0) {
return {
appropriate: false,
warnings: [`Procedure ${procedureCode} may not be appropriate for provided diagnoses`],
suggestedDiagnoses: indicationConcepts.slice(0, 5).map(c => c.concept_code)
};
}
return {
appropriate: true,
matchingDiagnoses: appropriateDiagnoses
};
} catch (error) {
return {
appropriate: false,
warnings: [`Error checking procedure-diagnosis relationships: ${error.message}`]
};
}
}
calculateFraudRisk(claim) {
let riskScore = 0.0;
if (claim.chargeAmount > 10000) {
riskScore += 0.3;
} else if (claim.chargeAmount > 5000) {
riskScore += 0.2;
}
if (claim.units > 10) {
riskScore += 0.2;
}
if (claim.diagnosisCodes.length > 5) {
riskScore += 0.1;
}
const comboKey = `${claim.procedureCode}_${[...claim.diagnosisCodes].sort().join(',')}`;
if (this.fraudPatterns.has(comboKey)) {
riskScore += this.fraudPatterns.get(comboKey);
}
return Math.min(riskScore, 1.0);
}
async enrichClaim(claim) {
const additionalDiagnoses = [];
const alternativeProcedures = [];
const supportingRelationships = [];
try {
// Find related diagnosis codes
for (const dxCode of claim.diagnosisCodes) {
const dxResponse = await axios.get(`${this.baseUrl}/v1/concepts/search`, {
headers: this.headers,
params: {
query: dxCode,
vocabulary_ids: 'ICD10CM',
page_size: 1
}
});
if (dxResponse.data.data && dxResponse.data.data.length > 0) {
const conceptId = dxResponse.data.data[0].concept_id;
const hierarchyResponse = await axios.get(
`${this.baseUrl}/v1/concepts/${conceptId}/hierarchy`,
{
headers: this.headers,
params: {
include_ancestors: true,
include_descendants: true,
max_levels: 2
}
}
);
const related = hierarchyResponse.data.data
?.filter(item =>
item.concept.vocabulary_id === 'ICD10CM' &&
!claim.diagnosisCodes.includes(item.concept.concept_code)
)
.map(item => item.concept.concept_code) || [];
additionalDiagnoses.push(...related);
}
}
} catch (error) {
console.error('Error enriching claim:', error.message);
}
return {
originalClaim: claim,
additionalDiagnosisCodes: [...new Set(additionalDiagnoses)].slice(0, 5),
alternativeProcedureCodes: alternativeProcedures.slice(0, 3),
supportingRelationships
};
}
}
// Example usage
async function main() {
const processor = new InsuranceClaimsProcessor('your-api-key');
const sampleClaim = {
procedureCode: '99213',
procedureCodeType: 'HCPCS',
diagnosisCodes: ['E11.9'],
diagnosisCodeType: 'ICD10CM',
units: 1,
chargeAmount: 250.00,
serviceDate: '2024-03-15'
};
// Validate claim
const validation = await processor.validateClaim(sampleClaim);
console.log('Claim valid:', validation.isValid);
console.log('Errors:', validation.errors);
console.log('Fraud risk score:', validation.fraudRiskScore);
// Enrich claim
const enriched = await processor.enrichClaim(sampleClaim);
console.log('Additional diagnosis suggestions:', enriched.additionalDiagnosisCodes);
}
if (require.main === module) {
main().catch(console.error);
}
module.exports = { InsuranceClaimsProcessor };
```
### R Implementation
```r
library(httr)
library(jsonlite)
library(dplyr)
library(purrr)
InsuranceClaimsProcessor <- R6::R6Class(
"InsuranceClaimsProcessor",
public = list(
api_key = NULL,
base_url = NULL,
headers = NULL,
fraud_patterns = NULL,
initialize = function(api_key, base_url = "https://api.omophub.com") {
self$api_key <- api_key
self$base_url <- base_url
self$headers <- add_headers(Authorization = paste("Bearer", api_key))
self$fraud_patterns <- list()
},
validate_claim = function(claim) {
errors <- character(0)
warnings <- character(0)
suggested_codes <- character(0)
# Validate procedure code
proc_validation <- tryCatch({
self$validate_procedure_code(claim$procedure_code, claim$procedure_code_type)
}, error = function(e) {
list(is_valid = FALSE, errors = paste("Procedure validation error:", e$message))
})
if (!proc_validation$is_valid) {
errors <- c(errors, proc_validation$errors)
}
# Validate diagnosis codes
for (dx_code in claim$diagnosis_codes) {
dx_validation <- tryCatch({
self$validate_diagnosis_code(dx_code, claim$diagnosis_code_type)
}, error = function(e) {
list(is_valid = FALSE, errors = paste("Diagnosis validation error:", e$message))
})
if (!dx_validation$is_valid) {
errors <- c(errors, dx_validation$errors)
}
}
# Check relationships
relationship_check <- tryCatch({
self$validate_procedure_diagnosis_relationships(
claim$procedure_code,
claim$diagnosis_codes
)
}, error = function(e) {
list(
appropriate = FALSE,
warnings = paste("Relationship validation error:", e$message)
)
})
if (!relationship_check$appropriate) {
if (!is.null(relationship_check$warnings)) {
warnings <- c(warnings, relationship_check$warnings)
}
if (!is.null(relationship_check$suggested_diagnoses)) {
suggested_codes <- c(suggested_codes, relationship_check$suggested_diagnoses)
}
}
fraud_score <- self$calculate_fraud_risk(claim)
list(
is_valid = length(errors) == 0,
errors = errors,
warnings = warnings,
suggested_codes = suggested_codes,
fraud_risk_score = fraud_score
)
},
validate_procedure_code = function(code, code_type) {
vocabulary_map <- list(
"HCPCS" = "HCPCS",
"HCPCS" = "HCPCS"
)
if (!code_type %in% names(vocabulary_map)) {
return(list(
is_valid = FALSE,
errors = paste("Unsupported procedure code type:", code_type)
))
}
response <- GET(
paste0(self$base_url, "/v1/concepts/search"),
self$headers,
query = list(
query = code,
vocabulary_ids = vocabulary_map[[code_type]],
standard_concept = "S",
page_size = 1
)
)
if (status_code(response) != 200) {
return(list(
is_valid = FALSE,
errors = paste("Error validating procedure code:", status_code(response))
))
}
data <- fromJSON(content(response, "text"))
if (length(data$data) > 0) {
list(
is_valid = TRUE,
concept_name = data$data$concept_name[1],
concept_id = data$data$concept_id[1]
)
} else {
list(
is_valid = FALSE,
errors = paste("Invalid", code_type, "code:", code)
)
}
},
validate_diagnosis_code = function(code, code_type) {
vocabulary_map <- list("ICD10CM" = "ICD10CM")
if (!code_type %in% names(vocabulary_map)) {
return(list(
is_valid = FALSE,
errors = paste("Unsupported diagnosis code type:", code_type)
))
}
response <- GET(
paste0(self$base_url, "/v1/concepts/search"),
self$headers,
query = list(
query = code,
vocabulary_ids = vocabulary_map[[code_type]],
standard_concept = "S",
page_size = 1
)
)
if (status_code(response) != 200) {
return(list(
is_valid = FALSE,
errors = paste("Error validating diagnosis code:", status_code(response))
))
}
data <- fromJSON(content(response, "text"))
if (length(data$data) > 0) {
list(
is_valid = TRUE,
concept_name = data$data$concept_name[1]
)
} else {
list(
is_valid = FALSE,
errors = paste("Invalid", code_type, "code:", code)
)
}
},
validate_procedure_diagnosis_relationships = function(procedure_code, diagnosis_codes) {
# Get procedure concept
proc_response <- GET(
paste0(self$base_url, "/v1/concepts/search"),
self$headers,
query = list(
query = procedure_code,
vocabulary_ids = "HCPCS,HCPCS",
page_size = 1
)
)
if (status_code(proc_response) != 200) {
return(list(
appropriate = FALSE,
warnings = paste("Error fetching procedure:", status_code(proc_response))
))
}
proc_data <- fromJSON(content(proc_response, "text"))
if (length(proc_data$data) == 0) {
return(list(
appropriate = FALSE,
warnings = paste("Procedure code not found:", procedure_code)
))
}
procedure_concept_id <- proc_data$data$concept_id[1]
# Get related conditions
relations_response <- GET(
paste0(self$base_url, "/v1/concepts/", procedure_concept_id, "/relationships"),
self$headers,
query = list(
relationship_types = "Has indication,Treats",
include_synonyms = TRUE
)
)
if (status_code(relations_response) != 200) {
return(list(
appropriate = FALSE,
warnings = "Error fetching procedure relationships"
))
}
relations_data <- fromJSON(content(relations_response, "text"))
indication_concepts <- relations_data$data %>%
filter(relationship_name %in% c("Has indication", "Treats")) %>%
pull(related_concept)
# Check diagnosis alignment
appropriate_diagnoses <- character(0)
for (dx_code in diagnosis_codes) {
dx_response <- GET(
paste0(self$base_url, "/v1/concepts/search"),
self$headers,
query = list(
query = dx_code,
vocabulary_ids = "ICD10CM",
page_size = 1
)
)
if (status_code(dx_response) == 200) {
dx_data <- fromJSON(content(dx_response, "text"))
if (length(dx_data$data) > 0) {
dx_concept_id <- dx_data$data$concept_id[1]
matching <- any(map_lgl(indication_concepts, ~ .x$concept_id == dx_concept_id))
if (matching) {
appropriate_diagnoses <- c(appropriate_diagnoses, dx_code)
}
}
}
}
if (length(appropriate_diagnoses) == 0 && length(diagnosis_codes) > 0) {
suggested_diagnoses <- if (length(indication_concepts) > 0) {
head(map_chr(indication_concepts, ~ .$concept_code %||% ""), 5)
} else {
character(0)
}
list(
appropriate = FALSE,
warnings = paste("Procedure", procedure_code, "may not be appropriate for provided diagnoses"),
suggested_diagnoses = suggested_diagnoses
)
} else {
list(
appropriate = TRUE,
matching_diagnoses = appropriate_diagnoses
)
}
},
calculate_fraud_risk = function(claim) {
risk_score <- 0.0
if (claim$charge_amount > 10000) {
risk_score <- risk_score + 0.3
} else if (claim$charge_amount > 5000) {
risk_score <- risk_score + 0.2
}
if (claim$units > 10) {
risk_score <- risk_score + 0.2
}
if (length(claim$diagnosis_codes) > 5) {
risk_score <- risk_score + 0.1
}
combo_key <- paste(claim$procedure_code, paste(sort(claim$diagnosis_codes), collapse = ","), sep = "_")
if (combo_key %in% names(self$fraud_patterns)) {
risk_score <- risk_score + self$fraud_patterns[[combo_key]]
}
min(risk_score, 1.0)
},
enrich_claim = function(claim) {
additional_diagnoses <- character(0)
alternative_procedures <- character(0)
supporting_relationships <- list()
tryCatch({
# Find related diagnosis codes
for (dx_code in claim$diagnosis_codes) {
dx_response <- GET(
paste0(self$base_url, "/v1/concepts/search"),
self$headers,
query = list(
query = dx_code,
vocabulary_ids = "ICD10CM",
page_size = 1
)
)
if (status_code(dx_response) == 200) {
dx_data <- fromJSON(content(dx_response, "text"))
if (length(dx_data$data) > 0) {
concept_id <- dx_data$data$concept_id[1]
hierarchy_response <- GET(
paste0(self$base_url, "/v1/concepts/", concept_id, "/hierarchy"),
self$headers,
query = list(
include_ancestors = TRUE,
include_descendants = TRUE,
max_levels = 2
)
)
if (status_code(hierarchy_response) == 200) {
hierarchy_data <- fromJSON(content(hierarchy_response, "text"))
related_codes <- hierarchy_data$data %>%
filter(
concept$vocabulary_id == "ICD10CM",
!concept$concept_code %in% claim$diagnosis_codes
) %>%
pull(concept) %>%
map_chr(~ .$concept_code)
additional_diagnoses <- c(additional_diagnoses, related_codes)
}
}
}
}
}, error = function(e) {
message("Error enriching claim: ", e$message)
})
list(
original_claim = claim,
additional_diagnosis_codes = unique(additional_diagnoses)[1:min(5, length(unique(additional_diagnoses)))],
alternative_procedure_codes = alternative_procedures[1:min(3, length(alternative_procedures))],
supporting_relationships = supporting_relationships
)
}
)
)
# Example usage
example_usage <- function() {
processor <- InsuranceClaimsProcessor$new("your-api-key")
sample_claim <- list(
procedure_code = "99213",
procedure_code_type = "HCPCS",
diagnosis_codes = c("E11.9"),
diagnosis_code_type = "ICD10CM",
units = 1,
charge_amount = 250.00,
service_date = "2024-03-15"
)
# Validate claim
validation_result <- processor$validate_claim(sample_claim)
cat("Claim valid:", validation_result$is_valid, "\n")
cat("Errors:", paste(validation_result$errors, collapse = ", "), "\n")
cat("Fraud risk score:", validation_result$fraud_risk_score, "\n")
# Enrich claim
enriched_claim <- processor$enrich_claim(sample_claim)
cat("Additional diagnosis suggestions:", paste(enriched_claim$additional_diagnosis_codes, collapse = ", "), "\n")
}
```
## Example Implementation Scenarios
### Scenario 1: Comprehensive Claim Processing
```python
# Process a complex surgical claim
processor = InsuranceClaimsProcessor("your-api-key")
complex_claim = ClaimLine(
procedure_code="27447", # Total knee replacement
procedure_code_type="HCPCS",
diagnosis_codes=["M17.11", "Z96.651"], # Osteoarthritis, knee implant status
diagnosis_code_type="ICD10CM",
units=1,
charge_amount=45000.00,
service_date="2024-03-15"
)
# Full processing pipeline
validation = processor.validate_claim(complex_claim)
auth_check = processor.check_prior_authorization(complex_claim, "member_456", "policy_789")
enrichment = processor.enrich_claim(complex_claim)
print("=== Claim Processing Results ===")
print(f"Valid: {validation.is_valid}")
print(f"Fraud Risk: {validation.fraud_risk_score:.2f}")
print(f"Authorization Required: {auth_check['authorization_required']}")
print(f"Additional Codes Suggested: {len(enrichment.additional_diagnosis_codes)}")
```
**Expected Output:**
```
=== Claim Processing Results ===
Valid: True
Fraud Risk: 0.30
Authorization Required: True
Additional Codes Suggested: 3
```
### Scenario 2: Batch Claims Processing
```python
# Process multiple claims efficiently
claims_batch = [
ClaimLine("99213", "HCPCS", ["E11.9"], "ICD10CM", 1, 250.00, "2024-03-15"),
ClaimLine("27447", "HCPCS", ["M17.11"], "ICD10CM", 1, 45000.00, "2024-03-15"),
ClaimLine("93000", "HCPCS", ["I25.9"], "ICD10CM", 1, 150.00, "2024-03-15")
]
batch_results = processor.process_batch_claims(claims_batch)
print(f"Total Claims: {batch_results['total_claims']}")
print(f"Valid Claims: {batch_results['valid_claims']}")
print(f"High Risk Claims: {batch_results['high_risk_claims']}")
print(f"Authorization Required: {batch_results['claims_requiring_auth']}")
```
**Expected Output:**
```
Total Claims: 3
Valid Claims: 3
High Risk Claims: 1
Authorization Required: 1
```
### Scenario 3: Fraud Detection Analysis
```python
# Analyze potentially fraudulent claim patterns
suspicious_claim = ClaimLine(
procedure_code="99215", # High-level office visit
procedure_code_type="HCPCS",
diagnosis_codes=["M79.601", "M79.602", "M79.603", "M25.511", "M25.512"], # Multiple pain codes
diagnosis_code_type="ICD10CM",
units=15, # Unusual high units
charge_amount=12000.00, # High charge
service_date="2024-03-15"
)
validation = processor.validate_claim(suspicious_claim)
print(f"Fraud Risk Score: {validation.fraud_risk_score:.2f}")
print(f"Risk Factors: High charge amount, excessive units, multiple similar diagnoses")
```
**Expected Output:**
## Integration Patterns
### Healthcare Payer Integration
```python
class PayerClaimsSystem:
def __init__(self, omophub_processor, payer_config):
self.processor = omophub_processor
self.payer_config = payer_config
def process_claim_submission(self, claim_data):
# Convert payer format to OMOPHub format
omophub_claim = self.convert_to_omophub_format(claim_data)
# Validate and enrich
validation = self.processor.validate_claim(omophub_claim)
enrichment = self.processor.enrich_claim(omophub_claim)
# Apply payer-specific business rules
payer_validation = self.apply_payer_rules(claim_data, validation)
return {
'claim_id': claim_data['claim_id'],
'status': 'approved' if validation.is_valid and payer_validation else 'denied',
'omophub_validation': validation,
'suggested_codes': enrichment.additional_diagnosis_codes,
'payer_notes': payer_validation.get('notes', [])
}
```
### EMR Integration
```javascript
class EMRClaimsValidator {
constructor(omophubProcessor, emrConfig) {
this.processor = omophubProcessor;
this.emrConfig = emrConfig;
}
async validateBeforeSubmission(encounterData) {
const claims = this.extractClaimsFromEncounter(encounterData);
const validationResults = [];
for (const claim of claims) {
const validation = await this.processor.validateClaim(claim);
const enrichment = await this.processor.enrichClaim(claim);
validationResults.push({
claim,
validation,
enrichment,
recommendedActions: this.generateRecommendations(validation, enrichment)
});
}
return {
overallStatus: validationResults.every(r => r.validation.isValid) ? 'ready' : 'needs_review',
claims: validationResults,
totalPotentialReimbursement: this.calculatePotentialReimbursement(validationResults)
};
}
generateRecommendations(validation, enrichment) {
const recommendations = [];
if (!validation.isValid) {
recommendations.push({
type: 'error',
message: 'Fix coding errors before submission',
actions: validation.errors
});
}
if (enrichment.additionalDiagnosisCodes.length > 0) {
recommendations.push({
type: 'enhancement',
message: 'Consider adding additional diagnosis codes',
actions: enrichment.additionalDiagnosisCodes
});
}
if (validation.fraudRiskScore > 0.7) {
recommendations.push({
type: 'warning',
message: 'High fraud risk - review documentation',
actions: ['Verify medical necessity', 'Check supporting documentation']
});
}
return recommendations;
}
}
```
## Best Practices
### Error Handling and Resilience
```python
# Required packages: pip install tenacity pybreaker
from tenacity import retry, stop_after_attempt, wait_exponential
from pybreaker import CircuitBreaker
import time
class RobustClaimsProcessor(InsuranceClaimsProcessor):
def __init__(self, api_key, base_url="https://api.omophub.com"):
super().__init__(api_key, base_url)
self.retry_config = {
'max_retries': 3,
'backoff_factor': 2,
'timeout': 30
}
self.circuit_breaker = CircuitBreaker(fail_max=5, reset_timeout=60)
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10))
def validate_claim_with_fallback(self, claim):
try:
return self.validate_claim(claim)
except requests.RequestException as e:
# Fallback to basic validation
return self._basic_validation_fallback(claim, str(e))
def _basic_validation_fallback(self, claim, error_msg):
"""Fallback validation when API is unavailable."""
basic_errors = []
# Basic format validation
if not claim.procedure_code or len(claim.procedure_code) < 3:
basic_errors.append("Invalid procedure code format")
if not claim.diagnosis_codes or len(claim.diagnosis_codes) == 0:
basic_errors.append("No diagnosis codes provided")
return ClaimValidationResult(
is_valid=len(basic_errors) == 0,
errors=basic_errors,
warnings=[f"API unavailable: {error_msg}"],
suggested_codes=[],
fraud_risk_score=0.0
)
```
### Performance Optimization
```python
from functools import lru_cache
import requests
from collections import defaultdict
# Module-level cached function for code validation
@lru_cache(maxsize=1000)
def _cached_code_validation(code: str, vocabulary: str, base_url: str, api_key: str) -> bool:
"""Cache frequently validated codes."""
try:
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(
f"{base_url}/v1/concepts/search",
headers=headers,
params={'query': code, 'vocabulary_ids': vocabulary, 'page_size': 1}
)
return len(response.json().get('data', [])) > 0
except:
return False
class OptimizedClaimsProcessor(InsuranceClaimsProcessor):
def __init__(self, api_key, base_url="https://api.omophub.com"):
super().__init__(api_key, base_url)
self.cache = {}
self.batch_size = 50
def process_claims_optimized(self, claims: List[ClaimLine]) -> Dict:
"""Optimized batch processing with caching and concurrent requests."""
# Group similar claims for batch processing
claim_groups = self._group_claims_by_similarity(claims)
results = []
for group in claim_groups:
# Process each group with cached lookups
group_results = self._process_claim_group(group)
results.extend(group_results)
return self._aggregate_results(results)
def _group_claims_by_similarity(self, claims):
"""Group claims with same procedure/diagnosis combinations."""
groups = defaultdict(list)
for claim in claims:
key = f"{claim.procedure_code}_{','.join(sorted(claim.diagnosis_codes))}"
groups[key].append(claim)
return list(groups.values())
def validate_code_cached(self, code: str, vocabulary: str) -> bool:
"""Validate code using cached lookup."""
return _cached_code_validation(code, vocabulary, self.base_url, self.api_key)
```
### Security and Compliance
```python
class HIPAACompliantClaimsProcessor(InsuranceClaimsProcessor):
def __init__(self, api_key, base_url="https://api.omophub.com"):
super().__init__(api_key, base_url)
self.audit_logger = self._setup_audit_logging()
self.phi_detector = PHIDetector()
def process_claim_secure(self, claim: ClaimLine, user_context: Dict) -> ClaimValidationResult:
"""HIPAA-compliant claim processing with full audit trail."""
audit_id = str(uuid.uuid4())
try:
# Log access attempt
self.audit_logger.info({
'audit_id': audit_id,
'action': 'claim_validation_start',
'user_id': user_context['user_id'],
'timestamp': datetime.utcnow().isoformat(),
'claim_id': getattr(claim, 'claim_id', 'unknown')
})
# Detect and mask PHI
sanitized_claim = self._sanitize_phi(claim)
# Process with standard validation
result = self.validate_claim(sanitized_claim)
# Log successful completion
self.audit_logger.info({
'audit_id': audit_id,
'action': 'claim_validation_complete',
'status': 'success',
'timestamp': datetime.utcnow().isoformat()
})
return result
except Exception as e:
# Log error
self.audit_logger.error({
'audit_id': audit_id,
'action': 'claim_validation_error',
'error': str(e),
'timestamp': datetime.utcnow().isoformat()
})
raise
def _sanitize_phi(self, claim: ClaimLine) -> ClaimLine:
"""Remove or mask potential PHI from claim data."""
# Implementation would depend on specific PHI detection requirements
return claim
def _setup_audit_logging(self):
"""Configure HIPAA-compliant audit logging."""
logger = logging.getLogger('hipaa_audit')
logger.setLevel(logging.INFO)
# Configure secure, encrypted log storage
handler = SecureFileHandler('/var/log/hipaa/claims_audit.log')
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
```
# Laboratory Result Mapping
Source: https://docs.omophub.com/guides/use-cases/laboratory-result-mapping
Standardize laboratory results using LOINC codes and OMOPHub API for interoperability across healthcare systems
## Overview
Laboratory result standardization is essential for healthcare interoperability. This guide demonstrates how to map local laboratory tests to LOINC (Logical Observation Identifiers Names and Codes) using the OMOPHub API, enabling consistent interpretation of lab results across different healthcare systems.
**Use Case**: Automatically map laboratory test codes and results to LOINC standards for data exchange, clinical decision support, and population health analytics.
## Business Problem
Healthcare organizations face significant challenges with laboratory data:
* **Data Silos**: Different labs use proprietary codes and naming conventions
* **Interoperability Issues**: Unable to aggregate lab data across systems
* **Clinical Decision Support**: Inconsistent reference ranges and units
* **Quality Reporting**: Difficulty in population health analytics
* **Regulatory Compliance**: HL7 FHIR and CMS requirements for standardized codes
## Solution Architecture
```mermaid
graph TD
A[Local Lab Results] --> B[Lab Code Extraction]
B --> C[OMOPHub API]
C --> D[LOINC Mapping]
C --> E[Unit Conversion]
C --> F[Reference Range Standardization]
D --> G[Standardized Lab Result]
E --> G
F --> G
G --> H[FHIR Resource]
G --> I[Clinical Decision Support]
G --> J[Analytics Database]
H --> K[Health Information Exchange]
I --> L[Clinical Alerts]
J --> M[Population Health Reports]
style D fill:#333333
style E fill:#333333
style F fill:#333333
```
## Implementation Guide
### Step 1: Set Up Laboratory Mapping Service
```python Python
from omophub import OMOPHubClient
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass
from decimal import Decimal
import re
import logging
@dataclass
class LabResult:
local_code: str
local_name: str
result_value: str
result_unit: str
reference_range: str
specimen_type: str
result_status: str
id: Optional[str] = None
collection_datetime: Optional[str] = None
result_datetime: Optional[str] = None
@dataclass
class StandardizedLabResult:
loinc_code: str
loinc_name: str
local_code: str
local_name: str
standardized_value: float
standardized_unit: str
standardized_reference_range: str
specimen_type: str
result_status: str
conversion_applied: bool
mapping_confidence: float
class LabResultMapper:
def __init__(self, api_key: str):
self.client = OMOPHubClient(api_key=api_key)
self.logger = logging.getLogger(__name__)
# Common unit conversions
self.unit_conversions = {
# Glucose: mg/dL to mmol/L
("glucose", "mg/dl", "mmol/l"): lambda x: x * 0.0555,
("glucose", "mmol/l", "mg/dl"): lambda x: x / 0.0555,
# Cholesterol: mg/dL to mmol/L
("cholesterol", "mg/dl", "mmol/l"): lambda x: x * 0.02586,
("cholesterol", "mmol/l", "mg/dl"): lambda x: x / 0.02586,
# Hemoglobin: g/dL to g/L
("hemoglobin", "g/dl", "g/l"): lambda x: x * 10,
("hemoglobin", "g/l", "g/dl"): lambda x: x / 10,
# Creatinine: mg/dL to μmol/L (clinical factor: 1 mg/dL = 88.4 μmol/L)
("creatinine", "mg/dl", "umol/l"): lambda x: x * 88.4,
("creatinine", "umol/l", "mg/dl"): lambda x: x * 0.0113,
}
# Common specimen types mapping
self.specimen_mapping = {
"serum": "SER",
"plasma": "PLAS",
"whole blood": "BLD",
"urine": "UR",
"cerebrospinal fluid": "CSF",
"saliva": "SAL"
}
def map_lab_result(self, lab_result: LabResult) -> Optional[StandardizedLabResult]:
"""Map a single lab result to LOINC standard"""
try:
# Step 1: Find LOINC code
loinc_mapping = self.find_loinc_code(lab_result)
if not loinc_mapping:
self.logger.warning(f"No LOINC mapping found for {lab_result.local_name}")
return None
# Step 2: Standardize units and values
standardized_value, standardized_unit = self.standardize_units(
lab_result.result_value,
lab_result.result_unit,
loinc_mapping["preferred_unit"],
lab_result.local_name # Use descriptive name for analyte identification
)
# Step 3: Standardize reference range
standardized_ref_range = self.standardize_reference_range(
lab_result.reference_range,
lab_result.result_unit,
standardized_unit,
loinc_mapping["loinc_name"]
)
return StandardizedLabResult(
loinc_code=loinc_mapping["loinc_code"],
loinc_name=loinc_mapping["loinc_name"],
local_code=lab_result.local_code,
local_name=lab_result.local_name,
standardized_value=standardized_value,
standardized_unit=standardized_unit,
standardized_reference_range=standardized_ref_range,
specimen_type=self.standardize_specimen_type(lab_result.specimen_type),
result_status=lab_result.result_status,
conversion_applied=lab_result.result_unit.lower() != standardized_unit.lower(),
mapping_confidence=loinc_mapping["confidence"]
)
except Exception as e:
self.logger.error(f"Error mapping lab result {lab_result.local_name}: {e}")
return None
def find_loinc_code(self, lab_result: LabResult) -> Optional[Dict[str, Any]]:
"""Find appropriate LOINC code for lab test"""
# Search strategies in order of preference
search_strategies = [
# Strategy 1: Exact local code lookup
{
"query": lab_result.local_code,
"vocabularies": ["LOINC"],
"domains": ["Measurement"],
"search_type": "code"
},
# Strategy 2: Test name search
{
"query": lab_result.local_name,
"vocabularies": ["LOINC"],
"domains": ["Measurement"],
"search_type": "name"
},
# Strategy 3: Broader search with specimen context
{
"query": f"{lab_result.local_name} {lab_result.specimen_type}",
"vocabularies": ["LOINC"],
"domains": ["Measurement"],
"search_type": "contextual"
}
]
for strategy in search_strategies:
try:
if strategy["search_type"] == "code":
# Direct code lookup
result = self.client.get_concept_by_code("LOINC", lab_result.local_code)
if result:
return {
"loinc_code": result["concept_code"],
"loinc_name": result["concept_name"],
"preferred_unit": self.extract_preferred_unit(result),
"confidence": 1.0
}
else:
# Text search
search_results = self.client.search_concepts({
"query": strategy["query"],
"vocabularies": strategy["vocabularies"],
"domains": strategy["domains"],
"standard_concepts_only": True,
"limit": 10
})
if search_results["concepts"]:
# Find best match considering specimen type
best_match = self.find_best_loinc_match(
search_results["concepts"],
lab_result
)
if best_match:
return {
"loinc_code": best_match["concept_code"],
"loinc_name": best_match["concept_name"],
"preferred_unit": self.extract_preferred_unit(best_match),
"confidence": best_match.get("relevance_score", 0.8)
}
except Exception as e:
self.logger.debug(f"Search strategy {strategy['search_type']} failed: {e}")
continue
return None
def find_best_loinc_match(self, loinc_concepts: List[Dict[str, Any]], lab_result: LabResult) -> Optional[Dict[str, Any]]:
"""Find best LOINC match considering context"""
scored_matches = []
for concept in loinc_concepts:
score = 0
concept_name = concept["concept_name"].lower()
local_name = lab_result.local_name.lower()
# Base relevance score
score += concept.get("relevance_score", 0.5)
# Specimen type matching bonus
specimen_keywords = {
"serum": ["ser", "serum"],
"plasma": ["plas", "plasma"],
"whole blood": ["bld", "blood", "whole"],
"urine": ["ur", "urine"],
"csf": ["csf", "cerebrospinal"]
}
specimen_type = lab_result.specimen_type.lower()
if specimen_type in specimen_keywords:
keywords = specimen_keywords[specimen_type]
if any(keyword in concept_name for keyword in keywords):
score += 0.2
# Method matching bonus
if any(method in concept_name for method in ["enzymatic", "immunoassay", "chromatography"]):
score += 0.1
scored_matches.append((score, concept))
if scored_matches:
# Sort by score and return best match
scored_matches.sort(key=lambda x: x[0], reverse=True)
return scored_matches[0][1]
return None
def extract_preferred_unit(self, loinc_concept: Dict[str, Any]) -> str:
"""Extract preferred unit from LOINC concept"""
concept_name = loinc_concept.get("concept_name", "")
# Common unit patterns in LOINC names
unit_patterns = {
r"mg/dL": "mg/dL",
r"mmol/L": "mmol/L",
r"g/dL": "g/dL",
r"g/L": "g/L",
r"μmol/L": "μmol/L",
r"umol/L": "μmol/L",
r"mEq/L": "mEq/L",
r"IU/L": "IU/L",
r"U/L": "U/L",
r"10\^9/L": "10^9/L",
r"10\^6/μL": "10^6/μL"
}
for pattern, unit in unit_patterns.items():
if re.search(pattern, concept_name, re.IGNORECASE):
return unit
# Default fallback
return "units"
```
```javascript JavaScript
import { OMOPHubClient } from '@omophub/sdk';
class LabResultMapper {
constructor(apiKey) {
this.client = new OMOPHubClient({ apiKey });
// Common unit conversions
this.unitConversions = new Map([
// Glucose: mg/dL to mmol/L
[JSON.stringify(['glucose', 'mg/dl', 'mmol/l']), (x) => x * 0.0555],
[JSON.stringify(['glucose', 'mmol/l', 'mg/dl']), (x) => x / 0.0555],
// Cholesterol: mg/dL to mmol/L
[JSON.stringify(['cholesterol', 'mg/dl', 'mmol/l']), (x) => x * 0.02586],
[JSON.stringify(['cholesterol', 'mmol/l', 'mg/dl']), (x) => x / 0.02586],
// Hemoglobin: g/dL to g/L
[JSON.stringify(['hemoglobin', 'g/dl', 'g/l']), (x) => x * 10],
[JSON.stringify(['hemoglobin', 'g/l', 'g/dl']), (x) => x / 10],
// Creatinine: mg/dL to μmol/L
[JSON.stringify(['creatinine', 'mg/dl', 'umol/l']), (x) => x * 88.42],
[JSON.stringify(['creatinine', 'umol/l', 'mg/dl']), (x) => x / 88.42]
]);
// Common specimen types mapping
this.specimenMapping = {
'serum': 'SER',
'plasma': 'PLAS',
'whole blood': 'BLD',
'urine': 'UR',
'cerebrospinal fluid': 'CSF',
'saliva': 'SAL'
};
}
async mapLabResult(labResult) {
try {
// Step 1: Find LOINC code
const loincMapping = await this.findLoincCode(labResult);
if (!loincMapping) {
console.warn(`No LOINC mapping found for ${labResult.local_name}`);
return null;
}
// Step 2: Standardize units and values
const [standardizedValue, standardizedUnit] = this.standardizeUnits(
labResult.result_value,
labResult.result_unit,
loincMapping.preferred_unit,
labResult.local_name // Use descriptive name for analyte identification
);
// Step 3: Standardize reference range
const standardizedRefRange = this.standardizeReferenceRange(
labResult.reference_range,
labResult.result_unit,
standardizedUnit,
loincMapping.loinc_name
);
return {
loinc_code: loincMapping.loinc_code,
loinc_name: loincMapping.loinc_name,
local_code: labResult.local_code,
local_name: labResult.local_name,
standardized_value: standardizedValue,
standardized_unit: standardizedUnit,
standardized_reference_range: standardizedRefRange,
specimen_type: this.standardizeSpecimenType(labResult.specimen_type),
result_status: labResult.result_status,
conversion_applied: labResult.result_unit.toLowerCase() !== standardizedUnit.toLowerCase(),
mapping_confidence: loincMapping.confidence
};
} catch (error) {
console.error(`Error mapping lab result ${labResult.local_name}:`, error);
return null;
}
}
async findLoincCode(labResult) {
// Search strategies in order of preference
const searchStrategies = [
// Strategy 1: Exact local code lookup
{
type: 'code',
query: labResult.local_code
},
// Strategy 2: Test name search
{
type: 'name',
query: labResult.local_name,
params: {
vocabularies: ['LOINC'],
domains: ['Measurement'],
standard_concepts_only: true,
limit: 10
}
},
// Strategy 3: Broader search with specimen context
{
type: 'contextual',
query: `${labResult.local_name} ${labResult.specimen_type}`,
params: {
vocabularies: ['LOINC'],
domains: ['Measurement'],
standard_concepts_only: true,
limit: 10
}
}
];
for (const strategy of searchStrategies) {
try {
if (strategy.type === 'code') {
// Direct code lookup
const result = await this.client.getConceptByCode('LOINC', labResult.local_code);
if (result.data) {
return {
loinc_code: result.data.concept_code,
loinc_name: result.data.concept_name,
preferred_unit: this.extractPreferredUnit(result.data),
confidence: 1.0
};
}
} else {
// Text search
const searchResults = await this.client.searchConcepts({
query: strategy.query,
...strategy.params
});
if (searchResults.data.concepts.length > 0) {
// Find best match considering specimen type
const bestMatch = this.findBestLoincMatch(
searchResults.data.concepts,
labResult
);
if (bestMatch) {
return {
loinc_code: bestMatch.concept_code,
loinc_name: bestMatch.concept_name,
preferred_unit: this.extractPreferredUnit(bestMatch),
confidence: bestMatch.relevance_score || 0.8
};
}
}
}
} catch (error) {
console.debug(`Search strategy ${strategy.type} failed:`, error);
continue;
}
}
return null;
}
findBestLoincMatch(loincConcepts, labResult) {
const scoredMatches = [];
for (const concept of loincConcepts) {
let score = 0;
const conceptName = concept.concept_name.toLowerCase();
const localName = labResult.local_name.toLowerCase();
// Base relevance score
score += concept.relevance_score || 0.5;
// Specimen type matching bonus
const specimenKeywords = {
'serum': ['ser', 'serum'],
'plasma': ['plas', 'plasma'],
'whole blood': ['bld', 'blood', 'whole'],
'urine': ['ur', 'urine'],
'csf': ['csf', 'cerebrospinal']
};
const specimenType = labResult.specimen_type.toLowerCase();
if (specimenKeywords[specimenType]) {
const keywords = specimenKeywords[specimenType];
if (keywords.some(keyword => conceptName.includes(keyword))) {
score += 0.2;
}
}
scoredMatches.push({ score, concept });
}
if (scoredMatches.length > 0) {
// Sort by score and return best match
scoredMatches.sort((a, b) => b.score - a.score);
return scoredMatches[0].concept;
}
return null;
}
extractPreferredUnit(loincConcept) {
const conceptName = loincConcept.concept_name || '';
// Common unit patterns in LOINC names
const unitPatterns = {
'mg/dL': /mg\/dL/i,
'mmol/L': /mmol\/L/i,
'g/dL': /g\/dL/i,
'g/L': /g\/L/i,
'μmol/L': /[μu]mol\/L/i,
'mEq/L': /mEq\/L/i,
'IU/L': /IU\/L/i,
'U/L': /U\/L/i,
'10^9/L': /10\^9\/L/i,
'10^6/μL': /10\^6\/[μu]L/i
};
for (const [unit, pattern] of Object.entries(unitPatterns)) {
if (pattern.test(conceptName)) {
return unit;
}
}
// Default fallback
return 'units';
}
}
```
```r R
library(omophub)
# Initialize lab mapper
init_lab_mapper <- function(api_key) {
client <- omophub_client(api_key = api_key)
# Common unit conversions
unit_conversions <- list(
# Glucose: mg/dL to mmol/L
list(analyte = "glucose", from = "mg/dl", to = "mmol/l", factor = 0.0555),
list(analyte = "glucose", from = "mmol/l", to = "mg/dl", factor = 18.018),
# Cholesterol: mg/dL to mmol/L
list(analyte = "cholesterol", from = "mg/dl", to = "mmol/l", factor = 0.02586),
list(analyte = "cholesterol", from = "mmol/l", to = "mg/dl", factor = 38.67),
# Hemoglobin: g/dL to g/L
list(analyte = "hemoglobin", from = "g/dl", to = "g/l", factor = 10),
list(analyte = "hemoglobin", from = "g/l", to = "g/dl", factor = 0.1),
# Creatinine: mg/dL to μmol/L (using normalized units)
list(analyte = "creatinine", from = "mg/dl", to = "umol/l", factor = 88.4),
list(analyte = "creatinine", from = "umol/l", to = "mg/dl", factor = 0.0113)
)
# Specimen type mapping
specimen_mapping <- list(
"serum" = "SER",
"plasma" = "PLAS",
"whole blood" = "BLD",
"urine" = "UR",
"cerebrospinal fluid" = "CSF",
"saliva" = "SAL"
)
list(
client = client,
unit_conversions = unit_conversions,
specimen_mapping = specimen_mapping
)
}
map_lab_result <- function(mapper, lab_result) {
tryCatch({
# Step 1: Find LOINC code
loinc_mapping <- find_loinc_code(mapper, lab_result)
if (is.null(loinc_mapping)) {
cat(paste("No LOINC mapping found for", lab_result$local_name, "\n"))
return(NULL)
}
# Step 2: Standardize units and values
standardized <- standardize_units(mapper,
lab_result$result_value,
lab_result$result_unit,
loinc_mapping$preferred_unit,
lab_result$local_name # Use descriptive name for analyte identification
)
# Step 3: Standardize reference range
standardized_ref_range <- standardize_reference_range(mapper,
lab_result$reference_range,
lab_result$result_unit,
standardized$unit,
loinc_mapping$loinc_name
)
return(list(
loinc_code = loinc_mapping$loinc_code,
loinc_name = loinc_mapping$loinc_name,
local_code = lab_result$local_code,
local_name = lab_result$local_name,
standardized_value = standardized$value,
standardized_unit = standardized$unit,
standardized_reference_range = standardized_ref_range,
specimen_type = standardize_specimen_type(mapper, lab_result$specimen_type),
result_status = lab_result$result_status,
conversion_applied = tolower(lab_result$result_unit) != tolower(standardized$unit),
mapping_confidence = loinc_mapping$confidence
))
}, error = function(e) {
cat(paste("Error mapping lab result", lab_result$local_name, ":", e$message, "\n"))
return(NULL)
})
}
```
### Step 2: Unit Standardization and Conversion
```python Python
def standardize_units(self, result_value: str, current_unit: str, preferred_unit: str, analyte_name: str) -> Tuple[float, str]:
"""Standardize lab result units
Args:
result_value: Numeric result value as string
current_unit: Original unit of measurement
preferred_unit: Target unit for standardization
analyte_name: Descriptive name/hint for the analyte (e.g., "glucose", "cholesterol")
"""
try:
# Parse numeric value
numeric_value = self.parse_numeric_value(result_value)
if numeric_value is None:
return float('nan'), current_unit
# Normalize unit strings
current_unit_norm = self.normalize_unit_string(current_unit)
preferred_unit_norm = self.normalize_unit_string(preferred_unit)
# If units are the same, no conversion needed
if current_unit_norm == preferred_unit_norm:
return numeric_value, preferred_unit
# Find conversion factor using analyte name
conversion_factor = self.find_unit_conversion(
analyte_name, current_unit_norm, preferred_unit_norm
)
if conversion_factor:
converted_value = conversion_factor(numeric_value)
return converted_value, preferred_unit
else:
# No conversion available, keep original
self.logger.warning(f"No conversion available from {current_unit} to {preferred_unit}")
return numeric_value, current_unit
except Exception as e:
self.logger.error(f"Error in unit standardization: {e}")
return float('nan'), current_unit
def parse_numeric_value(self, result_value: str) -> Optional[float]:
"""Extract numeric value from result string"""
# Handle common result formats
result_value = result_value.strip()
# Handle qualitative results
qualitative_mappings = {
"positive": 1.0,
"negative": 0.0,
"detected": 1.0,
"not detected": 0.0,
"reactive": 1.0,
"non-reactive": 0.0
}
if result_value.lower() in qualitative_mappings:
return qualitative_mappings[result_value.lower()]
# Handle numeric ranges (e.g., "5.0-10.0")
range_match = re.match(r'(\d+\.?\d*)\s*-\s*(\d+\.?\d*)', result_value)
if range_match:
# Take the midpoint of the range
lower = float(range_match.group(1))
upper = float(range_match.group(2))
return (lower + upper) / 2
# Handle comparison operators (e.g., ">100", "<0.5")
comparison_match = re.match(r'[<>=]+\s*(\d+\.?\d*)', result_value)
if comparison_match:
return float(comparison_match.group(1))
# Handle standard numeric values
numeric_match = re.search(r'(\d+\.?\d*)', result_value)
if numeric_match:
return float(numeric_match.group(1))
return None
def normalize_unit_string(self, unit: str) -> str:
"""Normalize unit string for comparison and handle micro symbols"""
if not unit:
return ""
# First handle micro symbols
unit_cleaned = unit.replace("μ", "u").replace("µ", "u")
normalized = unit_cleaned.strip().lower()
# Common unit normalizations
normalizations = {
"mg/dl": "mg/dl",
"mg/dL": "mg/dl",
"MG/DL": "mg/dl",
"mmol/l": "mmol/l",
"mmol/L": "mmol/l",
"MMOL/L": "mmol/l",
"g/dl": "g/dl",
"g/dL": "g/dl",
"G/DL": "g/dl",
"g/l": "g/l",
"g/L": "g/l",
"G/L": "g/l",
# Micro symbol variants (all converted to u)
"umol/l": "umol/l",
"umol/L": "umol/l",
"UMOL/L": "umol/l",
"μmol/l": "umol/l",
"μmol/L": "umol/l",
"µmol/l": "umol/l",
"µmol/L": "umol/l",
# Microgram variants
"mcg": "ug",
"μg": "ug",
"µg": "ug",
"ug": "ug"
}
# Apply direct mappings
for original, standard in normalizations.items():
if normalized == original.lower():
return standard
# Handle special characters
normalized = normalized.replace("μ", "u").replace("µ", "u")
return normalized
def find_unit_conversion(self, result_name: str, from_unit: str, to_unit: str) -> Optional[callable]:
"""Find appropriate unit conversion function"""
# Determine analyte type from result name
analyte_type = self.determine_analyte_type(result_name)
# Look up conversion
conversion_key = (analyte_type, from_unit, to_unit)
return self.unit_conversions.get(conversion_key)
def determine_analyte_type(self, result_name: str) -> str:
"""Determine analyte type for unit conversion"""
name_lower = result_name.lower()
# Glucose variants
if any(term in name_lower for term in ["glucose", "blood sugar", "bg"]):
return "glucose"
# Cholesterol variants
if any(term in name_lower for term in ["cholesterol", "chol", "ldl", "hdl"]):
return "cholesterol"
# Hemoglobin variants
if any(term in name_lower for term in ["hemoglobin", "hgb", "hb"]):
return "hemoglobin"
# Creatinine variants
if any(term in name_lower for term in ["creatinine", "creat", "cr"]):
return "creatinine"
# Default
return "unknown"
def standardize_reference_range(self, reference_range: str, original_unit: str,
new_unit: str, analyte_name: str) -> str:
"""Standardize reference range with unit conversion"""
if not reference_range or original_unit.lower() == new_unit.lower():
return reference_range
try:
# Parse reference range (e.g., "3.5-5.0", "< 100", "> 10")
range_patterns = [
r'(\d+\.?\d*)\s*-\s*(\d+\.?\d*)', # Range: "3.5-5.0"
r'<\s*(\d+\.?\d*)', # Less than: "< 100"
r'>\s*(\d+\.?\d*)', # Greater than: "> 10"
r'(\d+\.?\d*)' # Single value: "100"
]
for pattern in range_patterns:
match = re.search(pattern, reference_range)
if match:
# Get conversion function
analyte_type = self.determine_analyte_type(analyte_name)
original_unit_norm = self.normalize_unit_string(original_unit)
new_unit_norm = self.normalize_unit_string(new_unit)
conversion_func = self.unit_conversions.get(
(analyte_type, original_unit_norm, new_unit_norm)
)
if conversion_func:
if len(match.groups()) == 2: # Range
lower = conversion_func(float(match.group(1)))
upper = conversion_func(float(match.group(2)))
return f"{lower:.2f}-{upper:.2f}"
else: # Single value or comparison
converted = conversion_func(float(match.group(1)))
if '<' in reference_range:
return f"< {converted:.2f}"
elif '>' in reference_range:
return f"> {converted:.2f}"
else:
return f"{converted:.2f}"
break
return reference_range # Return original if no conversion possible
except Exception as e:
self.logger.error(f"Error standardizing reference range: {e}")
return reference_range
def standardize_specimen_type(self, specimen_type: str) -> str:
"""Standardize specimen type to common abbreviations"""
if not specimen_type:
return "Unknown"
specimen_lower = specimen_type.lower().strip()
return self.specimen_mapping.get(specimen_lower, specimen_type)
```
```javascript JavaScript
standardizeUnits(resultValue, currentUnit, preferredUnit, analyteName) {
try {
// Parse numeric value
const numericValue = this.parseNumericValue(resultValue);
if (numericValue === null) {
return [NaN, currentUnit];
}
// Normalize unit strings
const currentUnitNorm = this.normalizeUnitString(currentUnit);
const preferredUnitNorm = this.normalizeUnitString(preferredUnit);
// If units are the same, no conversion needed
if (currentUnitNorm === preferredUnitNorm) {
return [numericValue, preferredUnit];
}
// Find conversion factor
const conversionKey = JSON.stringify([
this.determineAnalyteType(analyteName),
currentUnitNorm,
preferredUnitNorm
]);
const conversionFunc = this.unitConversions.get(conversionKey);
if (conversionFunc) {
const convertedValue = conversionFunc(numericValue);
return [convertedValue, preferredUnit];
} else {
// No conversion available, keep original
console.warn(`No conversion available from ${currentUnit} to ${preferredUnit}`);
return [numericValue, currentUnit];
}
} catch (error) {
console.error('Error in unit standardization:', error);
return [NaN, currentUnit];
}
}
parseNumericValue(resultValue) {
const trimmed = resultValue.trim();
// Handle qualitative results
const qualitativeMappings = {
'positive': 1.0,
'negative': 0.0,
'detected': 1.0,
'not detected': 0.0,
'reactive': 1.0,
'non-reactive': 0.0
};
if (qualitativeMappings[trimmed.toLowerCase()]) {
return qualitativeMappings[trimmed.toLowerCase()];
}
// Handle numeric ranges (e.g., "5.0-10.0")
const rangeMatch = trimmed.match(/(\d+\.?\d*)\s*-\s*(\d+\.?\d*)/);
if (rangeMatch) {
const lower = parseFloat(rangeMatch[1]);
const upper = parseFloat(rangeMatch[2]);
return (lower + upper) / 2;
}
// Handle comparison operators (e.g., ">100", "<0.5")
const comparisonMatch = trimmed.match(/[<>=]+\s*(\d+\.?\d*)/);
if (comparisonMatch) {
return parseFloat(comparisonMatch[1]);
}
// Handle standard numeric values
const numericMatch = trimmed.match(/(\d+\.?\d*)/);
if (numericMatch) {
return parseFloat(numericMatch[1]);
}
return null;
}
normalizeUnitString(unit) {
if (!unit) return '';
const normalizations = {
'mg/dl': 'mg/dl',
'mg/dL': 'mg/dl',
'MG/DL': 'mg/dl',
'mmol/l': 'mmol/l',
'mmol/L': 'mmol/l',
'MMOL/L': 'mmol/l',
'g/dl': 'g/dl',
'g/dL': 'g/dl',
'G/DL': 'g/dl',
'μmol/l': 'umol/l',
'μmol/L': 'umol/l',
'umol/l': 'umol/l',
'umol/L': 'umol/l',
'UMOL/L': 'umol/l'
};
let normalized = unit.trim().toLowerCase();
// Apply direct mappings
for (const [original, standard] of Object.entries(normalizations)) {
if (normalized === original.toLowerCase()) {
return standard;
}
}
// Handle special characters
normalized = normalized.replace(/[μµ]/g, 'u');
return normalized;
}
determineAnalyteType(resultName) {
const nameLower = resultName.toLowerCase();
// Glucose variants
if (['glucose', 'blood sugar', 'bg'].some(term => nameLower.includes(term))) {
return 'glucose';
}
// Cholesterol variants
if (['cholesterol', 'chol', 'ldl', 'hdl'].some(term => nameLower.includes(term))) {
return 'cholesterol';
}
// Hemoglobin variants
if (['hemoglobin', 'hgb', 'hb'].some(term => nameLower.includes(term))) {
return 'hemoglobin';
}
// Creatinine variants
if (['creatinine', 'creat', 'cr'].some(term => nameLower.includes(term))) {
return 'creatinine';
}
return 'unknown';
}
```
```r R
standardize_units <- function(mapper, result_value, current_unit, preferred_unit, analyte_name) {
tryCatch({
# Parse numeric value
numeric_value <- parse_numeric_value(result_value)
if (is.na(numeric_value)) {
return(list(value = NA, unit = current_unit))
}
# Normalize unit strings
current_unit_norm <- normalize_unit_string(current_unit)
preferred_unit_norm <- normalize_unit_string(preferred_unit)
# If units are the same, no conversion needed
if (current_unit_norm == preferred_unit_norm) {
return(list(value = numeric_value, unit = preferred_unit))
}
# Find conversion using analyte name
conversion <- find_unit_conversion(mapper, analyte_name, current_unit_norm, preferred_unit_norm)
if (!is.null(conversion)) {
converted_value <- numeric_value * conversion$factor
return(list(value = converted_value, unit = preferred_unit))
} else {
# No conversion available, keep original
cat(paste("No conversion available from", current_unit, "to", preferred_unit, "\n"))
return(list(value = numeric_value, unit = current_unit))
}
}, error = function(e) {
cat(paste("Error in unit standardization:", e$message, "\n"))
return(list(value = NA, unit = current_unit))
})
}
find_unit_conversion <- function(mapper, result_name, from_unit, to_unit) {
# Normalize analyte name from result name
result_name_lower <- tolower(result_name)
# Determine analyte type based on result name
analyte_type <- "unknown"
# Glucose variants
if (grepl("glucose|blood sugar|bg", result_name_lower)) {
analyte_type <- "glucose"
} else if (grepl("cholesterol|chol|ldl|hdl", result_name_lower)) {
analyte_type <- "cholesterol"
} else if (grepl("hemoglobin|hgb|hb", result_name_lower)) {
analyte_type <- "hemoglobin"
} else if (grepl("creatinine|creat", result_name_lower)) {
analyte_type <- "creatinine"
} else if (grepl("urea|bun", result_name_lower)) {
analyte_type <- "urea"
} else if (grepl("bilirubin|bili", result_name_lower)) {
analyte_type <- "bilirubin"
}
# Normalize unit strings
from_unit_norm <- tolower(trimws(from_unit))
to_unit_norm <- tolower(trimws(to_unit))
# Create conversion key
conversion_key <- paste(analyte_type, from_unit_norm, to_unit_norm, sep = "_")
# Initialize unit conversions if not present
if (is.null(mapper$unit_conversions)) {
mapper$unit_conversions <- list(
# Glucose conversions
"glucose_mg/dl_mmol/l" = list(factor = 0.0555),
"glucose_mmol/l_mg/dl" = list(factor = 18.018),
# Cholesterol conversions
"cholesterol_mg/dl_mmol/l" = list(factor = 0.02586),
"cholesterol_mmol/l_mg/dl" = list(factor = 38.67),
# Hemoglobin conversions
"hemoglobin_g/dl_g/l" = list(factor = 10.0),
"hemoglobin_g/l_g/dl" = list(factor = 0.1),
# Creatinine conversions (using normalized unit names)
"creatinine_mg/dl_umol/l" = list(factor = 88.4),
"creatinine_umol/l_mg/dl" = list(factor = 0.0113)
)
}
# Look up conversion
conversion <- mapper$unit_conversions[[conversion_key]]
return(conversion)
}
standardize_reference_range <- function(mapper, reference_range, original_unit, new_unit, analyte_name) {
# Return original if no range or units are the same
if (is.null(reference_range) || trimws(reference_range) == "" ||
tolower(original_unit) == tolower(new_unit)) {
return(reference_range)
}
tryCatch({
# Parse reference range (e.g., "70-100", "<150", ">200")
range_parts <- strsplit(reference_range, "-")[[1]]
if (length(range_parts) == 2) {
# Range format: "70-100"
low_val <- parse_numeric_value(trimws(range_parts[1]))
high_val <- parse_numeric_value(trimws(range_parts[2]))
if (!is.na(low_val) && !is.na(high_val)) {
# Get conversion factor
conversion <- find_unit_conversion(mapper, analyte_name, original_unit, new_unit)
if (!is.null(conversion)) {
converted_low <- low_val * conversion$factor
converted_high <- high_val * conversion$factor
# Format to appropriate precision
precision <- if (converted_low < 10) 1 else 0
return(paste(round(converted_low, precision), round(converted_high, precision), sep = "-"))
}
}
} else {
# Single value with operator (e.g., "<150", ">200")
if (grepl("^[<>]", reference_range)) {
operator <- substr(reference_range, 1, 1)
value_str <- trimws(substr(reference_range, 2, nchar(reference_range)))
numeric_val <- parse_numeric_value(value_str)
if (!is.na(numeric_val)) {
conversion <- find_unit_conversion(mapper, analyte_name, original_unit, new_unit)
if (!is.null(conversion)) {
converted_val <- numeric_val * conversion$factor
precision <- if (converted_val < 10) 1 else 0
return(paste0(operator, round(converted_val, precision)))
}
}
}
}
# If conversion failed, return original
return(reference_range)
}, error = function(e) {
cat(paste("Error converting reference range:", e$message, "\n"))
return(reference_range)
})
}
parse_numeric_value <- function(result_value) {
result_value <- trimws(result_value)
# Handle qualitative results
qualitative_mappings <- list(
"positive" = 1.0,
"negative" = 0.0,
"detected" = 1.0,
"not detected" = 0.0,
"reactive" = 1.0,
"non-reactive" = 0.0
)
if (tolower(result_value) %in% names(qualitative_mappings)) {
return(qualitative_mappings[[tolower(result_value)]])
}
# Handle numeric ranges
range_match <- regexpr("(\\d+\\.?\\d*)\\s*-\\s*(\\d+\\.?\\d*)", result_value)
if (range_match > 0) {
matches <- regmatches(result_value, range_match)
numbers <- as.numeric(unlist(strsplit(matches, "-")))
return(mean(numbers))
}
# Handle comparison operators
comp_match <- regexpr("[<>=]+\\s*(\\d+\\.?\\d*)", result_value)
if (comp_match > 0) {
number_match <- regexpr("\\d+\\.?\\d*", result_value)
if (number_match > 0) {
return(as.numeric(regmatches(result_value, number_match)))
}
}
# Handle standard numeric values
num_match <- regexpr("\\d+\\.?\\d*", result_value)
if (num_match > 0) {
return(as.numeric(regmatches(result_value, num_match)))
}
return(NA)
}
normalize_unit_string <- function(unit) {
if (is.null(unit) || is.na(unit) || unit == "") {
return("")
}
# First handle micro symbols
unit_cleaned <- gsub("μ", "u", unit) # Replace μ with u
unit_cleaned <- gsub("µ", "u", unit_cleaned) # Replace µ with u
unit_trimmed <- trimws(tolower(unit_cleaned))
normalizations <- list(
"mg/dl" = "mg/dl",
"mg/dL" = "mg/dl",
"MG/DL" = "mg/dl",
"mmol/l" = "mmol/l",
"mmol/L" = "mmol/l",
"MMOL/L" = "mmol/l",
"g/dl" = "g/dl",
"g/dL" = "g/dl",
"G/DL" = "g/dl",
"g/l" = "g/l",
"g/L" = "g/l",
"G/L" = "g/l",
# Micro symbol variants (all converted to u)
"umol/l" = "umol/l",
"umol/L" = "umol/l",
"UMOL/L" = "umol/l",
"μmol/l" = "umol/l",
"μmol/L" = "umol/l",
"µmol/l" = "umol/l",
"µmol/L" = "umol/l",
# Microgram variants
"mcg" = "ug",
"μg" = "ug",
"µg" = "ug",
"ug" = "ug"
)
# Apply direct mappings
for (original in names(normalizations)) {
if (unit_trimmed == tolower(original)) {
return(normalizations[[original]])
}
}
return(unit_trimmed)
}
```
### Step 3: Batch Lab Processing and FHIR Generation
```python Python
import math
from datetime import datetime
def process_lab_batch(self, lab_results: List[LabResult], patient_id: str) -> Dict[str, Any]:
"""Process multiple lab results for a patient"""
processed_results = []
mapping_summary = {
"total_results": len(lab_results),
"successful_mappings": 0,
"failed_mappings": 0,
"unit_conversions": 0,
"confidence_scores": []
}
for lab_result in lab_results:
try:
standardized = self.map_lab_result(lab_result)
if standardized:
processed_results.append(standardized)
mapping_summary["successful_mappings"] += 1
mapping_summary["confidence_scores"].append(standardized.mapping_confidence)
if standardized.conversion_applied:
mapping_summary["unit_conversions"] += 1
else:
mapping_summary["failed_mappings"] += 1
except Exception as e:
self.logger.error(f"Error processing lab result {lab_result.local_name}: {e}")
mapping_summary["failed_mappings"] += 1
# Generate FHIR resources
fhir_resources = self.generate_fhir_resources(processed_results, patient_id)
# Calculate quality metrics
quality_metrics = self.calculate_quality_metrics(processed_results, mapping_summary)
return {
"patient_id": patient_id,
"processed_results": processed_results,
"fhir_resources": fhir_resources,
"mapping_summary": mapping_summary,
"quality_metrics": quality_metrics,
"processed_at": datetime.now().isoformat()
}
def generate_fhir_resources(self, standardized_results: List[StandardizedLabResult],
patient_id: str) -> List[Dict[str, Any]]:
"""Generate FHIR Specimen and Observation resources for lab results"""
fhir_resources = []
for result in standardized_results:
# Generate deterministic specimen ID
if hasattr(result, 'id') and result.id:
specimen_id = f"{result.id}-specimen"
else:
# Create deterministic ID from patient + local_code + timestamp
specimen_id = f"spec-{patient_id}-{result.local_code}-{int(datetime.now().timestamp())}"
# Create FHIR Specimen resource first
specimen = {
"resourceType": "Specimen",
"id": specimen_id,
"subject": {
"reference": f"Patient/{patient_id}"
},
"type": {
"coding": [{
"system": "http://snomed.info/sct",
"code": self.get_snomed_specimen_code(result.specimen_type),
"display": result.specimen_type
}]
},
"collection": {
"collectedDateTime": (
getattr(result, 'collection_datetime', None) or
getattr(result, 'result_datetime', None) or
datetime.now().isoformat()
)
}
}
# Add specimen to resources
fhir_resources.append(specimen)
# Determine value type
if isinstance(result.standardized_value, (int, float)) and not math.isnan(result.standardized_value):
value_element = {
"valueQuantity": {
"value": result.standardized_value,
"unit": result.standardized_unit,
"system": "http://unitsofmeasure.org",
"code": self.get_ucum_code(result.standardized_unit)
}
}
else:
# Handle qualitative results
value_element = {
"valueString": str(result.standardized_value)
}
# Create FHIR Observation resource
observation = {
"resourceType": "Observation",
"id": f"lab-{result.local_code}-{int(datetime.now().timestamp())}",
"status": self.map_result_status(result.result_status),
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "laboratory",
"display": "Laboratory"
}]
}],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": result.loinc_code,
"display": result.loinc_name
},
{
"system": "http://example.org/local-codes",
"code": result.local_code,
"display": result.local_name
}
]
},
"subject": {
"reference": f"Patient/{patient_id}"
},
"specimen": {
"reference": f"Specimen/{specimen_id}",
"display": result.specimen_type
},
**value_element
}
# Add reference range if available
if result.standardized_reference_range and result.standardized_reference_range != "":
ref_range = self.parse_reference_range(result.standardized_reference_range)
if ref_range:
observation["referenceRange"] = [{
"low": {"value": ref_range["low"], "unit": result.standardized_unit} if ref_range.get("low") else None,
"high": {"value": ref_range["high"], "unit": result.standardized_unit} if ref_range.get("high") else None,
"type": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/referencerange-meaning",
"code": "normal",
"display": "Normal Range"
}]
}
}]
fhir_resources.append(observation)
return fhir_resources
def get_snomed_specimen_code(self, specimen_type: str) -> str:
"""Get SNOMED code for specimen type"""
specimen_mappings = {
"serum": "119364003",
"plasma": "119361006",
"whole blood": "119297000",
"urine": "122575003",
"cerebrospinal fluid": "258450006",
"saliva": "119342007",
"tissue": "119376003"
}
return specimen_mappings.get(specimen_type.lower(), "123038009") # Default: specimen
def parse_reference_range(self, range_str: str) -> Optional[Dict[str, float]]:
"""Parse reference range string into low/high values"""
if not range_str:
return None
import re
# Handle various reference range formats
patterns = [
(r'^([\d.]+)\s*-\s*([\d.]+)$', 'range'), # "70-100"
(r'^<\s*([\d.]+)$', 'upper'), # "<200"
(r'^>\s*([\d.]+)$', 'lower'), # ">5"
(r'^([\d.]+)\s*-\s*$', 'lower_only'), # "70-"
(r'^\s*-\s*([\d.]+)$', 'upper_only') # "-100"
]
for pattern, range_type in patterns:
match = re.match(pattern, range_str.strip())
if match:
if range_type == 'range':
return {
'low': float(match.group(1)),
'high': float(match.group(2))
}
elif range_type == 'upper':
return {'low': None, 'high': float(match.group(1))}
elif range_type == 'lower':
return {'low': float(match.group(1)), 'high': None}
elif range_type == 'lower_only':
return {'low': float(match.group(1)), 'high': None}
elif range_type == 'upper_only':
return {'low': None, 'high': float(match.group(1))}
return None
def get_ucum_code(self, unit: str) -> str:
"""Get UCUM code for unit"""
ucum_mappings = {
"mg/dL": "mg/dL",
"mmol/L": "mmol/L",
"g/dL": "g/dL",
"g/L": "g/L",
"μmol/L": "umol/L",
"mEq/L": "meq/L",
"IU/L": "[IU]/L",
"U/L": "U/L"
}
return ucum_mappings.get(unit, unit)
def map_result_status(self, local_status: str) -> str:
"""Map local result status to FHIR status"""
status_mappings = {
"final": "final",
"complete": "final",
"resulted": "final",
"preliminary": "preliminary",
"pending": "preliminary",
"corrected": "amended",
"amended": "amended",
"cancelled": "cancelled"
}
return status_mappings.get(local_status.lower(), "final")
def calculate_quality_metrics(self, results: List[StandardizedLabResult],
mapping_summary: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate mapping quality metrics"""
if not results:
return {"overall_quality": 0.0}
# Calculate average confidence
confidence_scores = mapping_summary.get("confidence_scores", [])
avg_confidence = sum(confidence_scores) / len(confidence_scores) if confidence_scores else 0.0
# Calculate mapping success rate
total_results = mapping_summary["total_results"]
success_rate = mapping_summary["successful_mappings"] / total_results if total_results > 0 else 0.0
# Calculate unit standardization rate
unit_conversion_rate = mapping_summary["unit_conversions"] / len(results) if results else 0.0
# Overall quality score (weighted average)
overall_quality = (
avg_confidence * 0.4 +
success_rate * 0.4 +
unit_conversion_rate * 0.2
)
return {
"overall_quality": overall_quality,
"average_confidence": avg_confidence,
"mapping_success_rate": success_rate,
"unit_conversion_rate": unit_conversion_rate,
"total_processed": len(results),
"quality_grade": self.get_quality_grade(overall_quality)
}
def get_quality_grade(self, quality_score: float) -> str:
"""Convert quality score to letter grade"""
if quality_score >= 0.9:
return "A"
elif quality_score >= 0.8:
return "B"
elif quality_score >= 0.7:
return "C"
elif quality_score >= 0.6:
return "D"
else:
return "F"
```
```javascript JavaScript
async processLabBatch(labResults, patientId) {
const processedResults = [];
const mappingSummary = {
total_results: labResults.length,
successful_mappings: 0,
failed_mappings: 0,
unit_conversions: 0,
confidence_scores: []
};
for (const labResult of labResults) {
try {
const standardized = await this.mapLabResult(labResult);
if (standardized) {
processedResults.push(standardized);
mappingSummary.successful_mappings++;
mappingSummary.confidence_scores.push(standardized.mapping_confidence);
if (standardized.conversion_applied) {
mappingSummary.unit_conversions++;
}
} else {
mappingSummary.failed_mappings++;
}
} catch (error) {
console.error(`Error processing lab result ${labResult.local_name}:`, error);
mappingSummary.failed_mappings++;
}
}
// Generate FHIR resources
const fhirResources = this.generateFhirResources(processedResults, patientId);
// Calculate quality metrics
const qualityMetrics = this.calculateQualityMetrics(processedResults, mappingSummary);
return {
patient_id: patientId,
processed_results: processedResults,
fhir_resources: fhirResources,
mapping_summary: mappingSummary,
quality_metrics: qualityMetrics,
processed_at: new Date().toISOString()
};
}
generateFhirResources(standardizedResults, patientId) {
const fhirResources = [];
for (const result of standardizedResults) {
// Generate deterministic specimen ID
const specimenId = `spec-${result.local_code}-${Date.now()}`;
// Create FHIR Specimen resource first
const specimen = {
resourceType: 'Specimen',
id: specimenId,
subject: {
reference: `Patient/${patientId}`
},
type: {
coding: [{
system: 'http://snomed.info/sct',
code: this.getSnomedSpecimenCode(result.specimen_type),
display: result.specimen_type
}]
},
collection: {
collectedDateTime: result.collection_datetime || result.result_datetime || new Date().toISOString()
}
};
// Add specimen to resources
fhirResources.push(specimen);
// Determine value type
let valueElement;
if (typeof result.standardized_value === 'number' && !isNaN(result.standardized_value)) {
valueElement = {
valueQuantity: {
value: result.standardized_value,
unit: result.standardized_unit,
system: 'http://unitsofmeasure.org',
code: this.getUcumCode(result.standardized_unit)
}
};
} else {
valueElement = {
valueString: String(result.standardized_value)
};
}
// Create FHIR Observation resource
const observation = {
resourceType: 'Observation',
id: `lab-${result.local_code}-${Date.now()}`,
status: this.mapResultStatus(result.result_status),
category: [{
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/observation-category',
code: 'laboratory',
display: 'Laboratory'
}]
}],
code: {
coding: [
{
system: 'http://loinc.org',
code: result.loinc_code,
display: result.loinc_name
},
{
system: 'http://example.org/local-codes',
code: result.local_code,
display: result.local_name
}
]
},
subject: {
reference: `Patient/${patientId}`
},
specimen: {
reference: `Specimen/${specimenId}`,
display: result.specimen_type
},
...valueElement
};
// Add reference range if available
if (result.standardized_reference_range && result.standardized_reference_range !== '') {
const refRange = this.parseReferenceRange(result.standardized_reference_range);
if (refRange) {
observation.referenceRange = [{
low: refRange.low ? { value: refRange.low, unit: result.standardized_unit } : undefined,
high: refRange.high ? { value: refRange.high, unit: result.standardized_unit } : undefined,
type: {
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/referencerange-meaning',
code: 'normal',
display: 'Normal Range'
}]
}
}];
}
}
fhirResources.push(observation);
}
return fhirResources;
}
// Helper functions for FHIR resource creation
getSnomedSpecimenCode(specimenType) {
const specimenMappings = {
'serum': '119364003',
'plasma': '119361006',
'whole blood': '119297000',
'urine': '122575003',
'cerebrospinal fluid': '258450006',
'saliva': '119342007',
'tissue': '119376003'
};
return specimenMappings[specimenType.toLowerCase()] || '123038009'; // Default: specimen
}
getUcumCode(unit) {
const ucumMappings = {
'mg/dl': 'mg/dL',
'mmol/l': 'mmol/L',
'g/dl': 'g/dL',
'g/l': 'g/L',
'umol/l': 'umol/L',
'iu/l': '[IU]/L',
'ng/ml': 'ng/mL',
'pg/ml': 'pg/mL',
'mu/ml': 'mU/mL'
};
return ucumMappings[unit.toLowerCase()] || unit;
}
mapResultStatus(status) {
const statusMappings = {
'final': 'final',
'preliminary': 'preliminary',
'corrected': 'corrected',
'cancelled': 'cancelled',
'entered-in-error': 'entered-in-error'
};
return statusMappings[status.toLowerCase()] || 'final';
}
parseReferenceRange(rangeStr) {
if (!rangeStr) return null;
// Handle various reference range formats
const patterns = [
/^([\d.]+)\s*-\s*([\d.]+)$/, // "70-100"
/^<\s*([\d.]+)$/, // "<200"
/^>\s*([\d.]+)$/, // ">5"
/^([\d.]+)\s*-\s*$/, // "70-"
/^\s*-\s*([\d.]+)$/ // "-100"
];
for (const pattern of patterns) {
const match = rangeStr.match(pattern);
if (match) {
if (pattern.source.includes('-')) {
return {
low: match[1] ? parseFloat(match[1]) : null,
high: match[2] ? parseFloat(match[2]) : null
};
} else if (pattern.source.includes('<')) {
return { low: null, high: parseFloat(match[1]) };
} else if (pattern.source.includes('>')) {
return { low: parseFloat(match[1]), high: null };
}
}
}
return null;
}
calculateQualityMetrics(results, mappingSummary) {
if (results.length === 0) {
return { overall_quality: 0.0 };
}
// Calculate average confidence
const confidenceScores = mappingSummary.confidence_scores || [];
const avgConfidence = confidenceScores.length > 0 ?
confidenceScores.reduce((sum, score) => sum + score, 0) / confidenceScores.length : 0.0;
// Calculate mapping success rate
const totalResults = mappingSummary.total_results;
const successRate = totalResults > 0 ? mappingSummary.successful_mappings / totalResults : 0.0;
// Calculate unit standardization rate
const unitConversionRate = results.length > 0 ? mappingSummary.unit_conversions / results.length : 0.0;
// Overall quality score (weighted average)
const overallQuality = (
avgConfidence * 0.4 +
successRate * 0.4 +
unitConversionRate * 0.2
);
return {
overall_quality: overallQuality,
average_confidence: avgConfidence,
mapping_success_rate: successRate,
unit_conversion_rate: unitConversionRate,
total_processed: results.length,
quality_grade: this.getQualityGrade(overallQuality)
};
}
```
```r R
process_lab_batch <- function(mapper, lab_results, patient_id) {
processed_results <- list()
mapping_summary <- list(
total_results = length(lab_results),
successful_mappings = 0,
failed_mappings = 0,
unit_conversions = 0,
confidence_scores = c()
)
for (i in 1:length(lab_results)) {
tryCatch({
standardized <- map_lab_result(mapper, lab_results[[i]])
if (!is.null(standardized)) {
processed_results[[length(processed_results) + 1]] <- standardized
mapping_summary$successful_mappings <- mapping_summary$successful_mappings + 1
mapping_summary$confidence_scores <- c(mapping_summary$confidence_scores, standardized$mapping_confidence)
if (standardized$conversion_applied) {
mapping_summary$unit_conversions <- mapping_summary$unit_conversions + 1
}
} else {
mapping_summary$failed_mappings <- mapping_summary$failed_mappings + 1
}
}, error = function(e) {
cat(paste("Error processing lab result", lab_results[[i]]$local_name, ":", e$message, "\n"))
mapping_summary$failed_mappings <- mapping_summary$failed_mappings + 1
})
}
# Generate FHIR resources
fhir_resources <- generate_fhir_resources(processed_results, patient_id)
# Calculate quality metrics
quality_metrics <- calculate_quality_metrics(processed_results, mapping_summary)
return(list(
patient_id = patient_id,
processed_results = processed_results,
fhir_resources = fhir_resources,
mapping_summary = mapping_summary,
quality_metrics = quality_metrics,
processed_at = Sys.time()
))
}
```
## Example Implementation
### Sample Lab Results
```
Lab Results for Patient ID: PT12345
1. Glucose: 126 mg/dL (Ref: 70-100 mg/dL) - Serum
2. Total Cholesterol: 245 mg/dL (Ref: <200 mg/dL) - Serum
3. Hemoglobin: 12.5 g/dL (Ref: 12.0-15.5 g/dL) - Whole Blood
4. Creatinine: 1.2 mg/dL (Ref: 0.7-1.3 mg/dL) - Serum
5. Urinalysis Protein: Trace (Ref: Negative) - Urine
```
### Processing Report
```python Python
# Initialize the mapper
mapper = LabResultMapper("your_api_key")
# Sample lab results
lab_results = [
LabResult(
local_code="GLUC",
local_name="Glucose",
result_value="126",
result_unit="mg/dL",
reference_range="70-100",
specimen_type="serum",
result_status="final"
),
LabResult(
local_code="CHOL",
local_name="Total Cholesterol",
result_value="245",
result_unit="mg/dL",
reference_range="<200",
specimen_type="serum",
result_status="final"
),
LabResult(
local_code="HGB",
local_name="Hemoglobin",
result_value="12.5",
result_unit="g/dL",
reference_range="12.0-15.5",
specimen_type="whole blood",
result_status="final"
),
LabResult(
local_code="CREAT",
local_name="Creatinine",
result_value="1.2",
result_unit="mg/dL",
reference_range="0.7-1.3",
specimen_type="serum",
result_status="final"
)
]
# Process batch
report = mapper.process_lab_batch(lab_results, "PT12345")
# Print results
print("=== LABORATORY RESULT MAPPING REPORT ===")
print(f"Patient ID: {report['patient_id']}")
print(f"Quality Grade: {report['quality_metrics']['quality_grade']}")
print(f"Mapping Success Rate: {report['quality_metrics']['mapping_success_rate']:.1%}")
print("\n=== STANDARDIZED RESULTS ===")
for result in report['processed_results']:
print(f"✅ {result.local_name}")
print(f" LOINC: {result.loinc_code} - {result.loinc_name}")
print(f" Value: {result.standardized_value} {result.standardized_unit}")
print(f" Conversion Applied: {'Yes' if result.conversion_applied else 'No'}")
print(f" Confidence: {result.mapping_confidence:.1%}")
print()
print("=== FHIR RESOURCES GENERATED ===")
print(f"Generated {len(report['fhir_resources'])} FHIR Observation resources")
for resource in report['fhir_resources'][:2]: # Show first 2
print(f" Resource ID: {resource['id']}")
print(f" LOINC Code: {resource['code']['coding'][0]['code']}")
print(f" Value: {resource.get('valueQuantity', resource.get('valueString', 'N/A'))}")
print()
```
```javascript JavaScript
// Initialize the mapper
const mapper = new LabResultMapper('your_api_key');
// Sample lab results
const labResults = [
{
local_code: 'GLUC',
local_name: 'Glucose',
result_value: '126',
result_unit: 'mg/dL',
reference_range: '70-100',
specimen_type: 'serum',
result_status: 'final'
},
{
local_code: 'CHOL',
local_name: 'Total Cholesterol',
result_value: '245',
result_unit: 'mg/dL',
reference_range: '<200',
specimen_type: 'serum',
result_status: 'final'
},
{
local_code: 'HGB',
local_name: 'Hemoglobin',
result_value: '12.5',
result_unit: 'g/dL',
reference_range: '12.0-15.5',
specimen_type: 'whole blood',
result_status: 'final'
}
];
// Process batch
mapper.processLabBatch(labResults, 'PT12345')
.then(report => {
console.log('=== LABORATORY RESULT MAPPING REPORT ===');
console.log(`Patient ID: ${report.patient_id}`);
console.log(`Quality Grade: ${report.quality_metrics.quality_grade}`);
console.log(`Mapping Success Rate: ${(report.quality_metrics.mapping_success_rate * 100).toFixed(1)}%`);
console.log('\n=== STANDARDIZED RESULTS ===');
report.processed_results.forEach(result => {
console.log(`✅ ${result.local_name}`);
console.log(` LOINC: ${result.loinc_code} - ${result.loinc_name}`);
console.log(` Value: ${result.standardized_value} ${result.standardized_unit}`);
console.log(` Conversion Applied: ${result.conversion_applied ? 'Yes' : 'No'}`);
console.log(` Confidence: ${(result.mapping_confidence * 100).toFixed(1)}%`);
console.log();
});
console.log('=== FHIR RESOURCES GENERATED ===');
console.log(`Generated ${report.fhir_resources.length} FHIR Observation resources`);
})
.catch(error => {
console.error('Error processing lab batch:', error);
});
```
```r R
# Initialize the mapper
mapper <- init_lab_mapper("your_api_key")
# Sample lab results
lab_results <- list(
list(
local_code = "GLUC",
local_name = "Glucose",
result_value = "126",
result_unit = "mg/dL",
reference_range = "70-100",
specimen_type = "serum",
result_status = "final"
),
list(
local_code = "CHOL",
local_name = "Total Cholesterol",
result_value = "245",
result_unit = "mg/dL",
reference_range = "<200",
specimen_type = "serum",
result_status = "final"
)
)
# Process batch
report <- process_lab_batch(mapper, lab_results, "PT12345")
# Print results
cat("=== LABORATORY RESULT MAPPING REPORT ===\n")
cat(paste("Patient ID:", report$patient_id, "\n"))
cat(paste("Quality Grade:", report$quality_metrics$quality_grade, "\n"))
cat(paste("Mapping Success Rate:", round(report$quality_metrics$mapping_success_rate * 100, 1), "%\n"))
cat("\n=== STANDARDIZED RESULTS ===\n")
for (result in report$processed_results) {
cat(paste("✅", result$local_name, "\n"))
cat(paste(" LOINC:", result$loinc_code, "-", result$loinc_name, "\n"))
cat(paste(" Value:", result$standardized_value, result$standardized_unit, "\n"))
cat(paste(" Confidence:", round(result$mapping_confidence * 100, 1), "%\n\n"))
}
```
### Expected Output
```
=== LABORATORY RESULT MAPPING REPORT ===
Patient ID: PT12345
Quality Grade: A
Mapping Success Rate: 100.0%
=== STANDARDIZED RESULTS ===
✅ Glucose
LOINC: 2345-7 - Glucose [Mass/volume] in Serum or Plasma
Value: 126.0 mg/dL
Conversion Applied: No
Confidence: 95.0%
✅ Total Cholesterol
LOINC: 2093-3 - Cholesterol [Mass/volume] in Serum or Plasma
Value: 245.0 mg/dL
Conversion Applied: No
Confidence: 92.0%
✅ Hemoglobin
LOINC: 718-7 - Hemoglobin [Mass/volume] in Blood
Value: 12.5 g/dL
Conversion Applied: No
Confidence: 98.0%
✅ Creatinine
LOINC: 2160-0 - Creatinine [Mass/volume] in Serum or Plasma
Value: 1.2 mg/dL
Conversion Applied: No
Confidence: 96.0%
=== FHIR RESOURCES GENERATED ===
Generated 4 FHIR Observation resources
Resource ID: lab-GLUC-1703123456789
LOINC Code: 2345-7
Value: {value: 126.0, unit: 'mg/dL'}
Resource ID: lab-CHOL-1703123456790
LOINC Code: 2093-3
Value: {value: 245.0, unit: 'mg/dL'}
```
## Integration Patterns
### 1. EHR Integration with HL7 FHIR
```python Python
import requests
class FHIRLabIntegration:
def __init__(self, omophub_api_key: str, fhir_server_url: str):
self.mapper = LabResultMapper(omophub_api_key)
self.fhir_server = fhir_server_url
def sync_lab_results(self, patient_id: str, local_results: List[LabResult]) -> Dict[str, Any]:
"""Sync local lab results to FHIR server"""
# Process and standardize results
report = self.mapper.process_lab_batch(local_results, patient_id)
# Upload FHIR resources
upload_results = []
for fhir_resource in report['fhir_resources']:
try:
response = self.upload_fhir_observation(fhir_resource)
upload_results.append({
"resource_id": fhir_resource['id'],
"status": "success",
"fhir_id": response.get('id'),
"server_url": f"{self.fhir_server}/Observation/{response.get('id')}"
})
except Exception as e:
upload_results.append({
"resource_id": fhir_resource['id'],
"status": "failed",
"error": str(e)
})
return {
"processing_report": report,
"fhir_upload_results": upload_results,
"summary": {
"total_resources": len(report['fhir_resources']),
"successful_uploads": len([r for r in upload_results if r['status'] == 'success']),
"failed_uploads": len([r for r in upload_results if r['status'] == 'failed'])
}
}
def upload_fhir_observation(self, observation: Dict[str, Any]) -> Dict[str, Any]:
"""Upload FHIR Observation to server"""
headers = {
'Content-Type': 'application/fhir+json',
'Accept': 'application/fhir+json'
}
response = requests.post(
f"{self.fhir_server}/Observation",
json=observation,
headers=headers
)
if response.status_code in [200, 201]:
return response.json()
else:
raise Exception(f"FHIR upload failed: {response.status_code} - {response.text}")
```
```javascript JavaScript
class FHIRLabIntegration {
constructor(omophubApiKey, fhirServerUrl) {
this.mapper = new LabResultMapper(omophubApiKey);
this.fhirServer = fhirServerUrl;
}
async syncLabResults(patientId, localResults) {
// Process and standardize results
const report = await this.mapper.processLabBatch(localResults, patientId);
// Upload FHIR resources
const uploadResults = [];
for (const fhirResource of report.fhir_resources) {
try {
const response = await this.uploadFhirObservation(fhirResource);
uploadResults.push({
resource_id: fhirResource.id,
status: 'success',
fhir_id: response.id,
server_url: `${this.fhirServer}/Observation/${response.id}`
});
} catch (error) {
uploadResults.push({
resource_id: fhirResource.id,
status: 'failed',
error: error.message
});
}
}
return {
processing_report: report,
fhir_upload_results: uploadResults,
summary: {
total_resources: report.fhir_resources.length,
successful_uploads: uploadResults.filter(r => r.status === 'success').length,
failed_uploads: uploadResults.filter(r => r.status === 'failed').length
}
};
}
async uploadFhirObservation(observation) {
const response = await fetch(`${this.fhirServer}/Observation`, {
method: 'POST',
headers: {
'Content-Type': 'application/fhir+json',
'Accept': 'application/fhir+json'
},
body: JSON.stringify(observation)
});
if (response.ok) {
return await response.json();
} else {
const error = await response.text();
throw new Error(`FHIR upload failed: ${response.status} - ${error}`);
}
}
}
```
### 2. Population Health Analytics
```python Python
import math
import numpy as np
class PopulationHealthAnalytics:
def __init__(self, lab_mapper: LabResultMapper):
self.mapper = lab_mapper
def analyze_population_trends(self, lab_data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Analyze population health trends from standardized lab data"""
# Group results by LOINC code
grouped_results = {}
for patient_data in lab_data:
patient_id = patient_data['patient_id']
for result in patient_data['standardized_results']:
loinc_code = result['loinc_code']
if loinc_code not in grouped_results:
grouped_results[loinc_code] = {
'test_name': result['loinc_name'],
'values': [],
'patients': set()
}
if not math.isnan(result['standardized_value']):
grouped_results[loinc_code]['values'].append(result['standardized_value'])
grouped_results[loinc_code]['patients'].add(patient_id)
# Calculate statistics for each test
analytics_report = {
'test_analytics': {},
'population_summary': {},
'risk_indicators': []
}
for loinc_code, data in grouped_results.items():
if len(data['values']) < 10: # Skip tests with insufficient data
continue
values = np.array(data['values'])
test_stats = {
'loinc_code': loinc_code,
'test_name': data['test_name'],
'patient_count': len(data['patients']),
'result_count': len(values),
'mean': np.mean(values),
'median': np.median(values),
'std_dev': np.std(values),
'percentiles': {
'p25': np.percentile(values, 25),
'p75': np.percentile(values, 75),
'p95': np.percentile(values, 95)
},
'abnormal_percentage': self.calculate_abnormal_percentage(loinc_code, values)
}
analytics_report['test_analytics'][loinc_code] = test_stats
# Identify risk indicators
if test_stats['abnormal_percentage'] > 20: # >20% abnormal results
analytics_report['risk_indicators'].append({
'test_name': data['test_name'],
'loinc_code': loinc_code,
'abnormal_percentage': test_stats['abnormal_percentage'],
'risk_level': 'HIGH' if test_stats['abnormal_percentage'] > 40 else 'MEDIUM'
})
# Population summary
analytics_report['population_summary'] = {
'total_patients': len(set().union(*[data['patients'] for data in grouped_results.values()])),
'total_tests': len(grouped_results),
'total_results': sum(len(data['values']) for data in grouped_results.values()),
'high_risk_indicators': len([r for r in analytics_report['risk_indicators'] if r['risk_level'] == 'HIGH'])
}
return analytics_report
def calculate_abnormal_percentage(self, loinc_code: str, values: np.ndarray) -> float:
"""Calculate percentage of abnormal results based on reference ranges"""
# This would typically use established reference ranges for each LOINC code
# For demonstration, using simplified thresholds
reference_ranges = {
'2345-7': (70, 100), # Glucose mg/dL
'2093-3': (0, 200), # Total Cholesterol mg/dL
'718-7': (12.0, 15.5), # Hemoglobin g/dL
'2160-0': (0.7, 1.3) # Creatinine mg/dL
}
if loinc_code not in reference_ranges:
return 0.0
low, high = reference_ranges[loinc_code]
abnormal_count = np.sum((values < low) | (values > high))
return (abnormal_count / len(values)) * 100 if len(values) > 0 else 0.0
```
## Best Practices
### 1. Quality Assurance and Validation
```python Python
import math
class LabMappingValidator:
def __init__(self):
self.validation_rules = {
'glucose': {
'reasonable_range': (20, 800), # mg/dL
'critical_values': {'low': 40, 'high': 400}
},
'cholesterol': {
'reasonable_range': (50, 1000), # mg/dL
'critical_values': {'low': 100, 'high': 500}
},
'hemoglobin': {
'reasonable_range': (3.0, 25.0), # g/dL
'critical_values': {'low': 7.0, 'high': 20.0}
}
}
def validate_mapping_result(self, result: StandardizedLabResult) -> Dict[str, Any]:
"""Validate a standardized lab result"""
validation_result = {
'is_valid': True,
'warnings': [],
'errors': [],
'quality_score': 1.0
}
# Check mapping confidence
if result.mapping_confidence < 0.7:
validation_result['warnings'].append(
f"Low mapping confidence: {result.mapping_confidence:.1%}"
)
validation_result['quality_score'] -= 0.2
# Check value reasonableness
if not math.isnan(result.standardized_value):
analyte_type = self.determine_analyte_type(result.loinc_name)
if analyte_type in self.validation_rules:
rules = self.validation_rules[analyte_type]
low, high = rules['reasonable_range']
if not (low <= result.standardized_value <= high):
validation_result['errors'].append(
f"Value {result.standardized_value} outside reasonable range ({low}-{high})"
)
validation_result['is_valid'] = False
# Check critical values
critical = rules.get('critical_values', {})
if 'low' in critical and result.standardized_value < critical['low']:
validation_result['warnings'].append(
f"Critical low value: {result.standardized_value} < {critical['low']}"
)
elif 'high' in critical and result.standardized_value > critical['high']:
validation_result['warnings'].append(
f"Critical high value: {result.standardized_value} > {critical['high']}"
)
# Check unit consistency
expected_units = self.get_expected_units(result.loinc_code)
if expected_units and result.standardized_unit not in expected_units:
validation_result['warnings'].append(
f"Unexpected unit: {result.standardized_unit}, expected one of {expected_units}"
)
validation_result['quality_score'] -= 0.1
# Calculate final quality score
if validation_result['errors']:
validation_result['quality_score'] = 0.0
elif len(validation_result['warnings']) > 2:
validation_result['quality_score'] -= 0.3
return validation_result
def get_expected_units(self, loinc_code: str) -> Optional[List[str]]:
"""Get expected units for a LOINC code"""
unit_mappings = {
'2345-7': ['mg/dL', 'mmol/L'], # Glucose
'2093-3': ['mg/dL', 'mmol/L'], # Cholesterol
'718-7': ['g/dL', 'g/L'], # Hemoglobin
'2160-0': ['mg/dL', 'μmol/L'] # Creatinine
}
return unit_mappings.get(loinc_code)
```
## Next Steps
Screen patients for clinical trial eligibility using lab criteria
Analyze population health trends using standardized lab data
Check drug interactions based on lab values
Complete FHIR integration patterns for labs
# Population Health Analytics
Source: https://docs.omophub.com/guides/use-cases/population-health-analytics
Analyze population health trends and outcomes using OMOPHub API with concept hierarchies, disease classification, and quality measures
## Overview
Population health analytics involves aggregating and analyzing health data across large patient populations to identify trends, measure outcomes, and improve public health initiatives. This guide demonstrates how to use the OMOPHub API to standardize health data, leverage concept hierarchies, and generate meaningful population health insights.
**Use Case**: Analyze disease prevalence, quality measures, risk stratification, and health outcomes across patient populations using standardized medical vocabularies and hierarchical concept relationships.
## Business Problem
Healthcare organizations face significant challenges in population health management:
* **Data Fragmentation**: Health data exists in multiple formats and coding systems
* **Quality Reporting**: CMS, HEDIS, and other quality measures require standardized coding
* **Risk Stratification**: Identifying high-risk populations for intervention programs
* **Outcome Measurement**: Tracking health outcomes and intervention effectiveness
* **Public Health Surveillance**: Monitoring disease outbreaks and health trends
## Solution Architecture
```mermaid
graph TD
A[Patient Health Records] --> B[Data Standardization]
C[Claims Data] --> B
D[Registry Data] --> B
B --> E[OMOPHub API]
E --> F[Concept Mapping]
E --> G[Hierarchy Analysis]
E --> H[Disease Classification]
F --> I[Population Analytics Engine]
G --> I
H --> I
I --> J[Disease Prevalence]
I --> K[Quality Measures]
I --> L[Risk Stratification]
I --> M[Outcome Analytics]
J --> N[Population Dashboards]
K --> O[Quality Reports]
L --> P[Care Management]
M --> Q[Public Health Insights]
style F fill:#333333
style G fill:#333333
style H fill:#333333
style I fill:#333333
```
## Implementation Guide
### Step 1: Set Up Population Health Analytics Engine
```python Python
from omophub import OMOPHubClient
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import logging
from collections import defaultdict
class AnalysisScope(Enum):
PRACTICE = "practice"
HEALTH_SYSTEM = "health_system"
REGION = "region"
STATE = "state"
NATIONAL = "national"
class QualityMeasure(Enum):
DIABETES_HBA1C = "diabetes_hba1c_control"
HYPERTENSION_BP = "hypertension_bp_control"
BREAST_CANCER_SCREENING = "breast_cancer_screening"
COLORECTAL_SCREENING = "colorectal_cancer_screening"
CHOLESTEROL_MANAGEMENT = "cholesterol_management"
@dataclass
class PopulationCohort:
cohort_id: str
name: str
description: str
inclusion_criteria: List[str] # SNOMED concept IDs
exclusion_criteria: List[str]
age_range: Optional[Tuple[int, int]]
gender_filter: Optional[str]
@dataclass
class HealthOutcome:
outcome_id: str
name: str
measure_type: str # prevalence, incidence, mortality, etc.
concept_codes: List[str]
vocabularies: List[str]
numerator_criteria: Dict[str, Any]
denominator_criteria: Dict[str, Any]
class PopulationHealthAnalytics:
def __init__(self, api_key: str):
self.client = OMOPHubClient(api_key=api_key)
self.logger = logging.getLogger(__name__)
# Cache for concept hierarchies
self.hierarchy_cache = {}
# Pre-defined quality measures
self.quality_measures = {
QualityMeasure.DIABETES_HBA1C: {
'name': 'Diabetes HbA1c Control',
'denominator_concepts': ['44054006'], # Type 2 diabetes mellitus
'numerator_concepts': ['2345-7'], # HbA1c LOINC
'target_value': '<7.0',
'measure_period': 365 # days
},
QualityMeasure.HYPERTENSION_BP: {
'name': 'Hypertension Blood Pressure Control',
'denominator_concepts': ['38341003'], # Essential hypertension
'numerator_concepts': ['8480-6', '8462-4'], # Systolic/Diastolic BP
'target_value': '<140/90',
'measure_period': 365
}
}
def analyze_disease_prevalence(self, patient_data: List[Dict[str, Any]],
disease_categories: List[str],
analysis_scope: AnalysisScope) -> Dict[str, Any]:
"""Analyze disease prevalence using concept hierarchies"""
# Standardize condition codes to SNOMED
standardized_conditions = self.standardize_conditions(patient_data)
# Build disease category hierarchies
disease_hierarchies = {}
for category in disease_categories:
hierarchy = self.build_disease_hierarchy(category)
disease_hierarchies[category] = hierarchy
# Calculate prevalence for each category
prevalence_results = {}
total_patients = len(patient_data)
for category, hierarchy in disease_hierarchies.items():
# Count patients with conditions in this hierarchy
patients_with_condition = set()
for patient in standardized_conditions:
patient_conditions = set(patient.get('condition_codes', []))
# Check if any patient condition is in the hierarchy
if hierarchy['all_concepts'].intersection(patient_conditions):
patients_with_condition.add(patient['patient_id'])
prevalence_count = len(patients_with_condition)
prevalence_rate = (prevalence_count / total_patients) * 100 if total_patients > 0 else 0
# Age and gender stratification
age_stratification = self.calculate_age_stratified_prevalence(
patient_data, patients_with_condition
)
gender_stratification = self.calculate_gender_stratified_prevalence(
patient_data, patients_with_condition
)
prevalence_results[category] = {
'disease_name': hierarchy['root_concept_name'],
'total_patients': total_patients,
'affected_patients': prevalence_count,
'prevalence_rate': prevalence_rate,
'confidence_interval': self.calculate_confidence_interval(prevalence_count, total_patients),
'age_stratification': age_stratification,
'gender_stratification': gender_stratification,
'subcategory_breakdown': self.analyze_subcategories(
standardized_conditions, hierarchy, patients_with_condition
)
}
# Generate comparative analysis
comparative_analysis = self.generate_comparative_analysis(prevalence_results, analysis_scope)
return {
'analysis_scope': analysis_scope.value,
'total_population': total_patients,
'analysis_date': datetime.now().isoformat(),
'disease_prevalence': prevalence_results,
'comparative_analysis': comparative_analysis,
'methodology': {
'vocabulary_standard': 'SNOMED CT',
'hierarchy_based_classification': True,
'confidence_level': '95%'
}
}
def build_disease_hierarchy(self, root_concept_code: str) -> Dict[str, Any]:
"""Build complete disease hierarchy from root concept"""
# Check cache first
if root_concept_code in self.hierarchy_cache:
return self.hierarchy_cache[root_concept_code]
try:
# Get root concept information
root_concept = self.client.get_concept_by_code("SNOMED", root_concept_code)
if not root_concept:
raise ValueError(f"Root concept {root_concept_code} not found")
# Get all descendants
descendants = self.client.get_concept_descendants(
root_concept["concept_id"],
max_levels=10, # Deep hierarchy traversal
vocabulary_ids=["SNOMED"]
)
# Build concept set using concept_code for consistency with patient data
all_concepts = {root_concept["concept_code"]}
concept_details = {root_concept["concept_code"]: root_concept}
for descendant in descendants.get("descendants", []):
all_concepts.add(descendant["concept_code"])
concept_details[descendant["concept_code"]] = descendant
# Organize by hierarchy levels
hierarchy_levels = self.organize_by_hierarchy_levels(root_concept, descendants)
hierarchy = {
'root_concept_id': root_concept["concept_id"],
'root_concept_name': root_concept["concept_name"],
'all_concepts': all_concepts,
'concept_details': concept_details,
'hierarchy_levels': hierarchy_levels,
'total_concepts': len(all_concepts)
}
# Cache the result
self.hierarchy_cache[root_concept_code] = hierarchy
return hierarchy
except Exception as e:
self.logger.error(f"Error building disease hierarchy for {root_concept_code}: {e}")
return {
'root_concept_id': None,
'root_concept_name': f"Error: {root_concept_code}",
'all_concepts': set(),
'concept_details': {},
'hierarchy_levels': {},
'total_concepts': 0
}
def standardize_conditions(self, patient_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Standardize patient condition codes to SNOMED"""
standardized_data = []
for patient in patient_data:
standardized_patient = {
'patient_id': patient.get('patient_id'),
'age': patient.get('age'),
'gender': patient.get('gender'),
'condition_codes': []
}
# Process each condition
for condition in patient.get('conditions', []):
original_code = condition.get('code')
original_vocab = condition.get('vocabulary', 'ICD10CM')
# Convert to SNOMED if needed
snomed_code = self.map_to_snomed(original_code, original_vocab)
if snomed_code:
standardized_patient['condition_codes'].append(snomed_code)
standardized_data.append(standardized_patient)
return standardized_data
def map_to_snomed(self, code: str, vocabulary: str) -> Optional[str]:
"""Map a code from any vocabulary to SNOMED"""
try:
if vocabulary == "SNOMED":
return code
# Get source concept
source_concept = self.client.get_concept_by_code(vocabulary, code)
if not source_concept:
return None
# Get mappings to SNOMED
mappings = self.client.get_concept_mappings(
source_concept["concept_id"],
target_vocabularies=["SNOMED"]
)
# Find best SNOMED mapping
snomed_mappings = [
m for m in mappings.get("mappings", [])
if m["target_vocabulary_id"] == "SNOMED"
]
if snomed_mappings:
# Return the first mapping (could be enhanced with confidence scoring)
return snomed_mappings[0]["target_concept_code"]
return None
except Exception as e:
self.logger.error(f"Error mapping {code} from {vocabulary} to SNOMED: {e}")
return None
def calculate_age_stratified_prevalence(self, patient_data: List[Dict[str, Any]],
affected_patients: set) -> Dict[str, Dict[str, float]]:
"""Calculate prevalence by age groups"""
age_groups = {
'0-17': (0, 17),
'18-34': (18, 34),
'35-49': (35, 49),
'50-64': (50, 64),
'65-79': (65, 79),
'80+': (80, 150)
}
age_stratification = {}
for age_group, (min_age, max_age) in age_groups.items():
# Count patients in age group
patients_in_group = [
p for p in patient_data
if min_age <= p.get('age', 0) <= max_age
]
# Count affected patients in age group
affected_in_group = [
p for p in patients_in_group
if p.get('patient_id') in affected_patients
]
total_in_group = len(patients_in_group)
affected_count = len(affected_in_group)
prevalence_rate = (affected_count / total_in_group * 100) if total_in_group > 0 else 0
age_stratification[age_group] = {
'total_patients': total_in_group,
'affected_patients': affected_count,
'prevalence_rate': prevalence_rate,
'confidence_interval': self.calculate_confidence_interval(affected_count, total_in_group)
}
return age_stratification
def calculate_gender_stratified_prevalence(self, patient_data: List[Dict[str, Any]],
affected_patients: set) -> Dict[str, Dict[str, float]]:
"""Calculate prevalence by gender"""
gender_stratification = {}
for gender in ['M', 'F', 'Other']:
# Count patients of this gender
patients_of_gender = [
p for p in patient_data
if p.get('gender') == gender
]
# Count affected patients of this gender
affected_of_gender = [
p for p in patients_of_gender
if p.get('patient_id') in affected_patients
]
total_of_gender = len(patients_of_gender)
affected_count = len(affected_of_gender)
prevalence_rate = (affected_count / total_of_gender * 100) if total_of_gender > 0 else 0
gender_stratification[gender] = {
'total_patients': total_of_gender,
'affected_patients': affected_count,
'prevalence_rate': prevalence_rate,
'confidence_interval': self.calculate_confidence_interval(affected_count, total_of_gender)
}
return gender_stratification
def calculate_confidence_interval(self, numerator: int, denominator: int,
confidence_level: float = 0.95) -> Tuple[float, float]:
"""Calculate confidence interval for prevalence rate"""
if denominator == 0:
return (0.0, 0.0)
p = numerator / denominator
# Wilson score interval (better for small samples)
z = 1.96 if confidence_level == 0.95 else 2.576 # 99% CI
n = denominator
center = (p + z**2 / (2*n)) / (1 + z**2 / n)
margin = z * np.sqrt(p*(1-p)/n + z**2/(4*n**2)) / (1 + z**2/n)
lower = max(0, center - margin) * 100
upper = min(100, center + margin) * 100
return (round(lower, 2), round(upper, 2))
def organize_by_hierarchy_levels(self, root_concept, descendants):
"""Stub implementation for organizing concepts by hierarchy levels"""
return {}
def analyze_subcategories(self, standardized_conditions, hierarchy, patients_with_condition):
"""Stub implementation for analyzing subcategories"""
return {}
def generate_comparative_analysis(self, prevalence_results, analysis_scope):
"""Stub implementation for generating comparative analysis
Returns:
List[str]: List of analysis items/insights for iteration
"""
return []
def generate_quality_summary(self, measure_results):
"""Stub implementation for generating quality summary"""
return {}
```
```javascript JavaScript
import { OMOPHubClient } from '@omophub/sdk';
const AnalysisScope = {
PRACTICE: 'practice',
HEALTH_SYSTEM: 'health_system',
REGION: 'region',
STATE: 'state',
NATIONAL: 'national'
};
const QualityMeasure = {
DIABETES_HBA1C: 'diabetes_hba1c_control',
HYPERTENSION_BP: 'hypertension_bp_control',
BREAST_CANCER_SCREENING: 'breast_cancer_screening',
COLORECTAL_SCREENING: 'colorectal_cancer_screening',
CHOLESTEROL_MANAGEMENT: 'cholesterol_management'
};
class PopulationHealthAnalytics {
constructor(apiKey) {
this.client = new OMOPHubClient({ apiKey });
this.hierarchyCache = new Map();
// Pre-defined quality measures
this.qualityMeasures = {
[QualityMeasure.DIABETES_HBA1C]: {
name: 'Diabetes HbA1c Control',
denominator_concepts: ['44054006'], // Type 2 diabetes mellitus
numerator_concepts: ['2345-7'], // HbA1c LOINC
target_value: '<7.0',
measure_period: 365 // days
},
[QualityMeasure.HYPERTENSION_BP]: {
name: 'Hypertension Blood Pressure Control',
denominator_concepts: ['38341003'], // Essential hypertension
numerator_concepts: ['8480-6', '8462-4'], // Systolic/Diastolic BP
target_value: '<140/90',
measure_period: 365
}
};
}
async analyzeDiseasePrevalence(patientData, diseaseCategories, analysisScope) {
// Standardize condition codes to SNOMED
const standardizedConditions = await this.standardizeConditions(patientData);
// Build disease category hierarchies
const diseaseHierarchies = {};
for (const category of diseaseCategories) {
const hierarchy = await this.buildDiseaseHierarchy(category);
diseaseHierarchies[category] = hierarchy;
}
// Calculate prevalence for each category
const prevalenceResults = {};
const totalPatients = patientData.length;
for (const [category, hierarchy] of Object.entries(diseaseHierarchies)) {
// Count patients with conditions in this hierarchy
const patientsWithCondition = new Set();
for (const patient of standardizedConditions) {
const patientConditions = new Set(patient.condition_codes || []);
// Check if any patient condition is in the hierarchy
const hasCondition = [...hierarchy.all_concepts].some(concept =>
patientConditions.has(concept)
);
if (hasCondition) {
patientsWithCondition.add(patient.patient_id);
}
}
const prevalenceCount = patientsWithCondition.size;
const prevalenceRate = totalPatients > 0 ? (prevalenceCount / totalPatients) * 100 : 0;
// Age and gender stratification
const ageStratification = this.calculateAgeStratifiedPrevalence(
patientData, patientsWithCondition
);
const genderStratification = this.calculateGenderStratifiedPrevalence(
patientData, patientsWithCondition
);
prevalenceResults[category] = {
disease_name: hierarchy.root_concept_name,
total_patients: totalPatients,
affected_patients: prevalenceCount,
prevalence_rate: prevalenceRate,
confidence_interval: this.calculateConfidenceInterval(prevalenceCount, totalPatients),
age_stratification: ageStratification,
gender_stratification: genderStratification,
subcategory_breakdown: await this.analyzeSubcategories(
standardizedConditions, hierarchy, patientsWithCondition
)
};
}
// Generate comparative analysis
const comparativeAnalysis = this.generateComparativeAnalysis(prevalenceResults, analysisScope);
return {
analysis_scope: analysisScope,
total_population: totalPatients,
analysis_date: new Date().toISOString(),
disease_prevalence: prevalenceResults,
comparative_analysis: comparativeAnalysis,
methodology: {
vocabulary_standard: 'SNOMED CT',
hierarchy_based_classification: true,
confidence_level: '95%'
}
};
}
async buildDiseaseHierarchy(rootConceptCode) {
// Check cache first
if (this.hierarchyCache.has(rootConceptCode)) {
return this.hierarchyCache.get(rootConceptCode);
}
try {
// Get root concept information
const rootConceptResponse = await this.client.getConceptByCode('SNOMED', rootConceptCode);
const rootConcept = rootConceptResponse.data;
if (!rootConcept) {
throw new Error(`Root concept ${rootConceptCode} not found`);
}
// Get all descendants
const descendantsResponse = await this.client.getConceptDescendants(
rootConcept.concept_id,
{
max_levels: 10, // Deep hierarchy traversal
vocabulary_ids: ['SNOMED']
}
);
// Build concept set using concept_code for consistency with patient data
const allConcepts = new Set([rootConcept.concept_code]);
const conceptDetails = {
[rootConcept.concept_code]: rootConcept
};
for (const descendant of descendantsResponse.data.descendants || []) {
allConcepts.add(descendant.concept_code);
conceptDetails[descendant.concept_code] = descendant;
}
// Organize by hierarchy levels
const hierarchyLevels = this.organizeByHierarchyLevels(
rootConcept,
descendantsResponse.data.descendants || []
);
const hierarchy = {
root_concept_id: rootConcept.concept_id,
root_concept_name: rootConcept.concept_name,
all_concepts: allConcepts,
concept_details: conceptDetails,
hierarchy_levels: hierarchyLevels,
total_concepts: allConcepts.size
};
// Cache the result
this.hierarchyCache.set(rootConceptCode, hierarchy);
return hierarchy;
} catch (error) {
console.error(`Error building disease hierarchy for ${rootConceptCode}:`, error);
return {
root_concept_id: null,
root_concept_name: `Error: ${rootConceptCode}`,
all_concepts: new Set(),
concept_details: {},
hierarchy_levels: {},
total_concepts: 0
};
}
}
async standardizeConditions(patientData) {
const standardizedData = [];
for (const patient of patientData) {
const standardizedPatient = {
patient_id: patient.patient_id,
age: patient.age,
gender: patient.gender,
condition_codes: []
};
// Process each condition
for (const condition of patient.conditions || []) {
const originalCode = condition.code;
const originalVocab = condition.vocabulary || 'ICD10CM';
// Convert to SNOMED if needed
const snomedCode = await this.mapToSnomed(originalCode, originalVocab);
if (snomedCode) {
standardizedPatient.condition_codes.push(snomedCode);
}
}
standardizedData.push(standardizedPatient);
}
return standardizedData;
}
async mapToSnomed(code, vocabulary) {
try {
if (vocabulary === 'SNOMED') {
return code;
}
// Get source concept
const sourceConceptResponse = await this.client.getConceptByCode(vocabulary, code);
const sourceConcept = sourceConceptResponse.data;
if (!sourceConcept) {
return null;
}
// Get mappings to SNOMED
const mappingsResponse = await this.client.getConceptMappings(
sourceConcept.concept_id,
{ target_vocabularies: ['SNOMED'] }
);
// Find best SNOMED mapping
const snomedMappings = mappingsResponse.data.mappings.filter(
m => m.target_vocabulary_id === 'SNOMED'
);
if (snomedMappings.length > 0) {
// Return the first mapping (could be enhanced with confidence scoring)
return snomedMappings[0].target_concept_code;
}
return null;
} catch (error) {
console.error(`Error mapping ${code} from ${vocabulary} to SNOMED:`, error);
return null;
}
}
calculateAgeStratifiedPrevalence(patientData, affectedPatients) {
const ageGroups = {
'0-17': [0, 17],
'18-34': [18, 34],
'35-49': [35, 49],
'50-64': [50, 64],
'65-79': [65, 79],
'80+': [80, 150]
};
const ageStratification = {};
for (const [ageGroup, [minAge, maxAge]] of Object.entries(ageGroups)) {
// Count patients in age group
const patientsInGroup = patientData.filter(p =>
p.age >= minAge && p.age <= maxAge
);
// Count affected patients in age group
const affectedInGroup = patientsInGroup.filter(p =>
affectedPatients.has(p.patient_id)
);
const totalInGroup = patientsInGroup.length;
const affectedCount = affectedInGroup.length;
const prevalenceRate = totalInGroup > 0 ? (affectedCount / totalInGroup * 100) : 0;
ageStratification[ageGroup] = {
total_patients: totalInGroup,
affected_patients: affectedCount,
prevalence_rate: prevalenceRate,
confidence_interval: this.calculateConfidenceInterval(affectedCount, totalInGroup)
};
}
return ageStratification;
}
calculateGenderStratifiedPrevalence(patientData, affectedPatients) {
const genderStratification = {};
for (const gender of ['M', 'F', 'Other']) {
// Count patients of this gender
const patientsOfGender = patientData.filter(p => p.gender === gender);
// Count affected patients of this gender
const affectedOfGender = patientsOfGender.filter(p =>
affectedPatients.has(p.patient_id)
);
const totalOfGender = patientsOfGender.length;
const affectedCount = affectedOfGender.length;
const prevalenceRate = totalOfGender > 0 ? (affectedCount / totalOfGender * 100) : 0;
genderStratification[gender] = {
total_patients: totalOfGender,
affected_patients: affectedCount,
prevalence_rate: prevalenceRate,
confidence_interval: this.calculateConfidenceInterval(affectedCount, totalOfGender)
};
}
return genderStratification;
}
calculateConfidenceInterval(numerator, denominator, confidenceLevel = 0.95) {
if (denominator === 0) {
return [0.0, 0.0];
}
const p = numerator / denominator;
// Wilson score interval (better for small samples)
const z = confidenceLevel === 0.95 ? 1.96 : 2.576; // 99% CI
const n = denominator;
const center = (p + z**2 / (2*n)) / (1 + z**2 / n);
const margin = z * Math.sqrt(p*(1-p)/n + z**2/(4*n**2)) / (1 + z**2/n);
const lower = Math.max(0, center - margin) * 100;
const upper = Math.min(100, center + margin) * 100;
return [Math.round(lower * 100) / 100, Math.round(upper * 100) / 100];
}
organizeByHierarchyLevels(rootConcept, descendants) {
// Stub implementation for organizing concepts by hierarchy levels
// @returns {Object} Map of hierarchy levels to concept arrays
return {};
}
analyzeSubcategories(standardizedConditions, hierarchy, patientsWithCondition) {
// Stub implementation for analyzing subcategories
// @returns {Object} Analysis results by subcategory
return {};
}
generateComparativeAnalysis(prevalenceResults, analysisScope) {
// Stub implementation for generating comparative analysis
// @returns {Array} Array of comparative analysis insights
return [];
}
generateQualitySummary(measureResults) {
// Stub implementation for generating quality summary
// @returns {Object} Quality metrics and summary statistics
return {};
}
}
```
```r R
library(omophub)
# Define analysis scope and quality measures
ANALYSIS_SCOPE <- list(
PRACTICE = "practice",
HEALTH_SYSTEM = "health_system",
REGION = "region",
STATE = "state",
NATIONAL = "national"
)
QUALITY_MEASURE <- list(
DIABETES_HBA1C = "diabetes_hba1c_control",
HYPERTENSION_BP = "hypertension_bp_control",
BREAST_CANCER_SCREENING = "breast_cancer_screening",
COLORECTAL_SCREENING = "colorectal_cancer_screening",
CHOLESTEROL_MANAGEMENT = "cholesterol_management"
)
# Initialize population health analytics
init_population_analytics <- function(api_key) {
client <- omophub_client(api_key = api_key)
# Pre-defined quality measures
quality_measures <- list(
diabetes_hba1c_control = list(
name = "Diabetes HbA1c Control",
denominator_concepts = c("44054006"), # Type 2 diabetes mellitus
numerator_concepts = c("2345-7"), # HbA1c LOINC
target_value = "<7.0",
measure_period = 365
),
hypertension_bp_control = list(
name = "Hypertension Blood Pressure Control",
denominator_concepts = c("38341003"), # Essential hypertension
numerator_concepts = c("8480-6", "8462-4"), # Systolic/Diastolic BP
target_value = "<140/90",
measure_period = 365
)
)
list(
client = client,
hierarchy_cache = list(),
quality_measures = quality_measures
)
}
analyze_disease_prevalence <- function(analytics, patient_data, disease_categories, analysis_scope) {
# Standardize condition codes to SNOMED
standardized_conditions <- standardize_conditions(analytics, patient_data)
# Build disease category hierarchies
disease_hierarchies <- list()
for (category in disease_categories) {
hierarchy <- build_disease_hierarchy(analytics, category)
disease_hierarchies[[category]] <- hierarchy
}
# Calculate prevalence for each category
prevalence_results <- list()
total_patients <- length(patient_data)
for (category in names(disease_hierarchies)) {
hierarchy <- disease_hierarchies[[category]]
# Count patients with conditions in this hierarchy
patients_with_condition <- c()
for (i in 1:length(standardized_conditions)) {
patient <- standardized_conditions[[i]]
patient_conditions <- patient$condition_codes
# Check if any patient condition is in the hierarchy
if (any(patient_conditions %in% hierarchy$all_concepts)) {
patients_with_condition <- c(patients_with_condition, patient$patient_id)
}
}
prevalence_count <- length(patients_with_condition)
prevalence_rate <- if (total_patients > 0) (prevalence_count / total_patients) * 100 else 0
# Age and gender stratification
age_stratification <- calculate_age_stratified_prevalence(patient_data, patients_with_condition)
gender_stratification <- calculate_gender_stratified_prevalence(patient_data, patients_with_condition)
prevalence_results[[category]] <- list(
disease_name = hierarchy$root_concept_name,
total_patients = total_patients,
affected_patients = prevalence_count,
prevalence_rate = prevalence_rate,
confidence_interval = calculate_confidence_interval(prevalence_count, total_patients),
age_stratification = age_stratification,
gender_stratification = gender_stratification
)
}
# Generate comparative analysis
comparative_analysis <- generate_comparative_analysis(prevalence_results, analysis_scope)
return(list(
analysis_scope = analysis_scope,
total_population = total_patients,
analysis_date = Sys.time(),
disease_prevalence = prevalence_results,
comparative_analysis = comparative_analysis,
methodology = list(
vocabulary_standard = "SNOMED CT",
hierarchy_based_classification = TRUE,
confidence_level = "95%"
)
))
}
build_disease_hierarchy <- function(analytics, root_concept_code) {
# Check cache first
if (root_concept_code %in% names(analytics$hierarchy_cache)) {
return(analytics$hierarchy_cache[[root_concept_code]])
}
tryCatch({
# Get root concept information
root_concept <- get_concept_by_code(analytics$client, "SNOMED", root_concept_code)
if (is.null(root_concept)) {
stop(paste("Root concept", root_concept_code, "not found"))
}
# Get all descendants
descendants <- get_concept_descendants(analytics$client,
root_concept$concept_id,
max_levels = 10,
vocabulary_ids = "SNOMED"
)
# Build concept set using concept_code for consistency with patient data
all_concepts <- c(root_concept$concept_code)
concept_details <- list()
concept_details[[as.character(root_concept$concept_code)]] <- root_concept
if (!is.null(descendants$descendants) && nrow(descendants$descendants) > 0) {
for (i in 1:nrow(descendants$descendants)) {
descendant <- descendants$descendants[i, ]
# Only add if concept_code doesn't already exist
if (!descendant$concept_code %in% all_concepts) {
all_concepts <- c(all_concepts, descendant$concept_code)
concept_details[[as.character(descendant$concept_code)]] <- descendant
}
}
}
# Ensure uniqueness and get final count
all_concepts <- unique(all_concepts)
hierarchy <- list(
root_concept_id = root_concept$concept_id,
root_concept_name = root_concept$concept_name,
all_concepts = all_concepts,
concept_details = concept_details,
total_concepts = length(all_concepts)
)
# Cache the result
analytics$hierarchy_cache[[root_concept_code]] <- hierarchy
return(hierarchy)
}, error = function(e) {
cat(paste("Error building disease hierarchy for", root_concept_code, ":", e$message, "\n"))
return(list(
root_concept_id = NULL,
root_concept_name = paste("Error:", root_concept_code),
all_concepts = c(),
concept_details = list(),
total_concepts = 0
))
})
}
standardize_conditions <- function(analytics, patient_data) {
standardized_data <- list()
for (i in 1:length(patient_data)) {
patient <- patient_data[[i]]
standardized_patient <- list(
patient_id = patient$patient_id,
age = patient$age,
gender = patient$gender,
condition_codes = c()
)
# Process each condition
if (!is.null(patient$conditions)) {
for (condition in patient$conditions) {
original_code <- condition$code
original_vocab <- if (is.null(condition$vocabulary)) "ICD10CM" else condition$vocabulary
# Convert to SNOMED if needed
snomed_code <- map_to_snomed(analytics, original_code, original_vocab)
if (!is.null(snomed_code)) {
standardized_patient$condition_codes <- c(standardized_patient$condition_codes, snomed_code)
}
}
}
standardized_data[[i]] <- standardized_patient
}
return(standardized_data)
}
map_to_snomed <- function(analytics, code, vocabulary) {
tryCatch({
if (vocabulary == "SNOMED") {
return(code)
}
# Get source concept
source_concept <- get_concept_by_code(analytics$client, vocabulary, code)
if (is.null(source_concept)) {
return(NULL)
}
# Get mappings to SNOMED
mappings <- get_concept_mappings(analytics$client,
source_concept$concept_id,
target_vocabularies = "SNOMED"
)
# Find best SNOMED mapping
if (!is.null(mappings$mappings) && nrow(mappings$mappings) > 0) {
snomed_mappings <- mappings$mappings[mappings$mappings$target_vocabulary_id == "SNOMED", ]
if (nrow(snomed_mappings) > 0) {
return(snomed_mappings$target_concept_code[1])
}
}
return(NULL)
}, error = function(e) {
cat(paste("Error mapping", code, "from", vocabulary, "to SNOMED:", e$message, "\n"))
return(NULL)
})
}
calculate_age_stratified_prevalence <- function(patient_data, affected_patients) {
age_groups <- list(
"0-17" = c(0, 17),
"18-34" = c(18, 34),
"35-49" = c(35, 49),
"50-64" = c(50, 64),
"65-79" = c(65, 79),
"80+" = c(80, 150)
)
age_stratification <- list()
for (age_group in names(age_groups)) {
age_range <- age_groups[[age_group]]
min_age <- age_range[1]
max_age <- age_range[2]
# Count patients in age group
patients_in_group <- Filter(function(p) p$age >= min_age && p$age <= max_age, patient_data)
# Count affected patients in age group
affected_in_group <- Filter(function(p) p$patient_id %in% affected_patients, patients_in_group)
total_in_group <- length(patients_in_group)
affected_count <- length(affected_in_group)
prevalence_rate <- if (total_in_group > 0) (affected_count / total_in_group * 100) else 0
age_stratification[[age_group]] <- list(
total_patients = total_in_group,
affected_patients = affected_count,
prevalence_rate = prevalence_rate,
confidence_interval = calculate_confidence_interval(affected_count, total_in_group)
)
}
return(age_stratification)
}
calculate_confidence_interval <- function(numerator, denominator, confidence_level = 0.95) {
if (denominator == 0) {
return(c(0.0, 0.0))
}
p <- numerator / denominator
# Wilson score interval
z <- if (confidence_level == 0.95) 1.96 else 2.576
n <- denominator
center <- (p + z^2 / (2*n)) / (1 + z^2 / n)
margin <- z * sqrt(p*(1-p)/n + z^2/(4*n^2)) / (1 + z^2/n)
lower <- max(0, center - margin) * 100
upper <- min(100, center + margin) * 100
return(c(round(lower, 2), round(upper, 2)))
}
generate_comparative_analysis <- function(prevalence_results, analysis_scope) {
# Stub implementation for generating comparative analysis
return(list())
}
```
### Step 2: Quality Measure Calculation
```python Python
def calculate_quality_measures(self, patient_data: List[Dict[str, Any]],
measure_types: List[QualityMeasure],
measurement_period_days: int = 365) -> Dict[str, Any]:
"""Calculate healthcare quality measures"""
measure_results = {}
for measure_type in measure_types:
try:
measure_config = self.quality_measures[measure_type]
result = self.calculate_single_quality_measure(
patient_data, measure_config, measurement_period_days
)
measure_results[measure_type.value] = result
except Exception as e:
self.logger.error(f"Error calculating quality measure {measure_type.value}: {e}")
measure_results[measure_type.value] = {
'error': str(e),
'measure_rate': 0.0,
'numerator': 0,
'denominator': 0
}
# Calculate composite scores
composite_scores = self.calculate_composite_quality_scores(measure_results)
return {
'measurement_period_days': measurement_period_days,
'calculated_at': datetime.now().isoformat(),
'individual_measures': measure_results,
'composite_scores': composite_scores,
'quality_summary': self.generate_quality_summary(measure_results)
}
def calculate_single_quality_measure(self, patient_data: List[Dict[str, Any]],
measure_config: Dict[str, Any],
measurement_period_days: int) -> Dict[str, Any]:
"""Calculate a single quality measure"""
# Find denominator population (patients with condition)
denominator_patients = set()
denominator_concepts = set(measure_config['denominator_concepts'])
for patient in patient_data:
patient_conditions = set()
# Collect all condition codes for patient
for condition in patient.get('conditions', []):
snomed_code = self.map_to_snomed(
condition.get('code', ''),
condition.get('vocabulary', 'ICD10CM')
)
if snomed_code:
patient_conditions.add(snomed_code)
# Check if patient has denominator condition
if denominator_concepts.intersection(patient_conditions):
denominator_patients.add(patient['patient_id'])
# Find numerator population (patients meeting quality target)
numerator_patients = set()
cutoff_date = datetime.now() - timedelta(days=measurement_period_days)
for patient in patient_data:
if patient['patient_id'] not in denominator_patients:
continue
# Check if patient meets numerator criteria
if self.patient_meets_quality_target(patient, measure_config, cutoff_date):
numerator_patients.add(patient['patient_id'])
# Calculate measure rate
denominator = len(denominator_patients)
numerator = len(numerator_patients)
measure_rate = (numerator / denominator * 100) if denominator > 0 else 0
# Calculate confidence interval
confidence_interval = self.calculate_confidence_interval(numerator, denominator)
# Identify improvement opportunities
improvement_opportunities = self.identify_improvement_opportunities(
patient_data, denominator_patients, numerator_patients, measure_config
)
return {
'measure_name': measure_config['name'],
'measure_rate': round(measure_rate, 2),
'numerator': numerator,
'denominator': denominator,
'confidence_interval': confidence_interval,
'target_value': measure_config['target_value'],
'measurement_period': f"{measurement_period_days} days",
'improvement_opportunities': improvement_opportunities,
'performance_category': self.categorize_performance(measure_rate)
}
def patient_meets_quality_target(self, patient: Dict[str, Any],
measure_config: Dict[str, Any],
cutoff_date: datetime) -> bool:
"""Check if patient meets quality measure target"""
# Get recent lab results/vital signs within measurement period
recent_results = []
for lab in patient.get('lab_results', []):
result_date = datetime.fromisoformat(lab.get('date', '1900-01-01'))
if result_date >= cutoff_date:
# Check if this lab matches numerator concepts
if lab.get('loinc_code') in measure_config['numerator_concepts']:
recent_results.append(lab)
for vital in patient.get('vitals', []):
result_date = datetime.fromisoformat(vital.get('date', '1900-01-01'))
if result_date >= cutoff_date:
# Check if this vital matches numerator concepts
if vital.get('loinc_code') in measure_config['numerator_concepts']:
recent_results.append(vital)
if not recent_results:
return False
# Use most recent result
recent_results.sort(key=lambda x: x.get('date', ''), reverse=True)
most_recent = recent_results[0]
# Check if meets target
return self.check_target_achievement(most_recent, measure_config['target_value'])
def check_target_achievement(self, result: Dict[str, Any], target_value: str) -> bool:
"""Check if a result meets the target value"""
try:
result_value = float(result.get('value', 0))
# Parse target value (e.g., "<7.0", "<140/90")
if target_value.startswith('<'):
target = float(target_value[1:])
return result_value < target
elif target_value.startswith('>'):
target = float(target_value[1:])
return result_value > target
elif target_value.startswith('≤'):
target = float(target_value[1:])
return result_value <= target
elif target_value.startswith('≥'):
target = float(target_value[1:])
return result_value >= target
elif '/' in target_value:
# Blood pressure format (e.g., "<140/90")
if target_value.startswith('<'):
systolic_target, diastolic_target = target_value[1:].split('/')
# This is simplified - would need both systolic and diastolic values
return result_value < float(systolic_target)
else:
# Direct comparison
return result_value == float(target_value)
except (ValueError, IndexError) as e:
self.logger.warning(f"Unable to parse target value {target_value}: {e}")
return False
return False
def identify_improvement_opportunities(self, patient_data: List[Dict[str, Any]],
denominator_patients: set,
numerator_patients: set,
measure_config: Dict[str, Any]) -> List[str]:
"""Identify opportunities to improve quality measure performance"""
opportunities = []
# Patients who didn't meet target
gap_patients = denominator_patients - numerator_patients
gap_rate = len(gap_patients) / len(denominator_patients) * 100 if denominator_patients else 0
if gap_rate > 20:
opportunities.append(f"{gap_rate:.1f}% of eligible patients not meeting target - focus on care gaps")
# Check for missing recent measurements
missing_recent_tests = 0
cutoff_date = datetime.now() - timedelta(days=365)
for patient in patient_data:
if patient['patient_id'] in denominator_patients:
has_recent_test = False
# Check lab results
for lab in patient.get('lab_results', []):
result_date = datetime.fromisoformat(lab.get('date', '1900-01-01'))
if (result_date >= cutoff_date and
lab.get('loinc_code') in measure_config['numerator_concepts']):
has_recent_test = True
break
# Also check vitals if no recent lab test found
if not has_recent_test:
for vital in patient.get('vitals', []):
result_date = datetime.fromisoformat(vital.get('date', '1900-01-01'))
if (result_date >= cutoff_date and
vital.get('loinc_code') in measure_config.get('vital_concepts', [])):
has_recent_test = True
break
if not has_recent_test:
missing_recent_tests += 1
missing_rate = missing_recent_tests / len(denominator_patients) * 100 if denominator_patients else 0
if missing_rate > 15:
opportunities.append(f"{missing_rate:.1f}% of patients missing recent tests - improve monitoring")
# Age-specific opportunities
elderly_gap_patients = []
for patient in patient_data:
if (patient['patient_id'] in gap_patients and
patient.get('age', 0) >= 65):
elderly_gap_patients.append(patient)
if len(elderly_gap_patients) > len(gap_patients) * 0.4:
opportunities.append("Higher gap rate in elderly population - consider age-specific interventions")
return opportunities
def calculate_composite_quality_scores(self, measure_results: Dict[str, Any]) -> Dict[str, Any]:
"""Calculate composite quality scores"""
valid_measures = {k: v for k, v in measure_results.items() if 'error' not in v}
if not valid_measures:
return {'overall_score': 0.0, 'grade': 'F'}
# Simple average of all measures
total_score = sum(measure['measure_rate'] for measure in valid_measures.values())
overall_score = total_score / len(valid_measures)
# Weighted composite (diabetes and hypertension weighted higher)
weights = {
'diabetes_hba1c_control': 1.5,
'hypertension_bp_control': 1.5,
'breast_cancer_screening': 1.0,
'colorectal_cancer_screening': 1.0,
'cholesterol_management': 1.0
}
weighted_sum = 0
weight_total = 0
for measure_id, result in valid_measures.items():
weight = weights.get(measure_id, 1.0)
weighted_sum += result['measure_rate'] * weight
weight_total += weight
weighted_score = weighted_sum / weight_total if weight_total > 0 else 0
# Assign grade
grade = self.assign_quality_grade(overall_score)
return {
'overall_score': round(overall_score, 1),
'weighted_score': round(weighted_score, 1),
'grade': grade,
'total_measures': len(valid_measures),
'measures_above_benchmark': len([
m for m in valid_measures.values() if m['measure_rate'] >= 70
])
}
def assign_quality_grade(self, score: float) -> str:
"""Assign quality grade based on score"""
if score >= 90:
return 'A+'
elif score >= 85:
return 'A'
elif score >= 80:
return 'B+'
elif score >= 75:
return 'B'
elif score >= 70:
return 'C+'
elif score >= 65:
return 'C'
elif score >= 60:
return 'D'
else:
return 'F'
def categorize_performance(self, measure_rate: float) -> str:
"""Categorize performance level"""
if measure_rate >= 90:
return 'Excellent'
elif measure_rate >= 80:
return 'Good'
elif measure_rate >= 70:
return 'Satisfactory'
elif measure_rate >= 60:
return 'Needs Improvement'
else:
return 'Poor'
```
```javascript JavaScript
async calculateQualityMeasures(patientData, measureTypes, measurementPeriodDays = 365) {
const measureResults = {};
for (const measureType of measureTypes) {
try {
const measureConfig = this.qualityMeasures[measureType];
const result = await this.calculateSingleQualityMeasure(
patientData, measureConfig, measurementPeriodDays
);
measureResults[measureType] = result;
} catch (error) {
console.error(`Error calculating quality measure ${measureType}:`, error);
measureResults[measureType] = {
error: error.message,
measure_rate: 0.0,
numerator: 0,
denominator: 0
};
}
}
// Calculate composite scores
const compositeScores = this.calculateCompositeQualityScores(measureResults);
return {
measurement_period_days: measurementPeriodDays,
calculated_at: new Date().toISOString(),
individual_measures: measureResults,
composite_scores: compositeScores,
quality_summary: this.generateQualitySummary(measureResults)
};
}
async calculateSingleQualityMeasure(patientData, measureConfig, measurementPeriodDays) {
// Find denominator population (patients with condition)
const denominatorPatients = new Set();
const denominatorConcepts = new Set(measureConfig.denominator_concepts);
for (const patient of patientData) {
const patientConditions = new Set();
// Collect all condition codes for patient
for (const condition of patient.conditions || []) {
const snomedCode = await this.mapToSnomed(
condition.code || '',
condition.vocabulary || 'ICD10CM'
);
if (snomedCode) {
patientConditions.add(snomedCode);
}
}
// Check if patient has denominator condition
const hasCondition = [...denominatorConcepts].some(concept =>
patientConditions.has(concept)
);
if (hasCondition) {
denominatorPatients.add(patient.patient_id);
}
}
// Find numerator population (patients meeting quality target)
const numeratorPatients = new Set();
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - measurementPeriodDays);
for (const patient of patientData) {
if (!denominatorPatients.has(patient.patient_id)) {
continue;
}
// Check if patient meets numerator criteria
if (await this.patientMeetsQualityTarget(patient, measureConfig, cutoffDate)) {
numeratorPatients.add(patient.patient_id);
}
}
// Calculate measure rate
const denominator = denominatorPatients.size;
const numerator = numeratorPatients.size;
const measureRate = denominator > 0 ? (numerator / denominator * 100) : 0;
// Calculate confidence interval
const confidenceInterval = this.calculateConfidenceInterval(numerator, denominator);
// Identify improvement opportunities
const improvementOpportunities = await this.identifyImprovementOpportunities(
patientData, denominatorPatients, numeratorPatients, measureConfig
);
return {
measure_name: measureConfig.name,
measure_rate: Math.round(measureRate * 100) / 100,
numerator,
denominator,
confidence_interval: confidenceInterval,
target_value: measureConfig.target_value,
measurement_period: `${measurementPeriodDays} days`,
improvement_opportunities: improvementOpportunities,
performance_category: this.categorizePerformance(measureRate)
};
}
async patientMeetsQualityTarget(patient, measureConfig, cutoffDate) {
// Get recent lab results/vital signs within measurement period
const recentResults = [];
for (const lab of patient.lab_results || []) {
const resultDate = new Date(lab.date || '1900-01-01');
if (resultDate >= cutoffDate) {
// Check if this lab matches numerator concepts
if (measureConfig.numerator_concepts.includes(lab.loinc_code)) {
recentResults.push(lab);
}
}
}
for (const vital of patient.vitals || []) {
const resultDate = new Date(vital.date || '1900-01-01');
if (resultDate >= cutoffDate) {
// Check if this vital matches numerator concepts
if (measureConfig.numerator_concepts.includes(vital.loinc_code)) {
recentResults.push(vital);
}
}
}
if (recentResults.length === 0) {
return false;
}
// Use most recent result
recentResults.sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0));
const mostRecent = recentResults[0];
// Check if meets target
return this.checkTargetAchievement(mostRecent, measureConfig.target_value);
}
checkTargetAchievement(result, targetValue) {
try {
const resultValue = parseFloat(result.value || 0);
// Parse target value (e.g., "<7.0", "<140/90")
if (targetValue.startsWith('<')) {
const target = parseFloat(targetValue.substring(1));
return resultValue < target;
} else if (targetValue.startsWith('>')) {
const target = parseFloat(targetValue.substring(1));
return resultValue > target;
} else if (targetValue.startsWith('≤')) {
const target = parseFloat(targetValue.substring(1));
return resultValue <= target;
} else if (targetValue.startsWith('≥')) {
const target = parseFloat(targetValue.substring(1));
return resultValue >= target;
} else if (targetValue.includes('/')) {
// Blood pressure format (e.g., "<140/90")
if (targetValue.startsWith('<')) {
const [systolicTarget] = targetValue.substring(1).split('/');
// This is simplified - would need both systolic and diastolic values
return resultValue < parseFloat(systolicTarget);
}
} else {
// Direct comparison
return resultValue === parseFloat(targetValue);
}
} catch (error) {
console.warn(`Unable to parse target value ${targetValue}:`, error);
return false;
}
return false;
}
categorizePerformance(measureRate) {
if (measureRate >= 90) {
return 'Excellent';
} else if (measureRate >= 80) {
return 'Good';
} else if (measureRate >= 70) {
return 'Satisfactory';
} else if (measureRate >= 60) {
return 'Needs Improvement';
} else {
return 'Poor';
}
}
```
```r R
calculate_quality_measures <- function(analytics, patient_data, measure_types, measurement_period_days = 365) {
measure_results <- list()
for (measure_type in measure_types) {
tryCatch({
measure_config <- analytics$quality_measures[[measure_type]]
result <- calculate_single_quality_measure(analytics, patient_data, measure_config, measurement_period_days)
measure_results[[measure_type]] <- result
}, error = function(e) {
cat(paste("Error calculating quality measure", measure_type, ":", e$message, "\n"))
measure_results[[measure_type]] <- list(
error = e$message,
measure_rate = 0.0,
numerator = 0,
denominator = 0
)
})
}
# Calculate composite scores
composite_scores <- calculate_composite_quality_scores(measure_results)
return(list(
measurement_period_days = measurement_period_days,
calculated_at = Sys.time(),
individual_measures = measure_results,
composite_scores = composite_scores,
quality_summary = generate_quality_summary(measure_results)
))
}
calculate_single_quality_measure <- function(analytics, patient_data, measure_config, measurement_period_days) {
# Find denominator population (patients with condition)
denominator_patients <- c()
denominator_concepts <- measure_config$denominator_concepts
for (patient in patient_data) {
patient_conditions <- c()
# Collect all condition codes for patient
if (!is.null(patient$conditions)) {
for (condition in patient$conditions) {
snomed_code <- map_to_snomed(analytics,
if (is.null(condition$code)) "" else condition$code,
if (is.null(condition$vocabulary)) "ICD10CM" else condition$vocabulary
)
if (!is.null(snomed_code)) {
patient_conditions <- c(patient_conditions, snomed_code)
}
}
}
# Check if patient has denominator condition
if (any(denominator_concepts %in% patient_conditions)) {
denominator_patients <- c(denominator_patients, patient$patient_id)
}
}
# Find numerator population (patients meeting quality target)
numerator_patients <- c()
cutoff_date <- Sys.Date() - measurement_period_days
for (patient in patient_data) {
if (!(patient$patient_id %in% denominator_patients)) {
next
}
# Check if patient meets numerator criteria
if (patient_meets_quality_target(patient, measure_config, cutoff_date)) {
numerator_patients <- c(numerator_patients, patient$patient_id)
}
}
# Calculate measure rate
denominator <- length(denominator_patients)
numerator <- length(numerator_patients)
measure_rate <- if (denominator > 0) (numerator / denominator * 100) else 0
# Calculate confidence interval
confidence_interval <- calculate_confidence_interval(numerator, denominator)
return(list(
measure_name = measure_config$name,
measure_rate = round(measure_rate, 2),
numerator = numerator,
denominator = denominator,
confidence_interval = confidence_interval,
target_value = measure_config$target_value,
measurement_period = paste(measurement_period_days, "days"),
performance_category = categorize_performance(measure_rate)
))
}
patient_meets_quality_target <- function(patient, measure_config, cutoff_date) {
# Get recent lab results/vital signs within measurement period
recent_results <- list()
if (!is.null(patient$lab_results)) {
for (lab in patient$lab_results) {
result_date <- as.Date(if (is.null(lab$date)) "1900-01-01" else lab$date)
if (result_date >= cutoff_date) {
# Check if this lab matches numerator concepts
if (lab$loinc_code %in% measure_config$numerator_concepts) {
recent_results[[length(recent_results) + 1]] <- lab
}
}
}
}
if (length(recent_results) == 0) {
return(FALSE)
}
# Use most recent result
recent_results <- recent_results[order(sapply(recent_results, function(x) as.Date(x$date)), decreasing = TRUE)]
most_recent <- recent_results[[1]]
# Check if meets target
return(check_target_achievement(most_recent, measure_config$target_value))
}
check_target_achievement <- function(result, target_value) {
tryCatch({
result_value <- as.numeric(if (is.null(result$value)) 0 else result$value)
# Parse target value (e.g., "<7.0")
if (startsWith(target_value, "<")) {
target <- as.numeric(substring(target_value, 2))
return(result_value < target)
} else if (startsWith(target_value, ">")) {
target <- as.numeric(substring(target_value, 2))
return(result_value > target)
} else {
# Direct comparison
return(result_value == as.numeric(target_value))
}
}, error = function(e) {
cat(paste("Unable to parse target value", target_value, ":", e$message, "\n"))
return(FALSE)
})
}
categorize_performance <- function(measure_rate) {
if (measure_rate >= 90) {
return("Excellent")
} else if (measure_rate >= 80) {
return("Good")
} else if (measure_rate >= 70) {
return("Satisfactory")
} else if (measure_rate >= 60) {
return("Needs Improvement")
} else {
return("Poor")
}
}
```
### Step 3: Risk Stratification and Predictive Analytics
```python Python
def perform_risk_stratification(self, patient_data: List[Dict[str, Any]],
risk_models: List[str]) -> Dict[str, Any]:
"""Perform population risk stratification using multiple models"""
risk_results = {
'total_patients': len(patient_data),
'stratification_models': {},
'high_risk_patients': [],
'composite_risk_scores': {},
'intervention_recommendations': []
}
# Apply different risk models
for model_name in risk_models:
if model_name == 'cardiovascular_risk':
model_results = self.calculate_cardiovascular_risk(patient_data)
elif model_name == 'diabetes_complications':
model_results = self.calculate_diabetes_complication_risk(patient_data)
elif model_name == 'hospital_readmission':
model_results = self.calculate_readmission_risk(patient_data)
elif model_name == 'medication_adherence':
model_results = self.assess_medication_adherence_risk(patient_data)
else:
continue
risk_results['stratification_models'][model_name] = model_results
# Create composite risk scores
composite_scores = self.create_composite_risk_scores(
patient_data, risk_results['stratification_models']
)
risk_results['composite_risk_scores'] = composite_scores
# Identify high-risk patients across all models
high_risk_patients = self.identify_high_risk_patients(composite_scores)
risk_results['high_risk_patients'] = high_risk_patients
# Generate intervention recommendations
interventions = self.generate_intervention_recommendations(
high_risk_patients, risk_results['stratification_models']
)
risk_results['intervention_recommendations'] = interventions
return risk_results
def calculate_cardiovascular_risk(self, patient_data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Calculate cardiovascular disease risk using clinical factors"""
risk_categories = {'low': [], 'moderate': [], 'high': [], 'very_high': []}
risk_factors_analysis = {
'hypertension_prevalence': 0,
'diabetes_prevalence': 0,
'smoking_prevalence': 0,
'high_cholesterol_prevalence': 0,
'multiple_risk_factors': 0
}
for patient in patient_data:
risk_score = 0
risk_factors = []
# Age factor
age = patient.get('age', 0)
if age >= 65:
risk_score += 2
risk_factors.append('advanced_age')
elif age >= 45:
risk_score += 1
# Gender factor
if patient.get('gender') == 'M' and age >= 45:
risk_score += 1
risk_factors.append('male_gender')
elif patient.get('gender') == 'F' and age >= 55:
risk_score += 1
# Check for cardiovascular risk conditions
patient_conditions = [c.get('code') for c in patient.get('conditions', [])]
# Hypertension
hypertension_codes = ['38341003', 'I10'] # SNOMED and ICD10
if any(code in patient_conditions for code in hypertension_codes):
risk_score += 2
risk_factors.append('hypertension')
risk_factors_analysis['hypertension_prevalence'] += 1
# Diabetes
diabetes_codes = ['44054006', 'E11.9'] # SNOMED and ICD10
if any(code in patient_conditions for code in diabetes_codes):
risk_score += 2
risk_factors.append('diabetes')
risk_factors_analysis['diabetes_prevalence'] += 1
# Check lab values for additional risk factors
recent_labs = self.get_recent_lab_values(patient, days=365)
# High cholesterol (>200 mg/dL)
cholesterol_result = recent_labs.get('2093-3') # Total cholesterol LOINC
if cholesterol_result and float(cholesterol_result.get('value', 0)) > 200:
risk_score += 1
risk_factors.append('high_cholesterol')
risk_factors_analysis['high_cholesterol_prevalence'] += 1
# Smoking status (if available in patient data)
if patient.get('smoking_status') == 'current':
risk_score += 2
risk_factors.append('smoking')
risk_factors_analysis['smoking_prevalence'] += 1
# Count multiple risk factors
if len(risk_factors) >= 3:
risk_factors_analysis['multiple_risk_factors'] += 1
# Categorize risk level
if risk_score >= 7:
risk_categories['very_high'].append({
'patient_id': patient['patient_id'],
'risk_score': risk_score,
'risk_factors': risk_factors,
'age': age,
'gender': patient.get('gender', 'Unknown')
})
elif risk_score >= 5:
risk_categories['high'].append({
'patient_id': patient['patient_id'],
'risk_score': risk_score,
'risk_factors': risk_factors,
'age': age,
'gender': patient.get('gender', 'Unknown')
})
elif risk_score >= 3:
risk_categories['moderate'].append({
'patient_id': patient['patient_id'],
'risk_score': risk_score,
'risk_factors': risk_factors,
'age': age,
'gender': patient.get('gender', 'Unknown')
})
else:
risk_categories['low'].append({
'patient_id': patient['patient_id'],
'risk_score': risk_score,
'risk_factors': risk_factors,
'age': age,
'gender': patient.get('gender', 'Unknown')
})
# Calculate prevalence rates
total_patients = len(patient_data)
for factor in risk_factors_analysis:
risk_factors_analysis[factor] = (
risk_factors_analysis[factor] / total_patients * 100
if total_patients > 0 else 0
)
return {
'model_name': 'Cardiovascular Risk Assessment',
'risk_categories': risk_categories,
'population_summary': {
'total_patients': total_patients,
'low_risk': len(risk_categories['low']),
'moderate_risk': len(risk_categories['moderate']),
'high_risk': len(risk_categories['high']),
'very_high_risk': len(risk_categories['very_high'])
},
'risk_factors_analysis': risk_factors_analysis,
'high_risk_percentage': (
(len(risk_categories['high']) + len(risk_categories['very_high'])) /
total_patients * 100 if total_patients > 0 else 0
)
}
def get_recent_lab_values(self, patient: Dict[str, Any], days: int = 365) -> Dict[str, Dict[str, Any]]:
"""Get most recent lab values within specified time period"""
cutoff_date = datetime.now() - timedelta(days=days)
recent_labs = {}
for lab in patient.get('lab_results', []):
lab_date = datetime.fromisoformat(lab.get('date', '1900-01-01'))
loinc_code = lab.get('loinc_code')
if lab_date >= cutoff_date and loinc_code:
# Keep most recent result for each LOINC code
if (loinc_code not in recent_labs or
lab_date > datetime.fromisoformat(recent_labs[loinc_code].get('date', '1900-01-01'))):
recent_labs[loinc_code] = lab
return recent_labs
def create_composite_risk_scores(self, patient_data: List[Dict[str, Any]],
risk_models: Dict[str, Any]) -> Dict[str, Dict[str, Any]]:
"""Create composite risk scores combining multiple models"""
composite_scores = {}
for patient in patient_data:
patient_id = patient['patient_id']
patient_scores = {}
total_weighted_score = 0
total_weight = 0
# Collect scores from each model
for model_name, model_results in risk_models.items():
patient_risk_data = self.find_patient_in_risk_model(patient_id, model_results)
if patient_risk_data:
risk_level = patient_risk_data.get('risk_level', 'low')
# Convert risk level to numeric score
risk_score = {
'low': 1,
'moderate': 2,
'high': 3,
'very_high': 4
}.get(risk_level, 1)
# Weight different models
model_weight = {
'cardiovascular_risk': 1.5,
'diabetes_complications': 1.3,
'hospital_readmission': 1.2,
'medication_adherence': 1.0
}.get(model_name, 1.0)
patient_scores[model_name] = {
'risk_score': risk_score,
'risk_level': risk_level,
'weight': model_weight
}
total_weighted_score += risk_score * model_weight
total_weight += model_weight
# Calculate composite score
composite_score = total_weighted_score / total_weight if total_weight > 0 else 0
# Determine overall risk level
if composite_score >= 3.5:
overall_risk = 'very_high'
elif composite_score >= 2.5:
overall_risk = 'high'
elif composite_score >= 1.5:
overall_risk = 'moderate'
else:
overall_risk = 'low'
composite_scores[patient_id] = {
'composite_score': round(composite_score, 2),
'overall_risk_level': overall_risk,
'individual_model_scores': patient_scores,
'priority_rank': self.calculate_priority_rank(composite_score, patient)
}
return composite_scores
def identify_high_risk_patients(self, composite_scores: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Identify patients requiring immediate attention"""
high_risk_patients = []
for patient_id, scores in composite_scores.items():
if scores['overall_risk_level'] in ['high', 'very_high']:
high_risk_patients.append({
'patient_id': patient_id,
'composite_score': scores['composite_score'],
'risk_level': scores['overall_risk_level'],
'priority_rank': scores['priority_rank'],
'contributing_factors': list(scores['individual_model_scores'].keys())
})
# Sort by priority rank (highest first)
high_risk_patients.sort(key=lambda x: x['priority_rank'], reverse=True)
return high_risk_patients
def generate_intervention_recommendations(self, high_risk_patients: List[Dict[str, Any]],
risk_models: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate targeted intervention recommendations"""
recommendations = []
# Population-level recommendations
if len(high_risk_patients) > 0:
high_risk_rate = len(high_risk_patients) / len(risk_models.get('cardiovascular_risk', {}).get('population_summary', {}).get('total_patients', 1)) * 100
if high_risk_rate > 20:
recommendations.append({
'type': 'population_level',
'priority': 'high',
'intervention': 'Comprehensive Population Health Program',
'description': f'{high_risk_rate:.1f}% of population is high-risk - implement systematic screening and intervention programs',
'target_population': 'All patients',
'estimated_impact': 'Reduce high-risk population by 15-25%'
})
# Risk-specific interventions
cv_risk_data = risk_models.get('cardiovascular_risk', {})
if cv_risk_data:
cv_high_risk = len(cv_risk_data.get('risk_categories', {}).get('high', [])) + len(cv_risk_data.get('risk_categories', {}).get('very_high', []))
if cv_high_risk > 0:
recommendations.append({
'type': 'cardiovascular_focused',
'priority': 'high',
'intervention': 'Cardiovascular Risk Management Program',
'description': f'{cv_high_risk} patients at high cardiovascular risk need intensive management',
'target_population': 'High CV risk patients',
'components': [
'Statin therapy optimization',
'Blood pressure management',
'Diabetes control enhancement',
'Lifestyle modification counseling'
],
'estimated_impact': 'Reduce cardiovascular events by 20-30%'
})
# Individual patient recommendations (top 10 highest risk)
for patient in high_risk_patients[:10]:
patient_recommendations = []
for model_name in patient['contributing_factors']:
if model_name == 'cardiovascular_risk':
patient_recommendations.extend([
'Cardiology referral within 30 days',
'Lipid panel and blood pressure monitoring',
'Medication adherence counseling'
])
elif model_name == 'diabetes_complications':
patient_recommendations.extend([
'Endocrinology consultation',
'Diabetic eye exam',
'Foot care assessment'
])
elif model_name == 'hospital_readmission':
patient_recommendations.extend([
'Care transition planning',
'Medication reconciliation',
'Follow-up appointment within 7 days'
])
recommendations.append({
'type': 'individual_patient',
'patient_id': patient['patient_id'],
'priority': patient['risk_level'],
'intervention': 'Personalized Care Plan',
'recommendations': list(set(patient_recommendations)), # Remove duplicates
'urgency': 'Within 2 weeks' if patient['risk_level'] == 'very_high' else 'Within 4 weeks'
})
return recommendations
```
```javascript JavaScript
async performRiskStratification(patientData, riskModels) {
const riskResults = {
total_patients: patientData.length,
stratification_models: {},
high_risk_patients: [],
composite_risk_scores: {},
intervention_recommendations: []
};
// Apply different risk models
for (const modelName of riskModels) {
let modelResults;
if (modelName === 'cardiovascular_risk') {
modelResults = await this.calculateCardiovascularRisk(patientData);
} else if (modelName === 'diabetes_complications') {
modelResults = await this.calculateDiabetesComplicationRisk(patientData);
} else if (modelName === 'hospital_readmission') {
modelResults = await this.calculateReadmissionRisk(patientData);
} else if (modelName === 'medication_adherence') {
modelResults = await this.assessMedicationAdherenceRisk(patientData);
} else {
continue;
}
riskResults.stratification_models[modelName] = modelResults;
}
// Create composite risk scores
const compositeScores = this.createCompositeRiskScores(
patientData, riskResults.stratification_models
);
riskResults.composite_risk_scores = compositeScores;
// Identify high-risk patients across all models
const highRiskPatients = this.identifyHighRiskPatients(compositeScores);
riskResults.high_risk_patients = highRiskPatients;
// Generate intervention recommendations
const interventions = this.generateInterventionRecommendations(
highRiskPatients, riskResults.stratification_models
);
riskResults.intervention_recommendations = interventions;
return riskResults;
}
async calculateCardiovascularRisk(patientData) {
const riskCategories = { low: [], moderate: [], high: [], very_high: [] };
const riskFactorsAnalysis = {
hypertension_prevalence: 0,
diabetes_prevalence: 0,
smoking_prevalence: 0,
high_cholesterol_prevalence: 0,
multiple_risk_factors: 0
};
for (const patient of patientData) {
let riskScore = 0;
const riskFactors = [];
// Age factor
const age = patient.age || 0;
if (age >= 65) {
riskScore += 2;
riskFactors.push('advanced_age');
} else if (age >= 45) {
riskScore += 1;
}
// Gender factor
if (patient.gender === 'M' && age >= 45) {
riskScore += 1;
riskFactors.push('male_gender');
} else if (patient.gender === 'F' && age >= 55) {
riskScore += 1;
}
// Check for cardiovascular risk conditions
const patientConditions = (patient.conditions || []).map(c => c.code);
// Hypertension
const hypertensionCodes = ['38341003', 'I10']; // SNOMED and ICD10
if (hypertensionCodes.some(code => patientConditions.includes(code))) {
riskScore += 2;
riskFactors.push('hypertension');
riskFactorsAnalysis.hypertension_prevalence++;
}
// Diabetes
const diabetesCodes = ['44054006', 'E11.9']; // SNOMED and ICD10
if (diabetesCodes.some(code => patientConditions.includes(code))) {
riskScore += 2;
riskFactors.push('diabetes');
riskFactorsAnalysis.diabetes_prevalence++;
}
// Check lab values for additional risk factors
const recentLabs = this.getRecentLabValues(patient, 365);
// High cholesterol (>200 mg/dL)
const cholesterolResult = recentLabs['2093-3']; // Total cholesterol LOINC
if (cholesterolResult && parseFloat(cholesterolResult.value || 0) > 200) {
riskScore += 1;
riskFactors.push('high_cholesterol');
riskFactorsAnalysis.high_cholesterol_prevalence++;
}
// Smoking status
if (patient.smoking_status === 'current') {
riskScore += 2;
riskFactors.push('smoking');
riskFactorsAnalysis.smoking_prevalence++;
}
// Count multiple risk factors
if (riskFactors.length >= 3) {
riskFactorsAnalysis.multiple_risk_factors++;
}
// Categorize risk level
const patientRiskData = {
patient_id: patient.patient_id,
risk_score: riskScore,
risk_factors: riskFactors,
age,
gender: patient.gender || 'Unknown'
};
if (riskScore >= 7) {
riskCategories.very_high.push(patientRiskData);
} else if (riskScore >= 5) {
riskCategories.high.push(patientRiskData);
} else if (riskScore >= 3) {
riskCategories.moderate.push(patientRiskData);
} else {
riskCategories.low.push(patientRiskData);
}
}
// Calculate prevalence rates
const totalPatients = patientData.length;
for (const factor in riskFactorsAnalysis) {
riskFactorsAnalysis[factor] = totalPatients > 0 ?
(riskFactorsAnalysis[factor] / totalPatients * 100) : 0;
}
return {
model_name: 'Cardiovascular Risk Assessment',
risk_categories: riskCategories,
population_summary: {
total_patients: totalPatients,
low_risk: riskCategories.low.length,
moderate_risk: riskCategories.moderate.length,
high_risk: riskCategories.high.length,
very_high_risk: riskCategories.very_high.length
},
risk_factors_analysis: riskFactorsAnalysis,
high_risk_percentage: totalPatients > 0 ?
((riskCategories.high.length + riskCategories.very_high.length) / totalPatients * 100) : 0
};
}
getRecentLabValues(patient, days = 365) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - days);
const recentLabs = {};
for (const lab of patient.lab_results || []) {
const labDate = new Date(lab.date || '1900-01-01');
const loincCode = lab.loinc_code;
if (labDate >= cutoffDate && loincCode) {
// Keep most recent result for each LOINC code
if (!recentLabs[loincCode] ||
labDate > new Date(recentLabs[loincCode].date || '1900-01-01')) {
recentLabs[loincCode] = lab;
}
}
}
return recentLabs;
}
```
```r R
perform_risk_stratification <- function(analytics, patient_data, risk_models) {
risk_results <- list(
total_patients = length(patient_data),
stratification_models = list(),
high_risk_patients = list(),
composite_risk_scores = list(),
intervention_recommendations = list()
)
# Apply different risk models
for (model_name in risk_models) {
model_results <- NULL
if (model_name == "cardiovascular_risk") {
model_results <- calculate_cardiovascular_risk(patient_data)
} else if (model_name == "diabetes_complications") {
model_results <- calculate_diabetes_complication_risk(patient_data)
} else if (model_name == "hospital_readmission") {
model_results <- calculate_readmission_risk(patient_data)
} else if (model_name == "medication_adherence") {
model_results <- assess_medication_adherence_risk(patient_data)
}
if (!is.null(model_results)) {
risk_results$stratification_models[[model_name]] <- model_results
}
}
# Create composite risk scores
composite_scores <- create_composite_risk_scores(patient_data, risk_results$stratification_models)
risk_results$composite_risk_scores <- composite_scores
# Identify high-risk patients
high_risk_patients <- identify_high_risk_patients(composite_scores)
risk_results$high_risk_patients <- high_risk_patients
# Generate intervention recommendations
interventions <- generate_intervention_recommendations(high_risk_patients, risk_results$stratification_models)
risk_results$intervention_recommendations <- interventions
return(risk_results)
}
calculate_cardiovascular_risk <- function(patient_data) {
risk_categories <- list(
low = list(),
moderate = list(),
high = list(),
very_high = list()
)
risk_factors_analysis <- list(
hypertension_prevalence = 0,
diabetes_prevalence = 0,
smoking_prevalence = 0,
high_cholesterol_prevalence = 0,
multiple_risk_factors = 0
)
for (patient in patient_data) {
risk_score <- 0
risk_factors <- c()
# Age factor
age <- if (is.null(patient$age)) 0 else patient$age
if (age >= 65) {
risk_score <- risk_score + 2
risk_factors <- c(risk_factors, "advanced_age")
} else if (age >= 45) {
risk_score <- risk_score + 1
}
# Gender factor
if (patient$gender == "M" && age >= 45) {
risk_score <- risk_score + 1
risk_factors <- c(risk_factors, "male_gender")
} else if (patient$gender == "F" && age >= 55) {
risk_score <- risk_score + 1
}
# Check conditions
patient_conditions <- sapply(patient$conditions, function(c) c$code)
# Hypertension
hypertension_codes <- c("38341003", "I10")
if (any(hypertension_codes %in% patient_conditions)) {
risk_score <- risk_score + 2
risk_factors <- c(risk_factors, "hypertension")
risk_factors_analysis$hypertension_prevalence <- risk_factors_analysis$hypertension_prevalence + 1
}
# Diabetes
diabetes_codes <- c("44054006", "E11.9")
if (any(diabetes_codes %in% patient_conditions)) {
risk_score <- risk_score + 2
risk_factors <- c(risk_factors, "diabetes")
risk_factors_analysis$diabetes_prevalence <- risk_factors_analysis$diabetes_prevalence + 1
}
# Count multiple risk factors
if (length(risk_factors) >= 3) {
risk_factors_analysis$multiple_risk_factors <- risk_factors_analysis$multiple_risk_factors + 1
}
# Categorize risk level
patient_risk_data <- list(
patient_id = patient$patient_id,
risk_score = risk_score,
risk_factors = risk_factors,
age = age,
gender = if (is.null(patient$gender)) "Unknown" else patient$gender
)
if (risk_score >= 7) {
risk_categories$very_high[[length(risk_categories$very_high) + 1]] <- patient_risk_data
} else if (risk_score >= 5) {
risk_categories$high[[length(risk_categories$high) + 1]] <- patient_risk_data
} else if (risk_score >= 3) {
risk_categories$moderate[[length(risk_categories$moderate) + 1]] <- patient_risk_data
} else {
risk_categories$low[[length(risk_categories$low) + 1]] <- patient_risk_data
}
}
# Calculate prevalence rates
total_patients <- length(patient_data)
for (factor in names(risk_factors_analysis)) {
risk_factors_analysis[[factor]] <- if (total_patients > 0) {
(risk_factors_analysis[[factor]] / total_patients * 100)
} else {
0
}
}
return(list(
model_name = "Cardiovascular Risk Assessment",
risk_categories = risk_categories,
population_summary = list(
total_patients = total_patients,
low_risk = length(risk_categories$low),
moderate_risk = length(risk_categories$moderate),
high_risk = length(risk_categories$high),
very_high_risk = length(risk_categories$very_high)
),
risk_factors_analysis = risk_factors_analysis,
high_risk_percentage = if (total_patients > 0) {
((length(risk_categories$high) + length(risk_categories$very_high)) / total_patients * 100)
} else {
0
}
))
}
```
## Example Implementation
### Sample Population Health Analysis
```python Python
# Initialize the analytics engine
analytics = PopulationHealthAnalytics("your_api_key")
# Sample patient population (1000+ patients)
sample_population = [
{
'patient_id': 'PT001',
'age': 65,
'gender': 'M',
'conditions': [
{'code': '44054006', 'name': 'Type 2 diabetes mellitus', 'vocabulary': 'SNOMED'},
{'code': '38341003', 'name': 'Essential hypertension', 'vocabulary': 'SNOMED'}
],
'lab_results': [
{'loinc_code': '2345-7', 'value': '7.2', 'unit': '%', 'date': '2023-06-15'},
{'loinc_code': '2093-3', 'value': '220', 'unit': 'mg/dL', 'date': '2023-06-15'}
],
'smoking_status': 'former'
},
{
'patient_id': 'PT002',
'age': 45,
'gender': 'F',
'conditions': [
{'code': '254837009', 'name': 'Malignant neoplasm of breast', 'vocabulary': 'SNOMED'}
],
'lab_results': [
{'loinc_code': '2093-3', 'value': '180', 'unit': 'mg/dL', 'date': '2023-07-01'}
],
'smoking_status': 'never'
}
# ... more patients
]
# Analyze disease prevalence
disease_categories = [
'44054006', # Type 2 diabetes mellitus
'38341003', # Essential hypertension
'363346000', # Malignant neoplastic disease
'195967001', # Asthma
'13644009' # Hypercholesterolemia
]
prevalence_analysis = analytics.analyze_disease_prevalence(
sample_population,
disease_categories,
AnalysisScope.HEALTH_SYSTEM
)
# Calculate quality measures
quality_measures = [
QualityMeasure.DIABETES_HBA1C,
QualityMeasure.HYPERTENSION_BP
]
quality_results = analytics.calculate_quality_measures(
sample_population,
quality_measures,
measurement_period_days=365
)
# Perform risk stratification
risk_models = [
'cardiovascular_risk',
'diabetes_complications',
'hospital_readmission'
]
risk_analysis = analytics.perform_risk_stratification(
sample_population,
risk_models
)
# Generate comprehensive population health report
print("=== POPULATION HEALTH ANALYTICS REPORT ===")
print(f"Analysis Scope: {prevalence_analysis['analysis_scope']}")
print(f"Population Size: {prevalence_analysis['total_population']:,}")
print(f"Analysis Date: {prevalence_analysis['analysis_date']}")
print("\n=== DISEASE PREVALENCE ANALYSIS ===")
for disease, data in prevalence_analysis['disease_prevalence'].items():
print(f"\n{data['disease_name']}:")
print(f" Prevalence Rate: {data['prevalence_rate']:.1f}%")
print(f" Affected Patients: {data['affected_patients']:,}")
print(f" Confidence Interval: {data['confidence_interval'][0]:.1f}% - {data['confidence_interval'][1]:.1f}%")
# Age stratification
print(" Age Breakdown:")
for age_group, stats in data['age_stratification'].items():
print(f" {age_group}: {stats['prevalence_rate']:.1f}% ({stats['affected_patients']}/{stats['total_patients']})")
print("\n=== QUALITY MEASURES RESULTS ===")
print(f"Overall Quality Grade: {quality_results['composite_scores']['grade']}")
print(f"Overall Score: {quality_results['composite_scores']['overall_score']:.1f}%")
for measure_id, result in quality_results['individual_measures'].items():
if 'error' not in result:
print(f"\n{result['measure_name']}:")
print(f" Performance: {result['measure_rate']:.1f}% ({result['performance_category']})")
print(f" Numerator/Denominator: {result['numerator']}/{result['denominator']}")
print(f" Target: {result['target_value']}")
if result['improvement_opportunities']:
print(" Improvement Opportunities:")
for opportunity in result['improvement_opportunities']:
print(f" • {opportunity}")
print("\n=== RISK STRATIFICATION RESULTS ===")
print(f"Total Patients Analyzed: {risk_analysis['total_patients']:,}")
cv_risk = risk_analysis['stratification_models']['cardiovascular_risk']
print(f"\nCardiovascular Risk Distribution:")
print(f" Low Risk: {cv_risk['population_summary']['low_risk']:,} patients")
print(f" Moderate Risk: {cv_risk['population_summary']['moderate_risk']:,} patients")
print(f" High Risk: {cv_risk['population_summary']['high_risk']:,} patients")
print(f" Very High Risk: {cv_risk['population_summary']['very_high_risk']:,} patients")
print(f"\nHigh-Risk Patients Requiring Intervention: {len(risk_analysis['high_risk_patients'])}")
print("\n=== INTERVENTION RECOMMENDATIONS ===")
for recommendation in risk_analysis['intervention_recommendations'][:5]: # Show top 5
print(f"\n{recommendation['intervention']} ({recommendation['priority'].upper()} Priority):")
print(f" Type: {recommendation['type']}")
print(f" Description: {recommendation['description']}")
if 'components' in recommendation:
print(" Components:")
for component in recommendation['components']:
print(f" • {component}")
if 'estimated_impact' in recommendation:
print(f" Estimated Impact: {recommendation['estimated_impact']}")
print("\n=== COMPARATIVE ANALYSIS ===")
for insight in prevalence_analysis['comparative_analysis']:
print(f"• {insight}")
```
```javascript JavaScript
// Initialize the analytics engine
const analytics = new PopulationHealthAnalytics('your_api_key');
// Sample patient population
const samplePopulation = [
{
patient_id: 'PT001',
age: 65,
gender: 'M',
conditions: [
{ code: '44054006', name: 'Type 2 diabetes mellitus', vocabulary: 'SNOMED' },
{ code: '38341003', name: 'Essential hypertension', vocabulary: 'SNOMED' }
],
lab_results: [
{ loinc_code: '2345-7', value: '7.2', unit: '%', date: '2023-06-15' },
{ loinc_code: '2093-3', value: '220', unit: 'mg/dL', date: '2023-06-15' }
],
smoking_status: 'former'
},
{
patient_id: 'PT002',
age: 45,
gender: 'F',
conditions: [
{ code: '254837009', name: 'Malignant neoplasm of breast', vocabulary: 'SNOMED' }
],
lab_results: [
{ loinc_code: '2093-3', value: '180', unit: 'mg/dL', date: '2023-07-01' }
],
smoking_status: 'never'
}
// ... more patients
];
async function runPopulationAnalysis() {
try {
// Analyze disease prevalence
const diseaseCategories = [
'44054006', // Type 2 diabetes mellitus
'38341003', // Essential hypertension
'363346000', // Malignant neoplastic disease
'195967001', // Asthma
'13644009' // Hypercholesterolemia
];
const prevalenceAnalysis = await analytics.analyzeDiseasePrevalence(
samplePopulation,
diseaseCategories,
AnalysisScope.HEALTH_SYSTEM
);
// Calculate quality measures
const qualityMeasures = [
QualityMeasure.DIABETES_HBA1C,
QualityMeasure.HYPERTENSION_BP
];
const qualityResults = await analytics.calculateQualityMeasures(
samplePopulation,
qualityMeasures,
365
);
// Perform risk stratification
const riskModels = [
'cardiovascular_risk',
'diabetes_complications',
'hospital_readmission'
];
const riskAnalysis = await analytics.performRiskStratification(
samplePopulation,
riskModels
);
// Generate comprehensive report
console.log('=== POPULATION HEALTH ANALYTICS REPORT ===');
console.log(`Analysis Scope: ${prevalenceAnalysis.analysis_scope}`);
console.log(`Population Size: ${prevalenceAnalysis.total_population.toLocaleString()}`);
console.log(`Analysis Date: ${prevalenceAnalysis.analysis_date}`);
console.log('\n=== DISEASE PREVALENCE ANALYSIS ===');
for (const [disease, data] of Object.entries(prevalenceAnalysis.disease_prevalence)) {
console.log(`\n${data.disease_name}:`);
console.log(` Prevalence Rate: ${data.prevalence_rate.toFixed(1)}%`);
console.log(` Affected Patients: ${data.affected_patients.toLocaleString()}`);
console.log(` Confidence Interval: ${data.confidence_interval[0].toFixed(1)}% - ${data.confidence_interval[1].toFixed(1)}%`);
console.log(' Age Breakdown:');
for (const [ageGroup, stats] of Object.entries(data.age_stratification)) {
console.log(` ${ageGroup}: ${stats.prevalence_rate.toFixed(1)}% (${stats.affected_patients}/${stats.total_patients})`);
}
}
console.log('\n=== QUALITY MEASURES RESULTS ===');
console.log(`Overall Quality Grade: ${qualityResults.composite_scores.grade}`);
console.log(`Overall Score: ${qualityResults.composite_scores.overall_score.toFixed(1)}%`);
for (const [measureId, result] of Object.entries(qualityResults.individual_measures)) {
if (!result.error) {
console.log(`\n${result.measure_name}:`);
console.log(` Performance: ${result.measure_rate.toFixed(1)}% (${result.performance_category})`);
console.log(` Numerator/Denominator: ${result.numerator}/${result.denominator}`);
console.log(` Target: ${result.target_value}`);
if (result.improvement_opportunities && result.improvement_opportunities.length > 0) {
console.log(' Improvement Opportunities:');
result.improvement_opportunities.forEach(opportunity => {
console.log(` • ${opportunity}`);
});
}
}
}
console.log('\n=== RISK STRATIFICATION RESULTS ===');
console.log(`Total Patients Analyzed: ${riskAnalysis.total_patients.toLocaleString()}`);
const cvRisk = riskAnalysis.stratification_models.cardiovascular_risk;
console.log('\nCardiovascular Risk Distribution:');
console.log(` Low Risk: ${cvRisk.population_summary.low_risk.toLocaleString()} patients`);
console.log(` Moderate Risk: ${cvRisk.population_summary.moderate_risk.toLocaleString()} patients`);
console.log(` High Risk: ${cvRisk.population_summary.high_risk.toLocaleString()} patients`);
console.log(` Very High Risk: ${cvRisk.population_summary.very_high_risk.toLocaleString()} patients`);
console.log(`\nHigh-Risk Patients Requiring Intervention: ${riskAnalysis.high_risk_patients.length}`);
console.log('\n=== INTERVENTION RECOMMENDATIONS ===');
riskAnalysis.intervention_recommendations.slice(0, 5).forEach(recommendation => {
console.log(`\n${recommendation.intervention} (${recommendation.priority.toUpperCase()} Priority):`);
console.log(` Type: ${recommendation.type}`);
console.log(` Description: ${recommendation.description}`);
if (recommendation.components) {
console.log(' Components:');
recommendation.components.forEach(component => {
console.log(` • ${component}`);
});
}
if (recommendation.estimated_impact) {
console.log(` Estimated Impact: ${recommendation.estimated_impact}`);
}
});
console.log('\n=== COMPARATIVE ANALYSIS ===');
prevalenceAnalysis.comparative_analysis.forEach(insight => {
console.log(`• ${insight}`);
});
} catch (error) {
console.error('Population analysis failed:', error);
}
}
// Run the analysis
runPopulationAnalysis();
```
```r R
# Initialize the analytics engine
analytics <- init_population_analytics("your_api_key")
# Sample patient population
sample_population <- list(
list(
patient_id = "PT001",
age = 65,
gender = "M",
conditions = list(
list(code = "44054006", name = "Type 2 diabetes mellitus", vocabulary = "SNOMED"),
list(code = "38341003", name = "Essential hypertension", vocabulary = "SNOMED")
),
lab_results = list(
list(loinc_code = "2345-7", value = "7.2", unit = "%", date = "2023-06-15"),
list(loinc_code = "2093-3", value = "220", unit = "mg/dL", date = "2023-06-15")
),
smoking_status = "former"
),
list(
patient_id = "PT002",
age = 45,
gender = "F",
conditions = list(
list(code = "254837009", name = "Malignant neoplasm of breast", vocabulary = "SNOMED")
),
lab_results = list(
list(loinc_code = "2093-3", value = "180", unit = "mg/dL", date = "2023-07-01")
),
smoking_status = "never"
)
)
# Analyze disease prevalence
disease_categories <- c(
"44054006", # Type 2 diabetes mellitus
"38341003", # Essential hypertension
"363346000", # Malignant neoplastic disease
"195967001", # Asthma
"13644009" # Hypercholesterolemia
)
prevalence_analysis <- analyze_disease_prevalence(
analytics,
sample_population,
disease_categories,
ANALYSIS_SCOPE$HEALTH_SYSTEM
)
# Calculate quality measures
quality_measures <- c(
QUALITY_MEASURE$DIABETES_HBA1C,
QUALITY_MEASURE$HYPERTENSION_BP
)
quality_results <- calculate_quality_measures(
analytics,
sample_population,
quality_measures,
365
)
# Perform risk stratification
risk_models <- c(
"cardiovascular_risk",
"diabetes_complications",
"hospital_readmission"
)
risk_analysis <- perform_risk_stratification(
analytics,
sample_population,
risk_models
)
# Generate comprehensive report
cat("=== POPULATION HEALTH ANALYTICS REPORT ===\n")
cat(paste("Analysis Scope:", prevalence_analysis$analysis_scope, "\n"))
cat(paste("Population Size:", format(prevalence_analysis$total_population, big.mark = ","), "\n"))
cat(paste("Analysis Date:", prevalence_analysis$analysis_date, "\n"))
cat("\n=== DISEASE PREVALENCE ANALYSIS ===\n")
for (disease in names(prevalence_analysis$disease_prevalence)) {
data <- prevalence_analysis$disease_prevalence[[disease]]
cat(paste("\n", data$disease_name, ":\n"))
cat(paste(" Prevalence Rate:", round(data$prevalence_rate, 1), "%\n"))
cat(paste(" Affected Patients:", format(data$affected_patients, big.mark = ","), "\n"))
cat(paste(" Confidence Interval:", round(data$confidence_interval[1], 1), "% -", round(data$confidence_interval[2], 1), "%\n"))
cat(" Age Breakdown:\n")
for (age_group in names(data$age_stratification)) {
stats <- data$age_stratification[[age_group]]
cat(paste(" ", age_group, ":", round(stats$prevalence_rate, 1), "% (", stats$affected_patients, "/", stats$total_patients, ")\n"))
}
}
cat("\n=== QUALITY MEASURES RESULTS ===\n")
cat(paste("Overall Quality Grade:", quality_results$composite_scores$grade, "\n"))
cat(paste("Overall Score:", round(quality_results$composite_scores$overall_score, 1), "%\n"))
for (measure_id in names(quality_results$individual_measures)) {
result <- quality_results$individual_measures[[measure_id]]
if (is.null(result$error)) {
cat(paste("\n", result$measure_name, ":\n"))
cat(paste(" Performance:", round(result$measure_rate, 1), "% (", result$performance_category, ")\n"))
cat(paste(" Numerator/Denominator:", result$numerator, "/", result$denominator, "\n"))
cat(paste(" Target:", result$target_value, "\n"))
}
}
cat("\n=== RISK STRATIFICATION RESULTS ===\n")
cat(paste("Total Patients Analyzed:", format(risk_analysis$total_patients, big.mark = ","), "\n"))
cv_risk <- risk_analysis$stratification_models$cardiovascular_risk
cat("\nCardiovascular Risk Distribution:\n")
cat(paste(" Low Risk:", format(cv_risk$population_summary$low_risk, big.mark = ","), "patients\n"))
cat(paste(" Moderate Risk:", format(cv_risk$population_summary$moderate_risk, big.mark = ","), "patients\n"))
cat(paste(" High Risk:", format(cv_risk$population_summary$high_risk, big.mark = ","), "patients\n"))
cat(paste(" Very High Risk:", format(cv_risk$population_summary$very_high_risk, big.mark = ","), "patients\n"))
cat(paste("\nHigh-Risk Patients Requiring Intervention:", length(risk_analysis$high_risk_patients), "\n"))
```
### Expected Output
```
=== POPULATION HEALTH ANALYTICS REPORT ===
Analysis Scope: health_system
Population Size: 50,000
Analysis Date: 2023-12-01T10:30:00Z
=== DISEASE PREVALENCE ANALYSIS ===
Type 2 diabetes mellitus:
Prevalence Rate: 8.5%
Affected Patients: 4,250
Confidence Interval: 8.2% - 8.8%
Age Breakdown:
18-34: 1.2% (45/3,750)
35-49: 4.8% (576/12,000)
50-64: 12.1% (1,815/15,000)
65-79: 18.7% (1,496/8,000)
80+: 28.9% (318/1,100)
Essential hypertension:
Prevalence Rate: 15.2%
Affected Patients: 7,600
Confidence Interval: 14.8% - 15.6%
Age Breakdown:
18-34: 3.1% (116/3,750)
35-49: 8.9% (1,068/12,000)
50-64: 19.8% (2,970/15,000)
65-79: 35.4% (2,832/8,000)
80+: 55.8% (614/1,100)
=== QUALITY MEASURES RESULTS ===
Overall Quality Grade: B+
Overall Score: 82.3%
Diabetes HbA1c Control:
Performance: 78.5% (Satisfactory)
Numerator/Denominator: 3,337/4,250
Target: <7.0
Improvement Opportunities:
• 21.5% of eligible patients not meeting target - focus on care gaps
• 12.3% of patients missing recent tests - improve monitoring
Hypertension Blood Pressure Control:
Performance: 86.1% (Good)
Numerator/Denominator: 6,544/7,600
Target: <140/90
Improvement Opportunities:
• Higher gap rate in elderly population - consider age-specific interventions
=== RISK STRATIFICATION RESULTS ===
Total Patients Analyzed: 50,000
Cardiovascular Risk Distribution:
Low Risk: 28,500 patients
Moderate Risk: 12,750 patients
High Risk: 6,250 patients
Very High Risk: 2,500 patients
High-Risk Patients Requiring Intervention: 8,750
=== INTERVENTION RECOMMENDATIONS ===
Comprehensive Population Health Program (HIGH Priority):
Type: population_level
Description: 17.5% of population is high-risk - implement systematic screening and intervention programs
Estimated Impact: Reduce high-risk population by 15-25%
Cardiovascular Risk Management Program (HIGH Priority):
Type: cardiovascular_focused
Description: 8,750 patients at high cardiovascular risk need intensive management
Components:
• Statin therapy optimization
• Blood pressure management
• Diabetes control enhancement
• Lifestyle modification counseling
Estimated Impact: Reduce cardiovascular events by 20-30%
=== COMPARATIVE ANALYSIS ===
• Diabetes prevalence (8.5%) is above national average (7.8%)
• Hypertension control rate (86.1%) exceeds CMS benchmark (80%)
• High cardiovascular risk population (17.5%) indicates need for prevention programs
• Elderly population shows higher comorbidity burden requiring specialized care
```
## Integration Patterns
### 1. Public Health Surveillance Integration
```python Python
class PublicHealthSurveillance:
def __init__(self, analytics_engine: PopulationHealthAnalytics):
self.analytics = analytics_engine
def monitor_disease_outbreaks(self, patient_data: List[Dict[str, Any]],
surveillance_conditions: List[str],
time_window_days: int = 30) -> Dict[str, Any]:
"""Monitor for potential disease outbreaks"""
cutoff_date = datetime.now() - timedelta(days=time_window_days)
outbreak_alerts = []
for condition_code in surveillance_conditions:
# Get recent cases
recent_cases = []
for patient in patient_data:
for condition in patient.get('conditions', []):
condition_date = datetime.fromisoformat(condition.get('date', '1900-01-01'))
if (condition_date >= cutoff_date and
condition.get('code') == condition_code):
recent_cases.append({
'patient_id': patient['patient_id'],
'condition_date': condition_date,
'location': patient.get('location', {})
})
# Calculate expected vs actual cases
expected_cases = self.calculate_expected_cases(condition_code, time_window_days)
actual_cases = len(recent_cases)
# Statistical significance testing
if actual_cases > expected_cases * 1.5 and actual_cases >= 3:
# Potential outbreak detected
outbreak_alert = {
'condition_code': condition_code,
'condition_name': self.get_condition_name(condition_code),
'actual_cases': actual_cases,
'expected_cases': expected_cases,
'excess_cases': actual_cases - expected_cases,
'risk_ratio': actual_cases / expected_cases if expected_cases > 0 else float('inf'),
'time_window': time_window_days,
'geographic_clustering': self.analyze_geographic_clustering(recent_cases),
'alert_level': self.determine_alert_level(actual_cases, expected_cases),
'recommendations': self.generate_outbreak_recommendations(condition_code, actual_cases)
}
outbreak_alerts.append(outbreak_alert)
return {
'surveillance_period': f"{time_window_days} days",
'conditions_monitored': len(surveillance_conditions),
'outbreak_alerts': outbreak_alerts,
'total_alerts': len(outbreak_alerts),
'highest_risk_condition': max(outbreak_alerts, key=lambda x: x['risk_ratio']) if outbreak_alerts else None
}
```
## Next Steps
Use population analytics for trial recruitment
Analyze drug interactions across populations
Standardize lab data for population analysis
# Introduction
Source: https://docs.omophub.com/introduction
OMOPHub provides a healthcare vocabulary API with OHDSI and OMOP-compliant medical terminology access for developers.
## Quickstart
Lern how to get OMOPHub API set up in your project.
Get up and running with the OMOPHub API in minutes
Explore our comprehensive API documentation
Use our SDKs for Python, JavaScript, and R
Learn from real-world healthcare implementations
## Why OMOPHub API?
### Healthcare-Grade Infrastructure
Built specifically for healthcare applications with HIPAA compliance, audit logging, and enterprise security.
### Complete OHDSI Coverage
Full implementation of OMOP vocabulary standards with bi-annual updates and version management.
### Advanced Search
Full-text search, faceted filtering, similarity matching, and healthcare-specific ranking algorithms.
### Comprehensive Relationships
Navigate complex medical concept hierarchies with powerful relationship traversal APIs.
### Multi-Vocabulary Support
Access SNOMED CT, ICD-10, RxNorm, LOINC and 100+ other healthcare vocabularies.
## Key Features
Manage vocabulary versions with confidence:
* Bi-annual OHDSI vocabulary updates
* Version-specific API routing
* Seamless migration between versions
* Version comparison and diff tools
Find the right concepts quickly:
* Multi-field full-text search
* Faceted filtering and aggregations
* Fuzzy matching and typo tolerance
* Healthcare-specific ranking
Map between vocabulary systems:
* Direct and equivalent mappings
* Cross-vocabulary navigation
* Mapping quality metrics
* Batch mapping operations
## Getting Started
Sign up for a OMOPHub on [https://dashboard.omophub.com](https://dashboard.omophub.com)
Generate an API key from your dashboard
Use our SDKs or make direct API calls
Discover vocabularies, search concepts, and traverse relationships
## Quick Example
```bash cURL
curl -X GET "https://api.omophub.com/v1/search/concepts?query=diabetes%20mellitus%20type%202&vocabulary_ids=SNOMED&page_size=10" \
-H "Authorization: Bearer your_api_key"
```
```python Python
import requests
url = "https://api.omophub.com/v1/search/concepts"
headers = {
"Authorization": "Bearer your_api_key",
"Content-Type": "application/json"
}
params = {
"query": "diabetes mellitus type 2",
"vocabulary_ids": "SNOMED",
"page_size": 10
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
for concept in data["data"]["concepts"]:
print(f"{concept['concept_id']}: {concept['concept_name']}")
```
```javascript JavaScript
const url = 'https://api.omophub.com/v1/search/concepts';
const params = new URLSearchParams({
query: 'diabetes mellitus type 2',
vocabulary_ids: 'SNOMED',
page_size: 10
});
const response = await fetch(`${url}?${params}`, {
method: 'GET',
headers: {
'Authorization': 'Bearer your_api_key',
'Content-Type': 'application/json'
}
});
const data = await response.json();
data.data.concepts.forEach(concept => {
console.log(`${concept.concept_id}: ${concept.concept_name}`);
});
```
```r R
library(httr)
library(jsonlite)
url <- "https://api.omophub.com/v1/search/concepts"
headers <- add_headers(
"Authorization" = "Bearer your_api_key",
"Content-Type" = "application/json"
)
response <- GET(url, headers, query = list(
query = "diabetes mellitus type 2",
vocabulary_ids = "SNOMED",
page_size = 10
))
data <- fromJSON(content(response, "text"))
for (concept in data$data$concepts) {
cat(paste(concept$concept_id, concept$concept_name, sep = ": "), "\n")
}
```
## Need Help?
Comprehensive guides and API reference
Check API status and uptime
# Quick Start
Source: https://docs.omophub.com/quickstart
Get up and running with the OMOPHub API in 5 minutes
## Overview
This guide will help you make your first API call to OMOPHub in just a few minutes. By the end, you'll be able to search medical concepts and explore healthcare vocabularies.
**Prerequisites**
You'll need:
* A OMOPHub account ([sign up here](https://dashboard.omophub.com/register))
* An API key ([generate one here](https://dashboard.omophub.com/api-keys))
* A programming environment (Python, JavaScript/Node.js, R, or cURL)
## Step 1: Set Up Required Libraries
Choose your preferred programming language and install the necessary HTTP libraries:
```python Python
# Python - Install requests library
pip install requests
```
```javascript JavaScript
# JavaScript/Node.js - No additional installation needed
# fetch() is built-in for Node.js 18+ and all modern browsers
node --version
```
```r R
# R - Install HTTP libraries
install.packages(c("httr", "jsonlite"))
```
```bash cURL
# cURL - No installation needed, pre-installed on most systems
curl --version
```
## Step 2: Set Up Authentication
Prepare your API key and authentication headers for making requests:
```python Python
# Python
import requests
# Set up authentication
API_KEY = "your_api_key_here"
BASE_URL = "https://api.omophub.com/v1"
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
```
```javascript JavaScript
// JavaScript/TypeScript
const API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.omophub.com/v1';
const headers = {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
};
```
```r R
# R
library(httr)
library(jsonlite)
# Set up authentication
API_KEY <- "your_api_key_here"
BASE_URL <- "https://api.omophub.com/v1"
headers <- add_headers(
"Authorization" = paste("Bearer", API_KEY),
"Content-Type" = "application/json"
)
```
```bash cURL
# cURL - Set your API key as an environment variable
export OMOPHUB_API_KEY="your_api_key_here"
export OMOPHUB_BASE_URL="https://api.omophub.com/v1"
```
Never hardcode your API key in your source code. Use environment variables or secure key management systems in production.
## Step 3: Make Your First Request
Let's search for concepts related to "hypertension":
```python Python
# Python - Search for hypertension concepts
response = requests.get(f"{BASE_URL}/search/concepts",
headers=headers,
params={
"query": "hypertension",
"page_size": 5
}
)
data = response.json()
# Display the results
for concept in data["data"]["concepts"]:
print(f"ID: {concept['concept_id']}")
print(f"Name: {concept['concept_name']}")
print(f"Vocabulary: {concept['vocabulary_id']}")
print(f"Domain: {concept['domain_id']}")
print("---")
```
```javascript JavaScript
// JavaScript - Search for hypertension concepts
const params = new URLSearchParams({
query: 'hypertension',
page_size: 5
});
const response = await fetch(`${BASE_URL}/search/concepts?${params}`, {
headers: headers
});
const data = await response.json();
// Display the results
data.data.concepts.forEach(concept => {
console.log(`ID: ${concept.concept_id}`);
console.log(`Name: ${concept.concept_name}`);
console.log(`Vocabulary: ${concept.vocabulary_id}`);
console.log(`Domain: ${concept.domain_id}`);
console.log('---');
});
```
```r R
# R - Search for hypertension concepts
response <- GET(paste0(BASE_URL, "/search/concepts"),
headers,
query = list(
query = "hypertension",
page_size = 5
)
)
data <- fromJSON(content(response, "text"))
# Display the results
for (concept in data$data$concepts) {
cat(paste("ID:", concept$concept_id, "\n"))
cat(paste("Name:", concept$concept_name, "\n"))
cat(paste("Vocabulary:", concept$vocabulary_id, "\n"))
cat(paste("Domain:", concept$domain_id, "\n"))
cat("---\n")
}
```
```bash cURL
# cURL - Search for hypertension concepts
curl -X GET "$OMOPHUB_BASE_URL/search/concepts?query=hypertension&page_size=5" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" \
-H "Content-Type: application/json" | jq '.'
```
## Step 4: Explore Concept Details
Once you have a concept ID, you can get detailed information:
```python Python
# Python - Get detailed information about a specific concept
concept_id = 320128 # Hypertension concept ID
# Get concept details
response = requests.get(f"{BASE_URL}/concepts/{concept_id}", headers=headers)
concept = response.json()["data"]
print(f"Concept: {concept['concept_name']}")
print(f"Code: {concept['concept_code']}")
print(f"Class: {concept['concept_class_id']}")
print(f"Valid dates: {concept['valid_start_date']} to {concept['valid_end_date']}")
# Get concept relationships
rel_response = requests.get(f"{BASE_URL}/concepts/{concept_id}/relationships", headers=headers)
relationships = rel_response.json()["data"]
print(f"\nFound {len(relationships)} relationships")
```
```javascript JavaScript
// JavaScript - Get detailed information about a specific concept
const conceptId = 320128; // Hypertension concept ID
// Get concept details
const conceptResponse = await fetch(`${BASE_URL}/concepts/${conceptId}`, {
headers: headers
});
const conceptData = await conceptResponse.json();
const concept = conceptData.data;
console.log(`Concept: ${concept.concept_name}`);
console.log(`Code: ${concept.concept_code}`);
console.log(`Class: ${concept.concept_class_id}`);
console.log(`Valid dates: ${concept.valid_start_date} to ${concept.valid_end_date}`);
// Get concept relationships
const relResponse = await fetch(`${BASE_URL}/concepts/${conceptId}/relationships`, {
headers: headers
});
const relData = await relResponse.json();
console.log(`\nFound ${relData.data.length} relationships`);
```
```r R
# R - Get detailed information about a specific concept
concept_id <- 320128 # Hypertension concept ID
# Get concept details
response <- GET(paste0(BASE_URL, "/concepts/", concept_id), headers)
concept <- fromJSON(content(response, "text"))$data
cat(paste("Concept:", concept$concept_name, "\n"))
cat(paste("Code:", concept$concept_code, "\n"))
cat(paste("Class:", concept$concept_class_id, "\n"))
cat(paste("Valid dates:", concept$valid_start_date, "to", concept$valid_end_date, "\n"))
# Get concept relationships
rel_response <- GET(paste0(BASE_URL, "/concepts/", concept_id, "/relationships"), headers)
relationships <- fromJSON(content(rel_response, "text"))$data
cat(paste("\nFound", length(relationships), "relationships\n"))
```
```bash cURL
# cURL - Get concept details
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
# Get concept relationships
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128/relationships" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
```
## Step 5: Navigate Concept Hierarchies
Explore parent and child concepts:
```python Python
# Python - Get parent concepts (ancestors)
ancestors_response = requests.get(
f"{BASE_URL}/concepts/320128/ancestors",
headers=headers,
params={"max_levels": 2}
)
ancestors = ancestors_response.json()["data"]
print("Parent concepts:")
for ancestor in ancestors:
indent = " " * ancestor["level_of_separation"]
print(f"{indent}{ancestor['concept_name']}")
# Get child concepts (descendants)
descendants_response = requests.get(
f"{BASE_URL}/concepts/320128/descendants",
headers=headers,
params={"max_levels": 2}
)
descendants = descendants_response.json()["data"]
print("\nChild concepts:")
for descendant in descendants:
indent = " " * descendant["level_of_separation"]
print(f"{indent}{descendant['concept_name']}")
```
```javascript JavaScript
// JavaScript - Get parent concepts (ancestors)
const ancestorsParams = new URLSearchParams({ max_levels: 2 });
const ancestorsResponse = await fetch(
`${BASE_URL}/concepts/320128/ancestors?${ancestorsParams}`,
{ headers: headers }
);
const ancestorsData = await ancestorsResponse.json();
console.log('Parent concepts:');
ancestorsData.data.forEach(ancestor => {
const indent = ' '.repeat(ancestor.level_of_separation);
console.log(`${indent}${ancestor.concept_name}`);
});
// Get child concepts (descendants)
const descendantsParams = new URLSearchParams({ max_levels: 2 });
const descendantsResponse = await fetch(
`${BASE_URL}/concepts/320128/descendants?${descendantsParams}`,
{ headers: headers }
);
const descendantsData = await descendantsResponse.json();
console.log('\nChild concepts:');
descendantsData.data.forEach(descendant => {
const indent = ' '.repeat(descendant.level_of_separation);
console.log(`${indent}${descendant.concept_name}`);
});
```
```r R
# R - Get parent concepts (ancestors)
ancestors_response <- GET(
paste0(BASE_URL, "/concepts/320128/ancestors"),
headers,
query = list(max_levels = 2)
)
ancestors <- fromJSON(content(ancestors_response, "text"))$data
cat("Parent concepts:\n")
for (ancestor in ancestors) {
indent <- strrep(" ", ancestor$level_of_separation)
cat(paste(indent, ancestor$concept_name, "\n"))
}
# Get child concepts (descendants)
descendants_response <- GET(
paste0(BASE_URL, "/concepts/320128/descendants"),
headers,
query = list(max_levels = 2)
)
descendants <- fromJSON(content(descendants_response, "text"))$data
cat("\nChild concepts:\n")
for (descendant in descendants) {
indent <- strrep(" ", descendant$level_of_separation)
cat(paste(indent, descendant$concept_name, "\n"))
}
```
```bash cURL
# cURL - Get ancestors
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128/ancestors?max_levels=2" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
# Get descendants
curl -X GET "$OMOPHUB_BASE_URL/concepts/320128/descendants?max_levels=2" \
-H "Authorization: Bearer $OMOPHUB_API_KEY" | jq '.'
```
## Next Steps
Congratulations! You've successfully made your first API calls to OMOPHub. Here's what you can explore next:
Learn about OAuth setup and API key management
Master complex search queries and filters
Map concepts between different vocabularies
See real-world healthcare implementations
## Common Issues
If you receive a 401 Unauthorized error:
* Check that your API key is correct
* Ensure you're using the correct authorization header format
* Verify your API key hasn't expired
If you receive a 429 Too Many Requests error:
* Check your current rate limit in the response headers
* Implement exponential backoff for retries
* Consider upgrading your plan for higher limits
If your search returns no results:
* Try broader search terms
* Check spelling and remove special characters
* Use wildcards or partial matching
* Verify the vocabulary ID is correct
# Vocabulary Releases
Source: https://docs.omophub.com/vocabulary-versions
Learn more about available vocabulary release versions, how to select them, and version management
The OMOPHub API supports multiple vocabulary release versions through a sophisticated schema-based versioning system. Each release contains the complete OHDSI vocabulary data for that time period, ensuring data consistency and enabling time-based analysis.
## Current Available Releases
The following vocabulary releases are currently available in the system:
| Version | Release Date | Status | Default | Release Notes |
| ---------- | ------------ | ------ | ------- | ----------------------------------------------------------------------------------------- |
| **2025.2** | 2025-08-27 | Active | Yes | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20250827_1756288395.000000) |
| **2025.1** | 2025-02-27 | Active | No | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20250227_1740652703.000000) |
| **2024.2** | 2024-08-30 | Active | No | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20240830_1725004358.000000) |
| **2024.1** | 2024-02-29 | Active | No | [Link](https://github.com/OHDSI/Vocabulary-v5.0/releases/tag/v20240229_1709217174.000000) |
## Version Abbreviations
For convenience, the API supports several abbreviations for version specification:
| Abbreviation | Maps To | Description |
| ------------ | -------- | ---------------------------------------- |
| `latest` | `2025.2` | Always points to the most recent release |
| `default` | `2025.2` | Same as latest - the system default |
| `current` | `2025.2` | Alias for the default version |
| `2025v2` | `2025.2` | Quarter-based abbreviation |
| `2025v1` | `2025.1` | Quarter-based abbreviation |
| `2024v2` | `2024.2` | Quarter-based abbreviation |
| `2024v1` | `2024.1` | Quarter-based abbreviation |
## How to Select Vocabulary Versions
You can specify which vocabulary version to use in your API requests using any of these methods:
### Method 1: Query Parameter (Recommended)
Add the `vocab_release` parameter to any API endpoint:
```bash cURL
# Using specific version
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.1" \
-H "Authorization: Bearer YOUR_API_KEY"
# Using abbreviation
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024v1" \
-H "Authorization: Bearer YOUR_API_KEY"
# Using latest (default behavior)
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=latest" \
-H "Authorization: Bearer YOUR_API_KEY"
```
```javascript JavaScript
// Using specific version
const response = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.1', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
// Using abbreviation
const responseAbbrev = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024v1', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
// Using latest (default)
const responseLatest = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=latest', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
});
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
# Using specific version
response = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': 'diabetes',
'vocab_release': '2024.1'
}
)
# Using abbreviation
response_abbrev = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': 'diabetes',
'vocab_release': '2024v1'
}
)
# Using latest (default)
response_latest = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': 'diabetes',
'vocab_release': 'latest'
}
)
```
```r R
library(httr)
# Using specific version
response <- GET(
"https://api.omophub.com/v1/concepts/search",
add_headers(Authorization = "Bearer YOUR_API_KEY"),
query = list(
query = "diabetes",
vocab_release = "2024.1"
)
)
# Using abbreviation
response_abbrev <- GET(
"https://api.omophub.com/v1/concepts/search",
add_headers(Authorization = "Bearer YOUR_API_KEY"),
query = list(
query = "diabetes",
vocab_release = "2024v1"
)
)
# Using latest (default)
response_latest <- GET(
"https://api.omophub.com/v1/concepts/search",
add_headers(Authorization = "Bearer YOUR_API_KEY"),
query = list(
query = "diabetes",
vocab_release = "latest"
)
)
```
### Method 2: HTTP Header
Use the `X-Vocab-Release` header:
```bash cURL
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Vocab-Release: 2024.1"
```
```javascript JavaScript
const response = await fetch('https://api.omophub.com/v1/concepts/search?query=diabetes', {
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Vocab-Release': '2024.1'
}
});
```
```python Python
import requests
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Vocab-Release': '2024.1'
}
response = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={'query': 'diabetes'}
)
```
## Default Version Behavior
When no version is specified:
* **Default Version**: `2025.2`
* **Fallback**: If the default version is unavailable, the system uses the latest available active version
* **Caching**: Responses are cached with version-specific keys to ensure consistency
## Version Comparison Example
Here's how to compare concept availability across versions:
```python Python
import requests
def compare_concept_across_versions(concept_code, vocabulary_id):
"""Compare a concept across different vocabulary versions."""
headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
}
versions = ['2025.2', '2025.1', '2024.2', '2024.1']
results = {}
for version in versions:
response = requests.get(
'https://api.omophub.com/v1/concepts/search',
headers=headers,
params={
'query': concept_code,
'vocabulary_ids': vocabulary_id,
'vocab_release': version,
'page_size': 1
}
)
data = response.json()
if data.get('data'):
concept = data['data'][0]
results[version] = {
'found': True,
'concept_name': concept['concept_name'],
'standard_concept': concept.get('standard_concept'),
'valid_start_date': concept.get('valid_start_date'),
'valid_end_date': concept.get('valid_end_date')
}
else:
results[version] = {'found': False}
return results
# Example usage
concept_comparison = compare_concept_across_versions('E11.9', 'ICD10CM')
for version, info in concept_comparison.items():
if info['found']:
print(f"Version {version}: {info['concept_name']}")
else:
print(f"Version {version}: Not found")
```
```javascript JavaScript
async function compareConcept(conceptCode, vocabularyId) {
const headers = {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
};
const versions = ['2025.2', '2025.1', '2024.2', '2024.1'];
const results = {};
for (const version of versions) {
try {
const response = await fetch(
`https://api.omophub.com/v1/concepts/search?query=${conceptCode}&vocabulary_ids=${vocabularyId}&vocab_release=${version}&page_size=1`,
{ headers }
);
const data = await response.json();
if (data.data && data.data.length > 0) {
const concept = data.data[0];
results[version] = {
found: true,
conceptName: concept.concept_name,
standardConcept: concept.standard_concept,
validStartDate: concept.valid_start_date,
validEndDate: concept.valid_end_date
};
} else {
results[version] = { found: false };
}
} catch (error) {
results[version] = { found: false, error: error.message };
}
}
return results;
}
// Example usage
compareConcept('E11.9', 'ICD10CM').then(results => {
for (const [version, info] of Object.entries(results)) {
if (info.found) {
console.log(`Version ${version}: ${info.conceptName}`);
} else {
console.log(`Version ${version}: Not found`);
}
}
});
```
## Version-Specific Features
### Temporal Queries
Query concepts as they existed at specific points in time:
```bash
# Get diabetes concepts as they existed in 2024v2
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.2" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Historical Analysis
Compare vocabulary evolution over time:
```bash
# Compare concept relationships across versions
curl -X GET "https://api.omophub.com/v1/concepts/123456/relationships?vocab_release=2024.1" \
-H "Authorization: Bearer YOUR_API_KEY"
```
### Migration Support
Validate concept mappings when upgrading between versions:
```bash
# Check if concepts are still valid in newer version
curl -X GET "https://api.omophub.com/v1/concepts/validate?codes=E11.9,I10&vocab_release=latest" \
-H "Authorization: Bearer YOUR_API_KEY"
```
## Version Support Policy
### Active Versions
* **Current + 4 previous releases** are actively supported
### Deprecated Versions
* **Removal notice** provided 90 days before discontinuation
## Troubleshooting
### Common Issues
#### Version Not Found
```json
{
"error": "VOCABULARY_VERSION_NOT_FOUND",
"message": "Vocabulary version '2023.1' is not available",
"available_versions": ["2025.2", "2025.1", "2024.2", "2024.1"]
}
```
**Solution**: Use the `/v1/vocabularies/releases` endpoint to check available versions.
#### Version Mismatch in Results
If you receive unexpected results, verify the version is being applied correctly:
```bash
# Check which version was actually used
curl -X GET "https://api.omophub.com/v1/concepts/search?query=diabetes&vocab_release=2024.1" \
-H "Authorization: Bearer YOUR_API_KEY" \
-v # Verbose mode shows response headers
```
Look for the `X-Vocab-Release-Used` header in the response to confirm the version.
## FAQ
The system automatically uses the default version (`2025.2`). This provides the most recent vocabulary data and optimal performance through caching.
No, each API request operates against a single vocabulary version. To compare across versions, make separate requests for each version.
Major vocabulary updates typically occur twice a year (Q1, Q3), following the [OHDSI vocabulary release cycles](https://github.com/OHDSI/Vocabulary-v5.0/wiki/Release-planning).