build-container / build (push) Successful in 35s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
55 lines
5.3 KiB
Python
55 lines
5.3 KiB
Python
"""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
|