AI-based fundraising and donor management system
Nonprofits lose 60-70% of donors after the first donation due to a lack of personalized communication. An AI system predicts repeat donation propensity, optimizes the timing and channel of donations, and personalizes requested amounts.
Propensity to Donate Model
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from anthropic import Anthropic
import json
class DonorPropensityModel:
"""Предсказание вероятности следующего пожертвования"""
def __init__(self):
self.model = GradientBoostingClassifier(
n_estimators=200, learning_rate=0.05, max_depth=4, random_state=42
)
def build_rfm_features(self, donor_history: pd.DataFrame) -> pd.DataFrame:
"""RFM + дополнительные признаки для фандрайзинга"""
today = pd.Timestamp.now()
donor_stats = donor_history.groupby('donor_id').agg(
recency=('donation_date', lambda x: (today - x.max()).days),
frequency=('donation_id', 'count'),
monetary=('amount', 'sum'),
avg_donation=('amount', 'mean'),
last_amount=('amount', 'last'),
max_donation=('amount', 'max'),
first_donation_days=('donation_date', lambda x: (today - x.min()).days),
).reset_index()
# Тренд: растут ли суммы?
def donation_trend(group):
if len(group) < 3:
return 0
x = np.arange(len(group))
y = group['amount'].values
return np.polyfit(x, y, 1)[0] # Slope
trends = donor_history.groupby('donor_id').apply(donation_trend)
donor_stats['donation_trend'] = donor_stats['donor_id'].map(trends).fillna(0)
# Сезонность: давал ли в конце года (высокий сезон для НКО)?
year_end = donor_history[donor_history['donation_date'].dt.month.isin([11, 12])]
year_end_donors = set(year_end['donor_id'])
donor_stats['gives_year_end'] = donor_stats['donor_id'].isin(year_end_donors).astype(int)
return donor_stats
def predict_next_gift(self, donors: pd.DataFrame) -> pd.DataFrame:
"""Скоринг вероятности следующего пожертвования (90 дней)"""
features = self.build_rfm_features(donors)
feature_cols = ['recency', 'frequency', 'monetary', 'avg_donation',
'donation_trend', 'gives_year_end']
X = features[feature_cols].fillna(0)
probs = self.model.predict_proba(X)[:, 1]
features['propensity_score'] = probs
features['ask_amount'] = self._suggest_ask_amount(features)
features['donor_tier'] = pd.cut(
probs,
bins=[0, 0.2, 0.5, 0.75, 1.0],
labels=['lapsed', 'occasional', 'regular', 'loyal']
)
return features
def _suggest_ask_amount(self, donors: pd.DataFrame) -> pd.Series:
"""Предлагаемая сумма запроса: слегка выше средней"""
return (donors['avg_donation'] * 1.2).round(-1) # Округляем до десятков
class PersonalizedDonorOutreach:
"""Персонализированные обращения к донорам"""
def __init__(self):
self.llm = Anthropic()
def generate_appeal(self, donor: dict,
campaign: dict,
ask_amount: float) -> dict:
"""Персонализированное письмо для донора"""
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=350,
messages=[{
"role": "user",
"content": f"""Write a personalized fundraising appeal in Russian.
Donor profile:
- Name: {donor.get('first_name', 'Друг')}
- Giving history: {donor.get('frequency', 1)} gifts, average ${donor.get('avg_donation', 50):.0f}
- Last gift: {donor.get('last_amount', 50)} {donor.get('recency', 30)} days ago
- Main interests: {donor.get('cause_interests', ['general support'])}
Campaign: {campaign.get('name')}
Campaign story: {campaign.get('impact_story', '')[:200]}
Ask amount: ${ask_amount:.0f}
Write:
1. Personal opening (acknowledge their history)
2. Impact story (specific, emotional)
3. Clear ask with specific amount and its impact
4. Warm closing
Max 200 words. No generic phrases."""
}]
)
subject_response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=50,
messages=[{
"role": "user",
"content": f"Write a compelling email subject line in Russian for this fundraising appeal. Max 50 chars. Campaign: {campaign.get('name')}. Donor's interests: {donor.get('cause_interests', [])}."
}]
)
return {
'subject': subject_response.content[0].text.strip(),
'body': response.content[0].text,
'ask_amount': ask_amount,
'donor_id': donor.get('id')
}
def determine_best_channel(self, donor: dict) -> str:
"""Канал коммуникации на основе истории отклика"""
response_rates = donor.get('channel_response_rates', {})
if not response_rates:
return 'email'
return max(response_rates, key=response_rates.get)
Campaign Optimization
Key metrics for AI fundraising: Donor Retention Rate (target: 45%+ for mature nonprofits), Cost Per Dollar Raised (target: $0.10-0.20), and Average Gift Size. Personalized appeals with the right request amount increase the average gift by 15-25%—people give more when offered a specific amount with a detailed description of its impact.







