Search code examples
clojure

How to solve Medication posession ratio (MPR) problem?


I am trying to solve medication possession ration problem.

I tried using intervals and then comparing them with observation period.

(ns clara-rules.mpr-new
  (:require [clj-time.core :as t]
            [clj-time.format :as f]))

(defn observe [interval]
  (def start (map #(f/parse (f/formatter "dd MM yyyy") (% :start_)) interval))
  (def end (map #(f/parse (f/formatter "dd MM yyyy") (% :end_)) interval))

  )
(observe '({:start_ "20 01 2012" :end_ "20 02 2012"}
           {:start_ "20 02 2012" :end_ "20 03 2012"}
           {:start_ "20 04 2012" :end_ "20 05 2012"}
           {:start_ "20 06 2012" :end_ "20 07 2012"}))


(defn calc[a b]
(def start_date (f/parse (f/formatter "dd MM yyyy") a)
  )

  (def end_date (f/parse (f/formatter "dd MM yyyy")b)
      )

  (def observation_period(t/in-days(t/interval start_date end_date)))
  (println observation_period)
  )
(calc "01 02 2012" "01 12 2012")

(defn mpr_ratio[]

  (def overlp (map #(t/overlap (t/interval start_date end_date) (t/interval %1 %2))start end))

  (def x (map #(t/in-days %)overlp))
  (println x)
  (def ratio (reduce +(map #(float(*(/ % observation_period)100))x)))
  (println ratio)
  )
(mpr_ratio)

I expect the calculated ratio of all the intervals and the observation period.


Solution

  • A few suggestions:

    1. Use function with return value instead of relying on side effect (println and def inside a function) to obtain your result
    2. Use def only for top level var, use let for any temporary var inside a function
    3. Create some single purpose functions (e.g. function to parse date; function to convert list of observations to dates), then use those functions to compose your solution
    4. Use thread macro (e.g. -> ->>) to improve readability

    Possible solution:

    (def fmt
      "default date formatter"
      (f/formatter "dd MM yyyy"))
    
    (def ->date
      "utility function to convert string to date"
      (partial f/parse fmt))
    
    (->date "01 02 2012")
    ;; => #object[org.joda.time.DateTime 0x6e880ccd "2012-02-01T00:00:00.000Z"]
    
    (defn ->observations
      [intervals]
      (->> intervals
           (map (fn [{:keys [start_ end_]}]
                  {:start (->date start_)
                   :end   (->date end_)}))))
    
    (->observations '({:start_ "20 01 2012" :end_ "20 02 2012"}
                      {:start_ "20 02 2012" :end_ "20 03 2012"}))
    ;; => ({:start #object[org.joda.time.DateTime 0x4eb450bd "2012-01-20T00:00:00.000Z"], :end #object[org.joda.time.DateTime 0x558bd20f "2012-02-20T00:00:00.000Z"]} {:start #object[org.joda.time.DateTime 0x4009d145 "2012-02-20T00:00:00.000Z"], :end #object[org.joda.time.DateTime 0x42e32d6 "2012-03-20T00:00:00.000Z"]})
    
    (defn mpr_ratio
      [start_date end_date intervals]
      (let [intrvrl   (t/interval start_date end_date)
            obsrv-prd (t/in-days intrvrl)]
        (->> (map t/interval (map :start intervals) (map :end intervals))
             (map (partial t/overlap intrvrl))
             (map t/in-days)
             (map #(-> %
                       (/ obsrv-prd)
                       (* 100.0)))
             (reduce +))))
    
    (mpr_ratio (->date "01 02 2012")
               (->date "01 12 2012")
               (->observations '({:start_ "20 01 2012" :end_ "20 02 2012"}
                                 {:start_ "20 02 2012" :end_ "20 03 2012"}
                                 {:start_ "20 04 2012" :end_ "20 05 2012"}
                                 {:start_ "20 06 2012" :end_ "20 07 2012"})))
    ;; => 35.526315789473685
    

    UPDATE - PDC utility function

    (defn covered [state interval]
      (if (some #(t/overlaps? interval %) state)
        (->> state
             (map #(if (t/overlaps? interval %)
                     (t/interval (t/min-date (t/start %) (t/start interval))
                                 (t/max-date (t/end %) (t/end interval)))
                     %))
             (into (empty state)))
        (conj state interval)))
    
    (covered #{} (t/interval (->date "01 02 2012") (->date "05 02 2012")))
    ;; => #{#object[org.joda.time.Interval 0x30addc0b "2012-02-01T00:00:00.000Z/2012-02-05T00:00:00.000Z"]}
    (covered *1 (t/interval (->date "04 02 2012") (->date "07 02 2012")))
    ;; => #{#object[org.joda.time.Interval 0x7f8893c1 "2012-02-01T00:00:00.000Z/2012-02-07T00:00:00.000Z"]}
    (covered *1 (t/interval (->date "02 03 2012") (->date "07 03 2012")))
    ;; => #{#object[org.joda.time.Interval 0x7f8893c1 "2012-02-01T00:00:00.000Z/2012-02-07T00:00:00.000Z"] #object[org.joda.time.Interval 0x67adc8d1 "2012-03-02T00:00:00.000Z/2012-03-07T00:00:00.000Z"]}
    (reduce + (map (comp inc t/in-days) *1))
    ;; => 13
    

    pdc function in full: (note that only one line needs to be added)

    (defn pdc_ratio
      [start_date end_date intervals]
      (let [intrvrl   (t/interval start_date end_date)
            obsrv-prd (t/in-days intrvrl)]
        (->> (map t/interval (map :start intervals) (map :end intervals))
             (map (partial t/overlap intrvrl))
             ;; get covered days only
             (reduce covered #{})
             (map t/in-days)
             (map #(-> %
                       (/ obsrv-prd)
                       (* 100.0)))
             (reduce +))))