Coverage for tasks/honos.py: 51%

1"""

2 camcops_server/tasks/honos.py

3

4===============================================================================

5

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

8

9 This file is part of CamCOPS.

10

11 CamCOPS is free software: you can redistribute it and/or modify

12 it under the terms of the GNU General Public License as published by

13 the Free Software Foundation, either version 3 of the License, or

14 (at your option) any later version.

15

16 CamCOPS is distributed in the hope that it will be useful,

17 but WITHOUT ANY WARRANTY; without even the implied warranty of

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

19 GNU General Public License for more details.

20

21 You should have received a copy of the GNU General Public License

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.

23

24===============================================================================

25

26"""

27

28 from typing import Any, List, Optional, Type

29

30 from cardinal_pythonlib.stringfunc import strseq

31 from sqlalchemy.orm import Mapped, mapped_column

32 from sqlalchemy.sql.sqltypes import Integer, UnicodeText

33

34 from camcops_server.cc_modules.cc_constants import CssClass

35 from camcops_server.cc_modules.cc_ctvinfo import CTV_INCOMPLETE, CtvInfo

36 from camcops_server.cc_modules.cc_db import add_multiple_columns

37 from camcops_server.cc_modules.cc_html import (

38 answer,

39 subheading_spanning_two_columns,

40 tr,

41 tr_qa,

42)

43 from camcops_server.cc_modules.cc_request import CamcopsRequest

44 from camcops_server.cc_modules.cc_snomed import SnomedExpression, SnomedLookup

45 from camcops_server.cc_modules.cc_sqla_coltypes import (

46 mapped_camcops_column,

47 CharColType,

48 PermittedValueChecker,

49)

50 from camcops_server.cc_modules.cc_summaryelement import SummaryElement

51 from camcops_server.cc_modules.cc_task import (

52 get_from_dict,

53 Task,

54 TaskHasClinicianMixin,

55 TaskHasPatientMixin,

56)

57 from camcops_server.cc_modules.cc_text import SS

58 from camcops_server.cc_modules.cc_trackerhelpers import TrackerInfo

59

60

61 PV_MAIN = [0, 1, 2, 3, 4, 9]

62 PV_PROBLEMTYPE = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]

63

64 FOOTNOTE_SCORING = """

65 [1] 0 = no problem;

66 1 = minor problem requiring no action;

67 2 = mild problem but definitely present;

68 3 = moderately severe problem;

69 4 = severe to very severe problem;

70 9 = not known.

71"""

72

73

74# =============================================================================

75# HoNOS abstract base class

76# =============================================================================

77

78

79# noinspection PyAbstractClass

80 class HonosBase(TaskHasPatientMixin, TaskHasClinicianMixin, Task): # type: ignore[misc] # noqa: E501

81 __abstract__ = True

82 provides_trackers = True

83

84 period_rated: Mapped[Optional[str]] = mapped_column(

85 UnicodeText, comment="Period being rated"

86 )

87

88 COPYRIGHT_DIV = f"""

89 <div class="{CssClass.COPYRIGHT}">

90 Health of the Nation Outcome Scales:

91 Copyright © Royal College of Psychiatrists.

92 Used here with permission.

93 </div>

94 """

95

96 QFIELDS = None # type: List[str] # must be overridden

97 MAX_SCORE = None # type: int # must be overridden

98

99 def get_trackers(self, req: CamcopsRequest) -> List[TrackerInfo]:

100 return [

101 TrackerInfo(

102 value=self.total_score(),

103 plot_label=f"{self.shortname} total score",

104 axis_label=f"Total score (out of {self.MAX_SCORE})",

105 axis_min=-0.5,

106 axis_max=self.MAX_SCORE + 0.5,

107 )

108 ]

109

110 def get_clinical_text(self, req: CamcopsRequest) -> List[CtvInfo]:

111 if not self.is_complete():

112 return CTV_INCOMPLETE

113 return [

114 CtvInfo(

115 content=(

116 f"{self.shortname} total score "

117 f"{self.total_score()}/{self.MAX_SCORE}"

118 )

119 )

120 ]

121

122 def get_summaries(self, req: CamcopsRequest) -> List[SummaryElement]:

