/*
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/>.
*/
/*
===============================================================================
PRIMARY REFERENCE
===============================================================================
CIS-R: Lewis et al. 1992
- https://www.ncbi.nlm.nih.gov/pubmed/1615114
Helpful chronology:
- https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3347904/#b2-mjms-13-1-058
Primary IMPLEMENTATION reference:
- "BASIC CIS-R 02-03-2010.pqs"
===============================================================================
INTERNALS
===============================================================================
The internal methodology of the CIS-R is that the textfile
"BASIC CIS-R 02-03-2010.pqs" contains a sequence, of the type:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LABEL_A1
"Some question?"
[
1 "Option 1"
2 "Option 2"
3 "Option 3"
]
SOMEVAR := answer * 10
if answer == 1 then goto LABEL_B1;
if answer == 2 then goto LABEL_B2;
if answer == 3 then goto LABEL_B3;
&
LABEL_B1
...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
===============================================================================
IMPLEMENTATION: EARLY THOUGHTS -- NOT USED
===============================================================================
We could in principle do this with a Questionnaire interface, but to be honest
it's going to be easiest to translate with a "direct" interface. Maybe
something like:
void start()
{
goto_question(1);
}
void goto_question(int question)
{
m_current_question = question;
offer_question();
}
bool offer_question()
{
// do interesting things
connect(answer_1, answered, 1);
return true;
}
bool answered(int answer)
{
// returns: something we don't care about
switch (m_current_question) {
case 1:
if (answer == 1) {
m_diagnosis_blah = true;
return goto_question(2);
// C++11: can't do "return goto_question(2);" if goto_question() is
// of void type, as you're not allowed to return anything from a
// void function, even of void type:
// http://stackoverflow.com/questions/35987493/return-void-type-in-c-and-c
// ... so just make them bool or something.
// It would be OK in C++14.
}
break;
// ...
}
}
===============================================================================
IMPLEMENTATION: FURTHER THOUGHTS
===============================================================================
FURTHER THOUGHTS: we'll implement a DynamicQuestionnaire class; q.v.
*/
// #define DEBUG_SHOW_PAGE_TAGS
#define SHOW_QUESTIONS_CONSIDERED // helpful; leave it on
#include "cisr.h"
#include "lib/stringfunc.h"
#include "maths/mathfunc.h"
#include "questionnairelib/commonoptions.h"
#include "questionnairelib/dynamicquestionnaire.h"
#include "questionnairelib/namevalueoptions.h"
#include "questionnairelib/qumcq.h"
#include "questionnairelib/qumcqgrid.h"
#include "questionnairelib/qupage.h"
#include "questionnairelib/qutext.h"
#include "tasklib/taskfactory.h"
#include "tasklib/taskregistrar.h"
using namespace cisrconst;
#define CQ Cisr::CisrQuestion
// if you try "using", you get "'Cisr' is not a namespace or unscoped enum"
const QString Cisr::CISR_TABLENAME("cisr");
// FP field prefix; NUM field numbers; FN field name
// These match the electronic version of the CIS-R, as per
// "BASIC CIS-R 02-03-2010.pqs".
const QString FN_ETHNIC("ethnic");
const QString FN_MARRIED("married");
const QString FN_EMPSTAT("empstat");
const QString FN_EMPTYPE("emptype");
const QString FN_HOME("home");
const QString FN_APPETITE1("appetite1");
const QString FN_WEIGHT1("weight1");
const QString FN_WEIGHT2("weight2");
const QString FN_WEIGHT3("weight3");
const QString FN_APPETITE2("appetite2");
const QString FN_WEIGHT4("weight4");
// ... male/female responses unified (no "weight4a")
const QString FN_WEIGHT5("weight5");
const QString FN_GP_YEAR("gp_year");
const QString FN_DISABLE("disable");
const QString FN_ILLNESS("illness");
const QString FN_SOMATIC_MAND1("somatic_mand1");
const QString FN_SOMATIC_PAIN1("somatic_pain1");
const QString FN_SOMATIC_PAIN2("somatic_pain2");
const QString FN_SOMATIC_PAIN3("somatic_pain3");
const QString FN_SOMATIC_PAIN4("somatic_pain4");
const QString FN_SOMATIC_PAIN5("somatic_pain5");
const QString FN_SOMATIC_MAND2("somatic_mand2");
const QString FN_SOMATIC_DIS1("somatic_dis1");
const QString FN_SOMATIC_DIS2("somatic_dis2");
const QString FN_SOMATIC_DIS3("somatic_dis3");
const QString FN_SOMATIC_DIS4("somatic_dis4");
const QString FN_SOMATIC_DIS5("somatic_dis5");
const QString FN_SOMATIC_DUR("somatic_dur");
const QString FN_FATIGUE_MAND1("fatigue_mand1");
const QString FN_FATIGUE_CAUSE1("fatigue_cause1");
const QString FN_FATIGUE_TIRED1("fatigue_tired1");
const QString FN_FATIGUE_TIRED2("fatigue_tired2");
const QString FN_FATIGUE_TIRED3("fatigue_tired3");
const QString FN_FATIGUE_TIRED4("fatigue_tired4");
const QString FN_FATIGUE_MAND2("fatigue_mand2");
const QString FN_FATIGUE_CAUSE2("fatigue_cause2");
const QString FN_FATIGUE_ENERGY1("fatigue_energy1");
const QString FN_FATIGUE_ENERGY2("fatigue_energy2");
const QString FN_FATIGUE_ENERGY3("fatigue_energy3");
const QString FN_FATIGUE_ENERGY4("fatigue_energy4");
const QString FN_FATIGUE_DUR("fatigue_dur");
const QString FN_CONC_MAND1("conc_mand1");
const QString FN_CONC_MAND2("conc_mand2");
const QString FN_CONC1("conc1");
const QString FN_CONC2("conc2");
const QString FN_CONC3("conc3");
const QString FN_CONC_DUR("conc_dur");
const QString FN_CONC4("conc4");
const QString FN_FORGET_DUR("forget_dur");
const QString FN_SLEEP_MAND1("sleep_mand1");
const QString FN_SLEEP_LOSE1("sleep_lose1");
const QString FN_SLEEP_LOSE2("sleep_lose2");
const QString FN_SLEEP_LOSE3("sleep_lose3");
const QString FN_SLEEP_EMW("sleep_emw");
const QString FN_SLEEP_CAUSE("sleep_cause");
const QString FN_SLEEP_MAND2("sleep_mand2");
const QString FN_SLEEP_GAIN1("sleep_gain1");
const QString FN_SLEEP_GAIN2("sleep_gain2");
const QString FN_SLEEP_GAIN3("sleep_gain3");
const QString FN_SLEEP_DUR("sleep_dur");
const QString FN_IRRIT_MAND1("irrit_mand1");
const QString FN_IRRIT_MAND2("irrit_mand2");
const QString FN_IRRIT1("irrit1");
const QString FN_IRRIT2("irrit2");
const QString FN_IRRIT3("irrit3");
const QString FN_IRRIT4("irrit4");
const QString FN_IRRIT_DUR("irrit_dur");
const QString FN_HYPO_MAND1("hypo_mand1");
const QString FN_HYPO_MAND2("hypo_mand2");
const QString FN_HYPO1("hypo1");
const QString FN_HYPO2("hypo2");
const QString FN_HYPO3("hypo3");
const QString FN_HYPO4("hypo4");
const QString FN_HYPO_DUR("hypo_dur");
const QString FN_DEPR_MAND1("depr_mand1");
const QString FN_DEPR1("depr1");
const QString FN_DEPR_MAND2("depr_mand2");
const QString FN_DEPR2("depr2");
const QString FN_DEPR3("depr3");
const QString FN_DEPR4("depr4");
const QString FN_DEPR_CONTENT("depr_content");
const QString FN_DEPR5("depr5");
const QString FN_DEPR_DUR("depr_dur");
const QString FN_DEPTH1("depth1");
const QString FN_DEPTH2("depth2");
const QString FN_DEPTH3("depth3");
const QString FN_DEPTH4("depth4");
const QString FN_DEPTH5("depth5");
const QString FN_DEPTH6("depth6");
const QString FN_DEPTH7("depth7");
const QString FN_DEPTH8("depth8");
const QString FN_DEPTH9("depth9");
const QString FN_DEPTH10("depth10");
const QString FN_DOCTOR("doctor");
const QString FN_WORRY_MAND1("worry_mand1");
const QString FN_WORRY_MAND2("worry_mand2");
const QString FN_WORRY_CONT1("worry_cont1");
const QString FN_WORRY2("worry2");
const QString FN_WORRY3("worry3");
const QString FN_WORRY4("worry4");
const QString FN_WORRY5("worry5");
const QString FN_WORRY_DUR("worry_dur");
const QString FN_ANX_MAND1("anx_mand1");
const QString FN_ANX_MAND2("anx_mand2");
const QString FN_ANX_PHOBIA1("anx_phobia1");
const QString FN_ANX_PHOBIA2("anx_phobia2");
const QString FN_ANX2("anx2");
const QString FN_ANX3("anx3");
const QString FN_ANX4("anx4");
const QString FN_ANX5("anx5");
const QString FN_ANX_DUR("anx_dur");
const QString FN_PHOBIAS_MAND("phobias_mand");
const QString FN_PHOBIAS_TYPE1("phobias_type1");
const QString FN_PHOBIAS1("phobias1");
const QString FN_PHOBIAS2("phobias2");
const QString FN_PHOBIAS3("phobias3");
const QString FN_PHOBIAS4("phobias4");
const QString FN_PHOBIAS_DUR("phobias_dur");
const QString FN_PANIC_MAND("panic_mand");
const QString FN_PANIC1("panic1");
const QString FN_PANIC2("panic2");
const QString FN_PANIC3("panic3");
const QString FN_PANIC4("panic4");
const QString FN_PANSYM_A("pansym_a");
const QString FN_PANSYM_B("pansym_b");
const QString FN_PANSYM_C("pansym_c");
const QString FN_PANSYM_D("pansym_d");
const QString FN_PANSYM_E("pansym_e");
const QString FN_PANSYM_F("pansym_f");
const QString FN_PANSYM_G("pansym_g");
const QString FN_PANSYM_H("pansym_h");
const QString FN_PANSYM_I("pansym_i");
const QString FN_PANSYM_J("pansym_j");
const QString FN_PANSYM_K("pansym_k");
const QString FN_PANSYM_L("pansym_l");
const QString FN_PANSYM_M("pansym_m");
const QString FN_PANIC5("panic5");
const QString FN_PANIC_DUR("panic_dur");
const QString FN_COMP_MAND1("comp_mand1");
const QString FN_COMP1("comp1");
const QString FN_COMP2("comp2");
const QString FN_COMP3("comp3");
const QString FN_COMP4("comp4");
const QString FN_COMP_DUR("comp_dur");
const QString FN_OBSESS_MAND1("obsess_mand1");
const QString FN_OBSESS_MAND2("obsess_mand2");
const QString FN_OBSESS1("obsess1");
const QString FN_OBSESS2("obsess2");
const QString FN_OBSESS3("obsess3");
const QString FN_OBSESS4("obsess4");
const QString FN_OBSESS_DUR("obsess_dur");
const QString FN_OVERALL2("overall2");
const QString XSTRING_QUESTION_SUFFIX = "_q";
const QString XSTRING_EXTRA_STEM_SUFFIX = "_s";
// Define question types:
// Questions for which 1 = no, 2 = yes (+/- other options)
const QVector<CQ> QUESTIONS_1_NO_2_YES{
CQ::APPETITE1_LOSS_PAST_MONTH,
CQ::WEIGHT1_LOSS_PAST_MONTH,
CQ::WEIGHT2_TRYING_TO_LOSE,
CQ::APPETITE2_INCREASE_PAST_MONTH,
CQ::WEIGHT4_INCREASE_PAST_MONTH, // may also offer "yes but pregnant"
CQ::SOMATIC_MAND1_PAIN_PAST_MONTH,
CQ::SOMATIC_MAND2_DISCOMFORT,
CQ::SOMATIC_PAIN3_GT_3H_ANY_DAY,
CQ::SOMATIC_PAIN5_INTERRUPTED_INTERESTING, // also has other options
CQ::SOMATIC_DIS3_GT_3H_ANY_DAY,
CQ::SOMATIC_DIS5_INTERRUPTED_INTERESTING, // also has other options
CQ::FATIGUE_MAND1_TIRED_PAST_MONTH,
CQ::FATIGUE_TIRED2_GT_3H_ANY_DAY,
CQ::FATIGUE_TIRED3_HAD_TO_PUSH,
CQ::FATIGUE_TIRED4_DURING_ENJOYABLE, // also has other options
CQ::FATIGUE_MAND2_LACK_ENERGY_PAST_MONTH,
CQ::FATIGUE_ENERGY2_GT_3H_ANY_DAY,
CQ::FATIGUE_ENERGY3_HAD_TO_PUSH,
CQ::FATIGUE_ENERGY4_DURING_ENJOYABLE, // also has other options
CQ::CONC_MAND1_POOR_CONC_PAST_MONTH,
CQ::CONC_MAND2_FORGETFUL_PAST_MONTH,
CQ::CONC3_CONC_PREVENTED_ACTIVITIES,
CQ::CONC4_FORGOTTEN_IMPORTANT,
CQ::SLEEP_MAND1_LOSS_PAST_MONTH,
CQ::SLEEP_EMW_PAST_WEEK,
CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH,
CQ::IRRIT2_GT_1H_ANY_DAY,
CQ::HYPO_MAND1_WORRIED_RE_HEALTH_PAST_MONTH,
CQ::HYPO_MAND2_WORRIED_RE_SERIOUS_ILLNESS,
CQ::HYPO2_WORRY_TOO_MUCH,
CQ::DEPR_MAND1_LOW_MOOD_PAST_MONTH,
CQ::DEPR1_LOW_MOOD_PAST_WEEK,
CQ::DEPR4_GT_3H_ANY_DAY,
CQ::DEPTH3_RESTLESS,
CQ::DEPTH4_SLOWED,
CQ::DEPTH6_WORSE_THAN_OTHERS,
CQ::DEPTH7_HOPELESS,
CQ::DEPTH10_SUICIDE_METHOD,
CQ::WORRY_MAND2_ANY_WORRIES_PAST_MONTH,
CQ::WORRY3_TOO_MUCH,
CQ::WORRY5_GT_3H_ANY_DAY,
CQ::ANX_MAND1_ANXIETY_PAST_MONTH,
CQ::ANX_PHOBIA1_SPECIFIC_PAST_MONTH,
CQ::ANX4_GENERAL_PHYSICAL_SYMPTOMS,
CQ::ANX5_GENERAL_GT_3H_ANY_DAY,
CQ::PHOBIAS_MAND_AVOIDANCE_PAST_MONTH,
CQ::PHOBIAS2_PHYSICAL_SYMPTOMS,
CQ::PHOBIAS3_AVOIDANCE,
CQ::PANIC4_RAPID_ONSET,
CQ::PANIC5_ALWAYS_SPECIFIC_TRIGGER,
CQ::COMP2_TRIED_TO_STOP,
CQ::COMP3_UPSETTING,
CQ::OBSESS2_TRIED_TO_STOP,
CQ::OBSESS3_UPSETTING,
};
// Questions for which 1 = yes, 2 = no (+/- other options)
const QVector<CQ> QUESTIONS_1_YES_2_NO{
CQ::DISABLE,
CQ::CONC2_CONC_FOR_TV_READING_CONVERSATION,
CQ::HYPO4_CAN_DISTRACT,
};
// Yes-no (or no-yes) questions but with specific text
const QVector<CQ> QUESTIONS_YN_SPECIFIC_TEXT{
CQ::WEIGHT2_TRYING_TO_LOSE,
CQ::SOMATIC_PAIN3_GT_3H_ANY_DAY,
CQ::SOMATIC_DIS3_GT_3H_ANY_DAY,
CQ::FATIGUE_TIRED2_GT_3H_ANY_DAY,
CQ::FATIGUE_TIRED3_HAD_TO_PUSH,
CQ::FATIGUE_ENERGY2_GT_3H_ANY_DAY,
CQ::FATIGUE_ENERGY3_HAD_TO_PUSH,
CQ::CONC_MAND1_POOR_CONC_PAST_MONTH,
CQ::CONC2_CONC_FOR_TV_READING_CONVERSATION,
CQ::CONC4_FORGOTTEN_IMPORTANT,
CQ::SLEEP_EMW_PAST_WEEK,
CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH,
CQ::IRRIT2_GT_1H_ANY_DAY,
CQ::HYPO2_WORRY_TOO_MUCH,
CQ::HYPO4_CAN_DISTRACT,
CQ::DEPR1_LOW_MOOD_PAST_WEEK,
CQ::DEPR4_GT_3H_ANY_DAY,
CQ::DEPTH6_WORSE_THAN_OTHERS,
CQ::DEPTH7_HOPELESS,
CQ::WORRY3_TOO_MUCH,
CQ::WORRY5_GT_3H_ANY_DAY,
CQ::ANX4_GENERAL_PHYSICAL_SYMPTOMS,
CQ::ANX5_GENERAL_GT_3H_ANY_DAY,
CQ::PHOBIAS2_PHYSICAL_SYMPTOMS,
CQ::PHOBIAS3_AVOIDANCE,
CQ::COMP2_TRIED_TO_STOP,
CQ::COMP3_UPSETTING,
CQ::OBSESS2_TRIED_TO_STOP,
CQ::OBSESS3_UPSETTING,
};
// Demographics questions (optional for diagnosis)
const QVector<CQ> QUESTIONS_DEMOGRAPHICS{
CQ::ETHNIC,
CQ::MARRIED,
CQ::EMPSTAT,
CQ::EMPTYPE,
CQ::HOME,
};
// "Questions" that are just a prompt screen
const QMap<CQ, QString> QUESTIONS_PROMPT_ONLY{
// Maps questions to their prompt's xstring name
{CQ::INTRO_1, "intro_1"},
{CQ::INTRO_2, "intro_2"},
{CQ::INTRO_DEMOGRAPHICS, "intro_demographics_statement"},
{CQ::HEALTH_WELLBEING, "health_wellbeing_statement"},
{CQ::DOCTOR2_PLEASE_TALK_TO, "doctor2"},
{CQ::DEPR_OUTRO, "depr_outro"},
{CQ::WORRY1_INFO_ONLY, "worry1"},
{CQ::ANX1_INFO_ONLY, "anx1"},
{CQ::ANX_OUTRO, "anx_outro"},
{CQ::OVERALL1_INFO_ONLY, "overall1"},
{CQ::THANKS_FINISHED, "end"},
};
// "How many days per week" questions
// "Overall duration" questions
const QVector<CQ> QUESTIONS_OVERALL_DURATION{
CQ::SOMATIC_DUR,
CQ::FATIGUE_DUR,
CQ::CONC_DUR,
CQ::FORGET_DUR,
CQ::SLEEP_DUR,
CQ::IRRIT_DUR,
CQ::HYPO_DUR,
CQ::DEPR_DUR,
CQ::WORRY_DUR,
CQ::ANX_DUR_GENERAL,
CQ::PHOBIAS_DUR,
CQ::PANIC_DUR,
CQ::COMP_DUR,
CQ::OBSESS_DUR,
};
// Multi-way questions, other than yes/no ones.
const QMap<CQ, QPair<int, int>> QUESTIONS_MULTIWAY{
// Maps questions to first and last number of answers.
{CQ::WEIGHT3_LOST_LOTS, {1, 2}},
{CQ::WEIGHT4_INCREASE_PAST_MONTH, {1, 2}},
// ... may be modified to 3 if female
{CQ::WEIGHT5_GAINED_LOTS, {1, 2}},
{CQ::GP_YEAR, {0, 4}}, // unusual; starts at 0
{CQ::ILLNESS, {1, 8}},
{CQ::SOMATIC_PAIN1_PSYCHOL_EXAC, {1, 3}},
{CQ::SOMATIC_PAIN5_INTERRUPTED_INTERESTING, {1, 3}},
{CQ::SOMATIC_DIS1_PSYCHOL_EXAC, {1, 3}},
{CQ::SOMATIC_DIS5_INTERRUPTED_INTERESTING, {1, 3}},
{CQ::FATIGUE_TIRED4_DURING_ENJOYABLE, {1, 3}},
{CQ::FATIGUE_ENERGY4_DURING_ENJOYABLE, {1, 3}},
{CQ::SLEEP_LOSE2_DIS_WORST_DURATION, {1, 4}},
{CQ::SLEEP_CAUSE, {1, 6}},
{CQ::SLEEP_MAND2_GAIN_PAST_MONTH, {1, 3}},
{CQ::SLEEP_GAIN2_EXTRA_ON_LONGEST_NIGHT, {1, 4}},
{CQ::IRRIT_MAND2_THINGS_PAST_MONTH, {1, 3}},
{CQ::IRRIT3_WANTED_TO_SHOUT, {1, 3}},
{CQ::IRRIT4_ARGUMENTS, {1, 3}},
{CQ::DEPR_MAND2_ENJOYMENT_PAST_MONTH, {1, 3}},
{CQ::DEPR2_ENJOYMENT_PAST_WEEK, {1, 3}},
{CQ::DEPR5_COULD_CHEER_UP, {1, 3}},
{CQ::DEPTH1_DIURNAL_VARIATION, {1, 4}},
{CQ::DEPTH2_LIBIDO, {1, 4}},
{CQ::DEPTH5_GUILT, {1, 4}},
{CQ::DEPTH8_LNWL, {1, 3}},
{CQ::DEPTH9_SUICIDE_THOUGHTS, {1, 3}},
{CQ::DOCTOR, {1, 3}},
{CQ::ANX_PHOBIA2_SPECIFIC_OR_GENERAL, {1, 2}},
{CQ::PHOBIAS_TYPE1, {1, 9}},
{CQ::PHOBIAS4_AVOIDANCE_DAYS_PAST_WEEK, {1, 3}},
{CQ::PANIC_MAND_PAST_MONTH, {1, 3}},
{CQ::PANIC1_NUM_PAST_WEEK, {1, 3}},
{CQ::PANIC2_HOW_UNPLEASANT, {1, 3}},
{CQ::PANIC3_PANIC_GE_10_MIN, {1, 2}},
{CQ::COMP4_MAX_N_REPETITIONS, {1, 3}},
{CQ::OBSESS_MAND2_SAME_THOUGHTS_OR_GENERAL, {1, 2}},
{CQ::OBSESS4_MAX_DURATION, {1, 2}},
{CQ::OVERALL2_IMPACT_PAST_WEEK, {1, 4}},
};
const QMap<CQ, QPair<int, int>> QUESTIONS_MULTIWAY_WITH_EXTRA_STEM{
// Maps questions to first and last number of answers.
{CQ::ETHNIC, {1, 7}}, // 7 includes our additional "prefer not to say"
{CQ::MARRIED, {1, 6}}, // 6 includes our additional "prefer not to say"
{CQ::EMPSTAT, {1, 8}}, // 8 includes our additional "prefer not to say"
{CQ::EMPTYPE, {1, 7}},
// ... 7 includes our additional "not applicable" + "prefer not to say"
{CQ::HOME, {1, 7}}, // 7 includes our additional "prefer not to say"
};
const QVector<CQ> QUESTIONS_DAYS_PER_WEEK{
CQ::SOMATIC_PAIN2_DAYS_PAST_WEEK,
CQ::SOMATIC_DIS2_DAYS_PAST_WEEK,
CQ::FATIGUE_TIRED1_DAYS_PAST_WEEK,
CQ::FATIGUE_ENERGY1_DAYS_PAST_WEEK,
CQ::CONC1_CONC_DAYS_PAST_WEEK,
CQ::IRRIT1_DAYS_PER_WEEK,
CQ::HYPO1_DAYS_PAST_WEEK,
CQ::DEPR3_DAYS_PAST_WEEK,
CQ::WORRY2_DAYS_PAST_WEEK,
CQ::ANX2_GENERAL_DAYS_PAST_WEEK,
CQ::PHOBIAS1_DAYS_PAST_WEEK,
// not this: CQ::PHOBIAS4_AVOIDANCE_FREQUENCY -- different phrasing
// not this: CQ::PANIC1_FREQUENCY
CQ::COMP1_DAYS_PAST_WEEK,
CQ::OBSESS1_DAYS_PAST_WEEK,
};
const QVector<CQ> QUESTIONS_NIGHTS_PER_WEEK{
CQ::SLEEP_LOSE1_NIGHTS_PAST_WEEK,
CQ::SLEEP_LOSE3_NIGHTS_GT_3H_DIS_PAST_WEEK,
CQ::SLEEP_GAIN1_NIGHTS_PAST_WEEK, // (*) see below
// (*) Probably an error in the original:
// "On how many nights in the PAST SEVEN NIGHTS did you have problems
// with your sleep? (1) None. (2) Between one and three days. (3) Four
// days or more." Note day/night confusion. Altered to "nights".
CQ::SLEEP_GAIN3_NIGHTS_GT_3H_EXTRA_PAST_WEEK,
};
const QVector<CQ> QUESTIONS_HOW_UNPLEASANT_STANDARD{
CQ::SOMATIC_PAIN4_UNPLEASANT,
CQ::SOMATIC_DIS4_UNPLEASANT,
CQ::HYPO3_HOW_UNPLEASANT,
CQ::WORRY4_HOW_UNPLEASANT,
CQ::ANX3_GENERAL_HOW_UNPLEASANT,
};
const QVector<CQ> QUESTIONS_FATIGUE_CAUSES{
CQ::FATIGUE_CAUSE1_TIRED,
CQ::FATIGUE_CAUSE2_LACK_ENERGY,
};
const QVector<CQ> QUESTIONS_STRESSORS{
CQ::DEPR_CONTENT,
CQ::WORRY_CONT1,
};
const QVector<CQ> QUESTIONS_NO_SOMETIMES_OFTEN{
CQ::WORRY_MAND1_MORE_THAN_NEEDED_PAST_MONTH,
CQ::ANX_MAND2_TENSION_PAST_MONTH,
CQ::COMP_MAND1_COMPULSIONS_PAST_MONTH,
CQ::OBSESS_MAND1_OBSESSIONS_PAST_MONTH,
// and no-sometimes-often values also used by:
// CQ::PANIC_MAND_PAST_MONTH
// ... but with variations on the text.
};
// Next, we define constants for all answers, particularly as values are not
// consistent (e.g. some questions have no 1/yes 2, and some have yes 1/no 2).
// Note: "LT" less than, "LE" less than or equal to, "GT" greater than, "GE"
// greater than or equal to.
// Number of response values (numbered from 1 to N)
// const int N_ETHNIC = 7; // RNC: 6 in original; added "prefer not to say"
// const int N_MARRIED = 6; // RNC: 5 in original; added "prefer not to say"
const int N_DURATIONS = 5;
const int N_OPTIONS_DAYS_PER_WEEK = 3;
const int N_OPTIONS_NIGHTS_PER_WEEK = 3;
const int N_OPTIONS_HOW_UNPLEASANT = 4;
const int N_OPTIONS_FATIGUE_CAUSES = 8;
const int N_OPTIONS_STRESSORS = 9;
const int N_OPTIONS_NO_SOMETIMES_OFTEN = 3;
const int NUM_PANIC_SYMPTOMS = 13; // from a to m
// Actual response values
// For e.g. SOMATIC_DUR, etc.: "How long have you..."
// const int V_DURATION_LT_2W = 1;
const int V_DURATION_2W_6M = 2;
// const int V_DURATION_6M_1Y = 3;
// const int V_DURATION_1Y_2Y = 4;
// const int V_DURATION_GE_2Y = 5;
// For quite a few: "on how many days in the past week...?"
const int V_DAYS_IN_PAST_WEEK_0 = 1;
const int V_DAYS_IN_PAST_WEEK_1_TO_3 = 2;
const int V_DAYS_IN_PAST_WEEK_4_OR_MORE = 3;
const int V_NIGHTS_IN_PAST_WEEK_0 = 1;
// const int V_NIGHTS_IN_PAST_WEEK_1_TO_3 = 2;
const int V_NIGHTS_IN_PAST_WEEK_4_OR_MORE = 3;
// const int V_HOW_UNPLEASANT_NOT_AT_ALL = 1;
// const int V_HOW_UNPLEASANT_A_LITTLE = 2;
const int V_HOW_UNPLEASANT_UNPLEASANT = 3;
// const int V_HOW_UNPLEASANT_VERY = 4;
// const int V_FATIGUE_CAUSE_SLEEP = 1;
// const int V_FATIGUE_CAUSE_MEDICATION = 2;
// const int V_FATIGUE_CAUSE_PHYSICAL_ILLNESS = 3;
// const int V_FATIGUE_CAUSE_OVERWORK = 4;
// const int V_FATIGUE_CAUSE_PSYCHOLOGICAL = 5;
const int V_FATIGUE_CAUSE_EXERCISE = 6;
// const int V_FATIGUE_CAUSE_OTHER = 7;
// const int V_FATIGUE_CAUSE_DONT_KNOW = 8;
// const int V_STRESSOR_FAMILY = 1;
// const int V_STRESSOR_FRIENDS_COLLEAGUES = 2;
// const int V_STRESSOR_HOUSING = 3;
// const int V_STRESSOR_MONEY = 4;
// const int V_STRESSOR_PHYSICAL_HEALTH = 5;
// const int V_STRESSOR_MENTAL_HEALTH = 6;
// const int V_STRESSOR_WORK = 7;
// const int V_STRESSOR_LEGAL = 8;
// const int V_STRESSOR_POLITICAL_NEWS = 9;
const int V_NSO_NO = 1;
const int V_NSO_SOMETIMES = 2;
// const int V_NSO_OFTEN = 3;
const int V_SLEEP_CHANGE_LT_15_MIN = 1;
const int V_SLEEP_CHANGE_15_MIN_TO_1_H = 2;
const int V_SLEEP_CHANGE_1_TO_3_H = 3;
const int V_SLEEP_CHANGE_GT_3_H = 4;
const int V_ANHEDONIA_ENJOYING_NORMALLY = 1;
const int V_ANHEDONIA_ENJOYING_LESS = 2;
// const int V_ANHEDONIA_NOT_ENJOYING = 3;
// Specific other question values:
// const int V_EMPSTAT_FT = 1;
// const int V_EMPSTAT_PT = 2;
// const int V_EMPSTAT_STUDENT = 3;
// const int V_EMPSTAT_RETIRED = 4;
// const int V_EMPSTAT_HOUSEPERSON = 5;
// const int V_EMPSTAT_UNEMPJOBSEEKER = 6;
// const int V_EMPSTAT_UNEMPILLHEALTH = 7;
// const int V_EMPTYPE_SELFEMPWITHEMPLOYEES = 1;
// const int V_EMPTYPE_SELFEMPNOEMPLOYEES = 2;
// const int V_EMPTYPE_EMPLOYEE = 3;
// const int V_EMPTYPE_SUPERVISOR = 4;
// const int V_EMPTYPE_MANAGER = 5;
// const int V_EMPTYPE_NOT_APPLICABLE = 6;
// ... the last one: added by RNC, in case pt never employed. (Mentioned to
// Glyn Lewis 2017-12-04. Not, in any case, part of the important bits of the
// CIS-R.)
// const int V_HOME_OWNER = 1;
// const int V_HOME_TENANT = 2;
// const int V_HOME_RELATIVEFRIEND = 3;
// const int V_HOME_HOSTELCAREHOME = 4;
// const int V_HOME_HOMELESS = 5;
// const int V_HOME_OTHER = 6;
const int V_WEIGHT2_WTLOSS_NOTTRYING = 1;
const int V_WEIGHT2_WTLOSS_TRYING = 2;
const int V_WEIGHT3_WTLOSS_GE_HALF_STONE = 1;
// const int V_WEIGHT3_WTLOSS_LT_HALF_STONE = 2;
// const int V_WEIGHT4_WTGAIN_YES_PREGNANT = 3;
const int V_WEIGHT5_WTGAIN_GE_HALF_STONE = 1;
// const int V_WEIGHT5_WTGAIN_LT_HALF_STONE = 2;
// const int V_GPYEAR_NONE = 0;
// const int V_GPYEAR_1_2 = 1;
// const int V_GPYEAR_3_5 = 2;
// const int V_GPYEAR_6_10 = 3;
// const int V_GPYEAR_GT_10 = 4;
// const int V_ILLNESS_DIABETES = 1;
// const int V_ILLNESS_ASTHMA = 2;
// const int V_ILLNESS_ARTHRITIS = 3;
// const int V_ILLNESS_HEART_DISEASE = 4;
// const int V_ILLNESS_HYPERTENSION = 5;
// const int V_ILLNESS_LUNG_DISEASE = 6;
// const int V_ILLNESS_MORE_THAN_ONE = 7;
// const int V_ILLNESS_NONE = 8;
const int V_SOMATIC_PAIN1_NEVER = 1;
// const int V_SOMATIC_PAIN1_SOMETIMES = 2;
// const int V_SOMATIC_PAIN1_ALWAYS = 3;
// const int V_SOMATIC_PAIN3_LT_3H = 1;
// const int V_SOMATIC_PAIN3_GT_3H = 2;
// const int V_SOMATIC_PAIN4_NOT_AT_ALL = 1;
// const int V_SOMATIC_PAIN4_LITTLE_UNPLEASANT = 2;
// const int V_SOMATIC_PAIN4_UNPLEASANT = 3;
// const int V_SOMATIC_PAIN4_VERY_UNPLEASANT = 4;
// const int V_SOMATIC_PAIN5_NO = 1;
// const int V_SOMATIC_PAIN5_YES = 2;
// const int V_SOMATIC_PAIN5_NOT_DONE_ANYTHING_INTERESTING = 3;
// const int V_SOMATIC_MAND2_NO = 1;
// const int V_SOMATIC_MAND2_YES = 2;
const int V_SOMATIC_DIS1_NEVER = 1;
// const int V_SOMATIC_DIS1_SOMETIMES = 2;
// const int V_SOMATIC_DIS1_ALWAYS = 3;
// const int V_SOMATIC_DIS2_NONE = 1;
// const int V_SOMATIC_DIS2_1_TO_3_DAYS = 2;
// const int V_SOMATIC_DIS2_4_OR_MORE_DAYS = 3;
// const int V_SOMATIC_DIS3_LT_3H = 1;
// const int V_SOMATIC_DIS3_GT_3H = 2;
// const int V_SOMATIC_DIS4_NOT_AT_ALL = 1;
// const int V_SOMATIC_DIS4_LITTLE_UNPLEASANT = 2;
// const int V_SOMATIC_DIS4_UNPLEASANT = 3;
// const int V_SOMATIC_DIS4_VERY_UNPLEASANT = 4;
// const int V_SOMATIC_DIS5_NO = 1;
// const int V_SOMATIC_DIS5_YES = 2;
// const int V_SOMATIC_DIS5_NOT_DONE_ANYTHING_INTERESTING = 3;
const int V_SLEEP_MAND2_NO = 1;
const int V_SLEEP_MAND2_YES_BUT_NOT_A_PROBLEM = 2;
// const int V_SLEEP_MAND2_YES = 3;
const int V_IRRIT_MAND2_NO = 1;
// const int V_IRRIT_MAND2_SOMETIMES = 2;
// const int V_IRRIT_MAND2_YES = 3;
// const int V_IRRIT3_SHOUTING_NO = 1;
const int V_IRRIT3_SHOUTING_WANTED_TO = 2;
// const int V_IRRIT3_SHOUTING_DID = 3;
// const int V_IRRIT4_ARGUMENTS_NO = 1;
// const int V_IRRIT4_ARGUMENTS_YES_JUSTIFIED = 2;
const int V_IRRIT4_ARGUMENTS_YES_UNJUSTIFIED = 3;
// const int V_DEPR5_COULD_CHEER_UP_YES = 1;
const int V_DEPR5_COULD_CHEER_UP_SOMETIMES = 2;
// const int V_DEPR5_COULD_CHEER_UP_NO = 3;
const int V_DEPTH1_DMV_WORSE_MORNING = 1;
const int V_DEPTH1_DMV_WORSE_EVENING = 2;
// const int V_DEPTH1_DMV_VARIES = 3;
// const int V_DEPTH1_DMV_NONE = 4;
// const int V_DEPTH2_LIBIDO_NA = 1;
// const int V_DEPTH2_LIBIDO_NO_CHANGE = 2;
// const int V_DEPTH2_LIBIDO_INCREASED = 3;
const int V_DEPTH2_LIBIDO_DECREASED = 4;
// const int V_DEPTH5_GUILT_NEVER = 1;
// const int V_DEPTH5_GUILT_WHEN_AT_FAULT = 2;
const int V_DEPTH5_GUILT_SOMETIMES = 3;
// const int V_DEPTH5_GUILT_OFTEN = 4;
const int V_DEPTH8_LNWL_NO = 1;
const int V_DEPTH8_LNWL_SOMETIMES = 2;
// const int V_DEPTH8_LNWL_ALWAYS = 3;
const int V_DEPTH9_SUICIDAL_THOUGHTS_NO = 1;
const int V_DEPTH9_SUICIDAL_THOUGHTS_YES_BUT_NEVER_WOULD = 2;
const int V_DEPTH9_SUICIDAL_THOUGHTS_YES = 3;
const int V_DOCTOR_YES = 1;
// const int V_DOCTOR_NO_BUT_OTHERS = 2;
// const int V_DOCTOR_NO = 3;
const int V_ANX_PHOBIA2_ALWAYS_SPECIFIC = 1;
// const int V_ANX_PHOBIA2_SOMETIMES_GENERAL = 2;
const int V_PHOBIAS_TYPE1_ALONE_PUBLIC_TRANSPORT = 1;
const int V_PHOBIAS_TYPE1_FAR_FROM_HOME = 2;
const int V_PHOBIAS_TYPE1_PUBLIC_SPEAKING_EATING = 3;
const int V_PHOBIAS_TYPE1_BLOOD = 4;
const int V_PHOBIAS_TYPE1_CROWDED_SHOPS = 5;
const int V_PHOBIAS_TYPE1_ANIMALS = 6;
const int V_PHOBIAS_TYPE1_BEING_WATCHED = 7;
const int V_PHOBIAS_TYPE1_ENCLOSED_SPACES_HEIGHTS = 8;
const int V_PHOBIAS_TYPE1_OTHER = 9;
const int V_PANIC1_N_PANICS_PAST_WEEK_0 = 1;
const int V_PANIC1_N_PANICS_PAST_WEEK_1 = 2;
const int V_PANIC1_N_PANICS_PAST_WEEK_GT_1 = 3;
// const int V_PANIC3_WORST_LT_10_MIN = 1;
const int V_PANIC3_WORST_GE_10_MIN = 2;
// const int V_COMP4_MAX_N_REPEATS_1 = 1;
// const int V_COMP4_MAX_N_REPEATS_2 = 2;
const int V_COMP4_MAX_N_REPEATS_GE_3 = 3;
// const int V_OBSESS_MAND1_SAME_THOUGHTS_REPEATED = 1;
const int V_OBSESS_MAND1_GENERAL_WORRIES = 2;
// const int V_OBSESS4_LT_15_MIN = 1;
const int V_OBSESS4_GE_15_MIN = 2;
// const int V_OVERALL_IMPAIRMENT_NONE = 1;
// const int V_OVERALL_IMPAIRMENT_DIFFICULT = 2;
// const int V_OVERALL_IMPAIRMENT_STOP_1_ACTIVITY = 3;
// const int V_OVERALL_IMPAIRMENT_STOP_GT_1_ACTIVITY = 4;
// Scoring constants:
const int MAX_TOTAL = 57;
const int MAX_SOMATIC = 4;
const int MAX_HYPO = 4;
const int MAX_IRRIT = 4;
const int MAX_CONC = 4;
const int MAX_FATIGUE = 4;
const int MAX_SLEEP = 4;
const int MAX_DEPR = 4;
const int MAX_DEPTHTS = 5;
const int MAX_PHOBIAS = 4;
const int MAX_WORRY = 4;
const int MAX_ANX = 4;
const int MAX_PANIC = 4;
const int MAX_COMP = 4;
const int MAX_OBSESS = 4;
// const int MAX_DEPCRIT1 = 3;
// const int MAX_DEPCRIT2 = 7;
// const int MAX_DEPCRIT3 = 8;
// ============================================================================
// Task registration
// ============================================================================
void initializeCisr(TaskFactory& factory)
{
static TaskRegistrar<Cisr> registered(factory);
}
// ============================================================================
// Constructor
// ============================================================================
Cisr::Cisr(CamcopsApp& app, DatabaseManager& db, const int load_pk) :
Task(app, db, CISR_TABLENAME, false, false, false) // ... anon, clin, resp
{
const QStringList fieldnames{
FN_ETHNIC,
FN_MARRIED,
FN_EMPSTAT,
FN_EMPTYPE,
FN_HOME,
FN_APPETITE1,
FN_WEIGHT1,
FN_WEIGHT2,
FN_WEIGHT3,
FN_APPETITE2,
FN_WEIGHT4,
FN_WEIGHT5,
FN_GP_YEAR,
FN_DISABLE,
FN_ILLNESS,
FN_SOMATIC_MAND1,
FN_SOMATIC_PAIN1,
FN_SOMATIC_PAIN2,
FN_SOMATIC_PAIN3,
FN_SOMATIC_PAIN4,
FN_SOMATIC_PAIN5,
FN_SOMATIC_MAND2,
FN_SOMATIC_DIS1,
FN_SOMATIC_DIS2,
FN_SOMATIC_DIS3,
FN_SOMATIC_DIS4,
FN_SOMATIC_DIS5,
FN_SOMATIC_DUR,
FN_FATIGUE_MAND1,
FN_FATIGUE_CAUSE1,
FN_FATIGUE_TIRED1,
FN_FATIGUE_TIRED2,
FN_FATIGUE_TIRED3,
FN_FATIGUE_TIRED4,
FN_FATIGUE_MAND2,
FN_FATIGUE_CAUSE2,
FN_FATIGUE_ENERGY1,
FN_FATIGUE_ENERGY2,
FN_FATIGUE_ENERGY3,
FN_FATIGUE_ENERGY4,
FN_FATIGUE_DUR,
FN_CONC_MAND1,
FN_CONC_MAND2,
FN_CONC1,
FN_CONC2,
FN_CONC3,
FN_CONC_DUR,
FN_CONC4,
FN_FORGET_DUR,
FN_SLEEP_MAND1,
FN_SLEEP_LOSE1,
FN_SLEEP_LOSE2,
FN_SLEEP_LOSE3,
FN_SLEEP_EMW,
FN_SLEEP_CAUSE,
FN_SLEEP_MAND2,
FN_SLEEP_GAIN1,
FN_SLEEP_GAIN2,
FN_SLEEP_GAIN3,
FN_SLEEP_DUR,
FN_IRRIT_MAND1,
FN_IRRIT_MAND2,
FN_IRRIT1,
FN_IRRIT2,
FN_IRRIT3,
FN_IRRIT4,
FN_IRRIT_DUR,
FN_HYPO_MAND1,
FN_HYPO_MAND2,
FN_HYPO1,
FN_HYPO2,
FN_HYPO3,
FN_HYPO4,
FN_HYPO_DUR,
FN_DEPR_MAND1,
FN_DEPR1,
FN_DEPR_MAND2,
FN_DEPR2,
FN_DEPR3,
FN_DEPR4,
FN_DEPR_CONTENT,
FN_DEPR5,
FN_DEPR_DUR,
FN_DEPTH1,
FN_DEPTH2,
FN_DEPTH3,
FN_DEPTH4,
FN_DEPTH5,
FN_DEPTH6,
FN_DEPTH7,
FN_DEPTH8,
FN_DEPTH9,
FN_DEPTH10,
FN_DOCTOR,
FN_WORRY_MAND1,
FN_WORRY_MAND2,
FN_WORRY_CONT1,
FN_WORRY2,
FN_WORRY3,
FN_WORRY4,
FN_WORRY5,
FN_WORRY_DUR,
FN_ANX_MAND1,
FN_ANX_MAND2,
FN_ANX_PHOBIA1,
FN_ANX_PHOBIA2,
FN_ANX2,
FN_ANX3,
FN_ANX4,
FN_ANX5,
FN_ANX_DUR,
FN_PHOBIAS_MAND,
FN_PHOBIAS_TYPE1,
FN_PHOBIAS1,
FN_PHOBIAS2,
FN_PHOBIAS3,
FN_PHOBIAS4,
FN_PHOBIAS_DUR,
FN_PANIC_MAND,
FN_PANIC1,
FN_PANIC2,
FN_PANIC3,
FN_PANIC4,
FN_PANSYM_A,
FN_PANSYM_B,
FN_PANSYM_C,
FN_PANSYM_D,
FN_PANSYM_E,
FN_PANSYM_F,
FN_PANSYM_G,
FN_PANSYM_H,
FN_PANSYM_I,
FN_PANSYM_J,
FN_PANSYM_K,
FN_PANSYM_L,
FN_PANSYM_M,
FN_PANIC5,
FN_PANIC_DUR,
FN_COMP_MAND1,
FN_COMP1,
FN_COMP2,
FN_COMP3,
FN_COMP4,
FN_COMP_DUR,
FN_OBSESS_MAND1,
FN_OBSESS_MAND2,
FN_OBSESS1,
FN_OBSESS2,
FN_OBSESS3,
FN_OBSESS4,
FN_OBSESS_DUR,
FN_OVERALL2,
};
for (const QString& fn : fieldnames) {
addField(fn, QMetaType::fromType<int>());
}
load(load_pk); // MUST ALWAYS CALL from derived Task constructor.
}
// ============================================================================
// Class info
// ============================================================================
QString Cisr::shortname() const
{
return "CIS-R";
}
QString Cisr::longname() const
{
return tr("Clinical Interview Schedule – Revised");
}
QString Cisr::description() const
{
return tr(
"Structured diagnostic interview, yielding ICD-10 diagnoses for "
"depressive and anxiety disorders."
);
}
// ============================================================================
// Instance info
// ============================================================================
bool Cisr::isComplete() const
{
CisrResult result = getResult();
return !result.incomplete;
}
QStringList Cisr::summary() const
{
CisrResult result = getResult();
return summaryForResult(result);
}
QStringList Cisr::detail() const
{
const QString decision_prefix = "+++ ";
QStringList lines;
CisrResult result = getResult();
if (result.incomplete) {
lines.append(incompleteMarker());
}
lines += summaryForResult(result);
lines.append(""); // blank line
lines += recordSummaryLines();
lines.append(""); // blank line
for (const QString& decision : result.decisions) {
lines.append(decision_prefix + decision.toHtmlEscaped());
}
return lines;
}
OpenableWidget* Cisr::editor(const bool read_only)
{
m_questionnaire = new DynamicQuestionnaire(
m_app,
std::bind(&Cisr::makePage, this, std::placeholders::_1),
std::bind(&Cisr::morePagesToGo, this, std::placeholders::_1)
);
m_questionnaire->setType(QuPage::PageType::Patient);
m_questionnaire->setReadOnly(read_only);
return m_questionnaire;
}
// ============================================================================
// Task-specific calculations
// ============================================================================
QStringList Cisr::summaryForResult(const Cisr::CisrResult& result) const
{
// Used so that we don't recalculate results again and again!
using mathfunc::scorePhrase;
using mathfunc::totalScorePhrase;
using stringfunc::bold;
QStringList lines;
auto addLine = [&lines](const QString& q, const QString& a) -> void {
const QString line = QString("%1: %2.").arg(q, bold(a));
lines.append(line);
};
auto addScore
= [this, &lines](
const QString& xstringname, const int score, const int max_score
) -> void {
const QString line
= scorePhrase(xstring(xstringname), score, max_score);
lines.append(line);
};
if (!result.incomplete) {
addLine(
"Probable primary diagnosis",
result.diagnosisName(result.diagnosis_1)
);
addLine(
"Probable secondary diagnosis",
result.diagnosisName(result.diagnosis_1)
);
lines.append(totalScorePhrase(result.getScore(), MAX_TOTAL));
addLine(xstring("impair_label"), functionalImpairment(result));
addScore("somatic_label", result.somatic_symptoms, MAX_SOMATIC);
addScore("hypo_label", result.hypochondria, MAX_HYPO);
addScore("irrit_label", result.irritability, MAX_IRRIT);
addScore("conc_label", result.concentration_poor, MAX_CONC);
addScore("fatigue_label", result.fatigue, MAX_FATIGUE);
addScore("sleep_label", result.sleep_problems, MAX_SLEEP);
addScore("depr_label", result.depression, MAX_DEPR);
addScore("depthts_label", result.depressive_thoughts, MAX_DEPTHTS);
addScore("somatic_label", result.somatic_symptoms, MAX_SOMATIC);
addScore("phobias_label", result.phobias_score, MAX_PHOBIAS);
addScore("worry_label", result.worry, MAX_WORRY);
addScore("anx_label", result.anxiety, MAX_ANX);
addScore("panic_label", result.panic, MAX_PANIC);
addScore("comp_label", result.compulsions, MAX_COMP);
addScore("obsess_label", result.obsessions, MAX_OBSESS);
}
lines.append(
QString("CIS-R suicide intent: %1.").arg(suicideIntent(result))
);
return lines;
}
// ============================================================================
// DynamicQuestionnaire callbacks
// ============================================================================
QuPagePtr Cisr::makePage(const int current_qnum)
{
// current_qnum is zero-based.
// Return nullptr to finish.
CisrQuestion q = getPageEnum(current_qnum);
return makePageFromEnum(q);
}
bool Cisr::morePagesToGo(const int current_qnum) const
{
CisrQuestion q = getPageEnum(current_qnum + 1);
return q != CQ::END_MARKER;
}
// ============================================================================
// Internals to build questionnaires
// ============================================================================
// For methods of enum iteration, see also
// - https://stackoverflow.com/questions/1390703/enumerate-over-an-enum-in-c
// - https://stackoverflow.com/questions/8357240/how-to-automatically-convert-strongly-typed-enum-into-int
Cisr::CisrQuestion Cisr::intToEnum(int qi) const
{
auto start = static_cast<int>(CisrQuestion::START_MARKER);
auto end = static_cast<int>(CisrQuestion::END_MARKER);
Q_ASSERT(qi >= start && qi <= end);
return static_cast<CisrQuestion>(qi);
}
int Cisr::enumToInt(CisrQuestion qe) const
{
return static_cast<int>(qe);
}
QString Cisr::fieldnameForQuestion(CisrQuestion q) const
{
switch (q) {
// case CQ::INTRO_1: // information only
// case CQ::INTRO_2: // information only
// case CQ::INTRO_DEMOGRAPHICS: // information only
case CQ::ETHNIC:
return FN_ETHNIC;
case CQ::MARRIED:
return FN_MARRIED;
case CQ::EMPSTAT:
return FN_EMPSTAT;
case CQ::EMPTYPE:
return FN_EMPTYPE;
case CQ::HOME:
return FN_HOME;
// case CQ::HEALTH_WELLBEING: // information only
case CQ::APPETITE1_LOSS_PAST_MONTH:
return FN_APPETITE1;
case CQ::WEIGHT1_LOSS_PAST_MONTH:
return FN_WEIGHT1;
case CQ::WEIGHT2_TRYING_TO_LOSE:
return FN_WEIGHT2;
case CQ::WEIGHT3_LOST_LOTS:
return FN_WEIGHT3;
case CQ::APPETITE2_INCREASE_PAST_MONTH:
return FN_APPETITE2;
case CQ::WEIGHT4_INCREASE_PAST_MONTH:
return FN_WEIGHT4;
// case CQ::WEIGHT4A: not used (= WEIGHT4 + pregnancy option)
case CQ::WEIGHT5_GAINED_LOTS:
return FN_WEIGHT5;
case CQ::GP_YEAR:
return FN_GP_YEAR;
case CQ::DISABLE:
return FN_DISABLE;
case CQ::ILLNESS:
return FN_ILLNESS;
case CQ::SOMATIC_MAND1_PAIN_PAST_MONTH:
return FN_SOMATIC_MAND1;
case CQ::SOMATIC_PAIN1_PSYCHOL_EXAC:
return FN_SOMATIC_PAIN1;
case CQ::SOMATIC_PAIN2_DAYS_PAST_WEEK:
return FN_SOMATIC_PAIN2;
case CQ::SOMATIC_PAIN3_GT_3H_ANY_DAY:
return FN_SOMATIC_PAIN3;
case CQ::SOMATIC_PAIN4_UNPLEASANT:
return FN_SOMATIC_PAIN4;
case CQ::SOMATIC_PAIN5_INTERRUPTED_INTERESTING:
return FN_SOMATIC_PAIN5;
case CQ::SOMATIC_MAND2_DISCOMFORT:
return FN_SOMATIC_MAND2;
case CQ::SOMATIC_DIS1_PSYCHOL_EXAC:
return FN_SOMATIC_DIS1;
case CQ::SOMATIC_DIS2_DAYS_PAST_WEEK:
return FN_SOMATIC_DIS2;
case CQ::SOMATIC_DIS3_GT_3H_ANY_DAY:
return FN_SOMATIC_DIS3;
case CQ::SOMATIC_DIS4_UNPLEASANT:
return FN_SOMATIC_DIS4;
case CQ::SOMATIC_DIS5_INTERRUPTED_INTERESTING:
return FN_SOMATIC_DIS5;
case CQ::SOMATIC_DUR:
return FN_SOMATIC_DUR;
case CQ::FATIGUE_MAND1_TIRED_PAST_MONTH:
return FN_FATIGUE_MAND1;
case CQ::FATIGUE_CAUSE1_TIRED:
return FN_FATIGUE_CAUSE1;
case CQ::FATIGUE_TIRED1_DAYS_PAST_WEEK:
return FN_FATIGUE_TIRED1;
case CQ::FATIGUE_TIRED2_GT_3H_ANY_DAY:
return FN_FATIGUE_TIRED2;
case CQ::FATIGUE_TIRED3_HAD_TO_PUSH:
return FN_FATIGUE_TIRED3;
case CQ::FATIGUE_TIRED4_DURING_ENJOYABLE:
return FN_FATIGUE_TIRED4;
case CQ::FATIGUE_MAND2_LACK_ENERGY_PAST_MONTH:
return FN_FATIGUE_MAND2;
case CQ::FATIGUE_CAUSE2_LACK_ENERGY:
return FN_FATIGUE_CAUSE2;
case CQ::FATIGUE_ENERGY1_DAYS_PAST_WEEK:
return FN_FATIGUE_ENERGY1;
case CQ::FATIGUE_ENERGY2_GT_3H_ANY_DAY:
return FN_FATIGUE_ENERGY2;
case CQ::FATIGUE_ENERGY3_HAD_TO_PUSH:
return FN_FATIGUE_ENERGY3;
case CQ::FATIGUE_ENERGY4_DURING_ENJOYABLE:
return FN_FATIGUE_ENERGY4;
case CQ::FATIGUE_DUR:
return FN_FATIGUE_DUR;
case CQ::CONC_MAND1_POOR_CONC_PAST_MONTH:
return FN_CONC_MAND1;
case CQ::CONC_MAND2_FORGETFUL_PAST_MONTH:
return FN_CONC_MAND2;
case CQ::CONC1_CONC_DAYS_PAST_WEEK:
return FN_CONC1;
case CQ::CONC2_CONC_FOR_TV_READING_CONVERSATION:
return FN_CONC2;
case CQ::CONC3_CONC_PREVENTED_ACTIVITIES:
return FN_CONC3;
case CQ::CONC_DUR:
return FN_CONC_DUR;
case CQ::CONC4_FORGOTTEN_IMPORTANT:
return FN_CONC4;
case CQ::FORGET_DUR:
return FN_FORGET_DUR;
case CQ::SLEEP_MAND1_LOSS_PAST_MONTH:
return FN_SLEEP_MAND1;
case CQ::SLEEP_LOSE1_NIGHTS_PAST_WEEK:
return FN_SLEEP_LOSE1;
case CQ::SLEEP_LOSE2_DIS_WORST_DURATION:
return FN_SLEEP_LOSE2;
case CQ::SLEEP_LOSE3_NIGHTS_GT_3H_DIS_PAST_WEEK:
return FN_SLEEP_LOSE3;
case CQ::SLEEP_EMW_PAST_WEEK:
return FN_SLEEP_EMW;
case CQ::SLEEP_CAUSE:
return FN_SLEEP_CAUSE;
case CQ::SLEEP_MAND2_GAIN_PAST_MONTH:
return FN_SLEEP_MAND2;
case CQ::SLEEP_GAIN1_NIGHTS_PAST_WEEK:
return FN_SLEEP_GAIN1;
case CQ::SLEEP_GAIN2_EXTRA_ON_LONGEST_NIGHT:
return FN_SLEEP_GAIN2;
case CQ::SLEEP_GAIN3_NIGHTS_GT_3H_EXTRA_PAST_WEEK:
return FN_SLEEP_GAIN3;
case CQ::SLEEP_DUR:
return FN_SLEEP_DUR;
case CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH:
return FN_IRRIT_MAND1;
case CQ::IRRIT_MAND2_THINGS_PAST_MONTH:
return FN_IRRIT_MAND2;
case CQ::IRRIT1_DAYS_PER_WEEK:
return FN_IRRIT1;
case CQ::IRRIT2_GT_1H_ANY_DAY:
return FN_IRRIT2;
case CQ::IRRIT3_WANTED_TO_SHOUT:
return FN_IRRIT3;
case CQ::IRRIT4_ARGUMENTS:
return FN_IRRIT4;
case CQ::IRRIT_DUR:
return FN_IRRIT_DUR;
case CQ::HYPO_MAND1_WORRIED_RE_HEALTH_PAST_MONTH:
return FN_HYPO_MAND1;
case CQ::HYPO_MAND2_WORRIED_RE_SERIOUS_ILLNESS:
return FN_HYPO_MAND2;
case CQ::HYPO1_DAYS_PAST_WEEK:
return FN_HYPO1;
case CQ::HYPO2_WORRY_TOO_MUCH:
return FN_HYPO2;
case CQ::HYPO3_HOW_UNPLEASANT:
return FN_HYPO3;
case CQ::HYPO4_CAN_DISTRACT:
return FN_HYPO4;
case CQ::HYPO_DUR:
return FN_HYPO_DUR;
case CQ::DEPR_MAND1_LOW_MOOD_PAST_MONTH:
return FN_DEPR_MAND1;
case CQ::DEPR1_LOW_MOOD_PAST_WEEK:
return FN_DEPR1;
case CQ::DEPR_MAND2_ENJOYMENT_PAST_MONTH:
return FN_DEPR_MAND2;
case CQ::DEPR2_ENJOYMENT_PAST_WEEK:
return FN_DEPR2;
case CQ::DEPR3_DAYS_PAST_WEEK:
return FN_DEPR3;
case CQ::DEPR4_GT_3H_ANY_DAY:
return FN_DEPR4;
case CQ::DEPR_CONTENT:
return FN_DEPR_CONTENT;
case CQ::DEPR5_COULD_CHEER_UP:
return FN_DEPR5;
case CQ::DEPR_DUR:
return FN_DEPR_DUR;
case CQ::DEPTH1_DIURNAL_VARIATION:
return FN_DEPTH1;
case CQ::DEPTH2_LIBIDO:
return FN_DEPTH2;
case CQ::DEPTH3_RESTLESS:
return FN_DEPTH3;
case CQ::DEPTH4_SLOWED:
return FN_DEPTH4;
case CQ::DEPTH5_GUILT:
return FN_DEPTH5;
case CQ::DEPTH6_WORSE_THAN_OTHERS:
return FN_DEPTH6;
case CQ::DEPTH7_HOPELESS:
return FN_DEPTH7;
case CQ::DEPTH8_LNWL:
return FN_DEPTH8;
case CQ::DEPTH9_SUICIDE_THOUGHTS:
return FN_DEPTH9;
case CQ::DEPTH10_SUICIDE_METHOD:
return FN_DEPTH10;
case CQ::DOCTOR:
return FN_DOCTOR;
// case CQ::DOCTOR2_PLEASE_TALK_TO: // info only
// case CQ::DEPR_OUTRO: // info only
case CQ::WORRY_MAND1_MORE_THAN_NEEDED_PAST_MONTH:
return FN_WORRY_MAND1;
case CQ::WORRY_MAND2_ANY_WORRIES_PAST_MONTH:
return FN_WORRY_MAND2;
case CQ::WORRY_CONT1:
return FN_WORRY_CONT1;
// case CQ::WORRY1_INFO_ONLY: // info only
case CQ::WORRY2_DAYS_PAST_WEEK:
return FN_WORRY2;
case CQ::WORRY3_TOO_MUCH:
return FN_WORRY3;
case CQ::WORRY4_HOW_UNPLEASANT:
return FN_WORRY4;
case CQ::WORRY5_GT_3H_ANY_DAY:
return FN_WORRY5;
case CQ::WORRY_DUR:
return FN_WORRY_DUR;
case CQ::ANX_MAND1_ANXIETY_PAST_MONTH:
return FN_ANX_MAND1;
case CQ::ANX_MAND2_TENSION_PAST_MONTH:
return FN_ANX_MAND2;
case CQ::ANX_PHOBIA1_SPECIFIC_PAST_MONTH:
return FN_ANX_PHOBIA1;
case CQ::ANX_PHOBIA2_SPECIFIC_OR_GENERAL:
return FN_ANX_PHOBIA2;
// case CQ::ANX1_INFO_ONLY: // info only
case CQ::ANX2_GENERAL_DAYS_PAST_WEEK:
return FN_ANX2;
case CQ::ANX3_GENERAL_HOW_UNPLEASANT:
return FN_ANX3;
case CQ::ANX4_GENERAL_PHYSICAL_SYMPTOMS:
return FN_ANX4;
case CQ::ANX5_GENERAL_GT_3H_ANY_DAY:
return FN_ANX5;
case CQ::ANX_DUR_GENERAL:
return FN_ANX_DUR;
case CQ::PHOBIAS_MAND_AVOIDANCE_PAST_MONTH:
return FN_PHOBIAS_MAND;
case CQ::PHOBIAS_TYPE1:
return FN_PHOBIAS_TYPE1;
case CQ::PHOBIAS1_DAYS_PAST_WEEK:
return FN_PHOBIAS1;
case CQ::PHOBIAS2_PHYSICAL_SYMPTOMS:
return FN_PHOBIAS2;
case CQ::PHOBIAS3_AVOIDANCE:
return FN_PHOBIAS3;
case CQ::PHOBIAS4_AVOIDANCE_DAYS_PAST_WEEK:
return FN_PHOBIAS4;
case CQ::PHOBIAS_DUR:
return FN_PHOBIAS_DUR;
case CQ::PANIC_MAND_PAST_MONTH:
return FN_PANIC_MAND;
case CQ::PANIC1_NUM_PAST_WEEK:
return FN_PANIC1;
case CQ::PANIC2_HOW_UNPLEASANT:
return FN_PANIC2;
case CQ::PANIC3_PANIC_GE_10_MIN:
return FN_PANIC3;
case CQ::PANIC4_RAPID_ONSET:
return FN_PANIC4;
// case CQ::PANSYM: // multiple stems
case CQ::PANIC5_ALWAYS_SPECIFIC_TRIGGER:
return FN_PANIC5;
case CQ::PANIC_DUR:
return FN_PANIC_DUR;
// case CQ::ANX_OUTRO: // info only
case CQ::COMP_MAND1_COMPULSIONS_PAST_MONTH:
return FN_COMP_MAND1;
case CQ::COMP1_DAYS_PAST_WEEK:
return FN_COMP1;
case CQ::COMP2_TRIED_TO_STOP:
return FN_COMP2;
case CQ::COMP3_UPSETTING:
return FN_COMP3;
case CQ::COMP4_MAX_N_REPETITIONS:
return FN_COMP4;
case CQ::COMP_DUR:
return FN_COMP_DUR;
case CQ::OBSESS_MAND1_OBSESSIONS_PAST_MONTH:
return FN_OBSESS_MAND1;
case CQ::OBSESS_MAND2_SAME_THOUGHTS_OR_GENERAL:
return FN_OBSESS_MAND2;
case CQ::OBSESS1_DAYS_PAST_WEEK:
return FN_OBSESS1;
case CQ::OBSESS2_TRIED_TO_STOP:
return FN_OBSESS2;
case CQ::OBSESS3_UPSETTING:
return FN_OBSESS3;
case CQ::OBSESS4_MAX_DURATION:
return FN_OBSESS4;
case CQ::OBSESS_DUR:
return FN_OBSESS_DUR;
// case CQ::OVERALL1: // info only
case CQ::OVERALL2_IMPACT_PAST_WEEK:
return FN_OVERALL2;
default: // e.g. for info-only fields
return "";
}
}
QString Cisr::tagForQuestion(CisrQuestion q) const
{
const QString fieldname = fieldnameForQuestion(q);
if (!fieldname.isEmpty()) {
return fieldname.toUpper();
}
switch (q) {
// Aim to be relatively cryptic; use the original CIS-R tags, not our
// expanded explanatory versions (in case anyone uses the debug version
// for patient testing!).
case CQ::INTRO_1:
return "INTRO_1";
case CQ::INTRO_2:
return "INTRO_2";
case CQ::INTRO_DEMOGRAPHICS:
return "INTRO_DEMOGRAPHICS";
case CQ::HEALTH_WELLBEING:
return "HEALTH_WELLBEING";
case CQ::DOCTOR2_PLEASE_TALK_TO:
return "DOCTOR2";
case CQ::DEPR_OUTRO:
return "DEPR_OUTRO";
case CQ::WORRY1_INFO_ONLY:
return "WORRY1";
case CQ::ANX1_INFO_ONLY:
return "ANX1";
case CQ::PANSYM:
return "PANSYM";
case CQ::ANX_OUTRO:
return "ANX_OUTRO";
case CQ::OVERALL1_INFO_ONLY:
return "OVERALL1";
case CQ::THANKS_FINISHED:
return "THANKS_FINISHED";
case CQ::END_MARKER:
return "END_MARKER";
default:
return "UNKNOWN_TAG";
}
}
QVariant Cisr::valueForQuestion(CisrQuestion q) const
{
const QString fieldname = fieldnameForQuestion(q);
Q_ASSERT(!fieldname.isEmpty());
// ... have we asked for a field from an info-only page?
return value(fieldname);
}
int Cisr::intValueForQuestion(CisrQuestion q) const
{
return valueForQuestion(q).toInt();
}
bool Cisr::answerIsNo(CisrQuestion q, int value) const
{
if (value == V_UNKNOWN) { // "Please look it up for me"
value = intValueForQuestion(q);
}
if (QUESTIONS_1_NO_2_YES.contains(q)) {
return value == 1;
}
if (QUESTIONS_1_YES_2_NO.contains(q)) {
return value == 2;
}
qCritical() << "answerIsNo() called for inappropriate question"
<< enumToInt(q) << tagForQuestion(q);
return false;
}
bool Cisr::answerIsYes(CisrQuestion q, int value) const
{
if (value == V_UNKNOWN) { // "Please look it up for me"
value = intValueForQuestion(q);
}
if (QUESTIONS_1_NO_2_YES.contains(q)) {
return value == 2;
}
if (QUESTIONS_1_YES_2_NO.contains(q)) {
return value == 1;
}
qCritical() << "answerIsYes() called for inappropriate question"
<< enumToInt(q) << tagForQuestion(q);
return false;
}
bool Cisr::answered(CisrQuestion q, int value) const
{
if (value == V_UNKNOWN) { // "Please look it up for me"
value = intValueForQuestion(q);
}
return value != V_MISSING;
}
Cisr::CisrQuestion Cisr::nextQ(Cisr::CisrQuestion q, Cisr::CisrResult& r) const
{
// ANY CHANGES HERE MUST BE REFLECTED IN THE PYTHON CODE AND VICE VERSA.
// 1. Returns the next question to be offered.
// 2. Scores as it goes, also flagging up if the data are incomplete.
// Rationale: we have to apply a sequential logic to test for completeness
// and to determine the next question in a sequence, so we may as well
// score at the same time (very low overhead).
// Also, this method allows direct comparison with the original CIS-R
// code.
auto chooseFinalPage = [&r]() -> Cisr::CisrQuestion {
// If there have been significant symptoms:
// (1) OVERALL1_INFO_ONLY - "Thank you for answering..."
// (2) OVERALL2_IMPACT_PAST_WEEK - "What's the impact been?"
// (3) THANKS_FINISHED - "All done."
// Otherwise:
// (1) THANKS_FINISHED - "All done."
return r.needsImpairmentQuestion() ? CQ::OVERALL1_INFO_ONLY
: CQ::THANKS_FINISHED;
};
QVariant var_q;
int v = V_MISSING;
#ifdef SHOW_QUESTIONS_CONSIDERED
r.decide(QString("Considering question %1: %2")
.arg(QString::number(enumToInt(q)), tagForQuestion(q)));
#endif
const QString fieldname = fieldnameForQuestion(q);
if (!fieldname.isEmpty()) { // eliminates prompt-only questions
var_q = value(fieldname);
if (var_q.isNull()) {
if (!QUESTIONS_DEMOGRAPHICS.contains(q)) {
// From a diagnostic point of view, OK to have missing
// demographic information. Otherwise:
r.decide("INCOMPLETE INFORMATION. STOPPING.");
r.incomplete = true;
}
} else {
v = var_q.toInt();
}
}
int next_q = -1;
auto jumpTo = [this, &next_q](CisrQuestion qe) {
next_q = enumToInt(qe);
};
// If there is no special handling for a question, then after the
// switch() statement we will move to the next question in sequence.
// So only special "skip" situations are handled here.
switch (q) {
// FOLLOW THE EXACT SEQUENCE of the CIS-R. Don't agglomerate case
// statements just because it's shorter (except empty ones when
// they are in sequence). Clarity is key.
// ----------------------------------------------------------------
// Demographics/preamble
// ----------------------------------------------------------------
case CQ::INTRO_1:
case CQ::INTRO_2:
case CQ::INTRO_DEMOGRAPHICS:
case CQ::ETHNIC:
case CQ::MARRIED:
case CQ::EMPSTAT:
case CQ::EMPTYPE:
case CQ::HOME:
case CQ::HEALTH_WELLBEING:
// Nothing special
break;
// ----------------------------------------------------------------
// Appetite/weight
// ----------------------------------------------------------------
case CQ::APPETITE1_LOSS_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("No loss of appetite in past month.");
jumpTo(CQ::APPETITE2_INCREASE_PAST_MONTH);
} else if (answerIsYes(q, v)) {
r.decide(
"Loss of appetite in past month. "
"Incrementing depr_crit_3_somatic_synd."
);
r.depr_crit_3_somatic_synd += 1;
r.weight_change = WTCHANGE_APPETITE_LOSS;
}
break;
case CQ::WEIGHT1_LOSS_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("No weight loss.");
jumpTo(CQ::GP_YEAR);
}
break;
case CQ::WEIGHT2_TRYING_TO_LOSE:
if (v == V_WEIGHT2_WTLOSS_TRYING) {
// Trying to lose weight. Move on.
r.decide("Weight loss but it was deliberate.");
} else if (v == V_WEIGHT2_WTLOSS_NOTTRYING) {
r.decide("Non-deliberate weight loss.");
r.weight_change = WTCHANGE_NONDELIBERATE_WTLOSS_OR_WTGAIN;
}
break;
case CQ::WEIGHT3_LOST_LOTS:
if (v == V_WEIGHT3_WTLOSS_GE_HALF_STONE) {
r.decide(
"Weight loss >=0.5st in past month. "
"Incrementing depr_crit_3_somatic_synd."
);
r.weight_change = WTCHANGE_WTLOSS_GE_HALF_STONE;
r.depr_crit_3_somatic_synd += 1;
}
r.decide(
"Loss of weight, so skipping appetite/weight gain questions."
);
jumpTo(CQ::GP_YEAR);
break;
case CQ::APPETITE2_INCREASE_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("No increase in appetite in past month.");
jumpTo(CQ::GP_YEAR);
}
break;
case CQ::WEIGHT4_INCREASE_PAST_MONTH:
if (answerIsYes(q, v)) {
r.decide("Weight gain.");
r.weight_change = WTCHANGE_NONDELIBERATE_WTLOSS_OR_WTGAIN;
} else if (answered(q, v)) {
r.decide("No weight gain, or weight gain but pregnant.");
jumpTo(CQ::GP_YEAR);
}
break;
case CQ::WEIGHT5_GAINED_LOTS:
if (v == V_WEIGHT5_WTGAIN_GE_HALF_STONE
&& r.weight_change
== WTCHANGE_NONDELIBERATE_WTLOSS_OR_WTGAIN) {
// ... redundant check on weight_change, I think!
r.decide("Weight gain >=0.5 st in past month.");
r.weight_change = WTCHANGE_WTGAIN_GE_HALF_STONE;
}
break;
// ----------------------------------------------------------------
// Somatic symptoms
// ----------------------------------------------------------------
case CQ::GP_YEAR:
// Score the preceding block:
if (r.weight_change == WTCHANGE_WTLOSS_GE_HALF_STONE
&& answerIsYes(CQ::APPETITE1_LOSS_PAST_MONTH)) {
r.decide(
"Appetite loss and weight loss >=0.5st in past month. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
if (r.weight_change == WTCHANGE_WTGAIN_GE_HALF_STONE
&& answerIsYes(CQ::APPETITE2_INCREASE_PAST_MONTH)) {
r.decide(
"Appetite gain and weight gain >=0.5st in past month. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
break;
case CQ::DISABLE:
if (answerIsNo(q)) {
r.decide("No longstanding illness/disability/infirmity.");
jumpTo(CQ::SOMATIC_MAND1_PAIN_PAST_MONTH);
}
break;
case CQ::ILLNESS:
break;
case CQ::SOMATIC_MAND1_PAIN_PAST_MONTH:
if (answerIsNo(q)) {
r.decide("No aches/pains in past month.");
jumpTo(CQ::SOMATIC_MAND2_DISCOMFORT);
}
break;
case CQ::SOMATIC_PAIN1_PSYCHOL_EXAC:
if (v == V_SOMATIC_PAIN1_NEVER) {
r.decide("Pains never exacerbated by low mood/anxiety/stress."
);
jumpTo(CQ::SOMATIC_MAND2_DISCOMFORT);
}
break;
case CQ::SOMATIC_PAIN2_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("No pain in last 7 days.");
jumpTo(CQ::SOMATIC_MAND2_DISCOMFORT);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Pain on >=4 of last 7 days. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
case CQ::SOMATIC_PAIN3_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Pain for >3h on any day in past week. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
case CQ::SOMATIC_PAIN4_UNPLEASANT:
if (v >= V_HOW_UNPLEASANT_UNPLEASANT) {
r.decide(
"Pain 'unpleasant' or worse in past week. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
case CQ::SOMATIC_PAIN5_INTERRUPTED_INTERESTING:
if (answerIsYes(q, v)) {
r.decide(
"Pain interrupted an interesting activity in past week. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
r.decide("There was pain, so skip 'discomfort' section.");
jumpTo(CQ::SOMATIC_DUR); // skip SOMATIC_MAND2
break;
case CQ::SOMATIC_MAND2_DISCOMFORT:
if (answerIsNo(q, v)) {
r.decide("No discomfort.");
jumpTo(CQ::FATIGUE_MAND1_TIRED_PAST_MONTH);
}
break;
case CQ::SOMATIC_DIS1_PSYCHOL_EXAC:
if (v == V_SOMATIC_DIS1_NEVER) {
r.decide(
"Discomfort never exacerbated by being "
"low/anxious/stressed."
);
jumpTo(CQ::FATIGUE_MAND1_TIRED_PAST_MONTH);
}
break;
case CQ::SOMATIC_DIS2_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("No discomfort in last 7 days.");
jumpTo(CQ::FATIGUE_MAND1_TIRED_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Discomfort on >=4 days in past week. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
case CQ::SOMATIC_DIS3_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Discomfort for >3h on any day in past week. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
case CQ::SOMATIC_DIS4_UNPLEASANT:
if (v >= V_HOW_UNPLEASANT_UNPLEASANT) {
r.decide(
"Discomfort 'unpleasant' or worse in past week. "
"Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
case CQ::SOMATIC_DIS5_INTERRUPTED_INTERESTING:
if (answerIsYes(q, v)) {
r.decide(
"Discomfort interrupted an interesting activity in past "
"week. Incrementing somatic_symptoms."
);
r.somatic_symptoms += 1;
}
break;
// ----------------------------------------------------------------
// Fatigue/energy
// ----------------------------------------------------------------
case CQ::FATIGUE_MAND1_TIRED_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("Not tired.");
jumpTo(CQ::FATIGUE_MAND2_LACK_ENERGY_PAST_MONTH);
}
break;
case CQ::FATIGUE_CAUSE1_TIRED:
if (v == V_FATIGUE_CAUSE_EXERCISE) {
r.decide("Tired due to exercise. Move on.");
jumpTo(CQ::CONC_MAND1_POOR_CONC_PAST_MONTH);
}
break;
case CQ::FATIGUE_TIRED1_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("Not tired in past week.");
jumpTo(CQ::FATIGUE_MAND2_LACK_ENERGY_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Tired on >=4 days in past week. Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_TIRED2_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Tired for >3h on any day in past week. "
"Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_TIRED3_HAD_TO_PUSH:
if (answerIsYes(q, v)) {
r.decide(
"Tired enough to have to push self during past week. "
"Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_TIRED4_DURING_ENJOYABLE:
if (answerIsYes(q, v)) {
r.decide(
"Tired during an enjoyable activity during past week. "
"Incrementing fatigue."
);
r.fatigue += 1;
}
r.decide("There was tiredness, so skip 'lack of energy' section.");
jumpTo(CQ::FATIGUE_DUR); // skip FATIGUE_MAND2
break;
case CQ::FATIGUE_MAND2_LACK_ENERGY_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("Not lacking in energy.");
jumpTo(CQ::CONC_MAND1_POOR_CONC_PAST_MONTH);
}
break;
case CQ::FATIGUE_CAUSE2_LACK_ENERGY:
if (v == V_FATIGUE_CAUSE_EXERCISE) {
r.decide("Lacking in energy due to exercise. Move on.");
jumpTo(CQ::CONC_MAND1_POOR_CONC_PAST_MONTH);
}
break;
case CQ::FATIGUE_ENERGY1_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("Not lacking in energy during last week.");
jumpTo(CQ::CONC_MAND1_POOR_CONC_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Lacking in energy on >=4 days in past week. "
"Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_ENERGY2_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Lacking in energy for >3h on any day in past week. "
"Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_ENERGY3_HAD_TO_PUSH:
if (answerIsYes(q, v)) {
r.decide(
"Lacking in energy enough to have to push self during "
"past week. Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_ENERGY4_DURING_ENJOYABLE:
if (answerIsYes(q, v)) {
r.decide(
"Lacking in energy during an enjoyable activity during "
"past week. Incrementing fatigue."
);
r.fatigue += 1;
}
break;
case CQ::FATIGUE_DUR:
// Score preceding:
if (r.somatic_symptoms >= 2 && r.fatigue >= 2) {
r.decide(
"somatic >= 2 and fatigue >= 2. "
"Incrementing neurasthenia."
);
r.neurasthenia += 1;
}
break;
// ----------------------------------------------------------------
// Concentration/memory
// ----------------------------------------------------------------
case CQ::CONC_MAND1_POOR_CONC_PAST_MONTH:
// Score preceding:
if (r.fatigue >= 2) {
r.decide(
"fatigue >= 2. "
"Incrementing depr_crit_1_mood_anhedonia_energy."
);
r.depr_crit_1_mood_anhedonia_energy += 1;
}
break;
case CQ::CONC_MAND2_FORGETFUL_PAST_MONTH:
if (answerIsNo(CQ::CONC_MAND1_POOR_CONC_PAST_MONTH)
&& answerIsNo(q, v)) {
r.decide("No problems with concentration or forgetfulness.");
jumpTo(CQ::SLEEP_MAND1_LOSS_PAST_MONTH);
}
break;
case CQ::CONC1_CONC_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("No concentration/memory problems in past week.");
jumpTo(CQ::SLEEP_MAND1_LOSS_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Problems with concentration/memory problems on >=4 days "
"in past week. Incrementing concentration_poor."
);
r.concentration_poor += 1;
}
if (answerIsNo(CQ::CONC_MAND1_POOR_CONC_PAST_MONTH)
&& answerIsYes(CQ::CONC_MAND2_FORGETFUL_PAST_MONTH)) {
r.decide(
"Forgetfulness, not concentration, problems; skip over "
"more detailed concentration questions."
);
jumpTo(CQ::CONC4_FORGOTTEN_IMPORTANT);
// ... skip CONC2, CONC3, CONC_DUR
}
break;
case CQ::CONC2_CONC_FOR_TV_READING_CONVERSATION:
if (answerIsNo(q, v)) {
r.decide(
"Couldn't concentrate on at least one of {TV, newspaper, "
"conversation}. Incrementing concentration_poor."
);
r.concentration_poor += 1;
}
break;
case CQ::CONC3_CONC_PREVENTED_ACTIVITIES:
if (answerIsYes(q, v)) {
r.decide(
"Problems with concentration stopped usual/desired "
"activity. Incrementing concentration_poor."
);
r.concentration_poor += 1;
}
break;
case CQ::CONC_DUR:
if (answerIsNo(CQ::CONC_MAND2_FORGETFUL_PAST_MONTH)) {
jumpTo(CQ::SLEEP_MAND1_LOSS_PAST_MONTH);
}
break;
case CQ::CONC4_FORGOTTEN_IMPORTANT:
if (answerIsYes(q, v)) {
r.decide(
"Forgotten something important in past week. "
"Incrementing concentration_poor."
);
r.concentration_poor += 1;
}
break;
case CQ::FORGET_DUR:
break;
// ----------------------------------------------------------------
// Sleep
// ----------------------------------------------------------------
case CQ::SLEEP_MAND1_LOSS_PAST_MONTH:
// Score previous block:
if (r.concentration_poor >= 2) {
r.decide(
"concentration >= 2. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
// This question:
if (answerIsNo(q, v)) {
r.decide(
"No problems with sleep loss in past month. Moving on."
);
jumpTo(CQ::SLEEP_MAND2_GAIN_PAST_MONTH);
}
break;
case CQ::SLEEP_LOSE1_NIGHTS_PAST_WEEK:
if (v == V_NIGHTS_IN_PAST_WEEK_0) {
r.decide("No problems with sleep in past week. Moving on.");
jumpTo(CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH);
} else if (v == V_NIGHTS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Problems with sleep on >=4 nights in past week. "
"Incrementing sleep_problems."
);
r.sleep_problems += 1;
}
break;
case CQ::SLEEP_LOSE2_DIS_WORST_DURATION:
if (v == V_SLEEP_CHANGE_LT_15_MIN) {
r.decide(
"Less than 15min maximum delayed initiation of sleep in "
"past week. Moving on."
);
jumpTo(CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH);
} else if (v == V_SLEEP_CHANGE_15_MIN_TO_1_H) {
r.decide(
"15min-1h maximum delayed initiation of sleep in past "
"week. Incrementing sleep_problems."
);
r.sleep_problems += 1;
} else if (v == V_SLEEP_CHANGE_1_TO_3_H || v == V_SLEEP_CHANGE_GT_3_H) {
r.decide(
">=1h maximum delayed initiation of sleep in past week. "
"Adding 2 to sleep_problems."
);
r.sleep_problems += 2;
}
break;
case CQ::SLEEP_LOSE3_NIGHTS_GT_3H_DIS_PAST_WEEK:
if (v == V_NIGHTS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
">=4 nights in past week with >=3h delayed initiation of "
"sleep. Incrementing sleep_problems."
);
r.sleep_problems += 1;
}
break;
case CQ::SLEEP_EMW_PAST_WEEK:
if (answerIsYes(q, v)) {
r.decide(
"EMW of >2h in past week. "
"Setting sleep_change to SLEEPCHANGE_EMW. "
"Incrementing depr_crit_3_somatic_synd."
);
// Was: SLEEPCH += answer - 1 (which only does anything for a
// "yes" (2) answer).
// ... but at this point, SLEEPCH is always 0.
r.sleep_change = SLEEPCHANGE_EMW; // LIKELY REDUNDANT.
r.depr_crit_3_somatic_synd += 1;
if (r.sleep_problems >= 1) {
r.decide(
"EMW of >2h in past week and sleep_problems >= 1; "
"setting sleep_change to SLEEPCHANGE_EMW."
);
r.sleep_change = SLEEPCHANGE_EMW;
}
} else if (answerIsNo(q, v)) {
r.decide("No EMW of >2h in past week.");
if (r.sleep_problems >= 1) {
r.decide(
"No EMW of >2h in past week, and sleep_problems >= 1. "
"Setting sleep_change to SLEEPCHANGE_INSOMNIA_NOT_EMW."
);
r.sleep_change = SLEEPCHANGE_INSOMNIA_NOT_EMW;
}
}
break;
case CQ::SLEEP_CAUSE:
r.decide("Problems with sleep loss; skipping over sleep gain.");
jumpTo(CQ::SLEEP_DUR);
break;
case CQ::SLEEP_MAND2_GAIN_PAST_MONTH:
if (v == V_SLEEP_MAND2_NO
|| v == V_SLEEP_MAND2_YES_BUT_NOT_A_PROBLEM) {
r.decide("No problematic sleep gain. Moving on.");
jumpTo(CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH);
}
break;
case CQ::SLEEP_GAIN1_NIGHTS_PAST_WEEK:
if (v == V_NIGHTS_IN_PAST_WEEK_0) {
r.decide("No nights with sleep problems [gain] in past week.");
jumpTo(CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH);
} else if (v == V_NIGHTS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Problems with sleep [gain] on >=4 nights in past week. "
"Incrementing sleep_problems."
);
r.sleep_problems += 1;
}
break;
case CQ::SLEEP_GAIN2_EXTRA_ON_LONGEST_NIGHT:
if (v == V_SLEEP_CHANGE_LT_15_MIN) {
r.decide("Sleep gain <15min. Moving on.");
jumpTo(CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH);
} else if (v == V_SLEEP_CHANGE_15_MIN_TO_1_H) {
r.decide("Sleep gain 15min-1h. Incrementing sleep_problems.");
r.sleep_problems += 1;
} else if (v >= V_SLEEP_CHANGE_1_TO_3_H) {
r.decide(
"Sleep gain >=1h. "
"Adding 2 to sleep_problems. "
"Setting sleep_change to SLEEPCHANGE_INCREASE."
);
r.sleep_problems += 2;
r.sleep_change = SLEEPCHANGE_INCREASE;
// Note that in the original, if the answer was 3
// (V_SLEEP_CHANGE_1_TO_3_H) or greater, first 2 was added to
// sleep, and then if sleep was >=1, sleepch [sleep_change] was
// set to 3. However, sleep is never decremented/set below 0,
// so that was a redundant test (always true).
}
break;
case CQ::SLEEP_GAIN3_NIGHTS_GT_3H_EXTRA_PAST_WEEK:
if (v == V_NIGHTS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Sleep gain of >3h on >=4 nights in past week. "
"Incrementing sleep_problems."
);
r.sleep_problems += 1;
}
break;
case CQ::SLEEP_DUR:
break;
// ----------------------------------------------------------------
// Irritability
// ----------------------------------------------------------------
case CQ::IRRIT_MAND1_PEOPLE_PAST_MONTH:
// Score previous block:
if (r.sleep_problems >= 2) {
r.decide(
"sleep_problems >= 2. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
// This bit erroneously lived under IRRIT_DUR in the original; see
// discussion there:
if (r.sleep_problems >= 2 && r.fatigue >= 2) {
r.decide(
"sleep_problems >=2 and fatigue >=2. "
"Incrementing neurasthenia."
);
r.neurasthenia += 1;
}
// This question:
if (answerIsYes(q, v)) {
r.decide(
"Irritability (people) in past month; exploring further."
);
jumpTo(CQ::IRRIT1_DAYS_PER_WEEK);
}
break;
case CQ::IRRIT_MAND2_THINGS_PAST_MONTH:
if (v == V_IRRIT_MAND2_NO) {
r.decide("No irritability. Moving on.");
jumpTo(CQ::HYPO_MAND1_WORRIED_RE_HEALTH_PAST_MONTH);
} else if (answered(q, v)) {
r.decide(
"Irritability (things) in past month; exploring further."
);
}
break;
case CQ::IRRIT1_DAYS_PER_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("No irritability in past week. Moving on.");
jumpTo(CQ::HYPO_MAND1_WORRIED_RE_HEALTH_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Irritable on >=4 days in past week. "
"Incrementing irritability."
);
r.irritability += 1;
}
break;
case CQ::IRRIT2_GT_1H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Irritable for >1h on any day in past week. "
"Incrementing irritability."
);
r.irritability += 1;
}
break;
case CQ::IRRIT3_WANTED_TO_SHOUT:
if (v >= V_IRRIT3_SHOUTING_WANTED_TO) {
r.decide("Wanted to or did shout. Incrementing irritability.");
r.irritability += 1;
}
break;
case CQ::IRRIT4_ARGUMENTS:
if (v == V_IRRIT4_ARGUMENTS_YES_UNJUSTIFIED) {
r.decide(
"Arguments without justification. "
"Incrementing irritability."
);
r.irritability += 1;
}
break;
case CQ::IRRIT_DUR:
// Score recent things:
if (r.irritability >= 2 && r.fatigue >= 2) {
r.decide(
"irritability >=2 and fatigue >=2. "
"Incrementing neurasthenia."
);
r.neurasthenia += 1;
}
// In the original, we had the rule "sleep_problems >=2 and
// fatigue >=2 -> incrementing neurasthenia" here, but that would
// mean we would fail to score sleep if the patient didn't report
// irritability (because if you say no at IRRIT_MAND2, you jump
// beyond this point to HYPO_MAND1). Checked with Glyn Lewis
// 2017-12-04, who agreed on 2017-12-05. Therefore, moved to
// IRRIT_MAND1 as above.
// Note that the only implication would have been potential small
// mis-scoring of the CFS criterion (not any of the diagnoses that
// the CIS-R reports as its primary/secondary diagnoses).
break;
// ----------------------------------------------------------------
// Hypochondriasis
// ----------------------------------------------------------------
case CQ::HYPO_MAND1_WORRIED_RE_HEALTH_PAST_MONTH:
if (answerIsYes(q, v)) {
r.decide(
"No worries about physical health in past month. Moving "
"on."
);
jumpTo(CQ::HYPO1_DAYS_PAST_WEEK);
}
break;
case CQ::HYPO_MAND2_WORRIED_RE_SERIOUS_ILLNESS:
if (answerIsNo(q, v)) {
r.decide(
"No worries about having a serious illness. Moving on."
);
jumpTo(CQ::DEPR_MAND1_LOW_MOOD_PAST_MONTH);
}
break;
case CQ::HYPO1_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide(
"No days in past week worrying about health. Moving on."
);
jumpTo(CQ::DEPR_MAND1_LOW_MOOD_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Worries about health on >=4 days in past week. "
"Incrementing hypochondria."
);
r.hypochondria += 1;
}
break;
case CQ::HYPO2_WORRY_TOO_MUCH:
if (answerIsYes(q, v)) {
r.decide(
"Worrying too much about health. "
"Incrementing hypochondria."
);
r.hypochondria += 1;
}
break;
case CQ::HYPO3_HOW_UNPLEASANT:
if (v >= V_HOW_UNPLEASANT_UNPLEASANT) {
r.decide(
"Worrying re health 'unpleasant' or worse in past week. "
"Incrementing hypochondria."
);
r.hypochondria += 1;
}
break;
case CQ::HYPO4_CAN_DISTRACT:
if (answerIsNo(q, v)) {
r.decide(
"Cannot take mind off health worries by doing something "
"else. Incrementing hypochondria."
);
r.hypochondria += 1;
}
break;
case CQ::HYPO_DUR:
break;
// ----------------------------------------------------------------
// Depression
// ----------------------------------------------------------------
case CQ::DEPR_MAND1_LOW_MOOD_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("Mood not low in past month. Moving to anhedonia.");
jumpTo(CQ::DEPR_MAND2_ENJOYMENT_PAST_MONTH);
}
break;
case CQ::DEPR1_LOW_MOOD_PAST_WEEK:
break;
case CQ::DEPR_MAND2_ENJOYMENT_PAST_MONTH:
if (v == V_ANHEDONIA_ENJOYING_NORMALLY
&& answerIsNo(CQ::DEPR1_LOW_MOOD_PAST_WEEK)) {
r.decide(
"Neither low mood nor anhedonia in past month. Moving on."
);
jumpTo(CQ::WORRY_MAND1_MORE_THAN_NEEDED_PAST_MONTH);
}
break;
case CQ::DEPR2_ENJOYMENT_PAST_WEEK:
if (v == V_ANHEDONIA_ENJOYING_NORMALLY
&& answerIsNo(CQ::DEPR_MAND1_LOW_MOOD_PAST_MONTH)) {
r.decide(
"No anhedonia in past week and no low mood in past month. "
"Moving on."
);
jumpTo(CQ::WORRY_MAND1_MORE_THAN_NEEDED_PAST_MONTH);
} else if (v >= V_ANHEDONIA_ENJOYING_LESS) {
r.decide(
"Partial or complete anhedonia in past week. "
"Incrementing depression. "
"Incrementing depr_crit_1_mood_anhedonia_energy. "
"Incrementing depr_crit_3_somatic_synd."
);
r.depression += 1;
r.depr_crit_1_mood_anhedonia_energy += 1;
r.depr_crit_3_somatic_synd += 1;
}
break;
case CQ::DEPR3_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Low mood or anhedonia on >=4 days in past week. "
"Incrementing depression."
);
r.depression += 1;
}
break;
case CQ::DEPR4_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Low mood or anhedonia for >3h/day on at least one day "
"in past week. Incrementing depression."
);
r.depression += 1;
if (intValueForQuestion(CQ::DEPR3_DAYS_PAST_WEEK)
&& answerIsYes(CQ::DEPR1_LOW_MOOD_PAST_WEEK)) {
r.decide(
"(A) Low mood in past week, and "
"(B) low mood or anhedonia for >3h/day on at least "
"one day in past week, and "
"(C) low mood or anhedonia on >=4 days in past week. "
"Incrementing depr_crit_1_mood_anhedonia_energy."
);
r.depr_crit_1_mood_anhedonia_energy += 1;
}
}
break;
case CQ::DEPR_CONTENT:
break;
case CQ::DEPR5_COULD_CHEER_UP:
if (v >= V_DEPR5_COULD_CHEER_UP_SOMETIMES) {
r.decide(
"'Sometimes' or 'never' cheered up by nice things. "
"Incrementing depression. "
"Incrementing depr_crit_3_somatic_synd."
);
r.depression += 1;
r.depr_crit_3_somatic_synd += 1;
}
break;
case CQ::DEPR_DUR:
if (v >= V_DURATION_2W_6M) {
r.decide(
"Depressive symptoms for >=2 weeks. "
"Setting depression_at_least_2_weeks."
);
r.depression_at_least_2_weeks = true;
}
// This code was at the start of DEPTH1, but involves skipping over
// DEPTH1; since we never get to DEPTH1 without coming here, we can
// move it here:
if (r.depression == 0) {
r.decide(
"Score for 'depression' is 0; skipping over depressive "
"thought content questions."
);
jumpTo(CQ::WORRY_MAND1_MORE_THAN_NEEDED_PAST_MONTH);
}
break;
case CQ::DEPTH1_DIURNAL_VARIATION:
if (v == V_DEPTH1_DMV_WORSE_MORNING
|| v == V_DEPTH1_DMV_WORSE_EVENING) {
r.decide("Diurnal mood variation present.");
r.diurnal_mood_variation = v == V_DEPTH1_DMV_WORSE_MORNING
? DIURNAL_MOOD_VAR_WORSE_MORNING
: DIURNAL_MOOD_VAR_WORSE_EVENING;
if (v == V_DEPTH1_DMV_WORSE_MORNING) {
r.decide(
"Diurnal mood variation, worse in the mornings. "
"Incrementing depr_crit_3_somatic_synd."
);
r.depr_crit_3_somatic_synd += 1;
}
}
break;
case CQ::DEPTH2_LIBIDO:
if (v == V_DEPTH2_LIBIDO_DECREASED) {
r.decide(
"Libido decreased over past month. "
"Setting libido_decreased. "
"Incrementing depr_crit_3_somatic_synd."
);
r.libido_decreased = true;
r.depr_crit_3_somatic_synd += 1;
}
break;
case CQ::DEPTH3_RESTLESS:
if (answerIsYes(q)) {
r.decide("Psychomotor agitation.");
r.psychomotor_changes = PSYCHOMOTOR_AGITATION;
}
break;
case CQ::DEPTH4_SLOWED:
if (answerIsYes(q)) {
r.decide("Psychomotor retardation.");
r.psychomotor_changes = PSYCHOMOTOR_RETARDATION;
}
if (r.psychomotor_changes > PSYCHOMOTOR_NONE) {
r.decide(
"Psychomotor agitation or retardation. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui. "
"Incrementing depr_crit_3_somatic_synd."
);
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
r.depr_crit_3_somatic_synd += 1;
}
break;
case CQ::DEPTH5_GUILT:
if (v >= V_DEPTH5_GUILT_SOMETIMES) {
r.decide(
"Feel guilty when not at fault sometimes or often. "
"Incrementing depressive_thoughts. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depressive_thoughts += 1;
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
break;
case CQ::DEPTH6_WORSE_THAN_OTHERS:
if (answerIsYes(q, v)) {
r.decide(
"Feeling not as good as other people. "
"Incrementing depressive_thoughts. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depressive_thoughts += 1;
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
break;
case CQ::DEPTH7_HOPELESS:
if (answerIsYes(q, v)) {
r.decide(
"Hopelessness. "
"Incrementing depressive_thoughts. "
"Setting suicidality to "
"SUICIDE_INTENT_HOPELESS_NO_SUICIDAL_THOUGHTS."
);
r.depressive_thoughts += 1;
r.suicidality = SUICIDE_INTENT_HOPELESS_NO_SUICIDAL_THOUGHTS;
}
break;
case CQ::DEPTH8_LNWL:
if (v == V_DEPTH8_LNWL_NO) {
r.decide(
"No thoughts of life not being worth living. Skipping to "
"end of depression section."
);
jumpTo(CQ::DEPR_OUTRO);
} else if (v >= V_DEPTH8_LNWL_SOMETIMES) {
r.decide(
"Sometimes or always feeling life isn't worth living. "
"Incrementing depressive_thoughts. "
"Setting suicidality to "
"SUICIDE_INTENT_LIFE_NOT_WORTH_LIVING."
);
r.depressive_thoughts += 1;
r.suicidality = SUICIDE_INTENT_LIFE_NOT_WORTH_LIVING;
}
break;
case CQ::DEPTH9_SUICIDE_THOUGHTS:
if (v == V_DEPTH9_SUICIDAL_THOUGHTS_NO) {
r.decide(
"No thoughts of suicide. Skipping to end of depression "
"section."
);
jumpTo(CQ::DEPR_OUTRO);
}
if (v >= V_DEPTH9_SUICIDAL_THOUGHTS_YES_BUT_NEVER_WOULD) {
r.decide(
"Suicidal thoughts present. "
"Setting suicidality to SUICIDE_INTENT_SUICIDAL_THOUGHTS."
);
r.suicidality = SUICIDE_INTENT_SUICIDAL_THOUGHTS;
}
if (v == V_DEPTH9_SUICIDAL_THOUGHTS_YES_BUT_NEVER_WOULD) {
r.decide(
"Suicidal thoughts present but denies would ever act. "
"Skipping to talk-to-doctor section."
);
jumpTo(CQ::DOCTOR);
}
if (v == V_DEPTH9_SUICIDAL_THOUGHTS_YES) {
r.decide(
"Thoughts of suicide in past week. "
"Incrementing depressive_thoughts. "
"Incrementing depr_crit_2_app_cnc_slp_mtr_glt_wth_sui."
);
r.depressive_thoughts += 1;
r.depr_crit_2_app_cnc_slp_mtr_glt_wth_sui += 1;
}
break;
case CQ::DEPTH10_SUICIDE_METHOD:
if (answerIsYes(q, v)) {
r.decide(
"Suicidal thoughts without denying might ever act. "
"Setting suicidality to SUICIDE_INTENT_SUICIDAL_PLANS."
);
r.suicidality = SUICIDE_INTENT_SUICIDAL_PLANS;
}
break;
case CQ::DOCTOR:
if (v == V_DOCTOR_YES) {
r.decide(
"Has spoken to doctor about suicidality. Skipping "
"exhortation to do so."
);
jumpTo(CQ::DEPR_OUTRO);
}
break;
case CQ::DOCTOR2_PLEASE_TALK_TO:
case CQ::DEPR_OUTRO:
break;
// ----------------------------------------------------------------
// Worry/anxiety
// ----------------------------------------------------------------
case CQ::WORRY_MAND1_MORE_THAN_NEEDED_PAST_MONTH:
if (v >= V_NSO_SOMETIMES) {
r.decide(
"Worrying excessively 'sometimes' or 'often'. "
"Exploring further."
);
jumpTo(CQ::WORRY_CONT1);
}
break;
case CQ::WORRY_MAND2_ANY_WORRIES_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("No worries at all in the past month. Moving on.");
jumpTo(CQ::ANX_MAND1_ANXIETY_PAST_MONTH);
}
break;
case CQ::WORRY_CONT1:
case CQ::WORRY1_INFO_ONLY:
break;
case CQ::WORRY2_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide(
"Worry [other than re physical health] on 0 days in past "
"week. Moving on."
);
jumpTo(CQ::ANX_MAND1_ANXIETY_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Worry [other than re physical health] on >=4 days in "
"past week. Incrementing worry."
);
r.worry += 1;
}
break;
case CQ::WORRY3_TOO_MUCH:
if (answerIsYes(q, v)) {
r.decide("Worrying too much. Incrementing worry.");
r.worry += 1;
}
break;
case CQ::WORRY4_HOW_UNPLEASANT:
if (v >= V_HOW_UNPLEASANT_UNPLEASANT) {
r.decide(
"Worry [other than re physical health] 'unpleasant' or "
"worse in past week. Incrementing worry."
);
r.worry += 1;
}
break;
case CQ::WORRY5_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Worry [other than re physical health] for >3h on any "
"day in past week. Incrementing worry."
);
r.worry += 1;
}
break;
case CQ::WORRY_DUR:
break;
case CQ::ANX_MAND1_ANXIETY_PAST_MONTH:
if (answerIsYes(q, v)) {
r.decide(
"Anxious/nervous in past month. "
"Skipping tension question."
);
jumpTo(CQ::ANX_PHOBIA1_SPECIFIC_PAST_MONTH);
}
break;
case CQ::ANX_MAND2_TENSION_PAST_MONTH:
if (v == V_NSO_NO) {
r.decide(
"No tension in past month (and no anxiety, from previous "
"question). Moving on."
);
jumpTo(CQ::PHOBIAS_MAND_AVOIDANCE_PAST_MONTH);
}
break;
case CQ::ANX_PHOBIA1_SPECIFIC_PAST_MONTH:
if (answerIsNo(q, v)) {
r.decide("No phobias. Moving on to general anxiety.");
jumpTo(CQ::ANX2_GENERAL_DAYS_PAST_WEEK);
} else if (answerIsYes(q, v)) {
// This was in ANX_PHOBIA2; PHOBIAS_FLAG was set by arriving
// there (but that only happens when we get a 'yes' answer
// here).
r.decide("Phobias. Exploring further. Setting phobias flag.");
r.phobias_flag = true;
}
break;
case CQ::ANX_PHOBIA2_SPECIFIC_OR_GENERAL:
if (v == V_ANX_PHOBIA2_ALWAYS_SPECIFIC) {
r.decide(
"Anxiety always specific. Skipping generalized anxiety."
);
jumpTo(CQ::PHOBIAS_TYPE1);
}
break;
case CQ::ANX1_INFO_ONLY:
break;
case CQ::ANX2_GENERAL_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
if (r.phobias_flag) {
r.decide(
"No generalized anxiety in past week. "
"Skipping further generalized anxiety questions."
);
jumpTo(CQ::PHOBIAS1_DAYS_PAST_WEEK);
} else {
r.decide("No generalized anxiety in past week. Moving on."
);
jumpTo(CQ::COMP_MAND1_COMPULSIONS_PAST_MONTH);
}
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Generalized anxiety on >=4 days in past week. "
"Incrementing anxiety."
);
r.anxiety += 1;
}
break;
case CQ::ANX3_GENERAL_HOW_UNPLEASANT:
if (v >= V_HOW_UNPLEASANT_UNPLEASANT) {
r.decide(
"Anxiety 'unpleasant' or worse in past week. "
"Incrementing anxiety."
);
r.anxiety += 1;
}
break;
case CQ::ANX4_GENERAL_PHYSICAL_SYMPTOMS:
if (answerIsYes(q, v)) {
r.decide(
"Physical symptoms of anxiety. "
"Setting anxiety_physical_symptoms. "
"Incrementing anxiety."
);
r.anxiety_physical_symptoms = true;
r.anxiety += 1;
}
break;
case CQ::ANX5_GENERAL_GT_3H_ANY_DAY:
if (answerIsYes(q, v)) {
r.decide(
"Anxiety for >3h on any day in past week. "
"Incrementing anxiety."
);
r.anxiety += 1;
}
break;
case CQ::ANX_DUR_GENERAL:
if (v >= V_DURATION_2W_6M) {
r.decide(
"Anxiety for >=2 weeks. "
"Setting anxiety_at_least_2_weeks."
);
r.anxiety_at_least_2_weeks = true;
}
if (r.phobias_flag) {
r.decide("Phobias flag set. Exploring further.");
jumpTo(CQ::PHOBIAS_TYPE1);
} else {
if (r.anxiety <= 1) {
r.decide("Anxiety score <=1. Moving on to compulsions.");
jumpTo(CQ::COMP_MAND1_COMPULSIONS_PAST_MONTH);
} else {
r.decide("Anxiety score >=2. Exploring panic.");
jumpTo(CQ::PANIC_MAND_PAST_MONTH);
}
}
break;
case CQ::PHOBIAS_MAND_AVOIDANCE_PAST_MONTH:
if (answerIsNo(q, v)) {
if (r.anxiety <= 1) {
r.decide("Anxiety score <=1. Moving on to compulsions.");
jumpTo(CQ::COMP_MAND1_COMPULSIONS_PAST_MONTH);
} else {
r.decide("Anxiety score >=2. Exploring panic.");
jumpTo(CQ::PANIC_MAND_PAST_MONTH);
}
}
break;
case CQ::PHOBIAS_TYPE1:
switch (v) {
case V_PHOBIAS_TYPE1_ALONE_PUBLIC_TRANSPORT:
case V_PHOBIAS_TYPE1_FAR_FROM_HOME:
case V_PHOBIAS_TYPE1_CROWDED_SHOPS:
r.decide("Phobia type category: agoraphobia.");
r.phobias_type = PHOBIATYPES_AGORAPHOBIA;
break;
case V_PHOBIAS_TYPE1_PUBLIC_SPEAKING_EATING:
case V_PHOBIAS_TYPE1_BEING_WATCHED:
r.decide("Phobia type category: social.");
r.phobias_type = PHOBIATYPES_SOCIAL;
break;
case V_PHOBIAS_TYPE1_BLOOD:
r.decide("Phobia type category: blood/injury.");
r.phobias_type = PHOBIATYPES_BLOOD_INJURY;
break;
case V_PHOBIAS_TYPE1_ANIMALS:
case V_PHOBIAS_TYPE1_ENCLOSED_SPACES_HEIGHTS:
r.decide(
"Phobia type category: animals/enclosed "
"spaces/heights."
);
r.phobias_type = PHOBIATYPES_ANIMALS_ENCLOSED_HEIGHTS;
break;
case V_PHOBIAS_TYPE1_OTHER:
r.decide("Phobia type category: other.");
r.phobias_type = PHOBIATYPES_OTHER;
break;
default:
break;
}
break;
case CQ::PHOBIAS1_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Phobic anxiety on >=4 days in past week. "
"Incrementing phobias_score."
);
r.phobias_score += 1;
}
break;
case CQ::PHOBIAS2_PHYSICAL_SYMPTOMS:
if (answerIsYes(q, v)) {
r.decide(
"Physical symptoms during phobic anxiety in past week. "
"Incrementing phobias_score."
);
r.phobias_score += 1;
}
break;
case CQ::PHOBIAS3_AVOIDANCE:
if (answerIsNo(q, v)) { // no avoidance in past week
if (r.anxiety <= 1 && r.phobias_score == 0) {
r.decide(
"No avoidance in past week; "
"anxiety <= 1 and phobias_score == 0. "
"Finishing anxiety section."
);
jumpTo(CQ::ANX_OUTRO);
} else {
r.decide(
"No avoidance in past week; "
"anxiety >= 2 or phobias_score >= 1. "
"Moving to panic section."
);
jumpTo(CQ::PANIC_MAND_PAST_MONTH);
}
} else if (answerIsYes(q, v)) {
r.decide("Setting phobic_avoidance.");
r.phobic_avoidance = true;
}
break;
case CQ::PHOBIAS4_AVOIDANCE_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_1_TO_3) {
r.decide(
"Phobic avoidance on 1-3 days in past week. "
"Incrementing phobias_score."
);
r.phobias_score += 1;
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Phobic avoidance on >=4 days in past week. "
"Adding 2 to phobias_score."
);
r.phobias_score += 2;
}
if (r.anxiety <= 1
&& intValueForQuestion(CQ::PHOBIAS1_DAYS_PAST_WEEK)
== V_DAYS_IN_PAST_WEEK_0) {
r.decide(
"anxiety <= 1 and no phobic anxiety in past week. "
"Finishing anxiety section."
);
jumpTo(CQ::ANX_OUTRO);
}
break;
case CQ::PHOBIAS_DUR:
break;
case CQ::PANIC_MAND_PAST_MONTH:
if (v == V_NSO_NO) {
r.decide(
"No panic in the past month. Finishing anxiety section."
);
jumpTo(CQ::ANX_OUTRO);
}
break;
case CQ::PANIC1_NUM_PAST_WEEK:
if (v == V_PANIC1_N_PANICS_PAST_WEEK_0) {
r.decide("No panic in past week. Finishing anxiety section.");
jumpTo(CQ::ANX_OUTRO);
} else if (v == V_PANIC1_N_PANICS_PAST_WEEK_1) {
r.decide("One panic in past week. Incrementing panic.");
r.panic += 1;
} else if (v == V_PANIC1_N_PANICS_PAST_WEEK_GT_1) {
r.decide("More than one panic in past week. Adding 2 to panic."
);
r.panic += 2;
}
break;
case CQ::PANIC2_HOW_UNPLEASANT:
if (v >= V_HOW_UNPLEASANT_UNPLEASANT) {
r.decide(
"Panic 'unpleasant' or worse in past week. "
"Incrementing panic."
);
r.panic += 1;
}
break;
case CQ::PANIC3_PANIC_GE_10_MIN:
if (v == V_PANIC3_WORST_GE_10_MIN) {
r.decide(
"Worst panic in past week lasted >=10 min. Incrementing "
"panic."
);
r.panic += 1;
}
break;
case CQ::PANIC4_RAPID_ONSET:
if (answerIsYes(q, v)) {
r.decide(
"Rapid onset of panic symptoms. "
"Setting panic_rapid_onset."
);
r.panic_rapid_onset = true;
}
break;
case CQ::PANSYM:
// Multi-way answer. All are scored 1=no, 2=yes.
{
int n_panic_symptoms = 0;
for (const QString& fieldname : panicSymptomFieldnames()) {
const int panic_symptom = valueInt(fieldname);
const bool yes_present = panic_symptom == 2;
if (yes_present) {
n_panic_symptoms += 1;
}
}
r.decide(
QString("%1 out of %2 specific panic symptoms endorsed.")
.arg(n_panic_symptoms, NUM_PANIC_SYMPTOMS)
);
}
// The next bit was coded in PANIC5, but lives more naturally here:
if (answerIsNo(CQ::ANX_PHOBIA1_SPECIFIC_PAST_MONTH)) {
jumpTo(CQ::PANIC_DUR);
}
break;
case CQ::PANIC5_ALWAYS_SPECIFIC_TRIGGER:
case CQ::PANIC_DUR:
case CQ::ANX_OUTRO:
break;
// ----------------------------------------------------------------
// Compulsions and obsessions
// ----------------------------------------------------------------
case CQ::COMP_MAND1_COMPULSIONS_PAST_MONTH:
if (v == V_NSO_NO) {
r.decide("No compulsions in past month. Moving to obsessions."
);
jumpTo(CQ::OBSESS_MAND1_OBSESSIONS_PAST_MONTH);
}
break;
case CQ::COMP1_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("No compulsions in past week. Moving to obesssions.");
jumpTo(CQ::OBSESS_MAND1_OBSESSIONS_PAST_MONTH);
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Obsessions on >=4 days in past week. "
"Incrementing compulsions."
);
r.compulsions += 1;
}
break;
case CQ::COMP2_TRIED_TO_STOP:
if (answerIsYes(q, v)) {
r.decide(
"Attempts to stop compulsions in past week. "
"Setting compulsions_tried_to_stop. "
"Incrementing compulsions."
);
r.compulsions_tried_to_stop = true;
r.compulsions += 1;
}
break;
case CQ::COMP3_UPSETTING:
if (answerIsYes(q, v)) {
r.decide(
"Compulsions upsetting/annoying. Incrementing compulsions."
);
r.compulsions += 1;
}
break;
case CQ::COMP4_MAX_N_REPETITIONS:
if (v == V_COMP4_MAX_N_REPEATS_GE_3) {
r.decide("At worst, >=3 repeats. Incrementing compulsions.");
r.compulsions += 1;
}
break;
case CQ::COMP_DUR:
if (v >= V_DURATION_2W_6M) {
r.decide(
"Compulsions for >=2 weeks. "
"Setting compulsions_at_least_2_weeks."
);
r.compulsions_at_least_2_weeks = true;
}
break;
case CQ::OBSESS_MAND1_OBSESSIONS_PAST_MONTH:
if (v == V_NSO_NO) {
r.decide("No obsessions in past month. Moving on.");
jumpTo(chooseFinalPage());
}
break;
case CQ::OBSESS_MAND2_SAME_THOUGHTS_OR_GENERAL:
if (v == V_OBSESS_MAND1_GENERAL_WORRIES) {
r.decide(
"Worrying about something in general, not the same "
"thoughts over and over again. Moving on."
);
jumpTo(chooseFinalPage());
}
break;
case CQ::OBSESS1_DAYS_PAST_WEEK:
if (v == V_DAYS_IN_PAST_WEEK_0) {
r.decide("No obsessions in past week. Moving on.");
jumpTo(chooseFinalPage());
} else if (v == V_DAYS_IN_PAST_WEEK_4_OR_MORE) {
r.decide(
"Obsessions on >=4 days in past week. "
"Incrementing obsessions."
);
r.obsessions += 1;
}
break;
case CQ::OBSESS2_TRIED_TO_STOP:
if (answerIsYes(q, v)) {
r.decide(
"Tried to stop obsessional thoughts in past week. "
"Setting obsessions_tried_to_stop. "
"Incrementing obsessions."
);
r.obsessions_tried_to_stop = true;
r.obsessions += 1;
}
break;
case CQ::OBSESS3_UPSETTING:
if (answerIsYes(q, v)) {
r.decide(
"Obsessions upsetting/annoying in past week. "
"Incrementing obsessions."
);
r.obsessions += 1;
}
break;
case CQ::OBSESS4_MAX_DURATION:
if (v == V_OBSESS4_GE_15_MIN) {
r.decide(
"Obsessions lasting >=15 min in past week. "
"Incrementing obsessions."
);
r.obsessions += 1;
}
break;
case CQ::OBSESS_DUR:
if (v >= V_DURATION_2W_6M) {
r.decide(
"Obsessions for >=2 weeks. "
"Setting obsessions_at_least_2_weeks."
);
r.obsessions_at_least_2_weeks = true;
}
break;
// ----------------------------------------------------------------
// End
// ----------------------------------------------------------------
case CQ::OVERALL1_INFO_ONLY:
break;
case CQ::OVERALL2_IMPACT_PAST_WEEK:
if (answered(q, v)) {
r.functional_impairment = v - 1;
r.decide(QString("Setting functional_impairment to %1")
.arg(r.functional_impairment));
}
break;
case CQ::THANKS_FINISHED:
break;
case CQ::END_MARKER: // this is not a page
// we've reached the end; no point thinking further
return CQ::END_MARKER;
default:
break;
}
if (next_q == -1) {
// Nothing has expressed an overriding preference, so increment...
next_q = enumToInt(q) + 1;
}
return intToEnum(next_q);
}
Cisr::CisrQuestion Cisr::getPageEnum(const int qnum_zero_based) const
{
// This function embodies the logic about the question sequence.
//
// This is slightly tricky algorithmically, since the user can go backwards
// and forwards (which the original CIS-R doesn't do). The answers so far
// define a sequence of questions, and we offer the nth from that sequence.
//
// Since the CISR sequence is linear with answer-dependent skipping,
// it's a little bit easier than it might be.
// All we need to do is define the moments when we SKIP something.
// (See nextQ().)
//
// We can't use the incoming current_qnum directly, though, since that is
// the sequence WITHIN QUESTIONS BEING PRESENTED, not overall. Hence the
// iteration.
CisrQuestion internal_q = CQ::START_MARKER;
CisrResult result; // contents will be ignored!
result.record_decisions = false; // we don't care here
for (int external_qnum = 0; external_qnum < qnum_zero_based;
++external_qnum) {
internal_q = nextQ(internal_q, result);
} // loop until we have the right number of "external" pages
return internal_q;
}
Cisr::CisrResult Cisr::getResult() const
{
CisrQuestion internal_q = CQ::START_MARKER;
CisrResult result;
while (!result.incomplete && internal_q != CQ::END_MARKER) {
internal_q = nextQ(internal_q, result);
} // loop until we reach the end or have incomplete data
result.finalize();
return result;
}
QuPagePtr Cisr::makePageFromEnum(CisrQuestion q)
{
const int intq = enumToInt(q);
// Internals
auto makeOptions
= [this](
const QString& xstring_name_stem, int last, int first = 1
) -> NameValueOptions {
Q_ASSERT(first <= last);
NameValueOptions options;
for (int i = first; i <= last; ++i) {
const QString text
= xstring(xstring_name_stem + QString::number(i));
options.append(NameValuePair(text, i));
}
return options;
};
// Element makers
auto title = [this, &q]() -> QString {
#ifdef DEBUG_SHOW_PAGE_TAGS
return QString("CISR page %1 (%2)")
.arg(QString::number(enumToInt(q)), tagForQuestion(q));
#else
if (m_questionnaire && m_questionnaire->readOnly()) {
// Show title tags on facsimile (read-only) version
return QString("CISR page %1 (%2)")
.arg(QString::number(enumToInt(q)), tagForQuestion(q));
}
return QString("CISR page %1").arg(enumToInt(q));
#endif
};
auto prompttext = [this](const QString& xstringname) -> QuElement* {
return new QuText(xstring(xstringname));
};
auto question = [this](const QString& xstringname) -> QuElement* {
return new QuText(xstring(xstringname));
};
auto mcq = [this](
const QString& fieldname, const NameValueOptions& options
) -> QuElement* {
return new QuMcq(fieldRef(fieldname), options);
};
#ifdef DEBUG_SHOW_PAGE_TAGS
auto addTag = [this, &q](QuPage* p) {
const QString tag = tagForQuestion(q);
const QString text = QString("Debugging tags on: %1 (q%2)")
.arg(tag, QString::number(enumToInt(q)));
QuText* element = new QuText(text);
element->setWarning(true);
p->addElement(element);
};
#endif
// Page makers
auto promptPage = [&title,
&prompttext
#ifdef DEBUG_SHOW_PAGE_TAGS
,
&addTag
#endif
](const QString& prompt_xstringname) -> QuPagePtr {
auto p = new QuPage();
p->addElement(prompttext(prompt_xstringname));
p->setTitle(title());
#ifdef DEBUG_SHOW_PAGE_TAGS
addTag(p);
#endif
return QuPagePtr(p);
};
auto qPage
= [&title,
&question
#ifdef DEBUG_SHOW_PAGE_TAGS
,
&addTag
#endif
](const QString& question_xstringname,
QuElement* element,
QuElement* extra_between_q_and_element = nullptr) -> QuPagePtr {
auto p = new QuPage();
p->addElement(question(question_xstringname));
if (extra_between_q_and_element) {
p->addElement(extra_between_q_and_element);
}
p->addElement(element);
p->setTitle(title());
#ifdef DEBUG_SHOW_PAGE_TAGS
addTag(p);
#endif
return QuPagePtr(p);
};
auto standardOptionsQPage
= [this, &q, &qPage, &mcq](const NameValueOptions& options
) -> QuPagePtr {
const QString fieldname = fieldnameForQuestion(q);
const QString xstring_q = fieldname + XSTRING_QUESTION_SUFFIX;
return qPage(xstring_q, mcq(fieldname, options));
};
auto overallDuration
= [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(makeOptions("duration_a", N_DURATIONS));
};
auto daysPerWeek = [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(
makeOptions("dpw_a", N_OPTIONS_DAYS_PER_WEEK)
);
};
auto nightsPerWeek = [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(
makeOptions("npw_a", N_OPTIONS_NIGHTS_PER_WEEK)
);
};
auto howUnpleasantStandard
= [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(
makeOptions("how_unpleasant_a", N_OPTIONS_HOW_UNPLEASANT)
);
};
auto fatigueCauses = [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(
makeOptions("fatigue_causes_a", N_OPTIONS_FATIGUE_CAUSES)
);
};
auto stressors = [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(
makeOptions("stressors_a", N_OPTIONS_STRESSORS)
);
};
auto noSometimesOften
= [&standardOptionsQPage, &makeOptions]() -> QuPagePtr {
return standardOptionsQPage(
makeOptions("nso_a", N_OPTIONS_NO_SOMETIMES_OFTEN)
);
};
auto notImplemented = [this,
&title,
&q,
&intq
#ifdef DEBUG_SHOW_PAGE_TAGS
,
&addTag
#endif
]() -> QuPagePtr {
qCritical() << "CISR question not implemented:" << intq
<< tagForQuestion(q);
auto p = new QuPage();
p->addElement(
new QuText(QString("Question %1 not implemented yet!").arg(intq))
);
p->setTitle(title());
#ifdef DEBUG_SHOW_PAGE_TAGS
addTag(p);
#endif
return QuPagePtr(p);
};
auto ynQuestion = [this,
&q,
&intq,
¬Implemented,
&makeOptions,
&standardOptionsQPage]() -> QuPagePtr {
NameValueOptions options;
if (QUESTIONS_YN_SPECIFIC_TEXT.contains(q)) {
const QString fieldname = fieldnameForQuestion(q);
options = makeOptions(fieldname + "_a", 2);
} else {
const QString yes = CommonOptions::yes();
const QString no = CommonOptions::no();
if (QUESTIONS_1_NO_2_YES.contains(q)) {
options.append(NameValuePair(no, 1));
options.append(NameValuePair(yes, 2));
} else if (QUESTIONS_1_YES_2_NO.contains(q)) {
options.append(NameValuePair(yes, 1));
options.append(NameValuePair(no, 2));
} else {
qCritical() << "Bad question to ynQuestion():" << intq;
return notImplemented();
}
}
return standardOptionsQPage(options);
};
auto multiwayQuestion
= [this,
&q,
&intq,
&qPage,
¬Implemented,
&prompttext,
&mcq,
&makeOptions](
bool extra_stem, const QMap<CQ, QPair<int, int>>& first_last_map
) -> QuPagePtr {
if (!first_last_map.contains(q)) {
qCritical() << "Bad question to multiwayQuestion():" << intq
<< tagForQuestion(q);
return notImplemented();
}
const QPair<int, int>& first_last = first_last_map[q];
const int& first = first_last.first;
int last = first_last.second;
// Nasty hack...
if (q == CQ::WEIGHT4_INCREASE_PAST_MONTH && isFemale()) {
last = 3; // adds pregnancy option
}
const QString fieldname = fieldnameForQuestion(q);
const QString xstring_q = fieldname + XSTRING_QUESTION_SUFFIX;
NameValueOptions options = makeOptions(fieldname + "_a", last, first);
QuElement* extra_between_q_and_element = extra_stem
? prompttext(fieldname + XSTRING_EXTRA_STEM_SUFFIX)
: nullptr;
return qPage(
xstring_q, mcq(fieldname, options), extra_between_q_and_element
);
};
auto panicSymptoms = [this, &qPage]() -> QuPagePtr {
NameValueOptions options{
{CommonOptions::no(), 1},
{CommonOptions::yes(), 2},
};
QVector<QuestionWithOneField> q_field_pairs;
for (const QString& fieldname : panicSymptomFieldnames()) {
const QString xsymptom = fieldname + "_q";
const QString stemtext = xstring(xsymptom);
FieldRefPtr fr = fieldRef(fieldname);
q_field_pairs.append(QuestionWithOneField(stemtext, fr));
}
auto grid = new QuMcqGrid(q_field_pairs, options);
return qPage("pansym_q_prefix", grid);
};
// Now choose our question style.
if (q == CQ::END_MARKER) {
return nullptr;
}
if (QUESTIONS_MULTIWAY.contains(q)) {
// this test must PRECEDE Y/N tests since WEIGHT4 is both multiway and
// Y/N
return multiwayQuestion(false, QUESTIONS_MULTIWAY);
}
if (QUESTIONS_MULTIWAY_WITH_EXTRA_STEM.contains(q)) {
// this test must PRECEDE Y/N tests since WEIGHT4 is both multiway and
// Y/N
return multiwayQuestion(true, QUESTIONS_MULTIWAY_WITH_EXTRA_STEM);
}
if (QUESTIONS_PROMPT_ONLY.contains(q)) {
return promptPage(QUESTIONS_PROMPT_ONLY[q]);
}
if (QUESTIONS_OVERALL_DURATION.contains(q)) {
return overallDuration();
}
if (QUESTIONS_1_NO_2_YES.contains(q) || QUESTIONS_1_YES_2_NO.contains(q)) {
return ynQuestion();
}
if (QUESTIONS_DAYS_PER_WEEK.contains(q)) {
return daysPerWeek();
}
if (QUESTIONS_NIGHTS_PER_WEEK.contains(q)) {
return nightsPerWeek();
}
if (QUESTIONS_HOW_UNPLEASANT_STANDARD.contains(q)) {
return howUnpleasantStandard();
}
if (QUESTIONS_FATIGUE_CAUSES.contains(q)) {
return fatigueCauses();
}
if (QUESTIONS_STRESSORS.contains(q)) {
return stressors();
}
if (QUESTIONS_NO_SOMETIMES_OFTEN.contains(q)) {
return noSometimesOften();
}
if (q == CQ::PANSYM) { // special
return panicSymptoms();
}
return notImplemented();
}
QVector<QString> Cisr::panicSymptomFieldnames() const
{
QVector<QString> fieldnames;
for (int i = 0; i < NUM_PANIC_SYMPTOMS; ++i) {
const QString letter = QChar('a' + i);
const QString fieldname = QString("pansym_%1").arg(letter);
fieldnames.append(fieldname);
}
return fieldnames;
}
QString Cisr::diagnosisNameLong(int diagnosis_code) const
{
QString xstring_name = QString("diag_%1_desc").arg(diagnosis_code);
return xstring(xstring_name);
}
QString Cisr::diagnosisReason(int diagnosis_code) const
{
QString xstring_name = QString("diag_%1_explan").arg(diagnosis_code);
return xstring(xstring_name);
}
QString Cisr::suicideIntent(
const Cisr::CisrResult& result, bool with_warning
) const
{
QString intent;
if (result.incomplete) {
intent = "TASK INCOMPLETE. SO FAR: ";
}
intent += xstring(QString("suicid_%1").arg(result.suicidality));
if (with_warning
&& result.suicidality >= SUICIDE_INTENT_LIFE_NOT_WORTH_LIVING) {
intent += QString(" <i>%1</i>").arg(xstring("suicid_instruction"));
}
if (result.suicidality != SUICIDE_INTENT_NONE) {
intent = stringfunc::bold(intent);
}
return intent;
}
QString Cisr::functionalImpairment(const Cisr::CisrResult& result) const
{
const QString xstringname
= QString("impair_%1").arg(result.functional_impairment);
return xstring(xstringname);
}
// ============================================================================
// CisrResult
// ============================================================================
void Cisr::CisrResult::decide(const QString& decision)
{
if (record_decisions) {
decisions.append(decision);
}
}
int Cisr::CisrResult::getScore() const
{
// The original used score-as-we-go. Here we just collect the results:
return somatic_symptoms + fatigue + concentration_poor + sleep_problems
+ irritability + hypochondria + depression + depressive_thoughts
+ worry + anxiety + phobias_score + panic + compulsions + obsessions;
}
bool Cisr::CisrResult::needsImpairmentQuestion() const
{
const int threshold = 2; // for all symptoms
return somatic_symptoms >= threshold || hypochondria >= threshold
|| fatigue >= threshold || sleep_problems >= threshold
|| irritability >= threshold || concentration_poor >= threshold
|| depression >= threshold || depressive_thoughts >= threshold
|| phobias_score >= threshold || worry >= threshold
|| anxiety >= threshold || panic >= threshold
|| compulsions >= threshold || obsessions >= threshold;
}
void Cisr::CisrResult::finalize()
{
const bool at_least_1_activity_impaired
= functional_impairment >= OVERALL_IMPAIRMENT_STOP_1_ACTIVITY;
const int score = getScore();
// GAD
if (anxiety >= 2 && anxiety_physical_symptoms
&& anxiety_at_least_2_weeks) {
decide(
"Anxiety score >= 2 AND physical symptoms of anxiety AND "
"anxiety for at least 2 weeks. "
"Setting generalized_anxiety_disorder."
);
generalized_anxiety_disorder = true;
}
// Panic
if (panic >= 3 && panic_rapid_onset) {
decide(
"Panic score >= 3 AND panic_rapid_onset. "
"Setting panic_disorder."
);
panic_disorder = true;
}
// Phobias
if (phobias_type == PHOBIATYPES_AGORAPHOBIA && phobic_avoidance
&& phobias_score >= 2) {
decide(
"Phobia type is agoraphobia AND phobic avoidance AND"
"phobia score >= 2. Setting phobia_agoraphobia."
);
phobia_agoraphobia = true;
}
if (phobias_type == PHOBIATYPES_SOCIAL && phobic_avoidance
&& phobias_score >= 2) {
decide(
"Phobia type is social AND phobic avoidance AND"
"phobia score >= 2. Setting phobia_social."
);
phobia_social = true;
}
if (phobias_type == PHOBIATYPES_SOCIAL && phobic_avoidance
&& phobias_score >= 2) {
decide(
"Phobia type is (animals/enclosed/heights OR other) AND "
"phobic avoidance AND phobia score >= 2. "
"Setting phobia_specific."
);
phobia_specific = true;
}
// OCD
if (obsessions + compulsions >= 6 && obsessions_tried_to_stop
&& obsessions_at_least_2_weeks && at_least_1_activity_impaired) {
decide(
"obsessions + compulsions >= 6 AND "
"tried to stop obsessions AND "
"obsessions for at least 2 weeks AND "
"at least 1 activity impaired. "
"Setting obsessive_compulsive_disorder."
);
obsessive_compulsive_disorder = true;
}
if (obsessions + compulsions >= 6 && compulsions_tried_to_stop
&& compulsions_at_least_2_weeks && at_least_1_activity_impaired) {
decide(
"obsessions + compulsions >= 6 AND "
"tried to stop compulsions AND "
"compulsions for at least 2 weeks AND "
"at least 1 activity impaired. "
"Setting obsessive_compulsive_disorder."
);
obsessive_compulsive_disorder = true;
}
if (obsessions == 4 && obsessions_tried_to_stop
&& obsessions_at_least_2_weeks && at_least_1_activity_impaired) {
// NOTE: 4 is the maximum for obsessions
decide(
"obsessions == 4 AND "
"tried to stop obsessions AND "
"obsessions for at least 2 weeks AND "
"at least 1 activity impaired. "
"Setting obsessive_compulsive_disorder."
);
obsessive_compulsive_disorder = true;
}
if (compulsions == 4 && compulsions_tried_to_stop
&& compulsions_at_least_2_weeks && at_least_1_activity_impaired) {
// NOTE: 4 is the maximum for compulsions
decide(
"compulsions == 4 AND "
"tried to stop compulsions AND "
"compulsions for at least 2 weeks AND "
"at least 1 activity impaired. "
"Setting obsessive_compulsive_disorder."
);
obsessive_compulsive_disorder = true;
}
// Depression
if (depression_at_least_2_weeks && depr_crit_1_mood_anhedonia_energy > 1
&& depr_crit_1_mood_anhedonia_energy
+ depr_crit_2_app_cnc_slp_mtr_glt_wth_sui
> 3) {
decide(
"Depressive symptoms >=2 weeks AND "
"depr_crit_1_mood_anhedonia_energy > 1 AND "
"depr_crit_1_mood_anhedonia_energy + "
"depr_crit_2_app_cnc_slp_mtr_glt_wth_sui > 3. "
"Setting depression_mild."
);
depression_mild = true;
}
if (depression_at_least_2_weeks && depr_crit_1_mood_anhedonia_energy > 1
&& (depr_crit_1_mood_anhedonia_energy
+ depr_crit_2_app_cnc_slp_mtr_glt_wth_sui)
> 5) {
decide(
"Depressive symptoms >=2 weeks AND "
"depr_crit_1_mood_anhedonia_energy > 1 AND "
"depr_crit_1_mood_anhedonia_energy + "
"depr_crit_2_app_cnc_slp_mtr_glt_wth_sui > 5. "
"Setting depression_moderate."
);
depression_moderate = true;
}
if (depression_at_least_2_weeks && depr_crit_1_mood_anhedonia_energy == 3
&& depr_crit_2_app_cnc_slp_mtr_glt_wth_sui > 4) {
decide(
"Depressive symptoms >=2 weeks AND "
"depr_crit_1_mood_anhedonia_energy == 3 AND "
"depr_crit_2_app_cnc_slp_mtr_glt_wth_sui > 4. "
"Setting depression_severe."
);
depression_severe = true;
}
// CFS
if (neurasthenia >= 2) {
// The original had a pointless check for "DIAG1 == 0" too, but that
// was always true.
decide("neurasthenia >= 2. Setting chronic_fatigue_syndrome.");
chronic_fatigue_syndrome = true;
}
// Final diagnostic hierarchy
// ... primary diagnosis
if (score >= 12) {
decide(
"Total score >= 12. Setting diagnosis_1 to "
"DIAG_1_MIXED_ANX_DEPR_DIS_MILD."
);
diagnosis_1 = DIAG_1_MIXED_ANX_DEPR_DIS_MILD;
}
if (generalized_anxiety_disorder) {
decide(
"generalized_anxiety_disorder is true. Setting diagnosis_1 to "
"DIAG_2_GENERALIZED_ANX_DIS_MILD."
);
diagnosis_1 = DIAG_2_GENERALIZED_ANX_DIS_MILD;
}
if (obsessive_compulsive_disorder) {
decide(
"obsessive_compulsive_disorder is true. Setting diagnosis_1 to "
"DIAG_3_OBSESSIVE_COMPULSIVE_DIS."
);
diagnosis_1 = DIAG_3_OBSESSIVE_COMPULSIVE_DIS;
}
if (score >= 20) {
decide(
"Total score >= 20. Setting diagnosis_1 to "
"DIAG_4_MIXED_ANX_DEPR_DIS."
);
diagnosis_1 = DIAG_4_MIXED_ANX_DEPR_DIS;
}
if (phobia_specific) {
decide(
"phobia_specific is true. Setting diagnosis_1 to "
"DIAG_5_SPECIFIC_PHOBIA."
);
diagnosis_1 = DIAG_5_SPECIFIC_PHOBIA;
}
if (phobia_social) {
decide(
"phobia_social is true. Setting diagnosis_1 to "
"DIAG_6_SOCIAL_PHOBIA."
);
diagnosis_1 = DIAG_6_SOCIAL_PHOBIA;
}
if (phobia_agoraphobia) {
decide(
"phobia_agoraphobia is true. Setting diagnosis_1 to "
"DIAG_7_AGORAPHOBIA."
);
diagnosis_1 = DIAG_7_AGORAPHOBIA;
}
if (generalized_anxiety_disorder && score >= 20) {
decide(
"generalized_anxiety_disorder is true AND "
"score >= 20. Setting diagnosis_1 to "
"DIAG_8_GENERALIZED_ANX_DIS."
);
diagnosis_1 = DIAG_8_GENERALIZED_ANX_DIS;
}
if (panic_disorder) {
decide(
"panic_disorder is true. Setting diagnosis_1 to "
"DIAG_9_PANIC_DIS."
);
diagnosis_1 = DIAG_9_PANIC_DIS;
}
if (depression_mild) {
decide(
"depression_mild is true. Setting diagnosis_1 to "
"DIAG_10_MILD_DEPR_EPISODE."
);
diagnosis_1 = DIAG_10_MILD_DEPR_EPISODE;
}
if (depression_moderate) {
decide(
"depression_moderate is true. Setting diagnosis_1 to "
"DIAG_11_MOD_DEPR_EPISODE."
);
diagnosis_1 = DIAG_11_MOD_DEPR_EPISODE;
}
if (depression_severe) {
decide(
"depression_severe is true. Setting diagnosis_1 to "
"DIAG_12_SEVERE_DEPR_EPISODE."
);
diagnosis_1 = DIAG_12_SEVERE_DEPR_EPISODE;
}
// ... secondary diagnosis
if (score >= 12 && diagnosis_1 >= 2) {
decide(
"score >= 12 AND diagnosis_1 >= 2. "
"Setting diagnosis_2 to DIAG_1_MIXED_ANX_DEPR_DIS_MILD."
);
diagnosis_2 = DIAG_1_MIXED_ANX_DEPR_DIS_MILD;
}
if (generalized_anxiety_disorder && diagnosis_1 >= 3) {
decide(
"generalized_anxiety_disorder is true AND "
"diagnosis_1 >= 3. "
"Setting diagnosis_2 to DIAG_2_GENERALIZED_ANX_DIS_MILD."
);
diagnosis_2 = DIAG_2_GENERALIZED_ANX_DIS_MILD;
}
if (obsessive_compulsive_disorder && diagnosis_1 >= 4) {
decide(
"obsessive_compulsive_disorder is true AND "
"diagnosis_1 >= 4. "
"Setting diagnosis_2 to DIAG_3_OBSESSIVE_COMPULSIVE_DIS."
);
diagnosis_2 = DIAG_3_OBSESSIVE_COMPULSIVE_DIS;
}
if (score >= 20 && diagnosis_1 >= 5) {
decide(
"score >= 20 AND diagnosis_1 >= 5. "
"Setting diagnosis_2 to DIAG_4_MIXED_ANX_DEPR_DIS."
);
diagnosis_2 = DIAG_4_MIXED_ANX_DEPR_DIS;
}
if (phobia_specific && diagnosis_1 >= 6) {
decide(
"phobia_specific is true AND diagnosis_1 >= 6. "
"Setting diagnosis_2 to DIAG_5_SPECIFIC_PHOBIA."
);
diagnosis_2 = DIAG_5_SPECIFIC_PHOBIA;
}
if (phobia_social && diagnosis_1 >= 7) {
decide(
"phobia_social is true AND diagnosis_1 >= 7. "
"Setting diagnosis_2 to DIAG_6_SOCIAL_PHOBIA."
);
diagnosis_2 = DIAG_6_SOCIAL_PHOBIA;
}
if (phobia_agoraphobia && diagnosis_1 >= 8) {
decide(
"phobia_agoraphobia is true AND diagnosis_1 >= 8. "
"Setting diagnosis_2 to DIAG_7_AGORAPHOBIA."
);
diagnosis_2 = DIAG_7_AGORAPHOBIA;
}
if (generalized_anxiety_disorder && score >= 20 && diagnosis_1 >= 9) {
decide(
"generalized_anxiety_disorder is true AND "
"score >= 20 AND "
"diagnosis_1 >= 9. "
"Setting diagnosis_2 to DIAG_8_GENERALIZED_ANX_DIS."
);
diagnosis_2 = DIAG_8_GENERALIZED_ANX_DIS;
}
if (panic_disorder && diagnosis_1 >= 9) {
decide(
"panic_disorder is true AND diagnosis_1 >= 9. "
"Setting diagnosis_2 to DIAG_9_PANIC_DIS."
);
diagnosis_2 = DIAG_9_PANIC_DIS;
}
// In summary:
const QString scoreprefix = "... ";
auto showint = [this, &scoreprefix](const QString& name, int value) {
decide(
QString("%1%2: %3").arg(scoreprefix, name, QString::number(value))
);
};
auto showbool = [this, &scoreprefix](const QString& name, bool value) {
decide(QString("%1%2: %3")
.arg(scoreprefix, name, value ? "true" : "false"));
};
#define SHOWINT(x) showint(#x, x)
#define SHOWBOOL(x) showbool(#x, x)
decide("FINISHED.");
decide("--- Final scores:");
SHOWINT(depression);
SHOWINT(depr_crit_1_mood_anhedonia_energy);
SHOWINT(depr_crit_2_app_cnc_slp_mtr_glt_wth_sui);
SHOWINT(depr_crit_3_somatic_synd);
SHOWINT(weight_change);
SHOWINT(somatic_symptoms);
SHOWINT(fatigue);
SHOWINT(neurasthenia);
SHOWINT(concentration_poor);
SHOWINT(sleep_problems);
SHOWINT(sleep_change);
SHOWINT(depressive_thoughts);
SHOWINT(irritability);
SHOWINT(diurnal_mood_variation);
SHOWBOOL(libido_decreased);
SHOWINT(psychomotor_changes);
SHOWINT(suicidality);
SHOWBOOL(depression_at_least_2_weeks);
SHOWINT(hypochondria);
SHOWINT(worry);
SHOWINT(anxiety);
SHOWBOOL(anxiety_physical_symptoms);
SHOWBOOL(anxiety_at_least_2_weeks);
SHOWBOOL(phobias_flag);
SHOWINT(phobias_score);
SHOWINT(phobias_type);
SHOWBOOL(phobic_avoidance);
SHOWINT(panic);
SHOWBOOL(panic_rapid_onset);
SHOWINT(panic_symptoms_total);
SHOWINT(compulsions);
SHOWBOOL(compulsions_tried_to_stop);
SHOWBOOL(compulsions_at_least_2_weeks);
SHOWINT(obsessions);
SHOWBOOL(obsessions_tried_to_stop);
SHOWBOOL(obsessions_at_least_2_weeks);
SHOWINT(functional_impairment);
// Disorder flags
SHOWBOOL(obsessive_compulsive_disorder);
SHOWBOOL(depression_mild);
SHOWBOOL(depression_moderate);
SHOWBOOL(depression_severe);
SHOWBOOL(chronic_fatigue_syndrome);
SHOWBOOL(generalized_anxiety_disorder);
SHOWBOOL(phobia_agoraphobia);
SHOWBOOL(phobia_social);
SHOWBOOL(phobia_specific);
SHOWBOOL(panic_disorder);
decide("--- Final diagnoses:");
decide(QString("Probable primary diagnosis: %1")
.arg(diagnosisName(diagnosis_1)));
decide(QString("Probable secondary diagnosis: %1")
.arg(diagnosisName(diagnosis_2)));
}
QString Cisr::CisrResult::diagnosisName(int diagnosis_code) const
{
if (incomplete) {
// Do NOT offer diagnostic information based on partial data.
// Might be dangerous (e.g. say "mild depressive episode" when it's
// severe + incomplete information).
return "INFORMATION INCOMPLETE";
}
switch (diagnosis_code) {
case DIAG_0_NO_DIAGNOSIS:
return "No diagnosis identified";
case DIAG_1_MIXED_ANX_DEPR_DIS_MILD:
return "Mixed anxiety and depressive disorder (mild)";
case DIAG_2_GENERALIZED_ANX_DIS_MILD:
return "Generalized anxiety disorder (mild)";
case DIAG_3_OBSESSIVE_COMPULSIVE_DIS:
return "Obsessive–compulsive disorder";
case DIAG_4_MIXED_ANX_DEPR_DIS:
return "Mixed anxiety and depressive disorder";
case DIAG_5_SPECIFIC_PHOBIA:
return "Specific (isolated) phobia";
case DIAG_6_SOCIAL_PHOBIA:
return "Social phobia";
case DIAG_7_AGORAPHOBIA:
return "Agoraphobia";
case DIAG_8_GENERALIZED_ANX_DIS:
return "Generalized anxiety disorder";
case DIAG_9_PANIC_DIS:
return "Panic disorder";
case DIAG_10_MILD_DEPR_EPISODE:
return "Mild depressive episode";
case DIAG_11_MOD_DEPR_EPISODE:
return "Moderate depressive episode";
case DIAG_12_SEVERE_DEPR_EPISODE:
return "Severe depressive episode";
default:
return "[INTERNAL ERROR: BAD DIAGNOSIS CODE]";
}
}