Beers for Citations

Reading time ~5 minutes

A reasonably well-known astrophysics professor once gave me some unsolicited advice:

“I always told people that if they cited me, I’d buy them a beer for every citation.”

He went on to say that even though he had a very well-known astrophysical relationship named after him, many more people knew him because of his open beverage offer. I thought this was a good idea, and recently I’ve been toying with the new API for the NASA/SAO Astrophysics Data Service. You can check out my code on Github. For the most recent example I’ve written a little script that will check to see if I have any new citations, and will alert me who I owe beer(s) to. Here’s the code:

# coding: utf-8

""" Beers for citations. The new underground currency. """

__author__ = "Andy Casey <acasey@mso.anu.edu.au>"

# Standard library
import httplib
import json
import os
import urllib
from collections import Counter

# Module specific
import ads

# Couple of mutable variables for the reader
author_query = "^Casey, Andrew R."
records_filename = "citations.json"

my_papers = ads.search(author_query)

# How many citations did we have last time this ran?
if not os.path.exists(records_filename):
    all_citations_last_time = {"total": 0}

else:
    with open(records_filename, "r") as fp:
        all_citations_last_time = json.load(fp)

# Build a dictionary with all of our citations
bibcodes, citations = zip(*[(paper.bibcode, paper.citation_count) 
    for paper in my_papers])

all_citations = dict(zip(bibcodes, citations))
all_citations["total"] = sum(citations)

# Check if we have more citations than last time, but only if we have run 
# this script beforehand, too. Otherwise we'll get 1,000 notifications on
# the first time the script has been run
if  all_citations["total"] > all_citations_last_time["total"] \
and len(all_citations_last_time) > 1:

    # Someone has cited us since the last time we checked.
    newly_cited_papers = {}
    for bibcode, citation_count in zip(bibcodes, citations):

        new_citations = citation_count - all_citations_last_time[bibcode]

        if new_citations > 0:
            # Who were the first authors for the new papers that cited us?
            citing_papers = ads.search("citations(bibcode:{0})"
                .format(bibcode), rows=new_citations)
            newly_cited_papers[bibcode] = [paper.author[0] for paper in citing_papers]

    # Ok, so now we have a dictionary (called 'newly_cited_papers') that contains 
    # the bibcodes and names of authors who we owe beers to. But instead, we
    # would like to know how many beers we owe, and who we owe them to.
    beers_owed = Counter(sum(newly_cited_papers.values(), []))

    # Let's not buy ourself beers.
    if my_papers[0].author[0] in beers_owed:
        del beers_owed[my_papers[0].author[0]]

    for author, num_of_beers_owed in beers_owed.iteritems():

        readable_name = " ".join([name.strip() for name in author.split(",")[::-1]])
        this_many_beers = "{0} beers".format(num_of_beers_owed) \
            if num_of_beers_owed > 1 else "a beer"
        message = "You owe {0} {1} because they just cited you!"
            .format(readable_name, this_many_beers)

        print(message)

        if not "PUSHOVER_TOKEN" in os.environ \
        or not "PUSHOVER_USER" in os.environ:
            print("No pushover.net notification sent because PUSHOVER_TOKEN or"
                " PUSHOVER_USER environment variables not found.")
            continue

        conn = httplib.HTTPSConnection("api.pushover.net:443")
        conn.request("POST", "/1/messages.json",
          urllib.urlencode({
            "token": os.environ["PUSHOVER_TOKEN"],
            "user": os.environ["PUSHOVER_USER"],
            "message": message
          }), { "Content-type": "application/x-www-form-urlencoded" })
        conn.getresponse()

else:
    print("No new citations!")

# Save these citations
with open(records_filename, "w") as fp:
    json.dump(all_citations, fp)

That script will only work if you already have a ADS 2.0 username and an API key for ADS stored in ~/.ads/dev_key. The first time you run it, you won’t get any notifications. This is just to make sure you don’t get 1,000+ notifications the first time it’s run.

In the above example, it will keep track of your citations to the records_filename. That way you can run this script as frequent as you like (e.g., daily, weekly, monthly) and it will only notice citations since the last time it was run. That lets you set up a cron job really easily, so for example – we can be notified on the first of each month automatically when we’re cited:

andycasey@moron>crontab -l
# m h  dom mon dow   command
  0 7   1   *   *    python beers-for-cites.py 

This is great, but at the moment it will just print out who we owe beer(s) to. In reality if we’re running this as a cron job then we’ll want to be notified somehow. I like to use Pushover.net to send free notifications to my devices. So I’ve created an account and an application called “Beers for Citations”, then put the application token and user as the environment variables PUSHOVER_TOKEN and PUSHOVER_USER. Now I’ll get a notification to my phone when someone cites any of my papers.

The end result looks something like this:

beers-for-citations

So there you go. Cite any of my papers and I’ll buy you a beer the next time I see you.

Making Python GUIs

Sometimes I want to make a simple (or complex) graphical user interface (GUI) for exploratory data analysis. I use Python, but there are ...… Continue reading

Hiatus

Published on September 13, 2015

Best and Brightest EMP Stars

Published on September 18, 2014