Initial implementation: Brazil SWIFT Operations Platform

- Complete monorepo structure with pnpm workspaces and Turborepo
- All packages implemented: types, utils, rules-engine, iso20022, treasury, risk-models, audit
- React web application with TypeScript and Tailwind CSS
- Full Brazil regulatory compliance (BCB requirements)
- ISO 20022 message support (pacs.008, pacs.009, pain.001)
- Treasury and subledger management
- Risk, capital, and liquidity stress allocation
- Audit logging and BCB reporting
- E&O +10% uplift implementation
This commit is contained in:
defiQUG
2026-01-23 14:51:10 -08:00
parent 41ef0ead04
commit 8c771da399
67 changed files with 4930 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
/**
* Currency conversion and USD equivalent calculation utilities
*/
import Decimal from 'decimal.js';
export interface ExchangeRate {
fromCurrency: string;
toCurrency: string;
rate: number;
effectiveDate: Date;
source?: string;
}
export interface CurrencyConverter {
convert(amount: number, fromCurrency: string, toCurrency: string, date?: Date): number;
getUSDEquivalent(amount: number, currency: string, date?: Date): number;
getRate(fromCurrency: string, toCurrency: string, date?: Date): number | null;
}
/**
* Simple in-memory currency converter with configurable rates
* In production, this would integrate with a real-time FX rate service
*/
export class SimpleCurrencyConverter implements CurrencyConverter {
private rates: Map<string, ExchangeRate> = new Map();
private defaultUSDRates: Record<string, number> = {
USD: 1.0,
BRL: 0.2,
EUR: 1.1,
GBP: 1.27,
};
constructor(initialRates?: ExchangeRate[]) {
if (initialRates) {
initialRates.forEach((rate) => this.addRate(rate));
}
this.initializeDefaultRates();
}
private initializeDefaultRates(): void {
const now = new Date();
Object.entries(this.defaultUSDRates).forEach(([currency, rate]) => {
if (currency !== 'USD') {
this.addRate({
fromCurrency: currency,
toCurrency: 'USD',
rate,
effectiveDate: now,
source: 'default',
});
}
});
}
addRate(rate: ExchangeRate): void {
const key = this.getRateKey(rate.fromCurrency, rate.toCurrency);
this.rates.set(key, rate);
const inverseKey = this.getRateKey(rate.toCurrency, rate.fromCurrency);
this.rates.set(inverseKey, {
...rate,
fromCurrency: rate.toCurrency,
toCurrency: rate.fromCurrency,
rate: 1 / rate.rate,
});
}
private getRateKey(fromCurrency: string, toCurrency: string): string {
return `${fromCurrency}:${toCurrency}`;
}
getRate(fromCurrency: string, toCurrency: string, date?: Date): number | null {
if (fromCurrency === toCurrency) {
return 1.0;
}
const key = this.getRateKey(fromCurrency, toCurrency);
const rate = this.rates.get(key);
if (!rate) {
if (fromCurrency !== 'USD' && toCurrency !== 'USD') {
const fromToUSD = this.getRate(fromCurrency, 'USD', date);
const usdToTo = this.getRate('USD', toCurrency, date);
if (fromToUSD && usdToTo) {
return fromToUSD * usdToTo;
}
}
return null;
}
return rate.rate;
}
convert(amount: number, fromCurrency: string, toCurrency: string, date?: Date): number {
if (fromCurrency === toCurrency) {
return amount;
}
const rate = this.getRate(fromCurrency, toCurrency, date);
if (rate === null) {
throw new Error(
`No exchange rate available for ${fromCurrency} to ${toCurrency}`
);
}
const amountDecimal = new Decimal(amount);
const rateDecimal = new Decimal(rate);
return amountDecimal.mul(rateDecimal).toNumber();
}
getUSDEquivalent(amount: number, currency: string, date?: Date): number {
if (currency === 'USD') {
return amount;
}
return this.convert(amount, currency, 'USD', date);
}
}
let defaultConverter: CurrencyConverter | null = null;
export function getDefaultConverter(): CurrencyConverter {
if (!defaultConverter) {
defaultConverter = new SimpleCurrencyConverter();
}
return defaultConverter;
}
export function setDefaultConverter(converter: CurrencyConverter): void {
defaultConverter = converter;
}

