diff --git a/.gitignore b/.gitignore index 567d65d..5ff355a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ /budget/ /build/ /categorized.lst +/category-regex-*.lst /chromedriver /databases.lst /explore*.sql @@ -27,3 +28,4 @@ /test-urls /test_files /upload.sql +*.java_* diff --git a/budget.properties b/budget.properties index 9e99106..51c994d 100644 --- a/budget.properties +++ b/budget.properties @@ -3,4 +3,4 @@ db-url=jdbc:mysql://localhost:3306/budget_2023?serverTimezone=UTC&useSSL=false db-username=elephant db-password=zsKRtBw6gPi0B0hMg1c2 trilium-url=http://nuc1:8080/etapi -trilium-password=scale-daughter-twiddling-educate-afraid \ No newline at end of file +trilium-password=$cale-D@ughter-TWenty5 \ No newline at end of file diff --git a/category-regex.lst b/category-regex.lst index 6a27e50..9a2fbf1 100644 --- a/category-regex.lst +++ b/category-regex.lst @@ -1,587 +1,589 @@ -accounting, CHECK # 4046, , firstbank, , peakview -auto.equipment, .*garmin.*, i, -auto.gasoline, .*CONOCO.*, , chase -auto.gasoline, .*SHELL OIL.* -auto.gasoline, CONOCO .*, citi -auto.license, AIR CARE COLORADO.*, , chase -auto.services, .*PEDERSEN TOYOTA.* -auto.subscriptions, .*AARP ROADSIDE.*, , chase -auto.subscriptions, ROADSIDE ASSISTANCE.*, , chase -bank-fees, .* FEE, , firstbank -cellphone, .* VZ WIRELESS VW, , firstbank -clothing, .* barefoot shoes .*, i -clothing, .* cotton .* leggings .*, i -clothing, .* Grip Socks .* -clothing, .* House Shoes .* -clothing, .* Orthopeic Flip Flops .* -clothing, .* Underwear .* -clothing, .*columbia.*, i, -clothing, .*dress belt.*, i, -clothing, .*Flip Flop Socks.*, , amz.sandy.retail.ord -clothing, .*LLBEANINC.* -clothing, .*Nightgown.* -clothing, .*Wireless Bra.*, , amz.sandy.retail.ord -clothing, Cotton Tops.* -clothing, Gaiam.* -clothing, HAFLINGER.* -clothing, HiGropcore.* -clothing, Hugh Ugoli.* -clothing, Island Genius.* -clothing, LeIsfIt.* -clothing, MACYS.*, , chase -clothing, PAYPAL \*ZAPPOS.*, , chase -clothing, vanity fair .* nylon brief .*, i -clothing, Weintee .* -communication, .*DISCORD.* -computer.equipment, .* Charger Cable .* -computer.equipment, .* Charging Cable.* -computer.equipment, .* Computer Power Supply .* -computer.equipment, .* ipad .*, i -computer.equipment, .* Lightning Cable .* -computer.equipment, .* Magnetic Fast Charger .* -computer.equipment, .* mechanical keyboard .*, i, -computer.equipment, .* Power Adapter .* -computer.equipment, .* Power Over Ethernet .* -computer.equipment, .*3\.5mm jack.*, i, -computer.equipment, .*APPLE\.COM/US.* -computer.equipment, .*av cable.*, i, -computer.equipment, .*BESTBUY.*, , chase -computer.equipment, .*CANAKIT.*, , chase -computer.equipment, .*cyberpower.*, i, -computer.equipment, .*data charging cable.*, i, -computer.equipment, .*deskstar.*, i, -computer.equipment, .*DVD Drive.* -computer.equipment, .*flash memory.*, i, -computer.equipment, .*led display.*, i, -computer.equipment, .*mid tower case.*, i, -computer.equipment, .*mini plug.*, i, -computer.equipment, .*monitor cable.*, i, -computer.equipment, .*NEWEGG.* -computer.equipment, .*oem drive.*, i, -computer.equipment, .*scanner.*, i, -computer.equipment, .*videosecu.*, i, -computer.equipment, .*Wireless Vertical Ergonomic Optical Mouse.*, , amz.sandy.retail.ord -computer.equipment, Arducam .* -computer.equipment, Corsair .* -computer.equipment, HGST .* -computer.equipment, Micro HDMI to HDMI Adapter.* -computer.equipment, Mohu Leaf .* -computer.equipment, NETGEAR .* -computer.equipment, Raspberry Pi .* -computer.equipment, SiliconDust HDHomeRun .* -computer.equipment, U6 PRO .* -computer.equipment, WWZMDiB .* -computer.software, DejaOffice, , amz.sandy.dig.ord, 10 -computer.software, File Manager, , amz.sandy.dig.ord, 10 -computer.subscriptions, .*APPLE\.COM/BILL.* -computer.supplies, .* Compressed Air Duster .* -computer.supplies, .* Compressed Gas Duster.* -dental.services, NORTHERN COLORADO ENDO.*, , discover -dental.services, OWENS DENTAL.* -dental.services, RECLAIM DENTISTRY .* -dental.services, THE THERMOGRAM CENTER .*, , discover -dining, .*AUSTINS AMERICAN .*, , chase -dining, .*MOOT HOUSE.* -dining, .*Simmer ECOM.*, , chase -dining, .*SIMMER.*Food.*Drink.* -dining, .*YOUNG.*CAFE.* -education, PAYPAL \*DAILYOM .*, , chase -education, PAYPAL \*WHOLE WOMAN .*, , chase -entertainment.books, .*BN PAPERSRC .*, , chase, , Barnes and Noble -entertainment.books, Barnes \& Noble Inc .*, , paypal, 10 -entertainment.games, Humble Bundle.*, , paypal, 10 -entertainment.games, Valve Corp.*, , paypal, 10 -entertainment.subscriptions.spotify, Spotify USA .*, , paypal, 10 -entertainment.subscriptions.gaia, .*GAIA.* -entertainment.subscriptions.netflix, .*NETFLIX.* -entertainment.subscriptions.patreon, .*PATREON *MEMBER.* -entertainment.subscriptions.samsung, .*SAMSUNG.*, , chase -entertainment.subscriptions.spotify, .*SPOTIFY.* -entertainment.subscriptions.youtube, .*YOUTUBE SUBSC.* -entertainment.subscriptions.apple, Apple Services .*-10\.79 .*, , paypal -computer.support, Apple Services .*7\.99 .*, , paypal -computer.support, Apple Services .*2\.99 .*, , paypal -computer.support, Apple Services .*3\.99 .*, , paypal -entertainment.subscriptions.comcast, CABLE COMCAST.*, , firstbank -entertainment.subscriptions.antenna, CHANNELS .* -entertainment.subscriptions.newspaper, Ft Coll Coloradoan.* -entertainment.subscriptions.google, Google .* .2\.17 USD.*, , paypal, 10 -entertainment.subscriptions.hallmark, Hallmark Movies Now, , amz.sandy.dig.ord, 10 -entertainment.subscriptions.hbo, HBO, , amz.sandy.dig.ord, 10 -entertainment.subscriptions.hulu, Hulu .*, , paypal, 10 -entertainment.subscriptions.netflix, Netflix, , amz.sandy.dig.ord, 10 -entertainment.subscriptions.netflix, Netflix\.com .*, , paypal, 10 -entertainment.subscriptions.paramount, Paramount\+ with SHOWTIME, , amz.sandy.dig.ord, 10 -entertainment.subscriptions.patreon, Patreon .*, , paypal, 10 -entertainment.subscriptions.hulu, PAYPAL \*HULU.*, , chase -entertainment.subscriptions.samsung, PAYPAL \*SAMSUNGELEC .*, , chase -entertainment.tvshows/cds, Prime Video.* -entertainment.subscriptions.samsung, Samsung Electronics America.*, , paypal, 10 -entertainment.subscriptions.starz, STARZ, , amz.sandy.dig.ord, 10 -entertainment.subscriptions.tivo, TIVO PLATFORM TECH.* -entertainment.subscriptions.tivo, TIVOPLATTECH.*, , chase -entertainment.books, .*, , amz.sandy.dig.ord -entertainment.books, .*, , amz.steve.dig.ord -entertainment.books, .*transmetropolitan.*, i, -entertainment.books, Alternative Cures.* -entertainment.books, BOOKSHOP\.ORG .* -entertainment.books, this is not a game, i, -entertainment.books, uncanny x.men.*, i, -entertainment.books, zero history, i, -entertainment.equipment, .*amplifier.*, i, -entertainment.equipment, .*speakers.*, i, -entertainment.equipment, wii.*, i, -entertainment.games, .* Jigsaw Puzzle .* -entertainment.games, .*ARENANETLLC.* -entertainment.games, .*HUMBLEBUNDL.* -entertainment.games, .*STEAM GAMES.* -entertainment.tvshows/cds, .* Avengers: Endgame, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Battle Los Angeles, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Battleship, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Black Panther .*, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Captain Marvel, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Fantastic Fungi Remastered, , , 10 -entertainment.tvshows/cds, Jesse Stone .*, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Jupiter Ascending, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Justice League, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Knives Out, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Looper .*, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Lungs: The B.Sides, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, people of the wind, i, -entertainment.tvshows/cds, Predestination, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Spider.Man: .*, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Star Wars: .*, , amz.sandy.dig.ord, 10 -entertainment.tvshows/cds, Wild, , amz.sandy.dig.ord, 10 -home.yard-garden.equipment, .*Soil Moisture Meter.* -home.yard-garden.equipment, WayinTop .* -home.yard-garden.supplies, .*BATH NURSERY.*, , chase -home.yard-garden.supplies, .*GULLEY GREENHOUSE.*, , chase -home.yard-garden.supplies, .*HIGHMOWINGS.* -home.yard-garden.supplies, .*URBAN FARMER.* -home.yard-garden.supplies, .*URBANFARMER.* -gifts, .*ETSY GIFTCARD.*, , chase -donations, .*GREENAMERIC .* -donations, .*Rocky Mountain PBS.* -donations, .*THUNDERBIRD.* -donations, .*WIKIPEDIA.* -misc, Ancestry\.com .*, , paypal -gifts, CHECK # 4044, , firstbank, , leah -dues-subscriptions-memberships, CHECK # 4077, , firstbank, , aarp -donations, CPT12.* -donations, Green America .*, , paypal, 10 -groceries, .* Almond Flour.* -groceries, .* Microgreens Growing Kit.* -groceries, .* Nutritional Yeast .* -groceries, .* Protein Bar.* -groceries, .* SuperFood Starter Culture .* -groceries, .* Yogurt Starter Culture .* -groceries, .*\(panda01\), , amz.sandy.retail.ord -groceries, .*Bee Pollen.*, , amz.sandy.retail.ord -groceries, .*Breath Mints.*, , amz.sandy.retail.ord -groceries, .*Collagen Peptides.* -groceries, .*FAIRCHILDS VINEGAR.*, , chase -groceries, .*Gelatin Powder.* -groceries, .*INSTACART.* -groceries, .*KING SOOPERS.*, , chase -groceries, .*Macadamia Nuts.* -groceries, .*Manuka Honey.*, , amz.sandy.retail.ord -groceries, .*NATIVE HILL.* -groceries, .*Organic.*, i, , -1 -groceries, .*Plantain Chips.*, , amz.sandy.retail.ord -groceries, .*Pork Rinds.* -groceries, .*RED ROBIN.* -groceries, .*ribeye.*, i, -groceries, .*SWEETMARIAS.* -groceries, .*THRIVEMARKE.* -groceries, .*Top Sirloin Steak.*, , amz.sandy.retail.ord -groceries, .*Yogurt Coconut.* -groceries, BochaSweet.*, , amz.sandy.retail.ord -groceries, Bulletproof XCT MCT Oil.* -groceries, Califia Farms.* -groceries, CELTIC OCEAN .*, , chase -groceries, COSTCO WHSE .*, , citi -groceries, Essenzefruits.* -groceries, Instacart .*, , paypal, 10 -groceries, Kerrygold.* -groceries, Large paper bag fee.* -groceries, LILY'S.*, i -groceries, LUCKY.*MARKET.* -groceries, MT. CAPRA.* -groceries, NATURAL GROCERS FC.* -groceries, PALEOVALLEY .*, , chase -groceries, PAYPAL .IHERB LLC .* -groceries, PAYPAL \*WILDERNESSP .*, , chase -groceries, Pecan Shop .* -groceries, Pork Loin Chop .* -groceries, PUR Gum .* -groceries, SPROUTS FARMERS MAR .*, , chase -groceries, Sweet Maria.*, , paypal, 10 -groceries, Vital Farms.*, i, amz.sandy.retail.ord -groceries, Wild Planet .* -groceries, Wilderness Poets .*, , paypal, 10 -groceries, Wilderness Poets.*, , amz.sandy.retail.ord -health.equipment, .* Pulse Oximeter .* -personal.care, .* sleep mask .*, i -health.equipment, .*APOLLONEURO .* -health.equipment, .*balance disc.*, i, -health.equipment, .*heart rate sensor.*, i, -health.equipment, .*INAP SLEEP THERAPY.*, , chase -health.equipment, .*inversion therapy chair.*, i, -health.equipment, .*NOVAALAB.* -health.equipment, .*RINGCONN.* -health.equipment, .*SOMNICSHEAL.* -health.equipment, Apollo Neuroscience.*, , paypal -health.equipment, INAP SLEEP .* -health.equipment, LifePro.* -health.equipment, Omron .* -health.equipment, Optimal Circadian Health.*, , paypal -health.equipment, Oxygen Research Institute .*, , paypal -health.equipment, PAYPAL \*HEALTHYLINE .*, , chase -health.supplies, .* Hearing Aid Batteries.* -health.supplies, .*Breathable Strips.*, i, -health.supplies, .*Hypoallergenic Tape.*, i, -health.supplies, Dynarex .* -health.supplies, Nasacort .* -health.supplies, Polident .* -personal.care, Vibrant Blue Oils.*, , paypal -personal.electronics, Walmart\.com .* -24\.90.*, , paypal -hobby.equipment, uxcell .* -hobby.supplies, .* Acrylic Plastic Cement.* -hobby.supplies, .* rubber roller .*, i -hobby.supplies, .* Wood Screws.* -hobby.supplies, HATCHBOX .* -hobby.supplies, speedball .*, i -home.housewares, .*Bamboo Toilet Stool .*, , amz.sandy.retail.ord -home.appliances, LABIGO.* -home.housewares, .* blueair .* filter .*, i -home.appliances, .* Cordless Stick Vacuum .* -home.appliances, .* Dremel .* -home.appliances, .* Extension Cord .* -home.appliances, .* Extension Cord.* -home.housewares, .* replacement filter for levoit .*, i -home.appliances, .* Lithium Coin Battery .* -home.appliances, .*BLACK\+DECKER dustbuster.*, , amz.sandy.retail.ord -home.appliances, .* BLUEAIR .* Air Purifier.*, , amz.sandy.retail.ord -home.appliances, BLUEAIR Air Purifier.*, , amz.sandy.retail.ord -home.housewares, BLUEAIR Blue Pure .* Replacement Filter.*, , amz.sandy.retail.ord -home.appliances, .*cordless phone.*, i, -home.appliances, .*EMF Meter.* -home.appliances, .*HOME DEPOT.* -home.appliances, .*JOSEPH S HARDWARE.*, , chase -personal.electronics, .*LED Neck Reading Light.* -home.appliances, .*LOWES\.COM.* -home.appliances, .*Phone Headset.* -home.appliances, .*power strip.*, i, -home.appliances, .*PURE WATER PRODUCTS.* -home.appliances, BLACK+DECKER dustbuster.* -home.housewares, BLUEAIR .* Filter -home.appliances, Dremel.* -home.appliances, Kidde .* -home.appliances, levoit .*, i -home.appliances, Portable Charger Power Bank .* -home.appliances, Ryhiac .* -home.appliances, Sunlite .* -home.appliances, SUPRUS .* -home.appliances, Temtop .* -home.housewares, .* Toilet Lid Cover.* -home.furnishings, .*ART\.COM.* -home.furnishings, .*COLORADO BLINDS.*, , chase -home.furnishings, .*FURNITURE ROW.*, , chase -home.housewares, .*Toilet Seat.* -home.furnishings, .*Wall calendar.*, i, -home.furnishings, California Design Den.* -home.furnishings, HOME SMILE.* -home.furnishings, oskas .* -home.furnishings, PAYPAL \*BED BATH .*, , chase -home.furnishings, PUDDING CABIN .* -home.furnishings, CHECK # 4037, , firstbank, , move couch -home.maintenance, CHECK # 4041, , firstbank, , chimney cleaners -home.repair, CHECK # 4072, , firstbank, , freezer repair -home.repair, CHECK # 4073, , firstbank, , freezer repair -home.repair, CHECK # 4076, , firstbank, , fence repair -home.repair, CHECK # 4089, , firstbank, , fix water line in laundry room -home.housewares, .* hand soap.*, i, , 10 -home.housewares, .* Light Bulb.*, , , 10 -home.housewares, Everyone.* -home.housewares, MRS\. MEYER'S.* -home.housewares, PUREBURG .* -home.housewares, Quilted Northern .* -income, INTEREST EARNED, , firstbank -income, INVESTMENT EDWARD JONES, , firstbank -income, PAYROLL TRINET.*, , firstbank -income, XXSOC SEC SSA.*, , firstbank -insurance.auto, .* SAFECO, , firstbank -insurance.auto, Safeco Corporation .* -insurance.life, .*transamerica.*, i, , 10 -insurance.medical, .* HUMANA.*, , firstbank -insurance.product, asurion .*, i, , , breville -internet-services, .*GODADDY\.COM.* -internet-services, .*GOOGLE STORAGE.* -internet-services, .*NAMECHEAP.* -internet-services, .*NETDORM.* -internet-services, Amazon Drive, , amz.sandy.dig.ord, 10 -internet-services, NAME-CHEAP.* -internet-services, FORT COLLINS CONNEXION .*, , discover -internet-services, VISA FORT COLLINS CONNEXION.*, , firstbank -investment, MONEYLINK SCHWAB.*, , firstbank -investment, TRANSFER PAYPAL, , firstbank -home.appliances, .* Coffee Maker .* -home.appliances, .*COMFEE.*Electric Kettle.*, , amz.sandy.retail.ord -home.appliances, .*food processor.*, i, -home.appliances, Instant Pot.* -home.appliances, Vitamix .* -home.housewares, .* Pill Cutter .* -home.housewares, hemp mats .*, i -home.housewares, Melitta .* -home.housewares, Seventh Generation .*, , , 10 -home.housewares, .* coffee mug .*, i -home.housewares, .* cutting board.*, i -home.housewares, .* Dish Drying Mat .* -home.housewares, .* Jar Lids.* -home.housewares, .* Kitchen Utensils .* -home.housewares, .* Knife Edge Guards.* -home.housewares, .* Mason Jar Shaker Lids .* -home.housewares, .* Rubber Spatula.* -home.housewares, .* spatula .*, i -home.housewares, .*Coffee Carafe Tea Pot.*, , amz.sandy.retail.ord -home.housewares, .*Dish Towels.*, , amz.sandy.retail.ord -home.housewares, .*Donut Pan.* -home.housewares, .*Foam Ear Plugs.* -home.housewares, .*Food Storage Containers.* -home.housewares, .*GREENPAN.* -home.housewares, .*kitchen knife.*, i, -home.housewares, Euro Cuisine.* -home.housewares, Fino Pour-Over Coffee Brewing Filter Cone.* -home.housewares, GMISUN Oil Dispenser.* -home.housewares, GreenPan.* -home.housewares, IDEATECH.* -home.housewares, LOVE MOMENT.* -home.housewares, Misen .* -home.housewares, Noble Home .* -home.housewares, OXO .* -home.housewares, Pour Over Coffee Dripper.* -home.housewares, souper cubes .*, i -home.housewares, SPLF .* -home.housewares, TeamFar .* -home.housewares, Wooden Spurtle .* -home.housewares, Zeppoli .* -medical.services, .* UCHEALTH .*, , chase -medical.services, .* UCHEALTH .*, , discover -medical.services, .*Ortho Spine Ctr Rockies.* -medical.services, .*PERFORMANCE PHYSICAL THER.* -medical.services, .*UCHEALTH .*, , discover -medical.services, .*UCHEALTH.*, , chase -medical.services, ACCTVERIFY ATLAS\.MD, , firstbank -medical.services, ADVANCED MEDICAL IMAGING.*, , chase -medical.services, AUDIOLOGY GROUP .* -health.practitioners, CHECK # 4035, , firstbank, , shelby kahl -medical.services, CHECK # 4036, , firstbank, , -health.practitioners, CHECK # 4038, , firstbank, , shelby kahl -health.practitioners, CHECK # 4039, , firstbank, , shelby kahl -health.practitioners, CHECK # 4042, , firstbank, , shelby kahl -health.practitioners, CHECK # 4043, , firstbank, , shelby kahl -health.practitioners, CHECK # 4045, , firstbank, , shelby kahl -health.practitioners, CHECK # 4048, , firstbank, , shelby kahl -health.practitioners, CHECK # 4053, , firstbank, , shelby kahl -medical.services, CHECK # 4071, , firstbank, , copay kidney dr. -health.practitioners, CHECK # 4075, , firstbank, , ashley -health.practitioners, CHECK # 4078, , firstbank, , shelby kahl -health.practitioners, CHECK # 4081, , firstbank, , shelby kahl -health.practitioners, CHECK # 4082, , firstbank, , shelby kahl -health.practitioners, CHECK # 4083, , firstbank, , shelby kahl -health.practitioners, CHECK # 4085, , firstbank, , shelby kahl -health.practitioners, CHECK # 4086, , firstbank, , shelby kahl -health.practitioners, CHECK # 4090, , firstbank, , shelby kahl -supplements, CHECK # 4091, , firstbank, , shelby kahl -medical.services, CHIROPRACTIC ASSOCIATES.* -health.practitioners, IV NUTRITION .* -medical.services, NORTH VISTA.* -medical.services, OBGA MY OBGYN .*, , discover -medical.services, UCHEALTH .*, , firstbank -medical.subscriptions, DFC OF NOC ATLAS\.MD, , firstbank -medical.subscriptions, DFC OF NOCO .*, , chase -medical.subscriptions, DFC OF NOCO .*, , discover -medical.supplies, WALGREENS .*, , chase -medicines, .*BELMAR PHARMACY.*, , chase -medicines, .*EBM MEDICAL.*, , chase -medicines, .*SAFEWAY.* -medicines, GOOD DAY PHARMACY.* -medicines, UpSpring .* -dues-subscriptions-memberships, CHECK # 4047, , firstbank, , AARP -dues-subscriptions-memberships, CONSUMERREPORTS.*, , chase -dues-subscriptions-memberships, prime membership fee, i, amz.sandy.dig.ord, 10 -mortgage, CASH PENNYMAC.*, , firstbank -office.supplies, .*Ballpoint Pens.*, , amz.sandy.retail.ord -office.supplies, .*Magnifying Glass.* -office.supplies, .*NOKBOX.*, , chase -office.supplies, .*Spiral Notebook.* -office.supplies, HP Printer Paper .* -office.supplies, HP.*Ink Cartridge.* -office.supplies, Pentel .* -passports, CHECK # 4050, , firstbank, , larimer county clerk -passports, PAYMENT PASSPORTSERVICES, , firstbank, , us dept of state -passports, PAYMENT PASSPORTSERVICES, , firstbank, , us dept of state -payment, .* CHASE CREDIT CRD, , firstbank -payment, .*AMAZON PRIME.*, , chase -payment, .*ANNUAL MEMBERSHIP FEE.*, , chase -payment, .*Payment Thank You.* -payment, AMAZON MKTPL.*, , chase -payment, Amazon Prime.*, , chase -payment, Amazon\.com.* -payment, AMZN Mktp.* -payment, CASHBACK BONUS REDEMPTION .*, , discover -payment, INST XFER PAYPAL, , firstbank -payment, Kindle Svcs.*, , chase, 10 -payment, PAYMENT CITI.*, , firstbank +accounting, CHECK # 4046, , firstbank, , peakview, +auto.equipment, .*garmin.*, i, , , , +auto.gasoline, .*CONOCO.*, , chase, , , +auto.gasoline, .*SHELL OIL.*, , , , , +auto.gasoline, CONOCO .*, i, , , , +auto.license, AIR CARE COLORADO.*, , chase, , , +auto.services, .*PEDERSEN TOYOTA.*, , , , , +auto.subscriptions, .*AARP ROADSIDE.*, , chase, , , +auto.subscriptions, ROADSIDE ASSISTANCE.*, , chase, , , +bank-fees, .* FEE, , firstbank, , , +cellphone, .* VZ WIRELESS VW, , firstbank, , , +clothing, .* barefoot shoes .*, i, , , , +clothing, .* cotton .* leggings .*, i, , , , +clothing, .* Grip Socks .*, , , , , +clothing, .* House Shoes .*, , , , , +clothing, .* Orthopeic Flip Flops .*, , , , , +clothing, .* Underwear .*, , , , , +clothing, .*columbia.*, i, , , , +clothing, .*dress belt.*, i, , , , +clothing, .*Flip Flop Socks.*, , amz.sandy.retail.ord, , , +clothing, .*LLBEANINC.*, , , , , +clothing, .*Nightgown.*, , , , , +clothing, .*Wireless Bra.*, , amz.sandy.retail.ord, , , +clothing, Cotton Tops.*, , , , , +clothing, Gaiam.*, , , , , +clothing, HAFLINGER.*, , , , , +clothing, HiGropcore.*, , , , , +clothing, Hugh Ugoli.*, , , , , +clothing, Island Genius.*, , , , , +clothing, LeIsfIt.*, , , , , +clothing, MACYS.*, , chase, , , +clothing, PAYPAL \*ZAPPOS.*, , chase, , , +clothing, vanity fair .* nylon brief .*, i, , , , +clothing, Weintee .*, , , , , +communication, .*DISCORD.*, , , , , +computer.equipment, .* Charger Cable .*, , , , , +computer.equipment, .* Charging Cable.*, , , , , +computer.equipment, .* Computer Power Supply .*, , , , , +computer.equipment, .* ipad .*, i, , , , +computer.equipment, .* Lightning Cable .*, , , , , +computer.equipment, .* Magnetic Fast Charger .*, , , , , +computer.equipment, .* mechanical keyboard .*, i, , , , +computer.equipment, .* Power Adapter .*, , , , , +computer.equipment, .* Power Over Ethernet .*, , , , , +computer.equipment, .*3\.5mm jack.*, i, , , , +computer.equipment, .*APPLE\.COM/US.*, , , , , +computer.equipment, .*av cable.*, i, , , , +computer.equipment, .*BESTBUY.*, , chase, , , +computer.equipment, .*CANAKIT.*, , chase, , , +computer.equipment, .*cyberpower.*, i, , , , +computer.equipment, .*data charging cable.*, i, , , , +computer.equipment, .*deskstar.*, i, , , , +computer.equipment, .*DVD Drive.*, , , , , +computer.equipment, .*flash memory.*, i, , , , +computer.equipment, .*led display.*, i, , , , +computer.equipment, .*mid tower case.*, i, , , , +computer.equipment, .*mini plug.*, i, , , , +computer.equipment, .*monitor cable.*, i, , , , +computer.equipment, .*NEWEGG.*, , , , , +computer.equipment, .*oem drive.*, i, , , , +computer.equipment, .*scanner.*, i, , , , +computer.equipment, .*videosecu.*, i, , , , +computer.equipment, .*Wireless Vertical Ergonomic Optical Mouse.*, , amz.sandy.retail.ord, , , +computer.equipment, Arducam .*, , , , , +computer.equipment, Corsair .*, , , , , +computer.equipment, HGST .*, , , , , +computer.equipment, Micro HDMI to HDMI Adapter.*, , , , , +computer.equipment, Mohu Leaf .*, , , , , +computer.equipment, NETGEAR .*, , , , , +computer.equipment, Raspberry Pi .*, , , , , +computer.equipment, SiliconDust HDHomeRun .*, , , , , +computer.equipment, U6 PRO .*, , , , , +computer.equipment, WWZMDiB .*, , , , , +computer.software, DejaOffice, , amz.sandy.dig.ord, 10, , +computer.software, File Manager, , amz.sandy.dig.ord, 10, , +computer.subscriptions, .*APPLE\.COM/BILL.*, , , , , +computer.supplies, .* Compressed Air Duster .*, , , , , +computer.supplies, .* Compressed Gas Duster.*, , , , , +dental.services, NORTHERN COLORADO ENDO.*, , discover, , , +dental.services, OWENS DENTAL.*, , , , , +dental.services, RECLAIM DENTISTRY .*, , , , , +dental.services, THE THERMOGRAM CENTER .*, , discover, , , +dining, .*AUSTINS AMERICAN .*, , chase, , , +dining, .*MOOT HOUSE.*, , , , , +dining, .*Simmer ECOM.*, , chase, , , +dining, .*SIMMER.*Food.*Drink.*, , , , , +dining, .*YOUNG.*CAFE.*, , , , , +education, PAYPAL \*DAILYOM .*, , chase, , , +education, PAYPAL \*WHOLE WOMAN .*, , chase, , , +entertainment.books, .*BN PAPERSRC .*, , chase, , Barnes and Noble, +entertainment.books, Barnes \& Noble Inc .*, , paypal, 10, , +entertainment.games, Humble Bundle.*, , paypal, 10, , +entertainment.games, Valve Corp.*, , paypal, 10, , +entertainment.subscriptions.spotify, Spotify USA .*, , paypal, 10, , +entertainment.subscriptions.gaia, .*GAIA.*, , , , , +entertainment.subscriptions.netflix, .*NETFLIX.*, , , , , +entertainment.subscriptions.patreon, .*PATREON *MEMBER.*, , , , , +entertainment.subscriptions.samsung, .*SAMSUNG.*, , chase, , , +entertainment.subscriptions.spotify, .*SPOTIFY.*, , , , , +entertainment.subscriptions.youtube, .*YOUTUBE SUBSC.*, , , , , +entertainment.subscriptions.apple, Apple Services .*-10\.79 .*, , paypal, , , +computer.support, Apple Services .*7\.99 .*, , paypal, , , +computer.support, Apple Services .*2\.99 .*, , paypal, , , +computer.support, Apple Services .*3\.99 .*, , paypal, , , +entertainment.subscriptions.comcast, CABLE COMCAST.*, , firstbank, , , +entertainment.subscriptions.antenna, CHANNELS .*, , , , , +entertainment.subscriptions.newspaper, Ft Coll Coloradoan.*, , , , , +entertainment.subscriptions.google, Google .* .2\.17 USD.*, , paypal, 10, , +entertainment.subscriptions.hallmark, Hallmark Movies Now, , amz.sandy.dig.ord, 10, , +entertainment.subscriptions.hbo, HBO, , amz.sandy.dig.ord, 10, , +entertainment.subscriptions.hulu, Hulu .*, , paypal, 10, , +entertainment.subscriptions.netflix, Netflix, , amz.sandy.dig.ord, 10, , +entertainment.subscriptions.netflix, Netflix\.com .*, , paypal, 10, , +entertainment.subscriptions.paramount, Paramount\+ with SHOWTIME, , amz.sandy.dig.ord, 10, , +entertainment.subscriptions.patreon, Patreon .*, , paypal, 10, , +entertainment.subscriptions.hulu, PAYPAL \*HULU.*, , chase, , , +entertainment.subscriptions.samsung, PAYPAL \*SAMSUNGELEC .*, , chase, , , +entertainment.tvshows/cds, Prime Video.*, , , , , +entertainment.subscriptions.samsung, Samsung Electronics America.*, , paypal, 10, , +entertainment.subscriptions.starz, STARZ, , amz.sandy.dig.ord, 10, , +entertainment.subscriptions.tivo, TIVO PLATFORM TECH.*, , , , , +entertainment.subscriptions.tivo, TIVOPLATTECH.*, , chase, , , +entertainment.books, .*, , amz.sandy.dig.ord, , , +entertainment.books, .*, , amz.steve.dig.ord, , , +entertainment.books, .*transmetropolitan.*, i, , , , +entertainment.books, Alternative Cures.*, , , , , +entertainment.books, BOOKSHOP\.ORG .*, , , , , +entertainment.books, this is not a game, i, , , , +entertainment.books, uncanny x.men.*, i, , , , +entertainment.books, zero history, i, , , , +entertainment.equipment, .*amplifier.*, i, , , , +entertainment.equipment, .*speakers.*, i, , , , +entertainment.equipment, wii.*, i, , , , +entertainment.games, .* Jigsaw Puzzle .*, , , , , +entertainment.games, .*ARENANETLLC.*, , , , , +entertainment.games, .*HUMBLEBUNDL.*, , , , , +entertainment.games, .*STEAM GAMES.*, , , , , +entertainment.tvshows/cds, .* Avengers: Endgame, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Battle Los Angeles, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Battleship, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Black Panther .*, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Captain Marvel, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Fantastic Fungi Remastered, , , 10, , +entertainment.tvshows/cds, Jesse Stone .*, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Jupiter Ascending, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Justice League, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Knives Out, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Looper .*, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Lungs: The B.Sides, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, people of the wind, i, , , , +entertainment.tvshows/cds, Predestination, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Spider.Man: .*, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Star Wars: .*, , amz.sandy.dig.ord, 10, , +entertainment.tvshows/cds, Wild, , amz.sandy.dig.ord, 10, , +home.yard-garden.equipment, .*Soil Moisture Meter.*, , , , , +home.yard-garden.equipment, WayinTop .*, , , , , +home.yard-garden.supplies, .*BATH NURSERY.*, , chase, , , +home.yard-garden.supplies, .*GULLEY GREENHOUSE.*, , chase, , , +home.yard-garden.supplies, .*HIGHMOWINGS.*, , , , , +home.yard-garden.supplies, .*URBAN FARMER.*, , , , , +home.yard-garden.supplies, .*URBANFARMER.*, , , , , +gifts, .*ETSY GIFTCARD.*, , chase, , , +donations, .*GREENAMERIC .*, , , , , +donations, .*Rocky Mountain PBS.*, , , , , +donations, .*THUNDERBIRD.*, , , , , +donations, .*WIKIPEDIA.*, , , , , +misc, Ancestry\.com .*, , paypal, , , +gifts, CHECK # 4044, , firstbank, , leah, +dues-subscriptions-memberships, CHECK # 4077, , firstbank, , aarp, +donations, CPT12.*, , , , , +donations, Green America .*, , paypal, 10, , +groceries, .* Almond Flour.*, , , , , +groceries, .* Microgreens Growing Kit.*, , , , , +groceries, .* Nutritional Yeast .*, , , , , +groceries, .* Protein Bar.*, , , , , +groceries, .* SuperFood Starter Culture .*, , , , , +groceries, .* Yogurt Starter Culture .*, , , , , +groceries, .*\(panda01\), , amz.sandy.retail.ord, , , +groceries, .*Bee Pollen.*, , amz.sandy.retail.ord, , , +groceries, .*Breath Mints.*, , amz.sandy.retail.ord, , , +groceries, .*Collagen Peptides.*, , , , , +groceries, .*FAIRCHILDS VINEGAR.*, , chase, , , +groceries, .*Gelatin Powder.*, , , , , +groceries, .*INSTACART.*, , , , , +groceries, .*KING SOOPERS.*, , chase, , , +groceries, .*Macadamia Nuts.*, , , , , +groceries, .*Manuka Honey.*, , amz.sandy.retail.ord, , , +groceries, .*NATIVE HILL.*, , , , , +groceries, .*Organic.*, i, , -1, , +groceries, .*Plantain Chips.*, , amz.sandy.retail.ord, , , +groceries, .*Pork Rinds.*, , , , , +groceries, .*RED ROBIN.*, , , , , +groceries, .*ribeye.*, i, , , , +groceries, .*SWEETMARIAS.*, , , , , +groceries, .*THRIVEMARKE.*, , , , , +groceries, .*Top Sirloin Steak.*, , amz.sandy.retail.ord, , , +groceries, .*Yogurt Coconut.*, , , , , +groceries, BochaSweet.*, , amz.sandy.retail.ord, , , +groceries, Bulletproof XCT MCT Oil.*, , , , , +groceries, Califia Farms.*, , , , , +groceries, CELTIC OCEAN .*, , chase, , , +groceries, COSTCO WHSE .*, , citi, , , +groceries, COSTCO WHSE .*, , chase +groceries, Essenzefruits.*, , , , , +groceries, Instacart .*, , paypal, 10, , +groceries, Kerrygold.*, , , , , +groceries, Large paper bag fee.*, , , , , +groceries, LILY'S.*, i, , , , +groceries, LUCKY.*MARKET.*, , , , , +groceries, MT. CAPRA.*, , , , , +groceries, NATURAL GROCERS FC.*, , , , , +groceries, PALEOVALLEY .*, , chase, , , +groceries, PAYPAL .IHERB LLC .*, , , , , +groceries, PAYPAL \*WILDERNESSP .*, , chase, , , +groceries, Pecan Shop .*, , , , , +groceries, Pork Loin Chop .*, , , , , +groceries, PUR Gum .*, , , , , +groceries, SPROUTS FARMERS MAR .*, , chase, , , +groceries, Sweet Maria.*, , paypal, 10, , +groceries, Vital Farms.*, i, amz.sandy.retail.ord, , , +groceries, Wild Planet .*, , , , , +groceries, Wilderness Poets .*, , paypal, 10, , +groceries, Wilderness Poets.*, , amz.sandy.retail.ord, , , +health.equipment, .* Pulse Oximeter .*, , , , , +personal.care, .* sleep mask .*, i, , , , +health.equipment, .*APOLLONEURO .*, , , , , +health.equipment, .*balance disc.*, i, , , , +health.equipment, .*heart rate sensor.*, i, , , , +health.equipment, .*INAP SLEEP THERAPY.*, , chase, , , +health.equipment, .*inversion therapy chair.*, i, , , , +health.equipment, .*NOVAALAB.*, , , , , +health.equipment, .*RINGCONN.*, , , , , +health.equipment, .*SOMNICSHEAL.*, , , , , +health.equipment, Apollo Neuroscience.*, , paypal, , , +health.equipment, INAP SLEEP .*, , , , , +health.equipment, LifePro.*, , , , , +health.equipment, Omron .*, , , , , +health.equipment, Optimal Circadian Health.*, , paypal, , , +health.equipment, Oxygen Research Institute .*, , paypal, , , +health.equipment, PAYPAL \*HEALTHYLINE .*, , chase, , , +health.supplies, .* Hearing Aid Batteries.*, , , , , +health.supplies, .*Breathable Strips.*, i, , , , +health.supplies, .*Hypoallergenic Tape.*, i, , , , +health.supplies, Dynarex .*, , , , , +health.supplies, Nasacort .*, , , , , +health.supplies, Polident .*, , , , , +personal.care, Vibrant Blue Oils.*, , paypal, , , +personal.electronics, Walmart\.com .* -24\.90.*, , paypal, , , +hobby.equipment, uxcell .*, , , , , +hobby.supplies, .* Acrylic Plastic Cement.*, , , , , +hobby.supplies, .* rubber roller .*, i, , , , +hobby.supplies, .* Wood Screws.*, , , , , +hobby.supplies, HATCHBOX .*, , , , , +hobby.supplies, speedball .*, i, , , , +home.housewares, .*Bamboo Toilet Stool .*, , amz.sandy.retail.ord, , , +home.appliances, LABIGO.*, , , , , +home.housewares, .* blueair .* filter .*, i, , , , +home.appliances, .* Cordless Stick Vacuum .*, , , , , +home.appliances, .* Dremel .*, , , , , +home.appliances, .* Extension Cord .*, , , , , +home.appliances, .* Extension Cord.*, , , , , +home.housewares, .* replacement filter for levoit .*, i, , , , +home.appliances, .* Lithium Coin Battery .*, , , , , +home.appliances, .*BLACK\+DECKER dustbuster.*, , amz.sandy.retail.ord, , , +home.appliances, .* BLUEAIR .* Air Purifier.*, , amz.sandy.retail.ord, , , +home.appliances, BLUEAIR Air Purifier.*, , amz.sandy.retail.ord, , , +home.housewares, BLUEAIR Blue Pure .* Replacement Filter.*, , amz.sandy.retail.ord, , , +home.appliances, .*cordless phone.*, i, , , , +home.appliances, .*EMF Meter.*, , , , , +home.appliances, .*HOME DEPOT.*, , , , , +home.appliances, .*JOSEPH S HARDWARE.*, , chase, , , +personal.electronics, .*LED Neck Reading Light.*, , , , , +home.appliances, .*LOWES\.COM.*, , , , , +home.appliances, .*Phone Headset.*, , , , , +home.appliances, .*power strip.*, i, , , , +home.appliances, .*PURE WATER PRODUCTS.*, , , , , +home.appliances, BLACK+DECKER dustbuster.*, , , , , +home.housewares, BLUEAIR .* Filter, , , , , +home.appliances, Dremel.*, , , , , +home.appliances, Kidde .*, , , , , +home.appliances, levoit .*, i, , , , +home.appliances, Portable Charger Power Bank .*, , , , , +home.appliances, Ryhiac .*, , , , , +home.appliances, Sunlite .*, , , , , +home.appliances, SUPRUS .*, , , , , +home.appliances, Temtop .*, , , , , +home.housewares, .* Toilet Lid Cover.*, , , , , +home.furnishings, .*ART\.COM.*, , , , , +home.furnishings, .*COLORADO BLINDS.*, , chase, , , +home.furnishings, .*FURNITURE ROW.*, , chase, , , +home.housewares, .*Toilet Seat.*, , , , , +home.furnishings, .*Wall calendar.*, i, , , , +home.furnishings, California Design Den.*, , , , , +home.furnishings, HOME SMILE.*, , , , , +home.furnishings, oskas .*, , , , , +home.furnishings, PAYPAL \*BED BATH .*, , chase, , , +home.furnishings, PUDDING CABIN .*, , , , , +home.furnishings, CHECK # 4037, , firstbank, , move couch, +home.maintenance, CHECK # 4041, , firstbank, , chimney cleaners, +home.repair, CHECK # 4072, , firstbank, , freezer repair, +home.repair, CHECK # 4073, , firstbank, , freezer repair, +home.repair, CHECK # 4076, , firstbank, , fence repair, +home.repair, CHECK # 4089, , firstbank, , fix water line in laundry room, +home.housewares, .* hand soap.*, i, , 10, , +home.housewares, .* Light Bulb.*, , , 10, , +home.housewares, Everyone.*, , , , , +home.housewares, MRS\. MEYER'S.*, , , , , +home.housewares, PUREBURG .*, , , , , +home.housewares, Quilted Northern .*, , , , , +income, INTEREST EARNED, , firstbank, , , +income, INVESTMENT EDWARD JONES, , firstbank, , , +income, PAYROLL TRINET.*, , firstbank, , , +income, XXSOC SEC SSA.*, , firstbank, , , +insurance.auto, .* SAFECO, , firstbank, , , +insurance.auto, Safeco Corporation .*, , , , , +insurance.life, .*transamerica.*, i, , 10, , +insurance.medical, .* HUMANA.*, , firstbank, , , +insurance.product, asurion .*, i, , , breville, +internet-services, .*GODADDY\.COM.*, , , , , +internet-services, .*GOOGLE STORAGE.*, , , , , +internet-services, .*NAMECHEAP.*, , , , , +internet-services, .*NETDORM.*, , , , , +internet-services, Amazon Drive, , amz.sandy.dig.ord, 10, , +internet-services, NAME-CHEAP.*, , , , , +internet-services, FORT COLLINS CONNEXION .*, , discover, , , +internet-services, FORT COLLINS CONNEXION .*, , chase +internet-services, VISA FORT COLLINS CONNEXION.*, , firstbank, , , +investment, MONEYLINK SCHWAB.*, , firstbank, , , +investment, TRANSFER PAYPAL, , firstbank, , , +home.appliances, .* Coffee Maker .*, , , , , +home.appliances, .*COMFEE.*Electric Kettle.*, , amz.sandy.retail.ord, , , +home.appliances, .*food processor.*, i, , , , +home.appliances, Instant Pot.*, , , , , +home.appliances, Vitamix .*, , , , , +home.housewares, .* Pill Cutter .*, , , , , +home.housewares, hemp mats .*, i, , , , +home.housewares, Melitta .*, , , , , +home.housewares, Seventh Generation .*, , , 10, , +home.housewares, .* coffee mug .*, i, , , , +home.housewares, .* cutting board.*, i, , , , +home.housewares, .* Dish Drying Mat .*, , , , , +home.housewares, .* Jar Lids.*, , , , , +home.housewares, .* Kitchen Utensils .*, , , , , +home.housewares, .* Knife Edge Guards.*, , , , , +home.housewares, .* Mason Jar Shaker Lids .*, , , , , +home.housewares, .* Rubber Spatula.*, , , , , +home.housewares, .* spatula .*, i, , , , +home.housewares, .*Coffee Carafe Tea Pot.*, , amz.sandy.retail.ord, , , +home.housewares, .*Dish Towels.*, , amz.sandy.retail.ord, , , +home.housewares, .*Donut Pan.*, , , , , +home.housewares, .*Foam Ear Plugs.*, , , , , +home.housewares, .*Food Storage Containers.*, , , , , +home.housewares, .*GREENPAN.*, , , , , +home.housewares, .*kitchen knife.*, i, , , , +home.housewares, Euro Cuisine.*, , , , , +home.housewares, Fino Pour-Over Coffee Brewing Filter Cone.*, , , , , +home.housewares, GMISUN Oil Dispenser.*, , , , , +home.housewares, GreenPan.*, , , , , +home.housewares, IDEATECH.*, , , , , +home.housewares, LOVE MOMENT.*, , , , , +home.housewares, Misen .*, , , , , +home.housewares, Noble Home .*, , , , , +home.housewares, OXO .*, , , , , +home.housewares, Pour Over Coffee Dripper.*, , , , , +home.housewares, souper cubes .*, i, , , , +home.housewares, SPLF .*, , , , , +home.housewares, TeamFar .*, , , , , +home.housewares, Wooden Spurtle .*, , , , , +home.housewares, Zeppoli .*, , , , , +medical.services, .* UCHEALTH .*, , chase, , , +medical.services, .* UCHEALTH .*, , discover, , , +medical.services, .*Ortho Spine Ctr Rockies.*, , , , , +medical.services, .*PERFORMANCE PHYSICAL THER.*, , , , , +medical.services, .*UCHEALTH .*, , discover, , , +medical.services, .*UCHEALTH.*, , chase, , , +medical.services, ACCTVERIFY ATLAS\.MD, , firstbank, , , +medical.services, ADVANCED MEDICAL IMAGING.*, , chase, , , +medical.services, AUDIOLOGY GROUP .*, , , , , +health.practitioners, CHECK # 4035, , firstbank, , shelby kahl, +medical.services, CHECK # 4036, , firstbank, , , +health.practitioners, CHECK # 4038, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4039, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4042, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4043, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4045, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4048, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4053, , firstbank, , shelby kahl, +medical.services, CHECK # 4071, , firstbank, , copay kidney dr., +health.practitioners, CHECK # 4075, , firstbank, , ashley, +health.practitioners, CHECK # 4078, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4081, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4082, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4083, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4085, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4086, , firstbank, , shelby kahl, +health.practitioners, CHECK # 4090, , firstbank, , shelby kahl, +supplements, CHECK # 4091, , firstbank, , shelby kahl, +medical.services, CHIROPRACTIC ASSOCIATES.*, , , , , +health.practitioners, IV NUTRITION .*, , , , , +medical.services, NORTH VISTA.*, , , , , +medical.services, OBGA MY OBGYN .*, , discover, , , +medical.services, UCHEALTH .*, , firstbank, , , +medical.subscriptions, DFC OF NOC ATLAS\.MD, , firstbank, , , +medical.subscriptions, DFC OF NOCO .*, , chase, , , +medical.subscriptions, DFC OF NOCO .*, , discover, , , +medical.supplies, WALGREENS .*, , chase, , , +medicines, .*BELMAR PHARMACY.*, , chase, , , +medicines, .*EBM MEDICAL.*, , chase, , , +medicines, .*SAFEWAY.*, , , , , +medicines, GOOD DAY PHARMACY.*, , , , , +medicines, UpSpring .*, , , , , +dues-subscriptions-memberships, CHECK # 4047, , firstbank, , AARP, +dues-subscriptions-memberships, CONSUMERREPORTS.*, , chase, , , +dues-subscriptions-memberships, prime membership fee, i, amz.sandy.dig.ord, 10, , +mortgage, CASH PENNYMAC.*, , firstbank, , , +office.supplies, .*Ballpoint Pens.*, , amz.sandy.retail.ord, , , +office.supplies, .*Magnifying Glass.*, , , , , +office.supplies, .*NOKBOX.*, , chase, , , +office.supplies, .*Spiral Notebook.*, , , , , +office.supplies, HP Printer Paper .*, , , , , +office.supplies, HP.*Ink Cartridge.*, , , , , +office.supplies, Pentel .*, , , , , +passports, CHECK # 4050, , firstbank, , larimer county clerk, +passports, PAYMENT PASSPORTSERVICES, , firstbank, , us dept of state, +passports, PAYMENT PASSPORTSERVICES, , firstbank, , us dept of state, +payment, .* CHASE CREDIT CRD, , firstbank, , , +payment, .*AMAZON PRIME.*, , chase, , , +payment, .*ANNUAL MEMBERSHIP FEE.*, , chase, , , +payment, .*Payment Thank You.*, , , , , +payment, AMAZON MKTPL.*, , chase, , , +payment, Amazon Prime.*, , chase, , , +payment, Amazon\.com.*, , , , , +payment, AMZN Mktp.*, , , , , +payment, CASHBACK BONUS REDEMPTION .*, , discover, , , +payment, INST XFER PAYPAL, , firstbank, , , +payment, Kindle Svcs.*, , chase, 10, , +payment, PAYMENT CITI.*, , firstbank, , , payment, PAYPAL \*.*, , chase, 10, , 2025 -payment, PHONE PAY DISCOVER, , firstbank -payment, PHONE PAYMENT .*, , discover -payment, PP\*.*, , chase, 10 -payment, Prime Video Channels .*, , chase, 10 -payment, WHOLEFDS.* -personal.care, .* Scalp Serum .* -personal.care, .* Shaving Soap .* -personal.care, .*dental care.*, i, -personal.care, .*Dental Floss.*, , amz.sandy.retail.ord -personal.care, .*Eye Cream.*, , amz.sandy.retail.ord -personal.care, .*Eye Wrinkle Cream.*, , amz.sandy.retail.ord -personal.care, .*HENSONSHAVI.* -personal.care, .*Nail File.*, i, -personal.care, .*shave soap.*, i, -personal.care, .*Skin Scraping Tool.*, , amz.sandy.retail.ord -personal.care, .*VIBRANTBLUE .* -personal.care, AYR Saline.* -personal.care, Biossance.*, , amz.sandy.retail.ord -personal.care, Elysian Honey.* -personal.care, Kiss My Face.* -personal.care, Life-flo.* -personal.care, MagniLife.* -personal.care, micropore tape .*, i -personal.care, MyChelle Dermaceuticals .* -personal.care, Nioxin .* -personal.care, OraWellness .* -personal.care, PAYPAL \*BIOSSANCE .*, , chase -personal.care, PROFOUND HEALTH LTD .*, , , , eye drops -personal.care, Sondery .* -personal.care, The Naked Bee .* -personal.care, THG Beauty Limited .*, , paypal -personal.care, Zeasorb AF Jock Itch Powder.* -personal.electronics, .* apple watch .*, i -personal.care, .* toothbrush .*, i -personal.electronics, LiCB .* Watch Battery.* -personal.electronics, Moto G .* -personal.electronics, PAYPAL .WALMART COM .*, , , , hearing aid batteries -personal.electronics, Poetic Revolution .* -personal.electronics, RingConn .* -dues-subscriptions-memberships, .*BUFFMUFF.* -dues-subscriptions-memberships, CHECK # 4070, , firstbank, , green america -dues-subscriptions-memberships, GANNETT MEDIA .*, , chase -dues-subscriptions-memberships, Google .* -1\.08 USD.*, , paypal, 10 -dues-subscriptions-memberships, Google .* -3\.26 USD.*, , paypal, 10 -dues-subscriptions-memberships, INSTAPAPER .*, , chase -dues-subscriptions-memberships, PAYPAL \*CONSUMERLAB .*, , chase -supplements, .* Boron Complex .* -supplements, .*Ancestral Supplements.*, , amz.sandy.retail.ord -supplements, .*ANTIAGINGCO.* -supplements, .*BioGaia Gastrus.*, , amz.sandy.retail.ord -supplements, .*Glutamine.* -supplements, .*Gummies.* -supplements, .*LIFEEXTENSI.* -supplements, .*Melatonin.*, , amz.sandy.retail.ord -supplements, .*PUREFORMULA .* -supplements, .*SEEKHEALTH.* -supplements, .*SOMALIFE.* -supplements, .*Veggie Capsules.*, , amz.sandy.retail.ord -supplements, Biosil.*, , amz.sandy.retail.ord -supplements, Biotics.*, , amz.sandy.retail.ord -supplements, Boiron.*, , amz.sandy.retail.ord -supplements, Cardiovascular Research.* -supplements, CHECK # 4040, , firstbank, , shelby kahl -supplements, Designs for Health.* -supplements, Doctor's Best.* -supplements, Dr\. Berg.* -supplements, Dr\. Clark.* -supplements, Dr\. Mercola.* -supplements, DrFormulas.* -supplements, Enzymedica.* -supplements, FULLSCRIPT.*, , chase -supplements, Garden of Life.* -supplements, Health Thru Nutrition.* -supplements, HealthForce.* -supplements, Horbäach.* -supplements, Hyland's.* -supplements, iHerb.*, , paypal, 10 -supplements, Jarrow.* -supplements, keto chow .*, i -supplements, Life Extension.* -supplements, Living Silica Collagen Booster Liquid.* -supplements, Magnesium L Threonate Capsules.* -supplements, MARCO PHARMA.* -supplements, MICROVASCULAR.*, , chase, , endocaylx -supplements, MIRACLES OF HEALTH .*, , chase -supplements, multivitamin .*, i -supplements, Nattokinase .* -supplements, Natural Factors.* -supplements, Nature's Way .* -supplements, NEORA.* -supplements, New Chapter .* -supplements, Nordic Naturals .* -supplements, NOW .* -supplements, Nutricost.* -supplements, PAYPAL \*LIFE ENTH .*, , chase -supplements, PAYPAL \*SWANSONHEAL .*, , chase, , -supplements, PAYPAL \*VITACOSTCOM .*, , chase -supplements, Premier Research Labs .* -supplements, Pure Encapsulations .* -supplements, PureFormulas .*, , paypal, 10 -supplements, Renew Life .* -supplements, RnA ReSet .* -supplements, SeabuckWonders .*, i -supplements, SeekingHealth .*, , paypal, 10 -supplements, SOLARAY .*, i -supplements, Solgar .* -supplements, Source Naturals .* -supplements, Standard Process .* -supplements, SUBSCRIBE .* SAVE .*, , chase, , paleo valley -supplements, Swanson Health Products.*, , paypal -supplements, Teliaoils .* -supplements, THORNE .* -supplements, Trace Minerals Mega-Mag.* -supplements, Vital Earth .* -supplements, Zazzee .* -taxes.refunds, .* DEPT REVENUE, , firstbank -taxes.refunds, TAX REF IRS TREAS.*, , firstbank -taxes, TAXES PEAKVIEW.*, , firstbank -taxes, USATAXPYMT IRS.*, , firstbank -tips, .*Amazon Tips.*, , chase -unknown, .*OCHEALTH .* -supplements, CHECK # 4034, , firstbank, , shelby kahl -unknown, CHECK # 4084, , firstbank, , -unknown, CHECK # 4088, , firstbank, , -unknown, Colorado Interactive.*, , paypal -unknown, Google .* -31\.15.*, , paypal -unknown, MICROSOFT.*, , discover -unknown, PAYPAL \*COLORADOINT .*, , chase -unknown, Spigen .* -utilities.garbage, .* REPUBLICSERVICES, , firstbank -utilities.electricity/water, CITY OF FORT COLLINS .*, , discover -utilities.electricity/water, VISA CITY OF FORT COLLINS.*, , firstbank -utilities.gas, XCELENERGY.*, , firstbank -vision.eyewear, DOOViC.* -vision.eyewear, ThinOptics .* -vision.services, .*EYECARE ASSOCIATES.*, , chase -vision.services, EYECARE ASSOCIATES .*, , discover -home.yard-garden.equipment, .* Drip Irrigation .* -home.yard-garden.equipment, .* Replacement Spool for Black and Decker String Trimmer .* -home.yard-garden.equipment, .*APEX GARDEN Replacement Canopy Top.*, , amz.sandy.retail.ord -home.yard-garden.equipment, .*ETSY INC.* -home.yard-garden.equipment, .*Water Hose Nozzle.* -home.yard-garden.equipment, Bug Zapper.*, , amz.sandy.retail.ord -home.yard-garden.equipment, Drip Irrigation .* -home.yard-garden.equipment, ego power.*, i -home.yard-garden.equipment, Flame King .* -home.yard-garden.equipment, Yzert .* -home.yard-garden.services, .*ROYAL TURF.* -home.yard-garden.services, CHECK # 4052, , firstbank, , chris coopenhaver -home.yard-garden.services, CHECK # 4080, , firstbank, , chris coopenhaver -medical.services, CHECK # 4087, , firstbank, , hospital? +payment, PHONE PAY DISCOVER, , firstbank, , , +payment, PHONE PAYMENT .*, , discover, , , +payment, PP\*.*, , chase, 10, , +payment, Prime Video Channels .*, , chase, 10, , +payment, WHOLEFDS.*, , , , , +personal.care, .* Scalp Serum .*, , , , , +personal.care, .* Shaving Soap .*, , , , , +personal.care, .*dental care.*, i, , , , +personal.care, .*Dental Floss.*, , amz.sandy.retail.ord, , , +personal.care, .*Eye Cream.*, , amz.sandy.retail.ord, , , +personal.care, .*Eye Wrinkle Cream.*, , amz.sandy.retail.ord, , , +personal.care, .*HENSONSHAVI.*, , , , , +personal.care, .*Nail File.*, i, , , , +personal.care, .*shave soap.*, i, , , , +personal.care, .*Skin Scraping Tool.*, , amz.sandy.retail.ord, , , +personal.care, .*VIBRANTBLUE .*, , , , , +personal.care, AYR Saline.*, , , , , +personal.care, Biossance.*, , amz.sandy.retail.ord, , , +personal.care, Elysian Honey.*, , , , , +personal.care, Kiss My Face.*, , , , , +personal.care, Life-flo.*, , , , , +personal.care, MagniLife.*, , , , , +personal.care, micropore tape .*, i, , , , +personal.care, MyChelle Dermaceuticals .*, , , , , +personal.care, Nioxin .*, , , , , +personal.care, OraWellness .*, , , , , +personal.care, PAYPAL \*BIOSSANCE .*, , chase, , , +personal.care, PROFOUND HEALTH LTD .*, , , , eye drops, +personal.care, Sondery .*, , , , , +personal.care, The Naked Bee .*, , , , , +personal.care, THG Beauty Limited .*, , paypal, , , +personal.care, Zeasorb AF Jock Itch Powder.*, , , , , +personal.electronics, .* apple watch .*, i, , , , +personal.care, .* toothbrush .*, i, , , , +personal.electronics, LiCB .* Watch Battery.*, , , , , +personal.electronics, Moto G .*, , , , , +personal.electronics, PAYPAL .WALMART COM .*, , , , hearing aid batteries, +personal.electronics, Poetic Revolution .*, , , , , +personal.electronics, RingConn .*, , , , , +dues-subscriptions-memberships, .*BUFFMUFF.*, , , , , +dues-subscriptions-memberships, CHECK # 4070, , firstbank, , green america, +dues-subscriptions-memberships, GANNETT MEDIA .*, , chase, , , +dues-subscriptions-memberships, Google .* -1\.08 USD.*, , paypal, 10, , +dues-subscriptions-memberships, Google .* -3\.26 USD.*, , paypal, 10, , +dues-subscriptions-memberships, INSTAPAPER .*, , chase, , , +dues-subscriptions-memberships, PAYPAL \*CONSUMERLAB .*, , chase, , , +supplements, .* Boron Complex .*, , , , , +supplements, .*Ancestral Supplements.*, , amz.sandy.retail.ord, , , +supplements, .*ANTIAGINGCO.*, , , , , +supplements, .*BioGaia Gastrus.*, , amz.sandy.retail.ord, , , +supplements, .*Glutamine.*, , , , , +supplements, .*Gummies.*, , , , , +supplements, .*LIFEEXTENSI.*, , , , , +supplements, .*Melatonin.*, , amz.sandy.retail.ord, , , +supplements, .*PUREFORMULA .*, , , , , +supplements, .*SEEKHEALTH.*, , , , , +supplements, .*SOMALIFE.*, , , , , +supplements, .*Veggie Capsules.*, , amz.sandy.retail.ord, , , +supplements, Biosil.*, , amz.sandy.retail.ord, , , +supplements, Biotics.*, , amz.sandy.retail.ord, , , +supplements, Boiron.*, , amz.sandy.retail.ord, , , +supplements, Cardiovascular Research.*, , , , , +supplements, CHECK # 4040, , firstbank, , shelby kahl, +supplements, Designs for Health.*, , , , , +supplements, Doctor's Best.*, , , , , +supplements, Dr\. Berg.*, , , , , +supplements, Dr\. Clark.*, , , , , +supplements, Dr\. Mercola.*, , , , , +supplements, DrFormulas.*, , , , , +supplements, Enzymedica.*, , , , , +supplements, FULLSCRIPT.*, , chase, , , +supplements, Garden of Life.*, , , , , +supplements, Health Thru Nutrition.*, , , , , +supplements, HealthForce.*, , , , , +supplements, Horbäach.*, , , , , +supplements, Hyland's.*, , , , , +supplements, iHerb.*, , paypal, 10, , +supplements, Jarrow.*, , , , , +supplements, keto chow .*, i, , , , +supplements, Life Extension.*, , , , , +supplements, Living Silica Collagen Booster Liquid.*, , , , , +supplements, Magnesium L Threonate Capsules.*, , , , , +supplements, MARCO PHARMA.*, , , , , +supplements, MICROVASCULAR.*, , chase, , endocaylx, +supplements, MIRACLES OF HEALTH .*, , chase, , , +supplements, multivitamin .*, i, , , , +supplements, Nattokinase .*, , , , , +supplements, Natural Factors.*, , , , , +supplements, Nature's Way .*, , , , , +supplements, NEORA.*, , , , , +supplements, New Chapter .*, , , , , +supplements, Nordic Naturals .*, , , , , +supplements, NOW .*, , , , , +supplements, Nutricost.*, , , , , +supplements, PAYPAL \*LIFE ENTH .*, , chase, , , +supplements, PAYPAL \*SWANSONHEAL .*, , chase, , , +supplements, PAYPAL \*VITACOSTCOM .*, , chase, , , +supplements, Premier Research Labs .*, , , , , +supplements, Pure Encapsulations .*, , , , , +supplements, PureFormulas .*, , paypal, 10, , +supplements, Renew Life .*, , , , , +supplements, RnA ReSet .*, , , , , +supplements, SeabuckWonders .*, i, , , , +supplements, SeekingHealth .*, , paypal, 10, , +supplements, SOLARAY .*, i, , , , +supplements, Solgar .*, , , , , +supplements, Source Naturals .*, , , , , +supplements, Standard Process .*, , , , , +supplements, SUBSCRIBE .* SAVE .*, , chase, , paleo valley, +supplements, Swanson Health Products.*, , paypal, , , +supplements, Teliaoils .*, , , , , +supplements, THORNE .*, , , , , +supplements, Trace Minerals Mega-Mag.*, , , , , +supplements, Vital Earth .*, , , , , +supplements, Zazzee .*, , , , , +taxes.refunds, .* DEPT REVENUE, , firstbank, , , +taxes.refunds, TAX REF IRS TREAS.*, , firstbank, , , +taxes, TAXES PEAKVIEW.*, , firstbank, , , +taxes, USATAXPYMT IRS.*, , firstbank, , , +tips, .*Amazon Tips.*, , chase, , , +unknown, .*OCHEALTH .*, , , , , +supplements, CHECK # 4034, , firstbank, , shelby kahl, +unknown, CHECK # 4084, , firstbank, , , +unknown, CHECK # 4088, , firstbank, , , +unknown, Colorado Interactive.*, , paypal, , , +unknown, Google .* -31\.15.*, , paypal, , , +unknown, MICROSOFT.*, , discover, , , +unknown, PAYPAL \*COLORADOINT .*, , chase, , , +unknown, Spigen .*, , , , , +utilities.garbage, .* REPUBLICSERVICES, , firstbank, , , +utilities.electricity/water, CITY OF FORT COLLINS .*, , discover, , , +utilities.electricity/water, VISA CITY OF FORT COLLINS.*, , firstbank, , , +utilities.gas, XCELENERGY.*, , firstbank, , , +vision.eyewear, DOOViC.*, , , , , +vision.eyewear, ThinOptics .*, , , , , +vision.services, .*EYECARE ASSOCIATES.*, , chase, , , +vision.services, EYECARE ASSOCIATES .*, , discover, , , +home.yard-garden.equipment, .* Drip Irrigation .*, , , , , +home.yard-garden.equipment, .* Replacement Spool for Black and Decker String Trimmer .*, , , , , +home.yard-garden.equipment, .*APEX GARDEN Replacement Canopy Top.*, , amz.sandy.retail.ord, , , +home.yard-garden.equipment, .*ETSY INC.*, , , , , +home.yard-garden.equipment, .*Water Hose Nozzle.*, , , , , +home.yard-garden.equipment, Bug Zapper.*, , amz.sandy.retail.ord, , , +home.yard-garden.equipment, Drip Irrigation .*, , , , , +home.yard-garden.equipment, ego power.*, i, , , , +home.yard-garden.equipment, Flame King .*, , , , , +home.yard-garden.equipment, Yzert .*, , , , , +home.yard-garden.services, .*ROYAL TURF.*, , , , , +home.yard-garden.services, CHECK # 4052, , firstbank, , chris coopenhaver, +home.yard-garden.services, CHECK # 4080, , firstbank, , chris coopenhaver, +medical.services, CHECK # 4087, , firstbank, , hospital?, diff --git a/run b/run index 90d7483..7797553 100755 --- a/run +++ b/run @@ -1,3 +1,8 @@ #!/bin/bash cd "$(dirname "${BASH_SOURCE[0]}")" -java -cp target/*.jar com.stephenschafer.budget.schema.Schema "$@" +if java -cp target/*.jar com.stephenschafer.budget.schema.Schema "$@"; then + echo "success" +else + echo "failure" + exit 1 +fi diff --git a/run-categorize b/run-categorize index 1cb8ef8..25548ae 100755 --- a/run-categorize +++ b/run-categorize @@ -1,4 +1,8 @@ #!/bin/bash cd "$(dirname "${BASH_SOURCE[0]}")" -./run categorize budget.properties - +if ./run categorize budget.properties > run-categorize.log 2> run-categorize.err.log; then + echo "success" +else + echo "failure" + exit 1 +fi \ No newline at end of file diff --git a/run-debug b/run-debug index 5763704..e152673 100755 --- a/run-debug +++ b/run-debug @@ -1,4 +1,9 @@ #!/bin/bash cd "$(dirname "${BASH_SOURCE[0]}")" JVM_ARGS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8004" -java $JVM_ARGS -cp target/*.jar com.stephenschafer.budget.schema.Schema "$@" +if java $JVM_ARGS -cp target/*.jar com.stephenschafer.budget.schema.Schema "$@"; then + echo "success" +else + echo "failure" + exit 1 +fi diff --git a/run-debug-load b/run-debug-load deleted file mode 100755 index 28e1336..0000000 --- a/run-debug-load +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -cd "$(dirname "${BASH_SOURCE[0]}")" -./run-debug load-csv budget.properties trilium Budget - diff --git a/run-debug-load-csv b/run-debug-load-csv new file mode 100755 index 0000000..16ef91a --- /dev/null +++ b/run-debug-load-csv @@ -0,0 +1,9 @@ +#!/bin/bash +cd "$(dirname "${BASH_SOURCE[0]}")" +if ./run-debug load-csv budget.properties trilium Budget > run-debug-load-csv.log 2> run-debug-load-csv.err.log; then + echo "success" +else + echo "failure" + exit 1 +fi + diff --git a/run-init-db b/run-init-db new file mode 100755 index 0000000..3366c86 --- /dev/null +++ b/run-init-db @@ -0,0 +1,9 @@ +#!/bin/bash +cd "$(dirname "${BASH_SOURCE[0]}")" +if ./run init-db budget.properties > run-init-db.log 2> run-init-db.err.log; then + echo "success" +else + echo "failure" + exit 1 +fi + diff --git a/run-load-csv b/run-load-csv index 5da0f8f..615de42 100755 --- a/run-load-csv +++ b/run-load-csv @@ -1,4 +1,8 @@ #!/bin/bash cd "$(dirname "${BASH_SOURCE[0]}")" -./run load-csv budget.properties trilium Budget - +if ./run load-csv budget.properties trilium Budget > run-load-csv.log 2> run-load-csv.err.log; then + echo "success" +else + echo "failure" + exit 1 +fi diff --git a/run-load-regex b/run-load-regex index e2254f5..de91fca 100755 --- a/run-load-regex +++ b/run-load-regex @@ -1,4 +1,9 @@ #!/bin/bash cd "$(dirname "${BASH_SOURCE[0]}")" -./run load-regex budget.properties +if ./run load-regex budget.properties > run-load-regex.log 2> run-load-regex.err.log; then + echo "success" +else + echo "failure" + exit 1 +fi diff --git a/run-process b/run-process index cfd7841..a64733b 100755 --- a/run-process +++ b/run-process @@ -1,4 +1,8 @@ #!/bin/bash cd "$(dirname "${BASH_SOURCE[0]}")" -./run process budget.properties - +if ./run process budget.properties > run-process.log 2> run-process.err.log; then + echo "success" +else + echo "failure" + return 1 +fi diff --git a/run-save-regex b/run-save-regex new file mode 100755 index 0000000..377d06b --- /dev/null +++ b/run-save-regex @@ -0,0 +1,8 @@ +#!/bin/bash +cd "$(dirname "${BASH_SOURCE[0]}")" +if ./run save-regex budget.properties > run-save-regex.log 2> run-save-regex.err.log; then + echo "success" +else + echo "failure" + exit 1 +fi diff --git a/src/main/java/com/stephenschafer/budget/schema/CatRegex.java b/src/main/java/com/stephenschafer/budget/schema/CatRegex.java index d97add8..2ab5082 100644 --- a/src/main/java/com/stephenschafer/budget/schema/CatRegex.java +++ b/src/main/java/com/stephenschafer/budget/schema/CatRegex.java @@ -82,6 +82,31 @@ public class CatRegex { this.year = year; } + public String toLine() { + final StringBuilder sb = new StringBuilder(); + sb.append(category); + sb.append(", "); + sb.append(pattern.pattern()); + sb.append(", "); + String flags = ""; + if ((pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) { + flags += "i"; + } + if ((pattern.flags() & Pattern.MULTILINE) != 0) { + flags += "m"; + } + sb.append(flags); + sb.append(", "); + sb.append(source == null ? "" : source); + sb.append(", "); + sb.append(priority == 0 ? "" : String.valueOf(priority)); + sb.append(", "); + sb.append(extraDescription); + sb.append(", "); + sb.append(year == null ? "" : year.toString()); + return sb.toString(); + } + public String getSource() { return source; } diff --git a/src/main/java/com/stephenschafer/budget/schema/Category.java b/src/main/java/com/stephenschafer/budget/schema/Category.java index b85d3da..6da7767 100644 --- a/src/main/java/com/stephenschafer/budget/schema/Category.java +++ b/src/main/java/com/stephenschafer/budget/schema/Category.java @@ -18,6 +18,7 @@ class Category { if (parentId != null) { final Category parentCategory = categories.get(parentId); sb.append(parentCategory.getFullName(categories)); + sb.append("."); } sb.append(name); return sb.toString(); diff --git a/src/main/java/com/stephenschafer/budget/schema/Schema.java b/src/main/java/com/stephenschafer/budget/schema/Schema.java index 29899ea..2f69d8c 100644 --- a/src/main/java/com/stephenschafer/budget/schema/Schema.java +++ b/src/main/java/com/stephenschafer/budget/schema/Schema.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; +import java.io.PrintWriter; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; @@ -26,9 +27,12 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; +import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; @@ -115,17 +119,31 @@ public class Schema { final Schema schema = new Schema(argsHelper.get("properties file")); schema.loadRegex(); }); + PHASES.put("init-db", argsHelper -> { + final Schema schema = new Schema(argsHelper.get("properties file")); + schema.initDb(); + }); + PHASES.put("save-regex", argsHelper -> { + final Schema schema = new Schema(argsHelper.get("properties file")); + schema.saveRegex(); + }); PHASES.put("load-csv", argsHelper -> { final Schema schema = new Schema(argsHelper.get("properties file")); final String source = argsHelper.get("source"); final String name = argsHelper.get("name"); final Set years = new HashSet<>(); + final DatabaseInitializer databaseInitializer = year -> { + if (!years.contains(year)) { + years.add(year); + schema.initializeDatabase(year); + } + }; if ("file".equals(source)) { - years.addAll(schema.generate(null, 1, new File(name))); + schema.generate(null, 1, new File(name), databaseInitializer); } else if ("trilium".equals(source)) { schema.triliumLogin(schema.config.getTriliumPassword()); - years.addAll(schema.generate(null, 1, name)); + schema.generate(null, 1, name, databaseInitializer); } else { System.out.println("First argument must be 'file' or 'trilium'"); @@ -144,6 +162,10 @@ public class Schema { }); } + private interface DatabaseInitializer { + void initializeDatabase(String year) throws SQLException; + } + private void categorize() throws SQLException, UnsupportedEncodingException, FileNotFoundException, IOException { final Map> categoryMap = new HashMap<>(); @@ -175,7 +197,9 @@ public class Schema { final java.sql.Date date = rs.getDate(2); final String source = rs.getString(3); final String description = rs.getString(4); - final BigDecimal amount = rs.getBigDecimal(5); + final BigDecimal tmpAmount = rs.getBigDecimal(5); + final BigDecimal amount = tmpAmount == null ? new BigDecimal(0) + : tmpAmount; final Detail detail = new Detail(); detail.transactionId = transactionId; detail.source = source; @@ -258,8 +282,8 @@ public class Schema { "${catRegex}", catRegex.toString())); try (PreparedStatement insertStmt = connection.prepareStatement( insertSql)) { - insertStmt.setInt(1, transactionId); - insertStmt.setInt(2, catRegex.getId()); + insertStmt.setInt(1, catRegex.getId()); + insertStmt.setInt(2, transactionId); insertStmt.execute(); } } @@ -297,14 +321,58 @@ public class Schema { } } + private void initializeDatabase(final String year) throws SQLException { + try (Connection connection = pool.getConnection(8, false, true)) { + System.out.println("Initializing database budget_" + year); + dbExecute(connection, "drop database if exists budget_" + year); + dbExecute(connection, "create database budget_" + year); + } + } + + private void saveRegex() throws SQLException, IOException { + try (Connection connection = pool.getConnection(8, false, true)) { + final List catRegexes = getCatRegexes(connection); + final File catRegexFile = new File("category-regex.lst"); + if (catRegexFile.exists()) { + final DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss"); + final String newFilename = "category-regex-${timestamp}.lst" // + .replace("${timestamp}", df.format(new Date())); + catRegexFile.renameTo(new File(newFilename)); + } + try (PrintWriter pw = new PrintWriter(new FileOutputStream(catRegexFile))) { + for (final CatRegex catRegex : catRegexes) { + pw.println(catRegex.toLine()); + } + } + } + } + + private void initDb() throws SQLException, IOException { + try (Connection connection = pool.getConnection(8, false, true)) { + dbExecute(connection, "drop table if exists " + "budget.category"); + dbExecute(connection, Util.getResourceAsString("createCategoryTable.sql") // + .replace("${databaseName}", "budget")); + final Set years = getYears(connection); + for (final String year : years) { + dbExecute(connection, Util.getResourceAsString("dropBudgetAmountTable.sql") // + .replace("${databaseName}", "budget_" + year)); + dbExecute(connection, Util.getResourceAsString("createBudgetAmountTable.sql") // + .replace("${databaseName}", "budget_" + year)); + } + dbExecute(connection, "drop table if exists budget.regex"); + dbExecute(connection, Util.getResourceAsString("createRegexTable.sql") // + .replace("${databaseName}", "budget")); + } + } + private void loadRegex() throws SQLException, IOException { try (Connection connection = pool.getConnection(8, false, true)) { dbExecute(connection, "drop table if exists " + "budget.category"); dbExecute(connection, Util.getResourceAsString("createCategoryTable.sql") // .replace("${databaseName}", "budget")); dbExecute(connection, "drop table if exists budget.regex"); - dbExecute(connection, Util.getResourceAsString("createRegexTable.sql").replace( - "${databaseName}", "budget")); + dbExecute(connection, Util.getResourceAsString("createRegexTable.sql") // + .replace("${databaseName}", "budget")); final List catRegexes = new ArrayList<>(); final File catRegexesFile = new File("category-regex.lst"); try (BufferedReader reader = new BufferedReader( @@ -324,8 +392,8 @@ public class Schema { Map loadCategories(final Connection connection) throws IOException, SQLException { final Map categories = new HashMap<>(); - final String sql = Util.getResourceAsString("getCategories.sql").replace("${databaseName}", - "budget"); + final String sql = Util.getResourceAsString("getCategories.sql") // + .replace("${databaseName}", "budget"); try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -346,8 +414,8 @@ public class Schema { List getCatRegexes(final Connection connection) throws IOException, SQLException { final List catRegexes = new ArrayList<>(); final Map categories = loadCategories(connection); - final String sql = Util.getResourceAsString("getRegexes.sql").replace("${databaseName}", - "budget"); + final String sql = Util.getResourceAsString("getRegexes.sql") // + .replace("${databaseName}", "budget"); try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -380,10 +448,11 @@ public class Schema { final String tableName = "regex"; if (!tableExists(connection, null, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget").replace("${tableName}", tableName)); + "${databaseName}", "budget") // + .replace("${tableName}", tableName)); } - final String insertSql = Util.getResourceAsString("insertRegex.sql").replace( - "${databaseName}", "budget"); + final String insertSql = Util.getResourceAsString("insertRegex.sql") // + .replace("${databaseName}", "budget"); for (final CatRegex catRegex : catRegexes) { final Integer categoryId = getCategoryId(connection, catRegex.getCategory()); try (PreparedStatement insertStmt = connection.prepareStatement(insertSql, @@ -442,8 +511,8 @@ public class Schema { private Set getYears(final Connection connection) throws IOException, SQLException { final Set years = new HashSet<>(); - final String sql = Util.getResourceAsString("getYears.sql").replace("${databaseName}", - "budget"); + final String sql = Util.getResourceAsString("getYears.sql") // + .replace("${databaseName}", "budget"); try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -459,20 +528,22 @@ public class Schema { try (Connection connection = pool.getConnection(8, false, true)) { final Set years = getYears(connection); for (final String year : years) { - dbExecute(connection, "drop table if exists budget_" + year + ".transaction"); - dbExecute(connection, - Util.getResourceAsString("createTransactionTable.sql").replace( - "${databaseName}", "budget_" + year)); - ingestAmazonDigitalOrders(connection, year, "sandy"); - ingestAmazonDigitalOrders(connection, year, "steve"); - ingestAmazonDigitalReturns(connection, year, "sandy"); - ingestAmazonDigitalReturns(connection, year, "steve"); - ingestAmazonRetailOrders(connection, year, "sandy"); - ingestAmazonRetailOrders(connection, year, "steve"); + final String databaseName = "budget_${year}".replace("${year}", year); + dbExecute(connection, Util.getResourceAsString("dropTransactionTable.sql") // + .replace("${databaseName}", databaseName)); + dbExecute(connection, Util.getResourceAsString("createTransactionTable.sql") // + .replace("${databaseName}", databaseName)); + ingestAmazonDigitalOrders(connection, year, "Sandy"); + ingestAmazonDigitalOrders(connection, year, "Steve"); + ingestAmazonDigitalReturns(connection, year, "Sandy"); + ingestAmazonDigitalReturns(connection, year, "Steve"); + ingestAmazonRetailOrders(connection, year, "Sandy"); + ingestAmazonRetailOrders(connection, year, "Steve"); ingestChase(connection, year); ingestDiscover(connection, year); ingestCiti(connection, year); ingestFirstBank(connection, year); + ingestElevations(connection, year); ingestPaypal(connection, year); } } @@ -481,36 +552,58 @@ public class Schema { private void ingestAmazonDigitalOrders(final Connection connection, final String year, final String person) throws IOException, SQLException { System.out.println("process amazon digital orders budget_" + year + " " + person); - final String tableName = "amz_${person}_dig_ord_dig_items".replace("${person}", person); + String triliumName = "Amazon.${person}.Digital-Ordering.Digital Items" // + .replace("${person}", person); + final String tableName = getTableName(triliumName); + if (tableName == null) { + System.out.println("Table corresponding to " + triliumName + " not found"); + return; + } if (!tableExists(connection, year, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); + return; + } + triliumName = "Amazon.${person}.Digital-Ordering.Digital Orders" // + .replace("${person}", person); + final String digOrdersTableName = getTableName(triliumName); + if (digOrdersTableName == null) { + System.out.println("Table corresponding to " + triliumName + " not found"); return; } - final String digOrdersTableName = "amz_${person}_dig_ord_dig_orders".replace("${person}", - person); if (!tableExists(connection, year, digOrdersTableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", digOrdersTableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", digOrdersTableName)); + return; + } + triliumName = "Amazon.${person}.Digital-Ordering.Digital Orders Monetary" // + .replace("${person}", person); + // "amz_${person}_dig_ord_dig_orders_monetary" + final String digOrdersMonetaryTableName = getTableName(triliumName); + if (digOrdersMonetaryTableName == null) { + System.out.println("Table corresponding to " + triliumName + " not found"); return; } - final String digOrdersMonetaryTableName = "amz_${person}_dig_ord_dig_orders_monetary".replace( - "${person}", person); if (!tableExists(connection, year, digOrdersMonetaryTableName)) { - System.out.println( - " table ${databaseName}.${tableName} does not exist".replace("${databaseName}", - "budget_" + year).replace("${tableName}", digOrdersMonetaryTableName)); + System.out.println(" table ${databaseName}.${tableName} does not exist".replace( + "${databaseName}", "budget_" + year) // + .replace("${tableName}", digOrdersMonetaryTableName)); return; } - final String sql = Util.getResourceAsString("getDigitalOrders.sql").replace( - "${databaseName}", "budget_" + year).replace("${person}", person).replace( - "${productNameCol}", "2023".equals(year) ? "title" : "product_name").replace( - "${digOrdItems}", tableName).replace("${digOrders}", - digOrdersTableName).replace("${digOrdersMonetary}", - digOrdersMonetaryTableName); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year).replace("${person}", person); - final String source = "amz.${person}.dig.ord".replace("${person}", person); + final String databaseName = "budget_${year}".replace("${year}", year); + final String sql = Util.getResourceAsString("getDigitalOrders.sql") // + .replace("${databaseName}", databaseName) // + .replace("${person}", person) // + .replace("${productNameCol}", "2023".equals(year) ? "title" : "product_name") // + .replace("${digOrdItems}", tableName) // + .replace("${digOrders}", digOrdersTableName) // + .replace("${digOrdersMonetary}", digOrdersMonetaryTableName); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", databaseName) // + .replace("${person}", person); + final String source = "amz.${person}.dig.ord".replace("${person}", person.toLowerCase()); try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -541,19 +634,27 @@ public class Schema { private void ingestAmazonRetailOrders(final Connection connection, final String year, final String person) throws IOException, SQLException { System.out.println("process amazon retail orders " + year + " " + person); - final String tableName = "amz_${person}_retail_order_history".replace("${person}", person); - if (!tableExists(connection, year, tableName)) { - System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + final String triliumName = "Amazon.${person}.Retail.OrderHistory" // + .replace("${person}", person); + final String tableName = getTableName(triliumName); + if (tableName == null) { + System.out.println("Table corresponding to " + triliumName + " not found"); return; } - final String sql = Util.getResourceAsString("getRetailOrders.sql").replace( - "${databaseName}", "budget_" + year).replace("${person}", person).replace( - "${productNameCol}", "2023".equals(year) ? "title" : "product_name").replace( - "${retailOrders}", tableName); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year); - final String source = "amz.${person}.retail.ord".replace("${person}", person); + if (!tableExists(connection, year, tableName)) { + System.out.println(" table ${databaseName}.${tableName} does not exist".replace( + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); + return; + } + final String sql = Util.getResourceAsString("getRetailOrders.sql") // + .replace("${databaseName}", "budget_" + year) // + .replace("${person}", person) // + .replace("${productNameCol}", "2023".equals(year) ? "title" : "product_name") // + .replace("${retailOrders}", tableName); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); + final String source = "amz.${person}.retail.ord".replace("${person}", person.toLowerCase()); try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -587,13 +688,14 @@ public class Schema { final String tableName = "discover"; if (!tableExists(connection, year, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); return; } - final String sql = Util.getResourceAsString("getDiscover.sql").replace("${databaseName}", - "budget_" + year); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year); + final String sql = Util.getResourceAsString("getDiscover.sql") // + .replace("${databaseName}", "budget_" + year); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); final String source = "discover"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { @@ -628,13 +730,14 @@ public class Schema { final String tableName = "citi"; if (!tableExists(connection, year, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); return; } - final String sql = Util.getResourceAsString("getCiti.sql").replace("${databaseName}", - "budget_" + year); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year); + final String sql = Util.getResourceAsString("getCiti.sql") // + .replace("${databaseName}", "budget_" + year); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); final String source = "citi"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { @@ -669,13 +772,14 @@ public class Schema { final String tableName = "first_bank"; if (!tableExists(connection, year, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); return; } - final String sql = Util.getResourceAsString("getFirstBank.sql").replace("${databaseName}", - "budget_" + year); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year); + final String sql = Util.getResourceAsString("getFirstBank.sql") // + .replace("${databaseName}", "budget_" + year); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); final String source = "firstbank"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { @@ -703,19 +807,79 @@ public class Schema { } } + private void ingestElevations(final Connection connection, final String year) + throws IOException, SQLException { + System.out.println("process elevations " + year); + final String tableName = "elevations"; + if (!tableExists(connection, year, tableName)) { + System.out.println(" table ${databaseName}.${tableName} does not exist".replace( + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); + return; + } + final String sql = Util.getResourceAsString("getElevations.sql") // + .replace("${databaseName}", "budget_" + year); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); + final String source = "elevations"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + try (ResultSet resultSet = stmt.executeQuery()) { + while (resultSet.next()) { + int i = 0; + final String transactionId = resultSet.getString(++i); + final java.sql.Date postingDate = resultSet.getDate(++i); + final java.sql.Date effectiveDate = resultSet.getDate(++i); + final String transactionType = resultSet.getString(++i); + final BigDecimal amount = resultSet.getBigDecimal(++i); + final String checkNumber = resultSet.getString(++i); + final int referenceNumber = resultSet.getInt(++i); + final String description = resultSet.getString(++i); + final String transactionCategory = resultSet.getString(++i); + final String type = resultSet.getString(++i); + final BigDecimal balance = resultSet.getBigDecimal(++i); + final String memo = resultSet.getString(++i); + final Calendar cal = new GregorianCalendar(); + cal.setTime(postingDate); + if (cal.get(Calendar.YEAR) == Integer.parseInt(year)) { + try (PreparedStatement insertStmt = connection.prepareStatement( + insertSql)) { + System.out.println("Inserting elevations"); + System.out.println("transactionId = " + transactionId); + System.out.println("effectiveDate = " + effectiveDate); + System.out.println("transactionType = " + transactionType); + System.out.println("checkNumber = " + checkNumber); + System.out.println("referenceNumber = " + referenceNumber); + System.out.println("transactionCategory = " + transactionCategory); + System.out.println("balance = " + balance); + System.out.println("memo = " + memo); + insertStmt.setString(1, source); + insertStmt.setString(2, ""); // unique_id + insertStmt.setString(3, type); // type + insertStmt.setString(4, description); + insertStmt.setDate(5, postingDate); + insertStmt.setBigDecimal(6, amount.negate()); + insertStmt.execute(); + } + } + } + } + } + } + private void ingestChase(final Connection connection, final String year) throws IOException, SQLException { System.out.println("process chase " + year); final String tableName = "chase"; if (!tableExists(connection, year, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); return; } - final String sql = Util.getResourceAsString("getChase.sql").replace("${databaseName}", - "budget_" + year); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year); + final String sql = Util.getResourceAsString("getChase.sql") // + .replace("${databaseName}", "budget_" + year); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); final String source = "chase"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { @@ -750,13 +914,14 @@ public class Schema { final String tableName = "paypal"; if (!tableExists(connection, year, tableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", tableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", tableName)); return; } - final String sql = Util.getResourceAsString("getPaypal.sql").replace("${databaseName}", - "budget_" + year); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year); + final String sql = Util.getResourceAsString("getPaypal.sql") // + .replace("${databaseName}", "budget_" + year); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year); final String source = "paypal"; try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { @@ -821,29 +986,42 @@ public class Schema { private void ingestAmazonDigitalReturns(final Connection connection, final String year, final String person) throws IOException, SQLException { System.out.println("process amazon digital returns budget_" + year + " " + person); - final String digOrdItemsTableName = "amz_${person}_dig_ord_dig_items".replace("${person}", - person); - final String digOrdReturnsMonetaryTableName = "amz_${person}_dig_orders_returns_dig_orders_returns_monetary_1".replace( - "${person}", person); + String triliumName = "Amazon.${person}.Digital-Ordering.Digital Items" // + .replace("${person}", person); + final String digOrdItemsTableName = getTableName(triliumName); + if (digOrdItemsTableName == null) { + System.out.println("Table corresponding to " + triliumName + " not found"); + return; + } if (!tableExists(connection, year, digOrdItemsTableName)) { System.out.println(" table ${databaseName}.${tableName} does not exist".replace( - "${databaseName}", "budget_" + year).replace("${tableName}", digOrdItemsTableName)); + "${databaseName}", "budget_" + year) // + .replace("${tableName}", digOrdItemsTableName)); + return; + } + triliumName = "Amazon.${person}.Digital.Orders.Returns.Digital.Orders.Returns.Monetary.1" // + .replace("${person}", person); + final String digOrdReturnsMonetaryTableName = getTableName(triliumName); + if (digOrdReturnsMonetaryTableName == null) { + System.out.println("Table corresponding to " + triliumName + " not found"); return; } if (!tableExists(connection, year, digOrdReturnsMonetaryTableName)) { - System.out.println( - " table ${databaseName}.${tableName} does not exist".replace("${databaseName}", - "budget_" + year).replace("${tableName}", digOrdReturnsMonetaryTableName)); + System.out.println(" table ${databaseName}.${tableName} does not exist".replace( + "${databaseName}", "budget_" + year) // + .replace("${tableName}", digOrdReturnsMonetaryTableName)); return; } - final String sql = Util.getResourceAsString("getDigitalReturns.sql").replace( - "${databaseName}", "budget_" + year).replace("${person}", person).replace( - "${productNameCol}", "2023".equals(year) ? "title" : "product_name").replace( - "${digOrdItems}", digOrdItemsTableName).replace("${digOrdReturnsMonetary}", - digOrdReturnsMonetaryTableName); - final String insertSql = Util.getResourceAsString("insertTransaction.sql").replace( - "${databaseName}", "budget_" + year).replace("${person}", person); - final String source = "amz.${person}.dig.ret".replace("${person}", person); + final String sql = Util.getResourceAsString("getDigitalReturns.sql") // + .replace("${databaseName}", "budget_" + year) // + .replace("${person}", person) // + .replace("${productNameCol}", "2023".equals(year) ? "title" : "product_name") // + .replace("${digOrdItems}", digOrdItemsTableName) // + .replace("${digOrdReturnsMonetary}", digOrdReturnsMonetaryTableName); + final String insertSql = Util.getResourceAsString("insertTransaction.sql") // + .replace("${databaseName}", "budget_" + year) // + .replace("${person}", person); + final String source = "amz.${person}.dig.ret".replace("${person}", person.toLowerCase()); try (PreparedStatement stmt = connection.prepareStatement(sql)) { try (ResultSet resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -873,8 +1051,8 @@ public class Schema { private void linkRegex(final Connection connection, final String year, final Integer regexId, final int transactionId) throws IOException, SQLException { - final String sql = Util.getResourceAsString("updateRegexLink.sql").replace( - "${databaseName}", "budget_" + year); + final String sql = Util.getResourceAsString("updateRegexLink.sql") // + .replace("${databaseName}", "budget_" + year); try (PreparedStatement stmt = connection.prepareStatement(sql)) { stmt.setInt(1, regexId); stmt.setInt(2, transactionId); @@ -894,10 +1072,10 @@ public class Schema { private Integer getCategoryPartId(final Connection connection, final Integer parentCategoryId, final String categoryPart) throws SQLException, IOException { - final String getRootSql = Util.getResourceAsString("getRootCategoryId.sql").replace( - "${databaseName}", "budget"); - final String getChildSql = Util.getResourceAsString("getChildCategoryId.sql").replace( - "${databaseName}", "budget"); + final String getRootSql = Util.getResourceAsString("getRootCategoryId.sql") // + .replace("${databaseName}", "budget"); + final String getChildSql = Util.getResourceAsString("getChildCategoryId.sql") // + .replace("${databaseName}", "budget"); Integer categoryId = null; final String getSql = parentCategoryId == null ? getRootSql : getChildSql; try (PreparedStatement stmt = connection.prepareStatement(getSql)) { @@ -919,10 +1097,10 @@ public class Schema { } if (categoryId == null) { final String insertSql = parentCategoryId == null // - ? Util.getResourceAsString("insertRootCategory.sql").replace("${databaseName}", - "budget") // - : Util.getResourceAsString("insertChildCategory.sql").replace("${databaseName}", - "budget"); + ? Util.getResourceAsString("insertRootCategory.sql") // + .replace("${databaseName}", "budget") // + : Util.getResourceAsString("insertChildCategory.sql") // + .replace("${databaseName}", "budget"); try (PreparedStatement insertStmt = connection.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { insertStmt.setString(1, categoryPart); @@ -1142,9 +1320,9 @@ public class Schema { return result; } - private Set generate(final String name, final int siblingCount, - final String triliumSearch) throws Exception { - final Set years = new HashSet<>(); + private void generate(final String name, final int siblingCount, final String triliumSearch, + final DatabaseInitializer databaseInitializer) throws Exception { + new HashSet<>(); final String output = getTriliumContent( "/notes?search=" + triliumSearch + "&fastSearch=true"); final ObjectMapper mapper = new ObjectMapper(); @@ -1169,19 +1347,22 @@ public class Schema { if (!rootFound) { continue; } - years.addAll(generate(name, 1, noteNode)); + generate(name, 1, null, noteNode, databaseInitializer); } - return years; + return; } - public Set generate(String name, final int siblingCount, final JsonNode noteNode) + public void generate(String name, final int siblingCount, final JsonNode parentNoteNode, + final JsonNode noteNode, final DatabaseInitializer databaseInitializer) throws Exception { - final Set years = new HashSet<>(); final JsonNode titleNode = noteNode.get("title"); if (titleNode == null) { - return years; + return; } String childName = titleNode.textValue(); + final JsonNode parentTitleNode = parentNoteNode == null ? null + : parentNoteNode.get("title"); + final String parentTitle = parentTitleNode == null ? null : parentTitleNode.textValue(); String ext = null; final int lastIndexOfDot = childName.lastIndexOf("."); if (lastIndexOfDot >= 0) { @@ -1199,24 +1380,28 @@ public class Schema { if (noteIdNode != null) { final String noteId = noteIdNode.textValue(); final List records = getTriliumRecords("/notes/" + noteId + "/content"); - years.addAll(generate(name, siblingCount, records, - childName.startsWith("FirstbankDownload"), "qb.csv".equals(childName))); + generate(name, siblingCount, records, "Firstbank".equals(parentTitle), + "qb.csv".equals(childName), databaseInitializer); } } final JsonNode childNodeIdsNode = noteNode.get("childNoteIds"); if (childNodeIdsNode == null) { - return years; + return; } for (final JsonNode childNodeIdNode : childNodeIdsNode) { final String childNodeId = childNodeIdNode.textValue(); final JsonNode childNode = getTriliumJsonContent("/notes/" + childNodeId); - years.addAll(generate(name, childNodeIdsNode.size(), childNode)); + generate(name, childNodeIdsNode.size(), noteNode, childNode, databaseInitializer); } - return years; + return; } - private Set generate(String name, final int siblingCount, final File file) - throws Exception { + private boolean isFirstBankCsv(final String name) { + return name.startsWith("FirstbankDownload") || name.equals("2025-checking"); + } + + private void generate(String name, final int siblingCount, final File file, + final DatabaseInitializer databaseInitializer) throws Exception { String childName = file.getName(); String ext = null; if (file.isFile()) { @@ -1232,28 +1417,54 @@ public class Schema { else if (siblingCount > 1) { name = name + "." + childName; } - final Set years = new HashSet<>(); + new HashSet<>(); if (file.isDirectory()) { final File[] children = file.listFiles(); for (final File child : children) { - years.addAll(generate(name, children.length, child)); + generate(name, children.length, child, databaseInitializer); } } else if (file.isFile() && "csv".equalsIgnoreCase(ext)) { final List records = readAllLines(Paths.get(file.getPath())); - years.addAll(generate(name, siblingCount, records, - file.getName().startsWith("FirstbankDownload"), "qb.csv".equals(file.getName()))); + generate(name, siblingCount, records, isFirstBankCsv(file.getName()), + "qb.csv".equals(file.getName()), databaseInitializer); } - return years; + return; } private static Pattern NAME_PATTERN = Pattern.compile("^Budget\\.([0-9]+)\\.(.*)$"); + private static Map TABLE_NAMES = new HashMap<>(); + static { + TABLE_NAMES.put("Amazon.Steve.Digital-Ordering.Digital Items", "amz_steve_dig_ord_items"); + TABLE_NAMES.put("Amazon.Sandy.Digital-Ordering.Digital Items", "amz_sandy_dig_ord_items"); + TABLE_NAMES.put("Amazon.Steve.Digital-Ordering.Digital Orders", "amz_steve_dig_ord_ords"); + TABLE_NAMES.put("Amazon.Sandy.Digital-Ordering.Digital Orders", "amz_sandy_dig_ord_ords"); + // Amazon.${person}.Digital-Ordering.Digital Orders Monetary + TABLE_NAMES.put("Amazon.Steve.Digital-Ordering.Digital Orders Monetary", + "amz_steve_dig_ord_ords_mon"); + TABLE_NAMES.put("Amazon.Sandy.Digital-Ordering.Digital Orders Monetary", + "amz_sandy_dig_ord_ords_mon"); + TABLE_NAMES.put("Amazon.Steve.Digital.Orders.Returns.Digital.Orders.Returns.Monetary.1", + "amz_steve_dig_ords_rets_mon"); + TABLE_NAMES.put("Amazon.Sandy.Digital.Orders.Returns.Digital.Orders.Returns.Monetary.1", + "amz_sandy_dig_ords_rets_mon"); + TABLE_NAMES.put("Amazon.Steve.Retail.OrderHistory", "amz_steve_ret_ord_hist"); + TABLE_NAMES.put("Amazon.Sandy.Retail.OrderHistory", "amz_sandy_ret_ord_hist"); + TABLE_NAMES.put("Chase", "chase"); + TABLE_NAMES.put("Paypal", "paypal"); + TABLE_NAMES.put("Firstbank", "first_bank"); + TABLE_NAMES.put("Firstbank.2025-checking", "first_bank"); + TABLE_NAMES.put("Citi", "citi"); + TABLE_NAMES.put("Discover", "discover"); + TABLE_NAMES.put("Elevations", "elevations"); + TABLE_NAMES.put("Elevations.2025-checking", "elevations"); + } - private Set generate(final String name, final int siblingCount, - final List records, final boolean isFirstBank, final boolean isQuickBooks) - throws SQLException { + private void generate(final String name, final int siblingCount, final List records, + final boolean isFirstBank, final boolean isQuickBooks, + final DatabaseInitializer databaseInitializer) throws SQLException { int recordIndex = 0; - final Set years = new HashSet<>(); + new HashSet<>(); List fields = null; for (final String[] record : records) { if (recordIndex == 0) { @@ -1398,35 +1609,72 @@ public class Schema { } final Matcher matcher = NAME_PATTERN.matcher(name); if (!matcher.matches()) { - throw new RuntimeException("Name does not begin with Budget.[year]"); + System.out.println("Skipping " + name + " because name pattern does not match"); + return; } final String year = matcher.group(1); - years.add(year); - final String tableName = decamel(matcher.group(2)).replace("budget_", "").replace("amazon_", - "amz_").replace("_digital_", "_dig_").replace("_information", "_inf").replace( - "_payment_", "_pmt_").replace("_ordering_", "_ord_").replace("_customer_", - "_cust_").replace("_communication_", "_comm_").replace("_preferences_", - "_pref_").replace("_physical_", "_phys_").replace("_whole_foods_", "_wf_"); + final String triliumName = matcher.group(2); + final String tableName = getTableName(triliumName); if (fields != null) { - System.out.println("Loading CSV " + name); - try (Connection connection = pool.getConnection(8, false, true)) { - final String qualifiedTableName = "budget_" + year + "." + tableName; - dbExecute(connection, "drop table if exists " + qualifiedTableName); - dbExecute(connection, getCreateTableSql(qualifiedTableName, fields)); - doInserts(connection, qualifiedTableName, fields, records, isFirstBank, - isQuickBooks); + if (tableName == null) { + System.out.println("Skipping CSV " + name + " because table not found"); + } + else { + databaseInitializer.initializeDatabase(year); + try (Connection connection = pool.getConnection(8, false, true)) { + final String qualifiedTableName = "budget_" + year + "." + tableName; + System.out.println("Loading CSV " + name + " into " + qualifiedTableName); + dbExecute(connection, "drop table if exists " + qualifiedTableName); + dbExecute(connection, getCreateTableSql(qualifiedTableName, fields)); + doInserts(connection, qualifiedTableName, fields, records, isFirstBank, + isQuickBooks); + } } } - return years; + return; + } + + private static String getTableName(final String triliumName) { + final String tableName = TABLE_NAMES.get(triliumName); + return tableName; + /* + if (tableName != null) { + return tableName; + } + return decamel(triliumName) // + .replace("budget_", "") // + .replace("amazon_", "amz_") // + .replace("_digital_", "_dig_") // + .replace("_information", "_inf") // + .replace("_payment_", "_pmt_") // + .replace("_ordering_", "_ord_") // + .replace("_customer_", "_cust_") // + .replace("_communication_", "_comm_") // + .replace("_preferences_", "_pref_") // + .replace("_physical_", "_phys_") // + .replace("_whole_foods_", "_wf_") // + .replace("_subscriptions_subscriptions_", "_ssubs_") // + .replace("_subscriptions_", "_subs_") // + .replace("_subscription_", "_sub_") // + .replace("_status_", "_stat_") // + .replace("_billing_", "_bill_") // + .replace("_refunds_", "_refs_") // + .replace("_returns_", "_rets_") // + .replace("_orders_", "_ords") // + .replace("_monetary_", "_mon_") // + .replace("_and_", "_") // + .replace("_data", "_dat") // + .replace("_history", "_hist"); + */ } private void saveYears(final Set years) throws SQLException, IOException { try (Connection connection = pool.getConnection(8, false, true)) { dbExecute(connection, "drop table if exists budget.years"); - dbExecute(connection, Util.getResourceAsString("createYearsTable.sql").replace( - "${databaseName}", "budget")); - final String insertSql = Util.getResourceAsString("insertYear.sql").replace( - "${databaseName}", "budget"); + dbExecute(connection, Util.getResourceAsString("createYearsTable.sql") // + .replace("${databaseName}", "budget")); + final String insertSql = Util.getResourceAsString("insertYear.sql") // + .replace("${databaseName}", "budget"); for (final String year : years) { try (PreparedStatement insertStmt = connection.prepareStatement(insertSql)) { insertStmt.setInt(1, Integer.parseInt(year)); @@ -1454,7 +1702,17 @@ public class Schema { final String sql = getInsertSql(tableName, record, fields); try (final PreparedStatement stmt = connection.prepareStatement(sql)) { setValues(stmt, record, fields); - stmt.execute(); + try { + stmt.execute(); + } + catch (final SQLException e) { + System.out.println("SQL Exception: " + e); + System.out.println(sql); + for (int i = 0; i < record.length; i++) { + System.out.println(i + " " + record[i]); + } + throw e; + } } } recordIndex++; @@ -1579,13 +1837,14 @@ public class Schema { } } - private String decamel(final String name) { + private static String decamel(final String name) { if (name == null || name.trim().length() == 0) { return "unknown"; } final StringBuilder sb = new StringBuilder(); - final char[] chars = name.replace("ID", "Id").replace("RMA", "Rma").replace("ASIN", - "Asin").toCharArray(); + final char[] chars = name.replace("ID", "Id") // + .replace("RMA", "Rma") // + .replace("ASIN", "Asin").toCharArray(); int charIndex = 0; char prevChar = '_'; while (charIndex < chars.length) { diff --git a/src/main/resources/createBudgetAmountTable.sql b/src/main/resources/createBudgetAmountTable.sql new file mode 100644 index 0000000..e4c4ec5 --- /dev/null +++ b/src/main/resources/createBudgetAmountTable.sql @@ -0,0 +1,16 @@ +create table ${databaseName}.budget_amount ( + category_id int not null primary key, + year_amount decimal(10,2), + jan_amount decimal(10,2), + feb_amount decimal(10,2), + mar_amount decimal(10,2), + apr_amount decimal(10,2), + may_amount decimal(10,2), + jun_amount decimal(10,2), + jul_amount decimal(10,2), + aug_amount decimal(10,2), + sep_amount decimal(10,2), + oct_amount decimal(10,2), + nov_amount decimal(10,2), + dec_amount decimal(10,2) +) diff --git a/src/main/resources/dropBudgetAmountTable.sql b/src/main/resources/dropBudgetAmountTable.sql new file mode 100644 index 0000000..ed8258f --- /dev/null +++ b/src/main/resources/dropBudgetAmountTable.sql @@ -0,0 +1 @@ +drop table if exists ${databaseName}.budget_amount \ No newline at end of file diff --git a/src/main/resources/dropTransactionTable.sql b/src/main/resources/dropTransactionTable.sql new file mode 100644 index 0000000..c2a1d45 --- /dev/null +++ b/src/main/resources/dropTransactionTable.sql @@ -0,0 +1 @@ +drop table if exists ${databaseName}.transaction \ No newline at end of file diff --git a/src/main/resources/getElevations.sql b/src/main/resources/getElevations.sql new file mode 100644 index 0000000..21447b0 --- /dev/null +++ b/src/main/resources/getElevations.sql @@ -0,0 +1,15 @@ +select +transaction_id, +posting_date, +effective_date, +transaction_type, +amount, +check_number, +reference_number, +description, +transaction_category, +type, +balance, +memo, +extended_description +from ${databaseName}.elevations \ No newline at end of file