Exemplo n.º 1
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        strongly_disagree = self.wxstring(req, "strongly_disagree")
        disagree = self.wxstring(req, "disagree")
        agree = self.wxstring(req, "agree")

        # We store the actual answers given but these are scored 1 or 0
        forward_answer_dict = {
            None:
            None,
            self.STRONGLY_DISAGREE:
            "1 — " + strongly_disagree,
            self.DISAGREE:
            "1 — " + disagree,
            self.AGREE:
            "0 — " + agree,
            self.STRONGLY_OR_DEFINITELY_AGREE:
            "0 — " + self.wxstring(req, "strongly_agree"),
        }

        # Subtle difference in wording when options presented in reverse
        reverse_answer_dict = {
            None:
            None,
            self.STRONGLY_OR_DEFINITELY_AGREE:
            "0 — " + self.wxstring(req, "definitely_agree"),
            self.AGREE:
            "0 — " + agree,
            self.DISAGREE:
            "1 — " + disagree,
            self.STRONGLY_DISAGREE:
            "1 — " + strongly_disagree,
        }

        rows = ""
        for q_num in range(1, self.N_QUESTIONS + 1):
            q_field = "q" + str(q_num)
            question_cell = "{}. {}".format(q_num, self.wxstring(req, q_field))

            answer_dict = forward_answer_dict

            if q_num in self.REVERSE_QUESTIONS:
                answer_dict = reverse_answer_dict

            answer_cell = get_from_dict(answer_dict, getattr(self, q_field))
            rows += tr_qa(question_cell, answer_cell)

        html = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="40%">Answer</th>
                </tr>
                {rows}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Score 1 point for either ‘disagree’ option,
                    0 points for either ‘agree’ option.
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.sstring(SS.TOTAL_SCORE) + " <sup>[1]</sup>",
                "{} / {}".format(answer(self.total_score()), self.MAX_SCORE),
            ),
            rows=rows,
        )
        return html
Exemplo n.º 2
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        fullscale_range = f"[{self.MIN_SCORE}–{self.MAX_SCORE}]"
        subscale_range = f"[{self.MIN_SUBSCALE}–{self.MAX_SUBSCALE}]"

        rows = ""
        for q_num in range(1, self.N_QUESTIONS + 1):
            q_field = "q" + str(q_num)
            question_cell = "{}. {}".format(q_num, self.wxstring(req, q_field))

            score = getattr(self, q_field)

            rows += tr_qa(question_cell, score)

        html = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {general_fatigue_score}
                    {physical_fatigue_score}
                    {reduced_activity_score}
                    {reduced_motivation_score}
                    {mental_fatigue_score}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="40%">Answer <sup>[8]</sup></th>
                </tr>
                {rows}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Questions 2, 5, 9, 10, 13, 14, 16, 17, 18, 19
                    reverse-scored when summing.
                [2] Sum for questions 1–20.
                [3] General fatigue: Sum for questions 1, 5, 12, 16.
                [4] Physical fatigue: Sum for questions 2, 8, 14, 20.
                [5] Reduced activity: Sum for questions 3, 6, 10, 17.
                [6] Reduced motivation: Sum for questions 4, 9, 15, 18.
                [7] Mental fatigue: Sum for questions 7, 11, 13, 19.
                [8] All questions are rated from “1 – yes, that is true” to
                    “5 – no, that is not true”.
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.sstring(SS.TOTAL_SCORE) + " <sup>[1][2]</sup>",
                f"{answer(self.total_score())} {fullscale_range}",
            ),
            general_fatigue_score=tr(
                self.wxstring(req, "general_fatigue") + " <sup>[1][3]</sup>",
                f"{answer(self.general_fatigue_score())} {subscale_range}",
            ),
            physical_fatigue_score=tr(
                self.wxstring(req, "physical_fatigue") + " <sup>[1][4]</sup>",
                f"{answer(self.physical_fatigue_score())} {subscale_range}",
            ),
            reduced_activity_score=tr(
                self.wxstring(req, "reduced_activity") + " <sup>[1][5]</sup>",
                f"{answer(self.reduced_activity_score())} {subscale_range}",
            ),
            reduced_motivation_score=tr(
                self.wxstring(req, "reduced_motivation")
                + " <sup>[1][6]</sup>",
                f"{answer(self.reduced_motivation_score())} {subscale_range}",
            ),
            mental_fatigue_score=tr(
                self.wxstring(req, "mental_fatigue") + " <sup>[1][7]</sup>",
                f"{answer(self.mental_fatigue_score())} {subscale_range}",
            ),
            rows=rows,
        )
        return html
Exemplo n.º 3
0
 def get_task_html(self, req: CamcopsRequest) -> str:
     score = self.total_score()
     category = self.category(req)
     h = """
         {clinician_comments}
         <div class="{CssClass.SUMMARY}">
             <table class="{CssClass.SUMMARY}">
                 {tr_is_complete}
                 {total_score}
                 {category}
             </table>
         </div>
         <table class="{CssClass.TASKDETAIL}">
             <tr>
                 <th width="80%">Question</th>
                 <th width="20%">Score</th>
             </tr>
     """.format(
         clinician_comments=self.get_standard_clinician_comments_block(
             req, self.comments),
         CssClass=CssClass,
         tr_is_complete=self.get_is_complete_tr(req),
         total_score=tr(
             req.sstring(SS.TOTAL_SCORE),
             answer(score) + f" / {self.MAX_SCORE}",
         ),
         category=tr_qa(
             req.sstring(SS.CATEGORY) + " <sup>[1]</sup>", category),
     )
     h += tr_qa(self.wxstring(req, "alert_s"),
                get_yes_no_none(req, self.alert))
     h += tr_qa(
         self.wxstring(req, "highschool_s"),
         get_yes_no_none(req, self.highschooleducation),
     )
     h += tr_qa(self.wxstring(req, "q1_s"), self.q1)
     h += tr_qa(self.wxstring(req, "q2_s"), self.q2)
     h += tr_qa(self.wxstring(req, "q3_s"), self.q3)
     h += tr(
         "Q5 <sup>[2]</sup> (money spent, money left "
         "[<i>scores 2</i>]",
         ", ".join(answer(x) for x in (self.q5a, self.q5b)),
     )
     h += tr_qa(
         "Q6 (animal fluency) [<i>≥15 scores 3, 10–14 scores 2, "
         "5–9 scores 1, 0–4 scores 0</i>]",
         self.q6,
     )
     h += tr(
         "Q7 (recall: apple, pen, tie, house, car)",
         ", ".join(
             answer(x)
             for x in (self.q7a, self.q7b, self.q7c, self.q7d, self.q7e)),
     )
     h += tr(
         "Q8 (backwards: 648, 8537)",
         ", ".join(answer(x) for x in (self.q8b, self.q8c)),
     )
     h += tr(
         "Q9 (clock: hour markers, time [<i>score 2 each</i>]",
         ", ".join(answer(x) for x in (self.q9a, self.q9b)),
     )
     h += tr(
         "Q10 (X in triangle; which is biggest?)",
         ", ".join(answer(x) for x in (self.q10a, self.q10b)),
     )
     h += tr(
         "Q11 (story: Female’s name? Job? When back to work? "
         "State she lived in? [<i>score 2 each</i>])",
         ", ".join(
             answer(x)
             for x in (self.q11a, self.q11b, self.q11c, self.q11d)),
     )
     h += f"""
         </table>
         <table class="{CssClass.TASKDETAIL}">
     """
     h += subheading_spanning_two_columns("Images of tests: clock, shapes")
     # noinspection PyTypeChecker
     h += tr(
         td(
             get_blob_img_html(self.clockpicture),
             td_width="50%",
             td_class=CssClass.PHOTO,
         ),
         td(
             get_blob_img_html(self.shapespicture),
             td_width="50%",
             td_class=CssClass.PHOTO,
         ),
         literal=True,
     )
     h += f"""
         </table>
         <div class="{CssClass.FOOTNOTES}">
             [1] With high school education:
             ≥27 normal, ≥21 MCI, ≤20 dementia.
             Without high school education:
             ≥25 normal, ≥20 MCI, ≤19 dementia.
             (Tariq et al. 2006, PubMed ID 17068312.)
             [2] Q4 (learning the five words) isn’t scored.
         </div>
     """
     return h
Exemplo n.º 4
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        q8_problem_type_dict = {
            None: None,
            "A": self.wxstring(req, "q8problemtype_option_a"),
            "B": self.wxstring(req, "q8problemtype_option_b"),
            "C": self.wxstring(req, "q8problemtype_option_c"),
            "D": self.wxstring(req, "q8problemtype_option_d"),
            "E": self.wxstring(req, "q8problemtype_option_e"),
            "F": self.wxstring(req, "q8problemtype_option_f"),
            "G": self.wxstring(req, "q8problemtype_option_g"),
            "H": self.wxstring(req, "q8problemtype_option_h"),
            "I": self.wxstring(req, "q8problemtype_option_i"),
            "J": self.wxstring(req, "q8problemtype_option_j"),
        }
        one_to_eight = ""
        for i in range(1, 8 + 1):
            one_to_eight += tr_qa(
                self.get_q(req, i),
                self.get_answer(req, i, getattr(self, "q" + str(i))))
        nine_onwards = ""
        for i in range(9, Honos.NQUESTIONS + 1):
            nine_onwards += tr_qa(
                self.get_q(req, i),
                self.get_answer(req, i, getattr(self, "q" + str(i))))

        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="50%">Question</th>
                    <th width="50%">Answer <sup>[1]</sup></th>
                </tr>
                {period_rated}
                {one_to_eight}
                {q8problemtype}
                {q8otherproblem}
                {nine_onwards}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                {FOOTNOTE_SCORING}
            </div>
            {copyright_div}
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.wappstring("total_score"),
                answer(self.total_score()) + " / {}".format(self.MAX_SCORE)),
            period_rated=tr_qa(self.wxstring(req, "period_rated"),
                               self.period_rated),
            one_to_eight=one_to_eight,
            q8problemtype=tr_qa(
                self.wxstring(req, "q8problemtype_s"),
                get_from_dict(q8_problem_type_dict, self.q8problemtype)),
            q8otherproblem=tr_qa(self.wxstring(req, "q8otherproblem_s"),
                                 self.q8otherproblem),
            nine_onwards=nine_onwards,
            FOOTNOTE_SCORING=FOOTNOTE_SCORING,
            copyright_div=self.COPYRIGHT_DIV,
        )
        return h
