Coverage for middle_layer/closeout/application_layer/services/search_proposals.py: 89.47%

19 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2026-03-09 06:13 +0000

1# Copyright 2024 Associated Universities, Inc. 

2# 

3# This file is part of Telescope Time Allocation Tools (TTAT). 

4# 

5# TTAT is free software: you can redistribute it and/or modify 

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

7# the Free Software Foundation, either version 3 of the License, or 

8# any later version. 

9# 

10# TTAT is distributed in the hope that it will be useful, 

11# but WITHOUT ANY WARRANTY; without even the implied warranty of 

12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

13# GNU General Public License for more details. 

14# 

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

16# along with TTAT. If not, see <https://www.gnu.org/licenses/>. 

17 

18import logging 

19 

20from astropy.units import Quantity 

21 

22from allocate.domain_layer.entities.publication_destination import PublicationDestination 

23from closeout.domain_layer.entities.proposals_search_result import ProposalSearchResult 

24from common.application_layer.orm_repositories.orm_repository import ORMRepository 

25from propose.domain_layer.entities.proposal import Author, Proposal 

26 

27 

28def _format_author(author: Author): 

29 return f"{author.first_name} {author.last_name} ({author.institution})" 

30 

31 

32def save_proposal_for_search(proposal: Proposal, repo: ORMRepository): 

33 """ 

34 Saves the proposal metadata to the search table for the proposal search interface. 

35 

36 If the proposal has already been saved, logs an error but returns normally 

37 

38 :param proposal: the proposal to "publish" 

39 :param repo: the ORM repository to save with 

40 :return: 

41 """ 

42 if proposal.search_result: 

43 logging.error(f"Error: Proposal {proposal} is already searchable; skipping save for search") 

44 return 

45 

46 facilities = set([av.facility.facility_name for av in proposal.published_versions(PublicationDestination.CLOSEOUT)]) 

47 

48 allocated_hours: Quantity["time"] = proposal.proposal_disposition.total_approved_time 

49 

50 if not allocated_hours.value: 

51 logging.info(f"Proposal {proposal} was not granted time; skipping save for search") 

52 else: 

53 result = ProposalSearchResult( 

54 proposal=proposal, 

55 proposal_id=proposal.proposal_id, 

56 proposal_code=proposal.proposal_code, 

57 title=proposal.observatory_copy.title, 

58 abstract=proposal.observatory_copy.abstract, 

59 pi=_format_author(proposal.authors[0]), 

60 coauthors=", ".join(_format_author(author) for author in proposal.authors[1:]), 

61 facilities=", ".join(facilities), 

62 proposal_class=proposal.observatory_copy.proposal_class.proposal_class_name, 

63 solicitation=proposal.solicitation, 

64 solicitation_id=proposal.solicitation_id, 

65 requested_science_category=proposal.author_copy.science_category.science_category_name, 

66 is_joint=len(facilities) > 1, 

67 is_triggered=proposal.is_triggered, 

68 total_allocated_hours=allocated_hours.to_value("hour"), 

69 submission_date=proposal.submitted_timestamp, 

70 ) 

71 

72 repo.session.add(result) 

73 logging.info(f"Proposal {proposal} added to search table")