diff --git a/api/endpoints.go b/api/endpoints.go
index f80b20c2610836526edd1e1a660b112134510c40..b0008deca3c29c04561d53f77f36a473fb1b9506 100644
--- a/api/endpoints.go
+++ b/api/endpoints.go
@@ -178,13 +178,13 @@ type GetIndexResponseObject interface {
 	VisitGetIndexResponse(w http.ResponseWriter) error
 }
 
-type GetIndex200TexthtmlResponse struct {
+type GetIndex200TexthtmlCharsetUtf8Response struct {
 	Body          io.Reader
 	ContentLength int64
 }
 
-func (response GetIndex200TexthtmlResponse) VisitGetIndexResponse(w http.ResponseWriter) error {
-	w.Header().Set("Content-Type", "text/html")
+func (response GetIndex200TexthtmlCharsetUtf8Response) VisitGetIndexResponse(w http.ResponseWriter) error {
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 	if response.ContentLength != 0 {
 		w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength))
 	}
@@ -260,12 +260,12 @@ func (sh *strictHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
 // Base64 encoded, gzipped, json marshaled Swagger object
 var swaggerSpec = []string{
 
-	"H4sIAAAAAAAC/2RRTW/cIBD9K+idkdm2N07tqVq1VasmufVC8KyNagYEYzdW5P9eQTbSSntBMPPmfQyv",
-	"CHxJsK/wicV5aVeKLiyw+Etb4M/LProhEDTYRYLFt1ZW3/fRQWMtDTmL5GqNecceGiNVX0KWkBgWjzOp",
-	"lYNU9Y+e1dN5gMYSPHGlpnhl/nF+vKNMmbimtXgaUpnMdcjEIE1FqMT68/JAZQuebsamIMPVjek5TJeH",
-	"hgRZGvKp2/GJNypCBRoblfpm98NwGk6Nv6m7HGDxqZc0spO5Ns+mHRP1jd2HfXaVVHYTqUsqSmZSD7++",
-	"DH8YnbO4Bj2PsPhKcuaRXqBRqObElTr9x9Pp/VuIu4jQi5hZ4tIe1c8UXS/vucWpUgJPOI673UcXuFtB",
-	"69U1Rld2WPwmWQtXdds/jv8BAAD//xuhfPkSAgAA",
+	"H4sIAAAAAAAC/2RRTYvUQBD9K807x2TUi0QEPcmgori7Ny+9nZqkMV0dqirjhiX/Xbp3FgbmEkK9V++j",
+	"6xmRTxn9M0Jm88HKLyUfZ/T4S+fIn+dt8G0kNGCfCD2+lbH7vg0eDVYpzMls0b7rXrl7g4E0SFwsZkaP",
+	"+4ncytHU/aNH93Bs0WCOgVipOF6UfxzvbyTzQqx5lUBtlrG7LHUpWnExkqQ/T3ck5xjoam2M1l7SdLVH",
+	"V+3RwKLNhflQ44TMZxIjQYMzib7Efdse2kPRL+5+iejxvo4aLN4mLZm78hmpvtht2Uev5BY/kjtlcTaR",
+	"u/v1pf3DqJriC/U4oMdXsiMP9IQGQrpkVqry7w6H17MQVxOjJ+smS/NHFyYvSvZptdObDwXTMFHylbUt",
+	"pZ2aRB6x7zenSD5yTYaC6ZqSlw09fpOtwuqu8X3/HwAA//9j+EgjIQIAAA==",
 }
 
 // GetSwagger returns the content of the embedded swagger specification file
diff --git a/api/implementation.go b/api/implementation.go
index fa849e16eadc47135a55ac0748e189f95cefddfb..7461c68c9b8ed13f2bafe741e16c8f5af3d3676e 100644
--- a/api/implementation.go
+++ b/api/implementation.go
@@ -20,7 +20,7 @@ func (ua UnitsAPI) GetIndex(_ context.Context, _ GetIndexRequestObject) (GetInde
     <h1 x-data="{ message: 'I ❤️ Alpine' }" x-text="message"></h1>
 </body>
 </html>`
-	response := GetIndex200TexthtmlResponse{
+	response := GetIndex200TexthtmlCharsetUtf8Response{
 		Body:          strings.NewReader(body),
 		ContentLength: int64(len(body)),
 	}
diff --git a/api/units.yaml b/api/units.yaml
index 1b0f6a99c3cbd22e07569297f0fa3df1126dcc1e..80d925bbd6cf85462b4417581428166a5874d825 100644
--- a/api/units.yaml
+++ b/api/units.yaml
@@ -27,6 +27,6 @@ paths:
         '200':
           description: main page
           content:
-            text/html:
+            text/html; charset=utf-8:
               schema:
                 type: string
diff --git a/go.mod b/go.mod
index 7e8ed8568d035aa87c845e8a19697ecd53938101..0eb6a4b44a3251451d7d59019238d90f84c0b339 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,18 @@ module git.lyda.ie/kevin/units
 go 1.22.2
 
 require (
+	github.com/getkin/kin-openapi v0.124.0
+	github.com/ichiban/prolog v1.2.0
+	github.com/oapi-codegen/nethttp-middleware v1.0.2
+	github.com/oapi-codegen/runtime v1.1.1
 	github.com/spf13/cobra v1.8.0
 	github.com/spf13/viper v1.19.0
+	github.com/stretchr/testify v1.9.0
 )
 
 require (
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
-	github.com/getkin/kin-openapi v0.124.0 // indirect
 	github.com/go-openapi/jsonpointer v0.20.2 // indirect
 	github.com/go-openapi/swag v0.22.8 // indirect
 	github.com/gorilla/mux v1.8.1 // indirect
@@ -17,18 +22,13 @@ require (
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/invopop/yaml v0.2.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
-	github.com/labstack/echo/v4 v4.12.0 // indirect
-	github.com/labstack/gommon v0.4.2 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
-	github.com/oapi-codegen/nethttp-middleware v1.0.2 // indirect
-	github.com/oapi-codegen/runtime v1.1.1 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 	github.com/perimeterx/marshmallow v1.1.5 // indirect
+	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/sagikazarmark/locafero v0.4.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
@@ -36,13 +36,9 @@ require (
 	github.com/spf13/cast v1.6.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
-	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	github.com/valyala/fasttemplate v1.2.2 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.9.0 // indirect
-	golang.org/x/crypto v0.22.0 // indirect
 	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
-	golang.org/x/net v0.24.0 // indirect
 	golang.org/x/sys v0.19.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index a71f3670ff3ce7f3e06552b2eba82070cfdc50ce..b3a74f1b97ed499f43bc32a3cf6128cd855a104f 100644
--- a/go.sum
+++ b/go.sum
@@ -13,12 +13,16 @@ github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbX
 github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
 github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
 github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
+github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
+github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/ichiban/prolog v1.2.0 h1:DrwolRMxdzI3126nCSpyxJtK4OVVqmbu7XpGhy8phXs=
+github.com/ichiban/prolog v1.2.0/go.mod h1:RmvNfGaSktvEVZ7nmpn0gkWa5u0Y3zQcK0G+Pl+ul+s=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
@@ -29,19 +33,10 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
-github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
-github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
-github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
@@ -57,9 +52,8 @@ github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0V
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
 github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
@@ -80,6 +74,7 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -89,31 +84,21 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
-github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
+github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
 go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
-golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
-golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
 golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
-golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
-golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
-golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
 golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/rules/rules.go b/rules/rules.go
new file mode 100644
index 0000000000000000000000000000000000000000..e382a60ed454cfeecf5cd00a939fce6be8e64fd1
--- /dev/null
+++ b/rules/rules.go
@@ -0,0 +1,76 @@
+// Package rules has the unit conversion rules.
+package rules
+
+import (
+	"errors"
+
+	"github.com/ichiban/prolog"
+)
+
+// Engine is the rules engine.
+type Engine struct {
+	p *prolog.Interpreter
+}
+
+var (
+	prologKinds = `
+		unit_kind(m2, area).
+		unit_kind(cm2, area).
+		unit_kind(cm, distance).
+		unit_kind(m, distance).
+		unit_kind(km, distance).
+		unit_kind(feet, distance).
+		unit_kind(g, mass).
+		unit_kind(kg, mass).
+		unit_kind(lb, mass).
+		unit_kind(cup, volume).
+		unit_kind(litre, volume).
+		unit_kind(butter, density).
+		unit_kind(water, density).
+`
+	prologKindsConvert = `
+		convert_kind(mass, volume, density).
+		convert_kind(volume, area, distance).
+`
+)
+
+// New creates a new rules engine.
+func New() (*Engine, error) {
+	ngn := &Engine{
+		p: prolog.New(nil, nil),
+	}
+	// Treat a string argument as an atom.
+	if err := ngn.p.Exec(`:- set_prolog_flag(double_quotes, atom).`); err != nil {
+		return nil, err
+	}
+
+	if err := ngn.p.Exec(prologKinds); err != nil {
+		return nil, err
+	}
+
+	return ngn, nil
+}
+
+// Kind returns the kind of unit.
+func (ngn *Engine) Kind(unit string) (string, error) {
+	sols, err := ngn.p.Query("unit_kind(?, Kind).", unit)
+	if err != nil {
+		return "", err
+	}
+	defer sols.Close()
+
+	var result string
+	for sols.Next() {
+		var kind struct {
+			Kind string
+		}
+		if err := sols.Scan(&kind); err != nil {
+			return "", err
+		}
+		if result != "" {
+			return "", errors.New("Unit has multiple kinds")
+		}
+		result = kind.Kind
+	}
+	return result, nil
+}
diff --git a/rules/rules_test.go b/rules/rules_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f9d244ea9161708ea28e2aa7f96dbdd865c2babf
--- /dev/null
+++ b/rules/rules_test.go
@@ -0,0 +1,32 @@
+package rules
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestKinds(t *testing.T) {
+	cases := []struct {
+		name   string
+		query  string
+		result string
+	}{
+		{
+			"cm kind",
+			"cm",
+			"distance",
+		},
+	}
+
+	ngn, err := New()
+	assert.NoError(t, err)
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			result, err := ngn.Kind(tc.query)
+			assert.NoError(t, err)
+
+			assert.Equal(t, tc.result, result)
+		})
+	}
+}