Imp topics notes for practical of biochemistry

Asking for Preferences
Reading File
Finding Sources
Searching the Web

MBBS 1st year biochemistry practical important topics urine analysis blood analysis enzymes carbohydrates

Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Reading File
Finding Sources
I now have enough information from the library and my own training knowledge to compile a thorough set of notes. Let me write a comprehensive answer:

MBBS 1st Year Biochemistry Practical - Important Topics Notes


SECTION 1: QUALITATIVE TESTS FOR CARBOHYDRATES

A. General Tests (detect all sugars)

1. Molisch's Test (General test for carbohydrates)
  • Reagent: Alpha-naphthol in alcohol + conc. H₂SO₄
  • Principle: H₂SO₄ hydrolyzes carbohydrates to monosaccharides, dehydrates them to furfural/hydroxymethylfurfural, which reacts with alpha-naphthol to give a purple ring
  • Result: Purple/violet ring at junction of two liquids = POSITIVE
  • Note: All carbohydrates give positive; proteins give false positive (triose formation)
2. Fehling's Test (Reducing sugars)
  • Reagents: Fehling's A (copper sulfate) + Fehling's B (sodium potassium tartrate + NaOH)
  • Principle: Reducing sugars reduce Cu²⁺ (blue) to Cu⁺ (brick-red cuprous oxide precipitate)
  • Result: Brick-red precipitate = POSITIVE (reducing sugar present)
  • Positive: Glucose, fructose, maltose, lactose
  • Negative: Sucrose (non-reducing), starch
3. Benedict's Test (More sensitive than Fehling's)
  • Reagent: Sodium citrate + sodium carbonate + copper sulfate
  • Colors (semi-quantitative): Green < Yellow < Orange < Red/Brick-red (increasing sugar)
  • Advantage: Stable single reagent; more sensitive

B. Specific Tests

4. Barfoed's Test (Distinguishes monosaccharides from disaccharides)
  • Reagent: Copper acetate in acetic acid
  • Result: Monosaccharides give red precipitate within 2-3 min; disaccharides take longer (>10 min)
5. Seliwanoff's Test (For ketoses/fructose)
  • Reagent: Resorcinol + conc. HCl
  • Principle: Ketoses are dehydrated faster than aldoses; fructose → cherry-red color
  • Result: Cherry red = ketose (fructose); delayed/faint = aldose (glucose)
6. Osazone Test (Crystal morphology)
  • Reagent: Phenylhydrazine + sodium acetate + glacial acetic acid (+ heat)
  • Crystals:
    • Glucose → needle-shaped crystals
    • Fructose → same as glucose (both form glucosazone)
    • Maltose → sunflower/hedgehog crystals
    • Lactose → powder puff/cotton ball crystals
7. Iodine Test (For starch/polysaccharides)
  • Reagent: Iodine in potassium iodide solution
  • Result: Starch = blue-black; glycogen = reddish-brown; cellulose = no change

SECTION 2: QUALITATIVE TESTS FOR PROTEINS

A. Colour Reactions

1. Biuret Test (General test for proteins)
  • Reagent: NaOH + dilute CuSO₄
  • Principle: Copper ions form a complex with peptide bonds (2+ peptide bonds needed)
  • Result: Violet/purple = POSITIVE
  • Negative: Amino acids and dipeptides (no biuret reaction)
2. Ninhydrin Test (For amino acids and proteins)
  • Reagent: 0.1% ninhydrin
  • Result: Purple/blue-violet color = POSITIVE (alpha-amino acids)
  • Exception: Proline and hydroxyproline give yellow color (secondary amines)
3. Xanthoproteic Test (Aromatic amino acids)
  • Reagent: Conc. HNO₃ (+ heat, then NaOH)
  • Principle: Nitration of aromatic rings (Phe, Tyr, Trp)
  • Result: Yellow color (xanthoproteic acid), turns orange on adding NaOH = POSITIVE
4. Millon's Test (Tyrosine-containing proteins)
  • Reagent: Millon's reagent (mercuric nitrate + nitrous acid)
  • Result: Brick-red/rose color = POSITIVE (tyrosine present)
  • Note: Gives false positive with urea
5. Hopkins-Cole Test (Tryptophan)
  • Reagent: Glyoxylic acid + conc. H₂SO₄
  • Result: Violet ring at interface = POSITIVE
6. Lead Sulphide Test/Nitroprusside Test (Sulfur-containing amino acids - Cys, Met)
  • Reagent: NaOH + lead acetate (heat)
  • Result: Black precipitate of lead sulphide = POSITIVE (cysteine, cystine, methionine)

B. Precipitation Reactions

ReagentMechanismNotes
TCA (trichloroacetic acid)Irreversible denaturationForms white precipitate
Heat coagulationDenaturation at isoelectric pHAdd few drops acetic acid
Tannic acidHeavy metal precipitationTannin-protein complex
Picric acidHeavy metal/acid precipitantYellow precipitate
Salting out (ammonium sulphate)Reversible; removes hydration shellGlobulins (half saturation), albumin (full saturation)

SECTION 3: URINE ANALYSIS

A. Physical Examination

ParameterNormal ValueAbnormal
Volume1000-1500 mL/dayOliguria <400 mL, polyuria >3000 mL
ColorPale yellow to amberDark = bilirubinuria; red = hematuria
AppearanceClearTurbid = infection, crystals
Specific gravity1.010-1.025Fixed SG (isosthenuria) in renal failure
pH4.6-8.0 (avg 6.0)Acid = high protein diet; alkaline = UTI
OdorSlightly aromaticFruity = ketones (DM); ammoniacal = stale/UTI

B. Chemical Tests for Abnormal Constituents

1. Glucose in Urine (Glycosuria)
  • Benedict's Test - Brick-red precipitate (positive when blood glucose >180 mg/dL = renal threshold)
  • Dipstick - Glucose oxidase method (specific for glucose)
  • Causes: Diabetes mellitus, renal glycosuria, gestational diabetes
2. Protein in Urine (Proteinuria)
  • Heat & Acetic Acid Test: Heat urine → white cloudiness; add acetic acid → persists = protein
  • Sulphosalicylic Acid Test: White precipitate with SSA = protein present
  • Dipstick: Detects albumin (least 30 mg/dL)
  • Normal: <150 mg/day; Nephrotic syndrome: >3.5 g/day
3. Ketone Bodies (Ketonuria)
  • Rothera's Test: Sodium nitroprusside + NH₃ → purple/magenta ring = acetoacetate/acetone
  • Gerhardt's Test: FeCl₃ → wine red = acetoacetate (not acetone)
  • Causes: Uncontrolled DM, starvation, prolonged vomiting
4. Bilirubin (Bilirubinuria)
  • Fouchet's Test: BaCl₂ precipitates bilirubin → add Fouchet's reagent → green/blue color
  • Gmelin's Test: Conc. HNO₃ → color spectrum (yellow → red → violet → blue) = positive
  • Causes: Obstructive jaundice, hepatocellular jaundice (conjugated bilirubin only)
5. Urobilinogen
  • Ehrlich's Test: DMAB reagent (p-dimethylaminobenzaldehyde) → pink/red color
  • Normal: Traces (0.2-1.0 EU/day); Increased: hepatocellular damage, hemolysis
6. Blood/Hemoglobin
  • Benzidine Test (or Guaiac): Blue color = hemoglobin present
  • Dipstick: Peroxidase activity of hemoglobin
7. Bile Salts
  • Hay's Sulphur Test: Sulfur powder sprinkled on urine - sinks with bile salts (reduce surface tension)
  • Foam Test: Yellow/green foam on shaking
