diff --git a/src/aidbox_sdk/converter.clj b/src/aidbox_sdk/converter.clj index 586456b..37c0adb 100644 --- a/src/aidbox_sdk/converter.clj +++ b/src/aidbox_sdk/converter.clj @@ -191,6 +191,7 @@ :resource-name (url->resource-name (get schema :url)) :base-resource-name (when (get schema :base) (url->resource-name (get schema :base))) + :fhir-version (get schema :fhir-version) :package (get schema :package) :url (get schema :url) :type (get schema :type) @@ -262,6 +263,7 @@ ;; Convert main function ;; + (defn convert [schemas] (->> schemas (map resolve-element-references) @@ -362,7 +364,6 @@ ((fn [schema] (update schema :deps set/union #{"Meta"}))) ((fn [schema] (assoc schema :resource-name (url->resource-name (:url constraint))))))) - (defn apply-constraint [base-schema constraint] (-> base-schema ;; apply required diff --git a/src/aidbox_sdk/fhir.clj b/src/aidbox_sdk/fhir.clj index 5722519..472dcd8 100644 --- a/src/aidbox_sdk/fhir.clj +++ b/src/aidbox_sdk/fhir.clj @@ -159,3 +159,6 @@ (filter #(= url (:url %)) schemas)) (def find-by-url (comp first filter-by-url)) + +(defn base-package? [schema] + (contains? #{"hl7.fhir.r4.core" "hl7.fhir.r4b.core" "hl7.fhir.r5.core"} (:package schema))) diff --git a/src/aidbox_sdk/generator/typescript.clj b/src/aidbox_sdk/generator/typescript.clj index c8c2fee..318dfcc 100644 --- a/src/aidbox_sdk/generator/typescript.clj +++ b/src/aidbox_sdk/generator/typescript.clj @@ -5,12 +5,12 @@ ->camel-case]] [aidbox-sdk.generator.utils :as u] [clojure.java.io :as io] - [clojure.string :as str]) + [clojure.string :as str] + [aidbox-sdk.fhir :as fhir]) (:import [aidbox_sdk.generator CodeGenerator])) -(defn package->directory - "Generates directory name from package name. +(defn package->directory "Generates directory name from package name. Example: hl7.fhir.r4.core -> hl7-fhir-r4-core" @@ -149,24 +149,23 @@ (str/replace path #"(\.ts)|[\.\/]" "")) (defn generate-deps - "Takes a list of resource names and generates import declarations." - [deps] - (->> deps - (map (fn [{:keys [module members]}] - (if (seq members) - (format "import { %s } from \"%s\";" (str/join ", " members) module) - (format "import * as %s from \"%s\";" (path->name module) module)))) - (str/join "\n"))) + "Takes an IR schema generates import declarations." + [ir-schema] + (let [relative-path (if (fhir/base-package? ir-schema) + "./" + (str "../" (package->directory (:fhir-version ir-schema)) "/"))] + (->> (:deps ir-schema) + (map class-name) + (map (fn [d] {:module (str relative-path d) :members [d]})) + (map (fn [{:keys [module members]}] + (if (seq members) (format "import { %s } from \"%s\";" (str/join ", " members) module) (format "import * as %s from \"%s\";" (path->name module) module)))) + (str/join "\n")))) (defn generate-module [& {:keys [deps classes] :or {classes []}}] (->> (conj [] - (->> deps - (map class-name) - (map (fn [d] {:module (str "./" d) :members [d]})) - generate-deps) - + deps classes) (flatten) (str/join "\n\n"))) @@ -185,7 +184,7 @@ (generate-resource-module [_ ir-schema] {:path (resource-file-path ir-schema) :content (generate-module - {:deps (:deps ir-schema) + {:deps (generate-deps ir-schema) :classes [(generate-class ir-schema (map generate-class (:backbone-elements ir-schema)))]})}) @@ -205,7 +204,7 @@ (map (fn [ir-schema] {:path (resource-file-path ir-schema) :content (generate-module - {:deps (:deps ir-schema) + {:deps (generate-deps ir-schema) :classes [(generate-class ir-schema (map generate-class (:backbone-elements ir-schema)))]})}) ir-schemas)) diff --git a/src/aidbox_sdk/schema.clj b/src/aidbox_sdk/schema.clj index 2dd4f15..eea38f4 100644 --- a/src/aidbox_sdk/schema.clj +++ b/src/aidbox_sdk/schema.clj @@ -16,7 +16,6 @@ (filter #(str/ends-with? (.getName %) ".gz")))] (println "✅ Found packages:" (count packages)) packages)) - (defn create-gzip-reader [path] (-> path (io/input-stream) @@ -40,9 +39,7 @@ (map (fn [schema] (if-not (:resourceType schema) (assoc schema :resourceType "FHIRSchema") - schema) - - )))) + schema))))) (defn prepare-schemas [schemas] (map #(->> (get-in % [:package-meta :name]) @@ -106,6 +103,18 @@ (defn skip-root-package [packages] (rest packages)) +(defn get-fhir-version [package] + (let [allowed-base-packages #{"hl7.fhir.r4.core" "hl7.fhir.r4b.core" "hl7.fhir.r5.core"}] + (->> (keys (:dependencies package)) + (map name) + (filter #(contains? allowed-base-packages %)) + first))) + +(defn enrich-schema-with-fhir-version [schema version] + (if version + (assoc schema :fhir-version version) + (assoc schema :fhir-version (:package schema)))) + (defmethod retrieve :url [{:keys [source]} {:keys [exit] :as opts}] (let [extract-link (fn [package] (-> package :href)) @@ -122,7 +131,9 @@ []))] (->> fhir-packages ;; TODO using pmap for side effects is questionable - (pmap (fn [package] - (println "Downloading schemas for:" (extract-name package)) - (fetch-n-parse (extract-link package) opts))) + (map (fn [package] + (let [base-package-name (get-fhir-version package) + schemas (fetch-n-parse (extract-link package) opts)] + (println "Downloading schemas for:" (extract-name package)) + (map #(enrich-schema-with-fhir-version % base-package-name) schemas)))) (flatten)))) diff --git a/test/aidbox_sdk/converter_test.clj b/test/aidbox_sdk/converter_test.clj index ea78fc8..c4775ca 100644 --- a/test/aidbox_sdk/converter_test.clj +++ b/test/aidbox_sdk/converter_test.clj @@ -30,6 +30,37 @@ :summary true, :elements {:referenceRange {:array true, :type "Reference"}}}}})) +(deftest test-resolve-dependencies + (testing "simple case" + (is (= #{"Address" + "Attachment" + "Period" + "CodeableConcept" + "ContactPoint" + "HumanName" + "DomainResource" + "Reference" + "Identifier" + "BackboneElement"} + (-> (sut/resolve-dependencies [(dissoc (fixt/get-data :patient-ir-schema) :deps)]) + first + :deps)))) + + (testing "another package" + (is (= #{"Address" + "Attachment" + "Period" + "CodeableConcept" + "ContactPoint" + "HumanName" + "DomainResource" + "Reference" + "Identifier" + "BackboneElement"} + (-> (sut/resolve-dependencies [(dissoc (fixt/get-data :patient-ir-schema) :deps)]) + first + :deps))))) + (deftest test-url->resource-name (testing "one word" (is (= "Immunization" @@ -83,8 +114,6 @@ (fixt/get-data :us-core-vital-signs-fhir-schema) (fixt/get-data :us-core-bmi-fhir-schema)])))))) -(fixt/get-data :vitalsigns-fhir-schema) - (deftest test-sort-by-base (match (sut/sort-by-base diff --git a/test/aidbox_sdk/fixtures/ehrsrle_provenance_ir_schema.edn b/test/aidbox_sdk/fixtures/ehrsrle_provenance_ir_schema.edn new file mode 100644 index 0000000..e4a5c51 --- /dev/null +++ b/test/aidbox_sdk/fixtures/ehrsrle_provenance_ir_schema.edn @@ -0,0 +1,67 @@ +{:package "hl7.fhir.r4.core", + :derivation "constraint", + :name "Provenance", + :resource-name "ehrsrle-provenance", + :type "Provenance", + :elements + [{:name "agent", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "policy", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "reason", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "target", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "activity", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "location", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "recorded", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false} + {:name "signature", + :base "Provenance", + :array false, + :required false, + :value "string", + :type nil, + :choice-option false}], + :url "http://hl7.org/fhir/StructureDefinition/ehrsrle-provenance", + :base-resource-name "Provenance", + :backbone-elements [], + :base "http://hl7.org/fhir/StructureDefinition/Provenance", + :deps #{"Meta" "Provenance"}} diff --git a/test/aidbox_sdk/fixtures/organization_preferred_contact_fhir_schema.edn b/test/aidbox_sdk/fixtures/organization_preferred_contact_fhir_schema.edn index a7c0353..84f5c58 100644 --- a/test/aidbox_sdk/fixtures/organization_preferred_contact_fhir_schema.edn +++ b/test/aidbox_sdk/fixtures/organization_preferred_contact_fhir_schema.edn @@ -1,6 +1,7 @@ {:package "hl7.fhir.r4.core", :technical-id "hl7.fhir.r4.core/4.0.1/FHIRSchema/http://hl7.org/fhir/StructureDefinition/organization-preferredContact/4.0.1", + :fhir-version "hl7.fhir.r4.core" :derivation "constraint", :fhirVersion nil, :excluded ["extension"], diff --git a/test/aidbox_sdk/fixtures/organization_preferred_contact_ir_schema.edn b/test/aidbox_sdk/fixtures/organization_preferred_contact_ir_schema.edn index 6702d8b..bfc1463 100644 --- a/test/aidbox_sdk/fixtures/organization_preferred_contact_ir_schema.edn +++ b/test/aidbox_sdk/fixtures/organization_preferred_contact_ir_schema.edn @@ -2,6 +2,7 @@ :derivation "constraint", :name "Extension", :type "Extension", + :fhir-version "hl7.fhir.r4.core" :resource-name "organization-preferred-Contact" :elements [{:name "url", diff --git a/test/aidbox_sdk/fixtures/us_core_bmi_fhir_schema.edn b/test/aidbox_sdk/fixtures/us_core_bmi_fhir_schema.edn new file mode 100644 index 0000000..050bbe2 --- /dev/null +++ b/test/aidbox_sdk/fixtures/us_core_bmi_fhir_schema.edn @@ -0,0 +1,30 @@ +{:package "hl7.fhir.us.core", + :technical-id + "hl7.fhir.us.core/6.1.0/FHIRSchema/http://hl7.org/fhir/us/core/StructureDefinition/us-core-bmi/6.1.0", + :derivation "constraint", + :fhirVersion nil, + :name "us-core-bmi", + :type "Observation", + :resourceType "FHIRSchema", + :elements + {:code + {:type "CodeableConcept", + :pattern {:coding [{:code "39156-5", :system "http://loinc.org"}]}, + :mustSupport true}, + :valueQuantity + {:max 1, + :min 0, + :elements + {:code {:type "code", :fixed "kg/m2", :mustSupport true}, + :unit {:type "string", :mustSupport true}, + :value {:type "decimal", :mustSupport true}, + :system + {:type "uri", :fixed "http://unitsofmeasure.org", :mustSupport true}}, + :required ["value" "unit" "system" "code"], + :mustSupport true}}, + :id "us-core-bmi", + :kind "resource", + :url "http://hl7.org/fhir/us/core/StructureDefinition/us-core-bmi", + :packageVersion "6.1.0", + :base "http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs", + :version "6.1.0"} diff --git a/test/aidbox_sdk/fixtures/us_core_vital_signs_fhir_schema.edn b/test/aidbox_sdk/fixtures/us_core_vital_signs_fhir_schema.edn new file mode 100644 index 0000000..81fadb9 --- /dev/null +++ b/test/aidbox_sdk/fixtures/us_core_vital_signs_fhir_schema.edn @@ -0,0 +1,262 @@ +{:package "hl7.fhir.us.core", + :technical-id + "hl7.fhir.us.core/6.1.0/FHIRSchema/http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs/6.1.0", + :derivation "constraint", + :fhirVersion nil, + :name "us-core-vital-signs", + :type "Observation", + :resourceType "FHIRSchema", + :elements + {:category + {:type "CodeableConcept", + :array true, + :slicing + {:rules "open", + :slices + {:VSCat + {:max 1, + :min 1, + :match + {:type "pattern", + :value + {:coding + {:code "vital-signs", + :system + "http://terminology.hl7.org/CodeSystem/observation-category"}}}, + :schema + {:type "CodeableConcept", + :elements + {:coding + {:type "Coding", + :array true, + :elements + {:code {:type "code", :fixed "vital-signs", :mustSupport true}, + :system + {:type "uri", + :fixed + "http://terminology.hl7.org/CodeSystem/observation-category", + :mustSupport true}}, + :required ["system" "code"], + :mustSupport true}}, + :required ["coding"], + :mustSupport true}, + :sliceIsConstraining true}}, + :ordered false, + :discriminator + [{:path "coding.code", :type "value"} + {:path "coding.system", :type "value"}], + :constraining-slice? true}, + :mustSupport true}, + :valueTime + {:type "time", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueQuantity + {:type "Quantity", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :value + {:choices + ["valueQuantity" + "valueCodeableConcept" + "valueString" + "valueBoolean" + "valueInteger" + "valueRange" + "valueRatio" + "valueSampledData" + "valueTime" + "valueDateTime" + "valuePeriod"]}, + :valueString + {:type "string", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueRatio + {:type "Ratio", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueBoolean + {:type "boolean", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueDateTime + {:type "dateTime", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :component + {:elements + {:valueTime + {:type "time", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueQuantity + {:type "Quantity", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :value + {:choices + ["valueQuantity" + "valueCodeableConcept" + "valueString" + "valueBoolean" + "valueInteger" + "valueRange" + "valueRatio" + "valueSampledData" + "valueTime" + "valueDateTime" + "valuePeriod"]}, + :valueString + {:type "string", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueRatio + {:type "Ratio", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueBoolean + {:type "boolean", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueDateTime + {:type "dateTime", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueSampledData + {:type "SampledData", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :code + {:binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}, + :mustSupport true}, + :valueCodeableConcept + {:type "CodeableConcept", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valuePeriod + {:type "Period", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueRange + {:type "Range", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueInteger + {:type "integer", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :dataAbsentReason {:mustSupport true}}, + :mustSupport true}, + :valueSampledData + {:type "SampledData", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :effectiveDateTime + {:type "dateTime", :choiceOf "effective", :mustSupport true}, + :status {:mustSupport true}, + :effective {:choices ["effectiveDateTime" "effectivePeriod"]}, + :code + {:binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/us/core/ValueSet/us-core-vital-signs"}, + :mustSupport true}, + :valueCodeableConcept + {:type "CodeableConcept", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valuePeriod + {:type "Period", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueRange + {:type "Range", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :valueInteger + {:type "integer", + :binding + {:strength "extensible", + :valueSet "http://hl7.org/fhir/ValueSet/ucum-vitals-common"}, + :choiceOf "value", + :mustSupport true}, + :subject + {:type "Reference", + :refers + ["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"], + :mustSupport true}, + :dataAbsentReason {:mustSupport true}, + :effectivePeriod + {:type "Period", :choiceOf "effective", :mustSupport true}}, + :id "us-core-vital-signs", + :kind "resource", + :url "http://hl7.org/fhir/us/core/StructureDefinition/us-core-vital-signs", + :packageVersion "6.1.0", + :base "http://hl7.org/fhir/StructureDefinition/vitalsigns", + :version "6.1.0", + :required ["category"]} diff --git a/test/aidbox_sdk/generator/typescript_test.clj b/test/aidbox_sdk/generator/typescript_test.clj index 903dbdd..dd01932 100644 --- a/test/aidbox_sdk/generator/typescript_test.clj +++ b/test/aidbox_sdk/generator/typescript_test.clj @@ -10,18 +10,16 @@ (use-fixtures :once fixt/prepare-examples) (deftest test-generate-deps - (testing "no members" - (is (= "import * as base from \"./base.ts\";" - (gen.typescript/generate-deps [{:module "./base.ts" :members []}])))) - - (testing "with members" - (is (= "import { Dosage } from \"../datatypes.ts\";" - (gen.typescript/generate-deps [{:module "../datatypes.ts" :members ["Dosage"]}])))) - - (testing "multiple deps" - (is (= "import * as base from \"./base.ts\";\nimport { Dosage } from \"../datatypes.ts\";" - (gen.typescript/generate-deps [{:module "./base.ts" :members []} - {:module "../datatypes.ts" :members ["Dosage"]}]))))) + (testing "import for base package" + (is (= "import { Bundle } from \"./Bundle\";" + (gen.typescript/generate-deps {:package "hl7.fhir.r4.core" + :deps #{"Bundle"}})))) + + (testing "import for package outside of base" + (is (= "import { Bundle } from \"../hl7-fhir-r4-core/Bundle\";" + (gen.typescript/generate-deps {:package "hl7.fhir.us.core" + :deps #{"Bundle"} + :fhir-version "hl7.fhir.r4.core"}))))) (deftest test-generate-property (testing "simple case"