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

401 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-09 15:27 +0200

1import warnings 

2from typing import Any 

3from denofo.utils.constants import FUNCS_TO_MODELS_DICT, GoQBack 

4from denofo.utils.helpers import different_answers, get_model_from_qstack_dict 

5from denofo.models import ( 

6 AnnotGenome, 

7 DeNovoGeneAnnotation, 

8 EvolutionaryInformation, 

9 HomologyFilter, 

10 TranslationalEvidence, 

11 NonCodingHomologs, 

12 PhylogeneticTaxa, 

13 SyntenySearch, 

14 TaxonID, 

15 Transcriptome, 

16) 

17from 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 

31class 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()