Skip to content

Commit

Permalink
Merge pull request #141 from mojaloop/rules-fix
Browse files Browse the repository at this point in the history
fix failing rules
  • Loading branch information
johanfol authored Jan 26, 2021
2 parents da6c76c + e0f049e commit ff333c3
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 194 deletions.
101 changes: 52 additions & 49 deletions historical-data-store/src/data/historical-data-1607569708.csv

Large diffs are not rendered by default.

114 changes: 55 additions & 59 deletions typology-11/src/rules/co-located-parties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,84 @@ import { RedisClient } from 'redis';
import { get } from '../redis-client';

const getPayeesFromILPsArray = async (outgoingTransfersClient: RedisClient, ILPS: string[]): Promise<any> => {
const accountsTransfers = await Promise.all(
ILPS.map((ILP: string) => new Promise((resolve) => {
resolve(get(outgoingTransfersClient, ILP));
}))
);
const accountsTransfers = await Promise.all(
ILPS.map((ILP: string) => new Promise((resolve) => {
resolve(get(outgoingTransfersClient, ILP));
}))
);

if (accountsTransfers == undefined || accountsTransfers.length == 0) return undefined;
if (accountsTransfers == undefined || accountsTransfers.length == 0) return undefined;

const payeeNames = accountsTransfers.reduce((payeesAcc: any, payee: any) => {
if (payee === null) return payeesAcc;
const payeeTransfers = JSON.parse(payee);
if (payeeTransfers.length < 1) return payeesAcc;
const payeeNames = payeeTransfers.map((transfer: any) => transfer.ILPDestinationAccountAddress);
return [...payeesAcc, ...payeeNames];
}, []);

return { payeeNames, accountsTransfers };
return accountsTransfers.reduce((payeesAcc: any, payee: any) => {
if (payee === null) return payeesAcc;
const payeeTransfers = JSON.parse(payee);
if (payeeTransfers.length < 1) return payeesAcc;
const payeeNames = payeeTransfers.map((transfer: any) => transfer.ILPDestinationAccountAddress);
return [...payeesAcc, ...payeeNames];
}, []);
}

const removeDuplicates = (names: any) => names.filter((c: any, index: number) => {
return names.indexOf(c) === index;
return names.indexOf(c) === index;
});

const removeValuesFromSecondArray = (firstArr: any, names: any) => names.filter((c: any, index: number) => {
return firstArr.indexOf(c) === index;
return firstArr.indexOf(c) === index;
});

const deg2rad = (num: number): number => (num * Math.PI) / 180;

const distance = (location1: number[], location2: number[], useMiles = false): number => {
const coreRadius = 6371; // km
const dLat: number = deg2rad(location2[0] - location1[0]);
const dLon: number = deg2rad(location2[1] - location1[1]);
const a: number = Math.sin(dLat / 2)
* Math.sin(dLat / 2)
+ Math.cos(deg2rad(location1[0]))
* Math.cos(deg2rad(location2[0]))
* Math.sin(dLon / 2)
* Math.sin(dLon / 2);
const c: number = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
let d: number = Math.round(coreRadius * c);
if (useMiles) { // returns miles instead of kilometers
d = Math.round(d * 0.621371192);
}
return d;
const coreRadius = 6371; // km
const dLat: number = deg2rad(location2[0] - location1[0]);
const dLon: number = deg2rad(location2[1] - location1[1]);
const a: number = Math.sin(dLat / 2)
* Math.sin(dLat / 2)
+ Math.cos(deg2rad(location1[0]))
* Math.cos(deg2rad(location2[0]))
* Math.sin(dLon / 2)
* Math.sin(dLon / 2);
const c: number = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
let d: number = Math.round(coreRadius * c);
if (useMiles) { // returns miles instead of kilometers
d = Math.round(d * 0.621371192);
}
return d;
};

const handleCoLocatedParties = async (transfer: any,
outgoingTransfersClient: RedisClient,
historicalData: any) => {
if (historicalData == undefined || historicalData.length == 0) return false;

const payeesList = historicalData.map((transfer: any) => transfer.ILPDestinationAccountAddress);
payeesList.push(transfer.ILPDestinationAccountAddress);
const uniqueNames = removeDuplicates(payeesList);
outgoingTransfersClient: RedisClient,
historicalData: any) => {
if (historicalData == undefined || historicalData.length == 0) return false;

// get Payees names - tier 1
const tier1Payees = await getPayeesFromILPsArray(outgoingTransfersClient, uniqueNames);
if (tier1Payees != undefined) {
payeesList.push(tier1Payees.payeeNames);
uniqueNames.push(removeDuplicates(tier1Payees.payeeNames));
}
const payeesList = historicalData.map((transfer: any) => transfer.ILPDestinationAccountAddress);
payeesList.push(transfer.ILPDestinationAccountAddress);
const uniqueNames = removeDuplicates(payeesList);

// get Payees names - tier 1
const tier1Payees = await getPayeesFromILPsArray(outgoingTransfersClient, uniqueNames);
if (tier1Payees !== undefined) {
payeesList.push(tier1Payees);
uniqueNames.push(removeDuplicates(tier1Payees));
// get Payees names - tier 2
const uniqueTier2Names = removeValuesFromSecondArray(uniqueNames, tier1Payees);
const tier2Payees = await getPayeesFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier2Names));
if (tier2Payees != undefined) {
payeesList.push(tier2Payees.payeeNames);
uniqueNames.push(removeDuplicates(tier2Payees.payeeNames));
}

