Compare commits
4 Commits
ed3a861214
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 82780d4481 | |||
| dad2265871 | |||
| 6fe273d9d2 | |||
| 919d717675 |
@@ -0,0 +1,165 @@
|
|||||||
|
import csv
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def clean_instrument_name(instrument_raw, grade_level):
|
||||||
|
"""Cleans the instrument name based on Percussion, Winds, and Drum Major rules."""
|
||||||
|
# Strip numerical prefix
|
||||||
|
if '-' in instrument_raw:
|
||||||
|
instrument = instrument_raw.split('-', 1)[-1].strip()
|
||||||
|
else:
|
||||||
|
instrument = instrument_raw.strip()
|
||||||
|
|
||||||
|
lower_inst = instrument.lower()
|
||||||
|
|
||||||
|
# Pre-catch Drum Major to apply Junior/Senior title
|
||||||
|
if 'drum major' in lower_inst:
|
||||||
|
if grade_level.lower() == 'senior':
|
||||||
|
return 'Senior Drum Major'
|
||||||
|
else:
|
||||||
|
return 'Junior Drum Major'
|
||||||
|
|
||||||
|
# 1. The Percussion Rule
|
||||||
|
if 'percussion' in lower_inst and '(' in instrument and ')' in instrument:
|
||||||
|
match = re.search(r'\((.*?)\)', instrument)
|
||||||
|
if match:
|
||||||
|
extracted = match.group(1).strip().title()
|
||||||
|
|
||||||
|
# Explicitly only append "Drum(s)" to Snare and Tenors
|
||||||
|
if extracted.lower() == 'snare':
|
||||||
|
return 'Snare Drum'
|
||||||
|
elif extracted.lower() in ['tenor', 'tenors']:
|
||||||
|
return 'Tenor Drums'
|
||||||
|
|
||||||
|
return extracted
|
||||||
|
|
||||||
|
# 2. The Winds/General Rule
|
||||||
|
elif '(' in instrument and ')' in instrument:
|
||||||
|
return re.sub(r'\(.*?\)', '', instrument).strip()
|
||||||
|
|
||||||
|
# 3. Default Rule
|
||||||
|
return instrument
|
||||||
|
|
||||||
|
def get_category(instrument):
|
||||||
|
"""Categorize the instrument into groups, isolating Drum Majors first."""
|
||||||
|
inst_lower = instrument.lower()
|
||||||
|
|
||||||
|
# Catch Drum Majors first so they don't get trapped by the 'drum' keyword in Percussion
|
||||||
|
if 'drum major' in inst_lower:
|
||||||
|
return 'Drum Majors'
|
||||||
|
|
||||||
|
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', 'aux', 'keyboard']):
|
||||||
|
return 'Percussion'
|
||||||
|
if 'guard' in inst_lower or 'color' in inst_lower:
|
||||||
|
return 'Colorguard'
|
||||||
|
|
||||||
|
return 'Leadership' # Fallback for other potential roles
|
||||||
|
|
||||||
|
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)")
|
||||||
|
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:
|
||||||
|
reader = csv.DictReader(infile)
|
||||||
|
# lineterminator='\n' ensures consistent newlines across OS when printing to stdout
|
||||||
|
writer = csv.DictWriter(sys.stdout, fieldnames=google_headers, lineterminator='\n')
|
||||||
|
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 Grade and formulate Notes FIRST (so we have grade_level for instruments)
|
||||||
|
grade_raw = row.get('GRADE', '').strip()
|
||||||
|
grade_match = re.match(r"(\d{4})\s*\((.*?)\)", grade_raw)
|
||||||
|
grade_level = ""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Parse and Clean Instrument
|
||||||
|
section_raw = row.get('SECTION', '').strip()
|
||||||
|
instrument = clean_instrument_name(section_raw, grade_level)
|
||||||
|
if not instrument:
|
||||||
|
instrument = "Unknown"
|
||||||
|
|
||||||
|
# Determine Label
|
||||||
|
category = get_category(instrument)
|
||||||
|
label = f"{target_year} {category} ::: {target_year} 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)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user