docs: private Result getCandidateInfo(String candidateId){
  
          StatsStorage ss = knownSessionIDs.get(currentSessionID);
          if(ss == null){
              log.debug("getModelLastUpdateTimes(): Session ID is unknown: {}", currentSessionID);
              return ok();
          }
  
          GlobalConfigPersistable gcp = (GlobalConfigPersistable)ss.getStaticInfo(currentSessionID, ARBITER_UI_TYPE_ID, GlobalConfigPersistable.GLOBAL_WORKER_ID);;
          OptimizationConfiguration oc = gcp.getOptimizationConfiguration();
  
          Persistable p = ss.getLatestUpdate(currentSessionID, ARBITER_UI_TYPE_ID, candidateId);
          if(p == null){
              String title = "No results found for model " + candidateId + ".";
              ComponentText ct = new ComponentText.Builder(title,STYLE_TEXT_SZ12).build();
              return ok(asJson(ct)).as(JSON);
          }
  
          ModelInfoPersistable mip = (ModelInfoPersistable)p;
  
          //First: static info
          // Hyperparameter configuration/settings
          // Number of parameters
          // Maybe memory info in the future?
  
          //Second: dynamic info
          //Runtime
          // Performance stats (total minibatches, total time,
          // Score vs. time
  
          List<Component> components = new ArrayList<>();
  
          //First table: mix of static + dynamic in a table
          long runtimeDurationMs = mip.getLastUpdateTime() - mip.getTimeStamp();
          double avgMinibatchesPerSec = mip.getTotalNumUpdates() / (runtimeDurationMs/1000.0);
          String avgMinibatchesPerSecStr = DECIMAL_FORMAT_2DP.format(avgMinibatchesPerSec);
          String runtimeStr = UIUtils.formatDuration(runtimeDurationMs);
  
          if(mip.getStatus() == CandidateStatus.Failed){
              runtimeStr = "";
              avgMinibatchesPerSecStr = "";
          }
  
          String[][] table = new String[][]{
                  {"Model Index", String.valueOf(mip.getModelIdx())},
                  {"Status", mip.getStatus().toString()},
                  {"Model Score", mip.getScore() == null ? "" : String.valueOf(mip.getScore())},
                  {"Created", TIME_FORMATTER.print(mip.getTimeStamp())},
                  {"Runtime", runtimeStr},
                  {"Total Number of Model Updates", String.valueOf(mip.getTotalNumUpdates())},
                  {"Average # Updates / Sec", avgMinibatchesPerSecStr},
                  {"Number of Parameters", String.valueOf(mip.getNumParameters())},
                  {"Number of Layers", String.valueOf(mip.getNumLayers())}
          };
  
          ComponentTable cTable = new ComponentTable.Builder(STYLE_TABLE)
                  .content(table)
                  .header("Model Information", "")
                  .build();
          components.add(cTable);
  
  
          //Second: parameter space values, in multiple tables
          double[] paramSpaceValues = mip.getParamSpaceValues();
          if(paramSpaceValues != null){
              BaseNetworkSpace bns = (BaseNetworkSpace)oc.getCandidateGenerator().getParameterSpace();
              Map<String,ParameterSpace> m = bns.getNestedSpaces();
  
              String[][] hSpaceTable = new String[m.size()][3];
              int i=0;
              for(Map.Entry<String,ParameterSpace> e : m.entrySet()){
                  hSpaceTable[i][0] = e.getKey();
                  Object currCandidateValue = e.getValue().getValue(paramSpaceValues);
                  hSpaceTable[i][1] = ObjectUtils.valueToString(currCandidateValue);
                  hSpaceTable[i][2] = e.getValue().toString();
                  i++;
              }
  
              String[] hSpaceTableHeader = new String[]{"Hyperparameter", "Model Value", "Hyperparameter Space"};
  
              ComponentTable ct2 = new ComponentTable.Builder(STYLE_TABLE3_25_25_50)
                      .content(hSpaceTable)
                      .header(hSpaceTableHeader)
                      .build();
  
  
              String title = "Global Network Configuration";
              components.add(DIV_SPACER_20PX);
              components.add(new ComponentText.Builder(title, STYLE_TEXT_SZ12).build());
              components.add(ct2);
  
              List<BaseNetworkSpace.LayerConf> layerConfs = bns.getLayerSpaces();
  
              for(BaseNetworkSpace.LayerConf l : layerConfs){
                  LayerSpace<?> ls = l.getLayerSpace();
                  Map<String,ParameterSpace> lpsm = ls.getNestedSpaces();
  
                  String[][] t = new String[lpsm.size()][3];
                  i=0;
                  for(Map.Entry<String,ParameterSpace> e : lpsm.entrySet()){
                      t[i][0] = e.getKey();
                      Object currCandidateValue = e.getValue().getValue(paramSpaceValues);
                      t[i][1] = ObjectUtils.valueToString(currCandidateValue);
                      t[i][2] = e.getValue().toString();
                      i++;
                  }
  
                  ComponentTable ct3 = new ComponentTable.Builder(STYLE_TABLE3_25_25_50)
                          .content(t)
                          .header(hSpaceTableHeader)
                          .build();
  
                  title = "Layer Space: " + ls.getClass().getSimpleName() + ", Name: " + l.getLayerName();
  
                  components.add(DIV_SPACER_20PX);
                  components.add(new ComponentText.Builder(title, STYLE_TEXT_SZ12).build());
                  components.add(ct3);
              }
          }
  
  
          //Third: Score vs. time chart
          int[] iters = mip.getIter();
          float[] scores = mip.getScoreVsIter();
  
          if(iters != null) {
              double[] si = new double[iters.length];
              double[] scoresD = new double[iters.length];
  
              double minScore = Double.MAX_VALUE;
              double maxScore = -Double.MAX_VALUE;
              for( int i=0; i<iters.length; i++ ){
                  si[i] = iters[i];
                  scoresD[i] = scores[i];
                  minScore = Math.min(minScore, scoresD[i]);
                  maxScore = Math.max(maxScore, scoresD[i]);
              }
  
              double[] chartMinMax = UIUtils.graphNiceRange(maxScore, minScore, 5);
  
              ChartLine cl = new ChartLine.Builder("Model Score vs. Iteration", STYLE_CHART_800_400)
                      .addSeries("Score", si, scoresD )
                      .setYMin(chartMinMax[0])
                      .setYMax(chartMinMax[1])
                      .build();
  
              components.add(DIV_SPACER_60PX);
              components.add(cl);
          }
  
  
          //Post full network configuration JSON, if available:
          String modelJson = mip.getModelConfigJson();
          if(modelJson != null){
              components.add(DIV_SPACER_60PX);
              components.add(new ComponentDiv(STYLE_DIV_WIDTH_100_PC, new ComponentText("Model Configuration", STYLE_TEXT_SZ12)));
              ComponentText jsonText = new ComponentText(modelJson, STYLE_TEXT_SZ10_WHITESPACE_PRE);
              ComponentDiv cd = new ComponentDiv(STYLE_DIV_WIDTH_100_PC, jsonText);
              components.add(cd);
          }
  
  
          //Post exception stack trace, if necessary:
          if( mip.getExceptionStackTrace() != null ){
              components.add(DIV_SPACER_60PX);
              components.add(new ComponentDiv(STYLE_DIV_WIDTH_100_PC, new ComponentText("Model Exception - Stack Trace", STYLE_TEXT_SZ12)));
              ComponentText exText = new ComponentText(mip.getExceptionStackTrace(), STYLE_TEXT_SZ10_WHITESPACE_PRE);
              ComponentDiv cd = new ComponentDiv(STYLE_DIV_WIDTH_100_PC, exText);
              components.add(cd);
          }
  
          ComponentDiv cd = new ComponentDiv(STYLE_DIV_WIDTH_100_PC, components);
  
          return ok(asJson(cd)).as(JSON);
      }