123 return self.standard_task_summary_fields() + [

124 SummaryElement(

125 name="total",

126 coltype=Integer(),

127 value=self.total_score(),

128 comment=f"Total score (/{self.MAX_SCORE})",

129 )

130 ]

131

132 def _total_score_for_fields(self, fieldnames: List[str]) -> int:

133 total = 0

134 for qname in fieldnames:

135 value = getattr(self, qname)

136 if value is not None and 0 <= value <= 4:

137 # i.e. ignore null values and 9 (= not known)

138 total += value

139 return total

140

141 def total_score(self) -> int:

142 return self._total_score_for_fields(self.QFIELDS)

143

144 def get_q(self, req: CamcopsRequest, q: int) -> str:

145 return self.wxstring(req, "q" + str(q) + "_s")

146

147 def get_answer(self, req: CamcopsRequest, q: int, a: int) -> Optional[str]:

148 if a == 9:

149 return self.wxstring(req, "option9")

150 if a is None or a < 0 or a > 4:

151 return None

152 return self.wxstring(req, "q" + str(q) + "_option" + str(a))

153

154

155# =============================================================================

156# HoNOS

157# =============================================================================

158

159

160 class Honos(

161 HonosBase,

162):

163 """

164 Server implementation of the HoNOS task.

165 """

166

167 __tablename__ = "honos"

168 shortname = "HoNOS"

169 info_filename_stem = "honos"

170

171 @classmethod

172 def extend_columns(cls: Type["Honos"], **kwargs: Any) -> None:

173 add_multiple_columns(

174 cls,

175 "q",

176 1,

177 cls.NQUESTIONS,

178 pv=PV_MAIN,

179 comment_fmt="Q{n}, {s} (0-4, higher worse)",

180 comment_strings=[

181 "overactive/aggressive/disruptive/agitated",

182 "deliberate self-harm",

183 "problem-drinking/drug-taking",

184 "cognitive problems",

185 "physical illness/disability",

186 "hallucinations/delusions",

187 "depressed mood",

188 "other mental/behavioural problem",

189 "relationship problems",

190 "activities of daily living",

191 "problems with living conditions",

192 "occupation/activities",

193 ],

194 )

195

196 q8problemtype: Mapped[Optional[str]] = mapped_camcops_column(

197 CharColType,

198 permitted_value_checker=PermittedValueChecker(

199 permitted_values=PV_PROBLEMTYPE

200 ),

201 comment="Q8: type of problem (A phobic; B anxiety; "

202 "C obsessive-compulsive; D mental strain/tension; "

203 "E dissociative; F somatoform; G eating; H sleep; "

204 "I sexual; J other, specify)",

205 )

206 q8otherproblem: Mapped[Optional[str]] = mapped_column(

207 UnicodeText, comment="Q8: other problem: specify"

208 )

209

210 NQUESTIONS = 12

211 QFIELDS = strseq("q", 1, NQUESTIONS)

212 MAX_SCORE = 48

213

214 @staticmethod

215 def longname(req: "CamcopsRequest") -> str:

216 _ = req.gettext

217 return _("Health of the Nation Outcome Scales, working age adults")

218

219 # noinspection PyUnresolvedReferences

220 def is_complete(self) -> bool:

221 if self.any_fields_none(self.QFIELDS):

222 return False

223 if not self.field_contents_valid():

224 return False

225 if self.q8 != 0 and self.q8 != 9 and self.q8problemtype is None: # type: ignore[attr-defined] # noqa: E501

226 return False

227 if (

228 self.q8 != 0 # type: ignore[attr-defined]

229 and self.q8 != 9 # type: ignore[attr-defined]

230 and self.q8problemtype == "J"

231 and self.q8otherproblem is None

232 ):

233 return False

234 return self.period_rated is not None

235

236 def get_task_html(self, req: CamcopsRequest) -> str:

237 q8_problem_type_dict = {

238 None: None,

239 "A": self.wxstring(req, "q8problemtype_option_a"),

240 "B": self.wxstring(req, "q8problemtype_option_b"),

241 "C": self.wxstring(req, "q8problemtype_option_c"),

242 "D": self.wxstring(req, "q8problemtype_option_d"),

243 "E": self.wxstring(req, "q8problemtype_option_e"),

244 "F": self.wxstring(req, "q8problemtype_option_f"),

245 "G": self.wxstring(req, "q8problemtype_option_g"),

246 "H": self.wxstring(req, "q8problemtype_option_h"),

247 "I": self.wxstring(req, "q8problemtype_option_i"),

248 "J": self.wxstring(req, "q8problemtype_option_j"),

249 }

