I Built an AI That Matches People With Therapy Animals — Here's What I Learned
Pet therapy works. There's decades of research backing it up — reduced cortisol, lower blood pressure, improved mood. The problem? Finding the right animal for the right person has always been manual, slow, and largely guesswork.
So I built something to fix that.
The Problem With Traditional Pet Therapy Matching
Most platforms work like this:
- You fill out a form ("Do you prefer dogs or cats?")
- Someone manually reviews it
- You get a recommendation in 3–5 business days
This is 2024 energy applied to a 2026 problem.
The real matching factors are way more nuanced:
- Anxiety type (social vs. generalized vs. situational)
- Living situation and mobility
- Allergy profile
- Sensory sensitivities (noise, texture, unpredictability)
- Therapeutic goal (grounding, motivation, companionship, routine)
A golden retriever is perfect for some people. For others — too energetic, too unpredictable, overwhelming.
How the AI Matching Engine Works
Here's the core architecture I ended up with after several iterations:
import openai
from dataclasses import dataclass
from typing import Optional
@dataclass
class UserProfile:
anxiety_type: str
living_space: str # 'apartment', 'house', 'shared'
mobility: str # 'low', 'medium', 'high'
sensory_sensitivity: str # 'low', 'medium', 'high'
therapeutic_goal: str
allergies: list[str]
experience_with_animals: str
@dataclass
class AnimalProfile:
species: str
breed: Optional[str]
energy_level: str
predictability: str
care_intensity: str
interaction_style: str # 'active', 'calm', 'independent'
allergy_risk: str
def generate_match_explanation(user: UserProfile, animal: AnimalProfile) -> str:
prompt = f"""
You are a certified animal-assisted therapy specialist.
User profile:
- Anxiety type: {user.anxiety_type}
- Living space: {user.living_space}
- Mobility: {user.mobility}
- Sensory sensitivity: {user.sensory_sensitivity}
- Goal: {user.therapeutic_goal}
- Allergies: {', '.join(user.allergies) if user.allergies else 'None'}
- Animal experience: {user.experience_with_animals}
Proposed animal:
- Species/Breed: {animal.species} ({animal.breed or 'mixed'})
- Energy: {animal.energy_level}
- Predictability: {animal.predictability}
- Care intensity: {animal.care_intensity}
- Interaction style: {animal.interaction_style}
Provide a warm, clinical explanation (3-4 sentences) of why this is or isn't
a good match. Be specific. Reference the user's actual profile data.
End with one concrete first-step recommendation.
"""
response = openai.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
max_tokens=200
)
return response.choices[0].message.content
# Scoring function — weighted matching
def compute_match_score(user: UserProfile, animal: AnimalProfile) -> float:
score = 0.0
weights = {
'energy_alignment': 0.25,
'sensory_fit': 0.25,
'care_capacity': 0.20,
'allergy_safety': 0.20,
'goal_alignment': 0.10
}
# Energy alignment
energy_map = {'low': 1, 'medium': 2, 'high': 3}
mobility_score = energy_map.get(user.mobility, 2)
animal_energy = energy_map.get(animal.energy_level, 2)
energy_diff = abs(mobility_score - animal_energy)
score += weights['energy_alignment'] * (1 - energy_diff / 2)
# Sensory fit (high sensitivity = prefer calm/predictable animals)
sensory_map = {'low': 3, 'medium': 2, 'high': 1}
user_sensory = sensory_map.get(user.sensory_sensitivity, 2)
animal_predictability = sensory_map.get(animal.predictability, 2)
sensory_diff = abs(user_sensory - animal_predictability)
score += weights['sensory_fit'] * (1 - sensory_diff / 2)
# Allergy safety
allergy_score = 0 if any(a in animal.allergy_risk for a in user.allergies) else 1
score += weights['allergy_safety'] * allergy_score
# Simplified care capacity check
care_map = {'low': 1, 'medium': 2, 'high': 3}
mobility_care = energy_map.get(user.mobility, 2)
animal_care = care_map.get(animal.care_intensity, 2)
care_diff = abs(mobility_care - animal_care)
score += weights['care_capacity'] * (1 - care_diff / 2)
# Goal alignment (simplified rule-based)
goal_animal_map = {
'grounding': ['dog', 'cat', 'rabbit'],
'motivation': ['dog'],
'companionship': ['dog', 'cat', 'bird'],
'routine': ['dog', 'rabbit', 'fish']
}
preferred = goal_animal_map.get(user.therapeutic_goal, [])
goal_match = 1 if animal.species in preferred else 0.5
score += weights['goal_alignment'] * goal_match
return round(score, 3)
The Unexpected Insights
1. Rabbits Are Criminally Underrated
I ran matching simulations across ~500 synthetic user profiles and rabbits showed up as the #1 match for users with:
- High sensory sensitivity
- Apartment living
- Generalized anxiety
- Low mobility
They're quiet, soft, predictable, and genuinely responsive to human presence. But nobody thinks "rabbit" when they think "therapy animal." The AI kept recommending them. The AI was right.
2. Fish Work — But Only for Specific Profiles
Fish are the dark horse. They score extremely high for:
- Routine-building (feeding schedules)
- Visual calming (proven by research on aquarium watching)
- Zero allergy risk
- Sensory predictability
For users with ADHD who need environmental calm without physical interaction, fish consistently ranked top 3.
3. "Experience with animals" is the most predictive feature
People who grew up with animals and then had none in adulthood respond much faster to reintroduction therapy. The model learned this pattern quickly — it would weight "bring back what you know" heavily for reconnection goals.
What I'd Do Differently
Use embeddings for goal-to-animal alignment — my rule-based goal map is weak. A vector similarity approach on therapy outcome literature would be much stronger.
Add a feedback loop — right now the model doesn't learn from outcomes. A simple thumbs up/down on matches after 30 days would make it dramatically better over time.
Structured outputs — GPT-4o's structured output mode would make the explanation generation more reliable and parseable.
Try It
If you're dealing with anxiety, burnout, or just want a science-backed reason to get a pet — MyPetTherapist.com does exactly this kind of matching, done properly.
And if you want to play with the matching code above, it's a solid weekend project. Add a small FastAPI wrapper, a Pydantic layer for the profiles, and you've got a deployable microservice in an afternoon.
Built with Python, OpenAI API, FastAPI. Drop questions in the comments — happy to go deeper on any part of the stack.
Top comments (0)