feat(normalizer): точка — тысячи при любом числе цифр до неё, если после неё ровно три (AURORA-1405, #37)
Решение владельца 16.10.2026 (R-3): шаблон точки [+-]?[1-9]\d*(\.\d{3})+ —
«1 234.567», «1234.567», «12345.678», «(1 234.567)» -> тысячи. Дробная часть
не из трёх цифр, ведущий ноль, смешанные записи — как раньше. Правило для
запятой не расширено.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
68ffc5bc24
commit
dec3f5224f
@ -170,8 +170,10 @@ ack (с `attempt` повторного запроса); файл в ИС вто
|
||||
отбрасывал символов`.
|
||||
- **`Money` (решение №30).** Контракт допускает не больше двух знаков после запятой. Сумма с тремя и более знаками
|
||||
(`0,125`, `12,3456`; запятая-разделитель тысяч `4,200`/`1,234` — это 4200/1234, AURORA-1405, #35; точка так же: `4.200`/`837.994` — 4200/837994, #37) не принимается: `amount = null`, `string_value` — текст
|
||||
ячейки, `is_recognized = false` (таблица получает `needs_check`, остальные ячейки и документ целы). Точка-разделитель тысяч — только строго через три цифры (`[+-]?[1-9]\d{0,2}(\.\d{3})+`, скобки на всю
|
||||
строку; `4.200`, `(4.200)`, `1.234.567`), без потери `is_recognized`; `4.2`, `4.20` — дроби, `1.2345` — текст (№30).
|
||||
ячейки, `is_recognized = false` (таблица получает `needs_check`, остальные ячейки и документ целы). Точка-разделитель тысяч — ровно три цифры после каждой точки, цифр до первой точки сколько угодно
|
||||
(`[+-]?[1-9]\d*(\.\d{3})+`, скобки на всю строку; `4.200`, `(4.200)`, `1.234.567`, `1234.567`, `1 234.567`), без
|
||||
потери `is_recognized`; `4.2`, `4.20`, `1234.56` — дроби, `1.2345` — текст (№30); для запятой правило прежнее
|
||||
(`\d{0,2}` до первой запятой, `1234,567` — не тысячи).
|
||||
Смешанная сумма вида `1.234,56` (разделители обоих видов) разбирается как `123456` — все точки считаются разделителями тысяч (дословный перенос) —
|
||||
с `is_recognized = false`.
|
||||
- `confidence` ячеек и полей шапки приводится к диапазону 0–100, `NaN` и бесконечность дают 0 (`json.loads` принимает
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
|
||||
Что делает разбор: пробелы долой; если текст целиком (а при скобках на всю строку — их содержимое) имеет вид
|
||||
`[+-]?[1-9]\\d{0,2}(,\\d{3})+`, запятые — разделители тысяч и удаляются (`4,200` → `4200`, `(4,200)` → `-4200`,
|
||||
`1,234,567` → `1234567`); то же для точек (`4.200`, `837.994`, `(4.200)`, `1.234.567`; scan-gateway#37);
|
||||
`1,234,567` → `1234567`); то же для точек (`4.200`, `837.994`, `(4.200)`, `1.234.567`, а также `1234.567` →
|
||||
`1234567`: ровно три цифры после точки, цифр до неё сколько угодно; scan-gateway#37);
|
||||
это не отбрасывание символов, `verified` не страдает. Правило тысяч — решение владельца 15.10.2026 (AURORA-1405,
|
||||
scan-gateway#35; для точки — 16.10.2026, #37), см. docs/context/05-domain-rules.md §11.1 п. 7. Затем запятая → точка;
|
||||
если есть круглые скобки — берётся содержимое первой пары, и сумма
|
||||
@ -29,7 +30,9 @@ _BRACKET_RE = re.compile(r"\(([^()]*)\)")
|
||||
_THOUSANDS_RE = re.compile(r"[+-]?[1-9]\d{0,2}(?:,\d{3})+")
|
||||
# то же для точки (AURORA-1405, scan-gateway#37): ИС Сканирование иногда возвращает «4.200», «837.994»; смешанные
|
||||
# «1.234,56» / «1,234.56» не подходят ни под одну из двух регулярок (разделитель один и тот же по всей строке)
|
||||
_DOT_THOUSANDS_RE = re.compile(r"[+-]?[1-9]\d{0,2}(?:\.\d{3})+")
|
||||
# Для точки — шире, чем для запятой (решение владельца 16.10.2026): ровно три цифры после каждой точки, число цифр до
|
||||
# первой точки любое («1234.567», «12345.678»); первая цифра ненулевая. Запятая остаётся строго `\d{0,2}`.
|
||||
_DOT_THOUSANDS_RE = re.compile(r"[+-]?[1-9]\d*(?:\.\d{3})+")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -42,7 +45,7 @@ def parse_amount(raw: str) -> ParsedAmount:
|
||||
"""Парсит сумму из распознанного текста OCR-ячейки.
|
||||
|
||||
Убирает пробелы; если строка целиком (или пара скобок на всю строку — её содержимое) вида
|
||||
`[+-]?[1-9]\\d{0,2}(,\\d{3})+` или `[+-]?[1-9]\\d{0,2}(\\.\\d{3})+` — удаляет запятые или точки (разделители
|
||||
`[+-]?[1-9]\\d{0,2}(,\\d{3})+` или `[+-]?[1-9]\\d*(\\.\\d{3})+` — удаляет запятые или точки (разделители
|
||||
тысяч: `4,200`, `4.200` → 4200) без потери verified.
|
||||
Затем заменяет запятую на точку, извлекает содержимое круглых скобок
|
||||
(означает отрицательное число), удаляет непотребные символы; если точек больше одной — считает их разделителями
|
||||
|
||||
@ -47,6 +47,8 @@ from src.normalizer.ocr.amounts import ParsedAmount, is_blank, parse_amount
|
||||
("(4,200)(1,000)", Decimal("-4.200"), False),
|
||||
("1,234(5,678)", Decimal("-5.678"), False),
|
||||
("((4,200))", Decimal("-4.200"), True),
|
||||
("1234,567", Decimal("1234.567"), True), # запятая НЕ расширена (R-7): четыре цифры до неё — не тысячи
|
||||
("1 234,567", Decimal("1234.567"), True),
|
||||
("01,234", Decimal("1.234"), True), # ведущий ноль — не тысячи
|
||||
("4,2000", Decimal("4.2000"), True),
|
||||
("1, 234", Decimal("1234"), True), # пробелы убираются первыми
|
||||
@ -60,6 +62,13 @@ from src.normalizer.ocr.amounts import ParsedAmount, is_blank, parse_amount
|
||||
("1.234", Decimal("1234"), True),
|
||||
("100.000", Decimal("100000"), True),
|
||||
("1. 234", Decimal("1234"), True), # пробелы убираются первыми
|
||||
# ровно три цифры после точки — тысячи независимо от числа цифр до неё (R-3, решение владельца 16.10.2026)
|
||||
("1 234.567", Decimal("1234567"), True),
|
||||
("1234.567", Decimal("1234567"), True),
|
||||
("12345.678", Decimal("12345678"), True),
|
||||
("(1 234.567)", Decimal("-1234567"), True),
|
||||
("-1234.567", Decimal("-1234567"), True),
|
||||
("1234.567.890", Decimal("1234567890"), True),
|
||||
# не тысячи: не три цифры после точки — десятичная дробь, как раньше
|
||||
("4.2", Decimal("4.2"), True),
|
||||
("4.20", Decimal("4.20"), True),
|
||||
@ -67,7 +76,8 @@ from src.normalizer.ocr.amounts import ParsedAmount, is_blank, parse_amount
|
||||
("1.2345", Decimal("1.2345"), True),
|
||||
("0.125", Decimal("0.125"), True), # ведущий ноль — не тысячи
|
||||
("01.234", Decimal("1.234"), True),
|
||||
("1234.567", Decimal("1234.567"), True), # четыре цифры до точки — не группа тысяч
|
||||
("1234.56", Decimal("1234.56"), True), # дробная часть не из трёх цифр — дробь
|
||||
("1234.5678", Decimal("1234.5678"), True),
|
||||
# смешанные разделители и прочий мусор — правило не применяется (R-1): прежний разбор
|
||||
("1.234,567", Decimal("1234567"), False),
|
||||
("1,234.567", Decimal("1234567"), False),
|
||||
|
||||
@ -499,6 +499,8 @@ class TestMoneyPrecision:
|
||||
("837.994", Decimal("837994")),
|
||||
("(4.200)", Decimal("-4200")),
|
||||
("1.234.567", Decimal("1234567")),
|
||||
("1234.567", Decimal("1234567")),
|
||||
("1 234.567", Decimal("1234567")),
|
||||
],
|
||||
)
|
||||
def test_thousands_dot_gives_amount(self, text: str, amount: Decimal) -> None:
|
||||
|
||||
@ -238,7 +238,7 @@ SHALL отражать состояние.
|
||||
имени, иначе `confidence` ячейки наименования.
|
||||
- `values` — по одному на колонку документа (по позиции; если раскладка строки после повторной шапки даёт иное число колонок —
|
||||
недостающие значения как «ячейки нет», лишние отбрасываются, одно предупреждение на таблицу «повторная шапка изменила
|
||||
число колонок значений», решение №21; таблица без колонок значений — предупреждение «нет колонок значений», №25): `amount` — `parse_amount` (запятая-разделитель тысяч (`4,200`, `1,234` → 4200, 1234; AURORA-1405, #35; так же точка строго через три цифры: `4.200`, `837.994` → 4200, 837994, #37) удаляется, запятая → точка, скобки → минус, пробелы и прочие символы
|
||||
число колонок значений», решение №21; таблица без колонок значений — предупреждение «нет колонок значений», №25): `amount` — `parse_amount` (запятая-разделитель тысяч (`4,200`, `1,234` → 4200, 1234; AURORA-1405, #35; так же точка, после которой ровно три цифры, при любом числе цифр до неё: `4.200`, `837.994`, `1234.567` → 4200, 837994, 1234567, #37) удаляется, запятая → точка, скобки → минус, пробелы и прочие символы
|
||||
отбрасываются, несколько точек — разделители тысяч; прочерк/пусто → `null`; сумма с более чем двумя знаками после запятой (`0,125`) не помещается в `Money` контракта — `amount = null`,
|
||||
`string_value` — текст ячейки, `is_recognized = false`, решение №30); `string_value` — текст ячейки как
|
||||
распознан (`\n`/`\t` → пробел, края обрезаны) **только если `amount = null`** и текст не прочерк (DBML: «как распознано,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user