Coverage for denofo/questionnaire/questions.py: 86%

1 import warnings

2 from typing import Any

3 from denofo.utils.constants import FUNCS_TO_MODELS_DICT, GoQBack

4 from denofo.utils.helpers import different_answers, get_model_from_qstack_dict

5 from denofo.models import (

6 AnnotGenome,

7 DeNovoGeneAnnotation,

8 EvolutionaryInformation,

9 HomologyFilter,

10 TranslationalEvidence,

11 NonCodingHomologs,

12 PhylogeneticTaxa,

13 SyntenySearch,

14 TaxonID,

15 Transcriptome,

16)

17 from denofo.choices import (

18 AnchorChoices,

19 AnnotGenomeChoices,

20 GeneticContextChoices,

21 HomologyDBChoices,

22 InputDataChoices,

23 ORFChoices,

24 SeqTypeChoices,

25 TaxSpecificityChoices,

26 ThresholdChoices,

27 TranslationEvidenceChoices,

28)

29

30

31 class DeNovoQuestionnaire:

32 """

33 A class to handle the questionnaire for the de novo gene annotation.

34 Order of questions is defined here as well as the logic to call the next question

35 or go back to the previous question.

36

37 :param user_interface_funcs_dict: The user interface functions dictionary.

38 :type user_interface_funcs_dict: dict

39

40 :cvar question_stack: The question stack.

41 :vartype question_stack: list[tuple[callable, Any]]

42 :cvar current_idx: The current index in the question stack.

43 :vartype current_idx: int

44 :cvar interface_funcs_dict: The user interface functions dictionary.

45 :vartype interface_funcs_dict: dict

46

47 :func call_last_question: Call the previous question in the question stack.

48 :func call_next_question: Call the next question in the question stack.

49 :func q_end: End of the questionnaire.

50 :func start_questionnaire: Start the questionnaire.

51 """

52

53 def __init__(self, user_interface_funcs_dict: dict):

54 self.question_stack: list[tuple[callable, Any]] = []

55 self.current_idx: int = 0

56 self.interface_funcs_dict = user_interface_funcs_dict

57

58 self.deNovoGeneAnnotation = self.start_questionnaire()

59

60 def call_last_question(self):

61 """

62 Call the previous question in the question stack.

63 """

64 answer = None

65

66 if self.current_idx > 0:

67 self.current_idx -= 1

68 prev_answer = self.question_stack[self.current_idx][1]

69 self.question_stack[self.current_idx][0](prev_answer)

70 return

71

72 if self.question_stack:

73 answer = self.question_stack[self.current_idx][1]

74 self.question_stack[self.current_idx][0](answer)

75 return

76

77 self.q1(answer)

78 return

79

80 def call_next_question(

81 self,

82 current_answer: Any,

83 this_q: callable,

84 next_q: callable,

85 prev_answer: list = None,

86 ):

87 """

88 Call the next question in the question stack.

89

90 :param current_answer: The current answer.

91 :type current_answer: Any

92 :param this_q: The current question.

93 :type this_q: callable

94 :param next_q: The next question.

95 :type next_q: callable

96 :param prev_answer: The previous answer.

97 :type prev_answer: list, optional

98 """

99 next_answer = None

100

101 if different_answers(current_answer, prev_answer):

102 self.question_stack = self.question_stack[: self.current_idx]

103 self.question_stack.append((this_q, current_answer))

104 else:

105 if len(self.question_stack) > self.current_idx + 1:

106 next_answer = self.question_stack[self.current_idx + 1][1]

107 next_q = self.question_stack[self.current_idx + 1][0]

108

109 self.current_idx += 1

110 next_q(next_answer)

111

112 def q_end(self, answer: Any = None):

113 """

114 End of the questionnaire.

115 Calling this last function (without an actual question) is necessary to trigger

116 saving the last answer in the question stack through the call_next_question function.

117 """

118 return # end of questionnaire

119

120 def q6_1(self, answer: Any = None):

121 """

122 Get the URL/doi to the study/detailed methods.

123 """

124 studyURL = self.interface_funcs_dict["get_custom_entry"](

125 "Please provide the URL/doi to your study/detailed methods:",

126 multi_choice=True,

127 section_idx=5,

128 prev_answer=answer,

129 )

130

131 if isinstance(studyURL, GoQBack):

132 self.call_last_question()

133 return

134

135 self.call_next_question(studyURL, self.q6_1, self.q_end, answer)

136