Exemplo n.º 5
0
 def get_task_html(self, req: CamcopsRequest) -> str:
     dict_q1 = {None: None}
     dict_q3 = {None: None}
     dict_q4 = {None: None}
     dict_q5 = {None: None}
     dict_q11 = {None: None}
     dict_q12 = {None: None}
     for option in range(1, 5):
         dict_q1[option] = self.wxstring(req, "q1_option" + str(option))
     for option in range(1, 6):
         dict_q3[option] = self.wxstring(req, "q3_option" + str(option))
         dict_q11[option] = self.wxstring(req, "q11_option" + str(option))
     for option in range(0, 6):
         prefix = str(option) + " – " if option > 0 else ""
         dict_q4[option] = prefix + self.wxstring(req,
                                                  "q4_option" + str(option))
         dict_q5[option] = prefix + self.wxstring(req,
                                                  "q5_option" + str(option))
     for option in range(1, 17):
         dict_q12[option] = self.wxstring(req,
                                          "ethnicity_option" + str(option))
     h = f"""
         <div class="{CssClass.SUMMARY}">
             <table class="{CssClass.SUMMARY}">
                 {self.get_is_complete_tr(req)}
             </table>
         </div>
         <table class="{CssClass.TASKDETAIL}">
             <tr>
                 <th width="60%">Question</th>
                 <th width="40%">Answer</th>
             </tr>
     """
     ell = "&hellip; "  # horizontal ellipsis
     sep_row = subheading_spanning_two_columns("<br>")
     blank_cell = td("", td_class=CssClass.SUBHEADING)
     h += tr_qa(self.wxstring(req, "q_doctor"), ws.webify(self.doctor))
     h += sep_row
     h += tr_qa(self.wxstring(req, "q1"), get_from_dict(dict_q1, self.q1))
     h += tr(td(self.wxstring(req, "q2")), blank_cell, literal=True)
     h += tr_qa(
         ell + self.wxstring(req, "q2_a"),
         get_yes_no_none(req, self.q2a),
         default="",
     )
     h += tr_qa(
         ell + self.wxstring(req, "q2_b"),
         get_yes_no_none(req, self.q2b),
         default="",
     )
     h += tr_qa(
         ell + self.wxstring(req, "q2_c"),
         get_yes_no_none(req, self.q2c),
         default="",
     )
     h += tr_qa(
         ell + self.wxstring(req, "q2_d"),
         get_yes_no_none(req, self.q2d),
         default="",
     )
     h += tr_qa(
         ell + self.wxstring(req, "q2_e"),
         get_yes_no_none(req, self.q2e),
         default="",
     )
     h += tr_qa(
         ell + self.wxstring(req, "q2_f"),
         get_yes_no_none(req, self.q2f),
         default="",
     )
     h += tr_qa(
         ell + ell + self.wxstring(req, "q2f_s"),
         ws.webify(self.q2f_details),
     )
     h += tr_qa(self.wxstring(req, "q3"), get_from_dict(dict_q3, self.q3))
     h += tr(td(self.wxstring(req, "q4")), blank_cell, literal=True)
     h += tr_qa(ell + self.wxstring(req, "q4_a"),
                get_from_dict(dict_q4, self.q4a))
     h += tr_qa(ell + self.wxstring(req, "q4_b"),
                get_from_dict(dict_q4, self.q4b))
     h += tr_qa(ell + self.wxstring(req, "q4_c"),
                get_from_dict(dict_q4, self.q4c))
     h += tr_qa(ell + self.wxstring(req, "q4_d"),
                get_from_dict(dict_q4, self.q4d))
     h += tr_qa(ell + self.wxstring(req, "q4_e"),
                get_from_dict(dict_q4, self.q4e))
     h += tr_qa(ell + self.wxstring(req, "q4_f"),
                get_from_dict(dict_q4, self.q4f))
     h += tr_qa(ell + self.wxstring(req, "q4_g"),
                get_from_dict(dict_q4, self.q4g))
     h += tr(td(self.wxstring(req, "q5")), blank_cell, literal=True)
     h += tr_qa(ell + self.wxstring(req, "q5_a"),
                get_from_dict(dict_q5, self.q5a))
     h += tr_qa(ell + self.wxstring(req, "q5_b"),
                get_from_dict(dict_q5, self.q5b))
     h += tr_qa(self.wxstring(req, "q6"), get_yes_no_none(req, self.q6))
     h += tr_qa(self.wxstring(req, "q7"), get_yes_no_none(req, self.q7))
     h += tr_qa(self.wxstring(req, "q8"), get_yes_no_none(req, self.q8))
     h += tr_qa(self.wxstring(req, "q9_s"), ws.webify(self.q9))
     h += sep_row
     h += tr_qa(req.sstring(SS.SEX), ws.webify(self.q10))
     h += tr_qa(self.wxstring(req, "q11"),
                get_from_dict(dict_q11, self.q11))
     h += tr_qa(self.wxstring(req, "q12"),
                get_from_dict(dict_q12, self.q12))
     h += tr_qa(
         ell + self.wxstring(req, "ethnicity_other_s"),
         ws.webify(self.q12_details),
     )
     h += """
         </table>
     """
     return h
Exemplo n.º 6
0
    def get_trial_html(self, req: CamcopsRequest) -> str:
        trialarray = self.trials  # type: List[CardinalExpDetThresholdTrial]  # for type hinter!  # noqa
        html = CardinalExpDetThresholdTrial.get_html_table_header()
        for t in trialarray:
            html += t.get_html_table_row()
        html += """</table>"""

        # Don't add figures if we're incomplete
        if not self.is_complete():
            return html

        # Add figures

        figsize = (FULLWIDTH_PLOT_WIDTH / 2, FULLWIDTH_PLOT_WIDTH / 2)
        jitter_step = 0.02
        dp_to_consider_same_for_jitter = 3
        y_extra_space = 0.1
        x_extra_space = 0.02
        trialfig = req.create_figure(figsize=figsize)
        trialax = trialfig.add_subplot(WHOLE_PANEL)
        notcalc_detected_x = []
        notcalc_detected_y = []
        notcalc_missed_x = []
        notcalc_missed_y = []
        calc_detected_x = []
        calc_detected_y = []
        calc_missed_x = []
        calc_missed_y = []
        catch_detected_x = []
        catch_detected_y = []
        catch_missed_x = []
        catch_missed_y = []
        all_x = []
        all_y = []
        for t in trialarray:
            x = t.trial
            y = t.intensity
            all_x.append(x)
            all_y.append(y)
            if t.trial_num_in_calculation_sequence is not None:
                if t.yes:
                    calc_detected_x.append(x)
                    calc_detected_y.append(y)
                else:
                    calc_missed_x.append(x)
                    calc_missed_y.append(y)
            elif t.target_presented:
                if t.yes:
                    notcalc_detected_x.append(x)
                    notcalc_detected_y.append(y)
                else:
                    notcalc_missed_x.append(x)
                    notcalc_missed_y.append(y)
            else:  # catch trial
                if t.yes:
                    catch_detected_x.append(x)
                    catch_detected_y.append(y)
                else:
                    catch_missed_x.append(x)
                    catch_missed_y.append(y)
        trialax.plot(all_x,
                     all_y,
                     marker="",
                     color="0.9",
                     linestyle="-",
                     label=None)
        trialax.plot(notcalc_missed_x,
                     notcalc_missed_y,
                     marker="o",
                     color="k",
                     linestyle="None",
                     label="miss")
        trialax.plot(notcalc_detected_x,
                     notcalc_detected_y,
                     marker="+",
                     color="k",
                     linestyle="None",
                     label="hit")
        trialax.plot(calc_missed_x,
                     calc_missed_y,
                     marker="o",
                     color="r",
                     linestyle="None",
                     label="miss, scored")
        trialax.plot(calc_detected_x,
                     calc_detected_y,
                     marker="+",
                     color="b",
                     linestyle="None",
                     label="hit, scored")
        trialax.plot(catch_missed_x,
                     catch_missed_y,
                     marker="o",
                     color="g",
                     linestyle="None",
                     label="CR")
        trialax.plot(catch_detected_x,
                     catch_detected_y,
                     marker="*",
                     color="g",
                     linestyle="None",
                     label="FA")
        leg = trialax.legend(
            numpoints=1,
            fancybox=True,  # for set_alpha (below)
            loc="best",  # bbox_to_anchor=(0.75, 1.05)
            labelspacing=0,
            handletextpad=0,
            prop=req.fontprops)
        leg.get_frame().set_alpha(0.5)
        trialax.set_xlabel("Trial number (0-based)", fontdict=req.fontdict)
        trialax.set_ylabel("Intensity", fontdict=req.fontdict)
        trialax.set_ylim(0 - y_extra_space, 1 + y_extra_space)
        trialax.set_xlim(-0.5, len(trialarray) - 0.5)
        req.set_figure_font_sizes(trialax)

        fitfig = None
        if self.k is not None and self.theta is not None:
            fitfig = req.create_figure(figsize=figsize)
            fitax = fitfig.add_subplot(WHOLE_PANEL)
            detected_x = []
            detected_x_approx = []
            detected_y = []
            missed_x = []
            missed_x_approx = []
            missed_y = []
            all_x = []
            for t in trialarray:
                if t.trial_num_in_calculation_sequence is not None:
                    all_x.append(t.intensity)
                    approx_x = "{0:.{precision}f}".format(
                        t.intensity, precision=dp_to_consider_same_for_jitter)
                    if t.yes:
                        detected_y.append(1 -
                                          detected_x_approx.count(approx_x) *
                                          jitter_step)
                        detected_x.append(t.intensity)
                        detected_x_approx.append(approx_x)
                    else:
                        missed_y.append(0 + missed_x_approx.count(approx_x) *
                                        jitter_step)
                        missed_x.append(t.intensity)
                        missed_x_approx.append(approx_x)
            fit_x = np.arange(0.0 - x_extra_space, 1.0 + x_extra_space, 0.001)
            fit_y = logistic(fit_x, self.k, self.theta)
            fitax.plot(fit_x, fit_y, color="g", linestyle="-")
            fitax.plot(missed_x,
                       missed_y,
                       marker="o",
                       color="r",
                       linestyle="None")
            fitax.plot(detected_x,
                       detected_y,
                       marker="+",
                       color="b",
                       linestyle="None")
            fitax.set_ylim(0 - y_extra_space, 1 + y_extra_space)
            fitax.set_xlim(
                np.amin(all_x) - x_extra_space,
                np.amax(all_x) + x_extra_space)
            marker_points = []
            for y in (LOWER_MARKER, 0.5, UPPER_MARKER):
                x = inv_logistic(y, self.k, self.theta)
                marker_points.append((x, y))
            for p in marker_points:
                fitax.plot([p[0], p[0]], [-1, p[1]],
                           color="0.5",
                           linestyle=":")
                fitax.plot([-1, p[0]], [p[1], p[1]],
                           color="0.5",
                           linestyle=":")
            fitax.set_xlabel("Intensity", fontdict=req.fontdict)
            fitax.set_ylabel("Detected? (0=no, 1=yes; jittered)",
                             fontdict=req.fontdict)
            req.set_figure_font_sizes(fitax)

        html += """
            <table class="{CssClass.NOBORDER}">
                <tr>
                    <td class="{CssClass.NOBORDERPHOTO}">{trialfig}</td>
                    <td class="{CssClass.NOBORDERPHOTO}">{fitfig}</td>
                </tr>
            </table>
        """.format(CssClass=CssClass,
                   trialfig=req.get_html_from_pyplot_figure(trialfig),
                   fitfig=req.get_html_from_pyplot_figure(fitfig))

        return html
Exemplo n.º 7
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        total = self.total_score()
        distress = self.distress_score()
        intrusiveness = self.intrusiveness_score()
        frequency = self.frequency_score()

        q_a = ""
        for q in range(1, Caps.NQUESTIONS + 1):
            q_a += tr(
                self.wxstring(req, "q" + str(q)),
                answer(get_yes_no_none(req, getattr(self,
                                                    "endorse" + str(q)))),
                answer(
                    getattr(self, "distress" +
                            str(q)) if getattr(self, "endorse" +
                                               str(q)) else ""),
                answer(
                    getattr(self, "intrusiveness" +
                            str(q)) if getattr(self, "endorse" +
                                               str(q)) else ""),
                answer(
                    getattr(self, "frequency" +
                            str(q)) if getattr(self, "endorse" +
                                               str(q)) else ""))

        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {distress}
                    {intrusiveness}
                    {frequency}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                Anchor points: DISTRESS {distress1}, {distress5}.
                INTRUSIVENESS {intrusiveness1}, {intrusiveness5}.
                FREQUENCY {frequency1}, {frequency5}.
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="10%">Endorsed?</th>
                    <th width="10%">Distress (1–5)</th>
                    <th width="10%">Intrusiveness (1–5)</th>
                    <th width="10%">Frequency (1–5)</th>
                </tr>
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Total score: sum of endorsements (yes = 1, no = 0).
                Dimension scores: sum of ratings (0 if not endorsed).
                (Bell et al. 2006, PubMed ID 16237200)
            </div>
            <div class="{CssClass.COPYRIGHT}">
                CAPS: Copyright © 2005, Bell, Halligan & Ellis.
                Original article:
                    Bell V, Halligan PW, Ellis HD (2006).
                    The Cardiff Anomalous Perceptions Scale (CAPS): a new
                    validated measure of anomalous perceptual experience.
                    Schizophrenia Bulletin 32: 366–377.
                Published by Oxford University Press on behalf of the Maryland
                Psychiatric Research Center. All rights reserved. The online
                version of this article has been published under an open access
                model. Users are entitled to use, reproduce, disseminate, or
                display the open access version of this article for
                non-commercial purposes provided that: the original authorship
                is properly and fully attributed; the Journal and Oxford
                University Press are attributed as the original place of
                publication with the correct citation details given; if an
                article is subsequently reproduced or disseminated not in its
                entirety but only in part or as a derivative work this must be
                clearly indicated. For commercial re-use, please contact
                [email protected].<br>
                <b>This is a derivative work (partial reproduction, viz. the
                scale text).</b>
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr_qa(
                "{} <sup>[1]</sup> (0–32)".format(
                    req.wappstring("total_score")), total),
            distress=tr_qa("{} (0–160)".format(self.wxstring(req, "distress")),
                           distress),
            intrusiveness=tr_qa(
                "{} (0–160)".format(self.wxstring(req, "intrusiveness")),
                intrusiveness),
            frequency=tr_qa(
                "{} (0–160)".format(self.wxstring(req, "frequency")),
                frequency),
            distress1=self.wxstring(req, "distress_option1"),
            distress5=self.wxstring(req, "distress_option5"),
            intrusiveness1=self.wxstring(req, "intrusiveness_option1"),
            intrusiveness5=self.wxstring(req, "intrusiveness_option5"),
            frequency1=self.wxstring(req, "frequency_option1"),
            frequency5=self.wxstring(req, "frequency_option5"),
        )
        return h
