using this presentation create for me a presentation on cholera with about 20-25 slides for a clinical meeting as there is an outbreak ongoing. you can use images from this, make it bright themed and colourful with cartoon like images and also visually thrilling
cd /tmp/workspace/attachments/9febb409-b645-4895-b5f3-22ded5518d5a/ && python -c " from pptx import Presentation from pptx.util import Emu prs=Presentation('Cholera Case Management 11022025.pptx') print(prs.slide_width,prs.slide_height,len(prs.slides)) for i,s in enumerate(prs.slides,1): print('--- slide',i) for sh in s.shapes: t=sh.text_frame.text.strip().replace('\n',' | ') if sh.has_text_frame else '' print(sh.shape_type,sh.name,sh.width,sh.height,t[:400]) if s.has_notes_slide: print('NOTES',s.notes_slide.notes_text_frame.text[:300]) "
mkdir -p /tmp/workspace/cholera/src /tmp/workspace/cholera/media && cd /tmp/workspace/cholera && python -c " from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE as T import hashlib prs=Presentation('/tmp/workspace/attachments/9febb409-b645-4895-b5f3-22ded5518d5a/Cholera Case Management 11022025.pptx') seen={} def walk(shapes,i): for sh in shapes: if sh.shape_type==T.GROUP: walk(sh.shapes,i) elif sh.shape_type==T.PICTURE: im=sh.image; h=hashlib.md5(im.blob).hexdigest()[:8] fn=f'media/s{i:02d}_{h}.{im.ext}' open(fn,'wb').write(im.blob) print(i,fn,im.size,len(im.blob),sh.name) for i,s in enumerate(prs.slides,1): walk(s.shapes,i) "
cd /tmp/workspace/cholera && python -c " from pptx import Presentation prs=Presentation('/tmp/workspace/attachments/9febb409-b645-4895-b5f3-22ded5518d5a/Cholera Case Management 11022025.pptx') for i in [5,11,13,16,17,19,25,27,28,30,31,33,34,36,37,43,45,46]: s=prs.slides[i-1] print('=== slide',i) for sh in s.shapes: if sh.has_text_frame: print(sh.text_frame.text) if sh.has_table: for r in sh.table.rows: print(' || '.join(c.text.replace('\n',' / ') for c in r.cells)) if s.has_notes_slide: print('NOTES:',s.notes_slide.notes_text_frame.text[:900]) "
mkdir -p /tmp/workspace/cholera/media && cd /tmp/workspace/cholera && python -c " from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE_TYPE as T import hashlib prs=Presentation('/tmp/workspace/attachments/9febb409-b645-4895-b5f3-22ded5518d5a/Cholera Case Management 11022025.pptx') def walk(shapes,i): for sh in shapes: if sh.shape_type==T.GROUP: walk(sh.shapes,i) elif sh.shape_type==T.PICTURE: try: im=sh.image except Exception: continue h=hashlib.md5(im.blob).hexdigest()[:8] fn=f'media/s{i:02d}_{h}.{im.ext}' open(fn,'wb').write(im.blob) print(i,fn,im.size,len(im.blob),sh.name) for i,s in enumerate(prs.slides,1): walk(s.shapes,i) " python -c " from pptx import Presentation prs=Presentation('/tmp/workspace/attachments/9febb409-b645-4895-b5f3-22ded5518d5a/Cholera Case Management 11022025.pptx') for i in [5,11,13,16,17,19,25,28,31,33,34,36,37,43,45,46]: s=prs.slides[i-1] print('=== slide',i) for sh in s.shapes: if sh.has_text_frame: print(sh.text_frame.text[:700]) if getattr(sh,'has_table',False) and sh.has_table: for r in sh.table.rows: print(' || '.join(c.text.replace('\n',' / ') for c in r.cells)) if s.has_notes_slide: print('NOTES:',s.notes_slide.notes_text_frame.text[:500]) "
cd /tmp/workspace/cholera && python -c " from PIL import Image, ImageDraw import glob fs=sorted(glob.glob('media/*')) seen=set();items=[] for f in fs: k=f.split('_')[1] if k in seen: continue seen.add(k); items.append(f) W=300;cols=5 rows=(len(items)+cols-1)//cols sheet=Image.new('RGB',(cols*W,rows*W),'white') d=ImageDraw.Draw(sheet) for n,f in enumerate(items): im=Image.open(f).convert('RGBA');bg=Image.new('RGBA',im.size,(200,200,200,255));bg.alpha_composite(im);im=bg.convert('RGB') im.thumbnail((W-10,W-30)) x=(n%cols)*W;y=(n//cols)*W sheet.paste(im,(x+5,y+25));d.text((x+5,y+5),f.split('/')[1],fill='red') sheet.save('contact.png');print(len(items)) "
"cholera"[MeSH Terms] AND "treatment"
WHO cholera global situation 2026 outbreak cases deaths oral cholera vaccine
cd /tmp/workspace/cholera && python -c " from PIL import Image im=Image.open('contact.png'); im=im.resize((im.width*2//3, im.height*2//3)); im.convert('RGB').save('contact.jpg',quality=70) import os;print(os.path.getsize('contact.jpg'), im.size)"
A cute cartoon comma-shaped Vibrio cholerae bacterium villain character with one long flagellum tail and mischievous face, bright colorful flat illustration, white background
Cartoon illustration of a friendly doctor handing a glass of oral rehydration solution and an ORS sachet to a smiling child patient, bright colorful flat style, white background
Cartoon illustration of prevention: washing hands with soap under a tap, a clean water bucket with chlorine, and a toilet, bright colorful flat style, white background
Cartoon illustration of a sad dehydrated person with sunken eyes next to an empty water drop character and a thermometer, bright colorful flat style, white background
cd /tmp/workspace/cholera && mkdir -p gen && python /tmp/skills/shared/scripts/fetch_images.py "https://cdn.orris.care/image-gen/0c25856a008c4411a30cb59a5939ea1b.png" "https://cdn.orris.care/image-gen/46c911c2af094c8db3c6d4f11494fa8d.png" "https://cdn.orris.care/image-gen/c813d68be7954ca49d113eceb09bf081.png" "https://cdn.orris.care/image-gen/7e46b3236a3a45d4a76e4ec8279860d4.png" > gen/out.json; python -c " import json,base64 from PIL import Image r=json.load(open('gen/out.json')) names=['bact','doc','wash','dehyd'] ims=[] for n,x in zip(names,r): print(n,x['error']) b=x['base64'].split('base64,')[1] open(f'gen/{n}.png','wb').write(base64.b64decode(b)) im=Image.open(f'gen/{n}.png');print(im.size,im.mode, im.getpixel((5,5))) ims.append(im.convert('RGB').resize((400,400))) s=Image.new('RGB',(800,800)) for i,im in enumerate(ims): s.paste(im,((i%2)*400,(i//2)*400)) s.save('gen/sheet.jpg',quality=70) "
~/cholera/build.py
import math, os
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE
from PIL import Image, ImageChops
B = '/tmp/workspace/cholera/'
M = B + 'media/'
NAVY = '1B2A5E'; TEAL = '0DB7A9'; CORAL = 'FF5E6C'; SUN = 'FFC531'; PURPLE = '7A5AF8'
BLUE = '2E9BF0'; GREEN = '39C97E'; ORANGE = 'FF9142'; PINK = 'FF7EB6'; CREAM = 'FFF9EC'; WHITE = 'FFFFFF'
WARN = []
def rgb(h): return RGBColor.from_string(h)
def tint(h, f):
r, g, b = [int(h[i:i + 2], 16) for i in (0, 2, 4)]
return '%02X%02X%02X' % tuple(int(c + (255 - c) * f) for c in (r, g, b))
# autocrop generated cartoons
for n in ['bact', 'doc', 'wash', 'dehyd']:
im = Image.open(B + f'gen/{n}.png').convert('RGB')
bg = Image.new('RGB', im.size, (255, 255, 255))
bbox = ImageChops.difference(im, bg).getbbox()
x0, y0, x1, y1 = bbox
im.crop((max(0, x0 - 10), max(0, y0 - 10), min(im.width, x1 + 10), min(im.height, y1 + 10))).save(B + f'gen/{n}_c.png')
G = lambda n: B + f'gen/{n}_c.png'
prs = Presentation()
prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5)
blank = prs.slide_layouts[6]
CUR = {'n': 0, 'title': ''}
def box(s, x, y, w, h, fill, shape=MSO_SHAPE.ROUNDED_RECTANGLE, line=None, lw=0, radius=0.12):
sh = s.shapes.add_shape(shape, Inches(x), Inches(y), Inches(w), Inches(h))
if fill: sh.fill.solid(); sh.fill.fore_color.rgb = rgb(fill)
else: sh.fill.background()
if line: sh.line.color.rgb = rgb(line); sh.line.width = Pt(lw)
else: sh.line.fill.background()
if shape == MSO_SHAPE.ROUNDED_RECTANGLE: sh.adjustments[0] = radius
sh.shadow.inherit = False
return sh
def tx(s, x, y, w, h, paras, size=16, color=NAVY, bold=False, align='l', anchor='t', font='Calibri', spacing=3, shape=None, margin=0.08):
if shape is None:
shape = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h))
tf = shape.text_frame; tf.word_wrap = True
tf.margin_left = tf.margin_right = Inches(margin); tf.margin_top = tf.margin_bottom = Inches(0.04)
tf.vertical_anchor = {'t': MSO_ANCHOR.TOP, 'm': MSO_ANCHOR.MIDDLE, 'b': MSO_ANCHOR.BOTTOM}[anchor]
if isinstance(paras, str): paras = [paras]
total = 0
for i, p in enumerate(paras):
opts = {}
if isinstance(p, tuple): p, opts = p
sz = opts.get('size', size); col = opts.get('color', color); bd = opts.get('bold', bold)
para = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
para.alignment = {'l': PP_ALIGN.LEFT, 'c': PP_ALIGN.CENTER, 'r': PP_ALIGN.RIGHT}[opts.get('align', align)]
para.space_after = Pt(spacing)
r = para.add_run(); r.text = p
r.font.size = Pt(sz); r.font.bold = bd; r.font.color.rgb = rgb(col); r.font.name = opts.get('font', font)
cw = 0.58 if (bd or font != 'Calibri') else 0.5
lines = max(1, math.ceil(len(p) * sz * cw / 72 / max(0.3, w - 2 * margin)))
total += lines * sz * 1.2 / 72 + spacing / 72
if total > h - 0.05:
WARN.append(f'slide {CUR["n"]} ({CUR["title"]}): text est {total:.2f}in > {h:.2f}in : {str(paras[0])[:40]}')
return shape
def fit(s, path, x, y, w, h, frame=None, pad=0.0):
im = Image.open(path); iw, ih = im.size
sc = min((w - 2 * pad) / iw, (h - 2 * pad) / ih)
pw, ph = iw * sc, ih * sc
px, py = x + (w - pw) / 2, y + (h - ph) / 2
if frame: box(s, x, y, w, h, WHITE, line=frame, lw=3)
s.shapes.add_picture(path, Inches(px), Inches(py), Inches(pw), Inches(ph))
def new(title, color, notes=None, bg=CREAM):
s = prs.slides.add_slide(blank)
CUR['n'] += 1; CUR['title'] = title
s.background.fill.solid(); s.background.fill.fore_color.rgb = rgb(bg)
box(s, 0, 0, 13.333, 1.15, color, MSO_SHAPE.RECTANGLE)
box(s, 11.4, -0.6, 1.5, 1.5, tint(color, .25), MSO_SHAPE.OVAL)
box(s, 12.4, 0.1, 0.9, 0.9, tint(color, .5), MSO_SHAPE.OVAL)
box(s, 10.7, 0.55, 0.4, 0.4, SUN, MSO_SHAPE.OVAL)
tx(s, 0.5, 0.1, 10.2, 0.95, title, size=28, color=WHITE, bold=True, anchor='m', font='Trebuchet MS')
for i, c in enumerate([TEAL, CORAL, SUN, PURPLE, BLUE, GREEN]):
box(s, i * 13.333 / 6, 7.38, 13.333 / 6, 0.12, c, MSO_SHAPE.RECTANGLE)
tx(s, 12.3, 7.05, 0.9, 0.3, str(CUR['n']), size=11, color='8A93B5', align='r')
if notes: s.notes_slide.notes_text_frame.text = notes
return s
def card(s, x, y, w, h, color, title, lines, tsize=18, bsize=14, hh=0.55, bullet=True, fill=WHITE):
box(s, x, y, w, h, fill, line=color, lw=3)
hd = box(s, x, y, w, hh, color)
tx(s, 0, 0, 0, 0, title, size=tsize, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=hd)
ps = [('\u2022 ' + l if bullet else l) for l in lines]
tx(s, x + 0.08, y + hh + 0.06, w - 0.16, h - hh - 0.1, ps, size=bsize, spacing=4)
def circle_num(s, x, y, d, color, text, size=18):
c = box(s, x, y, d, d, color, MSO_SHAPE.OVAL)
tx(s, 0, 0, 0, 0, text, size=size, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=c, margin=0)
def chev(s, x, y, w, h, color, text, size=15, first=False):
sh = box(s, x, y, w, h, color, MSO_SHAPE.PENTAGON if first else MSO_SHAPE.CHEVRON)
tx(s, 0, 0, 0, 0, text, size=size, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=sh, margin=0.25)
def arrow(s, x, y, w, h, color, down=False):
box(s, x, y, w, h, color, MSO_SHAPE.DOWN_ARROW if down else MSO_SHAPE.RIGHT_ARROW)
def table(s, x, y, colw, rows, hdr, size=13, rowh=0.5, fills=None, bold_first=True):
gs = s.shapes.add_table(len(rows), len(colw), Inches(x), Inches(y), Inches(sum(colw)), Inches(rowh * len(rows)))
t = gs.table
tblPr = gs._element.graphic.graphicData.tbl.tblPr
tblPr.set('bandRow', '0'); tblPr.set('firstRow', '0')
for i, wv in enumerate(colw): t.columns[i].width = Inches(wv)
for r, row in enumerate(rows):
t.rows[r].height = Inches(rowh if not isinstance(rowh, list) else rowh[r])
for c, val in enumerate(row):
cell = t.cell(r, c); cell.margin_left = cell.margin_right = Inches(0.08)
cell.margin_top = cell.margin_bottom = Inches(0.04)
cell.vertical_anchor = MSO_ANCHOR.MIDDLE
cell.fill.solid()
cell.fill.fore_color.rgb = rgb(hdr if r == 0 else (fills[r - 1] if fills else (WHITE if r % 2 else tint(hdr, .88))))
tf = cell.text_frame; tf.word_wrap = True
lines = val.split('\n')
for i, ln in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
run = p.add_run(); run.text = ln; run.font.size = Pt(size); run.font.name = 'Calibri'
run.font.bold = (r == 0) or (c == 0 and bold_first)
run.font.color.rgb = rgb(WHITE if r == 0 else NAVY)
return t
# ============ 1. TITLE ============
s = prs.slides.add_slide(blank); CUR['n'] += 1; CUR['title'] = 'title'
s.background.fill.solid(); s.background.fill.fore_color.rgb = rgb(TEAL)
box(s, -1.5, -1.5, 5, 5, tint(TEAL, .25), MSO_SHAPE.OVAL)
box(s, 10.5, 4.8, 4.5, 4.5, tint(TEAL, .25), MSO_SHAPE.OVAL)
box(s, 9.6, 0.4, 0.5, 0.5, SUN, MSO_SHAPE.OVAL)
box(s, 0.6, 6.0, 0.35, 0.35, CORAL, MSO_SHAPE.OVAL)
box(s, 12.3, 3.9, 0.6, 0.6, PINK, MSO_SHAPE.OVAL)
box(s, 6.9, 1.0, 5.9, 4.4, WHITE, radius=0.08)
fit(s, G('bact'), 7.1, 1.2, 5.5, 4.0)
tx(s, 0.7, 1.2, 6.2, 1.4, 'CHOLERA', size=66, color=WHITE, bold=True, font='Trebuchet MS')
box(s, 0.8, 2.75, 5.6, 0.12, SUN, MSO_SHAPE.RECTANGLE)
tx(s, 0.7, 3.0, 6.0, 1.5, ['Outbreak response:', 'clinical case management'], size=30, color=WHITE, bold=True, font='Trebuchet MS')
tx(s, 0.7, 4.7, 6.0, 0.6, 'Clinical meeting | Rehydrate fast, save lives', size=20, color=NAVY, bold=True)
tx(s, 0.7, 6.3, 12, 0.7, 'Adapted from "Case Management in Cholera Control" (Dr Sinkala Annel) with WHO / GTFCC guidance and standard textbooks',
size=13, color=WHITE)
s.notes_slide.notes_text_frame.text = 'Opening: this is a case management refresher for the ongoing outbreak. Key message: cholera kills by dehydration, and dehydration is treatable.'
# ============ 2. OUTBREAK PICTURE ============
s = new('The outbreak picture: why we are here', CORAL,
'Regional figures are from the WHO AFRO rapid risk assessment (data to 20 September 2026). Replace the bottom box with our own line list: cases, deaths, CFR, hotspots.')
stats = [('160,799', 'suspected cases in 2026', CORAL), ('2,466', 'deaths reported', PURPLE),
('1.53%', 'case fatality ratio (goal: below 1%)', ORANGE), ('19', 'countries in WHO African Region', TEAL)]
for i, (n, l, c) in enumerate(stats):
x = 0.5 + i * 3.1
box(s, x, 1.5, 2.9, 2.5, c)
box(s, x + 2.2, 1.3, 0.5, 0.5, SUN, MSO_SHAPE.OVAL)
tx(s, x, 1.75, 2.9, 1.0, n, size=42, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS')
tx(s, x + 0.1, 2.8, 2.7, 1.1, l, size=17, color=WHITE, bold=True, align='c')
box(s, 0.5, 4.3, 12.3, 1.0, tint(BLUE, .8), line=BLUE, lw=2.5)
tx(s, 0.7, 4.3, 11.9, 1.0, 'DR Congo, Nigeria and South Sudan together account for 83% of cases. A CFR above 1% signals gaps in access to rehydration and early care.',
size=18, bold=True, anchor='m')
box(s, 0.5, 5.55, 12.3, 1.2, SUN, radius=0.15)
tx(s, 0.7, 5.55, 11.9, 1.2, [('OUR LOCAL SITUATION', {'size': 14, 'bold': True}), ('Insert today\'s cases, deaths, CFR and hotspot districts here', {'size': 20, 'bold': True})],
anchor='m', align='c', font='Trebuchet MS')
tx(s, 0.5, 6.85, 11.5, 0.3, 'Source: WHO AFRO rapid risk assessment, epidemiological weeks 1-38, data to 20 September 2026', size=11, color='6B7499')
# ============ 3. KEY FACTS ============
s = new('Cholera: key facts', PURPLE,
'Short incubation means rapid exponential rise. If you see some cases, there are probably more in the community.')
facts = [('All ages', 'Affects children and adults alike.', TEAL),
('Healthy adults die too', 'Unlike most diarrhoeal diseases, untreated cholera can kill healthy adults, not just children.', CORAL),
('Pregnancy', 'Pregnant women are at higher risk of complications, including loss of the pregnancy.', PINK),
('Under 5s', 'Children under 5, especially malnourished ones, are at greater risk of dying.', ORANGE)]
for i, (t, d, c) in enumerate(facts):
x = 0.5 + (i % 2) * 6.25; y = 1.45 + (i // 2) * 2.2
box(s, x, y, 6.0, 2.0, WHITE, line=c, lw=3)
circle_num(s, x + 0.25, y + 0.3, 0.9, c, str(i + 1), 30)
tx(s, x + 1.3, y + 0.15, 4.6, 0.55, t, size=22, bold=True, color=c, font='Trebuchet MS')
tx(s, x + 1.3, y + 0.7, 4.6, 1.2, d, size=16)
box(s, 0.5, 5.95, 12.3, 1.0, PURPLE)
tx(s, 0.7, 5.95, 11.9, 1.0, 'Hits the poorest and most vulnerable hardest. A short incubation period can cause an exponential rise in cases.', size=18, color=WHITE, bold=True, anchor='m', align='c')
# ============ 4. CULPRIT ============
s = new('Meet the culprit: Vibrio cholerae', TEAL,
'Source deck: transmission through contaminated water or food; fecal contamination can cause large epidemics; shellfish. Incubation 18 hours to 5 days.')
box(s, 0.5, 1.5, 5.0, 5.3, WHITE, line=TEAL, lw=3)
fit(s, G('bact'), 0.6, 1.9, 4.8, 3.6)
tx(s, 0.6, 5.7, 4.8, 1.0, 'Curved Gram-negative rod. Serogroups O1 (classical, El Tor) and O139 cause epidemics.', size=14, align='c')
rows = [('Organism', 'Vibrio cholerae serogroup O1 or O139, acquired by ingestion.', TEAL),
('Transmission', 'Contaminated drinking water or food. Faecal contamination can drive large epidemics. Raw or undercooked shellfish.', CORAL),
('Incubation', 'Short: 18 hours to 5 days. Cases can climb exponentially.', PURPLE)]
for i, (t, d, c) in enumerate(rows):
y = 1.5 + i * 1.8
box(s, 5.8, y, 7.0, 1.6, tint(c, .85), line=c, lw=2.5)
b = box(s, 5.8, y, 1.9, 1.6, c)
tx(s, 0, 0, 0, 0, t, size=18, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=b)
tx(s, 7.8, y, 4.9, 1.6, d, size=16, anchor='m')
# ============ 5. CHAIN ============
s = new('Breaking the chain of death', BLUE,
'Each link of the chain has an intervention. The last link, death, is almost entirely preventable with prompt rehydration.')
labels = ['Vibrio in the environment', 'Exposure', 'Infected', 'Diseased', 'Dead']
cols = [TEAL, BLUE, PURPLE, ORANGE, CORAL]
brk = ['Safe water, sanitation, chlorination', 'Handwashing, safe food, cook shellfish, OCV', 'Early detection, contact tracing, community alert',
'Fast ORS or IV fluids at the nearest point of care', 'Zero deaths is the target: rehydrate before shock']
for i in range(5):
chev(s, 0.4 + i * 2.5, 1.6, 2.65, 1.3, cols[i], labels[i], size=15, first=(i == 0))
arrow(s, 1.4 + i * 2.5, 3.1, 0.5, 0.6, SUN, down=True)
box(s, 0.5 + i * 2.5, 3.85, 2.3, 2.3, tint(cols[i], .85), line=cols[i], lw=2.5)
tx(s, 0.55 + i * 2.5, 3.9, 2.2, 0.4, 'Break it with', size=13, bold=True, color=cols[i], align='c')
tx(s, 0.55 + i * 2.5, 4.3, 2.2, 1.8, brk[i], size=15, align='c', anchor='m')
box(s, 0.5, 6.35, 12.3, 0.7, NAVY)
tx(s, 0.7, 6.35, 11.9, 0.7, 'Presence \u2192 Exposure \u2192 Infection \u2192 Disease \u2192 Death. We act hardest at the last two links.', size=17, color=WHITE, bold=True, anchor='m', align='c')
# ============ 6. PATHOGENESIS ============
s = new('Pathogenesis: how a toxin drains the gut', PURPLE,
'V. cholerae expresses toxin co-regulated pilus (TCP) and cholera toxin. CtxB binds GM1 ganglioside on enterocytes, CtxA raises cAMP, CFTR secretes chloride, sodium and water follow. Sodium-glucose cotransport is unaffected, which is the basis of ORS (Goldman-Cecil).')
steps = [('Swallowed', 'Vibrios in water or food survive stomach acid.', TEAL),
('Colonise', 'Attach to small bowel wall using toxin co-regulated pilus.', BLUE),
('Toxin released', 'Cholera toxin: A subunit and five B subunits.', PURPLE),
('Binds GM1', 'B subunits lock onto GM1 receptors on enterocytes.', ORANGE),
('cAMP surges', 'A subunit locks adenylate cyclase on, cAMP rises.', CORAL),
('Rice-water flood', 'Chloride pumped out, Na+ and water follow: massive watery stool.', PINK)]
for i, (t, d, c) in enumerate(steps):
x = 0.5 + (i % 3) * 4.15; y = 1.5 + (i // 3) * 2.45
box(s, x, y, 3.8, 2.2, WHITE, line=c, lw=3)
circle_num(s, x + 0.15, y + 0.15, 0.7, c, str(i + 1), 22)
tx(s, x + 0.95, y + 0.15, 2.8, 0.7, t, size=19, bold=True, color=c, anchor='m', font='Trebuchet MS')
tx(s, x + 0.15, y + 0.95, 3.5, 1.2, d, size=15)
if i % 3 < 2: arrow(s, x + 3.82, y + 0.85, 0.3, 0.45, SUN)
box(s, 0.5, 6.45, 12.3, 0.7, GREEN)
tx(s, 0.7, 6.45, 11.9, 0.7, 'Sodium-glucose co-transport still works, so oral rehydration works even in severe cholera.', size=17, color=WHITE, bold=True, anchor='m', align='c')
# ============ 7. SPECTRUM ============
s = new('Spectrum of illness: the iceberg', ORANGE,
'CDC: 75% asymptomatic, most of the 25% symptomatic have mild illness; about 2% of infections are severe (cholera gravis) and about 5% moderate. This is roughly 5-10% of symptomatic cases.')
cd = CategoryChartData(); cd.categories = ['Asymptomatic', 'Mild', 'Moderate', 'Severe']
cd.add_series('Share of infections', (75, 18, 5, 2))
gf = s.shapes.add_chart(XL_CHART_TYPE.DOUGHNUT, Inches(0.4), Inches(1.4), Inches(6.2), Inches(5.5), cd)
ch = gf.chart; ch.has_legend = False; ch.has_title = False
pl = ch.plots[0]; pl.has_data_labels = True
dl = pl.data_labels; dl.number_format = '0"%"'; dl.number_format_is_linked = False
dl.font.size = Pt(16); dl.font.bold = True; dl.font.color.rgb = rgb(WHITE)
for i, c in enumerate([TEAL, SUN, ORANGE, CORAL]):
pt = pl.series[0].points[i]; pt.format.fill.solid(); pt.format.fill.fore_color.rgb = rgb(c)
pt.format.line.color.rgb = rgb(WHITE)
leg = [('75%', 'Asymptomatic carriers: silent spreaders', TEAL), ('18%', 'Mild diarrhoea', SUN),
('5%', 'Moderate: seek care, may not need admission', ORANGE), ('2%', 'Severe cholera gravis: fatal if untreated', CORAL)]
for i, (n, d, c) in enumerate(leg):
y = 1.5 + i * 1.3
box(s, 6.9, y, 5.9, 1.1, tint(c, .85), line=c, lw=2.5)
b = box(s, 6.9, y, 1.5, 1.1, c)
tx(s, 0, 0, 0, 0, n, size=26, color=WHITE if c != SUN else NAVY, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=b)
tx(s, 8.5, y, 4.2, 1.1, d, size=16, anchor='m', bold=True)
tx(s, 6.9, 6.7, 5.9, 0.4, 'Biotype El Tor, share of all infections. Severe cases are 5-10% of symptomatic patients.', size=12, color='6B7499')
# ============ 8. CASE DEFINITIONS ============
s = new('Case definitions', GREEN,
'Use the right definition for the setting: before declaration, the suspected case needs severe dehydration; after declaration, any acute watery diarrhoea qualifies.')
cs = [('SUSPECTED', 'Outbreak not yet declared', 'Any person aged 2 years or older with acute watery diarrhoea and severe dehydration, or dying from acute watery diarrhoea.', TEAL),
('SUSPECTED', 'Outbreak declared', 'Any person presenting with, or dying from, acute watery diarrhoea.', ORANGE),
('CONFIRMED', 'Laboratory', 'A suspected case with V. cholerae O1 or O139 infection confirmed by culture or PCR.', PURPLE)]
for i, (a, b2, d, c) in enumerate(cs):
x = 0.5 + i * 4.15
box(s, x, 1.5, 3.9, 4.3, WHITE, line=c, lw=3)
hd = box(s, x, 1.5, 3.9, 1.3, c)
tx(s, 0, 0, 0, 0, [(a, {'size': 24, 'bold': True}), (b2, {'size': 15})], color=WHITE, align='c', anchor='m', font='Trebuchet MS', shape=hd)
tx(s, x + 0.15, 2.95, 3.6, 2.8, d, size=17)
box(s, 0.5, 6.05, 12.3, 0.9, tint(GREEN, .8), line=GREEN, lw=2.5)
tx(s, 0.7, 6.05, 11.9, 0.9, 'Rapid tests can flag early cases, but confirm by culture or PCR. Never wait for results before starting rehydration.', size=17, bold=True, anchor='m', align='c')
# ============ 9. CLINICAL PRESENTATION ============
s = new('Clinical presentation', CORAL,
'The symptoms and signs result from rapid loss of fluid and electrolytes. Leg cramps reflect hypokalaemia; vomiting is related to acidosis.')
box(s, 0.5, 1.45, 4.9, 5.0, WHITE, line=CORAL, lw=3)
fit(s, M + 's18_92732991.jpg', 0.6, 1.55, 4.7, 3.55)
tx(s, 0.6, 5.2, 4.7, 1.2, [('"Rice-water" stool', {'size': 20, 'bold': True, 'color': CORAL}), ('Pale, watery, flecked with mucus, little or no odour of blood', {'size': 14})], align='c', font='Calibri')
syms = [('Profuse watery diarrhoea', CORAL), ('Vomiting', ORANGE), ('Leg cramps (low K+)', PURPLE), ('Thirst', BLUE),
('Restless or irritable', TEAL), ('Fast heart rate', PINK), ('Low blood pressure', CORAL), ('Dry mouth, sunken eyes', ORANGE),
('Poor skin turgor', GREEN), ('Metabolic acidosis', PURPLE)]
for i, (t, c) in enumerate(syms):
x = 5.7 + (i % 2) * 3.65; y = 1.45 + (i // 2) * 1.0
b = box(s, x, y, 3.5, 0.85, c, radius=0.5)
tx(s, 0, 0, 0, 0, t, size=16, color=WHITE, bold=True, align='c', anchor='m', shape=b)
box(s, 5.7, 6.5, 7.15, 0.6, SUN)
tx(s, 5.8, 6.5, 6.95, 0.6, 'Severe disease: shock, renal failure, coma, death', size=17, bold=True, anchor='m', align='c')
# ============ 10. GRAVIS ============
s = new('Cholera gravis: the severe end', CORAL,
'A patient may produce 8 litres or more of stool in hours and lose over 10% of body weight. Untreated, about 50% of severe cases die; with proper fluid replacement mortality falls below 1%.')
box(s, 0.5, 1.4, 3.3, 5.6, WHITE, line=CORAL, lw=3)
fit(s, M + 's20_3ed0d4f2.png', 0.6, 1.5, 3.1, 5.4)
big = [('>10%', 'of body weight lost within hours', CORAL), ('1 L/hour', 'fluid loss possible in the first 24 hours', ORANGE),
('8 L+', 'of stool can be passed in a few hours', PURPLE)]
for i, (n, d, c) in enumerate(big):
x = 4.1 + i * 2.95
box(s, x, 1.4, 2.75, 2.5, c)
tx(s, x, 1.55, 2.75, 1.0, n, size=36, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS')
tx(s, x + 0.1, 2.6, 2.55, 1.2, d, size=16, color=WHITE, bold=True, align='c')
box(s, 4.1, 4.15, 4.2, 2.85, tint(CORAL, .85), line=CORAL, lw=3)
tx(s, 4.2, 4.2, 4.0, 0.9, '50%', size=48, color=CORAL, bold=True, align='c', font='Trebuchet MS')
tx(s, 4.2, 5.2, 4.0, 1.7, 'of severe cases die if untreated', size=19, bold=True, align='c')
box(s, 8.6, 4.15, 4.2, 2.85, tint(GREEN, .8), line=GREEN, lw=3)
tx(s, 8.7, 4.2, 4.0, 0.9, '<1%', size=48, color=GREEN, bold=True, align='c', font='Trebuchet MS')
tx(s, 8.7, 5.2, 4.0, 1.7, 'die with prompt, proper rehydration', size=19, bold=True, align='c')
# ============ 11. DDX ============
s = new('Differential diagnosis of diarrhoea', BLUE,
'Table adapted from LaRocque and Pietroni: common pathogens causing diarrhoea in adults in the developing world.')
box(s, 0.5, 1.45, 6.0, 5.0, WHITE, line=TEAL, lw=3)
hd = box(s, 0.5, 1.45, 6.0, 0.7, TEAL)
tx(s, 0, 0, 0, 0, 'Acute WATERY diarrhoea', size=20, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=hd)
chips = ['ETEC (most common)', 'Vibrio cholerae O1/O139', 'Norovirus (vomiting prominent)', 'Campylobacter', 'Non-typhoidal Salmonella', 'Aeromonas', 'EAEC', 'Enterotoxigenic B. fragilis']
for i, t in enumerate(chips):
cc = CORAL if i == 1 else tint(TEAL, .75)
b = box(s, 0.7 + (i % 2) * 2.9, 2.35 + (i // 2) * 1.0, 2.75, 0.85, cc, radius=0.4)
tx(s, 0, 0, 0, 0, t, size=14, color=WHITE if i == 1 else NAVY, bold=True, align='c', anchor='m', shape=b)
box(s, 6.85, 1.45, 6.0, 5.0, WHITE, line=PURPLE, lw=3)
hd = box(s, 6.85, 1.45, 6.0, 0.7, PURPLE)
tx(s, 0, 0, 0, 0, 'Acute BLOODY diarrhoea', size=20, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=hd)
chips = ['Shigella (most common)', 'Campylobacter', 'EIEC', 'EHEC', 'Salmonella (rare)', 'Entamoeba histolytica', 'Schistosoma mansoni']
for i, t in enumerate(chips):
b = box(s, 7.05 + (i % 2) * 2.9, 2.35 + (i // 2) * 1.0, 2.75, 0.85, tint(PURPLE, .75), radius=0.4)
tx(s, 0, 0, 0, 0, t, size=14, color=NAVY, bold=True, align='c', anchor='m', shape=b)
box(s, 0.5, 6.6, 12.3, 0.5, SUN)
tx(s, 0.7, 6.6, 11.9, 0.5, 'Profuse rice-water stool with rapid dehydration in an outbreak setting: think cholera, treat as cholera.', size=15, bold=True, anchor='m', align='c')
# ============ 12. ASSESS DEHYDRATION ============
s = new('Assess dehydration: look, feel, pinch', TEAL,
'Examine mental status, eyes, ability to drink, and skin pinch to classify quickly. Treatment follows the classification.')
cols3 = [('1. NO DEHYDRATION', GREEN, ['Awake and alert', 'Normal blood pressure and pulse', 'Normal thirst', 'Eyes not sunken', 'Skin pinch goes back normally'], 'Plan A'),
('2. MODERATE (5-10%)', ORANGE, ['Restless, irritable', 'Sunken eyes, dry mouth and tongue', 'Thirsty, drinks eagerly', 'Skin pinch goes back slowly', 'BP normal, pulse normal or fast', 'Infant: fewer tears, sunken fontanelle'], 'Plan B'),
('3. SEVERE (>10%)', CORAL, ['Hypovolaemic shock, low BP', 'Weak or absent pulse', 'Little or no urine', 'Skin tenting, very sunken eyes', 'Very dry mouth and tongue', 'Drowsy or unconscious'], 'Plan C')]
for i, (t, c, ls, p) in enumerate(cols3):
x = 0.5 + i * 4.15
card(s, x, 1.4, 3.9, 4.7, c, t, ls, tsize=18, bsize=15)
b = box(s, x + 0.6, 6.25, 2.7, 0.75, c, radius=0.5)
tx(s, 0, 0, 0, 0, '\u2192 ' + p, size=22, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS', shape=b)
# ============ 13. WHY DIE ============
s = new('Why do people really die?', CORAL,
'People do not die from cholera itself; they die from severe dehydration. Death definitions: community death occurs before admission to a facility or CTC; facility death occurs after admission regardless of timing.')
box(s, 0.5, 1.4, 12.3, 1.1, NAVY)
tx(s, 0.7, 1.4, 11.9, 1.1, 'People do not die "from cholera". They die because they become severely dehydrated and the body cannot survive without enough water.', size=19, color=WHITE, bold=True, anchor='m', align='c')
lab = ['Cholera', 'Diarrhoea', 'Dehydration', 'Severe dehydration', 'DEATH']
cc = [TEAL, BLUE, ORANGE, PURPLE, CORAL]
for i in range(5):
chev(s, 0.4 + i * 2.5, 2.75, 2.65, 1.05, cc[i], lab[i], size=15, first=(i == 0))
card(s, 0.5, 4.1, 4.0, 2.95, TEAL, 'Community death', ['Suspected or confirmed case', 'Dies in the community or on the way', 'Before admission to facility or CTC'], bsize=14, tsize=17)
card(s, 4.65, 4.1, 4.0, 2.95, PURPLE, 'Facility death', ['Suspected or confirmed case', 'Dies after admission to a health facility or CTC', 'Regardless of time since admission'], bsize=14, tsize=17)
box(s, 8.9, 4.1, 3.9, 2.95, WHITE, line=CORAL, lw=3)
fit(s, G('dehyd'), 9.0, 4.2, 3.7, 2.75)
# ============ 14. TRIAGE ============
s = new('Triage and rapid assessment', BLUE,
'Use the ABCD approach: assess and initiate management of emergency signs before moving on. Screen at the door so severe cases are treated first.')
box(s, 0.5, 1.4, 7.4, 3.6, WHITE, line=BLUE, lw=3)
fit(s, M + 's29_188ba4de.png', 0.6, 1.5, 7.2, 3.4)
card(s, 8.2, 1.4, 4.6, 3.6, CORAL, 'Emergency signs: act now', ['Airway and breathing: obstruction, distress', 'Circulation: weak pulse, low BP, cold hands, tenting skin', 'Disability: drowsy, convulsing, unconscious', 'Treat first, take history after'], bsize=14, tsize=17)
box(s, 0.5, 5.2, 12.3, 1.85, tint(SUN, .6), line=SUN, lw=3)
tx(s, 0.7, 5.25, 11.9, 0.45, 'Key history for acute watery diarrhoea', size=18, bold=True, font='Trebuchet MS')
hist = ['Onset and number of stools', 'Vomiting', 'Urine output', 'Sick contacts', 'Water and food source', 'Pregnancy', 'Other illness and drugs']
for i, t in enumerate(hist):
b = box(s, 0.7 + i * 1.7, 5.85, 1.6, 1.0, [TEAL, ORANGE, BLUE, PURPLE, GREEN, PINK, CORAL][i], radius=0.3)
tx(s, 0, 0, 0, 0, t, size=12, color=WHITE, bold=True, align='c', anchor='m', shape=b, margin=0.04)
# ============ 15. PLANS OVERVIEW ============
s = new('Treat by dehydration status: Plans A, B, C', PURPLE,
'Patients with no dehydration receive ORS after each loose stool until diarrhoea stops. Moderate: ORS by weight and age. Severe: IV fluid rapidly until BP normal.')
plans = [('A', 'No dehydration', GREEN, ['ORS after each loose stool', 'Treat at home or in the community', 'Teach danger signs', 'Return at once if worse']),
('B', 'Moderate dehydration', ORANGE, ['ORS by weight and age over 4 hours', 'Check every hour for the first 2 hours', 'Repeat ORS 10 minutes after vomiting', 'Reassess at 4 hours, then re-classify']),
('C', 'Severe dehydration', CORAL, ['IV Ringer\'s lactate, fast', 'Until BP is normal (3-6 hours)', 'Add ORS if able to drink', 'Admit to CTC / CTU'])]
for i, (l, t, c, ls) in enumerate(plans):
x = 0.5 + i * 4.15
box(s, x, 1.5, 3.9, 5.4, WHITE, line=c, lw=3)
box(s, x, 1.5, 3.9, 2.0, c)
circle_num(s, x + 1.25, 1.65, 1.4, WHITE, '', 10)
tx(s, x + 1.25, 1.65, 1.4, 1.4, l, size=56, color=c, bold=True, align='c', anchor='m', font='Trebuchet MS', margin=0)
tx(s, x + 0.1, 3.05, 3.7, 0.45, t, size=17, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS')
tx(s, x + 0.15, 3.7, 3.6, 3.1, ['\u2022 ' + z for z in ls], size=16, spacing=8)
# ============ 16. ORS ============
s = new('ORS: the lifesaving drink', TEAL,
'WHO ORS composition. The deck lists standard ORS (90/20/80/30, glucose 111 mmol/L). The current reduced-osmolarity WHO formula is 75/20/65, citrate 10, glucose 75 mmol/L, 245 mOsm/L. Zinc is given to children with diarrhoea.')
box(s, 0.5, 1.4, 4.3, 3.7, WHITE, line=TEAL, lw=3)
fit(s, G('doc'), 0.6, 1.5, 4.1, 3.5)
rows = [['mmol/L', 'Na+', 'K+', 'Cl-', 'Base', 'Glucose'],
['Standard WHO ORS', '90', '20', '80', '30 (HCO3)', '111'],
['Reduced osmolarity ORS', '75', '20', '65', '10 (citrate)', '75']]
table(s, 5.1, 1.5, [2.4, 0.9, 0.9, 0.9, 1.4, 1.2], rows, TEAL, size=15, rowh=0.62)
tx(s, 5.1, 3.5, 7.7, 0.4, 'Reduced osmolarity: 245 mOsm/L, recommended by WHO for routine use.', size=13, color='6B7499')
pts = [('80-90%', 'of patients can be treated with ORS alone', GREEN), ('Switch early', 'patients on IV move to ORS as soon as possible', BLUE), ('Be proactive', 'raise ORS volume gradually while IV is tapered', ORANGE)]
for i, (a, d, c) in enumerate(pts):
x = 5.1 + i * 2.6
box(s, x, 4.0, 2.45, 1.1, c)
tx(s, x, 4.0, 2.45, 0.5, a, size=18, color=WHITE, bold=True, align='c', anchor='m', font='Trebuchet MS')
tx(s, x + 0.05, 4.5, 2.35, 0.6, d, size=12, color=WHITE, align='c')
box(s, 0.5, 5.35, 12.3, 1.7, tint(TEAL, .85), line=TEAL, lw=3)
tx(s, 0.7, 5.4, 11.9, 1.6, ['Why it works: glucose pulls sodium and water across the gut wall, even when cholera toxin is active.',
'Practical: one sachet per litre of clean water, sip often, give the cup within reach, sit the patient up. In children add zinc (the Kit ya Moyo packs ORS with zinc).'], size=16, spacing=6, anchor='m')
# ============ 17. CHARTS ============
s = new('Quick-reference charts: Plans A, B and C', BLUE,
'Charts are from the source deck. Plan A and B dosing is by age and weight; Plan C shows the IV schedule.')
box(s, 0.4, 1.35, 4.9, 5.7, WHITE, line=GREEN, lw=3)
fit(s, M + 's22_d95ac0fe.png', 0.5, 1.45, 4.7, 5.5)
box(s, 5.5, 1.35, 4.9, 5.7, WHITE, line=CORAL, lw=3)
fit(s, M + 's24_f5d1c5db.png', 5.6, 1.45, 4.7, 5.5)
card(s, 10.6, 1.35, 2.35, 5.7, PURPLE, 'Tips', ['Weigh the patient if possible', 'Use age if weight unknown', 'Zinc for children', 'Reassess after each phase'], bsize=14, tsize=18)
# ============ 18. PLAN C ============
s = new('Plan C: IV rehydration for severe cases', CORAL,
'IV treatment should be rapid through large-bore catheters until BP is normal. Over 1 year: 30 mL/kg in 30 minutes then 70 mL/kg in 2.5 hours. Under 1 year: 30 mL/kg in the first hour then 70 mL/kg in 5 hours. 200 mL/kg or more may be needed in 24 hours.')
tx(s, 0.5, 1.35, 7.5, 0.45, 'Ringer\'s lactate: 100 mL/kg in total', size=20, bold=True, font='Trebuchet MS')
def bars(y, label, a, b2):
tx(s, 0.5, y, 7.5, 0.4, label, size=15, bold=True)
box(s, 0.5, y + 0.45, 7.5 * .3, 0.9, CORAL)
box(s, 0.5 + 7.5 * .3, y + 0.45, 7.5 * .7, 0.9, TEAL)
tx(s, 0.5, y + 0.45, 7.5 * .3, 0.9, a, size=15, color=WHITE, bold=True, align='c', anchor='m')
tx(s, 0.5 + 7.5 * .3, y + 0.45, 7.5 * .7, 0.9, b2, size=15, color=WHITE, bold=True, align='c', anchor='m')
bars(1.9, 'Age over 1 year', '30 mL/kg in 30 min', '70 mL/kg in 2.5 hours')
bars(3.55, 'Age under 1 year', '30 mL/kg in 1 hour', '70 mL/kg in 5 hours')
box(s, 0.5, 5.35, 7.5, 1.65, SUN)
tx(s, 0.6, 5.35, 7.3, 1.65, [('Adults: 1 litre in the first 15 minutes', {'size': 19, 'bold': True}),
('Keep going until blood pressure is normal (3-6 hours). Up to 200 mL/kg or more may be needed in the first 24 hours.', {'size': 15})], anchor='m', spacing=5)
card(s, 8.3, 1.35, 4.5, 5.65, PURPLE, 'IV checklist', ['Ringer\'s lactate is best; saline works but is less effective', 'D5W is ineffective (only if malnourished)', 'Two large-bore lines (16G or 18G) if needed', 'Hang the bag high, new IV set for every patient', 'Give ORS 5 mL/kg/hour alongside if able to drink', 'No oral or NG route if shocked or unconscious'], bsize=14, tsize=19)
# ============ 19. MONITORING ============
s = new('Monitoring and signs of rehydration', GREEN,
'Record input and output. Adequate rehydration: skin returns normally on pinch, thirst gone, urine passed, strong pulse.')
box(s, 0.5, 1.4, 3.3, 5.6, WHITE, line=GREEN, lw=3)
fit(s, M + 's30_6c7121fe.png', 0.6, 1.5, 3.1, 5.4)
card(s, 4.1, 1.4, 4.2, 5.6, BLUE, 'Chart every patient', ['INPUT: litres of IV fluid and cups of ORS', 'Mark the volume per hour on each bag', 'Keep cup and ORS within reach; sit up to drink', 'OUTPUT: volume and type of stool, vomit', 'OUTPUT: is urine being passed?'], bsize=15, tsize=19)
tx(s, 8.6, 1.4, 4.2, 0.5, 'Adequately rehydrated when...', size=19, bold=True, font='Trebuchet MS', color=GREEN)
sg = ['Skin pinch goes back normally', 'Thirst has gone', 'Urine has been passed', 'Pulse is strong']
for i, t in enumerate(sg):
y = 2.05 + i * 1.2
circle_num(s, 8.6, y, 0.85, GREEN, '\u2713', 28)
b = box(s, 9.6, y, 3.2, 0.85, tint(GREEN, .8), line=GREEN, lw=2, radius=0.3)
tx(~/cholera/build.py
Successfully replaced 1 instance(s) of the string in '/tmp/workspace/cholera/build.py'
cd /tmp/workspace/cholera && python build.py 2>&1 | tail -40
cd /tmp/workspace/cholera && python - <<'EOF' p='build.py' t=open(p).read() t=t.replace("Inches(rowh * len(rows)))","Inches(sum(rowh) if isinstance(rowh, list) else rowh * len(rows)))") open(p,'w').write(t) EOF python build.py 2>&1 | tail -40
cd /tmp/workspace/cholera && python - <<'EOF' p='build.py' t=open(p).read() t=t.replace(" if shape is None:\n shape = s.shapes.add_textbox"," if shape is not None and w == 0:\n w = shape.width / 914400; h = shape.height / 914400\n if shape is None:\n shape = s.shapes.add_textbox",1) open(p,'w').write(t) EOF python build.py 2>&1 | tail -40
cd /tmp/workspace/cholera && python - <<'EOF' p='build.py' t=open(p).read() reps=[("tx(s, 0.7, 4.7, 6.0, 0.6, 'Clinical meeting | Rehydrate fast, save lives', size=20","tx(s, 0.7, 4.7, 6.0, 0.8, 'Clinical meeting | Rehydrate fast, save lives', size=18"), ("tx(s, 6.9, 6.7, 5.9, 0.4, 'Biotype El Tor, share of all infections. Severe cases are 5-10% of symptomatic patients.', size=12","tx(s, 6.9, 6.6, 5.9, 0.55, 'Biotype El Tor, share of all infections. Severe cases are 5-10% of symptomatic patients.', size=12"), ("rehydration with rapid dehydration in an outbreak setting: think cholera, treat as cholera.', size=15","rehydration with rapid dehydration in an outbreak setting: think cholera, treat as cholera.', size=14"), ("'Adequately rehydrated when...', size=19","'Adequately rehydrated when...', size=17")] for a,b in reps: if a not in t: print('missing',a[:40]) t=t.replace(a,b) t=t.replace("Profuse rice-water stool with rapid dehydration in an outbreak setting: think cholera, treat as cholera.', size=15","Profuse rice-water stool with rapid dehydration in an outbreak setting: think cholera, treat as cholera.', size=14") open(p,'w').write(t) EOF python build.py 2>&1 | tail; ls -la Cholera_Outbreak_Clinical_Meeting.pptx python - <<'EOF' from pptx import Presentation p=Presentation('/tmp/workspace/cholera/Cholera_Outbreak_Clinical_Meeting.pptx') for i,s in enumerate(p.slides,1): t=[sh.text_frame.text for sh in s.shapes if sh.has_text_frame and sh.text_frame.text.strip()] print(i,t[0][:50] if t else '') EOF
Cholera Outbreak Clinical Meeting
Presentation · PPTX