Index: trunk/ippTools/src/dettool.c
===================================================================
--- trunk/ippTools/src/dettool.c	(revision 8489)
+++ trunk/ippTools/src/dettool.c	(revision 8498)
@@ -48,4 +48,5 @@
 static  detResidImfileRow *detNormalizedToDetResidImfile(pxConfig *config, detNormalizedImfileRow *normalizedImfile);
 static detResidExpRow *mdToDetResidExp(pxConfig *config, psMetadata *row);
+static detRunSummaryRow *mdToDetRunSummary(pxConfig *config, psMetadata *row);
 static psArray *validDetInputClassIds(pxConfig *config, const char *det_id);
 static psArray *searchInputImfiles(pxConfig *config, const char *det_id);
@@ -3486,5 +3487,198 @@
 {
     PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // select detRun.position
+    // select detRun.iteration
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_ids in the current det_id/iteration from detInputExp
+    // find all exp_ids in the current det_id/iteration from detResidExp
+    // compare the counts of exp_ids
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT"
+        "   det_id,"
+        "   iteration"
+        " FROM"
+        "   (SELECT DISTINCT"
+        "       detRun.position AS det_id,"
+        "       detRun.iteration,"
+        "       detInputExp.exp_id"
+        "   FROM detRun"
+        "       LEFT JOIN detInputExp"
+        "       ON detRun.position = detInputExp.det_id"
+        "       AND detRun.iteration = detInputExp.iteration"
+        "   LEFT JOIN rawDetrendExp"
+        "       ON detInputExp.exp_id = rawDetrendExp.exp_id"
+        "   LEFT JOIN detResidExp"
+        "       ON detRun.position = detResidExp.det_id"
+        "       AND detRun.iteration = detResidExp.iteration"
+        "       AND detInputExp.exp_id = detResidExp.exp_id"
+        "   GROUP BY"
+        "       detRun.position,"
+        "       detRun.iteration"
+        "   HAVING"
+        "       COUNT(detResidExp.exp_id) = COUNT(detInputExp.exp_id)"
+        " ) AS residdetrun"
+        ); 
+
+    {
+        // build a query to search by det_id, iteration, exp_id
+        psMetadata *where = psMetadataAlloc();
+        bool status = false;
+        psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        if (det_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "det_id", 0, "==", det_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+        psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        // always set iteration
+        if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", iteration)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+
+        // there's not
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psFree(where);
+        if (whereClause) {
+            psStringAppend(&query, " WHERE %s", whereClause);
+            psFree(whereClause);
+        }
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    } 
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no pending completed detrun iterations found");
+        return false;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detResidExp object
+        detRunSummaryRow *runSummary = mdToDetRunSummary(config, row);
+        if (!runSummary) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata to detResidExp");
+            psFree(output);
+            return false;
+        }
+        // insert detResidExp object into the database
+        if (!detRunSummaryInsertObject(config->dbh, runSummary)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(runSummary);
+            psFree(output);
+            return false;
+        }
+        psFree(runSummary);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
     return true;
+}
+
+static detRunSummaryRow *mdToDetRunSummary(pxConfig *config, psMetadata *row)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    // from row
+    psS32 det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for iteration");
+        return false;
+    }
+
+    // from config->args
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return NULL;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return NULL;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return NULL;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return NULL;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return NULL;
+    }
+    if (isnan(bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_mean_stdev is required");
+        return NULL;
+    }
+    // optional
+    bool reject = psMetadataLookupBool(&status, config->args, "-reject");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -reject");
+        return NULL;
+    }
+
+    return detRunSummaryRowAlloc(
+            det_id,
+            iteration,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            !reject
+        );
 }
 
