# 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).