HealthSamurai / xmas-hackathon-2021

1 stars 0 forks source link

ZenJUTE #10

Open apostaat opened 2 years ago

apostaat commented 2 years ago

INB4:

What is Jute?

What Jute does?

[YAML] -> [JuteTemplate] -> [YAML]

What is jute.clj?

IDEA

What we want to improve?

Sometimes, the syntax of JUTE is quite verbose.

For example 'map' directive requires declaring 'as' and 'body' variables:

type: book
author: $ book.author.name
title: $ book.title
content: 
  $map: $ book.chapters.*(this.type = "content")
  $as: i
  $body: $ i.content

How it may look like?

The idea is to use native Clojure data structures and functions:

We expect that it may look like this:

{:type "book"
  :author [:author :name]
  :title :title
  :content (partial mapv (fn [{:keys [type content]}] (when (= "content" type) content )))}

We expect that zen library will be handy there: https://github.com/zen-lang/zen

What is Zen?

Plan

Team:

@mlapshin Mikhail Lapshin @VictorGus Victor Gusakov @thezorkij Mikhail Pravilenko @apostaat Artem Alexeev

apostaat commented 2 years ago

Classic Jute:

id: financial-transaction
resourceType: Mapping
returns: resource
body:
  $let:
    - performer_external_identifier:
       - type:
          coding:
            - code: "EXTID"
         value: $ performer.id
    - referring_external_identifier:
       - type:
          coding:
            - code: "EXTID"
         value: $ referring.id
    - named_identifiers:
        $fn: ["ext_id", "id","entity"]
        $body:
          $if: $ ext_id && id && entity
          $then:
            $map: $ concat(ext_id, id)
            $as: idn
            $body:
              last_name: $ entity.name.0.family
              first_name: $ entity.name.0.given.0
              middle_initial: $ entity.name.0.given.1
              suffix: $ entity.name.0.suffix.0
              identifier: $ idn.value
              identifier_type:
                $if: $ idn.value && idn.type.coding.0.code
                $then: $ idn.type.coding.0.code
                $else:
                  $if: $ idn.value
                  $then: "UPIN"
  $body:
    transaction_date:
      from: $ currentDate
    transaction_type: "CG"
    department:
      code: $ organization.id
      name: $ organization.name
    patient_location:
      id: $ location.id
      name: $ location.name
    diagnosis:
      $map: $ diagnosis
      $as: dg
      $body:
        code: $ dg.code
        display: $ dg.display
        system:
          $if: $ dg.system = "ICD10"
          $then: "I10"
          $else: "I9"
    performing_provider:
      $call: named_identifiers
      $args:
        - $ performer_external_identifier
        - $ performer.identifier
        - $ performer
    referring_practitioner:
      $call: named_identifiers
      $args:
        - $ referring_external_identifier
        - $ referring.identifier
        - $ referring
    procedure: $ procedure

Zen Jute


{ns financial-transaction

;; boilerplate like
;; resourceType, returns, body
;; should be wrapped programmatically

 GetNameFn
 {:zen/tag #{get-name}
  :zj/type :lambda
  :zj/code (fn [{:keys name}]
             {:last_name (-> name
                             first
                             :family)
              :first_name (-> name
                              first
                              :given
                              first)
              :middle_initial (-> name
                                  first
                                  :given
                                  second)
              :suffix (-> name
                          first
                          :suffix
                          :first)})}

 GetIdFn
 {:zen/tag #{get-id-fn}
  :zj/type :lambda
  :zj/code (fn [{:keys [value]}]
             {:identifier value
              :identifier_type
              (if-let [code (and value (-> value :type :coding first :code))]
                code
                (and value "UPIN"))})}

 ;; thus we use lambdas only and code is just a string
 ;; so our macro is just format function
 ;;
 ;; (defn pseudo-macro
 ;;   [s & args]
 ;;  (apply format s args))

 ;; (pseudo-macro "(merge (%s id) (%s entity))"
 ;;               '(fn [x] {:k 5})
 ;;               '(fn [y] {:j 9}))

 ;; => "(merge ((fn [x] {:k 5}) id) ((fn [y] {:j 9}) entity))"

 ;; so eval-fn and args is just a macro that
 ;; wraps string of code inside brackets and passes it
 ;; to reader

 ;; (TODO) if you have ideas how to realize closures and
 ;; pass functions and arguments 'from the outside'
 ;; without macro - it will be nice

 GetNamedIdentifiers
 {:zen/tag #{named-identifiers}
  :zj/import-fn #{get-id-fn get-name-fn}
  :zj/type :lambda

  ;; pred is called before evaluation of zj/code
  ;; if falsey -> returns nil as result

  :zj/pred {:zj/code (fn [x y z] (and x y z))}
  :zj/code {:zj/macro
            (fn [extid id entity]
              (mapv (fn [idn#] (merge (%s entity)
                                      (%s idn#))))
              [extid id])
            :zj/args [{:zj/eval-fn get-id-fn}
                      {:zj/eval-fn get-name-fn}]}}

 FT1
{:zen/tag #{ft1-mapping}
 :zj/import-fn #{get-diagnosis named-identifiers}

 :transaction_date {:from ^zj/path[:currentDate]}
 :transaction_type "CG"
 :department {:code ^zj/path[:organization :id]
              :name  ^zj/path[:organization :name]}
 :patient_location {:id ^zj/path[:location :id ]
                    :name ^zj/path[:location :name]}
 :diagnosis {:zj/eval-fn get-diagnosis
             :zj/args ^zj/path[:diagnosis]}
 :performing_provider {:zj/eval-fn named-identifiers
                        :zj/args [^zj/path[:performer_external_identifier]
                                  ^zj/path[:performer :identifier]
                                  ^zj/path[:performer]]}
 :referring_practitioner {:zj/eval-fn named-identifiers
                          :zj/args [^zj/path[:referring_external_identifier]
                                  ^zj/path[:referring :identifier]
                                  ^zj/path[:referring]]}}