Swedish Healthcare Service - Health Condition Description
0.1.0 - CI Build Sweden

Swedish Healthcare Service - Health Condition Description - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions

Response Aggregation Rules

Response Aggregation Rules

This page documents how aggregating services combine responses from multiple source systems.

Overview

When a consumer queries a national or regional aggregator, the aggregator:

  1. Queries the engagement index to identify relevant source systems
  2. Sends parallel requests to all identified systems
  3. Collects responses from each system
  4. Aggregates the responses into a single result
  5. Returns the aggregated response to the consumer

Aggregation Process

sequenceDiagram
    participant C as Consumer
    participant A as Aggregator
    participant S1 as Source 1
    participant S2 as Source 2
    participant S3 as Source 3
    
    C->>A: Request (patientId)
    
    par Parallel Queries
        A->>S1: Request
        A->>S2: Request
        A->>S3: Request
    end
    
    par Responses
        S1-->>A: Response 1 (10 items)
        S2-->>A: Response 2 (5 items)
        S3-->>A: Timeout/Error
    end
    
    A->>A: Merge responses
    A->>A: Remove duplicates
    A->>A: Sort by timestamp
    A->>A: Apply limits
    
    A-->>C: Aggregated Response<br/>(15 items + partial failure info)

Deduplication Rules

Duplicate Detection

Records are considered duplicates if they have the same record.id (document identifier):

Example:

<!-- Record from Source System 1 -->
<record>
  <id root="550e8400-e29b-41d4-a716-446655440001"/>
  <timestamp>20241015100000</timestamp>
</record>

<!-- Same record from Source System 2 (duplicate) -->
<record>
  <id root="550e8400-e29b-41d4-a716-446655440001"/>
  <timestamp>20241015100500</timestamp>
</record>

Detection Logic:

Map<String, Record> uniqueRecords = new HashMap<>();

for (Record record : allRecords) {
    String recordId = record.getId().getRoot();
    
    if (!uniqueRecords.containsKey(recordId)) {
        uniqueRecords.put(recordId, record);
    } else {
        // Duplicate detected - keep most recent version
        Record existing = uniqueRecords.get(recordId);
        if (record.getTimestamp().isAfter(existing.getTimestamp())) {
            uniqueRecords.put(recordId, record);
        }
    }
}

Version Selection

When duplicates are found, the aggregator selects the most recent version:

Selection Criteria:

  1. Primary: Compare record.timestamp - keep latest
  2. Secondary: If timestamps equal, compare author.timestamp - keep latest
  3. Tertiary: If still equal, keep first encountered (arbitrary but consistent)

Example:

Source 1: record.id = abc123, timestamp = 20241015100000
Source 2: record.id = abc123, timestamp = 20241015103000
Result: Keep Source 2 version (later timestamp)

Cross-Domain Deduplication

Records are deduplicated within the same service domain only:

  • Care Documentation records deduplicated separately
  • Diagnosis records deduplicated separately
  • Alert Information records deduplicated separately
  • No cross-domain deduplication

Sorting Rules

Default Sort Order

Aggregated results are sorted by timestamp in descending order (newest first):

Collections.sort(records, (r1, r2) -> 
    r2.getRecordTimestamp().compareTo(r1.getRecordTimestamp())
);

Sort Priority

Primary Sort: record.timestamp (descending)

Record A: 20241127103000
Record B: 20241127094500
Record C: 20241115080000
Result order: A, B, C

Secondary Sort (if timestamps equal): documentId (lexicographic)

Record A: timestamp=20241127100000, id=doc-001
Record B: timestamp=20241127100000, id=doc-002
Result order: A, B (by id)

Service-Specific Sorting

Some services may have additional sorting requirements:

GetAlertInformation:

  • Active alerts (obsoleteTime empty) before obsolete alerts
  • Within each group, sort by validityTimePeriod.start descending

GetDiagnosis:

  • Sort by diagnosis date (if available), otherwise record.timestamp

Result Size Limits