250 one_to_eight = ""

251 for i in range(1, 8 + 1):

252 one_to_eight += tr_qa(

253 self.get_q(req, i),

254 self.get_answer(req, i, getattr(self, "q" + str(i))),

255 )

256 nine_onwards = ""

257 for i in range(9, self.NQUESTIONS + 1):

258 nine_onwards += tr_qa(

259 self.get_q(req, i),

260 self.get_answer(req, i, getattr(self, "q" + str(i))),

261 )

262

263 h = """

264 <div class="{CssClass.SUMMARY}">

265 <table class="{CssClass.SUMMARY}">

266 {tr_is_complete}

267 {total_score}

268 </table>

269 </div>

270 <table class="{CssClass.TASKDETAIL}">

271 <tr>

272 <th width="50%">Question</th>

273 <th width="50%">Answer <sup>[1]</sup></th>

274 </tr>

275 {period_rated}

276 {one_to_eight}

277 {q8problemtype}

278 {q8otherproblem}

279 {nine_onwards}

280 </table>

281 <div class="{CssClass.FOOTNOTES}">

282 {FOOTNOTE_SCORING}

283 </div>

284 {copyright_div}

285 """.format(

286 CssClass=CssClass,

287 tr_is_complete=self.get_is_complete_tr(req),

288 total_score=tr(

289 req.sstring(SS.TOTAL_SCORE),

290 answer(self.total_score()) + f" / {self.MAX_SCORE}",

291 ),

292 period_rated=tr_qa(

293 self.wxstring(req, "period_rated"), self.period_rated

294 ),

295 one_to_eight=one_to_eight,

296 q8problemtype=tr_qa(

297 self.wxstring(req, "q8problemtype_s"),

298 get_from_dict(q8_problem_type_dict, self.q8problemtype),

299 ),

300 q8otherproblem=tr_qa(

301 self.wxstring(req, "q8otherproblem_s"), self.q8otherproblem

302 ),

303 nine_onwards=nine_onwards,

304 FOOTNOTE_SCORING=FOOTNOTE_SCORING,

305 copyright_div=self.COPYRIGHT_DIV,

306 )

307 return h

308

309 # noinspection PyUnresolvedReferences

310 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:

311 codes = [

312 SnomedExpression(

313 req.snomed(SnomedLookup.HONOSWA_PROCEDURE_ASSESSMENT)

314 )

315 ]

316 if self.is_complete():

317 codes.append(

318 SnomedExpression(

319 req.snomed(SnomedLookup.HONOSWA_SCALE),

320 {

321 req.snomed(

322 SnomedLookup.HONOSWA_SCORE

323 ): self.total_score(),

324 req.snomed(

325 SnomedLookup.HONOSWA_1_OVERACTIVE_SCORE

326 ): self.q1, # type: ignore[attr-defined]

327 req.snomed(

328 SnomedLookup.HONOSWA_2_SELFINJURY_SCORE

329 ): self.q2, # type: ignore[attr-defined]

330 req.snomed(

331 SnomedLookup.HONOSWA_3_SUBSTANCE_SCORE

332 ): self.q3, # type: ignore[attr-defined]

333 req.snomed(

334 SnomedLookup.HONOSWA_4_COGNITIVE_SCORE

335 ): self.q4, # type: ignore[attr-defined]

336 req.snomed(

337 SnomedLookup.HONOSWA_5_PHYSICAL_SCORE

338 ): self.q5, # type: ignore[attr-defined]

339 req.snomed(

340 SnomedLookup.HONOSWA_6_PSYCHOSIS_SCORE

341 ): self.q6, # type: ignore[attr-defined]

342 req.snomed(

343 SnomedLookup.HONOSWA_7_DEPRESSION_SCORE

344 ): self.q7, # type: ignore[attr-defined]

345 req.snomed(

346 SnomedLookup.HONOSWA_8_OTHERMENTAL_SCORE

347 ): self.q8, # type: ignore[attr-defined]

348 req.snomed(

349 SnomedLookup.HONOSWA_9_RELATIONSHIPS_SCORE

350 ): self.q9, # type: ignore[attr-defined]

351 req.snomed(

352 SnomedLookup.HONOSWA_10_ADL_SCORE

353 ): self.q10, # type: ignore[attr-defined]

354 req.snomed(

355 SnomedLookup.HONOSWA_11_LIVINGCONDITIONS_SCORE

356 ): self.q11, # type: ignore[attr-defined]

357 req.snomed(

358 SnomedLookup.HONOSWA_12_OCCUPATION_SCORE

359 ): self.q12, # type: ignore[attr-defined]

360 },

361 )

362 )

