Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions scripts/README-fill-pdf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Filling PDF Forms - Jane Doe Example

This directory contains scripts to fill out the Colorado Prescription Drug Prior Authorization Request Form with mock patient data for Jane Doe.

## Jane Doe's Profile

**Patient:** Jane Doe, Age 42
**Occupation:** Marketing Director
**Location:** Denver, Colorado
**Personality:** Professional, detail-oriented, health-conscious, active lifestyle
**Condition:** Chronic migraines with aura, diagnosed 3 years ago
**Medication Needed:** Aimovig (erenumab-aooe) - CGRP inhibitor for migraine prevention

## Files

1. **`fill_pdf_form.py`** - Python script to automatically fill the PDF form
2. **`../assets/pdf-templates/jane-doe-filled-form-reference.txt`** - Text reference showing all filled information

## Installation

To run the PDF filling script, you need to install PyMuPDF:

```bash
pip install pymupdf
```

Or if you prefer pypdf:

```bash
pip install pypdf
```

## Usage

Run the script:

```bash
python3 scripts/fill_pdf_form.py
```

This will:
- Read the original PDF template from `assets/pdf-templates/co-prescription-drug-prior-authorization-request-form.pdf`
- Fill it with Jane Doe's information
- Save the filled PDF to `assets/pdf-templates/co-prescription-drug-prior-authorization-request-form-filled-jane-doe.pdf`

## Manual Filling

If you prefer to fill the form manually or the script doesn't work perfectly, refer to:
- `assets/pdf-templates/jane-doe-filled-form-reference.txt` for all the information organized by field

## Alternative Methods

If Python libraries aren't available, you can:
1. Open the PDF in Adobe Acrobat or another PDF editor
2. Use the reference document to fill each field manually
3. Or use online PDF fillers like PDFescape, PDFfiller, etc.
329 changes: 329 additions & 0 deletions scripts/fill_pdf_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""
Fill out the Colorado Prescription Drug Prior Authorization Request Form
with mock patient data for Jane Doe.

This script requires pypdf to be installed:
pip install pypdf

Or install PyMuPDF for better text positioning:
pip install pymupdf
"""

import sys
from pathlib import Path
from datetime import datetime

USE_PYMUPDF = False
USE_PYPDF = False

# Try to import PDF libraries
try:
import fitz # PyMuPDF
USE_PYMUPDF = True
except ImportError:
try:
from pypdf import PdfReader, PdfWriter
USE_PYPDF = True
except ImportError:
print("Error: No PDF library found. Please install one of:")
print(" pip install pymupdf (recommended)")
print(" pip install pypdf")
sys.exit(1)


# Jane Doe's Profile
# Age: 42, Marketing Director, lives in Denver, CO
# Condition: Chronic migraines with aura, diagnosed 3 years ago
# Medication: Aimovig (erenumab-aooe) - a CGRP inhibitor for migraine prevention
# Personality: Professional, detail-oriented, health-conscious, active lifestyle

PATIENT_DATA = {
# Request Type
"urgent": False, # Non-urgent

# Drug Information
"drug_name": "Aimovig (erenumab-aooe)",
"drug_scientific": "erenumab-aooe",
"opioid_dependence": False,

# Patient Information
"patient_name": "Jane Doe",
"member_number": "AET123456789",
"policy_group_number": "GRP-2024-001",
"date_of_birth": "1982-03-15", # Age 42
"patient_address": "2847 Elm Street, Apt 4B, Denver, CO 80202",
"patient_phone": "(303) 555-0147",
"patient_email": "jane.doe.email@example.com",

# Prescription Date
"prescription_date": "2026-02-20",

# Prescriber Information
"prescriber_name": "Dr. Sarah Chen, MD",
"prescriber_fax": "(303) 555-0198",
"prescriber_phone": "(303) 555-0197",
"prescriber_pager": "",
"prescriber_address": "1234 Medical Center Drive, Suite 200, Denver, CO 80218",
"prescriber_office_contact": "Maria Rodriguez",
"prescriber_npi": "1234567890",
"prescriber_dea": "BC1234567",
"prescriber_tax_id": "12-3456789",
"specialty_facility": "Denver Neurology Associates",
"prescriber_email": "schen@denverneuro.com",

# Prior Authorization Type
"pa_type": "New Request", # New Request or Reauthorization

# Diagnosis
"diagnosis": "Chronic migraine with aura, intractable, with status migrainosus",
"icd_code": "G43.109",

# Drug Details
"drug_requested": "Aimovig (erenumab-aooe)",
"strength_route_frequency": "70 mg subcutaneous injection monthly",
"unit_volume": "1 autoinjector pen (70 mg/1 mL)",
"start_date": "2026-03-01",
"length_of_therapy": "12 months",

# Treatment Location
"treatment_location": "Patient's Home - Self-administered subcutaneous injection",
"location_npi": "",
"location_address": "2847 Elm Street, Apt 4B, Denver, CO 80202",
"location_tax_id": "",

# Clinical Justification
"clinical_criteria": """Patient is a 42-year-old female with a 3-year history of chronic migraines
(15-18 headache days per month). Patient has failed multiple preventive therapies including:
- Topiramate 100mg BID for 6 months (discontinued due to cognitive side effects)
- Propranolol ER 120mg daily for 8 months (ineffective, minimal reduction in frequency)
- Amitriptyline 50mg at bedtime for 4 months (discontinued due to excessive sedation)

