Bera test report example for my clinic
mkdir -p /tmp/workspace/bera-report && ls /tmp/workspace/bera-report/
~/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}")
cd /tmp/workspace/bera-report && python3 create_bera_report.py
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') "
BERA Report Example
Word Document · DOCX