Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 1.2.1

### Fixed

- Fix mis-classification of STP (Systematic Transfer Plan) for
Kfintech-serviced schemes. (discussion #141)

## 1.2.0

### New
Expand Down
2 changes: 1 addition & 1 deletion casparser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
"CapitalGainsReport",
]

__version__ = "1.2.0"
__version__ = "1.2.1"
13 changes: 11 additions & 2 deletions casparser/parsers/_classify.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
)
REINVEST_RE = re.compile(r"reinvest", re.I)

# Systematic Transfer Plan. A switch by another name — money is moved
# between two schemes on a schedule. The two RTAs word it differently:
# CAMS prints "Systematic Transfer Plan Switch In/Out …" (already caught by
# the "switch" keyword), while KFintech prints it letter-spaced as
# "S T P In/Out (…)", which matches none of the plain substring checks and
# used to fall through to PURCHASE / REDEMPTION. Detecting it here — spacing
# tolerated — lets both RTAs classify consistently as SWITCH_IN / SWITCH_OUT.
STP_RE = re.compile(r"\bs\s*t\s*p\b|systematic\s+transfer", re.I)

# Counterparty folio embedded in a gift transfer description. Both RTAs
# name the other folio but punctuate differently — KFin uses a colon
# ("Folio No: 12345678901"), CAMS a dot ("Folio No.87654321") — so accept
Expand Down Expand Up @@ -82,7 +91,7 @@ def get_transaction_type(
elif units > 0:
if "gift" in description:
txn_type = TransactionType.GIFT_IN
elif "switch" in description:
elif "switch" in description or STP_RE.search(description):
txn_type = (
TransactionType.SWITCH_IN_MERGER
if "merger" in description
Expand All @@ -109,7 +118,7 @@ def get_transaction_type(
re.I,
):
txn_type = TransactionType.REVERSAL
elif "switch" in description:
elif "switch" in description or STP_RE.search(description):
txn_type = (
TransactionType.SWITCH_OUT_MERGER
if "merger" in description
Expand Down
33 changes: 33 additions & 0 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,39 @@ def test_gifts(self):
Decimal("4085.662"),
) == (TransactionType.GIFT_IN, None)

def test_stp(self):
# Systematic Transfer Plan legs must classify as SWITCH_OUT (source,
# negative units) / SWITCH_IN (target, positive units) regardless of
# how the RTA words them. CAMS spells it out ("Systematic Transfer
# Plan Switch …", already caught by the "switch" keyword); KFintech
# prints it letter-spaced as "S T P In/Out", which previously fell
# through to PURCHASE / REDEMPTION. (discussion #141)
assert get_transaction_type(
"Systematic Transfer Plan Switch Out - To SBI Small Cap Fund Dir Growth-",
Decimal("-5.938"),
) == (TransactionType.SWITCH_OUT, None)
assert get_transaction_type(
"Systematic Transfer Plan Switch In - From SBI Liquid Fund Direct Growth-",
Decimal("126.972"),
) == (TransactionType.SWITCH_IN, None)
# KFintech, letter-spaced.
assert get_transaction_type(
"S T P Out (To Axis Mid Cap Fund - Direct Growth F.No:91064932471)",
Decimal("-24.933"),
) == (TransactionType.SWITCH_OUT, None)
assert get_transaction_type(
"S T P In (From Axis Liquid Fund - Direct Growth F.No:91064932471)",
Decimal("571.574"),
) == (TransactionType.SWITCH_IN, None)

def test_sip_not_misread_as_stp(self):
# A genuine SIP must stay PURCHASE_SIP — the STP detection must not
# swallow "Systematic Investment" or the "SIP" token.
assert get_transaction_type(
"Systematic Investment-Instalment No 12",
Decimal("10"),
) == (TransactionType.PURCHASE_SIP, None)

def test_dividends(self):
assert get_transaction_type(
"IDCW Reinvestment @ Rs.2.00 per unit",
Expand Down