rpa-bot-handoff: email To the developer with their manager in CC, and hand ops a PDF

- Section 5 of the SOP now names a real "To" (the developer) and "CC"
  (the developer's manager, not the operator's), and the email block to
  copy starts with To: and CC: lines. The skill asks for both addresses.
- to_docx.py --pdf also writes the PDF (Word through PowerShell on Windows,
  LibreOffice elsewhere) and prints the real page count against MAX_PAGES.
  The PDF is what ops gets.
- Tebra reference and README updated; new test for the To/CC lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-28 17:07:30 +05:30
1 parent 717f2c40c5
commit 0188de7403
6 files changed
+62 -22

No files matched your search

@@ -1,6 +1,6 @@
# {{PROJECT_NAME}} Handover SOP
Version {{VERSION}} · {{DATE}} · Problems: email {{EMAIL_GROUP}} (section 5)
Version {{VERSION}} · {{DATE}} · Problems: email {{DEV_EMAIL}}, CC {{MANAGER_EMAIL}} (section 5)
{{ONE_LINE: what the bot does, from which app or portal, and what it produces}}
@@ -58,9 +58,11 @@ Version {{VERSION}} · {{DATE}} · Problems: email {{EMAIL_GROUP}} (section 5)
## 5. Email us
Email **{{EMAIL_GROUP}}** when section 4 says "email us", or when the same error comes back after one re-run. Answer: {{RESPONSE_TIME}}.
Email **{{DEV_EMAIL}}** with **{{MANAGER_NAME}}** ({{MANAGER_EMAIL}}) in CC when section 4 says "email us", or when the same error comes back after one re-run. Answer: {{RESPONSE_TIME}}.
```
To: {{DEV_EMAIL}}
CC: {{MANAGER_EMAIL}}
Subject: {{PROJECT_NAME}} - E<number> - <date>
Run started: <time from the screen>
@@ -1,12 +1,12 @@
# Worked example: Tebra Auto Post (v2.5.3), scrubbed
What the SOP looks like for a real bot: about 2.5 pages. Client names, IDs, amounts, credentials and server addresses are removed, and the email address is an example. **Copy the length and the plain wording, not the content.**
What the SOP looks like for a real bot: about 2.5 pages. Client names, IDs, amounts, credentials and server addresses are removed, and the email addresses are examples. **Copy the length and the plain wording, not the content.**
---
# Tebra Auto Post Handover SOP
Version 2.5.3 · 28 Oct 2026 · Problems: email rpa-support@example.com (section 5)
Version 2.5.3 · 28 Oct 2026 · Problems: email dev@example.com, CC manager@example.com (section 5)
Posts insurance ERA payments into the Tebra desktop app, company by company and practice by practice, from the input Excel, then turns each ERA into a PDF.
@@ -83,9 +83,11 @@ Posts insurance ERA payments into the Tebra desktop app, company by company and
## 5. Email us
Email **rpa-support@example.com** when section 4 says "email us", or when the same error comes back after one Resume. Answer: same working day.
Email **dev@example.com** with **the RPA manager** (manager@example.com) in CC when section 4 says "email us", or when the same error comes back after one Resume. Answer: same working day.
```
To: dev@example.com
CC: manager@example.com
Subject: Tebra Auto Post - E<number> - <date>
Batch ID: <from the Dashboard>
+33 -3
View File
@@ -1,16 +1,18 @@
"""Turn the Handover SOP (HANDOVER-SOP.md) into a Word document ops can open, print and pass around.
"""Turn the Handover SOP (HANDOVER-SOP.md) into a Word document, and a PDF, ops can open, print and pass around.
python to_docx.py HANDOVER-SOP.md # writes HANDOVER-SOP.docx next to it
python to_docx.py HANDOVER-SOP.md -o "<Project Name> Handover SOP.docx"
python to_docx.py HANDOVER-SOP.md -o "<Project Name> Handover SOP.docx" --pdf # + the .pdf next to it
python to_docx.py --selfcheck
Prints the word count: the SOP must stay at or under MAX_WORDS (about 4 pages).
Prints the word count (at most MAX_WORDS) and, with --pdf, the real page count (at most MAX_PAGES).
The PDF is made by Word on Windows (through PowerShell) or by LibreOffice (soffice) elsewhere.
Handles what the template uses: # headings, paragraphs, - bullets, - [ ] checkboxes, 1. steps,
| tables |, ``` blocks, **bold**, *italic* and `code`. Needs python-docx (pip install python-docx).
"""
import argparse
import re
import subprocess
import sys
import tempfile
from pathlib import Path
@@ -19,6 +21,7 @@ from docx import Document
from docx.shared import Cm, Pt
MAX_WORDS = 1400 # about 4 pages with the margins and table font below
MAX_PAGES = 4
INLINE = re.compile(r"(\*\*[^*]+\*\*|\*[^*\s][^*]*\*|`[^`]+`)")
@@ -96,6 +99,26 @@ def words(md_text):
return len(re.findall(r"[A-Za-z0-9][\w'’.-]*", re.sub(r"^\|[\s:|-]+\|\s*$", "", md_text, flags=re.M)))
def to_pdf(docx_path):
"""Write the PDF next to the docx. Returns (pdf path, page count or None); pdf path is None on failure."""
docx_path = Path(docx_path).resolve()
pdf = docx_path.with_suffix(".pdf")
pages = None
if sys.platform == "win32":
q = lambda p: str(p).replace("'", "''") # PowerShell single-quote escape
ps = (f"$w = New-Object -ComObject Word.Application; $w.Visible = $false; "
f"$d = $w.Documents.Open('{q(docx_path)}', $false, $true); "
f"Write-Output ('pages=' + $d.ComputeStatistics(2)); $d.SaveAs2('{q(pdf)}', 17); "
f"try {{ $d.Close(0) }} catch {{}}; try {{ $w.Quit() }} catch {{}}")
r = subprocess.run(["powershell", "-NoProfile", "-Command", ps], capture_output=True, text=True)
if m := re.search(r"pages=(\d+)", r.stdout):
pages = int(m[1])
else:
subprocess.run(["soffice", "--headless", "--convert-to", "pdf", "--outdir", str(pdf.parent), str(docx_path)],
capture_output=True)
return (pdf if pdf.exists() else None), pages
def selfcheck():
example = Path(__file__).parent / "reference" / "tebra-auto-post.md"
sop = "# " + example.read_text(encoding="utf-8").split("\n---\n", 1)[1].split("\n# ", 1)[1]
@@ -116,6 +139,7 @@ if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("md", nargs="?")
ap.add_argument("-o", "--out")
ap.add_argument("--pdf", action="store_true", help="also write the PDF next to the docx")
ap.add_argument("--selfcheck", action="store_true")
a = ap.parse_args()
if a.selfcheck:
@@ -129,3 +153,9 @@ if __name__ == "__main__":
convert(text, out)
n = words(text)
print(f"wrote {out} ({n} words{'' if n <= MAX_WORDS else f', OVER the {MAX_WORDS}-word limit: cut it'})")
if a.pdf:
pdf, pages = to_pdf(out)
if not pdf:
sys.exit("no PDF: needs Word (Windows) or LibreOffice (soffice). Hand over the docx instead.")
over = pages and pages > MAX_PAGES
print(f"wrote {pdf} ({pages or '?'} pages{f', OVER the {MAX_PAGES}-page limit: cut it' if over else ''})")