import csv import smtplib from email.mime.text import MIMEText from random import shuffle # 1. Load participants from CSV, now including id and last_match_id def load_participants(filename): participants = [] with open(filename, 'r') as file: reader = csv.DictReader(file) for row in reader: participants.append({ 'id': int(row['id']), 'name': row['name'], 'household': row['household'], 'email': row['email'], 'last_match_id': int(row['last_match_id']) if row['last_match_id'] else None }) return participants # 2. Backtracking algorithm to find valid matches def find_matches(givers, receivers, matches, used_receivers): if len(matches) == len(givers): return True # Found a complete set of matches giver = givers[len(matches)] # Process the next giver for receiver in receivers: # Check constraints: different person, different household, no last-year match, no reciprocal match if receiver['id'] not in used_receivers and \ giver['id'] != receiver['id'] and \ giver['household'] != receiver['household'] and \ giver['last_match_id'] != receiver['id'] and \ matches.get(receiver['id']) != giver['id']: # Assign receiver to giver matches[giver['id']] = receiver used_receivers.add(receiver['id']) # Recurse with the next giver if find_matches(givers, receivers, matches, used_receivers): return True # Solution found # Backtrack: remove the match and try another receiver del matches[giver['id']] used_receivers.remove(receiver['id']) return False # No valid match found, backtrack # 3. Wrapper function to initialize and run the backtracking def generate_complete_matches(participants): givers = participants.copy() receivers = participants.copy() shuffle(givers) # Shuffle for variety shuffle(receivers) # Shuffle for variety matches = {} used_receivers = set() # Start backtracking if find_matches(givers, receivers, matches, used_receivers): return matches else: print("Could not find a complete set of matches.") return None # 4. Confirm matches interactively def confirm_matches(participants, matches): print("Proposed Matches:") for giver_id, receiver in matches.items(): giver = next(p for p in participants if p['id'] == giver_id) print(f"{giver['name']} ({giver['email']}) will give a gift to {receiver['name']} ({receiver['email']})") response = input("Approve this match? (yes/no): ").strip().lower() if response != 'yes': print("Match not approved. Restarting the matching process...\n") return False # Signal to restart if any match is disapproved return True # 5. Final confirmation to save matches def final_confirmation(participants, matches): print("\nFinal Match List:") for giver_id, receiver in matches.items(): giver = next(p for p in participants if p['id'] == giver_id) print(f"{giver['name']} will give a gift to {receiver['name']}") # Final save prompt response = input("\nDo you want to save this match list? (yes to save, no to restart): ").strip().lower() return response == 'yes' # 6. Update last matches in the CSV file def update_last_match_in_file(participants, matches, filename): for giver_id, receiver in matches.items(): for participant in participants: if participant['id'] == giver_id: participant['last_match_id'] = receiver['id'] with open(filename, 'w', newline='') as file: fieldnames = ['id', 'name', 'household', 'email', 'last_match_id'] writer = csv.DictWriter(file, fieldnames=fieldnames) writer.writeheader() for participant in participants: writer.writerow({ 'id': participant['id'], 'name': participant['name'], 'household': participant['household'], 'email': participant['email'], 'last_match_id': participant['last_match_id'] }) print("Matches saved to 'participants.csv'.") # 7. Email sending function # 7. Email sending function with improved formatting def send_emails(matches, participants, sender_email, smtp_server, smtp_port, login, password): for giver_id, receiver in matches.items(): giver = next(p for p in participants if p['id'] == giver_id) # Compose email subject = "Dein Wichtel-Match für Weihnachten 2025" message = ( f"Hallo liebe/r {giver['name']},\n\n" f"Dein Match für das diesjährige Wichteln ist {receiver['name']}.\n\n" "Grüße,\n" "Das Wichtelsystem\n\n" "--Bitte beachtet, dass für ungefähr 25€ gewichtelt wird--" ) # Prepare email msg = MIMEText(message, 'plain') msg['Subject'] = subject msg['From'] = sender_email msg['To'] = giver['email'] # Send email try: with smtplib.SMTP(smtp_server, smtp_port) as server: server.starttls() # Secure the connection server.login(login, password) server.sendmail(sender_email, giver['email'], msg.as_string()) print(f"Email sent to {giver['name']} at {giver['email']}") except Exception as e: print(f"Error sending email to {giver['name']} ({giver['email']}): {e}") # 8. Main function to run the match process def main(): participants = load_participants('participants.csv') while True: matches = generate_complete_matches(participants) if matches and confirm_matches(participants, matches): if final_confirmation(participants, matches): update_last_match_in_file(participants, matches, 'participants.csv') # Send emails after saving the matches sender_email = "weihnachtswichteln0@gmail.com" # Update to your email smtp_server = "smtp.gmail.com" # Update to your SMTP server smtp_port = 587 # Typically 587 for TLS login = "weihnachtswichteln0@gmail.com" # SMTP login password = "" # SMTP password send_emails(matches, participants, sender_email, smtp_server, smtp_port, login, password) break else: print("\nRestarting the matching process...\n") else: print("No complete match found, restarting...\n") if __name__ == '__main__': main()