137 def q6(self, answer: Any = None):

138 """

139 Check if URL/doi to the study/detailed methods should be provided.

140 """

141 answerStudyURL = self.interface_funcs_dict["get_yes_no"](

142 "Do you want to provide a URL/doi to your study/detailed methods? (yes/no)",

143 section_idx=5,

144 prev_answer=answer,

145 )

146

147 if isinstance(answerStudyURL, GoQBack):

148 self.call_last_question()

149 return

150 elif answerStudyURL:

151 next_question_callable = self.q6_1

152 else:

153 next_question_callable = self.q_end

154

155 self.call_next_question(answerStudyURL, self.q6, next_question_callable, answer)

156

157 def q5_2(self, answer: Any = None):

158 """

159 Get the custom method used as evidence for translation.

160 """

161 cstmTranslatEvidnc = self.interface_funcs_dict["get_custom_entry"](

162 "Please provide your custom method used as evidence for translation:",

163 multi_choice=True,

164 section_idx=4,

165 prev_answer=answer,

166 )

167

168 if isinstance(cstmTranslatEvidnc, GoQBack):

169 self.call_last_question()

170 return

171

172 self.call_next_question(cstmTranslatEvidnc, self.q5_2, self.q6, answer)

173

174 def q5_1(self, answer: Any = None):

175 """

176 Get the custom method used as evidence for translation.

177 """

178 translatEvidence = self.interface_funcs_dict["get_enum_choice_conversion"](

179 TranslationEvidenceChoices,

180 "Please choose the method used as evidence for translation:",

181 multi_choice=True,

182 section_idx=4,

183 prev_answer=answer,

184 )

185

186 if isinstance(translatEvidence, GoQBack):

187 self.call_last_question()

188 return

189 elif translatEvidence and TranslationEvidenceChoices.CUSTOM in translatEvidence:

190 next_question_callable = self.q5_2

191 else:

192 next_question_callable = self.q6

193

194 self.call_next_question(

195 translatEvidence, self.q5_1, next_question_callable, answer

196 )

197

198 def q5(self, answer: Any = None):

199 """

200 Check if translation of the de novo genes was verified.

201 """

202 translationEvidence = self.interface_funcs_dict["get_yes_no"](

203 "Did you verify the translation of the de novo genes? (yes/no)",

204 section_idx=4,

205 prev_answer=answer,

206 )

207

208 if isinstance(translationEvidence, GoQBack):

209 self.call_last_question()

210 return

211 elif translationEvidence:

212 next_question_callable = self.q5_1

213 else:

214 next_question_callable = self.q6

215

216 self.call_next_question(

217 translationEvidence, self.q5, next_question_callable, answer

218 )

219

220 def q4_1(self, answer: Any = None):

221 """

222 Get the custom metric or method used to identify selection pressure.

223 """

224 selection = self.interface_funcs_dict["get_custom_entry"](

225 "Please provide the metric or method used to identify selection pressure:",

226 section_idx=3,

227 prev_answer=answer,

228 )

229

230 if isinstance(selection, GoQBack):

231 self.call_last_question()

232 return

233

234 self.call_next_question(selection, self.q4_1, self.q5, answer)

235

236 def q4(self, answer: Any = None):

237 """

238 Check if selection pressure was studied for the de novo genes.

239 """

240 selectionPressure = self.interface_funcs_dict["get_yes_no"](

241 "Did you study selection pressure of the de novo genes? (yes/no)",

242 section_idx=3,

243 prev_answer=answer,

244 )

245

246 if isinstance(selectionPressure, GoQBack):

247 self.call_last_question()

248 return

249 elif selectionPressure:

250 next_question_callable = self.q4_1

251 else:

252 next_question_callable = self.q5

253

254 self.call_next_question(

255 selectionPressure, self.q4, next_question_callable, answer

256 )

257

258 def q3_3_3(self, answer: Any = None):

259 """

260 Get the custom software used for the synteny search.

261 """

262 softwareSyntenySearch = self.interface_funcs_dict["get_custom_entry"](

263 "Please choose the software used for the synteny search:",

264 multi_choice=True,

265 section_idx=2,

266 prev_answer=answer,

267 )

268

269 if isinstance(softwareSyntenySearch, GoQBack):

270 self.call_last_question()

271 return

272

273 self.call_next_question(softwareSyntenySearch, self.q3_3_3, self.q4, answer)

274

275 def q3_3_2(self, answer: Any = None):

276 """

277 Check if specific software was used for the synteny search.

278 """

