From 1dae19433f6a9d7570a63ba26b4ed0b66c4a9eab Mon Sep 17 00:00:00 2001 From: Oliver Foster Date: Thu, 16 Apr 2026 18:04:32 +0100 Subject: [PATCH 1/6] Refactor --- js/LifecycleSet.js | 10 +++ js/LifecycleUpdateJournal.js | 141 +++--------------------------- js/ScoringSet.js | 6 +- js/ScoringUpdateJournal.js | 109 +++++++++++++++++++++++ js/TotalLifecycleUpdateJournal.js | 71 --------------- js/TotalSets.js | 4 +- js/TotalSetsUpdateJournal.js | 71 +++++++++++++++ js/adapt-contrib-scoring.js | 11 ++- 8 files changed, 217 insertions(+), 206 deletions(-) create mode 100644 js/ScoringUpdateJournal.js delete mode 100644 js/TotalLifecycleUpdateJournal.js create mode 100644 js/TotalSetsUpdateJournal.js diff --git a/js/LifecycleSet.js b/js/LifecycleSet.js index 61fa126..d7f08b8 100644 --- a/js/LifecycleSet.js +++ b/js/LifecycleSet.js @@ -2,6 +2,7 @@ import Adapt from 'core/js/adapt'; import Logging from 'core/js/logging'; import State from './State'; import IntersectionSet from './IntersectionSet'; +import LifecycleUpdateJournal from './LifecycleUpdateJournal'; /** * Set at which intersections and queries can be performed. @@ -20,6 +21,15 @@ export default class LifecycleSet extends IntersectionSet { return (this._state = this._state || new State({ set: this })); } + /** + * The journal for recording the updates to the set. + * @returns {LifecycleUpdateJournal} + */ + get journal() { + if (this.isIntersectedSet) return; + return (this._journal = this._journal || new LifecycleUpdateJournal({ set: this })); + } + /** * Signifies if onRestore returned true/false. * @returns {boolean} diff --git a/js/LifecycleUpdateJournal.js b/js/LifecycleUpdateJournal.js index cdd2a26..6120050 100644 --- a/js/LifecycleUpdateJournal.js +++ b/js/LifecycleUpdateJournal.js @@ -1,13 +1,7 @@ -import Logging from 'core/js/logging'; -import { - filterModelsByIntersectingModels, - isModelAvailableInHierarchy -} from './utils/models'; -import _ from 'underscore'; /** @typedef {import("./ScoringSet").default} ScoringSet */ /** - * A journal for recording the lifecycle updates to a set. + * A journal for recording the models and sets that triggered set updates in the current lifecycle. */ export default class LifecycleUpdateJournal { @@ -17,140 +11,33 @@ export default class LifecycleUpdateJournal { */ constructor({ set } = {}) { this.set = set; - this._pendingUpdateModels = new Set(); - this._pendingUpdateSets = new Set(); - this._pendingUpdateModifiers = []; + this.pendingUpdateModels = new Set(); + this.pendingUpdateSets = new Set(); } /** - * Add the model and intersected sets having triggered this set's next update. - * @param {Backbone.Model} model - * @param {ScoringSet[]} [sets] + * Add the model and intersecting sets which caused the set update to be triggered. + * @param {Backbone.Model} model Source model + * @param {ScoringSet[]} [sets] Intersecting sets */ addPendingUpdate(model, sets) { - this._pendingUpdateModels.add(model); - sets?.forEach(set => this._pendingUpdateSets.add(set)); + this.pendingUpdateModels.add(model); + sets?.forEach(set => this.pendingUpdateSets.add(set)); } /** - * Update the journal for the models pending updates. + * Update lifecycle phase has ended */ update() { - this._pendingUpdateModels.forEach(model => this._addUpdateModifiers(model)); - this._write(); - this._pendingUpdateModels.clear(); - this._pendingUpdateSets.clear(); - this._pendingUpdateModifiers = []; + this.clear(); } /** - * Returns the minimum score for the specified model. - * @param {Backbone.Model} model - * @returns {number} + * Clear for next pending updates. */ - getMinScoreByModel(model) { - if (!this.set.questions.includes(model)) return 0; - return model.minScore; - } - - /** - * Returns the maximum score for the specified model. - * @param {Backbone.Model} model - * @returns {number} - */ - getMaxScoreByModel(model) { - if (!this.set.questions.includes(model)) return 0; - return model.maxScore; - } - - /** - * Returns the score for the specified model. - * @param {Backbone.Model} model - * @returns {number} - */ - getScoreByModel(model) { - if (!this.set.questions.includes(model)) return 0; - return model.score; - } - - /** - * Returns the set data to log. - * @returns {object} - */ - get setData() { - return { - id: this.set.id, - type: this.set.type, - minScore: this.set.minScore, - maxScore: this.set.maxScore, - score: this.set.score, - scaledScore: this.set.scaledScore, - isComplete: this.set.isComplete, - isPassed: this.set.isPassed - }; - } - - /** - * Add modifier details for how the set has been updated. - * @protected - * @param {Backbone.Model} model - */ - _addUpdateModifiers(model) { - const isAvailabilityChange = Object.hasOwn(model.changed, '_isAvailable'); - if (isAvailabilityChange) { - this._addAvailabilityModifiers(model); - return; - } - this._addCompletionModifiers(model); - } - - /** - * Add modifier details for how the set has been updated by availability changes. - * @protected - * @param {Backbone.Model} model - */ - _addAvailabilityModifiers(model) { - const models = model.hasManagedChildren ? model.getChildren() : [model]; - const questions = filterModelsByIntersectingModels(this.set.questions, models); - questions.forEach(questionModel => { - const isAvailable = isModelAvailableInHierarchy(questionModel); - const minScore = this.getMinScoreByModel(questionModel); - const maxScore = this.getMaxScoreByModel(questionModel); - const score = this.getScoreByModel(questionModel); - const data = { - modelId: questionModel.get('_id'), - minScore: isAvailable ? minScore : -minScore, - maxScore: isAvailable ? maxScore : -maxScore - }; - if (questionModel.get('_isSubmitted')) data.score = isAvailable ? score : -score; - this._pendingUpdateModifiers.push(data); - }); - } - - /** - * Add modifier details for how the set has been updated by completion changes. - * @protected - * @param {Backbone.Model} model - */ - _addCompletionModifiers(model) { - this._pendingUpdateModifiers.push({ - modelId: model.get('_id'), - score: this.getScoreByModel(model) - }); - } - - /** - * Write the current state to the log if it has changed since the last update. - * @protected - */ - _write() { - const setData = this.setData; - const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); - if (!hasSetDataChanged) return; - const data = { ...setData }; - if (this._pendingUpdateModifiers.length) data.modifiers = this._pendingUpdateModifiers; - Logging.info('scoring:update', JSON.stringify(data)); - this._lastSetData = setData; + clear() { + this.pendingUpdateModels.clear(); + this.pendingUpdateSets.clear(); } } diff --git a/js/ScoringSet.js b/js/ScoringSet.js index 2dc0c64..a23fb86 100644 --- a/js/ScoringSet.js +++ b/js/ScoringSet.js @@ -2,7 +2,6 @@ import Adapt from 'core/js/adapt'; import Logging from 'core/js/logging'; import LifecycleSet from './LifecycleSet'; import Objective from './Objective'; -import LifecycleUpdateJournal from './LifecycleUpdateJournal'; import { getScaledScoreFromMinMax } from './utils/scoring'; @@ -12,6 +11,7 @@ import { import { hasHashChanged } from './utils/hash'; +import ScoringUpdateJournal from './ScoringUpdateJournal'; /** * The class provides an abstract that describes a set of models which can be extended with custom @@ -243,11 +243,11 @@ export default class ScoringSet extends LifecycleSet { /** * The journal for recording the updates to the set. - * @returns {LifecycleUpdateJournal} + * @returns {ScoringUpdateJournal} */ get journal() { if (this.isIntersectedSet) return; - return (this._journal = this._journal || new LifecycleUpdateJournal({ set: this })); + return (this._journal = this._journal || new ScoringUpdateJournal({ set: this })); } /** diff --git a/js/ScoringUpdateJournal.js b/js/ScoringUpdateJournal.js new file mode 100644 index 0000000..2f28e95 --- /dev/null +++ b/js/ScoringUpdateJournal.js @@ -0,0 +1,109 @@ +import Logging from 'core/js/logging'; +import LifecycleUpdateJournal from './LifecycleUpdateJournal'; +import { + filterModelsByIntersectingModels, + isModelAvailableInHierarchy +} from './utils/models'; +import _ from 'underscore'; +/** @typedef {import("./ScoringSet").default} ScoringSet */ + +/** + * A journal for recording the lifecycle updates to a set. + */ +export default class ScoringUpdateJournal extends LifecycleUpdateJournal { + + /** + * Log the updates to the set based on the pending update models and sets, then clear the pending updates. + */ + update() { + const sources = []; + for (const model of this.pendingUpdateModels) { + const isAvailabilityChange = Object.hasOwn(model.changed, '_isAvailable'); + if (isAvailabilityChange) { + // If the parent availability has changed, we log the score + // changes for all current child questions in the set. + const models = model.hasManagedChildren ? model.getChildren() : [model]; + const modelSetQuestions = filterModelsByIntersectingModels(this.set.questions, models); + modelSetQuestions.forEach(questionModel => { + const isAvailable = isModelAvailableInHierarchy(questionModel); + const minScore = this.getMinScoreByModel(questionModel); + const maxScore = this.getMaxScoreByModel(questionModel); + const score = this.getScoreByModel(questionModel); + // The score changes are logged as negative values + // if the question is now unavailable. + const data = { + modelId: questionModel.get('_id'), + minScore: isAvailable ? minScore : -minScore, + maxScore: isAvailable ? maxScore : -maxScore + }; + if (questionModel.get('_isSubmitted')) data.score = isAvailable ? score : -score; + sources.push(data); + }); + continue; + } + sources.push({ + modelId: model.get('_id'), + score: this.getScoreByModel(model) + }); + } + const setData = this.setData; + const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); + if (hasSetDataChanged) { + const data = { ...setData }; + if (sources.length) { + data.sources = sources; + } + Logging.info('scoring:update', JSON.stringify(data)); + this._lastSetData = setData; + } + this.clear(); + } + + /** + * Returns the minimum score for the specified model. + * @param {Backbone.Model} model + * @returns {number} + */ + getMinScoreByModel(model) { + if (!this.set.questions.includes(model)) return 0; + return model.minScore; + } + + /** + * Returns the maximum score for the specified model. + * @param {Backbone.Model} model + * @returns {number} + */ + getMaxScoreByModel(model) { + if (!this.set.questions.includes(model)) return 0; + return model.maxScore; + } + + /** + * Returns the score for the specified model. + * @param {Backbone.Model} model + * @returns {number} + */ + getScoreByModel(model) { + if (!this.set.questions.includes(model)) return 0; + return model.score; + } + + /** + * Returns the set data to log. + * @returns {object} + */ + get setData() { + return { + id: this.set.id, + type: this.set.type, + minScore: this.set.minScore, + maxScore: this.set.maxScore, + score: this.set.score, + scaledScore: this.set.scaledScore, + isComplete: this.set.isComplete, + isPassed: this.set.isPassed + }; + } + +} diff --git a/js/TotalLifecycleUpdateJournal.js b/js/TotalLifecycleUpdateJournal.js deleted file mode 100644 index e4b6fec..0000000 --- a/js/TotalLifecycleUpdateJournal.js +++ /dev/null @@ -1,71 +0,0 @@ -import LifecycleUpdateJournal from './LifecycleUpdateJournal'; -import { - filterModelsByIntersectingModels, - isModelAvailableInHierarchy -} from './utils/models'; -import { - sum -} from './utils/math'; -import { - getSubsetsByQuery -} from './utils/query'; -/** @typedef {import("./TotalSets").default} TotalSets */ -/** @typedef {import("./ScoringSet").default} ScoringSet */ - -export default class TotalLifecycleUpdateJournal extends LifecycleUpdateJournal { - - /** - * @override - * Using intersection queries doesn't log modifier scores correctly when models become unavailable. - * Intersection queries only include available models - retrieve scores from other journals accordingly. - */ - _addAvailabilityModifiers(model) { - const models = model.hasManagedChildren ? model.getChildren() : [model]; - const sets = this.set.scoringSets.filter(set => this._pendingUpdateSets.has(set)); - sets.forEach(set => { - const questions = filterModelsByIntersectingModels(set.questions, models); - const isAvailable = isModelAvailableInHierarchy(model); - const journal = set.journal; - const minScore = sum(questions, questionModel => journal.getMinScoreByModel(questionModel)); - const maxScore = sum(questions, questionModel => journal.getMaxScoreByModel(questionModel)); - const score = sum(questions, questionModel => journal.getScoreByModel(questionModel)); - const data = { - id: set.id, - minScore: isAvailable ? minScore : -minScore, - maxScore: isAvailable ? maxScore : -maxScore - }; - if (score !== 0) data.score = isAvailable ? score : -score; - this._pendingUpdateModifiers.push(data); - }); - } - - /** @override */ - _addCompletionModifiers(model) { - const sets = this._getScoringSetsByModel(model); - sets.forEach(set => { - this._pendingUpdateModifiers.push({ - id: set.id, - score: set.score - }); - }); - } - - /** - * Returns the intersected `TotalSets` of the model. - * @param {Backbone.Model} model - * @returns {TotalSets} - */ - _getTotalSetsByModelQuery(model) { - return getSubsetsByQuery(`#${model.get('_id')} ${this.set.type}`)[0]; - } - - /** - * Returns the intersected scoring sets of the model. - * @param {Backbone.Model} model - * @returns {ScoringSet[]} - */ - _getScoringSetsByModel(model) { - return this._getTotalSetsByModelQuery(model)?.scoringSets ?? []; - } - -} diff --git a/js/TotalSets.js b/js/TotalSets.js index dc69ad6..734adf7 100644 --- a/js/TotalSets.js +++ b/js/TotalSets.js @@ -1,7 +1,7 @@ import Adapt from 'core/js/adapt'; import Passmark from './Passmark'; import ScoringSet from './ScoringSet'; -import TotalLifecycleUpdateJournal from './TotalLifecycleUpdateJournal'; +import TotalSetsUpdateJournal from './TotalSetsUpdateJournal'; import { createIntersectedSet } from './utils/intersection'; @@ -189,7 +189,7 @@ export default class TotalSets extends ScoringSet { /** @override */ get journal() { if (this.isIntersectedSet) return; - return (this._journal = this._journal || new TotalLifecycleUpdateJournal({ set: this })); + return (this._journal = this._journal || new TotalSetsUpdateJournal({ set: this })); } } diff --git a/js/TotalSetsUpdateJournal.js b/js/TotalSetsUpdateJournal.js new file mode 100644 index 0000000..ce9f062 --- /dev/null +++ b/js/TotalSetsUpdateJournal.js @@ -0,0 +1,71 @@ +import Logging from 'core/js/logging'; +import LifecycleUpdateJournal from './ScoringUpdateJournal'; +import { + filterModelsByIntersectingModels, + isModelAvailableInHierarchy +} from './utils/models'; +import { + sum +} from './utils/math'; +import { + getSubsetsByQuery +} from './utils/query'; +/** @typedef {import("./TotalSets").default} TotalSets */ +/** @typedef {import("./ScoringSet").default} ScoringSet */ + +/** + * A journal for recording the models and sets that triggered set updates in the current lifecycle. + */ +export default class TotalSetsUpdateJournal extends LifecycleUpdateJournal { + + /** + * Log the updates to the set based on the pending update models and sets, then clear the pending updates. + */ + update() { + const sources = []; + for (const model of this.pendingUpdateModels) { + const isAvailabilityChange = Object.hasOwn(model.changed, '_isAvailable'); + if (isAvailabilityChange) { + // If the parent availability has changed, we log the score + // changes for all current child questions in the set. + const models = model.hasManagedChildren ? model.getChildren() : [model]; + const relevantSets = this.set.scoringSets.filter(set => this.pendingUpdateSets.has(set)); + relevantSets.forEach(set => { + const questions = filterModelsByIntersectingModels(set.questions, models); + const isAvailable = isModelAvailableInHierarchy(model); + const journal = set.journal; + const minScore = sum(questions, questionModel => journal.getMinScoreByModel(questionModel)); + const maxScore = sum(questions, questionModel => journal.getMaxScoreByModel(questionModel)); + const score = sum(questions, questionModel => journal.getScoreByModel(questionModel)); + const data = { + id: set.id, + minScore: isAvailable ? minScore : -minScore, + maxScore: isAvailable ? maxScore : -maxScore + }; + if (score !== 0) data.score = isAvailable ? score : -score; + sources.push(data); + }); + continue; + } + const modelIntersectedTotalSets = getSubsetsByQuery(`#${model.get('_id')} ${this.set.type}`)[0]?.scoringSets ?? []; + modelIntersectedTotalSets.forEach(set => { + sources.push({ + id: set.id, + score: set.score + }); + }); + } + const setData = this.setData; + const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); + if (hasSetDataChanged) { + const data = { ...setData }; + if (sources.length) { + data.sources = sources; + } + Logging.info('scoring:update', JSON.stringify(data)); + this._lastSetData = setData; + } + this.clear(); + } + +} diff --git a/js/adapt-contrib-scoring.js b/js/adapt-contrib-scoring.js index 0aa72eb..bcf18ab 100644 --- a/js/adapt-contrib-scoring.js +++ b/js/adapt-contrib-scoring.js @@ -24,6 +24,8 @@ import LifecycleSet from './LifecycleSet'; import ScoringSet from './ScoringSet'; import Objective from './Objective'; import LifecycleUpdateJournal from './LifecycleUpdateJournal'; +import ScoringUpdateJournal from './ScoringUpdateJournal'; +import TotalSetsUpdateJournal from './TotalSetsUpdateJournal'; import State from './State'; import StateModels from './StateModels'; import StateSetModelChildren from './StateSetModelChildren'; @@ -40,12 +42,15 @@ export { AdaptModelSet, IntersectionSet, LifecycleSet, - ScoringSet, - Objective, LifecycleUpdateJournal, + Objective, + ScoringSet, + ScoringUpdateJournal, State, + StateModels, StateSetModelChildren, - StateModels + TotalSets, + TotalSetsUpdateJournal }; /** From 448409896b5c0c561e7ea19c08880738700043dd Mon Sep 17 00:00:00 2001 From: Oliver Foster Date: Thu, 16 Apr 2026 18:10:42 +0100 Subject: [PATCH 2/6] Corrected comment --- js/TotalSetsUpdateJournal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/TotalSetsUpdateJournal.js b/js/TotalSetsUpdateJournal.js index ce9f062..8e7f981 100644 --- a/js/TotalSetsUpdateJournal.js +++ b/js/TotalSetsUpdateJournal.js @@ -27,7 +27,7 @@ export default class TotalSetsUpdateJournal extends LifecycleUpdateJournal { const isAvailabilityChange = Object.hasOwn(model.changed, '_isAvailable'); if (isAvailabilityChange) { // If the parent availability has changed, we log the score - // changes for all current child questions in the set. + // changes for all changed sets and their questions. const models = model.hasManagedChildren ? model.getChildren() : [model]; const relevantSets = this.set.scoringSets.filter(set => this.pendingUpdateSets.has(set)); relevantSets.forEach(set => { From 12525808d097e7936cb07ea655bab33266f5742c Mon Sep 17 00:00:00 2001 From: Oliver Foster Date: Thu, 16 Apr 2026 18:12:26 +0100 Subject: [PATCH 3/6] Fixed comment --- js/ScoringUpdateJournal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ScoringUpdateJournal.js b/js/ScoringUpdateJournal.js index 2f28e95..5a38d35 100644 --- a/js/ScoringUpdateJournal.js +++ b/js/ScoringUpdateJournal.js @@ -8,7 +8,7 @@ import _ from 'underscore'; /** @typedef {import("./ScoringSet").default} ScoringSet */ /** - * A journal for recording the lifecycle updates to a set. + * A journal for recording the models and sets that triggered set updates in the current lifecycle. */ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { From 3eb60411891e441f2035e35ddbbee393ae3c8ee3 Mon Sep 17 00:00:00 2001 From: Oliver Foster Date: Thu, 16 Apr 2026 18:50:39 +0100 Subject: [PATCH 4/6] Separate update into sourceData and log --- js/ScoringUpdateJournal.js | 69 +++++++++++++++++++++--------------- js/TotalSetsUpdateJournal.js | 28 ++++++--------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/js/ScoringUpdateJournal.js b/js/ScoringUpdateJournal.js index 5a38d35..245763c 100644 --- a/js/ScoringUpdateJournal.js +++ b/js/ScoringUpdateJournal.js @@ -1,10 +1,10 @@ -import Logging from 'core/js/logging'; import LifecycleUpdateJournal from './LifecycleUpdateJournal'; import { filterModelsByIntersectingModels, isModelAvailableInHierarchy } from './utils/models'; import _ from 'underscore'; +import Logging from 'core/js/logging'; /** @typedef {import("./ScoringSet").default} ScoringSet */ /** @@ -16,6 +16,44 @@ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { * Log the updates to the set based on the pending update models and sets, then clear the pending updates. */ update() { + this.log(); + this.clear(); + } + + /** + * Log the updates to the set based on the pending update models and sets, then clear the pending updates. + */ + log () { + const setData = this.setData; + const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); + if (!hasSetDataChanged) return; + const data = { ...setData }; + const sources = this.sourceData; + if (sources.length) { + data.sources = sources; + } + Logging.info('scoring:update', JSON.stringify(data)); + this._lastSetData = setData; + } + + /** + * Returns the set data to log. + * @returns {object} + */ + get setData() { + return { + id: this.set.id, + type: this.set.type, + minScore: this.set.minScore, + maxScore: this.set.maxScore, + score: this.set.score, + scaledScore: this.set.scaledScore, + isComplete: this.set.isComplete, + isPassed: this.set.isPassed + }; + } + + get sourceData() { const sources = []; for (const model of this.pendingUpdateModels) { const isAvailabilityChange = Object.hasOwn(model.changed, '_isAvailable'); @@ -46,17 +84,7 @@ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { score: this.getScoreByModel(model) }); } - const setData = this.setData; - const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); - if (hasSetDataChanged) { - const data = { ...setData }; - if (sources.length) { - data.sources = sources; - } - Logging.info('scoring:update', JSON.stringify(data)); - this._lastSetData = setData; - } - this.clear(); + return sources; } /** @@ -89,21 +117,4 @@ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { return model.score; } - /** - * Returns the set data to log. - * @returns {object} - */ - get setData() { - return { - id: this.set.id, - type: this.set.type, - minScore: this.set.minScore, - maxScore: this.set.maxScore, - score: this.set.score, - scaledScore: this.set.scaledScore, - isComplete: this.set.isComplete, - isPassed: this.set.isPassed - }; - } - } diff --git a/js/TotalSetsUpdateJournal.js b/js/TotalSetsUpdateJournal.js index 8e7f981..202c25f 100644 --- a/js/TotalSetsUpdateJournal.js +++ b/js/TotalSetsUpdateJournal.js @@ -1,5 +1,4 @@ -import Logging from 'core/js/logging'; -import LifecycleUpdateJournal from './ScoringUpdateJournal'; +import ScoringUpdateJournal from './ScoringUpdateJournal'; import { filterModelsByIntersectingModels, isModelAvailableInHierarchy @@ -16,12 +15,9 @@ import { /** * A journal for recording the models and sets that triggered set updates in the current lifecycle. */ -export default class TotalSetsUpdateJournal extends LifecycleUpdateJournal { +export default class TotalSetsUpdateJournal extends ScoringUpdateJournal { - /** - * Log the updates to the set based on the pending update models and sets, then clear the pending updates. - */ - update() { + get sourceData() { const sources = []; for (const model of this.pendingUpdateModels) { const isAvailabilityChange = Object.hasOwn(model.changed, '_isAvailable'); @@ -55,16 +51,14 @@ export default class TotalSetsUpdateJournal extends LifecycleUpdateJournal { }); }); } - const setData = this.setData; - const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); - if (hasSetDataChanged) { - const data = { ...setData }; - if (sources.length) { - data.sources = sources; - } - Logging.info('scoring:update', JSON.stringify(data)); - this._lastSetData = setData; - } + return sources; + } + + /** + * Log the updates to the set based on the pending update models and sets, then clear the pending updates. + */ + update() { + this.log(); this.clear(); } From d25235f6386d371ecd7d99ed8ddd5454c85d64f4 Mon Sep 17 00:00:00 2001 From: Oliver Foster Date: Thu, 16 Apr 2026 18:53:54 +0100 Subject: [PATCH 5/6] Remove unnecessary override --- js/TotalSetsUpdateJournal.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/js/TotalSetsUpdateJournal.js b/js/TotalSetsUpdateJournal.js index 202c25f..577dd36 100644 --- a/js/TotalSetsUpdateJournal.js +++ b/js/TotalSetsUpdateJournal.js @@ -54,12 +54,4 @@ export default class TotalSetsUpdateJournal extends ScoringUpdateJournal { return sources; } - /** - * Log the updates to the set based on the pending update models and sets, then clear the pending updates. - */ - update() { - this.log(); - this.clear(); - } - } From c17dc9af393db35f7edf6ec9228e8210585f9812 Mon Sep 17 00:00:00 2001 From: "AzureAD\\DanGhost" Date: Fri, 17 Apr 2026 11:02:57 +0100 Subject: [PATCH 6/6] Small changes to JSDoc references. Separated `modelId` from `setId` in `sources` data (other option is to make both use `id`, but opted to distinguish between the two for clearer context). --- js/ScoringSet.js | 2 +- js/ScoringUpdateJournal.js | 8 +++++++- js/TotalSetsUpdateJournal.js | 10 +++++++-- js/adapt-contrib-scoring.js | 40 ++++++++++++++++++------------------ 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/js/ScoringSet.js b/js/ScoringSet.js index a23fb86..cd7571d 100644 --- a/js/ScoringSet.js +++ b/js/ScoringSet.js @@ -2,6 +2,7 @@ import Adapt from 'core/js/adapt'; import Logging from 'core/js/logging'; import LifecycleSet from './LifecycleSet'; import Objective from './Objective'; +import ScoringUpdateJournal from './ScoringUpdateJournal'; import { getScaledScoreFromMinMax } from './utils/scoring'; @@ -11,7 +12,6 @@ import { import { hasHashChanged } from './utils/hash'; -import ScoringUpdateJournal from './ScoringUpdateJournal'; /** * The class provides an abstract that describes a set of models which can be extended with custom diff --git a/js/ScoringUpdateJournal.js b/js/ScoringUpdateJournal.js index 245763c..4162bfc 100644 --- a/js/ScoringUpdateJournal.js +++ b/js/ScoringUpdateJournal.js @@ -14,6 +14,7 @@ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { /** * Log the updates to the set based on the pending update models and sets, then clear the pending updates. + * @override */ update() { this.log(); @@ -23,7 +24,7 @@ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { /** * Log the updates to the set based on the pending update models and sets, then clear the pending updates. */ - log () { + log() { const setData = this.setData; const hasSetDataChanged = !(_.isEqual(this._lastSetData, setData)); if (!hasSetDataChanged) return; @@ -53,6 +54,11 @@ export default class ScoringUpdateJournal extends LifecycleUpdateJournal { }; } + /** + * Returns the pending update models score data for logging. + * For availability changes, all intersecting set questions are included with scores negated if now unavailable. + * @returns {{ modelId: string, minScore?: number, maxScore?: number, score?: number }[]} + */ get sourceData() { const sources = []; for (const model of this.pendingUpdateModels) { diff --git a/js/TotalSetsUpdateJournal.js b/js/TotalSetsUpdateJournal.js index 577dd36..f91bbce 100644 --- a/js/TotalSetsUpdateJournal.js +++ b/js/TotalSetsUpdateJournal.js @@ -17,6 +17,12 @@ import { */ export default class TotalSetsUpdateJournal extends ScoringUpdateJournal { + /** + * Returns the pending update sets score data for logging. + * For availability changes, all intersecting sets are included with scores negated if now unavailable. + * @override + * @returns {{ setId: string, minScore?: number, maxScore?: number, score?: number }[]} + */ get sourceData() { const sources = []; for (const model of this.pendingUpdateModels) { @@ -34,7 +40,7 @@ export default class TotalSetsUpdateJournal extends ScoringUpdateJournal { const maxScore = sum(questions, questionModel => journal.getMaxScoreByModel(questionModel)); const score = sum(questions, questionModel => journal.getScoreByModel(questionModel)); const data = { - id: set.id, + setId: set.id, minScore: isAvailable ? minScore : -minScore, maxScore: isAvailable ? maxScore : -maxScore }; @@ -46,7 +52,7 @@ export default class TotalSetsUpdateJournal extends ScoringUpdateJournal { const modelIntersectedTotalSets = getSubsetsByQuery(`#${model.get('_id')} ${this.set.type}`)[0]?.scoringSets ?? []; modelIntersectedTotalSets.forEach(set => { sources.push({ - id: set.id, + setId: set.id, score: set.score }); }); diff --git a/js/adapt-contrib-scoring.js b/js/adapt-contrib-scoring.js index d5a051a..880225b 100644 --- a/js/adapt-contrib-scoring.js +++ b/js/adapt-contrib-scoring.js @@ -1,5 +1,19 @@ import Adapt from 'core/js/adapt'; import data from 'core/js/data'; +import Lifecycle from './Lifecycle'; +import AdaptModelSet from './AdaptModelSet'; +import IntersectionSet from './IntersectionSet'; +import LifecycleSet from './LifecycleSet'; +import ScoringSet from './ScoringSet'; +import TotalSets from './TotalSets'; +import Passmark from './Passmark'; +import Objective from './Objective'; +import LifecycleUpdateJournal from './LifecycleUpdateJournal'; +import ScoringUpdateJournal from './ScoringUpdateJournal'; +import TotalSetsUpdateJournal from './TotalSetsUpdateJournal'; +import State from './State'; +import StateModels from './StateModels'; +import StateSetModelChildren from './StateSetModelChildren'; import { getSubsetsByQuery } from './utils/query'; @@ -16,20 +30,6 @@ import { setupBackwardCompatibility } from './compatibility'; import './helpers'; -import Lifecycle from './Lifecycle'; -import AdaptModelSet from './AdaptModelSet'; -import IntersectionSet from './IntersectionSet'; -import LifecycleSet from './LifecycleSet'; -import ScoringSet from './ScoringSet'; -import Objective from './Objective'; -import LifecycleUpdateJournal from './LifecycleUpdateJournal'; -import ScoringUpdateJournal from './ScoringUpdateJournal'; -import TotalSetsUpdateJournal from './TotalSetsUpdateJournal'; -import State from './State'; -import StateModels from './StateModels'; -import StateSetModelChildren from './StateSetModelChildren'; -import Passmark from './Passmark'; -import TotalSets from './TotalSets'; import Backbone from 'backbone'; export * from './utils/hash'; @@ -43,16 +43,16 @@ export { AdaptModelSet, IntersectionSet, LifecycleSet, - LifecycleUpdateJournal, - Objective, - Passmark, ScoringSet, + TotalSets, + Passmark, + Objective, + LifecycleUpdateJournal, ScoringUpdateJournal, + TotalSetsUpdateJournal, State, - StateModels, StateSetModelChildren, - TotalSets, - TotalSetsUpdateJournal + StateModels }; /**