#!/usr/bin/env python3 """ Stale Contacts Finder Identifies contacts that haven't been reached out to recently. """ import json from datetime import datetime, timedelta from pathlib import Path BRAIN_ROOT = Path(__file__).parent.parent.parent # Thresholds by circle (in days) THRESHOLDS = { 'inner': 14, # 2 weeks 'active': 30, # 1 month 'network': 60, # 2 months 'dormant': 180 # 6 months (for potential reactivation) } def load_jsonl(filepath): """Load JSONL file, skipping schema lines.""" items = [] if not filepath.exists(): return items with open(filepath, 'r') as f: for line in f: line = line.strip() if not line: continue try: data = json.loads(line) if '_schema' not in data: items.append(data) except json.JSONDecodeError: continue return items def days_since(date_str): """Calculate days since a date string.""" if not date_str: return 999 # Very stale if no date try: date = datetime.fromisoformat(date_str.replace('Z', '+00:00')) return (datetime.now(date.tzinfo) - date).days except (ValueError, TypeError): return 999 def find_stale_contacts(): """Find contacts needing outreach.""" contacts = load_jsonl(BRAIN_ROOT / 'network' / 'contacts.jsonl') stale = { 'urgent': [], # Way overdue 'due': [], # Due for contact 'coming_up': [] # Getting close } for contact in contacts: circle = contact.get('circle', 'network') threshold = THRESHOLDS.get(circle, 60) days = days_since(contact.get('last_contact')) contact_info = { 'name': contact.get('name', 'Unknown'), 'circle': circle, 'days_since': days, 'threshold': threshold, 'handle': contact.get('handle', ''), 'notes': contact.get('notes', '')[:100] } if days > threshold * 1.5: stale['urgent'].append(contact_info) elif days > threshold: stale['due'].append(contact_info) elif days > threshold * 0.75: stale['coming_up'].append(contact_info) return stale def generate_report(): """Generate stale contacts report.""" stale = find_stale_contacts() output = f""" # Stale Contacts Report Generated: {datetime.now().isoformat()} ## Urgently Overdue ({len(stale['urgent'])}) """ if stale['urgent']: for c in sorted(stale['urgent'], key=lambda x: -x['days_since']): output += f"- **{c['name']}** ({c['circle']}) - {c['days_since']} days since contact\n" if c['handle']: output += f" {c['handle']}\n" else: output += "None! You're on top of things.\n" output += f""" ## Due for Contact ({len(stale['due'])}) """ if stale['due']: for c in sorted(stale['due'], key=lambda x: -x['days_since']): output += f"- {c['name']} ({c['circle']}) - {c['days_since']} days\n" else: output += "None due right now.\n" output += f""" ## Coming Up ({len(stale['coming_up'])}) """ if stale['coming_up']: for c in sorted(stale['coming_up'], key=lambda x: -x['days_since']): output += f"- {c['name']} ({c['circle']}) - {c['days_since']} days (threshold: {c['threshold']})\n" else: output += "No contacts approaching threshold.\n" output += """ ## Suggested Actions 1. Send a quick "thinking of you" message to urgent contacts 2. Schedule calls with due inner-circle contacts 3. Engage with content from coming-up contacts ## Thresholds - Inner circle: Every 2 weeks - Active: Every month - Network: Every 2 months - Dormant: Quarterly check for reactivation """ return output if __name__ == '__main__': print(generate_report())