diff --git a/config/config.exs b/config/config.exs index b4c9e1e9..84a71c71 100644 --- a/config/config.exs +++ b/config/config.exs @@ -50,6 +50,8 @@ config :dart_sass, cd: Path.expand("../assets", __DIR__) ] +config :kindling, root_resources: ["Patient", "Observation", "Organization"] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/lib/epiviewpoint/bulk_fhir_parser.ex b/lib/epiviewpoint/bulk_fhir_parser.ex new file mode 100644 index 00000000..834e0a42 --- /dev/null +++ b/lib/epiviewpoint/bulk_fhir_parser.ex @@ -0,0 +1,229 @@ +defmodule EpiViewpoint.BulkFhirParser do + def parse_bulk_fhir(file_list) do + with {:ok, contents} <- combine_contents(file_list), + {:ok, resources} <- load_resources(file_list), + {:ok, extracted} <- extract_resources(resources), + {:ok, joined} <- join_resources(extracted) do + {:ok, to_map(joined, contents)} + end + end + + defp combine_contents(file_list) do + contents = file_list |> Enum.map(& &1.contents) |> List.to_string() + {:ok, contents} + end + + defp load_resources(file_list) do + result = + Enum.reduce_while(file_list, {:ok, %{}}, fn file, {:ok, acc} -> + case load_resource_file(file.contents) do + {:ok, resources} -> {:cont, {:ok, group_resources(resources, acc)}} + {:error, reason} -> {:halt, {:error, reason}} + end + end) + + case result do + {:ok, resources} -> {:ok, resources} + {:error, reason} -> {:error, reason} + end + end + + defp group_resources(resources, acc) do + Enum.reduce(resources, acc, fn resource, acc -> + Map.update(acc, resource.resource_type, [resource], &[resource | &1]) + end) + end + + defp load_resource_file(file_content) do + result = + file_content + |> String.split("\n") + |> Stream.map(&String.trim/1) + |> Stream.filter(&(&1 != "")) + |> Stream.map(&json_to_kindle_schema/1) + |> Enum.to_list() + + {:ok, result} + rescue + e -> {:error, "Failed to load resource file: #{inspect(e)}"} + end + + defp json_to_kindle_schema(json) do + case Jason.decode(json) do + {:ok, map} -> Kindling.Converter.convert("EpiViewpoint.R4", map) + {:error, reason} -> {:error, "Failed to decode JSON: #{inspect(reason)}"} + end + end + + defp extract_resources(resources) do + result = + Enum.reduce_while(resources, {:ok, %{}}, fn {resource_type, resource_list}, {:ok, acc} -> + case extract_resource(resource_type, resource_list) do + {:ok, extracted} -> {:cont, {:ok, Map.put(acc, resource_type, extracted)}} + {:error, reason} -> {:halt, {:error, reason}} + end + end) + + case result do + {:ok, extracted} -> {:ok, extracted} + {:error, reason} -> {:error, reason} + end + end + + defp extract_resource("Patient", resource_list) do + {:ok, Enum.map(resource_list, &extract_patient/1)} + end + + defp extract_resource("Observation", resource_list) do + {:ok, Enum.map(resource_list, &extract_observation/1)} + end + + defp extract_resource("Organization", resource_list) do + {:ok, Enum.map(resource_list, &extract_organization/1)} + end + + defp extract_resource(unknown_type, _) do + {:error, "Unknown resource type: #{unknown_type}"} + end + + defp extract_patient(%EpiViewpoint.R4.Patient{} = patient) do + %EpiViewpoint.R4.Patient{ + id: caseid, + identifier: [%EpiViewpoint.R4.Identifier{value: person_tid}], + name: [%EpiViewpoint.R4.HumanName{given: [search_firstname], family: search_lastname}], + birth_date: dateofbirth, + gender: sex, + address: [ + %EpiViewpoint.R4.Address{ + line: [diagaddress_street1], + city: diagaddress_city, + state: diagaddress_state, + postal_code: diagaddress_zip + } + ], + telecom: [%EpiViewpoint.R4.ContactPoint{value: phonenumber}], + extension: extensions + } = patient + + %{ + caseid: caseid, + person_tid: person_tid, + search_firstname: search_firstname, + search_lastname: search_lastname, + dateofbirth: format_date(dateofbirth), + sex: String.capitalize(to_string(sex)), + diagaddress_street1: diagaddress_street1, + diagaddress_city: diagaddress_city, + diagaddress_state: diagaddress_state, + diagaddress_zip: diagaddress_zip, + phonenumber: phonenumber, + ethnicity: find_extension(extensions, :ethnicity), + occupation: find_extension(extensions, :occupation), + race: find_extension(extensions, :race) + } + end + + defp extract_observation(%EpiViewpoint.R4.Observation{} = observation) do + %EpiViewpoint.R4.Observation{ + id: lab_result_tid, + subject: %EpiViewpoint.R4.Reference{reference: "Patient/" <> pat_id}, + effective_date_time: datecollected, + issued: resultdate, + code: %EpiViewpoint.R4.CodeableConcept{text: testname}, + interpretation: [%EpiViewpoint.R4.CodeableConcept{coding: [%EpiViewpoint.R4.Coding{display: result}]}], + performer: [%EpiViewpoint.R4.Reference{reference: "Organization/" <> org_id}], + extension: extensions + } = observation + + %{ + lab_result_tid: lab_result_tid, + pat_id: pat_id, + datecollected: datecollected, + resultdate: format_date(resultdate), + testname: testname, + result: result, + org_id: org_id, + datereportedtolhd: find_extension(extensions, :datereportedtolhd) + } + end + + defp extract_organization(%EpiViewpoint.R4.Organization{} = organization) do + %{ + organization_id: organization.id, + ordering_facility_name: organization.name + } + end + + defp find_extension(extensions, :race) do + Enum.find_value(extensions, nil, fn + %EpiViewpoint.R4.Extension{ + url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + extension: [%EpiViewpoint.R4.Extension{url: "ombCategory", value_coding: %EpiViewpoint.R4.Coding{display: value}}, _] + } -> + value + + _ -> + nil + end) + end + + defp find_extension(extensions, :ethnicity) do + Enum.find_value(extensions, nil, fn + %EpiViewpoint.R4.Extension{ + url: "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", + extension: [%EpiViewpoint.R4.Extension{url: "ombCategory", value_coding: %EpiViewpoint.R4.Coding{display: value}}, _] + } -> + value + + _ -> + nil + end) + end + + defp find_extension(extensions, :occupation) do + Enum.find_value(extensions, nil, fn + %EpiViewpoint.R4.Extension{url: "http://hl7.org/fhir/StructureDefinition/patient-occupation", value_string: value} -> + value + + _ -> + nil + end) + end + + defp find_extension(extensions, :datereportedtolhd) do + Enum.find_value(extensions, nil, fn + %EpiViewpoint.R4.Extension{url: "http://hl7.org/fhir/StructureDefinition/datereportedtolhd", value_date: value} -> + value + + _ -> + nil + end) + end + + defp join_resources(resources) do + patients = Map.get(resources, "Patient", []) + observations = Map.get(resources, "Observation", []) + organizations = Map.get(resources, "Organization", []) + + joined = Enum.map(observations, &merge_resources(&1, patients, organizations)) + + {:ok, joined} + end + + defp merge_resources(observation, patients, organizations) do + patient = Enum.find(patients, %{}, &(&1.caseid == observation.pat_id)) + organization = Enum.find(organizations, %{}, &(&1.organization_id == observation.org_id)) + + observation + |> Map.merge(patient) + |> Map.merge(organization) + |> Map.drop([:pat_id, :org_id, :organization_id]) + end + + defp to_map(resources, contents) do + %{file_name: "load.bulk_fhir", contents: contents, list: resources} + end + + defp format_date(date) when is_struct(date, Date), do: Calendar.strftime(date, "%m/%d/%Y") + defp format_date(datetime) when is_struct(datetime, DateTime), do: DateTime.to_date(datetime) |> format_date() +end diff --git a/lib/epiviewpoint/cases.ex b/lib/epiviewpoint/cases.ex index e39db651..f656664d 100644 --- a/lib/epiviewpoint/cases.ex +++ b/lib/epiviewpoint/cases.ex @@ -34,6 +34,10 @@ defmodule EpiViewpoint.Cases do def count_lab_results(), do: LabResult |> Repo.aggregate(:count) def create_lab_result!({attrs, audit_meta}), do: %LabResult{} |> change_lab_result(attrs) |> AuditingRepo.insert!(audit_meta) def import_lab_results(lab_result_data_file_string, originator), do: Import.import_data_file(lab_result_data_file_string, originator) + + def import_bulk_fhir_lab_results(lab_result_data_file_list, originator), + do: Import.import_bulk_fhir_data_file(lab_result_data_file_list, originator) + def list_lab_results(), do: LabResult.Query.all() |> Repo.all() def preload_initiating_lab_result(case_investigations_or_nil), do: case_investigations_or_nil |> Repo.preload(:initiating_lab_result) def preload_lab_results(person_or_people_or_nil), do: person_or_people_or_nil |> Repo.preload(lab_results: LabResult.Query.display_order()) diff --git a/lib/epiviewpoint/cases/import.ex b/lib/epiviewpoint/cases/import.ex index e9a20f81..ee31ba47 100644 --- a/lib/epiviewpoint/cases/import.ex +++ b/lib/epiviewpoint/cases/import.ex @@ -1,6 +1,7 @@ defmodule EpiViewpoint.Cases.Import do alias EpiViewpoint.Accounts alias EpiViewpoint.AuditLog + alias EpiViewpoint.BulkFhirParser alias EpiViewpoint.Cases alias EpiViewpoint.Cases.Import alias EpiViewpoint.Cases.LabResult @@ -59,6 +60,11 @@ defmodule EpiViewpoint.Cases.Import do ] end + def import_bulk_fhir_data_file(lab_result_data_file_list, originator) do + {:ok, bulk_fhir_data} = BulkFhirParser.parse_bulk_fhir(lab_result_data_file_list) + import_data_file(bulk_fhir_data, originator) + end + def import_data_file(_file, %{admin: false}), do: {:error, "Originator must be an admin"} def import_data_file(file, %Accounts.User{} = originator) do @@ -103,6 +109,7 @@ defmodule EpiViewpoint.Cases.Import do case Path.extname(file.file_name) do ".csv" -> DataFile.read(file.contents, :csv, &rename_headers/1, @fields) ".ndjson" -> DataFile.read(file.contents, :ndjson, &rename_headers/1, @fields) + ".bulk_fhir" -> DataFile.read(file.list, :bulk_fhir, &rename_headers/1, @fields) _ -> {:error, "Unsupported file type: #{file.extension}"} end end diff --git a/lib/epiviewpoint/data_file.ex b/lib/epiviewpoint/data_file.ex index cd645c9b..d4015242 100644 --- a/lib/epiviewpoint/data_file.ex +++ b/lib/epiviewpoint/data_file.ex @@ -21,6 +21,10 @@ defmodule EpiViewpoint.DataFile do read(string, header_transformer, headers, &parse_ndjson/1) end + def read(string, :bulk_fhir, header_transformer, headers) when is_list(string) do + read(string, header_transformer, headers, &parse_bulk_fhir/1) + end + def read(input, header_transformer, [required: required_headers, optional: optional_headers], parser) do with {:ok, df} <- parser.(input), {:ok, provided_headers} <- extract_provided_headers(df, header_transformer), @@ -84,6 +88,10 @@ defmodule EpiViewpoint.DataFile do DataFrame.load_ndjson(input) end + defp parse_bulk_fhir(input) do + {:ok, DataFrame.new(input)} + end + defp validate_csv(input) do EpiViewpoint.DataFile.Parser.parse_string(input, headers: true) input diff --git a/lib/epiviewpoint/r4/address.ex b/lib/epiviewpoint/r4/address.ex new file mode 100644 index 00000000..d74338b9 --- /dev/null +++ b/lib/epiviewpoint/r4/address.ex @@ -0,0 +1,68 @@ +defmodule EpiViewpoint.R4.Address do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :city, + :country, + :district, + :id, + :line, + :postal_code, + :state, + :text, + :type, + :use + ] + @required_fields [] + + embedded_schema do + # Fields + field(:city, :string) + field(:country, :string) + field(:district, :string) + field(:postal_code, :string) + field(:state, :string) + field(:text, :string) + + field(:line, {:array, :string}) + + # Enum + field(:type, Ecto.Enum, + values: [ + :postal, + :physical, + :both + ] + ) + + field(:use, Ecto.Enum, + values: [ + :home, + :work, + :temp, + :old, + :billing + ] + ) + + # Embed One + embeds_one(:period, EpiViewpoint.R4.Period) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:period) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/age.ex b/lib/epiviewpoint/r4/age.ex new file mode 100644 index 00000000..8ba38614 --- /dev/null +++ b/lib/epiviewpoint/r4/age.ex @@ -0,0 +1,47 @@ +defmodule EpiViewpoint.R4.Age do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :code, + :comparator, + :id, + :system, + :unit, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:code, :string) + field(:system, :string) + field(:unit, :string) + field(:value, :decimal) + + # Enum + field(:comparator, Ecto.Enum, + values: [ + :<, + :<=, + :>=, + :> + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/annotation.ex b/lib/epiviewpoint/r4/annotation.ex new file mode 100644 index 00000000..af33ac59 --- /dev/null +++ b/lib/epiviewpoint/r4/annotation.ex @@ -0,0 +1,46 @@ +defmodule EpiViewpoint.R4.Annotation do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :author_string, + :id, + :text, + :time + ] + @required_fields [] + + embedded_schema do + # Fields + field(:author_string, :string) + field(:text, :string) + field(:time, :utc_datetime_usec) + + # Embed One + embeds_one(:author_reference, EpiViewpoint.R4.Reference) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices("author") do + [:author_reference, :author_string] + end + + def choices("authorReference"), do: :error + + def choices("authorstring"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:author_reference) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/attachment.ex b/lib/epiviewpoint/r4/attachment.ex new file mode 100644 index 00000000..2ec77fb3 --- /dev/null +++ b/lib/epiviewpoint/r4/attachment.ex @@ -0,0 +1,44 @@ +defmodule EpiViewpoint.R4.Attachment do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :content_type, + :creation, + :data, + :hash, + :id, + :language, + :size, + :title, + :url + ] + @required_fields [] + + embedded_schema do + # Fields + field(:content_type, :string) + field(:creation, :utc_datetime_usec) + field(:data, :string) + field(:hash, :string) + field(:language, :string) + field(:size, :integer) + field(:title, :string) + field(:url, :string) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/codeable_concept.ex b/lib/epiviewpoint/r4/codeable_concept.ex new file mode 100644 index 00000000..720022ef --- /dev/null +++ b/lib/epiviewpoint/r4/codeable_concept.ex @@ -0,0 +1,32 @@ +defmodule EpiViewpoint.R4.CodeableConcept do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :text + ] + @required_fields [] + + embedded_schema do + # Fields + field(:text, :string) + + # Embed Many + embeds_many(:coding, EpiViewpoint.R4.Coding) + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:coding) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/coding.ex b/lib/epiviewpoint/r4/coding.ex new file mode 100644 index 00000000..c4818428 --- /dev/null +++ b/lib/epiviewpoint/r4/coding.ex @@ -0,0 +1,38 @@ +defmodule EpiViewpoint.R4.Coding do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :code, + :display, + :id, + :system, + :user_selected, + :version + ] + @required_fields [] + + embedded_schema do + # Fields + field(:code, :string) + field(:display, :string) + field(:system, :string) + field(:user_selected, :boolean) + field(:version, :string) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/contact_detail.ex b/lib/epiviewpoint/r4/contact_detail.ex new file mode 100644 index 00000000..a02981ea --- /dev/null +++ b/lib/epiviewpoint/r4/contact_detail.ex @@ -0,0 +1,32 @@ +defmodule EpiViewpoint.R4.ContactDetail do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :name + ] + @required_fields [] + + embedded_schema do + # Fields + field(:name, :string) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:telecom, EpiViewpoint.R4.ContactPoint) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> cast_embed(:telecom) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/contact_point.ex b/lib/epiviewpoint/r4/contact_point.ex new file mode 100644 index 00000000..b62d4840 --- /dev/null +++ b/lib/epiviewpoint/r4/contact_point.ex @@ -0,0 +1,61 @@ +defmodule EpiViewpoint.R4.ContactPoint do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :rank, + :system, + :use, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:rank, :integer) + field(:value, :string) + + # Enum + field(:system, Ecto.Enum, + values: [ + :phone, + :fax, + :email, + :pager, + :url, + :sms, + :other + ] + ) + + field(:use, Ecto.Enum, + values: [ + :home, + :work, + :temp, + :old, + :mobile + ] + ) + + # Embed One + embeds_one(:period, EpiViewpoint.R4.Period) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:period) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/contributor.ex b/lib/epiviewpoint/r4/contributor.ex new file mode 100644 index 00000000..29165086 --- /dev/null +++ b/lib/epiviewpoint/r4/contributor.ex @@ -0,0 +1,43 @@ +defmodule EpiViewpoint.R4.Contributor do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :name, + :type + ] + @required_fields [] + + embedded_schema do + # Fields + field(:name, :string) + + # Enum + field(:type, Ecto.Enum, + values: [ + :author, + :editor, + :reviewer, + :endorser + ] + ) + + # Embed Many + embeds_many(:contact, EpiViewpoint.R4.ContactDetail) + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:contact) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/count.ex b/lib/epiviewpoint/r4/count.ex new file mode 100644 index 00000000..d087f028 --- /dev/null +++ b/lib/epiviewpoint/r4/count.ex @@ -0,0 +1,47 @@ +defmodule EpiViewpoint.R4.Count do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :code, + :comparator, + :id, + :system, + :unit, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:code, :string) + field(:system, :string) + field(:unit, :string) + field(:value, :decimal) + + # Enum + field(:comparator, Ecto.Enum, + values: [ + :<, + :<=, + :>=, + :> + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/data_requirement.ex b/lib/epiviewpoint/r4/data_requirement.ex new file mode 100644 index 00000000..24b4a9de --- /dev/null +++ b/lib/epiviewpoint/r4/data_requirement.ex @@ -0,0 +1,57 @@ +defmodule EpiViewpoint.R4.DataRequirement do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :limit, + :must_support, + :profile, + :type + ] + @required_fields [] + + embedded_schema do + # Fields + field(:limit, :integer) + field(:type, :string) + + field(:must_support, {:array, :string}) + field(:profile, {:array, :string}) + + # Embed One + embeds_one(:subject_codeable_concept, EpiViewpoint.R4.CodeableConcept) + embeds_one(:subject_reference, EpiViewpoint.R4.Reference) + + # Embed Many + embeds_many(:code_filter, EpiViewpoint.R4.DataRequirement.CodeFilter) + embeds_many(:date_filter, EpiViewpoint.R4.DataRequirement.DateFilter) + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:sort, EpiViewpoint.R4.DataRequirement.Sort) + end + + def choices("subject") do + [:subject_codeable_concept, :subject_reference] + end + + def choices("subjectCodeableConcept"), do: :error + + def choices("subjectReference"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:subject_codeable_concept) + |> cast_embed(:subject_reference) + |> cast_embed(:code_filter) + |> cast_embed(:date_filter) + |> cast_embed(:extension) + |> cast_embed(:sort) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/data_requirement/code_filter.ex b/lib/epiviewpoint/r4/data_requirement/code_filter.ex new file mode 100644 index 00000000..73c9530f --- /dev/null +++ b/lib/epiviewpoint/r4/data_requirement/code_filter.ex @@ -0,0 +1,38 @@ +defmodule EpiViewpoint.R4.DataRequirement.CodeFilter do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :path, + :search_param, + :value_set + ] + @required_fields [] + + embedded_schema do + # Fields + field(:path, :string) + field(:search_param, :string) + field(:value_set, :string) + + # Embed Many + embeds_many(:code, EpiViewpoint.R4.Coding) + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:code) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/data_requirement/date_filter.ex b/lib/epiviewpoint/r4/data_requirement/date_filter.ex new file mode 100644 index 00000000..63684556 --- /dev/null +++ b/lib/epiviewpoint/r4/data_requirement/date_filter.ex @@ -0,0 +1,52 @@ +defmodule EpiViewpoint.R4.DataRequirement.DateFilter do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :path, + :search_param, + :value_date_time + ] + @required_fields [] + + embedded_schema do + # Fields + field(:path, :string) + field(:search_param, :string) + field(:value_date_time, :string) + + # Embed One + embeds_one(:value_duration, EpiViewpoint.R4.Duration) + embeds_one(:value_period, EpiViewpoint.R4.Period) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices("value") do + [:value_date_time, :value_period, :value_duration] + end + + def choices("valuedateTime"), do: :error + + def choices("valuePeriod"), do: :error + + def choices("valueDuration"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:value_duration) + |> cast_embed(:value_period) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/data_requirement/sort.ex b/lib/epiviewpoint/r4/data_requirement/sort.ex new file mode 100644 index 00000000..e99d7e19 --- /dev/null +++ b/lib/epiviewpoint/r4/data_requirement/sort.ex @@ -0,0 +1,41 @@ +defmodule EpiViewpoint.R4.DataRequirement.Sort do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :direction, + :id, + :path + ] + @required_fields [] + + embedded_schema do + # Fields + field(:path, :string) + + # Enum + field(:direction, Ecto.Enum, + values: [ + :ascending, + :descending + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/distance.ex b/lib/epiviewpoint/r4/distance.ex new file mode 100644 index 00000000..f4d0a7f5 --- /dev/null +++ b/lib/epiviewpoint/r4/distance.ex @@ -0,0 +1,47 @@ +defmodule EpiViewpoint.R4.Distance do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :code, + :comparator, + :id, + :system, + :unit, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:code, :string) + field(:system, :string) + field(:unit, :string) + field(:value, :decimal) + + # Enum + field(:comparator, Ecto.Enum, + values: [ + :<, + :<=, + :>=, + :> + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/dosage.ex b/lib/epiviewpoint/r4/dosage.ex new file mode 100644 index 00000000..7bd05eac --- /dev/null +++ b/lib/epiviewpoint/r4/dosage.ex @@ -0,0 +1,68 @@ +defmodule EpiViewpoint.R4.Dosage do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :as_needed_boolean, + :id, + :patient_instruction, + :sequence, + :text + ] + @required_fields [] + + embedded_schema do + # Fields + field(:as_needed_boolean, :boolean) + field(:patient_instruction, :string) + field(:sequence, :integer) + field(:text, :string) + + # Embed One + embeds_one(:as_needed_codeable_concept, EpiViewpoint.R4.CodeableConcept) + embeds_one(:max_dose_per_administration, EpiViewpoint.R4.Quantity) + embeds_one(:max_dose_per_lifetime, EpiViewpoint.R4.Quantity) + embeds_one(:max_dose_per_period, EpiViewpoint.R4.Ratio) + embeds_one(:method, EpiViewpoint.R4.CodeableConcept) + embeds_one(:route, EpiViewpoint.R4.CodeableConcept) + embeds_one(:site, EpiViewpoint.R4.CodeableConcept) + embeds_one(:timing, EpiViewpoint.R4.Timing) + + # Embed Many + embeds_many(:additional_instruction, EpiViewpoint.R4.CodeableConcept) + embeds_many(:dose_and_rate, EpiViewpoint.R4.Dosage.DoseAndRate) + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices("asNeeded") do + [:asNeeded_boolean, :asNeeded_codeable_concept] + end + + def choices("asNeededboolean"), do: :error + + def choices("asNeededCodeableConcept"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:as_needed_codeable_concept) + |> cast_embed(:max_dose_per_administration) + |> cast_embed(:max_dose_per_lifetime) + |> cast_embed(:max_dose_per_period) + |> cast_embed(:method) + |> cast_embed(:route) + |> cast_embed(:site) + |> cast_embed(:timing) + |> cast_embed(:additional_instruction) + |> cast_embed(:dose_and_rate) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/dosage/dose_and_rate.ex b/lib/epiviewpoint/r4/dosage/dose_and_rate.ex new file mode 100644 index 00000000..965eade0 --- /dev/null +++ b/lib/epiviewpoint/r4/dosage/dose_and_rate.ex @@ -0,0 +1,60 @@ +defmodule EpiViewpoint.R4.Dosage.DoseAndRate do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id + ] + @required_fields [] + + embedded_schema do + # Embed One + embeds_one(:dose_quantity, EpiViewpoint.R4.Quantity) + embeds_one(:dose_range, EpiViewpoint.R4.Range) + embeds_one(:rate_quantity, EpiViewpoint.R4.Quantity) + embeds_one(:rate_range, EpiViewpoint.R4.Range) + embeds_one(:rate_ratio, EpiViewpoint.R4.Ratio) + embeds_one(:type, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices("dose") do + [:dose_range, :dose_simple_quantity] + end + + def choices("doseRange"), do: :error + + def choices("doseSimpleQuantity"), do: :error + + def choices("rate") do + [:rate_ratio, :rate_range, :rate_simple_quantity] + end + + def choices("rateRatio"), do: :error + + def choices("rateRange"), do: :error + + def choices("rateSimpleQuantity"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:dose_quantity) + |> cast_embed(:dose_range) + |> cast_embed(:rate_quantity) + |> cast_embed(:rate_range) + |> cast_embed(:rate_ratio) + |> cast_embed(:type) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/duration.ex b/lib/epiviewpoint/r4/duration.ex new file mode 100644 index 00000000..f0004082 --- /dev/null +++ b/lib/epiviewpoint/r4/duration.ex @@ -0,0 +1,47 @@ +defmodule EpiViewpoint.R4.Duration do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :code, + :comparator, + :id, + :system, + :unit, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:code, :string) + field(:system, :string) + field(:unit, :string) + field(:value, :decimal) + + # Enum + field(:comparator, Ecto.Enum, + values: [ + :<, + :<=, + :>=, + :> + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/element.ex b/lib/epiviewpoint/r4/element.ex new file mode 100644 index 00000000..06fef920 --- /dev/null +++ b/lib/epiviewpoint/r4/element.ex @@ -0,0 +1,26 @@ +defmodule EpiViewpoint.R4.Element do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id + ] + @required_fields [] + + embedded_schema do + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/expression.ex b/lib/epiviewpoint/r4/expression.ex new file mode 100644 index 00000000..a62ea029 --- /dev/null +++ b/lib/epiviewpoint/r4/expression.ex @@ -0,0 +1,46 @@ +defmodule EpiViewpoint.R4.Expression do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :description, + :expression, + :id, + :language, + :name, + :reference + ] + @required_fields [] + + embedded_schema do + # Fields + field(:description, :string) + field(:expression, :string) + field(:name, :binary_id) + field(:reference, :string) + + # Enum + field(:language, Ecto.Enum, + values: [ + :"text/cql", + :"text/fhirpath", + :"application/x_fhir_query" + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/extension.ex b/lib/epiviewpoint/r4/extension.ex new file mode 100644 index 00000000..c902bef8 --- /dev/null +++ b/lib/epiviewpoint/r4/extension.ex @@ -0,0 +1,132 @@ +defmodule EpiViewpoint.R4.Extension do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :value_time, + :value_positive_int, + :value_date_time, + :value_markdown, + :value_date, + :value_unsigned_int, + :value_string, + :value_url, + :value_uri, + :value_integer, + :value_canonical, + :value_instant, + :url, + :value_oid, + :value_code, + :value_base64_binary, + :value_decimal, + :id, + :value_uuid, + :value_boolean, + :value_id + ] + @required_fields [] + + embedded_schema do + # Fields + field(:value_time, :string) + field(:value_positive_int, :integer) + field(:value_date_time, :string) + field(:value_markdown, :string) + field(:value_date, :string) + field(:value_unsigned_int, :integer) + field(:value_string, :string) + field(:value_url, :string) + field(:value_uri, :string) + field(:value_integer, :integer) + field(:value_canonical, :string) + field(:value_instant, :string) + field(:url, :string) + field(:value_oid, :string) + field(:value_code, :string) + field(:value_base64_binary, :string) + field(:value_decimal, :decimal) + field(:value_uuid, :string) + field(:value_boolean, :boolean) + field(:value_id, :string) + + # Embed One + embeds_one(:value_quantity, EpiViewpoint.R4.Quantity) + embeds_one(:value_expression, EpiViewpoint.R4.Expression) + embeds_one(:value_attachment, EpiViewpoint.R4.Attachment) + embeds_one(:value_identifier, EpiViewpoint.R4.Identifier) + embeds_one(:value_sampled_data, EpiViewpoint.R4.SampledData) + embeds_one(:value_parameter_definition, EpiViewpoint.R4.ParameterDefinition) + embeds_one(:value_timing, EpiViewpoint.R4.Timing) + embeds_one(:value_reference, EpiViewpoint.R4.Reference) + embeds_one(:value_contact_point, EpiViewpoint.R4.ContactPoint) + embeds_one(:value_age, EpiViewpoint.R4.Age) + embeds_one(:value_meta, EpiViewpoint.R4.Meta) + embeds_one(:value_annotation, EpiViewpoint.R4.Annotation) + embeds_one(:value_money, EpiViewpoint.R4.Money) + embeds_one(:value_usage_context, EpiViewpoint.R4.UsageContext) + embeds_one(:value_related_artifact, EpiViewpoint.R4.RelatedArtifact) + embeds_one(:value_contact_detail, EpiViewpoint.R4.ContactDetail) + embeds_one(:value_ratio, EpiViewpoint.R4.Ratio) + embeds_one(:value_distance, EpiViewpoint.R4.Distance) + embeds_one(:value_duration, EpiViewpoint.R4.Duration) + embeds_one(:value_human_name, EpiViewpoint.R4.HumanName) + embeds_one(:value_period, EpiViewpoint.R4.Period) + embeds_one(:value_range, EpiViewpoint.R4.Range) + embeds_one(:value_dosage, EpiViewpoint.R4.Dosage) + embeds_one(:value_contributor, EpiViewpoint.R4.Contributor) + embeds_one(:value_address, EpiViewpoint.R4.Address) + embeds_one(:value_signature, EpiViewpoint.R4.Signature) + embeds_one(:value_trigger_definition, EpiViewpoint.R4.TriggerDefinition) + embeds_one(:value_data_requirement, EpiViewpoint.R4.DataRequirement) + embeds_one(:value_count, EpiViewpoint.R4.Count) + embeds_one(:value_coding, EpiViewpoint.R4.Coding) + embeds_one(:value_codeable_concept, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:value_quantity) + |> cast_embed(:value_expression) + |> cast_embed(:value_attachment) + |> cast_embed(:value_identifier) + |> cast_embed(:value_sampled_data) + |> cast_embed(:value_parameter_definition) + |> cast_embed(:value_timing) + |> cast_embed(:value_reference) + |> cast_embed(:value_contact_point) + |> cast_embed(:value_age) + |> cast_embed(:value_meta) + |> cast_embed(:value_annotation) + |> cast_embed(:value_money) + |> cast_embed(:value_usage_context) + |> cast_embed(:value_related_artifact) + |> cast_embed(:value_contact_detail) + |> cast_embed(:value_ratio) + |> cast_embed(:value_distance) + |> cast_embed(:value_duration) + |> cast_embed(:value_human_name) + |> cast_embed(:value_period) + |> cast_embed(:value_range) + |> cast_embed(:value_dosage) + |> cast_embed(:value_contributor) + |> cast_embed(:value_address) + |> cast_embed(:value_signature) + |> cast_embed(:value_trigger_definition) + |> cast_embed(:value_data_requirement) + |> cast_embed(:value_count) + |> cast_embed(:value_coding) + |> cast_embed(:value_codeable_concept) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/human_name.ex b/lib/epiviewpoint/r4/human_name.ex new file mode 100644 index 00000000..145b3340 --- /dev/null +++ b/lib/epiviewpoint/r4/human_name.ex @@ -0,0 +1,57 @@ +defmodule EpiViewpoint.R4.HumanName do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :family, + :given, + :id, + :prefix, + :suffix, + :text, + :use + ] + @required_fields [] + + embedded_schema do + # Fields + field(:family, :string) + field(:text, :string) + + field(:given, {:array, :string}) + field(:prefix, {:array, :string}) + field(:suffix, {:array, :string}) + + # Enum + field(:use, Ecto.Enum, + values: [ + :usual, + :official, + :temp, + :nickname, + :anonymous, + :old, + :maiden + ] + ) + + # Embed One + embeds_one(:period, EpiViewpoint.R4.Period) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:period) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/identifier.ex b/lib/epiviewpoint/r4/identifier.ex new file mode 100644 index 00000000..a4be948a --- /dev/null +++ b/lib/epiviewpoint/r4/identifier.ex @@ -0,0 +1,52 @@ +defmodule EpiViewpoint.R4.Identifier do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :system, + :use, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:system, :string) + field(:value, :string) + + # Enum + field(:use, Ecto.Enum, + values: [ + :usual, + :official, + :temp, + :secondary, + :old + ] + ) + + # Embed One + embeds_one(:assigner, EpiViewpoint.R4.Reference) + embeds_one(:period, EpiViewpoint.R4.Period) + embeds_one(:type, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:assigner) + |> cast_embed(:period) + |> cast_embed(:type) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/meta.ex b/lib/epiviewpoint/r4/meta.ex new file mode 100644 index 00000000..0fd539fb --- /dev/null +++ b/lib/epiviewpoint/r4/meta.ex @@ -0,0 +1,41 @@ +defmodule EpiViewpoint.R4.Meta do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :last_updated, + :profile, + :source, + :version_id + ] + @required_fields [] + + embedded_schema do + # Fields + field(:last_updated, :utc_datetime_usec) + field(:source, :string) + field(:version_id, :binary_id) + + field(:profile, {:array, :string}) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:security, EpiViewpoint.R4.Coding) + embeds_many(:tag, EpiViewpoint.R4.Coding) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> cast_embed(:security) + |> cast_embed(:tag) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/money.ex b/lib/epiviewpoint/r4/money.ex new file mode 100644 index 00000000..0f9aa005 --- /dev/null +++ b/lib/epiviewpoint/r4/money.ex @@ -0,0 +1,32 @@ +defmodule EpiViewpoint.R4.Money do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :currency, + :id, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:currency, :string) + field(:value, :decimal) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/narrative.ex b/lib/epiviewpoint/r4/narrative.ex new file mode 100644 index 00000000..4f59f03f --- /dev/null +++ b/lib/epiviewpoint/r4/narrative.ex @@ -0,0 +1,43 @@ +defmodule EpiViewpoint.R4.Narrative do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :div, + :id, + :status + ] + @required_fields [ + :div + ] + + embedded_schema do + # Fields + field(:div, :string) + + # Enum + field(:status, Ecto.Enum, + values: [ + :generated, + :extensions, + :additional, + :empty + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/observation.ex b/lib/epiviewpoint/r4/observation.ex new file mode 100644 index 00000000..298c2703 --- /dev/null +++ b/lib/epiviewpoint/r4/observation.ex @@ -0,0 +1,184 @@ +defmodule EpiViewpoint.R4.Observation do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :value_time, + :effective_date_time, + :value_date_time, + :effective_instant, + :value_string, + :language, + :value_integer, + :implicit_rules, + :status, + :id, + :issued, + :value_boolean + ] + @required_fields [] + + @primary_key {:id, :binary_id, autogenerate: true} + schema "observation" do + # Constants + field(:resource_type, :string, virtual: true, default: "Observation") + + # Fields + field(:value_time, :string) + field(:effective_date_time, :string) + field(:value_date_time, :string) + field(:effective_instant, :string) + field(:value_string, :string) + field(:language, :string) + field(:value_integer, :integer) + field(:implicit_rules, :string) + field(:issued, :utc_datetime_usec) + field(:value_boolean, :boolean) + + # Enum + field(:status, Ecto.Enum, + values: [ + :registered, + :preliminary, + :final, + :amended, + :corrected, + :cancelled, + :entered_in_error, + :unknown + ] + ) + + # Embed One + embeds_one(:value_quantity, EpiViewpoint.R4.Quantity) + embeds_one(:effective_timing, EpiViewpoint.R4.Timing) + embeds_one(:value_sampled_data, EpiViewpoint.R4.SampledData) + embeds_one(:specimen, EpiViewpoint.R4.Reference) + embeds_one(:effective_period, EpiViewpoint.R4.Period) + embeds_one(:value_ratio, EpiViewpoint.R4.Ratio) + embeds_one(:encounter, EpiViewpoint.R4.Reference) + embeds_one(:code, EpiViewpoint.R4.CodeableConcept) + embeds_one(:subject, EpiViewpoint.R4.Reference) + embeds_one(:data_absent_reason, EpiViewpoint.R4.CodeableConcept) + embeds_one(:text, EpiViewpoint.R4.Narrative) + embeds_one(:body_site, EpiViewpoint.R4.CodeableConcept) + embeds_one(:meta, EpiViewpoint.R4.Meta) + embeds_one(:value_period, EpiViewpoint.R4.Period) + embeds_one(:value_range, EpiViewpoint.R4.Range) + embeds_one(:method, EpiViewpoint.R4.CodeableConcept) + embeds_one(:device, EpiViewpoint.R4.Reference) + embeds_one(:value_codeable_concept, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:contained, EpiViewpoint.R4.ResourceList) + embeds_many(:reference_range, EpiViewpoint.R4.Observation.ReferenceRange) + embeds_many(:derived_from, EpiViewpoint.R4.Reference) + embeds_many(:focus, EpiViewpoint.R4.Reference) + embeds_many(:based_on, EpiViewpoint.R4.Reference) + embeds_many(:component, EpiViewpoint.R4.Observation.Component) + embeds_many(:performer, EpiViewpoint.R4.Reference) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + embeds_many(:identifier, EpiViewpoint.R4.Identifier) + embeds_many(:part_of, EpiViewpoint.R4.Reference) + embeds_many(:has_member, EpiViewpoint.R4.Reference) + embeds_many(:category, EpiViewpoint.R4.CodeableConcept) + embeds_many(:note, EpiViewpoint.R4.Annotation) + embeds_many(:interpretation, EpiViewpoint.R4.CodeableConcept) + end + + def choices("effective") do + [:effective_date_time, :effective_period, :effective_timing, :effective_instant] + end + + def choices("effectivedateTime"), do: :error + + def choices("effectivePeriod"), do: :error + + def choices("effectiveTiming"), do: :error + + def choices("effectiveinstant"), do: :error + + def choices("value") do + [ + :value_quantity, + :value_codeable_concept, + :value_string, + :value_boolean, + :value_integer, + :value_range, + :value_ratio, + :value_sampled_data, + :value_time, + :value_date_time, + :value_period + ] + end + + def choices("valueQuantity"), do: :error + + def choices("valueCodeableConcept"), do: :error + + def choices("valuestring"), do: :error + + def choices("valueboolean"), do: :error + + def choices("valueinteger"), do: :error + + def choices("valueRange"), do: :error + + def choices("valueRatio"), do: :error + + def choices("valueSampledData"), do: :error + + def choices("valuetime"), do: :error + + def choices("valuedateTime"), do: :error + + def choices("valuePeriod"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + def path, do: "/Observation" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:value_quantity) + |> cast_embed(:effective_timing) + |> cast_embed(:value_sampled_data) + |> cast_embed(:specimen) + |> cast_embed(:effective_period) + |> cast_embed(:value_ratio) + |> cast_embed(:encounter) + |> cast_embed(:code) + |> cast_embed(:subject) + |> cast_embed(:data_absent_reason) + |> cast_embed(:text) + |> cast_embed(:body_site) + |> cast_embed(:meta) + |> cast_embed(:value_period) + |> cast_embed(:value_range) + |> cast_embed(:method) + |> cast_embed(:device) + |> cast_embed(:value_codeable_concept) + |> cast_embed(:extension) + |> cast_embed(:contained) + |> cast_embed(:reference_range) + |> cast_embed(:derived_from) + |> cast_embed(:focus) + |> cast_embed(:based_on) + |> cast_embed(:component) + |> cast_embed(:performer) + |> cast_embed(:modifier_extension) + |> cast_embed(:identifier) + |> cast_embed(:part_of) + |> cast_embed(:has_member) + |> cast_embed(:category) + |> cast_embed(:note) + |> cast_embed(:interpretation) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/observation/component.ex b/lib/epiviewpoint/r4/observation/component.ex new file mode 100644 index 00000000..7116f81a --- /dev/null +++ b/lib/epiviewpoint/r4/observation/component.ex @@ -0,0 +1,100 @@ +defmodule EpiViewpoint.R4.Observation.Component do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :value_boolean, + :value_date_time, + :value_integer, + :value_string, + :value_time + ] + @required_fields [] + + embedded_schema do + # Fields + field(:value_boolean, :boolean) + field(:value_date_time, :string) + field(:value_integer, :integer) + field(:value_string, :string) + field(:value_time, :string) + + # Embed One + embeds_one(:code, EpiViewpoint.R4.CodeableConcept) + embeds_one(:data_absent_reason, EpiViewpoint.R4.CodeableConcept) + embeds_one(:value_codeable_concept, EpiViewpoint.R4.CodeableConcept) + embeds_one(:value_period, EpiViewpoint.R4.Period) + embeds_one(:value_quantity, EpiViewpoint.R4.Quantity) + embeds_one(:value_range, EpiViewpoint.R4.Range) + embeds_one(:value_ratio, EpiViewpoint.R4.Ratio) + embeds_one(:value_sampled_data, EpiViewpoint.R4.SampledData) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:interpretation, EpiViewpoint.R4.CodeableConcept) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + embeds_many(:reference_range, EpiViewpoint.R4.Observation.ReferenceRange) + end + + def choices("value") do + [ + :value_quantity, + :value_codeable_concept, + :value_string, + :value_boolean, + :value_integer, + :value_range, + :value_ratio, + :value_sampled_data, + :value_time, + :value_date_time, + :value_period + ] + end + + def choices("valueQuantity"), do: :error + + def choices("valueCodeableConcept"), do: :error + + def choices("valuestring"), do: :error + + def choices("valueboolean"), do: :error + + def choices("valueinteger"), do: :error + + def choices("valueRange"), do: :error + + def choices("valueRatio"), do: :error + + def choices("valueSampledData"), do: :error + + def choices("valuetime"), do: :error + + def choices("valuedateTime"), do: :error + + def choices("valuePeriod"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:code) + |> cast_embed(:data_absent_reason) + |> cast_embed(:value_codeable_concept) + |> cast_embed(:value_period) + |> cast_embed(:value_quantity) + |> cast_embed(:value_range) + |> cast_embed(:value_ratio) + |> cast_embed(:value_sampled_data) + |> cast_embed(:extension) + |> cast_embed(:interpretation) + |> cast_embed(:modifier_extension) + |> cast_embed(:reference_range) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/observation/reference_range.ex b/lib/epiviewpoint/r4/observation/reference_range.ex new file mode 100644 index 00000000..c04b7a0f --- /dev/null +++ b/lib/epiviewpoint/r4/observation/reference_range.ex @@ -0,0 +1,44 @@ +defmodule EpiViewpoint.R4.Observation.ReferenceRange do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :text + ] + @required_fields [] + + embedded_schema do + # Fields + field(:text, :string) + + # Embed One + embeds_one(:age, EpiViewpoint.R4.Range) + embeds_one(:high, EpiViewpoint.R4.Quantity) + embeds_one(:low, EpiViewpoint.R4.Quantity) + embeds_one(:type, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:applies_to, EpiViewpoint.R4.CodeableConcept) + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:age) + |> cast_embed(:high) + |> cast_embed(:low) + |> cast_embed(:type) + |> cast_embed(:applies_to) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/organization.ex b/lib/epiviewpoint/r4/organization.ex new file mode 100644 index 00000000..00bfc582 --- /dev/null +++ b/lib/epiviewpoint/r4/organization.ex @@ -0,0 +1,68 @@ +defmodule EpiViewpoint.R4.Organization do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :active, + :alias, + :id, + :implicit_rules, + :language, + :name + ] + @required_fields [] + + @primary_key {:id, :binary_id, autogenerate: true} + schema "organization" do + # Constants + field(:resource_type, :string, virtual: true, default: "Organization") + + # Fields + field(:active, :boolean) + field(:implicit_rules, :string) + field(:language, :string) + field(:name, :string) + + field(:alias, {:array, :string}) + + # Embed One + embeds_one(:meta, EpiViewpoint.R4.Meta) + embeds_one(:part_of, EpiViewpoint.R4.Reference) + embeds_one(:text, EpiViewpoint.R4.Narrative) + + # Embed Many + embeds_many(:address, EpiViewpoint.R4.Address) + embeds_many(:contact, EpiViewpoint.R4.Organization.Contact) + embeds_many(:contained, EpiViewpoint.R4.ResourceList) + embeds_many(:endpoint, EpiViewpoint.R4.Reference) + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:identifier, EpiViewpoint.R4.Identifier) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + embeds_many(:telecom, EpiViewpoint.R4.ContactPoint) + embeds_many(:type, EpiViewpoint.R4.CodeableConcept) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + def path, do: "/Organization" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:meta) + |> cast_embed(:part_of) + |> cast_embed(:text) + |> cast_embed(:address) + |> cast_embed(:contact) + |> cast_embed(:contained) + |> cast_embed(:endpoint) + |> cast_embed(:extension) + |> cast_embed(:identifier) + |> cast_embed(:modifier_extension) + |> cast_embed(:telecom) + |> cast_embed(:type) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/organization/contact.ex b/lib/epiviewpoint/r4/organization/contact.ex new file mode 100644 index 00000000..06531120 --- /dev/null +++ b/lib/epiviewpoint/r4/organization/contact.ex @@ -0,0 +1,38 @@ +defmodule EpiViewpoint.R4.Organization.Contact do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id + ] + @required_fields [] + + embedded_schema do + # Embed One + embeds_one(:address, EpiViewpoint.R4.Address) + embeds_one(:name, EpiViewpoint.R4.HumanName) + embeds_one(:purpose, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + embeds_many(:telecom, EpiViewpoint.R4.ContactPoint) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:address) + |> cast_embed(:name) + |> cast_embed(:purpose) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> cast_embed(:telecom) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/parameter_definition.ex b/lib/epiviewpoint/r4/parameter_definition.ex new file mode 100644 index 00000000..5bf17c9f --- /dev/null +++ b/lib/epiviewpoint/r4/parameter_definition.ex @@ -0,0 +1,42 @@ +defmodule EpiViewpoint.R4.ParameterDefinition do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :documentation, + :id, + :max, + :min, + :name, + :profile, + :type, + :use + ] + @required_fields [] + + embedded_schema do + # Fields + field(:documentation, :string) + field(:max, :string) + field(:min, :integer) + field(:name, :string) + field(:profile, :string) + field(:type, :string) + field(:use, :string) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/patient.ex b/lib/epiviewpoint/r4/patient.ex new file mode 100644 index 00000000..8318feb8 --- /dev/null +++ b/lib/epiviewpoint/r4/patient.ex @@ -0,0 +1,108 @@ +defmodule EpiViewpoint.R4.Patient do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :active, + :multiple_birth_boolean, + :language, + :implicit_rules, + :birth_date, + :multiple_birth_integer, + :id, + :deceased_boolean, + :gender, + :deceased_date_time + ] + @required_fields [] + + @primary_key {:id, :binary_id, autogenerate: true} + schema "patient" do + # Constants + field(:resource_type, :string, virtual: true, default: "Patient") + + # Fields + field(:active, :boolean) + field(:multiple_birth_boolean, :boolean) + field(:language, :string) + field(:implicit_rules, :string) + field(:birth_date, :date) + field(:multiple_birth_integer, :integer) + field(:deceased_boolean, :boolean) + field(:deceased_date_time, :string) + + # Enum + field(:gender, Ecto.Enum, + values: [ + :male, + :female, + :other, + :unknown + ] + ) + + # Embed One + embeds_one(:marital_status, EpiViewpoint.R4.CodeableConcept) + embeds_one(:managing_organization, EpiViewpoint.R4.Reference) + embeds_one(:text, EpiViewpoint.R4.Narrative) + embeds_one(:meta, EpiViewpoint.R4.Meta) + + # Embed Many + embeds_many(:photo, EpiViewpoint.R4.Attachment) + embeds_many(:communication, EpiViewpoint.R4.Patient.Communication) + embeds_many(:name, EpiViewpoint.R4.HumanName) + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:telecom, EpiViewpoint.R4.ContactPoint) + embeds_many(:contained, EpiViewpoint.R4.ResourceList) + embeds_many(:link, EpiViewpoint.R4.Patient.Link) + embeds_many(:contact, EpiViewpoint.R4.Patient.Contact) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + embeds_many(:identifier, EpiViewpoint.R4.Identifier) + embeds_many(:general_practitioner, EpiViewpoint.R4.Reference) + embeds_many(:address, EpiViewpoint.R4.Address) + end + + def choices("deceased") do + [:deceased_boolean, :deceased_date_time] + end + + def choices("deceasedboolean"), do: :error + + def choices("deceaseddateTime"), do: :error + + def choices("multipleBirth") do + [:multipleBirth_boolean, :multipleBirth_integer] + end + + def choices("multipleBirthboolean"), do: :error + + def choices("multipleBirthinteger"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + def path, do: "/Patient" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:marital_status) + |> cast_embed(:managing_organization) + |> cast_embed(:text) + |> cast_embed(:meta) + |> cast_embed(:photo) + |> cast_embed(:communication) + |> cast_embed(:name) + |> cast_embed(:extension) + |> cast_embed(:telecom) + |> cast_embed(:contained) + |> cast_embed(:link) + |> cast_embed(:contact) + |> cast_embed(:modifier_extension) + |> cast_embed(:identifier) + |> cast_embed(:general_practitioner) + |> cast_embed(:address) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/patient/communication.ex b/lib/epiviewpoint/r4/patient/communication.ex new file mode 100644 index 00000000..dee2bebd --- /dev/null +++ b/lib/epiviewpoint/r4/patient/communication.ex @@ -0,0 +1,36 @@ +defmodule EpiViewpoint.R4.Patient.Communication do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :preferred + ] + @required_fields [] + + embedded_schema do + # Fields + field(:preferred, :boolean) + + # Embed One + embeds_one(:language, EpiViewpoint.R4.CodeableConcept) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:language) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/patient/contact.ex b/lib/epiviewpoint/r4/patient/contact.ex new file mode 100644 index 00000000..7faf2b52 --- /dev/null +++ b/lib/epiviewpoint/r4/patient/contact.ex @@ -0,0 +1,53 @@ +defmodule EpiViewpoint.R4.Patient.Contact do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :gender, + :id + ] + @required_fields [] + + embedded_schema do + # Enum + field(:gender, Ecto.Enum, + values: [ + :male, + :female, + :other, + :unknown + ] + ) + + # Embed One + embeds_one(:address, EpiViewpoint.R4.Address) + embeds_one(:name, EpiViewpoint.R4.HumanName) + embeds_one(:organization, EpiViewpoint.R4.Reference) + embeds_one(:period, EpiViewpoint.R4.Period) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + embeds_many(:relationship, EpiViewpoint.R4.CodeableConcept) + embeds_many(:telecom, EpiViewpoint.R4.ContactPoint) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:address) + |> cast_embed(:name) + |> cast_embed(:organization) + |> cast_embed(:period) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> cast_embed(:relationship) + |> cast_embed(:telecom) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/patient/link.ex b/lib/epiviewpoint/r4/patient/link.ex new file mode 100644 index 00000000..8798d8c7 --- /dev/null +++ b/lib/epiviewpoint/r4/patient/link.ex @@ -0,0 +1,43 @@ +defmodule EpiViewpoint.R4.Patient.Link do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :type + ] + @required_fields [] + + embedded_schema do + # Enum + field(:type, Ecto.Enum, + values: [ + :replaced_by, + :replaces, + :refer, + :seealso + ] + ) + + # Embed One + embeds_one(:other, EpiViewpoint.R4.Reference) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:other) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/period.ex b/lib/epiviewpoint/r4/period.ex new file mode 100644 index 00000000..9d083edc --- /dev/null +++ b/lib/epiviewpoint/r4/period.ex @@ -0,0 +1,32 @@ +defmodule EpiViewpoint.R4.Period do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :end, + :id, + :start + ] + @required_fields [] + + embedded_schema do + # Fields + field(:end, :utc_datetime_usec) + field(:start, :utc_datetime_usec) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/quantity.ex b/lib/epiviewpoint/r4/quantity.ex new file mode 100644 index 00000000..5db0642c --- /dev/null +++ b/lib/epiviewpoint/r4/quantity.ex @@ -0,0 +1,47 @@ +defmodule EpiViewpoint.R4.Quantity do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :code, + :comparator, + :id, + :system, + :unit, + :value + ] + @required_fields [] + + embedded_schema do + # Fields + field(:code, :string) + field(:system, :string) + field(:unit, :string) + field(:value, :decimal) + + # Enum + field(:comparator, Ecto.Enum, + values: [ + :<, + :<=, + :>=, + :> + ] + ) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/range.ex b/lib/epiviewpoint/r4/range.ex new file mode 100644 index 00000000..eef453e4 --- /dev/null +++ b/lib/epiviewpoint/r4/range.ex @@ -0,0 +1,32 @@ +defmodule EpiViewpoint.R4.Range do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id + ] + @required_fields [] + + embedded_schema do + # Embed One + embeds_one(:high, EpiViewpoint.R4.Quantity) + embeds_one(:low, EpiViewpoint.R4.Quantity) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:high) + |> cast_embed(:low) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/ratio.ex b/lib/epiviewpoint/r4/ratio.ex new file mode 100644 index 00000000..2b61c8c4 --- /dev/null +++ b/lib/epiviewpoint/r4/ratio.ex @@ -0,0 +1,32 @@ +defmodule EpiViewpoint.R4.Ratio do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id + ] + @required_fields [] + + embedded_schema do + # Embed One + embeds_one(:denominator, EpiViewpoint.R4.Quantity) + embeds_one(:numerator, EpiViewpoint.R4.Quantity) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:denominator) + |> cast_embed(:numerator) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/reference.ex b/lib/epiviewpoint/r4/reference.ex new file mode 100644 index 00000000..8eeb1625 --- /dev/null +++ b/lib/epiviewpoint/r4/reference.ex @@ -0,0 +1,38 @@ +defmodule EpiViewpoint.R4.Reference do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :display, + :id, + :reference, + :type + ] + @required_fields [] + + embedded_schema do + # Fields + field(:display, :string) + field(:reference, :string) + field(:type, :string) + + # Embed One + embeds_one(:identifier, EpiViewpoint.R4.Identifier) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:identifier) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/related_artifact.ex b/lib/epiviewpoint/r4/related_artifact.ex new file mode 100644 index 00000000..ce9afc08 --- /dev/null +++ b/lib/epiviewpoint/r4/related_artifact.ex @@ -0,0 +1,57 @@ +defmodule EpiViewpoint.R4.RelatedArtifact do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :citation, + :display, + :id, + :label, + :resource, + :type, + :url + ] + @required_fields [] + + embedded_schema do + # Fields + field(:citation, :string) + field(:display, :string) + field(:label, :string) + field(:resource, :string) + field(:url, :string) + + # Enum + field(:type, Ecto.Enum, + values: [ + :documentation, + :justification, + :citation, + :predecessor, + :successor, + :derived_from, + :depends_on, + :composed_of + ] + ) + + # Embed One + embeds_one(:document, EpiViewpoint.R4.Attachment) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:document) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/resource_list.ex b/lib/epiviewpoint/r4/resource_list.ex new file mode 100644 index 00000000..530cc946 --- /dev/null +++ b/lib/epiviewpoint/r4/resource_list.ex @@ -0,0 +1,21 @@ +defmodule EpiViewpoint.R4.ResourceList do + use Ecto.Schema + import Ecto.Changeset + + @fields [] + @required_fields [] + + embedded_schema do + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/sampled_data.ex b/lib/epiviewpoint/r4/sampled_data.ex new file mode 100644 index 00000000..2625bc49 --- /dev/null +++ b/lib/epiviewpoint/r4/sampled_data.ex @@ -0,0 +1,44 @@ +defmodule EpiViewpoint.R4.SampledData do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :data, + :dimensions, + :factor, + :id, + :lower_limit, + :period, + :upper_limit + ] + @required_fields [] + + embedded_schema do + # Fields + field(:data, :string) + field(:dimensions, :integer) + field(:factor, :decimal) + field(:lower_limit, :decimal) + field(:period, :decimal) + field(:upper_limit, :decimal) + + # Embed One + embeds_one(:origin, EpiViewpoint.R4.Quantity) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:origin) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/signature.ex b/lib/epiviewpoint/r4/signature.ex new file mode 100644 index 00000000..d25a7213 --- /dev/null +++ b/lib/epiviewpoint/r4/signature.ex @@ -0,0 +1,44 @@ +defmodule EpiViewpoint.R4.Signature do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :data, + :id, + :sig_format, + :target_format, + :when + ] + @required_fields [] + + embedded_schema do + # Fields + field(:data, :string) + field(:sig_format, :string) + field(:target_format, :string) + field(:when, :utc_datetime_usec) + + # Embed One + embeds_one(:on_behalf_of, EpiViewpoint.R4.Reference) + embeds_one(:who, EpiViewpoint.R4.Reference) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:type, EpiViewpoint.R4.Coding) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:on_behalf_of) + |> cast_embed(:who) + |> cast_embed(:extension) + |> cast_embed(:type) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/timing.ex b/lib/epiviewpoint/r4/timing.ex new file mode 100644 index 00000000..3c4a5394 --- /dev/null +++ b/lib/epiviewpoint/r4/timing.ex @@ -0,0 +1,37 @@ +defmodule EpiViewpoint.R4.Timing do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :event, + :id + ] + @required_fields [] + + embedded_schema do + field(:event, {:array, :utc_datetime_usec}) + + # Embed One + embeds_one(:code, EpiViewpoint.R4.CodeableConcept) + embeds_one(:repeat, EpiViewpoint.R4.Timing.Repeat) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:code) + |> cast_embed(:repeat) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/timing/repeat.ex b/lib/epiviewpoint/r4/timing/repeat.ex new file mode 100644 index 00000000..5ecf31ed --- /dev/null +++ b/lib/epiviewpoint/r4/timing/repeat.ex @@ -0,0 +1,100 @@ +defmodule EpiViewpoint.R4.Timing.Repeat do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :day_of_week, + :duration_unit, + :count, + :count_max, + :period_unit, + :when, + :frequency, + :period_max, + :duration_max, + :time_of_day, + :duration, + :frequency_max, + :offset, + :period, + :id + ] + @required_fields [] + + embedded_schema do + # Fields + field(:count, :integer) + field(:count_max, :integer) + field(:frequency, :integer) + field(:period_max, :decimal) + field(:duration_max, :decimal) + field(:duration, :decimal) + field(:frequency_max, :integer) + field(:offset, :integer) + field(:period, :decimal) + + field(:day_of_week, {:array, :string}) + field(:when, {:array, :string}) + field(:time_of_day, {:array, :time_usec}) + + # Enum + field(:duration_unit, Ecto.Enum, + values: [ + :s, + :min, + :h, + :d, + :wk, + :mo, + :a + ] + ) + + field(:period_unit, Ecto.Enum, + values: [ + :s, + :min, + :h, + :d, + :wk, + :mo, + :a + ] + ) + + # Embed One + embeds_one(:bounds_duration, EpiViewpoint.R4.Duration) + embeds_one(:bounds_range, EpiViewpoint.R4.Range) + embeds_one(:bounds_period, EpiViewpoint.R4.Period) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + embeds_many(:modifier_extension, EpiViewpoint.R4.Extension) + end + + def choices("bounds") do + [:bounds_duration, :bounds_range, :bounds_period] + end + + def choices("boundsDuration"), do: :error + + def choices("boundsRange"), do: :error + + def choices("boundsPeriod"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:bounds_duration) + |> cast_embed(:bounds_range) + |> cast_embed(:bounds_period) + |> cast_embed(:extension) + |> cast_embed(:modifier_extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/trigger_definition.ex b/lib/epiviewpoint/r4/trigger_definition.ex new file mode 100644 index 00000000..9364e4fd --- /dev/null +++ b/lib/epiviewpoint/r4/trigger_definition.ex @@ -0,0 +1,71 @@ +defmodule EpiViewpoint.R4.TriggerDefinition do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id, + :name, + :timing_date, + :timing_date_time, + :type + ] + @required_fields [] + + embedded_schema do + # Fields + field(:name, :string) + field(:timing_date, :string) + field(:timing_date_time, :string) + + # Enum + field(:type, Ecto.Enum, + values: [ + :named_event, + :periodic, + :data_changed, + :data_added, + :data_modified, + :data_removed, + :data_accessed, + :data_access_ended + ] + ) + + # Embed One + embeds_one(:condition, EpiViewpoint.R4.Expression) + embeds_one(:timing_reference, EpiViewpoint.R4.Reference) + embeds_one(:timing_timing, EpiViewpoint.R4.Timing) + + # Embed Many + embeds_many(:data, EpiViewpoint.R4.DataRequirement) + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices("timing") do + [:timing_timing, :timing_reference, :timing_date, :timing_date_time] + end + + def choices("timingTiming"), do: :error + + def choices("timingReference"), do: :error + + def choices("timingdate"), do: :error + + def choices("timingdateTime"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:condition) + |> cast_embed(:timing_reference) + |> cast_embed(:timing_timing) + |> cast_embed(:data) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint/r4/usage_context.ex b/lib/epiviewpoint/r4/usage_context.ex new file mode 100644 index 00000000..31c7f2f1 --- /dev/null +++ b/lib/epiviewpoint/r4/usage_context.ex @@ -0,0 +1,50 @@ +defmodule EpiViewpoint.R4.UsageContext do + use Ecto.Schema + import Ecto.Changeset + + @fields [ + :id + ] + @required_fields [] + + embedded_schema do + # Embed One + embeds_one(:code, EpiViewpoint.R4.Coding) + embeds_one(:value_codeable_concept, EpiViewpoint.R4.CodeableConcept) + embeds_one(:value_quantity, EpiViewpoint.R4.Quantity) + embeds_one(:value_range, EpiViewpoint.R4.Range) + embeds_one(:value_reference, EpiViewpoint.R4.Reference) + + # Embed Many + embeds_many(:extension, EpiViewpoint.R4.Extension) + end + + def choices("value") do + [:value_codeable_concept, :value_quantity, :value_range, :value_reference] + end + + def choices("valueCodeableConcept"), do: :error + + def choices("valueQuantity"), do: :error + + def choices("valueRange"), do: :error + + def choices("valueReference"), do: :error + + def choices(_), do: nil + + def version_namespace, do: EpiViewpoint.R4 + def version, do: "R4" + + def changeset(data \\ %__MODULE__{}, attrs) do + data + |> cast(attrs, @fields) + |> cast_embed(:code) + |> cast_embed(:value_codeable_concept) + |> cast_embed(:value_quantity) + |> cast_embed(:value_range) + |> cast_embed(:value_reference) + |> cast_embed(:extension) + |> validate_required(@required_fields) + end +end diff --git a/lib/epiviewpoint_web/controllers/import_controller.ex b/lib/epiviewpoint_web/controllers/import_controller.ex index 20706d19..9c250747 100644 --- a/lib/epiviewpoint_web/controllers/import_controller.ex +++ b/lib/epiviewpoint_web/controllers/import_controller.ex @@ -23,7 +23,34 @@ defmodule EpiViewpointWeb.ImportController do case result do {:ok, import_info} -> - {_imported_people, popped_import_info} = import_info |> Map.pop(:imported_people) + {_imported_people, popped_import_info} = Map.pop(import_info, :imported_people) + + conn + |> Session.set_last_file_import_info(popped_import_info) + |> redirect(to: ~p"/import/complete") + + {:error, [user_readable: user_readable_message]} -> + conn + |> Session.set_import_error_message(user_readable_message) + |> redirect(to: ~p"/import/start") + + {:error, %DateParsingError{user_readable: user_readable_message}} -> + conn + |> Session.set_import_error_message(user_readable_message) + |> redirect(to: ~p"/import/start") + end + end + + def create_bulk_fhir(%{assigns: %{current_user: %{admin: false}}} = conn, _file) do + redirect(conn, to: "/") + end + + def create_bulk_fhir(conn, %{"files" => plug_uploads}) do + result = plug_uploads |> UploadedFile.from_plug_uploads() |> Cases.import_bulk_fhir_lab_results(conn.assigns.current_user) + + case result do + {:ok, import_info} -> + {_imported_people, popped_import_info} = Map.pop(import_info, :imported_people) conn |> Session.set_last_file_import_info(popped_import_info) diff --git a/lib/epiviewpoint_web/live/import_live.html.heex b/lib/epiviewpoint_web/live/import_live.html.heex index 59c83bdf..d2774cee 100644 --- a/lib/epiviewpoint_web/live/import_live.html.heex +++ b/lib/epiviewpoint_web/live/import_live.html.heex @@ -6,12 +6,19 @@ <% end %>

