Skip to content

Commit

Permalink
Merge pull request #193 from samply/develop
Browse files Browse the repository at this point in the history
EHDS2, separating explorer API key from auth header CLA, thiserror version bump
  • Loading branch information
enola-dkfz authored Dec 11, 2024
2 parents 775d96c + c2c5f0b commit de85f09
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 9 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ on:
push:
workflow_dispatch:
pull_request:
schedule:
# Fetch new base image updates every night at 1am
- cron: '0 1 * * *'

jobs:
build-with-samply:
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Samply.Focus v0.8.0 2024-11-04

## Major changes
* EHDS2 query support

## Minor changes
* Separated exporter API key CLA from authorization header CLA


# Samply.Focus v0.8.0 2024-11-04

In this release, we are supporting 4 types of SQL queries for Exliquid and Organoids

## Major changes
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "focus"
version = "0.8.0"
version = "0.9.0"
edition = "2021"
license = "Apache-2.0"

Expand All @@ -11,12 +11,12 @@ base64 = "0.22.1"
reqwest = { version = "0.12", default-features = false, features = ["json", "default-tls"] }
serde = { version = "1.0.152", features = ["serde_derive"] }
serde_json = "1.0"
thiserror = "1.0.38"
thiserror = "2.0.3"
chrono = "0.4.31"
indexmap = "2.1.0"
tokio = { version = "1.25.0", default-features = false, features = ["signal", "rt-multi-thread", "macros"] }
beam-lib = { git = "https://github.com/samply/beam", branch = "develop", features = ["http-util"] }
laplace_rs = {git = "https://github.com/samply/laplace-rs.git", tag = "v0.3.0" }
laplace_rs = {git = "https://github.com/samply/laplace-rs.git", tag = "v0.4.0" }
uuid = "1.8.0"
rand = { default-features = false, version = "0.8.5" }
futures-util = { version = "0.3", default-features = false, features = ["std"] }
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ PROJECTS_NO_OBFUSCATION = "exliquid;dktk_supervisors;exporter;ehds2" # Projects
QUERIES_TO_CACHE = "queries_to_cache.conf" # The path to a file containing base64 encoded queries whose results are to be cached. If not set, no results are cached
PROVIDER = "name" #EUCAIM provider name
PROVIDER_ICON = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=" # Base64 encoded EUCAIM provider icon
AUTH_HEADER = "ApiKey XXXX" #Authorization header
AUTH_HEADER = "[Auth Type] XXXX" #Authorization header for accessing the store; Auth Type e.g. ApiKey, Basic, ...
EXPORTER_API_KEY = "XXXX" # Value of header x-api-key for accessing the Exporter application
```

In order to use Postgres querying, a Docker image built with the feature "dktk" needs to be used and this optional variable set:
Expand Down
5 changes: 5 additions & 0 deletions resources/cql/DKTK_STRAT_GENETIC_VARIANT
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
define GeneticVariantCount:
if InInitialPopulation then [Observation: Code '69548-6' from loinc] else {} as List <Observation>

define GeneticVariantCode:
First (from [Observation: Code '69548-6' from loinc] O return O.component.where(code.coding contains Code '48018-6' from loinc).value.coding.code.first())
1 change: 1 addition & 0 deletions resources/cql/EHDS2_IN_INITIAL_POPULATION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
define InInitialPopulation:
125 changes: 125 additions & 0 deletions resources/cql/EHDS2_OBSERVATION
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
define ObservationList:
if InInitialPopulation then [Observation] else {} as List<Observation>

// Return the value in the given system for the given Observation
define function ObservationValueCode(observation FHIR.Observation, system_var String):
(observation.value as CodeableConcept).coding.where(system = system_var).code.first()

// Check to see if there are any Observation resources with the
// given system/value pair.
define function ExistsObservationValueCode(system_var String, code_var String):
exists from [Observation] O
where ObservationValueCode(O, system_var) = code_var

// Return the value associated with the extension with the given URL for the given Observation
define function BaseObservationExtensionValue(observation FHIR.Observation, url_var String):
observation.extension.where(url = url_var).value

// Return the value of the Observation extension with the given URL
define function ObservationExtensionValue(observation FHIR.Observation, url_var String):
SensibleString(BaseObservationExtensionValue(observation, url_var))

define function ExistsObservationExtensionValue(url_var String, value_var String):
exists from [Observation] O
where ObservationExtensionValue(O, url_var) = value_var

// Return the pathogen type for the given Observation
define function PathogenCode(observation FHIR.Observation):
ObservationValueCode(observation, 'https://ecdc.amr/pathogen-codes')

// Check to see if there are any Observation resources with the
// given pathogen type.
define function ExistsPathogenCode(code_var String):
ExistsObservationValueCode('https://ecdc.amr/pathogen-codes', code_var)

// Return the antibiotic type for the given Observation
define function AntibioticCode(observation FHIR.Observation):
ObservationValueCode(observation, 'https://ecdc.amr/antibiotic-codes')

// Check to see if there are any Observation resources with the
// given antibiotic type.
define function ExistsAntibioticCode(code_var String):
ExistsObservationValueCode('https://ecdc.amr/antibiotic-codes', code_var)

// Return the resistance type for the given Observation
define function SirCode(observation FHIR.Observation):
ObservationValueCode(observation, 'https://ecdc.amr/sir-codes')

// Check to see if there are any Observation resources with the
// given resistance type.
define function ExistsSirCode(code_var String):
ExistsObservationValueCode('https://ecdc.amr/sir-codes', code_var)

// Return the data source for the given Observation
define function DataSource(observation FHIR.Observation):
ObservationExtensionValue(observation, 'https://ecdc.amr/fhir/StructureDefinition/ObservationDataSource')

// Check to see if there are any Observation resources with the
// given data source.
define function ExistsDataSource(value_var String):
ExistsObservationExtensionValue('https://ecdc.amr/fhir/StructureDefinition/ObservationDataSource', value_var)

// Return the isolate ID for the given Observation
define function IsolateId(observation FHIR.Observation):
ObservationExtensionValue(observation, 'https://ecdc.amr/fhir/StructureDefinition/ObservationIsolateId')

// Check to see if there are any Observation resources with the
// given isolate ID.
define function ExistsIsolateId(value_var String):
ExistsObservationExtensionValue('https://ecdc.amr/fhir/StructureDefinition/ObservationIsolateId', value_var)

// Return the patient type for the given Observation
define function PatientType(observation FHIR.Observation):
ObservationExtensionValue(observation, 'https://ecdc.amr/fhir/StructureDefinition/ObservationPatientType')

// Check to see if there are any Observation resources with the
// given patient type.
define function ExistsPatientType(value_var String):
ExistsObservationExtensionValue('https://ecdc.amr/fhir/StructureDefinition/ObservationPatientType', value_var)

// Return the reference guidelines SIR for the given Observation
define function ReferenceGuidelinesSir(observation FHIR.Observation):
ObservationExtensionValue(observation, 'https://ecdc.amr/fhir/StructureDefinition/ObservationReferenceGuidelinesSIR')

// Check to see if there are any Observation resources with the
// given reference guidelines SIR.
define function ExistsReferenceGuidelinesSir(value_var String):
ExistsObservationExtensionValue('https://ecdc.amr/fhir/StructureDefinition/ObservationReferenceGuidelinesSIR', value_var)

// Return the reporting country for the given Observation
define function ReportingCountry(observation FHIR.Observation):
ObservationExtensionValue(observation, 'https://ecdc.amr/fhir/StructureDefinition/ObservationReportingCountry')

// Check to see if there are any Observation resources with the
// given reporting country.
define function ExistsReportingCountry(value_var String):
ExistsObservationExtensionValue('https://ecdc.amr/fhir/StructureDefinition/ObservationReportingCountry', value_var)

// Return the year from the date used for statistics
define function YearDateUsedForStatistics(observation FHIR.Observation):
year from observation.issued

// Return the month from the date used for statistics
define function MonthDateUsedForStatistics(observation FHIR.Observation):
month from observation.issued

// Return the year-month from the date used for statistics
define function YearMonthDateUsedForStatistics(observation FHIR.Observation):
ToString(YearDateUsedForStatistics(observation)) + '-' + ToString(MonthDateUsedForStatistics(observation))

// Return the the date valid from
define function DateValidFrom(observation FHIR.Observation):
ToDate(observation.effective as dateTime)

// Return the year from the date valid from
define function YearDateValidFrom(observation FHIR.Observation):
year from DateValidFrom(observation)

// Return the month from the date valid from
define function MonthDateValidFrom(observation FHIR.Observation):
month from DateValidFrom(observation)

// Return the year-month from the date valid from
define function YearMonthDateValidFrom(observation FHIR.Observation):
ToString(YearDateValidFrom(observation)) + '-' + ToString(MonthDateValidFrom(observation))

30 changes: 30 additions & 0 deletions resources/cql/EHDS2_PATIENT
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Return the value of the Patient extension with the given URL.
// Return "Unknown" if not available or empty.
define function PatientExtensionValue(url_var String):
SensibleString(Patient.extension.where(url = url_var).value)

// Return gender of patient
define Gender:
SensibleString(Patient.gender)

// Return age of patient, as an integer
define AgeInYears:
ToInteger(PatientExtensionValue('https://ecdc.amr/fhir/StructureDefinition/PatientAge'))

// Round patient age to the nearest multiple of 10 (i/p for a histogram).
// Return result as a string
define AgeClass:
ToString((AgeInYears div 10) * 10)

// Return ID of hospital associated with patient
define HospitalId:
PatientExtensionValue('https://ecdc.amr/fhir/StructureDefinition/PatientHospitalId')

// Return hospital unit type associated with patient
define HospitalUnitType:
PatientExtensionValue('https://ecdc.amr/fhir/StructureDefinition/PatientHospitalUnitType')

// Return laboratory code associated with patient
define LaboratoryCode:
PatientExtensionValue('https://ecdc.amr/fhir/StructureDefinition/PatientLaboratoryCode')

13 changes: 13 additions & 0 deletions resources/cql/EHDS2_SPECIMEN
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
define SpecimenList:
if InInitialPopulation then [Specimen] else {} as List<Specimen>

// Return the isolate ID for the given Specimen
define function SpecimenIsolateId(specimen FHIR.Specimen):
SensibleString(specimen.id)

// Check to see if there are any Specimen resources with the
// given isolate ID.
define function ExistsSpecimenIsolateId(id_var String):
exists from [Specimen] S
where SpecimenIsolateId(S) = id_var

7 changes: 7 additions & 0 deletions resources/cql/EHDS2_UTIL
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Return "val" if the supplied string value is sensible.
// Return "Unknown" if null or empty.
define function SensibleString(val String):
if (val is null or val = '' or val = '-')
then 'Unkown'
else val

7 changes: 7 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,15 @@ struct CliArgs {
#[clap(long, env, value_parser)]
provider_icon: Option<String>,

// TODO - refactor to include multiple authorization headers for multiple stores / applications at the same time
/// Authorization header
#[clap(long, env, value_parser)]
auth_header: Option<String>,

/// Exporter API key
#[clap(long, env, value_parser)]
exporter_api_key: Option<String>,

/// Postgres connection string
#[cfg(feature = "query-sql")]
#[clap(long, env, value_parser)]
Expand Down Expand Up @@ -198,6 +203,7 @@ pub(crate) struct Config {
pub provider: Option<String>,
pub provider_icon: Option<String>,
pub auth_header: Option<String>,
pub exporter_api_key: Option<String>,
#[cfg(feature = "query-sql")]
pub postgres_connection_string: Option<String>,
#[cfg(feature = "query-sql")]
Expand Down Expand Up @@ -243,6 +249,7 @@ impl Config {
provider: cli_args.provider,
provider_icon: cli_args.provider_icon,
auth_header: cli_args.auth_header,
exporter_api_key: cli_args.exporter_api_key,
#[cfg(feature = "query-sql")]
postgres_connection_string: cli_args.postgres_connection_string,
#[cfg(feature = "query-sql")]
Expand Down
4 changes: 2 additions & 2 deletions src/exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ pub async fn post_exporter_query(body: &String, task_type: TaskType) -> Result<S

let mut headers = HeaderMap::new();

if let Some(auth_header_value) = CONFIG.auth_header.clone() {
if let Some(api_key) = CONFIG.exporter_api_key.clone() {
headers.insert(
"x-api-key",
HeaderValue::from_str(auth_header_value.as_str())
HeaderValue::from_str(api_key.as_str())
.map_err(FocusError::InvalidHeaderValue)?,
);
}
Expand Down

0 comments on commit de85f09

Please sign in to comment.