Exemplo n.º 8
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        score = self.total_score()
        severity = self.severity(req)
        answer_dict = {None: None}
        for option in range(0, 4):
            answer_dict[option] = (
                str(option) + " — " + self.wxstring(req, "a" + str(option))
            )

        q_a = ""
        for q in range(1, self.NQUESTIONS + 1):
            q_a += tr_qa(
                self.wxstring(req, "q" + str(q)),
                get_from_dict(answer_dict, getattr(self, "q" + str(q)))
            )

        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {anxiety_severity}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                Ratings are over the last 2 weeks.
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="50%">Question</th>
                    <th width="50%">Answer</th>
                </tr>
                {q_a}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] ≥15 severe, ≥10 moderate, ≥5 mild.
                Score ≥10 identifies: generalized anxiety disorder with
                sensitivity 89%, specificity 82% (Spitzer et al. 2006, PubMed
                ID 16717171);
                panic disorder with sensitivity 74%, specificity 81% (Kroenke
                et al. 2010, PMID 20633738);
                social anxiety with sensitivity 72%, specificity 80% (Kroenke
                et al. 2010);
                post-traumatic stress disorder with sensitivity 66%,
                specificity 81% (Kroenke et al. 2010).
                The majority of evidence contributing to these figures comes
                from primary care screening studies.
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.wappstring("total_score"),
                answer(score) + " / {}".format(self.MAX_SCORE)
            ),
            anxiety_severity=tr(
                self.wxstring(req, "anxiety_severity") + " <sup>[1]</sup>",
                severity
            ),
            q_a=q_a,
        )
        return h
Exemplo n.º 9
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        def percent(score: int, maximum: int) -> str:
            return ws.number_to_dp(100 * score / maximum, PERCENT_DP)

        a = self.attn_score()
        m = self.mem_score()
        f = self.fluency_score()
        lang = self.lang_score()
        v = self.vsp_score()
        t = a + m + f + lang + v
        if self.is_complete():
            figsize = (FULLWIDTH_PLOT_WIDTH / 3, FULLWIDTH_PLOT_WIDTH / 4)
            width = 0.9
            fig = req.create_figure(figsize=figsize)
            ax = fig.add_subplot(1, 1, 1)
            scores = numpy.array([a, m, f, lang, v])
            maxima = numpy.array(
                [ATTN_MAX, MEMORY_MAX, FLUENCY_MAX, LANG_MAX, VSP_MAX])
            y = 100 * scores / maxima
            x_labels = ["Attn", "Mem", "Flu", "Lang", "VSp"]
            # noinspection PyTypeChecker
            n = len(y)
            xvar = numpy.arange(n)
            ax.bar(xvar, y, width, color="b")
            ax.set_ylabel("%", fontdict=req.fontdict)
            ax.set_xticks(xvar)
            x_offset = -0.5
            ax.set_xlim(0 + x_offset, len(scores) + x_offset)
            ax.set_xticklabels(x_labels, fontdict=req.fontdict)
            fig.tight_layout()  # or the ylabel drops off the figure
            # fig.autofmt_xdate()
            req.set_figure_font_sizes(ax)
            figurehtml = req.get_html_from_pyplot_figure(fig)
        else:
            figurehtml = "<i>Incomplete; not plotted</i>"
        return (
            self.get_standard_clinician_comments_block(req, self.comments) +
            """
                <div class="{CssClass.SUMMARY}">
                    <table class="{CssClass.SUMMARY}">
                        <tr>
                            {is_complete}
                            <td class="{CssClass.FIGURE}" rowspan="7">{figurehtml}</td>
                        </tr>
            """.format(  # noqa
                CssClass=CssClass,
                is_complete=self.get_is_complete_td_pair(req),
                figurehtml=figurehtml) +
            tr("Total ACE-III score <sup>[1]</sup>",
               answer(t) + " / 100") + tr(
                   "Attention",
                   answer(a) +
                   " / {} ({}%)".format(ATTN_MAX, percent(a, ATTN_MAX))) +
            tr(
                "Memory",
                answer(m) +
                " / {} ({}%)".format(MEMORY_MAX, percent(m, MEMORY_MAX))) +
            tr(
                "Fluency",
                answer(f) +
                " / {} ({}%)".format(FLUENCY_MAX, percent(f, FLUENCY_MAX))) +
            tr(
                "Language",
                answer(lang) +
                " / {} ({}%)".format(LANG_MAX, percent(lang, LANG_MAX))) +
            tr("Visuospatial",
               answer(v) + " / {} ({}%)".format(VSP_MAX, percent(v, VSP_MAX)))
            + """
                    </table>
                </div>
                <table class="{CssClass.TASKDETAIL}">
                    <tr>
                        <th width="75%">Question</th>
                        <th width="25%">Answer/score</td>
                    </tr>
            """.format(CssClass=CssClass) +
            tr_qa("Age on leaving full-time education",
                  self.age_at_leaving_full_time_education) +
            tr_qa("Occupation", ws.webify(self.occupation)) +
            tr_qa("Handedness", ws.webify(self.handedness)) +
            subheading_spanning_two_columns("Attention") + tr(
                "Day? Date? Month? Year? Season?", ", ".join([
                    answer(x) for x in [
                        self.attn_time1, self.attn_time2, self.attn_time3,
                        self.attn_time4, self.attn_time5
                    ]
                ])) +
            tr(
                "House number/floor? Street/hospital? Town? County? Country?",
                ", ".join([
                    answer(x) for x in [
                        self.attn_place1, self.attn_place2, self.attn_place3,
                        self.attn_place4, self.attn_place5
                    ]
                ])) + tr(
                    "Repeat: Lemon? Key? Ball?", ", ".join([
                        answer(x) for x in [
                            self.attn_repeat_word1, self.attn_repeat_word2,
                            self.attn_repeat_word3
                        ]
                    ])) + tr(
                        "Repetition: number of trials <i>(not scored)</i>",
                        answer(self.attn_num_registration_trials,
                               formatter_answer=italic)) +
            tr(
                "Serial subtractions: First correct? Second? Third? Fourth? "
                "Fifth?", ", ".join([
                    answer(x) for x in [
                        self.attn_serial7_subtraction1,
                        self.attn_serial7_subtraction2,
                        self.attn_serial7_subtraction3,
                        self.attn_serial7_subtraction4,
                        self.attn_serial7_subtraction5
                    ]
                ])) + subheading_spanning_two_columns("Memory (1)") + tr(
                    "Recall: Lemon? Key? Ball?", ", ".join([
                        answer(x) for x in [
                            self.mem_recall_word1, self.mem_recall_word2,
                            self.mem_recall_word3
                        ]
                    ])) + subheading_spanning_two_columns("Fluency") +
            tr(
                "Score for words beginning with ‘P’ <i>(≥18 scores 7, 14–17 "
                "scores 6, 11–13 scores 5, 8–10 scores 4, 6–7 scores 3, "
                "4–5 scores 2, 2–3 scores 1, 0–1 scores 0)</i>",
                answer(self.fluency_letters_score) + " / 7") + tr(
                    "Score for animals <i>(≥22 scores 7, 17–21 scores 6, "
                    "14–16 scores 5, 11–13 scores 4, 9–10 scores 3, "
                    "7–8 scores 2, 5–6 scores 1, &lt;5 scores 0)</i>",
                    answer(self.fluency_animals_score) + " / 7") +
            subheading_spanning_two_columns("Memory (2)") + tr(
                "Third trial of address registration: Harry? Barnes? 73? "
                "Orchard? Close? Kingsbridge? Devon?", ", ".join([
                    answer(x) for x in [
                        self.mem_repeat_address_trial3_1,
                        self.mem_repeat_address_trial3_2,
                        self.mem_repeat_address_trial3_3,
                        self.mem_repeat_address_trial3_4,
                        self.mem_repeat_address_trial3_5,
                        self.mem_repeat_address_trial3_6,
                        self.mem_repeat_address_trial3_7
                    ]
                ])) +
            tr(
                "Current PM? Woman who was PM? USA president? USA president "
                "assassinated in 1960s?", ", ".join([
                    answer(x) for x in [
                        self.mem_famous1, self.mem_famous2, self.mem_famous3,
                        self.mem_famous4
                    ]
                ])) + subheading_spanning_two_columns("Language") + tr(
                    "<i>Practice trial (“Pick up the pencil and then the "
                    "paper”)</i>",
                    answer(self.lang_follow_command_practice,
                           formatter_answer=italic)) +
            tr_qa("“Place the paper on top of the pencil”",
                  self.lang_follow_command1) +
            tr_qa("“Pick up the pencil but not the paper”",
                  self.lang_follow_command2) +
            tr_qa("“Pass me the pencil after touching the paper”",
                  self.lang_follow_command3) +
            tr(
                "Sentence-writing: point for ≥2 complete sentences about "
                "the one topic? Point for correct grammar and spelling?",
                ", ".join([
                    answer(x) for x in [
                        self.lang_write_sentences_point1,
                        self.lang_write_sentences_point2
                    ]
                ])) +
            tr(
                "Repeat: caterpillar? eccentricity? unintelligible? "
                "statistician? <i>(score 2 if all correct, 1 if 3 correct, "
                "0 if ≤2 correct)</i>",
                "<i>{}, {}, {}, {}</i> (score <b>{}</b> / 2)".format(
                    answer(self.lang_repeat_word1, formatter_answer=italic),
                    answer(self.lang_repeat_word2, formatter_answer=italic),
                    answer(self.lang_repeat_word3, formatter_answer=italic),
                    answer(self.lang_repeat_word4, formatter_answer=italic),
                    self.get_repeat_word_score(),
                )) + tr_qa("Repeat: “All that glitters is not gold”?",
                           self.lang_repeat_sentence1) +
            tr_qa("Repeat: “A stitch in time saves nine”?",
                  self.lang_repeat_sentence2) +
            tr(
                "Name pictures: spoon, book, kangaroo/wallaby", ", ".join([
                    answer(x) for x in [
                        self.lang_name_picture1, self.lang_name_picture2,
                        self.lang_name_picture3
                    ]
                ])) +
            tr(
                "Name pictures: penguin, anchor, camel/dromedary", ", ".join([
                    answer(x) for x in [
                        self.lang_name_picture4, self.lang_name_picture5,
                        self.lang_name_picture6
                    ]
                ])) + tr(
                    "Name pictures: harp, rhinoceros/rhino, barrel/keg/tub",
                    ", ".join([
                        answer(x) for x in [
                            self.lang_name_picture7, self.lang_name_picture8,
                            self.lang_name_picture9
                        ]
                    ])) + tr(
                        "Name pictures: crown, alligator/crocodile, "
                        "accordion/piano accordion/squeeze box", ", ".join([
                            answer(x) for x in [
                                self.lang_name_picture10, self.
                                lang_name_picture11, self.lang_name_picture12
                            ]
                        ])) +
            tr(
                "Identify pictures: monarchy? marsupial? Antarctic? nautical?",
                ", ".join([
                    answer(x) for x in [
                        self.lang_identify_concept1, self.
                        lang_identify_concept2, self.lang_identify_concept3,
                        self.lang_identify_concept4
                    ]
                ])) +
            tr_qa("Read all successfully: sew, pint, soot, dough, height",
                  self.lang_read_words_aloud) +
            subheading_spanning_two_columns("Visuospatial") +
            tr("Copy infinity",
               answer(self.vsp_copy_infinity) + " / 1") +
            tr("Copy cube",
               answer(self.vsp_copy_cube) + " / 2") +
            tr("Draw clock with numbers and hands at 5:10",
               answer(self.vsp_draw_clock) + " / 5") + tr(
                   "Count dots: 8, 10, 7, 9", ", ".join([
                       answer(x) for x in [
                           self.vsp_count_dots1, self.vsp_count_dots2,
                           self.vsp_count_dots3, self.vsp_count_dots4
                       ]
                   ])) +
            tr(
                "Identify letters: K, M, A, T", ", ".join([
                    answer(x) for x in [
                        self.vsp_identify_letter1, self.vsp_identify_letter2,
                        self.vsp_identify_letter3, self.vsp_identify_letter4
                    ]
                ])) + subheading_spanning_two_columns("Memory (3)") + tr(
                    "Recall address: Harry? Barnes? 73? Orchard? Close? "
                    "Kingsbridge? Devon?", ", ".join([
                        answer(x) for x in [
                            self.mem_recall_address1, self.mem_recall_address2,
                            self.mem_recall_address3, self.mem_recall_address4,
                            self.mem_recall_address5, self.mem_recall_address6,
                            self.mem_recall_address7
                        ]
                    ])) +
            tr(
                "Recognize address: Jerry Barnes/Harry Barnes/Harry Bradford?",
                self.get_recog_text(
                    (self.mem_recall_address1 == 1 and self.mem_recall_address2
                     == 1), self.mem_recognize_address1)) + tr(
                         "Recognize address: 37/73/76?",
                         self.get_recog_text((self.mem_recall_address3 == 1),
                                             self.mem_recognize_address2)) +
            tr(
                "Recognize address: Orchard Place/Oak Close/Orchard "
                "Close?",
                self.get_recog_text(
                    (self.mem_recall_address4 == 1 and self.mem_recall_address5
                     == 1), self.mem_recognize_address3)) +
            tr(
                "Recognize address: Oakhampton/Kingsbridge/Dartington?",
                self.get_recog_text(
                    (self.mem_recall_address6 == 1),
                    self.mem_recognize_address4)) + tr(
                        "Recognize address: Devon/Dorset/Somerset?",
                        self.get_recog_text((self.mem_recall_address7 == 1),
                                            self.mem_recognize_address5)) +
            subheading_spanning_two_columns("Photos of test sheet") +
            tr_span_col(get_blob_img_html(self.picture1),
                        td_class=CssClass.PHOTO) +
            tr_span_col(get_blob_img_html(self.picture2),
                        td_class=CssClass.PHOTO) + """
                </table>
                <div class="{CssClass.FOOTNOTES}">
                    [1] In the ACE-R (the predecessor of the ACE-III),
                    scores ≤82 had sensitivity 0.84 and specificity 1.0 for
                    dementia, and scores ≤88 had sensitivity 0.94 and
                    specificity 0.89 for dementia, in a context of patients
                    with AlzD, FTD, LBD, MCI, and controls
                    (Mioshi et al., 2006, PMID 16977673).
                </div>
                <div class="{CssClass.COPYRIGHT}">
                    ACE-III: Copyright © 2012, John Hodges.
                    “The ACE-III is available for free. The copyright is held
                    by Professor John Hodges who is happy for the test to be
                    used in clinical practice and research projects. There is
                    no need to contact us if you wish to use the ACE-III in
                    clinical practice.”
                    (ACE-III FAQ, 7 July 2013, www.neura.edu.au).
                </div>
            """.format(CssClass=CssClass))
