CLI · commit 5705216

cardwise

Credit card rewards optimizer from your real spending

Download cardwise.zip
Quickstart
unzip cardwise.zip && cd cardwise
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

cardwise

Credit card rewards optimizer driven by your real spending. Feed it your card statements and a YAML description of each card's reward rules (category multipliers, monthly caps, milestones, fee waivers, excluded categories, fuel surcharge waivers), and it tells you what you actually earned, what you could have earned with the same spending, which card to use for each category going forward, and whether each card is worth its annual fee.

Features

Install

Python 3.12+.

git clone <this repo> && cd cardwise
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
source .venv/bin/activate

Quickstart

The repository ships twelve months of synthetic 2026 statements for three cards (examples/statements/) and a script that runs everything:

export CARDWISE_HOME=$PWD/.cardwise-demo       # where imported data is kept (default ~/.cardwise)

cardwise import examples/statements/cashback-plus-2026.csv --card cashback-plus
cardwise import examples/statements/dining-delight-2026.csv --card dining-delight --profile drcr
cardwise import examples/statements/travel-elite-2026.csv --card travel-elite \
        --profile examples/profiles/travel-elite.yaml

cardwise earned --year 2026
cardwise optimize --year 2026
cardwise policy --year 2026 --as-of 2026-11-20
cardwise fee-check --year 2026
cardwise simulate --spend-profile examples/spend-profile.yaml --card premium-metal --alongside cashback-plus,dining-delight

or simply examples/walkthrough.sh. The outputs below are real, captured from these commands (terminal width 130).

Usage

