Ally savings-only statements: separate account per savings number, own coverage row
build-container / build (push) Successful in 39s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-21 19:04:19 -04:00
1 parent 1f556bccfa
commit 5425d9913a
2 files changed
+8 -4

No files matched your search

+4 -3
View File
@@ -36,6 +36,7 @@ def coverage(sts):
out = [] out = []
for acct, lst in by.items(): for acct, lst in by.items():
csv_only = all(s.kind == "csv" for s in lst) csv_only = all(s.kind == "csv" for s in lst)
ends = {s.end.day for s in lst}; day = cfg.get(acct) or (ends.pop() if len(ends) == 1 and max(ends) < 28 else None) # accounts not in ACCOUNTS: reuse their usual closing day
cells, first = {}, min((s.start or s.end) for s in lst) cells, first = {}, min((s.start or s.end) for s in lst)
for mo in months: for mo in months:
y, m = int(mo[:4]), int(mo[5:]) y, m = int(mo[:4]), int(mo[5:])
@@ -44,7 +45,7 @@ def coverage(sts):
cells[mo] = "have" if have else ("future" if (y, m) > (max(s.end for s in lst).year, max(s.end for s in lst).month) else ("none" if (y, m) < (first.year, first.month) else "missing")) cells[mo] = "have" if have else ("future" if (y, m) > (max(s.end for s in lst).year, max(s.end for s in lst).month) else ("none" if (y, m) < (first.year, first.month) else "missing"))
continue continue
have = any((s.end.year, s.end.month) == (y, m) for s in lst) have = any((s.end.year, s.end.month) == (y, m) for s in lst)
due = dt.date(y, m, cfg.get(acct) or eom(y, m)) due = dt.date(y, m, day or eom(y, m))
started = (y, m) >= (min(s.end for s in lst).year, min(s.end for s in lst).month) started = (y, m) >= (min(s.end for s in lst).year, min(s.end for s in lst).month)
cells[mo] = "have" if have else ("none" if not started else ("future" if due > today else "missing")) cells[mo] = "have" if have else ("none" if not started else ("future" if due > today else "missing"))
last = max(s.end for s in lst) last = max(s.end for s in lst)
@@ -52,8 +53,8 @@ def coverage(sts):
nxt = f"Next download: from {last + dt.timedelta(days=1):%b %-d, %Y}" nxt = f"Next download: from {last + dt.timedelta(days=1):%b %-d, %Y}"
else: else:
ny, nm = (last.year, last.month + 1) if last.month < 12 else (last.year + 1, 1) ny, nm = (last.year, last.month + 1) if last.month < 12 else (last.year + 1, 1)
if cfg.get(acct) is None and last.day != eom(last.year, last.month): ny, nm = last.year, last.month if day is None and last.day != eom(last.year, last.month): ny, nm = last.year, last.month
nd = dt.date(ny, nm, cfg.get(acct) or eom(ny, nm)) nd = dt.date(ny, nm, day or eom(ny, nm))
nxt = f"Next statement closes ~{nd:%b %-d, %Y}" + (" (ready to download)" if nd <= today else "") nxt = f"Next statement closes ~{nd:%b %-d, %Y}" + (" (ready to download)" if nd <= today else "")
if acct in recent and recent[acct] > last: nxt += f" · recent activity loaded through {recent[acct]:%b %-d}" if acct in recent and recent[acct] > last: nxt += f" · recent activity loaded through {recent[acct]:%b %-d}"
out.append(dict(account=acct, cells=cells, count=len(lst), through=f"{last:%b %-d, %Y}", next=nxt, kind="csv" if csv_only else "pdf")) out.append(dict(account=acct, cells=cells, count=len(lst), through=f"{last:%b %-d, %Y}", next=nxt, kind="csv" if csv_only else "pdf"))
+4 -1
View File
@@ -135,10 +135,13 @@ def parse_ally(path, pages):
bal = g(r"Ending Balance, as of \S+ \$([\d,]+\.\d\d)") bal = g(r"Ending Balance, as of \S+ \$([\d,]+\.\d\d)")
if bal is None: continue if bal is None: continue
apy = re.search(r"Annual Percentage Yield Earned ([\d.]+)%", sec) apy = re.search(r"Annual Percentage Yield Earned ([\d.]+)%", sec)
st.snaps.append(dict(account="Ally " + re.sub(r" Account$", "", prod), group="Savings", date=end, balance=bal, num = re.search(r"Account Number: x+(\d{4})", full[max(0, h.start() - 200): h.start()]) # last 4 digits tell several savings accounts apart
st.snaps.append(dict(account="Ally " + re.sub(r" Account$", "", prod) + (f" ({num.group(1)})" if num else ""), group="Savings", date=end, balance=bal,
interest=g(r"Interest Paid This Period \$([\d,]+\.\d\d)"), ytd=g(r"Interest Paid Year to Date \$([\d,]+\.\d\d)"), interest=g(r"Interest Paid This Period \$([\d,]+\.\d\d)"), ytd=g(r"Interest Paid Year to Date \$([\d,]+\.\d\d)"),
deposits=g(r"Deposits and Other Credits \$([\d,]+\.\d\d)"), withdrawals=g(r"Withdrawals and Other Debits -?\$([\d,]+\.\d\d)"), deposits=g(r"Deposits and Other Credits \$([\d,]+\.\d\d)"), withdrawals=g(r"Withdrawals and Other Debits -?\$([\d,]+\.\d\d)"),
rate=float(apy.group(1)) if apy else None)) rate=float(apy.group(1)) if apy else None))
if not any(re.match(r"spending", h.group(1).strip(), re.I) for h in heads) and st.snaps:
st.account, st.stated_out = st.snaps[0]["account"], None # a savings-only statement: its own account in the coverage grid, not Ally Checking
return st return st
CV_TX = re.compile(r"^(\d\d/\d\d/\d{4}) (.+?) \$([\d,]+\.\d\d) \$([\d,]+\.\d\d)$") CV_TX = re.compile(r"^(\d\d/\d\d/\d{4}) (.+?) \$([\d,]+\.\d\d) \$([\d,]+\.\d\d)$")