Benjamin-eecs/openrsi-commit-runtime-assets
0168
1diff --git a/CHANGELOG.md b/CHANGELOG.md2index 0d5255e..d6d4e0b 1006443--- a/CHANGELOG.md4+++ b/CHANGELOG.md5@@ -1,5 +1,25 @@6 # Changelog7 8+## 1.2.x9+10+### New11+12+- `NSDLCASData.parse_warnings` — non-fatal warnings for demat holdings whose13+ numbers don't reconcile.14+15+### Fixed16+17+- NSDL demat holdings parsed by arithmetic (`units*nav≈value`) instead of18+ hardcoded pixel columns; fixes wrong nav/value on layout-shifted statements.19+- NSDL equities: same fix (`shares*price≈value`) + pledged sub-rows no longer20+ parsed as separate holdings.21+- CDSL mutual-fund profit/return assigned by `value-cost` identity, not22+ position; fixes return% being reported as profit.23+24+### Changed25+26+- `CapitalGainsReport` is lazy-loaded, keeping `import casparser` lighter.27+28 ## 1.2.129 30 ### Fixed31diff --git a/casparser/parsers/cdsl.py b/casparser/parsers/cdsl.py32index cf3774b..7fc0828 10064433--- a/casparser/parsers/cdsl.py34+++ b/casparser/parsers/cdsl.py35@@ -132,6 +132,66 @@ def _looks_numeric(text: str) -> bool:36 return bool(NUMERIC_RE.match(s))37 38 39+def _rel_close(a: Decimal, b: Decimal, rel: Decimal = Decimal("0.005")) -> bool:40+ if b == 0:41+ return abs(a) <= Decimal("0.01")42+ return abs(a - b) / abs(b) <= rel43+44+45+def _resolve_cdsl_mf_pnl_returns(46+ numerics: List[str],47+ value: Decimal,48+ invested: Optional[Decimal],49+ has_distrib_col: bool,50+) -> Tuple[Optional[Decimal], Optional[Decimal]]:51+ """Assign profit and return% from the post-value numeric tail.52+53+ Positional ``numerics[-2]`` / ``[-1]`` is wrong when the statement54+ omits a printed profit column and only shows return% — identity55+ ``value - invested`` picks profit when present; otherwise leave56+ ``pnl`` unset and treat a small trailing % as ``return_``."""57+ if not has_distrib_col:58+ return None, None59+60+ value_idx = 3 if len(numerics) >= 4 else 261+ remaining = [_to_decimal(n) for n in numerics[value_idx + 1 :]]62+ remaining = [r for r in remaining if r != 0]63+64+ expected_pnl: Optional[Decimal] = None65+ if invested is not None and invested > 0:66+ expected_pnl = value - invested67+68+ pnl: Optional[Decimal] = None69+ ret: Optional[Decimal] = None70+71+ if expected_pnl is not None:72+ for r in remaining:73+ if _rel_close(r, expected_pnl):74+ pnl = r75+ break76+ if pnl is None and len(numerics) >= 6:77+ pos_pnl = _opt_decimal(numerics[-2])78+ if pos_pnl is not None and _rel_close(pos_pnl, expected_pnl):79+ pnl = pos_pnl80+81+ others = [r for r in remaining if r != pnl]82+ if pnl is not None and others:83+ ret = others[-1]84+ elif pnl is None and expected_pnl is not None:85+ for r in others:86+ if abs(r) < Decimal("100"):87+ ret = r88+ break89+ elif len(numerics) >= 5:90+ ret = _opt_decimal(numerics[-1])91+92+ if pnl == 0:93+ pnl = None94+ if ret == 0:95+ ret = None96+ return pnl, ret97+98+99 # --- account key utilities ---100 101 102@@ -586,8 +646,7 @@ def _parse_mf_holdings_row(103 # Reduced row: units | NAV | value (no separate invested/cost).104 invested = None105 value = _to_decimal(numerics[2])106- pnl = _opt_decimal(numerics[-2]) if has_distrib_col and len(numerics) >= 6 else None107- ret = _opt_decimal(numerics[-1]) if has_distrib_col and len(numerics) >= 5 else None108+ pnl, ret = _resolve_cdsl_mf_pnl_returns(numerics, value, invested, has_distrib_col)109 110 # Pull UCC from scheme_meta keyed on scheme_code (prefix of name)111 ucc = None112 