tune up transaction generator

This commit is contained in:
kacperlo 2025-06-10 15:49:43 +02:00
parent 06f79923bb
commit 41abb20094

View File

@ -8,63 +8,70 @@ import java.util.concurrent.ThreadLocalRandom;
public class TransactionGenerator { public class TransactionGenerator {
private static final int NUM_CARDS = 10000; private static final int NUM_CARDS = 10000;
private static final int NUM_USERS = 5000; // Assuming each user has ~2 cards on average private static final int NUM_USERS = 5000; // Assuming each user has ~2 cards on average
private static final Map<String, Set<Double[]>> cardLocations = new HashMap<>(); private static final Map<String, Set<LocationPoint>> cardLocations = new HashMap<>();
private static final Map<String, Double> cardLimits = new HashMap<>(); private static final Map<String, Double> cardLimits = new HashMap<>();
private static final Map<String, String> cardToUser = new HashMap<>(); private static final Map<String, String> cardToUser = new HashMap<>();
private static final List<String> cardIds = new ArrayList<>(); private static final List<String> cardIds = new ArrayList<>();
private static final List<String> userIds = new ArrayList<>(); private static final List<String> userIds = new ArrayList<>();
// Anomaly types // Anomaly types
private static final int ANOMALY_NONE = 0; private static final int ANOMALY_NONE = 0;
private static final int ANOMALY_AMOUNT = 1; private static final int ANOMALY_AMOUNT = 1;
private static final int ANOMALY_LOCATION = 2; private static final int ANOMALY_LOCATION = 2;
private static final int ANOMALY_FREQUENCY = 3; private static final int ANOMALY_FREQUENCY = 3;
// Probability of generating an anomaly (1%) // Probability of generating an anomaly (1%)
private static final double ANOMALY_PROBABILITY = 0.5; private static final double ANOMALY_PROBABILITY = 0.5;
// Initialize card and user data // Initialize card and user data
static { static {
// Generate user IDs // Generate user IDs
for (int i = 0; i < NUM_USERS; i++) { for (int i = 0; i < NUM_USERS; i++) {
userIds.add("USER_" + String.format("%05d", i)); userIds.add("USER_" + String.format("%05d", i));
} }
// Generate card IDs and assign to users // Generate card IDs and assign to users
for (int i = 0; i < NUM_CARDS; i++) { for (int i = 0; i < NUM_CARDS; i++) {
String cardId = "CARD_" + String.format("%05d", i); String cardId = "CARD_" + String.format("%05d", i);
cardIds.add(cardId); cardIds.add(cardId);
String userId = userIds.get(ThreadLocalRandom.current().nextInt(NUM_USERS)); String userId = userIds.get(ThreadLocalRandom.current().nextInt(NUM_USERS));
cardToUser.put(cardId, userId); cardToUser.put(cardId, userId);
// Initialize empty set of locations for this card // Initialize empty set of locations for this card
cardLocations.put(cardId, new HashSet<>()); cardLocations.put(cardId, new HashSet<>());
// Assign random limit between $1,000 and $20,000 // Assign random limit between $1,000 and $20,000
cardLimits.put(cardId, 1000.0 + ThreadLocalRandom.current().nextDouble() * 19000.0); cardLimits.put(cardId, 1000.0 + ThreadLocalRandom.current().nextDouble() * 19000.0);
} }
} }
public Transaction generateTransaction(boolean forceAnomaly, int anomalyType) { public Transaction generateTransaction(boolean forceAnomaly, int anomalyType) {
// Select a random card // Select a random card
String cardId = cardIds.get(ThreadLocalRandom.current().nextInt(NUM_CARDS)); String cardId = cardIds.get(ThreadLocalRandom.current().nextInt(NUM_CARDS));
String userId = cardToUser.get(cardId); String userId = cardToUser.get(cardId);
double availableLimit = cardLimits.get(cardId); double availableLimit = cardLimits.get(cardId);
// Determine if this should be an anomaly // Determine if this should be an anomaly
int actualAnomalyType = ANOMALY_NONE; int actualAnomalyType = ANOMALY_NONE;
if (forceAnomaly) { if (forceAnomaly) {
actualAnomalyType = (anomalyType >= 0 && anomalyType <= 3) ? actualAnomalyType = (anomalyType >= 0 && anomalyType <= 3) ?
anomalyType : ThreadLocalRandom.current().nextInt(1, 4); anomalyType : ThreadLocalRandom.current().nextInt(1, 4);
} else if (ThreadLocalRandom.current().nextDouble() < ANOMALY_PROBABILITY) { } else if (ThreadLocalRandom.current().nextDouble() < ANOMALY_PROBABILITY) {
actualAnomalyType = ThreadLocalRandom.current().nextInt(1, 4); double roll = ThreadLocalRandom.current().nextDouble();
if (roll < 0.3) {
actualAnomalyType = ANOMALY_LOCATION;
} else if (roll < 0.8) {
actualAnomalyType = ANOMALY_AMOUNT;
} else {
actualAnomalyType = ANOMALY_FREQUENCY;
}
} }
// Get or generate location // Get or generate location
Double[] location = getLocationForCard(cardId, actualAnomalyType == ANOMALY_LOCATION); LocationPoint location = getLocationForCard(cardId, actualAnomalyType == ANOMALY_LOCATION);
double latitude = location[0]; double latitude = location.latitude;
double longitude = location[1]; double longitude = location.longitude;
// Generate transaction amount // Generate transaction amount
double amount; double amount;
if (actualAnomalyType == ANOMALY_AMOUNT) { if (actualAnomalyType == ANOMALY_AMOUNT) {
@ -74,11 +81,11 @@ public class TransactionGenerator {
// Normal amount (1-10% of available limit) // Normal amount (1-10% of available limit)
amount = availableLimit * (0.01 + ThreadLocalRandom.current().nextDouble() * 0.09); amount = availableLimit * (0.01 + ThreadLocalRandom.current().nextDouble() * 0.09);
} }
// Update available limit // Update available limit
double newLimit = availableLimit - amount; double newLimit = availableLimit - amount;
cardLimits.put(cardId, newLimit > 0 ? newLimit : 0); cardLimits.put(cardId, newLimit > 0 ? newLimit : 0);
// Create transaction // Create transaction
Transaction transaction = new Transaction( Transaction transaction = new Transaction(
cardId, cardId,
@ -89,56 +96,56 @@ public class TransactionGenerator {
newLimit, newLimit,
Instant.now() Instant.now()
); );
return transaction; return transaction;
} }
private Double[] getLocationForCard(String cardId, boolean generateAnomaly) { private LocationPoint getLocationForCard(String cardId, boolean generateAnomaly) {
Set<Double[]> locations = cardLocations.get(cardId); Set<LocationPoint> locations = cardLocations.get(cardId);
if (locations.isEmpty() || generateAnomaly) { if (locations.isEmpty() || generateAnomaly) {
// Generate a random worldwide location // Generate a random worldwide location
double latitude = ThreadLocalRandom.current().nextDouble(-90, 90); double latitude = ThreadLocalRandom.current().nextDouble(-90, 90);
double longitude = ThreadLocalRandom.current().nextDouble(-180, 180); double longitude = ThreadLocalRandom.current().nextDouble(-180, 180);
Double[] newLocation = {latitude, longitude}; LocationPoint newLocation = new LocationPoint(latitude, longitude);
// Store this location for future use unless it's an anomaly // Store this location for future use unless it's an anomaly
if (!generateAnomaly) { if (!generateAnomaly) {
locations.add(newLocation); locations.add(newLocation);
} }
return newLocation; return newLocation;
} else { } else {
// Pick a random location from the card's history // Pick a random location from the card's history
Double[][] locArray = locations.toArray(new Double[0][]); LocationPoint[] locArray = locations.toArray(new LocationPoint[0]);
return locArray[ThreadLocalRandom.current().nextInt(locArray.length)]; return locArray[ThreadLocalRandom.current().nextInt(locArray.length)];
} }
} }
// Method to simulate frequency anomaly by generating multiple transactions in short succession // Method to simulate frequency anomaly by generating multiple transactions in short succession
public List<Transaction> generateFrequencyAnomaly(String specificCardId) { public List<Transaction> generateFrequencyAnomaly(String specificCardId) {
List<Transaction> transactions = new ArrayList<>(); List<Transaction> transactions = new ArrayList<>();
String cardId = specificCardId != null ? String cardId = specificCardId != null ?
specificCardId : cardIds.get(ThreadLocalRandom.current().nextInt(NUM_CARDS)); specificCardId : cardIds.get(ThreadLocalRandom.current().nextInt(NUM_CARDS));
// Generate 5-10 transactions in quick succession // Generate 5-10 transactions in quick succession
int numTransactions = ThreadLocalRandom.current().nextInt(5, 11); int numTransactions = ThreadLocalRandom.current().nextInt(5, 11);
for (int i = 0; i < numTransactions; i++) { for (int i = 0; i < numTransactions; i++) {
transactions.add(generateTransactionForCard(cardId, false, ANOMALY_NONE)); transactions.add(generateTransactionForCard(cardId, false, ANOMALY_NONE));
} }
return transactions; return transactions;
} }
private Transaction generateTransactionForCard(String cardId, boolean forceAnomaly, int anomalyType) { private Transaction generateTransactionForCard(String cardId, boolean forceAnomaly, int anomalyType) {
String userId = cardToUser.get(cardId); String userId = cardToUser.get(cardId);
double availableLimit = cardLimits.get(cardId); double availableLimit = cardLimits.get(cardId);
// Get location // Get location
Double[] location = getLocationForCard(cardId, forceAnomaly && anomalyType == ANOMALY_LOCATION); LocationPoint location = getLocationForCard(cardId, forceAnomaly && anomalyType == ANOMALY_LOCATION);
double latitude = location[0]; double latitude = location.latitude;
double longitude = location[1]; double longitude = location.longitude;
// Generate amount // Generate amount
double amount; double amount;
if (forceAnomaly && anomalyType == ANOMALY_AMOUNT) { if (forceAnomaly && anomalyType == ANOMALY_AMOUNT) {
@ -146,11 +153,11 @@ public class TransactionGenerator {
} else { } else {
amount = availableLimit * (0.01 + ThreadLocalRandom.current().nextDouble() * 0.09); amount = availableLimit * (0.01 + ThreadLocalRandom.current().nextDouble() * 0.09);
} }
// Update available limit // Update available limit
double newLimit = availableLimit - amount; double newLimit = availableLimit - amount;
cardLimits.put(cardId, newLimit > 0 ? newLimit : 0); cardLimits.put(cardId, newLimit > 0 ? newLimit : 0);
return new Transaction( return new Transaction(
cardId, cardId,
userId, userId,
@ -161,4 +168,28 @@ public class TransactionGenerator {
Instant.now() Instant.now()
); );
} }
private static class LocationPoint {
final double latitude;
final double longitude;
LocationPoint(double latitude, double longitude) {
this.latitude = latitude;
this.longitude = longitude;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LocationPoint that = (LocationPoint) o;
return Double.compare(that.latitude, latitude) == 0 &&
Double.compare(that.longitude, longitude) == 0;
}
@Override
public int hashCode() {
return Objects.hash(latitude, longitude);
}
}
} }