editorial-system / directory.py
ICSAC's picture
Initial commit: ICSAC editorial system
37cb069
"""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