feat: validate OGRN/OGRNIP checksum instead of trusting anchor + digit count
Unlike INN, OGRN and OGRNIP had no checksum check — any 13/15-digit number next to the word was accepted. Both have a public mod-11/mod-13 algorithm (first 12/14 digits, remainder mod divisor mod 10 = the last digit), same shape as the existing INN/SNILS checks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c1a1319088
commit
c25ab9c99f
@@ -210,13 +210,16 @@ public class RuleRegistry {
|
||||
.anchoredBy("срок действия", "действительн", "exp"),
|
||||
|
||||
// ОГРНИП раньше ОГРН: без отрицательного просмотра «ОГРНИП» частично ловился бы
|
||||
// ещё и правилом ОГРН.
|
||||
// ещё и правилом ОГРН. Контрольная сумма отсекает случайные 13/15-значные
|
||||
// числа рядом со словом — раньше якоря было достаточно самого по себе.
|
||||
Rule.of(OGRNIP, "(?iu:огрнип)\\W{0,5}(\\d{15})\\b", 83)
|
||||
.groups(1)
|
||||
.validatedBy(Validators::ogrnip)
|
||||
.anchoredBy("огрнип"),
|
||||
|
||||
Rule.of(OGRN, "(?iu:огрн(?!ип))\\W{0,5}(\\d{13})\\b", 83)
|
||||
.groups(1)
|
||||
.validatedBy(Validators::ogrn)
|
||||
.anchoredBy("огрн"),
|
||||
|
||||
Rule.of(KPP, "(?iu:кпп)\\W{0,5}(\\d{9})\\b", 83)
|
||||
|
||||
@@ -63,6 +63,32 @@ public final class Validators {
|
||||
return control == d[9] * 10 + d[10];
|
||||
}
|
||||
|
||||
/** Контрольная сумма ОГРН: первые 12 цифр по модулю 11, младший разряд — 13-я цифра. */
|
||||
public static boolean ogrn(String value) {
|
||||
int[] d = digits(value);
|
||||
return d.length == 13 && d[12] == modReduce(d, 12, 11);
|
||||
}
|
||||
|
||||
/** Контрольная сумма ОГРНИП: первые 14 цифр по модулю 13, младший разряд — 15-я цифра. */
|
||||
public static boolean ogrnip(String value) {
|
||||
int[] d = digits(value);
|
||||
return d.length == 15 && d[14] == modReduce(d, 14, 13);
|
||||
}
|
||||
|
||||
/**
|
||||
* Остаток от деления первых {@code count} цифр как одного числа на {@code divisor},
|
||||
* взятый по младшему разряду. Числовое накопление по цифрам, а не парсинг строки
|
||||
* в {@code long}: у ОГРНИП 14 цифр — на грани переполнения {@code int}, и это тот же
|
||||
* приём, что уже применяется к самой длинной последовательности в {@link #luhn}.
|
||||
*/
|
||||
private static int modReduce(int[] d, int count, int divisor) {
|
||||
long remainder = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
remainder = (remainder * 10 + d[i]) % divisor;
|
||||
}
|
||||
return (int) (remainder % 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Дата в числовой записи при любом порядке частей: {@code 12.05.1985},
|
||||
* {@code 05/12/1985}, {@code 1985-05-12}. Отсекает похожие по форме
|
||||
|
||||
Reference in New Issue
Block a user