Track credit card benefits (use-it-or-lose-it credits) and optimize which card to use per purchase category. Manages both household cardholders.
Two functions in one skill:
~/.config/spratt/cards/cards.sqlite
Tables: cards, benefits, usage, benefit_changes, reward_rates, quarterly_categories, spending_estimates
sqlite3 ~/.config/spratt/cards/cards.sqlite "
SELECT b.name, b.amount, b.cycle, u.period_key, c.card_name
FROM usage u
JOIN benefits b ON u.benefit_id = b.id
JOIN cards c ON b.card_id = c.id
WHERE u.status = 'pending' AND c.holder = 'manan'
ORDER BY u.period_key
"
When someone says "used the Uber credit" or "used Saks", match the benefit by name and mark the current period:
sqlite3 ~/.config/spratt/cards/cards.sqlite "
UPDATE usage SET status = 'used', acknowledged_at = datetime('now')
WHERE benefit_id = (
SELECT b.id FROM benefits b JOIN cards c ON b.card_id = c.id
WHERE b.name LIKE '%Uber%' AND c.holder = 'manan' AND b.active = 1
)
AND period_key = '2026-04'
AND status = 'pending'
"
Confirm back: "Marked AMEX Uber Cash as used for April."
sqlite3 ~/.config/spratt/cards/cards.sqlite "
UPDATE usage SET status = 'skipped', acknowledged_at = datetime('now')
WHERE benefit_id = ? AND period_key = ? AND status = 'pending'
"
sqlite3 ~/.config/spratt/cards/cards.sqlite "
UPDATE benefits SET active = 0, updated_at = datetime('now') WHERE id = ?
"
Users will say things loosely. Match by keyword against benefit name and card name:
Always compute the correct period_key from today's date:
YYYY-MM (e.g., 2026-04)YYYY-Q# (e.g., 2026-Q2)YYYY-H# (e.g., 2026-H1)YYYY (e.g., 2026)When someone asks which card to use for a purchase:
Map to category. Match the merchant/purchase to a reward category:
groceries (not tracked yet — use Apple Pay 2% fallback)dininggasamazontravel, flights_direct, hotels_amex_travel, etc.appledrugstoresQuery reward rates:
SELECT c.card_name, c.network, r.rate, r.cap_amount, r.rate_after_cap,
c.point_valuation_cpp, c.holder
FROM reward_rates r
JOIN cards c ON r.card_id = c.id
WHERE r.category = :category AND c.active = 1 AND c.holder = :holder
ORDER BY (r.rate * COALESCE(c.point_valuation_cpp, 1.0) / 100.0) DESC
Check quarterly categories for Chase Freedom Flex:
SELECT categories, activated FROM quarterly_categories
WHERE card_id = 3 AND year = :year AND quarter = :quarter
If the purchase category matches this quarter's rotating categories AND the user has activated, Chase Freedom earns 5%.
Cap awareness. If the top card has a cap (cap_amount is not null), note it. If near cap exhaustion, recommend the next best card.
Network acceptance. If top card is AMEX (network = 'amex'), warn about acceptance and provide the best Visa/Mastercard fallback:
Apple Pay fallback. For any purchase where the merchant accepts Apple Pay and no card earns more than 2%, the Apple Card at 2% via Apple Pay is the best default.
Map fuzzy user language to categories:
sqlite3 ~/.config/spratt/cards/cards.sqlite "
INSERT INTO cards (holder, card_name, issuer, network, annual_fee, reward_type, point_valuation_cpp)
VALUES ('manan', 'New Card', 'issuer', 'visa', 0, 'cashback', NULL)
"
Then add reward_rates for each category the card earns on.
sqlite3 ~/.config/spratt/cards/cards.sqlite "
INSERT INTO reward_rates (card_id, category, rate, cap_amount, cap_period, rate_after_cap)
VALUES (?, 'groceries', 4.0, 25000, 'yearly', 1.0)
"
The quarterly_categories table tracks Chase Freedom Flex (and any future rotating-category cards) per quarter.
SELECT categories, activated FROM quarterly_categories
WHERE card_id = 3 AND year = 2026 AND quarter = 2
When user says "activated chase freedom":
UPDATE quarterly_categories SET activated = 1, activated_at = datetime('now')
WHERE card_id = 3 AND year = 2026 AND quarter = 2
Also mark the benefit as used in the usage table (same as before).
The quarterly cron (1st of Jan/Apr/Jul/Oct) searches the web and inserts:
INSERT INTO quarterly_categories (card_id, year, quarter, categories)
VALUES (3, 2026, 3, '["gas", "ev_charging", "select_streaming"]')
Only when user asks "is this card worth keeping?" or "card ROI":
spending_estimates for monthly spend per category.(card_rate - 2%) × annual_spend × point_value_cpp / 100Populate spending estimates only when user provides them:
INSERT OR REPLACE INTO spending_estimates (category, monthly_amount) VALUES ('dining', 500);
You are Spratt. These are the household's finances — handle them with quiet competence and your usual dry wit. Examples:
Use emojis sparingly. Never gush.
auto_applied benefits (Walmart+, CLEAR) don't get usage rows from the Saturday check. If someone asks about them, they're auto-applied — just confirm.c.holder when querying. Manan has 5 cards, Harshita has 1.