8. Creatinine (Jaffe's Reaction)
  • Creatinine + picric acid in alkaline medium → orange-red complex
  • Normal urine creatinine: 1-2 g/day

SECTION 4: BLOOD ANALYSIS / QUANTITATIVE ESTIMATIONS

A. Blood Glucose Estimation

Folin-Wu Method (Classical)
  • Principle: Glucose reduces alkaline cupric ions to cuprous ions → with phosphomolybdic acid → blue color (molybdenum blue); measured at 420 nm
  • Normal fasting blood glucose: 70-110 mg/dL
  • Diabetes diagnosis: FBS >126 mg/dL (two readings) or random >200 mg/dL
Glucose Oxidase-Peroxidase (GOD-POD) Method (Modern)
  • Glucose → gluconic acid + H₂O₂ (by glucose oxidase)
  • H₂O₂ + chromogen → colored complex (by peroxidase)
  • More specific, no interference from other sugars

B. Blood Urea Estimation

Berthelot Method / Diacetyl Monoxime Method
  • Principle: Urea + diacetyl monoxime → yellow color in acidic conditions (Fearon reaction)
  • Normal: 20-40 mg/dL (blood urea); BUN = 10-20 mg/dL
  • Clinical significance: Elevated in renal failure (azotemia), dehydration, high protein diet
Urease Method
  • Urea → NH₃ + CO₂ (by urease) → NH₃ measured with Nessler's reagent (yellow-brown) or Berthelot's reagent

C. Serum Creatinine

  • Jaffe's Reaction: Creatinine + alkaline picrate → orange-red (measured at 520 nm)
  • Normal: 0.6-1.2 mg/dL (males); 0.5-1.0 mg/dL (females)
  • Elevated in renal failure, rhabdomyolysis

D. Total Protein Estimation

Biuret Method
  • Protein + biuret reagent → violet complex (measured at 540 nm)
  • Normal serum total protein: 6.0-8.0 g/dL
  • Albumin: 3.5-5.0 g/dL; Globulins: 2.0-3.5 g/dL; A/G ratio: 1.2-2.0

E. Serum Bilirubin (van den Bergh Reaction)

  • Direct (conjugated): Bilirubin + diazo reagent → purple/pink azo pigment (without methanol)
  • Indirect (unconjugated): Requires methanol (accelerator) to react
  • Normal: Total <1.0 mg/dL; Direct <0.3 mg/dL; Indirect <0.8 mg/dL
  • Jaundice visible when total >2.5-3.0 mg/dL

F. Serum Alkaline Phosphatase (ALP)

  • Principle: ALP hydrolyzes p-nitrophenyl phosphate → p-nitrophenol (yellow color at 405 nm) + phosphate
  • Normal: 30-120 IU/L
  • Elevated in: Obstructive jaundice, bone disease (Paget's), liver disease

SECTION 5: ENZYMES - PRACTICAL ASPECTS

Key Enzyme Assay Concepts

Factors affecting enzyme activity:
  1. Temperature - Optimum ~37°C for most human enzymes; denaturation above 60°C
  2. pH - Pepsin works best at pH 2; trypsin at pH 8; ALP at pH 9-10
  3. Substrate concentration - Michaelis-Menten kinetics (Km, Vmax)
  4. Enzyme concentration - Activity proportional to enzyme concentration
  5. Inhibitors - Competitive, non-competitive, uncompetitive
Km and Vmax (Lineweaver-Burk Plot)
  • Plot 1/[S] vs 1/V
  • X-intercept = -1/Km; Y-intercept = 1/Vmax
  • Competitive inhibitor: increases Km (same Vmax); non-competitive: decreases Vmax (same Km)

Clinically Important Enzyme Estimations in Practicals

EnzymePrincipleNormalClinical Significance
ALPp-nitrophenyl phosphate hydrolysis30-120 IU/LObstructive jaundice, bone disease
SGPT/ALTTransamination, NADH oxidation7-56 IU/LViral hepatitis, liver necrosis
SGOT/ASTTransamination10-40 IU/LMI, liver disease
AmylaseStarch hydrolysis (iodometric/saccharogenic)25-125 IU/LAcute pancreatitis
LDHPyruvate/lactate interconversion100-190 IU/LMI, hemolysis

SECTION 6: LIPIDS

A. Qualitative Tests for Lipids

1. Solubility Test
  • Fats dissolve in organic solvents (chloroform, ether, benzene); insoluble in water
2. Sudan III Test
  • Lipids + Sudan III stain → orange-red color (fat droplets stain red)
3. Saponification / Acrolein Test
  • Fat + KHSO₄ (heat) → acrolein (pungent smell) = glycerol present = fat confirmed
4. Spot Test
  • Fat leaves a translucent (greasy) spot on paper = positive for fat

B. Quantitative Lipid Estimations

Serum Cholesterol (Liebermann-Burchard Reaction)
  • Cholesterol + acetic anhydride + conc. H₂SO₄ → green/emerald color → measured at 620 nm
  • Normal Total Cholesterol: <200 mg/dL (desirable); 200-239 borderline; >240 high
  • LDL <100 mg/dL (optimal); HDL >40 mg/dL (males), >50 (females)
Serum Triglycerides
  • GPO-PAP method: Triglycerides → glycerol + fatty acids (lipase) → glycerol-3-phosphate → H₂O₂ → colorimetric measurement
  • Normal: <150 mg/dL

SECTION 7: LABORATORY SAFETY & QUALITY CONTROL

Safety Rules

  • Always wear gloves and lab coat when handling reagents
  • Concentrated acids (H₂SO₄, HNO₃): add acid to water, never water to acid
  • Dispose biohazardous samples (blood, urine) in designated containers
  • Reagents labeled and stored appropriately

Quality Control Concepts

  • Accuracy vs Precision: Accuracy = closeness to true value; Precision = reproducibility
  • Normal range: Mean ± 2 SD covers 95% of normal population
  • Levey-Jennings chart: Plot daily QC values; Westgard rules detect errors
  • Internal QC vs External QC (EQAS)

QUICK REFERENCE: REAGENTS AND EXPECTED RESULTS

TestReagentPositive Result
Molischα-Naphthol + conc. H₂SO₄Purple ring
BenedictCu²⁺ in citrate-carbonateBrick-red ppt
SeliwanoffResorcinol + HClCherry-red
BiuretNaOH + CuSO₄Violet
XanthoproteicConc. HNO₃Yellow → orange (with NaOH)
Ninhydrin0.1% ninhydrinPurple/blue
RotheraNitroprusside + NH₃Purple ring
FouchetBaCl₂ + Fouchet's reagentGreen/blue
Hay's SulphurSulfur powder on urineSulfur sinks
Liebermann-BurchardAcetic anhydride + H₂SO₄Emerald green

EXAM TIPS FOR VIVA / SPOTTERS

  1. Always state the principle before describing the procedure
  2. Know the positive control for each test (e.g., glucose solution for Benedict's)
  3. For urine tests - state whether it is a test for conjugated or unconjugated bilirubin
  4. Know the renal threshold for glucose = 180 mg/dL
  5. For Osazone test - know crystal shapes (needle = glucose/fructose; sunflower = maltose; powder-puff = lactose)
  6. Biuret test needs minimum 2 peptide bonds - dipeptides do NOT give this reaction
  7. Ninhydrin gives yellow with proline/hydroxyproline, not purple
  8. Rothera's test is more sensitive than Gerhardt's for ketones (Rothera detects acetone + acetoacetate; Gerhardt only detects acetoacetate)
  9. The van den Bergh direct reaction tests conjugated bilirubin (water-soluble); indirect = unconjugated
  10. For enzyme assays - always mention measurement at specific wavelength and mention the substrate used

Sources: Harper's Illustrated Biochemistry 32nd Ed, Basic Medical Biochemistry (Lieberman & Peet) 6th Ed, Henry's Clinical Diagnosis and Management by Laboratory Methods, Tietz Textbook of Laboratory Medicine

Create a comprehensive study guide PDF with all practical procedures and flowcharts

Reading File
Running Command

ls -la /home/daytona/workspace/ && mkdir -p /home/daytona/workspace/biochem-practical-guide

Writing File

~/biochem-practical-guide/generate_pdf.py

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, PageBreak, KeepTogether
)
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import Flowable
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Arrow, Polygon
from reportlab.graphics import renderPDF
import os

OUTPUT_PATH = "/home/daytona/workspace/biochem-practical-guide/Biochemistry_Practical_Guide.pdf"

# ── Colours ──────────────────────────────────────────────────────────────────
DARK_BLUE   = colors.HexColor("#1A3560")
MED_BLUE    = colors.HexColor("#2B5DA8")
LIGHT_BLUE  = colors.HexColor("#D6E4F7")
TEAL        = colors.HexColor("#0D7A75")
LIGHT_TEAL  = colors.HexColor("#D0F0EE")
ORANGE      = colors.HexColor("#E06C2A")
LIGHT_ORANGE= colors.HexColor("#FDE8D8")
GOLD        = colors.HexColor("#C49A22")
LIGHT_GOLD  = colors.HexColor("#FFF3CC")
RED         = colors.HexColor("#C0392B")
LIGHT_RED   = colors.HexColor("#FADBD8")
GREEN       = colors.HexColor("#1E7E45")
LIGHT_GREEN = colors.HexColor("#D5F5E3")
PURPLE      = colors.HexColor("#6C3483")
LIGHT_PURPLE= colors.HexColor("#E8DAEF")
GREY_LIGHT  = colors.HexColor("#F4F6F8")
GREY_MED    = colors.HexColor("#BDC3C7")

PAGE_W, PAGE_H = A4

# ── Styles ────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()

def make_style(name, parent="Normal", **kw):
    s = ParagraphStyle(name, parent=styles[parent], **kw)
    return s

ST = {
    "cover_title": make_style("CoverTitle", fontSize=28, textColor=colors.white,
                               fontName="Helvetica-Bold", alignment=TA_CENTER, spaceAfter=6),
    "cover_sub":   make_style("CoverSub",   fontSize=14, textColor=LIGHT_BLUE,
                               fontName="Helvetica", alignment=TA_CENTER, spaceAfter=4),
    "cover_tag":   make_style("CoverTag",   fontSize=11, textColor=LIGHT_GOLD,
                               fontName="Helvetica-Oblique", alignment=TA_CENTER),
    "ch_title":    make_style("ChTitle",    fontSize=16, textColor=colors.white,
                               fontName="Helvetica-Bold", alignment=TA_CENTER,
                               spaceAfter=2, spaceBefore=4),
    "section":     make_style("Section",    fontSize=13, textColor=DARK_BLUE,
                               fontName="Helvetica-Bold", spaceBefore=12, spaceAfter=4,
                               borderPad=4),
    "subsection":  make_style("Subsection", fontSize=11, textColor=MED_BLUE,
                               fontName="Helvetica-Bold", spaceBefore=8, spaceAfter=3),
    "body":        make_style("Body",       fontSize=9.5, textColor=colors.black,
                               fontName="Helvetica", leading=14, spaceAfter=4,
                               alignment=TA_JUSTIFY),
    "bullet":      make_style("Bullet",     fontSize=9.5, textColor=colors.black,
                               fontName="Helvetica", leading=13, spaceAfter=2,
                               leftIndent=14, firstLineIndent=-8),
    "note":        make_style("Note",       fontSize=9, textColor=DARK_BLUE,
                               fontName="Helvetica-Oblique", leading=12,
                               backColor=LIGHT_GOLD, borderPad=6, spaceAfter=4),
    "table_hdr":   make_style("TblHdr",     fontSize=9, textColor=colors.white,
                               fontName="Helvetica-Bold", alignment=TA_CENTER),
    "table_cell":  make_style("TblCell",    fontSize=8.5, textColor=colors.black,
                               fontName="Helvetica", leading=12),
    "table_cell_c":make_style("TblCellC",   fontSize=8.5, textColor=colors.black,
                               fontName="Helvetica", leading=12, alignment=TA_CENTER),
    "footer":      make_style("Footer",     fontSize=8, textColor=GREY_MED,
                               fontName="Helvetica", alignment=TA_CENTER),
    "pos":         make_style("Pos",        fontSize=9, textColor=GREEN,
                               fontName="Helvetica-Bold"),
    "neg":         make_style("Neg",        fontSize=9, textColor=RED,
                               fontName="Helvetica-Bold"),
    "key_box":     make_style("KeyBox",     fontSize=9, textColor=PURPLE,
                               fontName="Helvetica-Bold", leading=13,
                               backColor=LIGHT_PURPLE, borderPad=5),
}

# ── Helper Flowables ──────────────────────────────────────────────────────────
def hline(color=GREY_MED, thickness=0.5):
    return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=4, spaceBefore=4)

def sp(h=6):
    return Spacer(1, h)

def P(text, style="body"):
    return Paragraph(text, ST[style])

def B(text):
    """Bullet paragraph."""
    return Paragraph(f"• {text}", ST["bullet"])

def chapter_banner(title, color=DARK_BLUE, sub=""):
    """Returns a coloured banner for chapter headings."""
    data = [[Paragraph(title, ST["ch_title"])]]
    if sub:
        data.append([Paragraph(sub, make_style("ChSub", fontSize=10,
                    textColor=LIGHT_BLUE, fontName="Helvetica", alignment=TA_CENTER))])
    t = Table(data, colWidths=[PAGE_W - 4*cm])
    t.setStyle(TableStyle([
        ("BACKGROUND",  (0,0), (-1,-1), color),
        ("ROWBACKGROUNDS", (0,0), (-1,-1), [color]),
        ("TOPPADDING",  (0,0), (-1,-1), 10),
        ("BOTTOMPADDING",(0,0),(-1,-1), 10),
        ("LEFTPADDING", (0,0), (-1,-1), 12),
        ("RIGHTPADDING",(0,0), (-1,-1), 12),
        ("ROUNDEDCORNERS", [6]),
    ]))
    return t

def section_box(title, color=MED_BLUE):
    t = Table([[Paragraph(title, ST["section"])]], colWidths=[PAGE_W - 4*cm])
    t.setStyle(TableStyle([
        ("BACKGROUND",  (0,0), (-1,-1), LIGHT_BLUE),
        ("LEFTPADDING", (0,0), (-1,-1), 8),
        ("TOPPADDING",  (0,0), (-1,-1), 4),
        ("BOTTOMPADDING",(0,0),(-1,-1),4),
        ("LINEBELOW",   (0,0), (-1,0), 2, color),
    ]))
    return t

def info_box(text, bg=LIGHT_GOLD, border=GOLD):
    t = Table([[Paragraph(f"<b>📌 Note:</b> {text}", ST["body"])]], colWidths=[PAGE_W - 4*cm])
    t.setStyle(TableStyle([
        ("BACKGROUND",  (0,0),(-1,-1), bg),
        ("LINEBEFORE",  (0,0),(0,-1), 3, border),
        ("LEFTPADDING", (0,0),(-1,-1),10),
        ("TOPPADDING",  (0,0),(-1,-1),6),
        ("BOTTOMPADDING",(0,0),(-1,-1),6),
    ]))
    return t

def make_table(headers, rows, col_widths=None, hdr_color=DARK_BLUE, alt=True):
    data = [[Paragraph(h, ST["table_hdr"]) for h in headers]]
    for row in rows:
        data.append([Paragraph(str(c), ST["table_cell"]) for c in row])
    if col_widths is None:
        n = len(headers)
        col_widths = [(PAGE_W - 4*cm) / n] * n
    t = Table(data, colWidths=col_widths, repeatRows=1)
    style = [
        ("BACKGROUND",  (0,0), (-1,0), hdr_color),
        ("TEXTCOLOR",   (0,0), (-1,0), colors.white),
        ("FONTNAME",    (0,0), (-1,0), "Helvetica-Bold"),
        ("FONTSIZE",    (0,0), (-1,0), 9),
        ("ROWBACKGROUNDS", (0,1), (-1,-1), [colors.white, GREY_LIGHT]),
        ("GRID",        (0,0), (-1,-1), 0.4, GREY_MED),
        ("VALIGN",      (0,0), (-1,-1), "MIDDLE"),
        ("TOPPADDING",  (0,0), (-1,-1), 5),
        ("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING", (0,0), (-1,-1), 5),
        ("RIGHTPADDING",(0,0), (-1,-1), 5),
    ]
    t.setStyle(TableStyle(style))
    return t

# ── Flowchart Drawing Helper ──────────────────────────────────────────────────
class FlowchartDrawing(Flowable):
    """A simple vertical flowchart rendered as a ReportLab Drawing."""
    def __init__(self, steps, width=460, title="", colors_list=None):
        Flowable.__init__(self)
        self.steps = steps
        self.fc_width = width
        self.title = title
        self.colors_list = colors_list
        BOX_H = 32
        ARROW_H = 18
        self.width = width
        n = len(steps)
        self.height = (BOX_H + ARROW_H) * n + (30 if title else 0)

    def draw(self):
        BOX_H = 32
        ARROW_H = 18
        BOX_W = self.fc_width - 20
        x0 = 10
        y_start = self.height - (30 if self.title else 0)
        from reportlab.graphics.shapes import Drawing as D, Rect, String, Line, Polygon
        from reportlab.lib import colors as C

        canvas = self.canv
        if self.title:
            canvas.setFont("Helvetica-Bold", 11)
            canvas.setFillColor(DARK_BLUE)
            canvas.drawCentredString(self.fc_width / 2, self.height - 18, self.title)

        default_colors = [MED_BLUE, TEAL, GREEN, ORANGE, PURPLE, DARK_BLUE, TEAL, MED_BLUE]

        for i, step in enumerate(self.steps):
            y = y_start - (BOX_H + ARROW_H) * i - BOX_H
            bcolor = (self.colors_list[i] if self.colors_list and i < len(self.colors_list)
                      else default_colors[i % len(default_colors)])
            # Box
            canvas.setFillColor(bcolor)
            canvas.setStrokeColor(bcolor)
            canvas.roundRect(x0, y, BOX_W, BOX_H, radius=5, fill=1, stroke=0)
            # Text
            canvas.setFillColor(colors.white)
            canvas.setFont("Helvetica-Bold", 8.5)
            # split label / detail
            if "|" in step:
                label, detail = step.split("|", 1)
                canvas.drawCentredString(x0 + BOX_W/2, y + BOX_H/2 + 3, label.strip())
                canvas.setFont("Helvetica", 7.5)
                canvas.drawCentredString(x0 + BOX_W/2, y + BOX_H/2 - 9, detail.strip())
            else:
                canvas.drawCentredString(x0 + BOX_W/2, y + BOX_H/2 - 4, step)
            # Arrow (not after last)
            if i < len(self.steps) - 1:
                ax = x0 + BOX_W / 2
                ay_top = y
                ay_bot = y - ARROW_H
                canvas.setStrokeColor(GREY_MED)
                canvas.setFillColor(GREY_MED)
                canvas.setLineWidth(1.5)
                canvas.line(ax, ay_top, ax, ay_bot + 5)
                # Arrow head
                canvas.setFillColor(ORANGE)
                canvas.setStrokeColor(ORANGE)
                canvas.polygon([ax-5, ay_bot+5, ax+5, ay_bot+5, ax, ay_bot], fill=1, stroke=0)

    def wrap(self, availWidth, availHeight):
        return self.width, self.height


# ── Page Template ─────────────────────────────────────────────────────────────
def on_page(canvas, doc):
    canvas.saveState()
    # Top bar
    canvas.setFillColor(DARK_BLUE)
    canvas.rect(0, PAGE_H - 1.2*cm, PAGE_W, 1.2*cm, fill=1, stroke=0)
    canvas.setFillColor(colors.white)
    canvas.setFont("Helvetica-Bold", 9)
    canvas.drawString(1.5*cm, PAGE_H - 0.8*cm, "BIOCHEMISTRY PRACTICAL GUIDE — MBBS 1st Year")
    canvas.setFont("Helvetica", 9)
    canvas.drawRightString(PAGE_W - 1.5*cm, PAGE_H - 0.8*cm, f"Page {doc.page}")
    # Bottom bar
    canvas.setFillColor(DARK_BLUE)
    canvas.rect(0, 0, PAGE_W, 0.8*cm, fill=1, stroke=0)
    canvas.setFillColor(LIGHT_BLUE)
    canvas.setFont("Helvetica", 7.5)
    canvas.drawCentredString(PAGE_W/2, 0.22*cm,
        "For educational use only  |  Based on Harper's Biochemistry, Lippincott & Henry's Lab Medicine")
    canvas.restoreState()


# ── Cover Page ────────────────────────────────────────────────────────────────
def cover_page():
    elems = []
    # Big background gradient rectangle simulated with stacked rects — just one colour block
    class CoverBg(Flowable):
        def wrap(self, w, h): return w, 14*cm
        def draw(self):
            c = self.canv
            c.setFillColor(DARK_BLUE)
            c.rect(-2*cm, -1*cm, PAGE_W + 4*cm, 15*cm, fill=1, stroke=0)
            # Decorative circles
            c.setFillColor(MED_BLUE)
            c.circle(PAGE_W - 1*cm, 12*cm, 4*cm, fill=1, stroke=0)
            c.setFillColor(colors.HexColor("#1E4A8A"))
            c.circle(1*cm, -1*cm, 3*cm, fill=1, stroke=0)
            c.setFillColor(TEAL)
            c.circle(PAGE_W/2 + 5*cm, 8*cm, 1.5*cm, fill=1, stroke=0)
    elems.append(CoverBg())
    elems.append(sp(28))
    elems.append(P("BIOCHEMISTRY", "cover_title"))
    elems.append(P("Practical Study Guide", "cover_sub"))
    elems.append(sp(8))
    line_t = Table([[""]], colWidths=[8*cm])
    line_t.setStyle(TableStyle([("LINEABOVE",(0,0),(0,0),2,GOLD),("TOPPADDING",(0,0),(-1,-1),0)]))
    elems.append(line_t)
    elems.append(sp(8))
    elems.append(P("MBBS 1st Year | Complete Practical Procedures &amp; Flowcharts", "cover_tag"))
    elems.append(sp(16))

    badges = [
        ["Carbohydrates", "Proteins", "Urine Analysis"],
        ["Blood Biochemistry", "Enzymes", "Lipids"],
    ]
    badge_data = []
    for row in badges:
        badge_row = []
        for b in row:
            cell = Table([[Paragraph(b, make_style("Badge", fontSize=9, textColor=DARK_BLUE,
                fontName="Helvetica-Bold", alignment=TA_CENTER))]],
                colWidths=[4.5*cm])
            cell.setStyle(TableStyle([
                ("BACKGROUND",(0,0),(-1,-1), LIGHT_GOLD),
                ("TOPPADDING",(0,0),(-1,-1),5),
                ("BOTTOMPADDING",(0,0),(-1,-1),5),
                ("ROUNDEDCORNERS",[4]),
            ]))
            badge_row.append(cell)
        badge_data.append(badge_row)
    badge_tbl = Table(badge_data, colWidths=[4.8*cm]*3, hAlign="CENTER")
    badge_tbl.setStyle(TableStyle([
        ("ALIGN",(0,0),(-1,-1),"CENTER"),
        ("VALIGN",(0,0),(-1,-1),"MIDDLE"),
        ("TOPPADDING",(0,0),(-1,-1),3),
        ("BOTTOMPADDING",(0,0),(-1,-1),3),
    ]))
    elems.append(badge_tbl)
    elems.append(sp(20))
    elems.append(P("Compiled from: Harper's Illustrated Biochemistry 32e | Lippincott Biochemistry 8e | "
                   "Henry's Clinical Lab Methods | Basic Medical Biochemistry 6e", "cover_tag"))
    elems.append(PageBreak())
    return elems


# ── TABLE OF CONTENTS ─────────────────────────────────────────────────────────
def toc_page():
    elems = []
    elems.append(sp(6))
    elems.append(chapter_banner("TABLE OF CONTENTS", DARK_BLUE))
    elems.append(sp(12))
    toc_items = [
        ("1", "Qualitative Tests for Carbohydrates", "3"),
        ("2", "Qualitative Tests for Proteins & Amino Acids", "7"),
        ("3", "Urine Analysis — Physical & Chemical", "12"),
        ("4", "Blood Biochemistry — Quantitative Estimations", "17"),
        ("5", "Enzyme Assays & Kinetics", "22"),
        ("6", "Lipid Tests & Estimations", "25"),
        ("7", "Quick Reference Tables & Viva Points", "28"),
    ]
    for num, title, pg in toc_items:
        row_data = [[
            Paragraph(f"<b>{num}.</b>", make_style("N", fontSize=11, textColor=DARK_BLUE,
                      fontName="Helvetica-Bold")),
            Paragraph(title, make_style("T", fontSize=11, textColor=colors.black, fontName="Helvetica")),
            Paragraph(pg, make_style("P", fontSize=10, textColor=MED_BLUE, fontName="Helvetica-Bold",
                      alignment=TA_CENTER)),
        ]]
        t = Table(row_data, colWidths=[1.2*cm, 12.5*cm, 1.3*cm])
        t.setStyle(TableStyle([
            ("VALIGN",(0,0),(-1,-1),"MIDDLE"),
            ("LINEBELOW",(0,0),(-1,0), 0.3, GREY_MED),
            ("TOPPADDING",(0,0),(-1,-1),7),
            ("BOTTOMPADDING",(0,0),(-1,-1),7),
        ]))
        elems.append(t)
    elems.append(PageBreak())
    return elems


# ── CHAPTER 1: CARBOHYDRATES ──────────────────────────────────────────────────
def chapter_carbohydrates():
    elems = []
    elems.append(chapter_banner("CHAPTER 1: QUALITATIVE TESTS FOR CARBOHYDRATES", DARK_BLUE,
                                 sub="Molisch • Benedict • Fehling • Seliwanoff • Barfoed • Osazone • Iodine"))
    elems.append(sp(10))

    # Overview flowchart
    elems.append(section_box("1.1  Overview — Classification of Carbohydrate Tests"))
    elems.append(sp(6))
    overview_data = [
        [Paragraph("<b>TYPE OF TEST</b>", ST["table_hdr"]),
         Paragraph("<b>TEST NAME</b>", ST["table_hdr"]),
         Paragraph("<b>DETECTS</b>", ST["table_hdr"]),
         Paragraph("<b>POSITIVE RESULT</b>", ST["table_hdr"])],
        [P("General (all CHO)", "table_cell"), P("Molisch's", "table_cell"),
         P("All carbohydrates", "table_cell"), P("Purple ring", "table_cell")],
        [P("Reducing sugar", "table_cell"), P("Benedict's", "table_cell"),
         P("Reducing sugars", "table_cell"), P("Brick-red precipitate", "table_cell")],
        [P("Reducing sugar", "table_cell"), P("Fehling's", "table_cell"),
         P("Reducing sugars", "table_cell"), P("Brick-red precipitate", "table_cell")],
        [P("Ketose specific", "table_cell"), P("Seliwanoff's", "table_cell"),
         P("Ketoses (fructose)", "table_cell"), P("Cherry-red color", "table_cell")],
        [P("Mono vs Di", "table_cell"), P("Barfoed's", "table_cell"),
         P("Monosaccharides", "table_cell"), P("Red ppt within 2-3 min", "table_cell")],
        [P("Crystal formation", "table_cell"), P("Osazone", "table_cell"),
         P("Reducing sugars", "table_cell"), P("Characteristic crystals", "table_cell")],
        [P("Polysaccharide", "table_cell"), P("Iodine", "table_cell"),
         P("Starch/glycogen", "table_cell"), P("Blue-black / Brown", "table_cell")],
    ]
    ov_tbl = Table(overview_data, colWidths=[3.5*cm, 3.2*cm, 4.2*cm, 4.1*cm], repeatRows=1)
    ov_tbl.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0), DARK_BLUE),
        ("TEXTCOLOR",(0,0),(-1,0), colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1), [colors.white, LIGHT_BLUE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("VALIGN",(0,0),(-1,-1),"MIDDLE"),
        ("TOPPADDING",(0,0),(-1,-1),5),
        ("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),
        ("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(ov_tbl)
    elems.append(sp(10))

    # Molisch
    elems.append(section_box("1.2  Molisch's Test — General Test for Carbohydrates"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Add 2 mL test solution to a test tube",
        "Add 2 drops of Molisch's reagent (α-naphthol in alcohol) | Mix well",
        "TILT the tube and add 1 mL conc. H₂SO₄ gently down the side | Do NOT mix",
        "OBSERVE the interface between two layers | Wait 2-3 minutes",
        "Purple/Violet ring at junction = POSITIVE ✓ | No ring = NEGATIVE ✗",
    ], title="MOLISCH'S TEST PROCEDURE", colors_list=[MED_BLUE, MED_BLUE, TEAL, ORANGE, GREEN]))
    elems.append(sp(4))
    elems.append(P("<b>Principle:</b> H₂SO₄ hydrolyzes polysaccharides to monosaccharides, then dehydrates "
                   "them to furfural (pentoses) or 5-hydroxymethylfurfural (hexoses). These condense with "
                   "α-naphthol to form a purple-coloured complex.", "body"))
    elems.append(info_box("Proteins give a false positive Molisch's test due to triose formation. "
                          "Sucrose gives a positive result (hydrolyzed to glucose + fructose)."))
    elems.append(sp(8))

    # Benedict's
    elems.append(section_box("1.3  Benedict's Test — Reducing Sugars"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Add 5 mL Benedict's reagent to a test tube",
        "Add 8 drops (0.5 mL) of test solution",
        "Mix and place in BOILING water bath for 5 minutes",
        "Remove and allow to cool — observe colour change",
        "Green → Yellow → Orange → Brick-Red = Increasing sugar concentration",
    ], title="BENEDICT'S TEST PROCEDURE", colors_list=[TEAL,TEAL,TEAL,ORANGE,GREEN]))
    elems.append(sp(4))
    elems.append(P("<b>Principle:</b> Reducing sugars reduce Cu²⁺ (blue cupric ions) to Cu⁺ (red cuprous "
                   "oxide precipitate) in alkaline medium. The degree of color change is "
                   "semi-quantitative.", "body"))
    semi_data = [
        [Paragraph("<b>Colour</b>", ST["table_hdr"]),
         Paragraph("<b>Approximate Glucose</b>", ST["table_hdr"]),
         Paragraph("<b>Interpretation</b>", ST["table_hdr"])],
        [P("Blue (no change)", "table_cell"), P("NIL", "table_cell"), P("Negative", "table_cell")],
        [P("Green", "table_cell"), P("<0.5%", "table_cell"), P("Trace / 1+", "table_cell")],
        [P("Yellow", "table_cell"), P("0.5–1%", "table_cell"), P("Moderate / 2+", "table_cell")],
        [P("Orange", "table_cell"), P("1–2%", "table_cell"), P("Significant / 3+", "table_cell")],
        [P("Brick-Red", "table_cell"), P(">2%", "table_cell"), P("Heavy / 4+", "table_cell")],
    ]
    st = Table(semi_data, colWidths=[4*cm, 5*cm, 5*cm])
    st.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),MED_BLUE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_BLUE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(st)
    elems.append(sp(8))

    # Seliwanoff's
    elems.append(section_box("1.4  Seliwanoff's Test — Ketoses"))
    elems.append(sp(4))
    elems.append(FlowchartDrawing([
        "Add 3 mL Seliwanoff's reagent (resorcinol + conc. HCl) to test tube",
        "Add 3 drops of test solution",
        "Heat in boiling water bath for 2 minutes — NOTE TIME",
        "Cherry-red within 2 min = KETOSE (fructose) POSITIVE",
        "Faint/delayed red (>5 min) = ALDOSE (glucose) — very slow reaction",
    ], title="SELIWANOFF'S TEST PROCEDURE", colors_list=[PURPLE,PURPLE,ORANGE,GREEN,TEAL]))
    elems.append(sp(4))
    elems.append(P("<b>Principle:</b> Ketoses are more readily dehydrated by HCl than aldoses. "
                   "Fructose → 5-hydroxymethylfurfural reacts with resorcinol to give a cherry-red colour. "
                   "Aldoses give a faint pink after prolonged heating.", "body"))
    elems.append(sp(8))

    # Osazone
    elems.append(section_box("1.5  Osazone Test — Crystal Identification"))
    elems.append(sp(4))
    elems.append(FlowchartDrawing([
        "Add 5 mL test solution + phenylhydrazine HCl + Na acetate + glacial acetic acid",
        "Heat in water bath at 70-80°C for 30 minutes",
        "Cool slowly and examine crystals under microscope",
        "Identify crystal shape to identify the sugar",
    ], title="OSAZONE TEST PROCEDURE", colors_list=[DARK_BLUE,DARK_BLUE,ORANGE,GREEN]))
    elems.append(sp(4))
    crystal_data = [
        [Paragraph("<b>Sugar</b>", ST["table_hdr"]),
         Paragraph("<b>Crystal Shape</b>", ST["table_hdr"]),
         Paragraph("<b>Melting Point</b>", ST["table_hdr"]),
         Paragraph("<b>Time to Form</b>", ST["table_hdr"])],
        [P("Glucose", "table_cell"), P("Needle-shaped (broom-like bundles)", "table_cell"),
         P("204°C", "table_cell"), P("5 min", "table_cell")],
        [P("Fructose", "table_cell"), P("Same as glucose (glucosazone)", "table_cell"),
         P("204°C", "table_cell"), P("2 min", "table_cell")],
        [P("Maltose", "table_cell"), P("Sunflower / hedgehog crystals", "table_cell"),
         P("206°C", "table_cell"), P("30-45 min", "table_cell")],
        [P("Lactose", "table_cell"), P("Powder puff / cotton ball", "table_cell"),
         P("200°C", "table_cell"), P("30 min", "table_cell")],
        [P("Sucrose", "table_cell"), P("No crystals (non-reducing)", "table_cell"),
         P("—", "table_cell"), P("Never", "table_cell")],
    ]
    ct = Table(crystal_data, colWidths=[3*cm, 6.5*cm, 2.5*cm, 3*cm])
    ct.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),TEAL),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_TEAL]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(ct)
    elems.append(info_box("Glucose and fructose give the SAME osazone crystal (glucosazone) because both "
                          "form the same C1-C2 derivative. This is how Osazone distinguishes them from "
                          "disaccharides."))
    elems.append(PageBreak())
    return elems


