docs: double getShrinkScore(int poolSize, double forecast, double throughput, boolean cpuHigh, boolean lowTput) {
          double shrinkScore = 0.0;
          double shrinkMagic = 0.0;
          boolean flippedCoin = false;
          int downwardCompareSpan = 0;
  
          if (poolSize >= coreThreads + poolDecrement) {
              // compareSpan is poolSize range used for throughput comparison
              downwardCompareSpan = Math.min(compareRange * poolDecrement, poolSize - coreThreads);
  
              // if poolSize already close to coreThreads, we can skip some shrinkScore tweaks
              boolean smallPool = ((poolSize - coreThreads) <= downwardCompareSpan);
  
              // average the probabilityGreaterThan results for all valid (not too old or out-of-range)
              // throughput data for compareRange smaller poolSizes ... discard invalid data found enroute
              Integer shrinkKey = threadStats.lowerKey(poolSize);
              Integer priorKey = Integer.valueOf(poolSize);
              int smallerPools = 0;
              int pruneLimit = Math.max(coreThreads, hangBufferPoolSize);
              while (true) {
                  // stop if we run out of data
                  if (shrinkKey == null)
                      break;
                  priorKey = shrinkKey;
                  shrinkKey = threadStats.lowerKey(shrinkKey);
                  int distance = 0;
                  boolean pruned = false;
                  boolean inHangBuffer = true;
                  // discard invalid data (old or out-of-range) found in comparison stats
                  ThroughputDistribution priorStats = threadStats.get(priorKey);
                  distance = poolSize - priorKey.intValue();
                  if (priorKey > pruneLimit) {
                      inHangBuffer = false;
                      if (pruneData(priorStats, forecast)) {
                          threadStats.remove(priorKey);
                          pruned = true;
                      }
                  }
                  if (!pruned) {
                      // found a valid smaller poolSize, so include that data in the shrinkScore
                      smallerPools++;
                      shrinkScore += priorStats.getProbabilityGreaterThan(forecast);
                      if (inHangBuffer) {
                          // stop when we get compareRange datapoints
                          if (smallerPools >= compareRange)
                              break;
                      } else {
                          // stop if reached/passed compareSpan
                          if (distance >= downwardCompareSpan)
                              break;
                      }
                  }
              }
  
              // if we didn't find compareRange datapoints, flip a coin
              if ((smallerPools < compareRange) && (!smallPool)) {
                  shrinkScore += (flipCoin()) ? 0.7 : 0.0;
                  smallerPools++;
                  flippedCoin = true;
              }
              // average the aggregated shrinkScore
              shrinkScore /= smallerPools;
  
              if (consecutiveQueueEmptyCount > 0) {
                  // Unless we reduced the pool size and tput went down, add the shrink magic
                  if (!(lastAction == LastAction.SHRINK && throughput < previousThroughput)) {
                      // Add a little extra weight to shrinking the pool if we keep seeing an empty queue
                      shrinkMagic = Math.min(consecutiveQueueEmptyCount * EMPTY_QUEUE_SHRINK_MAGIC_PER_INTERVAL, 0.5);
                      shrinkScore += shrinkMagic;
                      if (tc.isEventEnabled()) {
                          Tr.event(tc, "shrinkMagic info", (" shrinkMagic added: " + shrinkMagic));
                      }
                  }
              }
  
              // lean slightly toward shrinking if lowTput condition
              if (shrinkScore < 0.5 && lowTput) {
                  shrinkScore = (flipCoin()) ? 0.5 : shrinkScore;
              }
  
              // lean toward shrinking if cpuUtil is high
              if ((shrinkScore < 0.5) && (poolSize > hangBufferPoolSize) && (!smallPool)) {
                  if (cpuHigh) {
                      shrinkScore = (flipCoin()) ? 0.7 : shrinkScore;
                  } else {
                      if (flippedCoin) {
                          // shrink more eagerly if we randomly decided not to shrink (flippedCoin) and
                          // we have data showing that tput is not much better at much larger pool size
                          try {
                              // potentially different compare span when looking at larger poolSizes
                              int upwardCompareSpan = Math.min(compareRange * poolIncrement, maxThreads - poolSize);
                              Integer largestPoolSize = getLargestValidPoolSize(poolSize, forecast);
                              // only make this check if data is available well beyond compareSpan
                              if ((largestPoolSize - poolSize) > (upwardCompareSpan * compareSpanRatioMultiplier))
                                  if (leanTowardShrinking(poolSize, largestPoolSize, forecast, threadStats.get(largestPoolSize).getMovingAverage()))
                                      shrinkScore = 0.7;
                          } catch (Exception e) {
                              FFDCFilter.processException(e, getClass().getName(), "getShrinkScore - largestPoolSize", this);
                          }
                      } else {
                          // shrink more eagerly if we are not very inclined to shrink based on compareRange data,
                          // but we have much smaller poolSize data showing similar tput to current larger size
                          try {
                              Integer smallestPoolSize = getSmallestValidPoolSize(poolSize, forecast);
                              // only make this check if data is available well beyond compareSpan
                              if ((poolSize - smallestPoolSize) > (downwardCompareSpan * compareSpanRatioMultiplier))
                                  if (leanTowardShrinking(smallestPoolSize, poolSize, threadStats.get(smallestPoolSize).getMovingAverage(), forecast))
                                      shrinkScore = 0.7;
                          } catch (Exception e) {
                              FFDCFilter.processException(e, getClass().getName(), "getShrinkScore - smallestPoolSize", this);
                          }
                      }
                  }
              } else { // shrinkScore > 0.5
                  // if we are shrinking based on no data (coin flip) and the queue is deep,
                  // be less eager to shrink ...
                  if (flippedCoin && queueDepth > poolSize * 4)
                      shrinkScore = (flipCoin()) ? 0.0 : shrinkScore;
              }
              // Filter out small shrinkScores
              /**
               * Nov 2018 - moved this to the growScore/shrinkScore comparison in evaluateInterval
               * if (shrinkScore < shrinkScoreFilterLevel) {
               * if (tc.isEventEnabled()) {
               * Tr.event(tc, "shrinkScore pruning", (" shrinkScore " + shrinkScore + " pruned"));
               * }
               * shrinkScore = 0.0;
               * }
               */
  
          }
          return shrinkScore;
  
      }