diff --git a/Packages/lesiontracker/client/api/measurement.js b/Packages/lesiontracker/client/api/measurement.js new file mode 100644 index 000000000..f0e4336d9 --- /dev/null +++ b/Packages/lesiontracker/client/api/measurement.js @@ -0,0 +1,102 @@ +const hasValueAtTimepoint = timepointId => { + return measurement => { + if (measurement.timepoints[timepointId]) { + return true; + } + }; +}; + +const hasNoValueAtTimepoint = timepointId => { + return measurement => { + if (measurement.timepoints[timepointId] === undefined) { + return true; + } + }; +}; + +export const MeasurementApi = { + sortOptions: { + sort: { + lesionNumberAbsolute: 1 + } + }, + + // Return all Measurements + all(withPriors=false) { + let data = Measurements.find({}, this.sortOptions).fetch(); + + // If we don't have a prior for this Timepoint, + // this filter, we should just return all of the + // available Non-Targets measurements + if (this.priorTimepointId && withPriors === true) { + return data.filter(hasValueAtTimepoint(this.priorTimepointId)) + } + + return data; + }, + + unmarked() { + const withPriors = true; + return this.all(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));; + }, + + unmarkedTargets() { + const withPriors = true; + return this.targets(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));; + }, + + unmarkedNonTargets() { + const withPriors = true; + return this.nonTargets(withPriors).filter(hasNoValueAtTimepoint(this.currentTimepointId));; + }, + + // Return only Target Measurements + targets(withPriors=false) { + let data = Measurements.find({ + isTarget: true + }, this.sortOptions).fetch(); + + // If we don't have a prior for this Timepoint, + // this filter, we should just return all of the + // available Non-Targets measurements + if (this.priorTimepointId && withPriors === true) { + return data.filter(hasValueAtTimepoint(this.priorTimepointId)) + } + + return data; + }, + + // Return only Non-Target Measurements + nonTargets(withPriors=false) { + let data = Measurements.find({ + isTarget: false + }, this.sortOptions).fetch(); + + // If we don't have a prior for this Timepoint, + // this filter, we should just return all of the + // available Non-Targets measurements + if (this.priorTimepointId && withPriors === true) { + return data.filter(hasValueAtTimepoint(this.priorTimepointId)) + } + + return data; + }, + + // Return only New Lesions + newLesions() { + // If we are current editing a Baseline we won't have any priors, so newLesions + // should return an empty array. + if (!this.priorTimepointId) { + return []; + } + + // Find only lesions that have no value at the previous timepoint + return this.all().filter(hasNoValueAtTimepoint(this.priorTimepointId)); + }, + + firstLesion() { + return Measurements.findOne({ + target: true + }, this.sortOptions); + } +}; diff --git a/Packages/lesiontracker/lib/api/timepoint.js b/Packages/lesiontracker/client/api/timepoint.js similarity index 74% rename from Packages/lesiontracker/lib/api/timepoint.js rename to Packages/lesiontracker/client/api/timepoint.js index 21b5beb1f..5499da0ee 100644 --- a/Packages/lesiontracker/lib/api/timepoint.js +++ b/Packages/lesiontracker/client/api/timepoint.js @@ -19,12 +19,42 @@ class TimepointApi { return this.timepoints.find().fetch(); } - // Return only the current and prior timepoints - latest() { - const options = { - limit: 2 - }; - return this.timepoints.find({}, options).fetch(); + // Return only the current timepoint + current() { + return this.timepoints.findOne({ + timepointId: this.currentTimepointId + }); + } + + prior() { + const latestDate = this.current().latestDate; + return this.timepoints.findOne({ + latestDate: { + $lt: latestDate + } + }, { + sort: { + latestDate: -1 + }, + }); + } + + // Return only the current and prior Timepoints + currentAndPrior() { + let timepoints = [this.current()]; + const prior = this.prior(); + if (prior) { + timepoints.push(prior); + } + + return timepoints; + } + + // Return only the baseline timepoint + baseline() { + return this.timepoints.findOne({ + timepointType: 'baseline' + }); } // Return only the key timepoints (current, prior, nadir and baseline) diff --git a/Packages/lesiontracker/client/components/caseProgress/caseProgress.js b/Packages/lesiontracker/client/components/caseProgress/caseProgress.js index c5a630ea9..0f7303aa8 100644 --- a/Packages/lesiontracker/client/components/caseProgress/caseProgress.js +++ b/Packages/lesiontracker/client/components/caseProgress/caseProgress.js @@ -1,45 +1,37 @@ +import { MeasurementApi } from 'meteor/lesiontracker/client/api/measurement'; + Template.caseProgress.onCreated(() => { const instance = Template.instance(); instance.progressPercent = new ReactiveVar(); instance.progressText = new ReactiveVar(); instance.isLocked = new ReactiveVar(); - - if (!instance.data.currentTimepointId) { + + const current = instance.data.timepointApi.current(); + if (!current.timepointId) { console.warn('Case has no timepointId'); return; } - const currentTimepointId = instance.data.currentTimepointId; - const timepoint = Timepoints.findOne({ - timepointId: currentTimepointId - }); + instance.isLocked.set(current.isLocked); - const timepointType = timepoint.timepointType; + // Retrieve the initial number of targets left to measure at this + // follow-up. Note that this is done outside of the reactive function + // below so that new lesions don't change the initial target count. + const withPriors = true; + const totalTargets = MeasurementApi.targets(withPriors).length; - instance.isLocked.set(timepoint.isLocked); - - if (timepointType === 'baseline') { + // If we're currently reviewing a Baseline timepoint, don't do any + // progress measurement. + if (current.timepointType === 'baseline') { instance.progressPercent.set(100); } else { - // Retrieve the initial number of targets left to measure at this - // follow-up. Note that this is done outside of the reactive function - // below so that new lesions don't change the initial target count. - const totalTargets = Measurements.find({ - isTarget: true - }).count(); - // Setup a reactive function to update the progress whenever // a measurement is made instance.autorun(() => { // Obtain the number of Measurements for which the current Timepoint has // no Measurement data - let numRemainingMeasurements = 0; - Measurements.find().forEach(measurement => { - if (!measurement.timepoints[currentTimepointId]) { - numRemainingMeasurements++; - } - }); + const numRemainingMeasurements = MeasurementApi.unmarked().length; // Update the Case Progress text with the remaining measurement count instance.progressText.set(numRemainingMeasurements); @@ -67,7 +59,7 @@ Template.caseProgress.helpers({ }, progressComplete() { - let progressPercent = Template.instance().progressPercent.get(); + const progressPercent = Template.instance().progressPercent.get(); return progressPercent === 100; } }); diff --git a/Packages/lesiontracker/client/components/lesionTable/lesionTable.js b/Packages/lesiontracker/client/components/lesionTable/lesionTable.js index 1e8e1e210..cc4b6f417 100644 --- a/Packages/lesiontracker/client/components/lesionTable/lesionTable.js +++ b/Packages/lesiontracker/client/components/lesionTable/lesionTable.js @@ -1,3 +1,5 @@ +import { MeasurementApi } from 'meteor/lesiontracker/client/api/measurement'; + Template.lesionTable.onCreated(() => { const instance = Template.instance(); @@ -13,7 +15,7 @@ Template.lesionTable.onCreated(() => { if (tableLayout === 'key') { timepoints = instance.data.timepointApi.key(); } else { - timepoints = instance.data.timepointApi.latest(); + timepoints = instance.data.timepointApi.currentAndPrior(); } // Return key timepoints @@ -47,14 +49,10 @@ Session.setDefault('NewSeriesLoaded', false); Template.lesionTable.onRendered(() => { // Find the first measurement by Lesion Number - var firstLesion = Measurements.findOne({}, { - sort: { - lesionNumber: 1 - } - }); + const firstLesion = MeasurementApi.firstLesion(); // Create an object to store the ContentId inside - var templateData = { + const templateData = { contentId: Session.get('activeContentId') }; diff --git a/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js b/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js index 6fb869383..3a019ac97 100644 --- a/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js +++ b/Packages/lesiontracker/client/components/lesionTableHUD/lesionTableHUD.js @@ -1,7 +1,7 @@ Template.lesionTableHUD.onCreated(() => { const instance = Template.instance(); - instance.data.timepoints = new ReactiveVar(instance.data.timepointApi.latest()); + instance.data.timepoints = new ReactiveVar(instance.data.timepointApi.currentAndPrior()); }); Template.lesionTableHUD.onRendered(() => { diff --git a/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html b/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html index 055b6f2b3..bef8e048a 100644 --- a/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html +++ b/Packages/lesiontracker/client/components/lesionTableHeaderRow/lesionTableHeaderRow.html @@ -1,14 +1,16 @@