예제 #1
0
 def get_html_table_rows(self) -> str:
     # noinspection PyTypeChecker
     return """
         <tr class="{CssClass.SUBHEADING}">
             <td>Photo {num}: <b>{description}</b></td>
         </tr>
         <tr><td>{photo}</td></tr>
     """.format(CssClass=CssClass,
                num=self.seqnum,
                description=ws.webify(self.description),
                photo=get_blob_img_html(self.photo))
예제 #2
0
 def get_task_html(self, req: CamcopsRequest) -> str:
     # noinspection PyTypeChecker
     return """
         <table class="{CssClass.TASKDETAIL}">
             <tr class="{CssClass.SUBHEADING}"><td>Description</td></tr>
             <tr><td>{description}</td></tr>
             <tr class="{CssClass.SUBHEADING}"><td>Photo</td></tr>
             <tr><td>{photo}</td></tr>
         </table>
     """.format(
         CssClass=CssClass,
         description=answer(ws.webify(self.description),
                            default="(No description)",
                            default_for_blank_strings=True),
         # ... xhtml2pdf crashes if the contents are empty...
         photo=get_blob_img_html(self.photo))
예제 #3
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 = (
                PlotDefaults.FULLWIDTH_PLOT_WIDTH / 3,
                PlotDefaults.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) +
            f"""
                <div class="{CssClass.SUMMARY}">
                    <table class="{CssClass.SUMMARY}">
                        <tr>
                            {self.get_is_complete_td_pair(req)}
                            <td class="{CssClass.FIGURE}"
                                rowspan="7">{figurehtml}</td>
                        </tr>
            """ + tr("Total ACE-III score <sup>[1]</sup>",
                     answer(t) + " / 100") +
            tr(
                "Attention",
                answer(a) + f" / {ATTN_MAX} ({percent(a, ATTN_MAX)}%)",
            ) + tr(
                "Memory",
                answer(m) + f" / {MEMORY_MAX} ({percent(m, MEMORY_MAX)}%)",
            ) + tr(
                "Fluency",
                answer(f) + f" / {FLUENCY_MAX} ({percent(f, FLUENCY_MAX)}%)",
            ) + tr(
                "Language",
                answer(lang) + f" / {LANG_MAX} ({percent(lang, LANG_MAX)}%)",
            ) + tr(
                "Visuospatial",
                answer(v) + f" / {VSP_MAX} ({percent(v, VSP_MAX)}%)",
            ) + f"""
                    </table>
                </div>
                <table class="{CssClass.TASKDETAIL}">
                    <tr>
                        <th width="75%">Question</th>
                        <th width="25%">Answer/score</td>
                    </tr>
            """ + 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) + f"""
                </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>
            """)
예제 #4
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
예제 #5
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