Skip to content

YAML Lender Mapping System

The lender mapping system enables flexible ingestion of payment/NACH CSV files from different lenders without code changes. Each lender has a YAML configuration file that defines how CSV columns map to a standardized 10-column schema in the RAW database layer.

Standardized Schema

Every payment file, regardless of lender, is normalized to these 10 columns before being stored in the RAW table:

ColumnTypeDescription
loan_idtextLoan identifier (external ID in Fineract)
amountnumericTransaction/repayment amount
datetimestampTransaction date
statustextPayment status (success/failed/bounce)
chargenumericFees, charges, penalties
notetextRemarks, reason descriptions
payment_idtextUTR, payment reference
sub_reference_idtextUMRN / mandate reference
source_filetextS3 key of the uploaded file
lendertextLender identifier (auto-filled)

YAML Configuration Structure

Each YAML file is located at config/lender_mappings/{lender_key}.yml:

yaml
lender_key: arthmate
version: 1

# Map CSV column names to standardized fields
columns:
  loan_id:                    # Exact CSV header (case-insensitive)
    target: loan_id           # Standardized column name
    type: text                # text | numeric | date | timestamp
  Amount:
    target: amount
    type: numeric
  Transaction Date:
    target: date
    type: date
  Status:
    target: status
    type: text
  reason:
    target: note
    type: text

# Date format string (Python strptime format)
date_formats:
  date: "%d-%m-%Y"

# Row-level filters - only matching rows are loaded into RAW
filters:
  - column: amount
    op: ">"
    value: 0
  - column: status
    op: "in"
    value: ["success", "successful", "paid", "cleared", "clear",
            "settled", "realized", "received", "realised", "captured"]

# Constant values for every row
defaults:
  lender: arthmate

Column Mapping

How Matching Works

CSV headers are matched case-insensitively with whitespace trimmed:

yaml
# YAML key          CSV header it matches
"Link ID"        -> "Link ID", "link id", "LINK ID"
"Debit Amt "     -> "Debit Amt ", "debit amt"
" Final Amt "    -> " Final Amt ", "final amt"

Type Casting

TypeBehavior
textTrimmed, empty strings become NULL
numericCommas stripped, converted to float. Invalid values become NULL
dateParsed using date_formats.date if specified
timestampParsed using format if specified, else auto-detected by pandas

Auto-Parse Dates

If date_formats is empty ({}), pandas auto-detects the format. This handles Excel CSV exports where date formats vary (e.g., 4/5/2026 vs 2026-04-05):

yaml
date_formats: {}   # Auto-detect - use when format varies between exports

Post-Compute Operations

Perform column arithmetic after type casting but before filtering:

yaml
# DMI Payment: amount = net_pmt - other_charges_amount
post_compute:
  - column: amount           # Target column to update
    op: subtract             # subtract | add
    value_column: charge     # Column to subtract

After this, amount holds net_pmt - other_charges_amount and charge retains the original charge value.

Filters

Filters run in order. Rows that don't match are rejected (saved to RAW.RAW_REJECTED_RECORDS for visibility in the Bad Records tab).

Supported Operators

OperatorDescriptionExample
>Greater than (numeric)amount > 0
>=Greater than or equalamount >= 100
==Equals (case-insensitive)status == "success"
!=Not equalsstatus != "failed"
inIn list (case-insensitive)status in ["success", "clear"]
not_inNot in liststatus not_in ["bounce"]

Status Filtering

Files with a status column should filter to success-only records:

yaml
filters:
  - column: status
    op: "in"
    value: ["success", "successful", "paid", "cleared", "clear",
            "settled", "realized", "received", "realised", "captured"]

Files without a status column (e.g., Shivalik Payment) have no status filter - all records are loaded.

Current Lender Configurations

Lender KeyFile TypeLoan ID ColumnStatus ColumnDate Format
arthmateNACH Statusloan_idStatus (CLEAR/BOUNCE)%d-%m-%Y
dmiNACH StatusOpportunity NameStatus (Clear/Bounce)auto
dmi_paymentPayment Reportloan_numberstatus (Received)%Y-%m-%d %H:%M
gbCashfree NACHLink IDTransaction Status (SUCCESS)%Y-%m-%d %H:%M:%S
ikfNACH StatusConsumer Reference NumberStatus (Cleared)%d.%m.%Y
shivalikNACH StatusLANsettlement_status (SUCCESS)%d-%m-%Y
shivalik_paymentPayment ReportRef 1(none)auto
ugroCashfree NACHLink IDTransaction Status (SUCCESS)%Y-%m-%d %H:%M:%S
ugro_razorpayRZP Settlementorder_receipt(amount > 0 only)%d/%m/%Y %H:%M:%S
vivritiRZP Settlementorder_receipt(amount > 0 only)%d/%m/%Y %H:%M:%S

Loan ID Resolution

Some lenders include the loan ID embedded in a larger string. The BASE dbt layer handles parsing:

LenderRaw ValueParsed Loan IDMethod
GBGB00682_Apr26_NitinSinghGB00682split_part(loan_id, '_', 1)
UGROTURNO01446_Apr26_NameTURNO01446split_part(loan_id, '_', 1)
VivritiVCPL003039_Mar26_ShivaVCPL003039split_part(loan_id, '_', 1)
UGRO RazorpayTURNO00991_ENACHTURNO00991split_part(loan_id, '_', 1)

UMRN Fallback (GB & UGRO Cashfree)

When Link ID is blank in Cashfree NACH files, the system:

  1. Extracts subReferenceId from the Order Note column
  2. Looks up the UMRN Master table to resolve the loan ID
  3. Falls back to blank (record flagged as bad) if no match

See UMRN Master for details.

Turno Fineract LMS Documentation