Patient has tried acute treatments including:
- Sumatriptan 100mg tablets (partial relief, frequent use)
- Rizatriptan 10mg (inconsistent response)

Current headache diary shows average of 16 migraine days per month over the past 3 months.
Migraines significantly impact patient's ability to work and maintain daily activities.
Patient is a marketing director and headaches cause frequent work absences.

Aimovig (erenumab-aooe) is a CGRP monoclonal antibody indicated for the preventive treatment
of migraine in adults. This medication is appropriate given patient's treatment-resistant chronic
migraine and need for a well-tolerated preventive option that does not require daily dosing.

Patient has been counseled on proper injection technique and storage requirements.""",

"additional_info": "Patient has no contraindications to CGRP inhibitors. No history of cardiovascular disease.",
"clinical_trial": False,

# Prescription Details
"dose": "70 mg",
"route": "Subcutaneous",
"frequency": "Monthly (once every 28 days)",
"quantity": "1 autoinjector pen",
"refills": "11",

# Delivery
"delivery_location": "Patient's Home",

# Signature
"signature_date": "2026-02-20",

# Pharmacy
"pharmacy_name": "HealthMart Pharmacy",
"pharmacy_phone": "(303) 555-0200",
}


def format_date(date_str: str) -> str:
"""Convert YYYY-MM-DD to MM/DD/YYYY"""
dt = datetime.strptime(date_str, "%Y-%m-%d")
return dt.strftime("%m/%d/%Y")


def fill_pdf_with_pymupdf(input_pdf_path: str, output_pdf_path: str) -> None:
"""Fill PDF form fields using PyMuPDF."""
with fitz.open(input_pdf_path) as doc:
page = doc[0]
widgets = list(page.widgets())

# Track widgets that need to be updated
widgets_to_update = []

def set_widget_value(widget, value):
"""Set widget value and track it for update."""
if widget:
widget.field_value = value
widgets_to_update.append(widget)
return True
return False

def set_checkbox(widgets_list, index, checked=True):
"""Set checkbox value and track it."""
if widgets_list and len(widgets_list) > index:
widgets_list[index].field_value = checked
widgets_to_update.append(widgets_list[index])
return True
return False

# Urgent/Non-Urgent checkboxes (cb1) - Y=128
# First checkbox is Urgent, second is Non-Urgent
urgent_widgets = [w for w in widgets if w.field_name == "cb1" and abs(w.rect.y0 - 128) < 5]
if len(urgent_widgets) >= 2:
if not PATIENT_DATA["urgent"]:
urgent_widgets[1].field_value = True # Non-Urgent
widgets_to_update.append(urgent_widgets[1])
else:
urgent_widgets[0].field_value = True # Urgent
widgets_to_update.append(urgent_widgets[0])

# Requested Drug Name (T3) - Y=146
drug_name_widget = next((w for w in widgets if w.field_name == "T3"), None)
if drug_name_widget:
drug_name_widget.field_value = PATIENT_DATA["drug_name"]
widgets_to_update.append(drug_name_widget)

# Opioid dependence checkboxes (cb2) - Y=165
# First is Yes, second is No
opioid_widgets = [w for w in widgets if w.field_name == "cb2" and abs(w.rect.y0 - 165) < 5]
if len(opioid_widgets) >= 2:
if not PATIENT_DATA["opioid_dependence"]:
opioid_widgets[1].field_value = True # No
widgets_to_update.append(opioid_widgets[1])
else:
opioid_widgets[0].field_value = True # Yes
widgets_to_update.append(opioid_widgets[0])

# Patient Information fields (left column) - T4 through T11
set_widget_value(next((w for w in widgets if w.field_name == "T4"), None), PATIENT_DATA["patient_name"])
set_widget_value(next((w for w in widgets if w.field_name == "T5"), None), PATIENT_DATA["member_number"])
set_widget_value(next((w for w in widgets if w.field_name == "T6"), None), PATIENT_DATA["policy_group_number"])
set_widget_value(next((w for w in widgets if w.field_name == "T7"), None), format_date(PATIENT_DATA["date_of_birth"]))
set_widget_value(next((w for w in widgets if w.field_name == "T8"), None), PATIENT_DATA["patient_address"])
set_widget_value(next((w for w in widgets if w.field_name == "T9"), None), PATIENT_DATA["patient_phone"])
set_widget_value(next((w for w in widgets if w.field_name == "T10"), None), PATIENT_DATA["patient_email"])
set_widget_value(next((w for w in widgets if w.field_name == "T11"), None), format_date(PATIENT_DATA["prescription_date"]))

# Prescriber Information fields (right column) - T12 through T22
set_widget_value(next((w for w in widgets if w.field_name == "T12"), None), PATIENT_DATA["prescriber_name"])
set_widget_value(next((w for w in widgets if w.field_name == "T13"), None), PATIENT_DATA["prescriber_fax"])
set_widget_value(next((w for w in widgets if w.field_name == "T14"), None), PATIENT_DATA["prescriber_phone"])
if PATIENT_DATA["prescriber_pager"]:
set_widget_value(next((w for w in widgets if w.field_name == "T15"), None), PATIENT_DATA["prescriber_pager"])
set_widget_value(next((w for w in widgets if w.field_name == "T16"), None), PATIENT_DATA["prescriber_address"])
set_widget_value(next((w for w in widgets if w.field_name == "T17"), None), PATIENT_DATA["prescriber_office_contact"])
set_widget_value(next((w for w in widgets if w.field_name == "T18"), None), PATIENT_DATA["prescriber_npi"])
set_widget_value(next((w for w in widgets if w.field_name == "T19"), None), PATIENT_DATA["prescriber_dea"])
set_widget_value(next((w for w in widgets if w.field_name == "T20"), None), PATIENT_DATA["prescriber_tax_id"])
set_widget_value(next((w for w in widgets if w.field_name == "T21"), None), PATIENT_DATA["specialty_facility"])
set_widget_value(next((w for w in widgets if w.field_name == "T22"), None), PATIENT_DATA["prescriber_email"])

# Prior Authorization Type checkboxes (cb23) - Y=437
pa_widgets = [w for w in widgets if w.field_name == "cb23" and abs(w.rect.y0 - 437) < 5]
if len(pa_widgets) >= 2:
if PATIENT_DATA["pa_type"] == "New Request":
set_checkbox(pa_widgets, 0)
else:
set_checkbox(pa_widgets, 1)

# Clinical information fields
set_widget_value(next((w for w in widgets if w.field_name == "T25"), None),
f"{PATIENT_DATA['diagnosis']} (ICD-10: {PATIENT_DATA['icd_code']})")
set_widget_value(next((w for w in widgets if w.field_name == "T26"), None), PATIENT_DATA["drug_requested"])
set_widget_value(next((w for w in widgets if w.field_name == "T27"), None), PATIENT_DATA["strength_route_frequency"])
set_widget_value(next((w for w in widgets if w.field_name == "T28"), None), PATIENT_DATA["unit_volume"])
set_widget_value(next((w for w in widgets if w.field_name == "T30"), None),
f"Start: {format_date(PATIENT_DATA['start_date'])}, Duration: {PATIENT_DATA['length_of_therapy']}")
set_widget_value(next((w for w in widgets if w.field_name == "T29"), None), PATIENT_DATA["treatment_location"])
set_widget_value(next((w for w in widgets if w.field_name == "T31"), None), PATIENT_DATA["clinical_criteria"])
set_widget_value(next((w for w in widgets if w.field_name == "T32"), None), PATIENT_DATA["additional_info"])

# Drug details
set_widget_value(next((w for w in widgets if w.field_name == "T33.0"), None),
f"{PATIENT_DATA['drug_name']} {PATIENT_DATA['dose']}")
set_widget_value(next((w for w in widgets if w.field_name == "T34"), None), PATIENT_DATA["dose"])
set_widget_value(next((w for w in widgets if w.field_name == "T35"), None), PATIENT_DATA["route"])
set_widget_value(next((w for w in widgets if w.field_name == "T36"), None), PATIENT_DATA["frequency"])
set_widget_value(next((w for w in widgets if w.field_name == "T37"), None), PATIENT_DATA["quantity"])
set_widget_value(next((w for w in widgets if w.field_name == "T38"), None), PATIENT_DATA["refills"])

# Delivery location checkboxes (cb39, cb40, cb41) - Y=645.7
delivery_widgets = [w for w in widgets if w.field_name in ["cb39", "cb40", "cb41"] and abs(w.rect.y0 - 645.7) < 5]
if delivery_widgets:
if PATIENT_DATA["delivery_location"] == "Patient's Home":
cb39 = next((w for w in delivery_widgets if w.field_name == "cb39"), None)
if cb39:
set_checkbox([cb39], 0)
elif PATIENT_DATA["delivery_location"] == "Physician Office":
cb40 = next((w for w in delivery_widgets if w.field_name == "cb40"), None)
if cb40:
set_checkbox([cb40], 0)

# Signature and pharmacy
set_widget_value(next((w for w in widgets if w.field_name == "Signature3"), None), PATIENT_DATA["prescriber_name"])
set_widget_value(next((w for w in widgets if w.field_name == "T43"), None), format_date(PATIENT_DATA["signature_date"]))
set_widget_value(next((w for w in widgets if w.field_name == "T44"), None),
f"{PATIENT_DATA['pharmacy_name']}, {PATIENT_DATA['pharmacy_phone']}")

# Update all modified widgets
for widget in widgets_to_update:
try:
widget.update()
except Exception as e:
print(f"Warning: Could not update widget {widget.field_name}: {e}")

# Save the filled PDF
doc.save(output_pdf_path, incremental=False, encryption=fitz.PDF_ENCRYPT_KEEP)


def fill_pdf_with_pypdf(input_pdf_path: str, output_pdf_path: str) -> None:
"""Fill PDF using pypdf (limited - field mapping not yet implemented)."""
reader = PdfReader(input_pdf_path)
writer = PdfWriter()

# Copy pages
for page in reader.pages:
writer.add_page(page)

print("Warning: pypdf field mapping is not yet implemented. Output will be a copy of the original.")

# Save
with open(output_pdf_path, "wb") as output_file:
writer.write(output_file)
Comment on lines +279 to +292
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

The pypdf fallback fills zero fields — it's effectively a no-op.

update_page_form_field_values on line 293 receives an empty dict {}, so the "filled" output is identical to the input. This is misleading because main() prints "Successfully filled PDF form!" regardless. Either populate the field mapping (mirroring the PyMuPDF field names) or clearly warn the user that pypdf support is incomplete.

Option A: stub out with a clear warning
 def fill_pdf_with_pypdf(input_pdf_path: str, output_pdf_path: str) -> None:
-    """Fill PDF using pypdf (limited - mainly for form fields)."""
-    # pypdf is better for form fields, but this PDF may not have fillable fields
-    # So we'll create a text summary instead
+    """Fill PDF using pypdf (limited - field mapping not yet implemented)."""
     reader = PdfReader(input_pdf_path)
     writer = PdfWriter()
 
     # Copy pages
     for page in reader.pages:
         writer.add_page(page)
 
-    # Try to fill form fields if they exist
-    if reader.get_form_text_fields():
-        # PDF has form fields - fill them
-        writer.update_page_form_field_values(
-            writer.pages[0],
-            {
-                # Map field names if they exist
-            }
-        )
+    print("Warning: pypdf field mapping is not yet implemented. Output will be a copy of the original.")
 
     # Save
     with open(output_pdf_path, "wb") as output_file:
         writer.write(output_file)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/fill_pdf_form.py` around lines 279 - 302, The pypdf fallback in
fill_pdf_with_pypdf is a no-op because update_page_form_field_values is called
with an empty dict; change fill_pdf_with_pypdf to either (A) build and pass a
real field mapping by iterating reader.get_form_text_fields() and mapping those
names to values (mirroring the PyMuPDF field names used elsewhere) before
calling writer.update_page_form_field_values(writer.pages[0], mapping), or (B)
if you don't want to implement filling, replace the silent behavior with a clear
warning and a non-success signal: log/print a message indicating pypdf
form-filling is unsupported for this PDF and ensure main() does not print
"Successfully filled PDF form!" when no fields were filled (e.g., return a
status or raise an informative exception). Ensure references to
reader.get_form_text_fields(), update_page_form_field_values, and
fill_pdf_with_pypdf are updated accordingly so the caller can detect and handle
the no-op case.



def main():
"""Main function."""
script_dir = Path(__file__).parent
project_root = script_dir.parent
input_pdf = project_root / "assets" / "pdf-templates" / "co-prescription-drug-prior-authorization-request-form.pdf"
output_pdf = project_root / "assets" / "pdf-templates" / "co-prescription-drug-prior-authorization-request-form-filled-jane-doe.pdf"

if not input_pdf.exists():
print(f"Error: Input PDF not found at {input_pdf}")
sys.exit(1)

print(f"Filling PDF form for Jane Doe...")
print(f"Patient: {PATIENT_DATA['patient_name']}")
print(f"Drug: {PATIENT_DATA['drug_name']}")
print(f"Diagnosis: {PATIENT_DATA['diagnosis']}")
print()

try:
if USE_PYMUPDF:
print("Using PyMuPDF...")
fill_pdf_with_pymupdf(str(input_pdf), str(output_pdf))
elif USE_PYPDF:
print("Using pypdf...")
fill_pdf_with_pypdf(str(input_pdf), str(output_pdf))
except Exception as e:
print(f"\n✗ Failed to fill PDF form: {e}")
sys.exit(1)

print(f"\n✓ Successfully filled PDF form!")
print(f" Input: {input_pdf}")
print(f" Output: {output_pdf}")


if __name__ == "__main__":
main()