rpa-bot-handoff: one short Handover SOP (max 4 pages) instead of the 11-section guide
The first real handover (MEK Charge Reconciliation) came out at 9 pages. Ops want it short: which URL to open and what to click, what shows where after the click, what to do when an exception comes, and how to email us. - Output is "<Project Name> Handover SOP" (HANDOVER-SOP.md + .docx) with 5 sections: how to run, what you see while it runs, result, when something goes wrong, email us. HANDOVER-DEV.md is dropped; dev-team findings go in the chat. - At most ~1,400 words; to_docx.py prints the word count and flags overflow, and renders *italic* for the variable parts of messages. - Every quoted message, label and URL is checked against the code. The SOP must say what clicking Run again does to the data. - One scan subagent; failure classes stay as a scan checklist. - Tebra reference rewritten in the SOP format; test, README, glossary updated; ADR-0005 supersedes ADR-0004. The handoff eval runner (tests/evals/run_handoff_eval.py) still grades an older 13-section format and was already out of date; not touched here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
71281bdf91
commit
ff2f7fcd4a
13 files changed
+260
-377
No files matched your search
@@ -1,11 +1,13 @@
|
||||
"""Turn the ops guide (HANDOVER.md) into a Word document ops can open, print and pass around.
|
||||
"""Turn the Handover SOP (HANDOVER-SOP.md) into a Word document ops can open, print and pass around.
|
||||
|
||||
python to_docx.py HANDOVER.md # writes HANDOVER.docx next to it
|
||||
python to_docx.py HANDOVER.md -o guide.docx
|
||||
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 --selfcheck
|
||||
|
||||
Prints the word count: the SOP must stay at or under MAX_WORDS (about 4 pages).
|
||||
|
||||
Handles what the template uses: # headings, paragraphs, - bullets, - [ ] checkboxes, 1. steps,
|
||||
| tables |, ``` blocks, **bold** and `code`. Needs python-docx (pip install python-docx).
|
||||
| tables |, ``` blocks, **bold**, *italic* and `code`. Needs python-docx (pip install python-docx).
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
@@ -16,13 +18,16 @@ from pathlib import Path
|
||||
from docx import Document
|
||||
from docx.shared import Pt
|
||||
|
||||
INLINE = re.compile(r"(\*\*[^*]+\*\*|`[^`]+`)")
|
||||
MAX_WORDS = 1400 # about 4 pages with tables
|
||||
INLINE = re.compile(r"(\*\*[^*]+\*\*|\*[^*\s][^*]*\*|`[^`]+`)")
|
||||
|
||||
|
||||
def add_runs(par, text):
|
||||
for part in INLINE.split(text):
|
||||
if part.startswith("**") and part.endswith("**"):
|
||||
par.add_run(part[2:-2]).bold = True
|
||||
elif len(part) > 2 and part.startswith("*") and part.endswith("*"):
|
||||
par.add_run(part[1:-1]).italic = True
|
||||
elif part.startswith("`") and part.endswith("`"):
|
||||
par.add_run(part[1:-1]).font.name = "Consolas"
|
||||
elif part:
|
||||
@@ -82,18 +87,24 @@ def convert(md_text, out_path):
|
||||
return doc
|
||||
|
||||
|
||||
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 selfcheck():
|
||||
example = Path(__file__).parent / "reference" / "tebra-auto-post.md"
|
||||
guide = example.read_text(encoding="utf-8").split("# File 2:")[0].split("# File 1: `HANDOVER.md`")[1]
|
||||
out = Path(tempfile.mkdtemp()) / "HANDOVER.docx"
|
||||
convert(guide, out)
|
||||
sop = "# " + example.read_text(encoding="utf-8").split("\n---\n", 1)[1].split("\n# ", 1)[1]
|
||||
out = Path(tempfile.mkdtemp()) / "HANDOVER-SOP.docx"
|
||||
convert(sop, out)
|
||||
doc = Document(out)
|
||||
heads = [p.text for p in doc.paragraphs if p.style.name.startswith("Heading")]
|
||||
assert [h.split(".")[0] for h in heads if h[:1].isdigit()] == [str(n) for n in range(1, 12)], heads
|
||||
assert len(doc.tables) >= 6, len(doc.tables)
|
||||
assert doc.tables[0].cell(0, 0).text == "Where", doc.tables[0].cell(0, 0).text
|
||||
assert doc.paragraphs[0].text.endswith("Handover SOP"), doc.paragraphs[0].text
|
||||
assert [h.split(".")[0] for h in heads if h[:1].isdigit()] == [str(n) for n in range(1, 6)], heads
|
||||
assert len(doc.tables) >= 4, len(doc.tables)
|
||||
assert doc.tables[0].cell(0, 0).text == "Where on the screen", doc.tables[0].cell(0, 0).text
|
||||
assert not any("**" in p.text or "|---" in p.text for p in doc.paragraphs)
|
||||
print(f"selfcheck ok: {len(heads)} headings, {len(doc.tables)} tables -> {out}")
|
||||
assert words(sop) <= MAX_WORDS, words(sop)
|
||||
print(f"selfcheck ok: {len(heads)} headings, {len(doc.tables)} tables, {words(sop)} words -> {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -109,5 +120,7 @@ if __name__ == "__main__":
|
||||
ap.error("give the HANDOVER.md path, or --selfcheck")
|
||||
src = Path(a.md)
|
||||
out = Path(a.out) if a.out else src.with_suffix(".docx")
|
||||
convert(src.read_text(encoding="utf-8"), out)
|
||||
print(f"wrote {out}")
|
||||
text = src.read_text(encoding="utf-8")
|
||||
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'})")
|
||||
Reference in new issue
Block a user