Building a Post-Operative Care Agent: Recovery Instructions and Follow-Up Scheduling
Build an AI agent that delivers personalized post-operative care instructions, monitors patient recovery through symptom check-ins, triggers clinical alerts when needed, and schedules follow-up appointments automatically.
The Gap Between Discharge and Recovery
After a dental procedure, patients leave with a printed instruction sheet they often lose before reaching their car. Questions arise at night and on weekends when the office is closed. A post-operative care agent fills this gap by delivering instructions at the right time, checking in on recovery milestones, and escalating to the clinical team when symptoms suggest complications.
Post-Op Instruction Engine
The instruction engine maps each procedure type to a timeline of care instructions. Instead of dumping all information at once, it delivers relevant guidance at each stage of recovery.
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional
from enum import Enum
class RecoveryPhase(Enum):
IMMEDIATE = "immediate" # 0-2 hours
FIRST_DAY = "first_day" # 2-24 hours
EARLY_RECOVERY = "early" # 1-3 days
MID_RECOVERY = "mid" # 3-7 days
LATE_RECOVERY = "late" # 7-14 days
@dataclass
class CareInstruction:
phase: RecoveryPhase
title: str
instructions: list[str]
warnings: list[str] = field(default_factory=list)
send_at_offset_hours: int = 0
EXTRACTION_INSTRUCTIONS = [
CareInstruction(
phase=RecoveryPhase.IMMEDIATE,
title="Right After Your Extraction",
instructions=[
"Keep biting on the gauze for 30-45 minutes.",
"Do not spit, use a straw, or rinse your mouth.",
"Apply an ice pack to your cheek: 20 minutes "
"on, 20 minutes off.",
"Take prescribed pain medication before the "
"numbness wears off.",
],
warnings=[
"Some bleeding is normal. If bleeding does not "
"slow after 2 hours of steady gauze pressure, "
"call the office.",
],
send_at_offset_hours=0,
),
CareInstruction(
phase=RecoveryPhase.FIRST_DAY,
title="First 24 Hours",
instructions=[
"Eat soft foods: yogurt, mashed potatoes, soup.",
"Do not smoke or use tobacco products.",
"Sleep with your head elevated on an extra pillow.",
"Take ibuprofen 400mg every 6 hours for pain.",
],
warnings=[
"Call us if you develop a fever above 101F.",
],
send_at_offset_hours=4,
),
CareInstruction(
phase=RecoveryPhase.EARLY_RECOVERY,
title="Days 2-3 Recovery Check",
instructions=[
"Gently rinse with warm salt water after meals.",
"You can begin eating slightly firmer foods.",
"Continue taking medication as prescribed.",
"Swelling should start to decrease.",
],
warnings=[
"Increasing pain after day 3 could indicate "
"dry socket. Contact us if pain suddenly "
"worsens.",
],
send_at_offset_hours=48,
),
CareInstruction(
phase=RecoveryPhase.MID_RECOVERY,
title="One Week Check-In",
instructions=[
"Resume normal brushing, being gentle near "
"the extraction site.",
"Most discomfort should be gone by now.",
"Resume normal diet as comfort allows.",
],
send_at_offset_hours=168,
),
]
PROCEDURE_INSTRUCTIONS = {
"extraction": EXTRACTION_INSTRUCTIONS,
"root_canal": [], # similar structure omitted
"implant": [], # similar structure omitted
"crown": [], # similar structure omitted
}
Symptom Monitoring and Check-In System
The agent sends scheduled check-in messages that ask the patient to report their symptoms. It uses structured questions to gather quantifiable data.
@dataclass
class SymptomCheckIn:
question: str
response_type: str # "scale_1_10", "yes_no", "free_text"
alert_threshold: Optional[int] = None
follow_up_question: Optional[str] = None
CHECK_IN_QUESTIONS = {
RecoveryPhase.FIRST_DAY: [
SymptomCheckIn(
"On a scale of 1-10, how is your pain level?",
"scale_1_10",
alert_threshold=8,
),
SymptomCheckIn(
"Is the bleeding controlled?",
"yes_no",
follow_up_question=(
"Is it steady oozing or active bleeding?"
),
),
SymptomCheckIn(
"Have you been able to take your medication?",
"yes_no",
),
],
RecoveryPhase.EARLY_RECOVERY: [
SymptomCheckIn(
"How is your pain compared to yesterday? "
"(1=much better, 5=same, 10=much worse)",
"scale_1_10",
alert_threshold=7,
),
SymptomCheckIn(
"Do you have any swelling?",
"yes_no",
follow_up_question="Is it increasing or stable?",
),
SymptomCheckIn(
"Are you experiencing any unusual taste "
"or bad odor?",
"yes_no",
alert_threshold=1, # any yes triggers review
),
],
}
class SymptomMonitor:
def __init__(self, db, sms_client, alert_service):
self.db = db
self.sms = sms_client
self.alerts = alert_service
async def send_check_in(
self, patient_id: str, procedure_id: str,
phase: RecoveryPhase,
):
questions = CHECK_IN_QUESTIONS.get(phase, [])
if not questions:
return
patient = await self.db.fetchrow(
"SELECT * FROM patients WHERE id = $1",
patient_id,
)
for q in questions:
await self.sms.send(
patient["phone"], q.question
)
await self.db.execute("""
INSERT INTO symptom_check_ins
(patient_id, procedure_id, phase,
question, sent_at)
VALUES ($1, $2, $3, $4, $5)
""", patient_id, procedure_id, phase.value,
q.question, datetime.utcnow())
async def process_response(
self, patient_id: str, response_text: str,
):
latest_question = await self.db.fetchrow("""
SELECT * FROM symptom_check_ins
WHERE patient_id = $1
AND response IS NULL
ORDER BY sent_at DESC LIMIT 1
""", patient_id)
if not latest_question:
return
question_def = self._find_question_def(
latest_question["phase"],
latest_question["question"],
)
parsed_value = self._parse_response(
response_text, question_def.response_type
)
await self.db.execute("""
UPDATE symptom_check_ins
SET response = $2, responded_at = $3
WHERE id = $1
""", latest_question["id"], str(parsed_value),
datetime.utcnow())
if self._should_alert(question_def, parsed_value):
await self.alerts.notify_clinical_team(
patient_id=patient_id,
alert_type="symptom_concern",
details=(
f"Patient reported {parsed_value} for: "
f"{question_def.question}"
),
)
def _parse_response(self, text, response_type):
if response_type == "scale_1_10":
import re
numbers = re.findall(r"\d+", text)
return int(numbers[0]) if numbers else 5
elif response_type == "yes_no":
lower = text.lower().strip()
return 1 if lower in ("yes", "y", "yeah") else 0
return text
def _should_alert(self, question_def, value):
if question_def.alert_threshold is None:
return False
return int(value) >= question_def.alert_threshold
def _find_question_def(self, phase, question_text):
phase_enum = RecoveryPhase(phase)
for q in CHECK_IN_QUESTIONS.get(phase_enum, []):
if q.question == question_text:
return q
return SymptomCheckIn(question_text, "free_text")
Alert Trigger System
When symptom responses exceed thresholds, the agent escalates to the clinical team through the appropriate channel based on severity.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
class AlertSeverity(Enum):
LOW = "low" # log only
MEDIUM = "medium" # notification to provider
HIGH = "high" # immediate page
CRITICAL = "critical" # emergency protocol
class ClinicalAlertService:
def __init__(self, db, pager, notification_svc):
self.db = db
self.pager = pager
self.notify = notification_svc
async def notify_clinical_team(
self, patient_id: str, alert_type: str,
details: str,
):
severity = self._assess_severity(
alert_type, details
)
await self.db.execute("""
INSERT INTO clinical_alerts
(patient_id, alert_type, severity,
details, created_at, resolved)
VALUES ($1, $2, $3, $4, $5, false)
""", patient_id, alert_type, severity.value,
details, datetime.utcnow())
if severity == AlertSeverity.HIGH:
provider = await self._get_treating_provider(
patient_id
)
await self.pager.send(
provider["phone"],
f"POST-OP ALERT: {details}",
)
elif severity == AlertSeverity.CRITICAL:
await self.pager.send_emergency(
patient_id, details
)
def _assess_severity(self, alert_type, details):
if "fever" in details.lower():
return AlertSeverity.HIGH
if "bleeding" in details.lower():
return AlertSeverity.HIGH
if "increasing pain" in details.lower():
return AlertSeverity.MEDIUM
return AlertSeverity.LOW
Follow-Up Appointment Auto-Scheduling
The agent automatically schedules follow-up appointments based on the procedure type and recovery timeline.
FOLLOW_UP_RULES = {
"extraction": {"days_after": 7, "type": "post_op_check"},
"root_canal": {"days_after": 14, "type": "crown_consult"},
"implant": {"days_after": 10, "type": "implant_check"},
}
class FollowUpScheduler:
def __init__(self, db, schedule_manager):
self.db = db
self.scheduler = schedule_manager
async def schedule_follow_up(
self, patient_id: str, procedure_type: str,
procedure_date: datetime, provider_id: str,
):
rule = FOLLOW_UP_RULES.get(procedure_type)
if not rule:
return None
target_date = (
procedure_date + timedelta(days=rule["days_after"])
).date()
slots = await self.scheduler.find_available_slots(
appointment_type=rule["type"],
preferred_date=target_date,
provider_id=provider_id,
)
if slots:
appointment = await self.scheduler.book_appointment(
patient_id=patient_id,
slot=slots[0],
appointment_type=rule["type"],
)
return appointment
return None
FAQ
How does the agent know when to escalate versus when to reassure the patient?
The alert system uses a combination of threshold-based rules and trend analysis. A single high pain score triggers a notification, but the system also watches for trends — pain that increases day over day even if below the absolute threshold still gets flagged. The clinical team defines the thresholds per procedure type, and the system never provides medical advice beyond the pre-approved care instructions.
What if the patient does not respond to a symptom check-in?
If a patient misses a check-in, the agent sends one follow-up message two hours later. If there is still no response, the front desk receives a task to call the patient directly. The system never assumes that silence means everything is fine — a non-response after a surgical procedure is treated as a reason for human follow-up.
Can the post-op agent handle multiple simultaneous procedures, such as multiple extractions done in one visit?
Yes. Each procedure creates its own recovery timeline, but the agent consolidates messages so the patient receives one check-in that covers all procedures rather than separate messages for each tooth. The most conservative recovery instructions take precedence — for example, if one extraction was surgical and one was simple, the surgical recovery guidelines apply to the overall care plan.
#PostOpCare #RecoveryMonitoring #HealthcareAI #PatientFollowUp #Python #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.