# ── CHAPTER 2: PROTEINS ───────────────────────────────────────────────────────
def chapter_proteins():
    elems = []
    elems.append(chapter_banner("CHAPTER 2: QUALITATIVE TESTS FOR PROTEINS & AMINO ACIDS", TEAL,
                                 sub="Biuret • Ninhydrin • Xanthoproteic • Millon's • Hopkins-Cole • Precipitation"))
    elems.append(sp(10))

    # Overview table
    elems.append(section_box("2.1  Overview — Protein & Amino Acid Tests"))
    elems.append(sp(6))
    prot_data = [
        [P("<b>Test</b>", "table_hdr"), P("<b>Reagent</b>", "table_hdr"),
         P("<b>Detects</b>", "table_hdr"), P("<b>Positive Result</b>", "table_hdr")],
        [P("Biuret", "table_cell"), P("NaOH + CuSO₄", "table_cell"),
         P("Peptide bonds (≥2)", "table_cell"), P("Violet/purple", "table_cell")],
        [P("Ninhydrin", "table_cell"), P("0.1% ninhydrin", "table_cell"),
         P("α-amino acids", "table_cell"), P("Purple-blue (yellow for Pro)", "table_cell")],
        [P("Xanthoproteic", "table_cell"), P("Conc. HNO₃", "table_cell"),
         P("Aromatic AA (Phe, Tyr, Trp)", "table_cell"), P("Yellow → orange with NaOH", "table_cell")],
        [P("Millon's", "table_cell"), P("Mercuric nitrate", "table_cell"),
         P("Tyrosine", "table_cell"), P("Brick-red/rose", "table_cell")],
        [P("Hopkins-Cole", "table_cell"), P("Glyoxylic acid + H₂SO₄", "table_cell"),
         P("Tryptophan", "table_cell"), P("Violet ring at interface", "table_cell")],
        [P("Lead sulphide", "table_cell"), P("NaOH + lead acetate", "table_cell"),
         P("Cys, Cystine, Met", "table_cell"), P("Black precipitate", "table_cell")],
        [P("Sakaguchi", "table_cell"), P("α-Naphthol + NaOBr", "table_cell"),
         P("Arginine (guanidinium)", "table_cell"), P("Red colour", "table_cell")],
    ]
    pt = Table(prot_data, colWidths=[3.2*cm, 4*cm, 4.5*cm, 3.3*cm], repeatRows=1)
    pt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),TEAL),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_TEAL]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(pt)
    elems.append(sp(10))

    # Biuret
    elems.append(section_box("2.2  Biuret Test — General Test for Proteins"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Add 1 mL test solution to test tube",
        "Add 1 mL 10% NaOH — mix well",
        "Add 2-3 drops of 1% CuSO₄ (drop by drop) — mix after each drop",
        "Observe colour — DO NOT add excess CuSO₄",
        "Violet/Purple = POSITIVE (≥2 peptide bonds) | Blue = only 1 bond | Pink = dipeptides",
    ], title="BIURET TEST PROCEDURE", colors_list=[MED_BLUE,MED_BLUE,MED_BLUE,ORANGE,GREEN]))
    elems.append(sp(4))
    elems.append(P("<b>Principle:</b> In alkaline medium, Cu²⁺ ions form a coordinate complex with "
                   "two or more adjacent peptide bonds (-CO-NH-), producing a violet/purple colour. "
                   "Single amino acids and dipeptides do NOT give this reaction.", "body"))
    elems.append(info_box("Add CuSO₄ SPARINGLY. Excess CuSO₄ gives a blue colour that masks "
                          "the violet of a positive biuret reaction."))
    elems.append(sp(8))

    # Ninhydrin
    elems.append(section_box("2.3  Ninhydrin Test — α-Amino Acids"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Add 1 mL test solution to test tube",
        "Add 1 mL 0.1% ninhydrin reagent — mix",
        "Heat in boiling water bath for 2-5 minutes",
        "Purple-Blue (Ruhemann's purple) = α-Amino acid POSITIVE",
        "EXCEPTION: Proline/Hydroxyproline → Yellow-Orange (secondary amines)",
    ], title="NINHYDRIN TEST PROCEDURE", colors_list=[PURPLE,PURPLE,ORANGE,GREEN,GOLD]))
    elems.append(sp(4))
    elems.append(P("<b>Principle:</b> Ninhydrin reacts with α-amino groups (primary amines) to give "
                   "Ruhemann's purple (diketohydrindylidene-diketohydrindamine). Proline and hydroxyproline "
                   "give yellow (imino acids — secondary amine). Ammonia gives blue.", "body"))
    elems.append(sp(8))

    # Xanthoproteic
    elems.append(section_box("2.4  Xanthoproteic Test — Aromatic Amino Acids"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Add 1 mL protein solution to test tube",
        "Add 0.5 mL CONCENTRATED HNO₃ (carefully!) — heat gently",
        "White precipitate forms → dissolves on heating → Yellow colour",
        "Cool, then add 40% NaOH drop by drop",
        "Yellow → Deep Orange = POSITIVE (Phe, Tyr, Trp present)",
    ], title="XANTHOPROTEIC TEST PROCEDURE", colors_list=[RED,RED,ORANGE,ORANGE,GREEN]))
    elems.append(sp(4))
    elems.append(P("<b>Principle:</b> Concentrated HNO₃ nitrates aromatic rings of Phenylalanine, "
                   "Tyrosine, and Tryptophan to form yellow xanthoproteic acid. Adding NaOH converts it to "
                   "the orange sodium salt.", "body"))
    elems.append(sp(8))

    # Precipitation
    elems.append(section_box("2.5  Protein Precipitation Reactions"))
    elems.append(sp(6))
    prec_data = [
        [P("<b>Method</b>","table_hdr"), P("<b>Agent</b>","table_hdr"),
         P("<b>Mechanism</b>","table_hdr"), P("<b>Reversible?</b>","table_hdr")],
        [P("Heat coagulation","table_cell"), P("Heat (at pI)","table_cell"),
         P("Protein denaturation at isoelectric pH","table_cell"), P("No","table_cell")],
        [P("Acid precipitation","table_cell"), P("TCA, Picric acid","table_cell"),
         P("Charge neutralization at pI","table_cell"), P("No","table_cell")],
        [P("Salting out","table_cell"), P("(NH₄)₂SO₄","table_cell"),
         P("Removes hydration shell; globulins (50%), albumin (100%)","table_cell"), P("Yes","table_cell")],
        [P("Heavy metals","table_cell"), P("CuSO₄, lead acetate","table_cell"),
         P("Protein-metal complex","table_cell"), P("No","table_cell")],
        [P("Alkaloids","table_cell"), P("Tannic acid, picric acid","table_cell"),
         P("Ionic interactions","table_cell"), P("No","table_cell")],
    ]
    prect = Table(prec_data, colWidths=[3.5*cm, 3.5*cm, 5.5*cm, 2.5*cm], repeatRows=1)
    prect.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),TEAL),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_TEAL]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(prect)
    elems.append(PageBreak())
    return elems


