Compare commits

...

10 Commits

Author SHA1 Message Date
Eli Ribble 9783960857 Add some changes.
I no longer know what they mean, I found them many years later.
2024-09-15 14:22:22 -07:00
Eli Ribble 79b2bf0f1f Add a bunch of files I don't recognize
I'm cleaning up my machine, these were here, I'm not sure which ones
matter
2024-09-15 14:20:25 -07:00
Eli Ribble d2c56d8a98 Properly translate account type when creating accounts 2016-08-11 11:00:23 -06:00
Eli Ribble a23c0e2759 Fix display of transaction post on accounts detail
Bad copy-paste, I assume
2016-08-11 10:59:43 -06:00
Eli Ribble dd1706c70f Remove my SGML and OFX parsers
I'm going to use ofxparse. Promise.

This marks a really serious break with the automatic downloader code
because now we don't even have the code that it depended on for parsing
2016-08-11 10:58:25 -06:00
Eli Ribble 393ef748cc Get OFX file upload working through celery
I switched to using ofxparse because my own parser wasn't up to snuff on
handling the download file that I got from my bank and I got sick of
maintaining my own because SGML is an ugly mess

This commit means that my automatic download won't work any more because
it's expecting to pass data through my own parser, which we're not going
to do any more. That's okay because my bank's OFX integration is
actually down anyways
2016-08-11 10:54:29 -06:00
Eli Ribble 438c073e0f Add simple function for uploading OFX-based transactions
The OFX direct connection for my bank isn't working right now. I have a
support ticket in. Until then I'm going to work on doing a manual upload
and parse of transactions from this OFX document
2016-08-10 17:20:00 -06:00
Eli Ribble 8d0d8d72b1 Factor out the update button into its own small template
For code reuse, you know
2016-08-10 16:58:55 -06:00
Eli Ribble 23ca01f1b1 Fix query for current accounts
I had accidentally crafted the query so that we got something like an
outer join when I actually wanted an inner join. Figuring out how to get
this in SQLAlchemy took a bit of work, but I got it :)
2016-07-20 16:48:51 -06:00
Eli Ribble b9dcf0e9a9 Add script to import data from ofxhome into our DB
Makes my life a little easier to have all of these ofxsources so that I
can test out different sources and start building out support for
communicating with these different institutions

I'm throwing away quite a bit of the data I have but that's okay for now
until I know that I need them
2016-07-20 15:44:57 -06:00
40 changed files with 5549 additions and 334 deletions

4
bin/backend Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env python3
import vanth.celery
vanth.celery.run()

23
bin/import-sources Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
import argparse
import vanth.main
import vanth.ofxhome
import vanth.platform.ofxsource
def main():
vanth.main.setup_logging()
config = vanth.main.get_config()
vanth.main.create_db_connection(config)
parser = argparse.ArgumentParser()
parser.add_argument('dbfile', help='The database file of XML dumped from the open OFX Home DB')
args = parser.parse_args()
with open(args.dbfile, 'r') as f:
data = vanth.ofxhome.parse(f.read())
vanth.platform.ofxsource.ensure_exist(data)
if __name__ == '__main__':
main()

8
bin/test-celery Executable file
View File

@ -0,0 +1,8 @@
#!/usr/bin/env python3
import vanth.celery
def main():
vanth.celery.my_print.delay()
if __name__ == '__main__':
main()

7
bin/test-login Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env python3
import requests
import sys
payload = {'username': 'eliribble', 'password': sys.argv[1]}
response = requests.post('http://www.vanth.com/login/', data=payload)
print(response.status_code, response.text)

11
bin/test-update Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
import vanth.main
import vanth.download
def main():
config = vanth.main.get_config()
vanth.main.create_db_connection(config)
print(vanth.download.do_all())
if __name__ == '__main__':
main()

6
bootstrap-theme.min.css vendored Normal file

File diff suppressed because one or more lines are too long

6
bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

7
bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
celeryconfig.py Normal file
View File

@ -0,0 +1,7 @@
BROKER_URL = 'amqp://guest:guest@localhost:5672//'
CELERY_IMPORTS = ('vanth.celery_worker', 'vanth.celery')
CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
CELERY_TASK_SERIALIZER = 'json'
CELERY_ALWAYS_EAGER = True

6
jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

82
ofx.py Normal file
View File

@ -0,0 +1,82 @@
import datetime
import pprint
import logging
import ofxtools
import io
import requests
import sys
LOGGER = logging.getLogger(__name__)
def parse(content):
tree = ofxtools.Parser.OFXTree()
tree.parse(io.StringIO(content))
return tree
def get_transactions(tree):
return [parse_transaction(transaction) for transaction in tree.findall('.//STMTTRN')]
def _parse_datetime(timestamp):
return datetime.datetime.strptime(timestamp, '%Y%m%d%H%M%S.000')
def parse_transaction(transaction):
return {
'available' : _parse_datetime(transaction.find('./DTAVAIL').text),
'amount' : float(transaction.find('./TRNAMT').text),
'id' : transaction.find('./FITID').text,
'memo' : transaction.find('./MEMO').text,
'name' : transaction.find('./NAME').text,
'posted' : _parse_datetime(transaction.find('./DTPOSTED').text),
'type' : transaction.find('./TRNTYPE').text,
}
def main():
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
with open(sys.argv[1], 'r') as f:
content = f.read()
tree = parse(content)
transactions = get_transactions(tree)
for transaction in transactions[:10]:
if transaction['amount'] > 0:
continue
pprint.pprint(transaction)
payload = {
'amount' : transaction['amount'],
'currency' : {
'code' : 'USD',
'rate' : 1.0,
'fixed' : False,
},
'date' : transaction['posted'].date().isoformat(),
'desc' : transaction['name'],
'account' : '2553488',
'category' : '50448856',
'tags' : [],
'extra' : {
'available' : transaction['available'].date().isoformat(),
'fid' : transaction['id'],
'memo' : transaction['memo'],
'type' : transaction['type'],
},
}
continue
response = requests.post('https://api.toshl.com/entries', json=payload, auth=('4cc0e2c6-7b91-4198-9759-df7f873c4d0d4f44474d-e0e8-4e40-b755-7dfb71955cb2', ''))
if not response.ok:
print(response.status_code)
print(response.text)
return
else:
print("uploaded")
pprint.pprint(transactions[:10])
return
root = tree.getroot()
pprint(root)
def print_tree(root, indent=0):
print("{}{}: {}".format('\t'*indent, root.tag, root.text if root.text else ''))
for child in root.getchildren():
pprint(child, indent=indent+1)
if __name__ == '__main__':
main()

View File

