statement-parser 0.0.14__tar.gz → 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {statement_parser-0.0.14/statement_parser.egg-info → statement_parser-0.1.0}/PKG-INFO +46 -8
  2. statement_parser-0.1.0/README.md +78 -0
  3. {statement_parser-0.0.14 → statement_parser-0.1.0}/pyproject.toml +5 -2
  4. statement_parser-0.1.0/statement_parser/GenericBank.py +312 -0
  5. statement_parser-0.1.0/statement_parser/__init__.py +9 -0
  6. statement_parser-0.1.0/statement_parser/bank_configs.json +198 -0
  7. {statement_parser-0.0.14 → statement_parser-0.1.0/statement_parser.egg-info}/PKG-INFO +46 -8
  8. statement_parser-0.1.0/statement_parser.egg-info/SOURCES.txt +13 -0
  9. statement_parser-0.0.14/README.md +0 -40
  10. statement_parser-0.0.14/statement_parser/__init__.py +0 -19
  11. statement_parser-0.0.14/statement_parser/banks/HdfcCredit.py +0 -86
  12. statement_parser-0.0.14/statement_parser/banks/HsbcCredit.py +0 -68
  13. statement_parser-0.0.14/statement_parser/banks/HsbcDebit.py +0 -72
  14. statement_parser-0.0.14/statement_parser/banks/IciciCredit.py +0 -78
  15. statement_parser-0.0.14/statement_parser/banks/IciciDebit.py +0 -96
  16. statement_parser-0.0.14/statement_parser/banks/KotakDebit.py +0 -91
  17. statement_parser-0.0.14/statement_parser/banks/Wallet.py +0 -66
  18. statement_parser-0.0.14/statement_parser.egg-info/SOURCES.txt +0 -18
  19. {statement_parser-0.0.14 → statement_parser-0.1.0}/LICENSE +0 -0
  20. {statement_parser-0.0.14 → statement_parser-0.1.0}/setup.cfg +0 -0
  21. {statement_parser-0.0.14 → statement_parser-0.1.0}/statement_parser/Bank.py +0 -0
  22. {statement_parser-0.0.14 → statement_parser-0.1.0}/statement_parser/Transaction.py +0 -0
  23. {statement_parser-0.0.14 → statement_parser-0.1.0}/statement_parser.egg-info/dependency_links.txt +0 -0
  24. {statement_parser-0.0.14 → statement_parser-0.1.0}/statement_parser.egg-info/requires.txt +0 -0
  25. {statement_parser-0.0.14 → statement_parser-0.1.0}/statement_parser.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statement_parser
3
- Version: 0.0.14
3
+ Version: 0.1.0
4
4
  Summary: Bank Statement Parser is a Python library designed to parse and normalize transaction data from various bank statement formats ( CSV, Excel, etc.) into a consistent and easy-to-use Pandas DataFrame. It supports multiple banks and file formats, making it a versatile tool for financial data analysis.
5
5
  Author-email: Khuzema Challawala <khuzema.ac@gmail.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -34,10 +34,11 @@ Dynamic: license-file
34
34
  ## Features
35
35
 
36
36
  - **Multi-Format Support**: Parse bank statements from CSV, Excel, and more.
37
- - **Bank-Specific Parsing**: Customizable parsers for different banks.
38
- - **Consistent Output**: Normalized transaction data with standardized columns (`Date`, `Description`, `Amount`, etc.).
37
+ - **Config-Driven**: All parsing behaviour is described by a config dict — no per-bank classes to maintain.
38
+ - **Resilient**: Tolerant of header spacing/punctuation changes and messy number/date formatting.
39
+ - **Consistent Output**: Normalized transaction data with standardized columns (`bank`, `created_date`, `remarks`, `amount`, `hash`).
39
40
  - **Easy Integration**: Simple API for quick integration into your Python projects.
40
- - **Extensible**: Add support for new banks or formats with minimal effort.
41
+ - **Extensible**: Add support for a new bank by writing config, not code.
41
42
 
42
43
  ---
43
44
 
@@ -51,13 +52,50 @@ pip install statement_parser
51
52
 
