Commit 790a9351 authored by Joachim Schunk's avatar Joachim Schunk

unify calculation/multistep-calculation exercises

parent 5805d337
Pipeline #47571 failed with stages
in 1 minute and 59 seconds
(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))
......@@ -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))
......@@ -180,3 +180,6 @@
[: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"])
(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)))
......@@ -190,7 +190,8 @@
:with-units false
:calculations []}
%))
(set-migration-status :exercise-scheme "calculation" exercise-scheme-version)))))}))
(set-migration-status :exercise-scheme "calculation"
exercise-scheme-version)))))}))
(defn additional-forms [exercise-atom]
(when (= :current (get-in @exercise-atom [:authoring :migration-status]))
......
(ns lernmeister.components.exercise-types.calculation.views.show
(:require [lernmeister.components.common
:refer
[correct-tick latex-span wrong-tick]]
[correct-tick
get-result-color-class
latex-span
unmigrateable-answer
wrong-tick]]
[lernmeister.components.content-elements.exercise.show :as ce-ex]
[lernmeister.components.content-elements.exercise.task-description
:refer
[default-task-description]]
[lernmeister.components.exercise-types.calchelper
:refer
[build-latex-string calc-change-fn]]))
[build-latex-string calc-change-fn]]
[lernmeister.components.exercise-types.calculation.migrate-answer
:refer
[answer-scheme-version migrate-answer]]
[lernmeister.components.exercise-types.multistep-calculation.check
:refer
[number-obj]]
[lernmeister.components.exercise-types.multistep-calculation.views.show-steps
:refer
[point-result]]
[lernmeister.components.helper :refer [migrate-if-necessary]]
[lernmeister.components.modal-editor.core :refer [modal-editor]]
[lernmeister.components.modal-editor.renderers :as modal-renderers]
[lernmeister.components.ui :as ui]
[reagent.core :as reagent]))
(def modal-state-atom (reagent/atom {}))
(defn get-new-calculation [with-units]
(if with-units
{:number number-obj :units []}
{:number number-obj}))
(defn fresh-answer-obj [calculation-ids with-units]
{:calculations (reduce (fn [res id] (assoc res id (get-new-calculation with-units))) {} calculation-ids)
:answer-scheme {:type "calculation" :version answer-scheme-version}})
(defn calculation-renderer [{:keys [exercise calculation path result answer do-render modal-state-atom]}]
(let [id (:id calculation)
......@@ -63,47 +92,70 @@
[:span.icon [:i.fas.fa-edit]]
[:span "Einheit"]]])]])]]))
(defn calculations [{:keys [exercise result answer change-fn]}]
(let [calculations (:calculations exercise)]
(defn calculations [{:keys [exercise-core result answer change-fn]}]
(let [calculations (:calculations exercise-core)]
(if (pos? (count calculations))
[:<>
(doall (for [calculation calculations]
^{:key (:id calculation)}
[:div.my-4
[calculation-renderer {:exercise exercise
:calculation calculation
:result result
:answer answer
:change-fn change-fn
:do-render true}]]))]
(map (fn [calculation]
^{:key (:id calculation)}
[:div.my-4
[calculation-renderer {:exercise exercise-core
:calculation calculation
:path [:calculations (:id calculation)]
:result result
:answer (:calculations answer)
:change-fn change-fn
:do-render true
:modal-state-atom modal-state-atom}]])
calculations)]
[:span "No calculations available"])))
(defn print-calculation-exercise [{:keys [answer exercise change-fn result]}]
[:div.card {:class
(cond
(and (:points result) (:points-max result)
(== (:points result) (:points-max result))) "has-background-success"
(and (:points result) (:points-max result)
(< 0 (:points result) (:points-max result))) "has-background-warning"
(and (:points result) (<= (:points result) 0)) "has-background-danger"
:else "")}
[:div.card-content
[:span.card-title [:b (:title exercise)]]
[default-task-description (:task-description exercise)]
[:p [:b "Antwort:"]]
[calculations {:exercise exercise
:result result
:answer answer
:change-fn change-fn}]
(when result
[:span.secondary-content [:b (str (:points result) "P")]])]])
[:<>
[ui/card
{:title (:title exercise)
:class (get-result-color-class result)}
[:div
{:class (when result "has-background-light")}
[default-task-description (get-in exercise [:core :task-description])]
[:h5 [:b "Antwort"]]
[calculations {:exercise-core (:core exercise)
:result result
:answer answer
:change-fn change-fn}]
(when result
[:span.is-pulled-right [point-result result]])]]
[ui/modal-panel
{:state (reagent/cursor modal-state-atom [:modal-visible?])
:title [modal-renderers/editor-heading {:text "Größe editieren"
:symbol-data (:symbol-data @modal-state-atom)}]
:footer [:a.button
{:on-click (fn []
(swap! modal-state-atom update :modal-visible? not))}
"Schließen"]}
[:div
[modal-editor {:subject answer
:change-fn change-fn
:visible? (:modal-visible? @modal-state-atom)
:path (:path @modal-state-atom)
:type (:type @modal-state-atom)
:with-oom (:with-oom @modal-state-atom)
:with-units (:with-units @modal-state-atom)
:collats-fn (:collats-fn @modal-state-atom)}]]]])
(defn exercise-renderer [exercise {:keys [result answer on-change]}]
[print-calculation-exercise
{:answer answer
:exercise (:core exercise)
:change-fn (partial calc-change-fn on-change answer)
:result result}])
(let [calculation-ids (reduce (fn [res item] (conj res (:id item)))
#{} (get-in exercise [:core :calculations]))
ans-obj (or answer (fresh-answer-obj calculation-ids (get-in exercise [:core :with-units])))]
(if-let [migrated-answer (migrate-if-necessary ans-obj :answer-scheme "calculation" answer-scheme-version
(partial migrate-answer calculation-ids))]
[print-calculation-exercise
{:answer migrated-answer
:exercise exercise
:change-fn (partial calc-change-fn on-change migrated-answer)
:result result}]
[unmigrateable-answer])))
(defmethod ce-ex/render-exercise :calculation [_ _]
(fn [exercise data]
......
(ns lernmeister.components.exercise-types.multistep-calculation.views.show
(:require [lernmeister.components.common
:refer
[get-result-color-class latex-span]]
[get-result-color-class latex-span unmigrateable-answer]]
[lernmeister.components.content-elements.exercise.show :as ce-ex]
[lernmeister.components.content-elements.exercise.task-description
:refer
......@@ -251,7 +251,7 @@
:change-fn (partial calc-change-fn on-change migrated-answer)
:phrase-fn (partial get-phrase exercise [:core :phrasing] names-map)
:result result}]
[:div.is-block.has-text-centered.has-text-danger "Unmigrateble answer"]))
[unmigrateable-answer]))
(defmethod ce-ex/render-exercise :multistep-calculation [_ _]
(fn [exercise data]
......
(ns lernmeister.components.exercise-types.calculation.answer-migrater-test
(:require [lernmeister.components.exercise-types.calculation.migrate-answer
:refer [answer-scheme-version migrate-answer]]
[clojure.data :refer [diff]]
#?(:cljs
[cljs.test :refer-macros [deftest testing is]]
:clj
[clojure.test :refer [deftest testing is]])))
(def calc-ids #{"e1" "e2" "e3"})
(def old-scheme {"e1" {:number "3"
:order-of-magnitude "-3"
:units [{:prefix ""
:unit "g"
:expo "1"}]}
"e2" {:number "-3"
:units []}})
(def new-scheme {:calculations {"e1" {:number {:significand "3"
:order-of-magnitude "-3"}
:units [{:prefix ""
:unit "g"
:expo "1"}]}
"e2" {:number {:significand "-3"
:order-of-magnitude "0"}
:units []}
"e3" {:number {:significand nil
:order-of-magnitude "0"}}}
:answer-scheme {:type "calculation"
:version answer-scheme-version}})
(deftest multistep-calculation-answer-migrate-1
(testing "checker - multistep-calculation-migrate-1"
(let [differences (diff (migrate-answer calc-ids old-scheme) new-scheme)]
(is (= [nil nil] [(first differences) (second differences)])))))
(deftest multistep-calculation-answer-migrate-2
(testing "checker - multistep-calculation-migrate-2"
(let [differences (diff (migrate-answer calc-ids new-scheme) new-scheme)]
(is (= [nil nil] [(first differences) (second differences)])))))
(ns lernmeister.components.exercise-types.calculation.migrater-test
(:require [lernmeister.components.exercise-types.calculation.migrate
:refer [exercise-scheme-version migrate-exercise]]
[clojure.data :refer [diff]]
#?(:cljs
[cljs.test :refer-macros [deftest testing is]]
:clj
[clojure.test :refer [deftest testing is]])))
(def old-scheme-full
{:title "Zweifache Aufgabe"
:id "20180605T153201-atom54218"
:type :exercise
:core
{:shuffled true
:with-oom true
:with-units true
:type :calculation
:task-description
[{:id "ce2018-06-05T13-28-36-G__55"
:value "Zweifache Aufgabe"
:type :heading
:section "6"}
{:id "ce2018-06-05T13-28-58-G__60"
:value
"<p>Berechnen Sie 1. etwas mit dem Ergebnis $§5 \\, m \\Omega§$ ... und 2. mit dem Ergebnis $§3 \\cdot 10^{1} \\, \\mu g^{2}§$!</p>"
:type :section}]
:calculations
[{:id "ae2018-06-05T13-30-36-G__69"
:name "calc_1"
:number "5"
:error-relative "2"
:error nil
:correct-points 2
:incorrect-points 0}
{:id "ae2018-06-05T13-31-29-G__79"
:name "calc_2"
:number "3"
:order-of-magnitude "1"
:units [{:prefix "µ", :unit "g", :expo "2"}]
:correct-points 2
:incorrect-points 0}]}})
(def new-scheme-full
{:title "Zweifache Aufgabe"
:id "20180605T153201-atom54218"
:type :exercise
:core
{:shuffled true
:with-oom true
:with-units true
:type :calculation
:task-description
[{:id "ce2018-06-05T13-28-36-G__55"
:value "Zweifache Aufgabe"
:type :heading
:section "6"}
{:id "ce2018-06-05T13-28-58-G__60"
:value
"<p>Berechnen Sie 1. etwas mit dem Ergebnis $§5 \\, m \\Omega§$ ... und 2. mit dem Ergebnis $§3 \\cdot 10^{1} \\, \\mu g^{2}§$!</p>"
:type :section}]
:calculations
[{:id "ae2018-06-05T13-30-36-G__69"
:name "calc_1"
:number {:significand "5" :order-of-magnitude "0"}
:units []
:error-relative "2"
:correct-points 2
:incorrect-points 0}
{:id "ae2018-06-05T13-31-29-G__79"
:name "calc_2"
:number {:significand "3" :order-of-magnitude "1"}
:units [{:prefix "µ", :unit "g", :expo "2"}]
:error-relative "1"
:correct-points 2
:incorrect-points 0}]}
:authoring
{:migration-status :current}
:exercise-scheme
{:type "calculation" :version exercise-scheme-version}})
(def old-scheme-minimal
{:title "Zweifache Aufgabe"
:id "20180605T153201-atom54218"
:type :exercise
:core
{:shuffled true
:with-oom false
:with-units false
:type :calculation
:task-description
[{:id "ce2018-06-05T13-28-36-G__55"
:value "Zweifache Aufgabe"
:type :heading
:section "6"}
{:id "ce2018-06-05T13-28-58-G__60"
:value
"<p>Berechnen Sie 1. etwas mit dem Ergebnis $§5 \\, m \\Omega§$ ... und 2. mit dem Ergebnis $§3 \\cdot 10^{1} \\, \\mu g^{2}§$!</p>"
:type :section}]
:calculations
[{:id "ae2018-06-05T13-30-36-G__69"
:name "calc_1"
:number "5"
:error-relative "2"
:error nil
:correct-points 2
:incorrect-points 0}
{:id "ae2018-06-05T13-31-29-G__79"
:name "calc_2"
:number "3"
:order-of-magnitude "1"
:units [{:prefix "µ", :unit "g", :expo "2"}]
:correct-points 2
:incorrect-points 0}]}})
(def new-scheme-minimal
{:title "Zweifache Aufgabe"
:id "20180605T153201-atom54218"
:type :exercise
:core
{:shuffled true
:with-oom false
:with-units false
:type :calculation
:task-description
[{:id "ce2018-06-05T13-28-36-G__55"
:value "Zweifache Aufgabe"
:type :heading
:section "6"}
{:id "ce2018-06-05T13-28-58-G__60"
:value
"<p>Berechnen Sie 1. etwas mit dem Ergebnis $§5 \\, m \\Omega§$ ... und 2. mit dem Ergebnis $§3 \\cdot 10^{1} \\, \\mu g^{2}§$!</p>"
:type :section}]
:calculations
[{:id "ae2018-06-05T13-30-36-G__69"
:name "calc_1"
:number {:significand "5" :order-of-magnitude "0"}
:error-relative "2"
:correct-points 2
:incorrect-points 0}
{:id "ae2018-06-05T13-31-29-G__79"
:name "calc_2"
:number {:significand "3" :order-of-magnitude "0"}
:error-relative "1"
:correct-points 2
:incorrect-points 0}]}
:authoring
{:migration-status :current}
:exercise-scheme
{:type "calculation" :version exercise-scheme-version}})
(deftest multistep-calculation-migrate-1
(testing "checker - multistep-calculation-migrate-1"
(let [differences (diff (migrate-exercise old-scheme-full) new-scheme-full)]
(is (= [nil nil] [(first differences) (second differences)])))))
(deftest multistep-calculation-migrate-2
(testing "checker - multistep-calculation-migrate-2"
(let [differences (diff (migrate-exercise new-scheme-full) new-scheme-full)]
(is (= [nil nil] [(first differences) (second differences)])))))