Auto-categorize with built-in merchant rules, payee category editor, income/net on dashboard
build-container / build (push) Successful in 35s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-21 13:17:42 -04:00
1 parent 6073279a1b
commit 837f89e563
7 files changed
+191 -17

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
"""Generic merchant rules: statement text -> (payee, category, type, what it was for).
Keywords are matched as whole words (case-insensitive) in the statement description, in order: first match wins,
so put specific entries before generic ones. payee=None keeps the cleaned-up statement name.
Categories must exist on the workbook's Lists tab (otherwise the payee falls back to "Other / Unsorted").
No personal data belongs in this file; private rules go in loader_rules.json.
"""
import re
B, S = "Bill", "Spending"
# (keywords, payee, category, type, what)
SEEDS = [
# --- housing / insurance / taxes
(["SCHEDULED TRANSFER TO LOAN", "MORTGAGE"], "Mortgage", "Housing", B, "Monthly mortgage payment"),
(["RENT", "HOA", "PROPERTY MGMT"], None, "Housing", B, "Housing payment"),
(["GEICO", "PROGRESSIVE", "STATE FARM", "ALLSTATE", "LIBERTY MUTUAL", "FARMERS INS", "NATIONWIDE", "HOMESITE", "TRUSTAGE", "INSUR", "INS PREM"], None, "Insurance", B, "Insurance premium"),
(["DMV", "DEPARTMENT OF MOTOR", "DEPARTMENT MOTOR", "DEPT OF MOTOR", "TREASURER", "COUNTY TAX", "TAXFINESFEES", "IRS", "USPS", "POST OFFICE", "POSTAL"], None, "Taxes & Gov Fees", B, "Tax / government fee / postage"),
# --- phone, internet, utilities
(["T-MOBILE", "TMOBILE", "VERIZON", "AT&T", "ATT", "SPRINT", "XFINITY", "COMCAST", "SPECTRUM", "VONAGE", "SHENTEL", "COX", "FRONTIER", "MINT MOBILE", "GOOGLE FI", "STARLINK", "SIMPLEFIN"], None, "Phone & Internet", B, "Phone / internet service"),
(["ELECTRIC", "POWER", "ENERGY", "DOMINION", "DUKE", "WATER", "SEWER", "PROPANE", "NATURAL GAS", "UTILIT", "TRASH", "WASTE"], None, "Utilities", B, "Utility bill"),
# --- subscriptions & software
(["NETFLIX", "SPOTIFY", "HULU", "DISNEY", "HBO", "PEACOCK", "PARAMOUNT", "PRIME VIDEO", "YOUTUBE", "PURE FLIX", "EMBY", "AUDIBLE", "KINDLE", "AMZN DIGITAL", "AMAZON DIGITAL", "AMAZON PRIME", "APPLE.COM/BILL", "ITUNES", "GOOGLE", "MICROSOFT", "MSFT", "ADOBE", "DROPBOX", "ICLOUD", "ANTHROPIC", "CLAUDE.AI", "OPENAI", "CLOUDFLARE", "GITHUB", "ELEVENLABS", "1PASSWORD", "DISCORD", "NINTENDO", "PLAYSTATION", "XBOX", "STEAM", "LOGOS"], None, "Subscriptions & Software", B, "Subscription / software"),
# --- groceries & household
(["ALDI", "FOOD LION", "KROGER", "PUBLIX", "SAFEWAY", "HARRIS TEETER", "WEGMANS", "GIANT", "TRADER JOE", "WHOLE FOODS", "SAMS CLUB", "SAMSCLUB", "COSTCO", "WM SUPERCENTER", "WAL MART", "WALMART", "WAL-MART", "DOLLAR TREE", "DOLLARTREE", "DOLLAR GENERAL", "FAMILY DOLLAR", "OLLIES", "GROCERY"], None, "Groceries & Household", S, "Groceries / household"),
# --- dining
(["MCDONALD", "WENDY", "BURGER KING", "TACO BELL", "CHICK FIL", "SUBWAY", "DOMINO", "PIZZA", "LITTLE CAESARS", "PANDA EXPRESS", "STARBUCKS", "DUNKIN", "HARDEE", "CHIPOTLE", "SONIC", "ARBY", "KFC", "WAFFLE HOUSE", "APPLEBEE", "LONGHORN", "OLIVE GARDEN", "PANERA", "CICIS", "CAFE", "RESTAURANT", "GRILL", "DINER", "DOORDASH", "UBER EATS", "GRUBHUB", "CANTEEN", "VENDING", "MEXICAN", "WASABI", "SUSHI", "BBQ"], None, "Dining Out", S, "Restaurant / food"),
# --- fuel & auto
(["SHEETZ", "WAWA", "EXXON", "SHELL", "BP", "CHEVRON", "MARATHON", "SPEEDWAY", "CIRCLE K", "MURPHY", "QUIKTRIP", "RACETRAC", "7-ELEVEN", "CITGO", "GAS STATION", "FUEL", "AUTOZONE", "OREILLY", "O'REILLY", "ADVANCE AUTO", "NAPA", "JIFFY LUBE", "TIRE", "PEP BOYS", "AUTO"], None, "Fuel & Auto", S, "Fuel / auto"),
# --- health
(["CVS", "WALGREENS", "RITE AID", "PHARMACY", "DENTAL", "DENTIST", "ORTHODONT", "MYEYEDR", "OPTICAL", "EYE", "HOSPITAL", "MEDICAL", "CLINIC", "URGENT CARE", "PHYSICIAN", "HEALTH", "PLANET FITNESS", "GYM"], None, "Health & Medical", S, "Health / medical"),
# --- pets
(["PETCO", "PETSMART", "VETERINAR", "ANIMAL HOSPITAL", "PET &", "PET SUPPLIES", "CHEWY"], None, "Pets", S, "Pet care / supplies"),
# --- home & garden
(["LOWES", "HOME DEPOT", "TRACTOR SUPPLY", "ACE HARDWARE", "MENARDS", "HARBOR FREIGHT", "HARDWARE", "NURSERY", "GARDEN"], None, "Home & Garden", S, "Home / garden / hardware"),
# --- travel & outings
(["EXPEDIA", "AIRBNB", "HOTEL", "MARRIOTT", "HILTON", "DELTA", "SOUTHWEST", "UNITED AIR", "AMTRAK", "UBER", "LYFT", "CINEMA", "REGAL", "AMC", "THEATER", "THEATRE", "MUSEUM", "RESORT", "SETTLEMENT", "PARK", "BOWLING"], None, "Travel & Outings", S, "Travel / outing"),
# --- education, giving
(["SCHOOL", "TUITION", "ACADEMY", "UDEMY", "COURSERA", "UNIVERSITY", "COLLEGE", "ABEKA"], None, "Education", S, "Education"),
(["DONATION", "CHURCH", "CHARITY", "RED CROSS", "GOFUNDME", "BAPTIST", "MINISTR"], None, "Giving & Charity", S, "Giving / donation"),
# --- shopping (broad, keep last)
(["AMAZON", "AMZN", "EBAY", "ETSY", "BEST BUY", "TJMAXX", "TJ MAXX", "ROSS", "KOHL", "MACY", "STAPLES", "HOBBY LOBBY", "HOBBYLOBBY", "MICHAELS", "GOODWILL", "THRIFT", "ZENNI", "TARGET", "BOOKSHOP", "MARDEL", "CHRISTIANBOOK"], None, "Shopping", S, "Shopping"),
]
def _kw(k):
# match at the start of a word; short keywords (BP, ATT, IRS...) must also end a word
return re.escape(k) + (r"(?![A-Z0-9])" if len(k) <= 4 else "")
_COMPILED = [(re.compile(r"(?<![A-Z0-9])(?:" + "|".join(_kw(k) for k in kws) + r")"), p, c, t, w) for kws, p, c, t, w in SEEDS]
def seed_match(text):
"""Return (payee_or_None, category, type, what) for the first matching rule, else None."""
t = text.upper()
for rx, p, c, ty, w in _COMPILED:
if rx.search(t): return p, c, ty, w
return None