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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ cd api && prisma migrate dev --create-only --name migration_name
# Seed basic patient demographics (original script)
python scripts/seed.py

# Seed staging data with synthetic vitals (requires API running)
# Seed staging data with synthetic vitals (uses Prisma directly, no API required)
python scripts/seed_staging.py

# Or with environment variable (mimics Railway staging)
Expand Down
150 changes: 76 additions & 74 deletions scripts/seed_staging.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,18 @@

import asyncio
import os
import sys
from datetime import UTC, datetime, timedelta
from random import randint, uniform

import httpx
from faker import Faker

# Add the api/src directory to the path so we can import from the app
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "api", "src"))

from prisma import Prisma # noqa: E402

# Configuration
API_URL = os.getenv("API_URL", "http://localhost:8000")
RAILWAY_ENV = os.getenv("RAILWAY_ENVIRONMENT", "")
MIN_PATIENT_THRESHOLD = 3 # Re-seed if fewer than this many patients exist

Expand All @@ -31,7 +35,7 @@
}


async def should_seed() -> bool:
async def should_seed(db: Prisma) -> bool:
"""
Determine if seeding should run.

Expand All @@ -44,69 +48,62 @@ async def should_seed() -> bool:
print(f"Skipping seed: not staging environment (current: {RAILWAY_ENV})")
return False

# Check if API is available
try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(f"{API_URL}/health")
if response.status_code != 200:
print("Skipping seed: API not healthy")
return False
except Exception as e:
print(f"Skipping seed: API not accessible ({e})")
return False

# Check patient count
# Check patient count using Prisma directly
try:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(f"{API_URL}/api/patients")
if response.status_code == 200:
patients = response.json()
patient_count = len(patients)
if patient_count >= MIN_PATIENT_THRESHOLD:
print(
f"Skipping seed: sufficient patients exist ({patient_count} >= {MIN_PATIENT_THRESHOLD})"
)
return False
print(f"Patient count: {patient_count} (threshold: {MIN_PATIENT_THRESHOLD})")
patient_count = await db.patient.count()
if patient_count >= MIN_PATIENT_THRESHOLD:
print(
f"Skipping seed: sufficient patients exist ({patient_count} >= {MIN_PATIENT_THRESHOLD})"
)
return False
print(f"Patient count: {patient_count} (threshold: {MIN_PATIENT_THRESHOLD})")
except Exception as e:
print(f"Warning: Could not check patient count ({e}), proceeding with seed...")

return True


async def create_patient(
client: httpx.AsyncClient, patient_data: dict
) -> dict | None:
"""Create a patient via the API."""
async def create_patient(db: Prisma, patient_data: dict) -> dict | None:
"""Create a patient using Prisma."""
try:
response = await client.post(
f"{API_URL}/api/patients",
json=patient_data,
# Convert birth_date string to datetime if needed
birth_date = patient_data["birth_date"]
if isinstance(birth_date, str):
birth_date = datetime.fromisoformat(birth_date).date()

patient = await db.patient.create(
data={
"mrn": patient_data["mrn"],
"given_name": patient_data["given_name"],
"family_name": patient_data["family_name"],
"birth_date": birth_date,
}
)
if response.status_code == 201:
return response.json()
else:
print(f" ⚠️ Failed to create {patient_data['mrn']}: {response.text}")
return None
return patient.model_dump()
except Exception as e:
print(f" ❌ Error creating {patient_data['mrn']}: {e}")
return None


async def create_vital_signs(
client: httpx.AsyncClient, vital_signs_data: dict
) -> dict | None:
"""Create vital signs observation via the API."""
async def create_vital_signs(db: Prisma, vital_signs_data: dict) -> dict | None:
"""Create vital signs observation using Prisma."""
try:
response = await client.post(
f"{API_URL}/api/vital-signs",
json=vital_signs_data,
# Convert recorded_at string to datetime if needed
recorded_at = vital_signs_data["recorded_at"]
if isinstance(recorded_at, str):
recorded_at = datetime.fromisoformat(recorded_at)

vital_signs = await db.vitalsigns.create(
data={
"patient_id": vital_signs_data["patient_id"],
"encounter_id": vital_signs_data.get("encounter_id"),
"recorded_at": recorded_at,
"systolic": vital_signs_data["systolic"],
"diastolic": vital_signs_data["diastolic"],
"pulse_rate": vital_signs_data["pulse_rate"],
}
)
if response.status_code == 201:
return response.json()
else:
print(f" ⚠️ Failed to create vital signs: {response.text}")
return None
return vital_signs.model_dump()
except Exception as e:
print(f" ❌ Error creating vital signs: {e}")
return None
Expand Down Expand Up @@ -164,15 +161,15 @@ def generate_synthetic_patients(count: int = 15) -> list[dict]:


async def seed_patient_with_vitals(
client: httpx.AsyncClient, patient_data: dict, num_readings: int = 3
db: Prisma, patient_data: dict, num_readings: int = 3
) -> tuple[dict | None, list[dict]]:
"""
Seed a single patient with multiple vital signs readings.

Returns: (patient, list of vital signs)
"""
# Create patient
patient = await create_patient(client, patient_data)
patient = await create_patient(db, patient_data)
if not patient:
return None, []

Expand All @@ -193,7 +190,7 @@ async def seed_patient_with_vitals(
vital_signs_data["patient_id"] = patient_id
vital_signs_data["encounter_id"] = None

vital_signs = await create_vital_signs(client, vital_signs_data)
vital_signs = await create_vital_signs(db, vital_signs_data)
if vital_signs:
vital_signs_list.append(vital_signs)
print(
Expand All @@ -209,41 +206,46 @@ async def main():
print("🌱 Open CIS Staging Seed Script")
print("=" * 50)

# Check if seeding should run
if not await should_seed():
return
# Initialize Prisma
db = Prisma()
await db.connect()

print(f"\n✅ Starting seed for environment: {RAILWAY_ENV or 'local'}")
print(f" API URL: {API_URL}")
print()
try:
# Check if seeding should run
if not await should_seed(db):
return

# Generate synthetic patient data
synthetic_patients = generate_synthetic_patients(count=15)
print(f"Generated {len(synthetic_patients)} synthetic patients")
print()
print(f"\n✅ Starting seed for environment: {RAILWAY_ENV or 'local'}")
print()

# Seed patients with vital signs
async with httpx.AsyncClient(timeout=30.0) as client:
# Generate synthetic patient data
synthetic_patients = generate_synthetic_patients(count=15)
print(f"Generated {len(synthetic_patients)} synthetic patients")
print()

# Seed patients with vital signs
created_patients = 0
total_vital_signs = 0

for patient_data in synthetic_patients:
patient, vital_signs = await seed_patient_with_vitals(
client, patient_data, num_readings=randint(2, 5)
db, patient_data, num_readings=randint(2, 5)
)

if patient:
created_patients += 1
total_vital_signs += len(vital_signs)

# Summary
print()
print("=" * 50)
print(f"✅ Seeding complete!")
print(f" Patients created: {created_patients}/{len(synthetic_patients)}")
print(f" Vital signs created: {total_vital_signs}")
print()
print("🎉 Staging data is ready for testing!")
# Summary
print()
print("=" * 50)
print(f"✅ Seeding complete!")
print(f" Patients created: {created_patients}/{len(synthetic_patients)}")
print(f" Vital signs created: {total_vital_signs}")
print()
print("🎉 Staging data is ready for testing!")
finally:
await db.disconnect()


if __name__ == "__main__":
Expand Down