52
53
 
53
54
  # Usage
54
- ### Basic Example
55
+
56
+ ### Using a bundled preset
57
+
58
+ ```python
59
+ from statement_parser import GenericBank, list_banks
60
+
61
+ print(list_banks()) # ['HDFC-CREDIT', 'HSBC-CREDIT', ...]
62
+
63
+ parser = GenericBank.from_builtin("HSBC-CREDIT")
64
+ df = parser.getDataFrame("path/to/statement.csv")
65
+ print(df.head())
66
+ ```
67
+
68
+ ### Bring your own config
69
+
70
+ No subclassing required — define a config dict and hand it to `GenericBank`:
55
71
 
56
72
  ```python
57
- from statement_parser.banks.HdfcCredit import HdfcCredit
73
+ from statement_parser import GenericBank
74
+
75
+ config = {
76
+ "file": {
77
+ "delimiter": ",",
78
+ "header": {
79
+ "mode": "detect", # detect | fixed | none
80
+ "match": ["date", "description", "amount"],
81
+ "min_matches": 2,
82
+ },
83
+ },
84
+ "columns": { # logical -> candidate names
85
+ "date": ["Txn Date", "Date"],
86
+ "details": ["Description", "Narration"],
87
+ "amount": ["Amount"],
88
+ },
89
+ "date_field": "date",
90
+ "remarks": [{"field": "details"}],
91
+ "amount": {"mode": "direct", "field": "amount"},
92
+ }
58
93
 
59
- parser = HsbcCredit()
94
+ parser = GenericBank(config, bank_id="MY-BANK")
60
95
  df = parser.getDataFrame("path/to/statement.csv")
61
- # Display the parsed transactions
62
96
  print(df.head())
63
97
  ```
