refactor: подтянуть Legal NER, переформатировать код и добавить тесты

Слияние с 685ec97 (третья ступень NER для юридических реквизитов),
код приведён к google-java-format, добавлены юнит-тесты
AdaptiveConcurrencyLimiter/SystemsConfig/PayloadCipher.
This commit is contained in:
Максименко Никита Владимирович
2026-09-23 22:02:45 +03:00
parent 84b5adcb3f
commit 573d94cca8
79 changed files with 7385 additions and 6430 deletions
+48 -47
View File
@@ -1,5 +1,11 @@
package ru.pdguard;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import ru.pdguard.config.SystemPolicy;
@@ -9,60 +15,55 @@ import ru.pdguard.detect.RuleRegistry;
import ru.pdguard.detect.Span;
import ru.pdguard.mask.Masker;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
/**
* 200 вручную составленных текстовых тестов из {@code dataset-200.txt} — по одному
* предложению на строку, каждое своя отдельная проверка (не сборка одного большого
* текста, как в {@link HugeDatasetTest}). Набор покрывает все типы ПДН из
* {@link RuleRegistry} (кроме ADDRESS_REGION/ADDRESS_DISTRICT — для них нет правил,
* только модель второй ступени), варианты написания (регистр, формат даты, разделяющие
* слова) и несколько строк-ловушек без разметки (известный человек, адрес отделения,
* дата без якоря) — они не должны маскироваться вовсе.
* 200 вручную составленных текстовых тестов из {@code dataset-200.txt} — по одному предложению на
* строку, каждое своя отдельная проверка (не сборка одного большого текста, как в {@link
* HugeDatasetTest}). Набор покрывает все типы ПДН из {@link RuleRegistry} (кроме
* ADDRESS_REGION/ADDRESS_DISTRICT — для них нет правил, только модель второй ступени), варианты
* написания (регистр, формат даты, разделяющие слова) и несколько строк-ловушек без разметки
* (известный человек, адрес отделения, дата без якоря) — они не должны маскироваться вовсе.
*
* <p>На каждой строке: маскирование не оставляет исходное значение ПДН в открытом
* виде, а демаскирование побайтово восстанавливает исходный текст.
* <p>На каждой строке: маскирование не оставляет исходное значение ПДН в открытом виде, а
* демаскирование побайтово восстанавливает исходный текст.
*/
class Dataset200Test {
private static final RuleRegistry REGISTRY = new RuleRegistry();
private static final Masker MASKER = new Masker();
private static final List<BenchmarkFixtures.Sample> DATASET = BenchmarkFixtures.load("/dataset-200.txt");
private static final RuleRegistry REGISTRY = new RuleRegistry();
private static final Masker MASKER = new Masker();
private static final List<BenchmarkFixtures.Sample> DATASET =
BenchmarkFixtures.load("/dataset-200.txt");
@TestFactory
Stream<DynamicTest> datasetOf200Cases() {
List<DynamicTest> cases = new java.util.ArrayList<>(DATASET.size());
for (int i = 0; i < DATASET.size(); i++) {
BenchmarkFixtures.Sample sample = DATASET.get(i);
int index = i;
cases.add(dynamicTest(
String.format("#%03d: %s", index, preview(sample.text())),
() -> runCase(sample, index)));
}
return cases.stream();
@TestFactory
Stream<DynamicTest> datasetOf200Cases() {
List<DynamicTest> cases = new java.util.ArrayList<>(DATASET.size());
for (int i = 0; i < DATASET.size(); i++) {
BenchmarkFixtures.Sample sample = DATASET.get(i);
int index = i;
cases.add(
dynamicTest(
String.format("#%03d: %s", index, preview(sample.text())),
() -> runCase(sample, index)));
}
return cases.stream();
}
private void runCase(BenchmarkFixtures.Sample sample, int index) {
Pipeline pipeline = new Pipeline(REGISTRY, MASKER, new PayloadStore(1_000_000L, 30));
String payloadId = "dataset200-" + index;
String masked = pipeline.process(sample.text(), payloadId, SystemPolicy.DEFAULT);
for (Span gold : sample.gold()) {
String value = sample.text().substring(gold.start(), gold.end());
assertFalse(
masked.contains(value),
"ПДН типа " + gold.type() + " утекло в замаскированный текст: " + value);
}
private void runCase(BenchmarkFixtures.Sample sample, int index) {
Pipeline pipeline = new Pipeline(REGISTRY, MASKER, new PayloadStore(1_000_000L, 30));
String payloadId = "dataset200-" + index;
String restored = pipeline.process(masked, payloadId, SystemPolicy.DEFAULT);
assertEquals(sample.text(), restored, "демаскирование не восстановило исходный текст");
}
String masked = pipeline.process(sample.text(), payloadId, SystemPolicy.DEFAULT);
for (Span gold : sample.gold()) {
String value = sample.text().substring(gold.start(), gold.end());
assertFalse(masked.contains(value),
"ПДН типа " + gold.type() + " утекло в замаскированный текст: " + value);
}
String restored = pipeline.process(masked, payloadId, SystemPolicy.DEFAULT);
assertEquals(sample.text(), restored, "демаскирование не восстановило исходный текст");
}
private static String preview(String text) {
return text.length() <= 40 ? text : text.substring(0, 40) + "...";
}
private static String preview(String text) {
return text.length() <= 40 ? text : text.substring(0, 40) + "...";
}
}