Data Acquisition Service
Overview
The Data Acquisition service is responsible for connecting and querying a tenant's endpoint for FHIR resources that are needed to evaluate patients for a measure. For Epic installations, Link Cloud is utilizing the Epic FHIR STU3 Patient List resource to inform which patients are currently admitted in the facility. While this is the current solution to acquiring the patient census, there are other means of patient acquisition being investigated (ADT V2, Bulk FHIR) to provide universal support across multiple EHR vendors.
In addition to FHIR endpoints, the service supports SFTP-based acquisition for tenants that drop extract files (e.g., Cerner CCL pipe-delimited extracts) onto a remote SFTP server rather than exposing a queryable FHIR API. See SFTP Data Acquisition below.
flowchart LR
nDataAcquisitionService_3BF82DDB["Service: Data Acquisition Service"]
nPatientCensusScheduled_2383C0C6["Event: PatientCensusScheduled"] -->|consumed by| nDataAcquisitionService_3BF82DDB
nDataAcquisitionRequested_4BBEB8F8["Command: DataAcquisitionRequested"] -->|handled by| nDataAcquisitionService_3BF82DDB
nDataAcquisitionService_3BF82DDB -->|provides| nGetFacilityConfig_32C58A79["Query: GetFacilityConfig"]
nDataAcquisitionService_3BF82DDB -->|provides| nGetServiceInfo_13F02E06["Query: GetServiceInfo"]
subgraph nDataAccess_2A523756_domain["Domain: Data Access"]
nDataAcquisitionRequested_4BBEB8F8
nDataAcquisitionService_3BF82DDB
end
subgraph nReport_4D4FBBDA_domain["Domain: Report"]
nPatientCensusScheduled_2383C0C6
end
subgraph nTenant_379CD514_domain["Domain: Tenant"]
nGetFacilityConfig_32C58A79
nGetServiceInfo_13F02E06
end
Common Configurations
- Swagger
- Azure App Configuration
- Kafka Configuration
- Kafka Consumer Retry Configuration
- Service Registry Configuration
- CORS Configuration
- Token Service Configuration
- Service Authentication
- SQL Server Database Configuration
- Data Acquisition Service Configuration
- SFTP Acquisition Settings
Features and Functionality
Data Acquisition is a crucial step in the report generation pipeline, responsible for obtaining clinical data from external systems, such as FHIR R4 endpoints within electronic health record (EHR) systems. By systematically acquiring and managing data, it ensures that downstream processes like evaluation and reporting are equipped with the necessary information.
Key Roles of Data Acquisition
- Census Acquisition:
- Retrieves recent patient admits from the EHR.
- Serves as the initial step for identifying patients relevant to quality measure evaluations.
- Individual Patient Data Acquisition:
- Acquires detailed FHIR data for individual patients identified in the census.
- Includes all essential data elements required for initial measure evaluations.
- Supplemental Data Acquisition:
- Retrieves additional data elements that may not be needed for initial evaluations but are desirable for complete submission and reporting.
Where Data Acquisition Fits in the Pipeline
Data acquisition plays a role in three distinct stages of the report generation pipeline:
- Patient Identification:
- Acquiring a list of patients from the EHR to determine the cohort for evaluation.
- Initial Data Collection and Evaluation:
- Obtaining the primary dataset needed for evaluating quality measures.
- Determines whether a patient qualifies for reporting based on initial measure criteria.
- Supplemental Data Collection:
- Acquiring additional, non-essential data to enrich the submission.
- Completes the dataset for comprehensive evaluation and reporting.
Progressive Querying
To optimize data acquisition, the system employs a technique called Progressive Querying.
During progressive querying, data is acquired in stages to meet the evaluation pipeline's needs. It flows between services via Kafka topics/events, starting from Data Acquisition Worker Service to normalization, through initial evaluation to determine patient relevance, and back to acquire supplemental data, which is then normalized and re-evaluated.
This method minimizes the data retrieved from the EHR by acquiring only what is necessary at each stage of the pipeline:
- Initial Querying: Focuses on essential data needed to evaluate measures and determine patient inclusion.
- Supplemental Querying: Retrieves additional data elements after initial evaluations confirm patient relevance.
- Final Evaluation: Combines initial and supplemental data for comprehensive measure evaluation and reporting.
Benefits of Progressive Querying
- Efficiency: Reduces the volume of data retrieved from the EHR, optimizing system performance.
- Precision: Focuses on acquiring only data that is needed for specific stages in the pipeline.
- Scalability: Supports large-scale operations by limiting unnecessary data transfers.
Bulk FHIR in Data Acquisition
Bulk FHIR is a mechanism under exploration for acquiring data efficiently. However, several limitations impact its general use in data acquisition workflows:
- Challenges with Bulk FHIR:
- Most implementations lack sufficient support for acquiring specific patient data.
- Filtering returned data is often not robust enough.
- To align with the goal of acquiring only necessary data, the system does not currently implement Bulk FHIR for general initial or supplemental data acquisition.
- Use Cases for Bulk FHIR:
- Patient Census Identification:
- Bulk FHIR is a viable solution for identifying the "census of patients," analogous to using the FHIR "List" endpoint.
- It can acquire "Patient" resources for a group of patients associated with a query, filter, or registry in the EHR.
- This use case is limited to identifying patients of interest and does not address broader data acquisition.
Configuration for Data Acquisition
Data acquisition is configurable per tenant, ensuring flexibility to accommodate diverse EHR systems and data requirements. Key configurable parameters include:
Base FHIR URL:
- For general data acquisition.
- For FHIR List (patients of interest) retrieval.
Authentication Information:
- Configured per facility on the
FhirQueryConfiguration(data queries) andFhirListConfiguration(patient census) entities. Each entity holds an optionalAuthenticationblock, managed viaapi/data/{facilityId}/{configType}/authentication(GET/POST/PUT/DELETE,IsLinkAdminpolicy).configTypeisfhirQueryConfigurationorfhirQueryListConfiguration. - Secrets are never stored on the configuration row. Credential-bearing fields (
ClientId,ClientSecret,UserName,Password, andCustomHeadersvalues) hold secret manager key names. Actual secrets are resolved at runtime via theISecretManagerinterface against whichever backend the deployment is configured to use. - Successful token responses for Epic and OAuth flows are cached in-memory per
FacilityIdfor the lifetime ofexpires_in.
Supported
AuthTypevalues and their required fields:AuthType Description Required Fields EpicEpic FHIR JWT client assertion (RS256). Issues a signed JWT to TokenUrl.TokenUrl,Audience,ClientId(secret key),Key(RSA private key, PEM with escaped newlines)BasicHTTP Basic Authentication ( Authorization: Basic <base64(user:pass)>).UserName(secret key),Password(secret key)OAuthOAuth 2.0 client_credentialsgrant againstTokenUrl.TokenUrl,ClientId(secret key),ClientSecret(secret key),ScopeCustomHeadersStatic custom headers injected on each request (header value resolved from secret manager). CustomHeaders(non-emptyDictionary<string, string>: header name → secret key)OpenIDConnectReserved in the AuthTypeenum but not yet implemented (no service handler).— - Configured per facility on the
Patient Census Retrieval:
- FHIR List "id" or Bulk FHIR "Group ID" used for identifying the patient cohort.
EHR Query Throttling/Limitations:
- Configurable settings to respect EHR query limitations (e.g., maximum queries per minute).
Configuring Query Plans
Types of query plans:
- Daily
- Weekly
- Monthly
- Discharge
"Discharge" query plans are used when a patient is discharged from the hospital. This plan is triggered by a discharge event and is used to acquire data for the patient.
All other types of query plans are used to acquire data for patients who are currently in the hospital triggered by the end date/time of the scheduled report. The tenant's timezone is used for this so that if the reporting period ends at 12:59:59 PM, that represents 12:59:59 PM in the tenant's timezone, not UTC time.
All times are stored in UTC format. The tenant's time zone is configured with a valid value from IANA.
Initial / Supplemental Queries
The previously mentioned progressive query phases (initial, supplemental) are configurable through the query plan. Each configured phase can contain a list of FHIR resources that must be acquired from the configured endpoint.
Query Types
For each FHIR resource that must be queried, there are two main query types that are supported:
- Parameter: Parameters that will be appended to the FHIR search for the configured resource.
- Reference: Any FHIR references for the configured resource found in other acquired resources for that phase will be queried for.
Example Plan
Below is an example of a monthly query plan that's configured to acquire the following resources:
- Initial Query Phase:
| Resource | Query Type | Description |
|---|---|---|
| Patient | N/A | Patient resources will always be queried for each configured query plan. No configuration is needed. |
| Encounter | Parameter | The following parameters are included in the search: patient id, period start date and period end date |
| Location | Reference | Any Location FHIR references found in other acquired initial resources will be queried for. 'SearchPost' will perform an HTTP POST search for Locations rather than a GET. If an OperationType is not added, it will default to performing a GET search. Link Here for more info on FHIR searches. |
- Supplemental Query Phase:
| Resource | Query Type | Description |
|---|---|---|
| MedicationRequest | Parameter | The following parameters are included in the search: patient Id, period start date, period end date, and the literal value 'order' in the MedicationRequest.intent element. |
| Medication | Reference | Any Medication FHIR references found in other acquired supplemental resources will be queried for. |
{
"PlanName": "NHSNdQMAcuteCareHospitalInitialPopulation",
"FacilityId": "st-marys-hospital",
"EHRDescription": "",
"LookBack": "P0D",
"Type": "Monthly",
"InitialQueries": {
"0": {
"ResourceType": "Encounter",
"QueryConfigType": "Parameter",
"Parameters": [
{
"ParameterType": "Variable",
"Name": "patient",
"Variable": 0,
"Format": null
},
{
"ParameterType": "Variable",
"Name": "date",
"Variable": 1,
"Format": "ge{0}"
},
{
"ParameterType": "Variable",
"Name": "date",
"Variable": 3,
"Format": "le{0}"
}
]
},
"1": {
"QueryConfigType": "Reference",
"ResourceType": "Location",
"OperationType": "SearchPost",
"Paged": 100
}
},
"SupplementalQueries": {
"0": {
"QueryConfigType": "Parameter",
"ResourceType": "MedicationRequest",
"Parameters": [
{
"ParameterType": "Variable",
"Name": "patient",
"Variable": 0,
"Format": null
},
{
"ParameterType": "Variable",
"Name": "authoredon",
"Variable": 1,
"Format": "ge{0}"
},
{
"ParameterType": "Variable",
"Name": "authoredon",
"Variable": 3,
"Format": "le{0}"
},
{
"ParameterType": "Literal",
"Name": "intent",
"Literal": "order"
}
]
},
"1": {
"QueryConfigType": "Reference",
"ResourceType": "Medication",
"OperationType": "SearchPost",
"Paged": 100
}
}
}
Configuring Census and Data Sources
Data sources (where the FHIR server is located and how to authenticate) are configured via "Query Configs". There is currently no association between a query plan and data source. Whenever data acquisition attempts to execute a query plan against a data source, it uses the FHIR server and authentication method specified by the "Query Config", for the specified facility/tenant.
TODO: Add details about how to authenticate against Epic, Cerner, Basic, and/or OAuth data sources.
Query Plans and Acquisition Logs
The Data Acquisition Service uses Query Plans to define the strategy for retrieving data. These plans are translated into Data Acquisition Logs, which represent discrete units of work to be executed by the worker service.
Acquisition Log Lifecycle
Each acquisition task follows a strictly managed state machine:
- Scheduled: The log entry is created but not yet ready for execution. This usually happens when a Data Acquisition Requested event is received.
- Ready: The system has determined that the log is eligible for execution. A Ready to Acquire event is produced for the worker.
- Queued: The worker has received the Ready to Acquire event and successfully "claimed" the log.
- InProgress: The worker is actively querying the FHIR endpoint and processing resources.
- Completed: All resources for the log have been successfully acquired and normalized.
- Failed: An error occurred during acquisition that exceeded the maximum retry attempts.
Log Creation Process
When a Data Acquisition Requested event is received, the service:
- Retrieves the appropriate Query Plan for the facility and report type.
- Identifies the target patients (either from the event itself or by creating a Census log).
- Generates a set of
DataAcquisitionLogentries for each required resource type and patient, assigned to the Initial phase.
Execution and Dependency Management
The system determines when a log should be executed based on its Status and QueryPhase:
- Phase-Based Execution: Logs are typically executed in phases. Initial phase logs are created first. Once initial data is acquired and evaluated by downstream services, they may trigger Supplemental acquisition by sending new Data Acquisition Requested events with a supplemental phase flag.
- Wait Logic: Supplemental logs are not created until the initial evaluation confirms the patient's relevance to the report. This prevents unnecessary data retrieval for patients who do not meet the report's criteria.
- Retry Mechanism: If a log fails due to transient issues (e.g., network timeout), it is incremented and returned to a
PendingorScheduledstate for retry, up to a maximum of 5 attempts.
Query Plan Structure
A Query Plan relates to logs by defining the "blueprint" for their creation:
- ResourceType: The FHIR resource to be queried (e.g., Patient, Encounter, Observation).
- QueryConfigType:
Parameter: Appends specific FHIR search parameters (e.g.,date=ge2024-01-01).Reference: Instructs the system to find references to this resource type within other already acquired resources.
- QueryPhase: Categorizes the query into
InitialorSupplemental.
For more details on how these logs are processed, see the Data Acquisition Worker Service.
Tail Messages
Data Acquisition emits a tail Resource Acquired event for each (patientId, reportTrackingId, queryType) tuple when all resources for the phase are produced. This tail sets acquisitionComplete = true and signals downstream services to proceed. See:
- Events → Resource Acquired
- Docs → Tail Messages: Patient Completion Signals
FHIR List Acquisition
Epic EHR deployments will have FHIR Lists available for Link to track recently admitted and discharged patients within a facility. Currently, there are six separate FHIR List Id's that track the time difference since the patient was admitted:
| Status | Type Frame |
|---|---|
| Admitted | < 24 Hours |
| Admitted | 24-48 Hours |
| Admitted | > 24 Hours |
| Discharged | < 24 Hours |
| Discharged | 24-48 Hours |
| Discharged | > 48 Hours |
List Acquisition Processing Sequence
The following demonstrates the workflow when the Data Acquisition service consumes a PatientCensusScheduled event for an Epic EHR facility:
sequenceDiagram
participant CS as Census Service
participant SQL as SQL Server
participant K as Kafka
participant DAS as Data Acquisition Service
participant W as Data Acquisition Worker Service
participant EHR as EHR (FHIR)
CS->>K: Produces PatientCensusScheduled
K->>DAS: Consumes PatientCensusScheduled
DAS->>SQL: Create Log
DAS->>K: Produce ReadyToAcquire
K->>W: Consumes ReadyToAcquire
W->>EHR: Request FHIR List
EHR-->>W: FHIR List Response
W->>K: Produce PatientsListAcquired
K->>CS: Consume PatientsListAcquired
CS->>CS: Construct PatientEvent payload
CS->>SQL: Insert into Data Source (immutable)
CS->>CS: Build Materialized View (current state)
CS->>SQL: Insert into Materialized View Store
CS->>K: Produce PatientEvent (if patient is discharged)
SFTP Data Acquisition
Some EHR deployments (notably Cerner via CCL extracts) cannot expose a queryable FHIR endpoint for census or resource acquisition. For these tenants, Data Acquisition supports pulling extract files from a per-facility SFTP server, parsing them into Link's domain model, and emitting Kafka events that drive the rest of the report generation pipeline.
SFTP acquisition is implemented as an independent acquisition channel from FHIR querying. It does not consume DataAcquisitionRequested or produce ReadyToAcquire — instead, a background worker polls the SftpAcquisitionLog table on a fixed interval, opens an SFTP session per facility, and dispatches files to a processor selected by the log's AcquisitionType and SubType.
When to Use SFTP Acquisition
| Scenario | Channel |
|---|---|
| Tenant exposes a FHIR R4 endpoint with patient list / search support | FHIR (Query Plans) |
Tenant drops Cerner CCL census extracts (.dat, pipe-delimited) on SFTP |
SFTP — CernerCCLExtract |
| Tenant drops generic delimited resource extracts on SFTP | SFTP — extensible via FileParsingConfiguration |
Architecture
flowchart LR
subgraph Tenant["Tenant Environment"]
EHR[(EHR /<br/>Extract Job)]
SFTP[(SFTP Server)]
end
subgraph Link["Link Cloud — Data Acquisition"]
Cfg[SftpConfiguration<br/>per facility]
Sched[PatientCensusService<br/>creates SftpAcquisitionLog]
Worker[SftpAcquisitionService<br/>background poller]
Handler[SftpAcquisitionHandler<br/>batch + parallelism]
Proc[ISftpAcquisitionProcessor<br/>e.g. CernerCCLExtractProcessor]
Parser[IFileParser<T><br/>e.g. CernerCclExtractParser]
Secrets[(Secret Manager<br/>sftp-credentials-{orgId})]
Log[(SftpAcquisitionLog)]
end
subgraph Downstream["Downstream"]
Kafka{{Kafka<br/>CernerPatientsAcquired}}
Census[Census Service<br/>CernerPatientsAcquiredListener]
end
EHR -->|drops extract files| SFTP
Sched -->|on PatientCensusScheduled| Log
Worker -->|every JobIntervalSeconds| Log
Worker --> Handler
Handler -->|reads creds| Secrets
Handler -->|reads server config| Cfg
Handler -->|opens session per facility| SFTP
Handler --> Proc
Proc --> Parser
Proc -->|publishes per file| Kafka
Proc -->|move/delete file| SFTP
Proc -->|update status & benchmarks| Log
Kafka --> Census
Acquisition Lifecycle
Each SftpAcquisitionLog follows its own state machine, distinct from the FHIR DataAcquisitionLog lifecycle described above.
stateDiagram-v2
[*] --> Pending : Census schedule creates log
Pending --> Processing : Worker claims log
Processing --> Processed : File downloaded,<br/>parsed, event published
Processing --> Failed : Transient error<br/>(network, parse, EHR)
Failed --> Pending : Retry eligible<br/>(exponential backoff)
Failed --> MaxRetriesReached : RetryAttempts > MaxRetryAttempts
Processed --> [*]
MaxRetriesReached --> [*]
Retries use exponential backoff between BaseRetryDelaySeconds and MaxRetryDelaySeconds (e.g., 60s, 120s, 240s, 480s, ... capped at 3600s). The log's RetryAttempts field is incremented on each transition through Failed; once it exceeds MaxRetryAttempts, the log is parked in MaxRetriesReached and excluded from future polls.
Per-File Processing Sequence
sequenceDiagram
participant W as SftpAcquisitionService
participant H as SftpAcquisitionHandler
participant S as SftpSession (Renci.SshNet)
participant SFTP as SFTP Server
participant P as Processor
participant FP as IFileParser
participant K as Kafka
participant DB as SftpAcquisitionLog
loop every JobIntervalSeconds (default 30s)
W->>H: ExecuteAsync()
H->>DB: Get pending + retry-eligible logs
DB-->>H: logs grouped by FacilityId
loop per facility (parallel up to MaxConcurrency)
H->>S: Open session (Host, Port, creds)
S->>SFTP: Connect + authenticate
loop per log in facility batch
H->>P: ProcessWithSessionAsync(log, session)
P->>S: ListFilesAsync(remoteDir, pattern)
S->>SFTP: LIST
SFTP-->>S: matching files
P->>S: DownloadFileAsync(file)
S->>SFTP: GET
SFTP-->>P: file stream
P->>FP: ParseAsync(stream, config)
FP-->>P: parsed records
P->>K: Produce CernerPatientsAcquired
alt ProcessedDirectory configured
P->>S: MoveFileAsync(processedDir)
else RemoveAfterProcessing = true
P->>S: DeleteFileAsync()
end
P->>DB: Status = Processed (+ benchmarks)
end
H->>S: Dispose session
end
end
Configuration
SFTP acquisition is configured at three layers: service-wide settings, per-facility connection / acquisition definitions, and secret-managed credentials.
SFTP Acquisition Settings
Worker-level tuning, sourced from the SftpAcquisition configuration section in appsettings.json / Azure App Configuration. These apply to the SftpAcquisitionService background host.
| Property | Description | Required | Default Value | Secret? |
|---|---|---|---|---|
SftpAcquisition__JobIntervalSeconds |
Interval between background polls of the SftpAcquisitionLog table. |
No | 30 |
No |
SftpAcquisition__MaxBatchSize |
Maximum number of logs processed in a single poll cycle. | No | 10 |
No |
SftpAcquisition__EnableParallelProcessing |
When true, multiple facilities are processed concurrently. |
No | true |
No |
SftpAcquisition__ParallelProcessingThreshold |
Number of distinct facilities in a batch above which parallel mode engages. | No | 5 |
No |
SftpAcquisition__MaxConcurrency |
Maximum number of facilities processed in parallel (degree of parallelism). | No | 3 |
No |
SftpAcquisition__MaxRetryAttempts |
Maximum retry attempts before a log moves to MaxRetriesReached. |
No | 3 |
No |
SftpAcquisition__BaseRetryDelaySeconds |
Base delay for exponential backoff between retries. | No | 60 |
No |
SftpAcquisition__MaxRetryDelaySeconds |
Cap on the exponential backoff delay. | No | 3600 |
No |
Example:
"SftpAcquisition": {
"EnableParallelProcessing": true,
"ParallelProcessingThreshold": 5,
"MaxConcurrency": 3,
"MaxBatchSize": 10,
"JobIntervalSeconds": 30,
"MaxRetryAttempts": 3,
"BaseRetryDelaySeconds": 60,
"MaxRetryDelaySeconds": 3600
}
A separate SftpValidation section (bound to SftpValidationSettings) holds validation rules applied to incoming SftpConfiguration payloads via the configuration API.
Per-Facility SFTP Configuration
Each facility that uses SFTP acquisition has exactly one SftpConfiguration row, managed via the /api/data/sftp-configurations endpoints on the Data Acquisition service. This describes how to connect to the tenant's SFTP server and what files to look for.
| Field | Description |
|---|---|
FacilityId |
Tenant facility this configuration applies to. |
Host |
SFTP server hostname or IP. |
Port |
SFTP port (default 22). |
RemoteDirectory |
Default directory on the SFTP server to scan. Can be overridden per acquisition type. |
Timeout |
Connection timeout in seconds (default 60). |
AuthenticationProtocol |
Authentication scheme. Currently Basic (username + password) is supported. |
RemoveAfterProcessing |
When true and no ProcessedDirectory is configured, files are deleted after successful processing. |
EnableBenchmarking |
When true, per-stage timings (connect / list / download / parse / publish) are written to the log. |
AcquisitionConfigurations |
List of SftpAcquisitionTypeConfiguration entries — one per kind of file the facility produces. |
Each SftpAcquisitionTypeConfiguration describes a specific class of file:
| Field | Description |
|---|---|
AcquisitionType |
Census or Resources — selects which workflow consumes the parsed output. |
SubType |
None or CernerCCLExtract — selects the processor implementation. |
RemoteDirectory |
Optional override of the parent RemoteDirectory. |
ProcessedDirectory |
Optional archive path. If set, files are moved here after a successful run instead of being deleted. |
FileNamePattern |
Glob used to match files (e.g., lantana_census_extract_*.dat). |
ParsingConfiguration |
Optional FileParsingConfiguration for generic delimited parsers. Cerner CCL Extract has a built-in parser. |
FileParsingConfiguration (used by ConfigurableDelimitedParser):
| Field | Description |
|---|---|
FileExtension |
Expected file extension (e.g., .dat, .csv). |
ParserType |
Delimited, FixedWidth, Json, or Xml. |
Delimiter |
Field delimiter for Delimited (default pipe \|). |
HasHeaderRow |
Whether the first row is a header (default true). |
DateFormat |
DateTime.ParseExact format string (default yyyyMMddHHmmss). |
IdSuffixToStrip |
Suffix to remove from identifier columns (e.g., .00 from Cerner numeric IDs). |
ColumnMappings |
Map of logical field name → zero-based column index. |
AdditionalProperties |
Free-form parser-specific settings. |
Credentials
SFTP credentials are never stored on the SftpConfiguration row. They are persisted via the ISecretManager interface in whichever backend the deployment is configured to use, under the key:
sftp-credentials-{organizationId}
The payload is an SftpCredentialsModel (Username, Password). The configuration API exposes endpoints to set, retrieve status (without revealing the password), and delete credentials independently of the connection metadata.
Connection Test
A dedicated test endpoint (/api/data/sftp-configurations/{facilityId}/test) opens a live SFTP session using the saved configuration and credentials, lists the configured RemoteDirectory, and reports success or the underlying error. Use this when onboarding a tenant or after rotating credentials.
Kafka Events
SFTP acquisition emits events on a separate topic from FHIR acquisition. The current processor produces:
CernerPatientsAcquired— published byCernerCCLExtractProcessoronce per successfully parsed census file. Consumed by the Census service'sCernerPatientsAcquiredListener, which translates encounters intoPatientEventadmit/discharge messages that feed the standard census workflow.
SFTP processing intentionally does not emit PatientListsAcquired or ReadyToAcquire; those events remain specific to the FHIR-driven acquisition path.
Observability
When EnableBenchmarking is set on an SftpConfiguration, the worker writes an SftpAcquisitionBenchmark entry per processed file containing per-stage durations (connection, listing, download, parse, publish) and item counts. These are stored on the SftpAcquisitionLog and are queryable via /api/data/sftp-logs. The Notes field on the log captures human-readable error messages when a run fails.
Logging
The Logging configuration defines the logging levels for different parts of the application.
| Property | Description | Required | Default Value | Secret? |
|---|---|---|---|---|
| Logging__LogLevel__Default | Default log level | No | No | |
| Logging__LogLevel__Microsoft.AspNetCore | Log level for asp net core logs | No | No | |
| Logging__LogLevel__System | Level for system logs | No | No |
Database Schema
| Name | Type | Required? | Length |
|---|---|---|---|
| MigrationId | string | Yes | 150 |
| ProductVersion | string | Yes | 32 |
| Name | Type | Required? | Length |
|---|---|---|---|
| FacilityId | string | Yes | 128 |
| Priority | string | Yes | 50 |
| PatientId | string | No | |
| CorrelationId | string | No | 450 |
| FhirVersion | string | No | |
| QueryType | string | No | 50 |
| QueryPhase | string | No | 50 |
| Status | string | No | 50 |
| ExecutionDate | string (date-time) | No | |
| RetryAttempts | integer | No | |
| CompletionDate | string (date-time) | No | |
| CompletionTimeMilliseconds | integer | No | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No | |
| IsCensus | boolean | Yes | |
| ReportableEvent | integer | No | |
| TailSent | boolean | Yes | |
| ReportTrackingId | string | No | |
| TraceId | string | No | 64 |
| Id | integer | Yes | |
| IsDeleted | boolean | Yes | |
| SiblingCount | integer | No | |
| ReferenceResourceType | string | No | 128 |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | integer | Yes | |
| DataAcquisitionLogId | integer | Yes | |
| Note | string | Yes | |
| CreateDate | string (date-time) | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| DataAcquisitionLogId | integer | Yes | |
| ReferenceResourceId | string | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | integer | Yes | |
| DataAcquisitionLogId | integer | Yes | |
| ResourceId | string | Yes | 512 |
| CreateDate | string (date-time) | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| FacilityId | string | Yes | |
| FhirBaseServerUrl | string | Yes | |
| Authentication | string | No | |
| EHRPatientLists | string | Yes | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| FacilityId | string | Yes | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No | |
| Paged | integer | No | |
| QueryParameters | string | Yes | |
| QueryType | string | Yes | |
| MeasureId | string | No | |
| isReference | boolean | No | |
| CensusListId | string | No | |
| CensusPatientStatus | integer | No | |
| CensusTimeFrame | integer | No | |
| DataAcquisitionLogId | integer | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| FacilityId | string | Yes | |
| FhirServerBaseUrl | string | Yes | |
| Authentication | string | No | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No | |
| MaxAcquisitionPullTime | string (time) | No | |
| MaxConcurrentRequests | integer | No | |
| MinAcquisitionPullTime | string (time) | No | |
| MaxRetries | integer | No |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| FhirQueryId | string | Yes | |
| ResourceType | string | Yes | 255 |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | integer | Yes | |
| DataAcquisitionLogId | integer | Yes | |
| FacilityId | string | Yes | 256 |
| CorrelationId | string | Yes | 64 |
| ResourceType | string | Yes | 128 |
| ResourceId | string | Yes | 256 |
| CreateDate | string (date-time) | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| PlanName | string | Yes | |
| FacilityId | string | Yes | |
| EHRDescription | string | Yes | |
| LookBack | string | Yes | |
| InitialQueries | string | Yes | |
| SupplementalQueries | string | Yes | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No | |
| Type | integer | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| FacilityId | string | Yes | 256 |
| ResourceId | string | Yes | 256 |
| ResourceType | string | Yes | 128 |
| ReferenceResource | string | No | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No | |
| QueryPhase | string | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| FacilityId | string | Yes | |
| QueryPhase | string | Yes | |
| ResourceType | string | Yes | |
| FhirQueryId | string | No | |
| CreateDate | string (date-time) | Yes | |
| ModifyDate | string (date-time) | No |
| Name | Type | Required? | Length |
|---|---|---|---|
| ReportTrackingId | string | Yes | |
| Frequency | string | Yes | 50 |
| StartDate | string (date-time) | Yes | |
| EndDate | string (date-time) | Yes | |
| ReportTypes | string | No | 2000 |
| CreateDate | string (date-time) | Yes |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | integer | Yes | |
| ExternalId | string | Yes | |
| FacilityId | string | Yes | 250 |
| AcquisitionType | string | Yes | 50 |
| ProcessDate | string (date-time) | No | |
| FileNames | string | Yes | |
| Notes | string | Yes | |
| OriginatingSpanId | string | No | 16 |
| OriginatingTraceId | string | No | 32 |
| RetryAttempts | integer | No | |
| Benchmarks | string | No | |
| ScheduledDate | string (date-time) | No | |
| Status | string | Yes | 50 |
| SubType | string | Yes | 50 |
| Name | Type | Required? | Length |
|---|---|---|---|
| Id | string | Yes | |
| OrganizationId | string | Yes | 250 |
| Host | string | Yes | 256 |
| Port | integer | Yes | |
| RemoteDirectory | string | No | |
| Timeout | string (time) | Yes | |
| RemoveAfterProcessing | boolean | Yes | |
| AuthenticationProtocol | integer | Yes | |
| AcquisitionConfigurations | string | Yes | |
| EnableBenchmarking | boolean | Yes |
OpenAPI Operations
Gets authentication settings for a given facilityId and config type.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | ||
| queryConfigurationTypePathParameter | Yes |
Query Parameters
None
Creates a AuthenticationSettings for a facility. Supported Authentication Types: Basic, Epic
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | ||
| queryConfigurationTypePathParameter | Yes |
Query Parameters
None
Updates a AuthenticationSettings for a facility. Supported Authentication Types: Basic, Epic
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | ||
| queryConfigurationTypePathParameter | Yes |
Query Parameters
None
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | ||
| queryConfigurationTypePathParameter | Yes |
Query Parameters
None
Validates the connection between the facility and the Link.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| patientId | No | ||
| patientIdentifier | No | ||
| measureId | No | ||
| start | No | ||
| end | No |
Route Parameters
None
Query Parameters
None
Route Parameters
None
Query Parameters
None
Get query results for a correlation Id
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| correlationId | No | ||
| patientId | No | ||
| resourceType | No |
Get a list of data acquisition logs.
Route Parameters
None
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| FacilityId | No | ||
| PatientId | No | ||
| ReportId | No | ||
| ResourceId | No | ||
| QueryPhase | No | ||
| QueryType | No | ||
| Statuses | No | ||
| Priority | No | ||
| ResourceType | No | ||
| CreatedBefore | No | ||
| PageNumber | No | ||
| PageSize | No | ||
| SortBy | No | ||
| SortOrder | No | ||
| IncludeDeleted | No |
Get a data acquisition log entry.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes | The ID of the log entry to retrieve. |
Query Parameters
None
Update a data acquisition log entry.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes | The ID of the log entry to update. |
Query Parameters
None
Delete a data acquisition log entry.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes | The ID of the log entry to delete. |
Query Parameters
None
Get note entries for a data acquisition log entry.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes |
Query Parameters
None
Get reference resources for a data acquisition log entry.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes | The ID of the log entry. |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| pageNumber | No | Page number (1-based). Defaults to 1. | |
| pageSize | No | Page size. Defaults to 100. |
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| PageNumber | No | ||
| PageSize | No | ||
| SortBy | No | ||
| SortOrder | No | ||
| IncludeDeleted | No |
Soft delete all data acquisition log entries for a facility.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | The facility ID whose logs should be soft deleted. |
Query Parameters
None
Get a list of data acquisition logs for a patient.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | ||
| patientId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| PageNumber | No | ||
| PageSize | No | ||
| SortBy | No | ||
| SortOrder | No | ||
| IncludeDeleted | No |
Get data acquisition log statistics for a report.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| reportId | Yes |
Query Parameters
None
Get data acquisition log status counts for a report.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| reportId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| patientId | No |
Soft delete all data acquisition log entries for a report tracking ID.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| reportTrackingId | Yes | The report tracking ID whose logs should be soft deleted. |
Query Parameters
None
Restore soft deleted data acquisition log entries for a report tracking ID.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| reportTrackingId | Yes | The report tracking ID whose logs should be restored. |
Query Parameters
None
Restore soft deleted data acquisition log entries for a facility.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | The facility ID whose logs should be restored. |
Query Parameters
None
Process a data acquisition log entry.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes |
Query Parameters
None
Process multiple data acquisition log entries.
Route Parameters
None
Query Parameters
None
Cancel multiple data acquisition log entries.
Route Parameters
None
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| minAgeHours | No | Minimum age in hours a log must have to be eligible for cancellation. Defaults to 24. |
Process data acquisition log entries based on search criteria.
Route Parameters
None
Query Parameters
None
Cancel data acquisition log entries based on search criteria.
Route Parameters
None
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| minAgeHours | No |
Returns a lightweight summary of data acquisition activity for a report. Totals, status counts and resource-type counts computed via DB aggregates. Intended for dashboard polling and run monitoring.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| reportId | Yes |
Query Parameters
None
Get all ResourceAcquiredIds from completed logs for a facility/report pair. Intended for automation reconciliation.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| reportId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | No |
Gets a FhirQueryConfiguration record for a given facilityId.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
None
Hard deletes a FhirQueryConfiguration record for a given facilityId.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
None
Creates a FhirQueryConfiguration record for a facility. Should only be used for initial configuration. Supported Authentication Types: Basic, Epic
Route Parameters
None
Query Parameters
None
Updates a FhirQueryConfiguration record for a facility. This update will do a clean replace of the existing record and will not update the delta between the 2 records. Supported Authentication Types: Basic, Epic
Route Parameters
None
Query Parameters
None
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
None
Deletes a FhirQueryConfiguration record for a given facilityId. Supported Authentication Types: Basic, Epic
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
None
Creates a FhirQueryConfiguration record for a given facilityId. Supported Authentication Types: Basic, Epic
Route Parameters
None
Query Parameters
None
Updates a FhirQueryConfiguration record for a given facilityId. Supported Authentication Types: Basic, Epic
Route Parameters
None
Query Parameters
None
Gets a QueryPlanConfig record for a given facilityId.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| Type | Yes |
Creates a QueryPlanConfig for a facility
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
None
Updates a QueryPlanConfig record for a facilityId, queryPlanType, and queryPlan.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
None
Hard deletes a QueryPlanConfig for a given facilityId and queryPlanType.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes |
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| Type | Yes |
Hard deletes all QueryPlanConfigs for a given facilityId.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| facilityId | Yes | The ID of the facility whose query plans will be deleted. |
Query Parameters
None
Gets an SftpConfiguration record by its unique identifier.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| id | Yes | The unique identifier of the SFTP configuration. |
Query Parameters
None
Gets an SftpConfiguration record for a given organization.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The organization identifier representing either a health care system or individual facility. |
Query Parameters
None
Creates an SftpConfiguration record for an organization. Should only be used for initial configuration. Optionally accepts credentials which will be stored securely in a configured secret manager. Supported Authentication Types: Basic (default)
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The identifier for the reporting organization. |
Query Parameters
None
Updates an SftpConfiguration record for an organization. This update will do a clean replace of the existing record. Supported Authentication Types: Basic (default)
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The identifier for the reporting organization. | |
| configurationId | Yes | The identifier of the SFTP configuration. |
Query Parameters
None
Deletes an SftpConfiguration record for a given organization.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The identifier for the reporting organization | |
| configurationId | Yes | The identifier for the SFTP configuration |
Query Parameters
None
Updates SFTP credentials for an organization's configuration. Credentials are stored securely in a configured secret manager.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The organization identifier. |
Query Parameters
None
Deletes SFTP credentials for an organization. The SFTP configuration itself is not deleted.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The organization identifier. |
Query Parameters
None
Gets the credential status for an organization's SFTP configuration. Returns whether credentials exist, without exposing the actual values.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The organization identifier. |
Query Parameters
None
Tests the SFTP connection for an organization's configuration. Verifies connectivity to the SFTP server and optionally checks if the remote directory is accessible.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| organizationId | Yes | The organization identifier. |
Query Parameters
None
Search SFTP logs
Route Parameters
None
Query Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| FacilityId | No | ||
| Status | No | ||
| AcquisitionType | No | ||
| SubType | No | ||
| PageNumber | No | ||
| PageSize | No | ||
| SortBy | No | ||
| SortOrder | No | ||
| IncludeDeleted | No |
Create SFTP log
Route Parameters
None
Query Parameters
None
Get SFTP log by external ID
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| logId | Yes |
Query Parameters
None
Reset an SFTP log for retry after configuration has been fixed. Only logs in ConfigurationRequired or MaxRetriesReached status can be reset.
Route Parameters
| Name | Type | Required? | Description |
|---|---|---|---|
| logId | Yes | The external ID of the log to reset |
Query Parameters
None
Health check endpoint
Route Parameters
None
Query Parameters
None
Relationships
flowchart LR
nDataAcquisitionService_3BF82DDB["Service: Data Acquisition Service"]
nPatientCensusScheduled_2383C0C6["Event: PatientCensusScheduled"] -->|consumed by| nDataAcquisitionService_3BF82DDB
nDataAcquisitionRequested_4BBEB8F8["Command: DataAcquisitionRequested"] -->|handled by| nDataAcquisitionService_3BF82DDB
nDataAcquisitionService_3BF82DDB -->|provides| nGetFacilityConfig_32C58A79["Query: GetFacilityConfig"]
nDataAcquisitionService_3BF82DDB -->|provides| nGetServiceInfo_13F02E06["Query: GetServiceInfo"]
subgraph nDataAccess_2A523756_domain["Domain: Data Access"]
nDataAcquisitionRequested_4BBEB8F8
nDataAcquisitionService_3BF82DDB
end
subgraph nReport_4D4FBBDA_domain["Domain: Report"]
nPatientCensusScheduled_2383C0C6
end
subgraph nTenant_379CD514_domain["Domain: Tenant"]
nGetFacilityConfig_32C58A79
nGetServiceInfo_13F02E06
end