# ── CHAPTER 3: URINE ANALYSIS ────────────────────────────────────────────────
def chapter_urine():
    elems = []
    elems.append(chapter_banner("CHAPTER 3: URINE ANALYSIS", ORANGE,
                                 sub="Physical Examination • Chemical Tests • Interpretation"))
    elems.append(sp(10))

    elems.append(section_box("3.1  Physical Examination of Urine"))
    elems.append(sp(4))
    phys_data = [
        [P("<b>Parameter</b>","table_hdr"), P("<b>Normal Value</b>","table_hdr"),
         P("<b>Abnormal Finding</b>","table_hdr"), P("<b>Clinical Significance</b>","table_hdr")],
        [P("Volume","table_cell"), P("1000–1500 mL/day","table_cell"),
         P("Oliguria <400 mL | Polyuria >3000 mL","table_cell"),
         P("Dehydration | Diabetes insipidus","table_cell")],
        [P("Colour","table_cell"), P("Pale yellow to amber","table_cell"),
         P("Dark yellow-brown | Red","table_cell"),
         P("Bilirubinuria | Haematuria","table_cell")],
        [P("Appearance","table_cell"), P("Clear","table_cell"),
         P("Turbid/Cloudy","table_cell"),
         P("Infection, phosphaturia, chyluria","table_cell")],
        [P("Specific Gravity","table_cell"), P("1.010–1.025","table_cell"),
         P("Fixed 1.010 (isosthenuria)","table_cell"),
         P("Renal failure","table_cell")],
        [P("pH","table_cell"), P("4.6–8.0 (avg 6.0)","table_cell"),
         P("Persistent alkaline (>8)","table_cell"),
         P("UTI (urease-producing organisms)","table_cell")],
        [P("Odour","table_cell"), P("Slightly aromatic","table_cell"),
         P("Fruity/sweet | Ammoniacal","table_cell"),
         P("Ketosis, DM | Stale urine, UTI","table_cell")],
    ]
    phyt = Table(phys_data, colWidths=[2.8*cm, 3.2*cm, 4.5*cm, 4.5*cm], repeatRows=1)
    phyt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),ORANGE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_ORANGE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),4),("RIGHTPADDING",(0,0),(-1,-1),4),
    ]))
    elems.append(phyt)
    elems.append(sp(10))

    # Glucose in urine
    elems.append(section_box("3.2  Test for Glucose (Glycosuria)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Collect fresh urine sample (midstream)",
        "BENEDICT'S TEST: 5 mL Benedict's + 8 drops urine → Boil 5 min",
        "Brick-red precipitate = GLYCOSURIA POSITIVE",
        "CONFIRM with glucose oxidase dipstick (GOD-POD method)",
        "Positive if Blood glucose >180 mg/dL (renal threshold)",
    ], title="URINE GLUCOSE DETECTION", colors_list=[MED_BLUE,MED_BLUE,GREEN,TEAL,ORANGE]))
    elems.append(info_box("Renal threshold for glucose = 180 mg/dL. Below this level, glucose is "
                          "fully reabsorbed by SGLT2 transporters. Glycosuria occurs in DM, "
                          "pregnancy, and renal glycosuria (normal blood glucose)."))
    elems.append(sp(8))

    # Protein in urine
    elems.append(section_box("3.3  Test for Protein (Proteinuria)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Take 5 mL urine in a test tube",
        "Acidify with 2-3 drops dilute acetic acid",
        "HEAT the upper portion of tube over flame",
        "White cloudiness/turbidity = POSITIVE for protein",
        "Add more acetic acid: if cloudiness persists = PROTEIN, if dissolves = phosphates",
    ], title="HEAT & ACETIC ACID TEST FOR PROTEIN", colors_list=[MED_BLUE,MED_BLUE,ORANGE,GREEN,TEAL]))
    elems.append(sp(4))
    elems.append(P("<b>Sulphosalicylic Acid (SSA) Test:</b> Add 3 mL 3% SSA to 3 mL urine. "
                   "White precipitate = protein positive. More sensitive than heat test. "
                   "Detects albumin, globulins, and Bence Jones proteins.", "body"))
    prot_urine_data = [
        [P("<b>Degree</b>","table_hdr"), P("<b>Protein</b>","table_hdr"), P("<b>Clinical Significance</b>","table_hdr")],
        [P("Trace","table_cell"), P("<150 mg/day","table_cell"), P("Normal physiological limit","table_cell")],
        [P("Mild 1+","table_cell"), P("150–500 mg/day","table_cell"), P("UTI, mild nephritis","table_cell")],
        [P("Moderate 2+","table_cell"), P("500–3500 mg/day","table_cell"), P("Significant renal disease","table_cell")],
        [P("Heavy 3+/4+","table_cell"), P(">3.5 g/day","table_cell"), P("Nephrotic syndrome","table_cell")],
    ]
    put = Table(prot_urine_data, colWidths=[3*cm, 4*cm, 8*cm], repeatRows=1)
    put.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),ORANGE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_ORANGE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(put)
    elems.append(sp(8))

    # Ketones
    elems.append(section_box("3.4  Test for Ketone Bodies (Ketonuria)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Take 5 mL fresh urine",
        "ROTHERA'S TEST: Add solid ammonium sulphate (saturate) + 1-2 drops sodium nitroprusside",
        "Mix, then carefully layer strong ammonia solution on top",
        "PURPLE/MAGENTA ring at interface = POSITIVE",
        "Gerhardt's (FeCl₃) also done: Wine-red = acetoacetate positive",
    ], title="ROTHERA'S TEST FOR KETONES", colors_list=[PURPLE,PURPLE,PURPLE,GREEN,TEAL]))
    elems.append(sp(4))
    ketone_data = [
        [P("<b>Test</b>","table_hdr"), P("<b>Detects</b>","table_hdr"), P("<b>Positive Result</b>","table_hdr"), P("<b>Sensitivity</b>","table_hdr")],
        [P("Rothera's","table_cell"), P("Acetoacetate + Acetone","table_cell"), P("Purple ring","table_cell"), P("High","table_cell")],
        [P("Gerhardt's (FeCl₃)","table_cell"), P("Acetoacetate ONLY","table_cell"), P("Wine-red colour","table_cell"), P("Moderate","table_cell")],
        [P("Dipstick","table_cell"), P("Acetoacetate","table_cell"), P("Colour change","table_cell"), P("High","table_cell")],
    ]
    kt = Table(ketone_data, colWidths=[3.5*cm, 4.5*cm, 3.5*cm, 3.5*cm], repeatRows=1)
    kt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),PURPLE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_PURPLE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(kt)
    elems.append(sp(8))

    # Bilirubin + bile salts
    elems.append(section_box("3.5  Tests for Bilirubin, Urobilinogen & Bile Salts"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "FOUCHET'S TEST (Bilirubin): Add BaCl₂ to urine → filter precipitate",
        "Add Fouchet's reagent (FeCl₃ + trichloroacetic acid) to precipitate on filter paper",
        "Green/Blue colour = BILIRUBIN POSITIVE",
        "GMELIN'S TEST: Layer conc. HNO₃ under urine — colour play (yellow→violet) = positive",
        "HAY'S SULPHUR TEST (Bile salts): Sprinkle sulphur powder on urine surface",
        "Sulphur SINKS = bile salts present (reduced surface tension)",
    ], title="BILIRUBIN & BILE SALT TESTS", colors_list=[GOLD,GOLD,GREEN,TEAL,ORANGE,GREEN]))
    elems.append(sp(4))
    bili_data = [
        [P("<b>Type</b>","table_hdr"), P("<b>Found In</b>","table_hdr"), P("<b>Test Detects</b>","table_hdr")],
        [P("Conjugated bilirubin","table_cell"), P("Obstructive & hepatocellular jaundice","table_cell"),
         P("Fouchet, Gmelin, dipstick (water-soluble)","table_cell")],
        [P("Unconjugated bilirubin","table_cell"), P("Haemolytic jaundice","table_cell"),
         P("NOT detected in urine (protein-bound, not filtered)","table_cell")],
        [P("Urobilinogen","table_cell"), P("Hepatocellular & haemolytic (increased)","table_cell"),
         P("Ehrlich's reagent (DMAB) → pink/red","table_cell")],
    ]
    bt = Table(bili_data, colWidths=[4*cm, 5.5*cm, 5.5*cm], repeatRows=1)
    bt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),GOLD),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_GOLD]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(bt)
    elems.append(PageBreak())
    return elems