View File

@@ -0,0 +1,83 @@
/**
* Date utilities for effective date logic and rolling windows
*/
import { addDays, isAfter, isBefore, isWithinInterval, subDays } from 'date-fns';
export function isEffectiveDate(date: Date, effectiveDate: Date, expiryDate?: Date): boolean {
if (isBefore(date, effectiveDate)) {
return false;
}
if (expiryDate && isAfter(date, expiryDate)) {
return false;
}
return true;
}
export interface RollingWindow {
startDate: Date;
endDate: Date;
days: number;
}
export function calculateRollingWindow(
referenceDate: Date,
windowDays: number
): RollingWindow {
const startDate = subDays(referenceDate, windowDays);
return {
startDate,
endDate: referenceDate,
days: windowDays,
};
}
export function isWithinRollingWindow(
date: Date,
window: RollingWindow
): boolean {
return isWithinInterval(date, {
start: window.startDate,
end: window.endDate,
});
}
export function filterDatesInWindow(
dates: Date[],
window: RollingWindow
): Date[] {
return dates.filter((date) => isWithinRollingWindow(date, window));
}
export function calculateRetentionExpiry(
creationDate: Date,
retentionDays: number
): Date {
return addDays(creationDate, retentionDays);
}
export function shouldArchive(
creationDate: Date,
archivalAfterDays: number,
currentDate: Date = new Date()
): boolean {
const archivalDate = addDays(creationDate, archivalAfterDays);
return isAfter(currentDate, archivalDate);
}
export function shouldDelete(
creationDate: Date,
retentionDays: number,
currentDate: Date = new Date()
): boolean {
const expiryDate = calculateRetentionExpiry(creationDate, retentionDays);
return isAfter(currentDate, expiryDate);
}
export function formatISO20022Date(date: Date): string {
return date.toISOString().split('T')[0];
}
export function formatISO20022DateTime(date: Date): string {
return date.toISOString().split('.')[0];
}

View File

