Skip to content

handle nfo, reject ticob, flag dividend mismatches, name-match family pans#9

Closed
sandeeprjs92 wants to merge 3 commits intomainfrom
fix/linkedin-edge-cases
Closed

handle nfo, reject ticob, flag dividend mismatches, name-match family pans#9
sandeeprjs92 wants to merge 3 commits intomainfrom
fix/linkedin-edge-cases

Conversation

@sandeeprjs92
Copy link
Copy Markdown
Contributor

Acting on a LinkedIn comment raised about three edge cases the library didn't cover. Each commit addresses one concern, with tests.

Research first, code second

Before writing anything I searched public BSE StAR MF and CAMS docs and compared against the upstream source pipeline. Summary:

Code Public docs Source pipeline Decision
NFO Confirmed: standard 'New Fund Offer' transaction type documented by BSE StAR MF and every AMC. Not handled. Classify as (BUY, new_fund_offer).
TICOB / TOCOB No public documentation found. record_validator.py:237 explicitly rejects these for CAMS with 'unsupported type'. Port the rejection.
FC Not found in BSE StAR MF, CAMS public docs, or the source pipeline. Not handled. Intentionally left alone. Guessing at buy/sell semantics for a code I can't verify would be worse than a visible no-op. Commenter can clarify their registrar's specific meaning.
Dividend option mismatch Real issue — SEBI renamed 'Dividend' to 'IDCW' in 2021, so historical feeds disagree with updated masters. Captures the raw flag but does not reconcile. Capture it, normalize to canonical plan_type, reject the row to the correction queue on mismatch.
Messy investor data Both pipelines match by PAN only. Add canonical name fallback for family PANs.

Commits

1. add nfo classification and reject cams ticob/tocob

  • CamsAdapter and KFintechFormat1/2/CsvAdapter now classify NFO as (BUY, 'new_fund_offer')
  • New FeedAdapter.rejected_types class attribute (default empty). CamsAdapter sets it to {'TICOB', 'TOCOB'}
  • Cleaner.run drops rejected rows before any further processing
  • Tests: NFO classification for both registrars, TICOB/TOCOB rejection at the cleaner level

2. capture dividend option flag and reject plan_type mismatches

  • CAMS adapter field map picks up REINVEST_Fdividend_option_flag, normalized via CAMS_REINVEST_F_TO_PLAN_TYPE ({Y: idcw_reinvest, N: idcw_payout})
  • KFintech field map picks up DIVOPTdividend_option_flag, normalized via KFINTECH_DIVOPT_TO_PLAN_TYPE with both legacy 'DIVIDEND PAYOUT'/'DIVIDEND REINVESTMENT' wording and current 'IDCW PAYOUT'/'IDCW REINVESTMENT' wording folding to the same canonical values
  • adapter.normalize() emits a plan_type_from_feed column after the rename
  • core/validator.py raises ValidationError(CorrectionType.OTHER) when the feed's declared plan_type_from_feed disagrees with the scheme master's plan_type
  • Silent feeds (empty DIVOPT, growth-only funds) don't activate the check — scheme master stays the source of truth
  • Tests: mismatch raises, match passes, silent feed passes

3. resolve family pan by canonical investor name when ownership_type is silent

  • New private _canonicalize_name helper in core/account_resolver.py — upper-case plus whitespace collapse, deliberately not fuzzy
  • When a family PAN has multiple accounts and ownership_type doesn't tie-break, try unique name-equality match before falling through to individual default
  • Only activates when (a) multiple accounts for the PAN, (b) the row has an investor_name, (c) exactly one stored account matches after canonicalisation. Ambiguous multi-match still raises AmbiguousPanError so operators resolve it in the correction queue
  • Tests: happy match, multi-match falls through, silent feed unchanged

Local verification

  • 105 unit tests pass (was 96, added 9)
  • Ruff clean
  • Bandit clean
  • Forbidden-strings scan clean (108 files)

Acting on a LinkedIn comment about edge-case transaction types.

NFO (New Fund Offer) is a standard BSE STAR MF purchase type used
for initial subscriptions during a fund offer period. Classify it as
(BUY, new_fund_offer) in both CAMS and KFintech adapters. Add NFO to
the CAMS _BUY_TYPES set.

CAMS TICOB and TOCOB (transfer in/out close-of-business variants)
are not real orders — the source pipeline rejects them at validation
time. Port that rejection by adding a per-adapter rejected_types set
and filtering those rows out in Cleaner.run before anything else
runs. Default is an empty set so other adapters are unaffected.

I also researched FC and standalone COB via web search and the
BSE STAR MF file structure spec. Neither is documented in public
CAMS or BSE references and neither appears in the upstream source
pipeline. Leaving them unhandled until a commenter or maintainer
can clarify the specific registrar semantics — guessing at buy/sell
for a code I cannot verify would be worse than a visible no-op.
Acting on a LinkedIn comment about dividend option mismatches.

CAMS ships a REINVEST_F column ('Y' = reinvest, 'N' = payout) and
KFintech ships DIVOPT with human-readable strings that differ across
file vintages: legacy files still use 'DIVIDEND PAYOUT' /
'DIVIDEND REINVESTMENT' wording while post-SEBI-2021 files use
'IDCW PAYOUT' / 'IDCW REINVESTMENT'. Both map to the same canonical
plan_type in the scheme master.

Each adapter's normalize() now emits a plan_type_from_feed column
populated from the raw flag via a case-insensitive, whitespace-
collapsed lookup. The per-row validator reads that column and raises
a ValidationError (CorrectionType.OTHER) whenever the feed's
declared plan_type disagrees with the scheme master's value.

When the feed is silent on dividend option (empty column or 'GROWTH'
with no payout/reinvest distinction), the scheme master remains the
source of truth and validation passes.
…silent

Acting on a LinkedIn comment about messy / inconsistent investor
data on family PANs.

When a family PAN maps to multiple accounts and the row doesn't
carry an ownership_type the resolver can tie-break with, try an
investor-name equality match after canonicalisation. Canonicalisation
is upper-case + whitespace collapse — deliberately not fuzzy, because
false positives on family accounts are worse than an AmbiguousPanError
the operator can fix in the correction queue.

Only activates when (a) the PAN has multiple accounts, (b) the row
carries an investor_name, (c) exactly one stored account matches.
If two stored accounts share the same canonical name or the row is
silent on the name, we fall through to the existing individual
default / ambiguous path unchanged.

The adapters already map CAMS INVNAME and KFintech INV_NAME to the
investor_name canonical column, so no adapter change needed.
@sandeeprjs92
Copy link
Copy Markdown
Contributor Author

closing in favour of #TBD on the renamed branch fix/registrar-edge-cases — branch name should not reference a social platform

@sandeeprjs92 sandeeprjs92 deleted the fix/linkedin-edge-cases branch April 15, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant