/**
   * Runs QA checks against the data.
   *
   * @param modelId
   * @return
   * @throws Exception
   */
  public boolean testSingleModelDataQuality(Long modelId) throws Exception {
    Connection conn = SharedApplication.getInstance().getROConnection();

    // Get list of queries in properties file
    Properties props = new Properties();

    String path = this.getClass().getName().replace('.', '/') + ".properties";
    InputStream ins = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
    if (ins == null) {
      ins = Action.class.getResourceAsStream(path);
    }
    props.load(ins);

    Enumeration<?> elements = props.propertyNames();
    boolean passed = true;

    try {
      while (elements.hasMoreElements()) {
        String queryName = elements.nextElement().toString();
        log.debug("Running data validation test " + queryName + "'...");
        passed = passed & testSingleModelDataQuality(modelId, queryName, conn);
      }
    } finally {
      SharedApplication.closeConnection(conn, null);
    }

    if (passed) {
      log.debug("++++++++ Model #" + modelId + " PASSED all data validation tests ++++++++");
    } else {
      // The fail message is printed in the deligated method, so no need to reprint here.
    }
    return passed;
  }
  @Test
  public void test9680_WhichHasAOneThousanthFraction() throws Exception {
    String TEST_REACH_CLIENT_ID = "9680";
    Long TEST_REACH_SYSTEM_ID = 9680L;
    Long ONLY_REACH_UPSTREAM_OF_TEST_REACH_ID = 9681L;

    // Create terminal reaches and put in cache
    List<String> targetList = new ArrayList<String>();
    targetList.add(TEST_REACH_CLIENT_ID);
    TerminalReaches targets = new TerminalReaches(TEST_MODEL_ID, targetList);
    ConfiguredCache.TerminalReaches.put(targets.getId(), targets);

    // Load predict data and model incremental areas - used for comparison
    PredictData pd = SharedApplication.getInstance().getPredictData(TEST_MODEL_ID);
    DataTable incrementalReachAreas =
        SharedApplication.getInstance()
            .getCatchmentAreas(new UnitAreaRequest(TEST_MODEL_ID, AreaType.INCREMENTAL));

    // Stats on the test reach
    int testReachRowNumber = pd.getRowForReachID(TEST_REACH_SYSTEM_ID);
    Double testReachFrac = pd.getTopo().getDouble(testReachRowNumber, PredictData.TOPO_FRAC_COL);
    Double testReachFractionedWatershedArea =
        new CalcFractionedWatershedArea(
                new FractionedWatershedAreaRequest(
                    new ReachID(TEST_MODEL_ID, TEST_REACH_SYSTEM_ID)))
            .run();
    Double testReachUnfractionedWatershedArea =
        new CalcFractionedWatershedArea(
                new FractionedWatershedAreaRequest(
                    new ReachID(TEST_MODEL_ID, TEST_REACH_SYSTEM_ID), false, false, true))
            .run();
    Double testReachIncrementalArea = incrementalReachAreas.getDouble(testReachRowNumber, 1);

    // load stats on the only reach immediately upstream of the test reach
    Double upstreamReachFractionedWatershedArea =
        new CalcFractionedWatershedArea(
                new FractionedWatershedAreaRequest(
                    new ReachID(TEST_MODEL_ID, ONLY_REACH_UPSTREAM_OF_TEST_REACH_ID)))
            .run();

    // Test the assumptions about the basic (non-table form) of the area data
    assertTrue(
        Math.abs(testReachFractionedWatershedArea - testReachUnfractionedWatershedArea)
            > 1d); // These should be different values
    assertTrue(Math.abs(testReachFractionedWatershedArea - testReachIncrementalArea) > 1d);
    assertEquals(
        testReachFrac * upstreamReachFractionedWatershedArea + testReachIncrementalArea,
        testReachFractionedWatershedArea,
        .0001d);

    // Do the same calculation via the Table version of the action
    CalcFractionedWatershedAreaTable action = new CalcFractionedWatershedAreaTable(targets.getId());
    ColumnData data = action.run();

    assertEquals(testReachFractionedWatershedArea, data.getDouble(testReachRowNumber), .0001d);
  }
  @Before
  public void setup() {

    // Set up contexts
    AdjustmentGroups adjustments = new AdjustmentGroups(TEST_MODEL_ID);

    predictData = SharedApplication.getInstance().getPredictData(TEST_MODEL_ID);
    predictResult = SharedApplication.getInstance().getPredictResult(adjustments);
    UnitAreaRequest watershedAreaReq =
        new UnitAreaRequest(TEST_MODEL_ID, AreaType.TOTAL_CONTRIBUTING);
    DataTable watershedAreaTable =
        SharedApplication.getInstance().getCatchmentAreas(watershedAreaReq);
    watershedAreaColumn = new ColumnDataFromTable(watershedAreaTable, 1);
  }
  @Override
  public ReachRowValueMap createEntry(Object terminalReaches) throws Exception {
    TerminalReaches targets = (TerminalReaches) terminalReaches;

    PredictData predictData =
        SharedApplication.getInstance().getPredictData(new Long(targets.getModelID()));

    Set<String> clientTargetReachIdSet = targets.getReachIdsAsSet();

    List<Long> targetReachIdList =
        SharedApplication.getInstance()
            .getReachFullIdAsLong(targets.getModelID(), clientTargetReachIdSet);
    HashSet<Long> targetReachIdSet = new HashSet<Long>();
    targetReachIdSet.addAll(targetReachIdList);

    CalcDeliveryFractionMap action = new CalcDeliveryFractionMap(predictData, targetReachIdSet);

    ReachRowValueMap delFrac = action.run();

    return delFrac;
  }
  @Override
  protected void initFields() throws Exception {
    File dataDir = getDataDirectory();

    if (!dataDir.exists()) {
      Files.createDirectories(dataDir.toPath());
    }

    dataColumn = context.getDataColumn().getColumnData();
    columnIndex =
        SharedApplication.getInstance().getPredictData(context.getModelID()).getTopo().getIndex();
    outputFile =
        new File(
            dataDir,
            NamingConventions.convertContextIdToXMLSafeName(
                    context.getModelID().intValue(), context.getId())
                + ".dbf");
    outputFile.createNewFile();

    DataSeriesType type = context.getAnalysis().getDataSeries();

    // grab the delivery fraction map if this is a delivery data series.
    // This is used to weed out the reaches that are not upstream of the
    // user selected terminal reaches.
    if (type.isDeliveryRequired()) {

      TerminalReaches tReaches = context.getTerminalReaches();

      assert (tReaches != null) : "client should not submit a delivery request without reaches";

      reachRowValueMap = SharedApplication.getInstance().getDeliveryFractionMap(tReaches);

      if (reachRowValueMap == null) {
        throw new Exception("Unable to find or calculate the delivery fraction map");
      }
    } else {
      reachRowValueMap = null;
    }
  }
  /**
   * Tests all the source / total / incremental combinations for reasonable std error estimate
   * values.
   *
   * @param modelId
   * @return
   * @throws Exception
   */
  public boolean testSingleModelErrorEstimates(Long modelId) throws Exception {
    PredictData pd = SharedApplication.getInstance().getPredictData(modelId);
    int failCount = 0;

    for (int srcIndex = 0; srcIndex < pd.getSrcMetadata().getRowCount(); srcIndex++) {
      int srcId = pd.getSourceIdForSourceIndex(srcIndex);
      failCount += testSingleModelErrorEstimates(modelId, srcId, false);
      failCount += testSingleModelErrorEstimates(modelId, srcId, true);
    }

    // Add the total values (ie, all sources togeteher
    failCount += testSingleModelErrorEstimates(modelId, null, false);
    failCount += testSingleModelErrorEstimates(modelId, null, true);

    System.out.println("Grand Total Error est failures for model " + modelId + ": " + failCount);
    return failCount == 0;
  }
  protected File getDataDirectory() {
    File dDir;

    if (this.dataDirectory != null) {
      dDir = this.dataDirectory;
    } else {
      DynamicReadOnlyProperties props = SharedApplication.getInstance().getConfiguration();
      String fallbackDataDirectory =
          System.getProperty("user.home")
              + File.separatorChar
              + "sparrow"
              + File.separatorChar
              + "data";
      String sparrowDataDirectory = props.getProperty(DATA_EXPORT_DIRECTORY, fallbackDataDirectory);
      dDir = new File(sparrowDataDirectory);
      this.dataDirectory = dDir;
    }

    return dDir;
  }
  @Test
  public void testTargetWith4UpstreamReaches() throws Exception {
    Long TEST_ROW_SYSTEM_ID = 9687L;
    String TEST_ROW_CLIENT_ID = "9687";

    List<String> targetList = new ArrayList<String>();
    targetList.add(TEST_ROW_CLIENT_ID);
    TerminalReaches targets = new TerminalReaches(TEST_MODEL_ID, targetList);
    ConfiguredCache.TerminalReaches.put(targets.getId(), targets);

    PredictData pd = SharedApplication.getInstance().getPredictData(TEST_MODEL_ID);
    Double watershedArea =
        new CalcFractionedWatershedArea(
                new FractionedWatershedAreaRequest(new ReachID(TEST_MODEL_ID, TEST_ROW_SYSTEM_ID)))
            .run();

    int rowCount = pd.getTopo().getRowCount();
    int rowNumber = pd.getRowForReachID(TEST_ROW_SYSTEM_ID);

    CalcFractionedWatershedAreaTable action = new CalcFractionedWatershedAreaTable(targets.getId());
    ColumnData data = action.run();

    assertEquals(rowCount, data.getRowCount().intValue());
    assertNotNull(data.getDouble(rowNumber));

    for (int row = 0; row < rowCount; row++) {
      if (row == rowNumber) {
        assertEquals(
            "Row " + row + " should be " + watershedArea,
            watershedArea,
            data.getDouble(row),
            .000000001d);
      } else {
        assertNull("Row " + row + " should be null", data.getDouble(row));
      }
    }
  }
 /**
  * Returns a sorted list of identifiers for reaches that fall within the bounding box defined by
  * the specified arguments. A reach is considered to fall within the bounding box if any part of
  * its geometry falls within the bounding box.
  *
  * @return A list of identifiers for reaches that fall within the defined bounding box.
  * @throws Exception
  */
 private Long[] getResults(Long modelId, String bounds) throws Exception {
   ModelBBox modelBBox = new ModelBBox(modelId, bounds);
   Long[] ids = SharedApplication.getInstance().getReachesInBBox(modelBBox);
   return ids;
 }
  /**
   * Tests a single model / source / total/incremental combination for a reasonalbe std error
   * estimate.
   *
   * @param modelId
   * @param sourceId
   * @param total
   * @return
   * @throws Exception
   */
  public int testSingleModelErrorEstimates(Long modelId, Integer sourceId, boolean total)
      throws Exception {

    AdjustmentGroups noAdjustments = new AdjustmentGroups(modelId);
    int failCount = 0;
    String incTot = (total) ? "Total" : "Incremental";

    // Build the error data series
    DataSeriesType errSeries =
        (total)
            ? DataSeriesType.total_std_error_estimate
            : DataSeriesType.incremental_std_error_estimate;
    BasicAnalysis errAnalysis = new BasicAnalysis(errSeries, sourceId, null, null);
    PredictionContext errContext =
        new PredictionContext(modelId, noAdjustments, errAnalysis, null, null, null);
    SparrowColumnSpecifier errColumn =
        SharedApplication.getInstance().getAnalysisResult(errContext);

    ///
    // Build the related standard series
    DataSeriesType stdSeries = (total) ? DataSeriesType.total : DataSeriesType.incremental;
    BasicAnalysis stdAnalysis = new BasicAnalysis(stdSeries, sourceId, null, null);
    PredictionContext stdContext =
        new PredictionContext(modelId, noAdjustments, stdAnalysis, null, null, null);
    SparrowColumnSpecifier stdColumn =
        SharedApplication.getInstance().getAnalysisResult(stdContext);

    ///
    // Do the comparison
    for (int row = 0; row < stdColumn.getRowCount(); row++) {
      Double std = stdColumn.getDouble(row);
      Double err = errColumn.getDouble(row);

      if (err == null && std < 1d) {
        // I think its OK to have no err estimate if the predicted value is very small
        // System.out.println("Predict: " + std + " Err: null");
      } else if (err != null && std <= 1d && err <= 20) {
        // An error of 10 is pretty small, though it may be many times larger than the predicted
        // value
        // System.out.println("Predict: " + std + " Err: " + err);
      } else if (err != null && err <= (std * 20)) {
        // System.out.println("Predict: " + std + " Err: " + err);
      } else {
        System.out.println(
            "Model "
                + modelId
                + ", Src:"
                + sourceId
                + " "
                + incTot
                + ": Suspicious Std Error.  Value: "
                + std
                + " Err: "
                + err);
        failCount++;
      }
    }

    System.out.println(
        "Model "
            + modelId
            + ", Src:"
            + sourceId
            + " "
            + incTot
            + ": Total Error est failures: "
            + failCount);

    return failCount;
  }
  public boolean testSingleMmodel(URL predictFile, Long modelId, boolean logWithDetails)
      throws Exception {
    beforeEachTest();
    boolean pass = false;

    DataTable t = loadTextPredict(predictFile);

    if (t != null) {
      AdjustmentGroups ags = new AdjustmentGroups(modelId);
      PredictResult prs = SharedApplication.getInstance().getPredictResult(ags);
      PredictData pd = SharedApplication.getInstance().getPredictData(modelId);

      log.setLevel(Level.FATAL); // Turn off logging
      int noDecayFailures = testComparison(t, prs, pd, false, modelId, logWithDetails);
      int decayFailures = testComparison(t, prs, pd, true, modelId, logWithDetails);
      log.setLevel(Level.DEBUG); // Turn back on

      if (decayFailures < noDecayFailures) {
        if (decayFailures == 0) {
          log.debug(
              "++++++++ Model #" + modelId + " PASSED using DECAYED incremental values +++++++++");
          pass = true;
        } else {
          log.debug(
              "-------- Model #"
                  + modelId
                  + " FAILED using DECAYED incremental values.  Details: --------");
          testComparison(t, prs, pd, true, modelId, logWithDetails);
        }
      } else if (noDecayFailures < decayFailures) {
        if (noDecayFailures == 0) {
          log.debug(
              "++++++++ Model #"
                  + modelId
                  + " PASSED using NON-DECAYED incremental values +++++++++");
          pass = true;
        } else {
          log.debug(
              "-------- Model #"
                  + modelId
                  + " FAILED using NON-DECAYED incremental values.  Details: --------");
          testComparison(t, prs, pd, false, modelId, logWithDetails);
        }
      } else if (noDecayFailures == decayFailures) {
        // Equal failures mean there is a row count or other type of error
        log.debug(
            "Hey, found these no decay fails: "
                + noDecayFailures
                + " and these decay fails: "
                + decayFailures);
        log.debug(
            "-------- Model #"
                + modelId
                + " FAILED.  MAJOR ISSUE (no decay matches decay) - SEE DETAILS: --------");
        testComparison(t, prs, pd, false, modelId, logWithDetails);
      }
    } else {
      log.debug(
          "-------- Model #"
              + modelId
              + " FAILED.  MAJOR ISSUE (couldn't load file) - SEE DETAILS (above) --------");
    }

    return pass;
  }
  public void runTheTests() throws Exception {

    int failCount = 0;

    try {
      Connection conn = SharedApplication.getInstance().getROConnection();
      conn.close();
    } catch (Exception e) {
      throw new Exception("Oops, a bad pwd, or lack of network access to the db?", e);
    }

    if (singleModelPath != null) {
      File file = new File(singleModelPath);
      if (file.exists()) {

        String idString = singleModelPath.substring(0, singleModelPath.lastIndexOf('.'));
        idString = idString.substring(idString.lastIndexOf(File.separatorChar) + 1);
        Long id = null;

        try {
          id = Long.parseLong(idString);
        } catch (Exception e) {
          idString =
              prompt(
                  "Couldn't figure out the model number (expecting it as the file name).  What is the model number?");
          id = Long.parseLong(idString);
        }

        boolean pass = true;
        pass =
            pass & testSingleModelDataQuality(id); // Test 1 (already split off as failable tests)
        pass = pass & testSingleMmodel(file.toURL(), id, logShouldIncludeDetails); // Test 2
        pass = pass & testSingleModelErrorEstimates(id); // Test 3
        if (!pass) failCount++;
      } else {
        throw new Exception("The specified model path does not exist!");
      }

    } else {

      int modelCount = 0;
      for (long id = firstModelId; id <= lastModelId; id++) {

        String filePath = activeModelDirectory + id + ".txt";
        File file = new File(filePath);

        if (file.exists()) {
          modelCount++;

          boolean pass = true;
          pass = pass & testSingleModelDataQuality(id);
          pass = pass & testSingleMmodel(file.toURL(), id, logShouldIncludeDetails);

          if (!pass) failCount++;
        }
      }

      if (modelCount == 0) {
        throw new Exception(
            "The specified directory does not contain any models within the first & last model IDs!");
      }
    }

    if (failCount == 0) {
      log.debug("+ + + + + EVERYTHING LOOKS GREAT! + + + + +");
    } else {
      log.error(
          "- - - - - AT LEAST ONE MODEL FAILED VALIDATION.  PLEASE REVIEW THE LOG MESSAGES TO FIND THE VALIDATION ERRORS. + + + + +");
    }
  }
 public void beforeEachTest() {
   SharedApplication.getInstance().clearAllCaches();
 }