diff --git a/README.md b/README.md
index 8df7dd6..ea3f53d 100644
--- a/README.md
+++ b/README.md
@@ -12,10 +12,11 @@ I'm using this code to explore human survivability given global warming. The fol
2. [The 'everyone dies' event comes home](https://www.journeyman.cc/blog/posts-output/2026-07-15-Everyone-dies-UK/)
3. [Extreme Heat](https://www.journeyman.cc/blog/posts-output/2026-07-31-Extreme-Heat/)
4. [Analysis of 2026 heatwaves UK](https://www.journeyman.cc/blog/posts-output/2026-08-04-Analysis-of-2026-heatwaves-UK/)
+5. [Exploration of what happened on 26th June this year](https://www.journeyman.cc/blog/posts-output/2026-08-13-Exploration-of-what-happened-on-26th-June-this-year/)
-## Status
+## Disclaimer
-Alpha-quality code. It more or less works but it isn't at all polished.
+This library is supplied in the hope that it may be useful, but results computed with it should not be taken as authoritative but should be checked against other sources. I do not have enough expertise to guarantee its results are correct.
## General architecture
@@ -29,13 +30,13 @@ for some of the following keywords:
1. `:dew-point-celsius`
2. `:pressure-pascals`
3. `:pressure-millibars`
-4. `:saturation-vapour-pressure` *Pascals*
-5. `:relative-humidity` *percentage, 0...100.*
+4. `:relative-humidity` *percentage, 0...100.*
+5. `:saturation-vapour-pressure` *Pascals*
6. `:temperature` *assumed to be ° Celsius*
7. `:temperature-celsius`
8. `:temperature-kelvin`
9. `:volume` *in metres3, relative to 1 at standard temperature and pressure.*
-10. `:wet-bulb-temperature` *in ° Celsius.*
+10. `:wet-bulb-temperature-celsius` *in ° Celsius.*
These keywords represent variables in the pressure equations, and they are all
to a degree inter-related; so if you have (or can assume) some of them, you can
@@ -52,9 +53,58 @@ humidity.core=> (resolution :relative-humidity 100 :temperature-celsius 35)
{:relative-humidity 100, :temperature-celsius 35, :pressure-pascals 101325, :pressure-millibars 4053/4, :saturation-vapour-pressure 5622.488734516426, :temperature-kelvin 308.15, :absolute-humidity 3953.618101887827}
```
-At present there are no checks to ensure that the packet makes sense.
+### Naming
-Associated with each variable there is a namespace which contains a function that takes a packet, and, provided that packet contains enough information to compute that variable, returns a numeric value, else `nil`.
+Associated with each variable there is a namespace of the same name which contains a function, whose name is the name of the variable with `-fn` appended, that takes a packet, and, provided that packet contains enough information to compute that variable, returns a numeric value, else `nil`.
+
+### Validation
+
+A packet may be validated by calling the function `validate`. This recalculates all values in the packet, and, by default, tolerates discrepancies of up to 1%. This tolerance can be varied by binding the dynamic variable `*validation-limit*`. Validation merely checks that the values within the packet are consistent with one another, and does not properly account for pressure, so will not be valid at altitude.
+
+```clojure
+humidity.core=> (validate {:absolute-humidity 3953.618101887827,
+ #_=> :actual-vapour-pressure 4765.10094206357,
+ #_=> :dew-point-celsius 35.00000000000001,
+ #_=> :pressure-millibars 1013.25,
+ #_=> :pressure-pascals 101325,
+ #_=> :relative-humidity 100,
+ #_=> :saturation-vapour-pressure 5622.488734516426,
+ #_=> :temperature-celsius 35,
+ #_=> :temperature-kelvin 308.15,
+ #_=> :volume 1.1281347245103424,
+ #_=> :wet-bulb-temperature-celsius 35.11625928298915}
+ #_=> )
+true
+
+humidity.core=> (validate {:absolute-humidity 3953.618101887827,
+ #_=> :actual-vapour-pressure 4765.10094206357,
+ #_=> :dew-point-celsius 35.00000000000001,
+ #_=> :pressure-millibars 1013.25,
+ #_=> :pressure-pascals 101325,
+ #_=> :relative-humidity 95, ;; <-- intentionally wrong
+ #_=> :saturation-vapour-pressure 5622.488734516426,
+ #_=> :temperature-celsius 35,
+ #_=> :temperature-kelvin 308.15,
+ #_=> :volume 1.1281347245103424,
+ #_=> :wet-bulb-temperature-celsius 35.11625928298915}
+ #_=> )
+ERROR: key: :dew-point-celsius; a: 35.00000000000001; b 34.07737599732603; errror: 2.857142925262451%.
+false
+
+humidity.core=> (binding [*validation-limit* 5]
+ #_=> (validate {:absolute-humidity 3953.618101887827,
+ #_=> :actual-vapour-pressure 4765.10094206357,
+ #_=> :dew-point-celsius 35.00000000000001,
+ #_=> :pressure-millibars 1013.25,
+ #_=> :pressure-pascals 101325,
+ #_=> :relative-humidity 95, ;; <-- intentionally wrong
+ #_=> :saturation-vapour-pressure 5622.488734516426,
+ #_=> :temperature-celsius 35,
+ #_=> :temperature-kelvin 308.15,
+ #_=> :volume 1.1281347245103424,
+ #_=> :wet-bulb-temperature-celsius 35.11625928298915}))
+true
+```
## Developing
@@ -66,6 +116,8 @@ lein repl
and hook in your preferred editor as appropriate for that editor.
+Full documentation is available [here](https://www.journeyman.cc/humidity).
+
## Testing
Tests are reasonably complets but all could do with more examples, especially around the crucial 35° C wet bulb area and towards the extremities of normal weather range. If you are contributing additional code, I would appreciate it being accompanied by tests.
diff --git a/doc/intro.md b/doc/intro.md
new file mode 100644
index 0000000..db24108
--- /dev/null
+++ b/doc/intro.md
@@ -0,0 +1,128 @@
+# Introduction
+
+## Disclaimer
+
+This library is supplied in the hope that it may be useful, but results computed with it should not be taken as authoritative but should be checked against other sources. I do not have enough expertise to guarantee its results are correct.
+
+## General architecture
+
+### The packet
+
+The 'packet', in the terminology of this library, is a map which has bindings
+for some of the following keywords:
+
+1. `:absolute-humidity` *grammes per metre3.*
+2. `:actual-vapour-pressure` *Pascals*
+1. `:dew-point-celsius`
+2. `:pressure-pascals`
+3. `:pressure-millibars`
+4. `:relative-humidity` *percentage, 0...100.*
+5. `:saturation-vapour-pressure` *Pascals*
+6. `:temperature` *assumed to be ° Celsius*
+7. `:temperature-celsius`
+8. `:temperature-kelvin`
+9. `:volume` *in metres3, relative to 1 at standard temperature and pressure.*
+10. `:wet-bulb-temperature-celsius` *in ° Celsius.*
+
+These keywords represent variables in the pressure equations, and they are all
+to a degree inter-related; so if you have (or can assume) some of them, you can
+compute others.
+
+Rather than having a tangle of functions all of which call one another, which seemed likely to result in horrible messes and potential infinite recursion, there is one central function, `humidity.core/resolution`, which takes a partial packet and repeatedly tries to compute the values for more variables until it can compute no more, and then returns the enhanced packet.
+
+Thus:
+
+```clojure
+humidity.core=> (use 'humidity.core :reload)
+nil
+humidity.core=> (resolution :relative-humidity 100 :temperature-celsius 35)
+{:relative-humidity 100, :temperature-celsius 35, :pressure-pascals 101325, :pressure-millibars 4053/4, :saturation-vapour-pressure 5622.488734516426, :temperature-kelvin 308.15, :absolute-humidity 3953.618101887827}
+```
+
+### Naming
+
+Associated with each variable there is a namespace of the same name which contains a function, whose name is the name of the variable with `-fn` appended, that takes a packet, and, provided that packet contains enough information to compute that variable, returns a numeric value, else `nil`.
+
+### Validation
+
+A packet may be validated by calling the function `validate`. This recalculates all values in the packet, and, by default, tolerates discrepancies of up to 1%. This tolerance can be varied by binding the dynamic variable `*validation-limit*`. Validation merely checks that the values within the packet are consistent with one another, and does not properly account for pressure, so will not be valid at altitude.
+
+```clojure
+humidity.core=> (validate {:absolute-humidity 3953.618101887827,
+ #_=> :actual-vapour-pressure 4765.10094206357,
+ #_=> :dew-point-celsius 35.00000000000001,
+ #_=> :pressure-millibars 1013.25,
+ #_=> :pressure-pascals 101325,
+ #_=> :relative-humidity 100,
+ #_=> :saturation-vapour-pressure 5622.488734516426,
+ #_=> :temperature-celsius 35,
+ #_=> :temperature-kelvin 308.15,
+ #_=> :volume 1.1281347245103424,
+ #_=> :wet-bulb-temperature-celsius 35.11625928298915}
+ #_=> )
+true
+
+humidity.core=> (validate {:absolute-humidity 3953.618101887827,
+ #_=> :actual-vapour-pressure 4765.10094206357,
+ #_=> :dew-point-celsius 35.00000000000001,
+ #_=> :pressure-millibars 1013.25,
+ #_=> :pressure-pascals 101325,
+ #_=> :relative-humidity 95, ;; <-- intentionally wrong
+ #_=> :saturation-vapour-pressure 5622.488734516426,
+ #_=> :temperature-celsius 35,
+ #_=> :temperature-kelvin 308.15,
+ #_=> :volume 1.1281347245103424,
+ #_=> :wet-bulb-temperature-celsius 35.11625928298915}
+ #_=> )
+ERROR: key: :dew-point-celsius; a: 35.00000000000001; b 34.07737599732603; errror: 2.857142925262451%.
+false
+
+humidity.core=> (binding [*validation-limit* 5]
+ #_=> (validate {:absolute-humidity 3953.618101887827,
+ #_=> :actual-vapour-pressure 4765.10094206357,
+ #_=> :dew-point-celsius 35.00000000000001,
+ #_=> :pressure-millibars 1013.25,
+ #_=> :pressure-pascals 101325,
+ #_=> :relative-humidity 95, ;; <-- intentionally wrong
+ #_=> :saturation-vapour-pressure 5622.488734516426,
+ #_=> :temperature-celsius 35,
+ #_=> :temperature-kelvin 308.15,
+ #_=> :volume 1.1281347245103424,
+ #_=> :wet-bulb-temperature-celsius 35.11625928298915}))
+true
+```
+
+## Developing
+
+This is a fairly standard [leiningen](https://leiningen.org/) project. I anticipate that most Clojure developers will have leininen installed, even if they no longer use it much. Start a repl with
+
+```
+lein repl
+```
+
+and hook in your preferred editor as appropriate for that editor.
+
+Full documentation is available [here](https://www.journeyman.cc/humidity).
+
+## Testing
+
+Tests are reasonably complets but all could do with more examples, especially around the crucial 35° C wet bulb area and towards the extremities of normal weather range. If you are contributing additional code, I would appreciate it being accompanied by tests.
+
+I suggest running tests using
+
+```
+lein cloverage
+```
+
+As this gives not only pass/fail output, but also test coverage output.
+
+Current test results are [here](cloverage/index.html), for comparison.
+
+## Contributing
+
+Pull requests are welcomed, provided they do not contain AI generated code. I'm not going to waste time debugging Claude's mess, my own is bad enough.
+
+## License
+
+Copyright © 2026 Simon Brooke. Licensed under the GNU General Public License,
+version 2.0 or (at your option) any later version.
diff --git a/project.clj b/project.clj
index 268d971..430e3a3 100644
--- a/project.clj
+++ b/project.clj
@@ -1,10 +1,10 @@
(defproject humidity "0.1.1-SNAPSHOT"
- :cloverage {:output "docs/cloverage"
+ :cloverage {:output "docs/humidity/cloverage"
:ns-exclude-regex [#"beowulf\.gendoc" #"beowulf\.scratch"]}
:codox {:metadata {:doc "**TODO**: write docs"
:doc/format :markdown}
- :output-path "docs/codox"
- :source-uri "https://github.com/simon-brooke/humidity/blob/master/{filepath}#L{line}"}
+ :output-path "docs/humidity/"
+ :source-uri "https://git.journeyman.cc/simon/humidity/src/branch/main/{filepath}#L{line}"}
:dependencies [[org.clojure/clojure "1.12.5"]
[io.github.nextjournal/clerk "0.18.1158"]]
:deploy-repositories [["releases" :clojars]
@@ -18,4 +18,4 @@
:plugins [[lein-ancient "1.0.0"]
[lein-cloverage "1.2.2"]
[lein-codox "0.10.8"]]
- :url "https://git.journeyman.cc/simon/humidity")
+ :url "https://www.journeyman.cc/humidity/")
diff --git a/test/humidity/core_test.clj b/test/humidity/core_test.clj
index fca45ee..d76cf10 100644
--- a/test/humidity/core_test.clj
+++ b/test/humidity/core_test.clj
@@ -25,21 +25,36 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(deftest resolution-test
- (testing "resolution")
- (let [expected {:absolute-humidity 3953.618101887827,
- :actual-vapour-pressure 4765.10094206357,
- :dew-point-celsius 35.00000000000001,
- :pressure-millibars 1013.25,
- :pressure-pascals 101325,
- :relative-humidity 100,
- :saturation-vapour-pressure 5622.488734516426,
- :temperature-celsius 35,
- :temperature-kelvin 308.15,
- :volume 1.1281347245103424,
- :wet-bulb-temperature-celsius 35.11625928298915}
- packet {:relative-humidity 100 :temperature-celsius 35}
- actual (resolution packet)]
- (is (= actual expected))))
+ (testing "resolution"
+ (let [expected {:absolute-humidity 3953.618101887827,
+ :actual-vapour-pressure 4765.10094206357,
+ :dew-point-celsius 35.00000000000001,
+ :pressure-millibars 1013.25,
+ :pressure-pascals 101325,
+ :relative-humidity 100,
+ :saturation-vapour-pressure 5622.488734516426,
+ :temperature-celsius 35,
+ :temperature-kelvin 308.15,
+ :volume 1.1281347245103424,
+ :wet-bulb-temperature-celsius 35.11625928298915}]
+ (let [packet {:relative-humidity 100 :temperature-celsius 35}
+ actual (resolution packet)]
+ (is (= actual expected)))
+ (let [packet {:relative-humidity 100 :temperature-kelvin 308.15}
+ actual (resolution packet)]
+ (is (= actual expected)))
+ (let [packet {:relative-humidity 100 :temperature 35}
+ actual (resolution packet)]
+ (is (= actual expected)))
+ (let [packet {:relative-humidity 100 :pressure-pascals 101325 :temperature 35}
+ actual (resolution packet)]
+ (is (= actual expected)))
+ (let [packet {:relative-humidity 100 :pressure-millibars 1013.25 :temperature 35}
+ actual (resolution packet)]
+ (is (= actual expected)))
+ (let [packet {:absolute-humidity 3953.618101887827 :temperature-celsius 35}
+ actual (resolution packet)]
+ (is (= actual expected))))))
(deftest validation-test
(testing "validation"
diff --git a/test/humidity/saturation_vp_test.clj b/test/humidity/saturation_vp_test.clj
index b66f30b..c19a4ee 100644
--- a/test/humidity/saturation_vp_test.clj
+++ b/test/humidity/saturation_vp_test.clj
@@ -1,5 +1,6 @@
(ns humidity.saturation-vp-test
(:require [clojure.test :refer [deftest is testing]]
+ [humidity.constants :refer [celsius-offset]]
[humidity.saturation-vapour-pressure :refer [saturation-vapour-pressure-fn]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -28,4 +29,8 @@
(let [min-expected 5622
max-expected 5629
actual (saturation-vapour-pressure-fn {:temperature-celsius 35})]
- (is (< min-expected actual max-expected)))))
+ (is (< min-expected actual max-expected)))
+ (let [min-expected 5622
+ max-expected 5629
+ actual (saturation-vapour-pressure-fn {:temperature-kelvin (+ celsius-offset 35)})]
+ (is (< min-expected actual max-expected)))))