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
« 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/>.
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
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
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]
39 if is_tta_member: # TTA members do not need anonymized data
40 return json
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]
52def anonymize_proposal(prop_json: JSON_OBJECT, most_restrictive: bool) -> JSON_OBJECT:
53 """Anonymize a single Proposal's JSON
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
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