279 sftwrSyntSearch = self.interface_funcs_dict["get_yes_no"](

280 "Did you use a specific software for the synteny search? (yes/no)",

281 section_idx=2,

282 prev_answer=answer,

283 )

284

285 if isinstance(sftwrSyntSearch, GoQBack):

286 self.call_last_question()

287 return

288 elif sftwrSyntSearch:

289 next_question_callable = self.q3_3_3

290 else:

291 next_question_callable = self.q4

292

293 self.call_next_question(

294 sftwrSyntSearch, self.q3_3_2, next_question_callable, answer

295 )

296

297 def q3_3_1(self, answer: Any = None):

298 """

299 Get the custom anchor for synteny search.

300 """

301 customAnchor = self.interface_funcs_dict["get_custom_entry"](

302 "Please provide your custom anchor for synteny search:",

303 multi_choice=True,

304 section_idx=2,

305 prev_answer=answer,

306 )

307

308 if isinstance(customAnchor, GoQBack):

309 self.call_last_question()

310 return

311

312 self.call_next_question(customAnchor, self.q3_3_1, self.q3_3_2, answer)

313

314 def q3_3(self, answer: Any = None):

315 """

316 Get the synteny search information from the user.

317 """

318 geneAnchor = self.interface_funcs_dict["get_enum_choice_conversion"](

319 AnchorChoices,

320 "What was used to identify the syntenic region?:",

321 multi_choice=True,

322 section_idx=2,

323 prev_answer=answer,

324 )

325

326 if isinstance(geneAnchor, GoQBack):

327 self.call_last_question()

328 return

329 elif geneAnchor and AnchorChoices.CUSTOM in geneAnchor:

330 next_question_callable = self.q3_3_1

331 else:

332 next_question_callable = self.q3_3_2

333

334 self.call_next_question(geneAnchor, self.q3_3, next_question_callable, answer)

335

336 def q3_2(self, answer: Any = None):

337 """

338 Check if synteny was studied between de novo genes and homologous sequences.

339 """

340 answerSynteny = self.interface_funcs_dict[

341 "get_yes_no"

342 ](

343 "Did you check for synteny between de novo genes and their homologous sequences? (yes/no)", # TODO: +non-genic?

344 section_idx=2,

345 prev_answer=answer,

346 )

347

348 if isinstance(answerSynteny, GoQBack):

349 self.call_last_question()

350 return

351 elif answerSynteny:

352 next_question_callable = self.q3_3

353 else:

354 next_question_callable = self.q4

355

356 self.call_next_question(

357 answerSynteny, self.q3_2, next_question_callable, answer

358 )

359

360 def q3_1(self, answer: Any = None):

361 """

362 Check if conservation/mutations between de novo genes and homologous sequences were studied.

363 """

364 enablingMutations = self.interface_funcs_dict[

365 "get_yes_no"

366 ](

367 "Did you study conservation/mutations between de novo genes and homologous sequences?", # TODO: +non-genic?

368 section_idx=2,

369 prev_answer=answer,

370 )

371

372 if isinstance(enablingMutations, GoQBack):

373 self.call_last_question()

374 return

375

376 self.call_next_question(enablingMutations, self.q3_1, self.q3_2, answer)

377

378 def q3(self, answer: Any = None):

379 """

380 Check if non-genic homologous sequences were detected.

381 """

382 nonCodeHomolog = self.interface_funcs_dict["get_yes_no"](

383 "Did you detect non-genic homologous sequences in genomes from other taxonomic groups? (yes/no)",

384 section_idx=2,

385 prev_answer=answer,

386 )

387

388 if isinstance(nonCodeHomolog, GoQBack):

389 self.call_last_question()

390 return

391 elif nonCodeHomolog:

392 next_question_callable = self.q3_1

393 else:

394 next_question_callable = self.q4

395

396 self.call_next_question(nonCodeHomolog, self.q3, next_question_callable, answer)

397

398 def q2_8(self, answer: Any = None):

399 """

400 Get the custom database(s) used for homology filtering.

401 """

402 customDB = self.interface_funcs_dict["get_custom_entry"](

403 "Please provide your custom database used for homology filtering:",

404 multi_choice=True,

405 section_idx=1,

406 prev_answer=answer,

407 )

408

409 if isinstance(customDB, GoQBack):

410 self.call_last_question()

411 return

412

413 self.call_next_question(customDB, self.q2_8, self.q3, answer)

414