@@ -0,0 +1,128 @@
/**
* Errors & Omissions (E&O) Uplift calculator
*
* E&O uplift is a +10% exposure buffer applied to transaction amounts
* to account for errors and omissions outside of direct operations.
*
* Treatment: Off-balance-sheet, non-booked risk buffer
*/
import Decimal from 'decimal.js';
import type { EOUplift, TransactionEOUplift, BatchEOUplift } from '@brazil-swift-ops/types';
export const DEFAULT_EO_UPLIFT_RATE = 0.10; // 10%
/**
* Calculate E&O uplift for a single transaction
*/
export function calculateTransactionEOUplift(
baseAmount: number,
currency: string,
upliftRate: number = DEFAULT_EO_UPLIFT_RATE,
usdEquivalent?: number
): TransactionEOUplift {
// Use Decimal.js for precise calculations
const baseDecimal = new Decimal(baseAmount);
const rateDecimal = new Decimal(upliftRate);
const upliftDecimal = baseDecimal.mul(rateDecimal);
const adjustedDecimal = baseDecimal.add(upliftDecimal);
const upliftAmount = upliftDecimal.toNumber();
const adjustedExposure = adjustedDecimal.toNumber();
// Calculate USD equivalent for uplift if provided
let upliftUsdEquivalent: number | undefined;
if (usdEquivalent !== undefined) {
const usdBaseDecimal = new Decimal(usdEquivalent);
const usdUpliftDecimal = usdBaseDecimal.mul(rateDecimal);
upliftUsdEquivalent = usdBaseDecimal.add(usdUpliftDecimal).toNumber();
}
return {
transactionId: '', // Will be set by caller
baseAmount,
currency,
upliftRate,
upliftAmount,
adjustedExposure,
usdEquivalent: upliftUsdEquivalent,
treatment: 'off_balance_sheet',
};
}
/**
* Calculate E&O uplift for a batch of transactions
*/
export function calculateBatchEOUplift(
baseAmount: number,
currency: string,
transactionCount: number,
upliftRate: number = DEFAULT_EO_UPLIFT_RATE,
usdEquivalent?: number
): BatchEOUplift {
const baseDecimal = new Decimal(baseAmount);
const rateDecimal = new Decimal(upliftRate);
const upliftDecimal = baseDecimal.mul(rateDecimal);
const adjustedDecimal = baseDecimal.add(upliftDecimal);
const upliftAmount = upliftDecimal.toNumber();
const adjustedExposure = adjustedDecimal.toNumber();
// Calculate USD equivalent for uplift if provided
let upliftUsdEquivalent: number | undefined;
if (usdEquivalent !== undefined) {
const usdBaseDecimal = new Decimal(usdEquivalent);
const usdUpliftDecimal = usdBaseDecimal.mul(rateDecimal);
upliftUsdEquivalent = usdBaseDecimal.add(usdUpliftDecimal).toNumber();
}
return {
batchId: '', // Will be set by caller
baseAmount,
currency,
transactionCount,
upliftRate,
upliftAmount,
adjustedExposure,
usdEquivalent: upliftUsdEquivalent,
treatment: 'off_balance_sheet',
};
}
/**
* Calculate E&O uplift for a simple amount (no transaction context)
*/
export function calculateEOUplift(
baseAmount: number,
upliftRate: number = DEFAULT_EO_UPLIFT_RATE
): EOUplift {
const baseDecimal = new Decimal(baseAmount);
const rateDecimal = new Decimal(upliftRate);
const upliftDecimal = baseDecimal.mul(rateDecimal);
const adjustedDecimal = baseDecimal.add(upliftDecimal);
return {
baseAmount,
upliftRate,
upliftAmount: upliftDecimal.toNumber(),
adjustedExposure: adjustedDecimal.toNumber(),
treatment: 'off_balance_sheet',
};
}
/**
* Apply E&O uplift to an array of transaction amounts
*/
export function applyEOUpliftToAmounts(
amounts: number[],
upliftRate: number = DEFAULT_EO_UPLIFT_RATE
): { baseAmount: number; upliftAmount: number; adjustedExposure: number }[] {
return amounts.map((amount) => {
const uplift = calculateEOUplift(amount, upliftRate);
return {
baseAmount: uplift.baseAmount,
upliftAmount: uplift.upliftAmount,
adjustedExposure: uplift.adjustedExposure,
};
});
}

View File

@@ -0,0 +1,10 @@
/**
* @brazil-swift-ops/utils
*
* Shared utilities for the Brazil SWIFT Operations Platform
*/
export * from './currency';
export * from './dates';
export * from './validation';
export * from './eo-uplift';

View File

