We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
viewing history for a closed account 500s
https://app.getsentry.com/gratipay/gratipay-com/group/62465010/


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gratipay/utils/history.py`
Content:
```
1 from datetime import datetime
2 from decimal import Decimal
3 
4 from aspen import Response
5 from psycopg2 import IntegrityError
6 
7 
8 def get_end_of_year_balance(db, participant, year, current_year):
9     if year == current_year:
10         return participant.balance
11     if year < participant.claimed_time.year:
12         return Decimal('0.00')
13 
14     balance = db.one("""
15         SELECT balance
16           FROM balances_at
17          WHERE participant = %s
18            AND "at" = %s
19     """, (participant.id, datetime(year+1, 1, 1)))
20     if balance is not None:
21         return balance
22 
23     username = participant.username
24     start_balance = get_end_of_year_balance(db, participant, year-1, current_year)
25     delta = db.one("""
26         SELECT (
27                   SELECT COALESCE(sum(amount), 0) AS a
28                     FROM exchanges
29                    WHERE participant = %(username)s
30                      AND extract(year from timestamp) = %(year)s
31                      AND amount > 0
32                      AND (status is null OR status = 'succeeded')
33                ) + (
34                   SELECT COALESCE(sum(amount-fee), 0) AS a
35                     FROM exchanges
36                    WHERE participant = %(username)s
37                      AND extract(year from timestamp) = %(year)s
38                      AND amount < 0
39                      AND (status is null OR status <> 'failed')
40                ) + (
41                   SELECT COALESCE(sum(-amount), 0) AS a
42                     FROM transfers
43                    WHERE tipper = %(username)s
44                      AND extract(year from timestamp) = %(year)s
45                ) + (
46                   SELECT COALESCE(sum(amount), 0) AS a
47                     FROM transfers
48                    WHERE tippee = %(username)s
49                      AND extract(year from timestamp) = %(year)s
50                ) AS delta
51     """, locals())
52     balance = start_balance + delta
53     try:
54         db.run("""
55             INSERT INTO balances_at
56                         (participant, at, balance)
57                  VALUES (%s, %s, %s)
58         """, (participant.id, datetime(year+1, 1, 1), balance))
59     except IntegrityError:
60         pass
61     return balance
62 
63 
64 def iter_payday_events(db, participant, year=None):
65     """Yields payday events for the given participant.
66     """
67     current_year = datetime.utcnow().year
68     year = year or current_year
69 
70     username = participant.username
71     exchanges = db.all("""
72         SELECT *
73           FROM exchanges
74          WHERE participant=%(username)s
75            AND extract(year from timestamp) = %(year)s
76     """, locals(), back_as=dict)
77     transfers = db.all("""
78         SELECT *
79           FROM transfers
80          WHERE (tipper=%(username)s OR tippee=%(username)s)
81            AND extract(year from timestamp) = %(year)s
82     """, locals(), back_as=dict)
83 
84     if not (exchanges or transfers):
85         return
86 
87     if transfers:
88         yield dict(
89             kind='totals',
90             given=sum(t['amount'] for t in transfers if t['tipper'] == username and t['context'] != 'take'),
91             received=sum(t['amount'] for t in transfers if t['tippee'] == username),
92         )
93 
94     payday_dates = db.all("""
95         SELECT ts_start::date
96           FROM paydays
97       ORDER BY ts_start ASC
98     """)
99 
100     balance = get_end_of_year_balance(db, participant, year, current_year)
101     prev_date = None
102     get_timestamp = lambda e: e['timestamp']
103     events = sorted(exchanges+transfers, key=get_timestamp, reverse=True)
104     for event in events:
105 
106         event['balance'] = balance
107 
108         event_date = event['timestamp'].date()
109         if event_date != prev_date:
110             if prev_date:
111                 yield dict(kind='day-close', balance=balance)
112             day_open = dict(kind='day-open', date=event_date, balance=balance)
113             if payday_dates:
114                 while payday_dates and payday_dates[-1] > event_date:
115                     payday_dates.pop()
116                 payday_date = payday_dates[-1] if payday_dates else None
117                 if event_date == payday_date:
118                     day_open['payday_number'] = len(payday_dates) - 1
119             yield day_open
120             prev_date = event_date
121 
122         if 'fee' in event:
123             if event['amount'] > 0:
124                 kind = 'charge'
125                 if event['status'] in (None, 'succeeded'):
126                     balance -= event['amount']
127             else:
128                 kind = 'credit'
129                 if event['status'] != 'failed':
130                     balance -= event['amount'] - event['fee']
131         else:
132             kind = 'transfer'
133             if event['tippee'] == username:
134                 balance -= event['amount']
135             else:
136                 balance += event['amount']
137         event['kind'] = kind
138 
139         yield event
140 
141     yield dict(kind='day-close', balance=balance)
142 
143 
144 def export_history(participant, year, mode, key, back_as='namedtuple', require_key=False):
145     db = participant.db
146     params = dict(username=participant.username, year=year)
147     out = {}
148     if mode == 'aggregate':
149         out['given'] = lambda: db.all("""
150             SELECT tippee, sum(amount) AS amount
151               FROM transfers
152              WHERE tipper = %(username)s
153                AND extract(year from timestamp) = %(year)s
154           GROUP BY tippee
155         """, params, back_as=back_as)
156         out['taken'] = lambda: db.all("""
157             SELECT tipper AS team, sum(amount) AS amount
158               FROM transfers
159              WHERE tippee = %(username)s
160                AND context = 'take'
161                AND extract(year from timestamp) = %(year)s
162           GROUP BY tipper
163         """, params, back_as=back_as)
164     else:
165         out['exchanges'] = lambda: db.all("""
166             SELECT timestamp, amount, fee, status, note
167               FROM exchanges
168              WHERE participant = %(username)s
169                AND extract(year from timestamp) = %(year)s
170           ORDER BY timestamp ASC
171         """, params, back_as=back_as)
172         out['given'] = lambda: db.all("""
173             SELECT timestamp, tippee, amount, context
174               FROM transfers
175              WHERE tipper = %(username)s
176                AND extract(year from timestamp) = %(year)s
177           ORDER BY timestamp ASC
178         """, params, back_as=back_as)
179         out['taken'] = lambda: db.all("""
180             SELECT timestamp, tipper AS team, amount
181               FROM transfers
182              WHERE tippee = %(username)s
183                AND context = 'take'
184                AND extract(year from timestamp) = %(year)s
185           ORDER BY timestamp ASC
186         """, params, back_as=back_as)
187         out['received'] = lambda: db.all("""
188             SELECT timestamp, amount, context
189               FROM transfers
190              WHERE tippee = %(username)s
191                AND context NOT IN ('take', 'take-over')
192                AND extract(year from timestamp) = %(year)s
193           ORDER BY timestamp ASC
194         """, params, back_as=back_as)
195 
196     if key:
197         try:
198             return out[key]()
199         except KeyError:
200             raise Response(400, "bad key `%s`" % key)
201     elif require_key:
202         raise Response(400, "missing `key` parameter")
203     else:
204         return {k: v() for k, v in out.items()}
205
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gratipay/utils/history.py b/gratipay/utils/history.py
--- a/gratipay/utils/history.py
+++ b/gratipay/utils/history.py
@@ -8,7 +8,8 @@
 def get_end_of_year_balance(db, participant, year, current_year):
     if year == current_year:
         return participant.balance