415 def q2_7(self, answer: Any = None):

416 """

417 Get the custom database(s) used for homology filtering.

418 """

419 homologyDBChoice = self.interface_funcs_dict["get_enum_choice_conversion"](

420 HomologyDBChoices,

421 "Please choose the database(s) used for homology filtering:",

422 multi_choice=True,

423 section_idx=1,

424 prev_answer=answer,

425 )

426

427 if isinstance(homologyDBChoice, GoQBack):

428 self.call_last_question()

429 return

430 elif homologyDBChoice and HomologyDBChoices.CUSTOM in homologyDBChoice:

431 next_question_callable = self.q2_8

432 else:

433 next_question_callable = self.q3

434

435 self.call_next_question(

436 homologyDBChoice, self.q2_7, next_question_callable, answer

437 )

438

439 def q2_6_1(self, answer: Any = None):

440 """

441 Get the threshold value(s) for homology filtering based on selected metric(s).

442 """

443 thresholdValid = False

444 thresholdValue = []

445 thresholdChoice = dict(self.question_stack)[self.q2_5_3]

446 customThreshold = dict(self.question_stack).get(self.q2_6, [])

447

448 for idx, thChoice in enumerate(

449 [it for it in thresholdChoice if it != ThresholdChoices.CUSTOM]

450 + customThreshold

451 ):

452 while not thresholdValid:

453 if answer:

454 prev_answer = answer[idx] if len(answer) > idx else None

455 else:

456 prev_answer = None

457 answrThreshVal = self.interface_funcs_dict["get_custom_entry"](

458 f"Please provide the threshold value for your homology "

459 f"filtering based on {thChoice}:",

460 section_idx=1,

461 prev_answer=prev_answer,

462 )

463

464 if isinstance(answrThreshVal, GoQBack):

465 self.call_last_question()

466 return

467

468 if prev_answer and not different_answers(answrThreshVal, prev_answer):

469 thresholdValid = True

470 else:

471 thresholdValid = self.interface_funcs_dict[

472 "valid_input_for_pydmodel"

473 ](HomologyFilter, "thresholdValue", [answrThreshVal])

474

475 if thresholdValid:

476 thresholdValue.append(answrThreshVal)

477

478 thresholdValid = False

479

480 self.call_next_question(thresholdValue, self.q2_6_1, self.q2_7, answer)

481

482 def q2_6(self, answer: Any = None):

483 """

484 Get the custom metric for homology filtering.

485 """

486 customThreshold = self.interface_funcs_dict["get_custom_entry"](

487 "Please provide your custom metric for homology filtering:",

488 multi_choice=True,

489 section_idx=1,

490 prev_answer=answer,

491 )

492

493 if isinstance(customThreshold, GoQBack):

494 self.call_last_question()

495 return

496

497 self.call_next_question(customThreshold, self.q2_6, self.q2_6_1, answer)

498

499 def q2_5_3(self, answer: Any = None):

500 """

501 Get the metric used for homology filtering.

502 """

503 thresholdChoice = self.interface_funcs_dict["get_enum_choice_conversion"](

504 ThresholdChoices,

505 "Please choose the metric used for homology filtering:",

506 multi_choice=True,

507 section_idx=1,

508 prev_answer=answer,

509 )

510

511 if isinstance(thresholdChoice, GoQBack):

512 self.call_last_question()

513 return

514 elif thresholdChoice and ThresholdChoices.CUSTOM in thresholdChoice:

515 next_question_callable = self.q2_6

516 else:

517 next_question_callable = self.q2_6_1

518

519 self.call_next_question(

520 thresholdChoice, self.q2_5_3, next_question_callable, answer

521 )

522

523 def q2_5_2(self, answer: Any = None):

524 """

525 Get the custom structural similarity search software/method used for homology filtering

526 """

527 structSim = self.interface_funcs_dict["get_custom_entry"](

528 "Please provide the structural similarity search software/method used for homology filtering:",

529 multi_choice=False,

530 section_idx=1,

531 prev_answer=answer,

532 )

533

534 if isinstance(structSim, GoQBack):

535 self.call_last_question()

536 return

537

538 self.call_next_question(structSim, self.q2_5_2, self.q2_5_3, answer)

539

540 def q2_5_1(self, answer: Any = None):

541 """

542 Check if structural similarity was used for homology filtering.

543 """

544 structSim = self.interface_funcs_dict["get_yes_no"](

545 "Did you use structural similarity for homology filtering? (yes/no)",

546 section_idx=1,

547 prev_answer=answer,

548 )