# ── CHAPTER 4: BLOOD BIOCHEMISTRY ─────────────────────────────────────────────
def chapter_blood():
    elems = []
    elems.append(chapter_banner("CHAPTER 4: BLOOD BIOCHEMISTRY — QUANTITATIVE ESTIMATIONS", GREEN,
                                 sub="Glucose • Urea • Creatinine • Total Protein • Bilirubin • ALP"))
    elems.append(sp(10))

    elems.append(section_box("4.1  Blood Glucose Estimation"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Collect 2 mL fasting venous blood (sodium fluoride anticoagulant)",
        "Centrifuge → obtain plasma/serum",
        "GOD-POD METHOD: Glucose + O₂ → Gluconic acid + H₂O₂ (glucose oxidase)",
        "H₂O₂ + 4-aminoantipyrine + phenol → Pink complex (peroxidase)",
        "Measure absorbance at 505 nm | Compare with standard",
        "Calculate: Glucose (mg/dL) = (OD sample / OD standard) × standard concentration",
    ], title="GOD-POD METHOD FOR BLOOD GLUCOSE", colors_list=[GREEN,GREEN,TEAL,TEAL,ORANGE,ORANGE]))
    elems.append(sp(4))
    gluc_data = [
        [P("<b>Status</b>","table_hdr"), P("<b>Fasting (mg/dL)</b>","table_hdr"),
         P("<b>2-hr PP (mg/dL)</b>","table_hdr"), P("<b>Interpretation</b>","table_hdr")],
        [P("Normal","table_cell"), P("70–99","table_cell"), P("<140","table_cell"), P("Normal","table_cell")],
        [P("Impaired fasting","table_cell"), P("100–125","table_cell"), P("140–199","table_cell"), P("Pre-diabetes","table_cell")],
        [P("Diabetes","table_cell"), P("≥126","table_cell"), P("≥200","table_cell"), P("Diabetes mellitus","table_cell")],
    ]
    gt = Table(gluc_data, colWidths=[3.5*cm, 4*cm, 4*cm, 3.5*cm], repeatRows=1)
    gt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),GREEN),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_GREEN]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(gt)
    elems.append(sp(8))

    elems.append(section_box("4.2  Blood Urea Estimation (Berthelot Method)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Serum/plasma sample collected",
        "UREASE METHOD: Urea + H₂O → 2NH₃ + CO₂ (urease enzyme)",
        "NH₃ + Berthelot's reagent (hypochlorite + phenol) → Blue indophenol",
        "Measure absorbance at 630 nm",
        "Normal: Blood Urea 20–40 mg/dL | BUN 10–20 mg/dL",
    ], title="BLOOD UREA ESTIMATION", colors_list=[GREEN,TEAL,TEAL,ORANGE,GREEN]))
    elems.append(sp(4))
    elems.append(P("<b>Alternative (Diacetyl Monoxime):</b> Urea + diacetyl monoxime → yellow colour "
                   "in acidic conditions with heat. Measure at 520 nm.", "body"))
    elems.append(info_box("Urea is elevated in: Pre-renal azotemia (dehydration, heart failure), "
                          "Renal failure, Post-renal obstruction. BUN:Creatinine ratio >20:1 "
                          "suggests pre-renal cause."))
    elems.append(sp(8))

    elems.append(section_box("4.3  Serum Creatinine (Jaffe's Reaction)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Serum sample collected (no haemolysis — falsely lowers result)",
        "Add alkaline picrate reagent to sample",
        "Creatinine + Picric acid + NaOH → Orange-red creatinine picrate complex",
        "Measure absorbance at 520 nm | Compare to creatinine standard",
        "Male: 0.6–1.2 mg/dL | Female: 0.5–1.0 mg/dL",
    ], title="JAFFE'S REACTION — SERUM CREATININE", colors_list=[GREEN,GREEN,TEAL,ORANGE,MED_BLUE]))
    elems.append(sp(8))

    elems.append(section_box("4.4  Serum Bilirubin (van den Bergh Reaction)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Collect serum — protect from light (bilirubin is photosensitive)",
        "DIRECT (conjugated): Bilirubin + diazo reagent → purple azo pigment (no methanol)",
        "INDIRECT (unconjugated): Add methanol (accelerator) first, then diazo reagent",
        "Total bilirubin = Direct + Indirect reading at 540 nm",
        "Normal: Total <1.0 | Direct <0.3 | Indirect <0.8 mg/dL",
    ], title="VAN DEN BERGH — SERUM BILIRUBIN", colors_list=[GOLD,GOLD,GOLD,ORANGE,GREEN]))
    elems.append(sp(4))
    jaundice_data = [
        [P("<b>Type</b>","table_hdr"), P("<b>Direct</b>","table_hdr"),
         P("<b>Indirect</b>","table_hdr"), P("<b>Urine Bilirubin</b>","table_hdr"), P("<b>Urine Urobilinogen</b>","table_hdr")],
        [P("Haemolytic","table_cell"), P("Normal","table_cell"), P("↑↑","table_cell"),
         P("Absent","table_cell"), P("↑↑↑","table_cell")],
        [P("Hepatocellular","table_cell"), P("↑","table_cell"), P("↑","table_cell"),
         P("Present","table_cell"), P("↑ or Normal","table_cell")],
        [P("Obstructive","table_cell"), P("↑↑","table_cell"), P("Normal","table_cell"),
         P("Present (dark)","table_cell"), P("Absent","table_cell")],
    ]
    jt = Table(jaundice_data, colWidths=[3.2*cm, 2.3*cm, 2.5*cm, 3.2*cm, 3.8*cm], repeatRows=1)
    jt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),GOLD),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_GOLD]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(jt)
    elems.append(sp(8))

    elems.append(section_box("4.5  Total Protein Estimation (Biuret Method)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Collect 1 mL serum",
        "Add 4 mL Biuret reagent (CuSO₄ + NaKTartrate + NaOH + KI)",
        "Mix and incubate at room temperature for 30 minutes",
        "Measure absorbance at 540 nm against protein standard (e.g., bovine serum albumin)",
        "Normal: Total protein 6.0–8.0 g/dL | Albumin 3.5–5.0 | Globulin 2.0–3.5 g/dL",
    ], title="TOTAL PROTEIN — BIURET METHOD", colors_list=[TEAL,TEAL,TEAL,ORANGE,GREEN]))
    elems.append(info_box("A/G ratio = Albumin/Globulin = 1.2–2.0 normally. Ratio is reversed in "
                          "cirrhosis (↓ albumin), multiple myeloma (↑ globulins), nephrotic syndrome (↓ albumin)."))
    elems.append(PageBreak())
    return elems


