""" Export supervision notes and CPD logs to .docx format. """ from datetime import date from io import BytesIO HAS_DOCX = False try: from docx import Document from docx.shared import Pt, RGBColor, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH HAS_DOCX = True except ImportError: pass def build_supervision_docx(note_text: str, nurse_name: str = "") -> bytes | None: """Build a formatted .docx supervision note.""" if not HAS_DOCX: return None doc = Document() # Header header_para = doc.add_paragraph() header_para.alignment = WD_ALIGN_PARAGRAPH.CENTER run = header_para.add_run("Professional Nurse Advocate Assistant") run.bold = True run.font.size = Pt(16) run.font.color.rgb = RGBColor(0x1a, 0x24, 0x60) # brand navy sub = doc.add_paragraph() sub.alignment = WD_ALIGN_PARAGRAPH.CENTER sub.add_run("A-EQUIP Model Supervision Note").italic = True doc.add_paragraph() # Metadata meta = doc.add_paragraph() meta.add_run(f"Date: ").bold = True meta.add_run(date.today().strftime("%d %B %Y")) if nurse_name: nurse_p = doc.add_paragraph() nurse_p.add_run("Name: ").bold = True nurse_p.add_run(nurse_name) doc.add_paragraph() # Note content — parse markdown-ish headings for line in note_text.split("\n"): line = line.strip() if not line: doc.add_paragraph() continue if line.startswith("## "): h = doc.add_heading(line[3:], level=2) for run in h.runs: run.font.color.rgb = RGBColor(0x1a, 0x24, 0x60) elif line.startswith("### "): h = doc.add_heading(line[4:], level=3) for run in h.runs: run.font.color.rgb = RGBColor(0x0d, 0x94, 0x88) # teal elif line.startswith("**") and line.endswith("**"): p = doc.add_paragraph() p.add_run(line.strip("**")).bold = True elif line.startswith("- "): doc.add_paragraph(line[2:], style="List Bullet") else: doc.add_paragraph(line) # Footer doc.add_paragraph() footer = doc.add_paragraph() footer.alignment = WD_ALIGN_PARAGRAPH.CENTER footer_run = footer.add_run( "Contains public sector information licensed under the Open Government Licence v3.0 — NHS England.\n" "This tool supports but does not replace clinical judgment or formal supervision documentation." ) footer_run.font.size = Pt(8) footer_run.font.color.rgb = RGBColor(0x6b, 0x72, 0x80) footer_run.italic = True buf = BytesIO() doc.save(buf) return buf.getvalue() def build_cpd_docx(note_text: str, nurse_name: str = "", hours: float = 1.0) -> bytes | None: """Build NMC revalidation CPD record.""" if not HAS_DOCX: return None doc = Document() header_para = doc.add_paragraph() header_para.alignment = WD_ALIGN_PARAGRAPH.CENTER run = header_para.add_run("NMC Revalidation — CPD Record") run.bold = True run.font.size = Pt(16) run.font.color.rgb = RGBColor(0x1a, 0x24, 0x60) doc.add_paragraph() fields = [ ("Date", date.today().strftime("%d %B %Y")), ("Name", nurse_name or "___________________"), ("NMC PIN", "___________________"), ("Activity Type", "Restorative Clinical Supervision (A-EQUIP Model)"), ("Hours", f"{hours:.1f}"), ("Method", "1:1 Supervision with AI-assisted preparation/reflection"), ("NMC Standards", "Platform 1 (Accountable professional), Platform 6 (Safety & quality)"), ] for label, value in fields: p = doc.add_paragraph() p.add_run(f"{label}: ").bold = True p.add_run(value) doc.add_paragraph() doc.add_heading("Supervision Summary", level=2) for line in note_text.split("\n"): line = line.strip() if not line: continue if line.startswith("### "): doc.add_heading(line[4:], level=3) elif line.startswith("- "): doc.add_paragraph(line[2:], style="List Bullet") elif not line.startswith("## ") and not line.startswith("**Date"): doc.add_paragraph(line) doc.add_paragraph() sig_p = doc.add_paragraph() sig_p.add_run("Nurse signature: ").bold = True sig_p.add_run("_________________________") conf_p = doc.add_paragraph() conf_p.add_run("Confirming PNA/Supervisor: ").bold = True conf_p.add_run("_________________________") doc.add_paragraph() footer = doc.add_paragraph() footer.alignment = WD_ALIGN_PARAGRAPH.CENTER fr = footer.add_run( "Contains public sector information licensed under the Open Government Licence v3.0 — NHS England." ) fr.font.size = Pt(8) fr.italic = True buf = BytesIO() doc.save(buf) return buf.getvalue()