549

550 if isinstance(structSim, GoQBack):

551 self.call_last_question()

552 return

553

554 if structSim:

555 next_question_callable = self.q2_5_2

556 else:

557 next_question_callable = self.q2_5_3

558

559 self.call_next_question(structSim, self.q2_5_1, next_question_callable, answer)

560

561 def q2_4(self, answer: Any = None):

562 """

563 Get the custom sequence type(s) used for homology filtering.

564 """

565 customSeqType = self.interface_funcs_dict["get_custom_entry"](

566 "Please provide your custom sequence type(s) used for homology filtering:",

567 multi_choice=True,

568 section_idx=1,

569 prev_answer=answer,

570 )

571

572 if isinstance(customSeqType, GoQBack):

573 self.call_last_question()

574 return

575

576 self.call_next_question(customSeqType, self.q2_4, self.q2_5_1, answer)

577

578 def q2_3(self, answer: Any = None):

579 """

580 Get the sequence type(s) used for homology filtering.

581 """

582 seqTypeChoice = self.interface_funcs_dict["get_enum_choice_conversion"](

583 SeqTypeChoices,

584 "Please choose your sequence type(s) used for homology filtering:",

585 multi_choice=True,

586 section_idx=1,

587 prev_answer=answer,

588 )

589

590 if isinstance(seqTypeChoice, GoQBack):

591 self.call_last_question()

592 return

593 elif seqTypeChoice and SeqTypeChoices.CUSTOM in seqTypeChoice:

594 next_question_callable = self.q2_4

595 else:

596 next_question_callable = self.q2_5_1

597

598 self.call_next_question(

599 seqTypeChoice, self.q2_3, next_question_callable, answer

600 )

601

602 def q2_2_1(self, answer: Any = None):

603 """

604 Get the taxonomic ID where the de novo genes emerged.

605 """

606 taxIDvalid = False

607

608 while not taxIDvalid:

609 warnings.filterwarnings("error")

610

611 answerTaxonID = self.interface_funcs_dict["get_custom_entry"](

612 "Please provide the taxonomic ID (name or number from NCBI Taxonomy DB) where they emerged:",

613 section_idx=1,

614 prev_answer=answer,

615 )

616

617 if isinstance(answerTaxonID, GoQBack):

618 self.call_last_question()

619 return

620

621 if answer and not different_answers(answerTaxonID, answer):

622 taxIDvalid = True

623 else:

624 taxIDvalid = self.interface_funcs_dict["valid_input_for_pydmodel"](

625 TaxonID, "taxID", answerTaxonID

626 )

627

628 warnings.filterwarnings("ignore")

629

630 self.call_next_question(answerTaxonID, self.q2_2_1, self.q2_3, answer)

631

632 def q2_2(self, answer: Any = None):

633 """

634 Get the taxonomic group where the de novo genes emerged.

635 """

636 taxSpecificity = self.interface_funcs_dict["get_enum_choice_conversion"](

637 TaxSpecificityChoices,

638 "Please choose the specificity for the taxonomic group where they emerged:",

639 section_idx=1,

640 prev_answer=answer,

641 )

642

643 if isinstance(taxSpecificity, GoQBack):

644 self.call_last_question()

645 return

646

647 self.call_next_question(taxSpecificity, self.q2_2, self.q2_2_1, answer)

648

649 def q2_1(self, answer: Any = None):

650 """

651 Check if phylogenetic taxa information is known.

652 """

653 phylogeneticTaxa = self.interface_funcs_dict["get_yes_no"](

654 "Do you know in which taxonomic group your de novo gene candidates emerged? (yes/no)",

655 section_idx=1,

656 prev_answer=answer,

657 )

658

659 if isinstance(phylogeneticTaxa, GoQBack):

660 self.call_last_question()

661 return

662 elif phylogeneticTaxa:

663 next_question_callable = self.q2_2 # get_PhylogeneticTaxa

664 else:

665 next_question_callable = self.q2_3

666

667 self.call_next_question(

668 phylogeneticTaxa, self.q2_1, next_question_callable, answer

669 )

670

671 def q2(self, answer: Any = None):

672 """

673 Check if a homology filter was applied.

674 """

675 homologyFilter = self.interface_funcs_dict["get_yes_no"](

676 "Did you validate absence of homology of your de novo genes? (yes/no)",

677 section_idx=1,

678 prev_answer=answer,

679 )