# ── CHAPTER 5: ENZYMES ────────────────────────────────────────────────────────
def chapter_enzymes():
    elems = []
    elems.append(chapter_banner("CHAPTER 5: ENZYME ASSAYS & KINETICS", PURPLE,
                                 sub="ALP • ALT/AST • Amylase • LDH • Michaelis-Menten • Inhibition"))
    elems.append(sp(10))

    elems.append(section_box("5.1  Factors Affecting Enzyme Activity"))
    elems.append(sp(6))
    factor_data = [
        [P("<b>Factor</b>","table_hdr"), P("<b>Optimal Value</b>","table_hdr"),
         P("<b>Effect of Change</b>","table_hdr")],
        [P("Temperature","table_cell"), P("37°C (body temperature)","table_cell"),
         P("↑ temp → ↑ activity until denaturation occurs above ~60°C","table_cell")],
        [P("pH","table_cell"), P("Enzyme-specific (pepsin=2, ALP=9–10, trypsin=8)","table_cell"),
         P("Activity ↓ on either side of optimum pH","table_cell")],
        [P("Substrate [S]","table_cell"), P("Saturating conc. → Vmax","table_cell"),
         P("Activity ↑ with [S] until saturation (Michaelis-Menten)","table_cell")],
        [P("Enzyme [E]","table_cell"), P("Proportional","table_cell"),
         P("Activity directly proportional to enzyme concentration","table_cell")],
        [P("Competitive inhibitor","table_cell"), P("Increases apparent Km","table_cell"),
         P("↑ Km, Vmax unchanged; overcome with excess substrate","table_cell")],
        [P("Non-competitive inhibitor","table_cell"), P("Decreases Vmax","table_cell"),
         P("↓ Vmax, Km unchanged; NOT overcome with excess substrate","table_cell")],
    ]
    ft = Table(factor_data, colWidths=[4*cm, 4.5*cm, 6.5*cm], repeatRows=1)
    ft.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),PURPLE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_PURPLE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(ft)
    elems.append(sp(8))

    elems.append(section_box("5.2  Alkaline Phosphatase (ALP) Assay"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Serum sample collected",
        "Substrate: p-nitrophenyl phosphate (pNPP) in alkaline buffer (pH 10.0)",
        "ALP hydrolyses pNPP → p-nitrophenol (yellow) + phosphate",
        "Reaction stopped with NaOH at fixed time interval",
        "Measure absorbance at 405 nm (yellow p-nitrophenol)",
        "Normal: 30–120 IU/L (adults) | ↑ in obstructive jaundice, bone disease",
    ], title="ALP ASSAY PROCEDURE", colors_list=[PURPLE,PURPLE,TEAL,TEAL,ORANGE,GREEN]))
    elems.append(sp(8))

    elems.append(section_box("5.3  Lineweaver-Burk Plot (Enzyme Kinetics)"))
    elems.append(sp(4))
    elems.append(P("The Lineweaver-Burk (double-reciprocal) plot linearizes Michaelis-Menten "
                   "kinetics to determine Km and Vmax graphically.", "body"))
    lbdata = [
        [P("<b>Axis</b>","table_hdr"), P("<b>Representation</b>","table_hdr"), P("<b>Intercept Value</b>","table_hdr")],
        [P("X-axis","table_cell"), P("1/[S]","table_cell"), P("X-intercept = −1/Km","table_cell")],
        [P("Y-axis","table_cell"), P("1/V","table_cell"), P("Y-intercept = 1/Vmax","table_cell")],
        [P("Slope","table_cell"), P("Km/Vmax","table_cell"), P("Slope = Km/Vmax","table_cell")],
    ]
    lbt = Table(lbdata, colWidths=[3*cm, 5*cm, 7*cm], repeatRows=1)
    lbt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),PURPLE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_PURPLE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(lbt)
    elems.append(sp(8))

    elems.append(section_box("5.4  Clinically Important Enzyme Tests"))
    elems.append(sp(6))
    enz_data = [
        [P("<b>Enzyme</b>","table_hdr"), P("<b>Normal</b>","table_hdr"), P("<b>Substrate</b>","table_hdr"), P("<b>↑ In</b>","table_hdr")],
        [P("ALP","table_cell"), P("30–120 IU/L","table_cell"), P("p-nitrophenyl phosphate","table_cell"), P("Obstructive jaundice, bone disease","table_cell")],
        [P("ALT (SGPT)","table_cell"), P("7–56 IU/L","table_cell"), P("Alanine + α-KG","table_cell"), P("Viral hepatitis (most specific)","table_cell")],
        [P("AST (SGOT)","table_cell"), P("10–40 IU/L","table_cell"), P("Aspartate + α-KG","table_cell"), P("MI, hepatitis, muscle disease","table_cell")],
        [P("Amylase","table_cell"), P("25–125 IU/L","table_cell"), P("Starch","table_cell"), P("Acute pancreatitis (↑ 3× normal)","table_cell")],
        [P("Lipase","table_cell"), P("0–160 IU/L","table_cell"), P("Triglycerides","table_cell"), P("Acute pancreatitis (more specific)","table_cell")],
        [P("LDH","table_cell"), P("100–190 IU/L","table_cell"), P("Lactate/Pyruvate","table_cell"), P("MI, haemolysis, malignancy","table_cell")],
        [P("CK-MB","table_cell"), P("<25 IU/L","table_cell"), P("Creatine phosphate","table_cell"), P("MI (rises in 4–8 hrs)","table_cell")],
    ]
    et = Table(enz_data, colWidths=[2.8*cm, 2.8*cm, 4*cm, 5.4*cm], repeatRows=1)
    et.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),PURPLE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_PURPLE]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(et)
    elems.append(PageBreak())
    return elems


