Medical Instrument Middleware

Prolab LIS – Middleware Integration API Documentation

Overview

This document describes the API integration between Prolab LIS, Middleware, and Laboratory Analyzers. The middleware acts as a bridge, receiving pending test orders from Prolab LIS and forwarding analyzer results back to the system.

Integration Flow

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│ Prolab LIS  │ ◄─────► │  Middleware  │ ◄─────► │  Analyzer   │
│             │         │              │         │             │
└─────────────┘         └──────────────┘         └─────────────┘
     │                         │                        │
     │ 1. Get Pending Orders  │                        │
     │ ◄──────────────────────┘                        │
     │                         │                        │
     │                         │ 2. Send to Analyzer   │
     │                         │ ──────────────────────►│
     │                         │                        │
     │                         │ 3. Receive Results     │
     │                         │ ◄──────────────────────│
     │                         │                        │
     │ 4. Update Test Results  │                        │
     │ ◄───────────────────────┘                        │

Authentication

All API endpoints require JWT authentication. The middleware must authenticate before making any requests.

Endpoint

POST /dotnet_api/v1/auth/signin/

Request

{
  "email": "[email protected]",
  "password": "your-password"
}

Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "email": "[email protected]",
  "roles": ["SuperUser"],
  "businessOrganisationId": "4fd0a57b-2789-4e43-a6af-9c263f258a6f"
}

Usage

Include the token in the Authorization header for all subsequent requests:

Authorization: Bearer <token>

Token Expiration

Tokens expire after a set period. The middleware should implement token refresh logic to handle expired tokens gracefully.


Getting Pending Test Orders

The middleware needs to periodically poll Prolab LIS to retrieve pending test orders that need to be sent to analyzers.

Endpoint

GET /dotnet_api/v1/testresult/all

Query Parameters

ParameterTypeRequiredDescription
filterstringYesFilter field name (use status)
querystringYesFilter value (use Pending)
pageintegerNoPage number (default: 1)
limitintegerNoResults per page (default: 10, max: 100)
sortBystringNoSort field (default: CreatedAt)
sortOrderstringNoSort direction: ASC or DESC (default: DESC)

Request Example

GET /dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=50&sortBy=CreatedAt&sortOrder=ASC
Authorization: Bearer <token>

Response Structure

{
  "items": [
    {
      "id": "4310beb1-909e-4b5d-bad3-7748bd62cf71",
      "barcode": "1202463241407961",
      "orderCode": "ORD63241410P6G4",
      "orderNumber": "WO000000900955",
      "testId": "11223344-5566-7788-99aa-bbccddeeff00",
      "testName": "Cholésterol total",
      "testCode": "CHOT",
      "testAlias": "CHOL-T",
      "testPanel": "Lipid Panel",
      "result": "",
      "status": "Pending",
      "comments": "",
      "resultDate": "2025-11-06T09:00:00Z",
      "verifiedDate": "0001-01-01T00:00:00Z",
      "createdAt": "2025-11-06T09:00:00Z",
      "updatedAt": "2025-11-06T09:00:00Z"
    }
  ],
  "totalCount": 25,
  "currentPage": 1,
  "totalPages": 1,
  "statistics": {
    "totalCount": 25,
    "statusCounts": [
      {
        "status": "Pending",
        "count": 25
      }
    ]
  }
}

Key Fields for Middleware

  • barcode: Unique barcode identifier for the sample (use for barcode endpoint)
  • orderCode: Order code identifier (use for orderCode endpoint)
  • testCode: Test code to identify which test to update
  • testAlias: Alternative test identifier (can be used instead of testCode)
  • testName: Human-readable test name
  • status: Current status (should be “Pending” for new orders)

Polling Strategy

Recommended Approach:

  1. Poll every 30-60 seconds for new pending orders
  2. Track processed orders to avoid duplicates
  3. Use pagination to handle large batches
  4. Filter by date range if needed: startDate and endDate parameters

Example Polling Request:

# Get pending orders from the last hour
GET /dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=100&sortBy=CreatedAt&sortOrder=ASC&startDate=2025-11-06T08:00:00Z&endDate=2025-11-06T09:00:00Z

Sending Analyzer Results