Exemplo n.º 10
0
 def get_task_html(self, req: CamcopsRequest) -> str:
     p = self.score_p()
     n = self.score_n()
     g = self.score_g()
     composite = self.composite()
     total = p + n + g
     answers = {
         None: None,
         1: self.wxstring(req, "option1"),
         2: self.wxstring(req, "option2"),
         3: self.wxstring(req, "option3"),
         4: self.wxstring(req, "option4"),
         5: self.wxstring(req, "option5"),
         6: self.wxstring(req, "option6"),
         7: self.wxstring(req, "option7"),
     }
     q_a = ""
     for q in self.TASK_FIELDS:
         q_a += tr_qa(self.wxstring(req, "" + q + "_s"),
                      get_from_dict(answers, getattr(self, q)))
     h = """
         <div class="{CssClass.SUMMARY}">
             <table class="{CssClass.SUMMARY}">
                 {tr_is_complete}
                 {total_score}
                 {p}
                 {n}
                 {g}
                 {composite}
             </table>
         </div>
         <table class="{CssClass.TASKDETAIL}">
             <tr>
                 <th width="40%">Question</th>
                 <th width="60%">Answer</th>
             </tr>
             {q_a}
         </table>
         {DATA_COLLECTION_ONLY_DIV}
     """.format(
         CssClass=CssClass,
         tr_is_complete=self.get_is_complete_tr(req),
         total_score=tr_qa(
             "{} ({}–{})".format(req.wappstring("total_score"),
                                 self.MIN_TOTAL, self.MAX_TOTAL), total),
         p=tr_qa(
             "{} ({}–{})".format(self.wxstring(req, "p"), self.MIN_P,
                                 self.MAX_P), p),
         n=tr_qa(
             "{} ({}–{})".format(self.wxstring(req, "n"), self.MIN_N,
                                 self.MAX_N), n),
         g=tr_qa(
             "{} ({}–{})".format(self.wxstring(req, "g"), self.MIN_G,
                                 self.MAX_G), g),
         composite=tr_qa(
             "{} ({}–{})".format(self.wxstring(req, "composite"),
                                 self.MIN_P_MINUS_N, self.MAX_P_MINUS_N),
             composite),
         q_a=q_a,
         DATA_COLLECTION_ONLY_DIV=DATA_COLLECTION_ONLY_DIV,
     )
     return h
Exemplo n.º 11
0
 def get_task_html(self, req: CamcopsRequest) -> str:
     h = """
         <div class="{CssClass.SUMMARY}">
             <table class="{CssClass.SUMMARY}">
                 {tr_is_complete}
             </table>
         </div>
         <div class="{CssClass.EXPLANATION}">
             1. Simple discrimination (SD), and 2. reversal (SDr);
             3. compound discrimination (CD), and 4. reversal (CDr);
             5. intradimensional shift (ID), and 6. reversal (IDr);
             7. extradimensional shift (ED), and 8. reversal (EDr).
         </div>
         <table class="{CssClass.TASKCONFIG}">
             <tr>
                 <th width="50%">Configuration variable</th>
                 <th width="50%">Value</th>
             </tr>
     """.format(
         CssClass=CssClass,
         tr_is_complete=self.get_is_complete_tr(req),
     )
     h += tr_qa(self.wxstring(req, "last_stage"), self.last_stage)
     h += tr_qa(self.wxstring(req, "max_trials_per_stage"),
                self.max_trials_per_stage)
     h += tr_qa(self.wxstring(req, "progress_criterion_x"),
                self.progress_criterion_x)
     h += tr_qa(self.wxstring(req, "progress_criterion_y"),
                self.progress_criterion_y)
     h += tr_qa(self.wxstring(req, "min_number"), self.min_number)
     h += tr_qa(self.wxstring(req, "max_number"), self.max_number)
     h += tr_qa(self.wxstring(req, "pause_after_beep_ms"),
                self.pause_after_beep_ms)
     h += tr_qa(self.wxstring(req, "iti_ms"), self.iti_ms)
     h += tr_qa(
         self.wxstring(req, "counterbalance_dimensions") + "<sup>[1]</sup>",
         self.counterbalance_dimensions)
     h += tr_qa(req.wappstring("volume_0_to_1"), self.volume)
     h += tr_qa(self.wxstring(req, "offer_abort"), self.offer_abort)
     h += tr_qa(self.wxstring(req, "debug_display_stimuli_only"),
                self.debug_display_stimuli_only)
     h += tr_qa(
         "Shapes (as a JSON-encoded array of SVG "
         "definitions; X and Y range both –60 to +60)",
         ws.webify(self.shape_definitions_svg))
     h += """
         </table>
         <table class="{CssClass.TASKDETAIL}">
             <tr><th width="50%">Measure</th><th width="50%">Value</th></tr>
     """.format(CssClass=CssClass)
     h += tr_qa("Aborted?", get_yes_no_none(req, self.aborted))
     h += tr_qa("Finished?", get_yes_no_none(req, self.finished))
     h += tr_qa("Last trial completed", self.last_trial_completed)
     h += ("""
             </table>
             <div>Stage specifications and results:</div>
         """ + self.get_stage_html() +
           "<div>Trial-by-trial results:</div>" + self.get_trial_html() +
           """
             <div class="{CssClass.FOOTNOTES}">
                 [1] Counterbalancing of dimensions is as follows, with
                 notation X/Y indicating that X is the first relevant
                 dimension (for stages SD–IDr) and Y is the second relevant
                 dimension (for stages ED–EDr).
                 0: shape/colour.
                 1: colour/number.
                 2: number/shape.
                 3: shape/number.
                 4: colour/shape.
                 5: number/colour.
             </div>
         """.format(CssClass=CssClass))
     return h
