diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..5b15d5c
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,22 @@
+module.exports = {
+ root: true,
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react/recommended',
+ 'plugin:react-hooks/recommended'
+ ],
+ parser: '@typescript-eslint/parser',
+ plugins: ['@typescript-eslint', 'react', 'react-hooks'],
+ settings: {
+ react: {
+ version: 'detect'
+ }
+ },
+ rules: {
+ 'react/react-in-jsx-scope': 'off',
+ '@typescript-eslint/no-explicit-any': 'warn',
+ '@typescript-eslint/explicit-module-boundary-types': 'off'
+ },
+ ignorePatterns: ['dist', 'build', 'node_modules', '*.config.js']
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..22aa366
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,38 @@
+# Dependencies
+node_modules
+.pnp
+.pnp.js
+
+# Testing
+coverage
+*.log
+
+# Production
+dist
+build
+.next
+out
+
+# Misc
+.DS_Store
+*.pem
+.env*.local
+
+# Debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Turbo
+.turbo
+
+# IDE
+.vscode
+.idea
+*.swp
+*.swo
+*~
+
+# TypeScript
+*.tsbuildinfo
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..29b9d1f
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": true,
+ "printWidth": 100,
+ "tabWidth": 2,
+ "useTabs": false
+}
diff --git a/apps/web/index.html b/apps/web/index.html
new file mode 100644
index 0000000..ca854ab
--- /dev/null
+++ b/apps/web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Brazil SWIFT Operations Platform
+
+
+
+
+
+
diff --git a/apps/web/package.json b/apps/web/package.json
new file mode 100644
index 0000000..396b518
--- /dev/null
+++ b/apps/web/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@brazil-swift-ops/web",
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "@brazil-swift-ops/utils": "workspace:*",
+ "@brazil-swift-ops/rules-engine": "workspace:*",
+ "@brazil-swift-ops/iso20022": "workspace:*",
+ "@brazil-swift-ops/treasury": "workspace:*",
+ "@brazil-swift-ops/risk-models": "workspace:*",
+ "@brazil-swift-ops/audit": "workspace:*",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.20.0",
+ "zustand": "^4.4.7"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.43",
+ "@types/react-dom": "^18.2.17",
+ "@vitejs/plugin-react": "^4.2.1",
+ "autoprefixer": "^10.4.16",
+ "postcss": "^8.4.32",
+ "tailwindcss": "^3.3.6",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.8"
+ }
+}
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
new file mode 100644
index 0000000..a9e014e
--- /dev/null
+++ b/apps/web/src/App.tsx
@@ -0,0 +1,64 @@
+import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
+import DashboardPage from './pages/DashboardPage';
+import TransactionsPage from './pages/TransactionsPage';
+import TreasuryPage from './pages/TreasuryPage';
+import ReportsPage from './pages/ReportsPage';
+
+function App() {
+ return (
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/apps/web/src/index.css b/apps/web/src/index.css
new file mode 100644
index 0000000..634141e
--- /dev/null
+++ b/apps/web/src/index.css
@@ -0,0 +1,12 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
new file mode 100644
index 0000000..2339d59
--- /dev/null
+++ b/apps/web/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './index.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/apps/web/src/pages/DashboardPage.tsx b/apps/web/src/pages/DashboardPage.tsx
new file mode 100644
index 0000000..36bc523
--- /dev/null
+++ b/apps/web/src/pages/DashboardPage.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+export default function DashboardPage() {
+ return (
+
+
+
Dashboard
+
Brazil SWIFT Operations Platform
+
+
+ );
+}
diff --git a/apps/web/src/pages/ReportsPage.tsx b/apps/web/src/pages/ReportsPage.tsx
new file mode 100644
index 0000000..9549c34
--- /dev/null
+++ b/apps/web/src/pages/ReportsPage.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+
+export default function ReportsPage() {
+ return (
+
+
ReportsPage
+
ReportsPage interface
+
+ );
+}
diff --git a/apps/web/src/pages/TransactionsPage.tsx b/apps/web/src/pages/TransactionsPage.tsx
new file mode 100644
index 0000000..2771e0c
--- /dev/null
+++ b/apps/web/src/pages/TransactionsPage.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+
+export default function TransactionsPage() {
+ return (
+
+
TransactionsPage
+
TransactionsPage interface
+
+ );
+}
diff --git a/apps/web/src/pages/TreasuryPage.tsx b/apps/web/src/pages/TreasuryPage.tsx
new file mode 100644
index 0000000..b1b3984
--- /dev/null
+++ b/apps/web/src/pages/TreasuryPage.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+
+export default function TreasuryPage() {
+ return (
+
+
TreasuryPage
+
TreasuryPage interface
+
+ );
+}
diff --git a/apps/web/src/stores/transactionStore.ts b/apps/web/src/stores/transactionStore.ts
new file mode 100644
index 0000000..76a346a
--- /dev/null
+++ b/apps/web/src/stores/transactionStore.ts
@@ -0,0 +1,29 @@
+import { create } from 'zustand';
+import type { Transaction, BrazilRegulatoryResult } from '@brazil-swift-ops/types';
+import { evaluateTransaction, evaluateBatch } from '@brazil-swift-ops/rules-engine';
+
+interface TransactionStore {
+ transactions: Transaction[];
+ results: Map;
+ addTransaction: (txn: Transaction) => void;
+ evaluateTransaction: (txn: Transaction) => BrazilRegulatoryResult;
+}
+
+export const useTransactionStore = create((set) => ({
+ transactions: [],
+ results: new Map(),
+ addTransaction: (txn) => {
+ const result = evaluateTransaction(txn);
+ set((state) => ({
+ transactions: [...state.transactions, txn],
+ results: new Map(state.results).set(txn.id, result),
+ }));
+ },
+ evaluateTransaction: (txn) => {
+ const result = evaluateTransaction(txn);
+ set((state) => ({
+ results: new Map(state.results).set(txn.id, result),
+ }));
+ return result;
+ },
+}));
diff --git a/create_web_files.sh b/create_web_files.sh
new file mode 100755
index 0000000..4288237
--- /dev/null
+++ b/create_web_files.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# Create essential web app files
+
+cd apps/web
+
+# Create stores
+cat > src/stores/transactionStore.ts << 'EOFTXNSTORE'
+import { create } from 'zustand';
+import type { Transaction, BrazilRegulatoryResult, BatchTransaction } from '@brazil-swift-ops/types';
+import { evaluateTransaction, evaluateBatch } from '@brazil-swift-ops/rules-engine';
+import { calculateTransactionEOUplift, calculateBatchEOUplift } from '@brazil-swift-ops/utils';
+import { getDefaultConverter } from '@brazil-swift-ops/utils';
+
+interface TransactionStore {
+ transactions: Transaction[];
+ results: Map;
+ batches: BatchTransaction[];
+ addTransaction: (txn: Transaction) => void;
+ evaluateTransaction: (txn: Transaction) => BrazilRegulatoryResult;
+ addBatch: (batch: BatchTransaction) => void;
+ evaluateBatch: (txns: Transaction[]) => BrazilRegulatoryResult[];
+}
+
+export const useTransactionStore = create((set, get) => ({
+ transactions: [],
+ results: new Map(),
+ batches: [],
+ addTransaction: (txn) => {
+ const result = evaluateTransaction(txn);
+ set((state) => ({
+ transactions: [...state.transactions, txn],
+ results: new Map(state.results).set(txn.id, result),
+ }));
+ },
+ evaluateTransaction: (txn) => {
+ const result = evaluateTransaction(txn);
+ set((state) => ({
+ results: new Map(state.results).set(txn.id, result),
+ }));
+ return result;
+ },
+ addBatch: (batch) => {
+ set((state) => ({
+ batches: [...state.batches, batch],
+ }));
+ },
+ evaluateBatch: (txns) => {
+ return evaluateBatch(txns);
+ },
+}));
+EOFTXNSTORE
+
+# Create a simple page component
+cat > src/pages/DashboardPage.tsx << 'EOFDASH'
+import React from 'react';
+
+export default function DashboardPage() {
+ return (
+
+
+
Dashboard
+
Brazil SWIFT Operations Platform
+
+
+ );
+}
+EOFDASH
+
+cat > src/pages/TransactionsPage.tsx << 'EOFTXNPAGE'
+import React from 'react';
+
+export default function TransactionsPage() {
+ return (
+
+
Transactions
+
Transaction processing interface
+
+ );
+}
+EOFTXNPAGE
+
+cat > src/pages/TreasuryPage.tsx << 'EOFTREAS'
+import React from 'react';
+
+export default function TreasuryPage() {
+ return (
+
+
Treasury Management
+
Treasury and subledger management
+
+ );
+}
+EOFTREAS
+
+cat > src/pages/ReportsPage.tsx << 'EOFREP'
+import React from 'react';
+
+export default function ReportsPage() {
+ return (
+
+
Reports
+
Compliance and regulatory reports
+
+ );
+}
+EOFREP
+
+chmod +x create_web_files.sh
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1902c3d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "brazil-swift-ops",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Brazil SWIFT Operations Platform - Regulator-Grade Implementation",
+ "scripts": {
+ "dev": "turbo run dev",
+ "build": "turbo run build",
+ "test": "turbo run test",
+ "lint": "turbo run lint",
+ "type-check": "turbo run type-check",
+ "clean": "turbo run clean && rm -rf node_modules"
+ },
+ "devDependencies": {
+ "@types/node": "^20.10.0",
+ "turbo": "^1.11.0",
+ "typescript": "^5.3.3"
+ },
+ "packageManager": "pnpm@8.12.0",
+ "engines": {
+ "node": ">=18.0.0",
+ "pnpm": ">=8.0.0"
+ }
+}
diff --git a/packages/audit/package.json b/packages/audit/package.json
new file mode 100644
index 0000000..a957411
--- /dev/null
+++ b/packages/audit/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@brazil-swift-ops/audit",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "@brazil-swift-ops/utils": "workspace:*"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/audit/src/index.ts b/packages/audit/src/index.ts
new file mode 100644
index 0000000..d60b096
--- /dev/null
+++ b/packages/audit/src/index.ts
@@ -0,0 +1,4 @@
+export * from './logger';
+export * from './reports';
+export * from './retention';
+export * from './versions';
diff --git a/packages/audit/src/retention.ts b/packages/audit/src/retention.ts
new file mode 100644
index 0000000..c4fa3a0
--- /dev/null
+++ b/packages/audit/src/retention.ts
@@ -0,0 +1,67 @@
+import type { RetentionPolicy, AuditLog } from '@brazil-swift-ops/types';
+import { shouldArchive, shouldDelete } from '@brazil-swift-ops/utils';
+import { getAuditLogStore } from './logger';
+
+class RetentionPolicyStore {
+ private policies: RetentionPolicy[] = [];
+
+ add(policy: RetentionPolicy): void {
+ this.policies.push(policy);
+ }
+
+ get(dataType: RetentionPolicy['dataType']): RetentionPolicy | undefined {
+ return this.policies.find((p) => p.dataType === dataType);
+ }
+
+ getAll(): RetentionPolicy[] {
+ return [...this.policies];
+ }
+}
+
+const retentionPolicyStore = new RetentionPolicyStore();
+
+export function getRetentionPolicyStore(): RetentionPolicyStore {
+ return retentionPolicyStore;
+}
+
+export function applyRetentionPolicy(
+ log: AuditLog,
+ policy: RetentionPolicy
+): { shouldArchive: boolean; shouldDelete: boolean } {
+ if (policy.archivalAfterDays) {
+ const archive = shouldArchive(log.timestamp, policy.archivalAfterDays);
+ if (archive) {
+ return { shouldArchive: true, shouldDelete: false };
+ }
+ }
+
+ if (policy.autoDelete) {
+ const deleteLog = shouldDelete(log.timestamp, policy.retentionPeriodDays);
+ return { shouldArchive: false, shouldDelete: deleteLog };
+ }
+
+ return { shouldArchive: false, shouldDelete: false };
+}
+
+export function enforceRetentionPolicies(): void {
+ const auditLogStore = getAuditLogStore();
+ const policyStore = getRetentionPolicyStore();
+
+ const allPolicies = policyStore.getAll();
+ const allLogs = auditLogStore.getAll();
+
+ allLogs.forEach((log) => {
+ const policy = allPolicies.find(
+ (p) => p.dataType === 'audit_log' || p.dataType === 'all'
+ );
+
+ if (policy) {
+ const { shouldDelete: shouldDeleteLog } = applyRetentionPolicy(log, policy);
+ if (shouldDeleteLog && policy.autoDelete) {
+ // In production, would actually delete from persistent storage
+ // For now, just mark for deletion
+ console.log(`Log ${log.id} should be deleted per retention policy`);
+ }
+ }
+ });
+}
diff --git a/packages/audit/src/versions.ts b/packages/audit/src/versions.ts
new file mode 100644
index 0000000..5a9e3bf
--- /dev/null
+++ b/packages/audit/src/versions.ts
@@ -0,0 +1,55 @@
+import type { RuleVersion } from '@brazil-swift-ops/types';
+
+class RuleVersionStore {
+ private versions: RuleVersion[] = [];
+
+ add(version: RuleVersion): void {
+ this.versions.push(version);
+ }
+
+ get(version: string): RuleVersion | undefined {
+ return this.versions.find((v) => v.version === version);
+ }
+
+ getCurrent(): RuleVersion | undefined {
+ return this.versions
+ .filter((v) => !v.deprecated)
+ .sort((a, b) => b.effectiveDate.getTime() - a.effectiveDate.getTime())[0];
+ }
+
+ getAll(): RuleVersion[] {
+ return [...this.versions];
+ }
+
+ deprecate(version: string, deprecatedDate: Date): void {
+ const versionObj = this.versions.find((v) => v.version === version);
+ if (versionObj) {
+ versionObj.deprecated = true;
+ versionObj.deprecatedDate = deprecatedDate;
+ }
+ }
+}
+
+const ruleVersionStore = new RuleVersionStore();
+
+export function getRuleVersionStore(): RuleVersionStore {
+ return ruleVersionStore;
+}
+
+export function createRuleVersion(
+ version: string,
+ effectiveDate: Date,
+ approvalAuthority: string,
+ description?: string
+): RuleVersion {
+ const ruleVersion: RuleVersion = {
+ version,
+ effectiveDate,
+ approvalAuthority,
+ deprecated: false,
+ description,
+ };
+
+ ruleVersionStore.add(ruleVersion);
+ return ruleVersion;
+}
diff --git a/packages/audit/tsconfig.json b/packages/audit/tsconfig.json
new file mode 100644
index 0000000..5c8037b
--- /dev/null
+++ b/packages/audit/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "composite": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"],
+ "references": [
+ {
+ "path": "../types"
+ },
+ {
+ "path": "../utils"
+ }
+ ]
+}
diff --git a/packages/iso20022/package.json b/packages/iso20022/package.json
new file mode 100644
index 0000000..92c36b8
--- /dev/null
+++ b/packages/iso20022/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@brazil-swift-ops/iso20022",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "@brazil-swift-ops/utils": "workspace:*"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/iso20022/src/exporter.ts b/packages/iso20022/src/exporter.ts
new file mode 100644
index 0000000..f6f9c42
--- /dev/null
+++ b/packages/iso20022/src/exporter.ts
@@ -0,0 +1,34 @@
+import type { ISO20022Message } from '@brazil-swift-ops/types';
+
+export function exportToJSON(message: ISO20022Message): string {
+ return JSON.stringify(message, null, 2);
+}
+
+export function exportToXML(message: ISO20022Message): string {
+ // Simplified XML export - in production would use proper XML serialization
+ const xml = `
+
+ <${message.messageType}>
+
+ ${message.groupHeader.messageIdentification}
+ ${message.groupHeader.creationDateTime.toISOString()}
+ ${message.groupHeader.numberOfTransactions}
+
+ ${message.messageType}>
+`;
+ return xml;
+}
+
+export function exportMessage(
+ message: ISO20022Message,
+ format: 'json' | 'xml' = 'json'
+): string {
+ switch (format) {
+ case 'json':
+ return exportToJSON(message);
+ case 'xml':
+ return exportToXML(message);
+ default:
+ throw new Error(`Unsupported export format: ${format}`);
+ }
+}
diff --git a/packages/iso20022/src/index.ts b/packages/iso20022/src/index.ts
new file mode 100644
index 0000000..0fa81a4
--- /dev/null
+++ b/packages/iso20022/src/index.ts
@@ -0,0 +1,5 @@
+export * from './pacs008';
+export * from './pacs009';
+export * from './pain001';
+export * from './mt-mapper';
+export * from './exporter';
diff --git a/packages/iso20022/src/mt-mapper.ts b/packages/iso20022/src/mt-mapper.ts
new file mode 100644
index 0000000..045b145
--- /dev/null
+++ b/packages/iso20022/src/mt-mapper.ts
@@ -0,0 +1,101 @@
+import type { Transaction, ISO20022Message } from '@brazil-swift-ops/types';
+import { createPacs008Message } from './pacs008';
+import { createPacs009Message } from './pacs009';
+import { createPain001Message } from './pain001';
+
+export interface MT103Message {
+ field20: string; // Sender's reference
+ field23B: string; // Bank operation code
+ field32A: string; // Value date, currency code, amount
+ field50A?: string; // Ordering customer
+ field52A?: string; // Ordering institution
+ field53A?: string; // Sender's correspondent
+ field54A?: string; // Receiver's correspondent
+ field56A?: string; // Intermediary
+ field57A?: string; // Account with institution
+ field59: string; // Beneficiary customer
+ field70: string; // Remittance information
+ field71A: string; // Details of charges
+ field72: string; // Sender to receiver information
+}
+
+export function mapMT103ToTransaction(mt103: MT103Message): Transaction {
+ const [valueDate, currency, amountStr] = mt103.field32A.split(/(\d{6})([A-Z]{3})([\d,]+)/).filter(Boolean);
+ const amount = parseFloat(amountStr.replace(',', '.'));
+
+ return {
+ id: mt103.field20,
+ direction: 'outbound',
+ amount,
+ currency: currency || 'USD',
+ orderingCustomer: {
+ name: mt103.field50A || '',
+ country: 'BR',
+ },
+ beneficiary: {
+ name: mt103.field59,
+ country: 'BR',
+ },
+ purposeOfPayment: mt103.field70,
+ swiftReference: mt103.field20,
+ status: 'pending',
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+}
+
+export function mapTransactionToMT103(transaction: Transaction): MT103Message {
+ const valueDate = new Date().toISOString().slice(0, 10).replace(/-/g, '');
+ const amountStr = transaction.amount.toFixed(2).replace('.', ',');
+
+ return {
+ field20: transaction.swiftReference || transaction.id,
+ field23B: 'CRED',
+ field32A: `${valueDate}${transaction.currency}${amountStr}`,
+ field50A: transaction.orderingCustomer.name,
+ field59: transaction.beneficiary.name,
+ field70: transaction.purposeOfPayment || '',
+ field71A: 'OUR',
+ field72: `//${transaction.fxContractId || ''}`,
+ };
+}
+
+export function convertMT103ToISO20022(
+ mt103: MT103Message,
+ targetType: 'pacs.008' | 'pacs.009' | 'pain.001' = 'pacs.008',
+ version?: string
+): ISO20022Message {
+ const transaction = mapMT103ToTransaction(mt103);
+
+ switch (targetType) {
+ case 'pacs.008':
+ return createPacs008Message(transaction, version);
+ case 'pacs.009':
+ return createPacs009Message(transaction, version);
+ case 'pain.001':
+ return createPain001Message(transaction, version);
+ }
+}
+
+export function convertISO20022ToMT103(message: ISO20022Message): MT103Message {
+ // This is a simplified conversion - in production would need full mapping
+ const transaction: Transaction = {
+ id: message.groupHeader.messageIdentification,
+ direction: 'outbound',
+ amount: message.groupHeader.controlSum || 0,
+ currency: 'USD',
+ orderingCustomer: {
+ name: message.groupHeader.initiatingParty.name || '',
+ country: 'BR',
+ },
+ beneficiary: {
+ name: '',
+ country: 'BR',
+ },
+ status: 'pending',
+ createdAt: message.creationDateTime,
+ updatedAt: new Date(),
+ };
+
+ return mapTransactionToMT103(transaction);
+}
diff --git a/packages/risk-models/package.json b/packages/risk-models/package.json
new file mode 100644
index 0000000..0942bbe
--- /dev/null
+++ b/packages/risk-models/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@brazil-swift-ops/risk-models",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "@brazil-swift-ops/utils": "workspace:*",
+ "decimal.js": "^10.4.3"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/risk-models/src/capital.ts b/packages/risk-models/src/capital.ts
new file mode 100644
index 0000000..8c481ae
--- /dev/null
+++ b/packages/risk-models/src/capital.ts
@@ -0,0 +1,42 @@
+import Decimal from 'decimal.js';
+import type { Transaction, CapitalImpact, RiskWeight } from '@brazil-swift-ops/types';
+
+export interface CapitalConfig {
+ capitalRatio: number;
+ capitalBuffer: number;
+ riskWeights: RiskWeight[];
+}
+
+export function getRiskWeight(transaction: Transaction, riskWeights: RiskWeight[]): number {
+ const paymentWeight = riskWeights.find((w) => w.category === 'payment');
+ return paymentWeight?.weight ?? 0.1;
+}
+
+export function calculateCapitalImpact(transaction: Transaction, config: CapitalConfig): CapitalImpact {
+ const riskWeight = getRiskWeight(transaction, config.riskWeights);
+ const transactionDecimal = new Decimal(transaction.amount);
+ const weightDecimal = new Decimal(riskWeight);
+ const rwaDecimal = transactionDecimal.mul(weightDecimal);
+ const riskWeightedAssets = rwaDecimal.toNumber();
+ const ratioDecimal = new Decimal(config.capitalRatio);
+ const capitalConsumedDecimal = rwaDecimal.mul(ratioDecimal);
+ const capitalConsumed = capitalConsumedDecimal.toNumber();
+ const capitalBufferAfter = config.capitalBuffer - capitalConsumed;
+ const complianceCheck = capitalBufferAfter >= 0;
+
+ return {
+ transactionId: transaction.id,
+ transactionAmount: transaction.amount,
+ currency: transaction.currency,
+ riskWeight,
+ riskWeightedAssets,
+ capitalRatio: config.capitalRatio,
+ capitalConsumed,
+ capitalBufferBefore: config.capitalBuffer,
+ capitalBufferAfter,
+ complianceCheck,
+ rationale: complianceCheck
+ ? `Capital consumed: ${capitalConsumed.toFixed(2)}. Capital buffer after transaction: ${capitalBufferAfter.toFixed(2)} (above minimum).`
+ : `Capital consumed: ${capitalConsumed.toFixed(2)}. Capital buffer after transaction: ${capitalBufferAfter.toFixed(2)} (below minimum). Transaction may be blocked.`,
+ };
+}
diff --git a/packages/risk-models/src/escalation.ts b/packages/risk-models/src/escalation.ts
new file mode 100644
index 0000000..b1aabdc
--- /dev/null
+++ b/packages/risk-models/src/escalation.ts
@@ -0,0 +1,66 @@
+import type {
+ EscalationState,
+ EscalationLevel,
+ RuleDecision,
+} from '@brazil-swift-ops/types';
+
+export function determineEscalationLevel(
+ decision: RuleDecision,
+ hasLiquidityIssue: boolean,
+ hasCapitalIssue: boolean,
+ hasLCRIssue: boolean
+): EscalationLevel {
+ if (decision === 'Allow') {
+ return 'Allow';
+ }
+
+ if (hasLiquidityIssue || hasCapitalIssue || hasLCRIssue) {
+ return 'TreasuryApproval';
+ }
+
+ if (decision === 'Escalate') {
+ return 'ComplianceReview';
+ }
+
+ return 'Hold';
+}
+
+export function createEscalationState(
+ transactionId: string,
+ level: EscalationLevel,
+ reason: string
+): EscalationState {
+ return {
+ transactionId,
+ currentLevel: level,
+ reason,
+ escalatedAt: new Date(),
+ approvalRequired: level !== 'Allow',
+ };
+}
+
+export function escalate(
+ currentState: EscalationState,
+ newLevel: EscalationLevel,
+ reason: string
+): EscalationState {
+ return {
+ ...currentState,
+ previousLevel: currentState.currentLevel,
+ currentLevel: newLevel,
+ reason,
+ escalatedAt: new Date(),
+ };
+}
+
+export function approveEscalation(
+ state: EscalationState,
+ approvedBy: string
+): EscalationState {
+ return {
+ ...state,
+ approvalRequired: false,
+ approvedBy,
+ approvedAt: new Date(),
+ };
+}
diff --git a/packages/risk-models/src/index.ts b/packages/risk-models/src/index.ts
new file mode 100644
index 0000000..8e9b05d
--- /dev/null
+++ b/packages/risk-models/src/index.ts
@@ -0,0 +1,5 @@
+export * from './reserves';
+export * from './capital';
+export * from './lcr';
+export * from './escalation';
+export * from './risk-weights';
diff --git a/packages/risk-models/src/lcr.ts b/packages/risk-models/src/lcr.ts
new file mode 100644
index 0000000..942de7b
--- /dev/null
+++ b/packages/risk-models/src/lcr.ts
@@ -0,0 +1,44 @@
+import Decimal from 'decimal.js';
+import type { Transaction, LCRImpact } from '@brazil-swift-ops/types';
+import { getDefaultConverter } from '@brazil-swift-ops/utils';
+
+export interface LCRConfig {
+ hqla: number;
+ netOutflows: number;
+ minimumLCR: number;
+ runoffFactor: number;
+}
+
+export function calculateLCRImpact(transaction: Transaction, config: LCRConfig): LCRImpact {
+ const converter = getDefaultConverter();
+ const brlAmount = converter.convert(transaction.amount, transaction.currency, 'BRL');
+ const transactionDecimal = new Decimal(brlAmount);
+ const runoffDecimal = new Decimal(config.runoffFactor);
+ const outflowStressDecimal = transactionDecimal.mul(runoffDecimal);
+ const outflowStress = outflowStressDecimal.toNumber();
+ const netOutflowsAfter = config.netOutflows + outflowStress;
+ const hqlaDecimal = new Decimal(config.hqla);
+ const outflowsBeforeDecimal = new Decimal(config.netOutflows);
+ const outflowsAfterDecimal = new Decimal(netOutflowsAfter);
+ const lcrBefore = outflowsBeforeDecimal.gt(0) ? hqlaDecimal.div(outflowsBeforeDecimal).toNumber() : Infinity;
+ const lcrAfter = outflowsAfterDecimal.gt(0) ? hqlaDecimal.div(outflowsAfterDecimal).toNumber() : Infinity;
+ const complianceCheck = lcrAfter >= config.minimumLCR;
+
+ return {
+ transactionId: transaction.id,
+ transactionAmount: transaction.amount,
+ currency: transaction.currency,
+ runoffFactor: config.runoffFactor,
+ outflowStress,
+ hqlaBefore: config.hqla,
+ netOutflowsBefore: config.netOutflows,
+ netOutflowsAfter,
+ lcrBefore,
+ lcrAfter,
+ minimumLCR: config.minimumLCR,
+ complianceCheck,
+ rationale: complianceCheck
+ ? `LCR after transaction: ${(lcrAfter * 100).toFixed(2)}% (above minimum ${(config.minimumLCR * 100).toFixed(2)}%).`
+ : `LCR after transaction: ${(lcrAfter * 100).toFixed(2)}% (below minimum ${(config.minimumLCR * 100).toFixed(2)}%). Transaction may be blocked.`,
+ };
+}
diff --git a/packages/risk-models/src/reserves.ts b/packages/risk-models/src/reserves.ts
new file mode 100644
index 0000000..88433d7
--- /dev/null
+++ b/packages/risk-models/src/reserves.ts
@@ -0,0 +1,35 @@
+import Decimal from 'decimal.js';
+import type { Transaction, ReserveImpact } from '@brazil-swift-ops/types';
+import { getDefaultConverter } from '@brazil-swift-ops/utils';
+
+export interface ReserveConfig {
+ reserveRatio: number;
+ availableLiquidity: number;
+ requiredReserves: number;
+}
+
+export function calculateReserveImpact(transaction: Transaction, config: ReserveConfig): ReserveImpact {
+ const converter = getDefaultConverter();
+ const brlAmount = converter.convert(transaction.amount, transaction.currency, 'BRL');
+ const transactionDecimal = new Decimal(brlAmount);
+ const ratioDecimal = new Decimal(config.reserveRatio);
+ const reserveImpactDecimal = transactionDecimal.mul(ratioDecimal);
+ const reserveImpact = reserveImpactDecimal.toNumber();
+ const availableLiquidityAfter = config.availableLiquidity - brlAmount;
+ const complianceCheck = availableLiquidityAfter >= config.requiredReserves;
+
+ return {
+ transactionId: transaction.id,
+ transactionAmount: transaction.amount,
+ currency: transaction.currency,
+ reserveRatio: config.reserveRatio,
+ reserveImpact,
+ availableLiquidityBefore: config.availableLiquidity,
+ availableLiquidityAfter,
+ requiredReserves: config.requiredReserves,
+ complianceCheck,
+ rationale: complianceCheck
+ ? `Reserve impact: ${reserveImpact.toFixed(2)} BRL. Available liquidity after transaction: ${availableLiquidityAfter.toFixed(2)} BRL (above required ${config.requiredReserves.toFixed(2)} BRL).`
+ : `Reserve impact: ${reserveImpact.toFixed(2)} BRL. Available liquidity after transaction: ${availableLiquidityAfter.toFixed(2)} BRL (below required ${config.requiredReserves.toFixed(2)} BRL). Transaction may be blocked.`,
+ };
+}
diff --git a/packages/risk-models/src/risk-weights.ts b/packages/risk-models/src/risk-weights.ts
new file mode 100644
index 0000000..f4cda41
--- /dev/null
+++ b/packages/risk-models/src/risk-weights.ts
@@ -0,0 +1,37 @@
+import type { RiskWeight, RiskWeightTable } from '@brazil-swift-ops/types';
+
+export const DEFAULT_RISK_WEIGHTS: RiskWeight[] = [
+ {
+ category: 'payment',
+ description: 'Payment transactions',
+ weight: 0.1, // 10%
+ minWeight: 0.0,
+ maxWeight: 0.2,
+ },
+ {
+ category: 'fx_settlement',
+ description: 'FX settlement transactions',
+ weight: 0.35, // 35%
+ minWeight: 0.2,
+ maxWeight: 0.5,
+ },
+ {
+ category: 'nostro_exposure',
+ description: 'Nostro account exposure',
+ weight: 1.0, // 100%
+ minWeight: 0.5,
+ maxWeight: 1.0,
+ },
+];
+
+export function createRiskWeightTable(
+ version: string = '1.0.0',
+ effectiveDate: Date = new Date()
+): RiskWeightTable {
+ return {
+ id: `RWT-${version}`,
+ version,
+ effectiveDate,
+ weights: DEFAULT_RISK_WEIGHTS,
+ };
+}
diff --git a/packages/risk-models/tsconfig.json b/packages/risk-models/tsconfig.json
new file mode 100644
index 0000000..5c8037b
--- /dev/null
+++ b/packages/risk-models/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "composite": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"],
+ "references": [
+ {
+ "path": "../types"
+ },
+ {
+ "path": "../utils"
+ }
+ ]
+}
diff --git a/packages/rules-engine/package.json b/packages/rules-engine/package.json
new file mode 100644
index 0000000..dbbf4e1
--- /dev/null
+++ b/packages/rules-engine/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@brazil-swift-ops/rules-engine",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "@brazil-swift-ops/utils": "workspace:*",
+ "decimal.js": "^10.4.3"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/rules-engine/src/aml.ts b/packages/rules-engine/src/aml.ts
new file mode 100644
index 0000000..9d63b32
--- /dev/null
+++ b/packages/rules-engine/src/aml.ts
@@ -0,0 +1,236 @@
+/**
+ * AML (Anti-Money Laundering) and anti-structuring detection
+ */
+
+import type {
+ Transaction,
+ AMLCheckResult,
+ SingleTransactionAMLResult,
+ StructuringCheckResult,
+ RuleResult,
+ RuleSeverity,
+ RuleDecision,
+} from '@brazil-swift-ops/types';
+import { getDefaultConverter } from '@brazil-swift-ops/utils';
+import { calculateRollingWindow, filterDatesInWindow } from '@brazil-swift-ops/utils';
+import { getConfig } from './config';
+
+/**
+ * Transaction history for structuring detection (in production, this would be a database)
+ */
+interface TransactionHistory {
+ transactionId: string;
+ amount: number;
+ currency: string;
+ usdEquivalent: number;
+ date: Date;
+ orderingCustomerTaxId?: string;
+ beneficiaryTaxId?: string;
+}
+
+class TransactionHistoryStore {
+ private history: TransactionHistory[] = [];
+
+ add(entry: TransactionHistory): void {
+ this.history.push(entry);
+ }
+
+ getByDateRange(startDate: Date, endDate: Date): TransactionHistory[] {
+ return this.history.filter(
+ (entry) => entry.date >= startDate && entry.date <= endDate
+ );
+ }
+
+ getByCustomer(
+ taxId: string,
+ startDate: Date,
+ endDate: Date
+ ): TransactionHistory[] {
+ return this.history.filter(
+ (entry) =>
+ (entry.orderingCustomerTaxId === taxId ||
+ entry.beneficiaryTaxId === taxId) &&
+ entry.date >= startDate &&
+ entry.date <= endDate
+ );
+ }
+
+ getAll(): TransactionHistory[] {
+ return [...this.history];
+ }
+}
+
+const historyStore = new TransactionHistoryStore();
+
+export function getHistoryStore(): TransactionHistoryStore {
+ return historyStore;
+}
+
+/**
+ * Check single transaction AML threshold
+ */
+export function checkSingleTransactionAML(
+ transaction: Transaction
+): SingleTransactionAMLResult {
+ const config = getConfig();
+ const converter = getDefaultConverter();
+
+ const usdEquivalent = converter.getUSDEquivalent(
+ transaction.amount,
+ transaction.currency
+ );
+
+ const threshold = config.aml.singleTransactionThreshold;
+ const requiresEnhancedReview = usdEquivalent >= threshold;
+
+ let riskLevel: 'Low' | 'Medium' | 'High';
+ if (usdEquivalent >= threshold) {
+ riskLevel = 'High';
+ } else if (usdEquivalent >= threshold * 0.5) {
+ riskLevel = 'Medium';
+ } else {
+ riskLevel = 'Low';
+ }
+
+ return {
+ passed: true, // AML check doesn't fail, it flags for review
+ transactionAmount: transaction.amount,
+ usdEquivalent,
+ threshold,
+ requiresEnhancedReview,
+ riskLevel,
+ };
+}
+
+/**
+ * Check for structuring patterns (multiple small transactions that sum above threshold)
+ */
+export function checkStructuring(
+ transaction: Transaction,
+ historicalTransactions?: TransactionHistory[]
+): StructuringCheckResult | undefined {
+ const config = getConfig();
+ const converter = getDefaultConverter();
+
+ // Calculate rolling window
+ const window = calculateRollingWindow(
+ transaction.createdAt || new Date(),
+ config.aml.structuringWindowDays
+ );
+
+ // Get historical transactions if not provided
+ if (!historicalTransactions) {
+ const customerTaxId =
+ transaction.orderingCustomerTaxId || transaction.beneficiary?.taxId;
+ if (customerTaxId) {
+ historicalTransactions = historyStore.getByCustomer(
+ customerTaxId,
+ window.startDate,
+ window.endDate
+ );
+ } else {
+ historicalTransactions = historyStore.getByDateRange(
+ window.startDate,
+ window.endDate
+ );
+ }
+ }
+
+ // Filter to transactions in window
+ const windowTransactions = historicalTransactions.filter((t) =>
+ filterDatesInWindow([t.date], window).length > 0
+ );
+
+ // Calculate totals
+ const totalAmount = windowTransactions.reduce(
+ (sum, t) => sum + t.amount,
+ transaction.amount
+ );
+ const totalUsdEquivalent = windowTransactions.reduce(
+ (sum, t) => sum + t.usdEquivalent,
+ converter.getUSDEquivalent(transaction.amount, transaction.currency)
+ );
+
+ const individualAmounts = [
+ ...windowTransactions.map((t) => t.usdEquivalent),
+ converter.getUSDEquivalent(transaction.amount, transaction.currency),
+ ];
+
+ // Check if structuring detected
+ const threshold = config.aml.structuringThreshold;
+ const detected =
+ totalUsdEquivalent >= threshold &&
+ individualAmounts.every((amt) => amt < threshold);
+
+ return {
+ detected,
+ windowDays: window.days,
+ transactionCount: windowTransactions.length + 1,
+ totalAmount,
+ totalUsdEquivalent,
+ individualAmounts,
+ rationale: detected
+ ? `Structuring detected: ${windowTransactions.length + 1} transactions totaling ${totalUsdEquivalent.toFixed(2)} USD over ${window.days} days, each below ${threshold} USD threshold.`
+ : `No structuring pattern detected. Total: ${totalUsdEquivalent.toFixed(2)} USD over ${window.days} days.`,
+ };
+}
+
+/**
+ * Perform complete AML check
+ */
+export function performAMLCheck(
+ transaction: Transaction,
+ historicalTransactions?: TransactionHistory[]
+): AMLCheckResult {
+ const singleCheck = checkSingleTransactionAML(transaction);
+ const structuringCheck = checkStructuring(transaction, historicalTransactions);
+
+ // Determine overall risk level
+ let overallRiskLevel: 'Low' | 'Medium' | 'High';
+ if (singleCheck.riskLevel === 'High' || structuringCheck?.detected) {
+ overallRiskLevel = 'High';
+ } else if (singleCheck.riskLevel === 'Medium') {
+ overallRiskLevel = 'Medium';
+ } else {
+ overallRiskLevel = 'Low';
+ }
+
+ const passed = overallRiskLevel !== 'High';
+
+ return {
+ passed,
+ singleTransactionCheck: singleCheck,
+ structuringCheck,
+ overallRiskLevel,
+ rationale: passed
+ ? `AML check passed. Risk level: ${overallRiskLevel}.`
+ : `AML check flagged for review. Risk level: ${overallRiskLevel}. ${structuringCheck?.detected ? 'Structuring pattern detected.' : ''}`,
+ };
+}
+
+/**
+ * Create rule result for AML check
+ */
+export function createAMLRuleResult(check: AMLCheckResult): RuleResult {
+ const severity: RuleSeverity =
+ check.overallRiskLevel === 'High'
+ ? 'Critical'
+ : check.overallRiskLevel === 'Medium'
+ ? 'Warning'
+ : 'Info';
+ const decision: RuleDecision = check.passed ? 'Allow' : 'Escalate';
+
+ return {
+ ruleId: 'aml-check',
+ ruleName: 'AML & Anti-Structuring Check',
+ passed: check.passed,
+ severity,
+ decision,
+ rationale: check.rationale,
+ details: {
+ overallRiskLevel: check.overallRiskLevel,
+ singleTransactionCheck: check.singleTransactionCheck,
+ structuringCheck: check.structuringCheck,
+ },
+ };
+}
diff --git a/packages/rules-engine/src/fx-contract.ts b/packages/rules-engine/src/fx-contract.ts
new file mode 100644
index 0000000..e89ded4
--- /dev/null
+++ b/packages/rules-engine/src/fx-contract.ts
@@ -0,0 +1,157 @@
+import type {
+ Transaction,
+ FXContract,
+ FXContractCheckResult,
+ RuleResult,
+ RuleSeverity,
+ RuleDecision,
+} from '@brazil-swift-ops/types';
+import { isEffectiveDate } from '@brazil-swift-ops/utils';
+
+class FXContractStore {
+ private contracts: Map = new Map();
+
+ add(contract: FXContract): void {
+ this.contracts.set(contract.contractId, contract);
+ }
+
+ get(contractId: string): FXContract | undefined {
+ return this.contracts.get(contractId);
+ }
+
+ getAll(): FXContract[] {
+ return Array.from(this.contracts.values());
+ }
+
+ updateRemainingAmount(contractId: string, usedAmount: number): void {
+ const contract = this.contracts.get(contractId);
+ if (contract) {
+ contract.usedAmount += usedAmount;
+ contract.remainingAmount = contract.amount - contract.usedAmount;
+ contract.updatedAt = new Date();
+ if (contract.remainingAmount <= 0) {
+ contract.status = 'exhausted';
+ }
+ }
+ }
+}
+
+const contractStore = new FXContractStore();
+
+export function getContractStore(): FXContractStore {
+ return contractStore;
+}
+
+export function validateFXContract(
+ transaction: Transaction,
+ contract?: FXContract
+): FXContractCheckResult {
+ if (!transaction.fxContractId) {
+ return {
+ passed: false,
+ contractExists: false,
+ contractType: undefined,
+ contractAmount: 0,
+ contractRemainingAmount: 0,
+ transactionAmount: transaction.amount,
+ amountWithinLimit: false,
+ rationale: 'FX contract ID is required for cross-border transactions.',
+ };
+ }
+
+ if (!contract) {
+ contract = contractStore.get(transaction.fxContractId);
+ }
+
+ if (!contract) {
+ return {
+ passed: false,
+ fxContractId: transaction.fxContractId,
+ contractExists: false,
+ contractType: undefined,
+ contractAmount: 0,
+ contractRemainingAmount: 0,
+ transactionAmount: transaction.amount,
+ amountWithinLimit: false,
+ rationale: `FX contract ${transaction.fxContractId} not found.`,
+ };
+ }
+
+ const now = new Date();
+ const contractActive =
+ contract.status === 'active' &&
+ isEffectiveDate(now, contract.effectiveDate, contract.expiryDate);
+
+ if (!contractActive) {
+ return {
+ passed: false,
+ fxContractId: contract.contractId,
+ contractExists: true,
+ contractActive: false,
+ contractType: contract.type,
+ contractAmount: contract.amount,
+ contractRemainingAmount: contract.remainingAmount,
+ transactionAmount: transaction.amount,
+ amountWithinLimit: false,
+ rationale: `FX contract ${contract.contractId} is not active (status: ${contract.status}).`,
+ };
+ }
+
+ if (contract.type !== transaction.direction) {
+ return {
+ passed: false,
+ fxContractId: contract.contractId,
+ contractExists: true,
+ contractActive: false,
+ contractType: contract.type,
+ contractAmount: contract.amount,
+ contractRemainingAmount: contract.remainingAmount,
+ transactionAmount: transaction.amount,
+ amountWithinLimit: false,
+ rationale: `FX contract type (${contract.type}) does not match transaction direction (${transaction.direction}).`,
+ };
+ }
+
+ const amountWithinLimit = transaction.amount <= contract.remainingAmount;
+
+ return {
+ passed: amountWithinLimit && contractActive,
+ fxContractId: contract.contractId,
+ contractExists: true,
+ contractActive,
+ contractType: contract.type,
+ contractAmount: contract.amount,
+ contractRemainingAmount: contract.remainingAmount,
+ transactionAmount: transaction.amount,
+ amountWithinLimit,
+ rationale: amountWithinLimit
+ ? `Transaction amount (${transaction.amount} ${transaction.currency}) is within FX contract limit (${contract.remainingAmount} ${contract.currency} remaining).`
+ : `Transaction amount (${transaction.amount} ${transaction.currency}) exceeds FX contract remaining amount (${contract.remainingAmount} ${contract.currency}).`,
+ };
+}
+
+export function createFXContractRuleResult(
+ check: FXContractCheckResult
+): RuleResult {
+ const severity: RuleSeverity = check.passed ? 'Info' : 'Critical';
+ const decision: RuleDecision = check.passed ? 'Allow' : 'Hold';
+
+ return {
+ ruleId: 'fx-contract-check',
+ ruleName: 'FX Contract Validation',
+ passed: check.passed,
+ severity,
+ decision,
+ rationale: check.rationale,
+ details: {
+ fxContractId: check.fxContractId,
+ contractExists: check.contractExists,
+ contractActive: check.contractActive,
+ contractType: check.contractType,
+ contractAmount: check.contractAmount,
+ contractRemainingAmount: check.contractRemainingAmount,
+ transactionAmount: check.transactionAmount,
+ amountWithinLimit: check.amountWithinLimit,
+ },
+ };
+}
diff --git a/packages/rules-engine/src/index.ts b/packages/rules-engine/src/index.ts
new file mode 100644
index 0000000..a61e28f
--- /dev/null
+++ b/packages/rules-engine/src/index.ts
@@ -0,0 +1,13 @@
+/**
+ * @brazil-swift-ops/rules-engine
+ *
+ * Brazil regulatory rules engine for cross-border payments
+ */
+
+export * from './config';
+export * from './threshold';
+export * from './documentation';
+export * from './fx-contract';
+export * from './iof';
+export * from './aml';
+export * from './orchestrator';
diff --git a/packages/rules-engine/src/iof.ts b/packages/rules-engine/src/iof.ts
new file mode 100644
index 0000000..1e16528
--- /dev/null
+++ b/packages/rules-engine/src/iof.ts
@@ -0,0 +1,73 @@
+import Decimal from 'decimal.js';
+import type {
+ Transaction,
+ IOFCalculationResult,
+ RuleResult,
+} from '@brazil-swift-ops/types';
+import { getDefaultConverter } from '@brazil-swift-ops/utils';
+import { getConfig } from './config';
+
+export function calculateIOF(transaction: Transaction): IOFCalculationResult {
+ const config = getConfig();
+ const converter = getDefaultConverter();
+
+ const brlAmount = converter.convert(
+ transaction.amount,
+ transaction.currency,
+ 'BRL'
+ );
+
+ const iofRate =
+ transaction.direction === 'inbound'
+ ? config.iof.inboundRate
+ : config.iof.outboundRate;
+
+ const brlDecimal = new Decimal(brlAmount);
+ const rateDecimal = new Decimal(iofRate);
+ const iofDecimal = brlDecimal.mul(rateDecimal);
+
+ const iofAmount = iofDecimal.toNumber();
+
+ let netAmount: number;
+ if (transaction.direction === 'inbound') {
+ netAmount = brlAmount - iofAmount;
+ } else {
+ netAmount = brlAmount + iofAmount;
+ }
+
+ return {
+ direction: transaction.direction,
+ transactionAmount: transaction.amount,
+ currency: transaction.currency,
+ brlAmount,
+ iofRate,
+ iofAmount,
+ netAmount,
+ effectiveDate: config.iof.effectiveDate,
+ rateVersion: config.iof.rateVersion,
+ };
+}
+
+export function createIOFRuleResult(
+ calculation: IOFCalculationResult
+): RuleResult {
+ return {
+ ruleId: 'iof-calculation',
+ ruleName: 'IOF Tax Calculation',
+ passed: true,
+ severity: 'Info',
+ decision: 'Allow',
+ rationale: `IOF calculated: ${calculation.iofAmount.toFixed(2)} BRL (${(calculation.iofRate * 100).toFixed(2)}% rate) for ${calculation.direction} transaction. Net amount: ${calculation.netAmount.toFixed(2)} BRL.`,
+ details: {
+ direction: calculation.direction,
+ transactionAmount: calculation.transactionAmount,
+ currency: calculation.currency,
+ brlAmount: calculation.brlAmount,
+ iofRate: calculation.iofRate,
+ iofAmount: calculation.iofAmount,
+ netAmount: calculation.netAmount,
+ effectiveDate: calculation.effectiveDate,
+ rateVersion: calculation.rateVersion,
+ },
+ };
+}
diff --git a/packages/rules-engine/src/orchestrator.ts b/packages/rules-engine/src/orchestrator.ts
new file mode 100644
index 0000000..b284143
--- /dev/null
+++ b/packages/rules-engine/src/orchestrator.ts
@@ -0,0 +1,95 @@
+/**
+ * Rules engine orchestrator - coordinates all regulatory rule evaluations
+ */
+
+import type {
+ Transaction,
+ BrazilRegulatoryResult,
+ RuleDecision,
+ RuleSeverity,
+} from '@brazil-swift-ops/types';
+import { getConfig } from './config';
+import { evaluateThreshold, createThresholdRuleResult } from './threshold';
+import {
+ validateDocumentation,
+ createDocumentationRuleResult,
+} from './documentation';
+import {
+ validateFXContract,
+ createFXContractRuleResult,
+ getContractStore,
+} from './fx-contract';
+import { calculateIOF, createIOFRuleResult } from './iof';
+import {
+ performAMLCheck,
+ createAMLRuleResult,
+ getHistoryStore,
+} from './aml';
+
+/**
+ * Evaluate all Brazil regulatory rules for a transaction
+ */
+export function evaluateTransaction(
+ transaction: Transaction
+): BrazilRegulatoryResult {
+ const config = getConfig();
+ const timestamp = new Date();
+
+ // Run all rule checks
+ const thresholdCheck = evaluateThreshold(transaction);
+ const documentationCheck = validateDocumentation(transaction);
+ const fxContract = getContractStore().get(transaction.fxContractId || '');
+ const fxContractCheck = validateFXContract(transaction, fxContract);
+ const iofCalculation = calculateIOF(transaction);
+ const amlCheck = performAMLCheck(transaction);
+
+ // Create rule results
+ const rules = [
+ createThresholdRuleResult(thresholdCheck),
+ createDocumentationRuleResult(documentationCheck),
+ createFXContractRuleResult(fxContractCheck),
+ createIOFRuleResult(iofCalculation),
+ createAMLRuleResult(amlCheck),
+ ];
+
+ // Determine overall decision and severity
+ const criticalRules = rules.filter((r) => r.severity === 'Critical' && !r.passed);
+ const warningRules = rules.filter((r) => r.severity === 'Warning' && !r.passed);
+
+ let overallDecision: RuleDecision;
+ let overallSeverity: RuleSeverity;
+
+ if (criticalRules.length > 0) {
+ overallDecision = 'Hold';
+ overallSeverity = 'Critical';
+ } else if (warningRules.length > 0) {
+ overallDecision = 'Escalate';
+ overallSeverity = 'Warning';
+ } else {
+ overallDecision = 'Allow';
+ overallSeverity = 'Info';
+ }
+
+ return {
+ transactionId: transaction.id,
+ timestamp,
+ ruleSetVersion: config.ruleSetVersion,
+ overallDecision,
+ overallSeverity,
+ rules,
+ thresholdCheck,
+ documentationCheck,
+ fxContractCheck,
+ iofCalculation,
+ amlCheck,
+ };
+}
+
+/**
+ * Evaluate a batch of transactions
+ */
+export function evaluateBatch(
+ transactions: Transaction[]
+): BrazilRegulatoryResult[] {
+ return transactions.map((txn) => evaluateTransaction(txn));
+}
diff --git a/packages/treasury/package.json b/packages/treasury/package.json
new file mode 100644
index 0000000..9ad5796
--- /dev/null
+++ b/packages/treasury/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@brazil-swift-ops/treasury",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "@brazil-swift-ops/utils": "workspace:*"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/treasury/src/accounts.ts b/packages/treasury/src/accounts.ts
new file mode 100644
index 0000000..8db780c
--- /dev/null
+++ b/packages/treasury/src/accounts.ts
@@ -0,0 +1,72 @@
+import type { TreasuryAccount, SubledgerAccount, Account } from '@brazil-swift-ops/types';
+
+class AccountStore {
+ private accounts: Map = new Map();
+
+ add(account: Account): void {
+ this.accounts.set(account.id, account);
+ }
+
+ get(id: string): Account | undefined {
+ return this.accounts.get(id);
+ }
+
+ getByParent(parentId: string): SubledgerAccount[] {
+ return Array.from(this.accounts.values()).filter(
+ (acc): acc is SubledgerAccount =>
+ acc.type === 'subledger' && acc.parentAccountId === parentId
+ );
+ }
+
+ getAll(): Account[] {
+ return Array.from(this.accounts.values());
+ }
+
+ update(id: string, updates: Partial): void {
+ const account = this.accounts.get(id);
+ if (account) {
+ this.accounts.set(id, { ...account, ...updates, updatedAt: new Date() });
+ }
+ }
+
+ delete(id: string): void {
+ this.accounts.delete(id);
+ }
+}
+
+const accountStore = new AccountStore();
+
+export function getAccountStore(): AccountStore {
+ return accountStore;
+}
+
+export function createTreasuryAccount(accountNumber: string, name: string, currency: string): TreasuryAccount {
+ return {
+ id: `TREASURY-${Date.now()}`,
+ accountNumber,
+ name,
+ type: 'treasury',
+ currency,
+ status: 'active',
+ balance: 0,
+ availableBalance: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+}
+
+export function createSubledgerAccount(accountNumber: string, name: string, currency: string, parentAccountId: string): SubledgerAccount {
+ return {
+ id: `SUB-${Date.now()}`,
+ accountNumber,
+ name,
+ type: 'subledger',
+ currency,
+ status: 'active',
+ parentAccountId,
+ balance: 0,
+ availableBalance: 0,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+}
diff --git a/packages/treasury/src/index.ts b/packages/treasury/src/index.ts
new file mode 100644
index 0000000..1bf9572
--- /dev/null
+++ b/packages/treasury/src/index.ts
@@ -0,0 +1,4 @@
+export * from './accounts';
+export * from './posting';
+export * from './transfers';
+export * from './reporting';
diff --git a/packages/treasury/src/posting.ts b/packages/treasury/src/posting.ts
new file mode 100644
index 0000000..aed9aa0
--- /dev/null
+++ b/packages/treasury/src/posting.ts
@@ -0,0 +1,87 @@
+import type { Account, AccountPosting, PostingType, Transaction } from '@brazil-swift-ops/types';
+import { getDefaultConverter } from '@brazil-swift-ops/utils';
+import { getAccountStore } from './accounts';
+
+class PostingStore {
+ private postings: AccountPosting[] = [];
+
+ add(posting: AccountPosting): void {
+ this.postings.push(posting);
+ }
+
+ getByAccount(accountId: string): AccountPosting[] {
+ return this.postings.filter((p) => p.accountId === accountId);
+ }
+
+ getByTransaction(transactionId: string): AccountPosting[] {
+ return this.postings.filter((p) => p.transactionId === transactionId);
+ }
+
+ getAll(): AccountPosting[] {
+ return [...this.postings];
+ }
+}
+
+const postingStore = new PostingStore();
+
+export function getPostingStore(): PostingStore {
+ return postingStore;
+}
+
+export function postToAccount(account: Account, transaction: Transaction, postingType: PostingType, amount?: number): AccountPosting {
+ const accountStore = getAccountStore();
+ const converter = getDefaultConverter();
+ const transactionAmount = amount ?? transaction.amount;
+ let postingAmount = transactionAmount;
+ let fxRate: number | undefined;
+
+ if (transaction.currency !== account.currency) {
+ fxRate = converter.getRate(transaction.currency, account.currency);
+ if (fxRate) {
+ postingAmount = converter.convert(transactionAmount, transaction.currency, account.currency);
+ }
+ }
+
+ const balanceBefore = account.balance;
+ const balanceAfter = postingType === 'debit' ? balanceBefore - postingAmount : balanceBefore + postingAmount;
+
+ const posting: AccountPosting = {
+ id: `POST-${Date.now()}`,
+ accountId: account.id,
+ transactionId: transaction.id,
+ postingType,
+ amount: postingAmount,
+ currency: account.currency,
+ fxRate,
+ balanceBefore,
+ balanceAfter,
+ postedAt: new Date(),
+ description: `Transaction ${transaction.id} - ${postingType}`,
+ };
+
+ accountStore.update(account.id, { balance: balanceAfter, availableBalance: balanceAfter });
+ postingStore.add(posting);
+ return posting;
+}
+
+export function allocateTransactionToSubledger(transaction: Transaction, subledgerId: string): AccountPosting[] {
+ const accountStore = getAccountStore();
+ const subledger = accountStore.get(subledgerId);
+ if (!subledger || subledger.type !== 'subledger') {
+ throw new Error(`Subledger account ${subledgerId} not found`);
+ }
+ const parent = accountStore.get(subledger.parentAccountId);
+ if (!parent) {
+ throw new Error(`Parent account ${subledger.parentAccountId} not found`);
+ }
+
+ const postings: AccountPosting[] = [];
+ if (transaction.direction === 'inbound') {
+ postings.push(postToAccount(subledger, transaction, 'credit'));
+ postings.push(postToAccount(parent, transaction, 'credit'));
+ } else {
+ postings.push(postToAccount(subledger, transaction, 'debit'));
+ postings.push(postToAccount(parent, transaction, 'debit'));
+ }
+ return postings;
+}
diff --git a/packages/treasury/src/reporting.ts b/packages/treasury/src/reporting.ts
new file mode 100644
index 0000000..4455852
--- /dev/null
+++ b/packages/treasury/src/reporting.ts
@@ -0,0 +1,55 @@
+import type {
+ SubledgerReport,
+ AccountPosting,
+} from '@brazil-swift-ops/types';
+import { getPostingStore } from './posting';
+import { getAccountStore } from './accounts';
+
+export function generateSubledgerReport(
+ subledgerId: string,
+ periodStart: Date,
+ periodEnd: Date
+): SubledgerReport {
+ const accountStore = getAccountStore();
+ const postingStore = getPostingStore();
+
+ const subledger = accountStore.get(subledgerId);
+ if (!subledger || subledger.type !== 'subledger') {
+ throw new Error(`Subledger account ${subledgerId} not found`);
+ }
+
+ const postings = postingStore
+ .getByAccount(subledgerId)
+ .filter(
+ (p) => p.postedAt >= periodStart && p.postedAt <= periodEnd
+ );
+
+ const openingBalance = subledger.balance - postings.reduce((sum, p) => {
+ return p.postingType === 'debit' ? sum - p.amount : sum + p.amount;
+ }, 0);
+
+ const totalDebits = postings
+ .filter((p) => p.postingType === 'debit')
+ .reduce((sum, p) => sum + p.amount, 0);
+
+ const totalCredits = postings
+ .filter((p) => p.postingType === 'credit')
+ .reduce((sum, p) => sum + p.amount, 0);
+
+ const closingBalance = openingBalance + totalCredits - totalDebits;
+ const netPosition = totalCredits - totalDebits;
+
+ return {
+ subledgerId,
+ periodStart,
+ periodEnd,
+ openingBalance,
+ closingBalance,
+ totalDebits,
+ totalCredits,
+ netPosition,
+ currency: subledger.currency,
+ transactionCount: new Set(postings.map((p) => p.transactionId)).size,
+ postings,
+ };
+}
diff --git a/packages/treasury/src/transfers.ts b/packages/treasury/src/transfers.ts
new file mode 100644
index 0000000..4e865a3
--- /dev/null
+++ b/packages/treasury/src/transfers.ts
@@ -0,0 +1,99 @@
+import type {
+ SubledgerTransfer,
+ Account,
+} from '@brazil-swift-ops/types';
+import { getAccountStore } from './accounts';
+import { postToAccount } from './posting';
+
+class TransferStore {
+ private transfers: SubledgerTransfer[] = [];
+
+ add(transfer: SubledgerTransfer): void {
+ this.transfers.push(transfer);
+ }
+
+ get(id: string): SubledgerTransfer | undefined {
+ return this.transfers.find((t) => t.id === id);
+ }
+
+ getByAccount(accountId: string): SubledgerTransfer[] {
+ return this.transfers.filter(
+ (t) => t.fromAccountId === accountId || t.toAccountId === accountId
+ );
+ }
+
+ getAll(): SubledgerTransfer[] {
+ return [...this.transfers];
+ }
+}
+
+const transferStore = new TransferStore();
+
+export function getTransferStore(): TransferStore {
+ return transferStore;
+}
+
+export function executeSubledgerTransfer(
+ fromAccountId: string,
+ toAccountId: string,
+ amount: number,
+ currency: string,
+ description?: string
+): SubledgerTransfer {
+ const accountStore = getAccountStore();
+ const fromAccount = accountStore.get(fromAccountId);
+ const toAccount = accountStore.get(toAccountId);
+
+ if (!fromAccount) {
+ throw new Error(`Source account ${fromAccountId} not found`);
+ }
+
+ if (!toAccount) {
+ throw new Error(`Destination account ${toAccountId} not found`);
+ }
+
+ if (fromAccount.currency !== currency || toAccount.currency !== currency) {
+ throw new Error('Currency mismatch in transfer');
+ }
+
+ if (fromAccount.balance < amount) {
+ throw new Error('Insufficient balance in source account');
+ }
+
+ const transfer: SubledgerTransfer = {
+ id: `TRF-${Date.now()}`,
+ fromAccountId,
+ toAccountId,
+ amount,
+ currency,
+ description,
+ executedAt: new Date(),
+ status: 'pending',
+ };
+
+ // Execute transfer
+ try {
+ // Debit from source
+ const fromBalanceBefore = fromAccount.balance;
+ accountStore.update(fromAccountId, {
+ balance: fromBalanceBefore - amount,
+ availableBalance: fromBalanceBefore - amount,
+ });
+
+ // Credit to destination
+ const toBalanceBefore = toAccount.balance;
+ accountStore.update(toAccountId, {
+ balance: toBalanceBefore + amount,
+ availableBalance: toBalanceBefore + amount,
+ });
+
+ transfer.status = 'completed';
+ } catch (error) {
+ transfer.status = 'failed';
+ throw error;
+ }
+
+ transferStore.add(transfer);
+
+ return transfer;
+}
diff --git a/packages/treasury/tsconfig.json b/packages/treasury/tsconfig.json
new file mode 100644
index 0000000..5c8037b
--- /dev/null
+++ b/packages/treasury/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "composite": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"],
+ "references": [
+ {
+ "path": "../types"
+ },
+ {
+ "path": "../utils"
+ }
+ ]
+}
diff --git a/packages/types/package.json b/packages/types/package.json
new file mode 100644
index 0000000..6201c09
--- /dev/null
+++ b/packages/types/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@brazil-swift-ops/types",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/types/src/audit.ts b/packages/types/src/audit.ts
new file mode 100644
index 0000000..51a6075
--- /dev/null
+++ b/packages/types/src/audit.ts
@@ -0,0 +1,88 @@
+/**
+ * Audit logging and compliance reporting types
+ */
+
+export interface AuditLog {
+ id: string;
+ timestamp: Date;
+ transactionId?: string;
+ batchId?: string;
+ ruleSetVersion: string;
+ inputs: Record;
+ outputs: Record;
+ decision: 'Allow' | 'Hold' | 'Escalate';
+ severity: 'Info' | 'Warning' | 'Critical';
+ rationale: string;
+ userId?: string;
+ ipAddress?: string;
+ userAgent?: string;
+ action: string; // e.g., 'transaction_processed', 'rule_evaluated', 'report_generated'
+ metadata?: Record;
+}
+
+export interface RuleVersion {
+ version: string;
+ effectiveDate: Date;
+ approvalAuthority: string;
+ deprecated: boolean;
+ deprecatedDate?: Date;
+ description?: string;
+ changes?: string[];
+}
+
+export interface BCBReport {
+ reportId: string;
+ reportType: 'transaction' | 'batch' | 'periodic';
+ generatedAt: Date;
+ periodStart?: Date;
+ periodEnd?: Date;
+ transactions: BCBReportTransaction[];
+ summary: BCBReportSummary;
+ format: 'json' | 'csv';
+ data: string; // Serialized report data
+}
+
+export interface BCBReportTransaction {
+ transactionId: string;
+ executionDate: Date;
+ direction: 'inbound' | 'outbound';
+ amount: number;
+ currency: string;
+ usdEquivalent: number;
+ orderingCustomer: {
+ name: string;
+ taxId?: string;
+ country: string;
+ };
+ beneficiary: {
+ name: string;
+ taxId?: string;
+ country: string;
+ accountNumber?: string;
+ };
+ purposeOfPayment?: string;
+ fxContractId?: string;
+ iofAmount?: number;
+ reportingRequired: boolean;
+}
+
+export interface BCBReportSummary {
+ totalTransactions: number;
+ totalAmount: number;
+ totalUsdEquivalent: number;
+ inboundCount: number;
+ inboundAmount: number;
+ outboundCount: number;
+ outboundAmount: number;
+ reportingRequiredCount: number;
+ totalIOF: number;
+}
+
+export interface RetentionPolicy {
+ policyId: string;
+ dataType: 'audit_log' | 'transaction' | 'report' | 'all';
+ retentionPeriodDays: number;
+ archivalAfterDays?: number;
+ autoDelete: boolean;
+ effectiveDate: Date;
+}
diff --git a/packages/types/src/eo-uplift.ts b/packages/types/src/eo-uplift.ts
new file mode 100644
index 0000000..6a9d7dd
--- /dev/null
+++ b/packages/types/src/eo-uplift.ts
@@ -0,0 +1,24 @@
+/**
+ * Errors & Omissions (E&O) Uplift types
+ */
+
+export interface EOUplift {
+ baseAmount: number;
+ upliftRate: number; // 0.10 for 10%
+ upliftAmount: number;
+ adjustedExposure: number;
+ treatment: 'off_balance_sheet'; // Non-booked risk buffer
+}
+
+export interface TransactionEOUplift extends EOUplift {
+ transactionId: string;
+ currency: string;
+ usdEquivalent?: number;
+}
+
+export interface BatchEOUplift extends EOUplift {
+ batchId: string;
+ transactionCount: number;
+ currency: string;
+ usdEquivalent?: number;
+}
diff --git a/packages/types/src/fx-contract.ts b/packages/types/src/fx-contract.ts
new file mode 100644
index 0000000..a5d2e03
--- /dev/null
+++ b/packages/types/src/fx-contract.ts
@@ -0,0 +1,32 @@
+/**
+ * FX Contract (Contrato de Câmbio) types
+ */
+
+export interface FXContract {
+ contractId: string;
+ type: 'inbound' | 'outbound';
+ counterparty: string;
+ counterpartyTaxId?: string; // CNPJ
+ amount: number;
+ currency: string;
+ effectiveDate: Date;
+ expiryDate: Date;
+ remainingAmount: number;
+ usedAmount: number;
+ status: 'active' | 'expired' | 'exhausted' | 'cancelled';
+ createdAt: Date;
+ updatedAt: Date;
+ metadata?: Record;
+}
+
+export interface FXContractValidation {
+ contractId: string;
+ contractExists: boolean;
+ contractValid: boolean;
+ contractActive: boolean;
+ amountWithinLimit: boolean;
+ transactionAmount: number;
+ contractRemainingAmount: number;
+ errors: string[];
+ warnings: string[];
+}
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
new file mode 100644
index 0000000..087b675
--- /dev/null
+++ b/packages/types/src/index.ts
@@ -0,0 +1,14 @@
+/**
+ * @brazil-swift-ops/types
+ *
+ * Shared TypeScript types and interfaces for the Brazil SWIFT Operations Platform
+ */
+
+export * from './transaction';
+export * from './iso20022';
+export * from './regulatory';
+export * from './treasury';
+export * from './audit';
+export * from './risk';
+export * from './fx-contract';
+export * from './eo-uplift';
diff --git a/packages/types/src/iso20022.ts b/packages/types/src/iso20022.ts
new file mode 100644
index 0000000..e2b5997
--- /dev/null
+++ b/packages/types/src/iso20022.ts
@@ -0,0 +1,131 @@
+/**
+ * ISO 20022 message types and structures
+ */
+
+export type ISO20022MessageType = 'pacs.008' | 'pacs.009' | 'pain.001';
+export type ISO20022Version = string; // e.g., "001.08", "001.10"
+
+export interface ISO20022Message {
+ messageId: string;
+ messageType: ISO20022MessageType;
+ version: ISO20022Version;
+ creationDateTime: Date;
+ groupHeader: GroupHeader;
+ paymentInformation: PaymentInformation[];
+ rawMessage?: Record; // Full ISO 20022 JSON structure
+}
+
+export interface GroupHeader {
+ messageIdentification: string;
+ creationDateTime: Date;
+ numberOfTransactions: number;
+ controlSum?: number;
+ initiatingParty: PartyIdentification;
+}
+
+export interface PartyIdentification {
+ name?: string;
+ postalAddress?: PostalAddress;
+ identification?: PartyIdentificationDetails;
+}
+
+export interface PostalAddress {
+ streetName?: string;
+ buildingNumber?: string;
+ postCode?: string;
+ townName?: string;
+ country: string;
+}
+
+export interface PartyIdentificationDetails {
+ organisationIdentification?: string;
+ privateIdentification?: string; // CPF/CNPJ
+}
+
+export interface PaymentInformation {
+ paymentInformationIdentification: string;
+ paymentMethod: string;
+ requestedExecutionDate: Date;
+ debtor: PartyIdentification;
+ debtorAccount: CashAccount;
+ debtorAgent?: FinancialInstitutionIdentification;
+ creditTransferTransactionInformation: CreditTransferTransactionInformation[];
+}
+
+export interface CashAccount {
+ identification: string;
+ name?: string;
+ currency?: string;
+ type?: string;
+ iban?: string;
+}
+
+export interface FinancialInstitutionIdentification {
+ bic?: string;
+ name?: string;
+ postalAddress?: PostalAddress;
+}
+
+export interface CreditTransferTransactionInformation {
+ paymentIdentification: PaymentIdentification;
+ amount: Amount;
+ chargeBearer?: string;
+ creditor: PartyIdentification;
+ creditorAccount: CashAccount;
+ creditorAgent?: FinancialInstitutionIdentification;
+ remittanceInformation?: RemittanceInformation;
+ purpose?: string;
+}
+
+export interface PaymentIdentification {
+ instructionId?: string;
+ endToEndId: string;
+ transactionId?: string;
+}
+
+export interface Amount {
+ currency: string;
+ value: number;
+}
+
+export interface RemittanceInformation {
+ unstructured?: string;
+ structured?: Record;
+}
+
+// pacs.008 specific (FIToFICustomerCreditTransfer)
+export interface Pacs008Message extends ISO20022Message {
+ messageType: 'pacs.008';
+ creditTransferTransaction: CreditTransferTransaction[];
+}
+
+export interface CreditTransferTransaction {
+ paymentIdentification: PaymentIdentification;
+ amount: Amount;
+ chargeBearer?: string;
+ debtor: PartyIdentification;
+ debtorAccount: CashAccount;
+ debtorAgent?: FinancialInstitutionIdentification;
+ creditor: PartyIdentification;
+ creditorAccount: CashAccount;
+ creditorAgent?: FinancialInstitutionIdentification;
+ remittanceInformation?: RemittanceInformation;
+ purpose?: string;
+}
+
+// pacs.009 specific (FinancialInstitutionCreditTransfer)
+export interface Pacs009Message extends ISO20022Message {
+ messageType: 'pacs.009';
+ creditTransferTransaction: CreditTransferTransaction[];
+}
+
+// pain.001 specific (CustomerCreditTransferInitiation)
+export interface Pain001Message extends ISO20022Message {
+ messageType: 'pain.001';
+ customerCreditTransferInitiation: CustomerCreditTransferInitiation;
+}
+
+export interface CustomerCreditTransferInitiation {
+ groupHeader: GroupHeader;
+ paymentInformation: PaymentInformation[];
+}
diff --git a/packages/types/src/regulatory.ts b/packages/types/src/regulatory.ts
new file mode 100644
index 0000000..bf85735
--- /dev/null
+++ b/packages/types/src/regulatory.ts
@@ -0,0 +1,105 @@
+/**
+ * Brazil regulatory rule evaluation types
+ */
+
+export type RuleSeverity = 'Info' | 'Warning' | 'Critical';
+export type RuleDecision = 'Allow' | 'Hold' | 'Escalate';
+export type EscalationLevel = 'Allow' | 'Hold' | 'ComplianceReview' | 'TreasuryApproval' | 'ExceptionApproval';
+
+export interface RuleResult {
+ ruleId: string;
+ ruleName: string;
+ passed: boolean;
+ severity: RuleSeverity;
+ decision: RuleDecision;
+ rationale: string;
+ details?: Record;
+}
+
+export interface BrazilRegulatoryResult {
+ transactionId: string;
+ timestamp: Date;
+ ruleSetVersion: string;
+ overallDecision: RuleDecision;
+ overallSeverity: RuleSeverity;
+ rules: RuleResult[];
+ thresholdCheck?: ThresholdCheckResult;
+ documentationCheck?: DocumentationCheckResult;
+ fxContractCheck?: FXContractCheckResult;
+ iofCalculation?: IOFCalculationResult;
+ amlCheck?: AMLCheckResult;
+}
+
+export interface ThresholdCheckResult {
+ passed: boolean;
+ transactionAmount: number;
+ currency: string;
+ usdEquivalent: number;
+ threshold: number; // USD 10,000
+ requiresReporting: boolean;
+ rationale: string;
+}
+
+export interface DocumentationCheckResult {
+ passed: boolean;
+ hasOrderingCustomerName: boolean;
+ hasOrderingCustomerAddress: boolean;
+ hasOrderingCustomerTaxId: boolean;
+ hasBeneficiaryName: boolean;
+ hasBeneficiaryAccount: boolean;
+ hasBeneficiaryTaxId: boolean;
+ hasPurposeOfPayment: boolean;
+ missingFields: string[];
+ rationale: string;
+}
+
+export interface FXContractCheckResult {
+ passed: boolean;
+ fxContractId?: string;
+ contractExists: boolean;
+ contractType?: 'inbound' | 'outbound';
+ contractAmount: number;
+ contractRemainingAmount: number;
+ transactionAmount: number;
+ amountWithinLimit: boolean;
+ rationale: string;
+}
+
+export interface IOFCalculationResult {
+ direction: 'inbound' | 'outbound';
+ transactionAmount: number;
+ currency: string;
+ brlAmount: number;
+ iofRate: number; // e.g., 0.0038 for inbound, 0.0350 for outbound
+ iofAmount: number;
+ netAmount: number; // transactionAmount - iofAmount (inbound) or transactionAmount + iofAmount (outbound)
+ effectiveDate: Date;
+ rateVersion: string;
+}
+
+export interface AMLCheckResult {
+ passed: boolean;
+ singleTransactionCheck: SingleTransactionAMLResult;
+ structuringCheck?: StructuringCheckResult;
+ overallRiskLevel: 'Low' | 'Medium' | 'High';
+ rationale: string;
+}
+
+export interface SingleTransactionAMLResult {
+ passed: boolean;
+ transactionAmount: number;
+ usdEquivalent: number;
+ threshold: number;
+ requiresEnhancedReview: boolean;
+ riskLevel: 'Low' | 'Medium' | 'High';
+}
+
+export interface StructuringCheckResult {
+ detected: boolean;
+ windowDays: number;
+ transactionCount: number;
+ totalAmount: number;
+ totalUsdEquivalent: number;
+ individualAmounts: number[];
+ rationale: string;
+}
diff --git a/packages/types/src/risk.ts b/packages/types/src/risk.ts
new file mode 100644
index 0000000..ef8b6ed
--- /dev/null
+++ b/packages/types/src/risk.ts
@@ -0,0 +1,86 @@
+/**
+ * Risk, capital, and liquidity modeling types
+ */
+
+export interface ReserveImpact {
+ transactionId: string;
+ transactionAmount: number;
+ currency: string;
+ reserveRatio: number; // e.g., 0.21 for 21%
+ reserveImpact: number;
+ availableLiquidityBefore: number;
+ availableLiquidityAfter: number;
+ requiredReserves: number;
+ complianceCheck: boolean;
+ rationale: string;
+}
+
+export interface CapitalImpact {
+ transactionId: string;
+ transactionAmount: number;
+ currency: string;
+ riskWeight: number; // 0-1 (0% to 100%)
+ riskWeightedAssets: number;
+ capitalRatio: number; // e.g., 0.105 for 10.5%
+ capitalConsumed: number;
+ capitalBufferBefore: number;
+ capitalBufferAfter: number;
+ complianceCheck: boolean;
+ rationale: string;
+}
+
+export interface LCRImpact {
+ transactionId: string;
+ transactionAmount: number;
+ currency: string;
+ runoffFactor: number; // 0-1
+ outflowStress: number;
+ hqlaBefore: number; // High Quality Liquid Assets
+ netOutflowsBefore: number;
+ netOutflowsAfter: number;
+ lcrBefore: number;
+ lcrAfter: number;
+ minimumLCR: number; // e.g., 1.0 for 100%
+ complianceCheck: boolean;
+ rationale: string;
+}
+
+export interface RiskWeightTable {
+ id: string;
+ version: string;
+ effectiveDate: Date;
+ weights: RiskWeight[];
+}
+
+export interface RiskWeight {
+ category: string; // e.g., 'payment', 'fx_settlement', 'nostro_exposure'
+ description: string;
+ weight: number; // 0-1 (0% to 100%)
+ minWeight?: number;
+ maxWeight?: number;
+}
+
+export interface EscalationState {
+ transactionId: string;
+ currentLevel: EscalationLevel;
+ previousLevel?: EscalationLevel;
+ reason: string;
+ escalatedAt: Date;
+ escalatedBy?: string;
+ approvalRequired?: boolean;
+ approvedBy?: string;
+ approvedAt?: Date;
+}
+
+export type EscalationLevel = 'Allow' | 'Hold' | 'ComplianceReview' | 'TreasuryApproval' | 'ExceptionApproval';
+
+export interface StressTestResult {
+ transactionId: string;
+ reserveImpact: ReserveImpact;
+ capitalImpact: CapitalImpact;
+ lcrImpact: LCRImpact;
+ overallCompliance: boolean;
+ blockingIssues: string[];
+ warnings: string[];
+ recommendedAction: 'Allow' | 'Hold' | 'Escalate';
+}
diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts
new file mode 100644
index 0000000..6ae010f
--- /dev/null
+++ b/packages/types/src/transaction.ts
@@ -0,0 +1,48 @@
+/**
+ * Canonical transaction model for cross-border payments
+ */
+
+export type TransactionDirection = 'inbound' | 'outbound';
+export type TransactionStatus = 'pending' | 'approved' | 'held' | 'rejected' | 'escalated';
+export type Currency = string; // ISO 4217 currency code
+
+export interface Party {
+ name: string;
+ address?: string;
+ city?: string;
+ country: string;
+ taxId?: string; // CPF for individuals, CNPJ for corporates
+ email?: string;
+ phone?: string;
+ accountNumber?: string;
+ iban?: string;
+ bic?: string;
+}
+
+export interface Transaction {
+ id: string;
+ direction: TransactionDirection;
+ amount: number;
+ currency: Currency;
+ usdEquivalent?: number; // Calculated USD equivalent
+ orderingCustomer: Party;
+ beneficiary: Party;
+ purposeOfPayment?: string;
+ fxContractId?: string; // Link to FX contract (contrato de câmbio)
+ swiftReference?: string;
+ iso20022MessageId?: string;
+ status: TransactionStatus;
+ createdAt: Date;
+ updatedAt: Date;
+ metadata?: Record;
+}
+
+export interface BatchTransaction {
+ batchId: string;
+ transactions: Transaction[];
+ totalAmount: number;
+ totalUsdEquivalent: number;
+ currency: Currency;
+ createdAt: Date;
+ status: 'pending' | 'processing' | 'completed' | 'failed';
+}
diff --git a/packages/types/src/treasury.ts b/packages/types/src/treasury.ts
new file mode 100644
index 0000000..08b9a46
--- /dev/null
+++ b/packages/types/src/treasury.ts
@@ -0,0 +1,94 @@
+/**
+ * Treasury and subledger account types
+ */
+
+export type AccountStatus = 'active' | 'inactive' | 'closed' | 'suspended';
+export type AccountType = 'treasury' | 'subledger';
+export type PostingType = 'debit' | 'credit';
+
+export interface TreasuryAccount {
+ id: string;
+ accountNumber: string;
+ name: string;
+ type: 'treasury';
+ currency: string;
+ status: AccountStatus;
+ parentAccountId?: string; // For subledgers
+ balance: number;
+ availableBalance: number;
+ createdAt: Date;
+ updatedAt: Date;
+ metadata?: Record;
+}
+
+export interface SubledgerAccount {
+ id: string;
+ accountNumber: string;
+ name: string;
+ type: 'subledger';
+ currency: string;
+ status: AccountStatus;
+ parentAccountId: string; // Must have a parent treasury account
+ balance: number;
+ availableBalance: number;
+ routingRules?: RoutingRule[];
+ createdAt: Date;
+ updatedAt: Date;
+ metadata?: Record;
+}
+
+export type Account = TreasuryAccount | SubledgerAccount;
+
+export interface RoutingRule {
+ id: string;
+ subledgerAccountId: string;
+ conditions: RoutingCondition[];
+ priority: number;
+ active: boolean;
+}
+
+export interface RoutingCondition {
+ field: string; // e.g., 'currency', 'amount', 'counterparty'
+ operator: 'equals' | 'greaterThan' | 'lessThan' | 'contains';
+ value: string | number;
+}
+
+export interface AccountPosting {
+ id: string;
+ accountId: string;
+ transactionId: string;
+ postingType: PostingType;
+ amount: number;
+ currency: string;
+ fxRate?: number; // If currency conversion occurred
+ balanceBefore: number;
+ balanceAfter: number;
+ postedAt: Date;
+ description?: string;
+}
+
+export interface SubledgerTransfer {
+ id: string;
+ fromAccountId: string;
+ toAccountId: string;
+ amount: number;
+ currency: string;
+ transactionId?: string; // Link to original transaction if applicable
+ description?: string;
+ executedAt: Date;
+ status: 'pending' | 'completed' | 'failed';
+}
+
+export interface SubledgerReport {
+ subledgerId: string;
+ periodStart: Date;
+ periodEnd: Date;
+ openingBalance: number;
+ closingBalance: number;
+ totalDebits: number;
+ totalCredits: number;
+ netPosition: number;
+ currency: string;
+ transactionCount: number;
+ postings: AccountPosting[];
+}
diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json
new file mode 100644
index 0000000..a00ade4
--- /dev/null
+++ b/packages/types/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "composite": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/utils/package.json b/packages/utils/package.json
new file mode 100644
index 0000000..024947a
--- /dev/null
+++ b/packages/utils/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@brazil-swift-ops/utils",
+ "version": "1.0.0",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "type-check": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@brazil-swift-ops/types": "workspace:*",
+ "decimal.js": "^10.4.3",
+ "date-fns": "^3.0.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/packages/utils/src/currency.ts b/packages/utils/src/currency.ts
new file mode 100644
index 0000000..51a361a
--- /dev/null
+++ b/packages/utils/src/currency.ts
@@ -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 = new Map();
+ private defaultUSDRates: Record = {
+ 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;
+}
diff --git a/packages/utils/src/dates.ts b/packages/utils/src/dates.ts
new file mode 100644
index 0000000..0488d12
--- /dev/null
+++ b/packages/utils/src/dates.ts
@@ -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];
+}
diff --git a/packages/utils/src/eo-uplift.ts b/packages/utils/src/eo-uplift.ts
new file mode 100644
index 0000000..9edb9c2
--- /dev/null
+++ b/packages/utils/src/eo-uplift.ts
@@ -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,
+ };
+ });
+}
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
new file mode 100644
index 0000000..f5dfe73
--- /dev/null
+++ b/packages/utils/src/index.ts
@@ -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';
diff --git a/packages/utils/src/validation.ts b/packages/utils/src/validation.ts
new file mode 100644
index 0000000..a489e35
--- /dev/null
+++ b/packages/utils/src/validation.ts
@@ -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;
+}
diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json
new file mode 100644
index 0000000..e068ae4
--- /dev/null
+++ b/packages/utils/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "composite": true
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"],
+ "references": [
+ {
+ "path": "../types"
+ }
+ ]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..50afb75
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,1744 @@
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@types/node':
+ specifier: ^20.10.0
+ version: 20.19.30
+ turbo:
+ specifier: ^1.11.0
+ version: 1.13.4
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ apps/web:
+ dependencies:
+ '@brazil-swift-ops/audit':
+ specifier: workspace:*
+ version: link:../../packages/audit
+ '@brazil-swift-ops/iso20022':
+ specifier: workspace:*
+ version: link:../../packages/iso20022
+ '@brazil-swift-ops/risk-models':
+ specifier: workspace:*
+ version: link:../../packages/risk-models
+ '@brazil-swift-ops/rules-engine':
+ specifier: workspace:*
+ version: link:../../packages/rules-engine
+ '@brazil-swift-ops/treasury':
+ specifier: workspace:*
+ version: link:../../packages/treasury
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../../packages/types
+ '@brazil-swift-ops/utils':
+ specifier: workspace:*
+ version: link:../../packages/utils
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-router-dom:
+ specifier: ^6.20.0
+ version: 6.30.3(react-dom@18.3.1)(react@18.3.1)
+ zustand:
+ specifier: ^4.4.7
+ version: 4.5.7(@types/react@18.3.27)(react@18.3.1)
+ devDependencies:
+ '@types/react':
+ specifier: ^18.2.43
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.2.17
+ version: 18.3.7(@types/react@18.3.27)
+ '@vitejs/plugin-react':
+ specifier: ^4.2.1
+ version: 4.7.0(vite@5.4.21)
+ autoprefixer:
+ specifier: ^10.4.16
+ version: 10.4.23(postcss@8.5.6)
+ postcss:
+ specifier: ^8.4.32
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^3.3.6
+ version: 3.4.19
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+ vite:
+ specifier: ^5.0.8
+ version: 5.4.21(@types/node@20.19.30)
+
+ packages/audit:
+ dependencies:
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../types
+ '@brazil-swift-ops/utils':
+ specifier: workspace:*
+ version: link:../utils
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ packages/iso20022:
+ dependencies:
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../types
+ '@brazil-swift-ops/utils':
+ specifier: workspace:*
+ version: link:../utils
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ packages/risk-models:
+ dependencies:
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../types
+ '@brazil-swift-ops/utils':
+ specifier: workspace:*
+ version: link:../utils
+ decimal.js:
+ specifier: ^10.4.3
+ version: 10.6.0
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ packages/rules-engine:
+ dependencies:
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../types
+ '@brazil-swift-ops/utils':
+ specifier: workspace:*
+ version: link:../utils
+ decimal.js:
+ specifier: ^10.4.3
+ version: 10.6.0
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ packages/treasury:
+ dependencies:
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../types
+ '@brazil-swift-ops/utils':
+ specifier: workspace:*
+ version: link:../utils
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ packages/types:
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+ packages/utils:
+ dependencies:
+ '@brazil-swift-ops/types':
+ specifier: workspace:*
+ version: link:../types
+ date-fns:
+ specifier: ^3.0.0
+ version: 3.6.0
+ decimal.js:
+ specifier: ^10.4.3
+ version: 10.6.0
+ devDependencies:
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+
+packages:
+
+ /@alloc/quick-lru@5.2.0:
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /@babel/code-frame@7.28.6:
+ resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+ dev: true
+
+ /@babel/compat-data@7.28.6:
+ resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/core@7.28.6:
+ resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/generator@7.28.6:
+ resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+ dev: true
+
+ /@babel/helper-compilation-targets@7.28.6:
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/compat-data': 7.28.6
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+ dev: true
+
+ /@babel/helper-globals@7.28.0:
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-module-imports@7.28.6:
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6):
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/helper-plugin-utils@7.28.6:
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-string-parser@7.27.1:
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-identifier@7.28.5:
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-option@7.27.1:
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helpers@7.28.6:
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+ dev: true
+
+ /@babel/parser@7.28.6:
+ resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.28.6
+ dev: true
+
+ /@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6):
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ dev: true
+
+ /@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6):
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+ dev: true
+
+ /@babel/template@7.28.6:
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ dev: true
+
+ /@babel/traverse@7.28.6:
+ resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/types@7.28.6:
+ resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ dev: true
+
+ /@esbuild/aix-ppc64@0.21.5:
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-arm64@0.21.5:
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-arm@0.21.5:
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-x64@0.21.5:
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-arm64@0.21.5:
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-x64@0.21.5:
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-arm64@0.21.5:
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-x64@0.21.5:
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm64@0.21.5:
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm@0.21.5:
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ia32@0.21.5:
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-loong64@0.21.5:
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-mips64el@0.21.5:
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ppc64@0.21.5:
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-riscv64@0.21.5:
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-s390x@0.21.5:
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-x64@0.21.5:
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/netbsd-x64@0.21.5:
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/openbsd-x64@0.21.5:
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/sunos-x64@0.21.5:
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-arm64@0.21.5:
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-ia32@0.21.5:
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-x64@0.21.5:
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@jridgewell/gen-mapping@0.3.13:
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+ dev: true
+
+ /@jridgewell/remapping@2.3.5:
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ dev: true
+
+ /@jridgewell/resolve-uri@3.1.2:
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+ dev: true
+
+ /@jridgewell/sourcemap-codec@1.5.5:
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+ dev: true
+
+ /@jridgewell/trace-mapping@0.3.31:
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+ dev: true
+
+ /@nodelib/fs.scandir@2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat@2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk@1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+ dev: true
+
+ /@remix-run/router@1.23.2:
+ resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==}
+ engines: {node: '>=14.0.0'}
+ dev: false
+
+ /@rolldown/pluginutils@1.0.0-beta.27:
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+ dev: true
+
+ /@rollup/rollup-android-arm-eabi@4.56.0:
+ resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-android-arm64@4.56.0:
+ resolution: {integrity: sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-arm64@4.56.0:
+ resolution: {integrity: sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-darwin-x64@4.56.0:
+ resolution: {integrity: sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-freebsd-arm64@4.56.0:
+ resolution: {integrity: sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-freebsd-x64@4.56.0:
+ resolution: {integrity: sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-gnueabihf@4.56.0:
+ resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-musleabihf@4.56.0:
+ resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-gnu@4.56.0:
+ resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-musl@4.56.0:
+ resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-loong64-gnu@4.56.0:
+ resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-loong64-musl@4.56.0:
+ resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-ppc64-gnu@4.56.0:
+ resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-ppc64-musl@4.56.0:
+ resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-gnu@4.56.0:
+ resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-musl@4.56.0:
+ resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-s390x-gnu@4.56.0:
+ resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-gnu@4.56.0:
+ resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-musl@4.56.0:
+ resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-openbsd-x64@4.56.0:
+ resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-openharmony-arm64@4.56.0:
+ resolution: {integrity: sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==}
+ cpu: [arm64]
+ os: [openharmony]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-arm64-msvc@4.56.0:
+ resolution: {integrity: sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-ia32-msvc@4.56.0:
+ resolution: {integrity: sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-gnu@4.56.0:
+ resolution: {integrity: sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-msvc@4.56.0:
+ resolution: {integrity: sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@types/babel__core@7.20.5:
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+ dev: true
+
+ /@types/babel__generator@7.27.0:
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+ dependencies:
+ '@babel/types': 7.28.6
+ dev: true
+
+ /@types/babel__template@7.4.4:
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ dev: true
+
+ /@types/babel__traverse@7.28.0:
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+ dependencies:
+ '@babel/types': 7.28.6
+ dev: true
+
+ /@types/estree@1.0.8:
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ dev: true
+
+ /@types/node@20.19.30:
+ resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==}
+ dependencies:
+ undici-types: 6.21.0
+ dev: true
+
+ /@types/prop-types@15.7.15:
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ /@types/react-dom@18.3.7(@types/react@18.3.27):
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+ dependencies:
+ '@types/react': 18.3.27
+ dev: true
+
+ /@types/react@18.3.27:
+ resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
+ /@vitejs/plugin-react@4.7.0(vite@5.4.21):
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 5.4.21(@types/node@20.19.30)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+ dev: true
+
+ /anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+ dev: true
+
+ /arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+ dev: true
+
+ /autoprefixer@10.4.23(postcss@8.5.6):
+ resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+ dependencies:
+ browserslist: 4.28.1
+ caniuse-lite: 1.0.30001766
+ fraction.js: 5.3.4
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /baseline-browser-mapping@2.9.17:
+ resolution: {integrity: sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==}
+ hasBin: true
+ dev: true
+
+ /binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.1.1
+ dev: true
+
+ /browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ baseline-browser-mapping: 2.9.17
+ caniuse-lite: 1.0.30001766
+ electron-to-chromium: 1.5.278
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+ dev: true
+
+ /camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /caniuse-lite@1.0.30001766:
+ resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==}
+ dev: true
+
+ /chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ dev: true
+
+ /cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ /date-fns@3.6.0:
+ resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
+ dev: false
+
+ /debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.3
+ dev: true
+
+ /decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+ dev: false
+
+ /didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ dev: true
+
+ /dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+ dev: true
+
+ /electron-to-chromium@1.5.278:
+ resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==}
+ dev: true
+
+ /esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+ dev: true
+
+ /escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+ dev: true
+
+ /fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+ dependencies:
+ reusify: 1.1.0
+ dev: true
+
+ /fdir@6.5.0(picomatch@4.0.3):
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ dependencies:
+ picomatch: 4.0.3
+ dev: true
+
+ /fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+ dev: true
+
+ /fraction.js@5.3.4:
+ resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
+ dev: true
+
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ dev: true
+
+ /gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ function-bind: 1.1.2
+ dev: true
+
+ /is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ binary-extensions: 2.3.0
+ dev: true
+
+ /is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ hasown: 2.0.2
+ dev: true
+
+ /is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
+ /is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+ dev: true
+
+ /jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+ dev: true
+
+ /js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ /jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+ dev: true
+
+ /json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+ dev: true
+
+ /lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+ dev: true
+
+ /lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ dev: true
+
+ /loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+ dependencies:
+ js-tokens: 4.0.0
+ dev: false
+
+ /lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ dependencies:
+ yallist: 3.1.1
+ dev: true
+
+ /merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+ dev: true
+
+ /ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ dev: true
+
+ /mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+ dev: true
+
+ /nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
+ /node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+ dev: true
+
+ /normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ dev: true
+
+ /picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+ dev: true
+
+ /picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+ dev: true
+
+ /picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+ dev: true
+
+ /pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /postcss-import@15.1.0(postcss@8.5.6):
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.5.6
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.11
+ dev: true
+
+ /postcss-js@4.1.0(postcss@8.5.6):
+ resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.6
+ dev: true
+
+ /postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6):
+ resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ jiti: '>=1.21.0'
+ postcss: '>=8.0.9'
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+ postcss:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+ dependencies:
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ postcss: 8.5.6
+ dev: true
+
+ /postcss-nested@6.2.0(postcss@8.5.6):
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+ dependencies:
+ postcss: 8.5.6
+ postcss-selector-parser: 6.1.2
+ dev: true
+
+ /postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+ dev: true
+
+ /postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ dev: true
+
+ /postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+ dev: true
+
+ /queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
+ /react-dom@18.3.1(react@18.3.1):
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+ dev: false
+
+ /react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /react-router-dom@6.30.3(react-dom@18.3.1)(react@18.3.1):
+ resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+ dependencies:
+ '@remix-run/router': 1.23.2
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-router: 6.30.3(react@18.3.1)
+ dev: false
+
+ /react-router@6.30.3(react@18.3.1):
+ resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ dependencies:
+ '@remix-run/router': 1.23.2
+ react: 18.3.1
+ dev: false
+
+ /react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+ dependencies:
+ pify: 2.3.0
+ dev: true
+
+ /readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
+ /resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+ dev: true
+
+ /reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
+ /rollup@4.56.0:
+ resolution: {integrity: sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.56.0
+ '@rollup/rollup-android-arm64': 4.56.0
+ '@rollup/rollup-darwin-arm64': 4.56.0
+ '@rollup/rollup-darwin-x64': 4.56.0
+ '@rollup/rollup-freebsd-arm64': 4.56.0
+ '@rollup/rollup-freebsd-x64': 4.56.0
+ '@rollup/rollup-linux-arm-gnueabihf': 4.56.0
+ '@rollup/rollup-linux-arm-musleabihf': 4.56.0
+ '@rollup/rollup-linux-arm64-gnu': 4.56.0
+ '@rollup/rollup-linux-arm64-musl': 4.56.0
+ '@rollup/rollup-linux-loong64-gnu': 4.56.0
+ '@rollup/rollup-linux-loong64-musl': 4.56.0
+ '@rollup/rollup-linux-ppc64-gnu': 4.56.0
+ '@rollup/rollup-linux-ppc64-musl': 4.56.0
+ '@rollup/rollup-linux-riscv64-gnu': 4.56.0
+ '@rollup/rollup-linux-riscv64-musl': 4.56.0
+ '@rollup/rollup-linux-s390x-gnu': 4.56.0
+ '@rollup/rollup-linux-x64-gnu': 4.56.0
+ '@rollup/rollup-linux-x64-musl': 4.56.0
+ '@rollup/rollup-openbsd-x64': 4.56.0
+ '@rollup/rollup-openharmony-arm64': 4.56.0
+ '@rollup/rollup-win32-arm64-msvc': 4.56.0
+ '@rollup/rollup-win32-ia32-msvc': 4.56.0
+ '@rollup/rollup-win32-x64-gnu': 4.56.0
+ '@rollup/rollup-win32-x64-msvc': 4.56.0
+ fsevents: 2.3.3
+ dev: true
+
+ /run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
+ /scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+ dev: true
+
+ /source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /sucrase@3.35.1:
+ resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ commander: 4.1.1
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.7
+ tinyglobby: 0.2.15
+ ts-interface-checker: 0.1.13
+ dev: true
+
+ /supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /tailwindcss@3.4.19:
+ resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 3.1.3
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-import: 15.1.0(postcss@8.5.6)
+ postcss-js: 4.1.0(postcss@8.5.6)
+ postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)
+ postcss-nested: 6.2.0(postcss@8.5.6)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.11
+ sucrase: 3.35.1
+ transitivePeerDependencies:
+ - tsx
+ - yaml
+ dev: true
+
+ /thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+ dependencies:
+ thenify: 3.3.1
+ dev: true
+
+ /thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+ dependencies:
+ any-promise: 1.3.0
+ dev: true
+
+ /tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ dev: true
+
+ /to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+ dev: true
+
+ /ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+ dev: true
+
+ /turbo-darwin-64@1.13.4:
+ resolution: {integrity: sha512-A0eKd73R7CGnRinTiS7txkMElg+R5rKFp9HV7baDiEL4xTG1FIg/56Vm7A5RVgg8UNgG2qNnrfatJtb+dRmNdw==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-darwin-arm64@1.13.4:
+ resolution: {integrity: sha512-eG769Q0NF6/Vyjsr3mKCnkG/eW6dKMBZk6dxWOdrHfrg6QgfkBUk0WUUujzdtVPiUIvsh4l46vQrNVd9EOtbyA==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-linux-64@1.13.4:
+ resolution: {integrity: sha512-Bq0JphDeNw3XEi+Xb/e4xoKhs1DHN7OoLVUbTIQz+gazYjigVZvtwCvgrZI7eW9Xo1eOXM2zw2u1DGLLUfmGkQ==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-linux-arm64@1.13.4:
+ resolution: {integrity: sha512-BJcXw1DDiHO/okYbaNdcWN6szjXyHWx9d460v6fCHY65G8CyqGU3y2uUTPK89o8lq/b2C8NK0yZD+Vp0f9VoIg==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-windows-64@1.13.4:
+ resolution: {integrity: sha512-OFFhXHOFLN7A78vD/dlVuuSSVEB3s9ZBj18Tm1hk3aW1HTWTuAw0ReN6ZNlVObZUHvGy8d57OAGGxf2bT3etQw==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo-windows-arm64@1.13.4:
+ resolution: {integrity: sha512-u5A+VOKHswJJmJ8o8rcilBfU5U3Y1TTAfP9wX8bFh8teYF1ghP0EhtMRLjhtp6RPa+XCxHHVA2CiC3gbh5eg5g==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /turbo@1.13.4:
+ resolution: {integrity: sha512-1q7+9UJABuBAHrcC4Sxp5lOqYS5mvxRrwa33wpIyM18hlOCpRD/fTJNxZ0vhbMcJmz15o9kkVm743mPn7p6jpQ==}
+ hasBin: true
+ optionalDependencies:
+ turbo-darwin-64: 1.13.4
+ turbo-darwin-arm64: 1.13.4
+ turbo-linux-64: 1.13.4
+ turbo-linux-arm64: 1.13.4
+ turbo-windows-64: 1.13.4
+ turbo-windows-arm64: 1.13.4
+ dev: true
+
+ /typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
+
+ /undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ dev: true
+
+ /update-browserslist-db@1.2.3(browserslist@4.28.1):
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+ dev: true
+
+ /use-sync-external-store@1.6.0(react@18.3.1):
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ dependencies:
+ react: 18.3.1
+ dev: false
+
+ /util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ dev: true
+
+ /vite@5.4.21(@types/node@20.19.30):
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ '@types/node': 20.19.30
+ esbuild: 0.21.5
+ postcss: 8.5.6
+ rollup: 4.56.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ dev: true
+
+ /zustand@4.5.7(@types/react@18.3.27)(react@18.3.1):
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ dependencies:
+ '@types/react': 18.3.27
+ react: 18.3.1
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ dev: false
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..e9b0dad
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - 'apps/*'
+ - 'packages/*'
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..d9dd954
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "allowJs": true,
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "composite": true,
+ "incremental": true,
+ "jsx": "react-jsx",
+ "baseUrl": ".",
+ "paths": {
+ "@brazil-swift-ops/types": ["./packages/types/src"],
+ "@brazil-swift-ops/utils": ["./packages/utils/src"],
+ "@brazil-swift-ops/rules-engine": ["./packages/rules-engine/src"],
+ "@brazil-swift-ops/iso20022": ["./packages/iso20022/src"],
+ "@brazil-swift-ops/treasury": ["./packages/treasury/src"],
+ "@brazil-swift-ops/risk-models": ["./packages/risk-models/src"],
+ "@brazil-swift-ops/audit": ["./packages/audit/src"]
+ }
+ },
+ "exclude": ["node_modules", "dist", "build"]
+}
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 0000000..e0c0bb6
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "globalDependencies": ["**/.env.*local"],
+ "pipeline": {
+ "build": {
+ "dependsOn": ["^build"],
+ "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
+ },
+ "dev": {
+ "cache": false,
+ "persistent": true
+ },
+ "lint": {
+ "dependsOn": ["^build"]
+ },
+ "type-check": {
+ "dependsOn": ["^build"]
+ },
+ "test": {
+ "dependsOn": ["^build"]
+ },
+ "clean": {
+ "cache": false
+ }
+ }
+}