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}

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