After receiving results from the analyzer, the middleware sends them back to Prolab LIS using one of two endpoints. Both endpoints accept an array of test results, allowing batch updates.

Endpoint 1: Update by Barcode

PATCH /dotnet_api/v1/testresult/barcode/{barcode}

Use this endpoint when you have the sample barcode from the analyzer.

Endpoint 2: Update by Order Code

PATCH /dotnet_api/v1/testresult/ordercode/{orderCode}

Use this endpoint when you have the order code identifier.

Path Parameters

ParameterTypeRequiredDescription
barcodestringYes*Sample barcode identifier
orderCodestringYes*Order code identifier

*Use either barcode OR orderCode, not both.

Request Body

The request body is an array of test result objects. You can send a single test or multiple tests in one request.

[
  {
    "testCode": "CHOT",
    "result": "5.8",
    "secondaryResult": "580",
    "secondaryResultUnit": "mg/dL",
    "status": "Completed",
    "comments": "Analyzer result - within normal range",
    "interpretation": "Normal",
    "resultDate": "2025-11-06T10:30:00Z",
    "verifiedDate": "2025-11-06T10:30:00Z",
    "verifiedBy": "Analyzer-001"
  },
  {
    "testCode": "TG",
    "result": "1.65",
    "status": "Completed",
    "comments": "Normal triglycerides",
    "resultDate": "2025-11-06T10:30:00Z"
  }
]

Request Body Fields

FieldTypeRequiredDescription
testCodestringYes*Test code identifier
testAliasstringYes*Alternative test identifier (use if testCode not available)
resultstringNoPrimary test result value
secondaryResultstringNoSecondary result value (e.g., converted units)
secondaryResultUnitstringNoUnit for secondary result
statusstringNoTest status: Pending, Completed, Verified, Cancelled
commentsstringNoAdditional comments or notes
interpretationstringNoTest interpretation
resultDatedatetimeNoDate/time when result was obtained (ISO 8601 format)
verifiedDatedatetimeNoDate/time when result was verified (ISO 8601 format)
verifiedBystringNoUser/system that verified the result

*Either testCode OR testAlias must be provided to identify which test to update.

Response

Success Response (200 OK):

{
  "success": true,
  "message": "Test results updated successfully"
}

Error Response (400 Bad Request):

{
  "success": false,
  "message": "Failed to update test results"
}

Error Response (404 Not Found):

{
  "success": false,
  "message": "Test result with barcode 'BC123456789' not found"
}

Error Response (500 Internal Server Error):

{
  "success": false,
  "message": "An error occurred while updating test results"
}

Complete Request Examples

Example 1: Update by Barcode (Single Test)

PATCH /dotnet_api/v1/testresult/barcode/1202463241407961
Authorization: Bearer <token>
Content-Type: application/json

[
  {
    "testCode": "CHOT",
    "result": "5.8",
    "status": "Completed",
    "comments": "Analyzer result"
  }
]

Example 2: Update by Barcode (Multiple Tests)

PATCH /dotnet_api/v1/testresult/barcode/1202463241407961
Authorization: Bearer <token>
Content-Type: application/json

[
  {
    "testCode": "CHOT",
    "result": "5.8",
    "secondaryResult": "580",
    "secondaryResultUnit": "mg/dL",
    "status": "Completed",
    "comments": "Normal range"
  },
  {
    "testCode": "TG",
    "result": "1.65",
    "status": "Completed",
    "comments": "Normal triglycerides"
  },
  {
    "testAlias": "HDL",
    "result": "1.4",
    "status": "Completed"
  }
]

Example 3: Update by Order Code

PATCH /dotnet_api/v1/testresult/ordercode/ORD63241410P6G4
Authorization: Bearer <token>
Content-Type: application/json

[
  {
    "testCode": "MCYT",
    "result": "0.5",
    "status": "Completed",
    "comments": "Monocytes count normal"
  },
  {
    "testCode": "LYT",
    "result": "2.1",
    "status": "Completed",
    "comments": "Lymphocyte count within range"
  }
]

Test Identification Logic

The system uses the following logic to identify which test result to update:

  1. Filter by barcode/orderCode: First, filter test results by the provided barcode or orderCode
  2. Filter by testCode or testAlias:
    • If testCode is provided, match against Test.Code
    • If testAlias is provided (and testCode is not), match against Test.Alias
  3. Update: Update the first matching test result