680

681 if isinstance(homologyFilter, GoQBack):

682 self.call_last_question()

683 return

684 elif homologyFilter:

685 next_question_callable = self.q2_1

686 else:

687 next_question_callable = self.q3

688

689 self.call_next_question(homologyFilter, self.q2, next_question_callable, answer)

690

691 def q1_3(self, answer: Any = None):

692 """

693 Get the custom input data (not annotated genome or transcriptome).

694 """

695 customInputData = self.interface_funcs_dict["get_custom_entry"](

696 "Please provide your custom input data for de novo gene detection:",

697 section_idx=0,

698 prev_answer=answer,

699 )

700

701 if isinstance(customInputData, GoQBack):

702 self.call_last_question()

703 return

704

705 self.call_next_question(customInputData, self.q1_3, self.q2, answer)

706

707 def q1_2_7(self, answer: Any = None):

708 """

709 Get the additional, custom transcriptome information from the user.

710 """

711 inputDataChoice = self.question_stack[0][1]

712

713 transcriptomeInfo = self.interface_funcs_dict["get_custom_entry"](

714 "Please provide the information about the transcriptome (e.g. tissue, cell type, ...):",

715 section_idx=0,

716 prev_answer=answer,

717 )

718

719 if isinstance(transcriptomeInfo, GoQBack):

720 self.call_last_question()

721 return

722 elif inputDataChoice and InputDataChoices.CUSTOM in inputDataChoice:

723 next_question_callable = self.q1_3

724 else:

725 next_question_callable = self.q2

726

727 self.call_next_question(

728 transcriptomeInfo, self.q1_2_7, next_question_callable, answer

729 )

730

731 def q1_2_6(self, answer: Any = None):

732 """

733 Check if additional info about the transcriptome should be added.

734 """

735 inputDataChoice = self.question_stack[0][1]

736

737 transcriptomeInfo = self.interface_funcs_dict["get_yes_no"](

738 "Do you want to add additional information about the transcriptome (e.g. tissue, cell type, ...)? (yes/no)",

739 section_idx=0,

740 prev_answer=answer,

741 )

742

743 if isinstance(transcriptomeInfo, GoQBack):

744 self.call_last_question()

745 return

746 elif transcriptomeInfo:

747 next_question_callable = self.q1_2_7

748 elif inputDataChoice and InputDataChoices.CUSTOM in inputDataChoice:

749 next_question_callable = self.q1_3

750 else:

751 next_question_callable = self.q2

752

753 self.call_next_question(

754 transcriptomeInfo, self.q1_2_6, next_question_callable, answer

755 )

756

757 def q1_2_5(self, answer: Any = None):

758 """

759 Get the custom ORF selection for the transcriptome data.

760 """

761 customORF = self.interface_funcs_dict["get_custom_entry"](

762 "Please provide your custom ORF selection for your transcriptome data:",

763 multi_choice=True,

764 section_idx=0,

765 prev_answer=answer,

766 )

767

768 if isinstance(customORF, GoQBack):

769 self.call_last_question()

770 return

771

772 self.call_next_question(customORF, self.q1_2_5, self.q1_2_6, answer)

773

774 def q1_2_4(self, answer: Any = None):

775 """

776 Get which ORFs in transcripts were selected.

777 """

778 ORFChoiceValid = False

779

780 while not ORFChoiceValid:

781 transORFChoice = self.interface_funcs_dict["get_enum_choice_conversion"](

782 ORFChoices,

783 "Please choose which ORFs in the transcripts were selected:",

784 multi_choice=True,

785 section_idx=0,

786 prev_answer=answer,

787 )

788

789 if isinstance(transORFChoice, GoQBack):

790 self.call_last_question()

791 return

792

793 if answer and not different_answers(transORFChoice, answer):

794 ORFChoiceValid = True

795 else:

796 ORFChoiceValid = self.interface_funcs_dict["valid_input_for_pydmodel"](

797 Transcriptome, "transORFChoice", transORFChoice

798 )

799

800 if transORFChoice and ORFChoices.CUSTOM in transORFChoice:

801 next_question_callable = self.q1_2_5

802 else:

803 next_question_callable = self.q1_2_6

804

805 self.call_next_question(

806 transORFChoice, self.q1_2_4, next_question_callable, answer

807 )

808

809 def q1_2_3(self, answer: Any = None):

810 """

811 Get the custom genetic context information from the user.

812 """