# ── CHAPTER 6: LIPIDS ─────────────────────────────────────────────────────────
def chapter_lipids():
    elems = []
    elems.append(chapter_banner("CHAPTER 6: LIPID TESTS & ESTIMATIONS", RED,
                                 sub="Sudan III • Acrolein • Cholesterol (Liebermann-Burchard) • TG • Lipid Profile"))
    elems.append(sp(10))

    elems.append(section_box("6.1  Qualitative Tests for Lipids"))
    elems.append(sp(6))
    ql_data = [
        [P("<b>Test</b>","table_hdr"), P("<b>Reagent</b>","table_hdr"), P("<b>Positive Result</b>","table_hdr"), P("<b>Detects</b>","table_hdr")],
        [P("Solubility","table_cell"), P("Ether, chloroform, benzene","table_cell"), P("Dissolves (soluble)","table_cell"), P("All lipids","table_cell")],
        [P("Sudan III","table_cell"), P("Sudan III dye","table_cell"), P("Orange-red droplets","table_cell"), P("Fat droplets","table_cell")],
        [P("Spot test","table_cell"), P("Brown paper","table_cell"), P("Translucent grease spot","table_cell"), P("Fats and oils","table_cell")],
        [P("Acrolein (saponification)","table_cell"), P("KHSO₄ + heat","table_cell"), P("Pungent smell (acrolein)","table_cell"), P("Glycerol (confirms triglycerides)","table_cell")],
        [P("Saponification","table_cell"), P("KOH + ethanol + heat","table_cell"), P("Soap (soapy feel/foam)","table_cell"), P("Ester linkage (fats)","table_cell")],
    ]
    qlt = Table(ql_data, colWidths=[3.2*cm, 3.5*cm, 4*cm, 4.3*cm], repeatRows=1)
    qlt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),RED),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_RED]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(qlt)
    elems.append(sp(10))

    elems.append(section_box("6.2  Serum Cholesterol (Liebermann-Burchard Reaction)"))
    elems.append(sp(6))
    elems.append(FlowchartDrawing([
        "Serum sample (fasting 12 hours preferred)",
        "Add acetic anhydride + conc. H₂SO₄ to sample (LB reagent)",
        "Cholesterol → Cholesta-3,5-diene (dehydration by H₂SO₄) → Green colour",
        "Colour develops: Pink → Blue → Blue-Green → Green (final colour)",
        "Measure absorbance at 620 nm",
        "Normal: <200 mg/dL (desirable) | 200–239 borderline | ≥240 high",
    ], title="LIEBERMANN-BURCHARD REACTION — SERUM CHOLESTEROL", colors_list=[RED,RED,TEAL,TEAL,ORANGE,GREEN]))
    elems.append(sp(8))

    elems.append(section_box("6.3  Lipid Profile — Reference Values"))
    elems.append(sp(6))
    lipid_data = [
        [P("<b>Lipid Parameter</b>","table_hdr"), P("<b>Desirable</b>","table_hdr"),
         P("<b>Borderline</b>","table_hdr"), P("<b>High Risk</b>","table_hdr")],
        [P("Total Cholesterol","table_cell"), P("<200 mg/dL","table_cell"),
         P("200–239 mg/dL","table_cell"), P("≥240 mg/dL","table_cell")],
        [P("LDL-Cholesterol","table_cell"), P("<100 mg/dL (optimal)","table_cell"),
         P("130–159 mg/dL","table_cell"), P("≥160 mg/dL","table_cell")],
        [P("HDL-Cholesterol","table_cell"), P(">60 mg/dL (protective)","table_cell"),
         P("40–59 mg/dL","table_cell"), P("<40 mg/dL (risk)","table_cell")],
        [P("Triglycerides","table_cell"), P("<150 mg/dL","table_cell"),
         P("150–199 mg/dL","table_cell"), P("≥200 mg/dL","table_cell")],
        [P("Non-HDL Cholesterol","table_cell"), P("<130 mg/dL","table_cell"),
         P("130–159 mg/dL","table_cell"), P("≥160 mg/dL","table_cell")],
    ]
    ldt = Table(lipid_data, colWidths=[4*cm, 3.5*cm, 3.5*cm, 4*cm], repeatRows=1)
    ldt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),RED),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,LIGHT_RED]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ]))
    elems.append(ldt)
    elems.append(PageBreak())
    return elems


