-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgenerate_group_emails.py
More file actions
executable file
·171 lines (140 loc) · 5.66 KB
/
generate_group_emails.py
File metadata and controls
executable file
·171 lines (140 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Generér e-mail-lister pr. matchgruppe.
Brug:
python generate_group_emails.py [members.xlsx] [.match_groups.json]
Forventet Excel:
- Første fane indeholder data.
- Kolonner: "Navn" og "E-mail".
Forventet JSON (.match_groups.json):
[
{ "name": "Gruppenavn", "matches": ["Navn 1", "Navn 2", ...], "color": "valgfri" },
...
]
Uddata (til stdout):
Gruppenavn: "Navn 1" <mail1@eksempel.dk>, "Navn 2" <mail2@eksempel.dk>, ...
Bemærk:
- Navnematch er case-insensitive og trimmet for mellemrum.
- Overflødige mellemrum i Excel-navne fjernes (flere mellemrum -> ét).
- Hvis en e-mail ikke findes, udskrives <mangler email>.
- Evt. advarsler (fx dubletter) skrives til stderr.
"""
import argparse
import json
import sys
import re
from pathlib import Path
from typing import Dict, List, Tuple
import pandas as pd
def clean_name(name: str) -> str:
"""Rens navn: fjern start/slut mellemrum og reducer flere mellemrum til ét."""
if not isinstance(name, str):
return ""
# Fjern start/slut mellemrum
name = name.strip()
# Reducer sekvenser af mellemrum til ét
name = re.sub(r"\s+", " ", name)
return name
def normalize(s: str) -> str:
"""Normalisér navne for robust match: trim, reducer mellemrum og casefold."""
return clean_name(s).casefold()
def build_name_to_email(excel_path: Path) -> Tuple[Dict[str, str], List[str]]:
"""
Læs Excel og byg et opslagskort: normaliseret navn -> e-mail.
Returnerer (map, warnings).
"""
warnings: List[str] = []
try:
# Læs første fane
df = pd.read_excel(excel_path, sheet_name=0, dtype={"Navn": str, "E-mail": str})
except Exception as e:
print(f"Kunne ikke læse Excel-filen: {excel_path}: {e}", file=sys.stderr)
sys.exit(2)
# Tjek nødvendige kolonner
required = {"Navn", "E-mail"}
missing = required.difference(df.columns)
if missing:
print(f"Excel mangler kolonner: {', '.join(sorted(missing))}", file=sys.stderr)
sys.exit(2)
name_to_email: Dict[str, str] = {}
for i, row in df.iterrows():
name_raw = row.get("Navn")
email_raw = row.get("E-mail")
cleaned_name = clean_name(name_raw)
key = normalize(name_raw)
email = (str(email_raw).strip() if isinstance(email_raw, str) else "").strip()
if not key:
# Tomt navn – spring over
if email:
warnings.append(f"Række {i}: tomt navn men har e-mail '{email}' – ignoreres.")
continue
if key in name_to_email:
# Dubletnavn i Excel – log advarsel og behold eksisterende ikke-tomme e-mail
prev = name_to_email[key]
if prev and email and prev != email:
warnings.append(
f"Dublet af navn '{cleaned_name}' med forskellig e-mail: '{prev}' vs '{email}'. "
f"Beholder første forekomst."
)
elif not prev and email:
# Opgradér fra tom -> ny ikke-tom e-mail
name_to_email[key] = email
else:
name_to_email[key] = email
return name_to_email, warnings
def load_groups(json_path: Path) -> List[dict]:
"""Læs match-grupper fra JSON."""
try:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, list):
raise ValueError("JSON-roden skal være en liste af objekter.")
return data
except Exception as e:
print(f"Kunne ikke læse JSON-filen: {json_path}: {e}", file=sys.stderr)
sys.exit(2)
def format_entry(person_name: str, email: str) -> str:
"""
Formatér som: "Person Navn" <email>
Hvis email mangler: <mangler email>
"""
cleaned = clean_name(person_name)
email_part = email if email else "mangler email"
# Hvis email mangler, vis som <mangler email>; ellers <email>
email_bracketed = f"<{email_part}>" if email else "<mangler email>"
return f"\"{cleaned}\" {email_bracketed}"
def main() -> None:
parser = argparse.ArgumentParser(description="Generér e-mail-lister pr. matchgruppe.")
parser.add_argument("excel", type=Path, nargs="?", default=Path("members.xlsx"),
help="Sti til members.xlsx (første fane bruges)")
parser.add_argument("groups_json", type=Path, nargs="?", default=Path(".match_groups.json"),
help="Sti til .match_groups.json")
args = parser.parse_args()
name_to_email, warnings = build_name_to_email(args.excel)
groups = load_groups(args.groups_json)
# Skriv evt. advarsler til stderr allerede nu
for w in warnings:
print(f"ADVARSEL: {w}", file=sys.stderr)
# For hver gruppe, udskriv på den ønskede form
for idx, grp in enumerate(groups):
grp_name = grp.get("name", f"Gruppe {idx+1}")
matches = grp.get("matches", [])
if not isinstance(matches, list):
print(f"ADVARSEL: 'matches' i gruppe '{grp_name}' er ikke en liste – springer over.", file=sys.stderr)
continue
# Byg liste i den rækkefølge, de står i JSON
entries: List[str] = []
for person in matches:
person_str = str(person)
key = normalize(person_str)
email = name_to_email.get(key, "")
if not email and key not in name_to_email:
print(f"ADVARSEL: '{person_str}' findes ikke i Excel – udskrives uden e-mail.", file=sys.stderr)
entries.append(format_entry(person_str, email))
# Udskriv linjen
line = f"{grp_name}: " + ", ".join(entries)
print(line)
print()
if __name__ == "__main__":
main()