-    if year < participant.claimed_time.year:
+    start = participant.claimed_time or participant.ctime
+    if year < start.year:
         return Decimal('0.00')
 
     balance = db.one("""

{"golden_diff": "diff --git a/gratipay/utils/history.py b/gratipay/utils/history.py\n--- a/gratipay/utils/history.py\n+++ b/gratipay/utils/history.py\n@@ -8,7 +8,8 @@\n def get_end_of_year_balance(db, participant, year, current_year):\n     if year == current_year:\n         return participant.balance\n-    if year < participant.claimed_time.year:\n+    start = participant.claimed_time or participant.ctime\n+    if year < start.year:\n         return Decimal('0.00')\n \n     balance = db.one(\"\"\"\n", "issue": "viewing history for a closed account 500s\nhttps://app.getsentry.com/gratipay/gratipay-com/group/62465010/\n\n", "before_files": [{"content": "from datetime import datetime\nfrom decimal import Decimal\n\nfrom aspen import Response\nfrom psycopg2 import IntegrityError\n\n\ndef get_end_of_year_balance(db, participant, year, current_year):\n    if year == current_year:\n        return participant.balance\n    if year < participant.claimed_time.year:\n        return Decimal('0.00')\n\n    balance = db.one(\"\"\"\n        SELECT balance\n          FROM balances_at\n         WHERE participant = %s\n           AND \"at\" = %s\n    \"\"\", (participant.id, datetime(year+1, 1, 1)))\n    if balance is not None:\n        return balance\n\n    username = participant.username\n    start_balance = get_end_of_year_balance(db, participant, year-1, current_year)\n    delta = db.one(\"\"\"\n        SELECT (\n                  SELECT COALESCE(sum(amount), 0) AS a\n                    FROM exchanges\n                   WHERE participant = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n                     AND amount > 0\n                     AND (status is null OR status = 'succeeded')\n               ) + (\n                  SELECT COALESCE(sum(amount-fee), 0) AS a\n                    FROM exchanges\n                   WHERE participant = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n                     AND amount < 0\n                     AND (status is null OR status <> 'failed')\n               ) + (\n                  SELECT COALESCE(sum(-amount), 0) AS a\n                    FROM transfers\n                   WHERE tipper = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n               ) + (\n                  SELECT COALESCE(sum(amount), 0) AS a\n                    FROM transfers\n                   WHERE tippee = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n               ) AS delta\n    \"\"\", locals())\n    balance = start_balance + delta\n    try:\n        db.run(\"\"\"\n            INSERT INTO balances_at\n                        (participant, at, balance)\n                 VALUES (%s, %s, %s)\n        \"\"\", (participant.id, datetime(year+1, 1, 1), balance))\n    except IntegrityError:\n        pass\n    return balance\n\n\ndef iter_payday_events(db, participant, year=None):\n    \"\"\"Yields payday events for the given participant.\n    \"\"\"\n    current_year = datetime.utcnow().year\n    year = year or current_year\n\n    username = participant.username\n    exchanges = db.all(\"\"\"\n        SELECT *\n          FROM exchanges\n         WHERE participant=%(username)s\n           AND extract(year from timestamp) = %(year)s\n    \"\"\", locals(), back_as=dict)\n    transfers = db.all(\"\"\"\n        SELECT *\n          FROM transfers\n         WHERE (tipper=%(username)s OR tippee=%(username)s)\n           AND extract(year from timestamp) = %(year)s\n    \"\"\", locals(), back_as=dict)\n\n    if not (exchanges or transfers):\n        return\n\n    if transfers:\n        yield dict(\n            kind='totals',\n            given=sum(t['amount'] for t in transfers if t['tipper'] == username and t['context'] != 'take'),\n            received=sum(t['amount'] for t in transfers if t['tippee'] == username),\n        )\n\n    payday_dates = db.all(\"\"\"\n        SELECT ts_start::date\n          FROM paydays\n      ORDER BY ts_start ASC\n    \"\"\")\n\n    balance = get_end_of_year_balance(db, participant, year, current_year)\n    prev_date = None\n    get_timestamp = lambda e: e['timestamp']\n    events = sorted(exchanges+transfers, key=get_timestamp, reverse=True)\n    for event in events:\n\n        event['balance'] = balance\n\n        event_date = event['timestamp'].date()\n        if event_date != prev_date:\n            if prev_date:\n                yield dict(kind='day-close', balance=balance)\n            day_open = dict(kind='day-open', date=event_date, balance=balance)\n            if payday_dates:\n                while payday_dates and payday_dates[-1] > event_date:\n                    payday_dates.pop()\n                payday_date = payday_dates[-1] if payday_dates else None\n                if event_date == payday_date:\n                    day_open['payday_number'] = len(payday_dates) - 1\n            yield day_open\n            prev_date = event_date\n\n        if 'fee' in event:\n            if event['amount'] > 0:\n                kind = 'charge'\n                if event['status'] in (None, 'succeeded'):\n                    balance -= event['amount']\n            else:\n                kind = 'credit'\n                if event['status'] != 'failed':\n                    balance -= event['amount'] - event['fee']\n        else:\n            kind = 'transfer'\n            if event['tippee'] == username:\n                balance -= event['amount']\n            else:\n                balance += event['amount']\n        event['kind'] = kind\n\n        yield event\n\n    yield dict(kind='day-close', balance=balance)\n\n\ndef export_history(participant, year, mode, key, back_as='namedtuple', require_key=False):\n    db = participant.db\n    params = dict(username=participant.username, year=year)\n    out = {}\n    if mode == 'aggregate':\n        out['given'] = lambda: db.all(\"\"\"\n            SELECT tippee, sum(amount) AS amount\n              FROM transfers\n             WHERE tipper = %(username)s\n               AND extract(year from timestamp) = %(year)s\n          GROUP BY tippee\n        \"\"\", params, back_as=back_as)\n        out['taken'] = lambda: db.all(\"\"\"\n            SELECT tipper AS team, sum(amount) AS amount\n              FROM transfers\n             WHERE tippee = %(username)s\n               AND context = 'take'\n               AND extract(year from timestamp) = %(year)s\n          GROUP BY tipper\n        \"\"\", params, back_as=back_as)\n    else:\n        out['exchanges'] = lambda: db.all(\"\"\"\n            SELECT timestamp, amount, fee, status, note\n              FROM exchanges\n             WHERE participant = %(username)s\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n        out['given'] = lambda: db.all(\"\"\"\n            SELECT timestamp, tippee, amount, context\n              FROM transfers\n             WHERE tipper = %(username)s\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n        out['taken'] = lambda: db.all(\"\"\"\n            SELECT timestamp, tipper AS team, amount\n              FROM transfers\n             WHERE tippee = %(username)s\n               AND context = 'take'\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n        out['received'] = lambda: db.all(\"\"\"\n            SELECT timestamp, amount, context\n              FROM transfers\n             WHERE tippee = %(username)s\n               AND context NOT IN ('take', 'take-over')\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n\n    if key:\n        try:\n            return out[key]()\n        except KeyError:\n            raise Response(400, \"bad key `%s`\" % key)\n    elif require_key:\n        raise Response(400, \"missing `key` parameter\")\n    else:\n        return {k: v() for k, v in out.items()}\n", "path": "gratipay/utils/history.py"}], "after_files": [{"content": "from datetime import datetime\nfrom decimal import Decimal\n\nfrom aspen import Response\nfrom psycopg2 import IntegrityError\n\n\ndef get_end_of_year_balance(db, participant, year, current_year):\n    if year == current_year:\n        return participant.balance\n    start = participant.claimed_time or participant.ctime\n    if year < start.year:\n        return Decimal('0.00')\n\n    balance = db.one(\"\"\"\n        SELECT balance\n          FROM balances_at\n         WHERE participant = %s\n           AND \"at\" = %s\n    \"\"\", (participant.id, datetime(year+1, 1, 1)))\n    if balance is not None:\n        return balance\n\n    username = participant.username\n    start_balance = get_end_of_year_balance(db, participant, year-1, current_year)\n    delta = db.one(\"\"\"\n        SELECT (\n                  SELECT COALESCE(sum(amount), 0) AS a\n                    FROM exchanges\n                   WHERE participant = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n                     AND amount > 0\n                     AND (status is null OR status = 'succeeded')\n               ) + (\n                  SELECT COALESCE(sum(amount-fee), 0) AS a\n                    FROM exchanges\n                   WHERE participant = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n                     AND amount < 0\n                     AND (status is null OR status <> 'failed')\n               ) + (\n                  SELECT COALESCE(sum(-amount), 0) AS a\n                    FROM transfers\n                   WHERE tipper = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n               ) + (\n                  SELECT COALESCE(sum(amount), 0) AS a\n                    FROM transfers\n                   WHERE tippee = %(username)s\n                     AND extract(year from timestamp) = %(year)s\n               ) AS delta\n    \"\"\", locals())\n    balance = start_balance + delta\n    try:\n        db.run(\"\"\"\n            INSERT INTO balances_at\n                        (participant, at, balance)\n                 VALUES (%s, %s, %s)\n        \"\"\", (participant.id, datetime(year+1, 1, 1), balance))\n    except IntegrityError:\n        pass\n    return balance\n\n\ndef iter_payday_events(db, participant, year=None):\n    \"\"\"Yields payday events for the given participant.\n    \"\"\"\n    current_year = datetime.utcnow().year\n    year = year or current_year\n\n    username = participant.username\n    exchanges = db.all(\"\"\"\n        SELECT *\n          FROM exchanges\n         WHERE participant=%(username)s\n           AND extract(year from timestamp) = %(year)s\n    \"\"\", locals(), back_as=dict)\n    transfers = db.all(\"\"\"\n        SELECT *\n          FROM transfers\n         WHERE (tipper=%(username)s OR tippee=%(username)s)\n           AND extract(year from timestamp) = %(year)s\n    \"\"\", locals(), back_as=dict)\n\n    if not (exchanges or transfers):\n        return\n\n    if transfers:\n        yield dict(\n            kind='totals',\n            given=sum(t['amount'] for t in transfers if t['tipper'] == username and t['context'] != 'take'),\n            received=sum(t['amount'] for t in transfers if t['tippee'] == username),\n        )\n\n    payday_dates = db.all(\"\"\"\n        SELECT ts_start::date\n          FROM paydays\n      ORDER BY ts_start ASC\n    \"\"\")\n\n    balance = get_end_of_year_balance(db, participant, year, current_year)\n    prev_date = None\n    get_timestamp = lambda e: e['timestamp']\n    events = sorted(exchanges+transfers, key=get_timestamp, reverse=True)\n    for event in events:\n\n        event['balance'] = balance\n\n        event_date = event['timestamp'].date()\n        if event_date != prev_date:\n            if prev_date:\n                yield dict(kind='day-close', balance=balance)\n            day_open = dict(kind='day-open', date=event_date, balance=balance)\n            if payday_dates:\n                while payday_dates and payday_dates[-1] > event_date:\n                    payday_dates.pop()\n                payday_date = payday_dates[-1] if payday_dates else None\n                if event_date == payday_date:\n                    day_open['payday_number'] = len(payday_dates) - 1\n            yield day_open\n            prev_date = event_date\n\n        if 'fee' in event:\n            if event['amount'] > 0:\n                kind = 'charge'\n                if event['status'] in (None, 'succeeded'):\n                    balance -= event['amount']\n            else:\n                kind = 'credit'\n                if event['status'] != 'failed':\n                    balance -= event['amount'] - event['fee']\n        else:\n            kind = 'transfer'\n            if event['tippee'] == username:\n                balance -= event['amount']\n            else:\n                balance += event['amount']\n        event['kind'] = kind\n\n        yield event\n\n    yield dict(kind='day-close', balance=balance)\n\n\ndef export_history(participant, year, mode, key, back_as='namedtuple', require_key=False):\n    db = participant.db\n    params = dict(username=participant.username, year=year)\n    out = {}\n    if mode == 'aggregate':\n        out['given'] = lambda: db.all(\"\"\"\n            SELECT tippee, sum(amount) AS amount\n              FROM transfers\n             WHERE tipper = %(username)s\n               AND extract(year from timestamp) = %(year)s\n          GROUP BY tippee\n        \"\"\", params, back_as=back_as)\n        out['taken'] = lambda: db.all(\"\"\"\n            SELECT tipper AS team, sum(amount) AS amount\n              FROM transfers\n             WHERE tippee = %(username)s\n               AND context = 'take'\n               AND extract(year from timestamp) = %(year)s\n          GROUP BY tipper\n        \"\"\", params, back_as=back_as)\n    else:\n        out['exchanges'] = lambda: db.all(\"\"\"\n            SELECT timestamp, amount, fee, status, note\n              FROM exchanges\n             WHERE participant = %(username)s\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n        out['given'] = lambda: db.all(\"\"\"\n            SELECT timestamp, tippee, amount, context\n              FROM transfers\n             WHERE tipper = %(username)s\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n        out['taken'] = lambda: db.all(\"\"\"\n            SELECT timestamp, tipper AS team, amount\n              FROM transfers\n             WHERE tippee = %(username)s\n               AND context = 'take'\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n        out['received'] = lambda: db.all(\"\"\"\n            SELECT timestamp, amount, context\n              FROM transfers\n             WHERE tippee = %(username)s\n               AND context NOT IN ('take', 'take-over')\n               AND extract(year from timestamp) = %(year)s\n          ORDER BY timestamp ASC\n        \"\"\", params, back_as=back_as)\n\n    if key:\n        try:\n            return out[key]()\n        except KeyError:\n            raise Response(400, \"bad key `%s`\" % key)\n    elif require_key:\n        raise Response(400, \"missing `key` parameter\")\n    else:\n        return {k: v() for k, v in out.items()}\n", "path": "gratipay/utils/history.py"}]}