@@ -0,0 +1,151 @@
/**
* Brazilian ID validation utilities (CPF/CNPJ)
*/
/**
* Validate CPF (Cadastro de Pessoa Física) format and checksum
* CPF format: XXX.XXX.XXX-XX (11 digits)
*/
export function validateCPF(cpf: string): { valid: boolean; formatted?: string; error?: string } {
// Remove non-numeric characters
const cleaned = cpf.replace(/\D/g, '');
// Check length
if (cleaned.length !== 11) {
return { valid: false, error: 'CPF must have 11 digits' };
}
// Check for invalid patterns (all same digits)
if (/^(\d)\1{10}$/.test(cleaned)) {
return { valid: false, error: 'CPF cannot have all same digits' };
}
// Validate checksum digits
let sum = 0;
let remainder: number;
// Validate first check digit
for (let i = 1; i <= 9; i++) {
sum += parseInt(cleaned.substring(i - 1, i)) * (11 - i);
}
remainder = (sum * 10) % 11;
if (remainder === 10 || remainder === 11) remainder = 0;
if (remainder !== parseInt(cleaned.substring(9, 10))) {
return { valid: false, error: 'Invalid CPF checksum (first digit)' };
}
// Validate second check digit
sum = 0;
for (let i = 1; i <= 10; i++) {
sum += parseInt(cleaned.substring(i - 1, i)) * (12 - i);
}
remainder = (sum * 10) % 11;
if (remainder === 10 || remainder === 11) remainder = 0;
if (remainder !== parseInt(cleaned.substring(10, 11))) {
return { valid: false, error: 'Invalid CPF checksum (second digit)' };
}
// Format CPF
const formatted = `${cleaned.substring(0, 3)}.${cleaned.substring(3, 6)}.${cleaned.substring(6, 9)}-${cleaned.substring(9, 11)}`;
return { valid: true, formatted };
}
/**
* Validate CNPJ (Cadastro Nacional da Pessoa Jurídica) format and checksum
* CNPJ format: XX.XXX.XXX/XXXX-XX (14 digits)
*/
export function validateCNPJ(cnpj: string): { valid: boolean; formatted?: string; error?: string } {
// Remove non-numeric characters
const cleaned = cnpj.replace(/\D/g, '');
// Check length
if (cleaned.length !== 14) {
return { valid: false, error: 'CNPJ must have 14 digits' };
}
// Check for invalid patterns (all same digits)
if (/^(\d)\1{13}$/.test(cleaned)) {
return { valid: false, error: 'CNPJ cannot have all same digits' };
}
// Validate checksum digits
let length = cleaned.length - 2;
let numbers = cleaned.substring(0, length);
const digits = cleaned.substring(length);
let sum = 0;
let pos = length - 7;
// Validate first check digit
for (let i = length; i >= 1; i--) {
sum += parseInt(numbers.charAt(length - i)) * pos--;
if (pos < 2) pos = 9;
}
let result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
if (result !== parseInt(digits.charAt(0))) {
return { valid: false, error: 'Invalid CNPJ checksum (first digit)' };
}
// Validate second check digit
length = length + 1;
numbers = cleaned.substring(0, length);
sum = 0;
pos = length - 7;
for (let i = length; i >= 1; i--) {
sum += parseInt(numbers.charAt(length - i)) * pos--;
if (pos < 2) pos = 9;
}
result = sum % 11 < 2 ? 0 : 11 - (sum % 11);
if (result !== parseInt(digits.charAt(1))) {
return { valid: false, error: 'Invalid CNPJ checksum (second digit)' };
}
// Format CNPJ
const formatted = `${cleaned.substring(0, 2)}.${cleaned.substring(2, 5)}.${cleaned.substring(5, 8)}/${cleaned.substring(8, 12)}-${cleaned.substring(12, 14)}`;
return { valid: true, formatted };
}
/**
* Validate Brazilian tax ID (CPF or CNPJ)
*/
export function validateBrazilianTaxId(taxId: string): {
valid: boolean;
type?: 'CPF' | 'CNPJ';
formatted?: string;
error?: string;
} {
const cleaned = taxId.replace(/\D/g, '');
if (cleaned.length === 11) {
const cpfResult = validateCPF(taxId);
return {
...cpfResult,
type: cpfResult.valid ? 'CPF' : undefined,
};
} else if (cleaned.length === 14) {
const cnpjResult = validateCNPJ(taxId);
return {
...cnpjResult,
type: cnpjResult.valid ? 'CNPJ' : undefined,
};
}
return {
valid: false,
error: 'Tax ID must be 11 digits (CPF) or 14 digits (CNPJ)',
};
}
/**
* Format tax ID for display (CPF or CNPJ)
*/
export function formatTaxId(taxId: string): string {
const cleaned = taxId.replace(/\D/g, '');
if (cleaned.length === 11) {
return `${cleaned.substring(0, 3)}.${cleaned.substring(3, 6)}.${cleaned.substring(6, 9)}-${cleaned.substring(9, 11)}`;
} else if (cleaned.length === 14) {
return `${cleaned.substring(0, 2)}.${cleaned.substring(2, 5)}.${cleaned.substring(5, 8)}/${cleaned.substring(8, 12)}-${cleaned.substring(12, 14)}`;
}
return taxId;
}