summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--models/crop-requirement.rkt45
-rw-r--r--models/crop-rotation.rkt158
-rw-r--r--models/crop.rkt81
-rw-r--r--models/fertilizer-product.rkt35
-rw-r--r--models/nutrient-measurement.rkt35
-rw-r--r--models/nutrient-value.rkt150
6 files changed, 446 insertions, 58 deletions
diff --git a/models/crop-requirement.rkt b/models/crop-requirement.rkt
index 2dd8071..dc3f3a0 100644
--- a/models/crop-requirement.rkt
+++ b/models/crop-requirement.rkt
@@ -189,23 +189,23 @@
"../models/nutrient.rkt"
"../models/crop.rkt")
- (define requirement-profile "Tomato - Vegetative")
+ (define requirement-profile "Examplium Plant - Vegetative")
(run-tests
(test-suite "Crop requirement model"
#:before (λ ()
(connect! #:path 'memory)
(migrate-all!)
- (create-nutrient! "Nitrogen" "Azote" "N")
- (create-nutrient! "Phosphorus" "Phosphore" "P")
- (create-nutrient! "Potassium" "Potassium" "K"))
+ (create-nutrient! "Examplium" "Examplium" "Ex")
+ (create-nutrient! "Ignorium" "Ignorium" "Ig")
+ (create-nutrient! "Testium" "Testium" "Ts"))
#:after (λ () (disconnect!))
(test-case "Create requirement with profile and values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
- (create-crop-requirement! requirement-profile (hash nitrogen 150 phosphorus 50))
+ (create-crop-requirement! requirement-profile (hash examplium 150 ignorium 50))
(check-equal? (length (get-crop-requirements)) 1)
@@ -214,21 +214,22 @@
(check-equal? (crop-requirement-profile cr) requirement-profile))
(test-case "Create requirement with associated crop"
- (define tomato (create-crop! "Tomato"))
- (define nitrogen (get-nutrient #:name "Nitrogen"))
+ (define test-crop (create-crop! (crop #f "testium-plant")))
+ (define examplium (get-nutrient #:name "Examplium"))
- (define cr (create-crop-requirement! "Tomato - Fruiting" (hash nitrogen 200) tomato))
+ (define cr
+ (create-crop-requirement! "Testium Plant - Fruiting" (hash examplium 200) test-crop))
- (check-equal? (crop-requirement-crop-id cr) (crop-id tomato)))
+ (check-equal? (crop-requirement-crop-id cr) (crop-id test-crop)))
(test-case "Check all requirement values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
(define cr (get-crop-requirement #:profile requirement-profile))
- (check-= (get-crop-requirement-value cr nitrogen) 150 0)
- (check-= (get-crop-requirement-value cr phosphorus) 50 0)
+ (check-= (get-crop-requirement-value cr examplium) 150 0)
+ (check-= (get-crop-requirement-value cr ignorium) 50 0)
(define crv (crop-requirement-nutrient-values cr))
@@ -238,8 +239,8 @@
"return value of get-crop-requirement-values ≠ crop-requirement-values struct accessor")
(check-equal? (hash-count crv) 2)
- (check-= (hash-ref crv nitrogen) 150 0)
- (check-= (hash-ref crv phosphorus) 50 0))
+ (check-= (hash-ref crv examplium) 150 0)
+ (check-= (hash-ref crv ignorium) 50 0))
(test-case "Get requirement by id"
(define cr (get-crop-requirement #:profile requirement-profile))
@@ -248,19 +249,19 @@
(check-equal? cr cr-by-id))
(test-case "Average crop requirement nutrient values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
(define cr1 (get-crop-requirement #:profile requirement-profile))
- (define cr2 (create-crop-requirement! "Lettuce" (hash nitrogen 100 phosphorus 30)))
+ (define cr2 (create-crop-requirement! "Generic Growth" (hash examplium 100 ignorium 30)))
(define mix (hash cr1 60 cr2 40))
(define avg (average-crop-requirement-nutrient-values mix))
;; 150 * 0.6 + 100 * 0.4 = 90 + 40 = 130
- (check-= (hash-ref avg nitrogen) 130 0.01)
+ (check-= (hash-ref avg examplium) 130 0.01)
;; 50 * 0.6 + 30 * 0.4 = 30 + 12 = 42
- (check-= (hash-ref avg phosphorus) 42 0.01))
+ (check-= (hash-ref avg ignorium) 42 0.01))
(test-case "Delete requirement and cascade to requirement values"
(define cr (get-crop-requirement #:profile requirement-profile))
diff --git a/models/crop-rotation.rkt b/models/crop-rotation.rkt
index 3b0a69d..fb7775f 100644
--- a/models/crop-rotation.rkt
+++ b/models/crop-rotation.rkt
@@ -3,6 +3,7 @@
(provide crop-rotation
crop-rotation?
crop-rotation-id
+ crop-rotation-requirement
(rename-out [crop-rotation-rotation-date crop-rotation-date]
[crop-rotation-requirement-proportions crop-rotation-requirements])
(contract-out
@@ -20,6 +21,9 @@
(struct crop-rotation (id rotation-date requirement-proportions) #:transparent)
+(define (crop-rotation-requirement cr requirement)
+ (hash-ref (crop-rotation-requirement-proportions cr) requirement #f))
+
(define crop-rotation-or-id/c (or/c crop-rotation? db-id?))
(define requirement-proportion-hash/c (hash/c crop-requirement? (between/c 0 100) #:immutable #t))
@@ -112,3 +116,157 @@
(define (delete-crop-rotation! cr-or-id)
(query-exec (current-conn) (delete #:from crop_rotations #:where (= id ,(->cr-id cr-or-id)))))
+
+(module+ test
+ (require rackunit
+ rackunit/text-ui
+ "../db/conn.rkt"
+ "../db/migrations.rkt"
+ "../models/nutrient.rkt"
+ "../models/crop.rkt"
+ "../models/crop-requirement.rkt")
+
+ (define rotation-date-1 "2025-01-15")
+ (define rotation-date-2 "2025-02-20")
+
+ (run-tests
+ (test-suite "Crop rotation model"
+ #:before (λ ()
+ (connect! #:path 'memory)
+ (migrate-all!)
+ (create-nutrient! "Examplium" "Examplium" "Ex")
+ (create-nutrient! "Ignorium" "Ignorium" "Ig")
+ (create-nutrient! "Testium" "Testium" "Ts"))
+ #:after (λ () (disconnect!))
+
+ (test-case "Create crop rotation with requirement proportions"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+
+ (define req1 (create-crop-requirement! "vegetative" (hash ex 100 ig 50)))
+ (define req2 (create-crop-requirement! "fruiting" (hash ex 150 ig 75)))
+
+ (define proportions (hash req1 60 req2 40))
+ (define rotation (create-crop-rotation! rotation-date-1 proportions))
+
+ (check-true (crop-rotation? rotation))
+ (check-equal? (crop-rotation-rotation-date rotation) rotation-date-1)
+ (check-equal? (hash-count (crop-rotation-requirement-proportions rotation)) 2)
+
+ ;; Find requirements in the returned hash by profile
+ (define reqs (crop-rotation-requirement-proportions rotation))
+ (define req1-found
+ (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) (hash-keys reqs)))
+ (define req2-found
+ (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) (hash-keys reqs)))
+
+ (check-equal? (hash-ref reqs req1-found) 60)
+ (check-equal? (hash-ref reqs req2-found) 40))
+
+ (test-case "Create duplicate rotation returns existing"
+ ;; Get all requirements to find the ones we need
+ (define all-reqs (get-crop-requirements))
+ (define req1 (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) all-reqs))
+ (define req2 (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) all-reqs))
+
+ ;; Try to create with different proportions - should return existing
+ (define proportions (hash req1 70 req2 30))
+ (define rotation2 (create-crop-rotation! rotation-date-1 proportions))
+
+ (check-equal? (length (get-crop-rotations)) 1)
+
+ ;; The returned rotation should have original proportions (60/40), not new ones (70/30)
+ (define reqs2 (crop-rotation-requirement-proportions rotation2))
+ (define req1-from-rotation
+ (findf (λ (r) (equal? (crop-requirement-profile r) "vegetative")) (hash-keys reqs2)))
+ (define req2-from-rotation
+ (findf (λ (r) (equal? (crop-requirement-profile r) "fruiting")) (hash-keys reqs2)))
+
+ (check-equal? (hash-ref reqs2 req1-from-rotation) 60)
+ (check-equal? (hash-ref reqs2 req2-from-rotation) 40))
+
+ (test-case "Get crop rotation by id"
+ (define rotation1 (get-crop-rotation #:date rotation-date-1))
+ (define rotation2 (get-crop-rotation #:id (crop-rotation-id rotation1)))
+ (check-equal? (crop-rotation-id rotation1) (crop-rotation-id rotation2))
+ (check-equal? (crop-rotation-rotation-date rotation1) (crop-rotation-rotation-date rotation2)))
+
+ (test-case "Get crop rotation by date"
+ (define rotation (get-crop-rotation #:date rotation-date-1))
+ (check-true (crop-rotation? rotation))
+ (check-equal? (crop-rotation-rotation-date rotation) rotation-date-1))
+
+ (test-case "Get crop rotation with both id and date"
+ (define rotation1 (get-crop-rotation #:date rotation-date-1))
+ (define rotation2 (get-crop-rotation #:id (crop-rotation-id rotation1) #:date rotation-date-1))
+ (check-equal? (crop-rotation-id rotation1) (crop-rotation-id rotation2)))
+
+ (test-case "Get non-existent crop rotation"
+ (check-false (get-crop-rotation #:date "2099-12-31"))
+ (check-false (get-crop-rotation #:id 9999)))
+
+ (test-case "Get all crop rotations ordered by date descending"
+ (define ts (get-nutrient #:name "Testium"))
+ (define req3 (create-crop-requirement! "maintenance" (hash ts 80)))
+ (create-crop-rotation! rotation-date-2 (hash req3 100))
+
+ (define rotations (get-crop-rotations))
+ (check-equal? (length rotations) 2)
+ ;; Most recent first
+ (check-equal? (crop-rotation-rotation-date (first rotations)) rotation-date-2)
+ (check-equal? (crop-rotation-rotation-date (second rotations)) rotation-date-1))
+
+ (test-case "Get latest crop rotation"
+ (define latest (get-latest-crop-rotation))
+ (check-true (crop-rotation? latest))
+ (check-equal? (crop-rotation-rotation-date latest) rotation-date-2))
+
+ (test-case "Get latest returns #f when no rotations exist"
+ (for ([rotation (get-crop-rotations)])
+ (delete-crop-rotation! (crop-rotation-id rotation)))
+ (check-false (get-latest-crop-rotation)))
+
+ (test-case "Delete crop rotation by struct"
+ (define ts (get-nutrient #:name "Testium"))
+ (define req
+ (findf (λ (r) (equal? (crop-requirement-profile r) "maintenance")) (get-crop-requirements)))
+ (create-crop-rotation! "2025-03-01" (hash req 100))
+
+ (define rotation (get-crop-rotation #:date "2025-03-01"))
+ (delete-crop-rotation! rotation)
+ (check-false (get-crop-rotation #:id (crop-rotation-id rotation))))
+
+ (test-case "Delete crop rotation by id"
+ (define ts (get-nutrient #:name "Testium"))
+ (define req
+ (findf (λ (r) (equal? (crop-requirement-profile r) "maintenance")) (get-crop-requirements)))
+ (create-crop-rotation! "2025-04-01" (hash req 100))
+
+ (define rotation (get-crop-rotation #:date "2025-04-01"))
+ (define rotation-id-val (crop-rotation-id rotation))
+ (delete-crop-rotation! rotation-id-val)
+ (check-false (get-crop-rotation #:id rotation-id-val)))
+
+ (test-case "Delete crop rotation cascades to crop_rotation_requirements"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define req1 (create-crop-requirement! "test-profile-1" (hash ex 100)))
+ (define req2 (create-crop-requirement! "test-profile-2" (hash ex 200)))
+ (create-crop-rotation! "2025-05-01" (hash req1 50 req2 50))
+
+ (define initial-count (length (get-crop-rotations)))
+ (define rotation (get-crop-rotation #:date "2025-05-01"))
+ (delete-crop-rotation! rotation)
+ (check-equal? (length (get-crop-rotations)) (- initial-count 1)))
+
+ (test-case "Rotation with associated crop requirement"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define test-crop (create-crop! (crop #f "test-crop-for-rotation")))
+ (define req-with-crop (create-crop-requirement! "crop-specific" (hash ex 120) test-crop))
+ (define rotation (create-crop-rotation! "2025-06-01" (hash req-with-crop 100)))
+
+ (check-equal? (crop-requirement-crop-id req-with-crop) (crop-id test-crop))
+ (define retrieved (get-crop-rotation #:date "2025-06-01"))
+ (check-equal? (hash-count (crop-rotation-requirement-proportions retrieved)) 1))
+
+ (test-case "Error when neither id nor date provided"
+ (check-exn exn:fail? (λ () (get-crop-rotation)))))))
diff --git a/models/crop.rkt b/models/crop.rkt
index ba93550..4430543 100644
--- a/models/crop.rkt
+++ b/models/crop.rkt
@@ -61,3 +61,84 @@
(define (delete-crop! id)
(query-exec (current-conn) (delete #:from crops #:where (= id ,id))))
+
+(module+ test
+ (require rackunit
+ rackunit/text-ui
+ "../db/conn.rkt"
+ "../db/migrations.rkt")
+
+ (define test-crop-name "examplium-plant")
+
+ (run-tests (test-suite "Crop model"
+ #:before (λ ()
+ (connect! #:path 'memory)
+ (migrate-all!))
+ #:after (λ () (disconnect!))
+
+ (test-case "Create crop"
+ (check-equal? (length (get-crops)) 0)
+ (define c1 (create-crop! (crop #f test-crop-name)))
+ (check-equal? (length (get-crops)) 1)
+ (check-true (crop? c1))
+ (check-equal? (crop-name c1) test-crop-name)
+ (check-true (db-id? (crop-id c1))))
+
+ (test-case "Create duplicate crop returns existing"
+ (define c1 (create-crop! (crop #f test-crop-name)))
+ (define c2 (create-crop! (crop #f test-crop-name)))
+ (check-equal? (length (get-crops)) 1)
+ (check-equal? (crop-id c1) (crop-id c2))
+ (check-equal? c1 c2))
+
+ (test-case "Get crop by id"
+ (define c1 (get-crop #:name test-crop-name))
+ (define c2 (get-crop #:id (crop-id c1)))
+ (check-equal? c1 c2))
+
+ (test-case "Get crop by name"
+ (define c (get-crop #:name test-crop-name))
+ (check-true (crop? c))
+ (check-equal? (crop-name c) test-crop-name))
+
+ (test-case "Get crop with both id and name"
+ (define c1 (get-crop #:name test-crop-name))
+ (define c2 (get-crop #:id (crop-id c1) #:name test-crop-name))
+ (check-equal? c1 c2))
+
+ (test-case "Get non-existent crop"
+ (check-false (get-crop #:name "non-existent-crop"))
+ (check-false (get-crop #:id 9999)))
+
+ (test-case "Get all crops"
+ (create-crop! (crop #f "ignorium-plant"))
+ (create-crop! (crop #f "testium-plant"))
+ (define crops (get-crops))
+ (check-equal? (length crops) 3)
+ (check-true (andmap crop? crops)))
+
+ (test-case "Update crop name"
+ (define c1 (get-crop #:name test-crop-name))
+ (define updated-crop (crop (crop-id c1) "examplium-updated"))
+ (update-crop! updated-crop)
+ (define c2 (get-crop #:id (crop-id c1)))
+ (check-equal? (crop-name c2) "examplium-updated")
+ (check-equal? (crop-id c1) (crop-id c2))
+ (check-equal? (length (get-crops)) 3))
+
+ (test-case "Update crop with invalid id raises error"
+ (define invalid-crop (crop #f "invalid"))
+ (check-exn exn:fail:contract? (λ () (update-crop! invalid-crop))))
+
+ (test-case "Delete crop"
+ (define c (get-crop #:name "ignorium-plant"))
+ (delete-crop! (crop-id c))
+ (check-false (get-crop #:id (crop-id c)))
+ (check-equal? (length (get-crops)) 2))
+
+ (test-case "Delete crop by id"
+ (define c (get-crop #:name "testium-plant"))
+ (define crop-id-val (crop-id c))
+ (delete-crop! crop-id-val)
+ (check-false (get-crop #:id crop-id-val))
+ (check-equal? (length (get-crops)) 1)))))
diff --git a/models/fertilizer-product.rkt b/models/fertilizer-product.rkt
index 652b3c4..5d81681 100644
--- a/models/fertilizer-product.rkt
+++ b/models/fertilizer-product.rkt
@@ -189,41 +189,41 @@
"../db/migrations.rkt"
"../models/nutrient.rkt")
- (define canonical-product-name "MasterBlend 4-20")
+ (define canonical-product-name "ExampleBlend 4-20")
(run-tests
(test-suite "Fertilizer product model"
#:before (λ ()
(connect! #:path 'memory)
(migrate-all!)
- (create-nutrient! "Nitrogen" "Azote" "N")
- (create-nutrient! "Phosphorus" "Phosphore" "P")
- (create-nutrient! "Potassium" "Potassium" "K"))
+ (create-nutrient! "Examplium" "Examplium" "Ex")
+ (create-nutrient! "Ignorium" "Ignorium" "Ig")
+ (create-nutrient! "Testium" "Testium" "Ts"))
#:after (λ () (disconnect!))
(test-case "Create product with name, brand, and values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
(create-fertilizer-product! canonical-product-name
- "MasterBlend"
- (hash nitrogen 40 phosphorus 200))
+ "ExampleBrand"
+ (hash examplium 40 ignorium 200))
(check-equal? (length (get-fertilizer-products)) 1)
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
(check-true (fertilizer-product? fp))
(check-equal? (fertilizer-product-canonical-name fp) canonical-product-name)
- (check-equal? (fertilizer-product-brand-name fp) "MasterBlend"))
+ (check-equal? (fertilizer-product-brand-name fp) "ExampleBrand"))
(test-case "Check all product values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
- (check-= (get-fertilizer-product-value fp nitrogen) 40 0)
- (check-= (get-fertilizer-product-value fp phosphorus) 200 0)
+ (check-= (get-fertilizer-product-value fp examplium) 40 0)
+ (check-= (get-fertilizer-product-value fp ignorium) 200 0)
(define fpv (fertilizer-product-nutrient-values fp))
@@ -240,13 +240,13 @@
(check-equal? fp fp-by-id))
(test-case "Handle missing nutrient in product"
- (define potassium (get-nutrient #:name "Potassium"))
+ (define testium (get-nutrient #:name "Testium"))
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
- (check-false (get-fertilizer-product-value fp potassium)))
+ (check-false (get-fertilizer-product-value fp testium)))
(test-case "Custom write property formatting"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define fp (create-fertilizer-product! "Test Fertilizer" "TestBrand" (hash nitrogen 50)))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define fp (create-fertilizer-product! "Test Fertilizer" "TestBrand" (hash examplium 50)))
(define output (open-output-string))
(write fp output)
@@ -255,7 +255,6 @@
(check-true (string-contains? result "Fertilizer #"))
(check-true (string-contains? result "Test Fertilizer"))
(check-true (string-contains? result "TestBrand")))
-
(test-case "Delete product and cascade to product values"
(define fp (get-fertilizer-product #:canonical-name canonical-product-name))
(delete-fertilizer-product! fp)
diff --git a/models/nutrient-measurement.rkt b/models/nutrient-measurement.rkt
index 3a5238b..7f954b6 100644
--- a/models/nutrient-measurement.rkt
+++ b/models/nutrient-measurement.rkt
@@ -221,29 +221,28 @@
(test-suite "Nutrient measurement model"
#:before (λ ()
(connect! #:path 'memory)
- ;; (connect! #:path "test.sqlite3")
(migrate-all!)
- (create-nutrient! "Nitrogen" "" "N")
- (create-nutrient! "Phosphorus" "" "P")
- (create-nutrient! "Potassium" "" "K"))
+ (create-nutrient! "Examplium" "Examplium" "Ex")
+ (create-nutrient! "Ignorium" "Ignorium" "Ig")
+ (create-nutrient! "Testium" "Testium" "Ts"))
#:after (λ () (disconnect!))
(test-case "Create measurement with date and values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
- (create-nutrient-measurement! measurement-date (hash nitrogen 12.3 phosphorus 4.5))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
+ (create-nutrient-measurement! measurement-date (hash examplium 12.3 ignorium 4.5))
(check-equal? (length (get-nutrient-measurements)) 1)
(define nm (get-nutrient-measurement #:date measurement-date))
(check-true (nutrient-measurement? nm))
(check-equal? (nutrient-measurement-measurement-date nm) measurement-date))
(test-case "Check all measurement values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
(define nm (get-nutrient-measurement #:date measurement-date))
- (check-equal? (get-nutrient-measurement-value nm nitrogen) 12.3)
- (check-equal? (get-nutrient-measurement-value nm phosphorus) 4.5)
+ (check-equal? (get-nutrient-measurement-value nm examplium) 12.3)
+ (check-equal? (get-nutrient-measurement-value nm ignorium) 4.5)
(define nmv (nutrient-measurement-nutrient-values nm))
(check-equal?
@@ -251,17 +250,17 @@
nmv
"return value of get-nutrient-measurement-values ≠ nutrient-measurement-values struct accessor")
(check-equal? (hash-count nmv) 2)
- (check-equal? (hash-ref nmv nitrogen) 12.3)
- (check-equal? (hash-ref nmv phosphorus) 4.5))
+ (check-equal? (hash-ref nmv examplium) 12.3)
+ (check-equal? (hash-ref nmv ignorium) 4.5))
(test-case "Retrieve latest measurement values"
- (define nitrogen (get-nutrient #:name "Nitrogen"))
- (define phosphorus (get-nutrient #:name "Phosphorus"))
+ (define examplium (get-nutrient #:name "Examplium"))
+ (define ignorium (get-nutrient #:name "Ignorium"))
(define second-measurement-date "2025-09-02")
- (create-nutrient-measurement! second-measurement-date (hash nitrogen 6.7 phosphorus 8.9))
+ (create-nutrient-measurement! second-measurement-date (hash examplium 6.7 ignorium 8.9))
- (check-equal? (get-latest-nutrient-measurement-value nitrogen) 6.7)
- (check-equal? (get-latest-nutrient-measurement-value phosphorus) 8.9))
+ (check-equal? (get-latest-nutrient-measurement-value examplium) 6.7)
+ (check-equal? (get-latest-nutrient-measurement-value ignorium) 8.9))
(test-case "Delete measurement and cascade to measurement values"
(define nm (get-nutrient-measurement #:date measurement-date))
diff --git a/models/nutrient-value.rkt b/models/nutrient-value.rkt
index 9653c7f..4514d84 100644
--- a/models/nutrient-value.rkt
+++ b/models/nutrient-value.rkt
@@ -51,3 +51,153 @@
(for/hash ([r (in-list residuals)])
(match-define (vector n-id n-canonical-name n-french-name n-formula value-ppm) r)
(values (nutrient n-id n-canonical-name n-french-name n-formula) value-ppm)))
+
+(module+ test
+ (require rackunit
+ rackunit/text-ui
+ db
+ sql
+ "../db/conn.rkt"
+ "../db/migrations.rkt"
+ "../models/nutrient.rkt")
+
+ (run-tests (test-suite "Nutrient value model"
+ #:before (λ ()
+ (connect! #:path 'memory)
+ (migrate-all!)
+ (create-nutrient! "Examplium" "Examplium" "Ex")
+ (create-nutrient! "Ignorium" "Ignorium" "Ig")
+ (create-nutrient! "Testium" "Testium" "Ts")
+ (create-nutrient! "Zeroium" "Zeroium" "Zr"))
+ #:after (λ () (disconnect!))
+
+ #;(test-case "Insert nutrient values"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+
+ ;; Create a nutrient_value_set for testing
+ (define nvs-id
+ (insert-id (query (current-conn)
+ (insert #:into nutrient_value_sets
+ #:set [nutrient_measurement_id ,sql-null]
+ [crop_requirement_id ,sql-null]
+ [fertilizer_product_id ,sql-null]))))
+
+ (define nv-hash (hash ex 100.5 ig 50.25))
+ (define result (insert-nutrient-values (current-conn) nvs-id nv-hash))
+
+ (check-true (list? result))
+ (check-true (assoc 'insert-id result))
+
+ ;; Verify values were inserted
+ (define ex-value
+ (query-value (current-conn)
+ (select value_ppm
+ #:from nutrient_values
+ #:where (and (= value_set_id ,nvs-id)
+ (= nutrient_id ,(nutrient-id ex))))))
+ (check-= ex-value 100.5 0.001))
+
+ (test-case "Get sorted nutrient values filters positive and sorts descending"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+ (define ts (get-nutrient #:name "Testium"))
+ (define zr (get-nutrient #:name "Zeroium"))
+
+ (define nv-hash (hash ex 100 ig 50 ts 150 zr 0))
+ (define sorted (get-sorted-nutrient-values nv-hash))
+
+ ;; Should exclude zero values
+ (check-equal? (length sorted) 3)
+ ;; Should be sorted descending by value
+ (check-equal? (cdr (first sorted)) 150) ; Testium
+ (check-equal? (cdr (second sorted)) 100) ; Examplium
+ (check-equal? (cdr (third sorted)) 50) ; Ignorium
+ ;; Zeroium should not be in the list
+ (check-false (member zr (map car sorted))))
+
+ (test-case "Get sorted nutrient values with all zeros returns empty"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+
+ (define nv-hash (hash ex 0 ig 0))
+ (define sorted (get-sorted-nutrient-values nv-hash))
+
+ (check-equal? (length sorted) 0))
+
+ (test-case "Get sorted nutrient values with negatives excluded"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+
+ ;; Note: nutrient-value? contract should prevent this, but testing the function
+ (define nv-hash (hash ex 100 ig -50))
+ (define sorted (get-sorted-nutrient-values nv-hash))
+
+ (check-equal? (length sorted) 1)
+ (check-equal? (cdr (first sorted)) 100))
+
+ #;(test-case "Update nutrient values"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+
+ ;; Create initial values
+ (define nvs-id
+ (insert-id (query (current-conn)
+ (insert #:into nutrient_value_sets
+ #:set [nutrient_measurement_id ,sql-null]
+ [crop_requirement_id ,sql-null]
+ [fertilizer_product_id ,sql-null]))))
+
+ (define initial-hash (hash ex 100 ig 50))
+ (insert-nutrient-values (current-conn) nvs-id initial-hash)
+
+ ;; Update values
+ (define updated-hash (hash ex 200 ig 75))
+ (update-nutrient-values! (current-conn) nvs-id updated-hash)
+
+ ;; Verify updates
+ (define ex-value
+ (query-value (current-conn)
+ (select value_ppm
+ #:from nutrient_values
+ #:where (and (= value_set_id ,nvs-id)
+ (= nutrient_id ,(nutrient-id ex))))))
+ (check-= ex-value 200 0.001)
+
+ (define ig-value
+ (query-value (current-conn)
+ (select value_ppm
+ #:from nutrient_values
+ #:where (and (= value_set_id ,nvs-id)
+ (= nutrient_id ,(nutrient-id ig))))))
+ (check-= ig-value 75 0.001))
+
+ (test-case "Residuals to nutrient value hash conversion"
+ (define ex (get-nutrient #:name "Examplium"))
+ (define ig (get-nutrient #:name "Ignorium"))
+
+ (define residuals
+ (list (vector (nutrient-id ex) "Examplium" "Examplium" "Ex" 123.45)
+ (vector (nutrient-id ig) "Ignorium" "Ignorium" "Ig" 67.89)))
+
+ (define nv-hash (residuals->nutrient-value-hash residuals))
+
+ (check-equal? (hash-count nv-hash) 2)
+ (check-= (hash-ref nv-hash ex) 123.45 0.001)
+ (check-= (hash-ref nv-hash ig) 67.89 0.001))
+
+ (test-case "Residuals to nutrient value hash with empty list"
+ (define nv-hash (residuals->nutrient-value-hash '()))
+ (check-true (hash-empty? nv-hash)))
+
+ (test-case "Residuals to nutrient value hash preserves all fields"
+ (define residuals (list (vector 1 "Examplium" "Examplium-FR" "Ex" 100.0)))
+
+ (define nv-hash (residuals->nutrient-value-hash residuals))
+ (define nutrient-key (car (hash-keys nv-hash)))
+
+ (check-equal? (nutrient-id nutrient-key) 1)
+ (check-equal? (nutrient-canonical-name nutrient-key) "Examplium")
+ (check-equal? (nutrient-french-name nutrient-key) "Examplium-FR")
+ (check-equal? (nutrient-formula nutrient-key) "Ex")
+ (check-= (hash-ref nv-hash nutrient-key) 100.0 0.001)))))
Copyright 2019--2026 Marius PETER