Make a flowchart for management of acquired upper lid entropion, lower lid entropion and ectropion
"entropion" AND surgery
"ectropion" AND surgery
python3 -c "import graphviz; print(graphviz.__version__)"; which dot
~/entropion-ectropion-flowchart/build_flowcharts.py
import graphviz
COMMON_ATTRS = dict(
fontname="Helvetica",
fontsize="11",
)
def new_graph(name, label):
g = graphviz.Digraph(name, format="png")
g.attr(rankdir="TB", splines="ortho", nodesep="0.35", ranksep="0.45", bgcolor="white")
g.attr(label=label, labelloc="t", fontsize="18", fontname="Helvetica-Bold")
g.attr("node", fontname="Helvetica", fontsize="11")
g.attr("edge", fontname="Helvetica", fontsize="10")
return g
def box(g, name, text, shape="box", fill="#EAF2FB", color="#2E5C8A"):
g.node(name, text, shape=shape, style="filled,rounded" if shape == "box" else "filled",
fillcolor=fill, color=color, penwidth="1.4")
def diamond(g, name, text, fill="#FDF3D0", color="#B8860B"):
g.node(name, text, shape="diamond", style="filled", fillcolor=fill, color=color, penwidth="1.4")
def start(g, name, text, fill="#DCEFE1", color="#2E7D46"):
g.node(name, text, shape="oval", style="filled", fillcolor=fill, color=color, penwidth="1.6")
def end(g, name, text, fill="#F6D6D6", color="#B23A48"):
g.node(name, text, shape="oval", style="filled", fillcolor=fill, color=color, penwidth="1.6")
# ============================================================
# 1. ACQUIRED UPPER LID ENTROPION
# ============================================================
g1 = new_graph("upper_lid_entropion", "Management of Acquired Upper Lid Entropion")
start(g1, "s", "Acquired Upper Lid Entropion\n(lashes/lid margin rotated onto globe)")
box(g1, "hx", "History & exam:\ncicatrizing conjunctivitis, trachoma,\nchemical/thermal burn, trauma,\nOCP / Stevens-Johnson syndrome\nSlit-lamp: corneal staining, tarsal scarring")
diamond(g1, "cause", "Identify cause\n(almost always cicatricial\nin acquired upper lid disease)")
box(g1, "treat_underlying", "Treat/control underlying disease\n(e.g. systemic immunosuppression for\nmucous membrane pemphigoid,\nstop offending agent)")
box(g1, "temp", "Temporising measures\nLubricants, bandage contact lens,\nepilation / electrolysis / cryotherapy\nof aberrant lashes")
diamond(g1, "severity", "Severity of tarsal\nscarring / lid rotation?")
box(g1, "mild", "MILD\nTarsal fracture (transverse tarsotomy)\nwith anterior rotation of lid margin\n(as for marginal entropion)")
box(g1, "severe", "SEVERE\nComposite grafting:\nreplace keratinized conjunctiva +\nscarred/contracted tarsus\n(mucous membrane / hard palate /\namniotic membrane graft)")
diamond(g1, "laxity1", "Horizontal lid laxity\nalso present?")
box(g1, "sling1", "Add lateral tarsal strip\n(canthal tendon tightening)")
end(g1, "resolved1", "Lid margin realigned,\ncornea protected\n(follow up for recurrence)")
g1.edge("s", "hx")
g1.edge("hx", "cause")
g1.edge("cause", "treat_underlying")
g1.edge("treat_underlying", "temp")
g1.edge("temp", "severity")
g1.edge("severity", "mild", label="mild")
g1.edge("severity", "severe", label="severe / recurrent")
g1.edge("mild", "laxity1")
g1.edge("severe", "laxity1")
g1.edge("laxity1", "sling1", label="yes")
g1.edge("sling1", "resolved1")
g1.edge("laxity1", "resolved1", label="no")
g1.render("/tmp/workspace/entropion-ectropion-flowchart/1_upper_lid_entropion", cleanup=True)
# ============================================================
# 2. ACQUIRED LOWER LID ENTROPION
# ============================================================
g2 = new_graph("lower_lid_entropion", "Management of Acquired Lower Lid Entropion")
start(g2, "s", "Acquired Lower Lid Entropion\n(lid margin/lashes rotated onto globe,\npseudotrichiasis)")
box(g2, "hx", "History & exam: prior surgery/trauma,\nchemical burn, infection (trachoma, HSV/VZV)\nAssess: horizontal lid laxity, retractor\ndisinsertion, orbicularis override,\nblepharospasm, conjunctival/tarsal scarring")
diamond(g2, "etiology", "Predominant\nmechanism?")
box(g2, "involutional", "INVOLUTIONAL (most common)\nHorizontal lid laxity +/- lower lid\nretractor disinsertion +/- preseptal\norbicularis override")
box(g2, "cicatricial", "CICATRICIAL\nConjunctival scarring: OCP,\nStevens-Johnson, chemical burn,\ntrachoma, trauma")
box(g2, "spastic", "SPASTIC\nSustained orbicularis contraction:\npost-surgical irritation, blepharospasm")
box(g2, "temp", "Temporising measures (all types)\nLubrication + antibiotic ointment,\ntaping/lateral traction,\nQuickert suture (office/bedside),\nbotulinum toxin chemodenervation\nof orbicularis")
# involutional branch
diamond(g2, "mech", "Predominant mechanism:\noverride/disinsertion\nvs horizontal laxity?")
box(g2, "everting", "Everting sutures (transient) OR\nWies procedure (lid split + everting\nsutures) OR lower lid retractor\nreinsertion (more precise; primary\nor for recurrence)")
box(g2, "tarsalstrip", "Lateral tarsal strip\n(canthal sling) or pentagon excision\nfor horizontal laxity")
box(g2, "combo", "Combine retractor/override repair\nWITH lateral tarsal strip\nif both present (common)")
# cicatricial branch
diamond(g2, "cicseverity", "Severity of\ntarsal/conjunctival scarring?")
box(g2, "cicmild", "MILD: tarsal fracture\n(transverse tarsotomy) with\nanterior lid margin rotation")
box(g2, "cicsevere", "SEVERE: composite/mucous\nmembrane graft to replace\nscarred tarsus + keratinized\nconjunctiva; add lateral tarsal\nstrip if lax")
# spastic branch
box(g2, "spasticmgmt", "Treat underlying irritant/blepharospasm\n+ Quickert suture; botulinum toxin\nfor blepharospasm if recurrent")
end(g2, "resolved2", "Lid apposed to globe,\ncornea protected\n(surgery definitive; monitor for recurrence)")
g2.edge("s", "hx")
g2.edge("hx", "etiology")
g2.edge("etiology", "involutional")
g2.edge("etiology", "cicatricial")
g2.edge("etiology", "spastic")
g2.edge("involutional", "temp")
g2.edge("cicatricial", "temp")
g2.edge("spastic", "temp")
g2.edge("temp", "mech", label="involutional")
g2.edge("mech", "everting", label="override/\ndisinsertion")
g2.edge("mech", "tarsalstrip", label="horizontal\nlaxity")
g2.edge("everting", "combo")
g2.edge("tarsalstrip", "combo")
g2.edge("combo", "resolved2")
g2.edge("temp", "cicseverity", label="cicatricial")
g2.edge("cicseverity", "cicmild", label="mild")
g2.edge("cicseverity", "cicsevere", label="severe")
g2.edge("cicmild", "resolved2")
g2.edge("cicsevere", "resolved2")
g2.edge("temp", "spasticmgmt", label="spastic")
g2.edge("spasticmgmt", "resolved2")
g2.render("/tmp/workspace/entropion-ectropion-flowchart/2_lower_lid_entropion", cleanup=True)
# ============================================================
# 3. ECTROPION (acquired, lower lid)
# ============================================================
g3 = new_graph("ectropion", "Management of Acquired Ectropion")
start(g3, "s", "Acquired Ectropion\n(outward turning of lid margin)")
box(g3, "hx", "History & exam: prior surgery/trauma/burn,\nCN VII palsy, skin disease, tumour\nCheck orbicularis function, horizontal\nlaxity, punctal position, exposed\nconjunctiva/skin, herniated fat/mass\nSlit lamp: exposure keratopathy")
diamond(g3, "keratopathy", "Significant exposure\nkeratopathy / corneal risk?")
box(g3, "medtreat", "Immediate medical treatment:\nlubricants (day) + ointment (night),\nwarm compresses + antibiotic\n(+/- short-course steroid) ointment\nfor inflamed margin,\ntaping as temporising measure")
diamond(g3, "etiology", "Identify cause")
box(g3, "involutional3", "INVOLUTIONAL (most common)\nHorizontal lid laxity +/-\nlateral/medial canthal tendon laxity")
box(g3, "cicatricial3", "CICATRICIAL\nAnterior lamellar shortening:\nburns, trauma, actinic damage,\nchronic inflammation, dermatoses")
box(g3, "paralytic3", "PARALYTIC\nCN VII palsy\n(+/- lagophthalmos, poor Bell's)")
box(g3, "mechanical3", "MECHANICAL\nHerniated orbital fat,\neyelid tumour")
box(g3, "allergic3", "ALLERGIC / CONTACT DERMATITIS")
box(g3, "gentarsal", "Generalized laxity: lateral tarsal\nstrip (canthal tendon tightening)\n+/- tarsoconjunctival pentagon excision")
box(g3, "medspindle", "Medial ectropion/punctal malposition:\nmedial spindle procedure,\noften + lateral tarsal strip")
diamond(g3, "cicextent", "Extent of scarring?")
box(g3, "ciclocal", "Localized/mild:\nexcise scar tissue + Z-plasty")
box(g3, "cicgen", "Generalized/severe:\nfull-thickness skin graft or\ntransposition flap\n(donor: upper lid, pre/post-auricular,\nsupraclavicular skin)")
diamond(g3, "recovery", "Recovery expected /\nirreversible palsy?")
box(g3, "paralytictemp", "Awaiting recovery (<6-12 mo):\nlubrication + taping at night,\nbotulinum toxin-induced ptosis,\ntemporary tarsorrhaphy\n(esp. if poor Bell's phenomenon)")
box(g3, "paralyticperm", "Irreversible / no improvement\nat 6-12 months:\nmedial canthoplasty +/- lateral tarsal\nstrip, lid-loading (platinum weight)\nfor lagophthalmos, brow ptosis repair")
box(g3, "mechmgmt", "Remove causative lesion\n(excise tumour / debulk fat)\n+ correct residual horizontal laxity")
box(g3, "allergicmgmt", "Remove/avoid allergen,\ntopical steroid;\nsurgery rarely needed")
end(g3, "resolved3", "Lid apposed to globe,\nexposure keratopathy resolved\n(follow up; steroid ointment users\nmonitored for side effects)")
g3.edge("s", "hx")
g3.edge("hx", "keratopathy")
g3.edge("keratopathy", "medtreat", label="yes")
g3.edge("keratopathy", "etiology", label="no / after\ntreating")
g3.edge("medtreat", "etiology")
g3.edge("etiology", "involutional3")
g3.edge("etiology", "cicatricial3")
g3.edge("etiology", "paralytic3")
g3.edge("etiology", "mechanical3")
g3.edge("etiology", "allergic3")
g3.edge("involutional3", "gentarsal", label="generalized")
g3.edge("involutional3", "medspindle", label="medial")
g3.edge("gentarsal", "resolved3")
g3.edge("medspindle", "resolved3")
g3.edge("cicatricial3", "cicextent")
g3.edge("cicextent", "ciclocal", label="localized")
g3.edge("cicextent", "cicgen", label="generalized")
g3.edge("ciclocal", "resolved3")
g3.edge("cicgen", "resolved3")
g3.edge("paralytic3", "recovery")
g3.edge("recovery", "paralytictemp", label="expected /\n<6-12 mo")
g3.edge("recovery", "paralyticperm", label="irreversible /\n>6-12 mo")
g3.edge("paralytictemp", "resolved3")
g3.edge("paralyticperm", "resolved3")
g3.edge("mechanical3", "mechmgmt")
g3.edge("mechmgmt", "resolved3")
g3.edge("allergic3", "allergicmgmt")
g3.edge("allergicmgmt", "resolved3")
g3.render("/tmp/workspace/entropion-ectropion-flowchart/3_ectropion", cleanup=True)
print("Done")
mkdir -p /tmp/workspace/entropion-ectropion-flowchart && cd /tmp/workspace/entropion-ectropion-flowchart && python3 build_flowcharts.py && ls -la
~/entropion-ectropion-flowchart/build_flowcharts.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/entropion-ectropion-flowchart/build_flowcharts.py'
cd /tmp/workspace/entropion-ectropion-flowchart && rm -f *.png && python3 build_flowcharts.py && ls -la



1 upper lid entropion
Image · PNG
2 lower lid entropion
Image · PNG
3 ectropion
Image · PNG