363 return codes

364

365

366# =============================================================================

367# HoNOS 65+

368# =============================================================================

369

370

371 class Honos65(

372 HonosBase,

373):

374 """

375 Server implementation of the HoNOS 65+ task.

376 """

377

378 __tablename__ = "honos65"

379 shortname = "HoNOS 65+"

380 info_filename_stem = "honos"

381

382 @classmethod

383 def extend_columns(cls: Type["Honos65"], **kwargs: Any) -> None:

384 add_multiple_columns(

385 cls,

386 "q",

387 1,

388 cls.NQUESTIONS,

389 pv=PV_MAIN,

390 comment_fmt="Q{n}, {s} (0-4, higher worse)",

391 comment_strings=[ # not exactly identical to HoNOS

392 "behavioural disturbance",

393 "deliberate self-harm",

394 "problem drinking/drug-taking",

395 "cognitive problems",

396 "physical illness/disability",

397 "hallucinations/delusions",

398 "depressive symptoms",

399 "other mental/behavioural problem",

400 "relationship problems",

401 "activities of daily living",

402 "living conditions",

403 "occupation/activities",

404 ],

405 )

406

407 q8problemtype: Mapped[Optional[str]] = mapped_camcops_column(

408 CharColType,

409 permitted_value_checker=PermittedValueChecker(

410 permitted_values=PV_PROBLEMTYPE

411 ),

412 comment="Q8: type of problem (A phobic; B anxiety; "

413 "C obsessive-compulsive; D stress; " # NB slight difference: D

414 "E dissociative; F somatoform; G eating; H sleep; "

415 "I sexual; J other, specify)",

416 )

417 q8otherproblem: Mapped[Optional[str]] = mapped_column(

418 UnicodeText, comment="Q8: other problem: specify"

419 )

420

421 NQUESTIONS = 12

422 QFIELDS = strseq("q", 1, NQUESTIONS)

423 MAX_SCORE = 48

424

425 @staticmethod

426 def longname(req: "CamcopsRequest") -> str:

427 _ = req.gettext

428 return _("Health of the Nation Outcome Scales, older adults")

429

430 # noinspection PyUnresolvedReferences

431 def is_complete(self) -> bool:

432 if self.any_fields_none(self.QFIELDS):

433 return False

434 if not self.field_contents_valid():

435 return False

436 if self.q8 != 0 and self.q8 != 9 and self.q8problemtype is None: # type: ignore[attr-defined] # noqa: E501

437 return False

438 if (

439 self.q8 != 0 # type: ignore[attr-defined]

440 and self.q8 != 9 # type: ignore[attr-defined]

441 and self.q8problemtype == "J"

442 and self.q8otherproblem is None

443 ):

444 return False

445 return self.period_rated is not None

446

447 def get_task_html(self, req: CamcopsRequest) -> str:

448 q8_problem_type_dict = {

449 None: None,

450 "A": self.wxstring(req, "q8problemtype_option_a"),

451 "B": self.wxstring(req, "q8problemtype_option_b"),

452 "C": self.wxstring(req, "q8problemtype_option_c"),

453 "D": self.wxstring(req, "q8problemtype_option_d"),

454 "E": self.wxstring(req, "q8problemtype_option_e"),

455 "F": self.wxstring(req, "q8problemtype_option_f"),

456 "G": self.wxstring(req, "q8problemtype_option_g"),

457 "H": self.wxstring(req, "q8problemtype_option_h"),

458 "I": self.wxstring(req, "q8problemtype_option_i"),

459 "J": self.wxstring(req, "q8problemtype_option_j"),

460 }

461 one_to_eight = ""

462 for i in range(1, 8 + 1):

