transaction-signal
v1.8.0
Published
Local, deterministic extraction of transaction signals from financial messages
Downloads
95
Maintainers
Readme
transaction-signal
Local, deterministic extraction of transaction signals from financial messages for JavaScript and React Native.
Answers three core questions:
Is this message a real transaction signal? What payment channel was used? Is this a card spend, card bill, or card payment?
Features
- Pre-filter gate — fast-rejects OTPs, delivery updates, login alerts, stock alerts, promos, and balance-only notifications before any parsing runs
- Channel classification — every signal tagged as
UPI,Card,NEFT,IMPS,NACH,Account, orUnknown - Card tracking — extracts card last-4 digits, card type, and sub-type (
CardSpend,CardBillDue,CardBillPayment) - Bill-due support — card bill reminders produce a
BillDueintent withbill_due_dateandbill_amount_minorinstead of being discarded as marketing - Sender-aware scoring — Indian bank DLT sender IDs (e.g.
AD-HDFCBK) boost confidence; promotional senders reduce it - Local parsing (no network calls)
- Deterministic rule-based engine (no ML)
- Transaction amount and merchant extraction
- Single transaction time output (
transaction_time) - Category inference (
category,category_confidence) - Indian banking format support (UPI, card, IMPS, NEFT, NACH)
- TypeScript types and ESM/CJS builds
Installation
npm install transaction-signalUsage
Basic
import { analyzeSignal } from 'transaction-signal';
const [signal] = analyzeSignal({
body: 'Dear Customer, Rs.137.00 has been debited from account to VPA merchant@upi RAJDEEP SALES CORPORATION on 28-12-25. UPI reference 744082890790.',
sender: 'AD-HDFCBK',
rule_version: 'v1',
});
console.log(signal);
// {
// transaction_time: '2026-02-25T00:00:00',
// direction: 'Outflow',
// approx_amount_minor: 13700,
// merchant_hint: 'RAJDEEP SALES CORPORATION',
// category: 'Misc',
// category_confidence: 0.3,
// state: 'Completed',
// confidence: 0.85,
// intent: 'transactional',
// vetoed: undefined,
// channel: 'UPI',
// }Card tracking
A single signal.channel === 'Card' check gives you access to rich card details:
import { analyzeSignal, Channel, CardSubtype } from 'transaction-signal';
const [signal] = analyzeSignal({
body: 'Dear Customer, We write to confirm that your Credit card no ending with 7813 has been used for INR 300.00 for payment to CAS*SWIGGY on 26 Jan 2026 at 18:30.',
sender: 'AD-HSBC',
});
if (signal.channel === Channel.Card) {
const { card_last4, card_type, card_subtype } = signal.card!;
// card_last4 = '7813'
// card_type = 'credit'
// card_subtype = 'CardSpend'
switch (card_subtype) {
case CardSubtype.CardSpend:
// Add ₹(approx_amount_minor/100) spend to card ending card_last4
break;
case CardSubtype.CardBillDue:
// Total due: signal.card!.bill_amount_minor
// Due on: signal.card!.bill_due_date (ISO "YYYY-MM-DD")
break;
case CardSubtype.CardBillPayment:
// Payment of approx_amount_minor credited to card card_last4
break;
}
}Filtering non-transactional SMS
The gate auto-rejects these categories before the engine runs:
| Category | Example |
|---|---|
| OTP / verification | "Your OTP is 482910. Valid 10 min." |
| Login / security alerts | "New login from Chrome on Windows" |
| Delivery / order updates | "Your order has been shipped" |
| Stock / market alerts | "NIFTY 50 closed at 22,150" |
| Balance-only alerts | "Avl Bal: Rs.12,340" (no debit/credit) |
| Promotional / cashback | "Get 10% cashback. Use code SAVE10" |
All rejected messages return vetoed: true, intent: 'marketing', confidence: 0.05.
As a CLI
npx transaction-signal email.txt
npm run cli email.txtSignal fields
Each call returns Signal[] (always one element).
| Field | Type | Description |
|---|---|---|
| transaction_time | string | YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss |
| direction | Inflow \| Outflow | Money in or out from user's perspective |
| approx_amount_minor? | number | Amount in minor units (paise) |
| merchant_hint? | string | Extracted merchant or counterparty |
| category? | TransactionCategory | Inferred category |
| category_confidence? | number | Category confidence (0–1) |
| state | Completed \| Pending \| Blocked \| Refunded | Transaction state |
| confidence | number | Signal confidence (0.05–0.85) |
| intent? | transactional \| marketing \| bill_due \| unknown | Message intent |
| vetoed? | boolean | true when the message is non-transactional noise |
| channel? | Channel | UPI \| Card \| NEFT \| IMPS \| NACH \| Account \| Unknown |
| card? | CardDetails | Present only when channel === 'Card' |
CardDetails fields
| Field | Type | Description |
|---|---|---|
| card_last4? | string | Last 4 digits (e.g. '7813') |
| card_type? | 'credit' \| 'debit' | 'credit' or 'debit' |
| card_subtype? | CardSubtype | CardSpend \| CardBillDue \| CardBillPayment |
| bill_due_date? | string | ISO date 'YYYY-MM-DD' — CardBillDue only |
| bill_amount_minor? | number | Outstanding/total due in paise — CardBillDue only |
| available_limit_minor? | number | Available credit limit in paise — in some spend alerts |
Confidence and vetoing
confidenceranges from0.05(noise) to0.85(high-confidence transaction)- Signals below
0.3confidence are automatically vetoed unless they areBillDue intent: 'bill_due'signals are never vetoed — they carry real financial information
Development
npm install
npm run typecheck
npm test
npm run buildSee DEVELOPER.md for architecture, design, and contributor workflow.
License
MIT
