diff --git a/README.md b/README.md index 10a5369..66d6a47 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ ParseOutcome`. Структура ОСВ ищется **по содержимо | Модуль | Что делает | |---|---| | `grid.py` | модель листа: `Cell`, `Sheet`, `MergedRange`, `column_letter` (буква колонки — только для диагностики) | -| `readers/` | чтение книги в `Sheet`: `read_book(data, file_type) -> (листы, предупреждения)`; формат определяется по сигнатуре байтов (xlsx — openpyxl, xls — xlrd с `formatting_info`, xlsb — pyxlsb, без outline level); несовпадение с `file_type` — предупреждение; до библиотеки чтения — лимиты чтения (`_limits.py`, см. «Лимиты») | +| `readers/` | чтение книги в `Sheet`: `read_book(data, file_type) -> (листы, предупреждения)`; формат определяется по сигнатуре байтов (xlsx — openpyxl; части архива, отличающиеся от ссылок `[Content_Types].xml`/`*.rels` только регистром (`xl/SharedStrings.xml`), до openpyxl переименовываются по ссылкам, как их находит Excel — AURORA-1707; xls — xlrd с `formatting_info`, xlsb — pyxlsb, без outline level); несовпадение с `file_type` — предупреждение; до библиотеки чтения — лимиты чтения (`_limits.py`, см. «Лимиты») | | `rules.py` | загрузчик словарей `rules/*.yaml` в frozen-модели (pydantic), `normalize_text`, `Pattern`; ошибка словаря — `RuntimeError` с файлом и ключом при старте | | `detect.py` | вид листа (`classify_sheet`: ОСВ / форма ББ-ОФР / прочее), блок заголовка таблицы (`find_table_header`: 1–4 строки, шесть колонок сумм, колонка текста, ИНН, дополнительные колонки, правая граница, повтор заголовка внутри данных) | | `header.py` | шапка документа (организация, счёт, период, тип данных, валюта) и схема группировок `group_schema` (варианты шапки 1 и 2) | @@ -163,7 +163,7 @@ ParseOutcome`. Структура ОСВ ищется **по содержимо ### Строки-показатели и колонка «Показатели» (§1.3, AURORA-1613, AURORA-1661) Колонка «Показатели» (`service_columns.indicator`; заголовок часто «Показа-тели») несёт вид суммы строки: «БУ» — обычная -строка данных (`data_type = BU`), «НУ», «Вал.», «Кол.» — показатели (`NU`/`CURRENCY`/`QUANTITY` по словарю +строка данных (`data_type = BU`), «НУ», «Вал.», «Кол.», «Кол. доп.» — показатели (`NU`/`CURRENCY`/`QUANTITY` по словарю `data_types.yaml`: `indicator_column`), «ПР», «ВР» (`indicator_column_untyped`, `data_type = null`) и неизвестная метка — показатель с `data_type = null` (неизвестная — ещё и предупреждение `OSV_ROWS`). Показатель — строка с признаком `indicator`: он не входит в суммирование «сумма детей = родитель» и не проверяется как родитель, его родитель в дереве — предыдущая строка @@ -226,7 +226,7 @@ ParseOutcome`. Структура ОСВ ищется **по содержимо Словари — данные: `osv_headers.yaml` (признаки листов, группы сумм, «Дебет/Кредит», колонка ИНН, «Итого», метки шапки), `osv_groups.yaml` (группировки → `ACCOUNT/DEPARTMENT/COUNTERPARTY/CONTRACT/OTHER`), `periods.yaml` (период → квартал и -нарастающий итог), `data_types.yaml` (БУ/НУ/сумма, строки «Вал.»/«Кол.», коды и написания валют), `osv_checks.yaml` (счета без сальдо +нарастающий итог), `data_types.yaml` (БУ/НУ/сумма, строки «Вал.»/«Кол.»/«Кол-во, <единица>», коды и написания валют), `osv_checks.yaml` (счета без сальдо по аналитике — вне тождества строки). Формат и правила записи паттернов — [`api/rules/README.md`](api/rules/README.md). diff --git a/api/rules/README.md b/api/rules/README.md index 536b5ed..9acdd21 100644 --- a/api/rules/README.md +++ b/api/rules/README.md @@ -14,7 +14,7 @@ AST-линтер `tests/unit/parser/test_no_coordinates.py`. | `osv_groups.yaml` | `mapping`, `default` | название группировки из шапки -> `ACCOUNT` / `DEPARTMENT` / `COUNTERPARTY` / `CONTRACT` / `OTHER` (`default` — для всего прочего) | | `osv_checks.yaml` | `accounts_without_analytic_balance` | коды счетов верхнего уровня без сальдо по аналитике (состав `90`, `91`, `99`; OQ-132, 90 добавлен 19.10.2026): тождество строки «начало + обороты = конец» на них не проверяется (субсчета включены, `910` — нет); сверки «сумма детей = родитель» по оборотам и «Итого» сохраняются. Не `Pattern`: записи — коды счетов, а не регулярные выражения | | `periods.yaml` | `rules` (`pattern`, `quarter`, `is_cumulative`), `year`, `end`, `months` (названия месяцев, группы `m01…m12`), `not_single_month` (тексты, где один месяц не задаёт границы периода: дата на число, квартал, «N месяцев»), `range` (диапазон месяцев с годами, группы `m1`, `y1`, `m2`, `y2`), `standalone` (период отдельной ячейкой без метки) | текст периода -> `period_quarter` и `is_cumulative` (§1.5, `ASSUMPTION(OQ-02)`); `year` — паттерн года, `end` — конец текста периода в строке шапки («г.», «год») | -| `data_types.yaml` | `header`, `standalone_header` (тип данных отдельной ячейкой без метки), `row`, `currency_codes`, `currency_aliases` | «Выводимые данные» -> `BU` / `NU` / `SUM`; строки «Вал.» / «Кол.» -> `CURRENCY` / `QUANTITY`; коды валют ISO 4217 (строка, равная коду, — валютная); написания валюты в шапке (`руб.` -> `RUB`; ключ алиаса обязан быть в `currency_codes`) | +| `data_types.yaml` | `header`, `standalone_header` (тип данных отдельной ячейкой без метки), `row`, `currency_codes`, `currency_aliases` | «Выводимые данные» -> `BU` / `NU` / `SUM`; строки «Вал.» / «Кол.», «Кол-во», «Кол-во, <единица>» -> `CURRENCY` / `QUANTITY`; метки колонки «Показатели» (`indicator_column`: «Кол.», «Кол. доп.» -> `QUANTITY`); коды валют ISO 4217 (строка, равная коду, — валютная); написания валюты в шапке (`руб.` -> `RUB`; ключ алиаса обязан быть в `currency_codes`) | Словари форм ББ/ОФР (`statement_*.yaml` в прежней версии) удалены из сервиса: их держит пакет `arm_statements` (`api/arm_statements/rules/`), сервис их не читает. diff --git a/openspec/specs/osv-parsing/spec.md b/openspec/specs/osv-parsing/spec.md index 2102f69..2f5905d 100644 --- a/openspec/specs/osv-parsing/spec.md +++ b/openspec/specs/osv-parsing/spec.md @@ -336,7 +336,9 @@ balance_identity_violations, parent_sum_violations}`. Результат про ### Requirement: Форматы, лимиты, время Парсер SHALL читать XLSX (openpyxl, полное чтение), XLS (xlrd, `formatting_info`), XLSB (pyxlsb; outline level недоступен — -каскад начинается с отступа). Несоответствие `file_type` сигнатуре файла → попытка по сигнатуре и предупреждение файла `FILE_TYPE_MISMATCH` (`severity = info`) в `metrics.warnings`. +каскад начинается с отступа). Имена частей xlsx SHALL сравниваться без учёта регистра, как в Excel (AURORA-1707): часть, +отличающаяся от ссылки `[Content_Types].xml` (`Override PartName`) или `*.rels` (`Target` внутренней связи) только +регистром, читается под именем из ссылки; две части под одну ссылку не угадываются. Несоответствие `file_type` сигнатуре файла → попытка по сигнатуре и предупреждение файла `FILE_TYPE_MISMATCH` (`severity = info`) в `metrics.warnings`. Файл больше `PARSE_MAX_BYTES` (по умолчанию 50 МиБ) → `error UNSUPPORTED_FORMAT` с текстом про лимит. До вызова библиотеки чтения SHALL действовать лимиты чтения (R-23) → `error UNSUPPORTED_FORMAT` с текстом про лимит: сумма объявленных несжатых размеров записей zip (xlsx/xlsb) не больше `ZIP_UNCOMPRESSED_LIMIT_FACTOR × PARSE_MAX_BYTES` (10 ×); @@ -364,6 +366,11 @@ balance_identity_violations, parent_sum_violations}`. Результат про - **WHEN** запрос говорит `XLSX`, а байты — BIFF8 - **THEN** файл прочитан как xls, в `metrics.warnings` — `FILE_TYPE_MISMATCH` (`info`), статус по содержимому +#### Scenario: Имя части xlsx в другом регистре +- **WHEN** таблица строк лежит в архиве как `xl/SharedStrings.xml`, а ссылки ведут на `xl/sharedStrings.xml` (или лист + `Sheet1.xml` при ссылке на `sheet1.xml`) +- **THEN** книга читается так же, как с именами из ссылок; не `UNREADABLE` и без потери листа + #### Scenario: xls без кодовой страницы - **WHEN** разбирается xls без записи CODEPAGE (синтетика, `codepage=None`) - **THEN** строки прочитаны как cp1251, в `metrics.warnings` — предупреждение файла `XLS_NO_CODEPAGE` (`info`, без @@ -538,11 +545,11 @@ design #14). Строка-показатель — строка, которая несёт вид сумм предыдущей строки данных, а не уровень аналитики. Парсер SHALL считать строку показателем, если: 1. в таблице есть колонка «Показатели» (заголовок блока по словарю `osv_headers.yaml` — `service_columns.indicator`, - «Показатели», «Показа-тели»), ячейка строки в ней непуста и метка не «БУ»: «НУ» → `data_type = NU`, «Кол.» → + «Показатели», «Показа-тели»), ячейка строки в ней непуста и метка не «БУ»: «НУ» → `data_type = NU`, «Кол.», «Кол. доп.» → QUANTITY, «Вал.» → CURRENCY (словарь `data_types.yaml` — `indicator_column`); «ПР», «ВР» (`indicator_column_untyped`) → `data_type = null`; метка не из словарей → `data_type = null` и предупреждение `OSV_ROWS` «строка N: метка «X» в колонке «Показатели» не распознана — строка не участвует в проверках»; -2. иначе — текст строки матчит `data_types.yaml` — `row` («Вал.», «Кол.», «Кол-во», «В валюте» → CURRENCY/QUANTITY) или +2. иначе — текст строки матчит `data_types.yaml` — `row` («Вал.», «Кол.», «Кол-во», «Кол-во, <единица>», «В валюте» → CURRENCY/QUANTITY) или равен коду валюты из `currency_codes`. Строка «Валюта <код>» («Валюта USD») MUST NOT считаться показателем: это строка аналитики с рублёвыми суммами; код валюты