Service team handoff
" + "Review this draft with the people named by role. " + "It records a plan; it does not provide interpretation.
" + + "".join(cards) + "diff --git a/_config.yml b/_config.yml index 2e2da21..902aac0 100644 --- a/_config.yml +++ b/_config.yml @@ -170,6 +170,8 @@ authors: name: Steely stmaryscoltsneck: name: stmaryscoltsneck + szj2ys: + name: szj2ys trevor-james: name: trevor.james valerie-shpak: diff --git a/_posts/2026-10-20-offline-service-handoff.md b/_posts/2026-10-20-offline-service-handoff.md new file mode 100644 index 0000000..8a8ba41 --- /dev/null +++ b/_posts/2026-10-20-offline-service-handoff.md @@ -0,0 +1,165 @@ +--- +layout: post +title: "Turn a CSV service plan into an offline handoff sheet" +date: 2026-10-20 00:00:00 +0000 +categories: [tutorials, parish-technology] +author: szj2ys +comments: true +--- + +A service plan is more useful when the people carrying it out can read the same version. A parish technology volunteer can make a small printed handoff sheet from a spreadsheet without setting up another account or website. The example below uses Python's standard library to turn four CSV columns into a local HTML file. Open that file in a browser, review it with the team, and print it. + +The sheet records who leads each segment, what language support has been agreed, and what to do if the plan changes. It does not translate speech, confirm someone's availability, or decide liturgical practice. Those remain conversations with the people responsible for the service. + +## Start with a small, fictional plan + +Create a folder for the example. Save the following as `plan.csv`, using UTF-8 text. These three rows are fictional planning examples, not a report of parish use. Replace them with your team's agreed arrangements before using the output. + +On a narrow screen, scroll sideways inside a code block to read long lines. Copy the complete block when saving a file. + +```csv +segment,lead,language_support,fallback +Arrival,Welcome volunteer,Ask each visitor which language would help,Use the agreed printed welcome sheet if the display is unavailable +Announcements,Service leader,Allow the agreed interpreter to finish before the next item,Pause and agree the next step if the interpreter is absent +After the service,Welcome volunteer,Offer the planned conversation with the language-support volunteer,Explain honestly which support is available today +``` + +You can also prepare the four columns in a spreadsheet and export **CSV UTF-8**, with commas as separators. Keep the header names and their order exactly as shown. A cell containing a comma must be quoted in the CSV; a spreadsheet exporter normally handles that for you. This script deliberately rejects other layouts instead of guessing how they map to the sheet. + +Use role names where they are sufficient. A handoff for volunteers does not need an attendee list or private pastoral information. The example has no names, contact details, or accounts. + +## Create the converter + +Save this complete program in the same folder as `print_plan.py`. It uses a local Python 3 installation and has no packages to install. The program reads the CSV, checks its shape, then writes a new file. It refuses to overwrite an existing output file. + +```python +"""Create an offline handoff sheet from a four-column UTF-8 CSV file.""" +import argparse +import csv +from html import escape +from pathlib import Path + +COLUMNS = ["segment", "lead", "language_support", "fallback"] +STYLE = """ +* { box-sizing: border-box; } +body { max-width: 48rem; margin: 0 auto; padding: 1.25rem; + color: #111; background: white; font: 18px/1.5 Arial, sans-serif; } +h1 { font-size: 1.6rem; line-height: 1.2; } +h2 { margin: 0 0 .7rem; font-size: 1.15rem; } +section { border: 1px solid #555; padding: 1rem; margin: 1rem 0; } +dt { font-weight: bold; margin-top: .5rem; } +dd { margin: 0; } +h1, h2, dd { overflow-wrap: anywhere; } +@page { margin: 15mm; } +@media print { + body { max-width: none; padding: 0; font-size: 12pt; } + section { break-inside: avoid; } +} +""" + + +def read_plan(source): + with source.open(encoding="utf-8-sig", newline="") as handle: + reader = csv.DictReader(handle, strict=True) + if reader.fieldnames != COLUMNS: + raise ValueError("Header must be: " + ",".join(COLUMNS)) + rows = [] + for row in reader: + if None in row or any(value is None for value in row.values()): + raise ValueError(f"CSV line {reader.line_num}: expected four cells") + row = {key: value.strip() for key, value in row.items()} + if any(not value for value in row.values()): + raise ValueError(f"CSV line {reader.line_num}: fill every cell") + rows.append(row) + if not rows: + raise ValueError("Add at least one plan row below the header") + return rows + + +def render(rows): + cards = [] + for number, row in enumerate(rows, 1): + details = "".join( + f'
Review this draft with the people named by role. " + "It records a plan; it does not provide interpretation.
" + + "".join(cards) + "