- Choose a CSV or NDJSON file from your computer, click “Upload”, and then wait for the file to upload. + Choose a CSV or NDJSON file from your computer, click "Upload", and then wait for the file to upload.

- <%= form_tag("/import/upload", multipart: true, method: :post) %> + <%= form_tag("/import/upload", multipart: true, method: :post) do %> + + + <% end %> + +

Import Bulk FHIR

+

+ Choose bulk FHIR NDJSON files from your computer, click "Upload", and then wait for the file to upload. +

+ <%= form_tag("/import/upload_bulk_fhir", multipart: true, method: :post) do %> + + + <% end %> diff --git a/lib/epiviewpoint_web/router.ex b/lib/epiviewpoint_web/router.ex index 44e0b9df..af77d70f 100644 --- a/lib/epiviewpoint_web/router.ex +++ b/lib/epiviewpoint_web/router.ex @@ -56,6 +56,7 @@ defmodule EpiViewpointWeb.Router do live "/import/start", ImportLive, as: :import_start get "/import/complete", ImportController, :show post "/import/upload", ImportController, :create + post "/import/upload_bulk_fhir", ImportController, :create_bulk_fhir live "/case-investigations/:case_investigation_id/contact", CaseInvestigationContactLive, as: :create_case_investigation_contact live "/case-investigations/:case_investigation_id/contact/:id", CaseInvestigationContactLive, as: :edit_case_investigation_contact diff --git a/lib/epiviewpoint_web/uploaded_file.ex b/lib/epiviewpoint_web/uploaded_file.ex index 70a44c20..1967fed0 100644 --- a/lib/epiviewpoint_web/uploaded_file.ex +++ b/lib/epiviewpoint_web/uploaded_file.ex @@ -3,4 +3,8 @@ defmodule EpiViewpointWeb.UploadedFile do def from_plug_upload(%{path: path, filename: file_name}) do %{file_name: Zarex.sanitize(file_name), contents: File.read!(path)} end + + def from_plug_uploads(plug_uploads) do + Enum.map(plug_uploads, &from_plug_upload/1) + end end diff --git a/mix.exs b/mix.exs index ca4885e2..a9fa3dd9 100644 --- a/mix.exs +++ b/mix.exs @@ -56,6 +56,7 @@ defmodule EpiViewpoint.MixProject do {:gettext, "~> 0.11"}, {:inflex, "~> 2.1"}, {:jason, "~> 1.0"}, + {:kindling, "~> 1.0.1"}, {:logger_json, "~> 4.3"}, {:mix_audit, "~> 1.0", runtime: false}, {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index 2467b680..07177a7a 100644 --- a/mix.lock +++ b/mix.lock @@ -20,21 +20,28 @@ "explorer": {:hex, :explorer, "0.9.1", "9c6f175dfd2fa2f432d5fe9a86b81875438a9a1110af5b952c284842bee434e4", [:mix], [{:adbc, "~> 0.1", [hex: :adbc, repo: "hexpm", optional: true]}, {:aws_signature, "~> 0.3", [hex: :aws_signature, repo: "hexpm", optional: false]}, {:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:flame, "~> 0.3", [hex: :flame, repo: "hexpm", optional: true]}, {:fss, "~> 0.1", [hex: :fss, repo: "hexpm", optional: false]}, {:nx, "~> 0.4", [hex: :nx, repo: "hexpm", optional: true]}, {:rustler, "~> 0.34.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}, {:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "d88ec0e78f904c5eaf0b37c4a0ce4632de133515f3740a29fbddd2c0d0a78e77"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, "floki": {:hex, :floki, "0.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, "fss": {:hex, :fss, "0.1.1", "9db2344dbbb5d555ce442ac7c2f82dd975b605b50d169314a20f08ed21e08642", [:mix], [], "hexpm", "78ad5955c7919c3764065b21144913df7515d52e228c09427a004afe9c1a16b0"}, "gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"}, "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "kindling": {:hex, :kindling, "1.0.1", "1ddab6923d66ae400b5a4f130391546b6d3245ee072fbda4d3279e8708db0f1d", [:mix], [{:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:recase, "~> 0.7.0", [hex: :recase, repo: "hexpm", optional: false]}, {:req, "~> 0.4.11", [hex: :req, repo: "hexpm", optional: false]}, {:saxy, "~> 1.5.0", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "8e9831e1a5ce063cfa8332e4bf34899380f5eb540d89b04fc38314443cdd016a"}, "logger_json": {:hex, :logger_json, "4.3.0", "41aaaab2c2e1c071bfddbcc5a3f567884fdf312d222c7f1a7e3de6ab667774f7", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "001bbc34d7c451cfeed298c8384cb3aab10b364db2eb095c466c7a1a28bee6e0"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, "mix_audit": {:hex, :mix_audit, "1.0.0", "d2b5adbd69f34ba6b5b7d52812b1ba06f9110367e196d3ba5dba7753124cf8be", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "1b1ff6694e6eb12818ce5dcc276a39bbe03e27fcd11376c381bfe6b4900f2aa8"}, "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, "mox": {:hex, :mox, "1.0.1", "b651bf0113265cda0ba3a827fcb691f848b683c373b77e7d7439910a8d754d6e", [:mix], [], "hexpm", "35bc0dea5499d18db4ef7fe4360067a59b06c74376eb6ab3bd67e6295b133469"}, "nimble_csv": {:hex, :nimble_csv, "1.1.0", "b1dba4a86be9e03065c9de829050468e591f569100332db949e7ce71be0afc25", [:mix], [], "hexpm", "e986755bc302832cac429be6deda0fc9d82d3c82b47abefb68b3c17c9d949a3f"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_ownership": {:hex, :nimble_ownership, "0.3.2", "d4fa4056ade0ae33b5a9eb64554a1b3779689282e37513260125d2d6b32e4874", [:mix], [], "hexpm", "28b9a9f4094fda1aa8ca72f732ff3223eb54aa3eda4fed9022254de2c152b138"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_totp": {:hex, :nimble_totp, "0.1.3", "fb7db78c672b4a96dc4386d46809d66d4a6ee2b3d2e69f22f38b394676c1ed6c", [:mix], [], "hexpm", "1263ac5bcef8e73ba2729d09cf125906d4f87c6dad602730e854740ef65d6040"}, "number": {:hex, :number, "1.0.3", "932c8a2d478a181c624138958ca88a78070332191b8061717270d939778c9857", [:mix], [{:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "dd397bbc096b2ca965a6a430126cc9cf7b9ef7421130def69bcf572232ca0f18"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, @@ -54,7 +61,10 @@ "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "postgrex": {:hex, :postgrex, "0.19.0", "f7d50e50cb42e0a185f5b9a6095125a9ab7e4abccfbe2ab820ab9aa92b71dbab", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "dba2d2a0a8637defbf2307e8629cb2526388ba7348f67d04ec77a5d6a72ecfae"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"}, + "req": {:hex, :req, "0.4.14", "103de133a076a31044e5458e0f850d5681eef23dfabf3ea34af63212e3b902e2", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0 or ~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "2ddd3d33f9ab714ced8d3c15fd03db40c14dbf129003c4a3eb80fac2cc0b1b08"}, "rustler_precompiled": {:hex, :rustler_precompiled, "0.7.2", "097f657e401f02e7bc1cab808cfc6abdc1f7b9dc5e5adee46bf2fd8fdcce9ecf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "7663faaeadc9e93e605164dcf9e69168e35f2f8b7f2b9eb4e400d1a8e0fe2999"}, + "saxy": {:hex, :saxy, "1.5.0", "0141127f2d042856f135fb2d94e0beecda7a2306f47546dbc6411fc5b07e28bf", [:mix], [], "hexpm", "ea7bb6328fbd1f2aceffa3ec6090bfb18c85aadf0f8e5030905e84235861cf89"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"}, diff --git a/sample_data/bulk_fhir_download/Observation.ndjson b/sample_data/bulk_fhir_download/Observation.ndjson new file mode 100644 index 00000000..972027c8 --- /dev/null +++ b/sample_data/bulk_fhir_download/Observation.ndjson @@ -0,0 +1,2 @@ +{"resourceType":"Observation","id":"alice-result-1","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"]},"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/datereportedtolhd","valueDate":"08/05/2020"}],"category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"status":"final","code":{"text":"TestTest"},"subject":{"reference":"Patient/10000"},"effectiveDateTime":"08/01/2020","issued":"2020-08-03T00:00:00Z","performer":[{"reference":"Organization/city-hospital-lab"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"10828004","display":"Positive"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation","code":"POS","display":"Positive"}]}]} +{"resourceType":"Observation","id":"billy-result-1","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"]},"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/datereportedtolhd","valueDate":"08/05/2020"}],"category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"status":"final","code":{"text":"TestTest"},"subject":{"reference":"Patient/10004"},"effectiveDateTime":"08/02/2020","issued":"2020-08-04T00:00:00Z","performer":[{"reference":"Organization/city-hospital-lab"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260385009","display":"Negative"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation","code":"NEG","display":"Negative"}]}]} \ No newline at end of file diff --git a/sample_data/bulk_fhir_download/Organization.ndjson b/sample_data/bulk_fhir_download/Organization.ndjson new file mode 100644 index 00000000..d29695df --- /dev/null +++ b/sample_data/bulk_fhir_download/Organization.ndjson @@ -0,0 +1,2 @@ +{"resourceType":"Organization","id":"city-hospital-lab","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization|7.0.0"]},"active":true,"name":"City Hospital Lab"} +{"resourceType":"Organization","id":"lab-co-south","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-organization|7.0.0"]},"active":true,"name":"Lab Co South"} \ No newline at end of file diff --git a/sample_data/bulk_fhir_download/Patient.ndjson b/sample_data/bulk_fhir_download/Patient.ndjson new file mode 100644 index 00000000..04b6d91f --- /dev/null +++ b/sample_data/bulk_fhir_download/Patient.ndjson @@ -0,0 +1,2 @@ +{"resourceType":"Patient","id":"10000","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]},"identifier":[{"system":"urn:example:person_tid","value":"alice"}],"name":[{"family":"Testuser","given":["Alice"]}],"birthDate":"1970-01-01","telecom":[{"system":"phone","value":"1111111000"}],"address":[{"line":["1234 Test St"],"city":"City","state":"TS","postalCode":"00000"}],"gender":"female","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-occupation","valueString":"Doctor"}]} +{"resourceType":"Patient","id":"10004","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]},"identifier":[{"system":"urn:example:person_tid","value":"billy"}],"name":[{"family":"Testuser","given":["Billy"]}],"birthDate":"1971-01-01","telecom":[{"system":"phone","value":"1111111004"}],"address":[{"line":["1234 Test St"],"city":"City","state":"TS","postalCode":"00000"}],"gender":"female","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-occupation","valueString":"Doctor"}]} \ No newline at end of file diff --git a/test/epiviewpoint/data_file_test.exs b/test/epiviewpoint/data_file_test.exs index 649ad612..a07f13aa 100644 --- a/test/epiviewpoint/data_file_test.exs +++ b/test/epiviewpoint/data_file_test.exs @@ -197,5 +197,134 @@ defmodule EpiViewpoint.DataFileTest do ]} ) end + + test "reads a bulk FHIR file" do + input = [ + %{ + "first_name" => "Alice", + "last_name" => "Ant", + "dob" => "01/02/1970", + "sample_date" => "06/01/2020", + "result_date" => "06/03/2020", + "result" => "positive" + }, + %{ + "first_name" => "Billy", + "last_name" => "Bat", + "dob" => "03/04/1990", + "sample_date" => "06/06/2020", + "result_date" => "06/07/2020", + "result" => "negative" + } + ] + + {:ok, result} = + DataFile.read(input, :bulk_fhir, &Function.identity/1, + required: ~w{first_name last_name dob sample_date result_date result}, + optional: ~w{} + ) + + assert result == [ + %{ + "first_name" => "Alice", + "last_name" => "Ant", + "dob" => "01/02/1970", + "sample_date" => "06/01/2020", + "result_date" => "06/03/2020", + "result" => "positive" + }, + %{ + "first_name" => "Billy", + "last_name" => "Bat", + "dob" => "03/04/1990", + "sample_date" => "06/06/2020", + "result_date" => "06/07/2020", + "result" => "negative" + } + ] + end + + test "ignores unspecified headers in bulk FHIR" do + input = [ + %{ + "column_a" => "value_a", + "column_b" => "value_b", + "column_c" => "value_c" + } + ] + + {:ok, result} = + DataFile.read(input, :bulk_fhir, &Function.identity/1, + required: ~w{column_a}, + optional: ~w{column_b} + ) + + assert result == [%{"column_a" => "value_a", "column_b" => "value_b"}] + end + + test "fails if required header is missing in bulk FHIR" do + input = [ + %{ + "column_a" => "value_a", + "column_b" => "value_b" + } + ] + + assert {:error, :missing_headers, missing} = + DataFile.read(input, :bulk_fhir, &Function.identity/1, + required: ~w{column_a column_b column_c column_d}, + optional: ~w{} + ) + + assert Enum.sort(missing) == ["column_c", "column_d"] + end + + test "allows optional headers in bulk FHIR" do + input = [ + %{ + "column_a" => "value_a", + "column_b" => "value_b", + "optional_c" => "value_c" + } + ] + + {:ok, result} = + DataFile.read(input, :bulk_fhir, &Function.identity/1, + required: ~w{column_a column_b}, + optional: ~w{optional_c optional_d} + ) + + assert result == [%{"column_a" => "value_a", "column_b" => "value_b", "optional_c" => "value_c"}] + end + + test "header transformer transforms headers in bulk FHIR" do + input = [ + %{ + "first_name" => "Alice", + "last_name" => "Ant" + }, + %{ + "first_name" => "Billy", + "last_name" => "Bat" + } + ] + + {:ok, result} = + DataFile.read(input, :bulk_fhir, fn headers -> Enum.map(headers, &String.upcase/1) end, + required: ~w{FIRST_NAME LAST_NAME}, + optional: ~w{} + ) + + assert result == [ + %{ + "FIRST_NAME" => "Alice", + "LAST_NAME" => "Ant" + }, + %{ + "FIRST_NAME" => "Billy", + "LAST_NAME" => "Bat" + } + ] + end end end diff --git a/test/epiviewpoint_web/controllers/import_controller_test.exs b/test/epiviewpoint_web/controllers/import_controller_test.exs index fedb422a..441b6f7e 100644 --- a/test/epiviewpoint_web/controllers/import_controller_test.exs +++ b/test/epiviewpoint_web/controllers/import_controller_test.exs @@ -129,6 +129,46 @@ defmodule EpiViewpointWeb.ImportControllerTest do assert conn |> redirected_to() == "/import/start" assert "Invalid mm-dd-yyyy format: 06/02/bb" = Session.get_import_error_message(conn) end + + test "accepts multiple NDJSON files for bulk FHIR upload", %{conn: conn, tmp_dir: tmp_dir} do + patient_data = """ + {"resourceType":"Patient","id":"10000","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]},"identifier":[{"system":"urn:example:person_tid","value":"alice"}],"name":[{"family":"Testuser","given":["Alice"]}],"birthDate":"1970-01-01","telecom":[{"system":"phone","value":"1111111000"}],"address":[{"line":["1234 Test St"],"city":"City","state":"TS","postalCode":"00000"}],"gender":"female","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-occupation","valueString":"Doctor"}]} + {"resourceType":"Patient","id":"10004","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"]},"identifier":[{"system":"urn:example:person_tid","value":"billy"}],"name":[{"family":"Testuser","given":["Billy"]}],"birthDate":"1971-01-01","telecom":[{"system":"phone","value":"1111111004"}],"address":[{"line":["1234 Test St"],"city":"City","state":"TS","postalCode":"00000"}],"gender":"female","extension":[{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2106-3","display":"White"}},{"url":"text","valueString":"White"}]},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity","extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}]},{"url":"http://hl7.org/fhir/StructureDefinition/patient-occupation","valueString":"Doctor"}]} + """ + + observation_data = """ + {"resourceType":"Observation","id":"alice-result-1","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"]},"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/datereportedtolhd","valueDate":"08/05/2020"}],"category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"status":"final","code":{"text":"TestTest"},"subject":{"reference":"Patient/10000"},"effectiveDateTime":"08/01/2020","issued":"2020-08-03T00:00:00Z","performer":[{"reference":"Organization/city-hospital-lab"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"10828004","display":"Positive"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation","code":"POS","display":"Positive"}]}]} + {"resourceType":"Observation","id":"billy-result-1","meta":{"profile":["http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"]},"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/datereportedtolhd","valueDate":"08/05/2020"}],"category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"laboratory","display":"Laboratory"}]}],"status":"final","code":{"text":"TestTest"},"subject":{"reference":"Patient/10004"},"effectiveDateTime":"08/02/2020","issued":"2020-08-04T00:00:00Z","performer":[{"reference":"Organization/city-hospital-lab"}],"valueCodeableConcept":{"coding":[{"system":"http://snomed.info/sct","code":"260385009","display":"Negative"}]},"interpretation":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation","code":"NEG","display":"Negative"}]}]} + """ + + organization_data = """ + {"resourceType":"Organization","id":"org1","name":"Lab Co South"} + {"resourceType":"Organization","id":"org2","name":"City Hospital Lab"} + """ + + patient_file_path = Tempfile.write_ndjson!(patient_data, tmp_dir) + observation_file_path = Tempfile.write_ndjson!(observation_data, tmp_dir) + organization_file_path = Tempfile.write_ndjson!(organization_data, tmp_dir) + + conn = + conn + |> post(~p"/import/upload_bulk_fhir", %{ + "files" => [ + %Plug.Upload{path: patient_file_path, filename: "Patient.ndjson"}, + %Plug.Upload{path: observation_file_path, filename: "Observation.ndjson"}, + %Plug.Upload{path: organization_file_path, filename: "Organization.ndjson"} + ] + }) + + assert conn |> redirected_to() == "/import/complete" + + assert %EpiViewpoint.Cases.Import.ImportInfo{ + imported_lab_result_count: 2, + imported_person_count: 2, + total_lab_result_count: 2, + total_person_count: 2 + } = Session.get_last_file_import_info(conn) + end end describe "show" do