ABG analysis
| Parameter | Normal Range |
|---|---|
| pH | 7.35 - 7.45 |
| PaCO2 | 35 - 45 mmHg |
| PaO2 | 80 - 100 mmHg |
| HCO3- | 21 - 27 mEq/L |
| SaO2 | 95 - 100% |
| Condition | pH | PaCO2 | HCO3- |
|---|---|---|---|
| Respiratory acidosis | Low | High (>45) | Normal/High |
| Respiratory alkalosis | High | Low (<35) | Normal/Low |
| Metabolic acidosis | Low | Normal/Low | Low (<22) |
| Metabolic alkalosis | High | Normal/High | High (>26) |
| Primary Disorder | Expected Compensation |
|---|---|
| Metabolic acidosis | PCO2 decreases: APCO2 = 1.3 x AHCO3- (Winter's formula) |
| Metabolic alkalosis | PCO2 increases: APCO2 = 0.6 x AHCO3- |
| Acute respiratory acidosis | HCO3- rises 1 mEq/L per 10 mmHg rise in PCO2 |
| Chronic respiratory acidosis | HCO3- rises 3.5-5 mEq/L per 10 mmHg rise in PCO2 |
| Acute respiratory alkalosis | HCO3- falls 2 mEq/L per 10 mmHg fall in PCO2 |
| Chronic respiratory alkalosis | HCO3- falls 5 mEq/L per 10 mmHg fall in PCO2 |
| High AG Metabolic Acidosis (MUDPILES) | Normal AG Metabolic Acidosis (HARDASS/USED CARP) |
|---|---|
| Methanol | Hyperalimentation |
| Uremia | Addison's disease |
| DKA | Renal tubular acidosis (RTA) |
| Propylene glycol / Paracetamol | Diarrhea |
| Isoniazid / Iron | Acetazolamide |
| Lactic acidosis | Spironolactone / Saline excess |
| Ethylene glycol | |
| Salicylates |
| A-a Gradient | Interpretation |
|---|---|
| Normal | Hypoventilation (CNS depression, NMD, chest wall) |
| Elevated | V/Q mismatch, shunt, diffusion impairment |

| Parameter | ABG | VBG |
|---|---|---|
| pH | Gold standard | Correlates closely (±0.05 units) |
| PCO2 | Reference | Trends with arterial (±20 mmHg) |
| PO2 | Reference | Cannot substitute - do not use |
| Lactate | Reference | Useful for screening; confirm if mildly elevated |
Create a quick reference ABG interpretation card (PDF)
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not found"
mkdir -p /home/daytona/workspace/abg-card && python3 -c "from reportlab.lib import colors; print('reportlab ok')"
~/abg-card/generate_abg_card.py
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
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.platypus import KeepTogether
# ── Colour palette ──────────────────────────────────────────────────────────
NAVY = colors.HexColor('#1B3A6B')
TEAL = colors.HexColor('#1A7F7A')
RED = colors.HexColor('#C0392B')
ORANGE = colors.HexColor('#D35400')
GOLD = colors.HexColor('#F0A500')
PURPLE = colors.HexColor('#6C3483')
LIGHT_BG = colors.HexColor('#F4F8FC')
ACID_BG = colors.HexColor('#FFF3CD')
ALK_BG = colors.HexColor('#D4EDDA')
RESP_BG = colors.HexColor('#D1ECF1')
MET_BG = colors.HexColor('#F8D7DA')
WHITE = colors.white
DARK_GREY = colors.HexColor('#333333')
MID_GREY = colors.HexColor('#666666')
LIGHT_LINE= colors.HexColor('#CCDDEE')
OUT = '/home/daytona/workspace/abg-card/ABG_Quick_Reference.pdf'
doc = SimpleDocTemplate(
OUT,
pagesize=A4,
leftMargin=12*mm, rightMargin=12*mm,
topMargin=10*mm, bottomMargin=10*mm
)
W = A4[0] - 24*mm # usable width
styles = getSampleStyleSheet()
def sty(name, **kw):
s = ParagraphStyle(name, **kw)
return s
H1 = sty('H1', fontSize=18, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER, spaceAfter=0, leading=22)
H2 = sty('H2', fontSize=10, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER, spaceAfter=0, leading=13)
SEC = sty('SEC', fontSize=8.5, fontName='Helvetica-Bold',
textColor=NAVY, alignment=TA_LEFT, spaceAfter=1, leading=11)
BODY = sty('BODY', fontSize=7.5, fontName='Helvetica',
textColor=DARK_GREY, leading=10)
SMALL = sty('SMALL', fontSize=6.8, fontName='Helvetica',
textColor=MID_GREY, leading=9)
BOLD_SM = sty('BOLD_SM', fontSize=7.5, fontName='Helvetica-Bold',
textColor=DARK_GREY, leading=10)
WHITE_SM = sty('WHITE_SM', fontSize=7.5, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER, leading=10)
TINY = sty('TINY', fontSize=6.5, fontName='Helvetica',
textColor=MID_GREY, alignment=TA_CENTER, leading=8)
# ── helpers ──────────────────────────────────────────────────────────────────
def section_header(text, bg=NAVY, fg=WHITE):
data = [[Paragraph(text, sty('sh', fontSize=8.5, fontName='Helvetica-Bold',
textColor=fg, alignment=TA_LEFT, leading=11))]]
t = Table(data, colWidths=[W])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), bg),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
]))
return t
def mini_spacer(h=2):
return Spacer(1, h*mm)
story = []
# ══════════════════════════════════════════════════════════════════════════════
# TITLE BANNER
# ══════════════════════════════════════════════════════════════════════════════
title_data = [[
Paragraph('ABG QUICK REFERENCE CARD', H1),
Paragraph('Arterial Blood Gas Interpretation', H2),
]]
title_tbl = Table([[
Paragraph('ABG QUICK REFERENCE CARD', H1),
]], colWidths=[W])
title_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), NAVY),
('TOPPADDING', (0,0), (-1,-1), 7),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('LEFTPADDING', (0,0), (-1,-1), 8),
]))
subtitle_tbl = Table([[
Paragraph('Arterial Blood Gas — Systematic Interpretation Guide', H2),
]], colWidths=[W])
subtitle_tbl.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), TEAL),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
]))
story += [title_tbl, subtitle_tbl, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 1 – NORMAL VALUES
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header(' NORMAL VALUES'))
story.append(mini_spacer(1))
nv_data = [
[Paragraph('<b>Parameter</b>', BOLD_SM),
Paragraph('<b>Normal Range</b>', BOLD_SM),
Paragraph('<b>Acidosis</b>', BOLD_SM),
Paragraph('<b>Alkalosis</b>', BOLD_SM)],
[Paragraph('pH', BODY), Paragraph('7.35 – 7.45', BODY),
Paragraph('< 7.35', BODY), Paragraph('> 7.45', BODY)],
[Paragraph('PaCO₂', BODY), Paragraph('35 – 45 mmHg', BODY),
Paragraph('> 45 mmHg', BODY), Paragraph('< 35 mmHg', BODY)],
[Paragraph('HCO₃⁻', BODY), Paragraph('22 – 26 mEq/L', BODY),
Paragraph('< 22 mEq/L', BODY), Paragraph('> 26 mEq/L', BODY)],
[Paragraph('PaO₂', BODY), Paragraph('80 – 100 mmHg', BODY),
Paragraph('< 80 mmHg (hypoxia)', BODY), Paragraph('—', BODY)],
[Paragraph('SaO₂', BODY), Paragraph('95 – 100%', BODY),
Paragraph('< 95%', BODY), Paragraph('—', BODY)],
[Paragraph('Base Excess', BODY), Paragraph('−2 to +2 mEq/L', BODY),
Paragraph('< −2', BODY), Paragraph('> +2', BODY)],
]
cw = [W*0.22, W*0.25, W*0.27, W*0.26]
nv_t = Table(nv_data, colWidths=cw)
nv_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
]))
story += [nv_t, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 2 – 6-STEP METHOD (left) + PRIMARY DISORDERS (right)
# ══════════════════════════════════════════════════════════════════════════════
# Left: steps
steps_rows = [
[Paragraph('<b>Step</b>', BOLD_SM), Paragraph('<b>Action</b>', BOLD_SM)],
[Paragraph('1', BOLD_SM), Paragraph('Check <b>pH</b>: acidemia (<7.35) or alkalemia (>7.45)?', BODY)],
[Paragraph('2', BOLD_SM), Paragraph('Identify <b>primary disorder</b> from PaCO₂ and HCO₃⁻', BODY)],
[Paragraph('3', BOLD_SM), Paragraph('Assess <b>compensation</b> — is it adequate?', BODY)],
[Paragraph('4', BOLD_SM), Paragraph('Calculate <b>Anion Gap</b> (if metabolic acidosis)', BODY)],
[Paragraph('5', BOLD_SM), Paragraph('Delta-Delta ratio (if high AG metabolic acidosis)', BODY)],
[Paragraph('6', BOLD_SM), Paragraph('Assess <b>oxygenation</b>: PaO₂ and A-a gradient', BODY)],
]
steps_t = Table(steps_rows, colWidths=[W*0.06, W*0.36])
steps_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
# Right: primary disorders
pd_rows = [
[Paragraph('<b>Disorder</b>', BOLD_SM),
Paragraph('<b>pH</b>', BOLD_SM),
Paragraph('<b>PaCO₂</b>', BOLD_SM),
Paragraph('<b>HCO₃⁻</b>', BOLD_SM)],
[Paragraph('Resp. Acidosis', BODY), Paragraph('↓', BODY), Paragraph('↑ PRIMARY', BODY), Paragraph('↑ comp.', BODY)],
[Paragraph('Resp. Alkalosis', BODY), Paragraph('↑', BODY), Paragraph('↓ PRIMARY', BODY), Paragraph('↓ comp.', BODY)],
[Paragraph('Met. Acidosis', BODY), Paragraph('↓', BODY), Paragraph('↓ comp.', BODY), Paragraph('↓ PRIMARY', BODY)],
[Paragraph('Met. Alkalosis', BODY), Paragraph('↑', BODY), Paragraph('↑ comp.', BODY), Paragraph('↑ PRIMARY', BODY)],
]
pd_t = Table(pd_rows, colWidths=[W*0.27, W*0.08, W*0.16, W*0.14])
pd_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), TEAL),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('ALIGN', (1,0), (-1,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
left_hdr = section_header(' 6-STEP SYSTEMATIC APPROACH', bg=TEAL)
right_hdr = section_header(' PRIMARY DISORDERS AT A GLANCE', bg=TEAL)
lr_table = Table(
[[left_hdr, right_hdr],
[steps_t, pd_t]],
colWidths=[W*0.45, W*0.55],
spaceBefore=0
)
lr_table.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 0),
('RIGHTPADDING', (0,0), (-1,-1), 0),
('TOPPADDING', (0,0), (-1,-1), 0),
('BOTTOMPADDING',(0,0), (-1,-1), 0),
('LINEAFTER', (0,0), (0,-1), 0.5, LIGHT_LINE),
]))
story += [lr_table, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 3 – COMPENSATION FORMULAS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header(' COMPENSATION FORMULAS'))
story.append(mini_spacer(1))
comp_data = [
[Paragraph('<b>Primary Disorder</b>', BOLD_SM),
Paragraph('<b>Expected Compensatory Response</b>', BOLD_SM),
Paragraph('<b>If Actual ≠ Expected</b>', BOLD_SM)],
[Paragraph('Metabolic Acidosis', BODY),
Paragraph('PaCO₂ = 1.5 × HCO₃⁻ + 8 ± 2 (Winters formula)\nor ↓ PaCO₂ = 1.3 × ↓ HCO₃⁻', BODY),
Paragraph('Mixed metabolic + respiratory', BODY)],
[Paragraph('Metabolic Alkalosis', BODY),
Paragraph('↑ PaCO₂ = 0.6 × ↑ HCO₃⁻ (max ~55 mmHg)', BODY),
Paragraph('Mixed metabolic + respiratory', BODY)],
[Paragraph('Acute Resp. Acidosis', BODY),
Paragraph('↑ HCO₃⁻ = 1 mEq/L per 10 mmHg ↑ PaCO₂', BODY),
Paragraph('Superimposed metabolic disorder', BODY)],
[Paragraph('Chronic Resp. Acidosis', BODY),
Paragraph('↑ HCO₃⁻ = 3.5 mEq/L per 10 mmHg ↑ PaCO₂\n(onset 3–5 days)', BODY),
Paragraph('Superimposed metabolic disorder', BODY)],
[Paragraph('Acute Resp. Alkalosis', BODY),
Paragraph('↓ HCO₃⁻ = 2 mEq/L per 10 mmHg ↓ PaCO₂', BODY),
Paragraph('Superimposed metabolic disorder', BODY)],
[Paragraph('Chronic Resp. Alkalosis', BODY),
Paragraph('↓ HCO₃⁻ = 5 mEq/L per 10 mmHg ↓ PaCO₂', BODY),
Paragraph('Superimposed metabolic disorder', BODY)],
]
comp_t = Table(comp_data, colWidths=[W*0.22, W*0.45, W*0.33])
comp_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), NAVY),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
story += [comp_t, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 4 – ANION GAP + DELTA-DELTA
# ══════════════════════════════════════════════════════════════════════════════
ag_left_hdr = section_header(' ANION GAP', bg=PURPLE)
ag_right_hdr = section_header(' DELTA-DELTA RATIO (ΔAG / ΔHCO₃⁻)', bg=PURPLE)
story.append(mini_spacer(0))
ag_formula = Table([[
Paragraph('AG = Na⁺ − (Cl⁻ + HCO₃⁻)', sty('af', fontSize=9,
fontName='Helvetica-Bold', textColor=PURPLE, alignment=TA_CENTER, leading=12)),
]], colWidths=[W*0.42])
ag_formula.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#F3E9FF')),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 1, PURPLE),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
]))
ag_rows = [
[Paragraph('<b>AG</b>', BOLD_SM), Paragraph('<b>Interpretation</b>', BOLD_SM)],
[Paragraph('< 8', BODY), Paragraph('Normal / low (hypoalbuminaemia, bromism)', BODY)],
[Paragraph('8–12', BODY), Paragraph('Normal', BODY)],
[Paragraph('> 12', BODY), Paragraph('High AG — unmeasured anions present', BODY)],
]
ag_t = Table(ag_rows, colWidths=[W*0.10, W*0.32])
ag_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#6C3483')),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
note_alb = Paragraph(
'* Correct AG for albumin: AG + 2.5 × (4 − albumin g/dL)', SMALL)
# Delta-delta
dd_formula = Table([[
Paragraph('ΔAG = Measured AG − 12\n\nRatio = ΔAG / (24 − measured HCO₃⁻)',
sty('df', fontSize=8.5, fontName='Helvetica-Bold',
textColor=PURPLE, alignment=TA_CENTER, leading=12)),
]], colWidths=[W*0.55])
dd_formula.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#F3E9FF')),
('TOPPADDING', (0,0), (-1,-1), 4),
('BOTTOMPADDING', (0,0), (-1,-1), 4),
('BOX', (0,0), (-1,-1), 1, PURPLE),
('ALIGN', (0,0), (-1,-1), 'CENTER'),
]))
dd_rows = [
[Paragraph('<b>Ratio</b>', BOLD_SM), Paragraph('<b>Interpretation</b>', BOLD_SM)],
[Paragraph('< 1', BODY), Paragraph('Mixed high AG + non-AG metabolic acidosis', BODY)],
[Paragraph('1 – 2', BODY), Paragraph('Pure high AG metabolic acidosis', BODY)],
[Paragraph('> 2', BODY), Paragraph('High AG acidosis + concurrent metabolic alkalosis', BODY)],
]
dd_t = Table(dd_rows, colWidths=[W*0.11, W*0.44])
dd_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#6C3483')),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('ALIGN', (0,0), (0,-1), 'CENTER'),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
ag_side = Table(
[[ag_left_hdr, ag_right_hdr],
[ag_formula, dd_formula],
[ag_t, dd_t],
[note_alb, Paragraph('', SMALL)]],
colWidths=[W*0.44, W*0.56]
)
ag_side.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 0),
('RIGHTPADDING', (0,0), (-1,-1), 0),
('TOPPADDING', (0,0), (-1,-1), 2),
('BOTTOMPADDING',(0,0), (-1,-1), 2),
('LINEAFTER', (0,0), (0,-1), 0.5, LIGHT_LINE),
]))
story += [ag_side, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 5 – CAUSES
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header(' CAUSES'))
story.append(mini_spacer(1))
# Box helper
def cause_box(title, bg, items, width):
rows = [[Paragraph(f'<b>{title}</b>',
sty('cb', fontSize=7.5, fontName='Helvetica-Bold',
textColor=WHITE, alignment=TA_CENTER, leading=10))]]
for item in items:
rows.append([Paragraph(f'• {item}', BODY)])
t = Table(rows, colWidths=[width])
t.setStyle(TableStyle([
('BACKGROUND', (0,0), (0,0), bg),
('BACKGROUND', (0,1), (-1,-1), colors.HexColor('#FAFBFF')),
('BOX', (0,0), (-1,-1), 0.5, bg),
('INNERGRID', (0,0), (-1,-1), 0.2, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2),
('BOTTOMPADDING', (0,0), (-1,-1), 2),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
return t
bw = W / 4 - 2*mm
high_ag = cause_box('High AG Acidosis\n(MUDPILES)', RED, [
'Methanol', 'Uraemia', 'DKA', 'Propylene glycol',
'Isoniazid / Iron', 'Lactic acidosis',
'Ethylene glycol', 'Salicylates'
], bw)
norm_ag = cause_box('Normal AG Acidosis\n(HARDASS)', ORANGE, [
'Hyperalimentation', 'Addison\'s disease',
'Renal tubular acidosis', 'Diarrhoea',
'Acetazolamide',
'Spironolactone / Saline'
], bw)
resp_ac = cause_box('Respiratory\nAcidosis', colors.HexColor('#1A5276'), [
'COPD / asthma', 'Pneumonia',
'Pulmonary oedema', 'Obesity hypoventilation',
'NMD (GBS, MG)', 'Opioids / sedatives',
'Chest wall deformity'
], bw)
met_alk = cause_box('Metabolic\nAlkalosis', TEAL, [
'Vomiting / NG suction', 'Diuretics',
'Hypokalaemia', 'Hyperaldosteronism',
'Excess NaHCO₃',
'Post-hypercapnia'
], bw)
causes_t = Table([[high_ag, norm_ag, resp_ac, met_alk]],
colWidths=[bw]*4)
causes_t.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 2),
('RIGHTPADDING', (0,0), (-1,-1), 2),
]))
story += [causes_t, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 6 – OXYGENATION + RESP ALKALOSIS CAUSES
# ══════════════════════════════════════════════════════════════════════════════
oxy_hdr = section_header(' OXYGENATION & A-a GRADIENT', bg=colors.HexColor('#1A5276'))
ra_hdr = section_header(' RESPIRATORY ALKALOSIS CAUSES', bg=colors.HexColor('#1A5276'))
oxy_formula = Paragraph(
'<b>PAO₂ = FiO₂ × (713) − PaCO₂ / 0.8</b> (room air: FiO₂ = 0.21, Patm = 760 mmHg)',
sty('of', fontSize=8, fontName='Helvetica-Bold', textColor=colors.HexColor('#1A5276'),
leading=11))
aa_normal = Paragraph(
'Normal A-a gradient ≈ Age/4 + 4 (typically < 15 mmHg on room air)',
BODY)
oxy_rows = [
[Paragraph('<b>PaO₂ (room air)</b>', BOLD_SM), Paragraph('<b>Classification</b>', BOLD_SM)],
[Paragraph('80–100 mmHg', BODY), Paragraph('Normal', BODY)],
[Paragraph('60–79 mmHg', BODY), Paragraph('Mild hypoxaemia', BODY)],
[Paragraph('40–59 mmHg', BODY), Paragraph('Moderate hypoxaemia', BODY)],
[Paragraph('< 40 mmHg', BODY), Paragraph('Severe hypoxaemia', BODY)],
]
oxy_t = Table(oxy_rows, colWidths=[W*0.22, W*0.22])
oxy_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#1A5276')),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
]))
aa_rows = [
[Paragraph('<b>A-a gradient</b>', BOLD_SM), Paragraph('<b>Cause of hypoxaemia</b>', BOLD_SM)],
[Paragraph('Normal', BODY), Paragraph('Hypoventilation (CNS depression, NMD)', BODY)],
[Paragraph('Elevated', BODY), Paragraph('V/Q mismatch, shunt, diffusion defect', BODY)],
]
aa_t = Table(aa_rows, colWidths=[W*0.14, W*0.28])
aa_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), colors.HexColor('#1A5276')),
('TEXTCOLOR', (0,0), (-1,0), WHITE),
('ROWBACKGROUNDS', (0,1), (-1,-1), [LIGHT_BG, WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
]))
ra_causes = [
'• Anxiety / hyperventilation',
'• Pain', '• Fever', '• Sepsis (early)',
'• Altitude / hypoxia', '• Salicylates',
'• PE', '• Liver failure', '• Pregnancy',
'• Mechanical overventilation',
]
ra_items = Table([[Paragraph(c, BODY)] for c in ra_causes],
colWidths=[W*0.42 - 4*mm])
ra_items.setStyle(TableStyle([
('ROWBACKGROUNDS', (0,0), (-1,-1), [LIGHT_BG, WHITE]),
('TOPPADDING', (0,0), (-1,-1), 2),
('BOTTOMPADDING', (0,0), (-1,-1), 2),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
]))
left_oxy = Table(
[[oxy_hdr],
[oxy_formula],
[mini_spacer(1)],
[aa_normal],
[mini_spacer(1)],
[oxy_t],
[mini_spacer(1)],
[aa_t]],
colWidths=[W*0.58]
)
left_oxy.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 0),
('RIGHTPADDING', (0,0), (-1,-1), 3),
('TOPPADDING', (0,0), (-1,-1), 2),
('BOTTOMPADDING',(0,0), (-1,-1), 2),
]))
right_ra = Table(
[[ra_hdr],
[ra_items]],
colWidths=[W*0.42]
)
right_ra.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 0),
('RIGHTPADDING', (0,0), (-1,-1), 0),
('TOPPADDING', (0,0), (-1,-1), 2),
('BOTTOMPADDING',(0,0), (-1,-1), 2),
]))
oxy_ra_t = Table([[left_oxy, right_ra]], colWidths=[W*0.58, W*0.42])
oxy_ra_t.setStyle(TableStyle([
('VALIGN', (0,0), (-1,-1), 'TOP'),
('LEFTPADDING', (0,0), (-1,-1), 0),
('RIGHTPADDING', (0,0), (-1,-1), 0),
('TOPPADDING', (0,0), (-1,-1), 0),
('BOTTOMPADDING',(0,0), (-1,-1), 0),
('LINEAFTER', (0,0), (0,-1), 0.5, LIGHT_LINE),
]))
story += [oxy_ra_t, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# SECTION 7 – CLINICAL PEARLS
# ══════════════════════════════════════════════════════════════════════════════
story.append(section_header(' CLINICAL PEARLS', bg=GOLD, fg=DARK_GREY))
story.append(mini_spacer(1))
pearls = [
('pH movement direction', 'Never crosses 7.40 with simple compensation alone — if pH overcorrects, suspect a mixed disorder.'),
('Acute vs chronic resp. acidosis', 'Acute: pH drops 0.08 per 10 mmHg PCO₂ rise. Chronic (>3–5 d): renal compensation normalises pH with ↑ HCO₃⁻.'),
('Hidden AG metabolic acidosis', 'Always calculate the AG even with a normal pH — a masked AG acidosis can coexist with metabolic alkalosis.'),
('Albumin correction', 'For every 1 g/dL fall in albumin below 4, add 2.5 to the AG. Hypoalbuminaemia lowers the "normal" AG.'),
('VBG vs ABG', 'VBG pH ≈ ABG pH (±0.05). Venous PCO₂ trends with arterial. Venous PO₂ cannot assess oxygenation.'),
('Metabolic compensation speed', 'Respiratory compensation for metabolic disorders = minutes to hours. Renal compensation for respiratory disorders = 3–5 days.'),
]
pearl_rows = [[Paragraph('<b>Pearl</b>', BOLD_SM), Paragraph('<b>Detail</b>', BOLD_SM)]]
for p, d in pearls:
pearl_rows.append([Paragraph(f'<b>{p}</b>', BODY), Paragraph(d, BODY)])
pearl_t = Table(pearl_rows, colWidths=[W*0.28, W*0.72])
pearl_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,0), GOLD),
('TEXTCOLOR', (0,0), (-1,0), DARK_GREY),
('ROWBACKGROUNDS', (0,1), (-1,-1), [colors.HexColor('#FFFDF0'), WHITE]),
('GRID', (0,0), (-1,-1), 0.3, LIGHT_LINE),
('TOPPADDING', (0,0), (-1,-1), 2.5),
('BOTTOMPADDING', (0,0), (-1,-1), 2.5),
('LEFTPADDING', (0,0), (-1,-1), 5),
('RIGHTPADDING', (0,0), (-1,-1), 5),
('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
]))
story += [pearl_t, mini_spacer(3)]
# ══════════════════════════════════════════════════════════════════════════════
# FOOTER
# ══════════════════════════════════════════════════════════════════════════════
footer_t = Table([[
Paragraph('ABG Quick Reference Card | Sources: Roberts & Hedges\' Clinical Procedures; Murray & Nadel\'s Respiratory Medicine; Tintinalli\'s Emergency Medicine',
TINY),
Paragraph('For clinical use by trained healthcare professionals only', TINY),
]], colWidths=[W*0.70, W*0.30])
footer_t.setStyle(TableStyle([
('BACKGROUND', (0,0), (-1,-1), NAVY),
('TEXTCOLOR', (0,0), (-1,-1), WHITE),
('TOPPADDING', (0,0), (-1,-1), 3),
('BOTTOMPADDING', (0,0), (-1,-1), 3),
('LEFTPADDING', (0,0), (-1,-1), 6),
('RIGHTPADDING', (0,0), (-1,-1), 6),
('ALIGN', (1,0), (1,0), 'RIGHT'),
]))
story.append(footer_t)
# ── Build ────────────────────────────────────────────────────────────────────
doc.build(story)
print(f'PDF saved to {OUT}')
python3 /home/daytona/workspace/abg-card/generate_abg_card.py
ls -lh /home/daytona/workspace/abg-card/ABG_Quick_Reference.pdf
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/abg-card/ABG_Quick_Reference.pdf') print(f'Pages: {len(r.pages)}') for i, p in enumerate(r.pages): t = p.extract_text() print(f'Page {i+1} chars: {len(t)}') print(t[:300]) print('---') "
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/abg-card/ABG_Quick_Reference.pdf') print(f'Pages: {len(r.pages)}') for i, p in enumerate(r.pages): print(f'Page {i+1}: {p.mediabox.width:.0f} x {p.mediabox.height:.0f} pts = {p.mediabox.width/72*25.4:.0f} x {p.mediabox.height/72*25.4:.0f} mm') print(f' Text length: {len(p.extract_text())} chars') "
ABG Quick Reference
PDF Document · PDF
| Section | Content |
|---|---|
| Normal Values | pH, PaCO₂, HCO₃⁻, PaO₂, SaO₂, Base Excess with acidosis/alkalosis thresholds |
| 6-Step Approach | Systematic interpretation method side-by-side with primary disorders table |
| Compensation Formulas | All 6 formulas including Winter's formula, acute vs chronic |
| Anion Gap | Formula, normal ranges, albumin correction note |
| Delta-Delta Ratio | Formula + interpretation of <1, 1-2, >2 |
| Causes | MUDPILES, HARDASS, respiratory acidosis, metabolic alkalosis - colour-coded boxes |
| Oxygenation | A-a gradient formula, PaO₂ classification, hypoxaemia causes |
| Respiratory Alkalosis | Full cause list |
| Clinical Pearls | 6 high-yield pearls on mixed disorders, hidden AG, VBG vs ABG, compensation timing |