Important: If multiple test results match (same barcode/orderCode and testCode), only the first one will be updated. Ensure your test codes are unique per sample.


Error Handling

Common Error Scenarios

1. Authentication Errors

401 Unauthorized

{
  "detail": "Unauthorized"
}

Solution: Re-authenticate and obtain a new token.

2. Test Not Found

404 Not Found

{
  "success": false,
  "message": "Test result with barcode 'BC123456789' not found"
}

Possible Causes:

  • Barcode/orderCode doesn’t exist
  • Test code/alias doesn’t match any pending test
  • Test result was already processed

Solution: Verify the barcode/orderCode and testCode are correct. Check if the test was already completed.

3. Validation Errors

400 Bad Request

{
  "success": false,
  "message": "Failed to update test results"
}

Possible Causes:

  • Missing required fields
  • Invalid data format
  • Test result not found

Solution: Verify request body structure and required fields.

4. Server Errors

500 Internal Server Error

{
  "success": false,
  "message": "An error occurred while updating test results"
}

Solution: Retry the request. If the error persists, check server logs.

Retry Strategy

Recommended Retry Logic:

  1. Immediate Retry: Retry once immediately for transient errors (network issues)
  2. Exponential Backoff: For persistent errors, wait 1s, 2s, 4s before retrying
  3. Max Retries: Limit to 3 retries before logging as failed
  4. Dead Letter Queue: Store failed requests for manual review

Example Retry Implementation (Pseudo-code):

async function sendResultsWithRetry(endpoint, data, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(endpoint, {
        method: 'PATCH',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      });
      
      if (response.ok) {
        return await response.json();
      }
      
      // Don't retry on 404 (not found) or 400 (bad request)
      if (response.status === 404 || response.status === 400) {
        throw new Error(`Client error: ${response.status}`);
      }
      
      // Retry on 500 or network errors
      if (attempt < maxRetries - 1) {
        await sleep(Math.pow(2, attempt) * 1000); // Exponential backoff
      }
    } catch (error) {
      if (attempt === maxRetries - 1) {
        throw error; // Final attempt failed
      }
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }
}

Best Practices

1. Authentication

  • Store tokens securely: Never log or expose tokens
  • Implement token refresh: Handle token expiration gracefully
  • Use service accounts: Create dedicated service accounts for middleware integration
  • Rotate credentials: Regularly rotate API credentials

2. Polling for Pending Orders

  • Poll frequency: Poll every 30-60 seconds (adjust based on volume)
  • Track processed orders: Maintain a list of processed order IDs to avoid duplicates
  • Use date filters: Filter by date range to only get recent orders
  • Handle pagination: Process all pages of results
  • Idempotency: Ensure the same order isn’t processed twice

3. Sending Results

  • Batch updates: Send multiple test results in a single request when possible
  • Validate data: Validate analyzer results before sending to Prolab LIS
  • Include timestamps: Always include resultDate and verifiedDate
  • Error handling: Implement robust error handling and retry logic
  • Logging: Log all requests and responses for debugging

4. Data Mapping

  • Test Code Mapping: Maintain a mapping table between analyzer test codes and Prolab LIS test codes
  • Unit Conversion: Handle unit conversions if analyzer uses different units
  • Value Formatting: Ensure numeric values are formatted correctly (string format)
  • Status Mapping: Map analyzer statuses to Prolab LIS statuses:
    • Pending → New order, not yet processed
    • Completed → Result available
    • Verified → Result verified by technician
    • Cancelled → Test cancelled

5. Performance

  • Connection pooling: Reuse HTTP connections
  • Async processing: Process multiple orders in parallel
  • Rate limiting: Respect API rate limits
  • Batch size: Send results in batches of 10-50 tests per request

6. Monitoring

  • Health checks: Monitor middleware health and API connectivity
  • Metrics: Track:
    • Number of pending orders retrieved
    • Number of results sent
    • Success/failure rates
    • Average processing time
  • Alerts: Set up alerts for:
    • High error rates
    • Authentication failures
    • Processing delays

Complete Example Flow

Scenario: Processing a Lipid Panel Order

Step 1: Authenticate