// get Payees names - tier 3
const uniqueTier3Names = removeValuesFromSecondArray(uniqueNames, tier2Payees);
const tier3Payees = await getPayeesFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier3Names));
if (tier3Payees != undefined) {
payeesList.push(tier3Payees.payeeNames);
uniqueNames.push(removeDuplicates(tier3Payees.payeeNames));
if (tier2Payees !== undefined) {
payeesList.push(tier2Payees);
uniqueNames.push(removeDuplicates(tier2Payees));
// get Payees names - tier 3
const uniqueTier3Names = removeValuesFromSecondArray(uniqueNames, tier2Payees);
const tier3Payees = await getPayeesFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier3Names));
if (tier3Payees !== undefined) {
payeesList.push(tier3Payees);
uniqueNames.push(removeDuplicates(tier3Payees));
}
}
}

if (payeesList.length < 8) return false;
const uniqueEntitiesPercentage = uniqueNames.length / payeesList.length * 100;
return (uniqueEntitiesPercentage > 33.33) ? true : false;
if (payeesList.length < 8) return false;
const uniqueEntitiesPercentage = uniqueNames.length / payeesList.length * 100;
return (uniqueEntitiesPercentage > 33.33) ? true : false;
}

export default handleCoLocatedParties;
39 changes: 25 additions & 14 deletions typology-11/src/rules/transaction-mirroring.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
const isLessThanADay = (currentTransferDate: Date, transaction: any) => {
const oldTransferDate = new Date(transaction.HTTPTransactionDate);
return ((currentTransferDate.getHours() - oldTransferDate.getHours()) <= 24);
return ((currentTransferDate.getTime() - oldTransferDate.getTime()) <= 24);
}

const isOlder = (
firstTransferDate: Date,
secondTransferDate: Date
) => (firstTransferDate.getHours() - secondTransferDate.getHours() < 0);
): boolean => (firstTransferDate.getTime() - secondTransferDate.getTime() > 0);

const countDuplicates = (original: number[]) => {
const uniqueItems = new Set();
const duplicates = new Set();
let duplicates = 0
for (const value of original) {
if (uniqueItems.has(value)) {
duplicates.add(value);
uniqueItems.delete(value);
duplicates++
} else {
uniqueItems.add(value);
}
}
return duplicates.size;
return duplicates;
}

const handleTransactionMirroring = (
Expand All @@ -28,20 +27,25 @@ const handleTransactionMirroring = (
payeeHistoricalReceiveData: any,
): boolean => {
if (payeeHistoricalSendData == undefined || payeeHistoricalSendData.length < 1) return false;
if (payeeHistoricalReceiveData == undefined || payeeHistoricalReceiveData.length < 1) return false;

const { HTTPTransactionDate } = transfer;
const currentTransferDate = new Date(HTTPTransactionDate);

// Get all transactions received and made from the Payee account over the last day
const lastReceivedTransactions = payeeHistoricalSendData.filter((transaction: any) =>
const receivedTransactions = payeeHistoricalSendData.filter((transaction: any) =>
isLessThanADay(currentTransferDate, transaction));
const lastOutgoingTransactions = payeeHistoricalReceiveData.filter((transaction: any) =>
const outgoingTransactions = payeeHistoricalReceiveData.filter((transaction: any) =>
isLessThanADay(currentTransferDate, transaction));

const mirroringValues = lastOutgoingTransactions.reduce((acc: any, outgoingTransfer: any) => {
// Map through each transaction and return the relevant values for the transactions
// that have repeated Amounts and percentages across the entire list. this will map each
// Transaction and verify if the later transactions have similar values. Then repeat until the end.
const mirroringValues = outgoingTransactions.reduce((acc: any, outgoingTransfer: any) => {
const outgoingTransferDate = new Date(outgoingTransfer.HTTPTransactionDate);

// Get all outgoing transactions that are older than the currently received transaction.
const transfersMadeAfterReceived = lastReceivedTransactions
const transfersMadeAfterReceived = receivedTransactions
.filter((receivedTransfer: any) => isOlder(outgoingTransferDate, new Date(receivedTransfer.HTTPTransactionDate)));

// Compare current transfers amount
Expand All @@ -56,20 +60,27 @@ const handleTransactionMirroring = (
amountDifference
}
});

const trueMirroringTransfers = possibleMirroringTransfers
.filter((result: any) => result.is90Percentile ? true : false);
.filter((result: any) => (result && result.is90Percentile) ? true : false);
if (trueMirroringTransfers.length > 0) {
trueMirroringTransfers.forEach((mirroringResult: any) => acc.push(mirroringResult))
}
return acc;
}, []);
const mirroringFlatValues = mirroringValues.flat(1);

const percentages = mirroringFlatValues.map((transferValue: any) => {
return transferValue.amountDifferencePercent
});

const percentages = mirroringValues.map((value: any) => value.amountDifferencePercent);
const amounts = mirroringValues.map((value: any) => value.amountDifference);
const amounts = mirroringFlatValues.map((transferValue: any) => {
return transferValue.amountDifference
});

const repeatedPercentagesCount = countDuplicates(percentages);
const repeatedAmountsCount = countDuplicates(amounts);
return (repeatedPercentagesCount > 1 || repeatedAmountsCount > 1) ? true : false;
};

export default handleTransactionMirroring;
export default handleTransactionMirroring;
39 changes: 19 additions & 20 deletions typology-11/src/rules/transactions-between-parties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ const getTransfersFromILPsArray = async (outgoingTransfersClient: RedisClient, I

if (accountsTransfers == undefined || accountsTransfers.length == 0) return undefined;

const payees = accountsTransfers.reduce((payeesAcc: any, payee: any) => {
return accountsTransfers.reduce((payeesAcc: any, payee: any) => {
if (payee === null) return payeesAcc;
const payeeTransfers = JSON.parse(payee);
if (payeeTransfers.length < 1) return payeesAcc;
const payeeNames = payeeTransfers.map((transfer: any) => transfer.ILPDestinationAccountAddress);
return [...payeesAcc, ...payeeNames];
}, []);
return { payees, accountsTransfers };
}

const removeDuplicates = (names: any) => names.filter((c: any, index: number) => {
Expand All @@ -37,30 +36,30 @@ const handleTransactionsBetweenParties = async (
if (historicalData == undefined || historicalData.length == 0) return false;

const payeesList = historicalData.map((transfer: any) => transfer.ILPDestinationAccountAddress);
payeesList.push(transfer.d);
payeesList.push(transfer.ILPDestinationAccountAddress);
const uniqueNames = removeDuplicates(payeesList);

// get Payess names - tier 1
const tier1Payees = await getTransfersFromILPsArray(outgoingTransfersClient, uniqueNames);
if (tier1Payees != undefined) {
payeesList.push(tier1Payees.payees);
uniqueNames.push(removeDuplicates(tier1Payees.payees));
}
if (tier1Payees !== undefined) {
payeesList.push(tier1Payees);
uniqueNames.push(removeDuplicates(tier1Payees));

// get Payess names - tier 2
const uniqueTier2Names = removeValuesFromSecondArray(uniqueNames, tier1Payees.payees);
const tier2Payees = await getTransfersFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier2Names));
if (tier2Payees != undefined) {
payeesList.push(tier2Payees.payees);
uniqueNames.push(removeDuplicates(tier2Payees.payees));
}
// get Payess names - tier 2
const uniqueTier2Names = removeValuesFromSecondArray(uniqueNames, tier1Payees);
const tier2Payees = await getTransfersFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier2Names));
if (tier2Payees !== undefined) {
payeesList.push(tier2Payees);
uniqueNames.push(removeDuplicates(tier2Payees));

// get Payess names - tier 3
const uniqueTier3Names = removeValuesFromSecondArray(uniqueNames, tier2Payees.payees);
const tier3Payees = await getTransfersFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier3Names));
if (tier3Payees != undefined) {
payeesList.push(tier3Payees.payees);
uniqueNames.push(removeDuplicates(tier3Payees.payees));
// get Payess names - tier 3
const uniqueTier3Names = removeValuesFromSecondArray(uniqueNames, tier2Payees);
const tier3Payees = await getTransfersFromILPsArray(outgoingTransfersClient, removeDuplicates(uniqueTier3Names));
if (tier3Payees != undefined) {
payeesList.push(tier3Payees);
uniqueNames.push(removeDuplicates(tier3Payees));
}
}
}

if (payeesList.length < 8) return false;
Expand Down
3 changes: 1 addition & 2 deletions typology-11/src/scoring-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ const handleScores = (scores: any, topic: string, TransactionID: string, transac


publish(topic, `"typology":"typology-11","transactionID":"${TransactionID}","score":${score},"createDate":${transactionDate},
"textResult":"Typology 11 score is ${score}, Reason: ${
+ (scores.rule17 ? 'Transaction Divergence, ' : '')
"textResult":"Typology 11 score is ${score}, Reason: ${+ (scores.rule17 ? 'Transaction Divergence, ' : '')
+ (scores.rule27 ? 'Transaction Mirroring, ' : '')
+ (scores.rule86 ? 'Transaction Between Parties, ' : '')
+ (scores.rule87 ? 'Co-located Parties' : '')
Expand Down
Loading

0 comments on commit ff333c3

Please sign in to comment.