Exemplo n.º 12
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        score = self.total_score()

        # Suicidal thoughts:
        suicidality_score = getattr(self, SUICIDALITY_FN)
        if suicidality_score is None:
            suicidality_text = bold("? (not completed)")
            suicidality_css_class = CssClass.INCOMPLETE
        elif suicidality_score == 0:
            suicidality_text = str(suicidality_score)
            suicidality_css_class = ""
        else:
            suicidality_text = bold(str(suicidality_score))
            suicidality_css_class = CssClass.WARNING

        # Custom somatic score for Khandaker Insight study:
        somatic_css_class = ""
        if self.is_bdi_ii():
            somatic_values = self.get_values(
                CUSTOM_SOMATIC_KHANDAKER_BDI_II_FIELDS)
            somatic_missing = False
            somatic_score = 0
            for v in somatic_values:
                if v is None:
                    somatic_missing = True
                    somatic_css_class = CssClass.INCOMPLETE
                    break
                else:
                    somatic_score += int(v)
            somatic_text = ("incomplete" if somatic_missing
                            else str(somatic_score))
        else:
            somatic_text = "N/A"  # not the BDI-II

        # Question rows:
        q_a = ""
        qdict = TOPICS_BY_SCALE.get(self.bdi_scale)
        topic = "?"
        for q in range(1, NQUESTIONS + 1):
            if qdict:
                topic = qdict.get(q, "??")
            q_a += tr_qa(
                "{question} {qnum} ({topic})".format(
                    question=req.wappstring("question"),
                    qnum=q,
                    topic=topic,
                ),
                getattr(self, "q" + str(q))
            )

        # HTML:
        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {tr_total_score}
                    <tr>
                        <td>
                            Suicidal thoughts/wishes score
                            (Q{suicidality_qnum}) <sup>[1]</sup>
                        </td>
                        {td_suicidality}
                    </tr>
                    {tr_somatic_score}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                All questions are scored from 0–3
                (0 free of symptoms, 3 most symptomatic).
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="70%">Question</th>
                    <th width="30%">Answer</th>
                </tr>
                {tr_which_scale}
                {q_a}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Suicidal thoughts are asked about in Q{suicidality_qnum}
                    for all of: BDI-I (1961), BDI-IA (1978), and BDI-II (1996).

                [2] Insight study:
                    <a href="https://doi.org/10.1186/ISRCTN16942542">doi:10.1186/ISRCTN16942542</a>

                [3] See the
                    <a href="https://camcops.readthedocs.io/en/latest/tasks/bdi.html">CamCOPS
                    BDI help</a> for full references and bibliography for the
                    citations that follow.

                    <b>The BDI rates “right now” [Beck1988].
                    The BDI-IA rates the past week [Beck1988].
                    The BDI-II rates the past two weeks [Beck1996b].</b>

                    1961 BDI(-I) question topics from [Beck1988].
                    1978 BDI-IA question topics from [Beck1996b].
                    1996 BDI-II question topics from [Steer1999], [Gary2018].
                </ul>

            </div>
            {data_collection_only_div}
        """.format(  # noqa
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            tr_total_score=tr(
                req.wappstring("total_score"),
                answer(score) + " / {}".format(MAX_SCORE)
            ),
            suicidality_qnum=SUICIDALITY_QNUM,
            td_suicidality=td(suicidality_text, td_class=suicidality_css_class),  # noqa
            tr_somatic_score=tr(
                td(
                    "Custom somatic score for Insight study <sup>[2]</sup> "
                    "(sum of scores for questions {}, for BDI-II only)".format(
                        ", ".join("Q" + str(qnum) for qnum in
                                  CUSTOM_SOMATIC_KHANDAKER_BDI_II_QNUMS))
                ),
                td(somatic_text, td_class=somatic_css_class),
                literal=True
            ),
            tr_which_scale=tr_qa(
                req.wappstring("bdi_which_scale") + " <sup>[3]</sup>",
                ws.webify(self.bdi_scale)
            ),
            q_a=q_a,
            data_collection_only_div=DATA_COLLECTION_ONLY_DIV
        )
        return h
Exemplo n.º 13
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        h = """
            {clinician_comments}
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {date_pertains_to}
                    {category}
                    {n_core}
                    {n_total}
                    {n_somatic}
                    {psychotic_symptoms_or_stupor}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                {icd10_symptomatic_disclaimer}
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="80%">Question</th>
                    <th width="20%">Answer</th>
                </tr>
        """.format(
            clinician_comments=self.get_standard_clinician_comments_block(
                req, self.comments),
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            date_pertains_to=tr_qa(
                req.wappstring("date_pertains_to"),
                format_datetime(self.date_pertains_to,
                                DateFormat.LONG_DATE,
                                default=None)),
            category=tr_qa(
                req.wappstring("category") + " <sup>[1,2]</sup>",
                self.get_full_description(req)),
            n_core=tr(self.wxstring(req, "n_core"),
                      answer(self.n_core()) + " / 3"),
            n_total=tr(self.wxstring(req, "n_total"),
                       answer(self.n_total()) + " / 10"),
            n_somatic=tr(self.wxstring(req, "n_somatic"),
                         answer(self.n_somatic()) + " / 8"),
            psychotic_symptoms_or_stupor=tr(
                self.wxstring(req, "psychotic_symptoms_or_stupor") +
                " <sup>[2]</sup>",
                answer(
                    get_present_absent_none(req,
                                            self.is_psychotic_or_stupor()))),
            icd10_symptomatic_disclaimer=req.wappstring(
                "icd10_symptomatic_disclaimer"),
        )

        h += self.text_row(req, "duration_text")
        h += self.row_true_false(req, "duration_at_least_2_weeks")

        h += self.text_row(req, "core")
        for x in self.CORE_NAMES:
            h += self.row_present_absent(req, x)

        h += self.text_row(req, "additional")
        for x in self.ADDITIONAL_NAMES:
            h += self.row_present_absent(req, x)

        h += self.text_row(req, "clinical_text")
        h += self.row_true_false(req, "severe_clinically")

        h += self.text_row(req, "somatic")
        for x in self.SOMATIC_NAMES:
            h += self.row_present_absent(req, x)

        h += self.text_row(req, "psychotic")
        for x in self.PSYCHOSIS_NAMES:
            h += self.row_present_absent(req, x)

        h += """
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Mild depression requires ≥2 core symptoms and ≥4 total
                diagnostic symptoms.
                Moderate depression requires ≥2 core and ≥6 total.
                Severe depression requires 3 core and ≥8 total.
                All three require a duration of ≥2 weeks.
                In addition, the diagnosis of severe depression is allowed with
                a clinical impression of “severe” in a patient unable/unwilling
                to describe symptoms in detail.
                [2] ICD-10 nonpsychotic severe depression requires severe
                depression without hallucinations/delusions/depressive stupor.
                ICD-10 psychotic depression requires severe depression plus
                hallucinations/delusions other than those that are “typically
                schizophrenic”, or stupor.
                ICD-10 does not clearly categorize severe depression with only
                schizophreniform psychotic symptoms;
                however, such symptoms can occur in severe depression with
                psychosis (e.g. Tandon R &amp; Greden JF, 1987, PMID 2884810).
                Moreover, psychotic symptoms can occur in mild/moderate
                depression (Maj M et al., 2007, PMID 17915981).
            </div>
        """.format(CssClass=CssClass) + ICD10_COPYRIGHT_DIV
        return h
Exemplo n.º 14
0
 def category(self, req: CamcopsRequest) -> str:
     totalscore = self.total_score()
     return (req.wappstring("normal")
             if totalscore >= 26 else req.wappstring("abnormal"))
Exemplo n.º 15
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        vsp = self.score_vsp()
        naming = self.score_naming()
        attention = self.score_attention()
        language = self.score_language()
        abstraction = self.score_abstraction()
        memory = self.score_memory()
        orientation = self.score_orientation()
        totalscore = self.total_score()
        category = self.category(req)

        h = """
            {clinician_comments}
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {category}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="69%">Question</th>
                    <th width="31%">Score</th>
                </tr>
        """.format(
            clinician_comments=self.get_standard_clinician_comments_block(
                req, self.comments),
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.sstring(SS.TOTAL_SCORE),
                answer(totalscore) + f" / {self.MAX_SCORE}",
            ),
            category=tr_qa(
                self.wxstring(req, "category") + " <sup>[1]</sup>", category),
        )

        h += tr(
            self.wxstring(req, "subscore_visuospatial"),
            answer(vsp) + " / 5",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "Path, cube, clock/contour, clock/numbers, clock/hands",
            ", ".join(
                answer(x)
                for x in (self.q1, self.q2, self.q3, self.q4, self.q5)),
        )

        h += tr(
            self.wxstring(req, "subscore_naming"),
            answer(naming) + " / 3",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "Lion, rhino, camel",
            ", ".join(answer(x) for x in (self.q6, self.q7, self.q8)),
        )

        h += tr(
            self.wxstring(req, "subscore_attention"),
            answer(attention) + " / 6",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "5 digits forwards, 3 digits backwards, tapping, serial 7s "
            "[<i>scores 3</i>]",
            ", ".join(
                answer(x) for x in (self.q9, self.q10, self.q11, self.q12)),
        )

        h += tr(
            self.wxstring(req, "subscore_language"),
            answer(language) + " / 3",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "Repeat sentence 1, repeat sentence 2, fluency to letter ‘F’",
            ", ".join(answer(x) for x in (self.q13, self.q14, self.q15)),
        )

        h += tr(
            self.wxstring(req, "subscore_abstraction"),
            answer(abstraction) + " / 2",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "Means of transportation, measuring instruments",
            ", ".join(answer(x) for x in (self.q16, self.q17)),
        )

        h += tr(
            self.wxstring(req, "subscore_memory"),
            answer(memory) + " / 5",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "Registered on first trial [<i>not scored</i>]",
            ", ".join(
                answer(x, formatter_answer=italic) for x in (
                    self.register_trial1_1,
                    self.register_trial1_2,
                    self.register_trial1_3,
                    self.register_trial1_4,
                    self.register_trial1_5,
                )),
        )
        h += tr(
            "Registered on second trial [<i>not scored</i>]",
            ", ".join(
                answer(x, formatter_answer=italic) for x in (
                    self.register_trial2_1,
                    self.register_trial2_2,
                    self.register_trial2_3,
                    self.register_trial2_4,
                    self.register_trial2_5,
                )),
        )
        h += tr(
            "Recall FACE, VELVET, CHURCH, DAISY, RED with no cue",
            ", ".join(
                answer(x)
                for x in (self.q18, self.q19, self.q20, self.q21, self.q22)),
        )
        h += tr(
            "Recall with category cue [<i>not scored</i>]",
            ", ".join(
                answer(x, formatter_answer=italic) for x in (
                    self.recall_category_cue_1,
                    self.recall_category_cue_2,
                    self.recall_category_cue_3,
                    self.recall_category_cue_4,
                    self.recall_category_cue_5,
                )),
        )
        h += tr(
            "Recall with multiple-choice cue [<i>not scored</i>]",
            ", ".join(
                answer(x, formatter_answer=italic) for x in (
                    self.recall_mc_cue_1,
                    self.recall_mc_cue_2,
                    self.recall_mc_cue_3,
                    self.recall_mc_cue_4,
                    self.recall_mc_cue_5,
                )),
        )

        h += tr(
            self.wxstring(req, "subscore_orientation"),
            answer(orientation) + " / 6",
            tr_class=CssClass.SUBHEADING,
        )
        h += tr(
            "Date, month, year, day, place, city",
            ", ".join(
                answer(x) for x in (
                    self.q23,
                    self.q24,
                    self.q25,
                    self.q26,
                    self.q27,
                    self.q28,
                )),
        )

        h += subheading_spanning_two_columns(self.wxstring(req, "education_s"))
        h += tr_qa("≤12 years’ education?", self.education12y_or_less)
        # noinspection PyTypeChecker
        h += """
            </table>
            <table class="{CssClass.TASKDETAIL}">
                {tr_subhead_images}
                {tr_images_1}
                {tr_images_2}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Normal is ≥26 (Nasreddine et al. 2005, PubMed ID 15817019).
            </div>
            <div class="{CssClass.COPYRIGHT}">
                MoCA: Copyright © Ziad Nasreddine. In 2012, could be reproduced
                without permission for CLINICAL and EDUCATIONAL use (with
                permission from the copyright holder required for any other
                use), with no special restrictions on electronic versions.
                However, as of 2021, electronic versions are prohibited without
                specific authorization from the copyright holder; see <a
                href="https://camcops.readthedocs.io/en/latest/tasks/moca.html">
                https://camcops.readthedocs.io/en/latest/tasks/moca.html</a>.
            </div>
        """.format(
            CssClass=CssClass,
            tr_subhead_images=subheading_spanning_two_columns(
                "Images of tests: trail, cube, clock", th_not_td=True),
            tr_images_1=tr(
                td(
                    get_blob_img_html(self.trailpicture),
                    td_class=CssClass.PHOTO,
                    td_width="50%",
                ),
                td(
                    get_blob_img_html(self.cubepicture),
                    td_class=CssClass.PHOTO,
                    td_width="50%",
                ),
                literal=True,
            ),
            tr_images_2=tr(
                td(
                    get_blob_img_html(self.clockpicture),
                    td_class=CssClass.PHOTO,
                    td_width="50%",
                ),
                td("", td_class=CssClass.SUBHEADING),
                literal=True,
            ),
        )
        return h
Exemplo n.º 16
0
    def get_query(self, req: CamcopsRequest) -> SelectBase:
        which_idnum = req.get_int_param(ViewParam.WHICH_IDNUM, 1)
        if which_idnum is None:
            raise exc.HTTPBadRequest(f"{ViewParam.WHICH_IDNUM} not specified")

        group_ids = req.user.ids_of_groups_user_may_report_on

        # Step 1: link referral and patient
        p1 = Patient.__table__.alias("p1")
        i1 = PatientIdNum.__table__.alias("i1")
        desc = req.get_id_shortdesc(which_idnum)
        select_fields = [
            CPFTLPSReferral.lps_division,
            CPFTLPSReferral.referral_date_time,
            CPFTLPSReferral.referral_priority,
            p1.c.surname,
            p1.c.forename,
            p1.c.dob,
            i1.c.idnum_value.label(desc),
            CPFTLPSReferral.patient_location,
        ]
        select_from = p1.join(
            CPFTLPSReferral.__table__,
            and_(
                p1.c._current == True,  # noqa: E712
                CPFTLPSReferral.patient_id == p1.c.id,
                CPFTLPSReferral._device_id == p1.c._device_id,
                CPFTLPSReferral._era == p1.c._era,
                CPFTLPSReferral._current == True,
            ),
        )
        select_from = select_from.join(
            i1,
            and_(
                i1.c.patient_id == p1.c.id,
                i1.c._device_id == p1.c._device_id,
                i1.c._era == p1.c._era,
                i1.c._current == True,  # noqa: E712
            ),
        )
        wheres = [i1.c.which_idnum == which_idnum]
        if not req.user.superuser:
            # Restrict to accessible groups
            wheres.append(CPFTLPSReferral._group_id.in_(group_ids))

        # Step 2: not yet discharged
        p2 = Patient.__table__.alias("p2")
        i2 = PatientIdNum.__table__.alias("i2")
        discharge = (
            select(["*"]).select_from(
                p2.join(
                    CPFTLPSDischarge.__table__,
                    and_(
                        p2.c._current == True,  # noqa: E712
                        CPFTLPSDischarge.patient_id == p2.c.id,
                        CPFTLPSDischarge._device_id == p2.c._device_id,
                        CPFTLPSDischarge._era == p2.c._era,
                        CPFTLPSDischarge._current == True,
                    ),
                ).join(
                    i2,
                    and_(
                        i2.c.patient_id == p2.c.id,
                        i2.c._device_id == p2.c._device_id,
                        i2.c._era == p2.c._era,
                        i2.c._current == True,
                    ),
                )).where(
                    and_(
                        # Link on ID to main query: same patient
                        i2.c.which_idnum == which_idnum,
                        i2.c.idnum_value == i1.c.idnum_value,
                        # Discharge later than referral
                        (CPFTLPSDischarge.discharge_date >=
                         CPFTLPSReferral.referral_date_time),
                    )))  # nopep8
        if not req.user.superuser:
            # Restrict to accessible groups
            discharge = discharge.where(
                CPFTLPSDischarge._group_id.in_(group_ids))

        wheres.append(~exists(discharge))

        # Finish up
        order_by = [
            CPFTLPSReferral.lps_division,
            CPFTLPSReferral.referral_date_time,
            CPFTLPSReferral.referral_priority,
        ]
        query = (select(select_fields).select_from(select_from).where(
            and_(*wheres)).order_by(*order_by))
        return query
Exemplo n.º 17
0
    def get_query(self, req: CamcopsRequest) -> SelectBase:
        which_idnum = req.get_int_param(ViewParam.WHICH_IDNUM, 1)
        if which_idnum is None:
            raise exc.HTTPBadRequest("{} not specified".format(
                ViewParam.WHICH_IDNUM))

        group_ids = req.user.ids_of_groups_user_may_report_on

        # Step 1: link referral and patient
        # noinspection PyUnresolvedReferences
        p1 = Patient.__table__.alias("p1")
        # noinspection PyUnresolvedReferences
        i1 = PatientIdNum.__table__.alias("i1")
        desc = req.get_id_shortdesc(which_idnum)
        select_fields = [
            CPFTLPSReferral.lps_division,
            CPFTLPSReferral.referral_date_time,
            CPFTLPSReferral.referral_priority,
            p1.c.surname,
            p1.c.forename,
            p1.c.dob,
            i1.c.idnum_value.label(desc),
            CPFTLPSReferral.patient_location,
        ]
        # noinspection PyUnresolvedReferences
        select_from = p1.join(
            CPFTLPSReferral.__table__,
            and_(
                p1.c._current == True,
                CPFTLPSReferral.patient_id == p1.c.id,
                CPFTLPSReferral._device_id == p1.c._device_id,
                CPFTLPSReferral._era == p1.c._era,
                CPFTLPSReferral._current == True,
            ))  # nopep8
        select_from = select_from.join(i1,
                                       and_(
                                           i1.c.patient_id == p1.c.id,
                                           i1.c._device_id == p1.c._device_id,
                                           i1.c._era == p1.c._era,
                                           i1.c._current == True,
                                       ))  # nopep8
        wheres = [
            i1.c.which_idnum == which_idnum,
        ]
        if not req.user.superuser:
            # Restrict to accessible groups
            wheres.append(CPFTLPSReferral._group_id.in_(group_ids))

        # Step 2: not yet discharged
        # noinspection PyUnresolvedReferences
        p2 = Patient.__table__.alias("p2")
        # noinspection PyUnresolvedReferences
        i2 = PatientIdNum.__table__.alias("i2")
        # noinspection PyUnresolvedReferences
        discharge = (
            select(['*']).select_from(
                p2.join(
                    CPFTLPSDischarge.__table__,
                    and_(
                        p2.c._current == True,
                        CPFTLPSDischarge.patient_id == p2.c.id,
                        CPFTLPSDischarge._device_id == p2.c._device_id,
                        CPFTLPSDischarge._era == p2.c._era,
                        CPFTLPSDischarge._current == True,
                    )).join(
                        i2,
                        and_(
                            i2.c.patient_id == p2.c.id,
                            i2.c._device_id == p2.c._device_id,
                            i2.c._era == p2.c._era,
                            i2.c._current == True,
                        ))).where(
                            and_(
                                # Link on ID to main query: same patient
                                i2.c.which_idnum == which_idnum,
                                i2.c.idnum_value == i1.c.idnum_value,
                                # Discharge later than referral
                                (CPFTLPSDischarge.discharge_date >=
                                 CPFTLPSReferral.referral_date_time),
                            )))  # nopep8
        if not req.user.superuser:
            # Restrict to accessible groups
            discharge = discharge.where(
                CPFTLPSDischarge._group_id.in_(group_ids))
        wheres.append(~exists(discharge))

        # Step 3: not yet clerked
        # noinspection PyUnresolvedReferences
        p3 = Patient.__table__.alias("p3")
        # noinspection PyUnresolvedReferences
        i3 = PatientIdNum.__table__.alias("i3")
        # noinspection PyUnresolvedReferences
        clerking = (
            select(['*']).select_from(
                p3.join(
                    PsychiatricClerking.__table__,
                    and_(
                        p3.c._current == True,
                        PsychiatricClerking.patient_id == p3.c.id,
                        PsychiatricClerking._device_id == p3.c._device_id,
                        PsychiatricClerking._era == p3.c._era,
                        PsychiatricClerking._current == True,
                    )).join(
                        i3,
                        and_(
                            i3.c.patient_id == p3.c.id,
                            i3.c._device_id == p3.c._device_id,
                            i3.c._era == p3.c._era,
                            i3.c._current == True,
                        ))).where(
                            and_(
                                # Link on ID to main query: same patient
                                i3.c.which_idnum == which_idnum,
                                i3.c.idnum_value == i1.c.idnum_value,
                                # Discharge later than referral
                                (PsychiatricClerking.when_created >=
                                 CPFTLPSReferral.referral_date_time),
                            )))  # nopep8
        if not req.user.superuser:
            # Restrict to accessible groups
            clerking = clerking.where(
                PsychiatricClerking._group_id.in_(group_ids))
        wheres.append(~exists(clerking))

        # Finish up
        order_by = [
            CPFTLPSReferral.lps_division,
            CPFTLPSReferral.referral_date_time,
            CPFTLPSReferral.referral_priority,
        ]
        query = (select(select_fields).select_from(select_from).where(
            and_(*wheres)).order_by(*order_by))
        return query
Exemplo n.º 18
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        h = """
            {clinician_comments}
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {date_pertains_to}
                    {meets_general_criteria}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                {comments}
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="80%">Question</th>
                    <th width="20%">Answer</th>
                </tr>
        """.format(
            clinician_comments=self.get_standard_clinician_comments_block(
                req, self.comments),
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            date_pertains_to=tr_qa(
                req.wappstring("date_pertains_to"),
                format_datetime(self.date_pertains_to,
                                DateFormat.LONG_DATE,
                                default=None)),
            meets_general_criteria=tr_qa(
                self.wxstring(req, "meets_general_criteria") +
                " <sup>[1]</sup>",  # noqa
                get_true_false_none(req, self.meets_general_criteria())),
            comments=self.wxstring(req, "comments"),
        )

        h += self.heading_row(req, "core", " <sup>[2]</sup>")
        for x in Icd10Schizophrenia.A_NAMES:
            h += self.row_present_absent(req, x)

        h += self.heading_row(req, "other_positive")
        for x in Icd10Schizophrenia.B_NAMES:
            h += self.row_present_absent(req, x)

        h += self.heading_row(req, "negative_title")
        for x in Icd10Schizophrenia.C_NAMES:
            h += self.row_present_absent(req, x)

        h += self.heading_row(req, "other_criteria")
        for x in Icd10Schizophrenia.D_NAMES:
            h += self.row_true_false(req, x)
        h += self.text_row(req, "duration_comment")
        for x in Icd10Schizophrenia.E_NAMES:
            h += self.row_true_false(req, x)
        h += self.text_row(req, "affective_comment")
        for x in Icd10Schizophrenia.F_NAMES:
            h += self.row_true_false(req, x)

        h += self.heading_row(req, "simple_title")
        for x in Icd10Schizophrenia.G_NAMES:
            h += self.row_present_absent(req, x)

        h += self.heading_row(req, "subtypes")
        for x in Icd10Schizophrenia.H_NAMES:
            h += self.row_present_absent(req, x)

        h += """
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] All of:
                    (a) at least one core symptom, or at least two of the other
                        positive or negative symptoms;
                    (b) present for a month (etc.);
                    (c) if also manic/depressed, schizophreniform psychosis
                        came first;
                    (d) not attributable to organic brain disease or
                        psychoactive substances.
                [2] Symptom definitions from:
                    (a) Oyebode F (2008). Sims’ Symptoms in the Mind: An
                        Introduction to Descriptive Psychopathology. Fourth
                        edition, Saunders, Elsevier, Edinburgh.
                    (b) Pawar AV &amp; Spence SA (2003), PMID 14519605.
            </div>
        """.format(CssClass=CssClass) + ICD10_COPYRIGHT_DIV
        return h
Exemplo n.º 19
0
 def get_task_html(self, req: CamcopsRequest) -> str:
     if self.modality == MODALITY_AUDITORY:
         modality = req.wappstring("auditory")
     elif self.modality == MODALITY_VISUAL:
         modality = req.wappstring("visual")
     else:
         modality = None
     h = """
         <div class="{CssClass.SUMMARY}">
             <table class="{CssClass.SUMMARY}">
                 {tr_is_complete}
             </table>
         </div>
         <div class="{CssClass.EXPLANATION}">
             The ExpDet-Threshold task measures visual and auditory
             thresholds for stimuli on a noisy background, using a
             single-interval up/down method. It is intended as a prequel to
             the Expectation–Detection task.
         </div>
         <table class="{CssClass.TASKCONFIG}">
             <tr>
                 <th width="50%">Configuration variable</th>
                 <th width="50%">Value</th>
             </tr>
     """.format(
         CssClass=CssClass,
         tr_is_complete=self.get_is_complete_tr(req),
     )
     h += tr_qa("Modality", modality)
     h += tr_qa("Target number", self.target_number)
     h += tr_qa("Background filename", ws.webify(self.background_filename))
     h += tr_qa("Background intensity", self.background_intensity)
     h += tr_qa("Target filename", ws.webify(self.target_filename))
     h += tr_qa("(For visual targets) Target duration (s)",
                self.visual_target_duration_s)
     h += tr_qa("Start intensity (minimum)", self.start_intensity_min)
     h += tr_qa("Start intensity (maximum)", self.start_intensity_max)
     h += tr_qa("Initial (large) intensity step",
                self.initial_large_intensity_step)
     h += tr_qa("Main (small) intensity step",
                self.main_small_intensity_step)
     h += tr_qa("Number of trials in main sequence",
                self.num_trials_in_main_sequence)
     h += tr_qa("Probability of a catch trial", self.p_catch_trial)
     h += tr_qa("Prompt", self.prompt)
     h += tr_qa("Intertrial interval (ITI) (s)", self.iti_s)
     h += """
         </table>
         <table class="{CssClass.TASKDETAIL}">
             <tr><th width="50%">Measure</th><th width="50%">Value</th></tr>
     """.format(CssClass=CssClass)
     h += tr_qa("Finished?", get_yes_no_none(req, self.finished))
     h += tr_qa("Logistic intercept", ws.number_to_dp(self.intercept, DP))
     h += tr_qa("Logistic slope", ws.number_to_dp(self.slope, DP))
     h += tr_qa("Logistic k (= slope)", ws.number_to_dp(self.k, DP))
     h += tr_qa("Logistic theta (= –intercept/slope)",
                ws.number_to_dp(self.theta, DP))
     h += tr_qa("Intensity for {}% detection".format(100 * LOWER_MARKER),
                ws.number_to_dp(self.logistic_x_from_p(LOWER_MARKER), DP))
     h += tr_qa("Intensity for 50% detection",
                ws.number_to_dp(self.theta, DP))
     h += tr_qa("Intensity for {}% detection".format(100 * UPPER_MARKER),
                ws.number_to_dp(self.logistic_x_from_p(UPPER_MARKER), DP))
     h += """
         </table>
     """
     h += self.get_trial_html(req)
     return h
Exemplo n.º 20
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        normal_score_dict = {
            None: None,
            1: "1 — " + self.wxstring(req, "a0"),
            2: "2 — " + self.wxstring(req, "a1"),
            3: "3 — " + self.wxstring(req, "a2"),
            4: "4 — " + self.wxstring(req, "a3"),
        }
        reverse_score_dict = {
            None: None,
            4: "4 — " + self.wxstring(req, "a0"),
            3: "3 — " + self.wxstring(req, "a1"),
            2: "2 — " + self.wxstring(req, "a2"),
            1: "1 — " + self.wxstring(req, "a3"),
        }
        reverse_q_nums = {3, 6, 8, 9, 10, 13, 14, 15, 16, 17, 18, 20}
        fullscale_range = f"[{self.MIN_SCORE}–{self.MAX_SCORE}]"
        subscale_range = f"[{self.MIN_SUBSCALE}–{self.MAX_SUBSCALE}]"

        rows = ""
        for q_num in range(1, self.N_QUESTIONS + 1):
            q_field = "q" + str(q_num)
            question_cell = "{}. {}".format(q_num, self.wxstring(req, q_field))

            score = getattr(self, q_field)
            score_dict = normal_score_dict

            if q_num in reverse_q_nums:
                score_dict = reverse_score_dict

            answer_cell = get_from_dict(score_dict, score)

            rows += tr_qa(question_cell, answer_cell)

        html = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {negative_urgency_score}
                    {lack_of_perseverance_score}
                    {lack_of_premeditation_score}
                    {sensation_seeking_score}
                    {positive_urgency_score}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="40%">Score</th>
                </tr>
                {rows}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Sum for questions 1–20.
                [2] Sum for questions 6, 8, 13, 15.
                [3] Sum for questions 1, 4, 7, 11.
                [4] Sum for questions 2, 5, 12, 19.
                [5] Sum for questions 9, 14, 16, 18.
                [6] Sum for questions 3, 10, 17, 20.
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.sstring(SS.TOTAL_SCORE) + " <sup>[1]</sup>",
                f"{answer(self.total_score())} {fullscale_range}",
            ),
            negative_urgency_score=tr(
                self.wxstring(req, "negative_urgency") + " <sup>[2]</sup>",
                f"{answer(self.negative_urgency_score())} {subscale_range}",
            ),
            lack_of_perseverance_score=tr(
                self.wxstring(req, "lack_of_perseverance") + " <sup>[3]</sup>",
                f"{answer(self.lack_of_perseverance_score())} {subscale_range}",  # noqa: E501
            ),
            lack_of_premeditation_score=tr(
                self.wxstring(req, "lack_of_premeditation") +
                " <sup>[4]</sup>",
                f"{answer(self.lack_of_premeditation_score())} {subscale_range}",  # noqa: E501
            ),
            sensation_seeking_score=tr(
                self.wxstring(req, "sensation_seeking") + " <sup>[5]</sup>",
                f"{answer(self.sensation_seeking_score())} {subscale_range}",
            ),
            positive_urgency_score=tr(
                self.wxstring(req, "positive_urgency") + " <sup>[6]</sup>",
                f"{answer(self.positive_urgency_score())} {subscale_range}",
            ),
            rows=rows,
        )
        return html
Exemplo n.º 21
0
 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
     codes = [
         SnomedExpression(
             req.snomed(SnomedLookup.HONOSWA_PROCEDURE_ASSESSMENT))
     ]  # noqa
     if self.is_complete():
         codes.append(
             SnomedExpression(
                 req.snomed(SnomedLookup.HONOSWA_SCALE),
                 {
                     req.snomed(SnomedLookup.HONOSWA_SCORE):
                     self.total_score(),
                     req.snomed(SnomedLookup.HONOSWA_1_OVERACTIVE_SCORE):
                     self.q1,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_2_SELFINJURY_SCORE):
                     self.q2,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_3_SUBSTANCE_SCORE):
                     self.q3,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_4_COGNITIVE_SCORE):
                     self.q4,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_5_PHYSICAL_SCORE):
                     self.q5,
                     req.snomed(SnomedLookup.HONOSWA_6_PSYCHOSIS_SCORE):
                     self.q6,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_7_DEPRESSION_SCORE):
                     self.q7,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_8_OTHERMENTAL_SCORE):
                     self.q8,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_9_RELATIONSHIPS_SCORE):
                     self.q9,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_10_ADL_SCORE):
                     self.q10,
                     req.snomed(SnomedLookup.HONOSWA_11_LIVINGCONDITIONS_SCORE):
                     self.q11,  # noqa
                     req.snomed(SnomedLookup.HONOSWA_12_OCCUPATION_SCORE):
                     self.q12,  # noqa
                 }))
     return codes
Exemplo n.º 22
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        main_dict = {
            None: None,
            0: "0 — " + self.wxstring(req, "a0"),
            1: "1 — " + self.wxstring(req, "a1"),
            2: "2 — " + self.wxstring(req, "a2"),
            3: "3 — " + self.wxstring(req, "a3")
        }
        q10_dict = {
            None: None,
            0: "0 — " + self.wxstring(req, "fa0"),
            1: "1 — " + self.wxstring(req, "fa1"),
            2: "2 — " + self.wxstring(req, "fa2"),
            3: "3 — " + self.wxstring(req, "fa3")
        }
        q_a = ""
        for i in range(1, self.N_MAIN_QUESTIONS + 1):
            nstr = str(i)
            q_a += tr_qa(self.wxstring(req, "q" + nstr),
                         get_from_dict(main_dict, getattr(self, "q" + nstr)))
        q_a += tr_qa("10. " + self.wxstring(req, "finalq"),
                     get_from_dict(q10_dict, self.q10))

        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {depression_severity}
                    {n_symptoms}
                    {mds}
                    {ods}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                Ratings are over the last 2 weeks.
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="40%">Answer</th>
                </tr>
                {q_a}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Sum for questions 1–9.
                [2] Total score ≥20 severe, ≥15 moderately severe,
                    ≥10 moderate, ≥5 mild, &lt;5 none.
                [3] Number of questions 1–2 rated ≥2.
                [4] Number of questions 3–8 rated ≥2, or question 9
                    rated ≥1.
                [5] ≥1 core symptom and ≥5 total symptoms (as per
                    DSM-IV-TR page 356).
                [6] ≥1 core symptom and 2–4 total symptoms (as per
                    DSM-IV-TR page 775).
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.wappstring("total_score") + " <sup>[1]</sup>",
                answer(self.total_score()) +
                " / {}".format(self.MAX_SCORE_MAIN)),
            depression_severity=tr_qa(
                self.wxstring(req, "depression_severity") + " <sup>[2]</sup>",
                self.severity(req)),
            n_symptoms=tr(
                "Number of symptoms: core <sup>[3]</sup>, other "
                "<sup>[4]</sup>, total",
                answer(self.n_core()) + "/2, " + answer(self.n_other()) +
                "/7, " + answer(self.n_total()) + "/9"),
            mds=tr_qa(
                self.wxstring(req, "mds") + " <sup>[5]</sup>",
                get_yes_no(req, self.is_mds())),
            ods=tr_qa(
                self.wxstring(req, "ods") + " <sup>[6]</sup>",
                get_yes_no(req, self.is_ods())),
            q_a=q_a,
        )
        return h
Exemplo n.º 23
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        section_a = ""
        for i in range(1, 13 + 1):
            section_a += tr_qa(
                self.get_q(req, i),
                self.get_answer(req, i, getattr(self, "q" + str(i))))
        section_b = ""
        for i in range(14, self.NQUESTIONS + 1):
            section_b += tr_qa(
                self.get_q(req, i),
                self.get_answer(req, i, getattr(self, "q" + str(i))))

        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                    {section_a_total}
                    {section_b_total}
                </table>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="50%">Question</th>
                    <th width="50%">Answer <sup>[1]</sup></th>
                </tr>
                {period_rated}
                {section_a_subhead}
                {section_a}
                {section_b_subhead}
                {section_b}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                {FOOTNOTE_SCORING}
            </div>
            {copyright_div}
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.wappstring("total_score"),
                answer(self.total_score()) + " / {}".format(self.MAX_SCORE)),
            section_a_total=tr(
                self.wxstring(req, "section_a_total"),
                answer(self.section_a_score()) +
                " / {}".format(self.MAX_SECTION_A)),
            section_b_total=tr(
                self.wxstring(req, "section_b_total"),
                answer(self.section_b_score()) +
                " / {}".format(self.MAX_SECTION_B)),
            period_rated=tr_qa(self.wxstring(req, "period_rated"),
                               self.period_rated),
            section_a_subhead=subheading_spanning_two_columns(
                self.wxstring(req, "section_a_title")),
            section_a=section_a,
            section_b_subhead=subheading_spanning_two_columns(
                self.wxstring(req, "section_b_title")),
            section_b=section_b,
            FOOTNOTE_SCORING=FOOTNOTE_SCORING,
            copyright_div=self.COPYRIGHT_DIV,
        )
        return h
Exemplo n.º 24
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        main_dict = {
            None: None,
            0: "0 — " + self.wxstring(req, "old_option0"),
            1: "1 — " + self.wxstring(req, "old_option1"),
            2: "2 — " + self.wxstring(req, "old_option2"),
            3: "3 — " + self.wxstring(req, "old_option3"),
            4: "4 — " + self.wxstring(req, "old_option4"),
            5: "5 — " + self.wxstring(req, "old_option5"),
            6: "6 — " + self.wxstring(req, "old_option6"),
            7: "7 — " + self.wxstring(req, "old_option7")
        }
        q19_dict = {
            None: None,
            1: self.wxstring(req, "q19_option1"),
            2: self.wxstring(req, "q19_option2"),
            3: self.wxstring(req, "q19_option3"),
            4: self.wxstring(req, "q19_option4"),
            5: self.wxstring(req, "q19_option5"),
            6: self.wxstring(req, "q19_option6"),
            7: self.wxstring(req, "q19_option7")
        }
        q20_dict = {
            None: None,
            0: self.wxstring(req, "q20_option0"),
            1: self.wxstring(req, "q20_option1"),
            2: self.wxstring(req, "q20_option2"),
            3: self.wxstring(req, "q20_option3"),
            4: self.wxstring(req, "q20_option4"),
            5: self.wxstring(req, "q20_option5"),
            6: self.wxstring(req, "q20_option6"),
            7: self.wxstring(req, "q20_option7")
        }

        q_a = ""
        for i in range(1, Bprs.NQUESTIONS - 1):  # only does 1-18
            q_a += tr_qa(self.wxstring(req, "q" + str(i) + "_title"),
                         get_from_dict(main_dict, getattr(self, "q" + str(i))))
        q_a += tr_qa(self.wxstring(req, "q19_title"),
                     get_from_dict(q19_dict, self.q19))
        q_a += tr_qa(self.wxstring(req, "q20_title"),
                     get_from_dict(q20_dict, self.q20))

        h = """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
                    {tr_is_complete}
                    {total_score}
                </table>
            </div>
            <div class="{CssClass.EXPLANATION}">
                Ratings pertain to the past week, or behaviour during
                interview. Each question has specific answer definitions (see
                e.g. tablet app).
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="60%">Question</th>
                    <th width="40%">Answer <sup>[2]</sup></th>
                </tr>
                {q_a}
            </table>
            <div class="{CssClass.FOOTNOTES}">
                [1] Only questions 1–18 are scored.
                [2] All answers are in the range 1–7, or 0 (not assessed, for
                    some).
            </div>
        """.format(
            CssClass=CssClass,
            tr_is_complete=self.get_is_complete_tr(req),
            total_score=tr(
                req.wappstring("total_score") +
                " (0–{maxscore}; 18–{maxscore} if all rated) "
                "<sup>[1]</sup>".format(maxscore=self.MAX_SCORE),
                answer(self.total_score())),
            q_a=q_a,
        )
        return h
Exemplo n.º 25
0
    def get_task_html(self, req: CamcopsRequest) -> str:
        h = self.get_standard_clinician_comments_block(req, self.comments)
        h += """
            <div class="{CssClass.SUMMARY}">
                <table class="{CssClass.SUMMARY}">
        """.format(CssClass=CssClass, )
        h += self.get_is_complete_tr(req)
        h += tr_qa(
            req.wappstring("date_pertains_to"),
            format_datetime(self.date_pertains_to,
                            DateFormat.LONG_DATE,
                            default=None))
        h += tr_qa(self.wxstring(req, "meets_general_criteria"),
                   get_yes_no_none(req, self.has_pd()))
        h += tr_qa(self.wxstring(req, "paranoid_pd_title"),
                   get_yes_no_none(req, self.has_paranoid_pd()))
        h += tr_qa(self.wxstring(req, "schizoid_pd_title"),
                   get_yes_no_none(req, self.has_schizoid_pd()))
        h += tr_qa(self.wxstring(req, "dissocial_pd_title"),
                   get_yes_no_none(req, self.has_dissocial_pd()))
        h += tr_qa(self.wxstring(req, "eu_pd_i_title"),
                   get_yes_no_none(req, self.has_eupd_i()))
        h += tr_qa(self.wxstring(req, "eu_pd_b_title"),
                   get_yes_no_none(req, self.has_eupd_b()))
        h += tr_qa(self.wxstring(req, "histrionic_pd_title"),
                   get_yes_no_none(req, self.has_histrionic_pd()))
        h += tr_qa(self.wxstring(req, "anankastic_pd_title"),
                   get_yes_no_none(req, self.has_anankastic_pd()))
        h += tr_qa(self.wxstring(req, "anxious_pd_title"),
                   get_yes_no_none(req, self.has_anxious_pd()))
        h += tr_qa(self.wxstring(req, "dependent_pd_title"),
                   get_yes_no_none(req, self.has_dependent_pd()))

        h += """
                </table>
            </div>
            <div>
                <p><i>Vignette:</i></p>
                <p>{vignette}</p>
            </div>
            <table class="{CssClass.TASKDETAIL}">
                <tr>
                    <th width="80%">Question</th>
                    <th width="20%">Answer</th>
                </tr>
        """.format(
            CssClass=CssClass,
            vignette=answer(ws.webify(self.vignette),
                            default_for_blank_strings=True),
        )

        # General
        h += subheading_spanning_two_columns(self.wxstring(req, "general"))
        h += self.get_twocol_bool_row_true_false(req, "g1",
                                                 self.wxstring(req, "G1"))
        h += self.pd_b_text(req, "G1b")
        for i in range(1, Icd10SpecPD.N_GENERAL_1 + 1):
            h += self.get_twocol_bool_row_true_false(
                req, "g1_" + str(i), self.wxstring(req, "G1_" + str(i)))
        for i in range(2, Icd10SpecPD.N_GENERAL + 1):
            h += self.get_twocol_bool_row_true_false(
                req, "g" + str(i), self.wxstring(req, "G" + str(i)))

        # Paranoid, etc.
        h += self.standard_pd_html(req, "paranoid", Icd10SpecPD.N_PARANOID)
        h += self.standard_pd_html(req, "schizoid", Icd10SpecPD.N_SCHIZOID)
        h += self.standard_pd_html(req, "dissocial", Icd10SpecPD.N_DISSOCIAL)

        # EUPD is special
        h += self.pd_heading(req, "eu_pd_title")
        h += self.pd_skiprow(req, "eu")
        h += self.pd_general_criteria_bits(req)
        h += self.pd_subheading(req, "eu_pd_i_title")
        h += self.pd_b_text(req, "eu_pd_i_B")
        for i in range(1, Icd10SpecPD.N_EUPD_I + 1):
            h += self.pd_basic_row(req, "eu", i)
        h += self.pd_subheading(req, "eu_pd_b_title")
        h += self.pd_b_text(req, "eu_pd_b_B")
        for i in range(Icd10SpecPD.N_EUPD_I + 1, Icd10SpecPD.N_EU + 1):
            h += self.pd_basic_row(req, "eu", i)

        # Back to plain ones
        h += self.standard_pd_html(req, "histrionic", Icd10SpecPD.N_HISTRIONIC)
        h += self.standard_pd_html(req, "anankastic", Icd10SpecPD.N_ANANKASTIC)
        h += self.standard_pd_html(req, "anxious", Icd10SpecPD.N_ANXIOUS)
        h += self.standard_pd_html(req, "dependent", Icd10SpecPD.N_DEPENDENT)

        # Done
        h += """
            </table>
        """ + ICD10_COPYRIGHT_DIV
        return h
Exemplo n.º 26
0
 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
     codes = [SnomedExpression(req.snomed(SnomedLookup.BPRS1962_SCALE))]
     return codes
Exemplo n.º 27
0
def get_diagnosis_inc_exc_report_query(
    req: CamcopsRequest,
    diagnosis_class: Type[DiagnosisBase],
    item_class: Type[DiagnosisItemBase],
    item_fk_fieldname: str,
    system: str,
    which_idnum: int,
    inclusion_dx: List[str],
    exclusion_dx: List[str],
    age_minimum_y: int,
    age_maximum_y: int,
) -> SelectBase:
    """
    As for get_diagnosis_report_query, but this makes some modifications to
    do inclusion and exclusion criteria.

    - We need a linking number to perform exclusion criteria.
    - Therefore, we use a single ID number, which must not be NULL.
    """
    # The basics:
    desc = req.get_id_desc(which_idnum) or "BAD_IDNUM"
    # noinspection PyUnresolvedReferences
    select_fields = [
        Patient.surname.label("surname"),
        Patient.forename.label("forename"),
        Patient.dob.label("dob"),
        Patient.sex.label("sex"),
        PatientIdNum.idnum_value.label(desc),
        diagnosis_class.when_created.label("when_created"),
        literal(system).label("system"),
        item_class.code.label("code"),
        item_class.description.label("description"),
    ]
    # noinspection PyUnresolvedReferences
    select_from = (
        Patient.__table__.join(
            diagnosis_class.__table__,
            and_(
                diagnosis_class.patient_id == Patient.id,
                diagnosis_class._device_id == Patient._device_id,
                diagnosis_class._era == Patient._era,
                diagnosis_class._current == True,  # noqa: E712
            ),
        ).join(
            item_class.__table__,
            and_(
                getattr(item_class, item_fk_fieldname) == diagnosis_class.id,
                item_class._device_id == diagnosis_class._device_id,
                item_class._era == diagnosis_class._era,
                item_class._current == True,
            ),
        ).join(
            PatientIdNum.__table__,
            and_(
                PatientIdNum.patient_id == Patient.id,
                PatientIdNum._device_id == Patient._device_id,
                PatientIdNum._era == Patient._era,
                PatientIdNum._current == True,
                PatientIdNum.which_idnum == which_idnum,
                PatientIdNum.idnum_value.isnot(None),  # NOT NULL
            ),
        ))
    wheres = [Patient._current == True]  # noqa: E712
    if not req.user.superuser:
        # Restrict to accessible groups
        group_ids = req.user.ids_of_groups_user_may_report_on
        wheres.append(diagnosis_class._group_id.in_(group_ids))
    else:
        group_ids = []  # type: List[int]  # to stop type-checker moaning below

    # Age limits are simple, as the same patient has the same age for
    # all diagnosis rows.
    today = req.today
    if age_maximum_y is not None:
        # Example: max age is 40; earliest (oldest) DOB is therefore 41
        # years ago plus one day (e.g. if it's 15 June 2010, then earliest
        # DOB is 16 June 1969; a person born then will be 41 tomorrow).
        earliest_dob = pendulum_date_to_datetime_date(
            today.subtract(years=age_maximum_y + 1).add(days=1))
        wheres.append(Patient.dob >= earliest_dob)
    if age_minimum_y is not None:
        # Example: min age is 20; latest (youngest) DOB is therefore 20
        # years ago (e.g. if it's 15 June 2010, latest DOB is 15 June 1990;
        # if you're born after that, you're not 20 yet).
        latest_dob = pendulum_date_to_datetime_date(
            today.subtract(years=age_minimum_y))
        wheres.append(Patient.dob <= latest_dob)

    # Diagnosis criteria are a little bit more complex.
    #
    # We can reasonably do inclusion criteria as "show the diagnoses
    # matching the inclusion criteria" (not the more complex "show all
    # diagnoses for patients having at least one inclusion diagnosis",
    # which is likely to be too verbose for patient finding).
    inclusion_criteria = []  # type: List[ColumnElement]
    for idx in inclusion_dx:
        inclusion_criteria.append(item_class.code.like(idx))
    wheres.append(or_(*inclusion_criteria))

    # Exclusion criteria are the trickier: we need to be able to link
    # multiple diagnoses for the same patient, so we need to use a linking
    # ID number.
    if exclusion_dx:
        # noinspection PyUnresolvedReferences
        edx_items = item_class.__table__.alias("edx_items")
        # noinspection PyUnresolvedReferences
        edx_sets = diagnosis_class.__table__.alias("edx_sets")
        # noinspection PyUnresolvedReferences
        edx_patient = Patient.__table__.alias("edx_patient")
        # noinspection PyUnresolvedReferences
        edx_idnum = PatientIdNum.__table__.alias("edx_idnum")
        edx_joined = (
            edx_items.join(
                edx_sets,
                and_(
                    getattr(edx_items.c, item_fk_fieldname) == edx_sets.c.id,
                    edx_items.c._device_id == edx_sets.c._device_id,
                    edx_items.c._era == edx_sets.c._era,
                    edx_items.c._current == True,  # noqa: E712
                ),
            ).join(
                edx_patient,
                and_(
                    edx_sets.c.patient_id == edx_patient.c.id,
                    edx_sets.c._device_id == edx_patient.c._device_id,
                    edx_sets.c._era == edx_patient.c._era,
                    edx_sets.c._current == True,  # noqa: E712
                ),
            ).join(
                edx_idnum,
                and_(
                    edx_idnum.c.patient_id == edx_patient.c.id,
                    edx_idnum.c._device_id == edx_patient.c._device_id,
                    edx_idnum.c._era == edx_patient.c._era,
                    edx_idnum.c._current == True,  # noqa: E712
                    edx_idnum.c.which_idnum == which_idnum,
                ),
            ))
        exclusion_criteria = []  # type: List[ColumnElement]
        for edx in exclusion_dx:
            exclusion_criteria.append(edx_items.c.code.like(edx))
        edx_wheres = [
            edx_items.c._current == True,  # noqa: E712
            edx_idnum.c.idnum_value == PatientIdNum.idnum_value,
            or_(*exclusion_criteria),
        ]
        # Note the join above between the main and the EXISTS clauses.
        # We don't use an alias for the main copy of the PatientIdNum table,
        # and we do for the EXISTS version. This is fine; e.g.
        # https://msdn.microsoft.com/en-us/library/ethytz2x.aspx example:
        #   SELECT boss.name, employee.name
        #   FROM employee
        #   INNER JOIN employee boss ON employee.manager_id = boss.emp_id;
        if not req.user.superuser:
            # Restrict to accessible groups
            # group_ids already defined from above
            edx_wheres.append(edx_sets.c._group_id.in_(group_ids))
            # ... bugfix 2018-06-19: "wheres" -> "edx_wheres"
        exclusion_select = (select(["*"]).select_from(edx_joined).where(
            and_(*edx_wheres)))
        wheres.append(not_(exists(exclusion_select)))

    query = select(select_fields).select_from(select_from).where(and_(*wheres))
    return query
Exemplo n.º 28
0
 def category(self, req: CamcopsRequest) -> str:
     totalscore = self.total_score()
     return (req.sstring(SS.NORMAL)
             if totalscore >= 26 else req.sstring(SS.ABNORMAL))
Exemplo n.º 29
0
 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
     if not self.is_complete():
         return []
     return [SnomedExpression(req.snomed(SnomedLookup.SMAST_SCALE))]
Exemplo n.º 30
0
 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:
     codes = [
         SnomedExpression(
             req.snomed(SnomedLookup.PSYCHIATRIC_ASSESSMENT_PROCEDURE))
     ]  # noqa
     return codes