Motor Assessment of the Lower Limbs FORMAT
lower limb motor examination neurological assessment diagram

Clinical photographs demonstrating the assessment of motor function and lower limb power in a patient one year post-spinal surgery. Image A shows the patient performing a supine bridge exercise, with the hips and torso elevated off the examination table. The posture demonstrates antigravity muscle strength in the hip extensors, supported by bent knees and feet flat on the table. Image B depicts the patient lying supine while performing a straight-leg raise or maintaining leg elevation against gravity, demonstrating power in the hip flexors and knee extensors. The images serve as a clinical follow-up to document neurological recovery after surgical decompression for a spinal epidural mass. The clinical context indicates improvement in hip and ankle motor power (graded 4/5) and knee extension (4/5), sufficient for ambulation with assistive devices despite residual ankle dorsiflexion weakness and spasticity.

A clinical photograph demonstrating a physical examination technique for neurological assessment. The patient is positioned supine on an examination table with the lower extremities extended. An examiner, wearing a white clinical coat, is shown performing percussion using a Queen Square-style reflex hammer. The examiner's right hand holds the long, flexible handle of the hammer, poised to strike the medial epicondyle of the femur. The examiner's left hand is placed on the patient's distal thigh to stabilize the limb and palpate the anatomical landmarks. This procedural image illustrates the elicitation of a deep tendon reflex or periosteal reflex, specifically focusing on the medial aspect of the knee. The visual highlights the correct patient positioning, instrument grip, and anatomical targeting required for a standard neurological motor system evaluation.

Educational multi-panel illustration detailing neurophysiological assessment techniques for transcranial direct current stimulation (tDCS) of the lower limb motor cortex. Panel A: Coronal brain diagram with red shading identifying the targeted primary motor cortex (M1) area for anodal tDCS. Panel B: Anatomical illustration of a human lower limb showing electrode placement for surface electromyography (sEMG) over the tibialis anterior muscle. Panel C: Comparison chart of motor-evoked potential (MEP) traces recorded via transcranial magnetic stimulation (TMS). The traces illustrate a significant increase in peak-to-peak amplitude from 'Pre-tDCS' to 'Post-tDCS,' signifying enhanced corticospinal excitability. Panel D: Sagittal neuroimaging slice (fMRI) displaying a localized blood oxygen level-dependent (BOLD) signal in the paracentral lobule, corresponding to neural activation in the lower limb motor representation. This composite graphic demonstrates how electrophysiological (MEP) and hemodynamic (fMRI) markers are used to quantify brain response to neuromodulation therapy in clinical research and rehabilitation, such as post-stroke recovery.

This clinical photograph consists of two panels demonstrating a bedside neurological assessment for limb apraxia, specifically ideomotor apraxia. The images show a clinical interaction between an examiner (left) and a patient (right). In the first panel, the examiner presents a meaningless gesture with the index and middle fingers extended while other fingers are flexed; the patient attempts to mirror this configuration. In the second panel, the examiner presents a different hand pose with the index and pinky fingers extended and middle fingers flexed, forming a 'V' shape, which the patient again attempts to copy. This test evaluates left parietal lobe function, as deficits in copying these meaningless hand gestures can indicate ideomotor limb apraxia. Such findings are clinically relevant in the diagnosis of left parietal stroke or neurodegenerative conditions like Alzheimer's disease, dementia with Lewy bodies, or corticobasal syndrome. The setting is a clinical examination room, focusing on the motor execution and visuospatial imitation capabilities of the patient.
| Feature | What to Look For | Significance |
|---|---|---|
| Muscle bulk | Wasting (atrophy), hypertrophy | LMN lesion, disuse |
| Fasciculations | Fine, spontaneous twitching under skin | LMN / anterior horn cell disease |
| Involuntary movements | Tremor at rest, posture, or action | Parkinson's (resting), cerebellar (intention) |
| Posture | Limb deformity, foot drop, scissoring | UMN, pyramidal lesion |
| Skin/trophic changes | Hair loss, skin texture, ulcers | Chronic denervation or vascular |
| Asymmetry | Limb length, bulk, position | Congenital or acquired lesion |
| Scars/deformities | Surgical, traumatic | Relevant history |
In children, measure limb length - asymmetry suggests congenital spinal cord/brain malformation. Inspect the lumbar spine for scoliosis or hairy patches (spinal dysraphism).
| Finding | Description | Lesion |
|---|---|---|
| Hypotonia / Flaccidity | Reduced/absent resistance, floppy limb | LMN, peripheral nerve, cerebellar |
| Spasticity | Velocity-dependent resistance (clasp-knife) | UMN / corticospinal tract |
| Rigidity | Uniform resistance all directions (lead-pipe) | Extrapyramidal / basal ganglia |
| Cogwheel rigidity | Ratchet-like jerky interruptions on passive movement | Parkinsonism |
| Paratonia (Gegenhalten) | Fluctuating variable resistance | Frontal lobe pathways |
Key test: Rapidly raise the knees - immediate heel lift = increased tone (spasticity).
| Grade | Description |
|---|---|
| 0 | No movement (complete paralysis) |
| 1 | Flicker/trace of contraction - no joint movement |
| 2 | Movement possible with gravity eliminated (horizontal plane) |
| 3 | Movement against gravity but not against resistance |
| 4- | Movement against mild resistance |
| 4 | Movement against moderate resistance |
| 4+ | Movement against strong resistance |
| 5 | Full power (normal) |
| Movement | Muscles | Root Level |
|---|---|---|
| Hip flexion | Iliopsoas | L1, L2, L3 |
| Hip extension | Gluteus maximus | L4, L5, S1 |
| Hip abduction | Gluteus medius/minimus | L4, L5, S1 |
| Hip adduction | Adductors | L2, L3, L4 |
| Knee extension | Quadriceps femoris | L3, L4 |
| Knee flexion | Hamstrings | L5, S1, S2 |
| Ankle dorsiflexion | Tibialis anterior | L4, L5 |
| Ankle plantarflexion | Gastrocnemius/Soleus | S1, S2 |
| Ankle eversion | Peroneus longus/brevis | L5, S1 |
| Ankle inversion | Tibialis posterior | L4, L5 |
| Great toe extension | Extensor hallucis longus | L5 |
| Toe flexion | Flexor digitorum | S1, S2 |
Bare minimum screening: Check toe extensor strength (EHL) for lower limb weakness.
| Reflex | Root | Technique | Normal Response |
|---|---|---|---|
| Knee jerk (patellar) | L3, L4 | Patient sitting with one knee crossed over the other, or flexed knee resting on examiner's hand; brisk tap on ligamentum patellae | Extension (jerk) of leg - quadriceps contraction |
| Ankle jerk (Achilles) | S1, S2 | Foot slightly dorsiflexed (Achilles on stretch); tap on tendo Achilles | Plantar flexion of foot |
| Grade | Description |
|---|---|
| 0 | Absent |
| 1 | Present but diminished |
| 2 | Normoactive (normal) |
| 3 | Increased (brisk) |
| 4 | Clonus present |
| Type | Technique | Pathological Level |
|---|---|---|
| Ankle clonus (S1, S2) | Knee slightly flexed, support leg; sudden dorsiflexion of foot and maintain pressure | Rhythmic oscillating plantar/dorsiflexion = pyramidal lesion |
| Patellar clonus (L2, 3, 4) | Knee straight; suddenly push patella downward from upper border and maintain | Clonic movement of patella = pyramidal lesion |
Clonus is pathognomonic of pyramidal system lesions (UMN signs).
| Reflex | Root | Technique | Normal | Abnormal |
|---|---|---|---|---|
| Plantar reflex | S1 | Stroke lateral sole from heel to ball of foot | Toe flexion (downgoing) | Babinski sign = great toe extension + fanning (UMN lesion or infant) |
| Cremasteric | L1, L2 | Stroke inner upper thigh | Ipsilateral testis elevation | Absent = UMN or L1/L2 lesion |
| Anal reflex | S2, S3, S4 | Scratch perianal skin | Anal sphincter contraction | Absent = sacral lesion |
In pyramidal lesions: deep reflexes exaggerated (may elicit clonus); superficial reflexes diminished/absent (abdominal reflexes lost, plantar = Babinski). Note: Reliability of Babinski sign alone is limited; tone + strength + stretch reflexes + coordination are more useful collectively.

