Files @ c9dd9ffb28df
Branch filter:

Location: NPO-Accounting/conservancy_beancount/tests/test_reconcile.py - annotation

bsturmfels
reconciler: Fix totals. Never consider payee if check_id in books.
d8f4eac53bb7
d8f4eac53bb7
337791827906
337791827906
337791827906
d8f4eac53bb7
32fc4517a054
32fc4517a054
32fc4517a054
32fc4517a054
32fc4517a054
a3e60c639f1d
337791827906
c9dd9ffb28df
c9dd9ffb28df
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
337791827906
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
d8f4eac53bb7
32fc4517a054
32fc4517a054
32fc4517a054
32fc4517a054
a3e60c639f1d
a3e60c639f1d
a3e60c639f1d
a3e60c639f1d
a3e60c639f1d
a3e60c639f1d
a3e60c639f1d
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
337791827906
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
c9dd9ffb28df
import datetime
import decimal
import os
import tempfile
import textwrap

from conservancy_beancount.reconcile.prototype_amex_reconciler import (
    match_statement_and_books,
    remove_payee_junk,
    date_proximity,
    remove_duplicate_words,
    payee_match,
    metadata_for_match,
    write_metadata_to_books,
    totals,
)

S1 = {
    'date': datetime.date(2022, 1, 1),
    'amount': decimal.Decimal('10.00'),
    'payee': 'Patreon         / Patreon   / 123456/ ST-A1B2C3D4G5H6       /',
    'check_id': '',
    'line': 222,
}
S2 = {
    'date': datetime.date(2022, 1, 2),
    'amount': decimal.Decimal('20.00'),
    'payee': 'BT*LINODE           PHILADELPHIA        P',
    'check_id': '',
    'line': 333,
}
S3 = {
    'date': datetime.date(2022, 1, 3),
    'amount': decimal.Decimal('30.00'),
    'payee': 'USPS PO 4067540039 0PORTLAND            OR',
    'check_id': '',
    'line': 444,
}

B1 = {
    'date': datetime.date(2022, 1, 1),
    'amount': decimal.Decimal('10.00'),
    'payee': 'Patreon',
    'check_id': '',
    'filename': '2022/imports.beancount',
    'line': 777,
    'bank_statement': '',
}
B2 = {
    'date': datetime.date(2022, 1, 2),
    'amount': decimal.Decimal('20.00'),
    'payee': 'Linode',
    'check_id': '',
    'filename': '2022/main.beancount',
    'line': 888,
    'bank_statement': "Financial/Bank-Statements/AMEX/2022-01-12_AMEX_statement.pdf"
}
B3_next_day = {
    'date': datetime.date(2022, 1, 4),
    'amount': decimal.Decimal('30.00'),
    'payee': 'USPS',
    'check_id': '',
    'filename': '2022/main.beancount',
    'line': 999,
    'bank_statement': "Financial/Bank-Statements/AMEX/2022-01-12_AMEX_statement.pdf"
}
B3_next_week = {
    'date': datetime.date(2022, 1, 10),
    'amount': decimal.Decimal('30.00'),
    'payee': 'USPS',
    'check_id': '',
    'filename': '2022/main.beancount',
    'line': 999,
    'bank_statement': "Financial/Bank-Statements/AMEX/2022-01-12_AMEX_statement.pdf"
}
B3_mismatch_amount = {
    'date': datetime.date(2022, 1, 3),
    'amount': decimal.Decimal('31.00'),
    'payee': 'USPS',
    'check_id': '',
    'filename': '2022/main.beancount',
    'line': 999,
    'bank_statement': "Financial/Bank-Statements/AMEX/2022-01-12_AMEX_statement.pdf"
}
B3_payee_mismatch_1 = {
    'date': datetime.date(2022, 1, 3),
    'amount': decimal.Decimal('30.00'),
    'payee': 'Credit X',
    'check_id': '',
    'filename': '2022/main.beancount',
    'line': 999,
    'bank_statement': "Financial/Bank-Statements/AMEX/2022-01-12_AMEX_statement.pdf"
}
B3_payee_mismatch_2 = {
    'date': datetime.date(2022, 1, 3),
    'amount': decimal.Decimal('30.00'),
    'payee': 'Credit Y',
    'check_id': '',
    'filename': '2022/main.beancount',
    'line': 999,
    'bank_statement': "Financial/Bank-Statements/AMEX/2022-01-12_AMEX_statement.pdf"
}


def test_one_exact_match():
    statement = [S1]
    books = [B1]
    assert match_statement_and_books(statement, books) == [
        ([S1], [B1], []),
    ]

def test_multiple_exact_matches():
    statement = [S1, S2]
    books = [B1, B2]
    assert match_statement_and_books(statement, books) == [
        ([S1], [B1], []),
        ([S2], [B2], []),
    ]

