Coverage for middle_layer/review/domain_layer/services/anonymize_proposal_service.py: 100.00%

24 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 

18from common.domain_layer import JSON_OBJECT 

19from propose.domain_layer.entities.proposal import Proposal 

20from review.domain_layer.entities.individual_science_review import IndividualScienceReview 

21 

22 

23def anonymize_proposals( 

24 proposals: list[Proposal], 

25 is_tta_member: bool = False, 

26 individual_science_reviews: list[IndividualScienceReview] | None = None, 

27): 

28 """Generate anonymized JSON for a list of Proposals 

29 

30 :param proposals: List of Proposals to anonymize 

31 :param is_tta_member: Whether or not the user viewing the results is a TTA member 

32 :param individual_science_reviews: When present, the list of ISRs 

33 for the ScienceReviewer who will see the anonymized JSON. 

34 Proposals for which they are Available will be redacted less than the rest to permit reviewing them. 

35 :return: JSON List of Proposals, with sensitive information removed 

36 """ 

37 json = [prop.__json__() for prop in proposals] 

38 

39 if is_tta_member: # TTA members do not need anonymized data 

40 return json 

41 

42 available_prop_ids: set[int] = set() 

43 if individual_science_reviews: 

44 available_prop_ids = { 

45 isr.proposal_id 

46 for isr in individual_science_reviews 

47 if isr.conflict_declaration.conflict_state == "Available" 

48 } 

49 return [anonymize_proposal(prop_json, not prop_json["proposalId"] in available_prop_ids) for prop_json in json] 

50 

51 

52def anonymize_proposal(prop_json: JSON_OBJECT, most_restrictive: bool) -> JSON_OBJECT: 

53 """Anonymize a single Proposal's JSON 

54 

55 :param prop_json: JSON to anonymize 

56 :param most_restrictive: If True, redact for a ScienceReviewer to determine if they have a conflict of interest 

57 with reviewing prop_json 

58 If False, redact for an Available ScienceReviewer to review prop_json 

59 :return: Anonymized version of prop_json 

60 """ 

61 prop_json["vettingNotes"] = None 

62 prop_json["authors"] = [] 

63 if most_restrictive: 

64 prop_json["submittedTimestamp"] = None 

65 prop_json["authorCopy"]["modifiedTimestamp"] = None 

66 prop_json["authorCopy"]["allocationRequestIds"] = [] 

67 prop_json["authorCopy"]["scienceCategoryId"] = None 

68 

69 # When anonymizing a proposal it is almost always in a non-Draft state except possibly when being tested. 

70 # Be defensive in the event the proposal is in draft state 

71 if prop_json["observatoryCopy"]: 

72 prop_json["observatoryCopy"]["modifiedTimestamp"] = None 

73 prop_json["observatoryCopy"]["allocationRequestIds"] = [] 

74 prop_json["observatoryCopy"]["scienceCategoryId"] = None 

75 return prop_json