"""Community member privacy and directory rendering rules. Applies opt-in-only directory listing based on Google Form signup preferences. Never exposes information the member did not explicitly consent to. """ DIRECTORY_CHOICES = { "public": "Yes, list me publicly", "minimal": "Yes, but name and role only (no contact info)", "private": "No — keep me private", } CONTACT_FIELDS = { "email": "Email address", "orcid": "ORCID", "scholar": "Google Scholar profile", "website": "Personal website", "alias": "ICSAC-aliased email only (we forward to your real address)", } def directory_entry(member: dict) -> dict | None: """Build a directory entry that respects the member's privacy choices. Returns None for private members. For public/minimal members, returns only the fields they consented to expose. Never includes email unless they ticked 'Email address' explicitly. """ choice = member.get("directory_choice", "") if choice == DIRECTORY_CHOICES["private"]: return None entry = { "display_name": format_display_name(member), "affiliation": member.get("affiliation", ""), "role": member.get("contribution_role", ""), "research_interests": member.get("research_interests", []), } if choice == DIRECTORY_CHOICES["minimal"]: entry.pop("affiliation", None) return entry consented = set(member.get("public_contact_fields", [])) contact = {} if CONTACT_FIELDS["email"] in consented: contact["email"] = member.get("email", "") if CONTACT_FIELDS["alias"] in consented: contact["email_alias"] = member.get("icsac_alias", "") if CONTACT_FIELDS["orcid"] in consented: contact["orcid"] = member.get("orcid", "") if CONTACT_FIELDS["scholar"] in consented: contact["scholar"] = member.get("scholar_url", "") if CONTACT_FIELDS["website"] in consented: contact["website"] = member.get("website_url", "") if contact: entry["contact"] = contact return entry def format_display_name(member: dict) -> str: """Build a display name from title preference + name + post-nominals.""" title = member.get("title", "") name = member.get("full_name", "") postnoms = member.get("post_nominals", "") parts = [] if title and title not in ("No title (first name is fine)", "Prefer not to say"): parts.append(title) parts.append(name) display = " ".join(parts) if postnoms: display = f"{display}, {postnoms}" return display def public_directory(members: list[dict]) -> list[dict]: """Filter the member list into directory-visible entries only.""" entries = [] for m in members: e = directory_entry(m) if e is not None: entries.append(e) return entries