Maximum Results Per Response

Default Limit: 500 records per response

Rationale:

  • Prevents excessive response sizes
  • Maintains acceptable response times
  • Protects consumer systems from overload

Pagination Support

When results exceed the limit, pagination is used:

<GetCareDocumentationResponse>
  <result>
    <resultCode>INFO</resultCode>
    <message>Showing first 500 of 1200 results</message>
  </result>
  
  <careDocumentation><!-- 500 records --></careDocumentation>
  
  <hasMore>true</hasMore>
  <hasMoreReference>ref-abc123-page2</hasMoreReference>
</GetCareDocumentationResponse>

Consumer Follow-up Request:

<GetCareDocumentation>
  <patientId>...</patientId>
  <hasMoreReference>ref-abc123-page2</hasMoreReference>
</GetCareDocumentation>

Per-Source Limits

Before Aggregation: Each source system may apply its own limits

  • Source System A returns 200 records (local limit)
  • Source System B returns 500 records (local limit)
  • Source System C returns 150 records

After Aggregation: Aggregator combines and applies overall limit

  • Combined: 850 records
  • After deduplication: 820 records
  • After aggregator limit (500): Return first 500, set hasMore=true

Partial Failure Handling

Failure Scenarios

Timeout:

Source System 1: OK (150 records)
Source System 2: OK (80 records)
Source System 3: Timeout after 30 seconds

Error Response:

Source System 1: OK (150 records)
Source System 2: ERROR (ACCESS_DENIED)
Source System 3: OK (200 records)

System Unavailable:

Source System 1: OK (150 records)
Source System 2: Connection refused
Source System 3: OK (200 records)

Aggregator Behavior

Continue with Partial Results:

  • Return successful responses
  • Include failure information in response

Response Structure:

<GetCareDocumentationResponse>
  <result>
    <resultCode>INFO</resultCode>
    <message>Partial results: 2 of 3 systems responded successfully</message>
  </result>
  
  <careDocumentation><!-- Records from successful systems --></careDocumentation>
  
  <failedSources>
    <failedSource>
      <sourceSystemHSAId>SE2321000016-9999</sourceSystemHSAId>
      <errorCode>TIMEOUT</errorCode>
      <errorMessage>No response within 30 seconds</errorMessage>
    </failedSource>
  </failedSources>
</GetCareDocumentationResponse>

Consumer Handling

Display Partial Results:

Consumer should:
1. Display available data
2. Inform user of incomplete results
3. Show which sources failed
4. Offer retry option

User Message Example:

"Showing care documentation from 2 of 3 available sources.
 Unable to retrieve data from: Hospital XYZ (timeout)
 [Retry]"

Timeout Management

Request Timeouts

Per-Source Timeout: 27 seconds maximum

  • Aggregator waits up to 27 seconds for each source
  • After timeout, proceeds with available results

Total Request Timeout: 30 seconds maximum

  • Includes: EI query + parallel source queries + aggregation
  • Enforced at platform level

Configuration Example:

ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<Response>> futures = new ArrayList<>();

// Submit parallel requests
for (SourceSystem source : sources) {
    Future<Response> future = executor.submit(() -> 
        querySource(source, request)
    );
    futures.add(future);
}

// Collect with timeout
List<Response> responses = new ArrayList<>();
for (Future<Response> future : futures) {
    try {
        Response response = future.get(27, TimeUnit.SECONDS);
        responses.add(response);
    } catch (TimeoutException e) {
        // Log timeout, continue with other responses
        recordTimeout(source);
    }
}

Data Consistency

Eventual Consistency

Engagement Index Lag:

  • Source systems update EI within 60 minutes
  • Aggregator may query system before EI updated
  • Recent data may be missed (eventual consistency)

Mitigation:

  • Document EI lag in user documentation
  • Consider querying key systems directly if critical
  • Monitor EI update lag metrics

Stale Data

Timestamp Comparison:

  • Use record.timestamp to identify freshness
  • Display timestamp to users
  • Allow users to judge data currency

