All files / platform/core/src/services/HangingProtocolService ProtocolEngine.js

11.76% Statements 6/51
0% Branches 0/14
15.38% Functions 2/13
12.24% Lines 6/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175          68x 68x 68x 68x 68x                                                                                                                                                                         2800x                                                                                                                                                                
import { HPMatcher } from './HPMatcher.js';
import { sortByScore } from './lib/sortByScore';
 
export default class ProtocolEngine {
  constructor(protocols, customAttributeRetrievalCallbacks) {
    this.protocols = protocols;
    this.customAttributeRetrievalCallbacks = customAttributeRetrievalCallbacks;
    this.matchedProtocols = new Map();
    this.matchedProtocolScores = {};
    this.study = undefined;
  }
 
  /** Evaluate the hanging protocol matches on the given:
   * @param props.studies is a list of studies to compare against (for priors evaluation)
   * @param props.activeStudy is the current metadata for the study to display.
   * @param props.displaySets are the list of display sets which can be modified.
   */
  run({ studies, displaySets, activeStudy }) {
    this.studies = studies;
    this.study = activeStudy || studies[0];
    this.displaySets = displaySets;
    return this.getBestProtocolMatch();
  }
 
  // /**
  // * Resets the ProtocolEngine to the best match
  // */
  // reset() {
  //   const protocol = this.getBestProtocolMatch();
 
  //   this.setHangingProtocol(protocol);
  // }
 
  /**
   * Return the best matched Protocol to the current study or set of studies
   * @returns {*}
   */
  getBestProtocolMatch() {
    // Run the matching to populate matchedProtocols Set and Map
    this.updateProtocolMatches();
 
    // Retrieve the highest scoring Protocol
    const bestMatch = this._getHighestScoringProtocol();
 
    console.log('ProtocolEngine::getBestProtocolMatch bestMatch', bestMatch);
 
    return bestMatch;
  }
 
  /**
   * Populates the MatchedProtocols Collection by running the matching procedure
   */
  updateProtocolMatches() {
    console.log('ProtocolEngine::updateProtocolMatches');
 
    // Clear all data currently in matchedProtocols
    this._clearMatchedProtocols();
 
    // TODO: handle more than one study - this.studies has the list of studies
    const matched = this.findMatchByStudy(this.study, {
      studies: this.studies,
      displaySets: this.displaySets,
    });
 
    // For each matched protocol, check if it is already in MatchedProtocols
    matched.forEach(matchedDetail => {
      const protocol = matchedDetail.protocol;
      Iif (!protocol) {
        return;
      }
 
      // If it is not already in the MatchedProtocols Collection, insert it with its score
      Iif (!this.matchedProtocols.has(protocol.id)) {
        console.log(
          'ProtocolEngine::updateProtocolMatches inserting protocol match',
          matchedDetail
        );
        this.matchedProtocols.set(protocol.id, protocol);
        this.matchedProtocolScores[protocol.id] = matchedDetail.score;
      }
    });
  }
 
  /**
   * finds the match results against the given display set or
   * study instance by testing the given rules against this, and using
   * the provided options for testing.
   *
   * @param {*} metaData to match against as primary value
   * @param {*} rules to apply
   * @param {*} options are additional values that can be used for matching
   * @returns
   */
  findMatch(metaData, rules, options) {
    return HPMatcher.match(metaData, rules, this.customAttributeRetrievalCallbacks, options);
  }
 
  /**
   * Finds the best protocols from Protocol Store, matching each protocol matching rules
   * with the given study. The best protocol are ordered by score and returned in an array
   * @param  {Object} study StudyMetadata instance object
   * @param {object} options containing additional matching data.
   * @return {Array}       Array of match objects or an empty array if no match was found
   *                       Each match object has the score of the matching and the matched
   *                       protocol
   */
  findMatchByStudy(study, options) {
    const matched = [];
 
    this.protocols.forEach(protocol => {
      // Clone the protocol's protocolMatchingRules array
      // We clone it so that we don't accidentally add the
      // numberOfPriorsReferenced rule to the Protocol itself.
      let rules = protocol.protocolMatchingRules.slice();
      Iif (!rules || !rules.length) {
        console.warn(
          'ProtocolEngine::findMatchByStudy no matching rules - specify protocolMatchingRules for',
          protocol.id
        );
        return;
      }
 
      // Run the matcher and get matching details
      const matchedDetails = this.findMatch(study, rules, options);
      const score = matchedDetails.score;
 
      // The protocol matched some rule, add it to the matched list
      Iif (score > 0) {
        matched.push({
          score,
          protocol,
        });
      }
    });
 
    // If no matches were found, select the default protocol if provided
    // if not select the first protocol in the list
    Iif (!matched.length) {
      const protocol =
        this.protocols.find(protocol => protocol.id === 'default') ?? this.protocols[0];
      console.log('No protocol matches, defaulting to', protocol);
      return [
        {
          score: 0,
          protocol,
        },
      ];
    }
 
    // Sort the matched list by score
    sortByScore(matched);
 
    console.log('ProtocolEngine::findMatchByStudy matched', matched);
 
    return matched;
  }
 
  _clearMatchedProtocols() {
    this.matchedProtocols.clear();
    this.matchedProtocolScores = {};
  }
 
  _largestKeyByValue(obj) {
    return Object.keys(obj).reduce((a, b) => (obj[a] > obj[b] ? a : b));
  }
 
  _getHighestScoringProtocol() {
    Iif (!Object.keys(this.matchedProtocolScores).length) {
      return;
    }
    const highestScoringProtocolId = this._largestKeyByValue(this.matchedProtocolScores);
    return this.matchedProtocols.get(highestScoringProtocolId);
  }
}