Global options: --home DIR (data directory, default $CARDWISE_HOME or ~/.cardwise) and --cards-dir DIR (extra card YAML files). Commands that read data accept --year (default: latest year in the data) and --redeem NAME (use that redemption value from the cards' point_value maps).

Import

$ cardwise import STATEMENT.csv --card CARD_ID [--profile generic|drcr|FILE.yaml] [--replace]
cashback-plus: imported 273 new transactions (0 duplicates, 12 credits/refunds ignored)
dining-delight: imported 152 new transactions (0 duplicates, 0 credits/refunds ignored)
travel-elite: imported 101 new transactions (0 duplicates, 0 credits/refunds ignored)
cashback-plus: imported 0 new transactions (273 duplicates, 12 credits/refunds ignored)

Credits and refunds are skipped and counted. Each transaction gets a stable id, so importing a file twice adds nothing. Profile format: docs/rules-dsl.md.

Cards

$ cardwise cards
                                                         Cards                                                          
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━┓
┃ Id                  ┃ Name                ┃ Type     ┃ Fee (incl. GST) ┃ Waiver at ┃ Cycle ends ┃ Rules ┃ Milestones ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━┩
│ cashback-plus       │ Cashback Plus       │ cashback │ ₹590            │ ₹100,000  │ day 15     │ 2     │ 0          │
│ dining-delight      │ Dining Delight      │ points   │ ₹1,180          │ ₹200,000  │ day 5      │ 2     │ 1          │
│ entertainment-plus  │ Entertainment Plus  │ points   │ ₹708            │ ₹100,000  │ day 25     │ 2     │ 0          │
│ everyday-flat       │ Everyday Flat       │ cashback │ ₹0              │ -         │ day 20     │ 0     │ 0          │
│ fuel-saver          │ Fuel Saver          │ cashback │ ₹885            │ ₹150,000  │ day 10     │ 2     │ 0          │
│ grocery-rewards     │ Grocery Rewards     │ points   │ ₹589            │ ₹120,000  │ day 1      │ 2     │ 1          │
│ lifetime-free-basic │ Lifetime Free Basic │ points   │ ₹0              │ -         │ day 18     │ 1     │ 0          │
│ premium-metal       │ Premium Metal       │ points   │ ₹11,800         │ ₹800,000  │ day 12     │ 2     │ 2          │
│ shopping-voucher    │ Shopping Voucher    │ points   │ ₹1,770          │ ₹300,000  │ day 8      │ 1     │ 2          │
│ travel-elite        │ Travel Elite        │ points   │ ₹3,539          │ ₹300,000  │ day 22     │ 2     │ 2          │
└─────────────────────┴─────────────────────┴──────────┴─────────────────┴───────────┴────────────┴───────┴────────────┘
$ cardwise cards --validate my-card.yaml
error card: missing required key 'base_rate'
error card.rules[0].categories: unknown category 'dinning' (did you mean 'dining'?)
error card.rules[0]: set exactly one of 'rate' or 'multiplier'
error card.rules[1]: needs 'categories' and/or 'merchant'
error card.rules[1].cap.period: must be one of month, quarter, cycle, year, got 'week'

earned: what you actually earned

$ cardwise earned [--card ID]... [--json FILE] [--csv FILE]
                                               Rewards earned in 2026                                                
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Card           ┃    Spend ┃   Base ┃  Bonus ┃ Surcharge ┃ Milestones ┃  Earned ┃ Cap loss ┃         Fee ┃     Net ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ cashback-plus  │ ₹838,468 │ ₹4,927 │ ₹9,779 │      ₹424 │         ₹0 │ ₹15,130 │     ₹897 │ ₹0 (waived) │ ₹15,130 │
│ dining-delight │ ₹239,805 │ ₹1,072 │ ₹2,736 │        ₹0 │         ₹0 │  ₹3,808 │     ₹130 │ ₹0 (waived) │  ₹3,808 │
│ travel-elite   │ ₹326,443 │ ₹2,289 │ ₹3,226 │      ₹371 │         ₹0 │  ₹5,886 │       ₹0 │      ₹3,539 │  ₹2,347 │
└────────────────┴──────────┴────────┴────────┴───────────┴────────────┴─────────┴──────────┴─────────────┴─────────┘
                         Cap loss = bonus rewards that exceeded a monthly/cycle/quarter cap.                         
...
┃ Card           ┃    Spend ┃   Base ┃  Bonus ┃ Surcharge ┃ Milestones ┃  Earned ┃ Cap loss ┃         Fee ┃     Net ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ cashback-plus  │ ₹838,468 │ ₹4,927 │ ₹9,779 │      ₹424 │         ₹0 │ ₹15,130 │     ₹897 │ ₹0 (waived) │ ₹15,130 │
│ dining-delight │ ₹239,805 │ ₹1,072 │ ₹2,736 │        ₹0 │         ₹0 │  ₹3,808 │     ₹130 │ ₹0 (waived) │  ₹3,808 │
│ travel-elite   │ ₹326,443 │ ₹2,289 │ ₹3,226 │      ₹371 │         ₹0 │  ₹5,886 │       ₹0 │      ₹3,539 │  ₹2,347 │
└────────────────┴──────────┴────────┴────────┴───────────┴────────────┴─────────┴──────────┴─────────────┴─────────┘
                         Cap loss = bonus rewards that exceeded a monthly/cycle/quarter cap.                         
                       By category                       
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ Category        ┃    Spend ┃  Earned ┃ Effective rate ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ online-shopping │ ₹314,690 │ ₹12,549 │          3.99% │
│ rent            │ ₹264,000 │      ₹0 │          0.00% │
│ travel          │ ₹228,000 │  ₹5,116 │          2.24% │
│ dining          │ ₹180,532 │  ₹3,557 │          1.97% │
│ groceries       │ ₹142,917 │  ₹1,493 │          1.04% │
│ fuel            │  ₹79,507 │    ₹795 │          1.00% │
│ utilities       │  ₹55,758 │    ₹487 │          0.87% │
│ health          │  ₹25,390 │    ₹254 │          1.00% │
│ wallet          │  ₹24,844 │      ₹0 │          0.00% │
│ entertainment   │  ₹23,011 │    ₹152 │          0.66% │
│ transport       │  ₹21,578 │    ₹182 │          0.84% │
│ government      │  ₹18,000 │      ₹0 │          0.00% │
│ insurance       │  ₹14,500 │      ₹0 │          0.00% │
│ telecom         │  ₹11,988 │    ₹240 │          2.00% │
└─────────────────┴──────────┴─────────┴────────────────┘
                                                 Milestones                                                 
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Card           ┃ Milestone      ┃ Period  ┃ Progress                  ┃ Status                           ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ dining-delight │ annual-spend   │ 2026    │ ₹221,805 / ₹250,000 (89%) │ missed by ₹28,195 (worth ₹750)   │
│ travel-elite   │ annual-300k    │ 2026    │ ₹289,330 / ₹300,000 (96%) │ missed by ₹10,670 (worth ₹1,500) │
│ travel-elite   │ quarterly-100k │ 2026-Q1 │ ₹71,087 / ₹100,000 (71%)  │ missed by ₹28,913 (worth ₹300)   │
│ travel-elite   │ quarterly-100k │ 2026-Q2 │ ₹82,255 / ₹100,000 (82%)  │ missed by ₹17,745 (worth ₹300)   │
│ travel-elite   │ quarterly-100k │ 2026-Q3 │ ₹75,696 / ₹100,000 (76%)  │ missed by ₹24,304 (worth ₹300)   │
│ travel-elite   │ quarterly-100k │ 2026-Q4 │ ₹60,293 / ₹100,000 (60%)  │ missed by ₹39,707 (worth ₹300)   │
└────────────────┴────────────────┴─────────┴───────────────────────────┴──────────────────────────────────┘

The full output also has a per-category table and a monthly bar chart. Cap loss is bonus value that exceeded a cap. The milestone table shows near misses: travel-elite missed its annual target by Rs 10,670, which was worth Rs 1,500.

optimize: what you could have earned

$ cardwise optimize [--cards a,b] [--net-of-fees] [--json FILE] [--csv FILE]
                             Actual vs optimal, 2026 (rewards)                              
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Card           ┃ Actual spend ┃ Actual earned ┃ Optimal spend ┃ Optimal earned ┃  Change ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ cashback-plus  │     ₹838,468 │       ₹15,130 │      ₹693,095 │        ₹15,462 │   +₹332 │
│ dining-delight │     ₹239,805 │        ₹3,808 │      ₹274,288 │         ₹5,696 │ +₹1,888 │
│ travel-elite   │     ₹326,443 │        ₹5,886 │      ₹437,333 │        ₹10,114 │ +₹4,228 │
├────────────────┼──────────────┼───────────────┼───────────────┼────────────────┼─────────┤
│ TOTAL          │   ₹1,404,716 │       ₹24,824 │    ₹1,404,716 │        ₹31,272 │ +₹6,448 │
└────────────────┴──────────────┴───────────────┴───────────────┴────────────────┴─────────┘
You left ₹6,448 on the table (26.0% more) with the same spending, by moving transactions between cards.
                         Transactions to move                          
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━┓
┃ Category        ┃ From           ┃ To             ┃ Count ┃   Spend ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━┩
│ groceries       │ cashback-plus  │ dining-delight │    51 │ ₹79,233 │
│ online-shopping │ travel-elite   │ cashback-plus  │    11 │ ₹54,384 │
│ utilities       │ dining-delight │ cashback-plus  │    28 │ ₹41,360 │
│ online-shopping │ cashback-plus  │ travel-elite   │     9 │ ₹36,348 │
...
Earned per month: actual vs optimal (₹)                                  
Month    actual                           optimal                        
2026-01  ███████▊                  1,441  ████████▋                 1,599
2026-02  ██████████▎               1,898  ███████████               2,051
2026-03  ███████████████▊          2,918  ██████████████████▌       3,432
2026-04  ██████████                1,861  ████████████▉             2,406
2026-05  ███████████▍              2,106  ███████████▋              2,164
2026-06  ██████████████▎           2,658  ████████████████▉         3,138
...

Same spending, same cards: Rs 6,448 (26%) more, mostly by moving groceries to dining-delight and online shopping to the card whose cap is still open, and by steering enough spend onto travel-elite to reach its milestones. --net-of-fees also lets the optimizer chase fee waivers:

                        Actual vs optimal, 2026 (net of fees)                         
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Card           ┃ Actual spend ┃ Actual net ┃ Optimal spend ┃ Optimal net ┃  Change ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ cashback-plus  │     ₹838,468 │    ₹15,130 │      ₹693,217 │     ₹15,463 │   +₹333 │
│ dining-delight │     ₹239,805 │     ₹3,808 │      ₹273,820 │      ₹5,691 │ +₹1,884 │
│ travel-elite   │     ₹326,443 │     ₹2,347 │      ₹437,679 │     ₹10,116 │ +₹7,769 │
├────────────────┼──────────────┼────────────┼───────────────┼─────────────┼─────────┤
│ TOTAL          │   ₹1,404,716 │    ₹21,285 │    ₹1,404,716 │     ₹31,271 │ +₹9,986 │
└────────────────┴──────────────┴────────────┴───────────────┴─────────────┴─────────┘
You left ₹9,986 on the table (46.9% more) with the same spending, by moving transactions between cards.

The CSV has one row per transaction with the actual and optimal card and value; the JSON has totals and the list of moves.

policy: which card for which category

$ cardwise policy [--as-of YYYY-MM-DD]
                                           Card policy from 2026 spending                                           
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ Category        ┃ Monthly spend ┃ Use                                                           ┃ Effective rate ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ online-shopping │       ₹26,224 │ cashback-plus for the first ₹23,300/month, then travel-elite  │    4.9% / 0.8% │
│ rent            │       ₹22,000 │ any card (no card rewards this category)                      │          0.00% │
│ travel          │       ₹19,000 │ travel-elite                                                  │           2.5% │
│ dining          │       ₹15,044 │ dining-delight for the first ₹12,900/month, then travel-elite │    2.4% / 0.8% │
│ groceries       │       ₹11,910 │ dining-delight for the first ₹8,900/month, then travel-elite  │    1.2% / 0.8% │
│ fuel            │        ₹6,626 │ cashback-plus for the first ₹3,700/month, then travel-elite   │    1.0% / 1.0% │
│ utilities       │        ₹4,646 │ cashback-plus                                                 │           2.0% │
│ health          │        ₹2,116 │ travel-elite                                                  │           0.8% │
│ wallet          │        ₹2,070 │ any card (no card rewards this category)                      │          0.00% │
│ entertainment   │        ₹1,918 │ travel-elite                                                  │           0.7% │
│ transport       │        ₹1,798 │ travel-elite                                                  │           0.7% │
│ government      │        ₹1,500 │ any card (no card rewards this category)                      │          0.00% │
│ insurance       │        ₹1,208 │ any card (no card rewards this category)                      │          0.00% │
│ telecom         │          ₹999 │ cashback-plus                                                 │           2.0% │
└─────────────────┴───────────────┴───────────────────────────────────────────────────────────────┴────────────────┘
Following this policy would have earned ₹30,143 (actual ₹24,824, theoretical optimum ₹31,272).
                                                Milestone pacing as of 2026-11-20                                                 
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Card           ┃ Milestone               ┃ Progress            ┃ Days left ┃ Reward ┃ Needs shifting ┃ Verdict                 ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ dining-delight │ annual-spend (2026)     │ ₹194,738 / ₹250,000 │ 41        │ ₹750   │ ₹34,537        │ worth it: shift about   │
│                │                         │                     │           │        │                │ ₹34,537 of existing     │
│                │                         │                     │           │        │                │ spend here              │
│ travel-elite   │ annual-300k (2026)      │ ₹242,780 / ₹300,000 │ 41        │ ₹1,500 │ ₹30,818        │ worth it: shift about   │
│                │                         │                     │           │        │                │ ₹30,818 of existing     │
│                │                         │                     │           │        │                │ spend here              │
│ travel-elite   │ quarterly-100k          │ ₹13,743 / ₹100,000  │ 41        │ ₹300   │ ₹59,855        │ not worth shifting:     │
│                │ (2026-Q4)               │                     │           │        │                │ reward is below the     │
│                │                         │                     │           │        │                │ rewards you would give  │
│                │                         │                     │           │        │                │ up                      │
└────────────────┴─────────────────────────┴─────────────────────┴───────────┴────────┴────────────────┴─────────────────────────┘

The policy is derived from the optimum but is a simple monthly rule you can follow. It is replayed on your real transactions: Rs 30,143 against Rs 24,824 actually earned and Rs 31,272 for the theoretical optimum. The pacing table is evaluated on the data up to --as-of; it says whether shifting existing spend to a card to reach a milestone is worth it, never recommending extra spending.

fee-check: is the annual fee worth it

$ cardwise fee-check [--first-year] [--lounge-visits N]
                                                Annual fee worth-it analysis, 2026                                                
┏━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┓
┃                ┃        ┃      Actual ┃     Optimal ┃     Optimal ┃             ┃    Foregone ┃        ┃             ┃         ┃
┃ Card           ┃    Fee ┃      earned ┃       spend ┃      earned ┃ Fee payable ┃   elsewhere ┃ Extras ┃ Net benefit ┃ Verdict ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━┩
│ cashback-plus  │   ₹590 │     ₹15,130 │    ₹693,217 │     ₹15,463 │ ₹0 (waived) │      ₹3,139 │     ₹0 │    +₹12,325 │ KEEP    │
│ dining-delight │ ₹1,180 │      ₹3,808 │    ₹273,820 │      ₹5,691 │ ₹0 (waived) │      ₹2,613 │   ₹250 │     +₹3,328 │ KEEP    │
│ travel-elite   │ ₹3,539 │      ₹5,886 │    ₹437,679 │     ₹10,116 │ ₹0 (waived) │      ₹4,448 │ ₹3,900 │     +₹9,568 │ KEEP    │
└────────────────┴────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴────────┴─────────────┴─────────┘
             Net benefit = rewards on the card - fee payable - rewards other cards lose when it is removed + extras.              

Net benefit counts the card's rewards in the best possible usage, its fee after the optimal waiver, and the rewards your other cards lose if it is removed. --first-year adds welcome benefits, --lounge-visits values lounge access.

simulate: a card you do not have yet

$ cardwise simulate --spend-profile examples/spend-profile.yaml --card premium-metal --card everyday-flat --alongside cashback-plus,dining-delight
                                 Prospective cards for ₹103,000/month of spend                                  
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ Card          ┃ Standalone earned ┃ Fee payable ┃ Standalone net ┃ Spend routed to it ┃ Marginal net benefit ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ premium-metal │           ₹30,415 │          ₹0 │        ₹30,415 │         ₹1,100,000 │              +₹8,458 │
│ everyday-flat │           ₹12,060 │          ₹0 │        ₹12,060 │           ₹613,125 │              +₹1,419 │
└───────────────┴───────────────────┴─────────────┴────────────────┴────────────────────┴──────────────────────┘
           Marginal = optimal net with cashback-plus, dining-delight plus the card, minus without it.           

The profile is monthly rupees per category (examples/spend-profile.yaml). Standalone numbers use only the candidate; the marginal column is the gain on top of the cards you hold.

Configuration

Setting How
Data directory --home DIR or CARDWISE_HOME (default ~/.cardwise); holds transactions.json, optional cards/*.yaml and categories.yaml
Extra card definitions --cards-dir DIR or $CARDWISE_HOME/cards/
Statement formats --profile built-in name or YAML file
Category overrides $CARDWISE_HOME/categories.yaml (regex overrides and MCC table)
Redemption value --redeem NAME

Architecture

src/cardwise/
  models.py      dataclasses: Txn, Card, Rule, Milestone, ...
  dsl.py         YAML -> Card with aggregate validation errors
  categories.py  MCC table, keyword dictionary, user overrides
  importer.py    CSV profiles, amount/date parsing, stable transaction ids
  store.py       JSON transaction store, bundled + user card loading
  engine.py      cycle/period buckets and the exact chronological simulator
  optimizer.py   MILP assignment, post-solve cleanup, re-simulation
  policy.py      per-category policy derivation and replay
  advice.py      fee-check, milestone pacing, spend-profile expansion
  report.py      money format, unicode bars
  cli.py         click commands
src/cardwise/cards/   ten example card definitions
examples/             statements, profiles, spend profile, generator, walkthrough
docs/                 rules-dsl.md, optimization-model.md

See docs/optimization-model.md for the algorithms with worked examples and docs/rules-dsl.md for the rules reference.

Tests

.venv/bin/pytest

80 tests: each rule type (caps per month/quarter/cycle/year, exclusions, rounding, fuel surcharge, milestones, fee waiver), cycle boundaries, brute-force comparison of the optimizer on random small instances (with and without fee modelling), shared caps, policy derivation, fee analysis arithmetic, DSL validation errors, importer, and end-to-end CLI runs on the examples.

Limitations

License

MIT, see LICENSE.