POST /dotnet_api/v1/auth/signin/
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "secure-password"
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "email": "[email protected]"
}

Step 2: Get Pending Orders

GET /dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=50
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response:

{
  "items": [
    {
      "barcode": "1202463241407961",
      "orderCode": "ORD63241410P6G4",
      "testCode": "CHOT",
      "testName": "Cholésterol total",
      "status": "Pending"
    },
    {
      "barcode": "1202463241407961",
      "orderCode": "ORD63241410P6G4",
      "testCode": "TG",
      "testName": "Triglycerides",
      "status": "Pending"
    }
  ],
  "totalCount": 2
}

Step 3: Send to Analyzer

The middleware formats the order and sends it to the analyzer (implementation depends on analyzer protocol).

Example Analyzer Request:

Barcode: 1202463241407961
Tests: CHOT, TG

Step 4: Receive Analyzer Results

The analyzer returns results:

Barcode: 1202463241407961
CHOT: 5.8 mmol/L
TG: 1.65 mmol/L

Step 5: Send Results to Prolab LIS

Option A: Using Barcode

PATCH /dotnet_api/v1/testresult/barcode/1202463241407961
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

[
  {
    "testCode": "CHOT",
    "result": "5.8",
    "status": "Completed",
    "comments": "Analyzer: Auto-analyzer-001",
    "resultDate": "2025-11-06T10:30:00Z",
    "verifiedDate": "2025-11-06T10:30:00Z"
  },
  {
    "testCode": "TG",
    "result": "1.65",
    "status": "Completed",
    "comments": "Analyzer: Auto-analyzer-001",
    "resultDate": "2025-11-06T10:30:00Z",
    "verifiedDate": "2025-11-06T10:30:00Z"
  }
]

Option B: Using Order Code

PATCH /dotnet_api/v1/testresult/ordercode/ORD63241410P6G4
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

[
  {
    "testCode": "CHOT",
    "result": "5.8",
    "status": "Completed",
    "comments": "Analyzer: Auto-analyzer-001",
    "resultDate": "2025-11-06T10:30:00Z"
  },
  {
    "testCode": "TG",
    "result": "1.65",
    "status": "Completed",
    "comments": "Analyzer: Auto-analyzer-001",
    "resultDate": "2025-11-06T10:30:00Z"
  }
]

Response:

{
  "success": true,
  "message": "Test results updated successfully"
}

Step 6: Verify Update

GET /dotnet_api/v1/testresult/all?filter=barcode&query=1202463241407961
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response:

{
  "items": [
    {
      "testCode": "CHOT",
      "result": "5.8",
      "status": "Completed",
      "comments": "Analyzer: Auto-analyzer-001"
    },
    {
      "testCode": "TG",
      "result": "1.65",
      "status": "Completed",
      "comments": "Analyzer: Auto-analyzer-001"
    }
  ]
}

Middleware Implementation Example

Flow Structure

[HTTP Request: Get Pending Orders]
    ↓
[Parse JSON]
    ↓
[Filter & Group by Barcode]
    ↓
[Send to Analyzer]
    ↓
[Receive Analyzer Results]
    ↓
[Format Results]
    ↓
[HTTP Request: Update Results]
    ↓
[Handle Response]

Example Implementation

1. Get Pending Orders

HTTP Request Configuration:

  • Method: GET
  • URL: http://localhost:8081/dotnet_api/v1/testresult/all?filter=status&query=Pending&limit=50
  • Headers:
{
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
}

2. Update Results

HTTP Request Configuration:

  • Method: PATCH
  • URL: http://localhost:8081/dotnet_api/v1/testresult/barcode/{barcode}
  • Headers:
{
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
}

Example Data Processing:

// Group results by barcode
const results = response.items;
const grouped = {};

results.forEach(item => {
  if (!grouped[item.barcode]) {
    grouped[item.barcode] = [];
  }
  grouped[item.barcode].push({
    testCode: item.testCode,
    result: item.analyzerResult, // From analyzer
    status: "Completed",
    comments: "From analyzer",
    resultDate: new Date().toISOString()
  });
});

// Process each barcode group
Object.keys(grouped).forEach(barcode => {
  sendUpdateRequest(barcode, grouped[barcode]);
});