From 919d71767521cf0dacdb880b273ad0faeb4ed920 Mon Sep 17 00:00:00 2001 From: Abijah Date: Tue, 23 Jun 2026 09:46:47 -0700 Subject: [PATCH] Band Contacts to Google Contacts v1 --- band/band_contacts_to_google_contacts.py | 125 +++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 band/band_contacts_to_google_contacts.py diff --git a/band/band_contacts_to_google_contacts.py b/band/band_contacts_to_google_contacts.py new file mode 100644 index 0000000..5c87ec3 --- /dev/null +++ b/band/band_contacts_to_google_contacts.py @@ -0,0 +1,125 @@ +import csv +import argparse +import datetime +import re + +def get_category(instrument): + """Categorize the instrument into Woodwinds, Brass, Percussion, or Colorguard.""" + inst_lower = instrument.lower() + + if any(x in inst_lower for x in ['flute', 'clarinet', 'sax', 'oboe', 'bassoon', 'piccolo']): + return 'Woodwinds' + if any(x in inst_lower for x in ['trumpet', 'mellophone', 'horn', 'trombone', 'baritone', 'euphonium', 'tuba', 'sousaphone']): + return 'Brass' + if any(x in inst_lower for x in ['percussion', 'snare', 'tenor', 'drum', 'cymbal', 'marimba', 'vibraphone', 'timpani', 'bells', 'electronics']): + return 'Percussion' + if 'guard' in inst_lower or 'color' in inst_lower: + return 'Colorguard' + + return 'Leadership' # Fallback for generic roles like Drum Major + +def format_phone(phone_str): + """Clean and standardize the phone numbers.""" + phone = str(phone_str).strip() + if not phone: + return "" + + # Leave non-US international codes as they are + if phone.startswith('+') and not phone.startswith('+1'): + return phone + + # Standardize US numbers to XXX-XXX-XXXX + digits = re.sub(r'\D', '', phone) + if len(digits) == 10: + return f"{digits[:3]}-{digits[3:6]}-{digits[6:]}" + elif len(digits) == 11 and digits.startswith('1'): + return f"{digits[1:4]}-{digits[4:7]}-{digits[7:]}" + + return phone + +def main(): + parser = argparse.ArgumentParser(description="Convert Band Leadership CSV to Google Contacts format.") + parser.add_argument("input_file", help="Path to the input CSV file") + parser.add_argument("-y", "--year", type=int, default=datetime.datetime.now().year, + help="Target year for labels and grade calculation (defaults to current year)") + parser.add_argument("-o", "--output", default="google_contacts_import.csv", + help="Output file name") + args = parser.parse_args() + + target_year = args.year + + # Google Contacts Headers matching your expected output + google_headers = [ + "First Name", "Middle Name", "Last Name", "Phonetic First Name", "Phonetic Middle Name", + "Phonetic Last Name", "Name Prefix", "Name Suffix", "Nickname", "File As", + "Organization Name", "Organization Title", "Organization Department", "Birthday", "Notes", + "Photo", "Labels", "E-mail 1 - Label", "E-mail 1 - Value", "Phone 1 - Label", + "Phone 1 - Value", "Phone 2 - Label", "Phone 2 - Value", "Phone 3 - Label", "Phone 3 - Value", + "Address 1 - Label", "Address 1 - Formatted", "Address 1 - Street", "Address 1 - City", + "Address 1 - PO Box", "Address 1 - Region", "Address 1 - Postal Code", "Address 1 - Country", + "Address 1 - Extended Address" + ] + + with open(args.input_file, mode='r', encoding='utf-8') as infile, \ + open(args.output, mode='w', encoding='utf-8', newline='') as outfile: + + reader = csv.DictReader(infile) + writer = csv.DictWriter(outfile, fieldnames=google_headers) + writer.writeheader() + + for row in reader: + # Skip empty rows or the trailing blank commas in the source file + name_field = row.get('NAME', '').strip() + if not name_field or name_field == ',': + continue + + # Parse Name ("Last, First") + name_parts = name_field.split(',') + last_name = name_parts[0].strip() if len(name_parts) > 0 else "" + first_name = name_parts[1].strip() if len(name_parts) > 1 else "" + + # Parse Instrument (e.g., "04 - Tenor Sax" -> "Tenor Sax") + section_raw = row.get('SECTION', '').strip() + instrument = section_raw.split('-', 1)[-1].strip() if '-' in section_raw else section_raw + if not instrument: + instrument = "Unknown" + + # Parse Grade and formulate Notes + grade_raw = row.get('GRADE', '').strip() + grade_match = re.match(r"(\d{4})\s*\((.*?)\)", grade_raw) + if grade_match: + grad_year = grade_match.group(1) + grade_level = grade_match.group(2).capitalize() + notes = f"Class of {grad_year}. A {grade_level} in {target_year}" + else: + notes = grade_raw + + # Determine Label + category = get_category(instrument) + label = f"{target_year} {category} ::: Marching Band ::: * myContacts" + + # Build the output row + out_row = {key: "" for key in google_headers} + out_row["First Name"] = first_name + out_row["Last Name"] = f"{last_name} ({instrument})" + out_row["Notes"] = notes + out_row["Labels"] = label + + # Map Email + email = row.get('EMAIL', '').strip() + if email: + out_row["E-mail 1 - Label"] = "Home" + out_row["E-mail 1 - Value"] = email + + # Map Phone + phone = row.get('PHONE', '').strip() + if phone: + out_row["Phone 1 - Label"] = "Mobile" + out_row["Phone 1 - Value"] = format_phone(phone) + + writer.writerow(out_row) + + print(f"Success! Formatted contacts saved to {args.output}") + +if __name__ == '__main__': + main()