Skip to content
Last updated

Payment Rating and History

Payment Rating (Field 17B)

Payment Rating provides the current payment status as a single character:

CodeDescription
(blank)No payment rating applicable
00-19 days past due date
130-59 days past due
260-89 days past due
390-119 days past due
4120-149 days past due
5150-179 days past due
6180+ days past due
GCollection
LCharge-off

When Payment Rating is Blank

Payment Rating is left blank for non-terminal account statuses. It is populated only for terminal statuses:

  • 05 (Account Transferred)
  • 13 (Paid or Closed)
  • 65 (Paid in Full - Foreclosure Started)
  • 88 (Claim Filed for Insured Portion)
  • 89 (Deed Received in Lieu of Foreclosure)
  • 94 (Foreclosure Completed)
  • 95 (Voluntary Surrender)

Payment Rating Calculation Algorithm

FUNCTION calculate_payment_rating(account_status, loan_status, snapshot):
  
  TERMINAL_STATUSES = {05, 13, 65, 88, 89, 94, 95}
  
  # Only populate for terminal statuses
  IF account_status NOT IN TERMINAL_STATUSES:
    RETURN Blank
  
  # For active/frozen/accelerated loans, use current overdue days
  IF loan_status IN ('Active', 'Frozen', 'Accelerated'):
    RETURN overdue_days_to_rating(snapshot.overdue_number_days)
  
  # For paid-off loans, use the overdue days from before payoff
  IF loan_status = 'PaidOff':
    most_recent_active_snapshot = find_most_recent_active_snapshot()
    IF most_recent_active_snapshot:
      RETURN overdue_days_to_rating(most_recent_active_snapshot.overdue_number_days)
    RETURN '0'  # Default to current if no prior snapshot
  
  # For charged-off loans, always return charge-off rating
  IF loan_status = 'ChargedOff':
    RETURN 'L'  # Charge-off
  
  RAISE Error("Unknown loan status for payment rating")

FUNCTION overdue_days_to_rating(days):
  IF days < 30: RETURN '0'   # Current
  IF days < 60: RETURN '1'   # 30-59 days
  IF days < 90: RETURN '2'   # 60-89 days
  IF days < 120: RETURN '3'  # 90-119 days
  IF days < 150: RETURN '4'  # 120-149 days
  IF days < 180: RETURN '5'  # 150-179 days
  RETURN '6'  # 180+ days

Payment History Profile (Field 18)

The Payment History Profile is a 24-character string representing the payment status for each of the last 24 months, from most recent to oldest.

Profile Characters

CodeDescription
0Current
130-59 days past due
260-89 days past due
390-119 days past due
4120-149 days past due
5150-179 days past due
6180+ days past due
BNo prior history available
DNo payment reported
ECurrent with zero balance
GCollection
HForeclosure completed
JVoluntary surrender
KRepossession
LCharge-off

Payment History Profile Algorithm

FUNCTION calculate_payment_history_profile(loan, report_date):
  history = []
  
  # Start from the month before the reporting month
  last_reported_month = end_of_previous_month(report_date.replace(day=1) - 1)
  reported_months = get_24_months_ending_with(last_reported_month)
  
  FOR EACH reported_month IN reported_months:
    
    # Check if we're before reporting started
    IF reporting_start_date > reported_month:
      history.append('B')  # No prior history
      CONTINUE
    
    # Check for migration history
    IF migration_cutoff_date AND reported_month <= migration_cutoff_date:
      IF migration_payment_history_profile exists:
        month_delta = months_between(reported_month, migration_cutoff_date)
        IF month_delta < 24:
          history.append(migration_payment_history_profile[month_delta])
        CONTINUE
    
    # Find snapshot for the month
    snapshot = find_snapshot_for_month(reported_month)
    
    # Check for active bankruptcy case that affects this month
    # CRITICAL: Loan must be associated with the bankruptcy case
    bk_case = find_bankruptcy_case(
      loan_associated = True,  # Loan must be linked to this case
      status IN ('Processing', 'Completed'),
      date_filter: (
        reported_month >= courtCaseFiledDate AND
        reported_month < coalesce(courtCaseClosedDate, courtCaseDebtorDispositionDate, 
                                   reported_month + 1 calendar day)
      )
    )
    
    IF snapshot IS NULL:
      history.append('D')  # No payment reported
    ELIF snapshot.loan_status = 'Frozen' OR bk_case IS NOT NULL:
      history.append('D')  # No payment reported (frozen or bankruptcy protection)
    ELIF snapshot.loan_status = 'ChargedOff':
      history.append('L')  # Charge-off
    ELIF is_open_ended AND balance = 0 AND status = 'Active':
      history.append('E')  # Current zero balance
    ELSE:
      history.append(dpd_to_code(snapshot.overdue_number_days))
  
  RETURN join(history)