Open vial poloicy
open vial policy vaccines WHO guidelines
https://ihatepsm.com/blog/open-vial-policy
All opened vials must have the date and time of opening recorded on them.
| Vaccine Type | Action at End of Session |
|---|---|
| DPT, TT, Hep B, Pentavalent (fulfilling OVP criteria) | Return to Cold Chain Point for reuse in next session (within 28 days) |
| BCG, Measles/MR, JE | Discard within 48 hours or before next session, whichever is earlier |
| Reconstituted vaccines (general) | Discard within 6 hours of opening/reconstitution or end of session, whichever is first |
Create a quick reference guide PDF on open vial policy
ls /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not found"
mkdir -p /home/daytona/workspace/open-vial-policy && python3 -c "from reportlab.…
~/open-vial-policy/generate_pdf.py
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate
OUTPUT = "/home/daytona/workspace/open-vial-policy/Open_Vial_Policy_Quick_Reference.pdf"
# ── Colors ──────────────────────────────────────────────────────────────────
TEAL = colors.HexColor("#006D77")
LIGHT_TEAL = colors.HexColor("#83C5BE")
PALE_TEAL = colors.HexColor("#EDF6F9")
RED = colors.HexColor("#C0392B")
PALE_RED = colors.HexColor("#FDEDEC")
GREEN = colors.HexColor("#1A7A4A")
PALE_GREEN = colors.HexColor("#E9F7EF")
ORANGE = colors.HexColor("#E67E22")
PALE_ORANGE = colors.HexColor("#FEF9E7")
DARK_GRAY = colors.HexColor("#2C3E50")
MID_GRAY = colors.HexColor("#7F8C8D")
WHITE = colors.white
YELLOW = colors.HexColor("#F9CA24")
# ── Styles ───────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()
title_style = ParagraphStyle("title_style",
fontName="Helvetica-Bold", fontSize=22, textColor=WHITE,
alignment=TA_CENTER, leading=28, spaceAfter=4)
subtitle_style = ParagraphStyle("subtitle_style",
fontName="Helvetica", fontSize=11, textColor=colors.HexColor("#FFEAA7"),
alignment=TA_CENTER, leading=16)
section_header = ParagraphStyle("section_header",
fontName="Helvetica-Bold", fontSize=12, textColor=WHITE,
alignment=TA_LEFT, leading=16, leftIndent=6)
body = ParagraphStyle("body",
fontName="Helvetica", fontSize=9.5, textColor=DARK_GRAY,
leading=14, spaceAfter=3)
body_bold = ParagraphStyle("body_bold",
fontName="Helvetica-Bold", fontSize=9.5, textColor=DARK_GRAY,
leading=14, spaceAfter=3)
bullet_style = ParagraphStyle("bullet_style",
fontName="Helvetica", fontSize=9.5, textColor=DARK_GRAY,
leading=14, leftIndent=10, spaceAfter=2, bulletIndent=0)
small_note = ParagraphStyle("small_note",
fontName="Helvetica-Oblique", fontSize=8, textColor=MID_GRAY,
alignment=TA_CENTER, leading=12)
condition_title = ParagraphStyle("condition_title",
fontName="Helvetica-Bold", fontSize=9.5, textColor=DARK_GRAY, leading=13)
footer_style = ParagraphStyle("footer_style",
fontName="Helvetica", fontSize=7.5, textColor=MID_GRAY,
alignment=TA_CENTER, leading=10)
# ── Helper: colored section box ──────────────────────────────────────────────
def section_box(title, content_rows, bg_color, header_color=None):
if header_color is None:
header_color = TEAL
data = [[Paragraph(title, section_header)]]
t_header = Table(data, colWidths=[17.2*cm])
t_header.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), header_color),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [4, 4, 0, 0]),
]))
body_data = [[row] for row in content_rows]
t_body = Table(body_data, colWidths=[17.2*cm])
t_body.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg_color),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("ROUNDEDCORNERS", [0, 0, 4, 4]),
("LINEBELOW", (0,0), (-1,-2), 0.3, colors.HexColor("#DDDDDD")),
]))
return [t_header, t_body, Spacer(1, 8)]
def bullet(text, color=TEAL):
return Paragraph(f'<font color="#{color.hexval()[2:]}">●</font> {text}', bullet_style)
def cross(text):
return Paragraph(f'<font color="#C0392B">✗</font> {text}', bullet_style)
def tick(text):
return Paragraph(f'<font color="#1A7A4A">✓</font> {text}', bullet_style)
# ── Document setup ────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
topMargin=1.5*cm,
bottomMargin=2*cm,
leftMargin=2*cm,
rightMargin=2*cm,
title="Open Vial Policy - Quick Reference Guide",
author="UIP / MoHFW India",
subject="Vaccine Open Vial Policy 2015"
)
story = []
# ═══════════════════════════════════════════════════════════════════════════════
# HEADER BANNER
# ═══════════════════════════════════════════════════════════════════════════════
header_data = [[
Paragraph("OPEN VIAL POLICY", title_style),
],[
Paragraph("Quick Reference Guide | UIP / MoHFW India 2015 | Based on WHO MDVP 2014", subtitle_style),
]]
header_table = Table(header_data, colWidths=[17.2*cm])
header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 12),
("BOTTOMPADDING", (0,0), (-1,-1), 12),
("LEFTPADDING", (0,0), (-1,-1), 16),
("RIGHTPADDING", (0,0), (-1,-1), 16),
("ROUNDEDCORNERS", [6, 6, 6, 6]),
]))
story.append(header_table)
story.append(Spacer(1, 12))
# ── Definition box ────────────────────────────────────────────────────────────
def_data = [[Paragraph(
"<b>What is Open Vial Policy?</b> The Open Vial Policy (OVP) allows reuse of "
"partially used <b>multi-dose vials</b> of applicable vaccines under the Universal "
"Immunization Programme (UIP) in subsequent sessions (fixed & outreach) for up to "
"<b>28 days (4 weeks)</b>, subject to meeting defined safety conditions. "
"Its primary aim is to <b>minimize vaccine wastage</b> without compromising safety.",
ParagraphStyle("def", fontName="Helvetica", fontSize=9.5, textColor=DARK_GRAY,
leading=15, leftIndent=0, alignment=TA_JUSTIFY))]]
def_table = Table(def_data, colWidths=[17.2*cm])
def_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PALE_TEAL),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 14),
("RIGHTPADDING", (0,0), (-1,-1), 14),
("ROUNDEDCORNERS", [5, 5, 5, 5]),
("BOX", (0,0), (-1,-1), 1, LIGHT_TEAL),
]))
story.append(def_table)
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════════════════
# TWO COLUMNS: APPLICABLE vs NOT APPLICABLE
# ═══════════════════════════════════════════════════════════════════════════════
yes_vaccines = [
"DPT (Diphtheria, Pertussis, Tetanus)",
"TT / Td (Tetanus Toxoid)",
"Hepatitis B",
"OPV (Oral Polio Vaccine)",
"Liquid Pentavalent (incl. Hib)",
"PCV (Pneumococcal Conjugate Vaccine)",
"IPV (Injectable Inactivated Polio Vaccine)",
]
no_vaccines = [
"Measles / MR vaccine",
"BCG vaccine",
"Japanese Encephalitis (JE) vaccine",
"Rotavirus vaccine",
]
yes_rows = [[Paragraph('<b><font color="#1A7A4A">✓ APPLICABLE VACCINES</font></b>', section_header)]] + \
[[tick(v)] for v in yes_vaccines]
no_rows = [[Paragraph('<b><font color="#C0392B">✗ NOT APPLICABLE</font></b>', section_header)]] + \
[[cross(v)] for v in no_vaccines]
col_width = 8.3*cm
t_yes = Table(yes_rows, colWidths=[col_width])
t_yes.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), GREEN),
("BACKGROUND", (0,1), (-1,-1), PALE_GREEN),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [5, 5, 5, 5]),
("LINEBELOW", (0,1), (0,-2), 0.3, colors.HexColor("#C8E6C9")),
]))
t_no = Table(no_rows, colWidths=[col_width])
t_no.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), RED),
("BACKGROUND", (0,1), (-1,-1), PALE_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [5, 5, 5, 5]),
("LINEBELOW", (0,1), (0,-2), 0.3, colors.HexColor("#FADBD8")),
]))
two_col = Table([[t_yes, Spacer(0.6*cm, 1), t_no]], colWidths=[col_width, 0.6*cm, col_width])
two_col.setStyle(TableStyle([
("VALIGN", (0,0), (-1,-1), "TOP"),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
("LEFTPADDING", (0,0), (-1,-1), 0),
("RIGHTPADDING", (0,0), (-1,-1), 0),
]))
story.append(two_col)
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════════════════
# CONDITIONS FOR REUSE
# ═══════════════════════════════════════════════════════════════════════════════
conditions = [
("Expiry Date", "Expiry date of the vaccine has NOT passed."),
("Cold Chain", "Vaccines stored at appropriate temperature during transport and at the cold chain storage point."),
("Vial Septum", "Vial septum has NOT been submerged in water or contaminated in any way."),
("Aseptic Technique", "Aseptic technique used to withdraw all doses (needle/septum not contaminated)."),
("VVM Status", "Vaccine Vial Monitor (VVM) has NOT reached or crossed the discard point."),
("Sunlight", "Vial has NOT been exposed to direct sunlight."),
("Date Recorded", "Date and time of opening is recorded on the vial."),
]
cond_rows = []
for i, (label, desc) in enumerate(conditions):
bg = PALE_GREEN if i % 2 == 0 else WHITE
cond_rows.append([
Paragraph(f'<font color="#1A7A4A">✓</font> <b>{label}</b>', condition_title),
Paragraph(desc, body),
])
cond_header = [[
Paragraph("CONDITIONS FOR REUSE (All must be met)", section_header),
Paragraph("", section_header),
]]
cond_table = Table(
cond_header + cond_rows,
colWidths=[4.5*cm, 12.7*cm]
)
cond_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("SPAN", (0,0), (-1,0)),
("BACKGROUND", (0,1), (-1,-1), PALE_TEAL),
("ROWBACKGROUNDS", (0,1), (-1,-1), [PALE_TEAL, colors.HexColor("#F0FAFB")]),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("LINEBELOW", (0,1), (-1,-2), 0.3, colors.HexColor("#BEE3E8")),
("ROUNDEDCORNERS", [5, 5, 5, 5]),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
story.append(cond_table)
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════════════════
# DISCARD CRITERIA
# ═══════════════════════════════════════════════════════════════════════════════
discard_items = [
"VVM has reached or crossed the discard point",
"No label, partially torn label, or illegible writing on label",
"Vial exposed to a non-sterile withdrawal procedure",
"Vial has been submerged in water or removed from a carrier with water",
"Vaccine is frozen, contains floccules, or any foreign body",
"Crack, breakage, or leakage in the vial",
"Any reported AEFI following use of the vial (retain safely & report)",
"Vial without VVM or with disfigured/unreadable VVM",
]
discard_rows = [[Paragraph("DISCARD CRITERIA - Discard Immediately If:", section_header)]]
for item in discard_items:
discard_rows.append([cross(item)])
discard_table = Table(discard_rows, colWidths=[17.2*cm])
discard_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), RED),
("BACKGROUND", (0,1), (-1,-1), PALE_RED),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("LINEBELOW", (0,1), (0,-2), 0.3, colors.HexColor("#FADBD8")),
("ROUNDEDCORNERS", [5, 5, 5, 5]),
]))
story.append(discard_table)
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════════════════
# END OF SESSION PROTOCOL TABLE
# ═══════════════════════════════════════════════════════════════════════════════
protocol_header_row = [
Paragraph("Vaccine Type", ParagraphStyle("th", fontName="Helvetica-Bold", fontSize=9.5, textColor=WHITE, alignment=TA_CENTER)),
Paragraph("Action at End of Session", ParagraphStyle("th", fontName="Helvetica-Bold", fontSize=9.5, textColor=WHITE, alignment=TA_CENTER)),
Paragraph("Discard Timeframe", ParagraphStyle("th", fontName="Helvetica-Bold", fontSize=9.5, textColor=WHITE, alignment=TA_CENTER)),
]
protocol_rows = [
[
Paragraph("DPT, TT/Td, Hep B, Pentavalent, OPV, PCV, IPV\n(OVP applicable)", body_bold),
Paragraph("Return to Cold Chain Point. Reuse in next session if all OVP conditions met.", body),
Paragraph("Up to 28 days from date of opening", ParagraphStyle("green_b", fontName="Helvetica-Bold", fontSize=9.5, textColor=GREEN, leading=13)),
],
[
Paragraph("BCG, Measles/MR, JE\n(Reconstituted / Freeze-dried)", body_bold),
Paragraph("Segregate. Do NOT reuse.", body),
Paragraph("Within 48 hours OR before next session (whichever is earlier)", ParagraphStyle("red_b", fontName="Helvetica-Bold", fontSize=9.5, textColor=RED, leading=13)),
],
[
Paragraph("Any reconstituted vaccine\n(general WHO rule)", body_bold),
Paragraph("Discard at end of session.", body),
Paragraph("Within 6 hours of reconstitution OR end of session (whichever is first)", ParagraphStyle("orange_b", fontName="Helvetica-Bold", fontSize=9.5, textColor=ORANGE, leading=13)),
],
]
full_protocol = [protocol_header_row] + protocol_rows
proto_table = Table(full_protocol, colWidths=[5.2*cm, 7.0*cm, 5.0*cm])
proto_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#2C3E50")),
("ROWBACKGROUNDS", (0,1), (-1,-1), [PALE_TEAL, colors.HexColor("#F0FAFB"), PALE_ORANGE]),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("GRID", (0,0), (-1,-1), 0.5, colors.HexColor("#CCCCCC")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("ROUNDEDCORNERS", [4, 4, 4, 4]),
]))
proto_section_header = [[Paragraph("END OF SESSION PROTOCOL", section_header)]]
proto_section_header_table = Table(proto_section_header, colWidths=[17.2*cm])
proto_section_header_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), colors.HexColor("#2C3E50")),
("TOPPADDING", (0,0), (-1,-1), 7),
("BOTTOMPADDING", (0,0), (-1,-1), 7),
("LEFTPADDING", (0,0), (-1,-1), 10),
("ROUNDEDCORNERS", [4, 4, 0, 0]),
]))
story.append(KeepTogether([proto_section_header_table, proto_table]))
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════════════════
# KEY REMINDERS
# ═══════════════════════════════════════════════════════════════════════════════
reminders = [
"Always write the date and time of opening on the vial.",
"Never submerge open vials in water inside a vaccine carrier.",
"Segregate reusable and non-reusable open vials at the Cold Chain Point.",
"Report any AEFI immediately and retain the implicated vial safely.",
"Check VVM status before every use - discard if at or past discard point.",
"OVP does NOT override cold chain requirements - maintain temperature at all times.",
]
remind_rows = [[Paragraph("KEY REMINDERS", section_header)]]
for r in reminders:
remind_rows.append([bullet(f"<b>{r}</b>" if r.startswith("OVP") else r)])
remind_table = Table(remind_rows, colWidths=[17.2*cm])
remind_table.setStyle(TableStyle([
("BACKGROUND", (0,0), (0,0), ORANGE),
("BACKGROUND", (0,1), (-1,-1), PALE_ORANGE),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 12),
("RIGHTPADDING", (0,0), (-1,-1), 12),
("LINEBELOW", (0,1), (0,-2), 0.3, colors.HexColor("#FAD7A0")),
("ROUNDEDCORNERS", [5, 5, 5, 5]),
]))
story.append(remind_table)
story.append(Spacer(1, 10))
# ═══════════════════════════════════════════════════════════════════════════════
# FOOTER
# ═══════════════════════════════════════════════════════════════════════════════
story.append(HRFlowable(width="100%", thickness=0.5, color=LIGHT_TEAL, spaceAfter=6))
story.append(Paragraph(
"Source: MoHFW India — Guidelines on Open Vial Policy for UIP (September 2015) | "
"WHO/IVB/14.07 — Multi-dose Vial Policy 2014 | "
"Park's Textbook of Preventive and Social Medicine",
footer_style))
# ── Build ─────────────────────────────────────────────────────────────────────
doc.build(story)
print(f"PDF saved: {OUTPUT}")
python3 /home/daytona/workspace/open-vial-policy/generate_pdf.py
Open Vial Policy Quick Reference
PDF Document · PDF
Your quick reference guide PDF is ready: [Open_Vial_Policy_Quick_Reference.pdf](/home/daytona/workspace/open-vial-policy/Open_Vial_Policy_Quick_Reference.pdf) The PDF is a single A4 page and includes: - **Header banner** with title and source attribution - **Definition box** explaining what the OVP is - **Two-column vaccine list** - applicable (green ticks) vs. not applicable (red crosses) - **Conditions for reuse table** - all 7 criteria that must be met - **Discard criteria** - 8 triggers for immediate disposal - **End of session protocol table** - with timeframes (28 days / 48 hours / 6 hours) color-coded by vaccine type - **Key reminders** section - **Footer** citing MoHFW 2015, WHO/IVB/14.07, and Park's Textbook
I can't download the previous pdf
ls -lh /home/daytona/workspace/open-vial-policy/
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/…
Open Vial Policy Quick Reference
PDF Document · PDF