Created
April 30, 2014 15:55
-
-
Save holyjak/fb5cc1b18415df0c6daf to your computer and use it in GitHub Desktop.
Code for http://wp.me/pTHiC-10R, part 2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(require '[cheshire.core :refer [parse-string]]) | |
(require '[clojure.set :refer [subset? difference ]]) | |
(defn db-array [col] (reify java.sql.Array (getArray [_] (object-array col)))) | |
(defn- json->data [data fields] | |
{:pre [(sequential? data) (sequential? fields)]} | |
(map (fn [data] | |
(reduce (fn to-json [data field] | |
{:pre [(map? data) (string? (get data field)) (keyword? field)]} | |
(assoc data field (parse-string (get data field) true))) | |
data fields)) data)) | |
(defn- discount-size [{:keys [amount percent] :as discount}] | |
{:pre [(number? amount) (number? percent) (<= 0 amount) (<= 0 percent 100)] | |
:post [(#{:high :normal} %)]} | |
(if (or | |
(> amount 10000) | |
(> percent 5)) | |
:high | |
:normal)) | |
(defn- jdbc-array-to-set | |
"Convert a PostgreSQL JDBC4Array inside the map `m` - at the key `k` - into a se" | |
[key m] | |
{:pre [(keyword? key) (map? m) (let [a (key m)] (or (nil? a) (instance? java.sql.Array a)))]} | |
(update-in m [key] #(some->> % (.getArray) (into #{})))) | |
(defn car? [{:keys [id] :as car}] | |
(and (map? car) id)) | |
(defn- compute-discount | |
"Derive the :discount map based on the car's own discount and its active campaign, if applicable" | |
[{{:keys [discount_amount discount_percent] :as json} :json | |
:keys [campaign_discount_amount campaign_discount_percent] :as car}] | |
{:pre [(car? car) (map? json) (number? discount_amount) (number? discount_percent)] | |
:post [(:discount %) (:discount-size %)]} | |
(let [discount? (:use_campaign car) | |
amount (if discount? | |
(apply + (remove nil? [discount_amount campaign_discount_amount])) | |
discount_amount) | |
percent (if discount? | |
(apply + (remove nil? [discount_percent campaign_discount_percent])) | |
discount_percent) | |
discount {:amount amount | |
:percent percent} | |
discount-size (discount-size discount) | |
] | |
(assoc car :discount discount :discount-size discount-size))) | |
(defn select-campaign | |
"Return a single car map with a selected campaign." | |
[{:keys [campaigns] :as car}] | |
{:pre [(car? car) (sequential? campaigns)] | |
:post [(contains? % :best-campaign)]} | |
(let [best-campaign (->> campaigns | |
(filter :active) | |
(sort-by :use_campaign) ;; true, if any, will be last | |
last)] | |
(-> car | |
(dissoc :campaigns) | |
(assoc :best-campaign best-campaign)))) | |
(defn nest-campaign [car] | |
;; :pre check for campaing keys would require too much repetition => an assert instead | |
{:pre [(car? car)] | |
:post [((comp map? :campaign) %)]} | |
(let [ks (set (keys car)) | |
campaign-ks #{:campaign_discount_amount :campaign_discount_percent :use_campaign :active} | |
campaign (select-keys car campaign-ks)] | |
(assert (subset? campaign-ks ks) | |
(str "Campaign keys missing from the car " (:id car) ": " | |
(difference campaign-ks ks))) | |
(-> (apply dissoc car campaign-ks) | |
(assoc :campaign campaign)))) | |
(defn group-rows-by-car [cars-raw] | |
{:pre [(sequential? cars-raw) (every? map? cars-raw)] | |
:post [(sequential? %) (every? vector? %)]} | |
(vals (group-by :id cars-raw))) | |
(defn join-campaigns [[car+campaign :as all]] | |
{:pre [(sequential? all) (:campaign car+campaign)] | |
:post [(:campaigns %)]} | |
(-> car+campaign | |
(assoc :campaigns | |
(map :campaign all)) | |
(dissoc :campaign))) | |
(defn refine-car [car] | |
{:pre [(car? car)] | |
:post [(:discount %)]} ; keywords and :category_ref are optional | |
(->> car | |
(jdbc-array-to-set :category_ref) | |
(jdbc-array-to-set :keywords) | |
(#(update-in % [:keywords] (partial remove nil?))) ;; {NULL} => [] | |
(select-campaign) | |
(compute-discount))) | |
(defn refine-cars | |
"Process get-cars query result set - derive additional data, transform values into better ones | |
There is one row per car and campaign, a car may have more campaigns - we pick the best one. | |
" | |
[cars-raw] | |
(->> cars-raw | |
(#(json->data % [:json])) | |
(map nest-campaign) | |
(group-rows-by-car) | |
(map join-campaigns) | |
(map refine-car) | |
)) | |
(refine-cars [ | |
{:id 1 | |
:json "{\"discount_amount\":9000,\"discount_percent\":0}" | |
:campaign_discount_amount 2000 | |
:campaign_discount_percent nil | |
:use_campaign false | |
:active true | |
:keywords (db-array ["fast"]) | |
:category_ref (db-array [])}]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment