Bera test report example for my clinic

Asking for Preferences
Reading File
Running Command

mkdir -p /tmp/workspace/bera-report && ls /tmp/workspace/bera-report/

Writing File

~/bera-report/create_bera_report.py

from docx import Document
from docx.shared import Pt, Inches, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_ALIGN_VERTICAL
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
import copy

doc = Document()

# ─── Page margins ───────────────────────────────────────────────────────────
section = doc.sections[0]
section.top_margin    = Cm(1.8)
section.bottom_margin = Cm(1.8)
section.left_margin   = Cm(2.0)
section.right_margin  = Cm(2.0)

# ─── Default Normal style ────────────────────────────────────────────────────
normal = doc.styles["Normal"]
normal.font.name = "Arial"
normal.font.size = Pt(10)

# ─── Helper: set paragraph shading ──────────────────────────────────────────
def shade_paragraph(para, hex_color="1F4E79"):
    pPr = para._p.get_or_add_pPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:val"),   "clear")
    shd.set(qn("w:color"), "auto")
    shd.set(qn("w:fill"),  hex_color)
    pPr.append(shd)

def shade_cell(cell, hex_color):
    tc   = cell._tc
    tcPr = tc.get_or_add_tcPr()
    shd  = OxmlElement("w:shd")
    shd.set(qn("w:val"),   "clear")
    shd.set(qn("w:color"), "auto")
    shd.set(qn("w:fill"),  hex_color)
    tcPr.append(shd)

def set_cell_border(cell, **kwargs):
    tc   = cell._tc
    tcPr = tc.get_or_add_tcPr()
    tcBorders = OxmlElement("w:tcBorders")
    for edge in ("top", "start", "bottom", "end", "insideH", "insideV"):
        tag = OxmlElement(f"w:{edge}")
        tag.set(qn("w:val"),   "single")
        tag.set(qn("w:sz"),    "4")
        tag.set(qn("w:space"), "0")
        tag.set(qn("w:color"), "BFBFBF")
        tcBorders.append(tag)
    tcPr.append(tcBorders)

def bold_run(para, text, size=10, color=None, italic=False):
    run = para.add_run(text)
    run.bold  = True
    run.italic = italic
    run.font.size = Pt(size)
    if color:
        run.font.color.rgb = RGBColor(*bytes.fromhex(color))
    return run

def normal_run(para, text, size=10, color=None, italic=False):
    run = para.add_run(text)
    run.font.size = Pt(size)
    if color:
        run.font.color.rgb = RGBColor(*bytes.fromhex(color))
    run.italic = italic
    return run

# ════════════════════════════════════════════════════════════════════════════
# HEADER BLOCK
# ════════════════════════════════════════════════════════════════════════════

# Clinic name banner
p = doc.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
shade_paragraph(p, "1F4E79")
run = p.add_run("  SUNRISE ENT & AUDIOLOGY CLINIC  ")
run.bold = True
run.font.size = Pt(18)
run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
run.font.name = "Arial"

p2 = doc.add_paragraph()
p2.alignment = WD_ALIGN_PARAGRAPH.CENTER
shade_paragraph(p2, "2E75B6")
r = p2.add_run("123, Medical Complex, City Road, Mumbai – 400001  |  Tel: +91-22-1234 5678  |  Email: info@sunriseentclinic.com")
r.font.size  = Pt(9)
r.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
r.font.name = "Arial"

# Report title bar
p3 = doc.add_paragraph()
p3.alignment = WD_ALIGN_PARAGRAPH.CENTER
shade_paragraph(p3, "BDD7EE")
run3 = p3.add_run("BRAINSTEM EVOKED RESPONSE AUDIOMETRY (BERA) REPORT")
run3.bold = True
run3.font.size  = Pt(13)
run3.font.color.rgb = RGBColor(0x1F, 0x4E, 0x79)
run3.font.name  = "Arial"

doc.add_paragraph()  # spacer

# ════════════════════════════════════════════════════════════════════════════
# PATIENT DETAILS TABLE (2×6 grid)
# ════════════════════════════════════════════════════════════════════════════

def section_heading(doc, text):
    p = doc.add_paragraph()
    shade_paragraph(p, "D6E4F0")
    run = p.add_run(f"  {text}")
    run.bold = True
    run.font.size  = Pt(10.5)
    run.font.color.rgb = RGBColor(0x1F, 0x4E, 0x79)
    run.font.name  = "Arial"
    return p

section_heading(doc, "PATIENT INFORMATION")

pt_tbl = doc.add_table(rows=5, cols=4)
pt_tbl.style = "Table Grid"
pt_tbl.alignment = WD_TABLE_ALIGNMENT.LEFT

labels = [
    ("Patient Name:",    "Rajesh Kumar",          "Date of Test:",     "15 July 2026"),
    ("Age / DOB:",       "34 Years / 12-Mar-1992","Ref. Doctor:",      "Dr. Priya Sharma, MBBS, MS (ENT)"),
    ("Gender:",          "Male",                  "Test Performed By:","Ms. Anita Verma (Audiologist)"),
    ("Patient ID:",      "BERA-2026-0082",        "Report No.:",       "RPT-2026-0082"),
    ("Reason for Test:", "Bilateral hearing loss, suspected sensorineural",
                         "Cooperation:",          "Good"),
]

col_widths = [Cm(3.5), Cm(5.5), Cm(3.5), Cm(6.0)]
for i, row_data in enumerate(labels):
    row = pt_tbl.rows[i]
    for j, (w, txt) in enumerate(zip(col_widths, row_data)):
        cell = row.cells[j]
        cell.width = w
        p_cell = cell.paragraphs[0]
        p_cell.paragraph_format.space_before = Pt(2)
        p_cell.paragraph_format.space_after  = Pt(2)
        if j % 2 == 0:   # label columns
            shade_cell(cell, "EBF3FB")
            bold_run(p_cell, txt, size=9, color="1F4E79")
        else:             # value columns
            normal_run(p_cell, txt, size=9)
        set_cell_border(cell)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# TEST PARAMETERS
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "TEST PARAMETERS")

param_tbl = doc.add_table(rows=2, cols=6)
param_tbl.style = "Table Grid"
param_tbl.alignment = WD_TABLE_ALIGNMENT.LEFT

param_headers = ["Stimulus", "Rate (clicks/sec)", "Filter (Hz)", "Intensity Range", "Polarity", "Electrode Placement"]
param_values  = ["Click",    "11.1",              "100 – 3000", "20 – 90 dB nHL",  "Rarefaction","Cz (+), A1/A2 (–), FPz (G)"]

for j, (h, v) in enumerate(zip(param_headers, param_values)):
    hc = param_tbl.rows[0].cells[j]
    vc = param_tbl.rows[1].cells[j]
    shade_cell(hc, "1F4E79")
    ph = hc.paragraphs[0]
    ph.paragraph_format.space_before = Pt(2)
    ph.paragraph_format.space_after  = Pt(2)
    r = ph.add_run(h)
    r.bold = True; r.font.size = Pt(9)
    r.font.color.rgb = RGBColor(0xFF,0xFF,0xFF)
    pv = vc.paragraphs[0]
    pv.paragraph_format.space_before = Pt(2)
    pv.paragraph_format.space_after  = Pt(2)
    normal_run(pv, v, size=9)
    set_cell_border(hc); set_cell_border(vc)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# WAVEFORM LATENCY & AMPLITUDE TABLE (Right Ear)
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "RIGHT EAR (RE) – WAVE LATENCY & AMPLITUDE")

re_tbl = doc.add_table(rows=7, cols=7)
re_tbl.style = "Table Grid"

re_headers = ["Intensity\n(dB nHL)", "Wave I\nLatency (ms)", "Wave III\nLatency (ms)",
              "Wave V\nLatency (ms)", "I–III IPL\n(ms)", "III–V IPL\n(ms)", "I–V IPL\n(ms)"]

re_data = [
    ["90", "1.56", "3.72", "5.64", "2.16", "1.92", "4.08"],
    ["80", "1.62", "3.80", "5.72", "2.18", "1.92", "4.10"],
    ["60", "1.70", "3.96", "5.92", "2.26", "1.96", "4.22"],
    ["40", "—",    "—",    "6.38", "—",    "—",    "—"   ],
    ["20", "—",    "—",    "—",    "—",    "—",    "—"   ],
    ["Normal Range (90 dB)", "≤1.7", "≤3.9", "≤5.9", "≤2.5", "≤2.3", "≤4.4"],
]

for j, h in enumerate(re_headers):
    c = re_tbl.rows[0].cells[j]
    shade_cell(c, "2E75B6")
    ph = c.paragraphs[0]
    ph.alignment = WD_ALIGN_PARAGRAPH.CENTER
    ph.paragraph_format.space_before = Pt(2)
    ph.paragraph_format.space_after  = Pt(2)
    r = ph.add_run(h)
    r.bold = True; r.font.size = Pt(8.5)
    r.font.color.rgb = RGBColor(0xFF,0xFF,0xFF)
    set_cell_border(c)

for i, row_data in enumerate(re_data, start=1):
    is_normal = (i == 6)
    for j, val in enumerate(row_data):
        c = re_tbl.rows[i].cells[j]
        if is_normal:
            shade_cell(c, "E2EFDA")
        pv = c.paragraphs[0]
        pv.alignment = WD_ALIGN_PARAGRAPH.CENTER
        pv.paragraph_format.space_before = Pt(2)
        pv.paragraph_format.space_after  = Pt(2)
        if is_normal:
            bold_run(pv, val, size=8.5, color="375623")
        else:
            normal_run(pv, val, size=9)
        set_cell_border(c)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# WAVEFORM LATENCY & AMPLITUDE TABLE (Left Ear)
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "LEFT EAR (LE) – WAVE LATENCY & AMPLITUDE")

le_tbl = doc.add_table(rows=7, cols=7)
le_tbl.style = "Table Grid"

le_data = [
    ["90", "1.72", "4.06", "6.28", "2.34", "2.22", "4.56"],
    ["80", "1.78", "4.14", "6.42", "2.36", "2.28", "4.64"],
    ["60", "1.86", "4.30", "6.68", "2.44", "2.38", "4.82"],
    ["40", "—",    "—",    "7.14", "—",    "—",    "—"   ],
    ["20", "—",    "—",    "—",    "—",    "—",    "—"   ],
    ["Normal Range (90 dB)", "≤1.7", "≤3.9", "≤5.9", "≤2.5", "≤2.3", "≤4.4"],
]

for j, h in enumerate(re_headers):
    c = le_tbl.rows[0].cells[j]
    shade_cell(c, "7030A0")
    ph = c.paragraphs[0]
    ph.alignment = WD_ALIGN_PARAGRAPH.CENTER
    ph.paragraph_format.space_before = Pt(2)
    ph.paragraph_format.space_after  = Pt(2)
    r = ph.add_run(h)
    r.bold = True; r.font.size = Pt(8.5)
    r.font.color.rgb = RGBColor(0xFF,0xFF,0xFF)
    set_cell_border(c)

for i, row_data in enumerate(le_data, start=1):
    is_normal = (i == 6)
    for j, val in enumerate(row_data):
        c = le_tbl.rows[i].cells[j]
        if is_normal:
            shade_cell(c, "E2EFDA")
        pv = c.paragraphs[0]
        pv.alignment = WD_ALIGN_PARAGRAPH.CENTER
        pv.paragraph_format.space_before = Pt(2)
        pv.paragraph_format.space_after  = Pt(2)
        if is_normal:
            bold_run(pv, val, size=8.5, color="375623")
        else:
            normal_run(pv, val, size=9)
        set_cell_border(c)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# THRESHOLD ESTIMATION
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "ESTIMATED HEARING THRESHOLD (Wave V Threshold)")

thresh_tbl = doc.add_table(rows=3, cols=3)
thresh_tbl.style = "Table Grid"
thresh_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER

thresh_headers = ["", "Right Ear (RE)", "Left Ear (LE)"]
thresh_rows = [
    ["Wave V Threshold", "40 dB nHL", "40 dB nHL"],
    ["Estimated Audiological Threshold", "~50 dB HL (Moderate hearing loss)", "~50 dB HL (Moderate hearing loss)"],
]

for j, h in enumerate(thresh_headers):
    c = thresh_tbl.rows[0].cells[j]
    shade_cell(c, "1F4E79")
    ph = c.paragraphs[0]
    ph.alignment = WD_ALIGN_PARAGRAPH.CENTER
    r = ph.add_run(h)
    r.bold = True; r.font.size = Pt(9.5)
    r.font.color.rgb = RGBColor(0xFF,0xFF,0xFF)
    set_cell_border(c)

for i, row_d in enumerate(thresh_rows, start=1):
    for j, val in enumerate(row_d):
        c = thresh_tbl.rows[i].cells[j]
        if j == 0:
            shade_cell(c, "EBF3FB")
        pv = c.paragraphs[0]
        pv.alignment = WD_ALIGN_PARAGRAPH.CENTER
        pv.paragraph_format.space_before = Pt(3)
        pv.paragraph_format.space_after  = Pt(3)
        if j == 0:
            bold_run(pv, val, size=9, color="1F4E79")
        else:
            normal_run(pv, val, size=9)
        set_cell_border(c)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# INTERPRETATION / FINDINGS
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "INTERPRETATION & FINDINGS")

findings = [
    ("Right Ear:",
     "All waves (I, III, V) are identifiable. Wave V latency is within normal limits at 90 dB nHL. "
     "Inter-peak latencies (I–III, III–V, I–V) are within normal range, indicating no evidence of "
     "retrocochlear pathology. Wave V threshold estimated at 40 dB nHL, suggesting a mild-to-moderate "
     "hearing loss."),
    ("Left Ear:",
     "Wave I, III, and V are identifiable but show prolonged absolute latencies at all intensity levels "
     "compared to normative data. The I–V inter-peak latency is prolonged (4.56 ms vs. normal ≤4.4 ms), "
     "raising suspicion for mild retrocochlear involvement. Wave V threshold estimated at 40 dB nHL."),
    ("Interaural Latency Difference (ILD):",
     "ILD for Wave V at 90 dB nHL = 0.64 ms. An ILD >0.4 ms is considered significant; the present "
     "finding warrants further investigation including MRI of the internal auditory canals."),
    ("Overall Impression:",
     "Bilateral mild-to-moderate sensorineural hearing loss. Right ear: normal neural conduction. "
     "Left ear: prolonged I–V IPL and significant ILD suggestive of possible retrocochlear pathology. "
     "Correlation with pure-tone audiometry, speech audiometry, and acoustic immittance recommended."),
]

for label, text in findings:
    p = doc.add_paragraph()
    p.paragraph_format.left_indent   = Cm(0.3)
    p.paragraph_format.space_after   = Pt(4)
    bold_run(p, label + "  ", size=9.5, color="1F4E79")
    normal_run(p, text, size=9.5)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# RECOMMENDATIONS
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "RECOMMENDATIONS")

rec_items = [
    "Pure-tone audiometry (PTA) and speech audiometry for cross-confirmation of hearing thresholds.",
    "Acoustic immittance (tympanometry + stapedial reflexes) to rule out middle-ear pathology.",
    "MRI of internal auditory canals (IAC) with gadolinium contrast to rule out acoustic neuroma / vestibular schwannoma (in view of left ear ILD findings).",
    "ENT specialist review and otoscopic examination.",
    "Hearing aid evaluation if sensorineural hearing loss confirmed on PTA.",
    "Repeat BERA in 3 months if further clarification is needed.",
]

for i, item in enumerate(rec_items, 1):
    p = doc.add_paragraph(style="List Number")
    p.paragraph_format.left_indent   = Cm(0.5)
    p.paragraph_format.space_after   = Pt(3)
    normal_run(p, item, size=9.5)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# WAVEFORM DIAGRAM PLACEHOLDER
# ════════════════════════════════════════════════════════════════════════════

section_heading(doc, "WAVEFORM TRACING (Attach Printout / Scanned Image)")

p_wf = doc.add_paragraph()
p_wf.alignment = WD_ALIGN_PARAGRAPH.CENTER
shade_paragraph(p_wf, "F2F2F2")
r_wf = p_wf.add_run("[ BERA Waveform Tracing – Right & Left Ear – Attach Printout Here ]")
r_wf.italic = True
r_wf.font.size = Pt(9)
r_wf.font.color.rgb = RGBColor(0x80,0x80,0x80)

# placeholder box (empty paragraph with border effect)
for _ in range(6):
    doc.add_paragraph()

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# DISCLAIMER
# ════════════════════════════════════════════════════════════════════════════

p_disc = doc.add_paragraph()
shade_paragraph(p_disc, "FFF2CC")
r_disc = p_disc.add_run(
    "  DISCLAIMER: This report is based on electrophysiological findings only and should be correlated "
    "with clinical history, otoscopy, and behavioural audiometry. BERA measures neural synchrony; it is "
    "not a substitute for a complete audiological evaluation."
)
r_disc.italic = True
r_disc.font.size  = Pt(8.5)
r_disc.font.color.rgb = RGBColor(0x7F, 0x60, 0x00)

doc.add_paragraph()

# ════════════════════════════════════════════════════════════════════════════
# SIGNATURE BLOCK
# ════════════════════════════════════════════════════════════════════════════

sig_tbl = doc.add_table(rows=1, cols=2)
sig_tbl.style = "Table Grid"

# Left cell – Audiologist
lc = sig_tbl.rows[0].cells[0]
shade_cell(lc, "EBF3FB")
p_l = lc.paragraphs[0]
p_l.alignment = WD_ALIGN_PARAGRAPH.CENTER
p_l.paragraph_format.space_before = Pt(6)
p_l.paragraph_format.space_after  = Pt(6)
bold_run(p_l, "Ms. Anita Verma\n", size=10, color="1F4E79")
normal_run(p_l, "M.Sc. Audiology & Speech-Language Pathology\nRegistered Audiologist (RCI: A-00123)\n", size=9)
normal_run(p_l, "Signature: ______________________\nDate: 15 July 2026", size=9)

# Right cell – ENT Consultant
rc = sig_tbl.rows[0].cells[1]
shade_cell(rc, "EBF3FB")
p_r = rc.paragraphs[0]
p_r.alignment = WD_ALIGN_PARAGRAPH.CENTER
p_r.paragraph_format.space_before = Pt(6)
p_r.paragraph_format.space_after  = Pt(6)
bold_run(p_r, "Dr. Priya Sharma\n", size=10, color="1F4E79")
normal_run(p_r, "MBBS, MS (ENT), DNB\nSenior ENT Consultant\n", size=9)
normal_run(p_r, "Signature: ______________________\nDate: 15 July 2026", size=9)

set_cell_border(lc); set_cell_border(rc)

doc.add_paragraph()

# Footer note
p_fn = doc.add_paragraph()
p_fn.alignment = WD_ALIGN_PARAGRAPH.CENTER
normal_run(p_fn, "This is a computer-generated report. Valid only with authorized signatures.  |  Sunrise ENT & Audiology Clinic, Mumbai",
           size=8, color="808080", italic=True)

# ════════════════════════════════════════════════════════════════════════════
# SAVE
# ════════════════════════════════════════════════════════════════════════════

out_path = "/tmp/workspace/bera-report/BERA_Report_Example.docx"
doc.save(out_path)
print(f"Saved: {out_path}")
Running Command

cd /tmp/workspace/bera-report && python3 create_bera_report.py

Running Command

python3 -c " from docx import Document doc = Document('/tmp/workspace/bera-report/BERA_Report_Example.docx') print('Paragraphs:', len(doc.paragraphs)) print('Tables:', len(doc.tables)) for i, t in enumerate(doc.tables): print(f' Table {i}: {len(t.rows)} rows x {len(t.columns)} cols') print('File size:', __import__('os').path.getsize('/tmp/workspace/bera-report/BERA_Report_Example.docx'), 'bytes') "

Your BERA report is ready. Download it here:

BERA Report Example

Word Document · DOCX


Here is what the report contains (fully editable - just replace the sample data with your patient's details):
1. Clinic Header
  • Clinic name banner + address/contact bar in professional blue
  • "BERA REPORT" title bar
2. Patient Information Table
  • Name, Age/DOB, Gender, Patient ID, Date of Test, Referring Doctor, Test Performed By, Reason for Test, Patient Cooperation
3. Test Parameters
  • Stimulus type, click rate, filter settings, intensity range, polarity, electrode placement
4. Right Ear Waveform Table (color-coded blue)
  • Wave I, III, V absolute latencies at 90/80/60/40/20 dB nHL
  • I-III, III-V, I-V inter-peak latencies (IPL)
  • Normal reference range row highlighted in green
5. Left Ear Waveform Table (color-coded purple)
  • Same structure as right ear
6. Estimated Hearing Threshold Table
  • Wave V threshold (dB nHL) and estimated audiological threshold (dB HL) for both ears
7. Interpretation & Findings
  • Separate findings for RE, LE, Interaural Latency Difference (ILD), and Overall Impression
  • The example shows a case with normal right ear and mildly abnormal left ear (prolonged I-V IPL, significant ILD)
8. Recommendations
  • Numbered list: PTA, immittance, MRI IAC, ENT review, hearing aid evaluation
9. Waveform Tracing Placeholder
  • A labeled area to attach the printed/scanned waveform tracing
10. Disclaimer + Dual Signature Block
  • Audiologist and ENT consultant sign-off, with registration details
This is a shared conversation. Sign in to Orris to start your own chat.