| Test | Technique | Abnormal Finding |
|---|---|---|
| Heel-shin test | Patient supine; place heel on opposite knee and slide it down shin | Ataxia/tremor = cerebellar or proprioceptive deficit |
| Foot tapping | Rapid regular tapping of foot | Inability = bradykinesia (Parkinson's) |
| Foot circle drawing | Draw a circle with foot | Difficulty = dyspraxia (corticobasal) |
| Romberg's test | Stand feet together, eyes open then closed | Positive (falls with eyes closed) = sensory/proprioceptive ataxia (NOT cerebellar) |
A patient with cerebellar vermis lesion may perform the heel-shin test normally when supine but be ataxic when walking - gait examination is essential.
| Gait Pattern | Features | Lesion |
|---|---|---|
| Hemiplegic (circumduction) | Stiff leg swings outward | Contralateral UMN (stroke) |
| Spastic paraparesis (scissor) | Legs cross, stiff, toe-dragging | Bilateral UMN (spinal cord) |
| High-stepping (steppage) | Foot drop, exaggerated hip flexion | L4/L5, peroneal nerve, LMN |
| Waddling | Bilateral hip abductor weakness | Proximal myopathy, hip dysplasia |
| Ataxic (broad-based) | Irregular, staggering, wide base | Cerebellar |
| Parkinsonian | Shuffling, small steps, en bloc turns, festination | Basal ganglia |
| Antalgic | Short stance phase on painful limb | Musculoskeletal |
| Trendelenburg | Contralateral pelvis drops | Ipsilateral hip abductor weakness |
| Feature | UMN Lesion | LMN Lesion |
|---|---|---|
| Bulk | Normal or mild disuse atrophy | Marked wasting |
| Fasciculations | Absent | Present |
| Tone | Increased (spasticity) | Decreased (flaccidity) |
| Power | Reduced | Reduced |
| Deep reflexes | Exaggerated (hyperreflexia) | Diminished or absent |
| Plantar reflex | Extensor (Babinski +ve) | Flexor (normal) or absent |
| Clonus | May be present | Absent |
| Superficial reflexes | Absent (abdominal, cremasteric) | Present or absent |
i need a well-designed format for above examination that i can use
What format would you like? · How will you use this?
ls -la /tmp/workspace/ 2>/dev/null || echo "workspace not found"
mkdir -p /tmp/workspace/motor-assessment && python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import mm, cm from reportlab.platypus import (SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, HRFlowable, KeepTogether) from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT from reportlab.platypus import Flowable # ── colours ────────────────────────────────────────────────────────────────── DARK_BLUE = colors.HexColor('#1A3A5C') MID_BLUE = colors.HexColor('#2E6DA4') LIGHT_BLUE = colors.HexColor('#D6E8F7') PALE_BLUE = colors.HexColor('#EEF5FB') ACCENT = colors.HexColor('#E8821A') LIGHT_GREY = colors.HexColor('#F2F4F7') MID_GREY = colors.HexColor('#CCCCCC') WHITE = colors.white BLACK = colors.black W, H = A4 # 595 x 842 pt doc = SimpleDocTemplate( '/tmp/workspace/motor-assessment/Lower_Limb_Motor_Assessment.pdf', pagesize=A4, leftMargin=15*mm, rightMargin=15*mm, topMargin=15*mm, bottomMargin=15*mm, ) styles = getSampleStyleSheet() # ── custom paragraph styles ────────────────────────────────────────────────── def S(name, parent='Normal', **kw): return ParagraphStyle(name, parent=styles[parent], **kw) sTitle = S('sTitle', fontSize=18, textColor=WHITE, alignment=TA_CENTER, fontName='Helvetica-Bold', spaceAfter=0) sSub = S('sSub', fontSize=10, textColor=colors.HexColor('#BFD9F2'), alignment=TA_CENTER, fontName='Helvetica', spaceAfter=0) sSecHdr = S('sSecHdr', fontSize=9, textColor=WHITE, fontName='Helvetica-Bold', alignment=TA_LEFT, spaceAfter=0, leading=12) sBody = S('sBody', fontSize=7.5, leading=11, textColor=BLACK) sBold = S('sBold', fontSize=7.5, fontName='Helvetica-Bold', textColor=BLACK, leading=11) sSmall = S('sSmall', fontSize=7, textColor=colors.HexColor('#555555'), leading=10) sLabel = S('sLabel', fontSize=7.5, fontName='Helvetica-Bold', textColor=DARK_BLUE, leading=11) sNote = S('sNote', fontSize=7, fontName='Helvetica-Oblique', textColor=colors.HexColor('#666666'), leading=10) sCenter = S('sCenter', fontSize=7.5, alignment=TA_CENTER, leading=11) sCenterB = S('sCenterB', fontSize=7.5, alignment=TA_CENTER, fontName='Helvetica-Bold', leading=11) # ── helper: section header row ─────────────────────────────────────────────── def sec_hdr(text, col=MID_BLUE, colspan=6): return ([Paragraph(f' {text}', sSecHdr)] + [''] * (colspan - 1), [('BACKGROUND', (0,0), (-1,-1), col), ('SPAN', (0,0), (-1,-1)), ('TOPPADDING', (0,0), (-1,-1), 4), ('BOTTOMPADDING', (0,0), (-1,-1), 4), ('LEFTPADDING', (0,0), (-1,-1), 6)]) def line_row(label, cols=5, width=None): """A label + underline fill row.""" return [Paragraph(label, sLabel)] + [Paragraph('', sBody)] * cols def check_row(label, options, note=''): """Label + checkbox options.""" boxes = ' '.join(f'☐ {o}' for o in options) if note: boxes += f' <font size="6.5" color="#666666"><i>{note}</i></font>' return [Paragraph(label, sLabel), Paragraph(boxes, sBody)] def uline(width=35*mm): return '_' * 30 # ───────────────────────────────────────────────────────────────────────────── # PAGE ELEMENTS # ───────────────────────────────────────────────────────────────────────────── story = [] usable_w = W - 30*mm # 165 mm # ════════════════════════════════════════════════════════════════════════════ # HEADER BANNER # ════════════════════════════════════════════════════════════════════════════ banner_data = [ [Paragraph('MOTOR ASSESSMENT OF THE LOWER LIMBS', sTitle)], [Paragraph('Neurological Examination Proforma | Patient Record', sSub)], ] banner = Table(banner_data, colWidths=[usable_w]) banner.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), DARK_BLUE), ('TOPPADDING', (0,0), (-1,-1), 10), ('BOTTOMPADDING', (0,0), (-1,-1), 8), ('LEFTPADDING', (0,0), (-1,-1), 8), ('RIGHTPADDING', (0,0), (-1,-1), 8), ('BOX', (0,0), (-1,-1), 1.5, MID_BLUE), ])) story.append(banner) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # PATIENT DETAILS # ════════════════════════════════════════════════════════════════════════════ c1, c2, c3, c4 = 28*mm, 37*mm, 28*mm, 37*mm # label | value | label | value detail_rows = [ [Paragraph('Patient Name:', sLabel), Paragraph('___________________________', sBody), Paragraph('Date:', sLabel), Paragraph('__________________', sBody)], [Paragraph('Age / Sex:', sLabel), Paragraph('___________ / ☐ M ☐ F ☐ Other', sBody), Paragraph('Examiner:', sLabel), Paragraph('__________________', sBody)], [Paragraph('Hospital No.:', sLabel), Paragraph('___________________________', sBody), Paragraph('Ward / OPD:', sLabel), Paragraph('__________________', sBody)], [Paragraph('Diagnosis / Presenting Complaint:', sLabel), Paragraph('_________________________________', sBody), Paragraph('Dominant Side:', sLabel), Paragraph('☐ Right ☐ Left', sBody)], ] detail_tbl = Table(detail_rows, colWidths=[c1,c2,c3,c4]) detail_tbl.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), PALE_BLUE), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 4), ('RIGHTPADDING', (0,0), (-1,-1), 4), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ])) story.append(detail_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 1. INSPECTION # ════════════════════════════════════════════════════════════════════════════ insp_hdr_row, insp_hdr_style = sec_hdr('1. INSPECTION (Patient supine, fully exposed)', colspan=4) insp_hdr_row2 = ['Feature', 'Right', 'Left', 'Notes / Description'] insp_rows = [ insp_hdr_row, [Paragraph(t, sCenterB) for t in insp_hdr_row2], ['Muscle Bulk', '☐ Normal ☐ Wasted ☐ Hypertrophy', '☐ Normal ☐ Wasted ☐ Hypertrophy', ''], ['Fasciculations', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Involuntary Movements', '☐ None ☐ Tremor ☐ Myoclonus ☐ Other', '☐ None ☐ Tremor ☐ Myoclonus ☐ Other', ''], ['Limb Posture', '☐ Normal ☐ Foot drop ☐ Scissoring ☐ Other', '☐ Normal ☐ Foot drop ☐ Scissoring ☐ Other', ''], ['Trophic / Skin Changes', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Scars / Deformity', '☐ None ☐ Present (describe →)', '☐ None ☐ Present (describe →)', ''], ['Limb Length Asymmetry', 'R: _______ cm', 'L: _______ cm', '☐ Symmetric ☐ Asymmetric'], ] ia, ib, ic, id_ = 42*mm, 42*mm, 42*mm, 39*mm insp_tbl = Table( [[Paragraph(str(r[0]), sLabel) if isinstance(r[0], str) else r[0], Paragraph(str(r[1]), sBody) if isinstance(r[1], str) else r[1], Paragraph(str(r[2]), sBody) if isinstance(r[2], str) else r[2], Paragraph(str(r[3]), sBody) if isinstance(r[3], str) else r[3]] if r != insp_hdr_row else r for r in insp_rows], colWidths=[ia, ib, ic, id_] ) insp_tbl.setStyle(TableStyle([ # header band *[(s[0], s[1], s[2], s[3]) for s in insp_hdr_style], ('SPAN', (0,0), (3,0)), # col header ('BACKGROUND', (0,1), (3,1), LIGHT_BLUE), ('FONTNAME', (0,1), (3,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (3,1), 7.5), ('ALIGN', (0,1), (3,1), 'CENTER'), # body ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTNAME', (0,2), (0,-1), 'Helvetica-Bold'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(insp_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 2. TONE # ════════════════════════════════════════════════════════════════════════════ tone_hdr_row, tone_hdr_style = sec_hdr('2. TONE (Passive movement, heel-drop test)', colspan=4) tone_rows_data = [ ['Finding', 'Right', 'Left', 'Grade / Comment'], ['Tone Character', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', ''], ['Cogwheel Rigidity', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Heel-Drop Test\n(rapid knee lift)', '☐ Normal heel drag\n☐ Immediate heel lift', '☐ Normal heel drag\n☐ Immediate heel lift', ''], ['Hip', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Knee', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Ankle', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ] tone_tbl = Table( [tone_hdr_row] + [[Paragraph(str(cell).replace('\n', '<br/>'), sCenterB if i == 0 else sBody) if r_i == 0 else Paragraph(str(cell).replace('\n', '<br/>'), sLabel if c_i == 0 else sBody) for c_i, cell in enumerate(row)] for r_i, row in enumerate(tone_rows_data)], colWidths=[ia, ib, ic, id_] ) tone_tbl.setStyle(TableStyle([ *[(s[0], s[1], s[2], s[3]) for s in tone_hdr_style], ('SPAN', (0,0), (3,0)), ('BACKGROUND', (0,1), (3,1), LIGHT_BLUE), ('FONTNAME', (0,1), (3,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (3,1), 7.5), ('ALIGN', (0,1), (3,1), 'CENTER'), ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTNAME', (0,2), (0,-1), 'Helvetica-Bold'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(tone_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 3. POWER (MRC grading) # ════════════════════════════════════════════════════════════════════════════ pow_hdr_row, pow_hdr_style = sec_hdr('3. POWER (MRC Grade 0–5)', colspan=5) # MRC legend strip mrc_legend = Table([[ Paragraph('<b>MRC Scale:</b> 0 = No movement 1 = Flicker 2 = Gravity eliminated ' '3 = Against gravity 4 = Against resistance 5 = Full power', sSmall) ]], colWidths=[usable_w]) mrc_legend.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#FFF4E5')), ('BOX', (0,0), (-1,-1), 0.5, ACCENT), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 6), ])) power_movements = [ ('Hip Flexion (Iliopsoas)', 'L1–L3'), ('Hip Extension (Glut. Max.)', 'L4–S1'), ('Hip Abduction (Glut. Med.)', 'L4–S1'), ('Hip Adduction', 'L2–L4'), ('Knee Extension (Quadriceps)', 'L3–L4'), ('Knee Flexion (Hamstrings)', 'L5–S2'), ('Ankle Dorsiflexion (Tib. Ant.)','L4–L5'), ('Ankle Plantarflexion', 'S1–S2'), ('Ankle Eversion (Peronei)', 'L5–S1'), ('Ankle Inversion (Tib. Post.)', 'L4–L5'), ('Great Toe Extension (EHL)', 'L5'), ('Toe Flexion', 'S1–S2'), ] def mrc_cells(label, root): def grade_box(side): return Paragraph(f' 0 / 1 / 2 / 3 / 4⁻ / 4 / 4⁺ / 5\nCircle: ____', sSmall) return [Paragraph(label, sLabel), Paragraph(root, sCenter), Paragraph('0 1 2 3 4⁻ 4 4⁺ 5\n(circle)', sSmall), Paragraph('0 1 2 3 4⁻ 4 4⁺ 5\n(circle)', sSmall), Paragraph('', sBody)] pa, pb, pc, pd, pe = 52*mm, 15*mm, 34*mm, 34*mm, 30*mm pow_body = [pow_hdr_row, [Paragraph(t, sCenterB) for t in ['Movement', 'Root', 'Right', 'Left', 'Notes']]] for mv, rt in power_movements: pow_body.append(mrc_cells(mv, rt)) pow_tbl = Table(pow_body, colWidths=[pa, pb, pc, pd, pe]) pow_tbl.setStyle(TableStyle([ *[(s[0], s[1], s[2], s[3]) for s in pow_hdr_style], ('SPAN', (0,0), (4,0)), ('BACKGROUND', (0,1), (4,1), LIGHT_BLUE), ('FONTNAME', (0,1), (4,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (4,1), 7.5), ('ALIGN', (0,1), (4,1), 'CENTER'), ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(KeepTogether([mrc_legend, Spacer(1,2*mm), pow_tbl])) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 4. REFLEXES # ════════════════════════════════════════════════════════════════════════════ ref_hdr_row, ref_hdr_style = sec_hdr('4. REFLEXES', colspan=5) # Reflex grade legend ref_legend = Table([[ Paragraph('<b>Reflex Grade:</b> 0 = Absent 1 = Diminished ' '2 = Normal 3 = Brisk / Increased 4 = Clonus', sSmall) ]], colWidths=[usable_w]) ref_legend.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#EEF8EE')), ('BOX', (0,0), (-1,-1), 0.5, colors.HexColor('#4CAF50')), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 6), ])) deep_reflexes = [ ('Knee Jerk (Patellar)', 'L3–L4'), ('Ankle Jerk (Achilles)', 'S1–S2'), ] superficial_reflexes = [ ('Plantar Reflex', 'S1', '☐ Flexor (Normal) ☐ Extensor (Babinski +ve) ☐ No response', '☐ Flexor (Normal) ☐ Extensor (Babinski +ve) ☐ No response'), ('Cremasteric', 'L1–L2', '☐ Present ☐ Absent', '☐ Present ☐ Absent'), ('Abdominal', 'T7–T12','☐ Present ☐ Absent', '☐ Present ☐ Absent'), ] clonus_tests = [ ('Ankle Clonus', 'S1–S2'), ('Patellar Clonus','L2–L4'), ] ra, rb, rc, rd, re = 50*mm, 14*mm, 35*mm, 35*mm, 31*mm ref_rows = [ref_hdr_row, [Paragraph(t, sCenterB) for t in ['Reflex', 'Root', 'Right', 'Left', 'Notes']]] # sub-section: Deep Tendon ref_rows.append([Paragraph(' DEEP TENDON REFLEXES', sSecHdr)] + [''] * 4) for name, root in deep_reflexes: ref_rows.append([Paragraph(name, sLabel), Paragraph(root, sCenter), Paragraph('0 1 2 3 4\n(circle)', sSmall), Paragraph('0 1 2 3 4\n(circle)', sSmall), Paragraph('', sBody)]) # sub-section: Clonus ref_rows.append([Paragraph(' CLONUS', sSecHdr)] + [''] * 4) for name, root in clonus_tests: ref_rows.append([Paragraph(name, sLabel), Paragraph(root, sCenter), Paragraph('☐ Absent ☐ Present\nBeats: ___', sSmall), Paragraph('☐ Absent ☐ Present\nBeats: ___', sSmall), Paragraph('', sBody)]) # sub-section: Superficial ref_rows.append([Paragraph(' SUPERFICIAL / CUTANEOUS REFLEXES', sSecHdr)] + [''] * 4) for name, root, right_opts, left_opts in superficial_reflexes: ref_rows.append([Paragraph(name, sLabel), Paragraph(root, sCenter), Paragraph(right_opts, sSmall), Paragraph(left_opts, sSmall), Paragraph('', sBody)]) ref_tbl = Table(ref_rows, colWidths=[ra, rb, rc, rd, re]) sub_hdr_indices = [2+len(deep_reflexes), 2+len(deep_reflexes)+1+len(clonus_tests)] # clonus, superficial all_sub_indices = [2, sub_hdr_indices[0], sub_hdr_indices[1]] ref_style_cmds = [ *[(s[0], s[1], s[2], s[3]) for s in ref_hdr_style], ('SPAN', (0,0), (4,0)), ('BACKGROUND', (0,1), (4,1), LIGHT_BLUE), ('FONTNAME', (0,1), (4,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (4,1), 7.5), ('ALIGN', (0,1), (4,1), 'CENTER'), ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ] for idx in all_sub_indices: ref_style_cmds += [ ('BACKGROUND', (0, idx), (-1, idx), colors.HexColor('#3A7EBF')), ('SPAN', (0, idx), (-1, idx)), ('TOPPADDING', (0, idx), (-1, idx), 3), ('BOTTOMPADDING', (0, idx), (-1, idx), 3), ] ref_tbl.setStyle(TableStyle(ref_style_cmds)) story.append(KeepTogether([ref_legend, Spacer(1,2*mm), ref_tbl])) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 5. COORDINATION # ════════════════════════════════════════════════════════════════════════════ coord_hdr_row, coord_hdr_style = sec_hdr('5. COORDINATION (Cerebellar Function)', colspan=4) coord_tests = [ ('Heel-Shin Test', '☐ Normal ☐ Ataxia ☐ Tremor/Dysmetria', '☐ Normal ☐ Ataxia ☐ Tremor/Dysmetria', ''), ('Foot Tapping\n(rapid regular)', '☐ Normal ☐ Bradykinesia ☐ Dysdiadochokinesia', '☐ Normal ☐ Bradykinesia ☐ Dysdiadochokinesia', ''), ('Foot Circle\n(draw circle in air)', '☐ Normal ☐ Dyspraxia ☐ Abnormal', '☐ Normal ☐ Dyspraxia ☐ Abnormal', ''), ('Romberg\'s Test\n(eyes open → closed)', 'Eyes open: ☐ Stable ☐ Unsteady', 'Eyes closed: ☐ Stable ☐ Falls\n→ ☐ Positive ☐ Negative', ''), ] coa, cob, coc, cod = 38*mm, 45*mm, 45*mm, 37*mm coord_rows = [coord_hdr_row, [Paragraph(t, sCenterB) for t in ['Test', 'Right', 'Left', 'Notes']]] for t, r, l, n in coord_tests: coord_rows.append([Paragraph(t.replace('\n','<br/>'), sLabel), Paragraph(r, sBody), Paragraph(l, sBody), Paragraph(n, sBody)]) coord_tbl = Table(coord_rows, colWidths=[coa, cob, coc, cod]) coord_tbl.setStyle(TableStyle([ *[(s[0], s[1], s[2], s[3]) for s in coord_hdr_style], ('SPAN', (0,0), (3,0)), ('BACKGROUND', (0,1), (3,1), LIGHT_BLUE), ('FONTNAME', (0,1), (3,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (3,1), 7.5), ('ALIGN', (0,1), (3,1), 'CENTER'), ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(coord_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 6. GAIT # ════════════════════════════════════════════════════════════════════════════ gait_hdr_row, gait_hdr_style = sec_hdr('6. GAIT (Examine last)', colspan=3) gait_patterns = [ 'Hemiplegic (circumduction)', 'Spastic paraparesis (scissor)', 'Steppage (foot drop)', 'Waddling (proximal weakness)', 'Ataxic (broad-based)', 'Parkinsonian (shuffling/festination)', 'Antalgic (pain-avoidance)', 'Trendelenburg (hip abductor weakness)', 'Functional / Non-organic', 'Normal', ] gait_check_pairs = [] for i in range(0, len(gait_patterns), 2): left = f'☐ {gait_patterns[i]}' right = f'☐ {gait_patterns[i+1]}' if i+1 < len(gait_patterns) else '' gait_check_pairs.append([left, right]) ga, gb, gc = 25*mm, 70*mm, 70*mm gait_rows = [gait_hdr_row, [Paragraph(t, sCenterB) for t in ['', 'Gait Pattern (tick all that apply)', '']]] for l, r in gait_check_pairs: gait_rows.append(['', Paragraph(l, sBody), Paragraph(r, sBody)]) gait_rows.append(['', Paragraph('Additional Observations:', sLabel), Paragraph('______________________________________', sBody)]) gait_rows.append(['', Paragraph('Assistive Device:', sLabel), Paragraph('☐ None ☐ Walking stick ☐ Frame ☐ Crutches ☐ Wheelchair', sBody)]) gait_tbl = Table(gait_rows, colWidths=[ga, gb, gc]) gait_tbl.setStyle(TableStyle([ *[(s[0], s[1], s[2], s[3]) for s in gait_hdr_style], ('SPAN', (0,0), (2,0)), ('BACKGROUND', (0,1), (2,1), LIGHT_BLUE), ('FONTNAME', (0,1), (2,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (2,1), 7.5), ('SPAN', (0,1), (0,1)), ('ALIGN', (0,1), (2,1), 'CENTER'), ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(gait_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 7. UMN vs LMN SUMMARY BOX # ════════════════════════════════════════════════════════════════════════════ sum_hdr_row, sum_hdr_style = sec_hdr('7. UMN vs LMN PATTERN SUMMARY', colspan=4) sum_rows = [ sum_hdr_row, [Paragraph(t, sCenterB) for t in ['Feature', 'UMN (Central)', 'LMN (Peripheral)', 'Patient Findings']], ['Muscle Bulk', 'Normal / mild disuse atrophy', 'Marked wasting', ''], ['Fasciculations', 'Absent', 'Present', ''], ['Tone', 'Increased (spasticity)', 'Decreased (flaccidity)', ''], ['Power', 'Reduced (pyramidal pattern)', 'Reduced (root/nerve pattern)', ''], ['Deep Reflexes', 'Hyperreflexia', 'Diminished / Absent', ''], ['Plantar Reflex', 'Extensor (Babinski +ve)', 'Flexor (normal) / Absent', ''], ['Clonus', 'May be present', 'Absent', ''], ['Superficial Reflexes', 'Absent', 'Present / Absent', ''], [Paragraph('Clinical Pattern:', sLabel), Paragraph('☐ UMN ☐ LMN ☐ Mixed ☐ Cerebellar ☐ Extrapyramidal ☐ Normal', sBody), '', ''], ] sa, sb, sc, sd = 38*mm, 44*mm, 44*mm, 39*mm sum_tbl = Table( [row if row == sum_hdr_row else [Paragraph(str(c), sCenterB if r_i==1 else (sLabel if c_i==0 else sBody)) for c_i, c in enumerate(row)] for r_i, row in enumerate(sum_rows)], colWidths=[sa, sb, sc, sd] ) sum_tbl.setStyle(TableStyle([ *[(s[0], s[1], s[2], s[3]) for s in sum_hdr_style], ('SPAN', (0,0), (3,0)), ('SPAN', (1, len(sum_rows)-1), (3, len(sum_rows)-1)), ('BACKGROUND', (0,1), (3,1), LIGHT_BLUE), ('FONTNAME', (0,1), (3,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (3,1), 7.5), ('ALIGN', (0,1), (3,1), 'CENTER'), ('ROWBACKGROUNDS', (0,2), (-1,-2), [WHITE, LIGHT_GREY]), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ('BACKGROUND', (0, len(sum_rows)-1), (-1, len(sum_rows)-1), colors.HexColor('#FFF4E5')), ])) story.append(sum_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 8. CLINICAL NOTES + SIGNATURE # ════════════════════════════════════════════════════════════════════════════ notes_hdr_row, notes_hdr_style = sec_hdr('8. CLINICAL SUMMARY & NOTES', colspan=1) notes_data = [ notes_hdr_row, [Paragraph('Overall Impression / Provisional Diagnosis:', sLabel)], [Paragraph('_' * 120, sBody)], [Paragraph('_' * 120, sBody)], [Paragraph('_' * 120, sBody)], [Paragraph('Further Investigations Requested:', sLabel)], [Paragraph('_' * 120, sBody)], [Paragraph('_' * 120, sBody)], ] notes_tbl = Table(notes_data, colWidths=[usable_w]) notes_tbl.setStyle(TableStyle([ *[(s[0], s[1], s[2], s[3]) for s in notes_hdr_style], ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 6), ('ROWBACKGROUNDS', (0,1), (-1,-1), [PALE_BLUE, WHITE]), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(notes_tbl) story.append(Spacer(1, 5*mm)) # Signature row sig_data = [[ Paragraph('Examiner Signature: ___________________________', sBody), Paragraph('Designation: _______________________', sBody), Paragraph('Date: _______________', sBody), ]] sig_tbl = Table(sig_data, colWidths=[65*mm, 60*mm, 40*mm]) sig_tbl.setStyle(TableStyle([ ('TOPPADDING', (0,0), (-1,-1), 4), ('LEFTPADDING', (0,0), (-1,-1), 4), ('FONTSIZE', (0,0), (-1,-1), 7.5), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ])) story.append(sig_tbl) # ════════════════════════════════════════════════════════════════════════════ # FOOTER # ════════════════════════════════════════════════════════════════════════════ story.append(Spacer(1, 3*mm)) footer_tbl = Table([[ Paragraph('Sequence: Inspection → Tone → Power → Reflexes → Coordination → Gait', sNote), Paragraph('Based on Harrison\'s 22E | Bradley & Daroff\'s Neurology | Das Manual of Clinical Surgery', ParagraphStyle('fn2', parent=sNote, alignment=TA_RIGHT)) ]], colWidths=[90*mm, 75*mm]) footer_tbl.setStyle(TableStyle([ ('TOPPADDING', (0,0), (-1,-1), 2), ('BOTTOMPADDING', (0,0), (-1,-1), 2), ('LINEABOVE', (0,0), (-1,0), 0.5, MID_GREY), ])) story.append(footer_tbl) # ── BUILD ──────────────────────────────────────────────────────────────────── doc.build(story) print("PDF created successfully.") PYEOF
sec_hdr function returning a tuple being unpacked in a list context. Let me fix this properly.python3 << 'PYEOF' from reportlab.lib.pagesizes import A4 from reportlab.lib import colors from reportlab.lib.units import mm from reportlab.platypus import (SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, KeepTogether) from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT # ── colours ────────────────────────────────────────────────────────────────── DARK_BLUE = colors.HexColor('#1A3A5C') MID_BLUE = colors.HexColor('#2E6DA4') LIGHT_BLUE = colors.HexColor('#D6E8F7') PALE_BLUE = colors.HexColor('#EEF5FB') ACCENT = colors.HexColor('#E8821A') LIGHT_GREY = colors.HexColor('#F2F4F7') MID_GREY = colors.HexColor('#CCCCCC') WHITE = colors.white W, H = A4 USABLE_W = W - 30*mm doc = SimpleDocTemplate( '/tmp/workspace/motor-assessment/Lower_Limb_Motor_Assessment.pdf', pagesize=A4, leftMargin=15*mm, rightMargin=15*mm, topMargin=15*mm, bottomMargin=15*mm, ) styles = getSampleStyleSheet() def S(name, parent='Normal', **kw): return ParagraphStyle(name, parent=styles[parent], **kw) sTitle = S('sTitle', fontSize=18, textColor=colors.white, alignment=TA_CENTER, fontName='Helvetica-Bold') sSub = S('sSub', fontSize=10, textColor=colors.HexColor('#BFD9F2'), alignment=TA_CENTER, fontName='Helvetica') sSecHdr = S('sSecHdr', fontSize=9, textColor=colors.white, fontName='Helvetica-Bold', alignment=TA_LEFT, leading=12) sSubHdr = S('sSubHdr', fontSize=8, textColor=colors.white, fontName='Helvetica-Bold', alignment=TA_LEFT, leading=11) sBody = S('sBody', fontSize=7.5, leading=11, textColor=colors.black) sLabel = S('sLabel', fontSize=7.5, fontName='Helvetica-Bold', textColor=DARK_BLUE, leading=11) sSmall = S('sSmall', fontSize=7, textColor=colors.HexColor('#444444'), leading=10) sCenter = S('sCenter', fontSize=7.5, alignment=TA_CENTER, leading=11) sCenterB= S('sCenterB',fontSize=7.5, alignment=TA_CENTER, fontName='Helvetica-Bold', leading=11) sNote = S('sNote', fontSize=7, fontName='Helvetica-Oblique', textColor=colors.HexColor('#666666'), leading=10) def make_section(title, col_widths, col_headers, data_rows, hdr_color=MID_BLUE, sub_hdr_rows=None): """ Builds a section table. title: string for the coloured header band col_widths: list of widths col_headers: list of header strings data_rows: list of lists of (string or Paragraph) sub_hdr_rows: dict {row_index: color} for sub-header rows (0-based within data_rows) """ ncols = len(col_widths) # --- build raw rows --- all_rows = [] style_cmds = [] # Row 0: section header band header_cells = [Paragraph(f' {title}', sSecHdr)] + [''] * (ncols - 1) all_rows.append(header_cells) style_cmds += [ ('BACKGROUND', (0,0), (-1,0), hdr_color), ('SPAN', (0,0), (-1,0)), ('TOPPADDING', (0,0), (-1,0), 5), ('BOTTOMPADDING', (0,0), (-1,0), 5), ('LEFTPADDING', (0,0), (-1,0), 6), ] # Row 1: column headers ch_cells = [Paragraph(h, sCenterB) for h in col_headers] all_rows.append(ch_cells) style_cmds += [ ('BACKGROUND', (0,1), (-1,1), LIGHT_BLUE), ('ALIGN', (0,1), (-1,1), 'CENTER'), ('FONTNAME', (0,1), (-1,1), 'Helvetica-Bold'), ('FONTSIZE', (0,1), (-1,1), 7.5), ] # Data rows start at index 2 sub_hdr_rows = sub_hdr_rows or {} for i, row in enumerate(data_rows): tbl_row_idx = i + 2 if i in sub_hdr_rows: # sub-header row sh_cells = [Paragraph(f' {row[0]}', sSubHdr)] + [''] * (ncols - 1) all_rows.append(sh_cells) sh_col = sub_hdr_rows[i] style_cmds += [ ('BACKGROUND', (0, tbl_row_idx), (-1, tbl_row_idx), sh_col), ('SPAN', (0, tbl_row_idx), (-1, tbl_row_idx)), ('TOPPADDING', (0, tbl_row_idx), (-1, tbl_row_idx), 3), ('BOTTOMPADDING', (0, tbl_row_idx), (-1, tbl_row_idx), 3), ] else: formatted = [] for j, cell in enumerate(row): if isinstance(cell, Paragraph): formatted.append(cell) elif j == 0: formatted.append(Paragraph(str(cell), sLabel)) else: formatted.append(Paragraph(str(cell).replace('\n','<br/>'), sBody)) all_rows.append(formatted) # Global style style_cmds += [ ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,2), (-1,-1), 3), ('BOTTOMPADDING', (0,2), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING',(0,0), (-1,-1), 3), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,2), (-1,-1), 7.5), ('ROWBACKGROUNDS', (0,2), (-1,-1), [WHITE, LIGHT_GREY]), ('FONTNAME', (0,2), (0,-1), 'Helvetica-Bold'), ] tbl = Table(all_rows, colWidths=col_widths) tbl.setStyle(TableStyle(style_cmds)) return tbl story = [] # ════════════════════════════════════════════════════════════════════════════ # BANNER # ════════════════════════════════════════════════════════════════════════════ banner = Table([ [Paragraph('MOTOR ASSESSMENT OF THE LOWER LIMBS', sTitle)], [Paragraph('Neurological Examination Proforma · Patient Record / Documentation', sSub)], ], colWidths=[USABLE_W]) banner.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), DARK_BLUE), ('TOPPADDING', (0,0), (-1,-1), 10), ('BOTTOMPADDING', (0,0), (-1,-1), 8), ('LEFTPADDING', (0,0), (-1,-1), 8), ('BOX', (0,0), (-1,-1), 1.5, MID_BLUE), ])) story.append(banner) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # PATIENT DETAILS # ════════════════════════════════════════════════════════════════════════════ det = Table([ [Paragraph('Patient Name:', sLabel), Paragraph('___________________________', sBody), Paragraph('Date:', sLabel), Paragraph('__________________', sBody)], [Paragraph('Age / Sex:', sLabel), Paragraph('_________ / ☐ M ☐ F ☐ Other', sBody), Paragraph('Examiner:', sLabel), Paragraph('__________________', sBody)], [Paragraph('Hospital No.:', sLabel), Paragraph('___________________________', sBody), Paragraph('Ward / OPD:', sLabel), Paragraph('__________________', sBody)], [Paragraph('Diagnosis / Complaint:', sLabel), Paragraph('_________________________________', sBody), Paragraph('Dominant Side:', sLabel), Paragraph('☐ Right ☐ Left', sBody)], ], colWidths=[30*mm, 38*mm, 28*mm, 69*mm]) det.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), PALE_BLUE), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 5), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(det) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 1. INSPECTION # ════════════════════════════════════════════════════════════════════════════ insp = make_section( '1. INSPECTION (Patient supine, fully exposed, good lighting)', col_widths=[38*mm, 43*mm, 43*mm, 41*mm], col_headers=['Feature', 'Right', 'Left', 'Notes'], data_rows=[ ['Muscle Bulk', '☐ Normal ☐ Wasted ☐ Hypertrophy', '☐ Normal ☐ Wasted ☐ Hypertrophy', ''], ['Fasciculations', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Involuntary Movements', '☐ None ☐ Tremor ☐ Myoclonus ☐ Other', '☐ None ☐ Tremor ☐ Myoclonus ☐ Other', ''], ['Limb Posture', '☐ Normal ☐ Foot drop ☐ Scissoring ☐ Other', '☐ Normal ☐ Foot drop ☐ Scissoring ☐ Other', ''], ['Trophic / Skin Changes', '☐ Absent ☐ Present (describe)', '☐ Absent ☐ Present (describe)', ''], ['Scars / Deformity', '☐ None ☐ Present', '☐ None ☐ Present', ''], ['Limb Length', 'R: _______ cm', 'L: _______ cm', '☐ Symmetric ☐ Asymmetric'], ] ) story.append(insp) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 2. TONE # ════════════════════════════════════════════════════════════════════════════ tone = make_section( '2. TONE (Passive movement; heel-drop test)', col_widths=[38*mm, 43*mm, 43*mm, 41*mm], col_headers=['Finding', 'Right', 'Left', 'Comment'], data_rows=[ ['Tone Character', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', ''], ['Cogwheel Rigidity', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Heel-Drop Test\n(rapid knee lift)', '☐ Normal heel drag\n☐ Immediate heel lift', '☐ Normal heel drag\n☐ Immediate heel lift', ''], ['Hip', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Knee', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Ankle','☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ] ) story.append(tone) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 3. POWER # ════════════════════════════════════════════════════════════════════════════ mrc_legend = Table([[Paragraph( '<b>MRC Scale:</b> 0 = No movement 1 = Flicker 2 = Gravity eliminated ' '3 = Against gravity 4⁻ = Mild resistance 4 = Moderate resistance ' '4⁺ = Strong resistance 5 = Full power', sSmall) ]], colWidths=[USABLE_W]) mrc_legend.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#FFF4E5')), ('BOX', (0,0), (-1,-1), 0.5, ACCENT), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 6), ])) power_data = [ ['Hip Flexion (Iliopsoas)', 'L1–L3', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Hip Extension (Glut. Max.)', 'L4–S1', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Hip Abduction (Glut. Med.)', 'L4–S1', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Hip Adduction', 'L2–L4', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Knee Extension (Quadriceps)', 'L3–L4', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Knee Flexion (Hamstrings)', 'L5–S2', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Dorsiflexion (Tib. Ant.)','L4–L5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Plantarflexion', 'S1–S2', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Eversion (Peronei)', 'L5–S1', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Inversion (Tib. Post.)', 'L4–L5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Great Toe Extension (EHL)', 'L5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Toe Flexion', 'S1–S2', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ] pow_tbl = make_section( '3. POWER (MRC Grade 0–5; circle the grade)', col_widths=[48*mm, 14*mm, 37*mm, 37*mm, 29*mm], col_headers=['Movement', 'Root', 'Right (circle)', 'Left (circle)', 'Notes'], data_rows=power_data ) story.append(KeepTogether([mrc_legend, Spacer(1,2*mm), pow_tbl])) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 4. REFLEXES # ════════════════════════════════════════════════════════════════════════════ ref_legend = Table([[Paragraph( '<b>Reflex Grade:</b> 0 = Absent 1 = Diminished 2 = Normal ' '3 = Brisk / Increased 4 = Clonus', sSmall) ]], colWidths=[USABLE_W]) ref_legend.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#EEF8EE')), ('BOX', (0,0), (-1,-1), 0.5, colors.HexColor('#4CAF50')), ('TOPPADDING', (0,0), (-1,-1), 3), ('BOTTOMPADDING', (0,0), (-1,-1), 3), ('LEFTPADDING', (0,0), (-1,-1), 6), ])) # sub-header rows: index within data_rows reflex_data = [ ['DEEP TENDON REFLEXES'], # sub-hdr index 0 ['Knee Jerk (Patellar)', 'L3–L4', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ['Ankle Jerk (Achilles)', 'S1–S2', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ['CLONUS'], # sub-hdr index 3 ['Ankle Clonus', 'S1–S2', '☐ Absent ☐ Present Beats:___', '☐ Absent ☐ Present Beats:___', ''], ['Patellar Clonus', 'L2–L4', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['SUPERFICIAL / CUTANEOUS REFLEXES'], # sub-hdr index 6 ['Plantar Reflex', 'S1', '☐ Flexor (normal)\n☐ Extensor (Babinski +ve)\n☐ No response', '☐ Flexor (normal)\n☐ Extensor (Babinski +ve)\n☐ No response', ''], ['Cremasteric', 'L1–L2', '☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ['Abdominal', 'T7–T12','☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ['Anal Reflex', 'S2–S4', '☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ] sub_hdr_map = {0: colors.HexColor('#3A7EBF'), 3: colors.HexColor('#3A7EBF'), 6: colors.HexColor('#3A7EBF')} ref_tbl = make_section( '4. REFLEXES', col_widths=[44*mm, 14*mm, 38*mm, 38*mm, 31*mm], col_headers=['Reflex', 'Root', 'Right', 'Left', 'Notes'], data_rows=reflex_data, sub_hdr_rows=sub_hdr_map ) story.append(KeepTogether([ref_legend, Spacer(1,2*mm), ref_tbl])) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 5. COORDINATION # ════════════════════════════════════════════════════════════════════════════ coord_data = [ ['Heel-Shin Test', '☐ Normal ☐ Ataxia ☐ Dysmetria', '☐ Normal ☐ Ataxia ☐ Dysmetria', ''], ['Foot Tapping (rapid)', '☐ Normal ☐ Bradykinesia\n☐ Dysdiadochokinesia', '☐ Normal ☐ Bradykinesia\n☐ Dysdiadochokinesia', ''], ['Foot Circle Drawing', '☐ Normal ☐ Dyspraxia ☐ Abnormal', '☐ Normal ☐ Dyspraxia ☐ Abnormal', ''], ['Romberg\'s Test', 'Eyes open: ☐ Stable ☐ Unsteady', 'Eyes closed: ☐ Stable ☐ Falls → ☐ +ve ☐ −ve', ''], ] coord_tbl = make_section( '5. COORDINATION (Cerebellar / Proprioceptive Function)', col_widths=[38*mm, 46*mm, 50*mm, 31*mm], col_headers=['Test', 'Right', 'Left / Bilateral', 'Notes'], data_rows=coord_data ) story.append(coord_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 6. GAIT # ════════════════════════════════════════════════════════════════════════════ gait_data = [ ['TICK ALL THAT APPLY'], # sub-hdr ['☐ Normal', 'Pattern', ''], ['☐ Hemiplegic (circumduction)', '☐ Spastic paraparesis (scissor)', ''], ['☐ Steppage / Foot drop', '☐ Waddling (proximal weakness)', ''], ['☐ Ataxic (broad-based)', '☐ Parkinsonian (shuffling / festination)', ''], ['☐ Antalgic (pain-avoidance)', '☐ Trendelenburg (hip abductor weakness)', ''], ['☐ Functional / Non-organic', '☐ Other: ___________________', ''], ['Assistive Device', '☐ None ☐ Walking stick ☐ Frame ☐ Crutches ☐ Wheelchair', ''], ['Additional Observations', '____________________________________________', ''], ] gait_tbl = make_section( '6. GAIT (Examine last; observe before recumbent exam)', col_widths=[55*mm, 75*mm, 35*mm], col_headers=['', 'Finding', 'Notes'], data_rows=gait_data, sub_hdr_rows={0: colors.HexColor('#3A7EBF')} ) story.append(gait_tbl) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 7. UMN vs LMN SUMMARY # ════════════════════════════════════════════════════════════════════════════ summ_data = [ ['Muscle Bulk', 'Normal / mild disuse atrophy', 'Marked wasting', ''], ['Fasciculations', 'Absent', 'Present', ''], ['Tone', 'Increased (spasticity)', 'Decreased (flaccidity)', ''], ['Power', 'Reduced (pyramidal pattern)', 'Reduced (root/nerve)', ''], ['Deep Reflexes', 'Hyperreflexia', 'Diminished / Absent', ''], ['Plantar Reflex', 'Extensor (Babinski +ve)', 'Flexor / Absent', ''], ['Clonus', 'May be present', 'Absent', ''], ['Superficial Reflex','Absent', 'Present / Absent', ''], ] summ_tbl = make_section( '7. PATTERN SUMMARY (UMN vs LMN)', col_widths=[38*mm, 46*mm, 46*mm, 35*mm], col_headers=['Feature', 'UMN (Central)', 'LMN (Peripheral)', 'Patient Findings'], data_rows=summ_data, hdr_color=colors.HexColor('#2E7D32') ) # Clinical pattern selector (appended below the table as a separate mini-table) pattern_sel = Table([[ Paragraph('<b>Overall Clinical Pattern:</b>', sLabel), Paragraph('☐ Normal ☐ UMN ☐ LMN ☐ Mixed ☐ Cerebellar ☐ Extrapyramidal ☐ Functional', sBody) ]], colWidths=[45*mm, USABLE_W - 45*mm]) pattern_sel.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#FFF4E5')), ('BOX', (0,0), (-1,-1), 0.8, ACCENT), ('TOPPADDING', (0,0), (-1,-1), 4), ('BOTTOMPADDING', (0,0), (-1,-1), 4), ('LEFTPADDING', (0,0), (-1,-1), 6), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(KeepTogether([summ_tbl, Spacer(1,2*mm), pattern_sel])) story.append(Spacer(1, 4*mm)) # ════════════════════════════════════════════════════════════════════════════ # 8. CLINICAL NOTES + SIGNATURE # ════════════════════════════════════════════════════════════════════════════ notes_tbl = Table([ [Paragraph(' 8. CLINICAL SUMMARY & PLAN', sSecHdr)], [Paragraph('Impression / Provisional Diagnosis:', sLabel)], [Paragraph('_' * 110, sBody)], [Paragraph('_' * 110, sBody)], [Paragraph('Further Investigations Requested:', sLabel)], [Paragraph('_' * 110, sBody)], [Paragraph('Management / Referral Plan:', sLabel)], [Paragraph('_' * 110, sBody)], ], colWidths=[USABLE_W]) notes_tbl.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,0), MID_BLUE), ('TOPPADDING', (0,0), (-1,0), 5), ('BOTTOMPADDING', (0,0), (-1,0), 5), ('LEFTPADDING', (0,0), (-1,0), 6), ('BOX', (0,0), (-1,-1), 0.8, MID_BLUE), ('INNERGRID', (0,0), (-1,-1), 0.3, MID_GREY), ('ROWBACKGROUNDS', (0,1), (-1,-1), [PALE_BLUE, WHITE]), ('TOPPADDING', (0,1), (-1,-1), 3), ('BOTTOMPADDING', (0,1), (-1,-1), 3), ('LEFTPADDING', (0,1), (-1,-1), 6), ('FONTSIZE', (0,0), (-1,-1), 7.5), ])) story.append(notes_tbl) story.append(Spacer(1, 5*mm)) # Signature sig_tbl = Table([[ Paragraph('Examiner Signature: ___________________________', sBody), Paragraph('Designation: _______________________', sBody), Paragraph('Date: ________________', sBody), ]], colWidths=[65*mm, 60*mm, 40*mm]) sig_tbl.setStyle(TableStyle([ ('TOPPADDING', (0,0), (-1,-1), 4), ('LEFTPADDING', (0,0), (-1,-1), 4), ('FONTSIZE', (0,0), (-1,-1), 7.5), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ])) story.append(sig_tbl) story.append(Spacer(1, 3*mm)) # Footer footer = Table([[ Paragraph('Sequence: Inspection → Tone → Power → Reflexes → Coordination → Gait', sNote), Paragraph("Harrison's 22E · Bradley & Daroff's Neurology · Das Manual of Clinical Surgery", ParagraphStyle('ftr', parent=sNote, alignment=TA_RIGHT)) ]], colWidths=[90*mm, 75*mm]) footer.setStyle(TableStyle([ ('LINEABOVE', (0,0), (-1,0), 0.5, MID_GREY), ('TOPPADDING', (0,0), (-1,-1), 2), ('BOTTOMPADDING', (0,0), (-1,-1), 2), ])) story.append(footer) doc.build(story) print("PDF created successfully at /tmp/workspace/motor-assessment/Lower_Limb_Motor_Assessment.pdf") PYEOF
Lower Limb Motor Assessment
PDF Document · PDF
| Section | Contents |
|---|---|
| Patient Details | Name, Age/Sex, Hospital No., Date, Examiner, Ward/OPD, Dominant side |
| 1. Inspection | Muscle bulk, fasciculations, involuntary movements, posture, trophic changes, scars, limb length - with Right/Left columns |
| 2. Tone | Character (normal/hypotonia/spasticity/rigidity/paratonia), cogwheel rigidity, heel-drop test, joint-by-joint (hip/knee/ankle) |
| 3. Power | All 12 lower limb movements with root levels, MRC 0–5 circle-to-grade format, with a colour-coded MRC legend strip |
| 4. Reflexes | Deep tendon (knee/ankle jerk), clonus (ankle/patellar), superficial (plantar/Babinski, cremasteric, abdominal, anal) - with reflex grade legend |
| 5. Coordination | Heel-shin test, foot tapping, foot circle, Romberg's test |
| 6. Gait | All major gait patterns as tickboxes, assistive device, free-text observation |
| 7. UMN vs LMN Pattern Summary | Side-by-side comparison table + clinical pattern selector |
| 8. Clinical Summary | Impression, investigations, management/referral, signature block |
Generate a Word (.docx) version for easier digital note-taking and editing
python3 << 'PYEOF' 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 # ── colour helpers ──────────────────────────────────────────────────────────── def hex_rgb(h): h = h.lstrip('#') return RGBColor(int(h[0:2],16), int(h[2:4],16), int(h[4:6],16)) C_DARK_BLUE = '1A3A5C' C_MID_BLUE = '2E6DA4' C_LIGHT_BLUE = 'D6E8F7' C_PALE_BLUE = 'EEF5FB' C_ACCENT = 'E8821A' C_GREEN = '2E7D32' C_LT_GREEN = 'EEF8EE' C_GREY = 'F2F4F7' C_MID_GREY = 'CCCCCC' C_SUB_BLUE = '3A7EBF' C_ORANGE_LT = 'FFF4E5' C_WHITE = 'FFFFFF' C_BLACK = '000000' def set_cell_bg(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_borders(cell, top=None, bottom=None, left=None, right=None, color='CCCCCC', sz='4'): tc = cell._tc tcPr = tc.get_or_add_tcPr() tcBorders = OxmlElement('w:tcBorders') for side, val in [('top',top),('bottom',bottom),('left',left),('right',right)]: if val: el = OxmlElement(f'w:{side}') el.set(qn('w:val'), val) el.set(qn('w:sz'), sz) el.set(qn('w:space'),'0') el.set(qn('w:color'), color) tcBorders.append(el) tcPr.append(tcBorders) def set_table_outer_border(table, color='2E6DA4', sz='8'): for row in table.rows: for i, cell in enumerate(row.cells): is_first_row = (row == table.rows[0]) is_last_row = (row == table.rows[-1]) is_first_col = (i == 0) is_last_col = (i == len(row.cells)-1) tc = cell._tc tcPr = tc.get_or_add_tcPr() tcBorders = OxmlElement('w:tcBorders') for side in ['top','bottom','left','right']: el = OxmlElement(f'w:{side}') if ((side=='top' and is_first_row) or (side=='bottom' and is_last_row) or (side=='left' and is_first_col) or (side=='right' and is_last_col)): el.set(qn('w:val'), 'single') el.set(qn('w:sz'), sz) el.set(qn('w:space'),'0') el.set(qn('w:color'), color) else: el.set(qn('w:val'), 'single') el.set(qn('w:sz'), '2') el.set(qn('w:space'),'0') el.set(qn('w:color'), 'CCCCCC') tcBorders.append(el) tcPr.append(tcBorders) def set_col_widths(table, widths_cm): for i, row in enumerate(table.rows): for j, cell in enumerate(row.cells): tc = cell._tc tcPr = tc.get_or_add_tcPr() tcW = OxmlElement('w:tcW') tcW.set(qn('w:w'), str(int(widths_cm[j] * 567))) # 1 cm ≈ 567 twips tcW.set(qn('w:type'), 'dxa') tcPr.append(tcW) def para_in_cell(cell, text, bold=False, italic=False, size=9, color=C_BLACK, align=WD_ALIGN_PARAGRAPH.LEFT, bg=None, space_before=0, space_after=0): if bg: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER p = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph() p.clear() p.alignment = align p.paragraph_format.space_before = Pt(space_before) p.paragraph_format.space_after = Pt(space_after) p.paragraph_format.left_indent = Pt(0) run = p.add_run(text) run.bold = bold run.italic = italic run.font.size = Pt(size) run.font.color.rgb = hex_rgb(color) run.font.name = 'Calibri' return p def add_para_to_cell(cell, text, bold=False, italic=False, size=9, color=C_BLACK, align=WD_ALIGN_PARAGRAPH.LEFT): p = cell.add_paragraph() p.alignment = align p.paragraph_format.space_before = Pt(0) p.paragraph_format.space_after = Pt(0) run = p.add_run(text) run.bold = bold run.italic = italic run.font.size = Pt(size) run.font.color.rgb = hex_rgb(color) run.font.name = 'Calibri' return p # ── Document setup ──────────────────────────────────────────────────────────── doc = Document() # Page margins for section in doc.sections: section.page_width = Cm(21) section.page_height = Cm(29.7) section.left_margin = Cm(1.5) section.right_margin = Cm(1.5) section.top_margin = Cm(1.5) section.bottom_margin = Cm(1.5) # Normal style normal = doc.styles['Normal'] normal.font.name = 'Calibri' normal.font.size = Pt(10) def add_space(doc, pt=4): p = doc.add_paragraph() p.paragraph_format.space_before = Pt(0) p.paragraph_format.space_after = Pt(pt) # ──────────────────────────────────────────────────────────────────────────── # BANNER # ──────────────────────────────────────────────────────────────────────────── banner_tbl = doc.add_table(rows=2, cols=1) banner_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER banner_tbl.style = 'Table Grid' set_cell_bg(banner_tbl.rows[0].cells[0], C_DARK_BLUE) set_cell_bg(banner_tbl.rows[1].cells[0], C_DARK_BLUE) para_in_cell(banner_tbl.rows[0].cells[0], 'MOTOR ASSESSMENT OF THE LOWER LIMBS', bold=True, size=16, color=C_WHITE, align=WD_ALIGN_PARAGRAPH.CENTER, space_before=6, space_after=2) para_in_cell(banner_tbl.rows[1].cells[0], 'Neurological Examination Proforma · Patient Record / Documentation', bold=False, size=10, color='BFD9F2', align=WD_ALIGN_PARAGRAPH.CENTER, space_before=2, space_after=6) set_table_outer_border(banner_tbl, color=C_MID_BLUE, sz='12') add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # PATIENT DETAILS # ──────────────────────────────────────────────────────────────────────────── def add_detail_row(table, row_idx, items): # items: list of (label, value) pairs row = table.rows[row_idx] cells = row.cells ci = 0 for label, value in items: para_in_cell(cells[ci], label, bold=True, size=9, color=C_DARK_BLUE, bg=C_PALE_BLUE) para_in_cell(cells[ci+1], value, bold=False, size=9, bg=C_PALE_BLUE) ci += 2 detail_tbl = doc.add_table(rows=4, cols=4) detail_tbl.style = 'Table Grid' detail_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(detail_tbl, [3.5, 5.5, 3.2, 5.8]) add_detail_row(detail_tbl, 0, [('Patient Name:', ''), ('Date:', '')]) add_detail_row(detail_tbl, 1, [('Age / Sex:', ' / ☐ M ☐ F ☐ Other'), ('Examiner:', '')]) add_detail_row(detail_tbl, 2, [('Hospital No.:', ''), ('Ward / OPD:', '')]) add_detail_row(detail_tbl, 3, [('Diagnosis / Complaint:', ''), ('Dominant Side:', '☐ Right ☐ Left')]) set_table_outer_border(detail_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # Helper: section header row # ──────────────────────────────────────────────────────────────────────────── def add_section_header(table, title, color=C_MID_BLUE, ncols=None): """Merge first row across all cols and colour it as a section header.""" row = table.rows[0] if ncols is None: ncols = len(row.cells) # merge if ncols > 1: row.cells[0].merge(row.cells[ncols-1]) para_in_cell(row.cells[0], f' {title}', bold=True, size=9.5, color=C_WHITE, bg=color, space_before=4, space_after=4) def add_col_headers(table, row_idx, headers, bg=C_LIGHT_BLUE): row = table.rows[row_idx] for i, h in enumerate(headers): para_in_cell(row.cells[i], h, bold=True, size=8.5, align=WD_ALIGN_PARAGRAPH.CENTER, bg=bg) def style_data_rows(table, start_row=2): for i, row in enumerate(table.rows[start_row:], start=start_row): bg = C_WHITE if i % 2 == 0 else C_GREY for cell in row.cells: if not cell._tc.find(qn('w:shd')): set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER def fill_data_row(table, row_idx, cells_data, label_col=0): """cells_data: list of strings or (text, extra_lines) tuples.""" row = table.rows[row_idx] for j, data in enumerate(cells_data): cell = row.cells[j] if isinstance(data, tuple): text, extras = data[0], data[1:] para_in_cell(cell, text, bold=(j == label_col), size=9, color=C_DARK_BLUE if j==label_col else C_BLACK) for ex in extras: add_para_to_cell(cell, ex, size=8.5) else: para_in_cell(cell, str(data), bold=(j == label_col), size=9, color=C_DARK_BLUE if j==label_col else C_BLACK) # ──────────────────────────────────────────────────────────────────────────── # 1. INSPECTION (1 header + 1 col-hdr + 7 data rows = 9 rows, 4 cols) # ──────────────────────────────────────────────────────────────────────────── insp_tbl = doc.add_table(rows=9, cols=4) insp_tbl.style = 'Table Grid' insp_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(insp_tbl, [4.0, 4.5, 4.5, 5.0]) add_section_header(insp_tbl, '1. INSPECTION (Patient supine, fully exposed, good lighting)') add_col_headers(insp_tbl, 1, ['Feature', 'Right', 'Left', 'Notes']) insp_data = [ ['Muscle Bulk', '☐ Normal ☐ Wasted ☐ Hypertrophy', '☐ Normal ☐ Wasted ☐ Hypertrophy', ''], ['Fasciculations', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Involuntary Movements', '☐ None ☐ Tremor ☐ Myoclonus\n☐ Other: ___', '☐ None ☐ Tremor ☐ Myoclonus\n☐ Other: ___', ''], ['Limb Posture', '☐ Normal ☐ Foot drop\n☐ Scissoring ☐ Other', '☐ Normal ☐ Foot drop\n☐ Scissoring ☐ Other', ''], ['Trophic / Skin Changes', '☐ Absent ☐ Present (describe):', '☐ Absent ☐ Present (describe):', ''], ['Scars / Deformity', '☐ None ☐ Present', '☐ None ☐ Present', ''], ['Limb Length', 'R: _______ cm', 'L: _______ cm', '☐ Symmetric ☐ Asymmetric'], ] for i, row_data in enumerate(insp_data): fill_data_row(insp_tbl, i+2, row_data) style_data_rows(insp_tbl, 2) set_table_outer_border(insp_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # 2. TONE (1 header + 1 col-hdr + 6 data rows = 8 rows, 4 cols) # ──────────────────────────────────────────────────────────────────────────── tone_tbl = doc.add_table(rows=8, cols=4) tone_tbl.style = 'Table Grid' tone_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(tone_tbl, [4.0, 4.5, 4.5, 5.0]) add_section_header(tone_tbl, '2. TONE (Passive movement; heel-drop test)') add_col_headers(tone_tbl, 1, ['Finding', 'Right', 'Left', 'Comment']) tone_data = [ ['Tone Character', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', ''], ['Cogwheel Rigidity', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Heel-Drop Test\n(rapid knee lift)', '☐ Normal heel drag\n☐ Immediate heel lift', '☐ Normal heel drag\n☐ Immediate heel lift', ''], ['Hip', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Knee', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Ankle', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ] for i, row_data in enumerate(tone_data): fill_data_row(tone_tbl, i+2, row_data) style_data_rows(tone_tbl, 2) set_table_outer_border(tone_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # MRC LEGEND (coloured callout paragraph) # ──────────────────────────────────────────────────────────────────────────── mrc_leg = doc.add_table(rows=1, cols=1) mrc_leg.style = 'Table Grid' set_cell_bg(mrc_leg.rows[0].cells[0], C_ORANGE_LT) set_table_outer_border(mrc_leg, color=C_ACCENT, sz='4') set_col_widths(mrc_leg, [18.0]) cell = mrc_leg.rows[0].cells[0] p = cell.paragraphs[0] p.paragraph_format.space_before = Pt(3) p.paragraph_format.space_after = Pt(3) r = p.add_run('MRC Scale: ') r.bold = True; r.font.size = Pt(8.5); r.font.name = 'Calibri'; r.font.color.rgb = hex_rgb(C_DARK_BLUE) r2 = p.add_run('0 = No movement 1 = Flicker 2 = Gravity eliminated 3 = Against gravity ' '4⁻ = Mild resistance 4 = Moderate resistance 4⁺ = Strong resistance 5 = Full power') r2.font.size = Pt(8.5); r2.font.name = 'Calibri'; r2.font.color.rgb = hex_rgb('444444') add_space(doc, 2) # ──────────────────────────────────────────────────────────────────────────── # 3. POWER (1 header + 1 col-hdr + 12 data rows = 14 rows, 5 cols) # ──────────────────────────────────────────────────────────────────────────── pow_tbl = doc.add_table(rows=14, cols=5) pow_tbl.style = 'Table Grid' pow_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(pow_tbl, [5.2, 1.5, 4.0, 4.0, 3.3]) add_section_header(pow_tbl, '3. POWER (MRC Grade 0–5; circle the grade for each side)') add_col_headers(pow_tbl, 1, ['Movement', 'Root', 'Right (circle)', 'Left (circle)', 'Notes']) power_rows = [ ['Hip Flexion (Iliopsoas)', 'L1–L3', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Hip Extension (Glut. Max.)', 'L4–S1', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Hip Abduction (Glut. Med.)', 'L4–S1', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Hip Adduction', 'L2–L4', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Knee Extension (Quadriceps)', 'L3–L4', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Knee Flexion (Hamstrings)', 'L5–S2', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Dorsiflexion (Tib. Ant.)','L4–L5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Plantarflexion', 'S1–S2', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Eversion (Peronei)', 'L5–S1', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Ankle Inversion (Tib. Post.)', 'L4–L5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Great Toe Extension (EHL)', 'L5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ['Toe Flexion', 'S1–S2', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', '0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5', ''], ] for i, row_data in enumerate(power_rows): fill_data_row(pow_tbl, i+2, row_data) style_data_rows(pow_tbl, 2) set_table_outer_border(pow_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # REFLEX LEGEND # ──────────────────────────────────────────────────────────────────────────── ref_leg = doc.add_table(rows=1, cols=1) ref_leg.style = 'Table Grid' set_cell_bg(ref_leg.rows[0].cells[0], C_LT_GREEN) set_table_outer_border(ref_leg, color=C_GREEN, sz='4') set_col_widths(ref_leg, [18.0]) cell = ref_leg.rows[0].cells[0] p = cell.paragraphs[0] p.paragraph_format.space_before = Pt(3); p.paragraph_format.space_after = Pt(3) r = p.add_run('Reflex Grade: ') r.bold = True; r.font.size = Pt(8.5); r.font.name = 'Calibri'; r.font.color.rgb = hex_rgb(C_GREEN) r2 = p.add_run('0 = Absent 1 = Diminished 2 = Normal 3 = Brisk / Increased 4 = Clonus') r2.font.size = Pt(8.5); r2.font.name = 'Calibri'; r2.font.color.rgb = hex_rgb('444444') add_space(doc, 2) # ──────────────────────────────────────────────────────────────────────────── # 4. REFLEXES (header + col-hdr + 3 sub-headers + 8 data = 14 rows, 5 cols) # ──────────────────────────────────────────────────────────────────────────── ref_tbl = doc.add_table(rows=14, cols=5) ref_tbl.style = 'Table Grid' ref_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(ref_tbl, [4.6, 1.5, 4.0, 4.0, 3.9]) add_section_header(ref_tbl, '4. REFLEXES') add_col_headers(ref_tbl, 1, ['Reflex', 'Root', 'Right', 'Left', 'Notes']) reflex_rows = [ ('subhdr', 'DEEP TENDON REFLEXES'), ['Knee Jerk (Patellar)', 'L3–L4', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ['Ankle Jerk (Achilles)', 'S1–S2', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ('subhdr', 'CLONUS'), ['Ankle Clonus', 'S1–S2', '☐ Absent ☐ Present\nBeats: ___', '☐ Absent ☐ Present\nBeats: ___', ''], ['Patellar Clonus', 'L2–L4', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ('subhdr', 'SUPERFICIAL / CUTANEOUS REFLEXES'), ['Plantar Reflex', 'S1', '☐ Flexor (normal)\n☐ Extensor (Babinski +ve)\n☐ No response', '☐ Flexor (normal)\n☐ Extensor (Babinski +ve)\n☐ No response', ''], ['Cremasteric', 'L1–L2', '☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ['Abdominal', 'T7–T12','☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ['Anal Reflex', 'S2–S4', '☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ] row_idx = 2 for item in reflex_rows: row = ref_tbl.rows[row_idx] if isinstance(item, tuple) and item[0] == 'subhdr': # sub-header: merge all cols, colour darker blue row.cells[0].merge(row.cells[4]) para_in_cell(row.cells[0], f' {item[1]}', bold=True, size=8.5, color=C_WHITE, bg=C_SUB_BLUE, space_before=3, space_after=3) else: fill_data_row(ref_tbl, row_idx, item) bg = C_WHITE if row_idx % 2 == 0 else C_GREY for j, cell in enumerate(row.cells): existing_shd = cell._tc.find(qn('w:shd')) if existing_shd is None: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER row_idx += 1 set_table_outer_border(ref_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # 5. COORDINATION (header + col-hdr + 4 data rows = 6 rows, 4 cols) # ──────────────────────────────────────────────────────────────────────────── coord_tbl = doc.add_table(rows=6, cols=4) coord_tbl.style = 'Table Grid' coord_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(coord_tbl, [4.0, 4.8, 5.2, 4.0]) add_section_header(coord_tbl, '5. COORDINATION (Cerebellar / Proprioceptive Function)') add_col_headers(coord_tbl, 1, ['Test', 'Right', 'Left / Bilateral', 'Notes']) coord_data = [ ['Heel-Shin Test', '☐ Normal ☐ Ataxia ☐ Dysmetria', '☐ Normal ☐ Ataxia ☐ Dysmetria', ''], ['Foot Tapping (rapid)', '☐ Normal ☐ Bradykinesia\n☐ Dysdiadochokinesia', '☐ Normal ☐ Bradykinesia\n☐ Dysdiadochokinesia', ''], ['Foot Circle Drawing', '☐ Normal ☐ Dyspraxia ☐ Abnormal', '☐ Normal ☐ Dyspraxia ☐ Abnormal', ''], ["Romberg's Test", 'Eyes open: ☐ Stable ☐ Unsteady', 'Eyes closed: ☐ Stable ☐ Falls\n→ Result: ☐ +ve ☐ −ve', ''], ] for i, row_data in enumerate(coord_data): fill_data_row(coord_tbl, i+2, row_data) style_data_rows(coord_tbl, 2) set_table_outer_border(coord_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # 6. GAIT (header + col-hdr + 1 sub-hdr + 8 data = 11 rows, 3 cols) # ──────────────────────────────────────────────────────────────────────────── gait_tbl = doc.add_table(rows=11, cols=3) gait_tbl.style = 'Table Grid' gait_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(gait_tbl, [3.0, 8.0, 7.0]) add_section_header(gait_tbl, '6. GAIT (Examine last; observe before recumbent examination)') add_col_headers(gait_tbl, 1, ['', 'Gait Pattern', 'Finding / Notes']) # sub-header gait_tbl.rows[2].cells[0].merge(gait_tbl.rows[2].cells[2]) para_in_cell(gait_tbl.rows[2].cells[0], ' TICK ALL PATTERNS THAT APPLY', bold=True, size=8.5, color=C_WHITE, bg=C_SUB_BLUE, space_before=3, space_after=3) gait_data = [ ['☐ Normal', '', ''], ['☐ Hemiplegic (circumduction)', '☐ Spastic paraparesis (scissor)', ''], ['☐ Steppage / Foot drop', '☐ Waddling (proximal weakness)', ''], ['☐ Ataxic (broad-based)', '☐ Parkinsonian (shuffling / festination)', ''], ['☐ Antalgic (pain-avoidance)', '☐ Trendelenburg (hip abductor weakness)', ''], ['☐ Functional / Non-organic', '☐ Other: ___________________', ''], ['Assistive Device', '☐ None ☐ Walking stick ☐ Frame ☐ Crutches ☐ Wheelchair', ''], ['Additional Observations', '', ''], ] for i, row_data in enumerate(gait_data): ri = i + 3 row = gait_tbl.rows[ri] fill_data_row(gait_tbl, ri, row_data) bg = C_WHITE if ri % 2 == 0 else C_GREY for cell in row.cells: if cell._tc.find(qn('w:shd')) is None: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER set_table_outer_border(gait_tbl) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # 7. UMN vs LMN SUMMARY # ──────────────────────────────────────────────────────────────────────────── summ_tbl = doc.add_table(rows=10, cols=4) summ_tbl.style = 'Table Grid' summ_tbl.alignment = WD_TABLE_ALIGNMENT.CENTER set_col_widths(summ_tbl, [4.0, 4.8, 4.8, 4.4]) add_section_header(summ_tbl, '7. PATTERN SUMMARY (UMN vs LMN)', color=C_GREEN) add_col_headers(summ_tbl, 1, ['Feature', 'UMN (Central)', 'LMN (Peripheral)', 'Patient Findings']) summ_data = [ ['Muscle Bulk', 'Normal / mild disuse atrophy', 'Marked wasting', ''], ['Fasciculations', 'Absent', 'Present', ''], ['Tone', 'Increased (spasticity)', 'Decreased (flaccidity)', ''], ['Power', 'Reduced (pyramidal pattern)', 'Reduced (root/nerve)', ''], ['Deep Reflexes', 'Hyperreflexia', 'Diminished / Absent', ''], ['Plantar Reflex', 'Extensor (Babinski +ve)', 'Flexor / Absent', ''], ['Clonus', 'May be present', 'Absent', ''], ['Superficial Reflex','Absent', 'Present / Absent', ''], ] for i, row_data in enumerate(summ_data): fill_data_row(summ_tbl, i+2, row_data) style_data_rows(summ_tbl, 2) set_table_outer_border(summ_tbl) add_space(doc, 2) # Pattern selector pat_tbl = doc.add_table(rows=1, cols=2) pat_tbl.style = 'Table Grid' set_col_widths(pat_tbl, [5.0, 13.0]) set_cell_bg(pat_tbl.rows[0].cells[0], C_ORANGE_LT) set_cell_bg(pat_tbl.rows[0].cells[1], C_ORANGE_LT) set_table_outer_border(pat_tbl, color=C_ACCENT, sz='6') para_in_cell(pat_tbl.rows[0].cells[0], 'Overall Clinical Pattern:', bold=True, size=9, color=C_DARK_BLUE) para_in_cell(pat_tbl.rows[0].cells[1], '☐ Normal ☐ UMN ☐ LMN ☐ Mixed ☐ Cerebellar ☐ Extrapyramidal ☐ Functional', size=9) add_space(doc, 4) # ──────────────────────────────────────────────────────────────────────────── # 8. CLINICAL NOTES # ──────────────────────────────────────────────────────────────────────────── notes_tbl = doc.add_table(rows=9, cols=1) notes_tbl.style = 'Table Grid' set_col_widths(notes_tbl, [18.0]) add_section_header(notes_tbl, '8. CLINICAL SUMMARY & PLAN') notes_fields = [ ('Impression / Provisional Diagnosis:', True), ('', False), ('', False), ('Further Investigations Requested:', True), ('', False), ('Management / Referral Plan:', True), ('', False), ('', False), ] for i, (label, is_label) in enumerate(notes_fields): cell = notes_tbl.rows[i+1].cells[0] bg = C_PALE_BLUE if is_label else C_WHITE set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER para_in_cell(cell, label, bold=is_label, size=9, color=C_DARK_BLUE if is_label else C_BLACK) set_table_outer_border(notes_tbl) add_space(doc, 5) # Signature sig_tbl = doc.add_table(rows=1, cols=3) sig_tbl.style = 'Table Grid' set_col_widths(sig_tbl, [6.5, 6.5, 5.0]) para_in_cell(sig_tbl.rows[0].cells[0], 'Examiner Signature: ___________________', size=9) para_in_cell(sig_tbl.rows[0].cells[1], 'Designation: _______________________', size=9) para_in_cell(sig_tbl.rows[0].cells[2], 'Date: ________________', size=9) set_table_outer_border(sig_tbl, color=C_MID_GREY, sz='4') add_space(doc, 3) # Footer paragraph footer_p = doc.add_paragraph() footer_p.paragraph_format.space_before = Pt(2) footer_p.paragraph_format.space_after = Pt(0) pPr = footer_p._p.get_or_add_pPr() pBdr = OxmlElement('w:pBdr') top_el = OxmlElement('w:top') top_el.set(qn('w:val'), 'single'); top_el.set(qn('w:sz'), '4') top_el.set(qn('w:space'), '1'); top_el.set(qn('w:color'), 'CCCCCC') pBdr.append(top_el) pPr.append(pBdr) r_left = footer_p.add_run('Sequence: Inspection → Tone → Power → Reflexes → Coordination → Gait') r_left.italic = True; r_left.font.size = Pt(7.5); r_left.font.name = 'Calibri' r_left.font.color.rgb = hex_rgb('888888') r_tab = footer_p.add_run(' ') r_right = footer_p.add_run("Harrison's 22E · Bradley & Daroff's Neurology · Das Manual of Clinical Surgery") r_right.italic = True; r_right.font.size = Pt(7.5); r_right.font.name = 'Calibri' r_right.font.color.rgb = hex_rgb('AAAAAA') # ── Save ───────────────────────────────────────────────────────────────────── out_path = '/tmp/workspace/motor-assessment/Lower_Limb_Motor_Assessment.docx' doc.save(out_path) print(f"DOCX saved: {out_path}") PYEOF
Lower Limb Motor Assessment
Word Document · DOCX
0 / 1 / 2 / 3 / 4⁻/ 4 / 4⁺/ 5 - just bold or highlight the chosen grade digitallyCtrl+H (Find & Replace) to swap ☐ → ☑ for checked itemsCtrl+Alt+M) to flag findings for follow-upSIMILARLY MAKE A WORD FILE FOR UPPER LIMB EXAMINATION
python3 << 'PYEOF' from docx import Document from docx.shared import Pt, Cm, RGBColor 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 # ── colours ─────────────────────────────────────────────────────────────────── def hex_rgb(h): h = h.lstrip('#') return RGBColor(int(h[0:2],16), int(h[2:4],16), int(h[4:6],16)) C_DARK_BLUE = '1A3A5C' C_MID_BLUE = '2E6DA4' C_LIGHT_BLUE= 'D6E8F7' C_PALE_BLUE = 'EEF5FB' C_ACCENT = 'E8821A' C_GREEN = '2E7D32' C_LT_GREEN = 'EEF8EE' C_GREY = 'F2F4F7' C_MID_GREY = 'CCCCCC' C_SUB_BLUE = '3A7EBF' C_ORANGE_LT = 'FFF4E5' C_WHITE = 'FFFFFF' C_BLACK = '000000' # ── XML helpers ─────────────────────────────────────────────────────────────── def set_cell_bg(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_table_outer_border(table, color=C_MID_BLUE, sz='8'): for row in table.rows: for i, cell in enumerate(row.cells): is_first_row = (row == table.rows[0]) is_last_row = (row == table.rows[-1]) is_first_col = (i == 0) is_last_col = (i == len(row.cells)-1) tc = cell._tc; tcPr = tc.get_or_add_tcPr() tcBorders = OxmlElement('w:tcBorders') for side in ['top','bottom','left','right']: el = OxmlElement(f'w:{side}') outer = ((side=='top' and is_first_row) or (side=='bottom' and is_last_row) or (side=='left' and is_first_col) or (side=='right' and is_last_col)) el.set(qn('w:val'), 'single') el.set(qn('w:sz'), sz if outer else '2') el.set(qn('w:space'),'0') el.set(qn('w:color'), color if outer else 'CCCCCC') tcBorders.append(el) tcPr.append(tcBorders) def set_col_widths(table, widths_cm): for row in table.rows: for j, cell in enumerate(row.cells): tc = cell._tc; tcPr = tc.get_or_add_tcPr() tcW = OxmlElement('w:tcW') tcW.set(qn('w:w'), str(int(widths_cm[j]*567))) tcW.set(qn('w:type'),'dxa'); tcPr.append(tcW) def para_in_cell(cell, text, bold=False, italic=False, size=9, color=C_BLACK, align=WD_ALIGN_PARAGRAPH.LEFT, bg=None, space_before=0, space_after=0): if bg: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER p = cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph() p.clear(); p.alignment = align p.paragraph_format.space_before = Pt(space_before) p.paragraph_format.space_after = Pt(space_after) run = p.add_run(text); run.bold=bold; run.italic=italic run.font.size=Pt(size); run.font.color.rgb=hex_rgb(color); run.font.name='Calibri' return p def add_para_to_cell(cell, text, bold=False, size=9, color=C_BLACK): p = cell.add_paragraph(); p.alignment = WD_ALIGN_PARAGRAPH.LEFT p.paragraph_format.space_before=Pt(0); p.paragraph_format.space_after=Pt(0) run = p.add_run(text); run.bold=bold; run.font.size=Pt(size) run.font.color.rgb=hex_rgb(color); run.font.name='Calibri' def add_space(doc, pt=4): p = doc.add_paragraph() p.paragraph_format.space_before=Pt(0); p.paragraph_format.space_after=Pt(pt) def add_section_header(table, title, color=C_MID_BLUE, ncols=None): row = table.rows[0] if ncols is None: ncols = len(row.cells) if ncols > 1: row.cells[0].merge(row.cells[ncols-1]) para_in_cell(row.cells[0], f' {title}', bold=True, size=9.5, color=C_WHITE, bg=color, space_before=4, space_after=4) def add_col_headers(table, row_idx, headers, bg=C_LIGHT_BLUE): row = table.rows[row_idx] for i, h in enumerate(headers): para_in_cell(row.cells[i], h, bold=True, size=8.5, align=WD_ALIGN_PARAGRAPH.CENTER, bg=bg) def style_data_rows(table, start_row=2): for i, row in enumerate(table.rows[start_row:], start=start_row): bg = C_WHITE if i % 2 == 0 else C_GREY for cell in row.cells: if cell._tc.find(qn('w:shd')) is None: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER def fill_data_row(table, row_idx, cells_data, label_col=0): row = table.rows[row_idx] for j, data in enumerate(cells_data): cell = row.cells[j] if isinstance(data, (list, tuple)): lines = list(data) para_in_cell(cell, lines[0], bold=(j==label_col), size=9, color=C_DARK_BLUE if j==label_col else C_BLACK) for ln in lines[1:]: add_para_to_cell(cell, ln, size=8.5) else: para_in_cell(cell, str(data), bold=(j==label_col), size=9, color=C_DARK_BLUE if j==label_col else C_BLACK) def make_legend(doc, label, text, bg, border_color): tbl = doc.add_table(rows=1, cols=1) tbl.style = 'Table Grid' set_col_widths(tbl, [18.0]) set_cell_bg(tbl.rows[0].cells[0], bg) set_table_outer_border(tbl, color=border_color, sz='4') cell = tbl.rows[0].cells[0] p = cell.paragraphs[0] p.paragraph_format.space_before=Pt(3); p.paragraph_format.space_after=Pt(3) r1 = p.add_run(label+': ') r1.bold=True; r1.font.size=Pt(8.5); r1.font.name='Calibri' r1.font.color.rgb=hex_rgb(C_DARK_BLUE) r2 = p.add_run(text) r2.font.size=Pt(8.5); r2.font.name='Calibri'; r2.font.color.rgb=hex_rgb('444444') return tbl # ═════════════════════════════════════════════════════════════════════════════ # DOCUMENT # ═════════════════════════════════════════════════════════════════════════════ doc = Document() for sec in doc.sections: sec.page_width=Cm(21); sec.page_height=Cm(29.7) sec.left_margin=Cm(1.5); sec.right_margin=Cm(1.5) sec.top_margin=Cm(1.5); sec.bottom_margin=Cm(1.5) doc.styles['Normal'].font.name='Calibri'; doc.styles['Normal'].font.size=Pt(10) # ── BANNER ──────────────────────────────────────────────────────────────────── banner = doc.add_table(rows=2, cols=1) banner.alignment=WD_TABLE_ALIGNMENT.CENTER; banner.style='Table Grid' set_cell_bg(banner.rows[0].cells[0], C_DARK_BLUE) set_cell_bg(banner.rows[1].cells[0], C_DARK_BLUE) para_in_cell(banner.rows[0].cells[0], 'MOTOR ASSESSMENT OF THE UPPER LIMBS', bold=True, size=16, color=C_WHITE, align=WD_ALIGN_PARAGRAPH.CENTER, space_before=6, space_after=2) para_in_cell(banner.rows[1].cells[0], 'Neurological Examination Proforma · Patient Record / Documentation', size=10, color='BFD9F2', align=WD_ALIGN_PARAGRAPH.CENTER, space_before=2, space_after=6) set_table_outer_border(banner, color=C_MID_BLUE, sz='12') add_space(doc, 4) # ── PATIENT DETAILS ─────────────────────────────────────────────────────────── det = doc.add_table(rows=4, cols=4) det.style='Table Grid'; det.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(det, [3.5, 5.5, 3.2, 5.8]) rows_det = [ [('Patient Name:',''), ('Date:','')], [('Age / Sex:',' / ☐ M ☐ F ☐ Other'), ('Examiner:','')], [('Hospital No.:',''), ('Ward / OPD:','')], [('Diagnosis / Complaint:',''), ('Dominant Side:','☐ Right ☐ Left')], ] for ri, pairs in enumerate(rows_det): cells = det.rows[ri].cells ci = 0 for label, val in pairs: para_in_cell(cells[ci], label, bold=True, size=9, color=C_DARK_BLUE, bg=C_PALE_BLUE) para_in_cell(cells[ci+1], val, bold=False, size=9, bg=C_PALE_BLUE) ci += 2 set_table_outer_border(det) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # 1. INSPECTION # ═════════════════════════════════════════════════════════════════════════════ insp = doc.add_table(rows=11, cols=4) insp.style='Table Grid'; insp.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(insp, [4.0, 4.5, 4.5, 5.0]) add_section_header(insp, '1. INSPECTION (Patient seated, arms fully exposed, good lighting)') add_col_headers(insp, 1, ['Feature', 'Right', 'Left', 'Notes']) insp_data = [ ['Muscle Bulk', '☐ Normal ☐ Wasted ☐ Hypertrophy', '☐ Normal ☐ Wasted ☐ Hypertrophy', ''], ['Fasciculations', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Involuntary Movements', '☐ None ☐ Tremor (rest)\n☐ Tremor (postural/action) ☐ Other', '☐ None ☐ Tremor (rest)\n☐ Tremor (postural/action) ☐ Other', ''], ['Pronator Drift\n(arms extended, eyes closed)', '☐ Absent ☐ Present\n☐ Pronation ☐ Elbow flexion', '☐ Absent ☐ Present\n☐ Pronation ☐ Elbow flexion', ''], ['Posture / Deformity', '☐ Normal ☐ Wrist drop\n☐ Claw hand ☐ Other', '☐ Normal ☐ Wrist drop\n☐ Claw hand ☐ Other', ''], ['Trophic / Skin Changes', '☐ Absent ☐ Present (describe):', '☐ Absent ☐ Present (describe):', ''], ['Scars / Deformity', '☐ None ☐ Present', '☐ None ☐ Present', ''], ['Limb Circumference\n(mid-upper arm)', 'R: _______ cm', 'L: _______ cm', '☐ Symmetric ☐ Asymmetric'], ['Thenar / Hypothenar\nWasting', '☐ Normal ☐ Wasted', '☐ Normal ☐ Wasted', ''], ] for i, r in enumerate(insp_data): fill_data_row(insp, i+2, r) style_data_rows(insp, 2) set_table_outer_border(insp) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # 2. TONE # ═════════════════════════════════════════════════════════════════════════════ tone = doc.add_table(rows=9, cols=4) tone.style='Table Grid'; tone.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(tone, [4.0, 4.5, 4.5, 5.0]) add_section_header(tone, '2. TONE (Passive movement; patient relaxed and distracted)') add_col_headers(tone, 1, ['Finding', 'Right', 'Left', 'Comment']) tone_data = [ ['Tone Character', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', ''], ['Cogwheel Rigidity\n(at wrist)', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Lead-Pipe Rigidity', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ['Forearm\n(pronation/supination)', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Elbow\n(flexion/extension)', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Wrist\n(flexion/extension)', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ['Shoulder', '☐ Normal ☐ ↑ ☐ ↓', '☐ Normal ☐ ↑ ☐ ↓', ''], ] for i, r in enumerate(tone_data): fill_data_row(tone, i+2, r) style_data_rows(tone, 2) set_table_outer_border(tone) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # MRC Legend + 3. POWER # ═════════════════════════════════════════════════════════════════════════════ mrc_leg = make_legend(doc, 'MRC Scale', '0 = No movement 1 = Flicker 2 = Gravity eliminated 3 = Against gravity ' '4⁻ = Mild resistance 4 = Moderate resistance 4⁺ = Strong resistance 5 = Full power', C_ORANGE_LT, C_ACCENT) add_space(doc, 2) pow_tbl = doc.add_table(rows=18, cols=5) pow_tbl.style='Table Grid'; pow_tbl.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(pow_tbl, [5.2, 1.5, 4.0, 4.0, 3.3]) add_section_header(pow_tbl, '3. POWER (MRC Grade 0–5; circle the grade for each side)') add_col_headers(pow_tbl, 1, ['Movement', 'Root', 'Right (circle)', 'Left (circle)', 'Notes']) power_rows = [ # Shoulder ('subhdr', 'SHOULDER'), ['Shoulder Flexion (Ant. Deltoid)', 'C5–C6', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Shoulder Abduction (Deltoid)', 'C5–C6', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Shoulder Extension (Post. Deltoid)', 'C5–C6', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], # Elbow ('subhdr', 'ELBOW'), ['Elbow Flexion (Biceps)', 'C5–C6', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Elbow Extension (Triceps)', 'C6–C8', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], # Wrist ('subhdr', 'WRIST & FOREARM'), ['Wrist Extension (Ext. Carpi)', 'C6–C7', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Wrist Flexion (Flex. Carpi)', 'C7–C8', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Forearm Supination', 'C5–C6', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Forearm Pronation', 'C6–C7', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], # Hand ('subhdr', 'HAND & FINGERS'), ['Finger Extension (Ext. Digitorum)', 'C7–C8', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Finger Flexion (FDP/FDS)', 'C7–T1', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Finger Abduction (Interossei)', 'T1', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ['Thumb Abduction (Abd. Poll. Br.)', 'C8–T1', '0/1/2/3/4⁻/4/4⁺/5', '0/1/2/3/4⁻/4/4⁺/5', ''], ] ri = 2 for item in power_rows: row = pow_tbl.rows[ri] if isinstance(item, tuple) and item[0]=='subhdr': row.cells[0].merge(row.cells[4]) para_in_cell(row.cells[0], f' {item[1]}', bold=True, size=8.5, color=C_WHITE, bg=C_SUB_BLUE, space_before=3, space_after=3) else: fill_data_row(pow_tbl, ri, item) bg = C_WHITE if ri % 2 == 0 else C_GREY for cell in row.cells: if cell._tc.find(qn('w:shd')) is None: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER ri += 1 set_table_outer_border(pow_tbl) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # Reflex Legend + 4. REFLEXES # ═════════════════════════════════════════════════════════════════════════════ ref_leg = make_legend(doc, 'Reflex Grade', '0 = Absent 1 = Diminished 2 = Normal 3 = Brisk / Increased 4 = Clonus', C_LT_GREEN, C_GREEN) add_space(doc, 2) ref_tbl = doc.add_table(rows=12, cols=5) ref_tbl.style='Table Grid'; ref_tbl.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(ref_tbl, [4.6, 1.5, 4.0, 4.0, 3.9]) add_section_header(ref_tbl, '4. REFLEXES') add_col_headers(ref_tbl, 1, ['Reflex', 'Root', 'Right', 'Left', 'Notes']) reflex_rows = [ ('subhdr', 'DEEP TENDON REFLEXES'), ['Biceps Jerk', 'C5–C6', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ['Brachioradialis', 'C5–C6', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ['Triceps Jerk', 'C6–C7', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ['Finger Flexor Jerk','C8–T1', 'Grade: 0 1 2 3 4', 'Grade: 0 1 2 3 4', ''], ('subhdr', 'PATHOLOGICAL / SPECIAL REFLEXES'), ['Hoffmann Sign\n(digital tap C8–T1)', 'C8–T1', '☐ Absent ☐ Present\n(thumb flexion = UMN)', '☐ Absent ☐ Present', ''], ['Finger Escape\n(Wartenberg sign)', 'C8–T1', '☐ Absent ☐ Present', '☐ Absent ☐ Present', ''], ('subhdr', 'SUPERFICIAL / CUTANEOUS REFLEXES'), ['Abdominal Reflex\n(upper T7–T9)', 'T7–T9', '☐ Present ☐ Absent', '☐ Present ☐ Absent', ''], ['Palmomental Reflex', 'Frontal', '☐ Absent ☐ Present (frontal lobe sign)', '', ''], ] ri = 2 for item in reflex_rows: row = ref_tbl.rows[ri] if isinstance(item, tuple) and item[0]=='subhdr': row.cells[0].merge(row.cells[4]) para_in_cell(row.cells[0], f' {item[1]}', bold=True, size=8.5, color=C_WHITE, bg=C_SUB_BLUE, space_before=3, space_after=3) else: fill_data_row(ref_tbl, ri, item) bg = C_WHITE if ri % 2 == 0 else C_GREY for cell in row.cells: if cell._tc.find(qn('w:shd')) is None: set_cell_bg(cell, bg) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER ri += 1 set_table_outer_border(ref_tbl) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # 5. COORDINATION # ═════════════════════════════════════════════════════════════════════════════ coord = doc.add_table(rows=8, cols=4) coord.style='Table Grid'; coord.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(coord, [4.0, 4.8, 5.2, 4.0]) add_section_header(coord, '5. COORDINATION (Cerebellar / Proprioceptive Function)') add_col_headers(coord, 1, ['Test', 'Right', 'Left / Bilateral', 'Notes']) coord_data = [ ['Finger–Nose Test', '☐ Normal ☐ Dysmetria\n☐ Intention tremor', '☐ Normal ☐ Dysmetria\n☐ Intention tremor', ''], ['Rapid Alternating\nMovements (RAM)\n(dysdiadochokinesia)', '☐ Normal ☐ Irregular\n☐ Slow', '☐ Normal ☐ Irregular\n☐ Slow', ''], ['Rebound Test\n(cerebellar check reflex)', '☐ Normal ☐ Rebound +ve', '☐ Normal ☐ Rebound +ve', ''], ['Grip / Pinch Strength\n(subjective)', '☐ Normal ☐ Weak ☐ Very weak', '☐ Normal ☐ Weak ☐ Very weak', ''], ['Fine Motor / Dexterity\n(button/coin test)', '☐ Normal ☐ Impaired', '☐ Normal ☐ Impaired', ''], ['Writing Sample', '☐ Normal ☐ Micrographia\n☐ Tremulous ☐ Dyspraxic', '', ''], ] for i, r in enumerate(coord_data): fill_data_row(coord, i+2, r) style_data_rows(coord, 2) set_table_outer_border(coord) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # 6. SPECIAL TESTS (functional / screening) # ═════════════════════════════════════════════════════════════════════════════ spec = doc.add_table(rows=8, cols=4) spec.style='Table Grid'; spec.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(spec, [4.5, 4.3, 4.3, 4.9]) add_section_header(spec, '6. SPECIAL / SCREENING TESTS', color=C_SUB_BLUE) add_col_headers(spec, 1, ['Test', 'Right', 'Left', 'Significance']) spec_data = [ ['Pronator Drift\n(10 s, eyes closed)', '☐ Absent ☐ Present', '☐ Absent ☐ Present', 'UMN weakness screen'], ['Thumb Abduction\n(opponens / thenar)', '☐ Normal ☐ Weak', '☐ Normal ☐ Weak', 'Median nerve (T1); CTS'], ['Finger Abduction\n(index–little)', '☐ Normal ☐ Weak', '☐ Normal ☐ Weak', 'Ulnar nerve (T1)'], ['Wrist/Finger Extension', '☐ Normal ☐ Weak', '☐ Normal ☐ Weak', 'Radial nerve (C7); posterior cord'], ['Power Grip\n(hand-shake grip)', '☐ Normal ☐ Weak', '☐ Normal ☐ Weak', 'Overall hand strength'], ['Bulk: Thenar / Hypothenar', '☐ Normal ☐ Wasted', '☐ Normal ☐ Wasted', 'Median / Ulnar nerve'], ] for i, r in enumerate(spec_data): fill_data_row(spec, i+2, r) style_data_rows(spec, 2) set_table_outer_border(spec) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # 7. UMN vs LMN SUMMARY # ═════════════════════════════════════════════════════════════════════════════ summ = doc.add_table(rows=10, cols=4) summ.style='Table Grid'; summ.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(summ, [4.0, 4.8, 4.8, 4.4]) add_section_header(summ, '7. PATTERN SUMMARY (UMN vs LMN)', color=C_GREEN) add_col_headers(summ, 1, ['Feature', 'UMN (Central)', 'LMN (Peripheral)', 'Patient Findings']) summ_data = [ ['Muscle Bulk', 'Normal / mild disuse atrophy', 'Marked wasting', ''], ['Fasciculations', 'Absent', 'Present', ''], ['Tone', 'Increased (spasticity)', 'Decreased (flaccidity)', ''], ['Pronator Drift', 'Present (corticospinal)', 'Absent (unless weak)', ''], ['Deep Reflexes', 'Hyperreflexia', 'Diminished / Absent', ''], ['Hoffmann Sign', 'Positive', 'Negative', ''], ['Power', 'Reduced (pyramidal pattern)', 'Reduced (root/nerve)', ''], ['Coordination', 'May be affected (UMN)', 'Intact (unless sensory)', ''], ] for i, r in enumerate(summ_data): fill_data_row(summ, i+2, r) style_data_rows(summ, 2) set_table_outer_border(summ) add_space(doc, 2) # Pattern selector pat = doc.add_table(rows=1, cols=2) pat.style='Table Grid' set_col_widths(pat, [5.0, 13.0]) set_cell_bg(pat.rows[0].cells[0], C_ORANGE_LT) set_cell_bg(pat.rows[0].cells[1], C_ORANGE_LT) set_table_outer_border(pat, color=C_ACCENT, sz='6') para_in_cell(pat.rows[0].cells[0], 'Overall Clinical Pattern:', bold=True, size=9, color=C_DARK_BLUE) para_in_cell(pat.rows[0].cells[1], '☐ Normal ☐ UMN ☐ LMN ☐ Mixed ☐ Cerebellar ☐ Extrapyramidal ☐ Functional', size=9) add_space(doc, 4) # ═════════════════════════════════════════════════════════════════════════════ # 8. CLINICAL NOTES + SIGNATURE # ═════════════════════════════════════════════════════════════════════════════ notes = doc.add_table(rows=9, cols=1) notes.style='Table Grid' set_col_widths(notes, [18.0]) add_section_header(notes, '8. CLINICAL SUMMARY & PLAN') notes_fields = [ ('Impression / Provisional Diagnosis:', True), ('', False), ('', False), ('Further Investigations Requested:', True), ('', False), ('Management / Referral Plan:', True), ('', False), ('', False), ] for i, (label, is_label) in enumerate(notes_fields): cell = notes.rows[i+1].cells[0] set_cell_bg(cell, C_PALE_BLUE if is_label else C_WHITE) cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER para_in_cell(cell, label, bold=is_label, size=9, color=C_DARK_BLUE if is_label else C_BLACK) set_table_outer_border(notes) add_space(doc, 5) # Signature sig = doc.add_table(rows=1, cols=3) sig.style='Table Grid' set_col_widths(sig, [6.5, 6.5, 5.0]) para_in_cell(sig.rows[0].cells[0], 'Examiner Signature: ___________________', size=9) para_in_cell(sig.rows[0].cells[1], 'Designation: _______________________', size=9) para_in_cell(sig.rows[0].cells[2], 'Date: ________________', size=9) set_table_outer_border(sig, color=C_MID_GREY, sz='4') add_space(doc, 3) # Footer footer_p = doc.add_paragraph() footer_p.paragraph_format.space_before=Pt(2); footer_p.paragraph_format.space_after=Pt(0) pPr = footer_p._p.get_or_add_pPr() pBdr = OxmlElement('w:pBdr') top_el = OxmlElement('w:top') top_el.set(qn('w:val'),'single'); top_el.set(qn('w:sz'),'4') top_el.set(qn('w:space'),'1'); top_el.set(qn('w:color'),'CCCCCC') pBdr.append(top_el); pPr.append(pBdr) r1 = footer_p.add_run('Sequence: Inspection → Tone → Power → Reflexes → Coordination → Special Tests') r1.italic=True; r1.font.size=Pt(7.5); r1.font.name='Calibri'; r1.font.color.rgb=hex_rgb('888888') r2 = footer_p.add_run(' ') r3 = footer_p.add_run("Harrison's 22E · Bradley & Daroff's Neurology · Das Manual of Clinical Surgery") r3.italic=True; r3.font.size=Pt(7.5); r3.font.name='Calibri'; r3.font.color.rgb=hex_rgb('AAAAAA') # ── Save ────────────────────────────────────────────────────────────────────── out = '/tmp/workspace/motor-assessment/Upper_Limb_Motor_Assessment.docx' doc.save(out) print(f"Saved: {out}") PYEOF
python3 -c " power_rows = [ ('subhdr', 'SHOULDER'), ['Shoulder Flexion','C5-C6','','',''], ['Shoulder Abduction','C5-C6','','',''], ['Shoulder Extension','C5-C6','','',''], ('subhdr', 'ELBOW'), ['Elbow Flexion','C5-C6','','',''], ['Elbow Extension','C6-C8','','',''], ('subhdr', 'WRIST & FOREARM'), ['Wrist Extension','C6-C7','','',''], ['Wrist Flexion','C7-C8','','',''], ['Forearm Supination','C5-C6','','',''], ['Forearm Pronation','C6-C7','','',''], ('subhdr', 'HAND & FINGERS'), ['Finger Extension','C7-C8','','',''], ['Finger Flexion','C7-T1','','',''], ['Finger Abduction','T1','','',''], ['Thumb Abduction','C8-T1','','',''], ] print(f'power_rows count: {len(power_rows)}') print(f'rows needed (header+colhdr+data): {2 + len(power_rows)}') "
python3 << 'PYEOF' from docx import Document from docx.shared import Pt, Cm, RGBColor 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 def hex_rgb(h): h = h.lstrip('#') return RGBColor(int(h[0:2],16), int(h[2:4],16), int(h[4:6],16)) C_DARK_BLUE='1A3A5C'; C_MID_BLUE='2E6DA4'; C_LIGHT_BLUE='D6E8F7'; C_PALE_BLUE='EEF5FB' C_ACCENT='E8821A'; C_GREEN='2E7D32'; C_LT_GREEN='EEF8EE'; C_GREY='F2F4F7' C_MID_GREY='CCCCCC'; C_SUB_BLUE='3A7EBF'; C_ORANGE_LT='FFF4E5'; C_WHITE='FFFFFF'; C_BLACK='000000' def set_cell_bg(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_table_outer_border(table, color=C_MID_BLUE, sz='8'): for row in table.rows: for i, cell in enumerate(row.cells): is_first_row=(row==table.rows[0]); is_last_row=(row==table.rows[-1]) is_first_col=(i==0); is_last_col=(i==len(row.cells)-1) tc=cell._tc; tcPr=tc.get_or_add_tcPr() tcBorders=OxmlElement('w:tcBorders') for side in ['top','bottom','left','right']: el=OxmlElement(f'w:{side}') outer=((side=='top' and is_first_row) or (side=='bottom' and is_last_row) or (side=='left' and is_first_col) or (side=='right' and is_last_col)) el.set(qn('w:val'),'single'); el.set(qn('w:sz'),sz if outer else '2') el.set(qn('w:space'),'0'); el.set(qn('w:color'),color if outer else 'CCCCCC') tcBorders.append(el) tcPr.append(tcBorders) def set_col_widths(table, widths_cm): for row in table.rows: for j, cell in enumerate(row.cells): tc=cell._tc; tcPr=tc.get_or_add_tcPr() tcW=OxmlElement('w:tcW') tcW.set(qn('w:w'),str(int(widths_cm[j]*567))); tcW.set(qn('w:type'),'dxa') tcPr.append(tcW) def para_in_cell(cell, text, bold=False, italic=False, size=9, color=C_BLACK, align=WD_ALIGN_PARAGRAPH.LEFT, bg=None, space_before=0, space_after=0): if bg: set_cell_bg(cell, bg) cell.vertical_alignment=WD_ALIGN_VERTICAL.CENTER p=cell.paragraphs[0] if cell.paragraphs else cell.add_paragraph() p.clear(); p.alignment=align p.paragraph_format.space_before=Pt(space_before); p.paragraph_format.space_after=Pt(space_after) run=p.add_run(text); run.bold=bold; run.italic=italic run.font.size=Pt(size); run.font.color.rgb=hex_rgb(color); run.font.name='Calibri' def add_para_to_cell(cell, text, bold=False, size=9, color=C_BLACK): p=cell.add_paragraph(); p.alignment=WD_ALIGN_PARAGRAPH.LEFT p.paragraph_format.space_before=Pt(0); p.paragraph_format.space_after=Pt(0) run=p.add_run(text); run.bold=bold; run.font.size=Pt(size) run.font.color.rgb=hex_rgb(color); run.font.name='Calibri' def add_space(doc, pt=4): p=doc.add_paragraph() p.paragraph_format.space_before=Pt(0); p.paragraph_format.space_after=Pt(pt) def add_section_header(table, title, color=C_MID_BLUE): ncols=len(table.rows[0].cells) if ncols>1: table.rows[0].cells[0].merge(table.rows[0].cells[ncols-1]) para_in_cell(table.rows[0].cells[0], f' {title}', bold=True, size=9.5, color=C_WHITE, bg=color, space_before=4, space_after=4) def add_col_headers(table, row_idx, headers, bg=C_LIGHT_BLUE): row=table.rows[row_idx] for i,h in enumerate(headers): para_in_cell(row.cells[i], h, bold=True, size=8.5, align=WD_ALIGN_PARAGRAPH.CENTER, bg=bg) def fill_row(table, row_idx, cells_data): row=table.rows[row_idx] for j,data in enumerate(cells_data): cell=row.cells[j] if isinstance(data,(list,tuple)): lines=list(data) para_in_cell(cell, lines[0], bold=(j==0), size=9, color=C_DARK_BLUE if j==0 else C_BLACK) for ln in lines[1:]: add_para_to_cell(cell, ln, size=8.5) else: para_in_cell(cell, str(data), bold=(j==0), size=9, color=C_DARK_BLUE if j==0 else C_BLACK) def auto_style(table, start=2): for i,row in enumerate(table.rows[start:], start=start): bg=C_WHITE if i%2==0 else C_GREY for cell in row.cells: if cell._tc.find(qn('w:shd')) is None: set_cell_bg(cell, bg) cell.vertical_alignment=WD_ALIGN_VERTICAL.CENTER def build_mixed_table(doc, section_title, col_widths, col_headers, rows_spec, sec_color=C_MID_BLUE): """rows_spec: list of ('subhdr', text) or list-of-cell-strings.""" nrows = 2 + len(rows_spec) # section-hdr + col-hdr + data ncols = len(col_widths) tbl = doc.add_table(rows=nrows, cols=ncols) tbl.style='Table Grid'; tbl.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(tbl, col_widths) add_section_header(tbl, section_title, color=sec_color) add_col_headers(tbl, 1, col_headers) ri=2 for item in rows_spec: row=tbl.rows[ri] if isinstance(item,tuple) and item[0]=='subhdr': row.cells[0].merge(row.cells[ncols-1]) para_in_cell(row.cells[0], f' {item[1]}', bold=True, size=8.5, color=C_WHITE, bg=C_SUB_BLUE, space_before=3, space_after=3) else: fill_row(tbl, ri, item) bg=C_WHITE if ri%2==0 else C_GREY for cell in row.cells: if cell._tc.find(qn('w:shd')) is None: set_cell_bg(cell, bg) cell.vertical_alignment=WD_ALIGN_VERTICAL.CENTER ri+=1 set_table_outer_border(tbl) return tbl def make_legend(doc, label, text, bg, border_color): tbl=doc.add_table(rows=1,cols=1); tbl.style='Table Grid' set_col_widths(tbl,[18.0]); set_cell_bg(tbl.rows[0].cells[0], bg) set_table_outer_border(tbl, color=border_color, sz='4') cell=tbl.rows[0].cells[0]; p=cell.paragraphs[0] p.paragraph_format.space_before=Pt(3); p.paragraph_format.space_after=Pt(3) r1=p.add_run(label+': '); r1.bold=True; r1.font.size=Pt(8.5); r1.font.name='Calibri' r1.font.color.rgb=hex_rgb(C_DARK_BLUE) r2=p.add_run(text); r2.font.size=Pt(8.5); r2.font.name='Calibri'; r2.font.color.rgb=hex_rgb('444444') return tbl # ════ DOCUMENT ════════════════════════════════════════════════════════════════ doc=Document() for sec in doc.sections: sec.page_width=Cm(21); sec.page_height=Cm(29.7) sec.left_margin=Cm(1.5); sec.right_margin=Cm(1.5) sec.top_margin=Cm(1.5); sec.bottom_margin=Cm(1.5) doc.styles['Normal'].font.name='Calibri'; doc.styles['Normal'].font.size=Pt(10) # ── BANNER ──────────────────────────────────────────────────────────────────── banner=doc.add_table(rows=2,cols=1) banner.alignment=WD_TABLE_ALIGNMENT.CENTER; banner.style='Table Grid' set_cell_bg(banner.rows[0].cells[0], C_DARK_BLUE) set_cell_bg(banner.rows[1].cells[0], C_DARK_BLUE) para_in_cell(banner.rows[0].cells[0],'MOTOR ASSESSMENT OF THE UPPER LIMBS', bold=True,size=16,color=C_WHITE,align=WD_ALIGN_PARAGRAPH.CENTER,space_before=6,space_after=2) para_in_cell(banner.rows[1].cells[0], 'Neurological Examination Proforma · Patient Record / Documentation', size=10,color='BFD9F2',align=WD_ALIGN_PARAGRAPH.CENTER,space_before=2,space_after=6) set_table_outer_border(banner,color=C_MID_BLUE,sz='12') add_space(doc,4) # ── PATIENT DETAILS ─────────────────────────────────────────────────────────── det=doc.add_table(rows=4,cols=4) det.style='Table Grid'; det.alignment=WD_TABLE_ALIGNMENT.CENTER set_col_widths(det,[3.5,5.5,3.2,5.8]) rows_det=[ [('Patient Name:',''),('Date:','')], [('Age / Sex:',' / ☐ M ☐ F ☐ Other'),('Examiner:','')], [('Hospital No.:',''),('Ward / OPD:','')], [('Diagnosis / Complaint:',''),('Dominant Side:','☐ Right ☐ Left')], ] for ri,pairs in enumerate(rows_det): cells=det.rows[ri].cells; ci=0 for label,val in pairs: para_in_cell(cells[ci],label,bold=True,size=9,color=C_DARK_BLUE,bg=C_PALE_BLUE) para_in_cell(cells[ci+1],val,bold=False,size=9,bg=C_PALE_BLUE); ci+=2 set_table_outer_border(det); add_space(doc,4) # ── 1. INSPECTION ───────────────────────────────────────────────────────────── insp_rows=[ ['Muscle Bulk','☐ Normal ☐ Wasted ☐ Hypertrophy','☐ Normal ☐ Wasted ☐ Hypertrophy',''], ['Fasciculations','☐ Absent ☐ Present','☐ Absent ☐ Present',''], ['Involuntary Movements', '☐ None ☐ Tremor (rest)\n☐ Tremor (postural) ☐ Other', '☐ None ☐ Tremor (rest)\n☐ Tremor (postural) ☐ Other',''], ['Pronator Drift\n(arms extended,\neyes closed 10 s)', '☐ Absent\n☐ Present: pronation\n☐ Present: elbow flexion', '☐ Absent\n☐ Present: pronation\n☐ Present: elbow flexion',''], ['Posture / Deformity', '☐ Normal ☐ Wrist drop\n☐ Claw hand ☐ Other', '☐ Normal ☐ Wrist drop\n☐ Claw hand ☐ Other',''], ['Thenar / Hypothenar\nWasting', '☐ Normal ☐ Thenar wasted\n☐ Hypothenar wasted', '☐ Normal ☐ Thenar wasted\n☐ Hypothenar wasted',''], ['Trophic / Skin Changes','☐ Absent ☐ Present','☐ Absent ☐ Present',''], ['Scars / Deformity','☐ None ☐ Present','☐ None ☐ Present',''], ['Mid-upper Arm\nCircumference','R: _______ cm','L: _______ cm','☐ Symmetric ☐ Asymmetric'], ] tbl=build_mixed_table(doc,'1. INSPECTION (Patient seated, arms fully exposed, good lighting)', [4.0,4.5,4.5,5.0],['Feature','Right','Left','Notes'],insp_rows) add_space(doc,4) # ── 2. TONE ─────────────────────────────────────────────────────────────────── tone_rows=[ ['Tone Character', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia', '☐ Normal ☐ Hypotonia\n☐ Spasticity ☐ Rigidity ☐ Paratonia',''], ['Cogwheel Rigidity\n(wrist)', '☐ Absent ☐ Present','☐ Absent ☐ Present',''], ['Lead-Pipe Rigidity', '☐ Absent ☐ Present','☐ Absent ☐ Present',''], ['Shoulder','☐ Normal ☐ ↑ ☐ ↓','☐ Normal ☐ ↑ ☐ ↓',''], ['Elbow\n(flex/extend)','☐ Normal ☐ ↑ ☐ ↓','☐ Normal ☐ ↑ ☐ ↓',''], ['Forearm\n(pro/supination)','☐ Normal ☐ ↑ ☐ ↓','☐ Normal ☐ ↑ ☐ ↓',''], ['Wrist\n(flex/extend)','☐ Normal ☐ ↑ ☐ ↓','☐ Normal ☐ ↑ ☐ ↓',''], ] tbl=build_mixed_table(doc,'2. TONE (Passive movement; patient relaxed and distracted)', [4.0,4.5,4.5,5.0],['Finding','Right','Left','Comment'],tone_rows) add_space(doc,4) # ── MRC Legend ──────────────────────────────────────────────────────────────── make_legend(doc,'MRC Scale', '0 = No movement 1 = Flicker 2 = Gravity eliminated 3 = Against gravity ' '4⁻ = Mild resistance 4 = Moderate resistance 4⁺ = Strong resistance 5 = Full power', C_ORANGE_LT, C_ACCENT) add_space(doc,2) # ── 3. POWER ────────────────────────────────────────────────────────────────── MRC='0/1/2/3/4⁻/4/4⁺/5' power_rows=[ ('subhdr','SHOULDER'), ['Shoulder Flexion (Ant. Deltoid)','C5–C6',MRC,MRC,''], ['Shoulder Abduction (Deltoid)', 'C5–C6',MRC,MRC,''], ['Shoulder Extension', 'C5–C6',MRC,MRC,''], ('subhdr','ELBOW'), ['Elbow Flexion (Biceps)', 'C5–C6',MRC,MRC,''], ['Elbow Extension (Triceps)', 'C6–C8',MRC,MRC,''], ('subhdr','WRIST & FOREARM'), ['Wrist Extension (Ext. Carpi)', 'C6–C7',MRC,MRC,''], ['Wrist Flexion (Flex. Carpi)', 'C7–C8',MRC,MRC,''], ['Forearm Supination', 'C5–C6',MRC,MRC,''], ['Forearm Pronation', 'C6–C7',MRC,MRC,''], ('subhdr','HAND & FINGERS'), ['Finger Extension (Ext. Dig.)', 'C7–C8',MRC,MRC,''], ['Finger Flexion (FDP / FDS)', 'C7–T1',MRC,MRC,''], ['Finger Abduction (Interossei)', 'T1', MRC,MRC,''], ['Thumb Abduction (Abd. Poll. Br.)','C8–T1',MRC,MRC,''], ] # rows needed = 2 (header+colhdr) + len(power_rows) tbl=build_mixed_table(doc,'3. POWER (MRC Grade 0–5; circle the grade for each side)', [5.2,1.5,3.9,3.9,3.5], ['Movement','Root','Right (circle)','Left (circle)','Notes'], power_rows) add_space(doc,4) # ── Reflex Legend ───────────────────────────────────────────────────────────── make_legend(doc,'Reflex Grade', '0 = Absent 1 = Diminished 2 = Normal 3 = Brisk / Increased 4 = Clonus', C_LT_GREEN, C_GREEN) add_space(doc,2) # ── 4. REFLEXES ─────────────────────────────────────────────────────────────── reflex_rows=[ ('subhdr','DEEP TENDON REFLEXES'), ['Biceps Jerk', 'C5–C6','Grade: 0 1 2 3 4','Grade: 0 1 2 3 4',''], ['Brachioradialis', 'C5–C6','Grade: 0 1 2 3 4','Grade: 0 1 2 3 4',''], ['Triceps Jerk', 'C6–C7','Grade: 0 1 2 3 4','Grade: 0 1 2 3 4',''], ['Finger Flexor Jerk','C8–T1','Grade: 0 1 2 3 4','Grade: 0 1 2 3 4',''], ('subhdr','PATHOLOGICAL REFLEXES'), ['Hoffmann Sign\n(flick middle finger nail)', 'C8–T1', '☐ Absent ☐ Present\n(thumb adduction = UMN)', '☐ Absent ☐ Present',''], ['Inverted Supinator\n(tap brachioradialis)', 'C5–C6', '☐ Normal ☐ Inverted\n(finger flex = C7–C8 release)', '☐ Normal ☐ Inverted',''], ('subhdr','SUPERFICIAL / FRONTAL REFLEXES'), ['Abdominal Reflex\n(upper T7–T9)', 'T7–T9','☐ Present ☐ Absent','☐ Present ☐ Absent',''], ['Palmomental Reflex', 'Frontal','☐ Absent ☐ Present\n(frontal lobe sign)','',''], ] tbl=build_mixed_table(doc,'4. REFLEXES', [4.6,1.5,4.0,4.0,3.9], ['Reflex','Root','Right','Left','Notes'], reflex_rows) add_space(doc,4) # ── 5. COORDINATION ─────────────────────────────────────────────────────────── coord_rows=[ ['Finger–Nose Test', '☐ Normal ☐ Dysmetria\n☐ Intention tremor', '☐ Normal ☐ Dysmetria\n☐ Intention tremor',''], ['Rapid Alternating\nMovements (RAM)', '☐ Normal ☐ Slow\n☐ Irregular (dysdiadochokinesia)', '☐ Normal ☐ Slow\n☐ Irregular (dysdiadochokinesia)',''], ['Rebound Test\n(cerebellar check)', '☐ Normal ☐ Rebound +ve','☐ Normal ☐ Rebound +ve',''], ['Fine Motor / Dexterity\n(button / coin test)', '☐ Normal ☐ Impaired','☐ Normal ☐ Impaired',''], ['Handwriting Sample', '☐ Normal ☐ Micrographia\n☐ Tremulous ☐ Dyspraxic','',''], ] tbl=build_mixed_table(doc,'5. COORDINATION (Cerebellar / Proprioceptive Function)', [4.0,4.8,5.2,4.0], ['Test','Right','Left / Bilateral','Notes'], coord_rows) add_space(doc,4) # ── 6. SPECIAL / SCREENING TESTS ───────────────────────────────────────────── spec_rows=[ ['Pronator Drift\n(10 s, eyes closed)', '☐ Absent ☐ Present','☐ Absent ☐ Present', 'UMN weakness screen'], ['Thumb Abduction\n(Abd. Poll. Br.)', '☐ Normal ☐ Weak','☐ Normal ☐ Weak', 'Median nerve (T1) / CTS'], ['Finger Abduction\n(index–little)', '☐ Normal ☐ Weak','☐ Normal ☐ Weak', 'Ulnar nerve (T1)'], ['Wrist / Finger\nExtension', '☐ Normal ☐ Weak','☐ Normal ☐ Weak', 'Radial nerve (C7)'], ['Power Grip\n(handshake)', '☐ Normal ☐ Reduced','☐ Normal ☐ Reduced', 'Overall hand strength'], ['Intrinsic Muscle\nBulk (interossei)', '☐ Normal ☐ Guttered','☐ Normal ☐ Guttered', 'Ulnar nerve / T1'], ] tbl=build_mixed_table(doc,'6. SPECIAL / SCREENING TESTS', [4.5,4.3,4.3,4.9], ['Test','Right','Left','Significance'], spec_rows, sec_color=C_SUB_BLUE) add_space(doc,4) # ── 7. UMN vs LMN SUMMARY ──────────────────────────────────────────────────── summ_rows=[ ['Muscle Bulk', 'Normal / mild disuse atrophy','Marked wasting',''], ['Fasciculations', 'Absent', 'Present',''], ['Tone', 'Increased (spasticity)', 'Decreased (flaccidity)',''], ['Pronator Drift', 'Present (corticospinal)', 'Absent',''], ['Deep Reflexes', 'Hyperreflexia', 'Diminished / Absent',''], ['Hoffmann Sign', 'Positive', 'Negative',''], ['Power', 'Reduced (pyramidal pattern)', 'Reduced (root/nerve pattern)',''], ['Coordination', 'May be impaired', 'Intact (unless sensory)',''], ] tbl=build_mixed_table(doc,'7. PATTERN SUMMARY (UMN vs LMN)', [4.0,4.8,4.8,4.4], ['Feature','UMN (Central)','LMN (Peripheral)','Patient Findings'], summ_rows, sec_color=C_GREEN) add_space(doc,2) # Pattern selector pat=doc.add_table(rows=1,cols=2); pat.style='Table Grid' set_col_widths(pat,[5.0,13.0]) set_cell_bg(pat.rows[0].cells[0],C_ORANGE_LT); set_cell_bg(pat.rows[0].cells[1],C_ORANGE_LT) set_table_outer_border(pat,color=C_ACCENT,sz='6') para_in_cell(pat.rows[0].cells[0],'Overall Clinical Pattern:',bold=True,size=9,color=C_DARK_BLUE) para_in_cell(pat.rows[0].cells[1], '☐ Normal ☐ UMN ☐ LMN ☐ Mixed ☐ Cerebellar ☐ Extrapyramidal ☐ Functional',size=9) add_space(doc,4) # ── 8. CLINICAL NOTES ──────────────────────────────────────────────────────── notes=doc.add_table(rows=9,cols=1); notes.style='Table Grid' set_col_widths(notes,[18.0]) add_section_header(notes,'8. CLINICAL SUMMARY & PLAN') notes_fields=[ ('Impression / Provisional Diagnosis:',True),('',''),('',''), ('Further Investigations Requested:',True),('',''), ('Management / Referral Plan:',True),('',''),('',''), ] for i,(label,is_label) in enumerate(notes_fields): cell=notes.rows[i+1].cells[0] set_cell_bg(cell,C_PALE_BLUE if is_label else C_WHITE) cell.vertical_alignment=WD_ALIGN_VERTICAL.CENTER para_in_cell(cell,label,bold=bool(is_label),size=9,color=C_DARK_BLUE if is_label else C_BLACK) set_table_outer_border(notes); add_space(doc,5) # Signature sig=doc.add_table(rows=1,cols=3); sig.style='Table Grid' set_col_widths(sig,[6.5,6.5,5.0]) para_in_cell(sig.rows[0].cells[0],'Examiner Signature: ___________________',size=9) para_in_cell(sig.rows[0].cells[1],'Designation: _______________________',size=9) para_in_cell(sig.rows[0].cells[2],'Date: ________________',size=9) set_table_outer_border(sig,color=C_MID_GREY,sz='4'); add_space(doc,3) # Footer fp=doc.add_paragraph() fp.paragraph_format.space_before=Pt(2); fp.paragraph_format.space_after=Pt(0) pPr=fp._p.get_or_add_pPr(); pBdr=OxmlElement('w:pBdr') top_el=OxmlElement('w:top'); top_el.set(qn('w:val'),'single'); top_el.set(qn('w:sz'),'4') top_el.set(qn('w:space'),'1'); top_el.set(qn('w:color'),'CCCCCC') pBdr.append(top_el); pPr.append(pBdr) r1=fp.add_run('Sequence: Inspection → Tone → Power → Reflexes → Coordination → Special Tests') r1.italic=True; r1.font.size=Pt(7.5); r1.font.name='Calibri'; r1.font.color.rgb=hex_rgb('888888') fp.add_run(' ') r3=fp.add_run("Harrison's 22E · Bradley & Daroff's Neurology · Das Manual of Clinical Surgery") r3.italic=True; r3.font.size=Pt(7.5); r3.font.name='Calibri'; r3.font.color.rgb=hex_rgb('AAAAAA') out='/tmp/workspace/motor-assessment/Upper_Limb_Motor_Assessment.docx' doc.save(out) print(f"Saved: {out}") PYEOF
Upper Limb Motor Assessment
Word Document · DOCX
| Section | Upper Limb Content |
|---|---|
| Patient Details | Name, Age/Sex, Hospital No., Date, Examiner, Dominant side |
| 1. Inspection | Bulk, fasciculations, involuntary movements, pronator drift, posture (wrist drop/claw hand), thenar/hypothenar wasting, skin changes, arm circumference |
| 2. Tone | Character (spasticity/rigidity/paratonia), cogwheel/lead-pipe rigidity at wrist, joint-by-joint (shoulder/elbow/forearm/wrist) with ↑/↓ tickboxes |
| 3. Power | 16 movements grouped into 4 sub-sections (Shoulder / Elbow / Wrist & Forearm / Hand & Fingers) with nerve roots, MRC 0/1/2/3/4⁻/4/4⁺/5 circle-grade format |
| 4. Reflexes | Biceps (C5–C6), brachioradialis (C5–C6), triceps (C6–C7), finger flexor (C8–T1) + Hoffmann sign, inverted supinator sign, abdominal, palmomental |
| 5. Coordination | Finger-nose test, RAM / dysdiadochokinesia, rebound test, fine motor/dexterity, handwriting sample |
| 6. Special / Screening Tests | Pronator drift, thumb abduction (median/CTS screen), finger abduction (ulnar screen), wrist extension (radial nerve), power grip, interossei bulk |
| 7. UMN vs LMN Summary | Side-by-side table + clinical pattern selector (Normal / UMN / LMN / Mixed / Cerebellar / Extrapyramidal / Functional) |
| 8. Clinical Summary | Impression, investigations, management plan, signature block |