813 customGeneticContext = self.interface_funcs_dict["get_custom_entry"](

814 "Please provide your custom genetic context for your transcriptome data:",

815 multi_choice=True,

816 section_idx=0,

817 prev_answer=answer,

818 )

819

820 if isinstance(customGeneticContext, GoQBack):

821 self.call_last_question()

822 return

823

824 self.call_next_question(customGeneticContext, self.q1_2_3, self.q1_2_4, answer)

825

826 def q1_2_2(self, answer: Any = None):

827 """

828 Get the genetic context information from the user.

829 """

830 transContextChoice = self.interface_funcs_dict[

831 "get_enum_choice_conversion"

832 ](

833 GeneticContextChoices,

834 "Please indicate which transcripts were kept based on their overlap with the following genetic contexts:", # add a None / not filtererd any genetic context option?

835 multi_choice=True,

836 section_idx=0,

837 prev_answer=answer,

838 )

839

840 if isinstance(transContextChoice, GoQBack):

841 self.call_last_question()

842 return

843 elif transContextChoice and GeneticContextChoices.CUSTOM in transContextChoice:

844 next_question_callable = self.q1_2_3

845 else:

846 next_question_callable = self.q1_2_4

847

848 self.call_next_question(

849 transContextChoice, self.q1_2_2, next_question_callable, answer

850 )

851

852 def q1_2_1(self, answer: Any = None):

853 """

854 Get the TPM threshold used as a minimum level of transcript expression.

855 """

856 validExpressionLevel = False

857

858 while not validExpressionLevel:

859 expressionLevel = self.interface_funcs_dict["get_custom_entry"](

860 "Please provide the TPM threshold used as a minimum level of transcript expression:",

861 section_idx=0,

862 prev_answer=answer,

863 )

864 if isinstance(expressionLevel, GoQBack):

865 self.call_last_question()

866 return

867

868 if answer and not different_answers(expressionLevel, answer):

869 validExpressionLevel = True

870 else:

871 validExpressionLevel = self.interface_funcs_dict[

872 "valid_input_for_pydmodel"

873 ](Transcriptome, "expressionLevel", expressionLevel)

874

875 self.call_next_question(expressionLevel, self.q1_2_1, self.q1_2_2, answer)

876

877 def q1_2(self, answer: Any = None):

878 """

879 Get the transcriptome information from the user.

880 """

881 inputDataChoice = self.question_stack[0][1]

882

883 answerExpressionLevel = self.interface_funcs_dict[

884 "get_yes_no"

885 ](

886 "Did you apply a TPM threshold used as a minimum level of transcript expression? (yes/no)", # None means unknown/no threshold?

887 section_idx=0,

888 prev_answer=answer,

889 )

890

891 if isinstance(answerExpressionLevel, GoQBack):

892 self.call_last_question()

893 return

894 elif answerExpressionLevel:

895 next_question_callable = self.q1_2_1

896 elif inputDataChoice and InputDataChoices.CUSTOM in inputDataChoice:

897 next_question_callable = self.q1_3

898 else:

899 next_question_callable = self.q1_2_2

900

901 self.call_next_question(

902 answerExpressionLevel, self.q1_2, next_question_callable, answer

903 )

904

905 def q1_1(self, answer: Any = None):

906 """

907 Get the genome annotation method from the user.

908 """

909 inputDataChoice = self.question_stack[0][1]

910 genomeChoicevalid = False

911

912 while not genomeChoicevalid:

913 annoGenom = self.interface_funcs_dict["get_enum_choice_conversion"](

914 AnnotGenomeChoices,

915 "Please choose the genome annotation method:",

916 multi_choice=True,

917 section_idx=0,

918 prev_answer=answer,

919 )

920

921 if isinstance(annoGenom, GoQBack):

922 self.call_last_question()

923 return

924

925 if answer and not different_answers(annoGenom, answer):

926 genomeChoicevalid = True

927 else:

928 genomeChoicevalid = self.interface_funcs_dict[

929 "valid_input_for_pydmodel"

930 ](AnnotGenome, "annotGenomeChoice", annoGenom)

931

932 if inputDataChoice and InputDataChoices.TRANSCRIPTOME in inputDataChoice:

933 next_question_callable = self.q1_2

934 elif inputDataChoice and InputDataChoices.CUSTOM in inputDataChoice:

935 next_question_callable = self.q1_3

936 else:

937 next_question_callable = self.q2

938

939 self.call_next_question(annoGenom, self.q1_1, next_question_callable, answer)

940

