Call your mum #
Living in a different country to your family and friends is not easy. But there is something remarkable about the fact that I am among the first generation of human beings who have been able to live in a different country to their families and use technology to maintain meaningful relationships with them. Less than a hundred years ago the only way I could have communicated with my family was by writing a letter and sending it in the post. Even twenty years ago international phone calls were very expensive and internet communication still in its infancy. What an unbelievable difference. I am also acutely aware that this new era of hyperconnectivity has come at a cost. We are more connected than we have ever been: for better (we can always pick up a device and contact anyone we want) and for worse (we are never alone and never out of reach). I am the first to admit that finding the balance in this tradeoff eludes me.
Today, it is absolutely possible to keep communication channels with a family on the other side of the world open and meaningful and nurtured. The question I have come to grapple with is more about how to make a habit of nurturing them, because distance and the “out of sight, out of mind” rule makes it very easy to get caught up in your own local life and forget.
Solution using Python and org mode #
Since I’ve moved almost everything in my life to org mode, I figured my attempts to manage personal relationships could also be moved there, making my old Python scripts obselete.
There are solutions for this; things like Monica: personal CRMs. But I don’t really feel the need to obsessively track interactions with my “network” but just want to be reminded every now and then to check in with people that are important to me.
The basic idea is:
- Define a configuration file containing information about the contacts I need a little help with.
- Write a Python script to generate TODOs for a time range into the future and save them to a file.
- Generate one final TODO reminding me to rerun the script to regenerate after the time has run out. This will prevent the org file from getting unusably big by only keeping the upcoming (say) twelve months in the file.
- Read this
people.org
file into a separate section in my custom agenda.
For each contact I define a “contact frequency” and I would really like the TODOs to be spread out with a little variance around the calendar. There is also the B S
command in the agenda (reschedule randomly) which maybe if you generated exactly enough tasks with generic dates you could use and have more of the logic in org.
Script #
The script I wrote looks like this:
import json
import argparse
import numpy as np
import datetime as dt
class Contact():
def __init__(self,
name: str,
birthday: dt.datetime,
contact_frequency: dt.timedelta):
self._name = name
self._birthday = birthday
self._contact_frequency = contact_frequency
@property
def name(self):
return self._name
@property
def birthday(self) -> str:
return self._birthday
def is_birthday(self, date: dt.datetime) -> bool:
return date.month == self._birthday.month and date.day == self._birthday.day
@property
def contact_frequency(self) -> int:
return self._contact_frequency
class ContactTodo():
def __init__(self,
contact: Contact):
self._contact = contact
def get_todo(self,
date: dt.datetime,
lvl: int=1):
org_prefix = '*' * lvl
org_date = date.strftime('%Y-%m-%d %a')
indent = ' ' * (lvl + 1)
birthday_infix = ''': it\'s their birthday''' if self._contact.is_birthday(date) else ''
schedule = f'SCHEDULED: <{org_date}>'
return org_prefix + f' TODO Get in touch with {self._contact.name}{birthday_infix}\n{indent}{schedule}\n'
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-d', nargs='?', const=365, type=int)
parser.add_argument('-o', nargs='?', const='people.org', type=str)
args = parser.parse_args()
people = json.load(open('emacs/people.json', 'r'))
contacts = []
todos = []
today = dt.datetime.now()
for contact in people:
contacts.append(Contact(name=contact.get('short_name',
contact.get('name')),
birthday=dt.datetime.strptime(contact.get('birthday',
dt.datetime(1900, 1, 1)),
'%Y-%m-%d'),
contact_frequency=dt.timedelta(days=contact.get('contact_frequency', 30))))
for contact in contacts:
for date in [today + dt.timedelta(days=int(
np.random.normal(0 + contact.contact_frequency.days * i,
np.sqrt(contact.contact_frequency.days / 2)))) \
for i in range(0, args.d // contact.contact_frequency.days)]:
if not contact.is_birthday(date):
todos.append(ContactTodo(contact).get_todo(date))
birthdays = [date for date in [today + dt.timedelta(days = i) for i in range(0, args.d)] if contact.is_birthday(date)]
for birthday in birthdays:
todos.append(ContactTodo(contact).get_todo(birthday))
end_date = (today + dt.timedelta(days=args.d - 1)).strftime('%Y-%m-%d %a')
todos.append(f'* TODO Regenerate people.org\n SCHEDULED: <{end_date}>')
f = open(args.o, 'w')
f.writelines(todos)
if __name__ == '__main__':
main()
where entries in the configuration look like
{
"name": "Important Person",
"short_name": "VIP",
"birthday": "1901-01-01",
"contact_frequency": 7
}
I then run this by creating a Makefile containing
call_your_mum:
python3 emacs/call_your_mum.py -d 365 -o ~/Dropbox/org/people.org
Finally, the People
section gets added to my custom agenda:
(tags-todo "+SCHEDULED=\"<today>\""
((org-agenda-files (quote ("~/Dropbox/org/people.org")))
(org-agenda-overriding-header "People\n")
(org-agenda-prefix-format " ")))
(Old) solution using just Python #
This was my solution at the beginning of my time living overseas, when I still had to work on maintaining those connections. It tends to come a lot more naturally these days so I don’t have the program running anymore. I rented a DigitalOcean server for $10 a month. This is a computer that sits in a server farm somewhere in the U.S., a low-spec, not particularly powerful computer, with access to the internet. I can log into this computer remotely and do whatever I want on it. So I wrote a program, a little Python script which runs every minute, with a configuration file called person.json
with a list of all the people I want to stay in touch with, their time zones, and how frequently I want a reminder to get in touch with them.
people_list = load_people('people.json')
current_time = datetime.datetime.utcnow()
hour = current_time.hour
hour_local = hour + local_time
if hour_local >= min_contact_hour
and hour_local <= max_contact_hour:
for person in people_list:
X = np.random.uniform(0, 100 / person.freq())
if X <= 1:
person._is_contacted = True
for person in people_list:
if person._is_contacted == True
and (current_time - person.last_contact()).days > 0:
person_hour_local = (hour + person.timezone()) % 24
if person_hour_local >= min_contact_hour
and person_hour_local <= max_contact_hour:
send_message(current_time, person)
person._last_contact = current_time
elif person.is_birthday()
and (current_time - person.last_contact()).days > 0:
send_message(current_time, person, True)
person._last_contact = current_time
save_people(people_list, 'people.json')
Every minute, this script runs, generates some random numbers, and sends me an email if it thinks I should get in touch with someone. The nice thing about the random numbers (generated above by the line np.random.uniform
which is telling Python to generate a random number from an interval whose length varies depending on how frequently I want to contact the person) is that the messages are generated at different times, so I’m not always saying “hi” to the same person at the same time every week.
This script didn’t do the hard work. The hard work is keeping everyone in your mind, thinking about them, keeping the relationships alive. But it definitely helped me to form the habit, and reminded me when I was busy to take a couple of minutes out of the day to call my mum.