summaryrefslogtreecommitdiff
path: root/models/crop-requirement.rkt
blob: 6ddf1aa0408d7eecaff3e25ddc6de937318aa191 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#lang racket

(provide crop-requirement
         crop-requirement?
         crop-requirement-id
         crop-requirement-profile
         crop-requirement-crop-id
         (rename-out [crop-requirement-nutrient-values crop-requirement-values])
         (contract-out
          [create-crop-requirement!
           (->* (string? (listof nutrient-value-pair/c)) ((or/c #f crop?)) crop-requirement?)]
          [get-crop-requirements (-> (listof crop-requirement?))]
          [get-crop-requirement
           (->* ()
                (#:id (or/c #f exact-nonnegative-integer?) #:profile (or/c #f string?))
                (or/c crop-requirement? #f))]
          [get-crop-requirement-values (-> crop-requirement? (listof nutrient-value-pair/c))]
          [get-crop-requirement-value (-> crop-requirement? nutrient? number?)]
          [delete-crop-requirement! (-> crop-requirement? void?)]
          [average-crop-requirement-nutrient-values
           (-> (listof (cons/c crop-requirement? (and/c real? (>=/c 0) (<=/c 100))))
               (listof nutrient-value-pair/c))]))

(require racket/contract
         db
         sql
         "../db/conn.rkt"
         "nutrient.rkt"
         "crop.rkt")

;; Instances of this struct are persisted in the crop_requirements table.
(struct crop-requirement (id profile crop-id) #:transparent)


;; CREATE

(define (create-crop-requirement! profile nutrient-values [crop #f])
  (or
   (get-crop-requirement #:profile profile)
   (with-tx
    (query-exec
     (current-conn)
     (if crop
         (insert #:into crop_requirements #:set [crop_id ,(crop-id crop)] [profile ,profile])
         (insert #:into crop_requirements #:set [profile ,profile])))
    (define cr-id
      (query-value (current-conn) (select id #:from crop_requirements #:where (= profile ,profile))))
    (query-exec (current-conn) (insert #:into nutrient_value_sets #:set [crop_requirement_id ,cr-id]))
    (define nvs-id
      (query-value (current-conn)
                   (select id #:from nutrient_value_sets #:where (= crop_requirement_id ,cr-id))))
    (for ([nv nutrient-values])
      (match-define (cons n v) nv)
      (query-exec (current-conn)
                  (insert #:into nutrient_values
                          #:set [value_set_id ,nvs-id]
                          [nutrient_id ,(nutrient-id n)]
                          [value_ppm ,v])))
    (get-crop-requirement #:profile profile))))

;; READ

(define joined
  (table-expr-qq (inner-join (inner-join (inner-join (as crop_requirements cr)
                                                     (as nutrient_value_sets nvs)
                                                     #:on (= nvs.crop_requirement_id cr.id))
                                         (as nutrient_values nv)
                                         #:on (= nv.value_set_id nvs.id))
                             (as nutrients n)
                             #:on (= n.id nv.nutrient_id))))

(define (grouped-row->crop-requirement row)
  (match-define (vector cr-id profile crop-id residuals) row)
  (define nutrient-value-pairs (residuals->nutrient-value-pairs residuals))
  (crop-requirement cr-id profile crop-id nutrient-value-pairs))

(define (get-crop-requirements)
  (define grouped-rows
    (query-rows (current-conn)
                (select cr.id
                        cr.profile
                        cr.crop_id
                        n.id
                        n.canonical_name
                        n.formula
                        nv.value_ppm
                        #:from (TableExpr:AST ,joined)
                        #:order-by cr.id
                        #:asc)
                #:group '#(0 1 2)))
  (for/list ([row grouped-rows])
    (grouped-row->crop-requirement row)))

(define (get-crop-requirement #:id [cr-id #f] #:profile [profile #f] #:crop-id [crop-id #f])
  (define where
    (cond
      [(and cr-id profile crop-id)
       (scalar-expr-qq (and (= cr.id ,cr-id) (= cr.profile ,profile) (= cr.crop_id ,crop-id)))]
      [cr-id (scalar-expr-qq (= cr.id ,cr-id))]
      [profile (scalar-expr-qq (= cr.profile ,profile))]
      [crop-id (scalar-expr-qq (= cr.crop_id ,crop-id))]
      [else (error 'get-crop-requirement "one of #:id, #:profile or #:crop-id must be provided")]))

  (define grouped-rows
    (query-rows (current-conn)
                (select cr.id
                        cr.profile
                        cr.crop_id
                        n.id
                        n.canonical_name
                        n.formula
                        nv.value_ppm
                        #:from (TableExpr:AST ,joined)
                        #:where (ScalarExpr:AST ,where))
                #:group '#(0 1 2)))

  (match grouped-rows
    ['() #f]
    [(list row) (grouped-row->crop-requirement row)]
    [many (error 'get-crop-requirement "expected 1 crop requirement, got ~a" (length many))]))

(define (get-crop-requirement-values crop-requirement)
  (for/list ([(nutrient-id name formula value_ppm)
              (in-query (current-conn)
                        (select n.id
                                n.canonical_name
                                n.formula
                                nv.value_ppm
                                #:from (TableExpr:AST ,joined)
                                #:where (= cr.id ,(crop-requirement-id crop-requirement))))])
    (cons (nutrient nutrient-id name formula) value_ppm)))

(define (get-crop-requirement-value crop-requirement nutrient)
  (query-maybe-value (current-conn)
                     (select value_ppm
                             #:from (TableExpr:AST ,joined)
                             #:where (and (= cr.id ,(crop-requirement-id crop-requirement))
                                          (= nv.nutrient_id ,(nutrient-id nutrient))))))

;; UPDATE

;; DELETE

(define (delete-crop-requirement! crop-requirement)
  (define id (crop-requirement-id crop-requirement))
  (query-exec (current-conn) (delete #:from crop_requirements #:where (= id ,id))))

;; Helpers

(define (average-crop-requirement-nutrient-values mix)
  (define average-values
    (for/fold ([acc (hash)]) ([pair (in-list mix)])
      (match-define (cons crop-requirement percentage) pair)
      (for/fold ([acc acc]) ([nv (in-list (get-crop-requirement-values crop-requirement))])
        (match-define (cons n v) nv)
        (define nutrient-contribution (* v (/ percentage 100)))
        (hash-update acc n (λ (old) (+ old nutrient-contribution)) (λ () nutrient-contribution)))))
  (for/list ([(n v) (in-hash average-values)])
    (cons n v)))
Copyright 2019--2026 Marius PETER