15.1.611. tablet_qt/tasks/bmi.cpp

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    CamCOPS is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

// #define DEBUG_DATA_FLOW

#include "bmi.h"

#include "common/textconst.h"
#include "lib/convert.h"
#include "maths/mathfunc.h"
#include "questionnairelib/commonoptions.h"
#include "questionnairelib/questionnaire.h"
#include "questionnairelib/quheight.h"
#include "questionnairelib/qumass.h"
#include "questionnairelib/qutext.h"
#include "questionnairelib/qutextedit.h"
#include "questionnairelib/quwaist.h"
#include "tasklib/taskfactory.h"
#include "tasklib/taskregistrar.h"
using convert::toDp;
using mathfunc::noneNull;

const QString Bmi::BMI_TABLENAME("bmi");

const QString FN_MASS_KG("mass_kg");
const QString FN_HEIGHT_M("height_m");
const QString FN_WAIST_CM("waist_cm");
const QString FN_COMMENT("comment");

// const int BMI_DP = 2;


void initializeBmi(TaskFactory& factory)
{
    static TaskRegistrar<Bmi> registered(factory);
}

Bmi::Bmi(CamcopsApp& app, DatabaseManager& db, const int load_pk) :
    Task(app, db, BMI_TABLENAME, false, false, false),  // ... anon, clin, resp
    m_questionnaire(nullptr)
{
    addField(FN_MASS_KG, QMetaType::fromType<double>());
    addField(FN_HEIGHT_M, QMetaType::fromType<double>());
    addField(FN_WAIST_CM, QMetaType::fromType<double>());
    addField(FN_COMMENT, QMetaType::fromType<QString>());

    load(load_pk);  // MUST ALWAYS CALL from derived Task constructor.
}

// ============================================================================
// Class info
// ============================================================================

QString Bmi::shortname() const
{
    return "BMI";
}

QString Bmi::longname() const
{
    return tr("Body mass index");
}

QString Bmi::description() const
{
    return tr("Mass, height, BMI; also waist circumference.");
}

// ============================================================================
// Instance info
// ============================================================================

bool Bmi::isComplete() const
{
    // Waist circumference optional
    return noneNull(values({FN_MASS_KG, FN_HEIGHT_M}));
}

QStringList Bmi::summary() const
{
    QStringList lines;

    lines.append(QString("%1 kg, %2 m; BMI = %3 kg/m^2; %4.")
                     .arg(
                         prettyValue(FN_MASS_KG),
                         prettyValue(FN_HEIGHT_M),
                         bmiString(),
                         category()
                     ));

    if (!valueIsNullOrEmpty(FN_WAIST_CM)) {
        lines.append(
            QString("%1 %2 cm.")
                .arg(tr("Waist circumference:"), prettyValue(FN_WAIST_CM))
        );
    }

    if (!valueIsNullOrEmpty(FN_COMMENT)) {
        lines.append(
            QString("%1 %2").arg(tr("Comments:"), valueString(FN_COMMENT))
        );
    }

    return lines;
}

QStringList Bmi::detail() const
{
    return completenessInfo() + summary();
}

OpenableWidget* Bmi::editor(const bool read_only)
{
    auto heading = [this](const QString& xstringname) -> QuElement* {
        return (new QuText(xstring(xstringname)))->setBold(true);
    };

    auto height_units = new QuUnitSelector(CommonOptions::heightUnits());
    auto height_edit = new QuHeight(fieldRef(FN_HEIGHT_M), height_units);
    auto mass_units = new QuUnitSelector(CommonOptions::massUnits());
    auto mass_edit = new QuMass(fieldRef(FN_MASS_KG), mass_units);
    auto waist_units = new QuUnitSelector(CommonOptions::waistUnits());
    auto waist_edit = new QuWaist(
        fieldRef(FN_WAIST_CM),
        waist_units,
        false
    );  // Not mandatory

    // Comments
    FieldRefPtr fr_comment = fieldRef(FN_COMMENT, false);

    QuPagePtr page(
        (new QuPage{
             // ---------------------------------------------------------------
             // Height
             // ---------------------------------------------------------------
             heading("title_1"),
             height_units,
             heading("title_2"),
             height_edit,
             // ---------------------------------------------------------------
             // Mass
             // ---------------------------------------------------------------
             heading("title_3"),
             mass_units,
             heading("title_4"),
             mass_edit,
             // ---------------------------------------------------------------
             // Waist circumference
             // ---------------------------------------------------------------
             new QuText(xstring("optional")),
             heading("title_5"),
             waist_units,
             heading("title_6"),
             waist_edit,
             // ---------------------------------------------------------------
             // Comments
             // ---------------------------------------------------------------
             (new QuText(TextConst::comments()))->setBold(true),
             new QuTextEdit(fr_comment),
         })
            ->setTitle(longname())
    );

    m_questionnaire = new Questionnaire(m_app, {page});
    m_questionnaire->setType(QuPage::PageType::Clinician);
    m_questionnaire->setReadOnly(read_only);

    return m_questionnaire;
}

// ============================================================================
// Task-specific calculations
// ============================================================================

QVariant Bmi::bmiVariant() const
{
    if (!isCompleteCached()) {
        return QVariant();
    }
    const double mass_kg = valueDouble(FN_MASS_KG);
    const double height_m = valueDouble(FN_HEIGHT_M);

    if (abs(height_m) < 0.0001) {
        // It is possible that a platform may not handle division by zero. We
        // could also limit height to a sensible range. It's probably better to
        // allow a patient to skip this task altogether rather than end up with
        // silly small height values.
        return QVariant();
    }

    const double bmi = mass_kg / (height_m * height_m);
    return QVariant(bmi);
}

QString Bmi::bmiString(const int dp) const
{
    const QVariant bmi = bmiVariant();
    if (bmi.isNull()) {
        return "?";
    }
    return toDp(bmi.toDouble(), dp);
}

QString Bmi::category() const
{
    const QVariant bmiv = bmiVariant();
    if (bmiv.isNull()) {
        return "?";
    }
    const double bmi = bmiv.toDouble();
    if (bmi >= 40) {
        return xstring("obese_3");
    }
    if (bmi >= 35) {
        return xstring("obese_2");
    }
    if (bmi >= 30) {
        return xstring("obese_1");
    }
    if (bmi >= 25) {
        return xstring("overweight");
    }
    if (bmi >= 18.5) {
        return xstring("normal");
    }
    if (bmi >= 17.5) {
        return xstring("underweight_17.5_18.5");
    }
    if (bmi >= 17) {
        return xstring("underweight_17_17.5");
    }
    if (bmi >= 16) {
        return xstring("underweight_16_17");
    }
    if (bmi >= 15) {
        return xstring("underweight_15_16");
    }
    if (bmi >= 13) {
        return xstring("underweight_13_15");
    }
    return xstring("underweight_under_13");
}