Example:

Care Document from 2024-11-27 10:30 (today)
Care Document from 2024-11-15 08:00 (12 days ago)
Care Document from 2023-05-20 14:00 (1 year ago)

Error Propagation

Source System Errors

Logical Errors (e.g., ACCESS_DENIED):

<result>
  <resultCode>INFO</resultCode>
  <message>Unable to retrieve data from 1 source due to access restrictions</message>
</result>
<failedSources>
  <failedSource>
    <sourceSystemHSAId>SE2321000016-8888</sourceSystemHSAId>
    <errorCode>ACCESS_DENIED</errorCode>
    <errorMessage>Patient has blocked access to this care giver</errorMessage>
  </failedSource>
</failedSources>

Technical Errors:

<failedSources>
  <failedSource>
    <sourceSystemHSAId>SE2321000016-7777</sourceSystemHSAId>
    <errorCode>TECHNICAL_ERROR</errorCode>
    <errorMessage>Database connection failed</errorMessage>
  </failedSource>
</failedSources>

Aggregator Errors

Complete Failure (no successful responses):

<result>
  <resultCode>TECHNICAL_ERROR</resultCode>
  <message>Unable to retrieve data from any source system</message>
</result>

EI Failure:

<result>
  <resultCode>TECHNICAL_ERROR</resultCode>
  <message>Engagement Index unavailable</message>
</result>

Performance Optimization

Caching Strategy

Not Recommended for Patient Data:

  • Patient data changes frequently
  • Blocking status may change
  • Caching risks serving stale blocked data
  • Privacy and legal concerns

Acceptable for Metadata:

  • Code system definitions
  • Organization information
  • Static reference data

Parallel Processing

Thread Pool Configuration:

// Configure thread pool for parallel queries
int poolSize = Math.min(sourceCount, 20); // Max 20 parallel
ExecutorService executor = Executors.newFixedThreadPool(poolSize);

// Connection pool per source
HttpClient httpClient = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .executor(executor)
    .build();

Circuit Breaker

Protect Against Failing Sources:

CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("sourceSystem");

Supplier<Response> decoratedSupplier = 
    CircuitBreaker.decorateSupplier(circuitBreaker, 
        () -> querySource(source, request));

Try<Response> result = Try.of(decoratedSupplier);

States:

  • Closed: Normal operation
  • Open: Too many failures, skip system temporarily
  • Half-Open: Test if system recovered

Monitoring and Metrics

Key Metrics

Metric Description Target
Response Time Total aggregation time p95 < 10 sec
Source Response Time Per-source query time p95 < 5 sec
Deduplication Rate % of duplicate records Monitor trend
Partial Failure Rate % of requests with failures < 5%
Timeout Rate % of source timeouts < 2%

Alerting Thresholds

Warning Alerts:

  • Partial failure rate > 5%
  • Timeout rate > 2%
  • Response time p95 > 15 sec

Critical Alerts:

  • Partial failure rate > 20%
  • Timeout rate > 10%
  • Response time p95 > 27 sec

Best Practices

For Aggregator Implementers

  1. Use consistent record.id across systems
  2. Implement efficient deduplication (hash-based lookup)
  3. Set appropriate timeouts (source: 27s, total: 30s)
  4. Handle partial failures gracefully
  5. Include detailed failure information
  6. Monitor performance metrics
  7. Implement circuit breakers for failing sources

For Source System Implementers

  1. Generate unique, persistent record.id (use UUID)
  2. Update engagement index promptly (<60 min)
  3. Return consistent timestamps (record.timestamp)
  4. Respond within timeout (target <5 sec)
  5. Return appropriate error codes
  6. Implement pagination for large result sets

For Consumer Implementers

  1. Handle partial results appropriately
  2. Display failure information to users
  3. Implement retry logic for timeouts
  4. Handle pagination (hasMore/hasMoreReference)
  5. Display data freshness (timestamps)
  6. Log aggregation metadata for troubleshooting

See Also