From 2b91242362b562228f331e29647d2cc097bd4cb5 Mon Sep 17 00:00:00 2001
From: Karen Hausman <74921039+hausman-gdit@users.noreply.github.com>
Date: Wed, 7 Feb 2024 10:10:03 -0500
Subject: [PATCH 1/2] 328 improve error message (#4)

* set seed on dataset select & add options

* basic functionality

* move to utils, test, add node

* attempt to run tests in cicd

* add PAT

* update coverage reporting

* add coverage report to gitignore

* refactor - only set bootstrap_seed for nested dichotomous

* refactorings

* tweak

* standardize server error parsing

* remove

---------

Co-authored-by: Andy Shapiro <shapiromatron@gmail.com>
---
 frontend/src/common.js                        |  17 --
 .../Main/AnalysisForm/AnalysisForm.js         |   6 +-
 .../src/components/common/ErrorMessage.js     |   6 +-
 frontend/src/stores/DataStore.js              |   2 +-
 frontend/src/stores/MainStore.js              |  14 +-
 frontend/src/utils/parsers.js                 |  73 +++++++++
 frontend/tests/helpers.js                     |   4 +
 frontend/tests/utils/parsers.test.js          | 148 ++++++++++++++++++
 8 files changed, 241 insertions(+), 29 deletions(-)
 create mode 100644 frontend/src/utils/parsers.js
 create mode 100644 frontend/tests/utils/parsers.test.js

diff --git a/frontend/src/common.js b/frontend/src/common.js
index 7626d0a4..76d9c166 100644
--- a/frontend/src/common.js
+++ b/frontend/src/common.js
@@ -28,21 +28,4 @@ export const simulateClick = function(el) {
     },
     getLabel = function(value, mapping) {
         return _.find(mapping, d => d.value == value).label;
-    },
-    parseErrors = errorText => {
-        let errors = [],
-            textErrors = [];
-        try {
-            errors = JSON.parse(errorText);
-        } catch {
-            console.error("Cannot parse error response");
-            return {errors, textErrors};
-        }
-        textErrors = errors.map(error => {
-            if (error.loc && error.msg) {
-                return `${error.loc[0]}: ${error.msg}`;
-            }
-            return JSON.stringify(error);
-        });
-        return {errors, textErrors};
     };
diff --git a/frontend/src/components/Main/AnalysisForm/AnalysisForm.js b/frontend/src/components/Main/AnalysisForm/AnalysisForm.js
index fc87d11a..91ac80d5 100644
--- a/frontend/src/components/Main/AnalysisForm/AnalysisForm.js
+++ b/frontend/src/components/Main/AnalysisForm/AnalysisForm.js
@@ -3,6 +3,7 @@ import PropTypes from "prop-types";
 import React, {Component} from "react";
 
 import Button from "../../common/Button";
+import ErrorMessage from "../../common/ErrorMessage";
 import Icon from "../../common/Icon";
 import SelectInput from "../../common/SelectInput";
 import Spinner from "../../common/Spinner";
@@ -63,10 +64,7 @@ class AnalysisForm extends Component {
                             choices={mainStore.getModelTypeChoices}
                         />
                     </div>
-
-                    {mainStore.errorMessage ? (
-                        <div className="alert alert-danger">{mainStore.errorMessage}</div>
-                    ) : null}
+                    <ErrorMessage error={mainStore.errorMessage} />
                     <div id="controlPanel" className="card bg-light">
                         {mainStore.isExecuting ? (
                             <div className="card-body">
diff --git a/frontend/src/components/common/ErrorMessage.js b/frontend/src/components/common/ErrorMessage.js
index ef8a50f9..b89795a7 100644
--- a/frontend/src/components/common/ErrorMessage.js
+++ b/frontend/src/components/common/ErrorMessage.js
@@ -5,7 +5,11 @@ const ErrorMessage = ({error}) => {
     if (!error) {
         return null;
     }
-    return <p className="text-danger mb-1">{error}</p>;
+    return (
+        <div className="alert alert-danger mb-3">
+            <pre className="text-wrap mb-0">{error}</pre>
+        </div>
+    );
 };
 ErrorMessage.propTypes = {
     error: PropTypes.string,
diff --git a/frontend/src/stores/DataStore.js b/frontend/src/stores/DataStore.js
index 7f8c791b..99621e25 100644
--- a/frontend/src/stores/DataStore.js
+++ b/frontend/src/stores/DataStore.js
@@ -204,7 +204,7 @@ class DataStore {
 
     @computed get selectedDatasetErrorText() {
         const data = this.selectedDatasetErrors;
-        return _.isArray(data) ? data.map(el => el.msg).join(", ") : "";
+        return this.selectedDatasetErrors ? _.uniq(data.map(el => el.msg)).join("\n") : "";
     }
 
     @computed get getMappedArray() {
diff --git a/frontend/src/stores/MainStore.js b/frontend/src/stores/MainStore.js
index d991f65d..ccff27ca 100644
--- a/frontend/src/stores/MainStore.js
+++ b/frontend/src/stores/MainStore.js
@@ -3,8 +3,9 @@ import _ from "lodash";
 import {action, computed, observable, toJS} from "mobx";
 import slugify from "slugify";
 
-import {getHeaders, parseErrors, simulateClick} from "@/common";
+import {getHeaders, simulateClick} from "@/common";
 import * as mc from "@/constants/mainConstants";
+import {parseServerErrors} from "@/utils/parsers";
 
 class MainStore {
     constructor(rootStore) {
@@ -101,9 +102,9 @@ class MainStore {
                     response.json().then(data => this.updateModelStateFromApi(data));
                 } else {
                     response.json().then(errorText => {
-                        const {errors, textErrors} = parseErrors(errorText);
-                        this.errorMessage = textErrors.join(", ");
-                        this.errorData = errors;
+                        const error = parseServerErrors(errorText);
+                        this.errorMessage = error.message;
+                        this.errorData = error.data;
                     });
                 }
             })
@@ -227,8 +228,9 @@ class MainStore {
         this.analysisSavedAndValidated = false;
     }
     @action.bound updateModelStateFromApi(data) {
-        if (data.errors.length > 0) {
-            this.errorMessage = data.errors;
+        const errors = parseServerErrors(data.errors);
+        if (errors) {
+            this.errorMessage = errors.message;
             this.isUpdateComplete = true;
         }
 
diff --git a/frontend/src/utils/parsers.js b/frontend/src/utils/parsers.js
new file mode 100644
index 00000000..6e39aa70
--- /dev/null
+++ b/frontend/src/utils/parsers.js
@@ -0,0 +1,73 @@
+import _ from "lodash";
+
+export const parseServerErrors = errors => {
+        // parse errors from server response. They may come in a variety of formats, so we always
+        // want to show at least an error message if we cannot parse into a better format
+
+        if (_.isEmpty(errors)) {
+            return null;
+        }
+
+        let errorWasParsed = false;
+
+        const container = {
+            data: [],
+            messages: [],
+            message: "",
+        };
+
+        console.warn(`Complete errors:\n\n ${errors}`);
+
+        if (Array.isArray(errors)) {
+            errors.map(error => {
+                if (typeof error === "string") {
+                    const lower = error.toLowerCase();
+                    if (lower.includes("pydantic")) {
+                        errorWasParsed = true;
+                        const msg = JSON.parse(error);
+                        container.data.push(...msg);
+                        container.messages.push(...parsePydanticError(msg));
+                    } else if (lower.includes("traceback")) {
+                        errorWasParsed = true;
+                        container.data.push(error);
+                        container.messages.push(extractErrorFromTraceback(error));
+                    }
+                }
+            });
+        }
+
+        if (!errorWasParsed) {
+            container.data.push(errors);
+            container.messages.push("An error has occurred");
+        }
+
+        // return a single unique set of messages as a single string. This is used in the UI
+        container.message = _.uniq(container.messages)
+            .join("\n")
+            .trim();
+
+        return container;
+    },
+    parsePydanticError = errors => {
+        return errors
+            .map(error => {
+                if (error.loc && error.msg) {
+                    return `${error.loc[0]}: ${error.msg}`;
+                }
+                return JSON.stringify(error);
+            })
+            .sort();
+    },
+    extractErrorFromTraceback = error => {
+        // if this is a traceback from python just return the last line
+        if (!error.includes("Traceback")) {
+            return error;
+        }
+        const lines = error.trim().split("\n"),
+            line = lines[lines.length - 1],
+            colonIndex = line.indexOf(":");
+        if (colonIndex >= 0) {
+            return line.substring(colonIndex + 1).trim();
+        }
+        return line;
+    };
diff --git a/frontend/tests/helpers.js b/frontend/tests/helpers.js
index 7100f5c4..8051c156 100644
--- a/frontend/tests/helpers.js
+++ b/frontend/tests/helpers.js
@@ -8,9 +8,13 @@ const isClose = function(actual, expected, atol) {
         _.zip(actual, expected).map(d => {
             isClose(d[0], d[1], atol);
         });
+    },
+    allEqual = function(actual, expected) {
+        _.zip(actual, expected).map(d => d[0] === d[1]);
     };
 
 assert.isClose = isClose;
 assert.allClose = allClose;
+assert.allEqual = allEqual;
 
 export default assert;
diff --git a/frontend/tests/utils/parsers.test.js b/frontend/tests/utils/parsers.test.js
new file mode 100644
index 00000000..9e9869a5
--- /dev/null
+++ b/frontend/tests/utils/parsers.test.js
@@ -0,0 +1,148 @@
+import {
+    extractErrorFromTraceback,
+    parsePydanticError,
+    parseServerErrors,
+} from "../../src/utils/parsers";
+import assert from "../helpers";
+
+describe("Parsing", function() {
+    describe("parseServerErrors", function() {
+        it("handles no error correct", function() {
+            assert.equal(parseServerErrors(""), null);
+            assert.equal(parseServerErrors(null), null);
+            assert.equal(parseServerErrors(undefined), null);
+            assert.equal(parseServerErrors([]), null);
+        });
+
+        it("handles an unknown format", function() {
+            const expected = {
+                messages: ["An error has occurred"],
+                message: "An error has occurred",
+            };
+
+            expected.data = ["ERROR"];
+            assert.deepStrictEqual(parseServerErrors("ERROR"), expected);
+
+            expected.data = [["ERROR"]];
+            assert.deepStrictEqual(parseServerErrors(["ERROR"]), expected);
+
+            expected.data = [{err: "ERROR"}];
+            assert.deepStrictEqual(parseServerErrors({err: "ERROR"}), expected);
+
+            expected.data = [[{err: "ERROR"}]];
+            assert.deepStrictEqual(parseServerErrors([{err: "ERROR"}]), expected);
+        });
+
+        it("handles tracebacks", function() {
+            assert.deepStrictEqual(
+                parseServerErrors([
+                    'Traceback (most recent call last):\n  File "/bmds-server/bmds_server/analysis/models.py", line 246, in try_run_session\n    return AnalysisSession.run(inputs, dataset_index, option_index)\nValueError: Doses are not unique\n',
+                ]),
+                {
+                    data: [
+                        'Traceback (most recent call last):\n  File "/bmds-server/bmds_server/analysis/models.py", line 246, in try_run_session\n    return AnalysisSession.run(inputs, dataset_index, option_index)\nValueError: Doses are not unique\n',
+                    ],
+                    messages: ["Doses are not unique"],
+                    message: "Doses are not unique",
+                }
+            );
+        });
+
+        it("handles pydantic", function() {
+            assert.deepStrictEqual(
+                parseServerErrors([
+                    '[{"type":"float_type","loc":["datasets",0,"function-after[num_groups(), MaxContinuousDatasetSchema]","doses",1],"msg":"Input should be a valid number","url":"https://errors.pydantic.dev/2.4/v/float_type"},{"type":"float_type","loc":["datasets",0,"function-after[num_groups(), MaxContinuousIndividualDatasetSchema]","doses",1],"msg":"Input should be a valid number","url":"https://errors.pydantic.dev/2.4/v/float_type"},{"type":"missing","loc":["datasets",0,"function-after[num_groups(), MaxContinuousIndividualDatasetSchema]","responses"],"msg":"Field required","url":"https://errors.pydantic.dev/2.4/v/missing"}]',
+                ]),
+                {
+                    data: [
+                        {
+                            loc: [
+                                "datasets",
+                                0,
+                                "function-after[num_groups(), MaxContinuousDatasetSchema]",
+                                "doses",
+                                1,
+                            ],
+                            msg: "Input should be a valid number",
+                            type: "float_type",
+                            url: "https://errors.pydantic.dev/2.4/v/float_type",
+                        },
+                        {
+                            loc: [
+                                "datasets",
+                                0,
+                                "function-after[num_groups(), MaxContinuousIndividualDatasetSchema]",
+                                "doses",
+                                1,
+                            ],
+                            msg: "Input should be a valid number",
+                            type: "float_type",
+                            url: "https://errors.pydantic.dev/2.4/v/float_type",
+                        },
+                        {
+                            loc: [
+                                "datasets",
+                                0,
+                                "function-after[num_groups(), MaxContinuousIndividualDatasetSchema]",
+                                "responses",
+                            ],
+                            msg: "Field required",
+                            type: "missing",
+                            url: "https://errors.pydantic.dev/2.4/v/missing",
+                        },
+                    ],
+                    messages: [
+                        "datasets: Field required",
+                        "datasets: Input should be a valid number",
+                        "datasets: Input should be a valid number",
+                    ],
+                    message: "datasets: Field required\ndatasets: Input should be a valid number",
+                }
+            );
+        });
+    });
+
+    describe("extractErrorFromTraceback", function() {
+        it("extracts the error from a python traceback", function() {
+            const tracebackErrors = [
+                [
+                    'Traceback (most recent call last):\n  File "/bmds-server/bmds_server/analysis/models.py", line 246, in try_run_session\n    return AnalysisSession.run(inputs, dataset_index, option_index)\nValueError: Doses are not unique\n',
+                    "Doses are not unique",
+                ],
+                [
+                    'Traceback (most recent call last):\n  File "/bmds-server/bmds_server/analysis/models.py", line 246, in try_run_session\n    return AnalysisSession.run(inputs, dataset_index, option_index)\nDoses are not unique\n',
+                    "Doses are not unique",
+                ],
+                ["Fallthrough - no change", "Fallthrough - no change"],
+            ];
+
+            tracebackErrors.map(args => {
+                const input = args[0],
+                    result = args[1];
+                assert.equal(extractErrorFromTraceback(input), result);
+            });
+        });
+    });
+
+    describe("parsePydanticError", function() {
+        it("extracts the error from a pydantic exception", function() {
+            const pydanticErrors = [
+                [
+                    [
+                        {
+                            loc: ["datasets", "remove"],
+                            msg: "Input should be a valid number",
+                        },
+                    ],
+                    ["datasets: Input should be a valid number"],
+                ],
+            ];
+
+            pydanticErrors.map(args => {
+                const input = args[0],
+                    result = args[1];
+                assert.allEqual(parsePydanticError(input), result);
+            });
+        });
+    });
+});

From 71786b7cc0924c3782bc3e9654ee44d0f6065b51 Mon Sep 17 00:00:00 2001
From: Karen Hausman <74921039+hausman-gdit@users.noreply.github.com>
Date: Fri, 9 Feb 2024 18:11:08 -0500
Subject: [PATCH 2/2] 321 improve nomenclature for statistical outputs (#2)

* 8

* 25a, 26, 30 comment

* 27, 29, 33, 34, 35

* 2, 6

* 31

* testfix

* rm comment, 31

* 3 note, 2, 5a, 12, 24, rm comment

* js eol lf

* revert MT

* rename percentile to cummulative probability

* resize tables as needed

* remove change

* fix test w/ degree symbol

* rename log likelihood globally

* fix bug in layout

* rename scaled residual

* rename DOF to d.f.

* change Summary to Modeling Summary

* rename "Abs(Residual of interest)" -> "|Residual near BMD|"

* rename "Frequentist Model Results" to "Maximum Likelihood Approach Model Results"

* title case

* add vertical spacing

* Change "Confidence Level" to "Confidence Level (one-sided)" globally

* add on bound footnote to UI

* handle goodness of fit table

* use < and > instead of less than and greater than

* only show tail probability if hybrid continuous bmr

* make format

---------

Co-authored-by: Andy Shapiro <shapiromatron@gmail.com>
---
 .../components/IndividualModel/CDFTable.js    |   6 +-
 .../IndividualModel/ContinuousDeviance.js     |   4 +-
 .../IndividualModel/ContinuousSummary.js      |   6 +-
 .../ContinuousTestOfInterest.js               |   4 +-
 .../IndividualModel/DichotomousDeviance.js    |   4 +-
 .../IndividualModel/DichotomousSummary.js     |   6 +-
 .../components/IndividualModel/GoodnessFit.js | 172 ++++++++++++------
 .../IndividualModel/ModelDetailModal.js       |   4 +-
 .../IndividualModel/ModelOptionsTable.js      |  11 +-
 .../IndividualModel/ModelParameters.js        | 109 +++++------
 .../Main/OptionsForm/OptionsFormList.js       |  16 +-
 .../components/Output/BayesianResultTable.js  |   6 +-
 .../Output/FrequentistResultTable.js          |   6 +-
 .../Output/Multitumor/AnalysisOfDeviance.js   |   4 +-
 .../Output/Multitumor/ModelOptions.js         |   2 +-
 .../components/Output/Multitumor/MsCombo.js   |   5 +-
 .../Output/Multitumor/ResultTable.js          |   6 +-
 .../components/Output/Multitumor/Summary.js   |   7 +-
 .../NestedDichotomous/BootstrapResults.js     |   4 +-
 .../Output/NestedDichotomous/Summary.js       |   4 +-
 .../src/components/Output/OptionSetTable.js   |  32 ++--
 frontend/src/components/Output/Output.js      |  10 +-
 frontend/src/constants/logicConstants.js      |   8 +-
 frontend/src/constants/modelConstants.js      |   2 +-
 frontend/src/constants/optionsConstants.js    |   3 +
 frontend/src/constants/plotting.js            |   4 +-
 tests/analysis/test_executor.py               |   6 +-
 tests/analysis/test_validators.py             |   2 +-
 tests/data/db.yaml                            |  14 +-
 tests/integration/test_integration.py         |   4 +-
 30 files changed, 276 insertions(+), 195 deletions(-)

diff --git a/frontend/src/components/IndividualModel/CDFTable.js b/frontend/src/components/IndividualModel/CDFTable.js
index 38362208..d06069e3 100644
--- a/frontend/src/components/IndividualModel/CDFTable.js
+++ b/frontend/src/components/IndividualModel/CDFTable.js
@@ -11,12 +11,16 @@ class CDFTable extends Component {
         const {bmd_dist} = this.props;
         return (
             <table className="table table-sm table-bordered text-right">
+                <colgroup>
+                    <col width="50%" />
+                    <col width="50%" />
+                </colgroup>
                 <thead>
                     <tr className="bg-custom text-left">
                         <th colSpan="2">CDF</th>
                     </tr>
                     <tr>
-                        <th>Percentile</th>
+                        <th>Cumulative Probability</th>
                         <th>BMD</th>
                     </tr>
                 </thead>
diff --git a/frontend/src/components/IndividualModel/ContinuousDeviance.js b/frontend/src/components/IndividualModel/ContinuousDeviance.js
index e5c30b04..6c692bef 100644
--- a/frontend/src/components/IndividualModel/ContinuousDeviance.js
+++ b/frontend/src/components/IndividualModel/ContinuousDeviance.js
@@ -20,11 +20,11 @@ class ContinuousDeviance extends Component {
                 </colgroup>
                 <thead>
                     <tr className="bg-custom">
-                        <th colSpan="9">Likelihoods of Interest</th>
+                        <th colSpan="9">Likelihoods</th>
                     </tr>
                     <tr>
                         <th>Model</th>
-                        <th>Log Likelihood</th>
+                        <th>-2* Log(Likelihood Ratio)</th>
                         <th># of Parameters</th>
                         <th>AIC</th>
                     </tr>
diff --git a/frontend/src/components/IndividualModel/ContinuousSummary.js b/frontend/src/components/IndividualModel/ContinuousSummary.js
index 58acba62..30d211b5 100644
--- a/frontend/src/components/IndividualModel/ContinuousSummary.js
+++ b/frontend/src/components/IndividualModel/ContinuousSummary.js
@@ -19,7 +19,7 @@ class ContinuousSummary extends Component {
                 </colgroup>
                 <thead>
                     <tr className="bg-custom">
-                        <th colSpan="2">Summary</th>
+                        <th colSpan="2">Modeling Summary</th>
                     </tr>
                 </thead>
                 <tbody>
@@ -40,7 +40,7 @@ class ContinuousSummary extends Component {
                         <td>{ff(results.fit.aic)}</td>
                     </tr>
                     <tr>
-                        <td>Log Likelihood</td>
+                        <td>-2* Log(Likelihood Ratio)</td>
                         <td>{ff(results.fit.loglikelihood)}</td>
                     </tr>
                     <tr>
@@ -50,7 +50,7 @@ class ContinuousSummary extends Component {
                         <td>{fractionalFormatter(p_value)}</td>
                     </tr>
                     <tr>
-                        <td>Model DOF</td>
+                        <td>Model d.f.</td>
                         <td>{ff(results.tests.dfs[3])}</td>
                     </tr>
                 </tbody>
diff --git a/frontend/src/components/IndividualModel/ContinuousTestOfInterest.js b/frontend/src/components/IndividualModel/ContinuousTestOfInterest.js
index 6afca764..5058773a 100644
--- a/frontend/src/components/IndividualModel/ContinuousTestOfInterest.js
+++ b/frontend/src/components/IndividualModel/ContinuousTestOfInterest.js
@@ -22,7 +22,7 @@ class ContinuousTestOfInterest extends Component {
                 </colgroup>
                 <thead>
                     <tr className="bg-custom text-left">
-                        <th colSpan="4">Test of Interest</th>
+                        <th colSpan="4">Tests of Mean and Variance Fits</th>
                     </tr>
                     <tr>
                         <th>Test</th>
@@ -30,7 +30,7 @@ class ContinuousTestOfInterest extends Component {
                             LLR
                             <HelpTextPopover title="LLR" content="2 * Log(Likelihood Ratio)" />
                         </th>
-                        <th>Test DOF</th>
+                        <th>Test d.f.</th>
                         <th>
                             <i>P</i>-Value
                         </th>
diff --git a/frontend/src/components/IndividualModel/DichotomousDeviance.js b/frontend/src/components/IndividualModel/DichotomousDeviance.js
index cf7b7add..e948e2ff 100644
--- a/frontend/src/components/IndividualModel/DichotomousDeviance.js
+++ b/frontend/src/components/IndividualModel/DichotomousDeviance.js
@@ -26,10 +26,10 @@ class DichotomousDeviance extends Component {
                     </tr>
                     <tr>
                         <th>Model</th>
-                        <th>Log Likelihood</th>
+                        <th>-2* Log(Likelihood Ratio)</th>
                         <th># Parameters</th>
                         <th>Deviance</th>
-                        <th>Test DOF</th>
+                        <th>Test d.f.</th>
                         <th>
                             <i>P</i>-Value
                         </th>
diff --git a/frontend/src/components/IndividualModel/DichotomousSummary.js b/frontend/src/components/IndividualModel/DichotomousSummary.js
index b06b9aa5..be92923b 100644
--- a/frontend/src/components/IndividualModel/DichotomousSummary.js
+++ b/frontend/src/components/IndividualModel/DichotomousSummary.js
@@ -18,7 +18,7 @@ class DichotomousSummary extends Component {
                 </colgroup>
                 <thead>
                     <tr className="bg-custom">
-                        <th colSpan="2">Summary</th>
+                        <th colSpan="2">Modeling Summary</th>
                     </tr>
                 </thead>
                 <tbody>
@@ -39,7 +39,7 @@ class DichotomousSummary extends Component {
                         <td>{ff(results.fit.aic)}</td>
                     </tr>
                     <tr>
-                        <td>Log Likelihood</td>
+                        <td>-2* Log(Likelihood Ratio)</td>
                         <td>{ff(results.fit.loglikelihood)}</td>
                     </tr>
                     <tr>
@@ -49,7 +49,7 @@ class DichotomousSummary extends Component {
                         <td>{fourDecimalFormatter(results.gof.p_value)}</td>
                     </tr>
                     <tr>
-                        <td>Overall DOF</td>
+                        <td>Overall d.f.</td>
                         <td>{ff(results.gof.df)}</td>
                     </tr>
                     <tr>
diff --git a/frontend/src/components/IndividualModel/GoodnessFit.js b/frontend/src/components/IndividualModel/GoodnessFit.js
index 09ead3d8..b2413c18 100644
--- a/frontend/src/components/IndividualModel/GoodnessFit.js
+++ b/frontend/src/components/IndividualModel/GoodnessFit.js
@@ -6,87 +6,141 @@ import {Dtype} from "@/constants/dataConstants";
 import {isLognormal} from "@/constants/modelConstants";
 import {ff} from "@/utils/formatters";
 
-/* eslint-disable */
-const hdr_c_normal = [
-        "Dose", "Size", "Observed Mean", "Calculated Mean", "Estimated Mean",
-        "Observed SD", "Calculated SD", "Estimated SD", "Scaled Residual",
-    ],
-    hdr_c_lognormal = [
-        "Dose", "Size", "Observed Mean", "Calculated Median", "Estimated Median",
-        "Observed SD", "Calculated GSD", "Estimated GSD", "Scaled Residual",
-    ],
-    hdr_d = [ "Dose", "Size", "Observed", "Expected", "Estimated Probability", "Scaled Residual"];
-/* eslint-enable */
-
 @observer
 class GoodnessFit extends Component {
-    getHeaders(dtype, settings) {
-        if (dtype == Dtype.CONTINUOUS || dtype == Dtype.CONTINUOUS_INDIVIDUAL) {
-            const headers = isLognormal(settings.disttype) ? hdr_c_lognormal : hdr_c_normal;
-            return [headers, [10, 10, 10, 12, 12, 12, 10, 12, 12]];
-        }
-        if (dtype == Dtype.DICHOTOMOUS) {
-            return [hdr_d, [17, 16, 16, 17, 17, 17]];
-        }
-        throw Error("Unknown dtype");
+    getDichotomousData() {
+        const {store} = this.props,
+            gof = store.modalModel.results.gof,
+            dataset = store.selectedDataset;
+        return {
+            headers: [
+                "Dose",
+                "N",
+                "Observed",
+                "Expected",
+                "Estimated Probability",
+                "Scaled Residual",
+            ],
+            colwidths: [17, 16, 16, 17, 17, 17],
+            data: dataset.doses.map((dose, i) => {
+                return [
+                    dose,
+                    dataset.ns[i],
+                    dataset.incidences[i],
+                    ff(gof.expected[i]),
+                    ff(gof.expected[i] / dataset.ns[i]),
+                    ff(gof.residual[i]),
+                ];
+            }),
+        };
+    }
+
+    getContinuousNormalData(dtype) {
+        const {store} = this.props,
+            gof = store.modalModel.results.gof,
+            dataset = store.selectedDataset,
+            useFF = dtype === Dtype.CONTINUOUS_INDIVIDUAL;
+        return {
+            headers: [
+                "Dose",
+                "N",
+                "Sample Mean",
+                "Model Fitted Mean",
+                "Sample SD",
+                "Model Fitted SD",
+                "Scaled Residual",
+            ],
+            colwidths: [1, 1, 1, 1, 1, 1, 1],
+            data: dataset.doses.map((dose, i) => {
+                return [
+                    dose,
+                    dataset.ns[i],
+                    useFF ? ff(gof.obs_mean[i]) : gof.obs_mean[i],
+                    ff(gof.est_mean[i]),
+                    useFF ? ff(gof.obs_sd[i]) : gof.obs_sd[i],
+                    ff(gof.est_sd[i]),
+                    ff(gof.residual[i]),
+                ];
+            }),
+        };
+    }
+
+    getContinuousLognormalData(dtype) {
+        const {store} = this.props,
+            gof = store.modalModel.results.gof,
+            dataset = store.selectedDataset,
+            useFF = dtype === Dtype.CONTINUOUS_INDIVIDUAL;
+        return {
+            headers: [
+                "Dose",
+                "N",
+                "Sample Mean",
+                "Approximate Sample Median",
+                "Model Fitted Median",
+                "Sample SD",
+                "Approximate Sample GSD",
+                "Model Fitted GSD",
+                "Scaled Residual",
+            ],
+            colwidths: [10, 10, 10, 12, 12, 12, 10, 12, 12],
+            data: dataset.doses.map((dose, i) => {
+                return [
+                    dose,
+                    dataset.ns[i],
+                    useFF ? ff(gof.obs_mean[i]) : gof.obs_mean[i],
+                    ff(gof.calc_mean[i]),
+                    ff(gof.est_mean[i]),
+                    useFF ? ff(gof.obs_sd[i]) : gof.obs_sd[i],
+                    ff(gof.calc_mean[i]),
+                    ff(gof.est_sd[i]),
+                    ff(gof.residual[i]),
+                ];
+            }),
+        };
     }
     render() {
         const {store} = this.props,
             settings = store.modalModel.settings,
-            gof = store.modalModel.results.gof,
             dataset = store.selectedDataset,
-            {dtype} = dataset,
-            headers = this.getHeaders(dtype, settings);
+            {dtype} = dataset;
+
+        let data;
+        if (dtype == Dtype.DICHOTOMOUS) {
+            data = this.getDichotomousData();
+        } else {
+            if (isLognormal(settings.disttype)) {
+                data = this.getContinuousLognormalData(dtype);
+            } else {
+                data = this.getContinuousNormalData(dtype);
+            }
+        }
         return (
             <table className="table table-sm table-bordered text-right">
                 <colgroup>
-                    {headers[1].map((d, i) => (
+                    {data.colwidths.map((d, i) => (
                         <col key={i} width={`${d}%`} />
                     ))}
                 </colgroup>
                 <thead>
                     <tr className="bg-custom text-left">
-                        <th colSpan={headers[0].length}>Goodness of Fit</th>
+                        <th colSpan={data.headers.length}>Goodness of Fit</th>
                     </tr>
                     <tr>
-                        {headers[0].map((d, i) => (
+                        {data.headers.map((d, i) => (
                             <th key={i}>{d}</th>
                         ))}
                     </tr>
                 </thead>
                 <tbody>
-                    {dtype == Dtype.CONTINUOUS || dtype == Dtype.CONTINUOUS_INDIVIDUAL
-                        ? gof.dose.map((item, i) => {
-                              const useFF = dtype === Dtype.CONTINUOUS_INDIVIDUAL;
-                              return (
-                                  <tr key={i}>
-                                      <td>{item}</td>
-                                      <td>{gof.size[i]}</td>
-                                      <td>{useFF ? ff(gof.obs_mean[i]) : gof.obs_mean[i]}</td>
-                                      <td>{ff(gof.calc_mean[i])}</td>
-                                      <td>{ff(gof.est_mean[i])}</td>
-                                      <td>{useFF ? ff(gof.obs_sd[i]) : gof.obs_sd[i]}</td>
-                                      <td>{ff(gof.calc_sd[i])}</td>
-                                      <td>{ff(gof.est_sd[i])}</td>
-                                      <td>{ff(gof.residual[i])}</td>
-                                  </tr>
-                              );
-                          })
-                        : null}
-                    {dtype == Dtype.DICHOTOMOUS
-                        ? dataset.doses.map((dose, i) => {
-                              return (
-                                  <tr key={i}>
-                                      <td>{dose}</td>
-                                      <td>{dataset.ns[i]}</td>
-                                      <td>{dataset.incidences[i]}</td>
-                                      <td>{ff(gof.expected[i])}</td>
-                                      <td>{ff(gof.expected[i] / dataset.ns[i])}</td>
-                                      <td>{ff(gof.residual[i])}</td>
-                                  </tr>
-                              );
-                          })
-                        : null}
+                    {data.data.map((row, i) => {
+                        return (
+                            <tr key={i}>
+                                {row.map((cell, j) => (
+                                    <td key={j}>{cell}</td>
+                                ))}
+                            </tr>
+                        );
+                    })}
                 </tbody>
             </table>
         );
diff --git a/frontend/src/components/IndividualModel/ModelDetailModal.js b/frontend/src/components/IndividualModel/ModelDetailModal.js
index 85f985bc..e58e2e78 100644
--- a/frontend/src/components/IndividualModel/ModelDetailModal.js
+++ b/frontend/src/components/IndividualModel/ModelDetailModal.js
@@ -69,7 +69,7 @@ class ModelBody extends Component {
                     </Col>
                 </Row>
                 <Row>
-                    <Col xl={isDichotomous ? 8 : 10}>
+                    <Col xl={isDichotomous ? 8 : 12}>
                         <GoodnessFit store={outputStore} />
                     </Col>
                 </Row>
@@ -82,7 +82,7 @@ class ModelBody extends Component {
                 ) : null}
                 {isContinuous ? (
                     <Row>
-                        <Col xl={6}>
+                        <Col xl={8}>
                             <ContinuousDeviance store={outputStore} />
                             <ContinuousTestOfInterest store={outputStore} />
                         </Col>
diff --git a/frontend/src/components/IndividualModel/ModelOptionsTable.js b/frontend/src/components/IndividualModel/ModelOptionsTable.js
index 690a7fe4..b60a2994 100644
--- a/frontend/src/components/IndividualModel/ModelOptionsTable.js
+++ b/frontend/src/components/IndividualModel/ModelOptionsTable.js
@@ -6,6 +6,7 @@ import {getLabel} from "@/common";
 import TwoColumnTable from "@/components/common/TwoColumnTable";
 import {Dtype} from "@/constants/dataConstants";
 import {hasDegrees} from "@/constants/modelConstants";
+import {isHybridBmr} from "@/constants/optionsConstants";
 import {
     continuousBmrOptions,
     dichotomousBmrOptions,
@@ -39,7 +40,7 @@ class ModelOptionsTable extends Component {
             data = [
                 ["BMR Type", getLabel(model.settings.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(model.settings.bmr)],
-                ["Confidence Level", ff(1 - model.settings.alpha)],
+                ["Confidence Level (one sided)", ff(1 - model.settings.alpha)],
                 hasDegrees.has(model.model_class.verbose)
                     ? ["Degree", ff(model.settings.degree)]
                     : null,
@@ -53,8 +54,10 @@ class ModelOptionsTable extends Component {
                 ["BMRF", ff(model.settings.bmr)],
                 ["Distribution Type", getLabel(model.settings.disttype, distTypeOptions)],
                 ["Direction", model.settings.is_increasing ? "Up" : "Down"],
-                ["Confidence Level", 1 - ff(model.settings.alpha)],
-                ["Tail Probability", ff(model.settings.tail_prob)],
+                ["Confidence Level (one sided)", 1 - ff(model.settings.alpha)],
+                isHybridBmr(model.settings.bmr_type)
+                    ? ["Tail Probability", ff(model.settings.tail_prob)]
+                    : null,
                 hasDegrees.has(model.model_class.verbose)
                     ? ["Degree", ff(model.settings.degree)]
                     : null,
@@ -66,7 +69,7 @@ class ModelOptionsTable extends Component {
             data = [
                 ["BMR Type", getLabel(model.settings.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(model.settings.bmr)],
-                ["Confidence Level", ff(1 - model.settings.alpha)],
+                ["Confidence Level (one sided)", ff(1 - model.settings.alpha)],
                 ["Bootstrap Seed", model.settings.bootstrap_seed],
                 ["Bootstrap Iterations", model.settings.bootstrap_iterations],
                 [
diff --git a/frontend/src/components/IndividualModel/ModelParameters.js b/frontend/src/components/IndividualModel/ModelParameters.js
index d70b58e8..49dcf060 100644
--- a/frontend/src/components/IndividualModel/ModelParameters.js
+++ b/frontend/src/components/IndividualModel/ModelParameters.js
@@ -10,60 +10,65 @@ import {parameterFormatter} from "@/utils/formatters";
 class ModelParameters extends Component {
     render() {
         const {parameters} = this.props,
-            indexes = _.range(parameters.names.length);
+            indexes = _.range(parameters.names.length),
+            anyBounded = _.sum(parameters.bounded) > 0;
 
         return (
-            <table className="table table-sm table-bordered text-right col-l-1">
-                <colgroup>
-                    <col width="20%" />
-                    <col width="20%" />
-                    <col width="20%" />
-                    <col width="20%" />
-                    <col width="20%" />
-                </colgroup>
-                <thead>
-                    <tr className="bg-custom">
-                        <th colSpan="5">Model Parameters</th>
-                    </tr>
-                    <tr>
-                        <th>Variable</th>
-                        <th>Estimate</th>
-                        <th>Standard Error</th>
-                        <th>Lower Confidence</th>
-                        <th>Upper Confidence</th>
-                    </tr>
-                </thead>
-                <tbody>
-                    {indexes.map(i => {
-                        const bounded = parameters.bounded[i];
-                        return (
-                            <tr key={i}>
-                                <td>{parameters.names[i]}</td>
-                                <td>
-                                    {bounded ? (
-                                        <>
-                                            <span>Bounded</span>
-                                            <HelpTextPopover
-                                                title="Bounded"
-                                                content={`The value of this parameter, ${parameters.values[i]}, is within the tolerance of the bound`}
-                                            />
-                                        </>
-                                    ) : (
-                                        parameterFormatter(parameters.values[i])
-                                    )}
-                                </td>
-                                <td>{bounded ? "NA" : parameterFormatter(parameters.se[i])}</td>
-                                <td>
-                                    {bounded ? "NA" : parameterFormatter(parameters.lower_ci[i])}
-                                </td>
-                                <td>
-                                    {bounded ? "NA" : parameterFormatter(parameters.upper_ci[i])}
-                                </td>
-                            </tr>
-                        );
-                    })}
-                </tbody>
-            </table>
+            <>
+                <table className="table table-sm table-bordered text-right col-l-1">
+                    <colgroup>
+                        <col width="34%" />
+                        <col width="33%" />
+                        <col width="33%" />
+                    </colgroup>
+                    <thead>
+                        <tr className="bg-custom">
+                            <th colSpan="3">Model Parameters</th>
+                        </tr>
+                        <tr>
+                            <th>Variable</th>
+                            <th>Estimate</th>
+                            <th>Standard Error</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+                        {indexes.map(i => {
+                            const bounded = parameters.bounded[i];
+                            return (
+                                <tr key={i}>
+                                    <td>{parameters.names[i]}</td>
+                                    <td>
+                                        {bounded ? (
+                                            <>
+                                                <span>On Bound</span>
+                                                <HelpTextPopover
+                                                    title="On Bound"
+                                                    content={`The value of this parameter, ${parameters.values[i]}, is within the tolerance of the bound`}
+                                                />
+                                            </>
+                                        ) : (
+                                            parameterFormatter(parameters.values[i])
+                                        )}
+                                    </td>
+                                    <td>
+                                        {bounded
+                                            ? "Not Reported"
+                                            : parameterFormatter(parameters.se[i])}
+                                    </td>
+                                </tr>
+                            );
+                        })}
+                    </tbody>
+                </table>
+                {anyBounded ? (
+                    <p>
+                        Standard errors estimates are not generated for parameters estimated on
+                        corresponding bounds, although sampling error is present for all parameters,
+                        as a rule. Standard error estimates may not be reliable as a basis for
+                        confidence intervals or tests when one or more parameters are on bounds.
+                    </p>
+                ) : null}
+            </>
         );
     }
 }
diff --git a/frontend/src/components/Main/OptionsForm/OptionsFormList.js b/frontend/src/components/Main/OptionsForm/OptionsFormList.js
index 075cafea..83ed7b14 100644
--- a/frontend/src/components/Main/OptionsForm/OptionsFormList.js
+++ b/frontend/src/components/Main/OptionsForm/OptionsFormList.js
@@ -23,7 +23,8 @@ class OptionsFormList extends Component {
             modelType = optionsStore.getModelType,
             optionsList = toJS(optionsStore.optionsList),
             distTypeHelpText =
-                "If lognormal is selected, only the Exponential and Hill models can be executed. Other models will be removed during the execution process and will not be shown in the outputs.";
+                "If lognormal is selected, only the Exponential and Hill models can be executed. Other models will be removed during the execution process and will not be shown in the outputs.",
+            tailProbabilityHelpText = "Only used for Hybrid models.";
         return (
             <div>
                 <div className="panel panel-default">
@@ -36,8 +37,13 @@ class OptionsFormList extends Component {
                                         <>
                                             <th>BMR Type</th>
                                             <th>BMRF</th>
-                                            <th>Tail Probability</th>
-                                            <th>Confidence Level</th>
+                                            <th>
+                                                Tail Probability
+                                                <HelpTextPopover
+                                                    content={tailProbabilityHelpText}
+                                                />
+                                            </th>
+                                            <th>Confidence Level (one sided)</th>
                                             <th>
                                                 Distribution +<br />
                                                 Variance&nbsp;
@@ -50,14 +56,14 @@ class OptionsFormList extends Component {
                                         <>
                                             <th>Risk Type</th>
                                             <th>BMR</th>
-                                            <th>Confidence Level</th>
+                                            <th>Confidence Level (one sided)</th>
                                         </>
                                     ) : null}
                                     {modelType === MODEL_NESTED_DICHOTOMOUS ? (
                                         <>
                                             <th>Risk Type</th>
                                             <th>BMR</th>
-                                            <th>Confidence Level</th>
+                                            <th>Confidence Level (one sided)</th>
                                             <th>
                                                 Litter Specific
                                                 <br />
diff --git a/frontend/src/components/Output/BayesianResultTable.js b/frontend/src/components/Output/BayesianResultTable.js
index b71d7eaa..d41fd1a8 100644
--- a/frontend/src/components/Output/BayesianResultTable.js
+++ b/frontend/src/components/Output/BayesianResultTable.js
@@ -36,8 +36,8 @@ class BayesianResultTable extends Component {
                         <th>BMD</th>
                         <th>BMDU</th>
                         <th>Unnormalized Log Posterior Probability</th>
-                        <th>Scaled Residual for Dose Group near BMD</th>
-                        <th>Scaled Residual for Control Dose Group</th>
+                        <th>Scaled Residual at Control</th>
+                        <th>Scaled Residual near BMD</th>
                     </tr>
                 </thead>
                 <tbody className="table-bordered">
@@ -63,8 +63,8 @@ class BayesianResultTable extends Component {
                                 <td>{ff(model.results.bmd)}</td>
                                 <td>{ff(model.results.bmdu)}</td>
                                 <td>{ff(model.results.fit.bic_equiv)}</td>
-                                <td>{ff(model.results.gof.roi)}</td>
                                 <td>{ff(model.results.gof.residual[0])}</td>
+                                <td>{ff(model.results.gof.roi)}</td>
                             </tr>
                         );
                     })}
diff --git a/frontend/src/components/Output/FrequentistResultTable.js b/frontend/src/components/Output/FrequentistResultTable.js
index ae3a8bdc..4420415c 100644
--- a/frontend/src/components/Output/FrequentistResultTable.js
+++ b/frontend/src/components/Output/FrequentistResultTable.js
@@ -242,8 +242,8 @@ class FrequentistRow extends Component {
                 <td>{ff(results.bmdu)}</td>
                 <td>{fractionalFormatter(getPValue(dataset.dtype, results))}</td>
                 <td>{ff(results.fit.aic)}</td>
-                <td>{ff(results.gof.roi)}</td>
                 <td>{ff(results.gof.residual[0])}</td>
+                <td>{ff(results.gof.roi)}</td>
                 <RecommendationTd
                     store={store}
                     data={data}
@@ -326,8 +326,8 @@ class FrequentistResultTable extends Component {
                             <i>P</i>-Value
                         </th>
                         <th>AIC</th>
-                        <th>Scaled Residual for Dose Group near BMD</th>
-                        <th>Scaled Residual for Control Dose Group</th>
+                        <th>Scaled Residual at Control</th>
+                        <th>Scaled Residual near BMD</th>
                         {store.recommendationEnabled ? (
                             <th>
                                 <Button
diff --git a/frontend/src/components/Output/Multitumor/AnalysisOfDeviance.js b/frontend/src/components/Output/Multitumor/AnalysisOfDeviance.js
index 4209aad4..90435817 100644
--- a/frontend/src/components/Output/Multitumor/AnalysisOfDeviance.js
+++ b/frontend/src/components/Output/Multitumor/AnalysisOfDeviance.js
@@ -24,10 +24,10 @@ class AnalysisOfDeviance extends Component {
                     </tr>
                     <tr>
                         <th>Model</th>
-                        <th>Log Likelihood</th>
+                        <th>-2* Log(Likelihood Ratio)</th>
                         <th># Parameters</th>
                         <th>Deviance</th>
-                        <th>Test DOF</th>
+                        <th>Test d.f.</th>
                         <th>
                             <i>P</i>-Value
                         </th>
diff --git a/frontend/src/components/Output/Multitumor/ModelOptions.js b/frontend/src/components/Output/Multitumor/ModelOptions.js
index b073ac4b..85a34715 100644
--- a/frontend/src/components/Output/Multitumor/ModelOptions.js
+++ b/frontend/src/components/Output/Multitumor/ModelOptions.js
@@ -18,7 +18,7 @@ class ModelOptions extends Component {
             data = [
                 ["Risk Type", getLabel(options.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(options.bmr_value)],
-                ["Confidence Level", ff(options.confidence_level)],
+                ["Confidence Level (one sided)", ff(options.confidence_level)],
                 ["Degree", degree],
             ];
         return <TwoColumnTable data={data} label="Model Options" />;
diff --git a/frontend/src/components/Output/Multitumor/MsCombo.js b/frontend/src/components/Output/Multitumor/MsCombo.js
index 5038617d..16b364ac 100644
--- a/frontend/src/components/Output/Multitumor/MsCombo.js
+++ b/frontend/src/components/Output/Multitumor/MsCombo.js
@@ -16,7 +16,7 @@ class MsComboInfo extends Component {
                 ["Model", "Multitumor"],
                 ["Risk Type", getLabel(options.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(options.bmr_value)],
-                ["Confidence Level", ff(options.confidence_level)],
+                ["Confidence Level (one sided)", ff(options.confidence_level)],
             ];
         return <TwoColumnTable data={data} label={label} />;
     }
@@ -29,7 +29,6 @@ MsComboInfo.propTypes = {
 class MsComboSummary extends Component {
     render() {
         const {results} = this.props,
-            label = "Summary",
             data = [
                 ["BMD", ff(results.bmd)],
                 ["BMDL", ff(results.bmdl)],
@@ -38,7 +37,7 @@ class MsComboSummary extends Component {
                 ["Combined Log-Likelihood", ff(results.ll)],
                 ["Combined Log-Likelihood Constant", ff(results.ll_constant)],
             ];
-        return <TwoColumnTable data={data} label={label} />;
+        return <TwoColumnTable data={data} label={"Modeling Summary"} colwidths={[40, 60]} />;
     }
 }
 MsComboSummary.propTypes = {
diff --git a/frontend/src/components/Output/Multitumor/ResultTable.js b/frontend/src/components/Output/Multitumor/ResultTable.js
index ce51c436..7d1255e6 100644
--- a/frontend/src/components/Output/Multitumor/ResultTable.js
+++ b/frontend/src/components/Output/Multitumor/ResultTable.js
@@ -41,8 +41,8 @@ class ResultTable extends Component {
                             <i>P</i>-Value
                         </th>
                         <th>AIC</th>
-                        <th>Scaled Residual for Dose Group near BMD</th>
-                        <th>Scaled Residual for Control Dose Group</th>
+                        <th>Scaled Residual at Control</th>
+                        <th>Scaled Residual near BMD</th>
                     </tr>
                 </thead>
                 <tbody className="table-bordered">
@@ -101,8 +101,8 @@ class ResultTable extends Component {
                                             <td>{ff(model.slope_factor)}</td>
                                             <td>{ff(model.gof.p_value)}</td>
                                             <td>{ff(model.fit.aic)}</td>
-                                            <td>{ff(model.gof.roi)}</td>
                                             <td>{ff(model.gof.residual[0])}</td>
+                                            <td>{ff(model.gof.roi)}</td>
                                         </tr>
                                     );
                                 })
diff --git a/frontend/src/components/Output/Multitumor/Summary.js b/frontend/src/components/Output/Multitumor/Summary.js
index 244f2d09..11db6689 100644
--- a/frontend/src/components/Output/Multitumor/Summary.js
+++ b/frontend/src/components/Output/Multitumor/Summary.js
@@ -21,10 +21,11 @@ class Summary extends Component {
                     </span>,
                     fourDecimalFormatter(model.gof.p_value),
                 ],
-                ["Overall DOF", ff(model.gof.df)],
-                ["Chi²", ff(model.fit.chisq)][("Log Likelihood", ff(model.fit.loglikelihood))],
+                ["Overall d.f.", ff(model.gof.df)],
+                ["Chi²", ff(model.fit.chisq)],
+                ["-2* Log(Likelihood Ratio)", ff(model.fit.loglikelihood)],
             ];
-        return <TwoColumnTable data={data} label="Summary" />;
+        return <TwoColumnTable data={data} label="Modeling Summary" />;
     }
 }
 
diff --git a/frontend/src/components/Output/NestedDichotomous/BootstrapResults.js b/frontend/src/components/Output/NestedDichotomous/BootstrapResults.js
index 0b396ac2..ba2b74ff 100644
--- a/frontend/src/components/Output/NestedDichotomous/BootstrapResults.js
+++ b/frontend/src/components/Output/NestedDichotomous/BootstrapResults.js
@@ -12,7 +12,7 @@ class BootstrapResult extends Component {
             data = [
                 ["# Iterations", settings.bootstrap_iterations],
                 ["Bootstrap Seed", ff(settings.bootstrap_seed)],
-                ["Log-likelihood", ff(results.ll)],
+                ["-2* Log(Likelihood Ratio)", ff(results.ll)],
                 ["Observed Chi-square", ff(results.obs_chi_sq)],
                 [
                     <span key={0}>
@@ -21,7 +21,7 @@ class BootstrapResult extends Component {
                     ff(results.combined_pvalue),
                 ],
             ];
-        return <TwoColumnTable data={data} label="Bootstrap Results" />;
+        return <TwoColumnTable data={data} label="Bootstrap Results" colwidths={[40, 60]} />;
     }
 }
 BootstrapResult.propTypes = {
diff --git a/frontend/src/components/Output/NestedDichotomous/Summary.js b/frontend/src/components/Output/NestedDichotomous/Summary.js
index 9589f6f1..76432a88 100644
--- a/frontend/src/components/Output/NestedDichotomous/Summary.js
+++ b/frontend/src/components/Output/NestedDichotomous/Summary.js
@@ -20,7 +20,7 @@ class Summary extends Component {
                     </span>,
                     ff(results.combined_pvalue),
                 ],
-                ["D.O.F.", ff(results.dof)],
+                ["d.f.", ff(results.dof)],
                 [
                     <span key={1}>
                         Chi<sup>2</sup>
@@ -28,7 +28,7 @@ class Summary extends Component {
                     ff(results.summary.chi_squared),
                 ],
             ];
-        return <TwoColumnTable label="Summary" data={data} />;
+        return <TwoColumnTable label="Modeling Summary" data={data} />;
     }
 }
 Summary.propTypes = {
diff --git a/frontend/src/components/Output/OptionSetTable.js b/frontend/src/components/Output/OptionSetTable.js
index 5f9f9275..68e66f95 100644
--- a/frontend/src/components/Output/OptionSetTable.js
+++ b/frontend/src/components/Output/OptionSetTable.js
@@ -1,3 +1,4 @@
+import _ from "lodash";
 import {inject, observer} from "mobx-react";
 import PropTypes from "prop-types";
 import React, {Component} from "react";
@@ -11,6 +12,7 @@ import {
     distTypeOptions,
     litterSpecificCovariateOptions,
 } from "@/constants/optionsConstants";
+import {isHybridBmr} from "@/constants/optionsConstants";
 import {ff} from "@/utils/formatters";
 
 @inject("outputStore")
@@ -34,14 +36,16 @@ class OptionSetTable extends Component {
                     "Maximum Polynomial Degree",
                     getLabel(selectedDatasetOptions.degree, allDegreeOptions),
                 ],
-                ["Tail Probability", ff(selectedModelOptions.tail_probability)],
-                ["Confidence Level", ff(selectedModelOptions.confidence_level)],
+                isHybridBmr(selectedModelOptions.bmr_type)
+                    ? ["Tail Probability", ff(selectedModelOptions.tail_probability)]
+                    : null,
+                ["Confidence Level (one sided)", ff(selectedModelOptions.confidence_level)],
             ];
         } else if (getModelType === MODEL_DICHOTOMOUS) {
             rows = [
                 ["BMR Type", getLabel(selectedModelOptions.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(selectedModelOptions.bmr_value)],
-                ["Confidence Level", ff(selectedModelOptions.confidence_level)],
+                ["Confidence Level (one sided)", ff(selectedModelOptions.confidence_level)],
                 [
                     "Maximum Multistage Degree",
                     getLabel(selectedDatasetOptions.degree, allDegreeOptions),
@@ -51,7 +55,7 @@ class OptionSetTable extends Component {
             rows = [
                 ["BMR Type", getLabel(selectedModelOptions.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(selectedModelOptions.bmr_value)],
-                ["Confidence Level", ff(selectedModelOptions.confidence_level)],
+                ["Confidence Level (one sided)", ff(selectedModelOptions.confidence_level)],
                 ["Bootstrap Seed", selectedModelOptions.bootstrap_seed],
                 ["Bootstrap Iterations", selectedModelOptions.bootstrap_iterations],
                 [
@@ -66,7 +70,7 @@ class OptionSetTable extends Component {
             rows = [
                 ["BMR Type", getLabel(selectedModelOptions.bmr_type, dichotomousBmrOptions)],
                 ["BMR", ff(selectedModelOptions.bmr_value)],
-                ["Confidence Level", ff(selectedModelOptions.confidence_level)],
+                ["Confidence Level (one sided)", ff(selectedModelOptions.confidence_level)],
                 ["Degree Setting", outputStore.multitumorDegreeInputSettings.join(", ")],
             ];
         } else {
@@ -83,14 +87,16 @@ class OptionSetTable extends Component {
                         <col width="40%" />
                     </colgroup>
                     <tbody>
-                        {rows.map((d, i) => {
-                            return (
-                                <tr key={i}>
-                                    <th className="bg-custom">{d[0]}</th>
-                                    <td>{d[1]}</td>
-                                </tr>
-                            );
-                        })}
+                        {rows
+                            .filter(d => !_.isNull(d))
+                            .map((d, i) => {
+                                return (
+                                    <tr key={i}>
+                                        <th className="bg-custom">{d[0]}</th>
+                                        <td>{d[1]}</td>
+                                    </tr>
+                                );
+                            })}
                     </tbody>
                 </table>
             </>
diff --git a/frontend/src/components/Output/Output.js b/frontend/src/components/Output/Output.js
index f3aea0b9..f87c1085 100644
--- a/frontend/src/components/Output/Output.js
+++ b/frontend/src/components/Output/Output.js
@@ -81,7 +81,7 @@ class Output extends Component {
 
         return (
             <div className="container-fluid mb-3">
-                <div className="row">
+                <div className="row py-2">
                     {outputStore.outputs.length > 1 ? (
                         <div className="col-lg-2">
                             <SelectInput
@@ -117,9 +117,9 @@ class Output extends Component {
                             </div>
                         </div>
                     ) : (
-                        <div className="row">
+                        <div className="row py-2">
                             <div className="col-lg-8">
-                                <h4>Frequentist Model Results</h4>
+                                <h4>Maximum Likelihood Approach Model Results</h4>
                                 <FrequentistResultTable />
                                 {canEdit ? <SelectModel /> : null}
                             </div>
@@ -134,7 +134,7 @@ class Output extends Component {
                     )
                 ) : null}
                 {selectedBayesian ? (
-                    <div className="row">
+                    <div className="row py-2">
                         <div className="col-lg-12">
                             <h4>Bayesian Model Results</h4>
                             <BayesianResultTable />
@@ -150,7 +150,7 @@ class Output extends Component {
                 ) : null}
 
                 {isFuture && !outputStore.isMultiTumor ? (
-                    <div className="row">
+                    <div className="row py-2">
                         {selectedFrequentist ? (
                             <div className="col col-lg-6">
                                 <DoseResponsePlot
diff --git a/frontend/src/constants/logicConstants.js b/frontend/src/constants/logicConstants.js
index 9cb9dd62..8b184a96 100644
--- a/frontend/src/constants/logicConstants.js
+++ b/frontend/src/constants/logicConstants.js
@@ -136,15 +136,15 @@ export const RULES = Object.freeze({
             enabledNested: true,
         },
         [RULES.ROI_LARGE]: {
-            name: "Abs(Residual of interest) too large",
-            notes: val => `|Residual for Dose Group Near BMD| > ${val}`,
+            name: "|Residual near BMD| too large",
+            notes: val => `|Residual Near BMD| > ${val}`,
             hasThreshold: true,
             enabledContinuous: true,
             enabledDichotomous: true,
             enabledNested: true,
         },
         [RULES.WARNINGS]: {
-            name: "BMDS model Warning",
+            name: "BMDS model warning",
             notes: val => "BMD output file included warning",
             hasThreshold: false,
             enabledContinuous: false,
@@ -200,7 +200,7 @@ export const RULES = Object.freeze({
             enabledNested: true,
         },
         [RULES.CONTROL_RESIDUAL_HIGH]: {
-            name: "Abs(Residual at control) too large",
+            name: "|Residual at control| too large",
             notes: val => `|Residual at control| > ${val}`,
             hasThreshold: true,
             enabledContinuous: true,
diff --git a/frontend/src/constants/modelConstants.js b/frontend/src/constants/modelConstants.js
index 35dfcc18..4fda10c1 100644
--- a/frontend/src/constants/modelConstants.js
+++ b/frontend/src/constants/modelConstants.js
@@ -78,7 +78,7 @@ const modelsList = {
     hasDegrees = new Set(["Multistage", "Polynomial"]),
     getNameFromDegrees = function(model) {
         const degree = model.parameters.names.length - 1;
-        return `Multistage ${degree}°`;
+        return `Multistage ${degree}`;
     };
 
 export {allModelOptions, getNameFromDegrees, hasDegrees, isLognormal, models, modelsList};
diff --git a/frontend/src/constants/optionsConstants.js b/frontend/src/constants/optionsConstants.js
index e79cf094..c471f673 100644
--- a/frontend/src/constants/optionsConstants.js
+++ b/frontend/src/constants/optionsConstants.js
@@ -44,6 +44,9 @@ export const options = {
         {value: 6, label: "Hybrid-Extra Risk"},
         {value: 7, label: "Hybrid-Added Risk"},
     ],
+    isHybridBmr = function(val) {
+        return val === 6 || val === 7;
+    },
     distTypeOptions = [
         {value: 1, label: "Normal + Constant"},
         {value: 2, label: "Normal + Non-constant"},
diff --git a/frontend/src/constants/plotting.js b/frontend/src/constants/plotting.js
index 2135a8ff..014695e0 100644
--- a/frontend/src/constants/plotting.js
+++ b/frontend/src/constants/plotting.js
@@ -112,9 +112,9 @@ export const getResponse = dataset => {
     },
     getCdfLayout = function(dataset) {
         let layout = _.cloneDeep(doseResponseLayout);
-        layout.title.text = "BMD Cumulative distribution function";
+        layout.title.text = "BMD Cumulative Distribution Function";
         layout.xaxis.title.text = getDoseLabel(dataset);
-        layout.yaxis.title.text = "Percentile";
+        layout.yaxis.title.text = "Cumulative Probability";
         layout.yaxis.range = [0, 1];
         return layout;
     },
diff --git a/tests/analysis/test_executor.py b/tests/analysis/test_executor.py
index fec5669b..8fbf25cf 100644
--- a/tests/analysis/test_executor.py
+++ b/tests/analysis/test_executor.py
@@ -169,17 +169,17 @@ def test_disttype(self, bmds3_complete_continuous):
         session = AnalysisSession.create(data, 0, 0)
         assert len(session.frequentist.models) == 5
         names = [model.name() for model in session.frequentist.models]
-        assert names == ["ExponentialM3", "ExponentialM5", "Hill", "Linear", "Power"]
+        assert names == ["Exponential 3", "Exponential 5", "Hill", "Linear", "Power"]
 
         data["options"][0]["dist_type"] = 2
         session = AnalysisSession.create(data, 0, 0)
         assert len(session.frequentist.models) == 5
         names = [model.name() for model in session.frequentist.models]
-        assert names == ["ExponentialM3", "ExponentialM5", "Hill", "Linear", "Power"]
+        assert names == ["Exponential 3", "Exponential 5", "Hill", "Linear", "Power"]
 
         # lognormal
         data["options"][0]["dist_type"] = 3
         session = AnalysisSession.create(data, 0, 0)
         assert len(session.frequentist.models) == 2
         names = [model.name() for model in session.frequentist.models]
-        assert names == ["ExponentialM3", "ExponentialM5"]
+        assert names == ["Exponential 3", "Exponential 5"]
diff --git a/tests/analysis/test_validators.py b/tests/analysis/test_validators.py
index 425e9aca..879b7573 100644
--- a/tests/analysis/test_validators.py
+++ b/tests/analysis/test_validators.py
@@ -350,7 +350,7 @@ def test_dichotomous(self, bmds3_complete_dichotomous):
         # check incidence > n
         check = deepcopy(dataset)
         check["incidences"][0] = check["ns"][0] + 1
-        with pytest.raises(PydanticValidationError, match="Incidence cannot be greater than N"):
+        with pytest.raises(PydanticValidationError, match="Incidence > N"):
             datasets.MaxDichotomousDatasetSchema(**check)
 
         # check minimums
diff --git a/tests/data/db.yaml b/tests/data/db.yaml
index e5f6cc52..4d7e79b3 100644
--- a/tests/data/db.yaml
+++ b/tests/data/db.yaml
@@ -1612,10 +1612,10 @@
               - 1
               model_notes:
               - '0':
-                - Control stdev. fit greater than threshold (2.312 > 1.5)
+                - Control stdev. fit > threshold (2.312 > 1.5)
                 '1':
-                - Goodness of fit p-value less than threshold (0 < 0.1)
-                - Abs(Residual of interest) greater than threshold (2.246 > 2.0)
+                - Goodness of fit p-value < threshold (0 < 0.1)
+                - '|Residual near BMD| > threshold (2.246 > 2.0)'
                 - Variance test failed (Test 2 p-value 2 < 0.05)
                 - Incorrect variance model (p-value 2 = 0), constant variance selected
                 '2': []
@@ -7067,8 +7067,8 @@
       2.0, "enabled_dichotomous": true, "enabled_continuous": false, "enabled_nested":
       true}]}, "results": {"recommended_model_index": null, "recommended_model_variable":
       null, "model_bin": [1], "model_notes": [{"0": ["Control stdev. fit greater than
-      threshold (2.312 > 1.5)"], "1": ["Goodness of fit p-value less than threshold
-      (0 < 0.1)", "Abs(Residual of interest) greater than threshold (2.246 > 2.0)",
+      threshold (2.312 > 1.5)"], "1": ["Goodness of fit p-value < threshold
+      (0 < 0.1)", "|Residual near BMD| > threshold (2.246 > 2.0)",
       "Constant variance test failed (p-value 2 < 0.05)", "Incorrect variance model
       (p-value 2 = 0), constant variance selected"], "2": []}]}}, "selected": {"model_index":
       null, "notes": ""}}, "bayesian": null}]}, "errors": [], "created": "2021-11-15T18:42:28.857Z",
@@ -7342,8 +7342,8 @@
       2.0, "enabled_dichotomous": true, "enabled_continuous": false, "enabled_nested":
       true}]}, "results": {"recommended_model_index": null, "recommended_model_variable":
       null, "model_bin": [1], "model_notes": [{"0": ["Control stdev. fit greater than
-      threshold (2.312 > 1.5)"], "1": ["Goodness of fit p-value less than threshold
-      (0 < 0.1)", "Abs(Residual of interest) greater than threshold (2.246 > 2.0)",
+      threshold (2.312 > 1.5)"], "1": ["Goodness of fit p-value < threshold
+      (0 < 0.1)", "|Residual near BMD| > threshold (2.246 > 2.0)",
       "Constant variance test failed (p-value 2 < 0.05)", "Incorrect variance model
       (p-value 2 = 0), constant variance selected"], "2": []}]}}, "selected": {"model_index":
       null, "notes": "No best fitting model selected"}}, "bayesian": null}], "analysis_id":
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
index e435ce92..a05acc87 100644
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -250,8 +250,8 @@ def test_multi_tumor(self):
             page.locator("#close-modal").click()
 
             # check one result (individual)
-            page.get_by_role("link", name="Multistage 1°*").click()
-            expect(page.get_by_role("dialog")).to_contain_text("Multistage 1°")
+            page.get_by_role("link", name="Multistage 1*").click()
+            expect(page.get_by_role("dialog")).to_contain_text("Multistage 1")
             page.locator("#close-modal").click()
 
             # Read-only