def test_one_mismatch():
    statement = [S1]
    books = []
    assert match_statement_and_books(statement, books) == [
        ([S1], [], ['no match']),
    ]

def test_multiple_mismatches():
    statement = [S1]
    books = [B2]
    assert match_statement_and_books(statement, books) == [
        ([S1], [], ['no match']),
        ([], [B2], ['no match']),
    ]

def test_next_day_matches():
    statement = [S3]
    books = [B3_next_day]
    assert match_statement_and_books(statement, books) == [
        ([S3], [B3_next_day], ['+/- 1 days']),
    ]

def test_next_week_matches():
    statement = [S3]
    books = [B3_next_week]
    assert match_statement_and_books(statement, books) == [
        ([S3], [B3_next_week], ['+/- 7 days']),
    ]

def test_incorrect_amount_does_not_match():
    statement = [S3]
    books = [B3_mismatch_amount]
    assert match_statement_and_books(statement, books) == [
        ([S3], [], ['no match']),
        ([], [B3_mismatch_amount], ['no match']),
    ]

def test_payee_mismatch_ok_when_only_one_that_amount_and_date():
    statement = [S3]
    books = [B3_payee_mismatch_1]
    assert match_statement_and_books(statement, books) == [
        ([S3], [B3_payee_mismatch_1], ['payee mismatch', 'only one decent match']),
    ]

def test_payee_mismatch_not_ok_when_multiple_that_amount_and_date():
    statement = [S3]
    books = [B3_payee_mismatch_1, B3_payee_mismatch_2]
    match = match_statement_and_books(statement, books)
    assert match == [
        ([S3], [], ['no match']),
        ([], [B3_payee_mismatch_1], ['no match']),
        ([], [B3_payee_mismatch_2], ['no match']),
    ]

# def test_subset_sum_with_same_date_and_payee():

def test_remove_payee_junk():
    assert remove_payee_junk('WIDGETSRUS INC PAYMENT 1') == 'WIDGETSRUS'
    assert remove_payee_junk('0000010017') == '10017'

def test_date_proximity():
    assert date_proximity(datetime.date(2021, 8, 23), datetime.date(2021, 8, 23)) == 1.0
    assert date_proximity(datetime.date(2021, 8, 23), datetime.date(2021, 8, 23) - datetime.timedelta(days=30)) == 0.5
    assert date_proximity(datetime.date(2021, 8, 23), datetime.date(2021, 8, 23) - datetime.timedelta(days=60)) == 0.0


def test_remove_duplicate_words():
    assert remove_duplicate_words('Hi Foo Kow FOO') == 'Hi Foo Kow'

def test_remove_duplicate_words():
    assert remove_duplicate_words('Hi Foo Kow FOO') == 'Hi Foo Kow'

def test_payee_matches_when_first_word_matches():
    assert payee_match('Gandi San Francisco', 'Gandi example.com renewal 1234567') == 1.0
    assert payee_match('USPS 123456789 Portland', 'USPS John Brown') == 0.8

def test_metadata_for_match(monkeypatch):
    monkeypatch.setenv('CONSERVANCY_REPOSITORY', '.')
    assert metadata_for_match(([S1], [B1], []), 'statement.pdf', 'statement.csv') == [
        ('2022/imports.beancount', 777, '    bank-statement: statement.pdf'),
        ('2022/imports.beancount', 777, '    bank-statement-csv: statement.csv:222'),
    ]

def test_no_metadata_if_no_matches():
    assert metadata_for_match(([S1], [], ['no match']), 'statement.pdf', 'statement.csv') == []
    assert metadata_for_match(([], [B1], ['no match']), 'statement.pdf', 'statement.csv') == []
    assert metadata_for_match(([S1], [B2], ['no match']), 'statement.pdf', 'statement.csv') == []

def test_write_to_books():
    books = textwrap.dedent("""\
        2021-08-16 txn "Gandi" "transfer seleniumconf.us"
          Liabilities:CreditCard:AMEX            -15.50 USD
          Expenses:Hosting                        15.50 USD""")
    f = tempfile.NamedTemporaryFile('w', delete=False)
    f.write(books)
    f.close()
    metadata = [(f.name, 2, '    bank-statement: statement.pdf')]
    write_metadata_to_books(metadata)
    with open(f.name) as f:
        output = f.read()
    assert output == textwrap.dedent("""\
        2021-08-16 txn "Gandi" "transfer seleniumconf.us"
          Liabilities:CreditCard:AMEX            -15.50 USD
            bank-statement: statement.pdf
          Expenses:Hosting                        15.50 USD""")
    os.remove(f.name)

def test_totals():
    assert totals([
        ([S1], [B1], []),
        ([S2], [], []),
        ([], [B3_next_day], []),
    ]) == (decimal.Decimal('10'), decimal.Decimal('20'), decimal.Decimal('30'))