941 def q1(self, answer: Any = None):

942 """

943 Get the input data choice from the user.

944 """

945 inputDataChoice = self.interface_funcs_dict["get_enum_choice_conversion"](

946 InputDataChoices,

947 "Did you detect your candidate de novo genes from a:",

948 multi_choice=True,

949 section_idx=0,

950 prev_answer=answer,

951 )

952

953 if isinstance(inputDataChoice, GoQBack):

954 self.call_last_question()

955 return

956 elif inputDataChoice and InputDataChoices.ANNOT_GENOME in inputDataChoice:

957 next_question_callable = self.q1_1

958 elif inputDataChoice and InputDataChoices.TRANSCRIPTOME in inputDataChoice:

959 next_question_callable = self.q1_2

960 elif inputDataChoice and InputDataChoices.CUSTOM in inputDataChoice:

961 next_question_callable = self.q1_3

962 else:

963 raise ValueError(

964 "Invalid input data choice. Please select from the following options: "

965 "ANNOT_GENOME, TRANSCRIPTOME, CUSTOM."

966 )

967

968 self.call_next_question(

969 inputDataChoice, self.q1, next_question_callable, answer

970 )

971

972 def get_DeNovoGeneAnnotation_from_qstack(

973 self,

974 match_dict: dict[str, Any] = FUNCS_TO_MODELS_DICT,

975 ) -> DeNovoGeneAnnotation:

976 """

977 Create a DeNovoGeneAnnotation object from the given question stack.

978

979 :param question_stack: The question stack.

980 :type question_stack: list[tuple[callable, Any]]

981 :param match_dict: The dictionary to match function callables to field names.

982 :type match_dict: dict[str, Any]

983 :return: The DeNovoGeneAnnotation object.

984 :rtype: DeNovoGeneAnnotation

985 """

986 # match function callables to field names

987 qstack_dict = dict(

988 map(

989 lambda kv: (match_dict[kv[0].__func__.__name__], kv[1]),

990 self.question_stack,

991 )

992 )

993

994 # create all sub-models

995 annotGenome = get_model_from_qstack_dict(qstack_dict, AnnotGenome)

996 transcriptome = get_model_from_qstack_dict(qstack_dict, Transcriptome)

997 evolInfo = get_model_from_qstack_dict(qstack_dict, EvolutionaryInformation)

998 if qstack_dict.get("taxID", False) and qstack_dict.get(

999 "phylogeneticTaxa", False

1000 ):

1001 taxonID = get_model_from_qstack_dict(qstack_dict, TaxonID)

1002 qstack_dict["taxonID"] = taxonID

1003 phylogeneticTaxa = get_model_from_qstack_dict(qstack_dict, PhylogeneticTaxa)

1004 qstack_dict["phylogeneticTaxa"] = phylogeneticTaxa

1005 homologyFilter = get_model_from_qstack_dict(qstack_dict, HomologyFilter)

1006 if qstack_dict.get("synteny", False):

1007 syntenySearch = get_model_from_qstack_dict(qstack_dict, SyntenySearch)

1008 qstack_dict["synteny"] = syntenySearch

1009 nonCodingHomologs = get_model_from_qstack_dict(qstack_dict, NonCodingHomologs)

1010 translatEvidnce = get_model_from_qstack_dict(qstack_dict, TranslationalEvidence)

1011

1012 # create the DeNovoGeneAnnotation object

1013 return DeNovoGeneAnnotation(

1014 inputData=qstack_dict["inputData"],

1015 inputAnnotGenome=annotGenome,

1016 inputTranscriptome=transcriptome,

1017 customInputData=qstack_dict.get("customInputData", None),

1018 evolutionaryInformation=evolInfo,

1019 homologyFilter=homologyFilter,

1020 nonCodingHomologs=nonCodingHomologs,

1021 translationalEvidence=translatEvidnce,

1022 studyURL=qstack_dict.get("studyURL", None),

1023 )

1024

1025 def start_questionnaire(self) -> DeNovoGeneAnnotation:

1026 """

1027 Start the questionnaire to get the user input.

1028

1029 :return: The DeNovoGeneAnnotation object.

1030 :rtype: DeNovoGeneAnnotation

1031 """

1032

1033 self.q1()

1034

1035 if not self.question_stack:

1036 raise ValueError(

1037 "Question stack is empty. Ensure that questions are answered before proceeding."

1038 )

1039

1040 return self.get_DeNovoGeneAnnotation_from_qstack()