Commit 9a49c1b4 authored by Bruno Burke's avatar Bruno Burke 😁

Merge branch 'converge-calculation-and-multistep-calculation-exercise' into develop

parents a9c4b979 7881d0d9
(ns lernmeister.components.exercise-types.calculation.build
(:require [lernmeister.components.exercise-types.build :as e-build]
[lernmeister.components.exercise-types.calculation.migrate
:refer
[exercise-scheme-version migrate-exercise]]
[lernmeister.components.helper :refer [migrate-if-necessary]]))
(defn build [exercise]
(if-let [migrated-exercise (migrate-if-necessary exercise :exercise-scheme "calculation"
exercise-scheme-version migrate-exercise)]
migrated-exercise
(-> exercise
(select-keys [:title :id])
(assoc :debug-info "unmigrateable"))))
(defmethod e-build/build :calculation [exercise & _]
(build exercise))
......@@ -2,12 +2,14 @@
#?(:clj
(:require [lernmeister.components.exercise-types.calculation.spec :as spec]
[lernmeister.components.exercise-types.calculation.check :as check]
[lernmeister.components.exercise-types.calculation.prepare :as prepare])
[lernmeister.components.exercise-types.calculation.prepare :as prepare]
[lernmeister.components.exercise-types.calculation.build :as build])
:cljs
(:require [reagent.core :as reagent]
[lernmeister.components.content-elements.core :as content-manager]
[lernmeister.components.exercise-types.calculation.spec :as spec]
[lernmeister.components.exercise-types.calculation.prepare :as prepare]
[lernmeister.components.exercise-types.calculation.build :as build]
[lernmeister.components.exercise-types.calculation.check :as check]
[lernmeister.components.exercise-types.calculation.views.edit :as edit-view]
[lernmeister.components.exercise-types.calculation.views.show :as show-view])))
......@@ -17,7 +19,7 @@
:edit {:settings #?(:cljs edit-view/settings :clj nil)
:additional-forms #?(:cljs edit-view/additional-forms :clj nil)}
:prepare prepare/prepare
:build build/build
:renderer #?(:cljs show-view/exercise-renderer :clj nil)
:checker check/calculation-check
:spec ::spec/calculation-question})
(ns lernmeister.components.exercise-types.calculation.migrate
(:require [lernmeister.components.exercise-types.multistep-calculation.migrate
:refer
[convert-number-to-object
modify-exercise-field-vec
set-current-scheme]]))
(def exercise-scheme-version "2020-09-07")
(defn modify-calculations [exercise]
(partial modify-exercise-field-vec exercise [:core :calculations]))
(defn set-missing-error-relative [exercise]
((modify-calculations exercise) #(assoc % :error-relative (or (:error-relative %) "1"))))
(defn convert-numbers [exercise]
((modify-calculations exercise) convert-number-to-object))
(defn normalize-units [exercise]
(if (get-in exercise [:core :with-units])
((modify-calculations exercise) #(assoc % :units (or (:units %) [])))
((modify-calculations exercise) #(dissoc % :units))))
(defn remove-unneeded-ooms [exercise]
(if-not (get-in exercise [:core :with-oom])
((modify-calculations exercise) #(dissoc % :order-of-magnitude))
exercise))
(defn remove-unneeded-error-keys [exercise]
((modify-calculations exercise) #(dissoc % :error)))
(defn migrate-exercise [exercise]
(->> exercise
remove-unneeded-error-keys
remove-unneeded-ooms
normalize-units
convert-numbers
set-missing-error-relative
(set-current-scheme "calculation" exercise-scheme-version)))
(ns lernmeister.components.exercise-types.calculation.migrate-answer
(:require [clojure.set :refer [difference]]
[lernmeister.components.exercise-types.multistep-calculation.migrate
:refer
[convert-number-to-object migrate-object]]))
(def answer-scheme-version "2020-09-07")
(defn set-current-scheme [answer]
(assoc answer :answer-scheme {:type "calculation" :version answer-scheme-version}))
(defn remove-ooms [answer]
(update answer :calculations (fn [calcs] (migrate-object #(dissoc % :order-of-magnitude) calcs))))
(defn convert-calculation-numbers [answer]
(update answer :calculations (fn [calcs] (migrate-object convert-number-to-object calcs))))
(defn add-missing-calculation-objects [calculation-ids answer]
(let [calculations (:calculations answer)
missing-ids (difference calculation-ids (set (keys calculations)))]
(update answer :calculations (fn [calcs] (reduce (fn [res k] (assoc res k {:number nil}))
calcs missing-ids)))))
(defn wrap-answer-calculations [answer]
(if-not (contains? answer :calculations)
(let [wrapped-answer {}]
(assoc wrapped-answer :calculations answer))
answer))
(defn migrate-answer [calculation-ids answer]
(->> answer
wrap-answer-calculations
(add-missing-calculation-objects calculation-ids)
convert-calculation-numbers
remove-ooms
set-current-scheme))
......@@ -2,8 +2,11 @@
(:require [lernmeister.components.exercise-types.prepare :as e-prepare]))
(defn select-exercise-keys [exercise]
(update-in exercise [:core]
#(select-keys % [:with-oom :with-units :type :task-description :calculations])))
(select-keys exercise [:title :id :type :core :exercise-scheme]))
(defn select-core-keys [exercise]
(update exercise :core
#(select-keys % [:with-oom :with-units :type :task-description :calculations])))
(defn shuffle-calculations [exercise]
(if (get-in exercise [:core :shuffled])
......@@ -12,7 +15,7 @@
exercise))
(defn prep-calc [calculation]
(select-keys calculation [:id :name :number :order-of-magnitude :units]))
(select-keys calculation [:id :name]))
(defn prep-calcs [exercise]
(let [preped-calcs (into [] (map prep-calc (get-in exercise [:core :calculations])))]
......@@ -23,8 +26,8 @@
exercise
prep-calcs
shuffle-calculations
select-core-keys
select-exercise-keys))
(defmethod e-prepare/prepare :calculation [exercise & [options]]
(prepare exercise))
(defmethod e-prepare/prepare :calculation [exercise-build & _]
(prepare exercise-build))
(ns lernmeister.components.exercise-types.multistep-calculation.build
(:require [lernmeister.components.exercise-types.multistep-calculation.check
:refer [mathjs-number-from-number-obj mathjs-str-to-number-obj mathjs-eval-and-format
number-obj]]
[lernmeister.components.exercise-types.build :as e-build]))
(:require [lernmeister.components.exercise-types.build :as e-build]
[lernmeister.components.exercise-types.multistep-calculation.check
:refer
[mathjs-eval-and-format
mathjs-number-from-number-obj
mathjs-str-to-number-obj
number-obj]]
[lernmeister.components.exercise-types.multistep-calculation.migrate
:refer
[exercise-scheme-version migrate-exercise]]
[lernmeister.components.helper :refer [migrate-if-necessary]]))
(defmulti number-obj-from-variable (fn [variable] (:type variable)))
......@@ -24,21 +31,27 @@
(defmethod number-obj-from-variable :default [_] number-obj)
(defn prepare-variables [exercise]
(let [variables-map (reduce
(fn [res-map variable]
(assoc res-map (:id variable) (number-obj-from-variable variable)))
{} (:variables exercise))]
(update-in exercise [:core :params]
#(into []
(map (fn [param]
(if-let [variable-id (:variable-id param)]
(assoc param :number (get variables-map (name variable-id)))
param)) %)))))
(if (not-empty (get-in exercise [:core :params]))
(let [variables-map (reduce
(fn [res-map variable]
(assoc res-map (:id variable) (number-obj-from-variable variable)))
{} (:variables exercise))]
(update-in exercise [:core :params]
#(into []
(map (fn [param]
(if-let [variable-id (:variable-id param)]
(assoc param :number (get variables-map (name variable-id)))
param)) %))))
exercise))
(defn build [exercise]
(->
exercise
prepare-variables))
(if-let [migrated-exercise (migrate-if-necessary exercise :exercise-scheme "multistep-calculation"
exercise-scheme-version migrate-exercise)]
(-> migrated-exercise
prepare-variables)
(-> exercise
(select-keys [:title :id])
(assoc :debug-info "unmigrateable"))))
(defmethod e-build/build :multistep-calculation [exercise & [options]]
(build exercise))
......@@ -10,7 +10,9 @@
[lernmeister.components.exercise-types.multistep-calculation.migrate-answer
:refer
[answer-scheme-version migrate-answer]]
[lernmeister.components.helper :refer [math-abs]])]
[lernmeister.components.helper
:refer
[math-abs migrate-if-necessary]])]
:cljs
[(:require
[clojure.string :as clj-str :refer [join split trim]]
......@@ -19,7 +21,9 @@
[lernmeister.components.exercise-types.multistep-calculation.migrate-answer
:refer
[answer-scheme-version migrate-answer]]
[lernmeister.components.helper :refer [math-abs]])]))
[lernmeister.components.helper
:refer
[math-abs migrate-if-necessary]])]))
(def prefix-mapping {"Y" 24
"Z" 21
......@@ -427,8 +431,6 @@
:res-map res-map}))
(defmethod e-check/check-answer :multistep-calculation [exercise answer callback]
(let [migrated-answer (if-not (= (get answer :answer-scheme) {:type "multistep-calculation"
:version answer-scheme-version})
(migrate-answer answer)
answer)]
(let [migrated-answer (migrate-if-necessary answer :answer-scheme "multistep-calculation"
answer-scheme-version migrate-answer)]
(multistep-calculation-check exercise migrated-answer callback)))
......@@ -4,10 +4,10 @@
(def exercise-scheme-version "2019-11-22")
(defn set-current-scheme [exercise]
(defn set-current-scheme [type version exercise]
(-> exercise
(assoc-in [:authoring :migration-status] :current)
(assoc :exercise-scheme {:type "multistep-calculation" :version exercise-scheme-version})))
(assoc :exercise-scheme {:type type :version version})))
(defn migrate-object [migrate-fn obj]
(reduce-kv (fn [res-obj obj-k obj-v] (assoc res-obj obj-k (migrate-fn obj-v))) {} obj))
......@@ -247,4 +247,4 @@
select-calc-mapping-keys
select-var-mapping-keys
rename-ids
set-current-scheme))
(set-current-scheme "multistep-calculation" exercise-scheme-version)))
......@@ -18,12 +18,12 @@
(fn [assignments] (into [] (sort-by #(str (:sign %) (:idx %)) assignments))))
exercise))
(defn prepare [exercise-build]
(defn prepare [exercise]
(->
exercise-build
exercise
sort-assignments
add-number-of-steps
add-number-of-vars))
(defmethod e-prepare/prepare :multistep-calculation [exercise & [options]]
(prepare exercise))
(defmethod e-prepare/prepare :multistep-calculation [exercise-build & _]
(prepare exercise-build))
(ns lernmeister.components.helper
(:require #?(:clj [clojure.math.numeric-tower :as math])))
#?@
(:clj
[(:require
[clojure.tools.logging :as log]
[clojure.math.numeric-tower :as math])]))
(defn debug-message [message]
#?(:clj
(log/debug message)
:cljs
(js/console.debug message)))
(defn math-abs
"returns math-abs-val for clj&cljs"
......@@ -64,3 +74,46 @@
(defn find-keys-by-val [map val]
(reduce-kv (fn [res k v] (if (= v val) (conj res k) res)) #{} map))
(defn scheme-version-check [subject scheme-key current-version]
(let [subject-version (or (get-in subject [scheme-key :version]) "")
valid-subject-version? (re-matches #"^\d{4}-\d{2}-\d{2}$" subject-version)
valid-current-version? (re-matches #"^\d{4}-\d{2}-\d{2}$" current-version)]
(if (and valid-subject-version? valid-current-version?)
(let [comp-val (compare subject-version current-version)]
(if (pos? comp-val)
(do
(debug-message "Incompatible scheme-versions (subject-version more recent than current-version)!")
:incompatible)
(if (neg? comp-val) :migrate :current)))
(do (when-not valid-subject-version? (debug-message "Invalid subject-scheme-version declaration!"))
(when-not valid-current-version? (debug-message "Invalid current-scheme-version declaration!"))
:invalid))))
(defn scheme-type-check [subject scheme-key valid-type]
(if (and (map? subject) (contains? subject scheme-key))
(if (= (get-in subject [scheme-key :type]) valid-type)
:identical
(do
(debug-message "Subject-scheme-version is different from valid-scheme-type!")
:different))
:missing))
(defn get-migration-status [type-status subject scheme-key current-version]
(case type-status
:identical (scheme-version-check subject scheme-key current-version)
:missing :migrate
nil))
(defn set-migration-status [exercise scheme-key valid-type current-version]
(let [type-status (scheme-type-check exercise scheme-key valid-type)
migration-status (get-migration-status type-status exercise scheme-key current-version)]
(assoc-in exercise [:authoring :migration-status] migration-status)))
(defn migrate-if-necessary [subject scheme-key valid-type current-version migrate-fn]
(let [type-status (scheme-type-check subject scheme-key valid-type)
migration-status (get-migration-status type-status subject scheme-key current-version)]
(case migration-status
:migrate (migrate-fn subject)
:current subject
nil)))
......@@ -155,3 +155,31 @@
^{:key (str "info-" index)}
[info-line {:text text}])
texts)])
(def migration-strgs {:admin "Kontaktieren Sie Ihren Administrator!"
:wrong "Inkompatibler Datenstruktur-Typ."
:incompatible "Inkompatibilität von Datenstruktur und Umgebung."
:invalid "Ungültige Versionsbenennung der Datenstruktur."
:migrate (str "Um die Aufgabe bearbeiten zu können, "
"muss eine Migration der Datenstruktur durchgeführt werden!")})
(defn scheme-migration-dialogue [{:keys [migration-status migrate-fn exercise-atom]}]
[:div.is-block.has-text-centered
(case migration-status
:incompatible [:div.mt-6.mb-5.has-text-danger
[:h5.mb-3 [:b (:incompatible migration-strgs)]]
[:p (:admin migration-strgs)]]
:invalid [:div.mt-6.mb-5.has-text-danger
[:h5.mb-3 [:b (:invalid migration-strgs)]]
[:p (:admin migration-strgs)]]
:migrate [:<>
[:h5.mt-6.mb-5 [:b (:migrate migration-strgs)]]
[:a.button.is-info.mb-6
{:on-click (partial migrate-fn exercise-atom)}
"Datenstruktur migrieren"]]
[:div.mt-6.mb-5.has-text-danger
[:h5.mb-3 [:b (:wrong migration-strgs)]]
[:p (:admin migration-strgs)]])])
(defn unmigrateable-answer []
[:div.is-block.has-text-centered.has-text-danger "Unmigrateble answer"])
......@@ -126,8 +126,7 @@
[ui/icon "fa-code"]
[:span "EDN Code generieren"]]
[:pre {:style {:overflow :auto}}
(str @generated)
]])))
(str @generated)]])))
(defn exercise-dialog [& {:keys [title exercise answer add-fn! discard-url options]}]
(let [exercise-types (exercise-types/get-types)
......@@ -148,8 +147,7 @@
[:div.new-question.card
[:div.card-header
[:p.card-header-title
"Einstellungen"
]]
"Einstellungen"]]
[:div.card-content
(when (not-empty @error)
[:div.card-panel
......@@ -186,6 +184,4 @@
[exercise-preview @exercise (or answer answer-local)])}
{:label "Metainformationen"
:icon "settings_ethernet"
:content [metadata-settings (reagent/cursor exercise [:metadata])]}]}]
]]]))})))
:content [metadata-settings (reagent/cursor exercise [:metadata])]}]}]]]]))})))
......@@ -208,23 +208,27 @@
(defn get-collats-for-vec
([vec-indices collats]
(reduce (fn [res vec-index]
(if (map? collats)
(reduce-kv (fn [res k v]
(conj res {:path [vec-index k] :value v}))
res collats)
(reduce (fn [res k]
(conj res {:path [vec-index k]}))
res collats)))
(let [path-fn #(if (vector? %) (apply conj [vec-index] %)
[vec-index %])]
(if (map? collats)
(reduce-kv (fn [res k v]
(conj res {:path (path-fn k) :value v}))
res collats)
(reduce (fn [res k]
(conj res {:path (path-fn k)}))
res collats))))
'() vec-indices))
([base-path vec-indices collats]
(reduce (fn [res vec-index]
(if (map? collats)
(reduce-kv (fn [res k v]
(conj res {:path (conj base-path vec-index k) :value v}))
res collats)
(reduce (fn [res k]
(conj res {:path (conj base-path vec-index k)}))
res collats)))
(let [path-fn #(if (vector? %) (apply conj (conj base-path vec-index) %)
(conj base-path vec-index %))]
(if (map? collats)
(reduce-kv (fn [res k v]
(conj res {:path (path-fn k) :value v}))
res collats)
(reduce (fn [res k]
(conj res {:path (path-fn k)}))
res collats))))
'() vec-indices)))
(defn dissoc-in
......
(ns lernmeister.components.exercise-types.calculation.components.migration
(:require [lernmeister.components.exercise-types.calculation.migrate
:refer [migrate-exercise]]))
(defn migrate-exercise-atom [exercise-atom]
(reset! exercise-atom (migrate-exercise @exercise-atom)))
(ns lernmeister.components.exercise-types.calculation.views.edit
(:require [cljs.spec.alpha :as s]
[reagent.core :as reagent]
(:require [clojure.string :as str]
[clojure.tools.reader.edn :as edn]
[lernmeister.components.common :refer [scheme-migration-dialogue]]
[lernmeister.components.exercise-types.calchelper
:refer
[calc-change-fn get-collats-for-vec]]
[lernmeister.components.exercise-types.calculation.components.migration
:refer
[migrate-exercise-atom]]
[lernmeister.components.exercise-types.calculation.migrate
:refer
[exercise-scheme-version]]
[lernmeister.components.exercise-types.calculation.views.show :as show]
[lernmeister.components.exercise-types.edit :as e-edit]
[lernmeister.components.helper :refer [set-migration-status vec-remove]]
[lernmeister.components.jshelper :refer [get-unique-id]]
[lernmeister.components.modal-editor.core :refer [modal-editor]]
[lernmeister.components.modal-editor.renderers :as modal-renderers]
[lernmeister.components.ui :as ui]
[clojure.string :as str]
[clojure.tools.reader.edn :as edn])
(:use [lernmeister.components.jshelper :only [get-unique-id try-number-parse]]
[lernmeister.components.helper :only [vec-remove]]))
[reagent.core :as reagent]))
(defn get-new-calculation [with-oom]
(def modal-state-atom (reagent/atom {}))
(defn get-new-calculation []
{:id (get-unique-id "ae")
:name nil
:number nil
:order-of-magnitude (if with-oom nil "0")
:units []
:number {:significand nil :order-of-magnitude "0"}
:correct-points 0
:incorrect-points 0})
:incorrect-points 0
:error-relative "1"})
(defn set-prop-in-all-calcs [prop-key value calc-cursor]
(dorun (map-indexed
......@@ -34,131 +46,164 @@
:correct int-part
:incorrect (str "-" int-part))))))
(defn show-calculation [exercise-core index calculation & {:keys [delete-fn]}]
(fn [exercise-core index calculation & {:keys [delete-fn]}]
(let [calculations (reagent/cursor exercise-core [:calculations])
calc-cursor (reagent/cursor exercise-core [:calculations index])
answer (into {}
(map
(fn [calc] {(:id calc) (select-keys calc [:number :order-of-magnitude :prefix :units])})
@calculations))]
(letfn [(update-calc [path value] (reset! calc-cursor (assoc-in calculation path value)))]
[ui/card
{:title (str "Berechnung " (inc index))
:footer-items [[:a.button.footer-item
{:on-click (fn [] (delete-fn index))}
[:span.icon [:i.fas.fa-minus]]
[:span "Entfernen"]]]}
(defn show-calculation [{:keys [exercise-core index calculation change-fn delete-fn]}]
(let [calculations (:calculations exercise-core)
base-path [:core :calculations index]
answer (into {}
(map
(fn [calc] {(:id calc) (select-keys calc [:number :units])})
calculations))]
[ui/card
{:title (str "Berechnung " (inc index))
:footer-items [[:a.button.footer-item
{:on-click (fn [] (delete-fn index))}
[:span.icon [:i.fas.fa-minus]]
[:span "Entfernen"]]]}
[:div.columns.is-multiline.card-content
[:div.column.is-half
[ui/field {:label "Größe"}
[ui/input-text
{:value (:name calculation)
:on-change #(update-calc [:name] (str/triml %))}]]]
[:div.column.is-half
[ui/field {:label "Fehler (in %)"}
[ui/input-text
{:value (:error-relative calculation "1")
:on-change (fn [value]
(when-let [v (-> value
(->>
(re-matches #"^\-?(0*)(([1-9]\d*)|0)$"))
(nth 2))]
(update-calc [:error-relative]
(edn/read-string v))))}]]]
[:div.column.is-half
[ui/field {:label "Punkte (korrekt)"}
[ui/input-text
{:value (:correct-points calculation)
:on-change (fn [value]
(when-let [v (parse-points value :correct