""" Generate one Cascades Tucson user-confirmation questionnaire per recipient (Meredith, Ashley, John). Output: three .docx files plus a shared .md source. Format: Section 1 is the roster grouped by department with a [Keep] checkbox, editable name/title, and a Notes line for role description. Sections 2-7 are per-attribute checklists ("Who is a driver?", "Who uses ALIS?", etc.), organized by department so scanning is fast. Section 8 is free text. Usage: python generate-user-questionnaire.py """ from pathlib import Path from docx import Document from docx.shared import Pt, Inches, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH from lxml import etree OUT_DIR = Path(__file__).parent.parent / "docs" / "cloud" / "questionnaires" OUT_DIR.mkdir(parents=True, exist_ok=True) # Word XML namespaces for content control checkboxes (clickable in Word) W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" W14_NS = "http://schemas.microsoft.com/office/word/2010/wordml" # Global counter for unique SDT IDs _sdt_id_counter = [100000] def _next_sdt_id(): _sdt_id_counter[0] += 1 return _sdt_id_counter[0] def add_clickable_checkbox(paragraph): """ Append a Word content-control checkbox to the given paragraph. Renders as an empty box that toggles to a checked box on click in Word 2010+, Word for the web, and Word mobile. Adds a trailing space run for readability. """ sdt_id = _next_sdt_id() xml = f''' \u2610 ''' sdt = etree.fromstring(xml) paragraph._p.append(sdt) # Trailing space after the checkbox so the label has breathing room space_run = paragraph.add_run(" ") return space_run RECIPIENTS = [ ("Meredith Kuhn", "Executive Director"), ("Ashley Jensen", "Assistant Executive Director"), ("John Trozzi", "Maintenance Director"), ] # ----------------------------------------------------------------------------- # Staff roster by department # Each entry: (display_name, title, notes_flag) # notes_flag text is prefilled into the Notes line when helpful # ----------------------------------------------------------------------------- STAFF = { "Administrative": [ ("Meredith Kuhn", "Executive Director", ""), ("Ashley Jensen", "Assistant Executive Director", ""), ("Lauren Hasselman", "Business Office Director", ""), ("Allison Reibschied", "Accounting Assistant", ""), ], "Marketing / Sales": [ ("Megan Hiatt", "Sales Director", "Handles resident intake forms (PHI)"), ("Crystal Rodriguez", "Sales Associate", "Handles resident intake forms (PHI)"), ("Tamra Matthews", "Move-In Coordinator", "Leaving June 2026 — please confirm"), ], "Care, Assisted Living (Nursing / Clinical)": [ ("Lois Lane", "Health Services Director", ""), ("Karen Rossini", "Health Services Manager", ""), ("Britney Thompson", "Memory Care Nurse", "Title says Memory Care but department currently Assisted Living — which is correct?"), ("Veronica Feller", "Care, Assisted Living Aide", ""), ], "Care, Memory Care": [ ("Shelby Trozzi", "Memory Care Director", ""), ("Christine Nyanzunda", "Memory Care Admin Assistant", "Also appears on caregiver list (Sun/Mon AM) — same person, right?"), ], "Resident Services": [ ("Christina DuPras", "Resident Services Director", ""), ("Cathy Kingston", "Receptionist", "Front desk shared PC"), ("Shontiel Nunn", "Receptionist", "Front desk shared PC"), ("Kyla Quick Tiffany", "Receptionist", "NEW — not yet in AD. Spelling correct?"), ("Michelle Shestko", "MC Receptionist", "MC front desk shared PC"), ("Sebastian Leon", "Courtesy Patrol", ""), ("Sheldon Gardfrey", "Courtesy Patrol", ""), ("Ray Rai", "Courtesy Patrol", ""), ], "Life Enrichment": [ ("Susan Hicks", "Life Enrichment Director", ""), ("Sharon Edwards", "Life Enrichment Assistant", ""), ], "Culinary": [ ("JD Martin", "Culinary Director", ""), ("Ramon Castaneda", "Kitchen Manager", ""), ("Alyssa Brooks", "Dining Manager", ""), ], "Maintenance": [ ("John Trozzi", "Maintenance Director", ""), ("Matt Brooks", "Memory Care Receptionist", "HR record shows department = Maintenance. Which is correct — Maintenance or Memory Care?"), ], "Housekeeping": [ ("Lupe Sanchez", "Housekeeping Director", "AKA Guadalupe Sanchez"), ], "Transportation": [ ("Richard Adams", "Driver", ""), ("Julian Crim", "Driver", ""), ("Christopher Holick", "Driver", ""), ], } # ----------------------------------------------------------------------------- # Caregivers by shift pattern (39 total, none currently in AD/M365) # Each entry: (display_name, role, location, phone_number, flag_note) # ----------------------------------------------------------------------------- CAREGIVERS = { "Tuesday-Saturday (15)": [ ("Thelma Abainza", "Caregiver", "Tower", "520-867-2579", ""), ("Niel Castro", "MedTech / CCG", "Tower", "520-697-4644", ""), ("Espe Esperance", "MedTech", "Tower", "520-788-9558", ""), ("Barbara Johnson", "Caregiver", "Tower", "520-204-3449", ""), ("Kasey Flores", "Caregiver", "Memory Care", "520-250-1451", ""), ("Richard Flores", "Caregiver", "Memory Care", "520-873-7727", ""), ("Marie Kastner", "Caregiver", "Memory Care", "714-576-9858", ""), ("Bella Mendoza", "Caregiver", "Memory Care", "520-358-2000", ""), ("Rosa Morales", "MedTech", "Memory Care", "312-213-8780", ""), ("Sandra Padilla", "MedTech / CCG", "Tower", "520-585-3317", ""), ("Polett Pinazavala", "MedTech", "Memory Care", "520-449-5533", "Please confirm spelling"), ("Whisper Reed", "MedTech", "Tower (overnight)", "520-312-7575", ""), ("Patricia Sandoval-Beck", "MedTech", "Tower", "520-343-8093", "Hyphenated last name correct?"), ("Charity Sika", "Caregiver", "Memory Care", "623-251-8032", ""), ("Ederick Yuzon", "Caregiver", "Tower", "520-603-8816", "Please confirm spelling"), ], "Sunday-Thursday (10)": [ ("Juan Andrade", "Caregiver", "Memory Care", "520-528-4078", ""), ("Jahmeka Clarke", "MedTech", "Memory Care", "520-649-7034", ""), ("Karina Aziakpo", "MedTech / CCG", "Memory Care (overnight)", "520-392-6859", ""), ("Jinnelle Dittbenner", "Caregiver", "Tower", "520-499-9996", ""), ("Christine Nyanzunda (AM Sun/Mon only)", "MedTech", "Memory Care", "520-304-4251", "SAME person as the Memory Care Admin Assistant? (One account, not two)"), ("Agnes McFerren", "Caregiver", "Tower", "520-406-3063", ""), ("Samuel Ramirez", "Caregiver", "Tower", "520-488-5798", ""), ("Erica Sanchez", "Caregiver", "Memory Care", "520-528-3387", ""), ("Katrina Wyzykowski", "MedTech", "Memory Care", "520-347-1448", ""), ("Corey Tate", "Caregiver (no MedTech)", "Tower (NOC)", "520-535-7821", ""), ], "Friday-Monday / weekend (5)": [ ("Ashli Atwood", "MedTech / CCG", "Memory Care (overnight)", "715-200-1295", ""), ("Cole Johnson", "MedTech", "Tower", "818-970-0890", ""), ("Roseline Cooper", "Caregiver", "Memory Care (overnight)", "520-278-6817", ""), ("Monique Lopez", "Caregiver (Fri+Sat doubles)", "Tower", "520-596-0969", ""), ("Gloria Williford", "MedTech (Fri+Sat doubles)", "Memory Care", "928-551-1682", ""), ], "Thursday-Monday (3)": [ ("Sarah Carroll", "Caregiver", "Tower", "520-409-2341", ""), ("Luke Hogan", "Caregiver", "Tower", "520-312-0141", ""), ("Gina Williams", "Caregiver", "Tower", "520-612-5075", ""), ], "Split / partial-week (3)": [ ("Jen Higdon", "Caregiver", "Tower (M/W/F AM)", "520-730-3548", ""), ("Mary Kariuki", "Caregiver", "Tower (Sat-Mon + Wed PM)", "520-309-1247", ""), ("CeCe Lassey", "Caregiver", "Tower (Sun/Mon doubles + Tue PM)", "520-248-5982", ""), ], "Sunday & Monday only (1)": [ ("Paty Doran", "MedTech / CCG", "Tower", "520-591-7368", "Paty, Patti, or Patricia? Please confirm"), ], "PRN / float (2)": [ ("Ezekiel Huerta", "Caregiver (PRN)", "Tower", "520-591-6113", ""), ("Maia Baker", "MedTech (PRN)", "Memory Care", "TBD", "On secondary sheet only — still employed?"), ], } # Flat list of ALL people, grouped by section, for the attribute checklists def all_people_by_group(): groups = [] for dept, people in STAFF.items(): names = [p[0] for p in people] groups.append((dept, names)) cg_all = [] for _, people in CAREGIVERS.items(): cg_all.extend(p[0] for p in people) groups.append(("Caregivers (all 39)", cg_all)) return groups # ----------------------------------------------------------------------------- # Document helpers # ----------------------------------------------------------------------------- def add_heading(doc, text, level=1): h = doc.add_heading(text, level=level) return h def add_para(doc, text, bold=False, size=11, italic=False, color=None): p = doc.add_paragraph() run = p.add_run(text) run.font.size = Pt(size) run.bold = bold run.italic = italic if color: run.font.color.rgb = color return p def add_checkbox_line(doc, text, indent=0.25): p = doc.add_paragraph() p.paragraph_format.left_indent = Inches(indent) p.paragraph_format.space_after = Pt(2) run = p.add_run(f"{CHECKBOX} {text}") run.font.size = Pt(11) return p def add_notes_line(doc, label="Notes: ", indent=0.5): p = doc.add_paragraph() p.paragraph_format.left_indent = Inches(indent) p.paragraph_format.space_after = Pt(6) run = p.add_run(label + "_" * 80) run.font.size = Pt(10) run.font.color.rgb = RGBColor(0x60, 0x60, 0x60) return p def add_blank_line(doc): p = doc.add_paragraph() p.paragraph_format.space_after = Pt(0) return p def add_separator(doc): p = doc.add_paragraph() run = p.add_run("_" * 90) run.font.color.rgb = RGBColor(0xCC, 0xCC, 0xCC) p.alignment = WD_ALIGN_PARAGRAPH.CENTER def add_attribute_question(doc, question_num, question_text, helper_text, include_caregivers=True): """A question that lists names by department with a checkbox to mark yes.""" add_heading(doc, f"{question_num}. {question_text}", level=2) if helper_text: add_para(doc, helper_text, italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50)) add_blank_line(doc) for dept, people in STAFF.items(): add_para(doc, dept, bold=True, size=11) for name, title, _ in people: add_checkbox_line(doc, f"{name} — {title}", indent=0.35) add_blank_line(doc) if include_caregivers: add_para(doc, "Caregivers (none in M365 yet)", bold=True, size=11) for group_name, people in CAREGIVERS.items(): add_para(doc, f" {group_name}", italic=True, size=10) for name, role, location, phone, _ in people: add_checkbox_line(doc, f"{name} — {role} ({location})", indent=0.5) add_blank_line(doc) # ----------------------------------------------------------------------------- # Document builder # ----------------------------------------------------------------------------- def build_doc(recipient_name, recipient_title): doc = Document() style = doc.styles["Normal"] style.font.name = "Calibri" style.font.size = Pt(11) # ---- Title ---- title = doc.add_heading("Cascades of Tucson — User + Access Confirmation", level=0) p = doc.add_paragraph() r = p.add_run(f"Prepared for: {recipient_name}, {recipient_title}") r.bold = True r.font.size = Pt(12) p = doc.add_paragraph() r = p.add_run("Prepared by: Howard Enos, Computer Guru \u2022 Date: 2026-04-18") r.font.size = Pt(10) r.italic = True add_separator(doc) # ---- Intro ---- add_heading(doc, "Why we need this", level=1) add_para( doc, "Before we purchase the Microsoft 365 Business Premium licenses, create the 39 new " "caregiver accounts, and configure the security policies that lock down the shared " "phones and PHI access, I need to know four things about every person on your staff:", ) add_checkbox_line(doc, "Is their name and department correct?", indent=0.3) add_checkbox_line(doc, "How do they use technology day-to-day (company phone, PC, both)?", indent=0.3) add_checkbox_line(doc, "Should they be allowed to sign in from outside the building?", indent=0.3) add_checkbox_line(doc, "Which systems / mailboxes / apps do they actually need?", indent=0.3) add_para( doc, "Please answer the sections below at your own pace. Mark a box by double-clicking the " "\u2610 and replacing it with an X, or simply type an X in front of the name. Cross out " "anyone who should be removed. Write additions where it says \"Add anyone missing.\" " "Leave Comments / Notes where helpful \u2014 the more context, the better the lockdown.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_para( doc, "A quick note about ALIS: ALIS is the resident records website. When we tie it to " "Microsoft, users who log into their Microsoft account will automatically be signed " "into ALIS \u2014 no second username/password or second MFA prompt. That only works if " "we know who needs it (Section 5).", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) doc.add_page_break() # ---- Section 1: Roster ---- add_heading(doc, "Section 1 \u2014 The Staff Roster", level=1) add_para( doc, "Below is every person we currently have on file, organized by department. For " "each person: leave the Keep box checked if they are a current employee at that title, " "or uncheck and strike through anyone who should be removed. Correct spellings " "inline. Add a short note on the Notes line describing what they actually do day-to-day, " "especially anything involving resident information (PHI), medication, or outside-network " "access. Blank rows are at the bottom of each department for additions.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_blank_line(doc) for dept, people in STAFF.items(): add_heading(doc, dept, level=2) for name, title, flag in people: p = doc.add_paragraph() p.paragraph_format.space_after = Pt(2) p.paragraph_format.left_indent = Inches(0.25) r = p.add_run(f"{CHECKBOX} Keep ") r.bold = True r = p.add_run(f"{name} \u2014 {title}") if flag: p2 = doc.add_paragraph() p2.paragraph_format.left_indent = Inches(0.5) p2.paragraph_format.space_after = Pt(2) r = p2.add_run(f"\u203A {flag}") r.italic = True r.font.size = Pt(10) r.font.color.rgb = RGBColor(0xB0, 0x40, 0x00) add_notes_line(doc, " Notes: ") # Three blank add-row lines p = doc.add_paragraph() p.paragraph_format.left_indent = Inches(0.25) r = p.add_run("Add anyone missing from this department:") r.italic = True r.font.size = Pt(10) r.font.color.rgb = RGBColor(0x40, 0x40, 0x80) for _ in range(3): p = doc.add_paragraph() p.paragraph_format.left_indent = Inches(0.25) p.paragraph_format.space_after = Pt(2) r = p.add_run(f"{CHECKBOX} Add Name: ________________________ Title: ________________________") r.font.size = Pt(10) add_notes_line(doc, " Notes: ") add_blank_line(doc) # ---- Caregivers section ---- doc.add_page_break() add_heading(doc, "Section 1b \u2014 Caregivers (NEW \u2014 no current accounts)", level=1) add_para( doc, "These are the 39 staff currently using shared workstation logins. None has an email " "account or a Microsoft identity yet. They are the reason we are deploying the 25 " "shared Android phones. Please confirm each name + role, correct any spelling, and " "add anyone missing. Default assumption for every caregiver: phone-only, no outside " "access (locked to Cascades network + managed shared phone only). Flag exceptions " "in the Notes line.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_blank_line(doc) for group_name, people in CAREGIVERS.items(): add_heading(doc, group_name, level=2) for name, role, location, phone, flag in people: p = doc.add_paragraph() p.paragraph_format.space_after = Pt(2) p.paragraph_format.left_indent = Inches(0.25) r = p.add_run(f"{CHECKBOX} Keep ") r.bold = True r = p.add_run(f"{name} \u2014 {role} ({location}) [{phone}]") if flag: p2 = doc.add_paragraph() p2.paragraph_format.left_indent = Inches(0.5) p2.paragraph_format.space_after = Pt(2) r = p2.add_run(f"\u203A {flag}") r.italic = True r.font.size = Pt(10) r.font.color.rgb = RGBColor(0xB0, 0x40, 0x00) add_notes_line(doc, " Notes: ") add_blank_line(doc) # Add-new-caregiver block p = doc.add_paragraph() r = p.add_run("Add any caregivers missing from this list:") r.italic = True r.font.size = Pt(10) r.font.color.rgb = RGBColor(0x40, 0x40, 0x80) for _ in range(5): p = doc.add_paragraph() p.paragraph_format.left_indent = Inches(0.25) p.paragraph_format.space_after = Pt(2) r = p.add_run(f"{CHECKBOX} Add Name: _______________________ Role: _______________________ Phone: ______________") r.font.size = Pt(10) # ---- Attribute-based questions ---- doc.add_page_break() add_heading(doc, "Section 2 \u2014 Roles (check everyone who fits)", level=1) add_attribute_question( doc, "2a", "Who is a Driver? (Transportation / residents to appointments)", "Drivers typically use a company phone for dispatch and communication \u2014 usually no personal PC.", include_caregivers=False, ) add_attribute_question( doc, "2b", "Who is a Nurse, MedTech, or Clinical Staff?", "People in this group see resident medical information (PHI) and are the main reason we care about HIPAA audit logs and ALIS single sign-on.", include_caregivers=True, ) add_attribute_question( doc, "2c", "Who handles resident intake paperwork, leases, or medical forms by email?", "These people receive PHI via email from doctors, families, or facilities. They need the strongest anti-phishing and DLP (data loss prevention) policies.", include_caregivers=False, ) # ---- Section 3: Access questions ---- doc.add_page_break() add_heading(doc, "Section 3 \u2014 How They Use Technology", level=1) add_para( doc, "For each person, please tell us whether they sit at a company PC, use a company " "phone on shift, or both.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_attribute_question( doc, "3a", "Who uses a COMPANY PC / DESKTOP?", "Anyone who signs into a Windows computer at Cascades. Includes shared front-desk PCs.", include_caregivers=True, ) add_attribute_question( doc, "3b", "Who uses a COMPANY PHONE (the 25 shared Android phones)?", "Anyone who will pick up a shared phone at start of shift and use it for work (medication passes, resident records, email, rounds).", include_caregivers=True, ) add_attribute_question( doc, "3c", "Who uses a COMPANY TABLET / iPad?", "Specifically the 9 kitchen iPads + any others. Likely Culinary staff.", include_caregivers=True, ) # ---- Section 4: Outside access ---- doc.add_page_break() add_heading(doc, "Section 4 \u2014 Sign-In From Outside the Building", level=1) add_attribute_question( doc, "4a", "Who should be allowed to sign in from OUTSIDE the building?", "Home office, travel, personal cell, hotel, etc. Anyone NOT checked will be locked to " "the Cascades network and managed devices only \u2014 which is what HIPAA and " "Conditional Access prefer by default.", include_caregivers=True, ) add_attribute_question( doc, "4b", "Who should be allowed to check email on their PERSONAL cell phone?", "Separate question: some people may need outside access on a company laptop but NOT on a personal phone (HIPAA risk). Check only the people who genuinely need email on a personal device.", include_caregivers=True, ) # ---- Section 5: ALIS ---- doc.add_page_break() add_heading(doc, "Section 5 \u2014 ALIS (Resident Records)", level=1) add_para( doc, "ALIS is the resident records website. If we connect ALIS to Microsoft, users who " "sign into their Microsoft account will be automatically signed into ALIS \u2014 no " "second username / password, no second MFA prompt, no separate login. This is also " "where the per-person audit log lives (who opened what chart, when, from which device) " "which is the HIPAA story.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_para( doc, "We only want to connect ALIS for people who actually use it, so please check " "carefully.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_attribute_question( doc, "5a", "Who LOGS INTO ALIS today (resident charts, care plans, medication)?", "", include_caregivers=True, ) add_attribute_question( doc, "5b", "Who should be able to access ALIS from OUTSIDE the building?", "Only matters for the people checked in 5a. Example: an on-call nurse charting from home.", include_caregivers=True, ) # ---- Section 6: Shared mailboxes ---- doc.add_page_break() add_heading(doc, "Section 6 \u2014 Shared Mailboxes (Accounting@, Frontdesk@, etc.)", level=1) add_para( doc, "These are department mailboxes multiple people can access from their own sign-in. " "For each mailbox below, check everyone who should receive and send email through it. " "Leaving a mailbox blank means nobody needs it and we will decommission it.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_blank_line(doc) shared_mailboxes = [ ("accounting@cascadestucson.com", "Accounting / AR / AP"), ("accountingassistant@cascadestucson.com", "Accounting assistant desk"), ("boadmin@cascadestucson.com", "Business Office admin"), ("frontdesk@cascadestucson.com", "Main front desk (Tower)"), ("memcarereceptionist@cascadestucson.com", "Memory Care front desk"), ("hr@cascadestucson.com", "HR / employee matters"), ("nurse@cascadestucson.com", "Clinical nursing desk"), ("medtech@cascadestucson.com", "MedTech medication pass"), ("transportation@cascadestucson.com", "Drivers / resident transport"), ("security@cascadestucson.com", "Security / courtesy patrol"), ("courtesypatrol@cascadestucson.com", "Courtesy Patrol (new \u2014 please confirm it\u2019s needed)"), ("training@cascadestucson.com", "Training"), ("sales@cascadestucson.com", "Sales inquiries"), ("fax@cascadestucson.com", "Fax-to-email"), ] for addr, desc in shared_mailboxes: add_heading(doc, f"{addr} \u2014 {desc}", level=3) for dept, people in STAFF.items(): add_para(doc, dept, bold=True, size=10) for name, title, _ in people: add_checkbox_line(doc, f"{name} \u2014 {title}", indent=0.35) add_blank_line(doc) # ---- Section 7: Odds and ends ---- doc.add_page_break() add_heading(doc, "Section 7 \u2014 Specific Questions", level=1) add_heading(doc, "7a. Departments that may need a re-label", level=2) add_para(doc, "Please mark the correct department for each:") add_checkbox_line(doc, "Britney Thompson \u2014 currently \"Care, Assisted Living,\" title says Memory Care Nurse. Correct department is:", indent=0.35) add_notes_line(doc, " ") add_checkbox_line(doc, "Matt Brooks \u2014 HR record says Maintenance, title says Memory Care Receptionist. Correct department is:", indent=0.35) add_notes_line(doc, " ") add_checkbox_line(doc, "Christine Nyanzunda \u2014 Memory Care Admin Assistant AND appears on caregiver shift list (Sun/Mon AM). One account or two?", indent=0.35) add_notes_line(doc, " ") add_blank_line(doc) add_heading(doc, "7b. Spelling confirmations (caregivers)", level=2) for name, reason in [ ("Polett Pinazavala", "unusual spelling"), ("Patricia Sandoval-Beck", "hyphenated last name"), ("Ederick Yuzon", "unusual spelling"), ("Paty Doran", "Paty / Patti / Patricia"), ("Maia Baker", "only on secondary sheet \u2014 still employed?"), ("Kyla Quick Tiffany", "three names or hyphenated?"), ]: add_checkbox_line(doc, f"{name} \u2014 {reason} Correct spelling: ________________________", indent=0.35) add_blank_line(doc) add_heading(doc, "7c. Departures / upcoming changes", level=2) add_para(doc, "Anyone leaving, transferring, or changing role in the next 90 days?", italic=True, size=10) for _ in range(5): add_notes_line(doc, "") # ---- Section 8: Free text ---- doc.add_page_break() add_heading(doc, "Section 8 \u2014 Anything Else", level=1) add_para( doc, "Anything we should know about a specific person or situation? Unusual access needs, " "contractors, people who share a phone / PC, dual-role employees, anyone who handles " "legal or compliance records, etc. Write freely below.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_blank_line(doc) for _ in range(20): p = doc.add_paragraph() p.paragraph_format.space_after = Pt(2) r = p.add_run("_" * 100) r.font.color.rgb = RGBColor(0xCC, 0xCC, 0xCC) # ---- Closing ---- doc.add_page_break() add_heading(doc, "Thank you", level=1) add_para( doc, f"{recipient_name.split()[0]} \u2014 thank you for taking the time on this. Once I have " "your answers (and Meredith\u2019s and Ashley\u2019s / John\u2019s), I will merge the " "three responses into one final list and come back with the exact license count, the " "exact accounts we are creating, and the exact Conditional Access rules before I change " "anything in your tenant. Nothing gets purchased or changed until you\u2019ve seen the " "final plan.", ) add_para( doc, "Easiest way to return this: save and email back to howard@azcomputerguru.com. " "You can also drop a copy on our shared OneDrive or Teams folder and I\u2019ll pick it up.", italic=True, size=10, color=RGBColor(0x50, 0x50, 0x50), ) add_para( doc, "\u2014 Howard", bold=True, ) return doc # ----------------------------------------------------------------------------- # Generate files # ----------------------------------------------------------------------------- if __name__ == "__main__": for name, title in RECIPIENTS: doc = build_doc(name, title) first = name.split()[0] filename = OUT_DIR / f"cascades-user-access-questionnaire-{first}.docx" doc.save(str(filename)) print(f"[OK] {filename}") print(f"\nAll files written to: {OUT_DIR}")