From 45f6b972c71e574bed7cabb375b37d73de7debf5 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Wed, 13 Aug 2025 21:37:43 +0100 Subject: [PATCH] Frustratingly close to working, which is another way of saying not working at all. --- project.clj | 3 +- src/arboretum/dengine/case.clj | 16 ++++-- src/arboretum/dengine/core.clj | 34 +++++++++-- src/arboretum/dengine/dengine.clj | 33 +++++++++++ src/arboretum/dengine/feature.clj | 40 +++++-------- src/arboretum/dengine/kb.clj | 88 ++++++++++++++++++++--------- src/arboretum/dengine/node.clj | 81 ++++++++++++++++++++++++-- src/arboretum/dengine/protocols.clj | 70 +++++++++++++++-------- src/arboretum/dengine/utils.clj | 10 ++-- test/dengine/feature_test.clj | 57 +++++++++++++------ test/dengine/kb_test.clj | 34 +++++++---- 11 files changed, 340 insertions(+), 126 deletions(-) create mode 100644 src/arboretum/dengine/dengine.clj diff --git a/project.clj b/project.clj index 8255e37..6cd34cf 100644 --- a/project.clj +++ b/project.clj @@ -4,5 +4,6 @@ :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.11.1"] - [org.clojure/clojure "1.12.0"]] + [org.clojure/clojure "1.12.0"] + [com.taoensso/timbre "4.10.0"]] :repl-options {:init-ns arboretum.dengine.core}) diff --git a/src/arboretum/dengine/case.clj b/src/arboretum/dengine/case.clj index bb75613..7d4b032 100644 --- a/src/arboretum/dengine/case.clj +++ b/src/arboretum/dengine/case.clj @@ -1,10 +1,14 @@ (ns arboretum.dengine.case (:require [arboretum.dengine.protocols :refer [Case]])) -(defrecord CaseImpl [ id name knowledge] +(defrecord CaseImpl [case-id case-name knowledge] Case - (id [this] :foo);;(:id this)) - (name [this] "foo") ;;(or (:name this) (clojure.core/name (:id this)))) - (knowledge [this] (:knowledge this)) - (is? [this feature-id] ((:knowledge this) feature-id)) - ) \ No newline at end of file + (authority [this feature-id] (:authority ((:knowledge this) feature-id))) + (case-id [this] (:case-id this)) + (case-name [this] (or (:case-name this) (clojure.core/name (:case-id this))))) + +(defn make-case + ([id name] + (make-case id name {})) + ([id name knowledge] + (CaseImpl. id name knowledge))) \ No newline at end of file diff --git a/src/arboretum/dengine/core.clj b/src/arboretum/dengine/core.clj index 5e491d9..a4a96a7 100644 --- a/src/arboretum/dengine/core.clj +++ b/src/arboretum/dengine/core.clj @@ -1,14 +1,38 @@ (ns arboretum.dengine.core ;; (:require [arboretum.dengine.feature :refer [decide explain]] ;; [arboretum.dengine.protocols :refer [proposition]]) - ) -(defn foo - "I don't do a whole lot." - [x] - (println x "Hello, World!")) + (:require + [arboretum.dengine.case :refer [make-case]] + [arboretum.dengine.feature :refer [make-feature]] + [arboretum.dengine.kb :refer [!kb]] + [arboretum.dengine.utils :refer [string->keyword]]) + + (:import [arboretum.dengine.case CaseImpl])) ;; (defn is? ;; [feature case kb] ;; (let [evaluation (decide feature case kb)] ;; ((proposition feature) evaluation))) + + +(defn add-case! + "Add a case to the knowledge base with this `name`, and, if supplied, this `id`." + ([^String name] + (add-case! name (string->keyword name (set (map #(:case-id %) (.cases @!kb)))))) + ([^String name ^clojure.lang.Keyword id] + (let [c (make-case id name)] + (swap! !kb assoc-in [:cases id] c) + c)) + ([^String name ^clojure.lang.Keyword id ^clojure.lang.PersistentArrayMap knowledge] + (let [c (make-case id name knowledge)] + (swap! !kb assoc-in [:cases id] c) + c))) + +(defn add-feature! + ([^String proposition default rootnode] + (add-feature! proposition (string->keyword proposition (set (map #(:feature-id %) (.features @!kb)))) default rootnode)) + ([^String proposition ^clojure.lang.Keyword id default rootnode] + (let [f (make-feature proposition id default rootnode)] + (swap! !kb assoc-in [:features id] f) + f))) diff --git a/src/arboretum/dengine/dengine.clj b/src/arboretum/dengine/dengine.clj new file mode 100644 index 0000000..108b00f --- /dev/null +++ b/src/arboretum/dengine/dengine.clj @@ -0,0 +1,33 @@ +(ns arboretum.dengine.dengine + (:require + [arboretum.dengine.kb :refer [!kb find-case-for find-feature-for remember!]] + [arboretum.dengine.protocols :refer [Case]] + [taoensso.timbre :as timbre :refer [log]])) + + +(defn dengine [feat c] + (let [c' (if (satisfies? Case c) c (-> @!kb :cases c)) + f' (find-feature-for @!kb feat)] + ;; (log (format "Inferring value of `%s` for `%s`" + ;; (:case-id c') (:feature-id f'))) + (when (nil? c') (throw (ex-info "Unknown or invalid case" + {:case-identifier c}))) + (when (:rootnode f') + (first (remove nil? (map #(.evaluate % c) + (:children (:rootnode f')))))))) + +(defn decide-feat [feat c] + (let [c' (find-case-for @!kb c) + f' (find-feature-for @!kb feat) + v (.knowledge @!kb (.case-id c') (.feature-id f')) + k (cond v v + (:rootnode f') (dengine f' c') + :else {:value (:default f') + :authority :default + :reason (format + "I assumed %s was %s of %s." + (:name f') + (:default f') + (.name c'))})] + (remember! f' c' k) + (:value k))) diff --git a/src/arboretum/dengine/feature.clj b/src/arboretum/dengine/feature.clj index 1b19bbc..45a2762 100644 --- a/src/arboretum/dengine/feature.clj +++ b/src/arboretum/dengine/feature.clj @@ -1,35 +1,25 @@ (ns arboretum.dengine.feature - (:require [arboretum.dengine.kb :refer [!kb]] - [arboretum.dengine.protocols :refer [Case Feature Node]] - [arboretum.dengine.utils :refer [string->keyword]] - [clojure.spec.alpha :as spec])) + (:require + [arboretum.dengine.dengine :refer [decide-feat]] + [arboretum.dengine.protocols :refer [Feature]] + [arboretum.dengine.utils :refer [string->keyword]])) -(spec/def :arboretum.dengine.feature/name string?) +;; (spec/def :arboretum.dengine.feature/name string?) -(spec/def :arboretum.dengine.feature/default boolean?) +;; (spec/def :arboretum.dengine.feature/default boolean?) -(defn- dengine [this c] - (let [c' (if (satisfies? Case c) c (-> @!kb :cases c))] - (when (nil? c') (throw (ex-info "Unknown or invalid case" - {:case-identifier c}))) - (if (:rootnode this) - (when-not (satisfies? Node (:rootnode this)) - (throw (ex-info "Invalid DTree node" - {:node (:rootnode this) - :context this}))) - (first - (filter boolean? - (map #(.evaluate % c) - (-> this :rootnode :children))))))) -(defrecord FeatureImpl [^String proposition ^boolean default rootnode] +(defrecord FeatureImpl [^String proposition ^clojure.lang.Keyword feature-id ^boolean default rootnode] Feature - (decide [this c] (let [v ((.id this) c)] - (cond (boolean? v) v - (:rootnode this) (dengine (:rootnode this) c) - :else (:default this)))) + (decide [this c] (decide-feat this c)) (default [this] (:default this)) - (id [this] (string->keyword (:proposition this))) + (feature-id [this] (string->keyword (:proposition this))) (rootnode [this] (:rootnode this)) (proposition [this] (:proposition this))) +(defn make-feature + "A convenience function to make features." + ([proposition default rootnode] + (make-feature proposition (string->keyword proposition) default rootnode)) + ([proposition id default rootnode] + (FeatureImpl. proposition id default rootnode))) \ No newline at end of file diff --git a/src/arboretum/dengine/kb.clj b/src/arboretum/dengine/kb.clj index 3e7e2ce..b62f1d3 100644 --- a/src/arboretum/dengine/kb.clj +++ b/src/arboretum/dengine/kb.clj @@ -1,44 +1,78 @@ (ns arboretum.dengine.kb (:require - arboretum.dengine.case - [arboretum.dengine.protocols :refer [Case Feature Kb]] - [arboretum.dengine.utils :refer [string->keyword]] - [clojure.pprint :refer [pprint]]) - (:import - [arboretum.dengine.case CaseImpl])) + [arboretum.dengine.protocols :refer [Case Feature KB]] + [clojure.pprint :refer [pprint]] + [taoensso.timbre :as timbre :refer [log trace debug info warn error fatal report + logf tracef debugf infof warnf errorf fatalf reportf + spy]])) -(defrecord KBImpl [cases features] - Kb + +;; (timbre/set-ns-min-level! :info) + +(defn find-case-for [kb c] + (cond (satisfies? Case c) c + (keyword? c) (-> kb :cases c) + :else (throw (ex-info (format "Unexpected value `%s` passed for case" c) + {:case c + :type (type c)})))) + +(defn find-feature-for [kb f] + (cond (satisfies? Feature f) f + (keyword? f) (-> kb :features f) + :else (throw (ex-info (format "Unexpected value `%s` passed for feature" f) + {:case f + :type (type f)})))) + +(defn do-is? [kb case-or-id feature-or-id] + (let [c (find-case-for kb case-or-id) + f (find-feature-for kb feature-or-id)] + (-> c :knowledge (:feature-id f) :value))) + +(defrecord KBImpl [^clojure.lang.APersistentMap cases ^clojure.lang.APersistentMap features] + KB (cases [this] (:cases this)) (features [this] (:features this)) (get-case [this case-id] (-> this :cases case-id)) (get-feature [this feature-id] (-> this :features feature-id)) (is? [this case-or-id feature-or-id] - (let [c (if (satisfies? Case case-or-id) - case-or-id (-> this :cases case-or-id)) - f (if (satisfies? Feature feature-or-id) feature-or-id - (-> this :features feature-or-id))] - (-> c (:id f))))) + (do-is? this case-or-id feature-or-id)) + (knowledge [this case-or-id feature-or-id] + (let [c (find-case-for this case-or-id) + f (find-feature-for this feature-or-id)] + (-> c :knowledge (:feature-id f))))) (def !kb "The one instance of the knowledge base." - (atom (KBImpl. nil nil))) + (atom (KBImpl. {} {}))) -(defn add-case - "Add a case to the knowledge base with this `name`, and, if supplied, this `id`." - ([^String name] - (add-case name (string->keyword name (map :id (.cases @!kb))))) - ([^String name ^clojure.lang.Keyword id] - (let [c (CaseImpl. id name {})] - (swap! !kb update-in :cases conj c)))) +(defn remember! + "Remember this `value` for the feature with this `feature-id` of the case + with this `case-id`. Return the `value`." + [case-id feature-id value] + ;; (log (format "Remembering case `%s`, feature `%s`, value `%s`" + ;; case-id feature-id value)) + (cond + (boolean? value) (swap! !kb assoc-in + [:cases case-id :knowledge feature-id :value] value) + (map? value) (swap! !kb assoc-in + [:cases case-id :knowledge feature-id] value) + :else (throw (ex-info "Unexpected value?" {:case-id case-id + :feature-id feature-id + :value value}))) + value) -(defn persist [filepath] +(defn persist [filepath] (let [p (if (empty? filepath) "kb.edn" filepath)] - (spit p (with-out-str (pprint @!kb))))) -(defn restore [filepath] + ;; (info (format "Persisting knowledge base with %d cases, %d features to `%s`" + ;; (count (:cases @!kb)) (count (:features @!kb)) filepath)) + (spit p (with-out-str (pprint @!kb))))) + +(defn restore [filepath] (let [p (if (empty? filepath) "kb.edn" filepath) kb (read-string (slurp p))] - (if (satisfies? Kb kb) (reset! !kb kb) + ;; (info (format "Read knowledge base with %d cases, %d features from `%s`" + ;; (count (:cases kb)) (count (:features kb)) filepath)) + (if (satisfies? KB kb) (reset! !kb kb) (throw (ex-info "Not a knowledge base?" {:filepath filepath - :kb kb - :type (type kb)}))))) \ No newline at end of file + :kb kb + :type (type kb)}))))) \ No newline at end of file diff --git a/src/arboretum/dengine/node.clj b/src/arboretum/dengine/node.clj index 5c1a1e8..a80c7b4 100644 --- a/src/arboretum/dengine/node.clj +++ b/src/arboretum/dengine/node.clj @@ -1,12 +1,81 @@ (ns arboretum.dengine.node - (:require [arboretum.dengine.protocols :refer [Node]] - [clojure.spec.alpha :as spec])) + (:require + [arboretum.dengine.kb :refer [!kb]] + [arboretum.dengine.protocols :refer [Feature Node]] + [clojure.string :as string])) (defrecord NodeImpl [children ^Boolean colour feature ^String fragment] - Node + Node (children [this] "The children of this node" (:children this)) (colour ^Boolean [this] "The colour of this node" (:colour this)) (feature [this] "The feature at this node" (:feature this)) - (fragment ^String [this] "The explanation fragment at this node") - (evaluate [this case-id] ) - ) \ No newline at end of file + (fragment ^String [this] "The explanation fragment at this node" + (:fragment this)) + (evaluate [this case-id] + (let [c (-> !kb .cases case-id) + f (if (satisfies? Feature (:feature this)) (:feature this) + (-> !kb .features (:feature this))) + value (:value (.knowledge !kb c f))] + (when + value (if + (empty? (:children this)) + ;; If my feature value is true, and I have no + ;; children, return my colour and my fragment + {:value (:colour this) + :authority :rule + :reason (:fragment this)} + ;; but if I have some children... + (first (remove nil? (map #(.evaluate % c) + (:children this))))))))) + + +;; Here's the algorithm as published in A Graphical Inference Mechanism, +;; but it's not real Interlisp code, and I remember there being at least +;; one error in it. My memory is that Peter felt real Interlisp wasn't +;; sufficiently academic to publish in an academic paper, so he translated +;; it into an 'idealised' Lisp, for which no implementation existed. +;; However this was based on my Interlisp code, and, as we no longer have +;; that, is the best we can currently do. +;; +;; I have to say, given this is certainly based on my code, it was a long +;; time ago and I'm not proud of it. +;; +;; (DSearch (object feature) +;; (LOCAL (node nodesToDo newNodes children stickNodes) +;; (SET ’nodesToDo (LIST (GET feature ’dTreeRootNode)) +;; ;; {origin of the DTree} +;; (SET ’stickNodes NIL) +;; (LOOP +;; (UNTIL (EMPTY? nodesToDo)) +;; {if no more to do, exit loop} +;; (SET’node (POP nodesToDo)) +;; (SET’children (GET node ’children)) +;; {get a node from nodesToDo and obtain its children. ..} +;; (SET’newNodes NIL) +;; ;; {..the children that can be accessed will be newNodes} +;; (LOOP +;; (UNTIL (EMPTY? children)) +;; ;; {if all children have been done, exit this loop} +;; (SET child (POP children)) +;; ;; {get the next child} +;; (IF (EQUAL +;; (DecideFeat +;; object +;; (GET child 'feature)) +;; Yes) +;; ;; {Run the system to discover whether the feature of the child node is +;; ;; true of object} +;; (SET newNodes +;; (APPEND child newNodes)))) +;; ;; {..if it is then the node can be accessed and is added to newNodes, +;; ;; and loop} +;; (IF (EMPTY? newNodes) +;; (SET ’stickNodes +;; (PUSH child stickNodes)) +;; {if no new nodes were added the child is a stickNode} +;; (ELSE (PUSH newNodes nodesToDo))))) +;; ;; {otherwise the newNodes must themselves be examined for +;; ;; stickNodes} +;; (RETURN (Decide stickNodes)) +;; {finally, make a decision on the basis of the stickNodes found} + diff --git a/src/arboretum/dengine/protocols.clj b/src/arboretum/dengine/protocols.clj index 87660e1..201904e 100644 --- a/src/arboretum/dengine/protocols.clj +++ b/src/arboretum/dengine/protocols.clj @@ -10,14 +10,14 @@ (defprotocol Case "A case to be decided. It represents an object-in-the-world about which knowledge may be held and enquiries may be made." - (id ^Keyword [this] "Returns the `:id` of this case.") - (name ^String [this] "Returns the name associated with this case") - (knowledge ^APersistentMap [this] "Returns a map whose keys are ids of features - and whose values are the values of those - features in this case.") - (is? ^Boolean [this feature-id] "Returns `true` if feature-id true of `this`, - `false` if it's definitely not true, and - `nil` if its truth value is unknown.")) + (authority [this feature-id] "Returns information about how the value of the + feature identified be `feature-id` is known + about `this` case. + + At this stage, one of the keywords `:default`, + `:inference`, `:user`.") + (case-id ^Keyword [this] "Returns the `:case-id` of this case.") + (case-name ^String [this] "Returns the name associated with this case")) (defmacro case? "Return `true` if this `obj` appears to be a valid case. @@ -28,29 +28,42 @@ `(satisfies? Case ~obj)) (defprotocol Feature - "A feature has three essential elements: a proposition, a default value, and - a method of decision" - (decide ^Boolean [this case] "Returns the default value of my proposition for this `case`. + "A feature has three essential elements: a proposition, a default + value, and a method of decision" + (decide ^Boolean [this case] "Returns the default value of my + proposition for this `case`. - `case` should be either an object which satisfies the `Case` - protocol or else a keyword, in which case it should be the + `case` should be either an object which + satisfies the `Case` protocol or else a + keyword, in which case it should be the key of some case in the global `kb`.") + ;; it would be a sensible addition for Feature have an additional slot for + ;; a decision function other than a DTree, so that a value could be fetched + ;; from some algorithmic or online source. But Cedar didn't have this and + ;; I am currently just reimplementing Cedar. (default ^Boolean [this] "Returns the default value of my proposition") - (id ^Keyword [this] "Returns a keywordised representation of the proposition of this feature") - (proposition ^String [this] "Returns the proposition associated with this feature") - (rootnode [this] "Returns the root node of the dtree for this feature, if one exists, else `nil`")) + (feature-id ^Keyword [this] "Returns a keywordised representation of the proposition + of this feature") + (rootnode [this] "Returns the root node of the dtree for this feature, if one + exists, else `nil`") + (proposition ^String [this] "Returns the proposition associated with this + feature")) (defmacro feature? [obj] `(satisfies? Feature ~obj)) -(defprotocol Kb - ;; "The knowledge base" - (get-case ^APersistentMap [this ^Keyword id] +(defprotocol KB + "A knowledge base, comprising features and means to infer their value + in a given case, and cases which have features with values." + (ask-user ^Boolean [this case-or-id] + "Ask the user whether `feature-or-id` is true of the case identified + by `case-or-id?` If the user doesn't know, return `nil`.") + (get-case ^APersistentMap [this ^Keyword case_id] "Return the case with this `id` from among my cases, if present, else `nil`") (cases ^APersistentMap [this] - "All cases known in this knowledge base, keyed by `:id`") + "All cases known in this knowledge base, keyed by `:case-id`") (get-feature ^APersistentMap [this id-or-prop] "Return the feature with this `id` from among my features, if present, else `nil`") @@ -58,13 +71,24 @@ "All features known in this knowledge base, keyed by `:proposition`") (is? ^Boolean [this case-or-id feature-or-id] "Is the feature identified by `feature-or-id` true of the case identified - by `case-or-id?")) + by `case-or-id`? If feature is undecided for this case, return `nil`.") + (knowledge ^APersistentMap [this case-or-id feature-or-id] + "Return whatever is currently known for this `case-or-id` about this + `feature-or-id`. If the feature is undecided for this case, return + `nil`.")) (defprotocol Node "A node in a DTree" + ;; potentially add a field for `authority`, the authority for this (part of + ;; the) rule. But, version Cedar didn't have this -- this is really a + ;; Wildwood feature, and what I'm trying to do here is reimplement Cedar. (children [this] "The children of this node") (colour ^Boolean [this] "The colour of this node") (feature [this] "The feature at this node") (fragment ^String [this] "The explanation fragment at this node") - (evaluate [this case-id] "evaluate this node in the context of the case identified by `case-id`.") - (explain [this case-id] "return an explanation from this node or its children for the case identified by `case-id`")) \ No newline at end of file + (evaluate [this case-id] "evaluate this node in the context of the case + identified by `case-id`. + Returns a map with keys `:value`, `:explanation` + and possibly `:authority`.") + (explain [this case-id] "return an explanation from this node or its children + for the case identified by `case-id`")) \ No newline at end of file diff --git a/src/arboretum/dengine/utils.clj b/src/arboretum/dengine/utils.clj index 33b4022..cd243be 100644 --- a/src/arboretum/dengine/utils.clj +++ b/src/arboretum/dengine/utils.clj @@ -1,17 +1,17 @@ (ns arboretum.dengine.utils (:require - [clojure.string :refer [lower-case replace]]) + [clojure.string :as s :refer [lower-case replace]]) (:import [clojure.lang PersistentHashSet])) (defn string->keyword "Produce a keyword based on this `s`. If `others` are supplied, the return value will be different from any of those others." - ([^String s] - (let [x (replace - (replace (lower-case s) #"[^]a-z0-9- ]" "") + ([^String x] + (let [x' (s/replace + (s/replace (s/lower-case x) #"[^]a-z0-9- ]" "") #" " "-")] - (keyword (if (empty? x) (gensym) x)))) + (keyword (if (empty? x') (gensym) x')))) ([^String s ^PersistentHashSet others] (let [c (string->keyword s)] (if (others c) (keyword (gensym (name c))) c)))) diff --git a/test/dengine/feature_test.clj b/test/dengine/feature_test.clj index 6bf4f07..3745e78 100644 --- a/test/dengine/feature_test.clj +++ b/test/dengine/feature_test.clj @@ -1,20 +1,43 @@ (ns dengine.feature-test - (:require [clojure.test :refer [deftest is testing]] - [arboretum.dengine.feature :refer :all]) - (:import [arboretum.dengine.feature FeatureImpl])) + (:require + [arboretum.dengine.core :refer [add-case! add-feature!]] + [arboretum.dengine.kb :refer [!kb]] + [clojure.pprint :refer [pprint]] + [clojure.test :refer [deftest is testing]]) + (:import + [arboretum.dengine.kb KBImpl])) + +(def testkb (KBImpl. {} + {})) + +(reset! !kb testkb) + (deftest feature-impl-test - (testing "Testing feature implementation" - (let [f (FeatureImpl. "Is entitled to Widow's Allowance" false nil)] - (let [expected :is-entitled-to-widows-allowance - actual (.id f)] - (is (= actual expected))) - (let [expected false - actual (.default f)] - (is (= actual expected))) - (let [expected false - actual (.decide f {})] - (is (= actual expected) "If we have no knoledge and no decision method, take the defualt.")) - (let [expected false - actual (.decide f {:is-entitled-to-widows-benefit true})] - (is (= actual expected) "If we have knoledge, return it."))))) \ No newline at end of file + (testing "Testing feature implementation" + (let [f (add-feature! "is entitled to Widow's Allowance" :is-entitled-to-widows-allowance false nil) + c (add-case! "Mrs Nora Trellis" :case_1 + {:married {:value true + :authority :user + :reason "I have been told that married is true of Mrs Norah Trellis"} + :divorced {:value false + :authority :default + :reason "I assumed that divorced is false of Mrs Norah Trellis"} + :is-entitled-to-widows-benefit {:value true + :authority :rule + :reason "I infered that is-entitled-to-widows-benefit is true of Mrs Norah Trellis"}})] + (pprint {:f f + :c c}) + (let [expected :is-entitled-to-widows-allowance + actual (.feature-id f)] + (is (= actual expected))) + (let [expected false + actual (.default f)] + (is (= actual expected))) + (let [expected false + actual (.decide f c)] + (is (= actual expected) "If we have no knowledge and no decision method, take the defualt.")) + (let [expected true + actual (.decide (add-feature! "Married" :married false nil) :case_1)] + (is (= actual expected) "If we have knowledge, return it.")))) + ) \ No newline at end of file diff --git a/test/dengine/kb_test.clj b/test/dengine/kb_test.clj index 250dde7..1b27117 100644 --- a/test/dengine/kb_test.clj +++ b/test/dengine/kb_test.clj @@ -1,17 +1,30 @@ (ns dengine.kb-test - (:require [clojure.test :refer [deftest is testing]] - arboretum.dengine.case - arboretum.dengine.feature - [arboretum.dengine.kb :refer :all]) - (:import [arboretum.dengine.case CaseImpl] - [arboretum.dengine.feature FeatureImpl] - [arboretum.dengine.kb KBImpl])) + (:require + [arboretum.dengine.core :refer [add-case!]] + [arboretum.dengine.kb :refer :all] + [clojure.pprint :refer [pprint]] + [clojure.test :refer [deftest is testing]]) + (:import + [arboretum.dengine.kb KBImpl])) -(def testkb (KBImpl. [(CaseImpl. :case_1 "Mrs Nora Trellis" {:married true :divorced false})] - [(FeatureImpl. "is entitled to Widow's Benefit" false nil)])) +(def testkb (KBImpl. {} + {})) + +(pprint testkb) (reset! !kb testkb) +(add-case! "Mrs Nora Trellis" :case_1 + {:married {:value true + :authority :user + :reason "I have been told that married is true of Mrs Norah Trellis"} + :divorced {:value false + :authority :default + :reason "I assumed that divorced is false of Mrs Norah Trellis"} + :is-entitled-to-widows-benefit {:value true + :authority :rule + :reason "I infered that is-entitled-to-widows-benefit is true of Mrs Norah Trellis"}}) + (deftest access-test (testing "Accessing knowledge in the knowledge base" (let [expected nil @@ -22,5 +35,4 @@ (is (= actual expected))) (let [expected false actual (.is? testkb :case_1 :divorced)] - (is (= actual expected))) - )) \ No newline at end of file + (is (= actual expected))))) \ No newline at end of file