463 one_to_eight += tr_qa(

464 self.get_q(req, i),

465 self.get_answer(req, i, getattr(self, "q" + str(i))),

466 )

467 nine_onwards = ""

468 for i in range(9, Honos.NQUESTIONS + 1):

469 nine_onwards += tr_qa(

470 self.get_q(req, i),

471 self.get_answer(req, i, getattr(self, "q" + str(i))),

472 )

473

474 h = """

475 <div class="{CssClass.SUMMARY}">

476 <table class="{CssClass.SUMMARY}">

477 {tr_is_complete}

478 {total_score}

479 </table>

480 </div>

481 <table class="{CssClass.TASKDETAIL}">

482 <tr>

483 <th width="50%">Question</th>

484 <th width="50%">Answer <sup>[1]</sup></th>

485 </tr>

486 {period_rated}

487 {one_to_eight}

488 {q8problemtype}

489 {q8otherproblem}

490 {nine_onwards}

491 </table>

492 <div class="{CssClass.FOOTNOTES}">

493 {FOOTNOTE_SCORING}

494 </div>

495 {copyright_div}

496 """.format(

497 CssClass=CssClass,

498 tr_is_complete=self.get_is_complete_tr(req),

499 total_score=tr(

500 req.sstring(SS.TOTAL_SCORE),

501 answer(self.total_score()) + f" / {self.MAX_SCORE}",

502 ),

503 period_rated=tr_qa(

504 self.wxstring(req, "period_rated"), self.period_rated

505 ),

506 one_to_eight=one_to_eight,

507 q8problemtype=tr_qa(

508 self.wxstring(req, "q8problemtype_s"),

509 get_from_dict(q8_problem_type_dict, self.q8problemtype),

510 ),

511 q8otherproblem=tr_qa(

512 self.wxstring(req, "q8otherproblem_s"), self.q8otherproblem

513 ),

514 nine_onwards=nine_onwards,

515 FOOTNOTE_SCORING=FOOTNOTE_SCORING,

516 copyright_div=self.COPYRIGHT_DIV,

517 )

518 return h

519

520 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:

521 codes = [

522 SnomedExpression(

523 req.snomed(SnomedLookup.HONOS65_PROCEDURE_ASSESSMENT)

524 )

525 ]

526 if self.is_complete():

527 codes.append(

528 SnomedExpression(

529 req.snomed(SnomedLookup.HONOS65_SCALE),

530 {

531 req.snomed(

532 SnomedLookup.HONOS65_SCORE

533 ): self.total_score()

534 },

535 )

536 )

537 return codes

538

539

540# =============================================================================

541# HoNOSCA

542# =============================================================================

543

544

545 class Honosca(

546 HonosBase,

547):

548 """

549 Server implementation of the HoNOSCA task.

550 """

551

552 __tablename__ = "honosca"

553 shortname = "HoNOSCA"

554 info_filename_stem = "honos"

555

556 NQUESTIONS = 15

557

558 @classmethod

559 def extend_columns(cls: Type["Honosca"], **kwargs: Any) -> None:

560 add_multiple_columns(

561 cls,

562 "q",

563 1,

564 cls.NQUESTIONS,

565 pv=PV_MAIN,

566 comment_fmt="Q{n}, {s} (0-4, higher worse)",

567 comment_strings=[

568 "disruptive/antisocial/aggressive",

569 "overactive/inattentive",

570 "self-harm",

571 "alcohol/drug misuse",

572 "scholastic/language problems",

573 "physical illness/disability",

574 "delusions/hallucinations",

575 "non-organic somatic symptoms",

576 "emotional symptoms",

577 "peer relationships",

578 "self-care and independence",

579 "family life/relationships",

580 "school attendance",

581 "problems with knowledge/understanding of child's problems",

582 "lack of information about services",

583 ],

584 )

585

586 QFIELDS = strseq("q", 1, NQUESTIONS)

587 LAST_SECTION_A_Q = 13

588 FIRST_SECTION_B_Q = 14

589 SECTION_A_QFIELDS = strseq("q", 1, LAST_SECTION_A_Q)

590 SECTION_B_QFIELDS = strseq("q", FIRST_SECTION_B_Q, NQUESTIONS)

591 MAX_SCORE = 60