@ -101,6 +101,7 @@ def main():
'chryso==1.7', 'chryso==1.7',
'Flask==0.10.1', 'Flask==0.10.1',
'flask-login==0.3.2', 'flask-login==0.3.2',
'ofxparse==0.15',
'sepiida==5.27', 'sepiida==5.27',
], ],
extras_require = { extras_require = {

View File

@ -1,6 +1,7 @@
{% extends 'layout.html' %} {% extends 'layout.html' %}
{% block main_content %} {% block main_content %}
<h1>{{ account.name }}</h1> <h1>{{ account.name }}</h1>
{% include 'update_button.html' %}
<table class="table"> <table class="table">
<tr><td>Current balance</td><td>{{ account.balance }}</td></tr> <tr><td>Current balance</td><td>{{ account.balance }}</td></tr>
<tr><td>Last updated</td><td>{{ account.last_updated }}</td></tr> <tr><td>Last updated</td><td>{{ account.last_updated }}</td></tr>
@ -15,7 +16,7 @@
<td>{{ record.name }}</a></td> <td>{{ record.name }}</a></td>
<td>{{ record.type }}</td> <td>{{ record.type }}</td>
<td>{{ record.amount }}</td> <td>{{ record.amount }}</td>
<td>{{ account.posted }}</td> <td>{{ record.posted }}</td>
<td> <td>
<form method="POST" action="/update/"> <form method="POST" action="/update/">
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input> <input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
@ -28,4 +29,10 @@
{% else %} {% else %}
<p>This account does not have any transactions yet</p> <p>This account does not have any transactions yet</p>
{% endif %} {% endif %}
<form method="POST" action="/transactions/" enctype="multipart/form-data">
<p>Want to upload your own transactions? Cool. Do it here.</p>
<input type="hidden" name="account_uuid" value="{{ account.uuid }}">
<input type="file" name="transactions" id="transactions">
<input type="submit">
</form>
{% endblock %} {% endblock %}

View File

@ -11,10 +11,7 @@
<td>{{ account.source.name }}</td> <td>{{ account.source.name }}</td>
<td>{{ account.last_updated }}</td> <td>{{ account.last_updated }}</td>
<td> <td>
<form method="POST" action="/update/"> {% include 'update_button.html' %}
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
<input type="submit" value="Update" class="btn btn-primary"></input>
</form>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

5
templates/error.html Normal file
View File

@ -0,0 +1,5 @@
{% extends 'base.html' %}
{% block body %}
<h1>You hit an error</h1>
<p>{{ error }}</p>
{% endblock %}

View File

@ -0,0 +1,4 @@
<form method="POST" action="/update/">
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
<input type="submit" value="Update" class="btn btn-primary"></input>
</form>

52
test.py Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python3
import requests
headers = {'Content-Type': 'application/x-ofx'}
payload = (
"OFXHEADER:100\r\n"
"DATA:OFXSGML\r\n"
"VERSION:102\r\n"
"SECURITY:NONE\r\n"
"ENCODING:USASCII\r\n"
"CHARSET:1252\r\n"
"COMPRESSION:NONE\r\n"
"OLDFILEUID:NONE\r\n"
"NEWFILEUID:NONE\r\n"
"\r\n"
"<OFX>\r\n"
"<SIGNONMSGSRQV1>\r\n"
"<SONRQ>\r\n"
"<DTCLIENT>20140427213800.000[-7:MST]\r\n"
"<USERID>1242940\r\n"
"<USERPASS>0732\r\n"
"<LANGUAGE>ENG\r\n"
"<FI>\r\n"
"<ORG>America First Credit Union\r\n"
"<FID>54324\r\n"
"</FI>\r\n"
"<APPID>QWIN\r\n"
"<APPVER>1200\r\n"
"</SONRQ>\r\n"
"</SIGNONMSGSRQV1>\r\n"
"<BANKMSGSRQV1>\r\n"
"<STMTTRNRQ>\r\n"
"<TRNUID>00000000\r\n"
"<STMTRQ>\r\n"
"<BANKACCTFROM>\r\n"
"<BANKID>324377516\r\n"
"<ACCTID>124294-0.9:CHK\r\n"
"<ACCTTYPE>CHECKING\r\n"
"</BANKACCTFROM>\r\n"
"<INCTRAN>\r\n"
"<DTSTART>20160101\r\n"
"<INCLUDE>Y\r\n"
"</INCTRAN>\r\n"
"</STMTRQ>\r\n"
"</STMTTRNRQ>\r\n"
"</BANKMSGSRQV1>\r\n"
"</OFX>\r\n")
#print(payload)
response = requests.post('https://ofx.americafirst.com/', data=payload, headers=headers)
print(response.status_code)
print(response.headers)
print(response.text)

9
test_parse.py Normal file
View File

@ -0,0 +1,9 @@
import ofxparse
import codecs
import io
with open('tests/files/transactions-2.ofx', 'rb') as f:
ofx = ofxparse.OfxParser.parse(f)
import pdb;pdb.set_trace()
print(ofx)

View File

@ -0,0 +1,182 @@
OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<DTSERVER>20160810225540.951
<LANGUAGE>ENG
<DTPROFUP>20050531060000.000
<FI>
<ORG>AFCU
<FID>1001
</FI>
<INTU.BID>54324
<INTU.USERID>1234567
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>0
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<STMTRS>
<CURDEF>USD
<BANKACCTFROM>
<BANKID>324377516
<ACCTID>1234567~9
<ACCTTYPE>CHECKING
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>20160801060000.000
<DTEND>20160810060000.000
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20160802120000.000
<TRNAMT>8410.20
<FITID>0006882
<NAME>AUTOMATIC DEPOSIT, AUTHENTISE IN
<MEMO>AUTOMATIC DEPOSIT, AUTHENTISE INC DIRECT DEP PPD
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160802120000.000
<TRNAMT>-95.00
<FITID>0006883
<NAME>AUTOMATIC WITHDRAWAL, AMERICA FI
<MEMO>AUTOMATIC WITHDRAWAL, AMERICA FIRST EXTERNAL TRANSFER, CHRISTINE RIBBLE WEB
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160802120000.000
<TRNAMT>-200.00
<FITID>0006884
<NAME>AUTOMATIC WITHDRAWAL, UESP INVES
<MEMO>AUTOMATIC WITHDRAWAL, UESP INVESTMENT WEB(R )
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160802120000.000
<TRNAMT>-1943.73
<FITID>0006885
<NAME>AUTOMATIC WITHDRAWAL, SUNTRUST M
<MEMO>AUTOMATIC WITHDRAWAL, SUNTRUST MORTG MTG PMTS WEB(R )
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160803120000.000
<TRNAMT>-90.00
<FITID>0006886
<NAME>ONLINE BANKING FUNDS TRANSFER TO
<MEMO>ONLINE BANKING FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.9 LUCIANA CIALABRINI
</STMTTRN>
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20160805120000.000
<TRNAMT>519.68
<FITID>0006887
<NAME>ONLINE CHECK DEPOSIT
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160805120000.000
<TRNAMT>-97.00
<FITID>0006888
<NAME>FUNDS TRANSFER TO SHARE ACCOUNT:
<MEMO>FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.9 LUCIANA CIALABRINI
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160805120000.000
<TRNAMT>-100.00
<FITID>0006889
<NAME>FUNDS TRANSFER TO SHARE ACCOUNT:
<MEMO>FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.1 LUCIANA CIALABRINI
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160805120000.000
<TRNAMT>-519.68
<FITID>0006890
<NAME>ONLINE CHECK DEPOSIT
</STMTTRN>
<STMTTRN>
<TRNTYPE>CREDIT
<DTPOSTED>20160805120000.000
<TRNAMT>519.68
<FITID>0006891
<NAME>ONLINE CHECK DEPOSIT
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160805120000.000
<TRNAMT>-5097.73
<FITID>0006892
<NAME>AUTOMATIC WITHDRAWAL, DISCOVER E
<MEMO>AUTOMATIC WITHDRAWAL, DISCOVER E-PAYMENT WEB(S )
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160806120000.000
<TRNAMT>-300.00
<FITID>0006893
<NAME>WITHDRAWAL
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160808120000.000
<TRNAMT>-211.00
<FITID>0006894
<NAME>ONLINE BANKING FUNDS TRANSFER TO
<MEMO>ONLINE BANKING FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.9 LUCIANA CIALABRINI
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT
<DTPOSTED>20160808120000.000
<TRNAMT>-293.00
<FITID>0006895
<NAME>AUTOMATIC WITHDRAWAL, CHALLENGER
<MEMO>AUTOMATIC WITHDRAWAL, CHALLENGER SCHOOTUITION PPD
</STMTTRN>
<STMTTRN>
<TRNTYPE>CHECK
<DTPOSTED>20160809120000.000
<TRNAMT>-200.00
<FITID>0006896
<CHECKNUM>191
<NAME>CHECK # 191
</STMTTRN>
<STMTTRN>
<TRNTYPE>CHECK
<DTPOSTED>20160810120000.000
<TRNAMT>-270.00
<FITID>0006897
<CHECKNUM>192
<NAME>CHECK # 192
</STMTTRN>
</BANKTRANLIST>
<LEDGERBAL>
<BALAMT>26882.49
<DTASOF>20160810225540.951
</LEDGERBAL>
<AVAILBAL>
<BALAMT>26882.49
<DTASOF>20160810225540.951
</AVAILBAL>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>

View File

@ -0,0 +1,29 @@
OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0
<SEVERITY>INFO
</STATUS>
<DTSERVER>20160810225540.951
<LANGUAGE>ENG
<DTPROFUP>20050531060000.000
<FI>
<ORG>AFCU
<FID>1001
</FI>
<INTU.BID>54324
<INTU.USERID>1242940
</SONRS>
</SIGNONMSGSRSV1>
</OFX>

View File

@ -1,83 +0,0 @@
import datetime
import vanth.ofx
def MST():
return datetime.timezone(datetime.timedelta(hours=-7), 'MST')
def MDT():
return datetime.timezone(datetime.timedelta(hours=-6), 'MDT')
def test_query_transactions(mocker):
institution = {
'bankid' : "1234567",
'fid' : "12345",
'name' : "AFCU",
}
account = {
"account_id" : "123456-0.9:CHK",
"user_id" : "123456789",
"password" : "1234",
"type" : "checking",
}
with mocker.patch('vanth.ofx.now', return_value='20160102030405.000[-7:MST]'):
results = vanth.ofx.query_transactions(institution, account, start=datetime.date(2016, 1, 2))
with open('tests/files/query_transactions.ofx', 'rb') as f:
expected = f.read().decode('utf-8')
assert results == expected
def test_parse():
with open('tests/files/transactions.ofx', 'rb') as f:
transactions = f.read().decode('utf-8')
document = vanth.ofx.parse(transactions)
assert document.header == {
'CHARSET' : '1252',
'COMPRESSION' : 'NONE',
'DATA' : 'OFXSGML',
'ENCODING' : 'USASCII',
'NEWFILEUID' : 'NONE',
'OFXHEADER' : '100',
'OLDFILEUID' : 'NONE',
'SECURITY' : 'NONE',
'VERSION' : '102'
}
assert document.body.status.code == '0'
assert document.body.status.severity == 'INFO'
assert document.body.status.message == 'The operation succeeded.'
assert document.body.statement.status.code == '0'
assert document.body.statement.status.severity == 'INFO'
assert document.body.statement.status.message is None
assert document.body.statement.transactions.currency == 'USD'
assert document.body.statement.transactions.account.accountid == '123456-0.9:CHK'
assert document.body.statement.transactions.account.bankid == '324377516'
assert document.body.statement.transactions.account.type == 'CHECKING'
assert document.body.statement.transactions.start == datetime.datetime(2015, 12, 31, 17, 0, tzinfo=MST())
assert document.body.statement.transactions.end == datetime.datetime(2016, 6, 22, 11, 12, 42, tzinfo=MDT())
expected_items = [{
'amount' : -50.19,
'available' : datetime.datetime(2015, 12, 31, 12),
'id' : '0006547',
'memo' : 'POINT OF SALE PURCHASE #0006547',
'name' : 'UT LEHI COSTCO WHSE #0733',
'posted' : datetime.datetime(2015, 12, 31, 12),
'type' : 'POS',
},{
'amount' : -79.64,
'available' : datetime.datetime(2015, 12, 31, 12),
'id' : '0006548',
'memo' : '#0006548',
'name' : 'Payment to PACIFICORP ONLIN',
'posted' : datetime.datetime(2015, 12, 31, 12),
'type' : 'PAYMENT',
},{
'amount' : 0.84,
'available' : datetime.datetime(2015, 12, 31, 12),
'id' : '0006549',
'memo' : 'ANNUAL PERCENTAGE YIELD EARNED IS .05% #0006549',
'name' : 'DIVIDEND FOR 12/01/15 - 12/31/1',
'posted' : datetime.datetime(2015, 12, 31, 12),
'type' : 'INT',
}]
items = [dict(item) for item in document.body.statement.transactions.items]
assert items == expected_items

View File

@ -1,17 +0,0 @@
import vanth.sgml
def child_values(node):
return [(child.name, child.value) for child in node.children]
def test_siblings():
result = vanth.sgml.parse("<A><B><C>1<D>2<E>3</B></A>")
assert result.name == 'A'
assert child_values(result['B']) == [('C', '1'), ('D', '2'), ('E', '3')]
def test_closing():
result = vanth.sgml.parse("<A><B><C>1</B><D><E>2</D></A>")
assert result.name == 'A'
assert child_values(result) == [('B', ''), ('D', '')]
assert child_values(result['B']) == [('C', '1')]
assert child_values(result['D']) == [('E', '2')]

4434
unknown/api.php?dump=yes Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<institution id="533">
<name>America First Credit Union</name>
<fid>54324</fid>
<org>America First Credit Union</org>
<url>https://ofx.americafirst.com</url>
<ofxfail>0</ofxfail>
<sslfail>0</sslfail>
<lastofxvalidation>2016-05-18 01:02:37</lastofxvalidation>
<lastsslvalidation>2016-05-18 01:04:01</lastsslvalidation>
<profile addr1="PO Box 9199" city="Ogden" state="UT" postalcode="84409" country="USA" csphone="800.999.3961" tsphone="866.224.2158" url="http://www.americafirst.com" email="support@americafirst.com" signonmsgset="true" bankmsgset="true" creditcardmsgset="true"/>
</institution>

43
unknown/bad.ofx Normal file
View File

@ -0,0 +1,43 @@
OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<OFX>
<SIGNONMSGSRQV1>
<SONRQ>
<DTCLIENT>20160621180047.000[-7:MST]
<USERID>1242940
<USERPASS>0732
<LANGUAGE>ENG
<FI>
<ORG>America First Credit Union
<FID>54324
</FI>
<APPID>QWIN
<APPVER>1200
</SONRQ>
</SIGNONMSGSRQV1>
<BANKMSGSRQV1>
<STMTTRNRQ>
<TRNUID>00000000
<STMTRQ>
<BANKACCTFROM>
<BANKID>324377516
<ACCTID>124294-0.9:CHK
<ACCTTYPE>CHECKING
</BANKACCTFROM>
<INCTRAN>
<DTSTART>20140101
<INCLUDE>Y
</INCTRAN>
</STMTRQ>
</STMTTRNRQ>
</BANKMSGSRQV1>
</OFX>

43
unknown/good.ofx Normal file
View File

@ -0,0 +1,43 @@
OFXHEADER:100
DATA:OFXSGML
VERSION:102
SECURITY:NONE
ENCODING:USASCII
CHARSET:1252
COMPRESSION:NONE
OLDFILEUID:NONE
NEWFILEUID:NONE
<OFX>
<SIGNONMSGSRQV1>
<SONRQ>
<DTCLIENT>20140427213800.000[-7:MST]
<USERID>1242940
<USERPASS>0732
<LANGUAGE>ENG
<FI>
<ORG>America First Credit Union
<FID>54324
</FI>
<APPID>QWIN
<APPVER>1200
</SONRQ>
</SIGNONMSGSRQV1>
<BANKMSGSRQV1>
<STMTTRNRQ>
<TRNUID>00000000
<STMTRQ>
<BANKACCTFROM>
<BANKID>324377516
<ACCTID>124294-0.9:CHK
<ACCTTYPE>CHECKING
</BANKACCTFROM>
<INCTRAN>
<DTSTART>20160101
<INCLUDE>Y
</INCTRAN>
</STMTRQ>
</STMTTRNRQ>
</BANKMSGSRQV1>
</OFX>

407
unknown/institutions.xml Normal file
View File

@ -0,0 +1,407 @@
<?xml version="1.0" encoding="utf-8"?>
<institutionlist>
<institutionid name="121 Financial Credit Union" id="666"/>
<institutionid name="1st Advantage FCU" id="542"/>
<institutionid name="5 Star Bank" id="774"/>
<institutionid name="66 Federal Credit Union" id="553"/>
<institutionid name="A. G. Edwards and Sons, Inc." id="491"/>
<institutionid name="Abbott Laboratories Employee CU" id="667"/>
<institutionid name="ABNB Federal Credit Union" id="530"/>
<institutionid name="Achieva Credit Union" id="668"/>
<institutionid name="Addison Avenue Federal Credit Union" id="550"/>
<institutionid name="Advantis Credit Union" id="782"/>
<institutionid name="Affinity Plus Federal Credit Union" id="732"/>
<institutionid name="Affinity Plus Federal Credit Union-New" id="810"/>
<institutionid name="AIM Investments" id="497"/>
<institutionid name="Alaska Air Visa (Bank of America)" id="796"/>
<institutionid name="Allegiance Credit Union" id="531"/>
<institutionid name="Alpine Banks of Colorado" id="643"/>
<institutionid name="AltaOne" id="772"/>
<institutionid name="AltaOne Federal Credit Union" id="490"/>
<institutionid name="AmegyBank" id="786"/>
<institutionid name="America First Credit Union" id="533"/>
<institutionid name="American Century Investments" id="739"/>
<institutionid name="American Express Card" id="424"/>
<institutionid name="American Funds" id="590"/>
<institutionid name="American National Bank" id="669"/>
<institutionid name="Ameriprise Financial Services, Inc." id="489"/>
<institutionid name="Amplify Federal Credit Union" id="788"/>
<institutionid name="Andrews Federal Credit Union" id="670"/>
<institutionid name="APCO EMPLOYEES CREDIT UNION" id="521"/>
<institutionid name="Apple FCU" id="746"/>
<institutionid name="Ariel Mutual Funds" id="566"/>
<institutionid name="Arizona Federal Credit Union" id="637"/>
<institutionid name="Arizona State Credit Union" id="785"/>
<institutionid name="Ascentra Credit Union" id="423"/>
<institutionid name="AT&amp;T Universal Card" id="427"/>
<institutionid name="AXA Equitable" id="763"/>
<institutionid name="B-M S Federal Credit Union" id="505"/>
<institutionid name="BancFirst" id="644"/>
<institutionid name="Bancorpsouth" id="803"/>
<institutionid name="Bank of America" id="639"/>
<institutionid name="Bank of America (All except CA, WA,&amp;ID)" id="472"/>
<institutionid name="Bank of America (California)" id="635"/>
<institutionid name="Bank of America (Formerly Fleet)" id="674"/>
<institutionid name="Bank of America - 5959" id="765"/>
<institutionid name="Bank of America - access.ofx" id="790"/>
<institutionid name="Bank Of America(All except CA,WA,&amp;ID " id="661"/>
<institutionid name="Bank of George" id="733"/>
<institutionid name="Bank of Internet, USA" id="787"/>
<institutionid name="Bank of Stockton" id="429"/>
<institutionid name="Bank of Tampa, The" id="522"/>
<institutionid name="Bank of the Cascades" id="430"/>
<institutionid name="Bank of the West" id="656"/>
<institutionid name="Bank One" id="428"/>
<institutionid name="Bank One (Chicago)" id="672"/>
<institutionid name="Bank One (Michigan and Florida)" id="673"/>
<institutionid name="Bank-Fund Staff FCU" id="520"/>
<institutionid name="BankBoston PC Banking" id="675"/>
<institutionid name="bankfinancial" id="762"/>
<institutionid name="Baton Rouge City Parish Emp FCU" id="576"/>
<institutionid name="BB&amp;T" id="475"/>
<institutionid name="Belmont Savings Bank" id="775"/>
<institutionid name="Bernstein Global Wealth Mgmt" id="605"/>
<institutionid name="Beverly Co-Operative Bank" id="676"/>
<institutionid name="Billings Federal Credit Union" id="603"/>
<institutionid name="Boeing Employees Credit Union" id="647"/>
<institutionid name="Bofi federal bank" id="798"/>
<institutionid name="Bossier Federal Credit Union" id="611"/>
<institutionid name="California Bank&amp;Trust" id="487"/>
<institutionid name="Cambridge Portuguese Credit Union" id="677"/>
<institutionid name="Cambridge Savings Bank" id="760"/>
<institutionid name="Camino FCU" id="580"/>
<institutionid name="Campus USA Credit Union" id="546"/>
<institutionid name="Capital One" id="628"/>
<institutionid name="Capital One 360" id="783"/>
<institutionid name="Capital One Bank" id="631"/>
<institutionid name="Capital One Bank (after 12-15-13)" id="802"/>
<institutionid name="Capital One Bank - 2" id="648"/>
<institutionid name="Capitol Federal Savings Bank" id="789"/>
<institutionid name="Cedar Point Federal Credit Union" id="523"/>
<institutionid name="CenterState Bank" id="773"/>
<institutionid name="Centra Credit Union" id="431"/>
<institutionid name="Centra Credit Union2" id="662"/>
<institutionid name="Central Bank Utah" id="820"/>
<institutionid name="Central Florida Educators FCU" id="486"/>
<institutionid name="Central Maine FCU" id="543"/>
<institutionid name="Centura Bank" id="432"/>
<institutionid name="Century Federal Credit Union" id="529"/>
<institutionid name="Charles Schwab Bank, N.A." id="578"/>
<institutionid name="Charles Schwab Retirement" id="729"/>
<institutionid name="Charles Schwab Retirement Plan Services" id="730"/>
<institutionid name="Charles Schwab&amp;Co., INC" id="433"/>
<institutionid name="Chase (credit card) " id="636"/>
<institutionid name="Chemical Bank" id="747"/>
<institutionid name="Chesterfield Federal Credit Union" id="545"/>
<institutionid name="Chicago Patrolmens FCU" id="517"/>
<institutionid name="Citadel FCU" id="477"/>
<institutionid name="Citi Credit Card" id="629"/>
<institutionid name="Citi Personal Wealth Management" id="671"/>
<institutionid name="Citibank" id="678"/>
<institutionid name="Citizens Bank" id="664"/>
<institutionid name="Citizens Bank - Business" id="528"/>
<institutionid name="Citizens Bank - Consumer" id="527"/>
<institutionid name="Citizens National Bank" id="768"/>
<institutionid name="Clearview Federal Credit Union" id="478"/>
<institutionid name="Collegedale Credit Union" id="496"/>
<institutionid name="Colonial Bank" id="436"/>
<institutionid name="Columbia Credit Union" id="541"/>
<institutionid name="Comerica Bank" id="437"/>
<institutionid name="Commerce Bank" id="640"/>
<institutionid name="Commerce Bank NJ, PA, NY&amp;DE" id="438"/>
<institutionid name="Commerce Bank, NA" id="439"/>
<institutionid name="Commercial Federal Bank" id="440"/>
<institutionid name="Community 1st Credit Union" id="738"/>
<institutionid name="Community Bank, N.A." id="679"/>
<institutionid name="Community First Credit Union" id="592"/>
<institutionid name="Community Resource Bank" id="600"/>
<institutionid name="CompassPC" id="824"/>
<institutionid name="COMSTAR FCU" id="441"/>
<institutionid name="Consumers Credit Union" id="680"/>
<institutionid name="Continental Federal Credit Union" id="555"/>
<institutionid name="CPM Federal Credit Union" id="681"/>
<institutionid name="Credit Suisse Securities USA LLC" id="753"/>
<institutionid name="Credit Union 1 - IL" id="610"/>
<institutionid name="Credit Union ONE" id="618"/>
<institutionid name="Cyprus Federal Credit Union" id="569"/>
<institutionid name="D. A. Davidson" id="805"/>
<institutionid name="DATCU" id="682"/>
<institutionid name="Day Air Credit Union" id="585"/>
<institutionid name="Delta Community Credit Union" id="573"/>
<institutionid name="Denali Alaskan FCU" id="443"/>
<institutionid name="Denver Community Federal Credit Union" id="683"/>
<institutionid name="Desert Schools Federal Credit Union" id="645"/>
<institutionid name="Discover Bank" id="726"/>
<institutionid name="Discover Card" id="444"/>
<institutionid name="Discover Platinum" id="684"/>
<institutionid name="Dodge&amp;Cox Funds" id="621"/>
<institutionid name="Dominion Credit Union" id="561"/>
<institutionid name="Dreyfus" id="445"/>
<institutionid name="Dupaco Community Credit Union" id="519"/>
<institutionid name="DuPont Community Credit Union" id="485"/>
<institutionid name="E*TRADE" id="446"/>
<institutionid name="EAB" id="685"/>
<institutionid name="Eastern Bank" id="447"/>
<institutionid name="EDS Credit Union" id="448"/>
<institutionid name="Educational Employees CU Fresno" id="492"/>
<institutionid name="Edward Jones" id="608"/>
<institutionid name="Elevations Credit Union" id="727"/>
<institutionid name="Elevations Credit Union IB WC-DC" id="752"/>
<institutionid name="Envision Credit Union" id="540"/>
<institutionid name="FAA Credit Union" id="686"/>
<institutionid name="FAA Technical Center FCU" id="583"/>
<institutionid name="Fairwinds Credit Union" id="687"/>
<institutionid name="Fall River Municipal CU" id="559"/>
<institutionid name="FedChoice FCU" id="688"/>
<institutionid name="Fidelity Investments" id="449"/>
<institutionid name="Fidelity NetBenefits" id="558"/>
<institutionid name="Fifth Third Bancorp" id="450"/>
<institutionid name="Finance Center FCU (IN)" id="535"/>
<institutionid name="Financial Center CU" id="548"/>
<institutionid name="First Alliance Credit Union" id="602"/>
<institutionid name="First Citizens" id="690"/>
<institutionid name="First Citizens Bank - NC, VA, WV" id="480"/>
<institutionid name="First Clearing, LLC" id="689"/>
<institutionid name="First Command Bank" id="766"/>
<institutionid name="First Commonwealth FCU" id="488"/>
<institutionid name="First Community FCU" id="515"/>
<institutionid name="First Florida Credit Union" id="612"/>
<institutionid name="First Hawaiian Bank" id="691"/>
<institutionid name="First Internet Bank of Indiana" id="642"/>
<institutionid name="First Interstate Bank" id="693"/>
<institutionid name="First National Bank (Texas)" id="655"/>
<institutionid name="First National Bank of St. Louis" id="692"/>
<institutionid name="First Republic Bank" id="770"/>
<institutionid name="First Southwest Company" id="620"/>
<institutionid name="First Tech Credit Union" id="451"/>
<institutionid name="First Tech Federal Credit Union" id="731"/>
<institutionid name="First Tennessee" id="795"/>
<institutionid name="Firstar" id="494"/>
<institutionid name="FirstBank of Colorado" id="554"/>
<institutionid name="FivePoint Credit Union" id="599"/>
<institutionid name="Flagstar Bank" id="784"/>
<institutionid name="Florida Telco CU" id="484"/>
<institutionid name="Fort Knox Federal Credit Union" id="536"/>
<institutionid name="Fort Stewart GeorgiaFCU" id="506"/>
<institutionid name="Franklin Templeton Investments" id="734"/>
<institutionid name="Fremont Bank" id="556"/>
<institutionid name="GCS Federal Credit Union" id="498"/>
<institutionid name="Goldman Sachs" id="694"/>
<institutionid name="Haverhill Bank" id="757"/>
<institutionid name="Hawaii State FCU" id="588"/>
<institutionid name="Hawaiian Tel Federal Credit Union" id="549"/>
<institutionid name="Hawthorne Credit Union" id="493"/>
<institutionid name="Hewitt Associates LLC" id="572"/>
<institutionid name="HFS Federal Credit Union" id="562"/>
<institutionid name="Home Federal Savings Bank(MN/IA)" id="594"/>
<institutionid name="Hudson Valley FCU" id="695"/>
<institutionid name="Hughes Federal Credit Union" id="745"/>
<institutionid name="Huntington National Bank" id="574"/>
<institutionid name="IBM Southeast Employees Federal Credit Union" id="696"/>
<institutionid name="Iinvestor360" id="792"/>
<institutionid name="ING DIRECT" id="658"/>
<institutionid name="ING DIRECT (Canada)" id="421"/>
<institutionid name="ING Institutional Plan Services " id="735"/>
<institutionid name="Insight CU" id="697"/>
<institutionid name="Institution For Savings" id="816"/>
<institutionid name="International Bank of Commerce" id="743"/>
<institutionid name="IronStone Bank" id="563"/>
<institutionid name="J.P. Morgan" id="700"/>
<institutionid name="J.P. Morgan Clearing Corp." id="701"/>
<institutionid name="J.P. Morgan Private Banking" id="740"/>
<institutionid name="Janney Montgomery Scott LLC" id="698"/>
<institutionid name="Janus" id="615"/>
<institutionid name="JPMorgan Chase Bank" id="435"/>
<institutionid name="JPMorgan Chase Bank (Texas)" id="434"/>
<institutionid name="JPMorgan Retirement Plan Services" id="617"/>
<institutionid name="JSC Federal Credit Union" id="699"/>
<institutionid name="Kennedy Space Center FCU" id="501"/>
<institutionid name="KeyBank" id="453"/>
<institutionid name="Kinecta Federal Credit Union" id="646"/>
<institutionid name="Kirtland Federal Credit Union" id="544"/>
<institutionid name="Kitsap Community Credit Union" id="728"/>
<institutionid name="La Banque Postale" id="813"/>
<institutionid name="Landings Credit Union" id="822"/>
<institutionid name="Las Colinas FCU" id="524"/>
<institutionid name="LaSalle Bank Midwest" id="455"/>
<institutionid name="LaSalle Bank NA" id="474"/>
<institutionid name="Local Government Federal Credit Union" id="748"/>
<institutionid name="Los Alamos National Bank" id="476"/>
<institutionid name="M &amp; T Bank" id="702"/>
<institutionid name="Mainline National Bank" id="663"/>
<institutionid name="Marquette Banks" id="703"/>
<institutionid name="Mayo Employees Federal Credit Union" id="598"/>
<institutionid name="McCoy Federal Credit Union" id="525"/>
<institutionid name="Mellon Bank" id="454"/>
<institutionid name="Mercantile Brokerage Services" id="461"/>
<institutionid name="Mercer" id="704"/>
<institutionid name="Merck Sharp&amp;Dohme FCU" id="609"/>
<institutionid name="Merrill Lynch Online Payment" id="705"/>
<institutionid name="Merrill Lynch&amp;Co., Inc." id="510"/>
<institutionid name="Metro Bank" id="654"/>
<institutionid name="Michigan State University Federal CU" id="649"/>
<institutionid name="Mission Federal Credit Union" id="758"/>
<institutionid name="Missoula Federal Credit Union" id="706"/>
<institutionid name="Monterey Credit Union" id="804"/>
<institutionid name="Morgan Stanley (Smith Barney)" id="707"/>
<institutionid name="Morgan Stanley ClientServ" id="500"/>
<institutionid name="Morgan Stanley ClientServ - Quicken Win Format" id="806"/>
<institutionid name="Motorola Employees Credit Union" id="534"/>
<institutionid name="Mountain America Credit Union" id="657"/>
<institutionid name="MTC Federal Credit Union" id="593"/>
<institutionid name="Municipal Employees Credit Union of Baltimore, Inc." id="584"/>
<institutionid name="Mutual Bank" id="809"/>
<institutionid name="myStreetscape" id="495"/>
<institutionid name="Nantucket Bank" id="456"/>
<institutionid name="National City" id="627"/>
<institutionid name="National Penn Bank" id="457"/>
<institutionid name="Navy Army Federal Credit Union" id="551"/>
<institutionid name="NetxClient UAT" id="761"/>
<institutionid name="Nevada Federal Credit Union" id="552"/>
<institutionid name="Nevada State Bank - New" id="458"/>
<institutionid name="Nevada State Bank - OLD" id="708"/>
<institutionid name="New England Federal Credit Union" id="709"/>
<institutionid name="North Carolina State Employees Credit Union" id="742"/>
<institutionid name="North Carolina State Employees' Credit Union" id="632"/>
<institutionid name="North Country FCU" id="754"/>
<institutionid name="NorthEast Alliance FCU" id="613"/>
<institutionid name="Northern Trust - Banking" id="481"/>
<institutionid name="Northern Trust - Investments" id="507"/>
<institutionid name="Northwest Community CU" id="741"/>
<institutionid name="Norwest" id="710"/>
<institutionid name="Novartis Federal Credit Union" id="581"/>
<institutionid name="nuVision Financial FCU" id="821"/>
<institutionid name="NW Preferred Federal Credit Union" id="579"/>
<institutionid name="OCTFCU" id="587"/>
<institutionid name="Old National Bank" id="526"/>
<institutionid name="Oppenheimer &amp; Co. Inc." id="711"/>
<institutionid name="OptionsXpress, Inc" id="565"/>
<institutionid name="Oregon College Savings Plan" id="712"/>
<institutionid name="Oregon Community Credit Union" id="781"/>
<institutionid name="Patelco CU" id="460"/>
<institutionid name="Patriots Federal Credit Union" id="596"/>
<institutionid name="Peninsula Community Federal Credit Union" id="557"/>
<institutionid name="Pennsylvania State Employees Credit Union" id="814"/>
<institutionid name="Penson Financial Services" id="570"/>
<institutionid name="Picatinny Federal Credit Union" id="508"/>
<institutionid name="PNC Bank" id="634"/>
<institutionid name="PNC Banking Online" id="818"/>
<institutionid name="PNC Online Banking" id="817"/>
<institutionid name="Premier America Credit Union" id="764"/>
<institutionid name="Premier Members FCU" id="823"/>
<institutionid name="Prudential Retirement" id="567"/>
<institutionid name="PSECU" id="539"/>
<institutionid name="RaboBank America" id="744"/>
<institutionid name="RBC Dain Rauscher" id="713"/>
<institutionid name="Red Crown Federal Credit Union" id="504"/>
<institutionid name="Redstone Federal Credit Union" id="633"/>
<institutionid name="Regions Bank" id="462"/>
<institutionid name="Reliant Community Credit Union" id="595"/>
<institutionid name="Robert W. Baird &amp; Co." id="714"/>
<institutionid name="Royce&amp;Associates" id="589"/>
<institutionid name="SAC FEDERAL CREDIT UNION" id="509"/>
<institutionid name="Sacramento Credit Union" id="651"/>
<institutionid name="Safe Credit Union - OFX Beta" id="422"/>
<institutionid name="SafeAmerica Credit Union" id="597"/>
<institutionid name="Salt Lake City Credit Union" id="619"/>
<institutionid name="Sandia Laboratory Federal Credit Union" id="780"/>
<institutionid name="Santa Barbara Bank &amp; Trust" id="659"/>
<institutionid name="Schools Financial Credit Union" id="577"/>
<institutionid name="Schwab Retirement Plan Services" id="750"/>
<institutionid name="Scottrade Brokerage" id="808"/>
<institutionid name="Scottrade, Inc." id="623"/>
<institutionid name="Sears Card" id="715"/>
<institutionid name="Securities America" id="641"/>
<institutionid name="Security 1st FCU" id="601"/>
<institutionid name="ShareBuilder" id="614"/>
<institutionid name="Sierra Central Credit Union" id="502"/>
<institutionid name="Signal Financial Federal Credit Union" id="518"/>
<institutionid name="Silver State Schools CU" id="624"/>
<institutionid name="Siouxland Federal Credit Union" id="606"/>
<institutionid name="Smith Barney - Investments" id="625"/>
<institutionid name="Smith Barney - Transactions" id="464"/>
<institutionid name="Sound CU" id="793"/>
<institutionid name="South Carolina Bank and Trust" id="755"/>
<institutionid name="South Trust Bank" id="716"/>
<institutionid name="Southeastern CU" id="511"/>
<institutionid name="Southern Community Bank and Trust (SCB&amp;T)" id="751"/>
<institutionid name="Southwest Airlines FCU" id="465"/>
<institutionid name="Southwest Missouri Bank" id="759"/>
<institutionid name="Spectrum Connect/Reich&amp;Tang" id="463"/>
<institutionid name="St. Mary's Credit Union" id="815"/>
<institutionid name="Standard Federal Bank" id="717"/>
<institutionid name="Star One Credit Union" id="807"/>
<institutionid name="Sterne Agee" id="736"/>
<institutionid name="Summit Credit Union (WI)" id="547"/>
<institutionid name="Suncoast Credit Union" id="811"/>
<institutionid name="Suncoast Schools FCU" id="653"/>
<institutionid name="SunTrust" id="442"/>
<institutionid name="SVB" id="791"/>
<institutionid name="T. Rowe Price" id="466"/>
<institutionid name="Tangerine (Canada)" id="794"/>
<institutionid name="TD Ameritrade" id="425"/>
<institutionid name="TD Bank" id="652"/>
<institutionid name="Technology Credit Union" id="801"/>
<institutionid name="Technology Credit Union - CA" id="467"/>
<institutionid name="Texans Credit Union" id="771"/>
<institutionid name="Texas Dow Employees Credit Union" id="512"/>
<institutionid name="Texas State Bank - McAllen" id="586"/>
<institutionid name="The Community Bank" id="650"/>
<institutionid name="The Golden1 Credit Union" id="778"/>
<institutionid name="The Mechanics Bank" id="482"/>
<institutionid name="The Queen's Federal Credit Union" id="607"/>
<institutionid name="Think Federal Credit Union" id="538"/>
<institutionid name="Think Mutual Bank" id="812"/>
<institutionid name="TIAA-CREF" id="767"/>
<institutionid name="TIAA-CREF Retirement Services" id="797"/>
<institutionid name="Tower Federal Credit Union" id="769"/>
<institutionid name="Tri Boro Federal Credit Union" id="571"/>
<institutionid name="Truliant FCU" id="426"/>
<institutionid name="U.S. First FCU" id="582"/>
<institutionid name="UBS Financial Services Inc." id="459"/>
<institutionid name="UMB" id="660"/>
<institutionid name="UMB Bank" id="468"/>
<institutionid name="Umpqua Bank" id="725"/>
<institutionid name="Union Bank of California" id="469"/>
<institutionid name="United California Bank" id="718"/>
<institutionid name="United Federal CU - PowerLink" id="719"/>
<institutionid name="United Teletech Financial" id="470"/>
<institutionid name="UNIVERSITY &amp; STATE EMPLOYEES CU" id="776"/>
<institutionid name="University Credit Union" id="560"/>
<institutionid name="University Federal Credit Union" id="513"/>
<institutionid name="US Bank" id="471"/>
<institutionid name="USAA Federal Savings Bank" id="483"/>
<institutionid name="USAA Investment Mgmt Co" id="665"/>
<institutionid name="Utah Community Credit Union" id="564"/>
<institutionid name="UW Credit Union" id="638"/>
<institutionid name="VALIC" id="720"/>
<institutionid name="Van Kampen Funds, Inc." id="721"/>
<institutionid name="Vanguard" id="799"/>
<institutionid name="Vanguard Group" id="722"/>
<institutionid name="Vanguard Group, The" id="479"/>
<institutionid name="Vantage Credit Union" id="499"/>
<institutionid name="Velocity Credit Union" id="723"/>
<institutionid name="Virginia Educators Credit Union" id="503"/>
<institutionid name="VISA Information Source" id="626"/>
<institutionid name="Voya" id="819"/>
<institutionid name="Wachovia Bank" id="537"/>
<institutionid name="Waddell &amp; Reed - Ivy Funds" id="724"/>
<institutionid name="Weitz Funds" id="616"/>
<institutionid name="Wells Fargo" id="473"/>
<institutionid name="Wells Fargo Advantage Funds" id="591"/>
<institutionid name="Wells Fargo Advisor" id="516"/>
<institutionid name="Wells Fargo Advisors" id="737"/>
<institutionid name="Wells Fargo Bank" id="749"/>
<institutionid name="Wells Fargo Bank 2013" id="777"/>
<institutionid name="Wells Fargo Investments, LLC" id="568"/>
<institutionid name="Wells Fargo Trust-Investment Mgt" id="622"/>
<institutionid name="Windward Community FCU" id="604"/>
<institutionid name="Wings Financial" id="756"/>
<institutionid name="Woodsboro Bank" id="779"/>
<institutionid name="Wright Patman Congressional FCU" id="532"/>
<institutionid name="Wright Patt CU" id="800"/>
<institutionid name="WSECU" id="575"/>
<institutionid name="Yakima Valley Credit Union" id="514"/>
<institutionid name="Zions Bank" id="630"/>
<institutionid name="zWachovia" id="452"/>
</institutionlist>

1
unknown/transactions.txt Normal file
View File

@ -0,0 +1 @@
None

View File

@ -1,6 +1,8 @@
import logging import logging
import celery import celery
import ofxparse
import requests.exceptions
import vanth.download import vanth.download
import vanth.main import vanth.main
@ -14,13 +16,24 @@ LOGGER = logging.getLogger(__name__)
app = celery.Celery('vanth') app = celery.Celery('vanth')
app.conf.CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml'] app.conf.CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
app.conf.CELERY_TASK_SERIALIZER = 'json' app.conf.CELERY_TASK_SERIALIZER = 'json'
app.conf.CELERY_ALWAYS_EAGER = True #app.conf.CELERY_ALWAYS_EAGER = True
@app.task() @app.task()
def update_account(account_uuid): def update_account(account_uuid):
LOGGER.debug("Updating account %s", account_uuid) LOGGER.debug("Updating account %s", account_uuid)
account = vanth.platform.ofxaccount.by_uuid(account_uuid) account = vanth.platform.ofxaccount.by_uuid(account_uuid)
source = vanth.platform.ofxsource.by_uuid(account['source']['uuid'])[0] source = vanth.platform.ofxsource.by_uuid(account['source']['uuid'])[0]
document = vanth.download.transactions(source, account) try:
vanth.platform.ofxrecord.ensure_exists(account, document.body.statement.transactions.items) document = vanth.download.transactions(source, account)
vanth.platform.ofxupdate.OFXUpdate.create(ofxaccount=account_uuid) vanth.platform.ofxrecord.ensure_exists(account, document.body.statement.transactions.items)
vanth.platform.ofxupdate.OFXUpdate.create(ofxaccount=account_uuid)
except requests.exceptions.ConnectionError as e:
LOGGER.error("Failed to download account data: %s", e)
@app.task()
def process_transaction_upload(account_uuid, filename):
LOGGER.debug("Processing file %s", filename)
account = vanth.platform.ofxaccount.by_uuid(account_uuid)
with open(filename, 'rb') as f:
document = ofxparse.OfxParser.parse(f)
vanth.platform.ofxrecord.ensure_exists(account, document.account.statement.transactions)

18
vanth/celery_worker.py Normal file
View File

@ -0,0 +1,18 @@
import logging
import celery
import celery.signals
import vanth.main
from vanth.celery import app
logging.basicConfig()
LOGGER = logging.getLogger(__name__)
config = vanth.main.get_config()
vanth.main.create_db_connection(config)
@celery.signals.setup_logging.connect
def on_logging(*args, **kwargs):
logging.getLogger().setLevel(logging.DEBUG)
LOGGER.info("Logging has been set up")

View File

@ -1,6 +1,7 @@
import io
import ofxparse
import requests import requests
import vanth.ofx
import vanth.platform.ofxaccount import vanth.platform.ofxaccount
@ -12,6 +13,8 @@ def do_all():
def transactions(source, account): def transactions(source, account):
body = vanth.ofx.query_transactions(source, account) body = vanth.ofx.query_transactions(source, account)
response = requests.post('https://ofx.americafirst.com/', data=body, headers={'Content-Type': 'application/x-ofx'}) url = 'https://ofx.americafirst.com/'
response = requests.post(url, data=body, headers={'Content-Type': 'application/x-ofx'})
assert response.ok, response.text assert response.ok, response.text
return vanth.ofx.parse(response.text) LOGGER.debug("Response from %s: %s %d bytes", url, response.status_code, len(response.text))
return parse(response.text)

View File

@ -1,148 +0,0 @@
import collections
import datetime
import re
import vanth.sgml
Document = collections.namedtuple('Document', ['header', 'body'])
class Body(): # pylint:disable=too-few-public-methods
def __init__(self, sgml):
self.status = Status(sgml['SIGNONMSGSRSV1']['SONRS']['STATUS'])
self.statement = TransactionStatement(sgml['BANKMSGSRSV1']['STMTTRNRS'])
class Status(): # pylint:disable=too-few-public-methods
def __init__(self, sgml):
self.code = sgml['CODE'].value
self.severity = sgml['SEVERITY'].value
self.message = sgml['MESSAGE'].value if sgml['MESSAGE'] else None
class TransactionStatement(): # pylint:disable=too-few-public-methods
def __init__(self, sgml):
self.trnuid = sgml['TRNUID'].value
self.status = Status(sgml['STATUS'])
self.transactions = TransactionList(sgml['STMTRS'])
class TransactionList(): # pylint:disable=too-few-public-methods
def __init__(self, sgml):
self.currency = sgml['CURDEF'].value
self.account = Account(sgml['BANKACCTFROM'])
self.start = _parse_date_with_tz(sgml['BANKTRANLIST']['DTSTART'].value)
self.end = _parse_date_with_tz(sgml['BANKTRANLIST']['DTEND'].value)
self.items = [Transaction(child) for child in sgml['BANKTRANLIST'].children if child.name == 'STMTTRN']
class Transaction(): # pylint:disable=too-few-public-methods
def __init__(self, sgml):
self.amount = float(sgml['TRNAMT'].value)
self.available = _parse_date(sgml['DTAVAIL'].value)
self.id = sgml['FITID'].value
self.memo = sgml['MEMO'].value
self.name = sgml['NAME'].value
self.posted = _parse_date(sgml['DTPOSTED'].value)
self.type = sgml['TRNTYPE'].value
def __iter__(self):
return ((prop, getattr(self, prop)) for prop in ('amount', 'available', 'id', 'memo', 'name', 'posted', 'type'))
class Account(): # pylint:disable=too-few-public-methods
def __init__(self, sgml):
self.bankid = sgml['BANKID'].value
self.accountid = sgml['ACCTID'].value
self.type = sgml['ACCTTYPE'].value
def _fix_offset(offset):
result = int(offset) * 100
return "{:04d}".format(result) if result > 0 else "{:05d}".format(result)
def _parse_date(date):
return datetime.datetime.strptime(date, "%Y%m%d%H%M%S.000")
def _parse_date_with_tz(date):
match = re.match(r'(?P<datetime>\d+)\.\d+\[(?P<offset>[\d\-]+):(?P<tzname>\w+)\]', date)
if not match:
raise ValueError("Unable to extract datetime from {}".format(date))
formatted = "{datetime} {offset} {tzname}".format(
datetime = match.group('datetime'),
offset = _fix_offset(match.group('offset')),
tzname = match.group('tzname'),
)
return datetime.datetime.strptime(formatted, "%Y%m%d%H%M%S %z %Z")
def header():
return "\r\n".join([
"OFXHEADER:100",
"DATA:OFXSGML",
"VERSION:102",
"SECURITY:NONE",
"ENCODING:USASCII",
"CHARSET:1252",
"COMPRESSION:NONE",
"OLDFILEUID:NONE",
"NEWFILEUID:NONE",
])
def now():
return datetime.datetime.now().strftime("%Y%m%d%H%M%S.000[-7:MST]")
def signonmsg(institution, account):
return "\r\n".join([
"<SIGNONMSGSRQV1>",
"<SONRQ>",
"<DTCLIENT>{}".format(now()),
"<USERID>{}".format(account['user_id']),
"<USERPASS>{}".format(account['password']),
"<LANGUAGE>ENG",
"<FI>",
"<ORG>{}".format(institution['name']),
"<FID>{}".format(institution['fid']),
"</FI>",
"<APPID>QWIN",
"<APPVER>1200",
"</SONRQ>",
"</SIGNONMSGSRQV1>",
])
def bankmsg(institution, account, start):
return "\r\n".join([
"<BANKMSGSRQV1>",
"<STMTTRNRQ>",
"<TRNUID>00000000",
"<STMTRQ>",
"<BANKACCTFROM>",
"<BANKID>{}".format(institution['bankid']),
"<ACCTID>{}".format(account['account_id']),
"<ACCTTYPE>{}".format(account['type'].upper()),
"</BANKACCTFROM>",
"<INCTRAN>",
"<DTSTART>{}".format(start.strftime("%Y%m%d")),
"<INCLUDE>Y",
"</INCTRAN>",
"</STMTRQ>",
"</STMTTRNRQ>",
"</BANKMSGSRQV1>",
])
def body(institution, account, start):
return "<OFX>\r\n" + signonmsg(institution, account) + "\r\n" + bankmsg(institution, account, start) + "\r\n</OFX>"
def query_transactions(institution, account, start=None):
start = start or datetime.datetime.now() - datetime.timedelta(days=14)
return header() + (2*"\r\n") + body(institution, account, start) + "\r\n"
def _first_empty_line(lines):
for i, line in enumerate(lines):
if not line:
return i
def _parse_header(header_lines):
splits = [line.partition(':') for line in header_lines]
return {k: v for k, _, v in splits}
def parse(content):
lines = content.split('\r\n')
split = _first_empty_line(lines)
header_lines = lines[:split]
_header = _parse_header(header_lines)
_body = vanth.sgml.parse('\n'.join(lines[split+1:]))
return Document(_header, Body(_body))

12
vanth/ofxhome.py Normal file
View File

@ -0,0 +1,12 @@
import xml.etree.ElementTree
def parse_child(element):
values = {child.tag: child.text for child in element}
values['id'] = element.attrib['id']
return values
def parse(data):
root = xml.etree.ElementTree.fromstring(data)
assert root.tag == 'institutions'
return [parse_child(element) for element in root]

View File

@ -1,10 +1,14 @@
import logging
import flask import flask
import werkzeug.utils
import vanth.celery import vanth.celery
import vanth.pages.tools import vanth.pages.tools
import vanth.platform.ofxaccount import vanth.platform.ofxaccount
import vanth.platform.ofxsource import vanth.platform.ofxsource
LOGGER = logging.getLogger()
blueprint = flask.Blueprint('accounts', __name__) blueprint = flask.Blueprint('accounts', __name__)
@blueprint.route('/accounts/', methods=['GET']) @blueprint.route('/accounts/', methods=['GET'])
@ -40,3 +44,19 @@ def post_account(arguments):
def post_update(arguments): def post_update(arguments):
vanth.celery.update_account.delay(**arguments) vanth.celery.update_account.delay(**arguments)
return flask.redirect('/accounts/') return flask.redirect('/accounts/')
@blueprint.route('/transactions/', methods=['POST'])
@vanth.pages.tools.parse({
'account_uuid' : str,
})
def post_transactions(arguments):
if 'transactions' not in flask.request.files:
return flask.render_template('error.html', error="You did not include the content of the file in your upload")
transactions = flask.request.files['transactions']
if transactions.filename == '':
return flask.redirect('/accounts/{}/'.format(arguments['account_uuid']))
filename = werkzeug.utils.secure_filename(transactions.filename)
LOGGER.info("Saving uploaded file %s to %s", transactions.filename, filename)
transactions.save(filename)
vanth.celery.process_transaction_upload.delay(arguments['account_uuid'], filename)
return flask.redirect('/accounts/{}/'.format(arguments['account_uuid']))

View File

@ -15,7 +15,7 @@ def parse(args):
if supplied is None: if supplied is None:
missing_parameters.append(key) missing_parameters.append(key)
else: else:
values[key] = converter(supplied) values[key] = converter(supplied) if converter else supplied
if missing_parameters: if missing_parameters:
return (json.dumps({'errors': [{ return (json.dumps({'errors': [{
'title' : "Missing required paramter '{}'".format(parameter), 'title' : "Missing required paramter '{}'".format(parameter),

View File

@ -30,6 +30,8 @@ def _select():
vanth.tables.OFXSource.c.uuid.label('source.uuid'), vanth.tables.OFXSource.c.uuid.label('source.uuid'),
subselect, subselect,
]).select_from( ]).select_from(
vanth.tables.OFXAccount.join(vanth.tables.OFXSource)
).select_from(
subselect subselect
).where( ).where(
vanth.tables.OFXAccount.c.uuid == subselect.c.ofxaccount vanth.tables.OFXAccount.c.uuid == subselect.c.ofxaccount
@ -63,11 +65,12 @@ def by_user(user_id):
query = query.where( query = query.where(
vanth.tables.OFXAccount.c.owner == user_id vanth.tables.OFXAccount.c.owner == user_id
) )
LOGGER.debug(query)
return _execute_and_convert(query) return _execute_and_convert(query)
def create(values): def create(values):
engine = chryso.connection.get() engine = chryso.connection.get()
values['type'] = values.pop('account_type')
values['source'] = sqlalchemy.select([ values['source'] = sqlalchemy.select([
vanth.tables.OFXSource.c.uuid vanth.tables.OFXSource.c.uuid
]).where(vanth.tables.OFXSource.c.name == values.pop('institution')) ]).where(vanth.tables.OFXSource.c.name == values.pop('institution'))

View File

@ -20,12 +20,11 @@ def ensure_exists(account, transactions):
LOGGER.debug("Have %d new transactions to save", len(new_records)) LOGGER.debug("Have %d new transactions to save", len(new_records))
to_insert = [{ to_insert = [{
'amount' : transaction.amount, 'amount' : transaction.amount,
'available' : transaction.available,
'fid' : transaction.id, 'fid' : transaction.id,
'memo' : transaction.memo, 'memo' : transaction.memo,
'name' : transaction.name, 'name' : transaction.payee,
'ofxaccount' : account['uuid'], 'ofxaccount' : account['uuid'],
'posted' : transaction.posted, 'posted' : transaction.date,
'type' : transaction.type, 'type' : transaction.type,
'uuid' : uuid.uuid4(), 'uuid' : uuid.uuid4(),
} for transaction in new_records] } for transaction in new_records]

View File

@ -1,6 +1,8 @@
import logging import logging
import uuid
import chryso.connection import chryso.connection
import sqlalchemy
import vanth.tables import vanth.tables
@ -11,10 +13,32 @@ def _query_and_convert(query):
results = engine.execute(query).fetchall() results = engine.execute(query).fetchall()
return [dict(result) for result in results] return [dict(result) for result in results]
def by_uuid(uuid): def by_uuid(uuid_):
query = vanth.tables.OFXSource.select().where(vanth.tables.OFXSource.c.uuid == uuid) query = vanth.tables.OFXSource.select().where(vanth.tables.OFXSource.c.uuid == uuid_)
return _query_and_convert(query) return _query_and_convert(query)
def get(): def get():
query = vanth.tables.OFXSource.select() query = vanth.tables.OFXSource.select()
return _query_and_convert(query) return _query_and_convert(query)
def ensure_exist(institutions):
engine = chryso.connection.get()
query = sqlalchemy.select([
vanth.tables.OFXSource.c.fid,
])
results = engine.execute(query).fetchall()
LOGGER.debug("Found %d OFX sources", len(results))
known_records = {result[vanth.tables.OFXSource.c.fid] for result in results}
new_records = [institution for institution in institutions if institution['fid'] not in known_records]
LOGGER.debug("Have %d new transactions to save", len(new_records))
to_insert = [{
'name' : institution['name'],
'fid' : institution['fid'],
'url' : institution['url'],
'uuid' : str(uuid.uuid4()),
} for institution in new_records]
if to_insert:
engine.execute(vanth.tables.OFXSource.insert(), to_insert) # pylint: disable=no-value-for-parameter
LOGGER.debug("Done inserting %d records", len(new_records))
else:
LOGGER.debug("Not performing insert, nothing to do")

View File

@ -1,65 +0,0 @@
import logging
LOGGER = logging.getLogger(__name__)
class Node(): # pylint: disable=too-few-public-methods
def __init__(self, parent, name, children=None, value=None):
self.children = children or []
self.name = name
self.parent = parent
self.value = value
if parent:
parent.children.append(self)
def __getitem__(self, key):
for child in self.children:
if child.name == key:
return child
def __repr__(self):
return "SGMLNode {} ({})".format(self.name, self.parent.name if self.parent else None)
def parse(content):
state = 'node-content'
buf = ''
parent_node = None
current_node = None
for c in content:
if c == '<':
if state == 'node-content':
if buf == '':
parent_node = current_node
LOGGER.debug("Node content was empty, setting parent node to %s", parent_node)
if current_node:
current_node.value = buf
LOGGER.debug("Set %s to %s", current_node.name, current_node.value)
buf = ''
state = 'node-name'
elif c == '>':
if state == 'node-name':
LOGGER.debug("Saw opening tag %s. With parent %s", buf, parent_node)
state = 'node-content'
current_node = Node(parent_node, buf)
buf = ''
elif state == 'closing-tag':
LOGGER.debug("Saw closing tag %s", buf)
state = 'closed-tag'
parent_node = current_node
while parent_node.parent and parent_node.name != buf:
parent_node = parent_node.parent
parent_node = parent_node.parent
buf = ''
LOGGER.debug("Set new parent to %s", parent_node.name if parent_node else None)
elif c == '/' and buf == '':
state = 'closing-tag'
parent_node = current_node.parent if current_node else None
else:
buf += c
root = current_node or parent_node
while root.parent:
root = root.parent
return root
def pformat(node, indent=0):
children = '\n'.join(pformat(child, indent+1) for child in node.children)
return "{}{}: {}{}".format('\t' * indent, node.name, node.value, "\n" + children if node.children else '')

View File

@ -1,6 +1,6 @@
import chryso.constants import chryso.constants
from sqlalchemy import (Column, Date, DateTime, ForeignKey, Integer, MetaData, from sqlalchemy import (Column, Date, DateTime, ForeignKey, Integer,
Numeric, String, Table, UniqueConstraint, func, text) Numeric, MetaData, String, Table, UniqueConstraint, func, text)
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
metadata = MetaData(naming_convention=chryso.constants.CONVENTION) metadata = MetaData(naming_convention=chryso.constants.CONVENTION)
@ -75,3 +75,46 @@ OFXRecord = table('ofxrecord',
Column('posted', Date(), nullable=True), # The date the record posted Column('posted', Date(), nullable=True), # The date the record posted
Column('type', String(255), nullable=True), # The type of the record, like 'POS' Column('type', String(255), nullable=True), # The type of the record, like 'POS'
) )
Pool = table('pool',
Column('balance', Integer(), nullable=False), # The balance of the pool, like -10 or +12
Column('name', String(255), nullable=False), # The name of the pool, like 'Bills' or 'Heating oil'
Column('parent', None, ForeignKey('pool.uuid', name='fk_parent'), nullable=True),
Column('user_uri', String(2048), nullable=False), # The URI of the user that created the record
UniqueConstraint('name', 'user_uri', name='pool_name_by_user')
)
Spring = table('spring',
Column('description', String(1024), nullable=False), # A user-significant description
Column('name', String(256), nullable=False), # A user-significant name
Column('user_uri', String(2048), nullable=False), # The URI of the user that created the record
UniqueConstraint('name', 'user_uri', name='sprint_name_by_user')
)
Sink = table('drain',
Column('description', String(1024), nullable=False), # A user-significant description
Column('name', String(256), nullable=False), # A user-significant name
Column('user_uri', String(2048), nullable=False), # The URI of the user that created the record
UniqueConstraint('name', 'user_uri', name='sink_name_by_user')
)
Flow = table('flow',
Column('amount', Numeric(precision=20, scale=2, asdecimal=True), nullable=False), # The amount like -$177.91
Column('description', String(1024), nullable=False), # The description that is meaningful to the user
Column('destination', None, ForeignKey(Pool.c.uuid, name='fk_destination'), nullable=True), # The pool that receives the amount
Column('source', None, ForeignKey(Pool.c.uuid, name='fk_source'), nullable=True), # The pool that originally provides the amount
)
Inflow = table('inflow',
Column('amount', Numeric(precision=20, scale=2, asdecimal=True), nullable=False), # The amount like -$177.91
Column('description', String(1024), nullable=False), # The description that is meaningful to the user
Column('destination', None, ForeignKey(Pool.c.uuid, name='fk_destination'), nullable=False), # The pool that receives the amount
Column('source', None, ForeignKey(Spring.c.uuid, name='fk_source'), nullable=False), # The sprint that originally provided the amount
)
Outflow = table('outflow',
Column('amount', Numeric(precision=20, scale=2, asdecimal=True), nullable=False), # The amount like -$177.91
Column('description', String(1024), nullable=False), # The description that is meaningful to the user
Column('destination', None, ForeignKey(Sink.c.uuid, name='fk_destination'), nullable=False), # The sink that receives the amount
Column('source', None, ForeignKey(Pool.c.uuid, name='fk_source'), nullable=False), # The pool that originally provided the amount
)