Card rules DSL reference
A card is one YAML file. Bundled cards live in src/cardwise/cards/; your own go in $CARDWISE_HOME/cards/ or any directory passed as --cards-dir. A later directory overrides an earlier card with the same id. Validate a file with cardwise cards --validate my-card.yaml; every problem is reported at once, with a suggestion for typos.
The bundled cards are generic examples shaped like common Indian card structures. They are not any real product's terms.
Top level
| Key | Type | Default | Meaning |
|---|---|---|---|
id |
string | required | lowercase letters, digits, dashes |
name |
string | required | display name |
currency |
cashback or points |
required | cashback: rates are percent and earn is rupees. points: rates are points per base_unit rupees |
annual_fee |
number | 0 | rupees, before GST |
fee_gst |
bool | true | add 18% GST to the fee |
fee_waiver |
{spend, exclude} |
none | fee is waived if yearly spend reaches spend. exclude lists categories that do not count (default: the card's exclusions) |
statement_day |
1-28 | 1 | day the statement cycle ends (inclusive) |
base_rate |
number | required | earn on every eligible transaction |
base_unit |
number | 100 | points cards: rupees per base_rate points (e.g. 150) |
point_value |
number or map | 1 for cashback | rupees per point. A map needs a default key plus named redemption types |
redeem |
string | default |
which point_value key to use. --redeem NAME overrides it per run |
rounding |
{spend_step, points} |
none / floor for points | see below |
exclusions |
category list | [] | earn nothing at all (no base, no bonus) |
rules |
list | [] | category and merchant rules |
fuel_surcharge |
mapping | none | surcharge waiver |
milestones |
list | [] | spend milestones |
welcome |
mapping | none | one-time joining benefit |
lounge |
mapping | none | lounge access value |
Categories: dining groceries fuel travel online-shopping shopping entertainment utilities telecom health education transport rent wallet insurance government emi other.
Rounding
spend_step: 100 floors each transaction's eligible amount to a multiple of 100 before earning (Rs 1,299 earns on Rs 1,200). points is floor (default for points cards), nearest, or none (default for cashback), applied per transaction to the earn of the base rate and of a rule separately, so that the bonus is an exact difference of whole points.
Rules
rules:
- id: dining
categories: [dining]
merchant: "swiggy|zomato" # regex, case-insensitive
rate: 10 # absolute rate, or:
# multiplier: 5 # x base_rate
min_amount: 400
cap: {amount: 1000, period: cycle}
- Provide
categories,merchant, or both (both must match). - Set exactly one of
rateormultiplier. - A rule replaces the base rate for matching transactions: it earns
rate, and the part above the base earn is the bonus. caplimits the bonus (in points, or rupees for cashback cards) perperiod:monthandquarterare calendar,cyclefollowsstatement_day,yearis the calendar year. Once the cap is used, matching spend earns the base rate again.- When several rules match, the one with the largest bonus still available is used (ties: first listed). An exhausted rule therefore falls through to the next.
- Excluded categories never match a rule.
Fuel surcharge waiver
fuel_surcharge: {min_txn: 400, max_txn: 4000, rate: 1.0, cap: {amount: 250, period: cycle}}
Fuel transactions between min_txn and max_txn (inclusive) refund rate percent in rupees, capped per period. It works even when fuel is in exclusions, which is the usual case.
Milestones
milestones:
- id: annual-300k
period: year # month | quarter | cycle | year
spend: 300000
categories: [groceries] # optional: only these count
points: 5000 # or value: 1500 (rupees, e.g. a voucher)
Eligible spend (excluded categories never count) is accumulated per period. The reward is paid once per period, on the transaction that crosses spend. Tiers are separate milestones.
Welcome and lounge
welcome: {points: 3000, spend: 30000, days: 90, description: "..."} # or value: rupees
lounge: {visits_per_year: 8, value_per_visit: 750}
Neither affects the simulation. fee-check --first-year adds the welcome benefit to a card's net benefit; --lounge-visits N adds min(N, visits_per_year) * value_per_visit.
Errors
$ cardwise cards --validate bad.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'
Statement profiles
cardwise import --profile takes a built-in name (generic: Date, Description, Amount, MCC; drcr: Date, Narration, Debit, Credit) or a YAML file:
skip_rows: 2 # junk lines before the header
date: Txn Date # column names, matched case-insensitively
description: Details
amount: Amount (INR) # or debit: / credit: for split columns
mcc: Merchant Category # optional
date_format: "%d-%b-%Y" # default: tries common formats
delimiter: ","
debit_sign: positive # positive | negative (sign used for purchases)
Amounts may contain commas, currency symbols, Dr/Cr suffixes or parentheses. Credits and refunds are ignored (see limitations in the README).
Categorisation
Order: user override regex, then MCC (user table over built-in), then keyword dictionary, then other. Overrides live in $CARDWISE_HOME/categories.yaml and apply immediately because categories are computed when transactions are read, not when imported:
overrides:
- {match: "my gym", category: health}
mcc:
5999: shopping