# ── CHAPTER 7: QUICK REFERENCE & VIVA ────────────────────────────────────────
def chapter_quick_ref():
    elems = []
    elems.append(chapter_banner("CHAPTER 7: QUICK REFERENCE & VIVA POINTS", DARK_BLUE,
                                 sub="Master Table • High-Yield Viva Q&A • Lab Safety"))
    elems.append(sp(10))

    elems.append(section_box("7.1  Master Reagent-Result Table"))
    elems.append(sp(6))
    master_data = [
        [P("<b>Test</b>","table_hdr"), P("<b>Reagent(s)</b>","table_hdr"),
         P("<b>Positive Result</b>","table_hdr"), P("<b>Detects</b>","table_hdr")],
        [P("Molisch","table_cell"), P("α-Naphthol + conc. H₂SO₄","table_cell"), P("Purple ring","table_cell"), P("All carbohydrates","table_cell")],
        [P("Benedict","table_cell"), P("Cu²⁺ citrate-carbonate","table_cell"), P("Brick-red precipitate","table_cell"), P("Reducing sugars","table_cell")],
        [P("Fehling","table_cell"), P("Fehling A + Fehling B","table_cell"), P("Brick-red precipitate","table_cell"), P("Reducing sugars","table_cell")],
        [P("Seliwanoff","table_cell"), P("Resorcinol + HCl","table_cell"), P("Cherry-red <2 min","table_cell"), P("Ketoses (fructose)","table_cell")],
        [P("Barfoed","table_cell"), P("Cu acetate + acetic acid","table_cell"), P("Red ppt <3 min","table_cell"), P("Monosaccharides","table_cell")],
        [P("Iodine","table_cell"), P("Iodine-KI solution","table_cell"), P("Blue-black","table_cell"), P("Starch","table_cell")],
        [P("Biuret","table_cell"), P("NaOH + CuSO₄","table_cell"), P("Violet colour","table_cell"), P("Peptide bonds ≥2","table_cell")],
        [P("Ninhydrin","table_cell"), P("0.1% ninhydrin","table_cell"), P("Purple (yellow for Pro)","table_cell"), P("α-Amino acids","table_cell")],
        [P("Xanthoproteic","table_cell"), P("Conc. HNO₃ → NaOH","table_cell"), P("Yellow → Orange","table_cell"), P("Aromatic AA","table_cell")],
        [P("Millon's","table_cell"), P("Mercuric nitrate","table_cell"), P("Brick-red","table_cell"), P("Tyrosine","table_cell")],
        [P("Hopkins-Cole","table_cell"), P("Glyoxylic acid + H₂SO₄","table_cell"), P("Violet ring","table_cell"), P("Tryptophan","table_cell")],
        [P("Rothera's","table_cell"), P("Nitroprusside + NH₃","table_cell"), P("Purple ring","table_cell"), P("Ketones (urine)","table_cell")],
        [P("Fouchet's","table_cell"), P("BaCl₂ + Fouchet's","table_cell"), P("Green/Blue on paper","table_cell"), P("Bilirubin (urine)","table_cell")],
        [P("Hay's Sulphur","table_cell"), P("Sulphur powder","table_cell"), P("Sulphur sinks","table_cell"), P("Bile salts","table_cell")],
        [P("Liebermann-Burchard","table_cell"), P("Acetic anhydride + H₂SO₄","table_cell"), P("Emerald green","table_cell"), P("Cholesterol","table_cell")],
        [P("Jaffe's","table_cell"), P("Alkaline picrate","table_cell"), P("Orange-red","table_cell"), P("Creatinine","table_cell")],
        [P("van den Bergh","table_cell"), P("Diazo reagent","table_cell"), P("Purple/pink","table_cell"), P("Serum bilirubin","table_cell")],
    ]
    mt = Table(master_data, colWidths=[3*cm, 4.2*cm, 3.5*cm, 4.3*cm], repeatRows=1)
    mt.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,0),DARK_BLUE),("TEXTCOLOR",(0,0),(-1,0),colors.white),
        ("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,GREY_LIGHT]),
        ("GRID",(0,0),(-1,-1),0.4,GREY_MED),
        ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
        ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
        ("FONTSIZE",(0,0),(-1,-1),8.5),
    ]))
    elems.append(mt)
    elems.append(sp(10))

    elems.append(section_box("7.2  High-Yield Viva Questions & Answers"))
    elems.append(sp(6))
    viva = [
        ("Q: Why does sucrose not give Benedict's test positive?",
         "Sucrose is a non-reducing sugar — it has no free anomeric (C1) -OH group because glucose and "
         "fructose are joined at both anomeric carbons (α-1,β-2 glycosidic bond). No free aldehyde or "
         "ketone group means no reduction of Cu²⁺."),
        ("Q: Why does proline give yellow with ninhydrin instead of purple?",
         "Proline is an imino acid — it has a secondary amine (N is part of the ring), not a free primary "
         "α-amino group. The product formed is yellow (proline-ninhydrin complex) rather than Ruhemann's purple."),
        ("Q: Glucose and fructose give the same osazone. Explain.",
         "Phenylhydrazine reacts with C1 and C2 of both sugars. Both glucose (aldose) and fructose (ketose) "
         "have identical configurations at C3–C6. The C1–C2 derivative (glucosazone) is identical for both, "
         "so the same needle-shaped crystals form."),
        ("Q: What is the renal threshold for glucose?",
         "180 mg/dL. Below this blood glucose concentration, all filtered glucose is reabsorbed by SGLT2 "
         "(and SGLT1) transporters in the proximal tubule. Above 180 mg/dL, the transporters are saturated "
         "and glucose spills into urine (glycosuria)."),
        ("Q: Why is Rothera's test more sensitive than Gerhardt's for ketones?",
         "Rothera's test (nitroprusside method) detects both acetoacetate AND acetone in urine, with very "
         "high sensitivity. Gerhardt's test (FeCl₃) detects only acetoacetate, not acetone or "
         "β-hydroxybutyrate, and is less sensitive."),
        ("Q: Direct vs indirect bilirubin — which appears in urine in obstructive jaundice?",
         "Direct (conjugated) bilirubin appears in urine in obstructive jaundice. It is water-soluble "
         "(glucuronide conjugate) and passes freely through the glomerulus. Indirect (unconjugated) "
         "bilirubin is protein-bound, non-polar, and is NOT filtered — so it never appears in urine."),
        ("Q: What does a reversed A/G ratio indicate?",
         "Normally A/G ratio = 1.2–2.0 (albumin > globulin). Reversal (<1.0) indicates: "
         "decreased albumin (cirrhosis, nephrotic syndrome, malnutrition) or increased globulins "
         "(multiple myeloma, chronic infections, autoimmune disease)."),
        ("Q: Which enzyme is most specific for acute pancreatitis — amylase or lipase?",
         "Lipase is more specific for acute pancreatitis. Amylase rises faster (within 2–12 hours) "
         "but is also elevated in parotitis, bowel obstruction, and renal failure. Lipase is more "
         "specific to the pancreas and remains elevated longer (7–14 days vs 3–7 days for amylase)."),
    ]
    for q, a in viva:
        qrow = Table([[Paragraph(q, make_style("Q", fontSize=9.5, textColor=DARK_BLUE,
                                               fontName="Helvetica-Bold"))]],
                     colWidths=[PAGE_W - 4*cm])
        qrow.setStyle(TableStyle([
            ("BACKGROUND",(0,0),(-1,-1),LIGHT_BLUE),
            ("LINEBEFORE",(0,0),(0,-1),3,MED_BLUE),
            ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),3),
            ("LEFTPADDING",(0,0),(-1,-1),8),
        ]))
        arow = Table([[Paragraph(a, ST["body"])]], colWidths=[PAGE_W - 4*cm])
        arow.setStyle(TableStyle([
            ("BACKGROUND",(0,0),(-1,-1),colors.white),
            ("LINEBEFORE",(0,0),(0,-1),3,GREY_MED),
            ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
            ("LEFTPADDING",(0,0),(-1,-1),8),
        ]))
        elems.append(qrow)
        elems.append(arow)
        elems.append(sp(4))

    elems.append(sp(8))
    elems.append(section_box("7.3  Lab Safety Rules"))
    elems.append(sp(6))
    safety = [
        "Always wear lab coat, gloves, and safety glasses when handling reagents.",
        "When diluting concentrated acids: always add ACID to WATER (never water to acid).",
        "Concentrated H₂SO₄ and HNO₃ are corrosive — handle under fume hood.",
        "Sodium nitroprusside (used in Rothera's test) is toxic — avoid inhalation.",
        "Blood and urine samples are biohazardous — dispose in yellow biohazard containers.",
        "Mercury compounds (Millon's reagent) are highly toxic — avoid skin contact, dispose as chemical waste.",
        "Phenylhydrazine (osazone test) is toxic and carcinogenic — minimize exposure.",
        "Label all reagents clearly with name, concentration, date, and hazard symbols.",
        "Know the location of the fire extinguisher, eye wash station, and first aid kit.",
        "Never pipette by mouth — use mechanical pipette aids.",
    ]
    for s in safety:
        elems.append(B(s))
    elems.append(sp(20))

    # Final signature box
    sig = Table([[Paragraph(
        "<b>Best of Luck for your Biochemistry Practical Examination!</b><br/>"
        "Remember: State the PRINCIPLE before the PROCEDURE in your viva. Know your "
        "REAGENTS and EXPECTED RESULTS. Practice writing CHEMICAL REACTIONS where applicable.",
        make_style("Sig", fontSize=10, textColor=DARK_BLUE, fontName="Helvetica",
                   alignment=TA_CENTER, leading=16)
    )]], colWidths=[PAGE_W - 4*cm])
    sig.setStyle(TableStyle([
        ("BACKGROUND",(0,0),(-1,-1),LIGHT_BLUE),
        ("BOX",(0,0),(-1,-1),2,MED_BLUE),
        ("TOPPADDING",(0,0),(-1,-1),14),("BOTTOMPADDING",(0,0),(-1,-1),14),
        ("LEFTPADDING",(0,0),(-1,-1),16),("RIGHTPADDING",(0,0),(-1,-1),16),
    ]))
    elems.append(sig)
    return elems


# ── BUILD PDF ─────────────────────────────────────────────────────────────────
def build():
    doc = SimpleDocTemplate(
        OUTPUT_PATH,
        pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm,
        topMargin=1.6*cm, bottomMargin=1.4*cm,
        title="Biochemistry Practical Guide — MBBS 1st Year",
        author="Orris Medical Education",
        subject="MBBS Biochemistry Practicals",
    )
    story = []
    story += cover_page()
    story += toc_page()
    story += chapter_carbohydrates()
    story += chapter_proteins()
    story += chapter_urine()
    story += chapter_blood()
    story += chapter_enzymes()
    story += chapter_lipids()
    story += chapter_quick_ref()

    doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
    print(f"PDF generated: {OUTPUT_PATH}")

if __name__ == "__main__":
    build()
Running Command

cd /home/daytona/workspace/biochem-practical-guide && python generate_pdf.py 2>&1

Editing File

~/biochem-practical-guide/generate_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/biochem-practical-guide/generate_pdf.py'
Running Command

cd /home/daytona/workspace/biochem-practical-guide && python generate_pdf.py 2>&1

Editing File

~/biochem-practical-guide/generate_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/biochem-practical-guide/generate_pdf.py'
Running Command

cd /home/daytona/workspace/biochem-practical-guide && python generate_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/biochem-practical-guide/Biochemistry_Practical_Guide.pdf

Your comprehensive study guide PDF is ready!

Biochemistry Practical Guide

PDF Document · PDF


Here is what the PDF contains across 7 fully formatted chapters:
ChapterContents
1 - CarbohydratesMolisch, Benedict, Fehling, Seliwanoff, Barfoed, Osazone, Iodine - each with colour-coded step-by-step flowcharts
2 - Proteins & Amino AcidsBiuret, Ninhydrin, Xanthoproteic, Millon's, Hopkins-Cole, precipitation reactions - all with flowcharts
3 - Urine AnalysisPhysical examination table + flowcharts for glucose, protein, ketones, bilirubin, bile salts, urobilinogen
4 - Blood BiochemistryGOD-POD (glucose), urea (urease/Berthelot), creatinine (Jaffe's), bilirubin (van den Bergh), total protein - with diagnostic tables
5 - Enzyme AssaysALP assay, Michaelis-Menten kinetics, Lineweaver-Burk plot, complete enzyme reference table
6 - LipidsQualitative tests, Liebermann-Burchard reaction, full lipid profile reference values
7 - Quick ReferenceMaster reagent-result table, 8 high-yield viva Q&As with full answers, lab safety rules
Design features: Colour-coded chapters, orange-arrow flowcharts for every procedure, tables with alternating row colours, and exam tip boxes throughout. Compiled from Harper's Biochemistry 32e, Lippincott 8e, and Henry's Lab Medicine.
This is a shared conversation. Sign in to Orris to start your own chat.