98
+
99
+ **Amount modes**: `direct` (single amount column), `signed` (amount column whose
100
+ sign is decided by a CR/DR column), or `deposit_minus_withdrawal` (separate
101
+ deposit and withdrawal columns).
@@ -0,0 +1,78 @@
1
+ # Bank Statement Parser
2
+
3
+ ![Python Version](https://img.shields.io/badge/python-3.7%2B-blue)
4
+ ![License](https://img.shields.io/badge/license-MIT-green)
5
+ ![PyPI Version](https://img.shields.io/pypi/v/bank-statement-parser)
6
+
7
+ **Bank Statement Parser** is a Python library designed to parse and normalize transaction data from various bank statement formats ( CSV, Excel, etc.) into a consistent and easy-to-use Pandas DataFrame. It supports multiple banks and file formats, making it a versatile tool for financial data analysis.
8
+
9
+ ---
10
+
11
+ ## Features
12
+
13
+ - **Multi-Format Support**: Parse bank statements from CSV, Excel, and more.
14
+ - **Config-Driven**: All parsing behaviour is described by a config dict — no per-bank classes to maintain.
15
+ - **Resilient**: Tolerant of header spacing/punctuation changes and messy number/date formatting.
16
+ - **Consistent Output**: Normalized transaction data with standardized columns (`bank`, `created_date`, `remarks`, `amount`, `hash`).
17
+ - **Easy Integration**: Simple API for quick integration into your Python projects.
18
+ - **Extensible**: Add support for a new bank by writing config, not code.
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ You can install the library via pip:
25
+
26
+ ```bash
27
+ pip install statement_parser
28
+ ```
29
+
30
+
31
+ # Usage
32
+
33
+ ### Using a bundled preset
34
+
35
+ ```python
36
+ from statement_parser import GenericBank, list_banks
37
+
38
+ print(list_banks()) # ['HDFC-CREDIT', 'HSBC-CREDIT', ...]
39
+
40
+ parser = GenericBank.from_builtin("HSBC-CREDIT")
41
+ df = parser.getDataFrame("path/to/statement.csv")
42
+ print(df.head())
43
+ ```
44
+
45
+ ### Bring your own config
46
+
47
+ No subclassing required — define a config dict and hand it to `GenericBank`:
48
+
49
+ ```python
50
+ from statement_parser import GenericBank
51
+
52
+ config = {
53
+ "file": {
54
+ "delimiter": ",",
55
+ "header": {
56
+ "mode": "detect", # detect | fixed | none
57
+ "match": ["date", "description", "amount"],
58
+ "min_matches": 2,
59
+ },
60
+ },
61
+ "columns": { # logical -> candidate names
62
+ "date": ["Txn Date", "Date"],
63
+ "details": ["Description", "Narration"],
64
+ "amount": ["Amount"],
65
+ },
66
+ "date_field": "date",
67
+ "remarks": [{"field": "details"}],
68
+ "amount": {"mode": "direct", "field": "amount"},
69
+ }
70
+
71
+ parser = GenericBank(config, bank_id="MY-BANK")
72
+ df = parser.getDataFrame("path/to/statement.csv")
73
+ print(df.head())
74
+ ```
75
+
76
+ **Amount modes**: `direct` (single amount column), `signed` (amount column whose
77
+ sign is decided by a CR/DR column), or `deposit_minus_withdrawal` (separate
78
+ deposit and withdrawal columns).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "statement_parser"
7
- version = "0.0.14"
7
+ version = "0.1.0"
8
8
  authors = [
9
9
  { name="Khuzema Challawala", email="khuzema.ac@gmail.com" },
10
10
  ]
@@ -30,4 +30,7 @@ dev = [
30
30
  "pytest-cov>=6.0.0",
31
31
  "flake8>=7.1.2",
32
32
  "sphinx"
33
- ]
33
+ ]
34
+
35
+ [tool.setuptools.package-data]
36
+ statement_parser = ["bank_configs.json"]
@@ -0,0 +1,312 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+
7
+ import pandas as pd
8
+ from dateutil import parser as date_parser
9
+
10
+ from statement_parser.Bank import Bank
11
+ from statement_parser.Transaction import Transaction
12
+
13
+ CONFIG_PATH = Path(__file__).parent / "bank_configs.json"
14
+
15
+ _CONFIG_CACHE: dict | None = None
16
+
17
+
18
+ def load_configs() -> dict:
19
+ """Load and cache the bank configuration file."""
20
+ global _CONFIG_CACHE
21
+ if _CONFIG_CACHE is None:
22
+ with open(CONFIG_PATH, "r", encoding="utf-8") as handle:
23
+ _CONFIG_CACHE = json.load(handle)
24
+ return _CONFIG_CACHE
25
+
26
+
27
+ def list_banks() -> list[str]:
28
+ """Return the list of configured bank ids."""
29
+ return list(load_configs().keys())
30
+
31
+
32
+ def _normalize(name) -> str:
33
+ """Normalize a column name so spacing/punctuation changes don't matter."""
34
+ return re.sub(r"[^a-z0-9]", "", str(name).lower())
35
+
36
+
37
+ class GenericBank(Bank):
38
+ """
39
+ A single, configuration-driven statement parser.
40
+
41
+ Behaviour (delimiters, header location, column names, how the
42
+ amount/remarks are derived) is described entirely by a ``config`` dict, so
43
+ supporting a new bank means writing config - not code.
44
+
45
+ Usage::
46
+
47
+ # Bring your own config
48
+ parser = GenericBank(my_config, bank_id="MY-BANK")
49
+ df = parser.getDataFrame("statement.csv")
50
+
51
+ # Or use one of the bundled presets
52
+ parser = GenericBank.from_builtin("HDFC-CREDIT")
53
+
54
+ The engine is intentionally tolerant:
55
+ * Column names are matched after normalization, so an added/removed
56
+ space or punctuation change in a header does not break parsing.
57
+ * Numbers are cleaned (commas, currency symbols, blanks) before casting.
58
+ * Dates are parsed leniently and rows without a valid date are dropped,
59
+ which also removes header/footer junk automatically.
60
+ """
61
+
62
+ def __init__(self, config: dict, bank_id: str | None = None):
63
+ if not isinstance(config, dict):
64
+ raise TypeError(
65
+ "config must be a dict describing how to parse the statement. "
66
+ "Use GenericBank.from_builtin(<id>) for a bundled preset."
67
+ )
68
+ self.config = config
69
+ self.bank_id = bank_id or config.get("bank_id", "UNKNOWN")
70
+
71
+ @classmethod
72
+ def from_builtin(cls, bank_id: str) -> "GenericBank":
73
+ """Create a parser from one of the configs in ``bank_configs.json``."""
74
+ configs = load_configs()
75
+ if bank_id not in configs:
76
+ raise ValueError(
77
+ f"No built-in configuration for '{bank_id}'. "
78
+ f"Available: {', '.join(configs)}"
79
+ )
80
+ return cls(config=configs[bank_id], bank_id=bank_id)
81
+
82
+ # ------------------------------------------------------------------ #
83
+ # Public API
84
+ # ------------------------------------------------------------------ #
85
+ def getTransactions(self, filename: str) -> list[Transaction]:
86
+ df = self.getData(filename)
87
+ return self._build_transactions(df)
88
+
89
+ def getData(self, filename: str) -> pd.DataFrame:
90
+ df = self._load(filename)
91
+ df.columns = [str(c).strip() for c in df.columns]
92
+ return df
93
+
94
+ # ------------------------------------------------------------------ #
95
+ # Loading
96
+ # ------------------------------------------------------------------ #
97
+ def _load(self, filename: str) -> pd.DataFrame:
98
+ file_cfg = self.config.get("file", {})
99
+ header_cfg = file_cfg.get("header", {"mode": "fixed", "row": 0})
100
+ mode = header_cfg.get("mode", "fixed")
101
+ delimiter = file_cfg.get("delimiter", ",")
102
+ is_excel = filename.endswith((".xls", ".xlsx"))
103
+
104
+ if mode == "none":
105
+ names = header_cfg.get("names")
106
+ if is_excel:
107
+ df = pd.read_excel(filename, header=None)
108
+ else:
109
+ df = pd.read_csv(
110
+ filename,
111
+ delimiter=delimiter,
112
+ header=None,
113
+ engine="python",
114
+ on_bad_lines="skip",
115
+ )
116
+ if names:
117
+ df = df.iloc[:, : len(names)]
118
+ df.columns = names
119
+ return df
120
+
121
+ if mode == "detect":
122
+ skip_rows = self._find_header_row(
123
+ filename,
124
+ [t.lower() for t in header_cfg.get("match", [])],
125
+ header_cfg.get("min_matches", 1),
126
+ delimiter,
127
+ is_excel,
128
+ )
129
+ else:
130
+ skip_rows = header_cfg.get("row", 0)
131
+
132
+ if is_excel:
133
+ return pd.read_excel(filename, skiprows=skip_rows)
134
+
135
+ return pd.read_csv(
136
+ filename,
137
+ delimiter=delimiter,
138
+ skiprows=skip_rows,
139
+ engine="python",
140
+ on_bad_lines="skip",
141
+ )
142
+
143
+ def _find_header_row(self, filename, tokens, min_matches,
144
+ delimiter, is_excel) -> int:
145
+ if is_excel:
146
+ raw = pd.read_excel(filename, header=None)
147
+ lines = [
148
+ " ".join(str(v) for v in row if pd.notna(v)).lower()
149
+ for row in raw.values.tolist()
150
+ ]
151
+ else:
152
+ with open(filename, "r", encoding="utf-8") as handle:
153
+ lines = [line.lower() for line in handle.readlines()]
154
+
155
+ for i, line in enumerate(lines):
156
+ matches = sum(1 for token in tokens if token in line)
157
+ if matches >= min_matches:
158
+ return i
159
+
160
+ raise ValueError(
161
+ f"Could not locate header row for '{self.bank_id}'. "
162
+ f"Expected at least {min_matches} of: {tokens}"
163
+ )
164
+
165
+ # ------------------------------------------------------------------ #
166
+ # Column resolution
167
+ # ------------------------------------------------------------------ #
168
+ def _resolve_columns(self, df: pd.DataFrame) -> dict:
169
+ """Map each logical field to an actual dataframe column."""
170
+ normalized = {}
171
+ for actual in df.columns:
172
+ normalized.setdefault(_normalize(actual), actual)
173
+
174
+ resolved = {}
175
+ for logical, candidates in self.config.get("columns", {}).items():
176
+ if isinstance(candidates, str):
177
+ candidates = [candidates]
178
+ found = None
179
+ for candidate in candidates:
180
+ key = _normalize(candidate)
181
+ if key in normalized:
182
+ found = normalized[key]
183
+ break
184
+ if found is None:
185
+ raise ValueError(
186
+ f"[{self.bank_id}] Could not find a column for "
187
+ f"'{logical}'. Tried {candidates}. "
188
+ f"Available columns: {list(df.columns)}"
189
+ )
190
+ resolved[logical] = found
191
+ return resolved
192
+
193
+ # ------------------------------------------------------------------ #
194
+ # Value helpers
195
+ # ------------------------------------------------------------------ #
196
+ @staticmethod
197
+ def _to_float(series: pd.Series) -> pd.Series:
198
+ cleaned = (
199
+ series.astype(str)
200
+ .str.replace(",", "", regex=False)
201
+ .str.replace(r"[^0-9.\-]", "", regex=True)
202
+ .str.strip()
203
+ .replace("", "0")
204
+ )
205
+ return pd.to_numeric(cleaned, errors="coerce")
206
+
207
+ @staticmethod
208
+ def _to_date(series: pd.Series) -> pd.Series:
209
+ def parse(value):
210
+ text = str(value).strip()
211
+ if not text:
212
+ return pd.NaT
213
+ # A bare number (id/serial column) is not a real date. dateutil
214
+ # would happily turn "1" into a date, so reject these explicitly.
215
+ if re.fullmatch(r"[+-]?\d+(\.\d+)?", text):
216
+ return pd.NaT
217
+ try:
218
+ parsed = date_parser.parse(text, dayfirst=True)
219
+ if parsed.year < 1900:
220
+ return pd.NaT
221
+ return parsed
222
+ except (ValueError, OverflowError, TypeError):
223
+ return pd.NaT
224
+
225
+ return series.apply(parse)
226
+
227
+ # ------------------------------------------------------------------ #
228
+ # Transaction building
229
+ # ------------------------------------------------------------------ #
230
+ def _build_transactions(self, df: pd.DataFrame) -> list[Transaction]:
231
+ cols = self._resolve_columns(df)
232
+ work = pd.DataFrame(index=df.index)
233
+
234
+ # Date (used to drop non-transaction rows automatically)
235
+ date_field = self.config["date_field"]
236
+ work["_date"] = self._to_date(df[cols[date_field]])
237
+ work = work[work["_date"].notna()]
238
+ df = df.loc[work.index]
239
+
240
+ # Amount
241
+ work["_amount"] = self._compute_amount(df, cols).loc[work.index]
242
+
243
+ # Remarks (without the duplicate marker yet)
244
+ work["_remarks"] = self._compute_remarks(df, cols).loc[work.index]
245
+
246
+ # Duplicate sequence marker
247
+ seq = (
248
+ work.groupby(["_date", "_remarks", "_amount"]).cumcount().add(1)
249
+ )
250
+
251
+ transactions: list[Transaction] = []
252
+ for idx, row in work.iterrows():
253
+ remarks = row["_remarks"]
254
+ if seq[idx] > 1:
255
+ remarks = remarks + " (" + str(seq[idx]) + ") "
256
+ transactions.append(
257
+ Transaction(
258
+ bank=self.bank_id,
259
+ created_date=row["_date"],
260
+ remarks=remarks,
261
+ amount=row["_amount"],
262
+ )
263
+ )
264
+ return transactions
265
+
266
+ def _compute_amount(self, df: pd.DataFrame, cols: dict) -> pd.Series:
267
+ cfg = self.config["amount"]
268
+ mode = cfg.get("mode", "direct")
269
+
270
+ if mode == "direct":
271
+ return self._to_float(df[cols[cfg["field"]]]).fillna(0)
272
+
273
+ if mode == "deposit_minus_withdrawal":
274
+ deposit = self._to_float(df[cols[cfg["deposit"]]]).fillna(0)
275
+ withdrawal = self._to_float(df[cols[cfg["withdrawal"]]]).fillna(0)
276
+ return deposit - withdrawal
277
+
278
+ if mode == "signed":
279
+ value = self._to_float(df[cols[cfg["field"]]]).fillna(0)
280
+ credit_values = {
281
+ str(v).upper() for v in cfg.get("credit_values", ["CR"])
282
+ }
283
+ credit_sign = cfg.get("credit_sign", 1)
284
+ debit_sign = cfg.get("debit_sign", -1)
285
+ sign_col = df[cols[cfg["sign_field"]]].astype(str)
286
+ sign = sign_col.str.upper().str.strip().map(
287
+ lambda v: credit_sign if v in credit_values else debit_sign
288
+ )
289
+ return value * sign
290
+
291
+ raise ValueError(f"[{self.bank_id}] Unknown amount mode '{mode}'")
292
+
293
+ def _compute_remarks(self, df: pd.DataFrame, cols: dict) -> pd.Series:
294
+ parts_cfg = self.config["remarks"]
295
+ result = pd.Series([""] * len(df), index=df.index)
296
+
297
+ for part in parts_cfg:
298
+ field = part["field"]
299
+ prefix = part.get("prefix", "")
300
+ suffix = part.get("suffix", "")
301
+ skip = {str(s).lower() for s in part.get("skip", [])}
302
+ column = df[cols[field]]
303
+
304
+ def render(value):
305
+ text = "" if pd.isna(value) else str(value).strip()
306
+ if text.lower() in skip:
307
+ return ""
308
+ return prefix + text + suffix
309
+
310
+ result = result + column.map(render)
311
+
312
+ return result.str.strip()
@@ -0,0 +1,9 @@
1
+ from .Bank import Bank
2
+ from .Transaction import Transaction
3
+ from .GenericBank import GenericBank, list_banks, load_configs
4
+
5
+ __all__ = ['Bank',
6
+ 'Transaction',
7
+ 'GenericBank',
8
+ 'list_banks',
9
+ 'load_configs']
@@ -0,0 +1,198 @@
1
+ {
2
+ "HDFC-CREDIT": {
3
+ "file": {
4
+ "delimiter": "~",
5
+ "header": {
6
+ "mode": "detect",
7
+ "match": ["transaction type", "description", "debit / credit"],
8
+ "min_matches": 2
9
+ }
10
+ },
11
+ "columns": {
12
+ "date": ["DATE"],
13
+ "description": ["Description"],
14
+ "amount": ["AMT"],
15
+ "sign": ["Debit / Credit"]
16
+ },
17
+ "date_field": "date",
18
+ "remarks": [
19
+ {"field": "description"}
20
+ ],
21
+ "amount": {
22
+ "mode": "signed",
23
+ "field": "amount",
24
+ "sign_field": "sign",
25
+ "credit_values": ["CR"]
26
+ }
27
+ },
28
+
29
+ "HSBC-CREDIT": {
30
+ "file": {
31
+ "delimiter": ",",
32
+ "header": {
33
+ "mode": "none",
34
+ "names": ["Date", "Transaction Details", "Amount"]
35
+ }
36
+ },
37
+ "columns": {
38
+ "date": ["Date"],
39
+ "details": ["Transaction Details"],
40
+ "amount": ["Amount"]
41
+ },
42
+ "date_field": "date",
43
+ "remarks": [
44
+ {"field": "details"}
45
+ ],
46
+ "amount": {
47
+ "mode": "direct",
48
+ "field": "amount"
49
+ }
50
+ },
51
+
52
+ "HSBC-DEBIT": {
53
+ "file": {
54
+ "header": {
55
+ "mode": "detect",
56
+ "match": ["date", "transaction details", "deposits", "withdrawals"],
57
+ "min_matches": 3
58
+ }
59
+ },
60
+ "columns": {
61
+ "date": ["Date"],
62
+ "details": ["Transaction Details"],
63
+ "deposit": ["Deposits"],
64
+ "withdrawal": ["Withdrawals"]
65
+ },
66
+ "date_field": "date",
67
+ "remarks": [
68
+ {"field": "details"}
69
+ ],
70
+ "amount": {
71
+ "mode": "deposit_minus_withdrawal",
72
+ "deposit": "deposit",
73
+ "withdrawal": "withdrawal"
74
+ }
75
+ },
76
+
77
+ "ICICI-CREDIT": {
78
+ "file": {
79
+ "delimiter": ",",
80
+ "header": {
81
+ "mode": "detect",
82
+ "match": ["date", "sr.no", "transaction details", "amount(in rs)"],
83
+ "min_matches": 3
84
+ }
85
+ },
86
+ "columns": {
87
+ "date": ["Date"],
88
+ "details": ["Transaction Details"],
89
+ "amount": ["Amount(in Rs)"],
90
+ "sign": ["BillingAmountSign"]
91
+ },
92
+ "date_field": "date",
93
+ "remarks": [
94
+ {"field": "details"}
95
+ ],
96
+ "amount": {
97
+ "mode": "signed",
98
+ "field": "amount",
99
+ "sign_field": "sign",
100
+ "credit_values": ["CR"]
101
+ }
102
+ },
103
+
104
+ "ICICI-DEBIT": {
105
+ "file": {
106
+ "header": {
107
+ "mode": "detect",
108
+ "match": [
109
+ "transaction date",
110
+ "cheque number",
111
+ "transaction remarks",
112
+ "withdrawal amount",
113
+ "deposit amount"
114
+ ],
115
+ "min_matches": 3
116
+ }
117
+ },
118
+ "columns": {
119
+ "date": ["Transaction Date"],
120
+ "cheque": ["Cheque Number"],
121
+ "remarks": ["Transaction Remarks"],
122
+ "withdrawal": ["Withdrawal Amount (INR )"],
123
+ "deposit": ["Deposit Amount (INR )"]
124
+ },
125
+ "date_field": "date",
126
+ "remarks": [
127
+ {"field": "cheque", "prefix": "CHQ: ", "suffix": " ",
128
+ "skip": ["-", "", "nan"]},
129
+ {"field": "remarks"}
130
+ ],
131
+ "amount": {
132
+ "mode": "deposit_minus_withdrawal",
133
+ "deposit": "deposit",
134
+ "withdrawal": "withdrawal"
135
+ }
136
+ },
137
+
138
+ "KOTAK-DEBIT": {
139
+ "file": {
140
+ "delimiter": ",",
141
+ "header": {
142
+ "mode": "detect",
143
+ "match": [
144
+ "sl. no.",
145
+ "transaction date",
146
+ "description",
147
+ "amount",
148
+ "dr / cr"
149
+ ],
150
+ "min_matches": 3
151
+ }
152
+ },
153
+ "columns": {
154
+ "date": ["Transaction Date"],
155
+ "description": ["Description"],
156
+ "ref": ["Chq / Ref No."],
157
+ "amount": ["Amount"],
158
+ "sign": ["Dr / Cr"]
159
+ },
160
+ "date_field": "date",
161
+ "remarks": [
162
+ {"field": "ref", "prefix": "Ref: ", "suffix": " ",
163
+ "skip": ["nan", "", "-"]},
164
+ {"field": "description"}
165
+ ],
166
+ "amount": {
167
+ "mode": "signed",
168
+ "field": "amount",
169
+ "sign_field": "sign",
170
+ "credit_values": ["CR"]
171
+ }
172
+ },
173
+
174
+ "WALLET": {
175
+ "file": {
176
+ "header": {
177
+ "mode": "detect",
178
+ "match": ["date", "note", "category", "amount"],
179
+ "min_matches": 3
180
+ }
181
+ },
182
+ "columns": {
183
+ "date": ["date"],
184
+ "note": ["note"],
185
+ "category": ["category"],
186
+ "amount": ["amount"]
187
+ },
188
+ "date_field": "date",
189
+ "remarks": [
190
+ {"field": "category", "suffix": ": ", "skip": [""]},
191
+ {"field": "note"}
192
+ ],
193
+ "amount": {
194
+ "mode": "direct",
195
+ "field": "amount"
196
+ }
197
+ }
198
+ }