Proof of Capability
A production banking system written in 1999. No surviving source documentation. Seven programs, three shared data structures, 25 years of accumulated changes. We ingested it, mapped it, and produced working translations—in under an hour.
RippleForge ingested the entire codebase and produced this interactive dependency graph. Every function, every call path, every data structure—mapped automatically.
The interest calculation engine—one of seven programs. Original COBOL on the left, modern translations on the right. Same business logic, modern language, maintainable code.
IDENTIFICATION DIVISION.
PROGRAM-ID. INTCALC.
AUTHOR. EMBER-NATIONAL-IT.
DATE-WRITTEN. 1999-01-15.
*================================================*
* INTEREST-CALC - Interest Calculation Engine
* Computes daily accrued interest for savings,
* money market, and CD accounts. Applies tiered
* rate schedules and handles compounding.
*================================================*
DATA DIVISION.
FILE SECTION.
FD RATE-TABLE.
01 RATE-TABLE-RECORD.
05 RT-ACCT-TYPE PIC X(2).
05 RT-TIER-MIN PIC S9(11)V99.
05 RT-TIER-MAX PIC S9(11)V99.
05 RT-ANNUAL-RATE PIC S9(3)V9(6).
05 RT-COMPOUNDING PIC X(1).
88 RT-DAILY VALUE 'D'.
88 RT-MONTHLY VALUE 'M'.
88 RT-QUARTERLY VALUE 'Q'.
PROCEDURE DIVISION.
CALCULATE-INTEREST.
PERFORM LOOKUP-RATE-TIER
IF NOT RATE-FOUND
MOVE 'E007' TO WS-ERROR-CODE
EXIT PARAGRAPH
END-IF
PERFORM COMPUTE-DAILY-INTEREST
IF WS-INTEREST-AMT < WS-ZERO-FLOOR
MOVE ZEROS TO WS-INTEREST-AMT
END-IF.
COMPUTE-DAILY-INTEREST.
EVALUATE TRUE
WHEN RT-DAILY
COMPUTE WS-DAILY-RATE =
RT-ANNUAL-RATE / WS-DAYS-IN-YEAR
COMPUTE WS-INTEREST-AMT =
ACCT-BALANCE * WS-DAILY-RATE
WHEN RT-MONTHLY
COMPUTE WS-DAILY-RATE =
RT-ANNUAL-RATE / 12 / 30
COMPUTE WS-INTEREST-AMT =
ACCT-BALANCE * WS-DAILY-RATE
WHEN RT-QUARTERLY
COMPUTE WS-DAILY-RATE =
RT-ANNUAL-RATE / 4 / 90
COMPUTE WS-INTEREST-AMT =
ACCT-BALANCE * WS-DAILY-RATE
END-EVALUATE.
APPLY-INTEREST-CREDIT.
ADD WS-INTEREST-AMT TO ACCT-BALANCE
ADD WS-INTEREST-AMT TO ACCT-AVAIL-BALANCE
ADD WS-INTEREST-AMT TO WS-TOTAL-INTEREST
REWRITE ACCOUNT-RECORD.
CALC-CD-EARLY-WITHDRAWAL.
COMPUTE WS-PENALTY-AMOUNT =
ACCT-BALANCE * WS-CD-PENALTY-RATE
SUBTRACT WS-PENALTY-AMOUNT FROM ACCT-BALANCE
SUBTRACT WS-PENALTY-AMOUNT FROM ACCT-AVAIL-BALANCE
REWRITE ACCOUNT-RECORD.
"""
Interest Calculation Engine
Originally: INTCALC, COBOL, 1999-01-15
Translated by CausalWave RootCause pipeline
"""
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from enum import Enum
from typing import Optional
class AccountType(Enum):
SAVINGS = "SV"
MONEY_MARKET = "MM"
CD = "CD"
class CompoundingMethod(Enum):
DAILY = "D"
MONTHLY = "M"
QUARTERLY = "Q"
@dataclass
class RateTier:
account_type: AccountType
tier_min: Decimal
tier_max: Decimal
annual_rate: Decimal
compounding: CompoundingMethod
class InterestCalculator:
DAYS_IN_YEAR = Decimal("365")
CD_PENALTY_RATE = Decimal("0.100000")
def __init__(self, rate_table: list[RateTier]):
self.rate_table = rate_table
self.total_interest = Decimal("0.00")
def _lookup_rate_tier(self, account) -> Optional[RateTier]:
for tier in self.rate_table:
if (tier.account_type == account.account_type
and tier.tier_min <= account.balance <= tier.tier_max):
return tier
return None
def _compute_daily_interest(self, balance, tier):
match tier.compounding:
case CompoundingMethod.DAILY:
daily_rate = tier.annual_rate / self.DAYS_IN_YEAR
case CompoundingMethod.MONTHLY:
daily_rate = tier.annual_rate / 12 / 30
case CompoundingMethod.QUARTERLY:
daily_rate = tier.annual_rate / 4 / 90
return (balance * daily_rate).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP)
def calculate(self, account):
tier = self._lookup_rate_tier(account)
if tier is None:
raise ValueError(f"E007: No rate tier for {account.account_number}")
interest = self._compute_daily_interest(account.balance, tier)
# Zero floor (2021 patch)
if interest < Decimal("0"):
interest = Decimal("0.00")
account.balance += interest
account.available_balance += interest
self.total_interest += interest
return interest
def cd_early_withdrawal(self, account):
penalty = (account.balance * self.CD_PENALTY_RATE).quantize(
Decimal("0.01"), rounding=ROUND_HALF_UP)
account.balance -= penalty
account.available_balance -= penalty
return penalty
// Interest Calculation Engine
// Originally: INTCALC, COBOL, 1999-01-15
// Translated by CausalWave RootCause pipeline
using System;
using System.Collections.Generic;
using System.Linq;
namespace EmberNationalBank.Interest
{
public enum AccountType { Savings, MoneyMarket, CD }
public enum CompoundingMethod { Daily, Monthly, Quarterly }
public record RateTier(
AccountType AccountType,
decimal TierMin, decimal TierMax,
decimal AnnualRate,
CompoundingMethod Compounding);
public class InterestCalculator
{
private const decimal DaysInYear = 365m;
private const decimal CdPenaltyRate = 0.100000m;
private readonly List<RateTier> _rateTable;
public decimal TotalInterest { get; private set; }
public InterestCalculator(List<RateTier> rateTable)
=> _rateTable = rateTable;
private RateTier? LookupRateTier(Account acct)
=> _rateTable.FirstOrDefault(t =>
t.AccountType == acct.AccountType
&& acct.Balance >= t.TierMin
&& acct.Balance <= t.TierMax);
private static decimal ComputeDailyInterest(
decimal balance, RateTier tier)
{
decimal dailyRate = tier.Compounding switch
{
CompoundingMethod.Daily => tier.AnnualRate / DaysInYear,
CompoundingMethod.Monthly => tier.AnnualRate / 12m / 30m,
CompoundingMethod.Quarterly => tier.AnnualRate / 4m / 90m,
_ => throw new InvalidOperationException()
};
return Math.Round(balance * dailyRate, 2,
MidpointRounding.AwayFromZero);
}
public decimal Calculate(Account account)
{
var tier = LookupRateTier(account)
?? throw new InvalidOperationException(
$"E007: No rate tier for {account.AccountNumber}");
var interest = ComputeDailyInterest(account.Balance, tier);
// Zero floor (2021 patch)
if (interest < 0m) interest = 0m;
account.Balance += interest;
account.AvailableBalance += interest;
TotalInterest += interest;
return interest;
}
public decimal CdEarlyWithdrawal(Account account)
{
var penalty = Math.Round(
account.Balance * CdPenaltyRate, 2);
account.Balance -= penalty;
account.AvailableBalance -= penalty;
return penalty;
}
}
}
This is one example. The same pipeline handles any legacy system.
Translations are validated and scoped based on the target runtime, dependencies, and test harness available. Results vary by system complexity.
Tell us what you're dealing with. We'll tell you what we can do about it.
Talk to an Engineer