Files
pd-guard/src/main/java/ru/pdguard/mask/Synthetic.java
T
Максименко Никита Владимирович 1328a7b234 refactor: подтянуть Legal NER, переформатировать код и добавить тесты
Слияние с 685ec97 (третья ступень NER для юридических реквизитов),
код приведён к google-java-format, добавлены юнит-тесты
AdaptiveConcurrencyLimiter/SystemsConfig/PayloadCipher.
2026-09-23 22:02:45 +03:00

108 lines
4.4 KiB
Java

package ru.pdguard.mask;
import ru.pdguard.detect.PdTypes;
import ru.pdguard.detect.Validators;
/**
* Правдоподобные подставные значения вместо настоящих.
*
* <p>Модель получает текст, который выглядит естественно, и качество ответа страдает меньше, чем от
* звёздочек. Значения детерминированы: одно и то же исходное значение всегда даёт одну и ту же
* подстановку.
*/
final class Synthetic {
private static final String[] SURNAMES = {
"Лаврентьев", "Мещеряков", "Тихомиров", "Ясенев", "Бурмистров", "Кольцов"
};
private static final String[] NAMES = {"Артём", "Никита", "Глеб", "Тимур", "Марк", "Лев"};
private static final String[] PATRONYMICS = {
"Артёмович", "Никитич", "Глебович", "Тимурович", "Маркович", "Львович"
};
private static final String[] DOMAINS = {"example.com", "example.org", "example.net"};
private Synthetic() {}
static String forType(String type, String value, int ordinal) {
int seed = value.hashCode() & Integer.MAX_VALUE;
return switch (type) {
case PdTypes.FIO ->
pick(SURNAMES, seed) + " " + pick(NAMES, seed >> 3) + " " + pick(PATRONYMICS, seed >> 6);
case PdTypes.CARDHOLDER -> "IVAN PETROV";
case PdTypes.EMAIL -> "user" + ordinal + "@" + pick(DOMAINS, seed);
case PdTypes.PHONE ->
"+7 9"
+ digits(seed, 2)
+ " "
+ digits(seed >> 4, 3)
+ "-"
+ digits(seed >> 8, 2)
+ "-"
+ digits(seed >> 12, 2);
case PdTypes.CARD -> luhnCard(seed);
case PdTypes.PASSPORT,
PdTypes.DRIVER_LICENSE,
PdTypes.FOREIGN_PASSPORT,
PdTypes.MILITARY_ID ->
digits(seed, 4) + " " + digits(seed >> 6, 6);
case PdTypes.INN -> digits(seed, 12);
case PdTypes.MEDICAL_POLICY -> digits(seed, 16);
case PdTypes.SNILS ->
digits(seed, 3)
+ "-"
+ digits(seed >> 4, 3)
+ "-"
+ digits(seed >> 8, 3)
+ " "
+ digits(seed >> 12, 2);
case PdTypes.BIRTH_DATE, PdTypes.PASSPORT_DATE, PdTypes.DATE -> syntheticDate(seed);
case PdTypes.ADDRESS_CITY -> "Зареченск";
case PdTypes.ADDRESS_STREET -> "Сосновая";
case PdTypes.ADDRESS_HOUSE -> String.valueOf(1 + Math.floorMod(seed, 90));
case PdTypes.ADDRESS_FLAT -> String.valueOf(1 + Math.floorMod(seed, 200));
case PdTypes.ADDRESS_POSTCODE -> digits(seed, 6);
case PdTypes.ADDRESS_COUNTRY -> "Заречье";
case PdTypes.ADDRESS_REGION -> "Заречная область";
case PdTypes.ADDRESS_DISTRICT -> "Сосновый район";
case PdTypes.CVV -> digits(seed, 3);
case PdTypes.PIN -> digits(seed, 4);
// Для остальных типов правдоподобной замены нет — отдаём токен.
default -> "[" + type + "_" + ordinal + "]";
};
}
private static String pick(String[] options, int seed) {
return options[Math.floorMod(seed, options.length)];
}
private static String syntheticDate(int seed) {
int day = 1 + Math.floorMod(seed, 28);
int month = 1 + Math.floorMod(seed >> 5, 12);
int year = 1960 + Math.floorMod(seed >> 9, 45);
return String.format("%02d.%02d.%d", day, month, year);
}
private static String digits(int seed, int count) {
StringBuilder sb = new StringBuilder(count);
int value = Math.abs(seed);
for (int i = 0; i < count; i++) {
sb.append((char) ('0' + Math.floorMod(value, 10)));
value = value / 10 + (i + 1) * 7;
}
return sb.toString();
}
/** Номер карты, проходящий проверку алгоритмом Луна: подстановка должна выглядеть настоящей. */
private static String luhnCard(int seed) {
StringBuilder body = new StringBuilder("4").append(digits(seed, 14));
body.append(Validators.luhnCheckDigit(body.toString()));
return body.substring(0, 4)
+ " "
+ body.substring(4, 8)
+ " "
+ body.substring(8, 12)
+ " "
+ body.substring(12);
}
}