From 68ffc5bc248fc3fc57939d2241a9b38819b48b84 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 16 Oct 2026 13:29:09 +0400 Subject: [PATCH 1/2] =?UTF-8?q?feat(normalizer):=20=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BA=D0=B0=20=E2=80=94=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=20=D1=82=D1=8B=D1=81=D1=8F=D1=87?= =?UTF-8?q?=20=D0=B2=20=D1=81=D1=83=D0=BC=D0=BC=D0=B0=D1=85=20OCR=20(AUROR?= =?UTF-8?q?A-1405,=20#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit «4.200» разбирался как 4.200 с тремя знаками и отсекался решением №30 (amount = null, текст в string_value). Теперь точки строго через три цифры ([+-]?[1-9]\d{0,2}(\.\d{3})+, скобки на всю строку) — разделители тысяч, как для запятой (#35): 4.200 -> 4200, (4.200) -> -4200, 1.234.567 -> 1234567, verified не страдает. Дроби и смешанные записи (1.234,56) — как раньше. Co-Authored-By: Claude Opus 5.5 --- README.md | 7 ++-- api/src/normalizer/ocr/amounts.py | 16 +++++-- api/tests/unit/normalizer/test_amounts.py | 30 ++++++++++++- api/tests/unit/normalizer/test_document.py | 42 +++++++++++++++++++ .../specs/image-recognition-gateway/spec.md | 2 +- 5 files changed, 88 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3988913..d8d2bb2 100644 --- a/README.md +++ b/README.md @@ -169,9 +169,10 @@ ack (с `attempt` повторного запроса); файл в ИС вто `string_value` — текст ячейки, только когда сумма не разобрана; `is_recognized = confidence == 100 и разбор не отбрасывал символов`. - **`Money` (решение №30).** Контракт допускает не больше двух знаков после запятой. Сумма с тремя и более знаками - (`0,125`, `12,3456`; запятая-разделитель тысяч `4,200`/`1,234` — это 4200/1234, AURORA-1405, #35) не принимается: `amount = null`, `string_value` — текст - ячейки, `is_recognized = false` (таблица получает `needs_check`, остальные ячейки и документ целы). Сумма вида - `1.234,56` (несколько точек) разбирается как `123456` — все точки считаются разделителями тысяч (дословный перенос) — + (`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). + Смешанная сумма вида `1.234,56` (разделители обоих видов) разбирается как `123456` — все точки считаются разделителями тысяч (дословный перенос) — с `is_recognized = false`. - `confidence` ячеек и полей шапки приводится к диапазону 0–100, `NaN` и бесконечность дают 0 (`json.loads` принимает `NaN`/`Infinity`, а контракт требует 0–100). diff --git a/api/src/normalizer/ocr/amounts.py b/api/src/normalizer/ocr/amounts.py index 55d1ab5..f0ce980 100644 --- a/api/src/normalizer/ocr/amounts.py +++ b/api/src/normalizer/ocr/amounts.py @@ -3,8 +3,9 @@ Что делает разбор: пробелы долой; если текст целиком (а при скобках на всю строку — их содержимое) имеет вид `[+-]?[1-9]\\d{0,2}(,\\d{3})+`, запятые — разделители тысяч и удаляются (`4,200` → `4200`, `(4,200)` → `-4200`, -`1,234,567` → `1234567`); это не отбрасывание символов, `verified` не страдает. Правило тысяч — решение владельца -15.10.2026 (AURORA-1405, scan-gateway#35), см. docs/context/05-domain-rules.md §11.1 п. 7. Затем запятая → точка; +`1,234,567` → `1234567`); то же для точек (`4.200`, `837.994`, `(4.200)`, `1.234.567`; 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. Затем запятая → точка; если есть круглые скобки — берётся содержимое первой пары, и сумма отрицательная; всё, кроме цифр, `+`, `-` и `.`, отбрасывается; если точек больше одной, удаляются ВСЕ точки (`1.234,56` → `123456`, а не `1234.56`: последняя точка тоже теряется, такая сумма всегда `verified = False`). @@ -26,6 +27,9 @@ _BRACKET_RE = re.compile(r"\(([^()]*)\)") # запятая-разделитель тысяч (AURORA-1405, scan-gateway#35): «4,200», «-1,234,567»; только если строка целиком # (или содержимое пары скобок, занимающей всю строку) совпала; «(4,200) руб», «1,234(5,678)» — прежний разбор _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})+") @dataclass(frozen=True) @@ -38,7 +42,8 @@ def parse_amount(raw: str) -> ParsedAmount: """Парсит сумму из распознанного текста OCR-ячейки. Убирает пробелы; если строка целиком (или пара скобок на всю строку — её содержимое) вида - `[+-]?[1-9]\\d{0,2}(,\\d{3})+` — удаляет запятые (разделители тысяч: `4,200` → 4200) без потери verified. + `[+-]?[1-9]\\d{0,2}(,\\d{3})+` или `[+-]?[1-9]\\d{0,2}(\\.\\d{3})+` — удаляет запятые или точки (разделители + тысяч: `4,200`, `4.200` → 4200) без потери verified. Затем заменяет запятую на точку, извлекает содержимое круглых скобок (означает отрицательное число), удаляет непотребные символы; если точек больше одной — считает их разделителями тысяч и удаляет все (десятичная часть при этом не сохраняется: `1.234,56` → 123456). @@ -48,8 +53,11 @@ def parse_amount(raw: str) -> ParsedAmount: """ s = raw.strip().replace(" ", "") inner = _BRACKET_RE.search(s) - if _THOUSANDS_RE.fullmatch(inner.group(1) if inner and inner.group(0) == s else s): + core = inner.group(1) if inner and inner.group(0) == s else s + if _THOUSANDS_RE.fullmatch(core): s = s.replace(",", "") + elif _DOT_THOUSANDS_RE.fullmatch(core): + s = s.replace(".", "") s = s.replace(",", ".") stripped_raw = s if not stripped_raw: diff --git a/api/tests/unit/normalizer/test_amounts.py b/api/tests/unit/normalizer/test_amounts.py index a41cb50..329585d 100644 --- a/api/tests/unit/normalizer/test_amounts.py +++ b/api/tests/unit/normalizer/test_amounts.py @@ -14,7 +14,7 @@ from src.normalizer.ocr.amounts import ParsedAmount, is_blank, parse_amount ("(6032409)", Decimal("-6032409"), True), ("(7744536}", Decimal("7744536"), False), # закрывающая скобка не распознана: минус не ставится (решение №12) ("1 234,56", Decimal("1234.56"), True), - ("1.234.567", Decimal("1234567"), False), # несколько точек — разделители тысяч; текст изменён → не verified + ("1.234.567", Decimal("1234567"), True), # точки строго через три цифры — разделители тысяч (AURORA-1405, #37) ("30127", Decimal("30127"), True), ("211(1 |", Decimal("2111"), False), ("не ****", None, False), @@ -50,6 +50,34 @@ from src.normalizer.ocr.amounts import ParsedAmount, is_blank, parse_amount ("01,234", Decimal("1.234"), True), # ведущий ноль — не тысячи ("4,2000", Decimal("4.2000"), True), ("1, 234", Decimal("1234"), True), # пробелы убираются первыми + # точка-разделитель тысяч (AURORA-1405, scan-gateway#37): то же правило, что для запятой, verified=True + ("4.200", Decimal("4200"), True), + ("837.994", Decimal("837994"), True), + ("(4.200)", Decimal("-4200"), True), + ("-4.200", Decimal("-4200"), True), + ("+4.200", Decimal("4200"), True), + ("(-4.200)", Decimal("-4200"), True), + ("1.234", Decimal("1234"), True), + ("100.000", Decimal("100000"), True), + ("1. 234", Decimal("1234"), True), # пробелы убираются первыми + # не тысячи: не три цифры после точки — десятичная дробь, как раньше + ("4.2", Decimal("4.2"), True), + ("4.20", Decimal("4.20"), True), + ("12.34", Decimal("12.34"), True), + ("1.2345", Decimal("1.2345"), True), + ("0.125", Decimal("0.125"), True), # ведущий ноль — не тысячи + ("01.234", Decimal("1.234"), True), + ("1234.567", Decimal("1234.567"), True), # четыре цифры до точки — не группа тысяч + # смешанные разделители и прочий мусор — правило не применяется (R-1): прежний разбор + ("1.234,567", Decimal("1234567"), False), + ("1,234.567", Decimal("1234567"), False), + ("1.234.56", Decimal("123456"), False), + ("4.200.", Decimal("4200"), False), # хвостовая точка + ("(4.200}", Decimal("4.200"), False), # нет пары скобок: правило не применяется, минус не ставится + ("(4.200) руб", Decimal("-4.200"), False), + ("abc(4.200)", Decimal("-4.200"), False), + ("(4.200)(1.000)", Decimal("-4.200"), False), + ("1.234(5.678)", Decimal("-5.678"), False), # №13: скобки без содержимого — не сумма, но и не «неразобранный текст» ("()", None, True), ("( )", None, True), diff --git a/api/tests/unit/normalizer/test_document.py b/api/tests/unit/normalizer/test_document.py index d334701..bafbb29 100644 --- a/api/tests/unit/normalizer/test_document.py +++ b/api/tests/unit/normalizer/test_document.py @@ -492,6 +492,48 @@ class TestMoneyPrecision: value = _build(table).document.lines[0].values[0] assert value.amount == amount and value.is_recognized is True and value.string_value is None + @pytest.mark.parametrize( + ("text", "amount"), + [ + ("4.200", Decimal("4200")), + ("837.994", Decimal("837994")), + ("(4.200)", Decimal("-4200")), + ("1.234.567", Decimal("1234567")), + ], + ) + def test_thousands_dot_gives_amount(self, text: str, amount: Decimal) -> None: + """AURORA-1405, scan-gateway#37: ИС вернула точку как разделитель тысяч — «4.200» это 4200, не текст (№30).""" + table = _table( + "Бухгалтерский баланс", + CLEAN_BALANCE["rows"][0], + _row(1, (0, "ОС", 100), (1, "1150", 100), (2, text, 100), (3, "2", 100)), + ) + value = _build(table).document.lines[0].values[0] + assert value.amount == amount and value.is_recognized is True and value.string_value is None + + def test_thousands_dot_low_confidence_is_not_recognized(self) -> None: + table = _table( + "Бухгалтерский баланс", + CLEAN_BALANCE["rows"][0], + _row(1, (0, "ОС", 100), (1, "1150", 100), (2, "4.200", 90), (3, "2", 100)), + ) + value = _build(table).document.lines[0].values[0] + assert value.amount == Decimal("4200") and value.is_recognized is False + + @pytest.mark.parametrize("text", ["4.20", "4.2", "1.2345"]) + def test_dot_fraction_is_not_thousands(self, text: str) -> None: + """Не три цифры после точки — дробь, как раньше: до двух знаков принимается, больше — текст (№30).""" + table = _table( + "Бухгалтерский баланс", + CLEAN_BALANCE["rows"][0], + _row(1, (0, "ОС", 100), (1, "1150", 100), (2, text, 100), (3, "2", 100)), + ) + value = _build(table).document.lines[0].values[0] + if text == "1.2345": + assert value.amount is None and value.string_value == text + else: + assert value.amount == Decimal(text) + @pytest.mark.parametrize("text", ["1,23", "1,2", "0", "-5", "1 234,50"]) def test_up_to_two_decimals_is_kept(self, text: str) -> None: table = _table( diff --git a/openspec/specs/image-recognition-gateway/spec.md b/openspec/specs/image-recognition-gateway/spec.md index e564e90..fe53b73 100644 --- a/openspec/specs/image-recognition-gateway/spec.md +++ b/openspec/specs/image-recognition-gateway/spec.md @@ -238,7 +238,7 @@ SHALL отражать состояние. имени, иначе `confidence` ячейки наименования. - `values` — по одному на колонку документа (по позиции; если раскладка строки после повторной шапки даёт иное число колонок — недостающие значения как «ячейки нет», лишние отбрасываются, одно предупреждение на таблицу «повторная шапка изменила - число колонок значений», решение №21; таблица без колонок значений — предупреждение «нет колонок значений», №25): `amount` — `parse_amount` (запятая-разделитель тысяч (`4,200`, `1,234` → 4200, 1234; AURORA-1405, #35) удаляется, запятая → точка, скобки → минус, пробелы и прочие символы + число колонок значений», решение №21; таблица без колонок значений — предупреждение «нет колонок значений», №25): `amount` — `parse_amount` (запятая-разделитель тысяч (`4,200`, `1,234` → 4200, 1234; AURORA-1405, #35; так же точка строго через три цифры: `4.200`, `837.994` → 4200, 837994, #37) удаляется, запятая → точка, скобки → минус, пробелы и прочие символы отбрасываются, несколько точек — разделители тысяч; прочерк/пусто → `null`; сумма с более чем двумя знаками после запятой (`0,125`) не помещается в `Money` контракта — `amount = null`, `string_value` — текст ячейки, `is_recognized = false`, решение №30); `string_value` — текст ячейки как распознан (`\n`/`\t` → пробел, края обрезаны) **только если `amount = null`** и текст не прочерк (DBML: «как распознано, -- 2.47.2 From dec3f5224fd1bbac5938a6dc9731494655766d5a Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 16 Oct 2026 13:35:31 +0400 Subject: [PATCH 2/2] =?UTF-8?q?feat(normalizer):=20=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BA=D0=B0=20=E2=80=94=20=D1=82=D1=8B=D1=81=D1=8F=D1=87=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B8=20=D0=BB=D1=8E=D0=B1=D0=BE=D0=BC=20=D1=87?= =?UTF-8?q?=D0=B8=D1=81=D0=BB=D0=B5=20=D1=86=D0=B8=D1=84=D1=80=20=D0=B4?= =?UTF-8?q?=D0=BE=20=D0=BD=D0=B5=D1=91,=20=D0=B5=D1=81=D0=BB=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BD=D0=B5=D1=91=20=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D0=BE=20=D1=82=D1=80=D0=B8=20(AURORA-1405,?= =?UTF-8?q?=20#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Решение владельца 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 --- README.md | 6 ++++-- api/src/normalizer/ocr/amounts.py | 9 ++++++--- api/tests/unit/normalizer/test_amounts.py | 12 +++++++++++- api/tests/unit/normalizer/test_document.py | 2 ++ openspec/specs/image-recognition-gateway/spec.md | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d8d2bb2..6a9d0e2 100644 --- a/README.md +++ b/README.md @@ -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` принимает diff --git a/api/src/normalizer/ocr/amounts.py b/api/src/normalizer/ocr/amounts.py index f0ce980..8ea24a0 100644 --- a/api/src/normalizer/ocr/amounts.py +++ b/api/src/normalizer/ocr/amounts.py @@ -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. Затем заменяет запятую на точку, извлекает содержимое круглых скобок (означает отрицательное число), удаляет непотребные символы; если точек больше одной — считает их разделителями diff --git a/api/tests/unit/normalizer/test_amounts.py b/api/tests/unit/normalizer/test_amounts.py index 329585d..b01d616 100644 --- a/api/tests/unit/normalizer/test_amounts.py +++ b/api/tests/unit/normalizer/test_amounts.py @@ -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), diff --git a/api/tests/unit/normalizer/test_document.py b/api/tests/unit/normalizer/test_document.py index bafbb29..508acdb 100644 --- a/api/tests/unit/normalizer/test_document.py +++ b/api/tests/unit/normalizer/test_document.py @@ -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: diff --git a/openspec/specs/image-recognition-gateway/spec.md b/openspec/specs/image-recognition-gateway/spec.md index fe53b73..09ce667 100644 --- a/openspec/specs/image-recognition-gateway/spec.md +++ b/openspec/specs/image-recognition-gateway/spec.md @@ -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: «как распознано, -- 2.47.2