|
| 1 | +import logging |
1 | 2 | import re |
2 | 3 | from decimal import Decimal |
3 | 4 |
|
4 | | -from dateutil import parser as date_parser |
5 | | - |
6 | 5 | from cas2json import patterns |
7 | | -from cas2json.enums import CASFileType, TransactionType |
8 | | -from cas2json.flags import MULTI_TEXT_FLAGS, TEXT_FLAGS |
9 | | -from cas2json.types import TransactionData |
10 | | -from cas2json.utils import formatINR |
| 6 | +from cas2json.constants import MISCELLANEOUS_KEYWORDS |
| 7 | +from cas2json.enums import TransactionType |
| 8 | +from cas2json.flags import TEXT_FLAGS |
| 9 | + |
| 10 | +logger = logging.getLogger(__name__) |
| 11 | +logger.setLevel(logging.DEBUG) |
11 | 12 |
|
12 | 13 |
|
13 | 14 | def get_transaction_type(description: str, units: Decimal | None) -> tuple[TransactionType, Decimal | None]: |
14 | | - """Get transaction type from the description text.""" |
| 15 | + """Get transaction type from the description text and units.""" |
15 | 16 |
|
16 | 17 | description = description.lower() |
17 | 18 | # Dividend |
@@ -54,83 +55,17 @@ def get_transaction_type(description: str, units: Decimal | None) -> tuple[Trans |
54 | 55 | return (TransactionType.SWITCH_OUT_MERGER if "merger" in description else TransactionType.SWITCH_OUT, None) |
55 | 56 | return (TransactionType.REDEMPTION, None) |
56 | 57 |
|
57 | | - print("Warning: Error identifying transaction. Please report the issue with the transaction description") |
58 | | - print(f"Txn description: {description} :: Units: {units}") |
59 | | - return (TransactionType.UNKNOWN, None) |
| 58 | + for keyword in MISCELLANEOUS_KEYWORDS: |
| 59 | + if keyword in description: |
| 60 | + return (TransactionType.MISC, None) |
60 | 61 |
|
61 | | - |
62 | | -def get_transaction_values(values: str) -> tuple[str | None, str | None, str | None, str | None]: |
63 | | - """ |
64 | | - Extract transaction values in the order of amount, units, nav, and balance from the given string. |
65 | | - """ |
66 | | - values = re.findall(patterns.AMT, values.strip()) |
67 | | - units = nav = balance = amount = None |
68 | | - if len(values) >= 4: |
69 | | - # Normal entry |
70 | | - amount, units, nav, balance, *_ = values |
71 | | - elif len(values) == 3: |
72 | | - # Zero unit entry |
73 | | - amount, nav, balance = values |
74 | | - units = "0.000" |
75 | | - elif len(values) == 2: |
76 | | - # Segregated Portfolio Entries |
77 | | - units, balance = values |
78 | | - elif len(values) == 1: |
79 | | - # Tax entries |
80 | | - amount = values[0] |
81 | | - return amount, units, nav, balance |
| 62 | + logger.warning(f"Error identifying transaction. Description: {description} :: Units: {units}") |
| 63 | + return (TransactionType.UNKNOWN, None) |
82 | 64 |
|
83 | 65 |
|
84 | 66 | def get_parsed_scheme_name(scheme: str) -> str: |
| 67 | + """Helper to clean scheme names.""" |
85 | 68 | scheme = re.sub(r"\((formerly|erstwhile).+?\)", "", scheme, flags=TEXT_FLAGS).strip() |
86 | 69 | scheme = re.sub(r"\((Demat|Non-Demat).*", "", scheme, flags=TEXT_FLAGS).strip() |
87 | 70 | scheme = re.sub(r"\s+", " ", scheme).strip() |
88 | 71 | return re.sub(r"[^a-zA-Z0-9_)]+$", "", scheme).strip() |
89 | | - |
90 | | - |
91 | | -def detect_cas_type(parsed_lines: list[str]) -> CASFileType: |
92 | | - """Detect the type of CAS statement (detailed or summary) from the parsed lines.""" |
93 | | - text = "\u2029".join(parsed_lines) |
94 | | - if m := re.search(patterns.CAS_TYPE, text, MULTI_TEXT_FLAGS): |
95 | | - match = m.group(1).lower().strip() |
96 | | - if match == "statement": |
97 | | - return CASFileType.DETAILED |
98 | | - elif match == "summary": |
99 | | - return CASFileType.SUMMARY |
100 | | - return CASFileType.UNKNOWN |
101 | | - |
102 | | - |
103 | | -def parse_transaction(line: str) -> list[TransactionData]: |
104 | | - """ |
105 | | - Parse a transaction line and return a list of TransactionData objects. |
106 | | - """ |
107 | | - transactions: list[TransactionData] = [] |
108 | | - parsed_transactions = re.findall(patterns.TRANSACTIONS, line, MULTI_TEXT_FLAGS) |
109 | | - if not parsed_transactions: |
110 | | - return transactions |
111 | | - |
112 | | - for txn in parsed_transactions: |
113 | | - date, details, *_ = txn |
114 | | - if not details or not details.strip() or not date: |
115 | | - continue |
116 | | - description_match = re.match(patterns.DESCRIPTION, details.strip(), MULTI_TEXT_FLAGS) |
117 | | - if not description_match: |
118 | | - continue |
119 | | - description, values, *_ = description_match.groups() |
120 | | - amount, units, nav, balance = get_transaction_values(values) |
121 | | - description = description.strip() |
122 | | - units = formatINR(units) |
123 | | - txn_type, dividend_rate = get_transaction_type(description, units) |
124 | | - transactions.append( |
125 | | - TransactionData( |
126 | | - date=date_parser.parse(date).date(), |
127 | | - description=description, |
128 | | - type=txn_type.name, |
129 | | - amount=formatINR(amount), |
130 | | - units=units, |
131 | | - nav=formatINR(nav), |
132 | | - balance=formatINR(balance), |
133 | | - dividend_rate=dividend_rate, |
134 | | - ) |
135 | | - ) |
136 | | - return transactions |
0 commit comments