592 MAX_SECTION_A = 4 * len(SECTION_A_QFIELDS)

593 MAX_SECTION_B = 4 * len(SECTION_B_QFIELDS)

594 TASK_FIELDS = QFIELDS + ["period_rated"]

595

596 @staticmethod

597 def longname(req: "CamcopsRequest") -> str:

598 _ = req.gettext

599 return _(

600 "Health of the Nation Outcome Scales, Children and Adolescents"

601 )

602

603 def is_complete(self) -> bool:

604 return (

605 self.all_fields_not_none(self.TASK_FIELDS)

606 and self.field_contents_valid()

607 )

608

609 def section_a_score(self) -> int:

610 return self._total_score_for_fields(self.SECTION_A_QFIELDS)

611

612 def section_b_score(self) -> int:

613 return self._total_score_for_fields(self.SECTION_B_QFIELDS)

614

615 def get_task_html(self, req: CamcopsRequest) -> str:

616 section_a = ""

617 for i in range(1, 13 + 1):

618 section_a += tr_qa(

619 self.get_q(req, i),

620 self.get_answer(req, i, getattr(self, "q" + str(i))),

621 )

622 section_b = ""

623 for i in range(14, self.NQUESTIONS + 1):

624 section_b += tr_qa(

625 self.get_q(req, i),

626 self.get_answer(req, i, getattr(self, "q" + str(i))),

627 )

628

629 h = """

630 <div class="{CssClass.SUMMARY}">

631 <table class="{CssClass.SUMMARY}">

632 {tr_is_complete}

633 {total_score}

634 {section_a_total}

635 {section_b_total}

636 </table>

637 </div>

638 <table class="{CssClass.TASKDETAIL}">

639 <tr>

640 <th width="50%">Question</th>

641 <th width="50%">Answer <sup>[1]</sup></th>

642 </tr>

643 {period_rated}

644 {section_a_subhead}

645 {section_a}

646 {section_b_subhead}

647 {section_b}

648 </table>

649 <div class="{CssClass.FOOTNOTES}">

650 {FOOTNOTE_SCORING}

651 </div>

652 {copyright_div}

653 """.format(

654 CssClass=CssClass,

655 tr_is_complete=self.get_is_complete_tr(req),

656 total_score=tr(

657 req.sstring(SS.TOTAL_SCORE),

658 answer(self.total_score()) + f" / {self.MAX_SCORE}",

659 ),

660 section_a_total=tr(

661 self.wxstring(req, "section_a_total"),

662 answer(self.section_a_score()) + f" / {self.MAX_SECTION_A}",

663 ),

664 section_b_total=tr(

665 self.wxstring(req, "section_b_total"),

666 answer(self.section_b_score()) + f" / {self.MAX_SECTION_B}",

667 ),

668 period_rated=tr_qa(

669 self.wxstring(req, "period_rated"), self.period_rated

670 ),

671 section_a_subhead=subheading_spanning_two_columns(

672 self.wxstring(req, "section_a_title")

673 ),

674 section_a=section_a,

675 section_b_subhead=subheading_spanning_two_columns(

676 self.wxstring(req, "section_b_title")

677 ),

678 section_b=section_b,

679 FOOTNOTE_SCORING=FOOTNOTE_SCORING,

680 copyright_div=self.COPYRIGHT_DIV,

681 )

682 return h

683

684 def get_snomed_codes(self, req: CamcopsRequest) -> List[SnomedExpression]:

685 codes = [

686 SnomedExpression(

687 req.snomed(SnomedLookup.HONOSCA_PROCEDURE_ASSESSMENT)

688 )

689 ]

690 if self.is_complete():

691 a = self.section_a_score()

692 b = self.section_b_score()

693 total = a + b

694 codes.append(

695 SnomedExpression(

696 req.snomed(SnomedLookup.HONOSCA_SCALE),

697 {

698 req.snomed(SnomedLookup.HONOSCA_SCORE): total,

699 req.snomed(SnomedLookup.HONOSCA_SECTION_A_SCORE): a,

700 req.snomed(SnomedLookup.HONOSCA_SECTION_B_SCORE): b,

701 req.snomed(

702 SnomedLookup.HONOSCA_SECTION_A_PLUS_B_SCORE

703 ): total,

704 },

705 )

706 )

707 return codes